Base Project

AIを組み込む前のベースプロジェクトです。

前田稔(Maeda Minoru)の超初心者のプログラム入門

プログラムの説明

  1. CSForm.cs に全ソースをまとめます。
    // Osero Base Project
    using System;
    using System.Drawing;
    using System.Windows.Forms;
    using System.Collections;
    using System.Diagnostics;
    
    public class MyForm : Form
    {   // 黒番の局面に設定すること
        int[,] m_tt = new int[8, 8]
        {{  0,  0,  0,  0,  0,  0,  0,  0 },
         {  0,  0,  0,  0,  0,  0,  0,  0 },
         {  0,  0,  0,  0,  0,  0,  0,  0 },
         {  0,  0,  0,  1, -1,  0,  0,  0 },
         {  0,  0,  0, -1,  1,  0,  0,  0 },
         {  0,  0,  0,  0,  0,  0,  0,  0 },
         {  0,  0,  0,  0,  0,  0,  0,  0 },
         {  0,  0,  0,  0,  0,  0,  0,  0 }};
    
        int[,] m_vt = new int[8, 8]     // 1-1, 2-2 は別途
        {{  0,  -6,  4,  2,  2,  4,  -6,  0 },
         { -6,   0, -2, -1, -1, -2,   0, -6 },
         {  4,  -2,  0,  0,  0,  0,  -2,  4 },
         {  2,  -1,  0,  0,  0,  0,  -1,  2 },
         {  2,  -1,  0,  0,  0,  0,  -1,  2 },
         {  4,  -2,  0,  0,  0,  0,  -2,  4 },
         { -6,   0, -2, -1, -1, -2,   0, -6 },
         {  0,  -6,  4,  2,  2,  4,  -6,  0 }};
    
        int[,] m_t = new int[8, 8];     // オセロ盤
        int[,] t00 = new int[4, 2]      //0-0 座標テーブル(Y,X)
        {{ 0,0 }, { 0,7 }, { 7,7 }, { 7,0 }};
        int[,] t11 = new int[4, 2]      //1-1 座標テーブル(Y,X)
        {{ 1,1 }, { 1,6 }, { 6,6 }, { 6,1 }};
        int[,] dt =  new int[4, 2]      //上辺→,右辺↓,下辺←,左辺↑
        {{ 0, 1 }, { 1, 0 }, { 0, -1 }, { -1, 0 }};
        int[,] dt5 = new int[4, 2]      //左上,右上,右下,左下
        {{ 1, 1 }, { 1, -1 }, { -1, -1 }, { -1, 1 }};
    
        int[]   m_dat = new int[70];    // 棋譜の記録(Y*10+X)
        int     m_num;                  // m_dat の Index
        int     m_my, m_you;            // マシンの駒, プレイヤーの駒
        int     m_teban= 0;             // 手番(9:終局)
        int     m_pos;                  // Final 関数の座標値
        int     x,y;
        Random  rand = new Random();
    
        // Constructor
        public MyForm()
        {
            BackColor = SystemColors.AppWorkspace;
            Width  = 560;
            Height = 600;
            Paint += new PaintEventHandler(MyHandler);
            MouseDown += new MouseEventHandler(OnMyMouseDown);
            m_t = (int[,])m_tt.Clone();
        }
    
        // オセロ盤の描画
        private void MyHandler(object sender, PaintEventArgs e)
        {
            Graphics g = e.Graphics;
            g.DrawImage(new Bitmap("c:\\data\\test\\ban.gif"), new PointF(10F, 10F));
            for (y = 0; y < 8; y++)
            {   for (x = 0; x < 8; x++)
                {   if (m_t[y, x] == 1) g.DrawImage(new Bitmap("c:\\data\\test\\koma_b.gif"), new PointF(x * 61 + 28, y * 61 + 24));
                    if (m_t[y, x] == -1) g.DrawImage(new Bitmap("c:\\data\\test\\koma_w.gif"), new PointF(x * 61 + 28, y * 61 + 24));
                }
            }
            m_teban= Pass(m_teban);
        }
    
        // マウスのクリック
        private void OnMyMouseDown(object sender, MouseEventArgs e)
        {
            int x,y;
            if (e.Button == MouseButtons.Right)
            {
                m_t = (int[,])m_tt.Clone();
                if (m_num > 0) m_num--; // 一手戻す
                if (m_dat[m_num] == 99) m_num--;
                for (int i = 0; i < m_num; i++)
                {
                    if (i % 2 == 0) Reverse(1, m_dat[i], m_t);
                    else Reverse(-1, m_dat[i], m_t);
                }
                m_teban = Set_Teban(m_num);
                Invalidate();
                return;
            }
            if (m_teban==9)
            {   Pass(9);
                return;
            }
            x = (e.X - 28) / 61;
            y = (e.Y - 24) / 61;
            if (m_teban==-1)
            {   Think(0, m_teban, m_t);
                y= m_pos/10;
                x= m_pos%10;
            }
            if (Reverse(m_teban, x, y, m_t)==false)
            {   string str = "Play Error  C:" + m_teban + "[" + y + "," + x + "]";
                MessageBox.Show(str);
                return;
            }
            m_dat[m_num] = y*10+x;
            m_num++;
            m_teban = Set_Teban(m_num);
            Invalidate();
        }
    
        // m_num を参照して手番を設定
        int Set_Teban(int num)
        {
            if (m_num % 2 == 0)
            {
                m_my = 1;           // コンピュータ
                m_you = -1;         // プレイヤー
                return 1;
            }
            m_my = -1;
            m_you = 1;
            return -1;
        }
    
        // パス, 終局を調べる
        int Pass(int c)
        {
            int         nc;
            ArrayList   array;
            if (c == 0)
            {
                m_num = 0;
                m_my = 1;
                m_you = -1;
                return 1;           // 黒番で Start
            }
            if (c!=9)               // パスを調べる
            {   array = Search(c, m_t);
                if (array.Count>0)  return c;
            }
            nc= 0-c;                // 反手番で調べる
            array = Search(nc, m_t);
            if (array.Count>0)
            {   if (c==1)
                {   MessageBox.Show("黒はパスです");  }
                else
                {   MessageBox.Show("白はパスです");  }
                m_dat[m_num] = 99;
                m_num++;
                m_my = 0 - m_my;    // 手番を反転
                m_you = 0 - m_you;
                return nc;
            }
            string str= "★Game Over  黒:" + Count(1, m_t) + "  白:" + Count(-1, m_t);
            MessageBox.Show(str);
            return 9;       // 終局
        }
    
        // t[yp,xp] に打てるか調べる
        bool  Check(int c, int xp, int yp, int[,] t)
        {
            int     i,j,x,y;
            int     nc = 0-c;
            if (yp>7 || xp>7 || yp<0 || xp<0 || c==0 || t[yp,xp]!=0)   return false;
            for(i=-1; i<2; i++)
                for(j=-1; j<2; j++)
                {   y = yp + i;     // 上下左右を調べる
                    x = xp + j;
                    if (y<8 && x<8 && y>=0 && x>=0 && t[y,x]==nc)
                    {   for(; y<8 && x<8 && y>=0 && x>=0 && t[y,x]==nc; y+=i,x+=j);
                        if (y<8 && x<8 && y>=0 && x>=0 && t[y,x]==c)    return true;
                    }
                }
            return false;   //置くことができない
        }
        bool  Check(int c, int pos, int[,] t)
        {
            int x,y;
            y= pos/10;
            x= pos%10;
            return Check(c,x,y,t);
        }
    
        // t[y,x] に駒を置いて、挟んだ駒を裏返す
        bool Reverse(int c, int xp, int yp, int[,] t)
        {
            int     i, j, x, y;
            bool    sw = false;
            int     nc = 0-c;
            if (yp > 7 || xp > 7 || yp < 0 || xp < 0 || c == 0 || t[yp, xp] != 0) return false;
            for (i = -1; i < 2; i++)
                for (j = -1; j < 2; j++)
                {
                    y = yp + i;
                    x = xp + j;
                    if (y < 8 && x < 8 && y >= 0 && x >= 0 && t[y, x] == nc)
                    {
                        for (; y < 8 && x < 8 && y >= 0 && x >= 0 && t[y, x] == nc; y += i, x += j) ;
                        if (y < 8 && x < 8 && y >= 0 && x >= 0 && t[y, x] == c)
                        {
                            for (; y != yp || x != xp; y -= i, x -= j) t[y, x] = c;
                            sw = true;
                        }
                    }
                }
            if (sw == false) return false;
            t[yp, xp] = c;
            return true;
        }
        bool Reverse(int c, int pos, int[,] t)
        {
            int x,y;
            y= pos/10;
            x= pos%10;
            return Reverse(c,x,y,t);
        }
    
        // 打てる手をサーチ
        ArrayList Search(int c, int[,] t)
        {
            int     x,y,pos;
            ArrayList   array= new ArrayList();
            for(y=0; y<8; y++)
                for(x=0; x<8; x++)
                {   if (Check(c,x,y,t))
                    {   pos= y*10 + x;
                        array.Add(pos);
                    }
                }
            return array;
        }
    
        // 駒のカウント
        int Count(int c, int[,] t)
        {
            int cnt;
            cnt = 0;
            for (int y = 0; y < 8; y++)
            {
                for (int x = 0; x < 8; x++)
                    if (t[y, x] == c) cnt++;
            }
            return cnt;
        }
    
        // t[8][8] から辺を一列取り出す
        ArrayList GetLine(int no, int[,] t)
        {
            ArrayList   array= new ArrayList();
            int xp,yp,k;
            yp= t00[no,0];
            xp= t00[no,1];
            for(k=0; k<8; k++)
            {   array.Add(t[yp,xp]);
                yp+= dt[no,0];
                xp+= dt[no,1];
            }
            return array;
        }
    
        // コーナ(黒or白)から展開する駒をカウント
        int Corner(int no, int[,] t)
        {   int     xp, yp, cnt;
            int     koma;
            yp = t00[no, 0];
            xp = t00[no, 1];
            if (t[yp, xp] == 0) return 0;
            koma = t[yp, xp];
            //コーナ(no)のt[8,8]をt55にコピー
            int[,] t55 = new int[5, 5];
            for(y=0, yp=t00[no, 0]; y<5; y++, yp+=dt5[no, 0])
            {   for(x=0, xp=t00[no, 1]; x<5; x++, xp+=dt5[no, 1])
                {   t55[y,x] = t[yp,xp]; }
            }
            //コーナの確定した石数を求める
            cnt = 0;
            xp = 4;
            for(y=0; y<4 && t55[y,0]==koma; y++)
            {   for(x=0; x<xp; x++)
                {   if ((t55[y,x]!=koma) ||
                       (y>0 && x>0 && t55[y-1, x+1]!=koma && t55[y+1, x-1]!=koma))
                    {   xp=x;  break; }
                    cnt++;
                }
            }
            return cnt;
        }
    
        // 端の駒(黒or白)の立場で続く駒を評価
        int EvEdge(ArrayList s, int bcnt, int wcnt, int scnt)
        {
            if (scnt<3) return 0;
            if ((int)s[0]==1 && (int)s[1]==-1 && (int)s[2]==0 ) return wcnt*20; // [●○△・・・]
            if ((int)s[0]==-1 && (int)s[1]==1 && (int)s[2]==0 ) return bcnt*20; // [○●△・・・]
            return 0;
        }
    
        // 1-2 に駒(黒or白)があるとき、C の手番(立場)でパターンを調べる
        int EvPattern(ArrayList s, int c, int sc)
        {
            int you = 0-c;
            if (sc == 4)
            {   // [△○●・・・・] 黒(c)の手番で隅を取れる
                if ((int)s[1]==you && (int)s[2]==c) return 50;
                return 20;  // 辺が全て黒になる[△●○・・・]
            }
            if ((int)s[1]==c)   // 1-2 に黒 [△●・・・]
            {
                if ((int)s[2]==you && (int)s[3]==c) return -60;   // [△●○●・・・]
                if ((int)s[2]==you && (int)s[3]==0 && (int)s[4]==you)   return -40;  // [△●○  ○・・・]
                return 0;
            }
            // 1-2 に白[△○・・・]
            if ((int)s[2]==c && (int)s[3]==you) return 60;      // [△○●○・・・] 
            if ((int)s[2]==0 && (int)s[3]==you) return 20;      // [△○※○・・・]
            if ((int)s[2]==0 && (int)s[3]==0 && (int)s[4]==c)   return 30; //[△○※・●・・・]
            if ((int)s[2]==0 && (int)s[3]==c && (int)s[4]==you) return 30; //[△○※●○・・・]
            return 0;
        }
    
        // C の手番(立場)で取り出した1列の辺を評価
        int EvLine(ArrayList s, int c)
        {
            ArrayList   ws= new ArrayList();
            int mcnt, ycnt, scnt, rt, val, wk;
            int you= 0-c;
            // 黒石, 白石, 列の数をカウント
            mcnt = ycnt = 0;
            scnt = 1;
            wk = (int)s[0];
            foreach(int DAT in s)
            {
                if (DAT==c)     mcnt++;
                if (DAT==you)   ycnt++;
                if (DAT != wk)
                {
                    scnt++;
                    wk = DAT;
                }
            }
            if (mcnt+ycnt==8)   return (mcnt-ycnt)*20;
            if (scnt<3) return 0;
            //連続する(○,●)を一つに詰める
            wk = 9;
            foreach(int DAT in s)
            {
                if (DAT==0 || DAT!=wk)
                {
                    ws.Add(DAT);
                    wk = DAT;
                }
            }
            rt= ws.Count-1;
            // 端に黒(白)が置かれている
            if ((int)ws[0] != 0)
            {
                val = EvEdge(ws, mcnt, ycnt, scnt);
                if ((int)ws[0]==c)  return val;
                else    return(0-val);
            }
            if ((int)ws[rt]!=0)
            {
                ws.Reverse();
                val = EvEdge(ws, mcnt, ycnt, scnt);
                if ((int)ws[0]==c)  return val;
                else    return(0-val);
            }
            // [△・・・△]
            if (scnt==3)    return (mcnt-ycnt)*10;
            val= 0;
            // 1-2 に駒  [△*・・・△] 
            if ((int)ws[1]!=0 || (int)ws[rt-1]!=0)
            {
                if ((int)ws[1]!=0)
                {
                    if ((int)ws[1]==c)  val-= EvPattern(ws,c,scnt);
                    else    val+= EvPattern(ws,c,scnt);
                }
                if ((int)ws[rt-1]!=0)
                {
                    ws.Reverse();
                    if ((int)ws[1]==c)  val-= EvPattern(ws,c,scnt);
                    else    val+= EvPattern(ws,c,scnt);
                }
            }
            // 両端は[△△・・・△△]
            if (scnt==3)    return (mcnt-ycnt)*10;  //[△○△][△●△] 
            if (scnt==4)    return (mcnt+ycnt)*10;  //[△○●△]
            return val;
        }
    
        // 現在の局面を C で評価
        int Eval(int c, int[,] wt)
        {   ArrayList   line;
            int     x,y,val;
            int     nc= 0-c;
            bool    flg;
            val= 0;
            for(int i=0; i<4; i++)
            {   y= t00[i,0];
                x= t00[i,1];
                if (wt[y,x]!=0)     // Corner() 関数の評価
                {   if (wt[y,x]==c) val+= Corner(i,wt)*20+30;
                    else    val-= Corner(i,wt)*20+30;
                }
                else
                {
                    flg = false;
                    if (Check(c,x,y,wt))    // 隅を取れる
                    {   val += 60;
                        flg = true;            
                    }
                    if (flg==false)
                    {   y = t11[i, 0];
                        x = t11[i, 1];
                        if (wt[y,x]==nc)    val += 20;
                        if (wt[y,x]==c)     val -= 20;
                    }
                    line = GetLine(i, wt);
                    int wk= EvLine(line,c);
                    val += wk;
                }
            }
            for(int i=0; i<8; i++)
            {   for(int j=0; j<8; j++)
                {   if (wt[i,j]==c)     val+= m_vt[i,j];
                    if (wt[i,j]==nc)    val-= m_vt[i,j];
                }
            }
            return val;
        }
    
        //★ t[8,8] の局面を c の手番で再帰コールする
        //  終盤12手は読み切り(それ以外は3手先の白番で評価)
        int Think(int lev, int c, int[,] t)
        {
            ArrayList array;
            int[,] w_t;
            int best, pos, ans;
            int nc;
            if (lev > 24)
            {
                MessageBox.Show("Think Call  Over Level");
                return (Count(m_my, t) - Count(m_you, t));
            }
            nc = 0 - c;
            array = Search(c, t);
            if (array.Count == 0)
            {
                array = Search(nc, t);
                if (array.Count == 0)               //☆終局(両方がパス)
                {   return (Count(m_my, t) - Count(m_you, t));  }
                return (Think(lev+1, nc, t));       //☆反転して再起コール
            }
            // 白番・3手先で評価
            if (m_num<48 && lev>2 && c==m_my)   return Eval(c,t);
            pos = 99;
            best = 60000;
            if (c==m_my)    best= -60000;
            foreach (int DAT in array)
            {
                w_t = (int[,])t.Clone();
                Reverse(c, DAT, w_t);
                ans= Think(lev+1,nc,w_t);
                if (best<ans && c==m_my)
                {
                    best = ans;
                    pos = DAT;
                }
                if (best>ans && c==m_you)
                {
                    best = ans;
                    pos = DAT;
                }
                if (best==ans && rand.Next(2)==0)
                {
                    best = ans;
                    pos = DAT;
                }
            }
            array.Clear();
            if (lev == 0) m_pos = pos;      // lev=0 のとき座標を設定
            return best;
        }
    
        //☆ Debug 関数
        void T_Log(int sz, int[,] t)
        {
            string str = "";
            for (int y = 0; y < sz; y++)
            {
                for (int x = 0; x < sz; x++)
                {
                    if (t[y, x] == 0) str = str + ".";
                    if (t[y, x] == 1) str = str + "X";
                    if (t[y, x] == -1) str = str + "O";
                }
                str = str + "\n";
            }
            Debug.Write(str);
        }
        void Ary_Log(ArrayList ary)
        {
            string str= "ArrayList:[";
            foreach (int DAT in ary)
            {   str += DAT + "  ";  }
            str += "]\n";
            Debug.Write(str);
        }
    }
    
    class osero
    {
        public static void Main()
        {
            MyForm mf = new MyForm();
            Application.Run(mf);
        }
    }
    
  2. AI(人工知能)を組み込む前のベースプロジェクトです。
    プレイヤーの先手(黒番)でオセロゲームをします。
    プレイヤーは駒を置く座標をポイントして左クリックして下さい。
    コンピュータ(白番)の時は、盤上で左クリックすると Think() 関数が呼ばれて駒が打たれます。
    前回の「コンピュータがプレイする」に比べてプログラムが改善されています。
  3. マウスの右クリックで手を戻します。(待った)
    m_dat[70] がプレイの記録で m_num がその Index です。
    座標データは「Y座標*10+X座標」の値を int 型で記録します。
    パスのときは99で記録します。
    続けて右クリックを繰り返すと、最初の局面まで戻ることが出来ます。
        if (e.Button == MouseButtons.Right)
        {
            m_t = (int[,])m_tt.Clone();
            if (m_num > 0) m_num--; // 一手戻す
            if (m_dat[m_num] == 99) m_num--;
            for (int i = 0; i < m_num; i++)
            {
                if (i % 2 == 0) Reverse(1, m_dat[i], m_t);
                else Reverse(-1, m_dat[i], m_t);
            }
            m_teban = Set_Teban(m_num);
            Invalidate();
            return;
        }
    
  4. マウスの左クリックでプレイします。
    コンピュータの手番のときは Think() で打つ手を決定します。
            if (m_teban==9)
            {   Pass(9);
                return;
            }
            x = (e.X - 28) / 61;
            y = (e.Y - 24) / 61;
            if (m_teban==-1)
            {   Think(0, m_teban, m_t);
                y= m_pos/10;
                x= m_pos%10;
            }
            if (Reverse(m_teban, x, y, m_t)==false)
            {   string str = "Play Error  C:" + m_teban + "[" + y + "," + x + "]";
                MessageBox.Show(str);
                return;
            }
    
  5. コンピュータが白番で打つ手を考える Think() 関数です。
    終盤12手は読み切りで、この程度であれば待ち時間は気になりません。
    終盤以外は3手先の白番で Eval() 関数で局面を評価します。
    Think() 関数が選んだ手は m_pos に設定されます。
        //★ t[8,8] の局面を c の手番で再帰コールする
        //  終盤12手は読み切り(それ以外は3手先の白番で評価)
        int Think(int lev, int c, int[,] t)
        {
            ArrayList array;
            int[,] w_t;
            int best, pos, ans;
            int nc;
            if (lev > 24)
            {
                MessageBox.Show("Think Call  Over Level");
                return (Count(m_my, t) - Count(m_you, t));
            }
            nc = 0 - c;
            array = Search(c, t);
            if (array.Count == 0)
            {
                array = Search(nc, t);
                if (array.Count == 0)               //☆終局(両方がパス)
                {   return (Count(m_my, t) - Count(m_you, t));  }
                return (Think(lev+1, nc, t));       //☆反転して再起コール
            }
            // 白番・3手先で評価
            if (m_num<48 && lev>2 && c==m_my)   return Eval(c,t);
                 ・
                 ・
                 ・
    
  6. C#でオセロ・ゲームを作成した理由はAI(人工知能 artificial insemination)に少しでも触れられたらと思ったからです。
    チェスでコンピュータがプロに勝ったのは1996年で、囲碁のような複雑なゲームではコンピュータは勝てないと言われてきました。
    所が最近(2017年)では、将棋や囲碁でプロが敵わないぐらいにコンピュータが強くなってきました。
    囲碁で使われているAIは「ディープラーニング」と呼ばれる深層学習機能です。
    囲碁のAIは無理でも、オセロ・ゲームぐらいならと始めたのですが? (^_^;)

[Next Chapter ↓] Learning
[Previous Chapter ↑] コンピュータがプレイする

超初心者のプログラム入門(C# Frame Work)