【C#】メモ帳で作成 スロットゲームの作り方 徹底解説します【応用編】

C#ライン

メモ帳でスロットゲームを作成

メモ帳でパチスロ風ゲームの作り方をご説明します。前回作成したスロットゲームは表示窓3個、絵柄3種類とシンプルでしたが、今回は表示窓9個で絵柄は5種類のパチスロ風画像を使用します(フリー画像)。使用するソフトはプログラムを記述するメモ帳とプログラムを実行するコマンドプロンプトのみです。Windows標準ソフトなので他にインストールする必要はありません。また、前回記事のプログラミングではメインループのwhile文しかループを使いませんでしたが、今回は表示する画像が9個と多いのでコントロールの作成や画像の表示をループ(for文)を使用してコードを記述しています。本記事ではゲーム作りをとおしてプログラミングを学んでいきましょうという内容です。ぜひこの機会にゲーム作りを体験してみましょう!

■完成品のイメージ

まずは完成したゲーム画像を見てみましょう。

今回もWindowsのフォームアプリで作成します。コントロールの構成は、スタートボタン、絵柄9つ(5種類)、ストップボタン3つ、結果表示ラベル、コイン残数ラベル、出目ログテキストという内容です。ゲーム内容は、スタートボタンをクリック(もしくはエンターキー)するとピクチャーボックス9つの絵柄に5種類の画像(7,=,🍒,🔔,🍉)がランダムに表示され、ストップボタンを順番に3つクリック(もしくはエンターキー)すると出目ログテキストに9個の出目結果が記録され、横ラインに3つ絵柄が揃うと結果ラベルに表示し、コイン表示ラベルの残数が絵柄種類に応じてコインが増えます。そしてコインが0になるとゲームオーバーという内奥です。それでは一から作成していきましょう。

■コンパイラの実行ファイルの作成

今回も説明は省略します。下記記事を参考にして「C:\Users\sumio」のフォルダ内にC#コンパイラの「csc.bat」ファイルを作成します。(PCごとにフォルダのユーザ名は違います)

【C#】Windowsメモ帳でプログラミングを始めよう【入門編】
あえてメモ帳でC#プログラムを実行する方法を説明いたします。すでにVisualStudioがインストールされているのであればVisualStudioを使ってプログラミングしたほうが圧倒的に便利ではあります。しかし初心者にはプログラムソフトのインストールやテキストエディタなどの開発環境を構築するハードルが高くプログラミングを始める障害になってしまいます。WindowsにはC#のコンパイラが標準に付属されています。今回お手軽にプログラミングができる方法をご紹介しましたので、これを機会にプログラミングを始めてみましょう!

■csファイルの作成

上記記事を参考にして、「C:\Users\sumio」のフォルダ内に「slot9.cs」ファイルを作成します。ここまでも以前作成したスロットゲーム記事の内容と同様になります。

■絵柄の画像ファイルの準備

今回はペイントアプリは使用しません。下記に5つの画像リンクを掲載しますので各リンクをクリックして画像を表示させた状態で、画像を右クリックし名前を付けて保存ダイアログでプログラムファイル「slot9.cs」と同じフォルダへ保存すます。ファイル名と拡張子「.png」そのままで保存してください。

7画像

BAR画像

チェリー画像

ベル画像

スイカ画像

■フォームの作成

それではこれからプログラムを記述していきます。コマンドプロンプトを起動し「notepad slot9.cs」を入力し実行します。

C:\Users\sumio>notepad slot9.cs

コマンドを実行するとメモ帳から「slot9.cs」ファイルが立ち上がります。(中身は空です)

そのcsファイルにWindowsフォームを作成するプログラムを下記のように記述します。

using System.Windows.Forms;

class FormSlot9 : Form
{
    static void Main()
  {
        Application.Run( new FormSlot9());
    }
}

このコードは「System.Windows.Forms」のFormクラスを継承した「FormSlot」クラスを定義し、Mainメソッド内でFormSlotを実行しフォームを表示するという内容です。
ここで一度プログラムを実行してみましょう。コマンドプロンプトで下記のように入力し実行します

C:\Users\sumio>csc slot9.cs

最初の項目で作成したC#コンパイラ実行ファイル「csc.bat」をファイル指定「slot9.cs」で実行するというコマンドです。下図のように表示されるとコンパイルは成功です。

C:\Users\sumio>csc slot9.cs

C:\Users\sumio>C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe slot9.cs
Microsoft (R) Visual C# Compiler version 4.8.9037.0
for C# 5
Copyright (C) Microsoft Corporation. All rights reserved.

This compiler is provided as part of the Microsoft (R) .NET Framework, but only supports language versions up to C# 5, which is no longer the latest version. For compilers that support newer versions of the C# programming language, see http://go.microsoft.com/fwlink/?LinkID=533240

C:\Users\sumio>

これにより「C:\Users\sumio」フォルダ内に「slot9.exe」という実行ファイルが作成されます。そして下記コマンドを実行してみましょう。

C:\Users\sumio>slot9

別ウインドウでフォーム画面が起動します。

フォーム作成のところは、前回のスロットゲーム記事とファイル名がslot3.csからslot9.csに変わっただけで説明内容は同じです。コード記述→コンパイル→プログラム実行というようにこまめに実行していくことでエラー発生時のエラー箇所を特定しやすくなります。

■フォームのサイズ変更

「public FormSlot9()」コンストラクタを作成しフォームのサイズを変更する記述をします。また今後使用するusingディレクティブを追記します。//1の6行と//2の3行を//3と//4追記します。「//」の右の部分はコメントアウトされます。

using System;        //1
using System.IO;    //1
using System.Drawing; //1
using System.Text;   //1
using System.Threading.Tasks; //1
using System.Windows.Forms;
using System.Text.RegularExpressions; //1

class FormSlot9 : Form  
{
    static int SIZE = 30;   //2
    int WIDTH = SIZE * 22;  //2
    int HEIGHT = SIZE * 15; //2


    public FormSlot9()  //3
    { //3
    this.Load += new EventHandler(this.FormSlot9_Load);  //3
    }  //3

    private void FormSlot9_Load(object sender, EventArgs e) //4
    { //4
        Size = new Size(WIDTH, HEIGHT); //4
    } //4

//1のusingディレクティブを記述することでプロブラムの記述を短く省略できます。
//2では画面サイズを定義します。SIZEは1単位30ピクセルで設定しています。WIDTHは幅で30*22、HEIGHTは高さで30*15でそれぞれ変数に代入します。
//3はFormSlot9のコンストラクタです。コンストラクタとはオブジェクト(FormSlot9)の生成とともに自動的に呼び出されるメソッドです。クラスと同じ名前の特殊なメソッドです。「this.Load += new EventHandler(this.FormSlot9_Load); 」のコードで//4が呼び出されます。
//4のFormSlot9_Loadメソッドではフォームのサイズを設定しています。

ここでコンパイラとプログラム実行の下記2つのコマンドを実行してみましょう。

C:\Users\sumio>csc slot9.cs
C:\Users\sumio>slot9

コンパイルが成功しフォームのサイズが横660ピクセル、縦450ピクセルで表示されるようになりました。

■コントロールの配置その1(スタートボタン)

これからフォーム上にボタンやラベルや画像などを表示するプログラムを記述していきます。まずはスタートボタンとフォームのタイトル表示をさせてみます。FormSlot9クラス内に//5を追記、FormSlot9コンストラクタ内に//5を追記します。次に//7で囲まれたInitializeComponentメソッドを全て記述します。

class FormSlot9 : Form
{
    static int SIZE = 30;
    int WIDTH = SIZE * 22;
    int HEIGHT = SIZE * 15;

    Button btnStart = new Button(); //5

    public FormSlot9()
    {
        InitializeComponent(); //6
    this.Load += new EventHandler(this.FormSlot9_Load);
    }
 
    private void InitializeComponent()  //7
    {
        ///
        ///スタートボタンの設定
        ///
        this.btnStart.Font = new Font(   //8
      "メイリオ", 11, FontStyle.Bold); //8
        this.btnStart.Location =       //8
            new Point(SIZE * 15, SIZE * 1); //8
        this.btnStart.Name = "btnStart";   //8
        this.btnStart.Size = new Size(SIZE * 5, SIZE); //8
        this.btnStart.TabIndex = 0; //8
        this.btnStart.Text = "スタート"; //8
 
        ///
        ///フォームにコントロールを追加
        ///
        this.Name = "FormSlot9";
        this.Text = "9スロットゲーム";   //9
        this.Controls.Add(this.btnStart); //10
    }                                    //7

//5ではスタートボタン(btnStart)を生成します。
//6ではInitializeComponentを呼び出してます。
//7ではコントロールのプロパティを設定するInitializeComponentメソッドです。
//8ではスタートボタン(btnStart)のプロパティを設定しています。
//9ではフォームの上部左のタイトルバーを表示します。
//10ではスタートボタンをフォーム上に追加します。

ここで下記2つのコマンドを実行してフォームに表示されるか確認してみましょう。

C:\Users\sumio>csc slot9.cs
C:\Users\sumio>slot9

下図の様にフォームにタイトル「9スロットゲーム」とスタートボタンが表示されました。スタートボタンはクリックできますがまだイベントハンドラを記述していませんので何も起きません。

■コントロールの配置その2(ストップボタン)

次にスタートボタンを3個配置します。 FormSlot9クラスの//11にストップボタン(stopBtns)を配列で宣言します。

class FormSlot9 : Form
{
    static int SIZE = 30;
    int WIDTH = SIZE * 22;
    int HEIGHT = SIZE * 15;

    Button btnStart = new Button();
    Button[] stopBtns = new Button[3];  //11

そして、InitializeComponentメソッドの「スタートボタンの設定」のコードの下に下記コードを全て追記します。今回はfor文で変数iをストップボタンの個数分(3回)ループさせボタンを作成します。

        ///
        ///ストップボタン(3個)の設定
        ///
        this.SuspendLayout();
        for (int i = 0; i < this.stopBtns.Length; i++)
        {
            this.stopBtns[i] = new Button();  //12
            this.stopBtns[i].Name = "btnStop" + (i + 1).ToString();
            this.stopBtns[i].Text = "ストップ";
            this.stopBtns[i].Font = new Font(
                "メイリオ", 11, FontStyle.Bold);
            this.stopBtns[i].Size = new Size( SIZE * 3, SIZE ); //13
            this.stopBtns[i].Location = new Point(
                SIZE + i * SIZE * 4, SIZE * 11); //14
            this.stopBtns[i].TabStop = true;  //15
            this.stopBtns[i].Enabled = false; //16
        }

//12では//11で宣言した「stopBtns」配列変数を変数iのループを使い「stopBtns0」「stopBtns1」「stopBtns2」のストップボタンを作成しています。
//13ではストップボタンのサイズを変数SIZEを使い横30 * 3, 縦30で設定しています。
//14では各ストップボタンの場所を変数SIZEと変数iを使い設定しています。変数iの値が1増えるごとに右に30ピクセル(SIZE)*4ずれてストップボタンを作成します。左の隙間はSIZE(30)分、空けています。
//15ではタブストップをtrueにしています。これによりエンターキーでもボタンが押せるように設定しておきます。
//16ではストップボタン3個に対しフォームを読み込む初期状態で無効に設定しています。(後でスタートボタンを押したら有効にします)

コントロールのプロパティを設定しただけではフォームにコントロールが表示されません。ストップボタンをフォームに表示するコードを「フォームにコントロールを追加」の最後に追記します。//17の1行を記述します。

        ///
        ///フォームにコントロールを追加
        ///
        this.Name = "FormSlot9";
        this.Text = "9スロットゲーム";
        this.Controls.Add(this.btnStart);
        this.Controls.AddRange(this.stopBtns);  //17

ここでコマンドを実行してフォームに表示されるか確認してみましょう。

C:\Users\sumio>csc slot9.cs
C:\Users\sumio>slot9

ストップボタンが3個表示されました。初期状態でストップボタンは無効になっています。(押せません)

■コントロールの配置その3(ピクチャボックス)

今度はピクチャボックスを9個配置していきます。まずはFormSlot9クラスに//18から//21を追記し、クラスレベルで変数を宣言します。メソッドをまたいで使用する変数はクラスレベルに変数宣言する必要があります。

class FormSlot9 : Form
{
    static int SIZE = 30;
    int WIDTH = SIZE * 22;
    int HEIGHT = SIZE * 15;

    Button btnStart = new Button();
    Button[] stopBtns = new Button[3];

    PictureBox[] picBoxes = new PictureBox[9]; //18
    String[] imgPath = new String[5];           //19
    String ExePath = Application.ExecutablePath; //20
    String ExeDirPath = Path.GetDirectoryName(   //21
           Application.ExecutablePath);          //21

//18では「picBoxes」を配列変数で宣言しています。
//19では5個の画像(絵柄)の保存先パスを「imgPath」配列変数で宣言しています。
//20ではプログラム本体のファイル「slot9.cs」のフルパス(ファイル名を含んだパス)をExePath変数で宣言しています。
//21では//20の「slot9.cs」ファイル名が無いファイルが保存されているパスをExeDirPath変数で宣言しています。5個の画像ファイルをこのフォルダに置くことでファイル保存先を固定できます。(他のPCでも共通コードで使用できます)

次はプロパティの設定です。InitializeComponentメソッド内の「ストップボタン(3個)の設定」の下に下記コードを全て記述します。前回記事のスロットゲームでは絵柄が3枚だけでしたのでピクチャボックスを3つ同じようなコードをベタで記述しました。今回は9個のピクチャボックスを使いますのでfor分で行、列を入れ子ループで作成します。

        ///
        ///ピクチャーボックス(3*3個)の作成
        ///
      int pbROWS = 3;
      int pbCOLS = 3;
        for (int i = 0; i < pbROWS; i++)   //22
        {
            for (int j = 0; j < pbCOLS; j++) //23
            {
                int index = i * pbCOLS + j; //24
                this.picBoxes[index] = new PictureBox();  //25
                this.picBoxes[index].Location = new Point( //26
           SIZE + j * SIZE * 4, SIZE + i * SIZE * 3); //26
                this.picBoxes[index].Name = 
                    "picBox" + (index + 1).ToString();
                this.picBoxes[index].Size = 
                    new Size(SIZE * 3, SIZE * 3);  //27
                this.picBoxes[index].SizeMode = PictureBoxSizeMode.Zoom; //28
                this.picBoxes[index].TabStop = false;
                this.picBoxes[index].ImageLocation =  //29
                    imgPath[index % imgPath.Length];  //29
            }
        }

//22では外側のループ変数iで縦の個数3個分ループします。
//23では内側のループ変数jで横の個数3個分ループします。
//24ではピクチャボックス配列用のインデックス番号のindex変数を定義しています。「i * pbCOLS + j」の式によりループ1回ごとに0から8までインクリメントするindex変数です。
//25では0から8のピクチャボックス配列を作成しています。
//26ではピクチャボックスの表示位置を設定しています。内側のループ変数jで「SIZE * 4」分ずれて横に3個作成します。外側のループ変数iで「SIZE * 3」分ずれて縦に3個作成します。
//27ではピクチャボックスのサイズを設定しています。縦SIZE * 3 横 SIZE * 3です。
//28では画像サイズをピクチャボックスのサイズ一杯に表示します。
//29ではピクチャボックス0から8に画像(imgPath)を0,1,2,3,4,0,1,2,3の順番に初期設定しています。(後でランダム表示します)

コントロールのプロパティを設定しただけではフォームにコントロールが表示されません。ピクチャボックスをフォームに表示するコードを「フォームにコントロールを追加」の最後に追記します。//30の1行を記述します。

        ///
        ///フォームにコントロールを追加
        ///
        this.Name = "FormSlot9";
        this.Text = "9スロットゲーム";
        this.Controls.Add(this.btnStart);
        this.Controls.AddRange(this.stopBtns);
        this.Controls.AddRange(this.picBoxes); //30

次にフォームが読み込まれたときのFormSlot9_Loadメソッドに//31の5行を追記します。

    private void FormSlot9_Load(object sender, EventArgs e)
    {
        Size = new Size(WIDTH, HEIGHT);
        imgPath[0] = ExeDirPath + @"\seven.png"; //31
        imgPath[1] = ExeDirPath + @"\bar.png";    //31
        imgPath[2] = ExeDirPath + @"\cherry.png"; //31
        imgPath[3] = ExeDirPath + @"\bell.png";   //31
        imgPath[4] = ExeDirPath + @"\watermelon.png"; //31
    }

//31では絵柄の画像を保存したパスをimgPath配列変数に代入します。変数ExeDirPathはプログラム本体のファイル(slot9.cs)のパスなので、画像ファイルの名前「\seven.png」を文字列で連結しています(頭に\を付けフォルダを区切っています)。このPCを例にすると「C:\Users\sumio\seven.png」の文字列がimgPath[0]へ代入されます。

現時点ではまだ画像は表示されませんがピクチャボックスへ画像を表示する準備ができました。

■スタートボタンとストップボタンのイベントハンドラ

InitializeComponentメソッド内の「スタートボタンの設定」と「ストップボタン(3個)の設定」の最後に//32と//33を追記します。ストップボタンとストップボタンをクリックしたときのイベントハンドラを作成します。

       ///
        ///スタートボタンの設定
        ///
       //途中省略//

        this.btnStart.Text = "スタート";
        this.btnStart.Click +=                      //32
             new EventHandler(this.btnStart_Click); //32
        ///
        ///ストップボタン(3個)の設定
        ///
        this.SuspendLayout();
        for (int i = 0; i < this.stopBtns.Length; i++)
        {
        //途中省略//

            this.stopBtns[i].Enabled = false;
            this.stopBtns[i].Click +=                  //33
                new EventHandler(this.stopBtns_Click); //33
      }

今度はイベントハンドラのメソッドを作成します。下記コードを「static void Main()」メソッドの上に全て記述します。

    private async void btnStart_Click(object sender, EventArgs e) //34
    {
        btnStart.Enabled = false;  //35
        for (int i = 0; i < stopBtns.Length; i++) //36
        {                                         //36
            stopBtns[i].Enabled = true;           //36
        }                                         //36
        await Task.Delay(100); //37
    }

    private void stopBtns_Click(object sender, EventArgs e) //38
    {
        Button btn = (Button)sender; //39
        btn.Enabled = false;    //40
    }

//34では「async」を追記し非同期処理とし、スタートボタンをクリックしたときのメソッドです。
//35ではスタートボタンをクリックしたらスタートボタンを無効にします。
//36ではストップボタン3個を有効にしています。
//37では非同期処理で100ミリ秒ごとの処理をします。
//38ではストップボタンをクリックしたときのメソッドです。
//39では変数btnにクリックしたボタンを代入しています。(詳細は前回記事を参照してください)
//40ではクリックしたストップボタンを無効にしています。

ここでコマンドを実行してボタンをクリックして確認してみましょう。

C:\Users\sumio>csc slot9.cs
C:\Users\sumio>slot9

スタートボタンをクリックすると、スタートボタンが無効になりストップボタンが有効になりました。

■エンターキーでクリックできるようにする

ボタンをクリックする代わりにエンターキーでもクリックできるようにします。スタートボタンをクリックするとストップ3個が有効になり、ストップボタンが全て無効になると再びスタートボタンが有効になりリプレイが可能になります。btnStart_Clickメソッドに//41から下を全て追記します。

    private async void btnStart_Click(object sender, EventArgs e)
    {
        btnStart.Enabled = false;

        for (int i = 0; i < stopBtns.Length; i++)
        {
            stopBtns[i].Enabled = true;
        }

        stopBtns[0].Focus();   //41
        bool playFlag = true;     //42

        while( playFlag )  //43
        {    
            if( stopBtns[0].Enabled == false &&  //44
                stopBtns[1].Enabled == false &&  //44
                stopBtns[2].Enabled == false)    //44
            {
                btnStart.Enabled = true;   //45
                btnStart.Focus();          //46
                break;    //47
            }             //47

            await Task.Delay(100);  //48
        }
    }

//41ではストップボタン1にフォーカスされエンターキーでクリックできるようになります。
//42ではメインループで使用するplayFlag変数をtrueかfalseで定義します。初期値はtrueです。
//43では1プレイのメインループです。playFlagがtrueの間ループします。
//44ではif分でスタートボタンが3個とも無効(クリック済み)となったときの条件式です。
//45では//44の条件(ストップボタンが全て無効)のときスタートボタンを有効にします。
//46ではスタートボタンにフォーカスしエンターキーでクリックできるようになります。
//47ではスタートボタンが全て無効になったのでメインループwhileをbreakで抜けます。
//48ではawait Task.Delay(100);の文をこの位置に移動します。

こでコマンドを実行してエンターキーでクリックしてみましょう。

C:\Users\sumio>csc slot9.cs
C:\Users\sumio>slot9

これでマウスを使わずにエンターキーだけでプレイが可能になりました。

■画僧ランダム表示と画像停止

それではいよいよピクチャボックスに画像が表示されます。スタートボタンをクリックすると9個のピクチャボックスに5種類の絵柄がランダムに表示されます。ストップボタンをクリックするとボタン上の3個の絵柄が停止します。

まず、btnStart_Clickメソッドのwhile文の上に//49と//50を追記します。

    ///途中省略///
        Random r = new Random(); //49
        int rIndex = 0;           //50

        while( playFlag )
    ///途中省略///

//49ではランダム変数rを定義しています。
//50ではrIndexを数値型で定義しています。rIndexは0から4の整数を画像の保存先の配列変数に使用します。

そして、btnStart_Clickメソッドのwhileループ内に先ほど記述した//47の「{」の下と//48の「await Task.Delay(100); 」の間に下記コードを全て追記します。

            for ( int j = 0; j < picBoxes.Length; j++)  //51
            {	
                for (int i = 0; i < stopBtns.Length; i++)  //52
                {
                    if (stopBtns[i].Enabled)  //53
                    {
                        for (int k = i; k < picBoxes.Length; k += 3)  //54
                        {
                            rIndex = r.Next(imgPath.Length);   //55
                            picBoxes[k].ImageLocation = imgPath[rIndex];  //56
                        }
                    }
                }
            }

//51ではピクチャボックスの個数9個に対して(picBoxes.Length)変数jで0から8のインデックス番号でループしています。
//52ではストップボタンの個数3個に対して(stopBtns.Length)変数iで0から2のインデックス番号でループしています。//49の入れ子ループです。
//53ではストップボタン(stopBtns[i])が無効(クリックした)になったときの条件式です。変数iでストップボタンを順番に処理しています。
//54ではピクチャボックスのインデックス番号(変数k)を2個飛ばしでループします。すなわちiが0のときは変数kを(0、3、6)、iが1のときは変数kを(1、4、7)、iが2のときは変数kを(2、5、8)で3回ループします。
//55では変数rIndexに0から4の5個の乱数を代入します。
//56ではピクチャボックス(picBoxes[k])のImageLocationに画像パスを0から4のrIndexで代入しています。これでピクチャボックスに絵柄の画像が表示されます。

こでコマンドを実行してストップボタンをクリックしてみましょう。

C:\Users\sumio>csc slot9.cs
C:\Users\sumio>slot9

エンターを押下するとピクチャボックスに絵柄の画像がランダム表示されます。さらにエンターキーを押下すると画像が停止させることができました。そしてスタートボタンにフォーカスされリプレイが可能となりました。

■コントロールの配置その4(ラベル、テキスト)

絵柄が揃ったときのメッセージを表示するラベル、コイン残数のラベル、出目の絵柄ログのテキストボックスをフォームに追加します。
FormSlot9クラスの先頭部分に//57//58//59の宣言文3行を追加します。

class FormSlot9 : Form
{
    static int SIZE = 30;
    int WIDTH = SIZE * 22;
    int HEIGHT = SIZE * 15;

    Label lblTitle = new Label();   //57
    Label lblScore = new Label();   //58
    TextBox txtLog = new TextBox(); //59

//57では絵柄が揃ったときに表示するラベルです。
//58ではコイン残数を表示するラベルです。
//59では出目の絵柄ログを表示するテキストボックスです。

次にInitializeComponentメソッドに下記コードを全て追加します。追加する場所は「フォームにコントロールを追加」の上にします。

        ///
        ///タイトルラベルの設定
        ///
        this.lblTitle.AutoSize = true;
        this.lblTitle.Font = new Font("メイリオ", 14, FontStyle.Bold);
        this.lblTitle.Location = new Point(SIZE * 15, SIZE * 3);
        this.lblTitle.Name = "labelTitle";
        this.lblTitle.Size = new Size(SIZE * 11, SIZE * 1);
        this.lblTitle.Text = "スロットゲーム";
        this.lblTitle.TabStop = false;
        ///
        ///スコアラベルの設定
        ///
        this.lblScore.AutoSize = true;
        this.lblScore.Font = new Font("メイリオ", 14, FontStyle.Bold);
        this.lblScore.Location = new Point(SIZE * 15, SIZE * 5);
        this.lblScore.Name = "labelScore";
        this.lblScore.Size = new Size(SIZE * 11, SIZE * 1);
        this.lblScore.Text = "コイン  "
        this.lblScore.TabStop = false;
        ///
        ///ログテキストボックスの設定
        ///
        this.txtLog.AutoSize = true;
        this.txtLog.Font = new Font("メイリオ", 8);
        this.txtLog.Location = new Point(SIZE * 13, SIZE * 7);
        this.txtLog.Name = "textBoxLog";
        this.txtLog.Size = new Size(SIZE * 8, SIZE * 5);
        this.txtLog.Text = "PLAY HIT COIN REEL" + Environment.NewLine;
        this.txtLog.Multiline = true;     
        this.txtLog.TabStop = false;
        this.txtLog.ScrollBars = ScrollBars.Both;
     this.txtLog.ReadOnly = true; 

こでコマンドを実行してフォームに表示されるか確認してみましょう。

C:\Users\sumio>csc slot9.cs
C:\Users\sumio>slot9

フォーム右側にラベル2個とテキストボックスが表示されました。

■コイン残数表示とゲームオーバー表示

コイン残数を表示し、1プレイごとにコインが1減るようにします。コインが0より少なくなると「GAME OVER」の表示をします。FormSlot9クラスの先頭部分に//60を追記します。

class FormSlot9 : Form
{
    static int SIZE = 30;
    int WIDTH = SIZE * 22;
    int HEIGHT = SIZE * 15;

    int coin = 20;         //60

//60では変数coinを数値型で宣言しコイン残数の初期値を20にしています。

次にbtnStart_Clickメソッド内のwhile文の上に//61//61//63を追記します。

        coin -= 1;         //61
        lblTitle.Text = "";  //62

        while( playFlag )

//61では1プレイごとに変数coinの値を1づつ減らしています。
//62ではタイトルラベルの文字を初期化(空欄)にしています。

次にbtnStart_Clickメソッド内whileループの最後の「await Task.Delay(100);」の上に//63を追記します。そしてのbtnStart_Clickメソッドの一番最後に//64//65//66//66を追記します。(//63はメソッドの最後の}です)

            lblScore.Text = " コイン  " + coin.ToString(); //63
            await Task.Delay(100);
        }       

        if (coin < 1)   //64
        {               //64
            playFlag = false;  //65
            lblTitle.Text = "GAME OVER"; //66
            btnStart.Enabled = false;    //67
         } //67 
    }  //68

//63ではスコアラベルの表示を「コイン 」と変数coinを文字列型に変換し連結しています。コイン残数が表示されます。whileループ内に記述することでラベルへの表示タイミングが早く反映されます。
//64ではコインが1より少ない場合(0になったとき)の条件式です。
//65ではplayFlagをfalseにしメインループを終了します。
//66ではタイトルラベルに「GAME OVER」を表示します。
//67ではストップボタンを無効にします。
//68ではbtnStart_Clickメソッドの最後の波カッコ}なので記述しません。

こでコマンドを実行してフォームに表示されるか確認してみましょう。

C:\Users\sumio>csc slot9.cs
C:\Users\sumio>slot9

コインの初期値20が表示され1プレイごとにコインが1減り、0になったら「GAME OVER」が表示されます。

■プレイ数とコイン残数と出目の絵柄のログ表示

今度はテキストボックスにプレイ数、コイン残数、出目の絵柄を1プレイごとログ表示するようにします。
FormSlot9クラス内の上部の「int coin = 20;」の下に//68//69の2行を追記します。そして「FormSlot9()」メソッドの上に//70//71//72の3行を追記します。

class FormSlot9 : Form
{
    static int SIZE = 30;
    int WIDTH = SIZE * 22;
    int HEIGHT = SIZE * 15;

    int coin = 20;
    int playCount = 0;  //68
    int hit = 0;        //69

  ///途中省略///

    String[] imgSign = new String[5]{"7","=","🍒","🔔","🍉"};  //70
    String[] reelSign = new String[9];  //71
    String reelLink = "";   //72

    String[] reelSignJudg = new String[9];  //73
    String reelLinkJudg = "";  //74

    public FormSlot9()
    {

//68では変数playCountを数値型で定義し初期値を0にします。この変数はプレイ数です。
//69では変数hitを数値型で定義し初期値を0にします。この変数は絵柄が揃ったときのスコアです。ただし、このhit変数に当たりスコアを割り当てるのは次の項目になります。
//70では配列変数imgSignを定義し、絵柄の文字列5種(7,=,🍒,🔔,🍉)を代入します。この文字列がログに表示されます。
//71では配列変数reelSignを9要素で定義します。ピクチャボックスの9個のインデックス番号に割り当てられる変数です。
//72では文字列型変数reelLinkを定義し初期化(空欄)します。このreelLinkに絵柄の文字列5種が9個連結されます。
//73では配列変数reelSignJudgを9要素で定義します。reelSignは絵柄の文字列を代入しますが、reelSignJudgは当たり判定用に絵柄のインデックス番号を配列に代入します。
//74では文字列型変数reelLinkJudgを定義し初期化(空欄)します。このreelLinkJudgに絵柄の文字列のインデックス番号が9個連結されます

次に、btnStart_Clickメソッドのwhileループ内の下記for文の中に//75//76の2行を追記します。

            //変数jを9個のピクチャボックスに対しループ
            for ( int j = 0; j < picBoxes.Length; j++ )
            {	
                //変数lを3個のストップボタンに対しループ
                for ( int i = 0; i < stopBtns.Length; i++ )
                {
                    if (stopBtns[i].Enabled)
                    {
                        for ( int k = i; k < picBoxes.Length; k += 3 )
                        {
                            rIndex = r.Next(imgPath.Length);
                            picBoxes[k].ImageLocation = imgPath[rIndex];
                            reelSign[k] = imgSign[rIndex];    //75
                            reelSignJudg[k] = rIndex.ToString(); //76
                        }
                    }
                }
            }

//75では配列変数reelSignに9個の絵柄文字列を代入します。ここで「imgPath[rIndex]」と「imgSign[rIndex]」が紐づけられ各ピクチャボックス0から8に表示された絵柄画像が絵柄文字列(7,=,🍒,🔔,🍉)に変換されreelSignの0から8のインデックス番号に代入されます。
//76では配列変数reelSignJudgに9個の絵柄文字列のインデックス番号を文字列型に変換し代入します。絵柄文字列のインデックス番号(0,1,2,3,4)に変換されreelSignJudgの0から8のインデックス淳に代入されます。

次にbtnStart_Clickメソッドの最後の方の「if ( coin < 1 )」の上に//77//78//79を追記します。

        for ( int p = 0; p < reelSign.Length; p++ ) //77
        {   //77
            reelLink += reelSign[p];  //77
            reelLinkJudg += reelSignJudg[p]; //77
        }   //77

        txtLog.Text += playCount + "   " + hit.ToString() + "   " +   //78
            coin.ToString() + "   " + reelLink + Environment.NewLine; //78

        reelLink = "";  //79 
        reelLinkJudg = "";  //79

        if ( coin < 1 )
        { 
            playFlag = false;
            lblTitle.Text = "GAME OVER";
            btnStart.Enabled = false;
         }    
    }

//77ではfor文で9回(reelSign.Length)ループ処理します。文字列変数reelLinkに配列変数reelSignの要素9個を文字列で連結します。これで出目の絵柄文字列が9個連結されます。例(7=🍒🔔7🍉7🔔🔔)
同様にreelLinkJudgには出目の絵柄文字列のインデックス番号が連結されます。例(012304033)
//78ではログテキストボックスにプレイ数、当たりスコア、コイン残数、出目の絵柄文字列が表示されます。ただしこの時点ではまだ当たりスコアは0が表示されます。
//79では次のプレイに備えて、文字列変数reelLinkとreelLinkJudgを初期化(空欄)しています。reelLinkJudgは次の当たり判定で使用します。

こでコマンドを実行してログテキストに表示されるか確認してみましょう。

C:\Users\sumio>csc slot9.cs
C:\Users\sumio>slot9

ログテキストに結果が表示されました。

■絵柄の当たり表示とスコア表示

いよいよ最後です。先ほど記述したreelLinkJudg変数を利用して当たり判定を行います。同じ絵柄が横に3個揃ったらラベルに「BIG BONUS」などと表示しログにスコアを表示させます。
FormSlot9クラスのフィールド部に//80//81//82の3行を追記します。

class FormSlot9 : Form
{
  ///途中省略///
    int coin = 20;
    int playCount = 0;
    int hit = 0;

    int BIGBONUS = 20;   //80
    int REGULERBONUS = 10; //81
    int BONUS = 5  //82

//80では変数BIGBONUSを初期値20で定義します。777と揃ったときのビッグボーナスです。
//81では変数REGULERBONUSを初期値10で定義します。===(BAR)と揃ったときのレギュラーボーナスです。
//82では変数BONUSを初期値5で定義します。ベルやフルーツが揃ったときのボーナスです。

次のコードが絵柄の当たり判定になります。先ほど記述した//77と//78の間に下記コードを全て記述します。

        string ptnSEVEN  = "^000|^...000|000$"; //83
        string ptnBAR    = "^111|^...111|111$"; //83
        string ptnCHERRY = "^222|^...222|222$"; //83
        string ptnBELL   = "^333|^...333|333$"; //83
        string ptnWMELON = "^444|^...444|444$"; //83

        if (Regex.IsMatch(reelLinkJudg, ptnSEVEN)) //84
        {
            lblTitle.Text = "BIG BONUS!!!";  //85
            hit = BIGBONUS;   //86
            coin += hit;      //87
        }
        else if (Regex.IsMatch(reelLinkJudg, ptnBAR)) //88
        {
            lblTitle.Text = "REGULER BONUS!!";  //88
            hit = REGULERBONUS; //88
            coin += hit;   //88
        }
        else if ( Regex.IsMatch(reelLinkJudg, ptnCHERRY)|| //89
                  Regex.IsMatch(reelLinkJudg, ptnBELL)  || //89
                  Regex.IsMatch(reelLinkJudg, ptnWMELON) ) //89
        {
            lblTitle.Text = "BONUS!";  //89
            hit = BONUS;   //89
            coin += hit;   //89
        }
        else
        {
            hit = 0;  //90
        }

//83では5種類の絵柄に対応する文字列変数(ptnSEVEN、ptnBAR、ptnCHERRY、ptnBELL、ptnWMELON)を正規表現のパターンにマッチさせるた条件式として代入します。上段、中断、下段の横一列に同じ文字が揃った時のパターンです。
//84ではRegexクラスのIsMatchメソッドでreelLinkJudg(例:012304033)がptnSEVENのパターンにマッチするかのif文です。マッチすればtrueとなり//85//86//87が実行されます。
//85ではタイトルラベルに「BIG BONUS!!!」と表示します。
//86では変数hitにBIGBONUS(20)を代入します。こにれよりログのスコアに表示されます。
//87では変数coinにhitの値を結合代入します。これによりラベルとログにコイン残数が表示されます。
//88ではptnBARのパターンにマッチするかのif文です。
//89ptnCHERRYもしくはptnBELLならびにptnWMELONのパターンにマッチするかのif文です。
//90は絵柄が揃わなかったときhitを0に代入しています。

以上で完成です。

それではコマンドを実行して完成したゲームを試してみましょう。

C:\Users\sumio>csc slot9.cs
C:\Users\sumio>slot9

絵柄の7が揃いラベルに「BIG BONUS!!!」され、ログのスコアに20が表示されコイン残数が20増えていることが分かると思います。

■まとめ

お疲れさまでした!!いかがだったでしょうか。Windowsのフォームアプリをメモ帳プログラミングでスロットゲームを作成してみました。記事通りに進めることでゲームが少しずつ出来上がっていきプログラミングでゲーム作りの楽しさが伝わったのかなと思います。本記事を投稿するうえで一からプログラムを書いていきましたが、最後の方の絵柄当たり判定のところで、中段のパターンがなかなかマッチせずに1週間ほど足踏みしてしまいました。ハマった原因は🍒、🍉、🔔の文字列絵柄を9個連結してパターンにマッチさせていたのですが、その文字絵柄だとパターンの2文字分だったためマッチできなかったのです。改善策は配列のインデックス番号を連結してその文字列を利用してマッチさせました。このようにプログラミングしていると常にバグとの闘いになります。きらめずにいろいろ調べたりトライアンドエラーを繰り返すことでスキルアップにつながっていくと思います。今はCHATGPTに聞いたりできますので皆さんもバグを恐れずにプログラミングを続けていきましょう!

次は下記の記事で別のゲームつくりにもチャレンジしてみましょう!
メモ帳でパックマン風スネークゲーム

■プログラム全コード

using System;
using System.IO;
using System.Drawing;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Text.RegularExpressions;

class FormSlot9 : Form
{
    static int SIZE = 30;
    int WIDTH = SIZE * 22;
    int HEIGHT = SIZE * 15;

    int coin = 20;
    int playCount = 0;
    int hit = 0;

    int BIGBONUS = 20;
    int REGULERBONUS = 10;
    int BONUS = 5;

    Label lblTitle = new Label();
    Label lblScore = new Label();
    TextBox txtLog = new TextBox();

    Button btnStart = new Button();
    Button[] stopBtns = new Button[3];

    PictureBox[] picBoxes = new PictureBox[9];
    String[] imgPath = new String[5];
    String ExePath = Application.ExecutablePath;
    String ExeDirPath = Path.GetDirectoryName(
           Application.ExecutablePath);

    String[] imgSign = new String[5]{"7","=","🍒","🔔","🍉"};
    String[] reelSign = new String[9];
    String reelLink = "";

    String[] reelSignJudg = new String[9];
    String reelLinkJudg = "";

    public FormSlot9()
    {
        InitializeComponent();
    this.Load += new EventHandler(this.FormSlot9_Load);
    }

    private void InitializeComponent()
    {
        ///
        ///スタートボタンの設定
        ///
        this.btnStart.Font = new Font(
      "メイリオ", 11, FontStyle.Bold);
        this.btnStart.Location = 
            new Point(SIZE * 15, SIZE * 1);
        this.btnStart.Name = "btnStart";
        this.btnStart.Size = new Size(SIZE * 5, SIZE);
        this.btnStart.TabIndex = 0;
        this.btnStart.Text = "スタート";
        this.btnStart.Click +=
             new EventHandler(this.btnStart_Click);
        ///
        ///ストップボタン(3個)の設定
        ///
        this.SuspendLayout();
        for (int i = 0; i < this.stopBtns.Length; i++)
        {
            this.stopBtns[i] = new Button();
            this.stopBtns[i].Name = "btnStop" + (i + 1).ToString();
            this.stopBtns[i].Text = "ストップ";
            this.stopBtns[i].Font = new Font(
                "メイリオ", 11, FontStyle.Bold);
            this.stopBtns[i].Size = new Size( SIZE * 3, SIZE );
            this.stopBtns[i].Location = new Point(
                SIZE + i * SIZE * 4, SIZE * 11);
            this.stopBtns[i].TabStop = true;
            this.stopBtns[i].Enabled = false;
            this.stopBtns[i].Click +=
                new EventHandler(this.stopBtns_Click);
        }
        ///
        ///ピクチャーボックス(3*3個)の作成
        ///
      int pbROWS = 3;
      int pbCOLS = 3;
        for (int i = 0; i < pbROWS; i++)
        {
            for (int j = 0; j < pbCOLS; j++)
            {
                int index = i * pbCOLS + j;
        //インスタンス作成
                this.picBoxes[index] = new PictureBox(); 
                this.picBoxes[index].Location = 
                    new Point(SIZE + j * SIZE * 4, 
           SIZE + i * SIZE * 3);
                this.picBoxes[index].Name = 
                    "picBox" + (index + 1).ToString();
                this.picBoxes[index].Size = 
                    new Size(SIZE * 3, SIZE * 3);
                this.picBoxes[index].SizeMode = PictureBoxSizeMode.Zoom;
                this.picBoxes[index].TabStop = false;
                this.picBoxes[index].ImageLocation = 
                    imgPath[index % imgPath.Length];
            }
        }
        ///
        ///タイトルラベルの設定
        ///
        this.lblTitle.AutoSize = true;
        this.lblTitle.Font = new Font("メイリオ", 14, FontStyle.Bold);
        this.lblTitle.Location = new Point(SIZE * 15, SIZE * 3);
        this.lblTitle.Name = "labelTitle";
        this.lblTitle.Size = new Size(SIZE * 11, SIZE * 1);
        this.lblTitle.Text = "スロットゲーム";
        this.lblTitle.TabStop = false;
        ///
        ///スコアラベルの設定
        ///
        this.lblScore.AutoSize = true;
        this.lblScore.Font = new Font("メイリオ", 14, FontStyle.Bold);
        this.lblScore.Location = new Point(SIZE * 15, SIZE * 5);
        this.lblScore.Name = "labelScore";
        this.lblScore.Size = new Size(SIZE * 11, SIZE * 1);
        this.lblScore.Text = "コイン  " + coin.ToString();  
        this.lblScore.TabStop = false;
        ///
        ///ログテキストボックスの設定
        ///
        this.txtLog.AutoSize = true;
        this.txtLog.Font = new Font("メイリオ", 8);
        this.txtLog.Location = new Point(SIZE * 13, SIZE * 7);
        this.txtLog.Name = "textBoxLog";
        this.txtLog.Size = new Size(SIZE * 8, SIZE * 5);
        this.txtLog.Text = "PLAY HIT COIN REEL" + Environment.NewLine;
        this.txtLog.Multiline = true;     
        this.txtLog.TabStop = false;
        //水平垂直スクロールバー 
        this.txtLog.ScrollBars = ScrollBars.Both;//Vertical; 
     this.txtLog.ReadOnly = true; 
        ///
        ///フォームにコントロールを追加
        ///
        this.Name = "FormSlot9";
        this.Text = "9スロットゲーム";
        this.Controls.Add(this.btnStart);
        this.Controls.AddRange(this.stopBtns);
        this.Controls.AddRange(this.picBoxes);
        this.Controls.Add(this.lblTitle);
        this.Controls.Add(this.lblScore);
        this.Controls.Add(this.txtLog);
    }

    private void FormSlot9_Load(object sender, EventArgs e)
    {
        Size = new Size(WIDTH, HEIGHT);
        imgPath[0] = ExeDirPath + @"\seven.png";
        imgPath[1] = ExeDirPath + @"\bar.png";
        imgPath[2] = ExeDirPath + @"\cherry.png";
        imgPath[3] = ExeDirPath + @"\bell.png";
        imgPath[4] = ExeDirPath + @"\watermelon.png";
    }

    private async void btnStart_Click(object sender, EventArgs e)
    {
        btnStart.Enabled = false;

        for (int i = 0; i < stopBtns.Length; i++)
        {
            stopBtns[i].Enabled = true;
        }

        stopBtns[0].Focus();
        bool playFlag = true;

        Random r = new Random();
        int rIndex = 0;

        coin -= 1;
        lblTitle.Text = "";
        playCount += 1;

        while( playFlag )
        {    
            if( stopBtns[0].Enabled == false &&
                stopBtns[1].Enabled == false &&
                stopBtns[2].Enabled == false)
            {
                btnStart.Enabled = true;
                btnStart.Focus();
                break;
            }

            for ( int j = 0; j < picBoxes.Length; j++ )
            {	
                for ( int i = 0; i < stopBtns.Length; i++ )
                {
                    if (stopBtns[i].Enabled)
                    {
                        for ( int k = i; k < picBoxes.Length; k += 3 )
                        {
                            rIndex = r.Next(imgPath.Length);
                            picBoxes[k].ImageLocation = imgPath[rIndex];
                            reelSign[k] = imgSign[rIndex];
                            reelSignJudg[k] = rIndex.ToString();
                        }
                    }
                }
            }
            lblScore.Text = " コイン  " + coin.ToString();
            await Task.Delay(100);
        }

        for ( int p = 0; p < reelSignJudg.Length; p++ )
        { 
             reelLink += reelSign[p];
             reelLinkJudg += reelSignJudg[p];
        }

        string ptnSEVEN  = "^000|^...000|000$";
        string ptnBAR    = "^111|^...111|111$";
        string ptnCHERRY = "^222|^...222|222$";
        string ptnBELL   = "^333|^...333|333$";
        string ptnWMELON = "^444|^...444|444$";

        if (Regex.IsMatch(reelLinkJudg, ptnSEVEN))
        {
            lblTitle.Text = "BIG BONUS!!!";
            hit = BIGBONUS;
            coin += hit; 
        }
        else if (Regex.IsMatch(reelLinkJudg, ptnBAR))
        {
            lblTitle.Text = "REGULER BONUS!!";
            hit = REGULERBONUS;
            coin += hit;
        }
        else if ( Regex.IsMatch(reelLinkJudg, ptnCHERRY)|| 
                  Regex.IsMatch(reelLinkJudg, ptnBELL)  || 
                  Regex.IsMatch(reelLinkJudg, ptnWMELON) )
        {
            lblTitle.Text = "BONUS!";
            hit = BONUS;
            coin += hit;
        }
        else
        {
            hit = 0;
        }

        txtLog.Text += playCount + "   " + hit.ToString() + "   " + 
            coin.ToString() + "   " + reelLink + Environment.NewLine;

        reelLink = ""; 
        reelLinkJudg = "";     

        if ( coin < 1 )
        { 
            playFlag = false;
            lblTitle.Text = "GAME OVER";
            btnStart.Enabled = false;
         }    
    }

    private void stopBtns_Click(object sender, EventArgs e)
    {
        Button btn = (Button)sender;
        btn.Enabled = false;
    }

    static void Main()
  {
        Application.Run( new FormSlot9());
    }
}

コメント