コラム
2026/08/27
プログラミングについて 第135回目
『たまにはオセロで遊ぼうよ! その7』
今回も前回の続きです。変更があった resource.h と プログラムのリストです。
[resource.h]
#define IDBLACK_STATUS 1000
#define IDWHITE_STATUS 1001
#define IDMESSAGE 1002
#define IDFRAME_BOX 1003
#define IDPUT_STONE 1004
#define IDASK_ORDER 1005
#define IDTHINK_PUT_STONE 1006
[othello.cpp]
#include <afxwin.h>
#include "res\resource.h"
#include "dummy.h"
#define MAX_BOX 8
#define STONE_SIZE_RATIO 0.8
#define RED_COLOR RGB(255, 0, 0)
#define BLACK_COLOR RGB( 0, 0, 0)
#define WHITE_COLOR RGB(255,255,255)
#define DGREEN_COLOR RGB( 0,128, 0)
#define NO_STONE 0
#define BLACK_STONE 1
#define WHITE_STONE 2
#define NOTIFY_STONE 3
#define MAX_DRC 8
int xdrc_table[MAX_DRC]={ 1,-1, 0, 0, 1,-1, 1,-1 };
int ydrc_table[MAX_DRC]={ 0, 0, 1,-1, 1,-1,-1, 1 };
class Othello : public CDialog
{
private:
int stone_size;
struct box_data_strct{
RECT rect;
int stone;
};
struct box_data_strct box_data[MAX_BOX][MAX_BOX];
CPen pen ,*old_pen ;
CBrush brush,*old_brush;
int pen_brush_using ;
int on_playing;
int player_stone;
int cpu_stone;
int current_stone;
int total_black_stone;
int total_white_stone;
afx_msg void OnClose();
afx_msg BOOL OnInitDialog();
afx_msg void OnPaint();
afx_msg void OnLButtonDown(UINT nflags,CPoint point);
afx_msg void OnPutStone();
afx_msg void OnAskOrder();
afx_msg void OnThinkPutStone();
void Redraw(CDC *dc);
void SetColor (CDC *dc,COLORREF outline_color,COLORREF fill_color);
void FreeColor(CDC *dc);
void InitializeBoxData();
void WriteStone(CDC *dc,int x,int y);
int IsEnablePutStone(
int stone,int xput,int yput,
int check_this=1,int drc= -1);
void WriteStoneTotal();
void PutStone(int stone,int xput,int yput);
void Delay(double delta_time);
void WriteOrder();
int EnablePutStoneAnywhere();
int WriteEndMessage();
void PurgeLButtonDownMessage();
public:
Othello(HICON othello_icon);
DECLARE_MESSAGE_MAP()
};
class OthelloApp : public CWinApp
{
public:
virtual BOOL InitInstance();
virtual ~OthelloApp();
};
BOOL OthelloApp::InitInstance()
{
m_pMainWnd=new Othello(LoadIcon("OTHELLO_ICON"));
return TRUE;
}
OthelloApp::~OthelloApp()
{
delete m_pMainWnd;
}
afx_msg void Othello::OnClose()
{
PostQuitMessage(0);
}
Othello::Othello(HICON othello_icon)
{
Create("OTHELLO");
SetIcon(othello_icon,TRUE);
SetWindowText("オセロゲーム");
}
afx_msg BOOL Othello::OnInitDialog()
{
RECT rect;
double xsize,ysize,t;
int x,y;
on_playing=0;
pen_brush_using=0;
GetDlgItem(IDFRAME_BOX)->GetWindowRect(&rect);
xsize=((double)(rect.right -rect.left))/((double)MAX_BOX);
ysize=((double)(rect.bottom-rect.top ))/((double)MAX_BOX);
if(xsize<ysize)t=xsize;
else t=ysize;
stone_size=(int)(t*STONE_SIZE_RATIO);
ScreenToClient(&rect);
for(x=0;x<MAX_BOX;x++){
for(y=0;y<MAX_BOX;y++){
box_data[x][y].rect.left =(int)(rect.left+xsize* x +0.5);
box_data[x][y].rect.top =(int)(rect.top +ysize* y +0.5);
box_data[x][y].rect.right =(int)(rect.left+xsize*(x+1)+0.5);
box_data[x][y].rect.bottom=(int)(rect.top +ysize*(y+1)+0.5);
}
}
InitializeBoxData();
PostMessage(WM_COMMAND,IDASK_ORDER);
return TRUE;
}
afx_msg void Othello::OnPaint()
{
CPaintDC dc(this);
Redraw(&dc);
}
void Othello::SetColor(CDC *dc,COLORREF outline_color,COLORREF fill_color)
{
FreeColor(dc);
pen.CreatePen(PS_SOLID,0,outline_color);
brush.CreateSolidBrush(fill_color);
old_pen =dc->SelectObject(&pen );
old_brush=dc->SelectObject(&brush);
pen_brush_using=1;
}
void Othello::FreeColor(CDC *dc)
{
if(pen_brush_using){
dc->SelectObject(old_pen );
dc->SelectObject(old_brush);
pen.DeleteObject();
brush.DeleteObject();
}
pen_brush_using=0;
}
void Othello::Redraw(CDC *dc)
{
RECT rect;
int x,y;
GetDlgItem(IDFRAME_BOX)->GetWindowRect(&rect);
ScreenToClient(&rect);
SetColor(dc,DGREEN_COLOR,DGREEN_COLOR);
dc->Rectangle(&rect);
SetColor(dc,BLACK_COLOR,BLACK_COLOR);
for(x=0;x<MAX_BOX;x++){
for(y=0;y<MAX_BOX;y++){
dc->MoveTo(box_data[x][y].rect.left ,box_data[x][y].rect.top );
dc->LineTo(box_data[x][y].rect.right,box_data[x][y].rect.top );
dc->LineTo(box_data[x][y].rect.right,box_data[x][y].rect.bottom);
dc->LineTo(box_data[x][y].rect.left ,box_data[x][y].rect.bottom);
dc->LineTo(box_data[x][y].rect.left ,box_data[x][y].rect.top );
}
}
for(x=0;x<MAX_BOX;x++){
for(y=0;y<MAX_BOX;y++)WriteStone(dc,x,y);
}
FreeColor(dc);
}
void Othello::InitializeBoxData()
{
int x,y;
for(x=0;x<MAX_BOX;x++){
for(y=0;y<MAX_BOX;y++){
box_data[x][y].stone=NO_STONE;
}
}
box_data[MAX_BOX/2-1][MAX_BOX/2-1].stone=BLACK_STONE;
box_data[MAX_BOX/2 ][MAX_BOX/2-1].stone=WHITE_STONE;
box_data[MAX_BOX/2-1][MAX_BOX/2 ].stone=WHITE_STONE;
box_data[MAX_BOX/2 ][MAX_BOX/2 ].stone=BLACK_STONE;
total_black_stone=2;
total_white_stone=2;
WriteStoneTotal();
}
void Othello::WriteStone(CDC *dc,int x,int y)
{
COLORREF color;
int xc,yc;
if(box_data[x][y].stone== NO_STONE)return;
else if(box_data[x][y].stone== BLACK_STONE)color=BLACK_COLOR;
else if(box_data[x][y].stone== WHITE_STONE)color=WHITE_COLOR;
else if(box_data[x][y].stone==NOTIFY_STONE)color= RED_COLOR;
xc=(box_data[x][y].rect.left+box_data[x][y].rect.right )/2;
yc=(box_data[x][y].rect.top +box_data[x][y].rect.bottom)/2;
SetColor(dc,color,color);
dc->Ellipse(
xc-stone_size/2,yc-stone_size/2,
xc+stone_size/2,yc+stone_size/2);
FreeColor(dc);
}
afx_msg void Othello::OnLButtonDown(UINT nflags,CPoint point)
{
int x,y;
for(x=0;x<MAX_BOX;x++){
for(y=0;y<MAX_BOX;y++){
if(point.x>box_data[x][y].rect.left
&& point.x<box_data[x][y].rect.right
&& point.y>box_data[x][y].rect.top
&& point.y<box_data[x][y].rect.bottom){
PostMessage(WM_COMMAND,IDPUT_STONE,(((player_stone<<8)|x)<<8)|y);
return;
}
}
}
}
afx_msg void Othello::OnAskOrder()
{
if(MessageBox(
"●は先攻、○は後攻です。\n\n"
"先攻しますか?",
"順番の問い合わせ",MB_YESNO)==IDYES){
player_stone=BLACK_STONE;
cpu_stone=WHITE_STONE;
}
else{
player_stone=WHITE_STONE;
cpu_stone=BLACK_STONE;
}
current_stone=BLACK_STONE;
on_playing=1;
WriteOrder();
if(player_stone==current_stone){
PurgeLButtonDownMessage();
}
else{
PostMessage(WM_PAINT);
PostMessage(WM_COMMAND,IDTHINK_PUT_STONE);
}
}
afx_msg void Othello::OnPutStone()
{
LPARAM lparam;
int stone,xput,yput;
if(!on_playing)return;
lparam=GetCurrentMessage()->lParam;
yput =lparam & 0x000000ff; lparam=lparam>>8;
xput =lparam & 0x000000ff; lparam=lparam>>8;
stone=lparam & 0x000000ff;
if(stone!=current_stone)return;
if(!IsEnablePutStone(stone,xput,yput))return;
PutStone(stone,xput,yput);
}
int Othello::IsEnablePutStone(
int stone,int xput,int yput,int check_this,int drc)
{
int drc_from,drc_to,x,y,flag,xstone;
if(check_this && box_data[xput][yput].stone!=NO_STONE)return 0;
xstone=stone==BLACK_STONE ? WHITE_STONE : BLACK_STONE;
if(drc== -1){ drc_from=0 ; drc_to=MAX_DRC-1; }
else { drc_from=drc; drc_to=drc ; }
for(drc=drc_from;drc<=drc_to;drc++){
flag=0;
for(x=xput+xdrc_table[drc] , y=yput+ydrc_table[drc];
x>=0 && y>=0 && x<MAX_BOX && y<MAX_BOX;
x=x +xdrc_table[drc] ,y=y +ydrc_table[drc]){
if(box_data[x][y].stone==NO_STONE)break;
if(flag==0){
if(box_data[x][y].stone==xstone)flag=1;
else break;
}
else{
if(box_data[x][y].stone== stone)return 1;
}
}
}
return 0;
}
void Othello::WriteStoneTotal()
{
char tmp[31];
sprintf(tmp,"● %2d 個",total_black_stone);
SetDlgItemText(IDBLACK_STATUS,tmp);
sprintf(tmp,"○ %2d 個",total_white_stone);
SetDlgItemText(IDWHITE_STATUS,tmp);
}
void Othello::PutStone(int stone,int xput,int yput)
{
CClientDC dc(this);
int drc,x,y,loop,xstone,*add,*sub;
if(stone==BLACK_STONE){
xstone=WHITE_STONE;
add= &total_black_stone;
sub= &total_white_stone;
}
else{
xstone=BLACK_STONE;
add= &total_white_stone;
sub= &total_black_stone;
}
box_data[xput][yput].stone=stone;
(*add)++;
WriteStone(&dc,xput,yput);
Delay(0.3);
for(drc=0;drc<MAX_DRC;drc++){
if(!IsEnablePutStone(stone,xput,yput,0,drc))continue;
for(x=xput+xdrc_table[drc] , y=yput+ydrc_table[drc];
x>=0 && y>=0 && x<MAX_BOX && y<MAX_BOX;
x=x +xdrc_table[drc] , y=y +ydrc_table[drc]){
if(box_data[x][y].stone==stone)break;
box_data[x][y].stone=stone;
(*add)++;
(*sub)--;
WriteStone(&dc,x,y);
Delay(0.3);
}
}
WriteStoneTotal();
for(loop=0;loop<=1;loop++){
if(current_stone==BLACK_STONE)current_stone=WHITE_STONE;
else current_stone=BLACK_STONE;
if(EnablePutStoneAnywhere())break;
}
if(loop>1){
if(!WriteEndMessage()){
PostMessage(WM_CLOSE);
return;
}
InitializeBoxData();
WriteStoneTotal();
Redraw(&dc);
PostMessage(WM_COMMAND,IDASK_ORDER);
return;
}
WriteOrder();
if(player_stone==current_stone)PurgeLButtonDownMessage();
else PostMessage(WM_COMMAND,IDTHINK_PUT_STONE);
}
void Othello::Delay(double delta_time)
{
DWORD to,dt;
dt=(int)(delta_time*1000.0);
to=GetTickCount();
while(1){
if(GetTickCount()-to>dt)break;
}
}
void Othello::WriteOrder()
{
char tmp[81];
if(current_stone==player_stone){
sprintf(
tmp,"あなたの %s 石の番です",
current_stone==BLACK_STONE ? "●" : "○");
}
else{
strcpy(tmp,"お待ち下さい");
}
SetDlgItemText(IDMESSAGE,tmp);
}
int Othello::WriteEndMessage()
{
char tmp[81];
int diff;
diff=player_stone==WHITE_STONE ? total_white_stone-total_black_stone
: total_black_stone-total_white_stone;
if(diff< 0)sprintf(tmp,"あなたの %d 個の負けです。",-diff);
else if(diff==0)strcpy (tmp,"引き分けです。");
else sprintf(tmp,"あなたの %d 個の勝ちです。", diff);
strcat(tmp,"\nもう一度勝負しますか?");
if(MessageBox(tmp,"終了",MB_YESNO)==IDYES)return 1;
else return 0;
}
int Othello::EnablePutStoneAnywhere()
{
int x,y;
for(x=0;x<MAX_BOX;x++){
for(y=0;y<MAX_BOX;y++){
if(IsEnablePutStone(current_stone,x,y))return 1;
}
}
return 0;
}
afx_msg void Othello::OnThinkPutStone()
{
CClientDC dc(this);
int x,y;
for(x=0;x<MAX_BOX;x++){
for(y=0;y<MAX_BOX;y++){
if(box_data[x][y].stone!=NO_STONE)continue;
if(IsEnablePutStone(cpu_stone,x,y)){
Delay(1.0);
box_data[x][y].stone=NOTIFY_STONE;
WriteStone(&dc,x,y);
box_data[x][y].stone=NO_STONE;
Delay(0.3);
PostMessage(WM_COMMAND,IDPUT_STONE,(((cpu_stone<<8)|x)<<8)|y);
return;
}
}
}
}
void Othello::PurgeLButtonDownMessage()
{
MSG msg;
while(PeekMessage(&msg,m_hWnd,WM_LBUTTONDOWN,WM_LBUTTONDOWN,PM_REMOVE));
}
BEGIN_MESSAGE_MAP(Othello,CDialog)
ON_COMMAND(IDASK_ORDER,OnAskOrder)
ON_COMMAND(IDPUT_STONE,OnPutStone)
ON_COMMAND(IDTHINK_PUT_STONE,OnThinkPutStone)
ON_COMMAND(IDOK ,Default)
ON_COMMAND(IDCANCEL,Default)
ON_WM_LBUTTONDOWN()
ON_WM_PAINT()
ON_WM_CLOSE()
END_MESSAGE_MAP()
OthelloApp OthelloApp;
今回からは少しずつですが計算機を賢くすることに専念しましょう。前回までの計算機は、X方向Y方向を順に検査していき最初に発見したマス目に石を打っていました。まずこれをなんとかしましょう。考えられることは、石を打てるマス目を全部探してからランダムにマス目を決定する方法です。この方法を試してみましょう。
Othello クラスの宣言に、
struct able_data_strct{
int x,y;
};
struct able_data_strct able_data[MAX_BOX * MAX_BOX];
int lable_data;
を追加します。able_data はXとYの値のみをもった構造体の1次元の配列でマス目の数だけ要素数を確保しておきます。lable_data は able_data の最後のインデックス番号を格納する目的に使う変数です。更にランダムな値を返す関数も追加しておきます。
int Random(int total);
この関数は、
int Othello::Random(int total)
{
return GetTickCount()%total;
}
としておき、現在の時間のカウントを取得した後、able_data の総数での剰余を返すようにします。このようにしておけば必ず配列の要素番号を返すことになります。
次に問題の OnThinkPutStone() は次のようにします。
afx_msg void Othello::OnThinkPutStone()
{
CClientDC dc(this);
int x,y,i;
lable_data= -1;
for(x=0;x<MAX_BOX;x++){
for(y=0;y<MAX_BOX;y++){
if(box_data[x][y].stone!=NO_STONE )continue;
if(!IsEnablePutStone(cpu_stone,x,y))continue;
lable_data++;
able_data[lable_data].x=x;
able_data[lable_data].y=y;
}
}
i=Random(lable_data+1);
x=able_data[i].x;
y=able_data[i].y;
Delay(1.0);
box_data[x][y].stone=NOTIFY_STONE;
WriteStone(&dc,x,y);
box_data[x][y].stone=NO_STONE;
Delay(0.3);
PostMessage(WM_COMMAND,IDPUT_STONE,(((cpu_stone<<8)|x)<<8)|y);
}
これはじっくりと見ていただければ理解できると思います。
ここまで改修してプログラムを実行してみると前回よりもだいぶ良くなったのが分かると思います。といっても素人とプレイしているのと同じ感じだと思います。
もう少し賢くしてみましょう。次に考えられるのは各マスに点数を付けておき、出来る限り点数の高いマスに打つようにしましょう。点数が同じマスがあったときにはランダムに決定することにします。まず Othello クラスの box_data_strct 構造体にメンバーを1つ追加して次のようにしましょう。
struct box_data_strct{
RECT rect;
int stone;
int point;
};
この構造体のメンバー point に各マス目の点数を設定しておくようにします。次に、
OnInitDialog 関数に、
int point_table[MAX_BOX][MAX_BOX]={
10, 1, 2, 3, 3, 2, 1,10,
1, 1, 4, 4, 4, 4, 1, 1,
2, 4, 5, 5, 5, 5, 4, 2,
3, 4, 5, 0, 0, 5, 4, 3,
3, 4, 5, 0, 0, 5, 4, 3,
2, 4, 5, 5, 5, 5, 4, 2,
1, 1, 4, 4, 4, 4, 1, 1,
10, 1, 2, 3, 3, 2, 1,10
};
を追加し、各マス目の矩形のサイズを設定するループにマス目の点数を設定する行を次のように追加しましょう。
for(x=0;x<MAX_BOX;x++){
for(y=0;y<MAX_BOX;y++){
:
:
box_data[x][y].point=point_table[x][y]; //追加。
}
}
ここまででマス目の点数を設定できましたが、まだ点数に従って石は打てません。
OnThinkPutStone を直さなければなりません。まず able_data_strct 構造体にもint point; を追加して、
struct able_data_strct{
int x,y;
int point;
};
のようにします。次に OnThinkPutStone を、
afx_msg void Othello::OnThinkPutStone()
{
:
:
for(x=0;x<MAX_BOX;x++){
for(y=0;y<MAX_BOX;y++){
:
:
able_data[lable_data].point=box_data[x][y].point;
}
}
SortAbleData();
for(i=0;i<lable_data;i++){
if(able_data[i].point>able_data[i+1].point){
lable_data=i;
break;
}
}
:
:
とし、able_data に1つ要素を追加するときにマス目の点数も付加します。すべてのマス目を検査した後、SortAbleData() 関数でポイントの大きい順に並べ直し、点数の最大数のみを配列の要素とします。後の処理は今までと同じです。
SortAbleData() は未定義ですので Othello クラスの宣言に、
void SortAbleData();
を追加します、そして SortAbleData() は、
void Othello::SortAbleData()
{
struct able_data_strct tmp;
int i,j,gap;
gap=(lable_data+1)/2;
while(gap){
i=gap;
while(i<=lable_data){
j=i-gap;
while( j>=0 && CompareAbleData(able_data,j,j+gap)==1){
tmp =able_data[j ];
able_data[j ]=able_data[j+gap];
able_data[j+gap]=tmp;
j-=gap;
}
i++;
}
gap/=2;
}
}
とします。ソート方法は要素数が少ないのでどんなアルゴリズムでも構いませんが、ここではシェルソートを使用しています。CompareAbleData() は未定義ですので、これもOthello クラスに、
int CompareAbleData(struct able_data_strct *data,int i,int j);
を追加し、
int Othello::CompareAbleData(struct able_data_strct *data,int i,int j)
{
if(data[i].point>data[j].point)return 0;
if(data[i].point<data[j].point)return 1;
return 0;
}
とし点数の大きい順に並ぶように値を返します。
ここまででこの改修は終了です。試しに実行してみると、なんとなく人間臭くなってきたように思えませんか。それでもちょっと頭が悪いのかコーナーを取れるとなると喜んで取りに来るのが可愛いと言えば言えなくもありません。
いつもホームページの私のこのコーナーを編集してくれる美人のお姉さんの話によると、オセロは序盤戦のときにはあまり多く石をひっくり返さない方がいいということなので、この機能を追加してみましょう。OnThinkPutStone の SortAbleData(); の行の前に
ThinkAbleData();
を追加します。この関数も未定義ですので Othello クラスの宣言に、
void ThinkAbleData();
を追加しておきます。この ThinkAbleData() 内で石を打てるマス目の点数を調整しようという魂胆です。
局面を序盤、中盤、終盤の3つに分けることにして、打ってある石の総数が36個未満のときに序盤、36個以上48個未満のときに中盤、それ以外のときに終盤ということにします。この切り分けは適当で、盤面の一つ内側のマスの数が6×6ですので、序盤はだいたい36ぐらい、通常最後の攻防になる4つのコーナーの4つずつのマス目の総数が4×4ですので全体のマス目の数から引くと 8×8ー4×4=48 ということで適当に決めました。この切り分けの違いで強くなったり弱くなったりする可能性があります。そこで、
#define PERIOD12 36
#define PERIOD23 48
#define PERIOD1 1
#define PERIOD2 2
#define PERIOD3 3
を定義しておきましょう。
#define PERIOD12 36
は序盤と中盤の境、
#define PERIOD23 48
は中盤と終盤の境の石の総数です。
#define PERIOD1 1
#define PERIOD2 2
#define PERIOD3 3
は序盤、中盤、終盤を示す値とします。
このように決め、ThinkAbleData() を、
void Othello::ThinkAbleData()
{
int total;
total=total_black_stone+total_white_stone;
if(total<PERIOD12)ThinkAbleDataPass1(PERIOD1);
}
とし、序盤戦の打てるマス目の点数の操作を行ないましょう。ThinkAbleDataPass1()は未定義ですので Othello クラスの宣言に、
void ThinkAbleDataPass1(int period);
を追加します。
ThinkAbleDataPass1() を考えていきましょう。あまりいっぱいひっくり返さないということは、相手に打つ場所の選択の余地を出来る限り与えないということになりますので、ひっくり返す石の少ない場所で、なおかつひっくり返す方向の少ないものを優先させればいいということになります。これを実現するには、現在打てるマス目すべてについて、その数を検査し、大きいもの程点数を下げる。という処理を行えばなんとかなりそうです。そこで、ThinkAbleDataPass1() を次のようにしました。
void Othello::ThinkAbleDataPass1(int period)
{
#define POINT_DECREASE_PITCH 5
struct rbdata_strct{
int index,reverse,branch;
struct rbdata_strct *next;
};
struct rbdata_strct rbdata[MAX_BOX * MAX_BOX];
struct rbdata_strct *top_rbdata,*rbp,*rbc;
int i,x,y;
top_rbdata=NULL;
for(i=0;i<=lable_data;i++){
GetReverseBranchCount(
cpu_stone,able_data[i].x,able_data[i].y,
&rbdata[i].reverse,&rbdata[i].branch);
rbdata[i].index=i;
rbdata[i].next =NULL;
rbp=NULL;
rbc=top_rbdata;
while(rbc){
if(rbdata[i].reverse < rbc->reverse)break;
if(rbdata[i].reverse == rbc->reverse
&& rbdata[i].branch == rbc->branch )break;
rbp=rbc;
rbc=rbc->next;
}
rbdata[i].next=rbc;
if(rbp==NULL)top_rbdata=rbdata+i;
else rbp->next=rbdata+i;
}
i=0;
rbp=NULL;
rbc=top_rbdata;
while(rbc){
if(rbp){
if(rbc->reverse!=rbp->reverse
|| rbc->branch !=rbp->branch )i-=POINT_DECREASE_PITCH;
}
able_data[rbc->index].point+=i;
rbp=rbc;
rbc=rbc->next;
}
#undef POINT_DECREASE_PITCH
}
ちょっと難しいので説明が必要ですね。
#define POINT_DECREASE_PITCH 5
は点数を下げる幅の値です。
struct rbdata_strct{
int index,reverse,branch;
struct rbdata_strct *next;
};
は、石を打てるマス目のひっくり返す数と方向(分岐)リストの構造体で、index にはable_data の配列の要素番号を、reverse にはひっくり返す数、branch には分岐する数を入れます。next は次のリストの要素へのポインタです。
struct rbdata_strct rbdata[MAX_BOX * MAX_BOX];
は、rbdata_strct 構造体の配列です。new と delete の演算子を使ってもいいのですがリストの要素数が最大でも64個しか必要ないので配列にしてしまいました。
struct rbdata_strct *top_rbdata,*rbp,*rbc;
の top_rbdata はリストの先頭へのポインタです。rbp と rbc は処理用に使用します。
int i;
これも処理用のものです。
top_rbdata=NULL;
リストの先頭へのポインタを初期化します。
for(i=0;i<=lable_data;i++){
GetReverseBranchCount(
cpu_stone,able_data[i].x,able_data[i].y,
&rbdata[i].reverse,&rbdata[i].branch);
の GetReverseBranchCount() は未定義ですが、指定したマス目に石を置いたときにひっくり返る数と分岐する数を返す機能を持った関数にします。
rbdata[i].index=i;
rbdata[i].next =NULL;
able_data の配列の要素番号を設定し、次のリストの要素へのポインタを初期化します。
rbp=NULL;
rbc=top_rbdata;
while(rbc){
if(rbdata[i].reverse < rbc->reverse)break;
if(rbdata[i].reverse == rbc->reverse
&& rbdata[i].branch == rbc->branch )break;
rbp=rbc;
rbc=rbc->next;
}
このループはリストの先頭から検査していき、今追加したリストの要素をリストのどこに追加するかを調べています。
rbdata[i].next=rbc;
if(rbp==NULL)top_rbdata=rbdata+i;
else rbp->next=rbdata+i;
リストに追加します。上のループが終了したとき、rbp がヌルになっていればリストのトップに挿入することになり、そうでないときには直前のリストの要素の次に挿入するようにします。
}
このループが終了すると、リストはひっくり返る数の小さい順でなおかつ分岐数の小さい順で作成されます。
i=0;
rbp=NULL;
rbc=top_rbdata;
while(rbc){
if(rbp){
if(rbc->reverse!=rbp->reverse
|| rbc->branch !=rbp->branch )i-=POINT_DECREASE_PITCH;
}
1つ前のリストの要素のひっくり返る数と分岐数が違うときには、点数に追加する数を減らします。
able_data[rbc->index].point+=i;
ここで点数を減らします。
rbp=rbc;
rbc=rbc->next;
}
#undef POINT_DECREASE_PITCH
}
未定義になっていた GetReverseBranchCount() は Othello クラスの宣言に、
void GetReverseBranchCount(
int stone,int xput,int yput,int *reverse,int *branch);
を追加します。そして、
void Othello::GetReverseBranchCount(
int stone,int xput,int yput,int *reverse,int *branch)
{
int drc,x,y,flag,xstone;
int rev;
*reverse=0;
*branch =0;
xstone=stone==BLACK_STONE ? WHITE_STONE : BLACK_STONE;
for(drc=0;drc<MAX_DRC;drc++){
flag=0;
for(x=xput+xdrc_table[drc] , y=yput+ydrc_table[drc];
x>=0 && y>=0 && x<MAX_BOX && y<MAX_BOX;
x=x +xdrc_table[drc] ,y=y +ydrc_table[drc]){
if(box_data[x][y].stone==NO_STONE)break;
if(flag==0){
if(box_data[x][y].stone==stone)break;
flag=1;
rev =1;
}
else{
if(box_data[x][y].stone==xstone){
rev++;
}
else{
(*reverse)+=rev;
(*branch )+=1 ;
break;
}
}
}
}
}
とします。この関数は PutStone() をちょっと変更したものですので特に説明の必要はないでしょう。
ここまでの状態で実行してみると、結構良い感じになってきました。
続きはまた次回にしましょう。
第135回 プログラミングについて『たまにはオセロで遊ぼうよ!その7』
プログラミングについて 第135回目
『たまにはオセロで遊ぼうよ! その7』
今回も前回の続きです。変更があった resource.h と プログラムのリストです。
[resource.h]
#define IDBLACK_STATUS 1000
#define IDWHITE_STATUS 1001
#define IDMESSAGE 1002
#define IDFRAME_BOX 1003
#define IDPUT_STONE 1004
#define IDASK_ORDER 1005
#define IDTHINK_PUT_STONE 1006
[othello.cpp]
#include <afxwin.h>
#include "res\resource.h"
#include "dummy.h"
#define MAX_BOX 8
#define STONE_SIZE_RATIO 0.8
#define RED_COLOR RGB(255, 0, 0)
#define BLACK_COLOR RGB( 0, 0, 0)
#define WHITE_COLOR RGB(255,255,255)
#define DGREEN_COLOR RGB( 0,128, 0)
#define NO_STONE 0
#define BLACK_STONE 1
#define WHITE_STONE 2
#define NOTIFY_STONE 3
#define MAX_DRC 8
int xdrc_table[MAX_DRC]={ 1,-1, 0, 0, 1,-1, 1,-1 };
int ydrc_table[MAX_DRC]={ 0, 0, 1,-1, 1,-1,-1, 1 };
class Othello : public CDialog
{
private:
int stone_size;
struct box_data_strct{
RECT rect;
int stone;
};
struct box_data_strct box_data[MAX_BOX][MAX_BOX];
CPen pen ,*old_pen ;
CBrush brush,*old_brush;
int pen_brush_using ;
int on_playing;
int player_stone;
int cpu_stone;
int current_stone;
int total_black_stone;
int total_white_stone;
afx_msg void OnClose();
afx_msg BOOL OnInitDialog();
afx_msg void OnPaint();
afx_msg void OnLButtonDown(UINT nflags,CPoint point);
afx_msg void OnPutStone();
afx_msg void OnAskOrder();
afx_msg void OnThinkPutStone();
void Redraw(CDC *dc);
void SetColor (CDC *dc,COLORREF outline_color,COLORREF fill_color);
void FreeColor(CDC *dc);
void InitializeBoxData();
void WriteStone(CDC *dc,int x,int y);
int IsEnablePutStone(
int stone,int xput,int yput,
int check_this=1,int drc= -1);
void WriteStoneTotal();
void PutStone(int stone,int xput,int yput);
void Delay(double delta_time);
void WriteOrder();
int EnablePutStoneAnywhere();
int WriteEndMessage();
void PurgeLButtonDownMessage();
public:
Othello(HICON othello_icon);
DECLARE_MESSAGE_MAP()
};
class OthelloApp : public CWinApp
{
public:
virtual BOOL InitInstance();
virtual ~OthelloApp();
};
BOOL OthelloApp::InitInstance()
{
m_pMainWnd=new Othello(LoadIcon("OTHELLO_ICON"));
return TRUE;
}
OthelloApp::~OthelloApp()
{
delete m_pMainWnd;
}
afx_msg void Othello::OnClose()
{
PostQuitMessage(0);
}
Othello::Othello(HICON othello_icon)
{
Create("OTHELLO");
SetIcon(othello_icon,TRUE);
SetWindowText("オセロゲーム");
}
afx_msg BOOL Othello::OnInitDialog()
{
RECT rect;
double xsize,ysize,t;
int x,y;
on_playing=0;
pen_brush_using=0;
GetDlgItem(IDFRAME_BOX)->GetWindowRect(&rect);
xsize=((double)(rect.right -rect.left))/((double)MAX_BOX);
ysize=((double)(rect.bottom-rect.top ))/((double)MAX_BOX);
if(xsize<ysize)t=xsize;
else t=ysize;
stone_size=(int)(t*STONE_SIZE_RATIO);
ScreenToClient(&rect);
for(x=0;x<MAX_BOX;x++){
for(y=0;y<MAX_BOX;y++){
box_data[x][y].rect.left =(int)(rect.left+xsize* x +0.5);
box_data[x][y].rect.top =(int)(rect.top +ysize* y +0.5);
box_data[x][y].rect.right =(int)(rect.left+xsize*(x+1)+0.5);
box_data[x][y].rect.bottom=(int)(rect.top +ysize*(y+1)+0.5);
}
}
InitializeBoxData();
PostMessage(WM_COMMAND,IDASK_ORDER);
return TRUE;
}
afx_msg void Othello::OnPaint()
{
CPaintDC dc(this);
Redraw(&dc);
}
void Othello::SetColor(CDC *dc,COLORREF outline_color,COLORREF fill_color)
{
FreeColor(dc);
pen.CreatePen(PS_SOLID,0,outline_color);
brush.CreateSolidBrush(fill_color);
old_pen =dc->SelectObject(&pen );
old_brush=dc->SelectObject(&brush);
pen_brush_using=1;
}
void Othello::FreeColor(CDC *dc)
{
if(pen_brush_using){
dc->SelectObject(old_pen );
dc->SelectObject(old_brush);
pen.DeleteObject();
brush.DeleteObject();
}
pen_brush_using=0;
}
void Othello::Redraw(CDC *dc)
{
RECT rect;
int x,y;
GetDlgItem(IDFRAME_BOX)->GetWindowRect(&rect);
ScreenToClient(&rect);
SetColor(dc,DGREEN_COLOR,DGREEN_COLOR);
dc->Rectangle(&rect);
SetColor(dc,BLACK_COLOR,BLACK_COLOR);
for(x=0;x<MAX_BOX;x++){
for(y=0;y<MAX_BOX;y++){
dc->MoveTo(box_data[x][y].rect.left ,box_data[x][y].rect.top );
dc->LineTo(box_data[x][y].rect.right,box_data[x][y].rect.top );
dc->LineTo(box_data[x][y].rect.right,box_data[x][y].rect.bottom);
dc->LineTo(box_data[x][y].rect.left ,box_data[x][y].rect.bottom);
dc->LineTo(box_data[x][y].rect.left ,box_data[x][y].rect.top );
}
}
for(x=0;x<MAX_BOX;x++){
for(y=0;y<MAX_BOX;y++)WriteStone(dc,x,y);
}
FreeColor(dc);
}
void Othello::InitializeBoxData()
{
int x,y;
for(x=0;x<MAX_BOX;x++){
for(y=0;y<MAX_BOX;y++){
box_data[x][y].stone=NO_STONE;
}
}
box_data[MAX_BOX/2-1][MAX_BOX/2-1].stone=BLACK_STONE;
box_data[MAX_BOX/2 ][MAX_BOX/2-1].stone=WHITE_STONE;
box_data[MAX_BOX/2-1][MAX_BOX/2 ].stone=WHITE_STONE;
box_data[MAX_BOX/2 ][MAX_BOX/2 ].stone=BLACK_STONE;
total_black_stone=2;
total_white_stone=2;
WriteStoneTotal();
}
void Othello::WriteStone(CDC *dc,int x,int y)
{
COLORREF color;
int xc,yc;
if(box_data[x][y].stone== NO_STONE)return;
else if(box_data[x][y].stone== BLACK_STONE)color=BLACK_COLOR;
else if(box_data[x][y].stone== WHITE_STONE)color=WHITE_COLOR;
else if(box_data[x][y].stone==NOTIFY_STONE)color= RED_COLOR;
xc=(box_data[x][y].rect.left+box_data[x][y].rect.right )/2;
yc=(box_data[x][y].rect.top +box_data[x][y].rect.bottom)/2;
SetColor(dc,color,color);
dc->Ellipse(
xc-stone_size/2,yc-stone_size/2,
xc+stone_size/2,yc+stone_size/2);
FreeColor(dc);
}
afx_msg void Othello::OnLButtonDown(UINT nflags,CPoint point)
{
int x,y;
for(x=0;x<MAX_BOX;x++){
for(y=0;y<MAX_BOX;y++){
if(point.x>box_data[x][y].rect.left
&& point.x<box_data[x][y].rect.right
&& point.y>box_data[x][y].rect.top
&& point.y<box_data[x][y].rect.bottom){
PostMessage(WM_COMMAND,IDPUT_STONE,(((player_stone<<8)|x)<<8)|y);
return;
}
}
}
}
afx_msg void Othello::OnAskOrder()
{
if(MessageBox(
"●は先攻、○は後攻です。\n\n"
"先攻しますか?",
"順番の問い合わせ",MB_YESNO)==IDYES){
player_stone=BLACK_STONE;
cpu_stone=WHITE_STONE;
}
else{
player_stone=WHITE_STONE;
cpu_stone=BLACK_STONE;
}
current_stone=BLACK_STONE;
on_playing=1;
WriteOrder();
if(player_stone==current_stone){
PurgeLButtonDownMessage();
}
else{
PostMessage(WM_PAINT);
PostMessage(WM_COMMAND,IDTHINK_PUT_STONE);
}
}
afx_msg void Othello::OnPutStone()
{
LPARAM lparam;
int stone,xput,yput;
if(!on_playing)return;
lparam=GetCurrentMessage()->lParam;
yput =lparam & 0x000000ff; lparam=lparam>>8;
xput =lparam & 0x000000ff; lparam=lparam>>8;
stone=lparam & 0x000000ff;
if(stone!=current_stone)return;
if(!IsEnablePutStone(stone,xput,yput))return;
PutStone(stone,xput,yput);
}
int Othello::IsEnablePutStone(
int stone,int xput,int yput,int check_this,int drc)
{
int drc_from,drc_to,x,y,flag,xstone;
if(check_this && box_data[xput][yput].stone!=NO_STONE)return 0;
xstone=stone==BLACK_STONE ? WHITE_STONE : BLACK_STONE;
if(drc== -1){ drc_from=0 ; drc_to=MAX_DRC-1; }
else { drc_from=drc; drc_to=drc ; }
for(drc=drc_from;drc<=drc_to;drc++){
flag=0;
for(x=xput+xdrc_table[drc] , y=yput+ydrc_table[drc];
x>=0 && y>=0 && x<MAX_BOX && y<MAX_BOX;
x=x +xdrc_table[drc] ,y=y +ydrc_table[drc]){
if(box_data[x][y].stone==NO_STONE)break;
if(flag==0){
if(box_data[x][y].stone==xstone)flag=1;
else break;
}
else{
if(box_data[x][y].stone== stone)return 1;
}
}
}
return 0;
}
void Othello::WriteStoneTotal()
{
char tmp[31];
sprintf(tmp,"● %2d 個",total_black_stone);
SetDlgItemText(IDBLACK_STATUS,tmp);
sprintf(tmp,"○ %2d 個",total_white_stone);
SetDlgItemText(IDWHITE_STATUS,tmp);
}
void Othello::PutStone(int stone,int xput,int yput)
{
CClientDC dc(this);
int drc,x,y,loop,xstone,*add,*sub;
if(stone==BLACK_STONE){
xstone=WHITE_STONE;
add= &total_black_stone;
sub= &total_white_stone;
}
else{
xstone=BLACK_STONE;
add= &total_white_stone;
sub= &total_black_stone;
}
box_data[xput][yput].stone=stone;
(*add)++;
WriteStone(&dc,xput,yput);
Delay(0.3);
for(drc=0;drc<MAX_DRC;drc++){
if(!IsEnablePutStone(stone,xput,yput,0,drc))continue;
for(x=xput+xdrc_table[drc] , y=yput+ydrc_table[drc];
x>=0 && y>=0 && x<MAX_BOX && y<MAX_BOX;
x=x +xdrc_table[drc] , y=y +ydrc_table[drc]){
if(box_data[x][y].stone==stone)break;
box_data[x][y].stone=stone;
(*add)++;
(*sub)--;
WriteStone(&dc,x,y);
Delay(0.3);
}
}
WriteStoneTotal();
for(loop=0;loop<=1;loop++){
if(current_stone==BLACK_STONE)current_stone=WHITE_STONE;
else current_stone=BLACK_STONE;
if(EnablePutStoneAnywhere())break;
}
if(loop>1){
if(!WriteEndMessage()){
PostMessage(WM_CLOSE);
return;
}
InitializeBoxData();
WriteStoneTotal();
Redraw(&dc);
PostMessage(WM_COMMAND,IDASK_ORDER);
return;
}
WriteOrder();
if(player_stone==current_stone)PurgeLButtonDownMessage();
else PostMessage(WM_COMMAND,IDTHINK_PUT_STONE);
}
void Othello::Delay(double delta_time)
{
DWORD to,dt;
dt=(int)(delta_time*1000.0);
to=GetTickCount();
while(1){
if(GetTickCount()-to>dt)break;
}
}
void Othello::WriteOrder()
{
char tmp[81];
if(current_stone==player_stone){
sprintf(
tmp,"あなたの %s 石の番です",
current_stone==BLACK_STONE ? "●" : "○");
}
else{
strcpy(tmp,"お待ち下さい");
}
SetDlgItemText(IDMESSAGE,tmp);
}
int Othello::WriteEndMessage()
{
char tmp[81];
int diff;
diff=player_stone==WHITE_STONE ? total_white_stone-total_black_stone
: total_black_stone-total_white_stone;
if(diff< 0)sprintf(tmp,"あなたの %d 個の負けです。",-diff);
else if(diff==0)strcpy (tmp,"引き分けです。");
else sprintf(tmp,"あなたの %d 個の勝ちです。", diff);
strcat(tmp,"\nもう一度勝負しますか?");
if(MessageBox(tmp,"終了",MB_YESNO)==IDYES)return 1;
else return 0;
}
int Othello::EnablePutStoneAnywhere()
{
int x,y;
for(x=0;x<MAX_BOX;x++){
for(y=0;y<MAX_BOX;y++){
if(IsEnablePutStone(current_stone,x,y))return 1;
}
}
return 0;
}
afx_msg void Othello::OnThinkPutStone()
{
CClientDC dc(this);
int x,y;
for(x=0;x<MAX_BOX;x++){
for(y=0;y<MAX_BOX;y++){
if(box_data[x][y].stone!=NO_STONE)continue;
if(IsEnablePutStone(cpu_stone,x,y)){
Delay(1.0);
box_data[x][y].stone=NOTIFY_STONE;
WriteStone(&dc,x,y);
box_data[x][y].stone=NO_STONE;
Delay(0.3);
PostMessage(WM_COMMAND,IDPUT_STONE,(((cpu_stone<<8)|x)<<8)|y);
return;
}
}
}
}
void Othello::PurgeLButtonDownMessage()
{
MSG msg;
while(PeekMessage(&msg,m_hWnd,WM_LBUTTONDOWN,WM_LBUTTONDOWN,PM_REMOVE));
}
BEGIN_MESSAGE_MAP(Othello,CDialog)
ON_COMMAND(IDASK_ORDER,OnAskOrder)
ON_COMMAND(IDPUT_STONE,OnPutStone)
ON_COMMAND(IDTHINK_PUT_STONE,OnThinkPutStone)
ON_COMMAND(IDOK ,Default)
ON_COMMAND(IDCANCEL,Default)
ON_WM_LBUTTONDOWN()
ON_WM_PAINT()
ON_WM_CLOSE()
END_MESSAGE_MAP()
OthelloApp OthelloApp;
今回からは少しずつですが計算機を賢くすることに専念しましょう。前回までの計算機は、X方向Y方向を順に検査していき最初に発見したマス目に石を打っていました。まずこれをなんとかしましょう。考えられることは、石を打てるマス目を全部探してからランダムにマス目を決定する方法です。この方法を試してみましょう。
Othello クラスの宣言に、
struct able_data_strct{
int x,y;
};
struct able_data_strct able_data[MAX_BOX * MAX_BOX];
int lable_data;
を追加します。able_data はXとYの値のみをもった構造体の1次元の配列でマス目の数だけ要素数を確保しておきます。lable_data は able_data の最後のインデックス番号を格納する目的に使う変数です。更にランダムな値を返す関数も追加しておきます。
int Random(int total);
この関数は、
int Othello::Random(int total)
{
return GetTickCount()%total;
}
としておき、現在の時間のカウントを取得した後、able_data の総数での剰余を返すようにします。このようにしておけば必ず配列の要素番号を返すことになります。
次に問題の OnThinkPutStone() は次のようにします。
afx_msg void Othello::OnThinkPutStone()
{
CClientDC dc(this);
int x,y,i;
lable_data= -1;
for(x=0;x<MAX_BOX;x++){
for(y=0;y<MAX_BOX;y++){
if(box_data[x][y].stone!=NO_STONE )continue;
if(!IsEnablePutStone(cpu_stone,x,y))continue;
lable_data++;
able_data[lable_data].x=x;
able_data[lable_data].y=y;
}
}
i=Random(lable_data+1);
x=able_data[i].x;
y=able_data[i].y;
Delay(1.0);
box_data[x][y].stone=NOTIFY_STONE;
WriteStone(&dc,x,y);
box_data[x][y].stone=NO_STONE;
Delay(0.3);
PostMessage(WM_COMMAND,IDPUT_STONE,(((cpu_stone<<8)|x)<<8)|y);
}
これはじっくりと見ていただければ理解できると思います。
ここまで改修してプログラムを実行してみると前回よりもだいぶ良くなったのが分かると思います。といっても素人とプレイしているのと同じ感じだと思います。
もう少し賢くしてみましょう。次に考えられるのは各マスに点数を付けておき、出来る限り点数の高いマスに打つようにしましょう。点数が同じマスがあったときにはランダムに決定することにします。まず Othello クラスの box_data_strct 構造体にメンバーを1つ追加して次のようにしましょう。
struct box_data_strct{
RECT rect;
int stone;
int point;
};
この構造体のメンバー point に各マス目の点数を設定しておくようにします。次に、
OnInitDialog 関数に、
int point_table[MAX_BOX][MAX_BOX]={
10, 1, 2, 3, 3, 2, 1,10,
1, 1, 4, 4, 4, 4, 1, 1,
2, 4, 5, 5, 5, 5, 4, 2,
3, 4, 5, 0, 0, 5, 4, 3,
3, 4, 5, 0, 0, 5, 4, 3,
2, 4, 5, 5, 5, 5, 4, 2,
1, 1, 4, 4, 4, 4, 1, 1,
10, 1, 2, 3, 3, 2, 1,10
};
を追加し、各マス目の矩形のサイズを設定するループにマス目の点数を設定する行を次のように追加しましょう。
for(x=0;x<MAX_BOX;x++){
for(y=0;y<MAX_BOX;y++){
:
:
box_data[x][y].point=point_table[x][y]; //追加。
}
}
ここまででマス目の点数を設定できましたが、まだ点数に従って石は打てません。
OnThinkPutStone を直さなければなりません。まず able_data_strct 構造体にもint point; を追加して、
struct able_data_strct{
int x,y;
int point;
};
のようにします。次に OnThinkPutStone を、
afx_msg void Othello::OnThinkPutStone()
{
:
:
for(x=0;x<MAX_BOX;x++){
for(y=0;y<MAX_BOX;y++){
:
:
able_data[lable_data].point=box_data[x][y].point;
}
}
SortAbleData();
for(i=0;i<lable_data;i++){
if(able_data[i].point>able_data[i+1].point){
lable_data=i;
break;
}
}
:
:
とし、able_data に1つ要素を追加するときにマス目の点数も付加します。すべてのマス目を検査した後、SortAbleData() 関数でポイントの大きい順に並べ直し、点数の最大数のみを配列の要素とします。後の処理は今までと同じです。
SortAbleData() は未定義ですので Othello クラスの宣言に、
void SortAbleData();
を追加します、そして SortAbleData() は、
void Othello::SortAbleData()
{
struct able_data_strct tmp;
int i,j,gap;
gap=(lable_data+1)/2;
while(gap){
i=gap;
while(i<=lable_data){
j=i-gap;
while( j>=0 && CompareAbleData(able_data,j,j+gap)==1){
tmp =able_data[j ];
able_data[j ]=able_data[j+gap];
able_data[j+gap]=tmp;
j-=gap;
}
i++;
}
gap/=2;
}
}
とします。ソート方法は要素数が少ないのでどんなアルゴリズムでも構いませんが、ここではシェルソートを使用しています。CompareAbleData() は未定義ですので、これもOthello クラスに、
int CompareAbleData(struct able_data_strct *data,int i,int j);
を追加し、
int Othello::CompareAbleData(struct able_data_strct *data,int i,int j)
{
if(data[i].point>data[j].point)return 0;
if(data[i].point<data[j].point)return 1;
return 0;
}
とし点数の大きい順に並ぶように値を返します。
ここまででこの改修は終了です。試しに実行してみると、なんとなく人間臭くなってきたように思えませんか。それでもちょっと頭が悪いのかコーナーを取れるとなると喜んで取りに来るのが可愛いと言えば言えなくもありません。
いつもホームページの私のこのコーナーを編集してくれる美人のお姉さんの話によると、オセロは序盤戦のときにはあまり多く石をひっくり返さない方がいいということなので、この機能を追加してみましょう。OnThinkPutStone の SortAbleData(); の行の前に
ThinkAbleData();
を追加します。この関数も未定義ですので Othello クラスの宣言に、
void ThinkAbleData();
を追加しておきます。この ThinkAbleData() 内で石を打てるマス目の点数を調整しようという魂胆です。
局面を序盤、中盤、終盤の3つに分けることにして、打ってある石の総数が36個未満のときに序盤、36個以上48個未満のときに中盤、それ以外のときに終盤ということにします。この切り分けは適当で、盤面の一つ内側のマスの数が6×6ですので、序盤はだいたい36ぐらい、通常最後の攻防になる4つのコーナーの4つずつのマス目の総数が4×4ですので全体のマス目の数から引くと 8×8ー4×4=48 ということで適当に決めました。この切り分けの違いで強くなったり弱くなったりする可能性があります。そこで、
#define PERIOD12 36
#define PERIOD23 48
#define PERIOD1 1
#define PERIOD2 2
#define PERIOD3 3
を定義しておきましょう。
#define PERIOD12 36
は序盤と中盤の境、
#define PERIOD23 48
は中盤と終盤の境の石の総数です。
#define PERIOD1 1
#define PERIOD2 2
#define PERIOD3 3
は序盤、中盤、終盤を示す値とします。
このように決め、ThinkAbleData() を、
void Othello::ThinkAbleData()
{
int total;
total=total_black_stone+total_white_stone;
if(total<PERIOD12)ThinkAbleDataPass1(PERIOD1);
}
とし、序盤戦の打てるマス目の点数の操作を行ないましょう。ThinkAbleDataPass1()は未定義ですので Othello クラスの宣言に、
void ThinkAbleDataPass1(int period);
を追加します。
ThinkAbleDataPass1() を考えていきましょう。あまりいっぱいひっくり返さないということは、相手に打つ場所の選択の余地を出来る限り与えないということになりますので、ひっくり返す石の少ない場所で、なおかつひっくり返す方向の少ないものを優先させればいいということになります。これを実現するには、現在打てるマス目すべてについて、その数を検査し、大きいもの程点数を下げる。という処理を行えばなんとかなりそうです。そこで、ThinkAbleDataPass1() を次のようにしました。
void Othello::ThinkAbleDataPass1(int period)
{
#define POINT_DECREASE_PITCH 5
struct rbdata_strct{
int index,reverse,branch;
struct rbdata_strct *next;
};
struct rbdata_strct rbdata[MAX_BOX * MAX_BOX];
struct rbdata_strct *top_rbdata,*rbp,*rbc;
int i,x,y;
top_rbdata=NULL;
for(i=0;i<=lable_data;i++){
GetReverseBranchCount(
cpu_stone,able_data[i].x,able_data[i].y,
&rbdata[i].reverse,&rbdata[i].branch);
rbdata[i].index=i;
rbdata[i].next =NULL;
rbp=NULL;
rbc=top_rbdata;
while(rbc){
if(rbdata[i].reverse < rbc->reverse)break;
if(rbdata[i].reverse == rbc->reverse
&& rbdata[i].branch == rbc->branch )break;
rbp=rbc;
rbc=rbc->next;
}
rbdata[i].next=rbc;
if(rbp==NULL)top_rbdata=rbdata+i;
else rbp->next=rbdata+i;
}
i=0;
rbp=NULL;
rbc=top_rbdata;
while(rbc){
if(rbp){
if(rbc->reverse!=rbp->reverse
|| rbc->branch !=rbp->branch )i-=POINT_DECREASE_PITCH;
}
able_data[rbc->index].point+=i;
rbp=rbc;
rbc=rbc->next;
}
#undef POINT_DECREASE_PITCH
}
ちょっと難しいので説明が必要ですね。
#define POINT_DECREASE_PITCH 5
は点数を下げる幅の値です。
struct rbdata_strct{
int index,reverse,branch;
struct rbdata_strct *next;
};
は、石を打てるマス目のひっくり返す数と方向(分岐)リストの構造体で、index にはable_data の配列の要素番号を、reverse にはひっくり返す数、branch には分岐する数を入れます。next は次のリストの要素へのポインタです。
struct rbdata_strct rbdata[MAX_BOX * MAX_BOX];
は、rbdata_strct 構造体の配列です。new と delete の演算子を使ってもいいのですがリストの要素数が最大でも64個しか必要ないので配列にしてしまいました。
struct rbdata_strct *top_rbdata,*rbp,*rbc;
の top_rbdata はリストの先頭へのポインタです。rbp と rbc は処理用に使用します。
int i;
これも処理用のものです。
top_rbdata=NULL;
リストの先頭へのポインタを初期化します。
for(i=0;i<=lable_data;i++){
GetReverseBranchCount(
cpu_stone,able_data[i].x,able_data[i].y,
&rbdata[i].reverse,&rbdata[i].branch);
の GetReverseBranchCount() は未定義ですが、指定したマス目に石を置いたときにひっくり返る数と分岐する数を返す機能を持った関数にします。
rbdata[i].index=i;
rbdata[i].next =NULL;
able_data の配列の要素番号を設定し、次のリストの要素へのポインタを初期化します。
rbp=NULL;
rbc=top_rbdata;
while(rbc){
if(rbdata[i].reverse < rbc->reverse)break;
if(rbdata[i].reverse == rbc->reverse
&& rbdata[i].branch == rbc->branch )break;
rbp=rbc;
rbc=rbc->next;
}
このループはリストの先頭から検査していき、今追加したリストの要素をリストのどこに追加するかを調べています。
rbdata[i].next=rbc;
if(rbp==NULL)top_rbdata=rbdata+i;
else rbp->next=rbdata+i;
リストに追加します。上のループが終了したとき、rbp がヌルになっていればリストのトップに挿入することになり、そうでないときには直前のリストの要素の次に挿入するようにします。
}
このループが終了すると、リストはひっくり返る数の小さい順でなおかつ分岐数の小さい順で作成されます。
i=0;
rbp=NULL;
rbc=top_rbdata;
while(rbc){
if(rbp){
if(rbc->reverse!=rbp->reverse
|| rbc->branch !=rbp->branch )i-=POINT_DECREASE_PITCH;
}
1つ前のリストの要素のひっくり返る数と分岐数が違うときには、点数に追加する数を減らします。
able_data[rbc->index].point+=i;
ここで点数を減らします。
rbp=rbc;
rbc=rbc->next;
}
#undef POINT_DECREASE_PITCH
}
未定義になっていた GetReverseBranchCount() は Othello クラスの宣言に、
void GetReverseBranchCount(
int stone,int xput,int yput,int *reverse,int *branch);
を追加します。そして、
void Othello::GetReverseBranchCount(
int stone,int xput,int yput,int *reverse,int *branch)
{
int drc,x,y,flag,xstone;
int rev;
*reverse=0;
*branch =0;
xstone=stone==BLACK_STONE ? WHITE_STONE : BLACK_STONE;
for(drc=0;drc<MAX_DRC;drc++){
flag=0;
for(x=xput+xdrc_table[drc] , y=yput+ydrc_table[drc];
x>=0 && y>=0 && x<MAX_BOX && y<MAX_BOX;
x=x +xdrc_table[drc] ,y=y +ydrc_table[drc]){
if(box_data[x][y].stone==NO_STONE)break;
if(flag==0){
if(box_data[x][y].stone==stone)break;
flag=1;
rev =1;
}
else{
if(box_data[x][y].stone==xstone){
rev++;
}
else{
(*reverse)+=rev;
(*branch )+=1 ;
break;
}
}
}
}
}
とします。この関数は PutStone() をちょっと変更したものですので特に説明の必要はないでしょう。
ここまでの状態で実行してみると、結構良い感じになってきました。
続きはまた次回にしましょう。






