前回(注)、アップロードサイズオーバーでアップできなかった残りの部分を解説します。

注:前回はプログラムの「アプリケーションクラス」、「Othelloクラス」の内「メンバーフィールド(C++でいうメンバー変数)」、「コンストラクター」、「ウィンドウイベント処理」と「コントロールイベント処理」までをカバーしました。

 

今回カバーするのは「オセロゲーム関連処理」、「グラフィック表示」と「ログファイル処理」になります。

 

        ///////////////////////
        //オセロゲーム関連処理
        ///////////////////////
        //コンピュータ側判定処理

        public void computer()
        {
            if(isOver())
                return;            //ゲーム終了なら何もしない
            int num;            //裏返すことが出来る石の数
            byte diskColor = radioButton1.Checked ? (byte)2 : (byte)1;    //先攻後攻判断(解説:これも簡略化表記です。)
            int MaxEval = -1, Bestx = -1, Besty = -1, MaxNum = 0;        //MaxEval-最大戦略的評価値

            //解説:オリジナルよりも分かり易くMaxPr→MaxEval、MaxI→Besty、MaxJ→Bestx変更しています。
            for(int y = 0; y < 8; y++)
            {    //オセロ盤の全桝目をチェック
                for(int x = 0; x < 8; x++)
                {
                    int numTotal = 0;                    //裏返すことが出来る石の数の合計
                    for(int dir = 0; dir < 8; dir++)    //iは桝目(x, y)の周囲8方向(解説:意味が分かるように'i'から変更)
                    {
                        if((num = numberCount(x, y, diskColor, dir)) > 0)
                            numTotal += num;            //裏返せる石の合計
                    }
                    if(numTotal > 0 &&                    //裏返せる石があり、且つ
                    (MaxEval < EvalTable[y, x] ||      //戦略評価が以前の最大値を超えるか(EvalTableは1から200)

                    //解説:オリジナルの「評価の最大値が0未満」は「その枠の評価値」が必ず1以上(評価テーブル参照)なので不要としています。

                    (MaxEval == EvalTable[y, x] && MaxNum < numTotal)))    //同じでも裏返せる石の数が多い場合


                    {
                        MaxEval = EvalTable[y, x];        //戦略評価を更新
                        MaxNum = numTotal;                //裏返せる石の最大数更新
                        Bestx = x; Besty = y;            //その升目の座標を記録
                    }
                }
            }
            if (MaxEval < 0)
                MessageBox.Show("打つ場所がありません。パスします", "確認", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
            else
                flipDisks(Bestx, Besty, diskColor);    //石を裏返す(解説:メソッドの名称を変更しています。)
        }

        //人間側判定処理
        public void person(int posx, int posy)
        {
            if(dataTable[posy, posx] != 0)            //空でない場合は何もしない
                return;
            byte diskColor = radioButton1.Checked ? (byte)1 : (byte)2;    //先攻後攻判断(解説:簡略表記にしています。)
            if(canPlace(posx, posy, diskColor))        //石を置くことができる場合
            {
                flipDisks(posx, posy, diskColor);    //裏返し処理
                Thread.Sleep(200);                    //waitの代わり(解説:無くてもよいのですが、一応ウェイトを置きました。)
                computer();                            //コンピュータ側処理
            }
            else
                MessageBox.Show("そこには打てません。", "確認", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
            isOver();                 //ゲーム終了チェック(解説:終了判定メソッドを名称変更し、結果表示も移行しました。)
        }

        //石の配置初期設定
        private void InitTable()        //解説:Initialize()から名称変更
        {
            //データテーブルを空き(0)で初期化
            for(int y = 0; y < 8; y++)
            {
                for (int x = 0; x < 8; x++)
                {
                    dataTable[y, x] = 0;
                }
            }
            //中央に黒白石の初期配置
            dataTable[3, 3] = dataTable[4, 4] = 1;
            dataTable[4, 3] = dataTable[3, 4] = 2;
        }

        //空の桝目(x, y)から指定方向に裏返せる石の数を返す。置けない場合は0を返す。(解説:オリジナルでは置けない場合、-1を返していましたが、このメソッドの条件判定がすべて"> 0"であったので、falseらしく0を返すようにしました。)
        //引数:座標(x, y)、自石色(黒-1、白-2)、指定座標周囲の方向(0-7)

        public int numberCount(int x, int y, int diskColor, int dir)
        {
            if(dataTable[y, x] != 0)
                return 0;        //既に石が置かれている場合
            int oponentColor = 3 - diskColor;    //敵石の色(黒 - 1、白 - 2)
            int dx = vectorTable[dir, 1], dy = vectorTable[dir, 0];    //設定方向のx、y座標差分(vectorTable参照)
            int nextX = x + dx, nextY = y + dy;                        //設定方向1個先のx、y座標
            if(nextX < 0 || nextX > 7 || nextY < 0 || nextY > 7)
                return 0;        //指定方向1個先が盤外の場合(一次チェック-無いと範囲外エラーとなる)
            int numStone = 0;    //(nextX, nextY)から指定方向へ続く敵石の数
            while(dataTable[nextY, nextX] == oponentColor)
            {
                numStone++;        //裏返すことが出来る敵石の数を増やす
                nextX += dx;    //指定方向に沿って(nextX, nextY)の桝目の先を
                nextY += dy;    //x、y座標差分を繰り返してチェック
                if(nextX < 0 || nextX > 7 || nextY < 0 || nextY > 7)
                    return 0;    //盤外の場合
            }
            if(dataTable[nextY, nextX] == diskColor)    //敵石の連続が終わる座標をチェック
                return numStone;    //自石であれば裏返せる
            else
                return 0;            //それ以外(空-0)の場合
        }

        //桝目(x, y)に石を置くことができるかを判定
        public bool canPlace(int x, int y, int diskColor)
        {
            for(int dir = 0; dir < 8; dir++)
            {
                if(numberCount(x, y, diskColor, dir) > 0)
                    return true;
            }
            return false;
        }

        //盤上に石を置く場所があるか判定
        public bool canPlace(int diskColor)
        {
            for(int y = 0; y < 8; y++)
            {
                for (int x = 0; x < 8; x++)
                {
                    if(canPlace(x, y, diskColor))
                        return true;
                }
            }
            return false;
        }

        //終了判定(解説:終了-true、未終了-false)
        public bool isOver()
        {
            //既に試合終了しておれば何もしない(解説:これを入れておかないと、personの中のcomputerが呼ばれるので。)
            if(startButton.Enabled == true)
                return true;
            for(int diskColor = 1; diskColor < 3; diskColor++)
            {
                if(canPlace(diskColor))
                    return false;    //敵味方一つでも石を置ける場合
            }
            //両者の石をカウントする
            int diskColor1 = 0, diskColor2 = 0;    //diskColor1が先攻(黒)、diskColor2が後攻(白)
            for(int y = 0; y < 8; y++)
            {
                for(int x = 0; x < 8; x++)
                {
                    if(dataTable[y, x] == 1)
                        diskColor1++;
                    else if(dataTable[y, x] == 2)
                        diskColor2++;
                }
            }
            //石の数で勝負判定
            if(diskColor1 == diskColor2)
                MessageBox.Show("引き分けです。", "結果", MessageBoxButtons.OK, MessageBoxIcon.Information);
            else if((diskColor1 > diskColor2 && radioButton1.Checked) || (diskColor2 > diskColor1 && radioButton2.Checked))
            {
                Wins++;        //勝数を更新
                MessageBox.Show("貴方の勝ちです。黒 = " + diskColor1 + ", 白 = " + diskColor2, "結果", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
            else
            {
                Loses++;    //負数を更新
                MessageBox.Show("PCの勝ちです。黒 = " + diskColor1 + ", 白 = " + diskColor2, "結果", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                //解説:「貴方」「私」がはっきりしなかったので、「貴方(人間)」「PC」にしました。

            }
            Games++;                        //ゲーム数を更新
            label.Text = pmpt;                //プロンプト表示
            radioButton1.Enabled = true;    //「先攻」を有効化する
            radioButton2.Enabled = true;    //「後攻」を有効化する
            radioButton1.Checked = radioButton2.Checked = false;    //先攻、後攻のチェックを外す
            startButton.Enabled = true;        //「開始」ボタンを有効化する
            passButton.Enabled = false;        //「パス」ボタンを無効化する
            return true;
       }

        //石を裏返す
        public void flipDisks(int x, int y, byte diskColor)        //解説:分かり易いように、名称変更しています。
        {
            byte [, ] Temp = new byte[8, 8];    //盤の状態を作業用盤コピー(注)
            //注:8方向でチェックはオリジナルで行うので、オリジナルの盤面を変えないように

            for(int i = 0; i < 8; i++)
            {
                for(int j = 0; j < 8; j++)
                {
                    Temp[i, j] = dataTable[i, j];
                }
            }
            for(int dir = 0; dir < 8; dir++)    //作業用盤を8方向でチェック
            {       
                int num = numberCount(x, y, diskColor, dir);
                if(num > 0)        //裏返すことが出来る石があるなら
                {
                    int flipx = x, flipy = y;
                    int dx = vectorTable[dir, 1], dy = vectorTable[dir, 0];    //指定方向(dir)のx、y差分
                    for(int i = 0; i <= num; i++, flipx += dx, flipy += dy)    //裏返す桝目+現在の位置
                    //解説:裏返す数が「num + 1」なのは、敵の石と現在の位置(空)を自分の石にする為です。

                    {
                        Temp[flipy, flipx] = diskColor;
                    }
                }
            }
            for(int i = 0; i < 8; i++)            //解説:作業用盤面をデータテーブルにコピーする
            {
                for(int j = 0; j < 8; j++)
                {
                    dataTable[i, j] = Temp[i, j];
                }
            }
            this.Invalidate();    //フォームの再描画
        }

        ///////////////////
        //グラフィック表示
        ///////////////////
        //石の表示

        private void displayStone(int x, int y, Brush b, Pen p, float R)
        {
            //クライアントエリアオフセット(10, 10)と盤面のオフセット(10, 10)で(20, 20)
            float X = x * 50 + 20, X1 = X + 1;
            float Y = y * 50 + 20, Y1 = Y + 1;
            gr_image.FillEllipse(bG, X1, Y1, R, R);    //石に厚さがあるような形で表示
            gr_image.FillEllipse(b , X, Y, R, R);
            gr_image.DrawEllipse(p , X, Y, R, R);
        }

        //盤面、桝目と石の表示
        public void displayBoard()
        {
            //(10,10)から背景(400 x 400)を暗い緑色で塗潰す
            gr_image.FillRectangle(bD, 10, 10, 400, 400);
            //太さ2の白色で内側に縦横線(格子)を引く
            for(float X = 60; X < 390; X += 50)
                gr_image.DrawLine(p1, X, 10, X, 410);
            for(float Y = 60; Y < 390; Y += 50)
                gr_image.DrawLine(p1, 10, Y, 410, Y);
            //太さ4の白色で外枠を描く
            gr_image.DrawRectangle(p2, 10, 10, 400, 400);
            //dataTable(0-空、1-黒石、2-白石)を描く
            for(int y = 0; y < 8; y++)
            {
                for (int x = 0; x < 8; x++)
                {    //白い石は大きく見えるので若干小さく表示
                    if(dataTable[y, x] == 1)        //黒石
                        displayStone(x, y, bB, pG, 30);
                    else if(dataTable[y, x] == 2)    //白石
                        displayStone(x, y, bW, pB, 29);
                }
            }
        }

        ///////////////////
        //ログファイル処理
        ///////////////////
       //解説:以下が戦績データをLog.datファイルに読み書きするメソッドです。

        private bool LogRead()
        {
            string data = "";
        /*    出力UTF-8    :Encoding.UTF8;
            出力UTF-16LE:Encoding.Unicode;
            出力S-JIS    :GetEncoding("shift-jis")    */

            using (StreamReader file = new StreamReader(".\\Log.dat", Encoding.UTF8, false))
            {
                //ファイルを読む
                data = file.ReadToEnd();
                if(data == "")    //ファイルが読み込めない場合
                    return false;
                //data文のデータ化(GT_Games、GT_Winns、GT_Loses)
                string[] words = data.Split(',');
                if(!Int32.TryParse(words[0], out GT_Games))
                    return false;
                if(!Int32.TryParse(words[1], out GT_Wins))
                    return false;
                if(!Int32.TryParse(words[2], out GT_Loses))
                    return false;
                // ファイルを閉じる
                file.Close();
            }
            return true;
        }

        private void LogWrite()
        {
            //累計を今回のデータで更新する
            GT_Games += Games;
            GT_Wins += Wins;;
            GT_Loses += Loses;
            string data = GT_Games.ToString() + "," + GT_Wins.ToString() + "," + GT_Loses.ToString();
        /*    出力UTF-8    :Encoding.UTF8;
            出力UTF-16LE:Encoding.Unicode;
            出力S-JIS    :GetEncoding("shift-jis")    */

            using (StreamWriter file = new StreamWriter(".\\Log.dat", false, Encoding.UTF8))
            {
                //ファイルを書く
                file.Write(data);
                // ファイルを閉じる
                file.Close();
            }
        }
    }
}

 

以上です。フィールドやメソッドの名称をかえ、関連するメソッドをまとめて集めたりすることで、大分分かり易くなったのではないでしょうか?

 

なお、このオセロゲームは単純なゲームですが、結構入れ込んで遊べます。特に盤や石のグラフィック表示や、評価テーブルが素晴らしいですね。

 

wikiによれば、完全勝利手順はまだ分かっていないそうですが、定石があるようで、それに基づけば結構な戦績を残せられそうです。(私はオセロの初心者で、PCの評価テーブルを見ているのですが、↓のような有様です。)

 

ps. 尚、このプログラムのソースファイルとコンパイルオプションファイルは、BCCForm_and_BCCSkeltonパッケージの".\SampleBCCSkelton\MSCompAss\Debug\Samples\Othello"フォールダーにアイコンファイルと共に入っています。