コラム
2026/10/30
プログラミングについて 第136回目
『たまにはオセロで遊ぼうよ! その8』
今回も前回の続きです。リソースは何も変更がないので、プログラム本体のリストです。
#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 };
#define PERIOD12 36
#define PERIOD23 48
#define PERIOD1 1
#define PERIOD2 2
#define PERIOD3 3
class Othello : public CDialog
{
private:
int stone_size;
struct box_data_strct{
RECT rect;
int stone;
int point;
};
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;
struct able_data_strct{
int x,y;
int point;
};
struct able_data_strct able_data[MAX_BOX*MAX_BOX];
int lable_data;
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();
int Random(int total);
void SortAbleData();
int CompareAbleData(struct able_data_strct *data,int i,int j);
void ThinkAbleData();
void ThinkAbleDataPass1(int period);
void GetReverseBranchCount(
int stone,int xput,int yput,int *reverse,int *branch);
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()
{
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
};
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);
box_data[x][y].point=point_table[x][y];
}
}
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;
}
void Othello::PurgeLButtonDownMessage()
{
MSG msg;
while(PeekMessage(&msg,m_hWnd,WM_LBUTTONDOWN,WM_LBUTTONDOWN,PM_REMOVE));
}
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;
able_data[lable_data].point=box_data[x][y].point;
}
}
ThinkAbleData();
SortAbleData();
for(i=0;i<lable_data;i++){
if(able_data[i].point>able_data[i+1].point){
lable_data=i;
break;
}
}
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);
}
int Othello::Random(int total)
{
return GetTickCount()%total;
}
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;
}
}
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;
}
void Othello::ThinkAbleData()
{
int total;
total=total_black_stone+total_white_stone;
if(total<PERIOD12)ThinkAbleDataPass1(PERIOD1);
}
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;
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
}
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;
}
}
}
}
}
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;
だいぶ人間らしくなってきましたが、問題が2つ残っています。1つはひっくり返る数と分岐数が小さいからといって相手にコーナーを打てるマス目に打ってしまったり、ひっくり返る数や分岐数が小さいのでこれが仇になって自分の石が全部なくなってしまい、序盤戦で負けてしまうことがあることです。少なくともこのようにならないように対策を行なっておかなければなりません。そこで ThinkAbleDataPass1() の最初に次の処理を追加します。
#define BAD_DECREASE_PITCH 10
struct box_data_strct tmp_box_data1[MAX_BOX][MAX_BOX];
struct box_data_strct tmp_box_data2[MAX_BOX][MAX_BOX];
int x,y;
for(i=0;i<=lable_data;i++){
memcpy(tmp_box_data1,box_data,sizeof(box_data));
TempPutStone(
tmp_box_data1,cpu_stone,able_data[i].x,able_data[i].y);
for(x=0;x<MAX_BOX;x++){
for(y=0;y<MAX_BOX;y++){
if(!IsEnablePutStone(player_stone,x,y,1,-1,tmp_box_data1))continue;
if((x==0 || x==MAX_BOX-1)
&& (y==0 || y==MAX_BOX-1)){
able_data[i].point-=BAD_DECREASE_PITCH;
continue;
}
memcpy(tmp_box_data2,tmp_box_data1,sizeof(tmp_box_data1));
TempPutStone(tmp_box_data2,player_stone,x,y);
if(GetStoneCount(tmp_box_data2,cpu_stone)==0)able_data[i].point-=10;
}
}
}
if(period!=PERIOD1)return;
これも説明が必要です。目的の処理を行なう方法にもいろいろとあるとは思いますが、ここでは実際に石をひっくり返していって検査することにしました。
struct box_data_strct tmp_box_data1[MAX_BOX][MAX_BOX];
struct box_data_strct tmp_box_data2[MAX_BOX][MAX_BOX];
検査に使うマス目の配列です。
int x,y;
for(i=0;i<=lable_data;i++){
memcpy(tmp_box_data1,box_data,sizeof(box_data));
現在のマス目の内容をすべて tmp_box_data1 にコピーします。
TempPutStone(
tmp_box_data1,cpu_stone,able_data[i].x,able_data[i].y);
コピーした tmp_box_data1 に自分の石を置きます。この TempPutStone() は未定義ですが、PutStone の石をひっくり返す部分は全く同じで、指定した box_data_strct 構造体の配列に対してそれを行なうものです。
for(x=0;x<MAX_BOX;x++){
for(y=0;y<MAX_BOX;y++){
if(!IsEnablePutStone(player_stone,x,y,1,-1,tmp_box_data1))continue;
ここで相手が石を打てるか否かを調べます。
IsEnablePutStone() はこれまでは検査する対象の box_data_strct 構造体の配列は固定でしたが、指定した配列に対しても行なえるように変更します。その変更は後で説明します。
if((x==0 || x==MAX_BOX-1)
&& (y==0 || y==MAX_BOX-1)){
able_data[i].point-=BAD_DECREASE_PITCH;
continue;
}
石が打てるとき、マスの位置がコーナーだったら BAD_DECREASE_PITCH だけ点数を減らします。
memcpy(tmp_box_data2,tmp_box_data1,sizeof(tmp_box_data1));
相手が次に石を打ったら自分の石がなくなるか否かを検査するため、tmp_box_data1 の内容を tmp_box_data2 にコピーします。
TempPutStone(tmp_box_data2,player_stone,x,y);
相手の石を tmp_box_data2 に打ちます。
if(GetStoneCount(tmp_box_data2,cpu_stone)==0){
able_data[i].point-=BAD_DECREASE_PITCH;
}
自分の石の個数が0になってしまうときには、 BAD_DECREASE_PITCH だけ点数を減らします。
}
}
}
if(period!=PERIOD1)return;
局面が序盤でないときには、ひっくり返す数や分岐数を小さくする処理を行なわないようにするため処理を呼び出し側に返します。
未定義だった TempPutStone() は Othello クラスの宣言に、
void TempPutStone(
struct box_data_strct (*tmp_box_data)[MAX_BOX],
int stone,int xput,int yput);
と追加し、
void Othello::TempPutStone(
struct box_data_strct (*tmp_box_data)[MAX_BOX],
int stone,int xput,int yput)
{
int drc,x,y,xstone;
xstone=stone==BLACK_STONE ? WHITE_STONE : BLACK_STONE;
tmp_box_data[xput][yput].stone=stone;
for(drc=0;drc<MAX_DRC;drc++){
if(!IsEnablePutStone(stone,xput,yput,0,drc,tmp_box_data))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(tmp_box_data[x][y].stone==stone)break;
tmp_box_data[x][y].stone=stone;
}
}
}
とします。これは PutStone の不要な部分を削除したものですので説明の必要はないでしょう。次に IsEnablePutStone() は任意の box_data_strct 構造体の配列も検査できるようにするため、
int IsEnablePutStone(
int stone,int xput,int yput,
int check_this=1,int drc= -1,
struct box_data_strct (*box_data_arg)[MAX_BOX]=NULL);
と Othello クラスの宣言を変更し、
int Othello::IsEnablePutStone(
int stone,int xput,int yput,int check_this,int drc,
struct box_data_strct (*box_data_arg)[MAX_BOX])
{
int drc_from,drc_to,x,y,flag,xstone;
if(!box_data_arg)box_data_arg=box_data;
if(check_this && box_data_arg[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_arg[x][y].stone==NO_STONE)break;
if(flag==0){
if(box_data_arg[x][y].stone==xstone)flag=1;
else break;
}
else{
if(box_data_arg[x][y].stone== stone)return 1;
}
}
}
return 0;
}
と変更します。
GetStoneCount() も未定義ですので Othello クラスの宣言に、
int GetStoneCount(struct box_data_strct (*box_data)[MAX_BOX],int stone);
と追加し、
int Othello::GetStoneCount(
struct box_data_strct (*box_data)[MAX_BOX],int stone)
{
int x,y,total;
total=0;
for(y=0;y<MAX_BOX;y++){
for(x=0;x<MAX_BOX;x++){
if(box_data[x][y].stone==stone)total++;
}
}
return total;
}
とします。これは説明の必要はないでしょう。
ここまでの状態で実行してみると序盤で間抜けなことはしなくなりました。
次は中盤の処理です。とりあえずは中盤はこれといったことをしないので、
ThinkAbleData() を次のようにします。
void Othello::ThinkAbleData()
{
int total;
total=total_black_stone+total_white_stone;
if(total<PERIOD12)ThinkAbleDataPass1(PERIOD1);
else if(total<PERIOD23)ThinkAbleDataPass1(PERIOD2);
}
序盤と中盤で共通で行なった方が良い処理があります。それは盤面の周辺に石を打てるときです。この処理は ThinkAbleDataPass1() よりも優先して処理しなければならないため、ThinkAbleDataPass1() の関数名を ThinkAbleDataPass2() に変更し、これから追加する関数を ThinkAbleDataPass1() とすることにします。
Othello クラスの宣言を次のように ThinkAbleDataPass1() を ThinkAbleDataPass2()に名称変更し、ThinkAbleDataPass1() を追加します。
int ThinkAbleDataPass1();
void ThinkAbleDataPass2(int period);
当然ですが、これまで ThinkAbleDataPass1 だった他の部分も名称変更をしておきます。
そして ThinkAbleData() は次のようにします。
void Othello::ThinkAbleData()
{
int total;
total=total_black_stone+total_white_stone;
if(total<PERIOD12){
if(ThinkAbleDataPass1())return;
ThinkAbleDataPass2(PERIOD1);
}
else if(total<PERIOD23){
ThinkAbleDataPass1();
}
else{
ThinkAbleDataPass1();
}
}
序盤であっても ThinkAbleDataPass1() が真を返したときには緊急度の高いものがあったということになるので、ThinkAbleDataPass2() は実行せずに呼び出し側に戻るようにします。終盤はまだですので、動作を確認するため ThinkAbleDataPass1() を実行することにします。
この ThinkAbleDataPass1() ではどのようなことを行なうかというと、盤面の周辺の状態によってゲームの大勢が決定されてしまいますので、できる限り不利な状態にしないようにすることが主目的になります。例えば、今プレイヤーが●だったときに(□は石がないマス)計算機が次のように○を置くのは変ですね。
□□□□●○□□
また、下のようなときにはコーナーを取れるマスに打つべきですね。
□●○●□●□□
このような処理を行うのです。これもマス目の点数を操作することで行います。
周辺の一辺のみを考えてみると、単純計算で各マスの1つがこれから石を打つマスとすると、各マスには □●○ の3つの状態が考えられるので、8×3×3×3×3×3×3×3=17496 通りの組み合わせが考えられます。この組み合わせすべてについて点数を付けるのは絶望的な数ですので、なんとか工夫しなければなりません。そこで、
石の置かれていないマス n
プレイヤーの石 p
計算機の石 c
計算機の打てるマス x
と記号を決めます。こう決めると上の2つの例は、
nnnnpxnn
npxpnpnn
になります(x となっているのはこれから置こうとしているマスだからです)。少しは単純化しましたが、もう1つ単純化するための工夫をしましょう。同じ状態が連続(1つ以上)しているときには大文字で表現することにすると、
NpxN
npxpnpN
と表現できます。最初の例は次のような場合にも同様のことが言えますので、
□□□●●○□□
□□●●●○□□
NPxN
と表現すれば1つの表現で多くの状態を定義できます。更に、
●□□□●○□□
●□□□●○□●
といった状態と区別するために、コーナーから始まる場合とコーナーで終了する場合の表現もできるように、
コーナー $
と定義します。こうすると、
●□□□●○□□
は、
$PNPxN$
と表現でき、
●□□□●○□●
は、
$PNPxNP$
と表現できます。このようにパターンを定義しておき、それに当てはまったときに点数を操作するようにします。周辺の処理を ThinkAbleDataPass1() で行ないますので、OnInitDialog() で、各マス目に付けた点数は、
int point_table[MAX_BOX][MAX_BOX]={
0, 0, 2, 3, 3, 2, 0, 0,
0, 0, 4, 4, 4, 4, 0, 0,
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,
0, 0, 4, 4, 4, 4, 0, 0,
0, 0, 2, 3, 3, 2, 0, 0
};
のように変更しておきます(あまり意味はありませんが)。
次は ThinkAbleDataPass1() 関数です。
int Othello::ThinkAbleDataPass1()
{
struct pattern_data_strct{
char *pattern;
int add;
};
struct pattern_data_strct pattern_data[]={
{ "",0 }
};
int able,x,y,dx,dy,add,i,flag;
char sts[MAX_BOX+1];
sts[MAX_BOX]='\0';
flag=0;
for(able=0;able<=lable_data;able++){
if(able_data[able].x!=0 && able_data[able].x!=MAX_BOX-1
&& able_data[able].y!=0 && able_data[able].y!=MAX_BOX-1)continue;
if(able_data[able].x==0 || able_data[able].x==MAX_BOX-1){
y=0; dy=1; x=able_data[able].x; dx=0;
}
else{
x=0; dx=1; y=able_data[able].y; dy=0;
}
for(i=0;i<MAX_BOX;i++,x+=dx,y+=dy){
if(x==able_data[able].x && y==able_data[able].y)sts[i]='x';
else if(box_data[x][y].stone ==player_stone )sts[i]='p';
else if(box_data[x][y].stone == cpu_stone )sts[i]='c';
else sts[i]='n';
}
add=0;
for(i=0;pattern_data[i].pattern[0];i++){
if(IsExistPattern(sts,pattern_data[i].pattern)){
if(abs(pattern_data[i].add)>abs(add))add=pattern_data[i].add;
}
}
able_data[able].point+=add;
if(add<= -10 || add>=10)flag=1;
}
return flag;
}
この関数は説明が必要です。
struct pattern_data_strct{
char *pattern;
int add;
};
struct pattern_data_strct pattern_data[]={
{ "",0 }
};
この部分は先程お話しした周辺の一辺のパターンとその点数の定義です。今は、パターンの定義の終了を示すデータのみを定義してあります。
int able,x,y,dx,dy,add,i,flag;
char sts[MAX_BOX+1];
の sts は一辺の現在の状態を入れる配列です。
sts[MAX_BOX]='\0';
では、一辺の状態が単純な文字列にしますので、その文字列の最後をヌルにしておきます。
flag=0;
for(able=0;able<=lable_data;able++){
if(able_data[able].x!=0 && able_data[able].x!=MAX_BOX-1
&& able_data[able].y!=0 && able_data[able].y!=MAX_BOX-1)continue;
打てるマス目が周辺でないときには次の候補にし、打てるときには次以下の処理をします。
if(able_data[able].x==0 || able_data[able].x==MAX_BOX-1){
y=0; dy=1; x=able_data[able].x; dx=0;
}
else{
x=0; dx=1; y=able_data[able].y; dy=0;
}
ここでは一辺の状態を生成するためのマス目をスキャンする位置と方向を決定しています。
for(i=0;i<MAX_BOX;i++,x+=dx,y+=dy){
if(x==able_data[able].x && y==able_data[able].y)sts[i]='x';
else if(box_data[x][y].stone ==player_stone )sts[i]='p';
else if(box_data[x][y].stone == cpu_stone )sts[i]='c';
else sts[i]='n';
}
ここで一辺のパターンを生成します。
add=0;
for(i=0;pattern_data[i].pattern[0];i++){
if(IsExistPattern(sts,pattern_data[i].pattern)){
if(abs(pattern_data[i].add)>abs(add))add=pattern_data[i].add;
}
}
このループでは定義した各パターンに一致するパターンが一辺に存在するか否かを検査し、一致したときには点数の絶対値の最大値を取得します。IsExistPattern() はパターンが存在するか否かを検査する関数ですが未定義です。
able_data[able].point+=add;
取得した点数を able_data に加算します。
if(add<= -10 || add>=10)flag=1;
取得した値の絶対値が10以上のときには緊急度の高いマス目とし flag をセットします。
}
return flag;
flag の値を返します。
次に未定義の IsExistPattern() です。この関数は一辺の状態の中にパターンがあるか否かを検査するのですが、パターンの定義が部分的なものの場合は、必ずしも全体が一致していなくてもパターンを満足する場合に真を返します。またパターンの検査は一辺の状態の反対側からも検査するようにします。こうすることで、定義するパターンの量を減らす事ができます。
まず Othello クラスの宣言に次の行を追加します。
int IsExistPattern(char *string,char *pattern);
そして、
int Othello::IsExistPattern(char *string,char *pattern)
{
char tmp[81],c,*str,*s,*p;
int cont,loop;
strcpy(tmp,string);
for(loop=0;loop<=1;loop++){
if(loop==1){
int i,j,k;
j=strlen(tmp)-1;
k=j/2;
for(i=0;i<=k;i++,j--){ c=tmp[i]; tmp[i]=tmp[j]; tmp[j]=c; }
}
str=tmp;
while(*str){
s=str;
p=pattern;
c= *p++; if(c=='$')c= *p++;
if(c=='N' || c=='P' || c=='C'){ cont=1; c+='a'-'A'; }
else { cont=0; }
while(1){
if(*s==c || c=='*'){
if(!*p)return 1;
if(!*++s && *p=='$')return 1;
if(cont){
cont=2;
continue;
}
}
else{
if(cont!=2)break;
if(!*p )break;
}
c= *p++;
if(c=='N' || c=='P' || c=='C'){ cont=1; c+='a'-'A'; }
else { cont=0; }
}
if(*pattern=='$')break;
else str++;
}
}
return 0;
}
とします。これも説明が必要です。
strcpy(tmp,string);
変数 tmp に string (一辺の状態)をコピーします。
一辺の状態そのままと、反転した状態との2回検査を行ないますので、このループは2回繰返されます。
for(loop=0;loop<=1;loop++){
if(loop==1){
int i,j,k;
j=strlen(tmp)-1;
k=j/2;
for(i=0;i<=k;i++,j--){ c=tmp[i]; tmp[i]=tmp[j]; tmp[j]=c; }
}
ここでは、一辺の状態を反転しています。
str=tmp;
一辺の状態の先頭へのポインタを str に設定します。
while(*str){
s=str;
p=pattern;
c= *p++; if(c=='$')c= *p++;
c にパターンの先頭文字を代入します。もし先頭文字が $ のときには端を示すものですので、次の1文字を c に代入します。
if(c=='N' || c=='P' || c=='C'){ cont=1; c+='a'-'A'; }
else { cont=0; }
パターンの文字が大文字のときには連続していることを示すので、cont を1にし、文字を小文字にします。小文字のときには cont を 0 にします。
while(1){
if(*s==c || c=='*'){
パターンの文字と一辺の文字が一致するか、パターンの文字が * のとき(* はどんな文字があってもいいことを意味する文字)の処理です。
if(!*p)return 1;
パターンがこれ以上ないときには一致していますので真を返します。
if(!*++s && *p=='$')return 1;
一辺の次の文字がなく、パターンの次の文字が $(一辺の端)のときには一致していますので真を返します。
if(cont){
cont=2;
continue;
}
cont が真のときには連続していることを示しますので1つ以上一致したことを示すために cont を 2 にし、一辺の次の文字の検査を繰返します。
}
else{
ここは一致しなかったときの処理です。
if(cont!=2)break;
cont が 2 以外のときには一致していないので、このループを終了させます。
if(!*p )break;
パターンがこれ以上ないときには、このループを終了させます。
}
c= *p++;
パターンの次の文字を c に代入します。
if(c=='N' || c=='P' || c=='C'){ cont=1; c+='a'-'A'; }
else { cont=0; }
c に代入した文字によって変数をセットします。これはこのループの前に行なった処理と同じです。
}
if(*pattern=='$')break;
else str++;
この行は、上のループでパターンが一致しなかったときで、*pattern 即ちパターンの先頭文字が $ のときには端から一致していなければならないので、これ以上検査を行わず、このループを終了させます。それ以外のときには一辺の次の文字からの検査を繰返します。
}
上のループが終了したときには一辺全体で一致していなかったときですので、もう一度このループを繰返します。次のループのときには、一辺の状態を反転してから検査を行ないます。
}
return 0;
結局一致しなかったので偽を返します。
ここまでで、ThinkAbleDataPass1() の機能はできました。しかし、問題はパターンです。最初にお話ししたように一辺のパターンの組み合わせは絶望的な数がありますので、それらすべてを網羅することは諦めました。結局は実際にプレイしてみて、変な動作をしないように1つ1つ定義していきました。次のものがそのパターンです。
{ "NxN" , 0 },
{ "NxPN" , -10 },
{ "NPxPN" , 5 },
{ "NPxCN" , -10 },
{ "NxCPN" , -20 },
{ "NxPCN" , 15 },
{ "NxPCPN" , -10 },
{ "NxPCP$" , -10 },
{ "NxCN" , 0 },
{ "NPxCPN" , 10 },
{ "PxCP" , 10 },
{ "NPxPCN" , -10 },
{ "NPxPCPN" , 10 },
{ "NPCPxN" , -10 },
{ "$x" , 20 },
{ "$CxN" , 10 },
{ "$CxP" , 10 },
{ "$CxPC" , 12 },
{ "$CPx" , 10 },
{ "$nPxPN" , 18 },
{ "$nPxP$" , 18 },
{ "$nPxCPN" , 18 },
{ "$nPxCP$" , 18 },
{ "$nPxPCN" , -10 },
{ "$nPxPC$" , 18 },
{ "$PxP" , 10 },
{ "$PCPxN$" , -10 },
{ "$PCxN$" , -10 },
{ "$CxCn" , 15 },
{ "$CxC$" , 5 },
{ "$PxCN" , -10 },
{ "$NxPCPC$" , -10 },
{ "$NxCPC$" , -10 },
{ "$CNxCP$" , -10 },
{ "CCxCN" , 10 },
{ "CCxCP$" , 10 },
{ "$CPCnN$" , -5 },
{ "$CPCn$" , -10 },
{ "$CxC$" , 10 },
{ "$PxNP" , -10 },
パターンの定義の順番は動作に影響を与えません。私はオセロは強い方ではなく、このパターンが最適かどうかは甚だ疑わしいので、興味のある方は色々と定義して試して下さい。
今回はここまでです。
第136回 プログラミングについて『たまにはオセロで遊ぼうよ!その8』
プログラミングについて 第136回目
『たまにはオセロで遊ぼうよ! その8』
今回も前回の続きです。リソースは何も変更がないので、プログラム本体のリストです。
#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 };
#define PERIOD12 36
#define PERIOD23 48
#define PERIOD1 1
#define PERIOD2 2
#define PERIOD3 3
class Othello : public CDialog
{
private:
int stone_size;
struct box_data_strct{
RECT rect;
int stone;
int point;
};
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;
struct able_data_strct{
int x,y;
int point;
};
struct able_data_strct able_data[MAX_BOX*MAX_BOX];
int lable_data;
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();
int Random(int total);
void SortAbleData();
int CompareAbleData(struct able_data_strct *data,int i,int j);
void ThinkAbleData();
void ThinkAbleDataPass1(int period);
void GetReverseBranchCount(
int stone,int xput,int yput,int *reverse,int *branch);
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()
{
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
};
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);
box_data[x][y].point=point_table[x][y];
}
}
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;
}
void Othello::PurgeLButtonDownMessage()
{
MSG msg;
while(PeekMessage(&msg,m_hWnd,WM_LBUTTONDOWN,WM_LBUTTONDOWN,PM_REMOVE));
}
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;
able_data[lable_data].point=box_data[x][y].point;
}
}
ThinkAbleData();
SortAbleData();
for(i=0;i<lable_data;i++){
if(able_data[i].point>able_data[i+1].point){
lable_data=i;
break;
}
}
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);
}
int Othello::Random(int total)
{
return GetTickCount()%total;
}
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;
}
}
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;
}
void Othello::ThinkAbleData()
{
int total;
total=total_black_stone+total_white_stone;
if(total<PERIOD12)ThinkAbleDataPass1(PERIOD1);
}
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;
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
}
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;
}
}
}
}
}
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;
だいぶ人間らしくなってきましたが、問題が2つ残っています。1つはひっくり返る数と分岐数が小さいからといって相手にコーナーを打てるマス目に打ってしまったり、ひっくり返る数や分岐数が小さいのでこれが仇になって自分の石が全部なくなってしまい、序盤戦で負けてしまうことがあることです。少なくともこのようにならないように対策を行なっておかなければなりません。そこで ThinkAbleDataPass1() の最初に次の処理を追加します。
#define BAD_DECREASE_PITCH 10
struct box_data_strct tmp_box_data1[MAX_BOX][MAX_BOX];
struct box_data_strct tmp_box_data2[MAX_BOX][MAX_BOX];
int x,y;
for(i=0;i<=lable_data;i++){
memcpy(tmp_box_data1,box_data,sizeof(box_data));
TempPutStone(
tmp_box_data1,cpu_stone,able_data[i].x,able_data[i].y);
for(x=0;x<MAX_BOX;x++){
for(y=0;y<MAX_BOX;y++){
if(!IsEnablePutStone(player_stone,x,y,1,-1,tmp_box_data1))continue;
if((x==0 || x==MAX_BOX-1)
&& (y==0 || y==MAX_BOX-1)){
able_data[i].point-=BAD_DECREASE_PITCH;
continue;
}
memcpy(tmp_box_data2,tmp_box_data1,sizeof(tmp_box_data1));
TempPutStone(tmp_box_data2,player_stone,x,y);
if(GetStoneCount(tmp_box_data2,cpu_stone)==0)able_data[i].point-=10;
}
}
}
if(period!=PERIOD1)return;
これも説明が必要です。目的の処理を行なう方法にもいろいろとあるとは思いますが、ここでは実際に石をひっくり返していって検査することにしました。
struct box_data_strct tmp_box_data1[MAX_BOX][MAX_BOX];
struct box_data_strct tmp_box_data2[MAX_BOX][MAX_BOX];
検査に使うマス目の配列です。
int x,y;
for(i=0;i<=lable_data;i++){
memcpy(tmp_box_data1,box_data,sizeof(box_data));
現在のマス目の内容をすべて tmp_box_data1 にコピーします。
TempPutStone(
tmp_box_data1,cpu_stone,able_data[i].x,able_data[i].y);
コピーした tmp_box_data1 に自分の石を置きます。この TempPutStone() は未定義ですが、PutStone の石をひっくり返す部分は全く同じで、指定した box_data_strct 構造体の配列に対してそれを行なうものです。
for(x=0;x<MAX_BOX;x++){
for(y=0;y<MAX_BOX;y++){
if(!IsEnablePutStone(player_stone,x,y,1,-1,tmp_box_data1))continue;
ここで相手が石を打てるか否かを調べます。
IsEnablePutStone() はこれまでは検査する対象の box_data_strct 構造体の配列は固定でしたが、指定した配列に対しても行なえるように変更します。その変更は後で説明します。
if((x==0 || x==MAX_BOX-1)
&& (y==0 || y==MAX_BOX-1)){
able_data[i].point-=BAD_DECREASE_PITCH;
continue;
}
石が打てるとき、マスの位置がコーナーだったら BAD_DECREASE_PITCH だけ点数を減らします。
memcpy(tmp_box_data2,tmp_box_data1,sizeof(tmp_box_data1));
相手が次に石を打ったら自分の石がなくなるか否かを検査するため、tmp_box_data1 の内容を tmp_box_data2 にコピーします。
TempPutStone(tmp_box_data2,player_stone,x,y);
相手の石を tmp_box_data2 に打ちます。
if(GetStoneCount(tmp_box_data2,cpu_stone)==0){
able_data[i].point-=BAD_DECREASE_PITCH;
}
自分の石の個数が0になってしまうときには、 BAD_DECREASE_PITCH だけ点数を減らします。
}
}
}
if(period!=PERIOD1)return;
局面が序盤でないときには、ひっくり返す数や分岐数を小さくする処理を行なわないようにするため処理を呼び出し側に返します。
未定義だった TempPutStone() は Othello クラスの宣言に、
void TempPutStone(
struct box_data_strct (*tmp_box_data)[MAX_BOX],
int stone,int xput,int yput);
と追加し、
void Othello::TempPutStone(
struct box_data_strct (*tmp_box_data)[MAX_BOX],
int stone,int xput,int yput)
{
int drc,x,y,xstone;
xstone=stone==BLACK_STONE ? WHITE_STONE : BLACK_STONE;
tmp_box_data[xput][yput].stone=stone;
for(drc=0;drc<MAX_DRC;drc++){
if(!IsEnablePutStone(stone,xput,yput,0,drc,tmp_box_data))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(tmp_box_data[x][y].stone==stone)break;
tmp_box_data[x][y].stone=stone;
}
}
}
とします。これは PutStone の不要な部分を削除したものですので説明の必要はないでしょう。次に IsEnablePutStone() は任意の box_data_strct 構造体の配列も検査できるようにするため、
int IsEnablePutStone(
int stone,int xput,int yput,
int check_this=1,int drc= -1,
struct box_data_strct (*box_data_arg)[MAX_BOX]=NULL);
と Othello クラスの宣言を変更し、
int Othello::IsEnablePutStone(
int stone,int xput,int yput,int check_this,int drc,
struct box_data_strct (*box_data_arg)[MAX_BOX])
{
int drc_from,drc_to,x,y,flag,xstone;
if(!box_data_arg)box_data_arg=box_data;
if(check_this && box_data_arg[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_arg[x][y].stone==NO_STONE)break;
if(flag==0){
if(box_data_arg[x][y].stone==xstone)flag=1;
else break;
}
else{
if(box_data_arg[x][y].stone== stone)return 1;
}
}
}
return 0;
}
と変更します。
GetStoneCount() も未定義ですので Othello クラスの宣言に、
int GetStoneCount(struct box_data_strct (*box_data)[MAX_BOX],int stone);
と追加し、
int Othello::GetStoneCount(
struct box_data_strct (*box_data)[MAX_BOX],int stone)
{
int x,y,total;
total=0;
for(y=0;y<MAX_BOX;y++){
for(x=0;x<MAX_BOX;x++){
if(box_data[x][y].stone==stone)total++;
}
}
return total;
}
とします。これは説明の必要はないでしょう。
ここまでの状態で実行してみると序盤で間抜けなことはしなくなりました。
次は中盤の処理です。とりあえずは中盤はこれといったことをしないので、
ThinkAbleData() を次のようにします。
void Othello::ThinkAbleData()
{
int total;
total=total_black_stone+total_white_stone;
if(total<PERIOD12)ThinkAbleDataPass1(PERIOD1);
else if(total<PERIOD23)ThinkAbleDataPass1(PERIOD2);
}
序盤と中盤で共通で行なった方が良い処理があります。それは盤面の周辺に石を打てるときです。この処理は ThinkAbleDataPass1() よりも優先して処理しなければならないため、ThinkAbleDataPass1() の関数名を ThinkAbleDataPass2() に変更し、これから追加する関数を ThinkAbleDataPass1() とすることにします。
Othello クラスの宣言を次のように ThinkAbleDataPass1() を ThinkAbleDataPass2()に名称変更し、ThinkAbleDataPass1() を追加します。
int ThinkAbleDataPass1();
void ThinkAbleDataPass2(int period);
当然ですが、これまで ThinkAbleDataPass1 だった他の部分も名称変更をしておきます。
そして ThinkAbleData() は次のようにします。
void Othello::ThinkAbleData()
{
int total;
total=total_black_stone+total_white_stone;
if(total<PERIOD12){
if(ThinkAbleDataPass1())return;
ThinkAbleDataPass2(PERIOD1);
}
else if(total<PERIOD23){
ThinkAbleDataPass1();
}
else{
ThinkAbleDataPass1();
}
}
序盤であっても ThinkAbleDataPass1() が真を返したときには緊急度の高いものがあったということになるので、ThinkAbleDataPass2() は実行せずに呼び出し側に戻るようにします。終盤はまだですので、動作を確認するため ThinkAbleDataPass1() を実行することにします。
この ThinkAbleDataPass1() ではどのようなことを行なうかというと、盤面の周辺の状態によってゲームの大勢が決定されてしまいますので、できる限り不利な状態にしないようにすることが主目的になります。例えば、今プレイヤーが●だったときに(□は石がないマス)計算機が次のように○を置くのは変ですね。
□□□□●○□□
また、下のようなときにはコーナーを取れるマスに打つべきですね。
□●○●□●□□
このような処理を行うのです。これもマス目の点数を操作することで行います。
周辺の一辺のみを考えてみると、単純計算で各マスの1つがこれから石を打つマスとすると、各マスには □●○ の3つの状態が考えられるので、8×3×3×3×3×3×3×3=17496 通りの組み合わせが考えられます。この組み合わせすべてについて点数を付けるのは絶望的な数ですので、なんとか工夫しなければなりません。そこで、
石の置かれていないマス n
プレイヤーの石 p
計算機の石 c
計算機の打てるマス x
と記号を決めます。こう決めると上の2つの例は、
nnnnpxnn
npxpnpnn
になります(x となっているのはこれから置こうとしているマスだからです)。少しは単純化しましたが、もう1つ単純化するための工夫をしましょう。同じ状態が連続(1つ以上)しているときには大文字で表現することにすると、
NpxN
npxpnpN
と表現できます。最初の例は次のような場合にも同様のことが言えますので、
□□□●●○□□
□□●●●○□□
NPxN
と表現すれば1つの表現で多くの状態を定義できます。更に、
●□□□●○□□
●□□□●○□●
といった状態と区別するために、コーナーから始まる場合とコーナーで終了する場合の表現もできるように、
コーナー $
と定義します。こうすると、
●□□□●○□□
は、
$PNPxN$
と表現でき、
●□□□●○□●
は、
$PNPxNP$
と表現できます。このようにパターンを定義しておき、それに当てはまったときに点数を操作するようにします。周辺の処理を ThinkAbleDataPass1() で行ないますので、OnInitDialog() で、各マス目に付けた点数は、
int point_table[MAX_BOX][MAX_BOX]={
0, 0, 2, 3, 3, 2, 0, 0,
0, 0, 4, 4, 4, 4, 0, 0,
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,
0, 0, 4, 4, 4, 4, 0, 0,
0, 0, 2, 3, 3, 2, 0, 0
};
のように変更しておきます(あまり意味はありませんが)。
次は ThinkAbleDataPass1() 関数です。
int Othello::ThinkAbleDataPass1()
{
struct pattern_data_strct{
char *pattern;
int add;
};
struct pattern_data_strct pattern_data[]={
{ "",0 }
};
int able,x,y,dx,dy,add,i,flag;
char sts[MAX_BOX+1];
sts[MAX_BOX]='\0';
flag=0;
for(able=0;able<=lable_data;able++){
if(able_data[able].x!=0 && able_data[able].x!=MAX_BOX-1
&& able_data[able].y!=0 && able_data[able].y!=MAX_BOX-1)continue;
if(able_data[able].x==0 || able_data[able].x==MAX_BOX-1){
y=0; dy=1; x=able_data[able].x; dx=0;
}
else{
x=0; dx=1; y=able_data[able].y; dy=0;
}
for(i=0;i<MAX_BOX;i++,x+=dx,y+=dy){
if(x==able_data[able].x && y==able_data[able].y)sts[i]='x';
else if(box_data[x][y].stone ==player_stone )sts[i]='p';
else if(box_data[x][y].stone == cpu_stone )sts[i]='c';
else sts[i]='n';
}
add=0;
for(i=0;pattern_data[i].pattern[0];i++){
if(IsExistPattern(sts,pattern_data[i].pattern)){
if(abs(pattern_data[i].add)>abs(add))add=pattern_data[i].add;
}
}
able_data[able].point+=add;
if(add<= -10 || add>=10)flag=1;
}
return flag;
}
この関数は説明が必要です。
struct pattern_data_strct{
char *pattern;
int add;
};
struct pattern_data_strct pattern_data[]={
{ "",0 }
};
この部分は先程お話しした周辺の一辺のパターンとその点数の定義です。今は、パターンの定義の終了を示すデータのみを定義してあります。
int able,x,y,dx,dy,add,i,flag;
char sts[MAX_BOX+1];
の sts は一辺の現在の状態を入れる配列です。
sts[MAX_BOX]='\0';
では、一辺の状態が単純な文字列にしますので、その文字列の最後をヌルにしておきます。
flag=0;
for(able=0;able<=lable_data;able++){
if(able_data[able].x!=0 && able_data[able].x!=MAX_BOX-1
&& able_data[able].y!=0 && able_data[able].y!=MAX_BOX-1)continue;
打てるマス目が周辺でないときには次の候補にし、打てるときには次以下の処理をします。
if(able_data[able].x==0 || able_data[able].x==MAX_BOX-1){
y=0; dy=1; x=able_data[able].x; dx=0;
}
else{
x=0; dx=1; y=able_data[able].y; dy=0;
}
ここでは一辺の状態を生成するためのマス目をスキャンする位置と方向を決定しています。
for(i=0;i<MAX_BOX;i++,x+=dx,y+=dy){
if(x==able_data[able].x && y==able_data[able].y)sts[i]='x';
else if(box_data[x][y].stone ==player_stone )sts[i]='p';
else if(box_data[x][y].stone == cpu_stone )sts[i]='c';
else sts[i]='n';
}
ここで一辺のパターンを生成します。
add=0;
for(i=0;pattern_data[i].pattern[0];i++){
if(IsExistPattern(sts,pattern_data[i].pattern)){
if(abs(pattern_data[i].add)>abs(add))add=pattern_data[i].add;
}
}
このループでは定義した各パターンに一致するパターンが一辺に存在するか否かを検査し、一致したときには点数の絶対値の最大値を取得します。IsExistPattern() はパターンが存在するか否かを検査する関数ですが未定義です。
able_data[able].point+=add;
取得した点数を able_data に加算します。
if(add<= -10 || add>=10)flag=1;
取得した値の絶対値が10以上のときには緊急度の高いマス目とし flag をセットします。
}
return flag;
flag の値を返します。
次に未定義の IsExistPattern() です。この関数は一辺の状態の中にパターンがあるか否かを検査するのですが、パターンの定義が部分的なものの場合は、必ずしも全体が一致していなくてもパターンを満足する場合に真を返します。またパターンの検査は一辺の状態の反対側からも検査するようにします。こうすることで、定義するパターンの量を減らす事ができます。
まず Othello クラスの宣言に次の行を追加します。
int IsExistPattern(char *string,char *pattern);
そして、
int Othello::IsExistPattern(char *string,char *pattern)
{
char tmp[81],c,*str,*s,*p;
int cont,loop;
strcpy(tmp,string);
for(loop=0;loop<=1;loop++){
if(loop==1){
int i,j,k;
j=strlen(tmp)-1;
k=j/2;
for(i=0;i<=k;i++,j--){ c=tmp[i]; tmp[i]=tmp[j]; tmp[j]=c; }
}
str=tmp;
while(*str){
s=str;
p=pattern;
c= *p++; if(c=='$')c= *p++;
if(c=='N' || c=='P' || c=='C'){ cont=1; c+='a'-'A'; }
else { cont=0; }
while(1){
if(*s==c || c=='*'){
if(!*p)return 1;
if(!*++s && *p=='$')return 1;
if(cont){
cont=2;
continue;
}
}
else{
if(cont!=2)break;
if(!*p )break;
}
c= *p++;
if(c=='N' || c=='P' || c=='C'){ cont=1; c+='a'-'A'; }
else { cont=0; }
}
if(*pattern=='$')break;
else str++;
}
}
return 0;
}
とします。これも説明が必要です。
strcpy(tmp,string);
変数 tmp に string (一辺の状態)をコピーします。
一辺の状態そのままと、反転した状態との2回検査を行ないますので、このループは2回繰返されます。
for(loop=0;loop<=1;loop++){
if(loop==1){
int i,j,k;
j=strlen(tmp)-1;
k=j/2;
for(i=0;i<=k;i++,j--){ c=tmp[i]; tmp[i]=tmp[j]; tmp[j]=c; }
}
ここでは、一辺の状態を反転しています。
str=tmp;
一辺の状態の先頭へのポインタを str に設定します。
while(*str){
s=str;
p=pattern;
c= *p++; if(c=='$')c= *p++;
c にパターンの先頭文字を代入します。もし先頭文字が $ のときには端を示すものですので、次の1文字を c に代入します。
if(c=='N' || c=='P' || c=='C'){ cont=1; c+='a'-'A'; }
else { cont=0; }
パターンの文字が大文字のときには連続していることを示すので、cont を1にし、文字を小文字にします。小文字のときには cont を 0 にします。
while(1){
if(*s==c || c=='*'){
パターンの文字と一辺の文字が一致するか、パターンの文字が * のとき(* はどんな文字があってもいいことを意味する文字)の処理です。
if(!*p)return 1;
パターンがこれ以上ないときには一致していますので真を返します。
if(!*++s && *p=='$')return 1;
一辺の次の文字がなく、パターンの次の文字が $(一辺の端)のときには一致していますので真を返します。
if(cont){
cont=2;
continue;
}
cont が真のときには連続していることを示しますので1つ以上一致したことを示すために cont を 2 にし、一辺の次の文字の検査を繰返します。
}
else{
ここは一致しなかったときの処理です。
if(cont!=2)break;
cont が 2 以外のときには一致していないので、このループを終了させます。
if(!*p )break;
パターンがこれ以上ないときには、このループを終了させます。
}
c= *p++;
パターンの次の文字を c に代入します。
if(c=='N' || c=='P' || c=='C'){ cont=1; c+='a'-'A'; }
else { cont=0; }
c に代入した文字によって変数をセットします。これはこのループの前に行なった処理と同じです。
}
if(*pattern=='$')break;
else str++;
この行は、上のループでパターンが一致しなかったときで、*pattern 即ちパターンの先頭文字が $ のときには端から一致していなければならないので、これ以上検査を行わず、このループを終了させます。それ以外のときには一辺の次の文字からの検査を繰返します。
}
上のループが終了したときには一辺全体で一致していなかったときですので、もう一度このループを繰返します。次のループのときには、一辺の状態を反転してから検査を行ないます。
}
return 0;
結局一致しなかったので偽を返します。
ここまでで、ThinkAbleDataPass1() の機能はできました。しかし、問題はパターンです。最初にお話ししたように一辺のパターンの組み合わせは絶望的な数がありますので、それらすべてを網羅することは諦めました。結局は実際にプレイしてみて、変な動作をしないように1つ1つ定義していきました。次のものがそのパターンです。
{ "NxN" , 0 },
{ "NxPN" , -10 },
{ "NPxPN" , 5 },
{ "NPxCN" , -10 },
{ "NxCPN" , -20 },
{ "NxPCN" , 15 },
{ "NxPCPN" , -10 },
{ "NxPCP$" , -10 },
{ "NxCN" , 0 },
{ "NPxCPN" , 10 },
{ "PxCP" , 10 },
{ "NPxPCN" , -10 },
{ "NPxPCPN" , 10 },
{ "NPCPxN" , -10 },
{ "$x" , 20 },
{ "$CxN" , 10 },
{ "$CxP" , 10 },
{ "$CxPC" , 12 },
{ "$CPx" , 10 },
{ "$nPxPN" , 18 },
{ "$nPxP$" , 18 },
{ "$nPxCPN" , 18 },
{ "$nPxCP$" , 18 },
{ "$nPxPCN" , -10 },
{ "$nPxPC$" , 18 },
{ "$PxP" , 10 },
{ "$PCPxN$" , -10 },
{ "$PCxN$" , -10 },
{ "$CxCn" , 15 },
{ "$CxC$" , 5 },
{ "$PxCN" , -10 },
{ "$NxPCPC$" , -10 },
{ "$NxCPC$" , -10 },
{ "$CNxCP$" , -10 },
{ "CCxCN" , 10 },
{ "CCxCP$" , 10 },
{ "$CPCnN$" , -5 },
{ "$CPCn$" , -10 },
{ "$CxC$" , 10 },
{ "$PxNP" , -10 },
パターンの定義の順番は動作に影響を与えません。私はオセロは強い方ではなく、このパターンが最適かどうかは甚だ疑わしいので、興味のある方は色々と定義して試して下さい。
今回はここまでです。






