今朝入浴中(20年以上、朝風呂派なんで...)、26歳で横浜に勤務していた時、転勤してきた大学の先輩の課長さんから「ねぇ、横浜に良いメキシコ料理店ないかな?ああ、急がないけど、探しておいて。よろしく。」とご下命があったことを思い出し、

 

昔若かったころ、店や行き先をどう探していたっけ?

 

等と考えてしまいました。

 

今でこそ、GoogleやCopilot等にキーワードや質問/依頼を投げかければ、直ぐに場所、アクセス、電話番号、評判、メニュー(画像付)で教えてくれる利便性の高い環境となりましたが、当時の

 

駅の伝言板、公衆電話や電話帖の時代

 

(今でも余りポピュラーではないメキシコ料理レストランを44年前に)どうやって探したか、直ぐには思い出せないのです。

 

そんなこんなで「多分こうやっていたんだろう」と考えて風呂から上がった後、神さんに「どうやっていたっけ?」と訊いて、そうだったなぁ、と思い出しました。つまり、

 

(1)求められる情報の姿、形を考える(i.e. メキシコ料理店の場所、アクセス、電話番号、評判)

(2)それらの情報が得られるソースを考える(当時はこのような情報は雑誌、口コミ、電話帳や電話番号案内位しかありませんでした。)

(3)情報検索(雑誌、電話帳は本屋、図書館等で調べるか、口コミは「土地や店情報に詳しい知人またはそういう人間を知っていそうな知人」に訊く)

(4)得られた情報の検証は情報検索先で(セカンドオピニオンとして)再検索(現罪と同じく不確実性リスクは残ります。)

(5)最終的に取捨選択した情報を編集(勿論「紙とペン」で。)

 

という一連の調査活動を行うわけですね。

 

私の場合は運よく、職場の横浜っ子のおばさん同僚が洒落ていて、「関内に素敵なメキシカンレストランがあるわよ。」というアドバイスで簡単に調査完了したことを覚えていますが、そういう幸運が無ければ結構時間も手間もかかるような仕事になったでしょう。

 

一方、

 

不便ではあっても、昔は↑のように自分で予め最適解を考えだすトレーニングを積むことになり、生存能力を向上させていたことは疑いがありません。現在は利便性が高い一方、災害や事故等の障害発生時は脆弱性が高く、代替情報取得手段や能力が劣ってしまったことから危険回避行動の冗長化不良となり、更にパニック危険も加わることになります。

 

結局どっちが幸せかわかんないよね?

(「人間万事塞翁が馬」)

 

ということが人間の人生の実相かもしれません。

 

ps. などと偉そうなことを書いていますが、「じゃ、もう一度黒電話や電話帳の時代に戻るかい?」と言われれば、

 

Definitely NO!!!!

 

というでしょうね。

【Python日記】は矢張り教条的になるせいか、余り好評ではありませんね。(でも、私のようなC系アマチュアプログラマーから見た目でどう特徴的なのかを記すのも面白いと思うんですが...)

 

ということで、

 

たまにはを入れた方が良いかなと思い、時々「番外編」を挿入してみようかと思います。

 

今回はワイアーフレーム!

 

先ずはwebで見つけたプログラムを紹介します。

 

【WIれFrame.pynb】

import numpy as np

import matplotlib.pyplot as plt

from mpl_toolkits.mplot3d import Axes3D

 

#3次元データの生成

x = np.linspace(-10, 10, 100)

y = np.linspace(-10, 10, 100)

x, y = np.meshgrid(x, y)

z = np.sin(np.sqrt(x**2 + y**2))

#ワイヤーフレームの描画

fig = plt.figure()

ax = fig.add_subplot(111, projection='3d')

ax.plot_wireframe(x, y, z, color='red')

#軸ラベルの設定

ax.set_xlabel('X')

ax.set_ylabel('Y')

ax.set_zlabel('Z')

#表示

plt.show()

 

たったこれだけ。なのに、

 

恐るべし、Python。

 

前回の後、我が神さんの神業により、ダンボールを規定サイズに纏めていただき、

 

(1)PCのクリーニング(起動用USBメモリースティックを用意し、DESTROY.exeソフトを使用します。)

(2)段ボール梱包

(3)廃棄業者の選定(ReNetを使うつもりです)

(4)予約、引き取り

 

という手順の(1)~(3)まで完了しました。

 

そして、

 

とうとう昨日、7年間お世話になった兄の自作PCとお別れしました。

 

特に、

 

2020年4月の完全定年退職から、再度プログラミングを再開し、Embarcadero C++とBatchGoodによるるBCCForm and BCCSkeltonプログラミング、WIndows 10付属cs.exeとMSCompAssによるC#プログラミングで目覚ましく活躍し、多くの自作ソフトウェアを作ることが出来たのは、ひとえにこのPCのおかげでした。

 

しかし、「脱皮せざる蛇は滅びん」(ニーチェ)

 

という箴言の通り、世は64bit、インターネットベースの時代になっており、今回の引退は

 

歴史的必然

 

だったのでしょう。

 

そう、私もその例外ではない。

 

もう一度気を引き締めて、

 

余生を悔いのないように過ごそう

 

と思う水曜日でした。

 

合掌

前回までの「Python初体験」の続きです。

今回はタプル(tuple-型宣言は必要ありませんが...)をやりました。タプルはリスト(list)と同じ、複数のデータの組み合わせなのですが、リストが可変長、要素の値が可変なのに対して、タプルは定義時に長さも要素も固定されます。(C++をやる人なら、constのイメージで考えていただければよいかなと。)
また、表記方法も異なります。

【リスト】
list_sample = [abc, def, ghi]

【タプル】
tuple_sample = abc, def, ghi  #実際のプログラミングでは、
tuple_sample = (abc, def, ghi)  #間違えないように"()"で括られます。

リストとタプルの特性からどのように使い分けるかについて、Python初体験では、特定の生徒の教科別得点を記録する場合(データ記録用という意味が強いので)タプル、(生徒数が異なる等)教科毎に(動的)データを処理する可能性があるような場合にはリスト、というような説明をしています。

「複数の要素から構成される独立したデータ をあらわすときは、タプルを使用します。」
「固定的な形式をもつ独立したデータではなく、不定個数の独立したデータをたくさん集約してまとめておきたい、という用途には、リストが適しています。」

タプルの使い方がいまいちピンときませんが、色々と遊んでいると「んん」と感じることもあります。

value1 = (1, 2, 3)
value2 = (1, 2, 3, 4)
value3 = (2, 3, 4)
value4 = (2, 3, 4, 5)
value5 = (3, 4, 5)
value6 = (3, 4, 5, 6)
value7 = (4, 5, 6)
value8 = (4, 5, 6, 7)

value_a = value1 + value2 + value3 + value4
value_b =  value5 + value6 + value7 + value8

print("value1, value2, value3, value4 : ", value1, value2, value3, value4)
print("value5, value6, value7, value8 : ", value5, value6, value7, value8)
print("value_a[0], value_a[1], value_a[2], value_a[3] : ", value_a[0], value_a[1], value_a[2], value_a[3], )
print("value_b[0], value_b[1], value_b[2], value_b[3] : ", value_b[0], value_b[1], value_b[2], value_b[3], )

等号演算子(==)や不等号演算子(!=)が使えますが、要素数と要素が全く同じ出ないとなりません。比較演算子は次のように判断されるそうです。
「タプル同士の値の比較は、先頭の要素から順番に同じインデックス同士の値を比較して、先に小さい値となったタプルが小さい値となります。」
「比較するタプル同士の長さが異なる場合、短いタプルの要素と長いタプルの要素を比較してすべて等しければ、短い要素のほうが小さい値となります。」

print("value2 > value1? : ", value2 > value1)
print("value3 > value1? : ", value3 > value1)
print("value_a == value1 + value2 + value3 + value4 ? :", value_a == value1 + value2 + value3 + value4)
print("value_b == value5 + value6 + value7 + value8 ? :", value_b == value5 + value6 + value7 + value8)

print("value1 in value_a? : ", value1 in value_a)
mix = (1, 2, 3, "A", "B", "C", 'a', 'b', 'c')
print("mix : ", mix)
print("mix > value1 :", mix > value1)

【出力】

value1, value2, value3, value4 :  (1, 2, 3) (1, 2, 3, 4) (2, 3, 4) (2, 3, 4, 5)
value5, value6, value7, value8 :  (3, 4, 5) (3, 4, 5, 6) (4, 5, 6) (4, 5, 6, 7)
value_a[0], value_a[1], value_a[2], value_a[3] :  1 2 3 1
value_b[0], value_b[1], value_b[2], value_b[3] :  3 4 5 3
value2 > value1? :  True
value3 > value1? :  True
value_a == value1 + value2 + value3 + value4 ? : True
value_b == value5 + value6 + value7 + value8 ? : True
value1 in value_a? :  False
mix :  (1, 2, 3, 'A', 'B', 'C', 'a', 'b', 'c')
mix > value1 : True

二次元のタプルを作るには、このようにするのでしょうか?一次元のタプルを要素にしたタプル配列を作るようです。

single1 = (1, 2, 3, 4, 5)
single2 = ("ABC", "DEF", "GHI", "JKL",)
single3 = (6, "MNO", 7, "PQR")
#multi1 = (,)  #エラー(空の配列ポインターはできない)
multi1 = (single1, single2, single3)
print("multi1 : ", multi1)
print("multi1's elements : ", multi1[0], multi1[1], multi1[2])
multi2 = single1
multi2 += single2
multi2 += single3
print("multi2 : ", multi2)
print("multi2's elements : ", multi2[0], multi2[1], multi2[2], multi2[3], multi2[4], multi2[5], multi2[6], multi2[7], multi2[8], multi2[9], multi2[10], multi2[11], multi2[12])

【出力】
multi1 :  ((1, 2, 3, 4, 5), ('ABC', 'DEF', 'GHI', 'JKL'), (6, 'MNO', 7, 'PQR'))
multi1's elements :  (1, 2, 3, 4, 5) ('ABC', 'DEF', 'GHI', 'JKL') (6, 'MNO', 7, 'PQR')
multi2 :  (1, 2, 3, 4, 5, 'ABC', 'DEF', 'GHI', 'JKL', 6, 'MNO', 7, 'PQR')
multi2's elements :  1 2 3 4 5 ABC DEF GHI JKL 6 MNO 7 PQR

最初から要素だった場合(multi1)と、後で'+'演算子で追加された場合(multi2)の取り扱いが異なるようです。multi1は二次配列になっていますが、multi2は一次配列の要素が追加された一次配列になっています。

関連するメソッドとして、以下のようにtupleとlistを変換する関数や、(内部的に変更不可能なtupleを一旦リストに変換してソートしてまたタプルにする)sorted関数があるそうです。

tuple(list)
list(tuple)

tp = (1, 9, 3, 7, 5, 8)
print("タプル tp : ", tp)
lst = sorted(tp)  #ntpはリスト
print("Make a list, lst made of sorted tp : ", lst)
ntp = tuple(lst)  #リストをタプルに変換
print(ntp)

【出力】
タプル tp :  (1, 9, 3, 7, 5, 8)
Make a list, lst made of sorted tp :  [1, 3, 5, 7, 8, 9]
(1, 3, 5, 7, 8, 9)

 

まぁ(リストがあれば足りるのですが、書き換えできないデータを使いたい等の事態もあるでしょうし)、おいおいタプルの必然性が分かってくるでしょう。

 

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

注:前回はプログラムの「アプリケーションクラス」、「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"フォールダーにアイコンファイルと共に入っています。

 

前回はオリジナルのVisual Studioベースの(即ちInitializeComponent()を使った)コードの解説をしましたので、今回は完全コードベースのMSCompAssベースの改造オセロプログラムをやります。(今もちょっと対戦し、1勝4敗でした。残念無念。)

改造のポイントは以下の通りです。

(1)(フィールド(変数)やメソッド(関数)の名称を含め)プログラムの可読性を高めた。

(2)不要、または効率性の点からコードを修正。

(3)機能的には最初に先攻、後攻を選択させる形にして、都度メッセージボックスでユーザー指示を与え、簡単に対戦結果を保存する。

 

いつも通り、コメントで"解説:"します。

 

【Othello.cs】

/////////////////////////////////////////////////////////////////
//オセロゲーム(Othello.cs)
//原典:http://www.souzousha.iinaa.net/Source/Othello.txt
//オセロ(またはReversi):https://en.wikipedia.org/wiki/Reversi
//【用語】(解説:余計なようですが、一応こういう用語で統一しました。)
//Board - 盤
//Disks - 石
//Square - 升目
/////////////////////////////////////////////////////////////////

using System;
using System.Drawing;
using System.Windows.Forms;
using System.Threading;
using System.Reflection;        //Assemblyを使う為
using System.IO;                 //StreamReader/Writerを使用するのに必要
using System.Text;                 //Encoding.GetEncodingを使用するのに必要

namespace Othello
{
    //アプリケーションクラス(解説:プログラムのエントリーポイント用のクラスです。)
    public class App
    {
        [STAThread]    //解説:これをいつも忘れないように。(SingleThreadApartment)
        public static void Main()
        {
            OthelloForm of = new OthelloForm();
            Application.Run(of);
        }
    }

    //Othelloクラス
    public partial class OthelloForm : Form
    {
        //ウィンドウコントロール
        Label label;                                    //ラベルコントロール
        RadioButton radioButton1, radioButton2;            //ラジオボタンコントロール
        Button startButton, passButton, exitButton, datButton;    //ボタンコントロール
        //表示関係
        public Image image2disp = new Bitmap(412, 412);    //表示用ビットマップ(幅412、高さ412)

        //解説:オリジナルはでかかったのですが、マージンを入れてもこれだけで十分なので。又名称も変更しました。
        public Graphics gr_image;                        //表示用ビットマップのグラフィックス
        //解説:これも名称変更しました。

        Brush bD = new SolidBrush(Color.DarkGreen);        //ブラシ暗い緑色
        Brush bW = new SolidBrush(Color.White);            //        白色
        Brush bB = new SolidBrush(Color.Black);            //        黒色
        Brush bG = new SolidBrush(Color.Gray);            //        灰色
        Pen pB = new Pen(Color.Black, 1);                //ペン    黒色
        Pen pW = new Pen(Color.White, 1);                //        白色
        Pen pG = new Pen(Color.DarkGray, 1);            //        暗い灰色
        Pen p1 = new Pen(Color.White, 2);                //        白色(太さ2)
        Pen p2 = new Pen(Color.White, 4);                //        白色(太さ4)
        //解説:変更なしです。

        string pmpt = "先攻(黒石)、後攻(白石)を選択して、開始ボタンをクリックして下さい。";
        //解説:2か所、プロンプト用の文字列を使っていたので、この文字列に統一しました。

        //オセロデータ関係
        byte[,] dataTable = new byte[8, 8];                //オセロ盤配列[y, x](0-空、1-黒、2-白)

        //解説:略称のdTabが分かりにくかったので変更しました。また添字が[y, x]になる点明記しました。
        int[,] EvalTable =                                //評価用テーブル[y, x](8 x 8の盤に対応)
        //解説:オリジナルのEvalだけだと分かりづらいので変更しました。また添字が[y, x]になる点明記しました。

                {{200,  6, 70, 30, 30, 70,  6, 200},    //戦略的重要度を数値で表している。
                {  6,  5,  7,  6,  6,  7,  5,   6},        //四隅が200で最も高く、四隅の一つ手前等
                 { 70,  7, 40, 30, 30, 40,  7,  70},        //要所が高く設定されている。
                { 30,  6, 30,  1,  1, 30,  6,  30},        //本プログラムの最も重要なノウハウ部分。
                { 30,  6, 30,  1,  1, 30,  6,  30},
                { 70,  7, 40, 30, 30, 40,  7,  70},
                {  6,  5,  7,  6,  6,  7,  5,   6},
                {200,  6, 70, 30, 30, 70,  6, 200}};
        //オセロ盤の特定位置の周囲8方向を表すテーブルで第1引数は↓の方向(0 - 7)を表し、
        //第2引数(0 - 1)はy座標、x座標の差分を表している。
        int[,] vectorTable                                //指定点から伸ばす方向表

        //解説:オリジナルのprocTableでは意味不明なので、名称変更しました。
            = {    {-1, -1 }, {-1, 0 }, {-1, 1},            //上(左、中、右)
                { 0, -1 }, { 0, 1 },                    //左、右
                { 1, -1 }, { 1, 0 }, { 1, 1}};            //下(左、中、右)
        //戦績データ(Log.datを作成する為、追加)・・・解説:戦績データを今回、累計で持ちます。
        int Games, Wins, Loses;                            //今回の勝負数、人間の勝数、人間の負数
        int GT_Games, GT_Wins, GT_Loses;                //その累積数

        //コンストラクター
        public OthelloForm()
        {
            //プログラムアイコンをフォームにつける
            Assembly myOwn = Assembly.GetEntryAssembly();
            this.Icon = Icon.ExtractAssociatedIcon(myOwn.Location);

            //解説:因みにこういうアイコンを作りました。(BCCSkeltonで作ったIconViewerの画像)


            this.Size = new Size(560, 528);            //初期サイズ
            this.MinimumSize = new Size(530, 508);    //最小サイズ
            this.Text = "Othello";
            //解説:以下がメインフォームのイベントです。

            this.Load += Form_Load;
            this.MouseClick += MainForm_MouseClick;
        }

        /////////////////////////
        //ウィンドウイベント処理
        /////////////////////////

        //解説:オリジナルではウィンドウイベントメソッド、コントロールイベントメソッド、ユーザーメソッドが混在していたので、このような表示でまとめています。
        //WM_CREATE処理
        private void Form_Load(object sender, EventArgs e)
        {
            //Labelの設定
            label = new Label();
            label.Location = new Point(10, 10);
            label.Width = 400;    //Othello盤面幅(400)
            label.Text = pmpt;    //プロンプト表示
            this.Controls.Add(label);
            //RadioButtonsの設定
            radioButton1 = new RadioButton();
            radioButton1.Location = new Point(10, label.Height + 10);
            radioButton1.Text = "先攻(黒)";
            this.Controls.Add(radioButton1);
            radioButton2 = new RadioButton();
            radioButton2.Location = new Point(radioButton1.Width + 10, label.Height + 10);
            radioButton2.Text = "後攻(白)";
            this.Controls.Add(radioButton2);
            //Buttons(開始、パス、終了、戦績)の設定
            startButton = new Button();
            startButton.Location = new Point(ClientSize.Width - startButton.Width - 10, 20);
            startButton.Text = "開始";
            startButton.Anchor = (AnchorStyles.Top | AnchorStyles.Right);
            startButton.Click += startButton_Click;
            this.Controls.Add(startButton);
            passButton = new Button();
            passButton.Location = new Point(ClientSize.Width - passButton.Width - 10, startButton.Height + 30);
            passButton.Text = "パス";
            passButton.Anchor = (AnchorStyles.Top | AnchorStyles.Right);
            passButton.Click += passButton_Click;
            passButton.Enabled = false;
            this.Controls.Add(passButton);
            exitButton = new Button();
            exitButton.Location = new Point(ClientSize.Width - exitButton.Width - 10, startButton.Height + passButton.Height + 40);
            exitButton.Text = "終了";
            exitButton.Anchor = (AnchorStyles.Top | AnchorStyles.Right);
            exitButton.Click += exitButton_Click;
            this.Controls.Add(exitButton);
            datButton = new Button();
            datButton.Location = new Point(ClientSize.Width - datButton.Width - 10, this.ClientSize.Height - datButton.Height - 10);
            datButton.Text = "戦績";
            datButton.Anchor = (AnchorStyles.Bottom | AnchorStyles.Right);
            datButton.Click += datButton_Click;
            this.Controls.Add(datButton);
            //ビットマップimage2dispのGraphicsを取得
            gr_image = Graphics.FromImage(image2disp);
            gr_image.Clear(this.BackColor);

            //解説:ビットマップ(image2disp)のグラフィックハンドル(gr_image)でimage2dispの背景を描画します。
            this.Invalidate();    //フォームの再描画(解説:このメソッドでOnPaintイベントが発生します。)
            //ログファイルの読み込み(解説:Log.datファイルから↑の戦績データを読み込みます。)
            LogRead();
        }

        //WM_L/RBUTTONDOWN処理(解説:右でも左でもマウスボタンが押されると呼ばれます。)
        private void MainForm_MouseClick(object sender, MouseEventArgs e)
        {
            //座標の指定範囲内か否かを判別し、外なら何もしない
            Rectangle rect = new Rectangle(20, 60, 400, 400);
            if(!rect.Contains(new Point(e.X, e.Y)))
                return;
            //解説:オリジナルのコードは不完全な条件式を使ったので、オセロ盤の左外で条件が成就するのでこのように書き換えました。

/*            解説:以下は前回のコメントの引用です。

            int x = (e.X - 20) / 50, y=(e.Y-60)/50; if(x>7 || y<0) return;
            // 解説:マウス座標を外れていた場合は何もしない、という意味でしょう。

            //          マウス座標(e.Xとe.Y→共にフォームのクライアント領域の座標)

            //          から、オセロ盤の描画始点(20, 60)をオフセットし、オセロ盤の

            //          枠のサイズ(50 x 50)で(整数)除算したものをオセロ盤の枠座

            //          標(0 - 7, 0 - 7)として使うつもりだったんだと思います。
            //          しかし、クライアント座標がマイナス(例:e.X==10でe.Y==60)

            //          の場合、x(-0.2)が0となり、条件式("if(x(0)>7 || y(0)<0)")

            //          を充足してしまいます。また何故正確に

            //              "if((x < 0 || x > 7) || (y < 0 || y > 7))"

            //          としなかったのかも不明です。

*/

            //解説:このRectangleクラスのContainsメソッドはゲームなどで結構役に立ちそうです。

            else if(!radioButton1.Checked && !radioButton2.Checked)    //ゲームが開始されていない
            {
                //ユーザーインストラクションを追加(解説:ラジオボタンにチェックが入っていないとエラー表示します。)
                MessageBox.Show("ゲームは開始していません。\r\n先攻、後攻を指定して「開始」ボタンを押してください。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }
            else
            {
                //マウスポインター座標をオセロ盤座標(0 - 7 x 0 - 7)に変換(解説:↑の赤字部分を直接引数としています。)
                person((e.X - 20) / 50, (e.Y - 60) / 50);
            }
        }

        //WM_PAINT処理
        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);            //解説:このbase.~は標準のOnPaint処理を行います。
            displayBoard();              //グリッドと盤面をimage2dispに描画(解説:まだウインドウには描画されません。)
            e.Graphics.DrawImage(image2disp, 10, 50);    //ビットマップをPoint(10, 50)に表示(解説:ここで初めてオセロ盤が描画されます。)
        }

        //WM_CLOSE処理(解説:オリジナルになかったので追加しました。)
        protected override void OnFormClosing(FormClosingEventArgs e)
        {
            base.OnFormClosing(e);
            DialogResult dr = MessageBox.Show("終了しますか?", "確認", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
            if(dr == DialogResult.No)
            {
                e.Cancel = true;            //解説:キャンセル処理です。
            }
            else
            {
                LogWrite();                //勝敗データの更新(解説:戦績データをLog.datファイルに書き込みます。)
                //ビットマップimage2dispとGraphicsの開放(元は無かったので追加)

                //解説:コメント通りです。アンマネージドリソースの未開放はメモリーリークの原因となります。
                gr_image.Dispose();        //image由来のGraphicsを開放
                image2disp.Dispose();    //imageを開放
            }
        }

        ///////////////////////////
        //コントロールイベント処理
        ///////////////////////////
        //「開始」ボタン処理

        private void startButton_Click(object sender, EventArgs e)
        {
            if(!radioButton1.Checked && !radioButton2.Checked)    //ゲームが開始されていない
            {
                //ユーザーインストラクションを追加(解説:ラジオボタンがチェックされない限り始められません。)
                MessageBox.Show("先攻、後攻の指定がありません。\r\n指定してください。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }
            InitTable();                    //盤の初期化と石の初期配置(解説:Initialize()だと何をするのか不明だったので。)
            this.Invalidate();                //フォームの再描画(解説:これで中央に二つづつ石が置かれます。)
            label.Text = "";                //ラベルの初期表示(pmpt)を消す
            startButton.Enabled = false;    //「開始」ボタンを無効化する
            passButton.Enabled = true;        //「パス」ボタンを有効化する
            radioButton1.Enabled = false;    //「先攻」を無効化する
            radioButton2.Enabled = false;    //「後攻」を無効化する
            //解説:オリジナルで「ゲーム中か否か」を表すBooleanフィールド gameStartが使われていなかったので削除し、その役割をstartButton.Enabledに持たせ、同時にゲーム中に変更されては困る先攻、後攻ラジオボタンや押してほしくないボタンを無効にしました。

            //ユーザーインストラクションを追加(解説:一応、どうやって遊ぶのかの指示は必要ですよね?)
            string str =    "オセロゲームを開始します。\r\n" + 
                            "あなたが手を打つには、オセロ盤上で石を置く位置をマウスでクリックしてください。\r\n" + 
                            "あなたの手の後、PCは自動的に石を打ちます。\r\n" +
                            "あなたのご健闘を祈ります。";
            MessageBox.Show(str, "遊び方", MessageBoxButtons.OK, MessageBoxIcon.Information);
            if(radioButton2.Checked)        //後攻ならPCに先手を行わせる
                computer();
        }

        //パスボタン処理
        private void passButton_Click(object sender, EventArgs e)
        {
            //石を置ける場所があるかどうか判定し、置ける場合はパス解消。ない場合はパス。
            int diskColor = radioButton1.Checked ? 1 : 2;  //解説:オリジナルのTBを名称変更し、簡潔なコードにしました。
            if(canPlace(diskColor))
                MessageBox.Show("置ける場所がありますのでパスできません。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
            else
                computer();
        }

        //終了ボタン処理(解説:オリジナルに終了ボタンがなかったので追加しました。)
        private void exitButton_Click(object sender, EventArgs e)
        {
            Close();
        }

        //戦績ボタン処理(解説:これは機能追加の為に追加したボタンです。)
        private void datButton_Click(object sender, EventArgs e)
        {
            //ユーザーインストラクション
            string str =    "【今回の戦績】\r\n" + 
                            "ゲーム数\t\t:" + Games.ToString() + 
                            "\r\nプレーヤー勝数\t:" + Wins.ToString() + 
                            "\r\nPC勝数\t\t:" + Loses.ToString() + 
                            "\r\n引分数\t\t:" + (Games - Wins - Loses).ToString() + 
                            "\r\n\r\n【前回迄の累計】\r\n" + 
                            "ゲーム数\t\t:" + GT_Games.ToString() + 
                            "\r\nプレーヤー勝数\t:" + GT_Wins.ToString() + 
                            "\r\nPC勝数\t\t:" + GT_Loses.ToString() +
                            "\r\n引分数\t\t:" + (GT_Games - GT_Wins - GT_Loses).ToString();
            MessageBox.Show(str, "戦績の表示", MessageBoxButtons.OK, MessageBoxIcon.Information);
        }

アップしようとしたら、

「大きすぎる」

としかられたので、今回は此処迄。悪しからず。

 

前回

 

(1)PCのクリーニング(起動用USBメモリースティックを用意し、DESTROY.exeソフトを使用します。)

(2)段ボール梱包

(3)廃棄業者の選定(ReNetを使うつもりです)

(4)予約、引き取り

 

という手順の(1)は完了し、ついでに

2024年3月までのフルデータを喪失

し、過去ときっぱり決別する用意が出来ました。トホホ。(とはいえ、64bit機にある一部のデータは残っていますが。)

 

しかし、

 

デスクトップPCの廃棄で一番問題となるのは、

 

ダンボール収集

 

です。私も今回、近所の某〇ネコ運輸や、スーパーマーケット等うろついて、「物乞い」(NHKでは不適切用語のようですが)を行いました。

 

しかし、どれも小さい、小さすぎる!

 

という問題が生じました。

 

何せ、フルタワーPCなので、本体だけでも560mm x 490mm x 240mmというガタイに加え、(何処も単体では引き取ってくれない)液晶ディスプレー(500mm x 320mm x 150mm)を入れなければならないのです。

 

最終的に、行きつけのスーパーのマネージャーさんがとても親切で「どれも小さいですね。」というと、色々と探していただき、「無理されなくて結構ですから。」と言ったにも拘らず、商品の入っている大型段ボールを空けて、無償で譲っていただきました。

 

あー、よかった!

 

と思うには、しかし、まだ早く、この段ボール、ビミョーにサイズが「帯に短し、たすきに長し」なんです。

650mm(おつりがくる) x 460mm(ちょい足りない) x 420mm(十分にLCDディスプレーを入れておつりがくる)というサイズ。

 

どーしようかな?

 

と途方に暮れていたら、私の愛しい女房殿が(贈り物包みや祝い事包み等々)「梱包の神様」で、この週末にカッターとガムテープで何とかしてくれる、と言ってくれました。

 

嬉しいっ!

 

と思う反面、「役立たず」という語が脳裏から離れない金曜日でした。

 

古くからの訪問者の方はご存じですが、私が65歳で定年退職して、ボケ防止から昔開発したBCCForm and BCCSkeltonでのプログラミングを再開し、翌年サポート目的で本ブログを始めた当時、私は兄が作ったフルタワーデスクトップPC(32bit Core i7マシン)を使っていました。

【ご参考ー旧PCについて】

 

 

ところが最近旧PCのCMOS用バッテリーが衰弱したのか、F1キーを叩かないと起動しなくなり、最終的にバックアップを残して旧32bit機は処分することに決定しました。

 

が、

 

廃棄するとなるとやることがいっぱいありますが、過去↓のような経験があったので見通しは立ちます。

 

 

基本的に、

 

(1)PCのクリーニング(起動用USBメモリースティックを用意し、DESTROY.exeソフトを使用します。)

(2)段ボール梱包

(3)廃棄業者の選定(ReNetを使うつもりです)

(4)予約、引き取り

 

という手順になります。

 

昨日↑の(1)を行いました。前に起動用USBメモリースティックとDESTROY.exeを使用したので、これを使います。

先ずPCを(念のため)「設定→セキュリティ→回復→クリーニング付き初期化」を行い、初期化されたPCでメモリースティックからbootし、DESTROYを実行。なんと6時間以上かかりました。その時、「アッ」と気が付いたのですが、

 

悲劇っ!

 

バックアップ用HDをUSBでつないだまま初期化を実行してしまいました!そしてバックアップデータの入ったHDも初期化されていました!!!

 

まぁ、そろそろ(BCCForm and BCCSkeltonも私も)終活だし、

 

まっ、いっか!!!

 

ということで過去ときっぱり決別する用意が出来ました。

 

前回までの「Python初体験」の続きです。

前回はリスト(List)という単純な要素の配列を学びましたが、Pythonの配列には二つの対(つい)になった要素が並ぶ、辞書(Dictionary)というものがあります。
この場合対になった要素の最初のもの(添字に相当)を「キー(key」と言い、後のものを「値(value」と覚えましょう。

例題では簡単な英語と日本語の対になった(将に)辞書を取り上げます。

【サンプル1】
english_words = {"apple" : "りんご", "orange" : "みかん", "peach" : "もも"}
print(english_words)

はこうなります。

<出力>
{'apple': 'りんご', 'orange': 'みかん', 'peach': 'もも'}

キーによる検索も簡単に行えるのが良いですね。

【サンプル2】
print(english_words["apple"])

はこうなります。

<出力>

りんご

辞書にないキーを指定したときにはエラーになります。例えば"apple"の代わりに"banana"とすると、こうなります。

<出力>

KeyError                                  Traceback (most recent call last)
<ipython-input-4-699ba29fe30b> in <module>
----> 1 english_words['banana']


KeyError: 'banana'

次に辞書の要素の追加、変更、削除を見てゆきます。

【サンプル3】

dict_obj = {}
#空の辞書オブジェクトにキーと値を追加します。
dict_obj["python"] = "パイソン"
dict_obj["list"] = "リスト"
dict_obj["dictionary"] = "ディクショナリー"
dict_obj["tuple"] = "タプル"
#すべて印字
print()
#"dictionary"だけ印字
print(dict_obj["dictionary"])

#"dictionary"を「辞書」に変更します
dict_obj["dictionary"] = "辞書"
print(dict_obj["dictionary"])

#"distionary"を削除します
del dict_obj["dictionary"]
print(dict_obj["dictionary"])

<出力>
ディクショナリー
辞書
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-3-4da2418cf3d3> in <cell line: 18>()
     16 #"dictionary"を削除します
     17 del dict_obj["dictionary"]
---> 18 print(dict_obj["dictionary"])
     19 

KeyError: 'dictionary'


次に辞書でよく使う"in"演算子について学習します。

"in"演算子は辞書内の検索を行い、あればTrue、なければFalseを返します。↑の例を使って、実験します。

dict_obj = {}
#空の辞書オブジェクトにキーと値を追加します。
dict_obj["python"] = "パイソン"
dict_obj["list"] = "リスト"
dict_obj["dictionary"] = "ディクショナリー"
dict_obj["tuple"] = "タプル"
#すべて印字
print(dict_obj)
#"in"演算子を使った検索
key = input("辞書に登録されている語を入力してください")
if(key in dict_obj):
  print("辞書に" + key + "という単語がありました。")
else:
  print("辞書に" + key + "という単語が見当たりません。")

<出力>
{'python': 'パイソン', 'list': 'リスト', 'dictionary': 'ディクショナリー', 'tuple': 'タプル'}
辞書に登録されている語を入力してくださいlist
辞書にlistという単語がありました。
 または
辞書に登録されている語を入力してくださいkey
辞書にkeyという単語が見当たりません。

 

辞書は対の相関データの保管用に可也使えそうですね。では、また。

 

オセロゲームに焦点を合わせたので、細部に入る前にこれを概観することにしました。リンクを貼っていますが、よく分かる様にコードを載せ、コメントオリジナルのコメントは)します。

 

【Othello.txt】

// ■オセロゲームプログラム
//   ソースプログラムをコピー&ペーストしたあと、Form1を選択状態にした状態で、
//   イベントハンドラを指定できるようにして、
//   (プロパティウィンドウでイベントアイコンをクリック)

//   ① Form1のMuseClickイベントハンドラをForm1_MouseClickに設定してください。
//   ② Form1のLoadイベントハンドラをForm1_Loadに設定してください。
//   ③ timer1コントロールをFormに貼り付け、timer1_Tickイベントハンドラ
//     を指定してください。
//   ④ label1コントロールを貼り付けてください。
//   ⑤ radioButton1(Text="先攻"), radioButton2(Text="後"), button1(Text="開始"), 
//      button2(Text="パス")をForm1の上方に配置し, ボタンにはClickイベントハンドラ
//     を指定してください。
// 
解説:Formとロードイベントにマウスクリックイベント、TimerとTickイベント、Label、RadioButton x 2(Enabledの初期状態は不明)、Button(数不明)が必要とされることが分かりました。これらは自分で実装する必要があります。
//
 

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;

using System.Drawing;
using System.Linq;         // C# 2005以前の場合,Linqsを外すこと。

using System.Text;             //Encoding.GetEncodingを使用するのに必要(オリジナルのままでは必要なし。)

//  解説:Visual Studioによるコンパイルの為にいくつかのDLLを使用しますが、MSConpAssでは不要です。なお、以下は機能拡張のために追加しました。
using System.Windows.Forms;
using System.Reflection;    //Assemblyを使う為
using System.IO;               //StreamReader/Writerを使用するのに必要


namespace Othello
{
    public partial class Form1 : Form    //  解説:Formから派生させているので、ここでフォームが作れます。
    {
        public Image image = new Bitmap(1000, 1000);// 表示用ビットマップ
        public static Graphics g;                   // 表示用グラフィックス
        //  解説:↑が描画用のフィールドです。

        byte[,] dTab = new byte[8, 8];              // マス目のデータ(解説:後でわかりますが、これが「オセロ盤」です。
        int[,] Eval =                               // 評価用テーブル(解説:8 x 8のオセロ盤と同じだと分かります。)
            {{200,  6, 70, 30, 30, 70,  6,200},   // 解説:この配列[8, 8]を(y, x)座標で読むことが後で重要になります。

             {  6,  5,  7,  6,  6,  7,  5,  6},
              { 70,  7, 40, 30, 30, 40,  7, 70},
             { 30,  6, 30,  1,  1, 30,  6, 30},
             { 30,  6, 30,  1,  1, 30,  6, 30},
             { 70,  7, 40, 30, 30, 40,  7, 70},
             {  6,  5,  7,  6,  6,  7,  5,  6},
             {200,  6, 70, 30, 30, 70,  6,200}};   
        // 解説:この8 x 8の盤は「どこに打つと戦略的に有利か」という評価を表すものだということが後でわかります。
        int[,] procTable                            // チェック方向増分表
            = { { -1, -1 }, { -1,  0 }, { -1, 1 }, { 0, -1 }, 
                {  0,  1 }, {  1, -1 }, {  1, 0 }, { 1,  1 }};
        // 解説:この0 - 7の2次配列は「盤上の位置の周囲8方向を(y, x)の増減」で表していることが後でわかります。
        Boolean gameStart;                          //  ゲーム開始フラグ(解説:これがあとで問題となりす。)
        Brush bD = new SolidBrush(Color.DarkGreen); // ブラシ  暗い緑色
        Brush bW = new SolidBrush(Color.White);     //          白色
        Brush bB = new SolidBrush(Color.Black);     //          黒色  
        Brush bG = new SolidBrush(Color.Gray);      //          灰色
        Pen pB = new Pen(Color.Black, 1);           //  ペン    黒色
        Pen pW = new Pen(Color.White, 1);           //          白色
        Pen pG = new Pen(Color.DarkGray, 1);        //          暗い灰色
        Pen p1 = new Pen(Color.White, 2);           //          白色(太さ2)
        Pen p2 = new Pen(Color.White, 4);           //          白色(太さ4)
        // 解説:↑はオセロの石を描画する為に定義されています。

 

        public Form1()   // 解説:コンストラクターですね。
        {
            InitializeComponent();  //解説:これがVisual Studioのフォーム、リソース管理用メソッドです。

            //解説:MSCompAssではForm1_Loadでコントロール等を配置しようと思います。
        }
        protected override void OnPaint(PaintEventArgs e)   //  描画

        // 解説:OnPaintイベント処理ですが、ハンドラー処理でなく、オーバーライド処理をとっています。
        {
            base.OnPaint(e); display();

       // 解説:分かりづらいですが、先ず標準のOnPaintメソッド、次にオセロ盤を描画するdisplay()メソッドを実行します。
            e.Graphics.DrawImage(image, 10, 50);
       // 解説:Formのクライアントエリア(10, 50)の座標にimageビットマップを貼り付けます。
        }
        private void Form1_Load(object sender, EventArgs e) //  開始処理

       //解説:WM_CREATEに相当するLoadイベント処理です。
        {
            g = Graphics.FromImage(image); g.Clear(this.BackColor);
       // 解説:分かりづらいですが、imageというビットマップのグラフィックハンドルを取り、ウィンドウ背景色で初期化しています。本プログラムではこのimageビットマップに描画して、Formの描画処理(OnPaint)でそれを貼り付けます。
            initDtab(); this.Invalidate();
       // 解説:分かりづらいですが、オセロ盤の初期化(imageへの描画あり)をして、再描画(Invalidate)しています。
            label1.Text = "先攻,後攻を選択して,開始ボタンをクリックして下さい。";
        }
        private void wait()                                 //  待ち処理
        {
            timer1.Enabled = true; while (!timer1.Enabled) ;
       // 解説:分かりづらいですが、タイマーを有効にし(て、実際に有効になるまで待機し)ます。

// 解説:(補足)タイマーが1/1000秒で呼び出すtimer1_Tickイベントではtimer1.Enabledを偽(false)にするので、一回のイベントでタイマーは無効になります。ひょっとすると別途タイマーのTickインターバルをデフォルトの1/1000秒から変更しているのかもしれません。
        }
        private void button1_Click(object sender, EventArgs e)
        {                                                   //  開始ボタン処理
            Initialize(); if (radioButton2.Checked) computer();
       // 解説:分かりづらいですが、初期設定(gameStartフラグを偽、オセロ盤の初期化、再描画)を行っています。(初回はForm1_Loadの処理と被ります。)その後、人間が後手ならコンピューターの処理に移ります。

            label1.Text = ""; gameStart = true;

       // 解説:Form1_Loadの「先攻,後攻を選択して,開始ボタンをクリックして下さい。」を消し、gameStartフラグを偽にしています。

        }
        public void computer()              //  コンピュータ側判定処理
        {
            if (judgeEnd()) return ;        //  ゲーム終了時なら処理なし(解説:JudgeEndが勝敗判定メソッドのようです。
            label1.Text = ""; gameStart = false; int num;

       // 解説:またここで「先攻,後攻を選択して,開始ボタンをクリックして下さい。」を消し、gameStartフラグを偽にして、作業用整数変数を宣言しています。被ってますね。

            byte TB = 1; if (radioButton1.Checked) TB = 2;
       // 解説:分かりづらいですが、TBが「先攻(1)または後攻(2)」を意味し、初期値は「先攻」で、人間が「先攻(先攻ラジオボタンがチェック済)なら「後攻(2)」にしています。

           int MaxPr = -1, MaxI = -1, MaxJ = -1, MaxNum = 0;
       // 解説:(後で分かったのですが)MaxPr、MaxI、MaxJ、MaxNumは夫々(評価テーブルの)「最大値」、その位置のY座標とX座標、及び裏返すことが出来る石の数の最大値で、あり得ない値(偽)で初期化しています。

            for (int ii = 0; ii < 8; ii++) for (int jj = 0; jj < 8; jj++)

      // 解説:面白い書き方ですが、for文のネスト(i==y座標、j==x座標)ですね。(このように書いたり、i, jを多用することと言い、この作者はBasic派じゃなかったのかと思います。)
            {  // 解説:以下がオセロ盤のすべての枠(8 x 8)をネストしたforループでチェックする内容です。
                int numTotal = 0;  // 解説:以下のforループのnumの合計用です。
                for (int i = 0; i < 8; i++) //  セル(ii, jj)に置いたとき裏返す石の数カウント

                    // 解説:コメントはこう書いていますが、チェックする枠についてforループで8方向をチェックします。
                    if ((num = numberCount(ii, jj, TB, i)) > 0) numTotal += num;

                    // 解説:numberCountメソッドで石を幾つ裏返せるか(num)をチェックし、1つ以上だと加算して行きます。
                // 解説:ここで8方向チェックを抜け、評価処理に移ります。

                if (numTotal > 0 &&( MaxPr < 0 || MaxPr < Eval[ii, jj] ||
                   (MaxPr == Eval[ii, jj]&& MaxNum < numTotal)))    //  同一優先順位のとき
                {   //  裏返すことができる中で最優先順位,同一優先順位のとき裏返す石が多い方

                // 解説:コメントが分かりづらいですが、条件式は、

                //    ①8方向の裏返せる石の数の合計が1以上の場合で、且つ

                //     ②(ア)評価の最大値が0未満または評価の最大値がその枠の評価テーブルの値より低い、または

                //     (補足)「その枠の評価値」は必ず1以上(評価テーブル参照)なので赤字の条件式は不要ですね。

                //     ②(イ)その枠の評価値が評価の最大値と同じで且つ裏返せる石の数が多い場合

                //     要すれば、「今までの評価最大値を上回るか、同じでも裏返せる石の数が多い場合」ということです。

                    MaxPr = Eval[ii, jj];
                    MaxI = ii; MaxJ = jj; MaxNum = numTotal;

                // 解説:評価用のMaxPr、MaxI、MaxJ、MaxNumの値を優越する枠の値で更新します。

                }                   
            }
            if (MaxPr < 0) MessageBox.Show("打つ場所がありません。パスします");
                 // 解説:MaxProが-1のままであればすべての枠が対象外になるので。

            else           replacePlane(MaxI, MaxJ, TB);    //  石を裏返す

                 // 解説:そうでなければこの枠の石を裏返します。(メソッド名が「面を置き換える」で違和感があります。)

            this.Invalidate(); gameStart = true;
                // 解説:再描画して、gameSmtart(何に使っているのだか?)フラグを真にします

      }
        public void person(int ip, int jp)                  //  対戦者の処理
        {
            if (dTab[ip, jp] != 0) return;  //  既に石が置いてあるところは無視(解説:「無視」というよりも「何もしない」ですね。
            gameStart = false;         // 解説:gameStartフラグを偽にします。何故でしょう?
            byte TB = 2; if (radioButton1.Checked) TB = 1;   //  対戦者の石の種類

            // 解説:コンピューターの時と同様、初期値は「後攻(2)」で「先攻ラジオボタンがチェック済なら「先攻(1)」にします。

            if (canPlace(ip, jp, TB))       //  石を置くことができるとき,
            {                               //  裏返し処理
                replacePlane(ip, jp, TB); this.Invalidate(); wait();
                // 解説:分かりずらいですが、指定座標(ip-Y座標、jp-X座標)の石を裏返し、再描画し、タイマーを有効にします。(↑のwaitメソッドの通り、デフォルトでは1/1000秒の待機でしかない。)

                computer();                 //  コンピュータ側処理
            }
            else MessageBox.Show("そこには打てません。");
            if (judgeEnd())                 //  ゲーム終了の場合
            // 解説:以下に勝敗が付いた時の処理が書かれていますが、これはcomputerメソッドでも同じことであり、むしろjudgeEndメソッドに纏めるべきではないでしょうか?

            {
                int TB1 = 0; int TB2 = 0;   //  両者の石をカウントする(解説:TB1が「先攻」、TB2が「後攻」ですね。
                for (int ii = 0; ii < 8; ii++) for (int jj = 0; jj < 8; jj++)
                // 解説:ここでもfor文のネスト(i==y座標、j==x座標)をこのように書いています。
                    {
                        if (dTab[ii, jj] == 1) TB1++;
                        else if (dTab[ii, jj] == 2) TB2++;
                    }
                if (TB1 == TB2) MessageBox.Show("引き分けです。");  //  石の数で勝負判定
                else if ((TB1 > TB2 && radioButton1.Checked) || (TB2 > TB1 && radioButton2.Checked))
                     MessageBox.Show("貴方の勝ちです。黒 = " + TB1 + ", 白 = " + TB2);
                else MessageBox.Show("私の勝ちです。黒 = " + TB1 + ", 白 = " + TB2);
                // 解説:実際に動かすと、「人間」と「PC」のいずれもが「貴方」か「私」と言っても問題がないので、一瞬「ん?」となります。正確には「先攻の勝ち、後攻の勝ち」という表示にすべきでしょう。
                label1.Text = "先攻,後攻を選択して,開始ボタンをクリックして下さい。";
                // 解説:ここで消していた(同じ)メッセージを復活させます。
             }
            gameStart = true; this.Invalidate();
            // 解説:GameStartフラグを真にして再描画していますが、このフラグで何をするのでしょうか?
        }
        private void Form1_MouseClick(object sender, MouseEventArgs e)  //  画面をクリックしたら

        // 解説:マウスクリックのイベント処理です。イベント処理メソッドがユーザーメソッドと混ざっています。

        {                                                               //  対戦者の処理
            int x = (e.X - 20) / 50, y=(e.Y-60)/50; if(x>7 || y<0) return;
            // 解説:マウス座標を外れていた場合は何もしない、という意味でしょう。

            //          マウス座標(e.Xとe.Y→共にフォームのクライアント領域の座標)

            //          から、オセロ盤の描画始点(20, 60)をオフセットし、オセロ盤の

            //          枠のサイズ(50 x 50)で(整数)除算したものをオセロ盤の枠座

            //          標(0 - 7, 0 - 7)として使うつもりだったんだと思います。
            //          しかし、クライアント座標がマイナス(例:e.X==10でe.Y==60)

            //          の場合、x(-0.2)が0となり、条件式("if(x(0)>7 || y(0)<0)")

            //          を充足してしまいます。また何故正確に

            //              "if((x < 0 || x > 7) || (y < 0 || y > 7))"

            //          としなかったのかも不明です。

            person(y,x);
            // 解説:「画面をクリックしたら対戦者の処理」はその通りなのですが、

            //          ゲームを開始する前からこのイベント処理が有効となってしまって

            //          います。ひょっとしてgameStartフラグはこの為にあったのかもし

            //          れませんが、不明です。
         }

        private void button2_Click(object sender, EventArgs e)  //  「パス」ボタンが押されたら

        // 解説:ボタンのイベント処理です。イベント処理メソッドがユーザーメソッドと混ざっています。

        {   // 石を置ける場所があるかどうか判定し、置ける場合はパス解消。ない場合にパス。
            int TB = 2; if (radioButton1.Checked) TB = 1;

            // 解説:コンピューターの時と同様、初期値は「後攻(2)」で「先攻ラジオボタンがチェック済なら「先攻(1)」にします。

            if (canPlace(TB)) MessageBox.Show("置ける場所がありますのでパスできません");
            else computer();
        }
       private void Initialize()    //  初期設定
       {
           gameStart = false; initDtab(); this.Invalidate();
            // 解説:gameStartフラグを偽にし、オセロ盤を初期化し、再描画する初期設定処理です。

       }
       private void initDtab()      //  石の配置初期設定
        {
            for (int i = 0; i < 8; i++) 
                for (int j = 0; j < 8; j++) dTab[i, j] = 0;
            dTab[3, 3] = dTab[4, 4] = 1; dTab[4, 3] = dTab[3, 4] = 2;

            // 解説:オセロ盤を初期化する処理です。黒と白の石を交差して二つづつ置いています。

        }
       public int numberCount(int ii, int jj, int TB, int i)   

       // 解説:オセロ盤上の(ii(y)、jj(z))座標にある空の枠に、先手(TB==1)、後手(TB==2)

       //    が石を置いた場合、いくつの敵石を裏返せるか、というメソッド。

       //    引数はy座標、x座標、先手(1)・後手(2)、方向(0 - 7)。

       //    戻り値はおけない場合-1、おける場合は裏返せる石の合計数(含0)。

       {    //  石を置いたとき裏返す石をカウント。なければ -1 を返却する。
           if (dTab[ii,jj] != 0) return -1; //  既に石が配置されていれば置けない。

           // 解説:指定座標に石が置かれている場合、「置けない場合(-1)」を返す。

           int RB = 3 - TB, ip = procTable[i, 0], jp = procTable[i, 1]; //  チェック方向設定

           // 解説:RBはTBの敵(1または2)を表す。ip、jpは方向iに関わるx,y座標の増減差分

           int IIP = ii + ip, JJP = jj + jp;                       // 1 個先の配列添字設定。

           // 解説:IIP(y)、JJP(x)は指定座標の方向iで差分差だけ隣接する座標

           if (IIP < 0 || IIP > 7 || JJP < 0 || JJP > 7) return -1;// 1 個先が(解説:オセロ盤の)範囲外なら置けない。
           if (dTab[IIP, JJP] != RB) return -1;                    // 1 個先が対戦者の石でない(解説:自分の石か空)とき

           int numStone = 1;                                       // 置けない(自分の石/石がない)。  // 解説:これは上の行のコメントの続きであろうと思われる。

           // 解説:numStoneは裏返せる石の数を意味する。1を代入しているのは隣接するIIP、JJPの石がRBだから。

           while (dTab[IIP, JJP] == RB)                            // 対戦者の石が続くときカウント。

           // 解説:既にカウントしたIIP、JJPからループさせる。

           {
               IIP = IIP + ip; JJP = JJP + jp;

               // 解説:IIP、JJPに更に差分を加え、次にチェックする隣接枠にする。

               if (IIP < 0 || IIP > 7 || JJP < 0 || JJP > 7) return -1;

               // 解説:オセロ盤外であれば自石で挟めず、「置けない場合(-1)」を返す。

               numStone++;

               // 解説:おける場合は裏返せる石の数を増やす。

           }
           if (dTab[IIP, JJP] == TB) return numStone--;            // 対戦者の石が途切れ箇所が
           else return -1;                                         //  自分の石のとき石を置ける。

           // 解説:指定した「空枠」に隣接する「敵石」の数を数え、「自石」にたどり着けた場合、

           //   既に1をカウントした始点から更にカウントしている為、-1して調整する。

           //   【例示】

           //   (指定座標-空)

           //   (指定座標-空)●(敵石●があれば、その座標を記録し、カウントを1)

           //   (指定座標-空)●(ループはその座標からなので、カウントが+1)

           //   (指定座標-空)●〇(自石に辿り着いた時、カウントは2になっている。)

           //   自石に辿り着けなければ(空の場合)、「置けない場合(-1)」を返す。

       }                                                           

       // 解説:「置けない場合(-1)」が定義され、computerメソッドで使われるが、この

       //   メソッドが使われる3か所の条件式がすべて"> 0"なので、戻り値を0にしても

       //   差し支えない。

       public Boolean canPlace(int ii, int jj, int TB)             // セル(ii, jj)に石を置くこと
       {                                           // ができるかを判定。
           for (int i = 0; i < 8; i++)
                if (numberCount(ii, jj, TB, i) > 0) return true;

           // 解説:オセロ盤の(ii、jj)座標の周囲8方向を、先手(TB==1)または後手

           //    (TB==2)について、おける場所がないかチェックし、一つでもあれ

           //    ば真、なければ偽を返します。

           return false;
       }
       public Boolean canPlace(int TB)                             // 石を置く場所があるかを判定。
       {
           for (int ii = 0; ii < 8; ii++) for (int jj = 0; jj < 8; jj++)        
                  if (canPlace(ii, jj, TB))  return true;

           // 解説:先手(TB==1)または後手(TB==2)について、オセロ盤上でおける

           //    i場所がないか、ii(y)、jj(x)のネストfor文にすべての枠をチェックし、

           //    一つでもあれば真、なければ偽を返します。

          return false;
       }
       public Boolean judgeEnd()                                    // 終了判定。
       {
           for (int TB = 1; TB < 3; TB++) if (canPlace(TB)) return false;
           // 解説:先手(TB==1)、後手(TB==2)共における場所がないかチェックし、

           //    一つでもあれば偽、なければ真を返します。

           return true;
       }

       public void replacePlane(int ii, int jj, byte TB)     // 石の裏返し。
       {   // 解説:「面の置き換え」は違和感があります。FlipStoneではないでしょうか? 
            byte [, ]Temp=new byte[8, 8];// 石の状態を作業領域に移して
            for(int i=0;i<8;i++)for(int j=0;j<8;j++)Temp[i, j]=dTab[i, j];
            // 解説:「石の状態を作業領域に移」す、とはオセロ盤をTempにコピーすることです。
            for (int i = 0; i < 8; i++)  // 作業領域を判定して(解説:正確には「オセロ盤を判定して」です。
            {       
                int num = numberCount(ii, jj, TB, i),ip = ii, jp = jj;
                int di = procTable[i, 0], dj = procTable[i, 1];
                if (num > 0)             // 石を裏返す(解説:「(作業用盤の)石を裏返す」です。
                    for (int j = 0; j <= num; j++, ip += di, jp += dj) Temp[ip, jp] = TB;
            }    
            // 解説:本来のオセロ盤のデータから裏返す(データを書き換える)石(のある枠)

            //    のデータを読み、データ書き込みはTempを使っています。これは処理に

            //    よりオセロ盤のデータが書き換わるので、それで誤った判定をしないよう

            //    にするためです。
            for(int i=0;i<8;i++)for(int j=0;j<8;j++)dTab[i, j]=Temp[i, j];

            // 解説:最後に書き換えたTempのデータでオセロ盤を更新します。

        }
       private void dspStone(int i, int j, Brush b, Pen p, float R)// 石の表示
       {   // 解説:disp(lay)Stone"の略ですね。
           float X = j * 50 + 20, X1 = X + 1;
           float Y = i * 50 + 20, Y1 = Y + 1;
           g.FillEllipse(bG, X1, Y1, R, R); //  石に厚さがあるような形で表示
           g.FillEllipse(b , X, Y, R, R);
           g.DrawEllipse(p , X, Y, R, R);
           // 解説:見事な描画処理ですね。

        }
        public void display()    // 解説:”display what?"と訊き返したくなりますね。
        {
            g.FillRectangle(bD, 10, 10, 400, 400); // マス目の表示
            for (float X = 60; X < 390; X += 50) g.DrawLine(p1, X, 10, X, 410);
            for (float Y = 60; Y < 390; Y += 50) g.DrawLine(p1, 10, Y, 410, Y);
            g.DrawRectangle(p2, 10, 10, 400, 400);

            // 解説:先ず背景を塗り、縦横の線を描き、外枠線を描いています。

            for (int i = 0; i < 8; i++)
                for (int j = 0; j < 8; j++) // 白い石は大きく見えるので若干小さく表示
                    if      (dTab[i, j] == 1) dspStone(i, j, bB, pG, 30);
                    else if (dTab[i, j] == 2) dspStone(i, j, bW, pB, 29);

            // 解説:各桝に1(先攻-黒)または2(後攻-白)のデータがあれば、石を描画します。

        }
        private void timer1_Tick(object sender, EventArgs e)
        {
            timer1.Enabled = false;

            // 解説:Timerが1/1000秒毎に呼ぶメソッドで、呼ばれたらすぐにタイマーを無効にしています。

        }
    }

}

 

いかがでしょうか?

 

取り敢えず、プログラムを動かせるように、

 

    public class App
    {
        [STAThread]
        public static void Main()
        {
            OthelloForm of = new OthelloForm();
            Application.Run(of);
        }
    }

 

を追加し、コンストラクター

 

        //コンストラクター
        public OthelloForm()  //解説:クラス名は
Form1からOthelloForm名称変更しました。
        {
            //プログラムアイコンをフォームにつける
            Assembly myOwn = Assembly.GetEntryAssembly();
            this.Icon = Icon.ExtractAssociatedIcon(myOwn.Location);
            this.Size = new Size(560, 528);
            this.MinimumSize = new Size(530, 508);
            this.Text = "Othello";
            this.Load += Form_Load;
            this.MouseClick += MainForm_MouseClick;
        }

 

Form1_LoadForm_Load名称変更しています)でコントロールを適当に配置しました。なお、変数名をより分かり易くするよう変更したものは赤字にしています。

 

            //Timerの設定
            
GameTimer = new Timer();    //解説:旧名称timer1
            GameTimer.Tick += GameTimer_Tick;
            //Labelの設定
            
label = new Label();    //解説:旧名称label1
            label.Location = new Point(10, 10);
            label.Width = 400;    //Othello盤面幅(400)
            label.Text = "先攻、後攻を選択して、開始ボタンをクリックして下さい。";
            this.Controls.Add(label);
            //RadioButtonsの設定
            radioButton1 = new RadioButton();
            radioButton1.Location = new Point(10, label.Height + 10);
            radioButton1.Text = "先攻";
            this.Controls.Add(radioButton1);
            radioButton2 = new RadioButton();
            radioButton2.Location = new Point(radioButton1.Width + 10, label.Height + 10);
            radioButton2.Text = "後攻";
            this.Controls.Add(radioButton2);
            //Buttonsの設定
            
startButton = new Button();    //解説:旧名称button1
            startButton.Location = new Point(ClientSize.Width - startButton.Width - 10, 20);
            startButton.Text = "開始";
            startButton.Anchor = (AnchorStyles.Top | AnchorStyles.Right);
            startButton.Click += startButton_Click;
            this.Controls.Add(startButton);
            
passButton = new Button();    //解説:旧名称button2
            passButton.Location = new Point(ClientSize.Width - passButton.Width - 10, startButton.Height + 30);
            passButton.Text = "パス";
            passButton.Anchor = (AnchorStyles.Top | AnchorStyles.Right);
            passButton.Click += passButton_Click;
            this.Controls.Add(passButton);
            
exitButton = new Button();    //解説:旧名称button3
            exitButton.Location = new Point(ClientSize.Width - exitButton.Width - 10, startButton.Height + passButton.Height + 40);
            exitButton.Text = "終了";
            exitButton.Anchor = (AnchorStyles.Top | AnchorStyles.Right);
            exitButton.Click += exitButton_Click;
            this.Controls.Add(exitButton);
            //ビットマップ
disp_imageのGraphicsを取得(解説:旧名称image
            gr_image = Graphics.FromImage(disp_image);    //解説:旧名称g
            gr_image.Clear(this.BackColor);
            this.Invalidate();    //フォームの再描画
            //盤の初期化と石の初期配置
          
 InitTable();    //解説:旧名称initDtab
 

等の変更を加え、一応オリジナルで動くようにはしました。

 

が、

 

↑の解説:で書いたようにタイマーをつける意味がない、使われないフラグ(gameStart)がある、開始ボタンを押さないでもオセロ盤をクリックするとperson()メソッドが動いてしまう等の不明点があり、この段階で、

 

えいやっ...

 

自分流に処理の変更やコードをいじくらせていただきました。

 

最終形

のコードはまたご披露いたします。