Osero Game

AIを組み込んだオセロゲームです。

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

プログラムの説明

  1. CSForm.cs に全ソースをまとめます。
    // Osero AI Version-1
    using System;
    using System.Drawing;
    using System.Windows.Forms;
    using System.Diagnostics;
    using System.Collections;   //ArrayList
    using System.IO;    // for File, StreamReader
    using System.Text;  // for Encoding
    
    class Cell
    {   public ArrayList AL = null;
        public int n;       // N 手目
        public int p;       // 座標
        public int v=0;     // 点数
    }
    
    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();
        Cell    m_top = new Cell();
        int     m_black, m_white, m_v;
        string  file_name = "C:\\tmp\\osero.txt";
        StreamReader reader;
    
        // Constructor
        public MyForm()
        {
            BackColor = SystemColors.AppWorkspace;
            Width  = 560;
            Height = 600;
            Paint += new PaintEventHandler(MyHandler);
            MouseDown += new MouseEventHandler(OnMyMouseDown);
            Set_Cell();
            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);
                Cell_Srh(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;
            }
            if (c!=9)   Put();
            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 > 30)
            {
                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;
        }
    
        // Cell List を検索して手を決める(未登録のときは Think 関数で決定)
        int Cell_Srh(int c, int[,] t)
        {   Cell    p,q;
            int     i,k,v,best=0;
            p=q= m_top;
            for(i=0; i<m_num; i++)
            {
                if (p.AL == null) return Think(0,c,t);
                for (k=0; k<p.AL.Count; k++)
                {   q= (Cell)p.AL[k];
                    if (q.p==m_dat[i])  break;
                }
                if (k>=p.AL.Count)  return Think(0,c,t);    // 未登録
                p= q;
            }
            v = -100000;                    // 先手番のときは最大値
            if (c==-1)  v= 100000;          // 後手番のときは最小値
            if (p.AL == null) return Think(0,c,t);
            for (k = 0; k < p.AL.Count; k++)
            {   q = (Cell)p.AL[k];
                if (c==-1 && v>q.v)         // 後手番
                {   v= q.v;
                    best= k;
                }
                if (c==1 && v<q.v)          // 先手番
                {   v= q.v;
                    best= k;
                }
            }
            if (c==-1 && v>0)   return Think(0,c,t);    // 登録は不利な局面のみ
            m_pos = ((Cell)p.AL[best]).p;
            return m_pos;
        }
    
        // osero.txt を Cell に登録
        void Set_Cell()
        {
            int num;
            if (!File.Exists(file_name))    return;     // 存在確認
            reader = new StreamReader(file_name,Encoding.GetEncoding("utf-8"));
            for(num = 0; num<10000; num++)
            {   if (Input()==false) break;
                m_v = m_black-m_white;
                if (m_v>0)  m_v = 1;
                if (m_v<0)  m_v = -1;
                AddData(m_v, m_num, m_dat);
            }
            Debug.Write("対局データが終わりました 件数:" + num + "\n");
            reader.Close();
        }
    
        // 対局データを osero.txt に書き出す
        void Put()
        {
            int     bc, wc;
            string  str;
            StreamWriter writer = new StreamWriter(file_name,true,Encoding.GetEncoding("utf-8"));
            bc = Count(1, m_t);
            wc = Count(-1, m_t);
            str = bc.ToString() + "," + wc.ToString() + ",";
            for(int j=0; j<m_num; j++)  str += m_dat[j] + ",";
            writer.WriteLine(str);
            writer.Close();
        }
    
        // 1件の対局データを osero.txt に入力
        bool Input()
        {
            char[]  delimiter = { ',' };    // 区切り符号
            string  str;
            string[]    swk;
            str = reader.ReadLine();
            if (str==null || str.Length<8)
            {   return false;  }
            swk= str.Split(delimiter);      // カンマで分割
            m_num = swk.GetLength(0)-3;
            m_black = int.Parse(swk[0]);
            m_white = int.Parse(swk[1]);
            for(int i=0; i<m_num; i++)
            {   m_dat[i] = int.Parse(swk[i+2]);  }
            return true;
        }
    
        // dat[num] の一局を top(Cell) に登録
        void AddData(int val, int num, int[] dat)
        {   Cell pt = m_top;
            int  k;
            for(int i=0; i<num; i++)
            {   k = AddCell(pt, i, dat[i]);
                pt = (Cell)pt.AL[k];
                pt.v += val;
            }
        }
    
        // pt の ArrayList に i, p のセルを追加する
        int AddCell(Cell pt, int i, int p)
        {   Cell wk,wk2;
            int  j;
            wk = new Cell();
            wk.n = i;
            wk.p = p;
            if (pt.AL==null)    pt.AL= new ArrayList();
            for (j=0; j<pt.AL.Count; j++)
            {   wk2 = (Cell)pt.AL[j];
                if (wk2.p==p) break;
            }
            if (j>=pt.AL.Count) pt.AL.Add(wk);
            return j;
        }
    }
    
    class osero
    {
        public static void Main()
        {
            MyForm mf = new MyForm();
            Application.Run(mf);
        }
    }
    
  2. AI(人工知能)を組み込んだオセロゲームです。
    プレイヤーの先手(黒番)でオセロゲームをします。
    プレイヤーは駒を置く座標をポイントして左クリックして下さい。
    コンピュータ(白番)の時は、盤上で左クリックすると Cell_Srh() 関数が呼ばれて駒が打たれます。
    "c:\\data\\test\\" のフォルダーに ban.gif, koma_b.gif, koma_w.gif の画像ファイルを格納しておいて下さい。
    また "C:\\tmp\\" のフォルダーに "osero.txt" を格納しておいて下さい。
  3. "osero.txt" は対局データを記録したファイルで、ゲームを開始する前に Cell 構造体(ArrayList)にロードします。
    "osero.txt" は TEXT FILE で次のような形式で記録されています。
    23,41,53,52,35,54,42,32,51,50,22,25,15,23,24,6,41,45,26,37,17,40,4,36,55,56,65,46,57,14,62,73,71,63,64,75,74,72,12,2,5,3,31,13,21,10,47,27,16,7,20,30,66,67,60,70,61,11,77,76,1,0,
    25,39,53,54,25,42,52,32,22,24,23,51,35,12,60,45,2,15,55,31,56,14,20,30,40,65,36,37,46,50,27,17,75,47,57,67,26,41,3,5,64,13,63,21,11,73,66,62,4,16,74,61,10,0,7,1,70,77,76,6,71,72,
    ・・・  以下同様の形式で対局データが続きます  ・・・      
    
    最初の二個は終局したときの黒の駒数と白の駒数です。
    盤上の全てのマスに駒が打たれたときは加えると64個ですが、双方ともに打てないマスが生じると、その分少なくなります。
    3個目からは駒が打たれた座標で Y*10+X の値で表します。
    例えば 53 は Y座標=5 で X座標=3 のマスを表します(8*8の盤の座標を0~7で指定)。
    パスの場合は 99 を格納するので、奇数番目は黒の手番で偶数番目は白の手番です。
    "osero.txt" を圧縮形式で提供します。
    まだまだデータ件数が少ないのですが、数万件集まれば敵わなくなるかも知れません。 (^_^;)
    osero.txt を Down Load
  4. 対局データを記録する Cell 構造体(Class)です。
    ディープラーニング(deep learning)は三目並べゲームの方が先輩です。
    詳細は 三目並べの学習機能 を参照して下さい。
    class Cell
    {   public ArrayList AL = null;
        public int n;       // N 手目
        public int p;       // 座標
        public int v=0;     // 点数
    }
    
  5. 対局データを記録する Cell 構造体(m_top)の定義です。
    file_name がデータファイルの定義で、StreamReader で読み込みます。
        Cell    m_top = new Cell();
        int     m_black, m_white, m_v;
        string  file_name = "C:\\tmp\\osero.txt";
        StreamReader reader;
    
  6. Set_Cell() で osero.txt を Cell 構造体(m_top)に登録します。
    Input() 関数で1局分のデータを読み込んで AddData() で登録します。
        void Set_Cell()
        {
            int num;
            if (!File.Exists(file_name))    return;     // 存在確認
            reader = new StreamReader(file_name,Encoding.GetEncoding("utf-8"));
            for(num = 0; num<10000; num++)
            {   if (Input()==false) break;
                m_v = m_black-m_white;
                if (m_v>0)  m_v = 1;
                if (m_v<0)  m_v = -1;
                AddData(m_v, m_num, m_dat);
            }
            Debug.Write("対局データが終わりました 件数:" + num + "\n");
            reader.Close();
        }
    
  7. Input() 関数で1局分の対局データを m_dat[] に入力します。
        bool Input()
        {
            char[]  delimiter = { ',' };    // 区切り符号
            string  str;
            string[]    swk;
            str = reader.ReadLine();
            if (str==null || str.Length<8)
            {   return false;  }
            swk= str.Split(delimiter);      // カンマで分割
            m_num = swk.GetLength(0)-3;
            m_black = int.Parse(swk[0]);
            m_white = int.Parse(swk[1]);
            for(int i=0; i<m_num; i++)
            {   m_dat[i] = int.Parse(swk[i+2]);  }
            return true;
        }
    
  8. AddData() 関数で m_dat[] の対局データを Cell に登録します。
        void AddData(int val, int num, int[] dat)
        {   Cell pt = m_top;
            int  k;
            for(int i=0; i<num; i++)
            {   k = AddCell(pt, i, dat[i]);
                pt = (Cell)pt.AL[k];
                pt.v += val;
            }
        }
    
  9. AddCell() 関数で1件のプレイをセルに追加します。
    (i 手目を p の座標の打ちます)
        int AddCell(Cell pt, int i, int p)
        {   Cell wk,wk2;
            int  j;
            wk = new Cell();
            wk.n = i;
            wk.p = p;
            if (pt.AL==null)    pt.AL= new ArrayList();
            for (j=0; j<pt.AL.Count; j++)
            {   wk2 = (Cell)pt.AL[j];
                if (wk2.p==p) break;
            }
            if (j>=pt.AL.Count) pt.AL.Add(wk);
            return j;
        }
    
  10. 対局データを osero.txt に追加する Put() 関数です。
    対局を重ねる毎に強くなります。
        void Put()
        {
            int     bc, wc;
            string  str;
            StreamWriter writer = new StreamWriter(file_name,true,Encoding.GetEncoding("utf-8"));
            bc = Count(1, m_t);
            wc = Count(-1, m_t);
            str = bc.ToString() + "," + wc.ToString() + ",";
            for(int j=0; j<m_num; j++)  str += m_dat[j] + ",";
            writer.WriteLine(str);
            writer.Close();
        }
    
  11. コンピュータは Cell List を検索して有利な局面を選びます。
    Cell List に登録されていないときは Think 関数で決定します。
    対局データが少ないときは Think 関数が呼ばれる率が高くなります。
        int Cell_Srh(int c, int[,] t)
        {   Cell    p,q;
            int     i,k,v,best=0;
            p=q= m_top;
            for(i=0; i<m_num; i++)
            {
                if (p.AL == null) return Think(0,c,t);
                for (k=0; k<p.AL.Count; k++)
                {   q= (Cell)p.AL[k];
                    if (q.p==m_dat[i])  break;
                }
                if (k>=p.AL.Count)  return Think(0,c,t);    // 未登録
                p= q;
            }
            v = -100000;                    // 先手番のときは最大値
            if (c==-1)  v= 100000;          // 後手番のときは最小値
            if (p.AL == null) return Think(0,c,t);
            for (k = 0; k < p.AL.Count; k++)
            {   q = (Cell)p.AL[k];
                if (c==-1 && v>q.v)         // 後手番
                {   v= q.v;
                    best= k;
                }
                if (c==1 && v<q.v)          // 先手番
                {   v= q.v;
                    best= k;
                }
            }
            if (c==-1 && v>0)   return Think(0,c,t);    // 登録は不利な局面のみ
            m_pos = ((Cell)p.AL[best]).p;
            return m_pos;
        }
    
  12. C#でオセロ・ゲームを作成した理由はAI(人工知能 artificial insemination)に少しでも触れられたらと思ったからです。
    チェスでコンピュータがプロに勝ったのは1996年で、囲碁のような複雑なゲームではコンピュータは勝てないと言われてきました。
    所が最近(2017年)では、将棋や囲碁でプロが敵わないぐらいにコンピュータが強くなってきました。
    囲碁で使われているAIは「ディープラーニング」と呼ばれる深層学習機能です。
    囲碁のAIは無理でも、オセロ・ゲームぐらいならと始めたのですが? (^_^;)
    当初数万件以上のデータが集まらなければ効果が期待できないと思っていたのですが、そうでも無いようです。
    このプログラムのAI機能は、過去に対局したデータを記録して最も勝率の高い局面を選択します。
    局面の組み合わせは膨大でコンピュータと言えども全てを網羅することは出来ません。
    対局データが千件以下では、序盤の数手で機能するのが関の山でしょう。
    それでも強くなったと感じるのは私だけでしょうか (^_^;)
    対局データはプログラムの思考関数に依存します。
    例えば囲碁では「実利重視の戦略や厚み重視の戦略」があり、勝率の高い局面も違ってくると思われます。
    AI機能は補助であり、最終的には思考関数が物を言うのでしょうね。

[Previous Chapter ↑] Learnning

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