オセロゲームβ版

オセロゲームβ版の全ソースコードです。

前田稔の超初心者のプログラム入門

プログラムの作成

  1. メモ帳などでタイプして osero.java の名前で保存して下さい。
    //★ オセロゲームβ版    前田 稔
    import java.applet.*;
    import java.awt.*;
    import javax.swing.*;
    import java.awt.event.*;
    import java.util.*;
    import java.io.*;
    import java.io.IOException;
    import com.sun.j3d.utils.applet.MainFrame;
    
    public class osero extends JApplet implements MouseListener
    {   oseroobj    obj;
        byte        ply= 1;     // 1:black  -1:white
        // 0:初期  1:ManPlay  2:CompPlay  3:GameOver  4:Exit
        int         state= 0;
        String      msg;
    
        // main Method
        public static void main(String[] args)
        {   new MainFrame(new osero(), 640, 480);  }
    
        // Initialize
        public void init()
        {   obj = new oseroobj();
            addMouseListener(this);
            setBackground(new Color(200,200,200));
            msg= "後手のときは、番の外をクリックします";
            state= 0;
        }
    
        // Paint
        public void paint(Graphics g)
        {
            Dimension  size = getSize();
            g.setColor(getBackground());
            g.fillRect(0, 0, size.width, size.height);
            g.setFont(new Font("Helvetica",Font.PLAIN,32));
            g.setColor(Color.black);
            g.drawString(msg,20,460);
            obj.View(g);
        }
    
        // Mouse Click
        public void mouseClicked(MouseEvent e)
        {   int     rc;
    
            if (state>3)    System.exit(1);
            if (e.getX()<20 || e.getX()>600)
            {   if (e.getX()<20)
                {   obj.LC--;
                    if (obj.LC<1)   obj.LC= 0;
                }
                else
                {   obj.LC++;
                    if (obj.LG[obj.LC].LogC==0) obj.LC--;
                }
                obj.Matta();
                if (obj.LC>0)   ply= (byte)(0-obj.LG[obj.LC-1].LogC);
                else    ply= 1;
                state= 1;
                repaint();
                return;
            }
    
            switch(state)
            {   case 0:     //※ゲーム開始
                    rc= obj.Check_Play(ply,e.getX(),e.getY());
                    state= 2;               //2:CompPlay
                    msg= "わたしの手番です";
                    if (rc==0)  ply= -1;    //Comp:白番
                    else        ply= 1;     //Comp:黒番
                    break;
                case 1:     //※ManPlay
                    rc= obj.Check_Play(ply,e.getX(),e.getY());
                    switch(rc)
                    {   case 0:
                            ply= (byte)(0-ply);
                            state= 2;       //2:CompPlay
                            msg= "わたしの手番です";
                            break;
                        case 1:
                            ply= (byte)(0-ply);
                            state= 2;       //2:CompPlay
                            msg= "あなたはパスで、わたしの番です";
                            break;
                        case 2:
                            msg= "エラーです";
                            break;
                        case 3:
                            state= 3;       //3:GameOver
                            msg= "ゲームの結果を確認します";
                            break;
                    }
                    break;
                case 2:     //※CompPlay
                    rc= obj.Comp_Play(ply);
                    switch(rc)
                    {   case 0:
                            ply= (byte)(0-ply);
                            state= 1;       //1:ManPlay
                            msg= "あなたの手番です";
                            break;
                        case 1:
                            ply= (byte)(0-ply);
                            state= 1;       //1:ManPlay
                            msg= "私はパスで、あなたの番です";
                            break;
                        case 2:
                            msg= "プログラムのエラーです";
                            break;
                        case 3:
                            msg= "ゲームの結果を確認します";
                            state= 3;       //3:GameOver
                            break;
                    }
                    break;
                case 3:     //※結果の確認
                    msg= "ゲームの結果です  黒=" + obj.B_C + "  白=" + obj.W_C;
                    state= 4;
                    break;
            }
            repaint();
        }
    
        public void mousePressed(MouseEvent e){}
        public void mouseReleased(MouseEvent e){}
        public void mouseEntered(MouseEvent e){}
        public void mouseExited(MouseEvent e){}
    }
    
    //★ オセロ Object Class
    class oseroobj extends JApplet
    {   int     WSIZE= 50;              //格子のサイズ
        int     KSIZE= (WSIZE*8/10);    //駒の直径
        byte    T[][] = new byte[8][8]; //テーブル
        byte    ST[][] = new byte[8][8];//作業用テーブル
        byte    T55[][]= new byte[5][5];//コーナー評価用
        Point   S[] = new Point[30];    //手の検索
        Point   WS[] = new Point[30];   //検索作業領域(一時的に使用する領域)
        Random  Rand;                   //乱数の発生
        int     Xpos,Ypos;              //プレイの位置
        int     Countdown;              //残り手数
        byte    my,you;                 //コンピュータと人間の駒
        int     B_C,W_C;                //駒の数
        int     x,y,i,j;                //作業領域(一時的に使用する領域)
        log     LG[] = new log[62];     //ログを記録
        int     LC;                     //ログのカウント
        int     LV1 = 48;               //LV1超 初期レベル
        int     LV2 = 30;               //LV2超 中期レベル
        int     LV3 = 12;               //LV3超 終期レベル、LV3以下 読切
    
        int  VT[][]=        //OSERO ウエイトテーブル
        { { 150,-20, 10,  8,   8, 10,-20,150 },
          { -20,-64, -5, -4,  -4, -5,-64,-20 },
          {  10, -5,  2,  0,   0,  2, -5, 10 },
          {   8, -4, -1,  0,   0, -1, -4,  8 },
          {   8, -4, -1,  0,   0, -1, -4,  8 },
          {  10, -5,  2, -1,  -1,  2, -5, 10 },
          { -20,-64, -5, -4,  -4, -5,-64,-20 },
          { 150,-20, 10,  8,   8, 10,-20,150 },
        };
    
        // 0-0 座標(左上,右上,右下,左下)
        int     T00Y[]= { 0, 0, 7, 7 };
        int     T00X[]= { 0, 7, 7, 0 };
        int     T11Y[]= { 1, 1, 6, 6 };
        int     T11X[]= { 1, 6, 6, 1 };
        int     TD[][]=     //上辺→,右辺↓,下辺←,左辺↑, 左辺↓,上辺←,右辺↑, 下辺→
        { { 0,1 }, { 1,0 }, { 0,-1 }, { -1,0 }, { 1,0 }, { 0,-1 }, { -1,0 }, { 0,1 } };
    
        // Constructor
        oseroobj()
        {
            T[4][3]=T[3][4]= 1;         //黒
            T[3][3]=T[4][4]= -1;        //白
            Countdown= 60;              //残り手数
            Rand = new Random();        //乱数の初期化
            for(i=0; i<30; i++)
            {   S[i]= new Point();
                WS[i]= new Point();
            }
            for(i=0; i<62; i++) LG[i]= new log();
            LC= 0;
        }
    
        // コンピュータがプレイする
        int  Comp_Play(byte c)
        {   Point   bst = new Point();      //手の検索
            byte    str[] = new byte[8];    //辺の解析用
            int     n,val,v;
    
            my= c;                      //コンピュータの駒
            you= (byte)(0-my);          //人間の駒
            n= Search(my,T,S);
            if (n==0)
            {   if (Search(you,T,S)>0)  return 1;   //パス
                B_C= Count((byte)1,T);
                W_C= Count((byte)-1,T);
                return 3;
            }
    
            // LV1 以上はウエイトテーブルと打てる箇所で評価
            if (Countdown>LV1)
            {   bst= S[0];
                val= -60000;
                for(int ii=0; ii<n; ii++)
                {   v= VT[S[ii].y][S[ii].x];
                    v+= PlyCnt(n,S[ii]);
                    if (val<v)
                    {   bst= S[ii];
                        val= v;
                    }
                }
                if (Reverse(c,bst,T)==false)    return 2;   //プログラムのエラー
                Countdown--;
                LG[LC].Set(Countdown,c,bst.x,bst.y);
                LC++;
                return 0;
            }
    
            // 残り LV1 を切ると Think() を呼ぶ
            for(y=0; y<8; y++)
                for(x=0; x<8; x++)
                    ST[y][x]= T[y][x];  //ST[8][8] ← T[8][8]
    
            Think(0,my);                //コンピュータがプレイ
            if (Reverse(c,Xpos,Ypos,T)==false)  return 2;
            Countdown--;
            LG[LC].Set(Countdown,c,Xpos,Ypos);
            LC++;
            return 0;
        }
    
        //★★ コンピュータが考える再起関数
        //  lev=        再起のレベル
        //  c=          手番(黒=1, 白=-1)
        //  ST[8][8]=   評価する局面
        //  Return=     局面の評価値
        //  Xpos,Ypos=  最善手を格納する
        int  Think(int lev, byte c)
        {   byte    tt[][] = new byte[8][8];
            Point   s[] = new Point[30];    //手の検索
            Point   Best = new Point();     //レベル毎の最善手
            int     val,v;                  //評価値
            int     sn,n;
    
            if (lev>24)
            {   System.out.println("再起レベルが 24 を超えた");
                return -1;
            }
            for(i=0; i<30; i++) s[i]= new Point();
            sn= Search(c,ST,s);                     //打てる場所を検索
            if (sn==0)                              //パスのとき
            {   if (Search((byte)-c,ST,s)==0)       //終局(両方がパス)
                {
                    W_C= Count(my,ST);
                    B_C= Count(you,ST);
                    return((W_C-B_C)*20);           //COM の得点で評価
                }
                return(Think(lev+1,(byte)-c));      //★再起コール★
            }
    
            //★再起を終了して盤面を評価
            if ((Countdown>LV2 && lev>=2 && c==my)||
                (Countdown<=LV2 && Countdown>LV3 && lev>=4 && c==my))
            {   return(Eval()); }
    
            for(y=0; y<8; y++)
                for(x=0; x<8; x++)
                    tt[y][x]= ST[y][x];             //tt[8][8] ← ST[8][8]
            Best.x= 8;
            Best.y= 8;
            // c==my の時は最大値を c==you の時は最小値を求める
            val= 60000;
            if (c==my)  val= -60000;
            for(n=0; n<sn; n++)
            {   for(y=0; y<8; y++)
                    for(x=0; x<8; x++)
                        ST[y][x]= tt[y][x];         //ST[8][8] ← tt[8][8]
                Reverse(c,s[n],ST);
                v= Think(lev+1,(byte)-c);           //★再起コール★
                if (val<v && (c==my))
                {   val=v;
                    Best= s[n];
                }
                else if (val>v && (c==you))
                {   val=v;
                    Best= s[n];
                }
                else if (val==v && (Rand.nextInt(2)==0))
                {   Best= s[n]; }
            }
            // lev=0 のときの Best が最善手
            if (lev==0)
            {   Xpos= Best.x;
                Ypos= Best.y;
            }
            return(val);
        }
    
        //★ my の手番で盤面(ST[8][8])を評価するメソッド
        // 盤面(ST[8][8])全体を評価する
        int  Eval()
        {   int     i,sn;
            int     val,w;
            byte    str[]= new byte[8];
    
            val= 0;
            val+= PlayCnt();    //打てる場所の数で評価
    
            for(i=0; i<8; i++)
            {   Getstr(i,str,ST);
                if (i<4)
                {   //コーナの判定
                    if (str[0]!=0)  val+= Corner(T00X[i],T00Y[i]);
                    else
                    {   //手番でコーナに置けるか調べる
                        if (Check(my,T00X[i],T00Y[i],ST))   val+= 220;
                        else    if (ST[T11Y[i]][T11X[i]]==my)   val-= 100;
                        else    if (ST[T11Y[i]][T11X[i]]==you)  val+= 100;
                    }
                    if (str[0]==0 && str[7]==0) val+= EvStr4(str);
                }
                //辺を取り出して評価
                w= EvStr8(str);
                val+= w;
            }
            return(val);
        }
    
        // コーナの抽出と評価
        int  Corner(int xp, int yp)
        {   byte    koma;
            int     cnt;
            int     val;
    
            if (ST[yp][xp]==0)  return 0;
            y= yp;
            for(i=0; i<5; i++)
            {   x= xp;
                for(j=0; j<5; j++)
                {   T55[i][j]= ST[y][x];
                    if (xp!=0)  x--;
                    else        x++;
                }
                if (yp!=0)  y--;
                else        y++;
            }
    
            //コーナの確定した石数を求める
            koma= T55[0][0];
            cnt= 0;
            x= 4;
            for(i=0; i<4 && T55[i][0]==koma; i++)
                for(j=0; j<x; j++)
                {   if ((T55[i][j]!=koma) ||
                        (i>0 && j>0 && T55[i-1][j+1]!=koma && T55[i+1][j-1]!=koma))
                    {   x= j;  break;  }
                    cnt++;
                }
            val= cnt*15+100;
            if (koma==you)   val= 0-val;
            return val;
        }
    
        // 辺を一列取り出して評価(4列分)
        int  EvStr4(byte s[])
        {   int     lp,rp,i,mc,yc,sc;
    
            if (s[0]!=0 || s[7]!=0) return 0;   //コーナーに石(Cornerで評価)
            for(lp=1; lp<7 && s[lp]==0; lp++);  //先頭の空白を除外
            if (lp>=7)  return(0);              //全て空白
            for(rp=6; rp>0 && s[rp]==0; rp--);  //後部の空白を除外
            mc=yc=sc= 0;
            for(i=lp; i<=rp; i++)
            {   if (s[i]==my)   mc++;           //my Count
                if (s[i]==you)  yc++;           //you Count
                if (s[i-1]!=s[i])   sc++;       //String Count
            }
            if (sc==2 && mc>0 && yc>0)  return((mc+yc)*6);
            if (yc==0 && sc>1)  return(mc*6-10);
            if (mc==0 && sc>1)  return(yc*6-10);
            return((mc-yc)*6);
        }
    
        // コーナーに関係する辺のパターンを調べる(反転して8通り)
        int  EvStr8(byte s[])
        {   byte    w[]= new byte[10];
    
            if (s[0]!=0)    return 0;   //コーナーに石(Cornerで評価)
            //連続する(○,●)を一つに詰める
            for(i=j=0; j<8; i++)
            {   w[i]= s[j];
                for(j++; j<8 && w[i]!=0 && w[i]==s[j]; j++);
            }
            w[i]= 2;        //終了マーク
            if (w[1]==my && w[2]==you)
                if (w[3]==2 || w[3]==my || (w[3]==0 && w[4]==you))  return(-220);
            if (w[1]==you && w[2]==0)
            {   if (w[3]==0 && w[4]==my)    return(100);
                if (w[3]==my && w[4]==you)  return(100);
                if (w[3]==you)
                    if (w[4]==0 || w[4]==2 || (w[4]==my && w[5]==you))  return(100);
            }
            return(0);
        }
    
        // my の打てる場所(n)とプレイ後の you の打てる場所数で評価
        int  PlyCnt(int n, Point p)
        {   int     w;
            for(y=0; y<8; y++)
                for(x=0; x<8; x++)
                    ST[y][x]= T[y][x];  //ST[8][8] ← T[8][8]
            Reverse(my,p,ST);
            w= Search(you,ST,WS);
            if (n==0)   return (w+5)*3;     //my はパス
            if (w==0)   return (n+5)*3;     //you はパス
            return  n-w;
        }
    
        // my と you の打てる場所の数で評価
        int  PlayCnt()
        {   int     w,sn;
    
            w= Search(you,ST,WS);
            sn= Search(my,ST,WS);
            if (w==0)   sn+= 5;     //you はパス
            if (sn==0)  w+= 5;      //my はパス
            return  (sn-w)*3;
        }
    
        //★ 補助メソッド
        // n(0〜7)で指示した辺を t[8][8] から取り出す
        void  Getstr(int n, byte str[], byte t[][])
        {   y= T00Y[n%4];
            x= T00X[n%4];
            for(i=0; i<8; i++)
            {   str[i]= t[y][x];
                y+= TD[n][0];
                x+= TD[n][1];
            }
        }
    
        // 全ての打てる場所を調べる
        int  Search(byte c, byte t[][], Point s[])
        {   int     n,x,y;
            for(n=y=0; y<8; y++)
                for(x=0; x<8 && n<30; x++)
                    if (Check(c,x,y,t))
                    {   s[n].x= x;
                        s[n].y= y;
                        n++;
                    }
            return n;
        }
    
        // 人間のプレイ & 判定
        int  Check_Play(byte c, int xp, int yp)
        {
            SetPos(xp,yp);
            if (Check(c,Xpos,Ypos,T))
            {   Reverse(c,Xpos,Ypos,T);             //駒を置く
                Countdown--;
                LG[LC].Set(Countdown,c,Xpos,Ypos);
                LC++;
                return 0;                           //Play
            }
            if (Search(c,T,S)>0)    return 2;       //エラー
            if (Search((byte)-c,T,S)>0) return 1;   //手番側がパス
            //ゲーム終了
            B_C= Count((byte)1,T);
            W_C= Count((byte)-1,T);
            return 3;
        }
    
        // 置けるかどうかをチェックする
        boolean  Check(byte c, int xp, int yp, byte t[][])
        {   int     k,l;
            if (yp>7 || xp>7 || yp<0 || xp<0 || c==0 || t[yp][xp]!=0)   return false;
            for(k=-1; k<2; k++)
                for(l=-1; l<2; l++)
                {   i=yp+k;
                    j=xp+l;
                    if (i<8 && j<8 && i>=0 && j>=0 && t[i][j]==-c)
                    {   for(; i<8 && j<8 && i>=0 && j>=0 && t[i][j]==-c; i+=k,j+=l);
                        if (i<8 && j<8 && i>=0 && j>=0 && t[i][j]==c)   return true;
                    }
                }
            return false;
        }
    
        // t[yp][xp] に駒を置いて、挟んだ駒を裏返す
        boolean  Reverse(byte c, int xp, int yp, byte t[][])
        {   int     k,l;
            boolean sw;
            if (yp>7 || xp>7 || yp<0 || xp<0 || c==0 || t[yp][xp]!=0)   return false;
            sw= false;
            for(k=-1; k<2; k++)
                for(l=-1; l<2; l++)
                {   i=yp+k;
                    j=xp+l;
                    if (i<8 && j<8 && i>=0 && j>=0 && t[i][j]==-c)
                    {   for(; i<8 && j<8 && i>=0 && j>=0 && t[i][j]==-c; i+=k,j+=l);
                        if (i>7 || j>7 || i<0 || j<0 || t[i][j]!=c) continue;
                        for(i-=k,j-=l; i!=yp || j!=xp; i-=k,j-=l)   t[i][j]= c;
                        sw= true;
                    }
                }
            if (sw==false)  return false;
            t[yp][xp]= c;
            return true;
        }
    
        // t[yp][xp] に駒を置いて、挟んだ駒を裏返す
        boolean  Reverse(byte c, Point p, byte t[][])
        {   return Reverse(c, p.x, p.y, t);  }
    
        // クリックした座標を盤の位置に変換して Xpos Ypos に格納する
        void  SetPos(int xp, int yp)
        {   Xpos= (xp-20)/WSIZE;
            Ypos= (yp-20)/WSIZE;
        }
    
        // 駒をカウントする
        int  Count(byte c, byte t[][])
        {   int n;
            for(n=y=0; y<8; y++)
                for(x=0; x<8; x++)
                    if (t[y][x]==c)     n++;
            return n;
        }
    
        // 盤を描画
        public void View(Graphics g)
        {
            g.setColor(new Color(0,140,0));
            g.fillRect(10,10,WSIZE*8+20,WSIZE*8+20);
            g.setColor(Color.black);
            for(i=0; i<9; i++)
            {   g.drawLine(20,WSIZE*i+20,WSIZE*8+20,WSIZE*i+20);    // 横線
                g.drawLine(WSIZE*i+20,20,WSIZE*i+20,WSIZE*8+20);    // 縦線
            }
            for(i=0,y=WSIZE-25; i<8; i++,y+=WSIZE)
            {   for(j=0,x=WSIZE-25; j<8; j++,x+=WSIZE)
                {   if (T[i][j]!=0) Koma(g,x,y,T[i][j]);
                }
            }
        }
    
        // 駒を表示する
        void  Koma(Graphics g, int xp, int yp, byte c)
        {
            if (c==1)
            {   g.setColor(Color.black);
                g.fillArc(xp,yp,KSIZE,KSIZE,0,360);
            }
            if (c==-1)
            {   g.setColor(Color.white);
                g.fillArc(xp,yp,KSIZE,KSIZE,0,360);
            }
        }
    
        //「待った」の処理
        void  Matta()
        {   int     ii;
    
            for(y=0; y<8; y++)
                for(x=0; x<8; x++)  T[y][x]= 0;
            T[4][3]=T[3][4]= 1;         //黒
            T[3][3]=T[4][4]= -1;        //白
            if (LC==0)
            {   Countdown= 60;
                return;
            }
            for(ii=0; ii<LC; ii++)
            {   if (Reverse(LG[ii].LogC,LG[ii].LogX,LG[ii].LogY,T)==false)  break;  }
            Countdown= LG[LC-1].LogDN;
        }
    }
    
    //☆ Log Data 構造体 Object Class
    class log
    {   int     LogDN;      //カウントダウン
        byte    LogC;       //プレイのコマ
        int     LogX;       //X座標
        int     LogY;       //Y座標
    
        // Constructor
        public log()
        {   LogC= 0;
            LogC= 0;
            LogX= 0;
            LogY= 0;
        }
    
        // 値を設定
        void  Set(int down, byte c, int xp, int yp)
        {   LogDN= down;
            LogC= c;
            LogX= xp;
            LogY= yp;
        }
    }
    
  2. オセロゲームの案内をする HTMLファイル(oseroguid.htm)です。
    <html>
      <body>
        <h1>オセロゲームの案内</h1>
        <BGSOUND SRC="_osrstart.wav">
        <IMG SRC="oseronet.jpg"><br>
        <h2>オセロゲームの操作</h2>
          <ol>
            <li>「オセロゲームを開始します」のリンクからゲームを楽しめます。<br>
                セキュリティの警告画面が表示されるときは「解除」して下さい。<br>
            <li>黒の手番でゲームを開始します。<br>
                後手(白)を選ぶときは、盤の外をクリックして始めて下さい。<br>
            <li>「わたしの手番です」でクリックするとコンピュータがプレイします。<br>
                終盤になると2〜3分考えるときがあります。<br>
                「あなたの手番です」を確認してからプレイして下さい。<br>
            <li>パスや終局やゲームの結果は、コンピュタが知らせてくれます。<br>
            <li>ソースコードを見れば解るのですが、秘密の機能が隠されています。<br>
          </ol>
        <h2><a href="osero.htm">オセロゲームを開始します</a></h2>
      </body>
    </html>
    

プログラムの説明

  1. Java Applet でオセロゲームを作成します。
    オセロゲームの戦略は「C言語 Windows」で詳しく解説しています。
    前田稔(Maeda Minoru)の超初心者のプログラム入門から「C言語 Windows/Game Program/オセロゲーム」参照して下さい。
  2. 今回は局面の進行(ゲームの残り手数)によって、戦略を四段階に分けています。
    考慮時間を長くすればもっと強くなるのですが、考慮時間と強さのバランスを考えるとこんなものでしょう。
    1. 第一段階(残手数が48手超)
      ウエイトテーブルと白と黒の打てる箇所の数で評価します。
    2. 第二段階(残手数が30手超)
      Think() メソッドを再起レベル2で評価します。
      一般的に「3手読み」と言われる方法です。
    3. 第三段階(残手数が12手超)
      Think() メソッドを再起レベル4で評価します。
    4. 第四段階(残手数が12手以下)
      Think() メソッドで最後まで読み切ります。
  3. Comp_Play() がコンピュータがプレイするメソッドで、Think() が再起で呼ばれるメソッドです。
    ソースコードの説明は下記を参照して下さい。

[Previous Chapter ↑] コンピュータが乱数でプレイ

Java Game Program