コラム
2026/08/27
プログラミングについて 第134回目
『たまにはオセロで遊ぼうよ! その6』
今回も前回の続きです。リソースは何も変更がないので、プログラム本体のリストです。
#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 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();
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);
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;
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;
}
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,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();
x =player_stone;
player_stone= cpu_stone;
cpu_stone=x;
current_stone=player_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;
}
}
BEGIN_MESSAGE_MAP(Othello,CDialog)
ON_COMMAND(IDASK_ORDER,OnAskOrder)
ON_COMMAND(IDPUT_STONE,OnPutStone)
ON_WM_LBUTTONDOWN()
ON_WM_PAINT()
ON_WM_CLOSE()
END_MESSAGE_MAP()
OthelloApp OthelloApp;
今回からは計算機が相手になるようにしていきましょう。
まず次に打つのがプレイヤーなのか計算機なのかを表示することにします。関数の本体を先に作りましょう。まず Othello クラスの宣言に、
void WriteOrder();
を追加します。そして関数本体は、
void Othello::WriteOrder()
{
char tmp[81];
if(current_stone==player_stone){
sprintf(
tmp,"あなたの %s 石の番です",
current_stone==BLACK_STONE ? "●" : "○");
}
else{
strcpy(tmp,"お待ち下さい");
}
SetDlgItemText(IDMESSAGE,tmp);
}
というように current_stone が player_stone と同じか否かで表示内容を変えます。表示する場所は、リソースに予め配置しておいたID番号 IDMESSAGE のスタティックテキストです。
この関数を呼び出す場所は置く石の色が変るところですから、OnAskOrder と PutStoneになりますので、OnAskOrder の最後と PutStone の最後に WriteOrder(); を追加します。今のところ白も黒もプレイヤーが置くので、この状態で実行してみると表示されるメッセージは『あなたの ○ 石の番です』と『あなたの ● 石の番です』とが交互に切り替わります(人間が先攻を選んだときだけです)。
この状態でもちょっとは遊べますが、これではまだ不十分です。大きな不具合は、プレイ中にエスケープキーやリターンキーを押すとプログラムが中途半端な終了をしてしまうのです。これを最初に処理しておきましょう。やることは簡単で Othello のメッセージマップに、
ON_COMMAND(IDOK ,Default)
ON_COMMAND(IDCANCEL,Default)
追加しておけば完了です。リターンキーを押したときには IDOK が、エスケープキーを押したときには IDCANCEL のメッセージをWindowsが発行してきますので、CWndクラスの Default メンバー関数にその処理をさせればOKです。
次に解決しなければならない問題は、次に石を置く側に置く場所がないときの処理です。
オセロは相手が打てないときには自分が連続して打てることと、お互いが打てないときにはゲームが終了しますので、この機能を付けておかないとゲームが途中でどうにもならなくなってしまいます。そこで PutStone の
WriteStoneTotal(); と
x =player_stone;
player_stone= cpu_stone;
cpu_stone=x;
current_stone=player_stone;
の間でその処理を行います。挿入後は上の current_stone=player_stone; は不要になりますので削除します。
挿入する内容は、
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;
}
です。変数 loop は未定義ですので、この関数の最初に int loop; を追加しておきます。
最初のループ
for(loop=0;loop<=1;loop++){
if(current_stone==BLACK_STONE)current_stone=WHITE_STONE;
else current_stone=BLACK_STONE;
if(EnablePutStoneAnywhere())break;
}
の EnablePutStoneAnywhere() は現在は未定義ですが current_stone の石をどこかに打てるか否かを調べる関数です。ループは最大2回繰返されます。最初の一回目は今打った石と違う色に変えて検査をします。もしどこかに打てるのであればループを抜け出します。打てないときには再び石の色を変えて(今打った石の色になる)検査します。打てるのであればループを抜け出し、打てないのであればもう一度ループを繰返そうとしますが、これ以上繰返せないのでループは終了します。
ループが終了したとき、変数 loop が1よりも大きいときには、どちらの石も打てないことになるのでゲームはここで終了です。それが次のIF文です。
if(loop>1){
if(!WriteEndMessage()){
PostMessage(WM_CLOSE);
return;
}
InitializeBoxData();
WriteStoneTotal();
Redraw(&dc);
PostMessage(WM_COMMAND,IDASK_ORDER);
return;
}
WriteEndMessage() 関数も未定義ですが、ゲームの結果を表示し、もう一度行うか否かを問い合わせるものにします。この関数の戻り値が偽のときにはゲーム終了とし、真のときにはもう一度ゲームをすることにしますので、偽のときには WM_CLOSE メッセージを自分自身(Othello)に送り、真のときには必要な初期化を行って IDASK_ORDERメッセージを自分自身に送ります。
その未定義の関数 EnablePutStoneAnywhere() は、
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;
}
で、マス目を順番に検査していき打てることが分かったときには真を返し、打てないときには偽を返すようにします。
もう1つ WriteEndMessage() は、
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;
}
と石の数の差で表示内容を変え、もう一度プレイするか否かを問い合わせるようにします。もう一度プレイするときには真を、終了するときには偽を返すようにします。この2つの関数も Othello クラスの宣言に、
int EnablePutStoneAnywhere();
int WriteEndMessage();
と追加しておきます。
ここまでの状態で人間が2人で遊べるようになりました。
次は一人で遊べるように計算機が石を打つようにします。
この機能の追加も OnAskOrder() と PutStone() の最後に、
if(cpu_stone==current_stone)PostMessage(WM_COMMAND,IDTHINK_PUT_STONE);
を追加します。IDTHINK_PUT_STONE はresource.h に、
#define IDTHINK_PUT_STONE 1006
追加しておきます。また、PutStone() に動作テストのために仮に記述していた、
x =player_stone;
player_stone= cpu_stone;
cpu_stone=x;
は不要ですので削除します。
このままの状態ではプログラムを実行しても IDTHINK_PUT_STONE メッセージの処理をしていませんので計算機の順番になっても何事も起こりません。そこで、このメッセージの処理を行います。
Othello のメッセージマップに
ON_COMMAND(IDTHINK_PUT_STONE,OnThinkPutStone)
を追加し、Othello クラスの宣言に、
afx_msg void OnThinkPutStone();
を追加します。そして OnThinkPutStone() は、
afx_msg void Othello::OnThinkPutStone()
{
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)){
PostMessage(WM_COMMAND,IDPUT_STONE,(((cpu_stone<<8)|x)<<8)|y);
return;
}
}
}
}
とします。とりあえずのところはマス目を順番に検査していき、最初に石を打てるところを発見したらそこに石を打つことにします。メッセージの送り方は OnLButtonDown 関数での方法と同じですが、石の色は cpu_stone にセットされているものにします。
ここまで作るとどうにかこうにか一人で遊べるようになります。がしかし、実際に実行してみると色々と問題があることが分かると思います。1つは後攻を選んだとき、計算機が石を打つのですが、メッセージボックスが消去された後の灰色の部分が残っていて、そこに石を打ってしまうのです。これは OnAskOrder 内から IDTHINK_PUT_STONE メッセージを送っていますので、メッセージボックスが消去された後にWindowsが発行する WM_PAINT メッセージと順番が逆になってしまっているからです。
WM_COMMAND IDTHINK_PUT_STONE --- OnAskOrder() が発行。
WM_PAINT --- メッセージボックスが消去されたことで
Windowsが発行。
という順になってしまうからです。そこで OnAskOrder() の、
if(cpu_stone==current_stone)PostMessage(WM_COMMAND,IDTHINK_PUT_STONE);
を、
if(cpu_stone==current_stone){
PostMessage(WM_PAINT);
PostMessage(WM_COMMAND,IDTHINK_PUT_STONE);
}
と変更し、
WM_PAINT --- OnAskOrder() が発行。
WM_COMMAND IDTHINK_PUT_STONE --- OnAskOrder() が発行。
WM_PAINT --- メッセージボックスが消去されたことで
Windowsが発行。
というメッセージの順番になるようにします。これで1つは解決しました。
まだまだ問題があります。プレイヤーが数箇所のマス目を左ボタンで連続してクリックすると、計算機が石を置いた後にどんどんプレイヤーの石を置いていってしまうのです。これは、左ボタンをクリックしたときにWindowsから発行されるメッセージが、メッセージのバッファにどんどん行を成して溜まってしまうからなのです。これをなんとかしなければプレイするときに気を使わなければなりませんのでなんとかしなければなりません。
そこで PeekMessage という関数を使用します。この関数は現在のメッセージのバッファの内容を取得できるというものです。まず OnAskOrder の IDTHINK_PUT_STONE メッセージを発行する部分を、
if(player_stone==current_stone){
PurgeLButtonDownMessage();
}
else{
PostMessage(WM_PAINT);
PostMessage(WM_COMMAND,IDTHINK_PUT_STONE);
}
と変更し、PutStone 関数の IDTHINK_PUT_STONE メッセージを発行する部分も、
if(player_stone==current_stone)PurgeLButtonDownMessage();
else PostMessage(WM_COMMAND,IDTHINK_PUT_STONE);
のように変更します。
PurgeLButtonDownMessage() 関数は未定義ですので、Othello クラスの宣言に、
void PurgeLButtonDownMessage();
を追加します。そして関数本体は、
void Othello::PurgeLButtonDownMessage()
{
MSG msg;
while(PeekMessage(&msg,m_hWnd,WM_LBUTTONDOWN,WM_LBUTTONDOWN,PM_REMOVE));
}
といたって簡単なものになります。PeekMessage の詳細はコンパイラのオンラインヘルプを参照してもらうこととして、行っている内容は、メッセージバッファ内にWM_LBUTTONDOWN メッセージがなくなるまで繰返しています。最後の引数が PM_REMOVE としていますので、メッセージを取得するたびにWindowsはそのメッセージをメッセージバッファから削除していきます。
ここまで直してもまだ不十分です。人間が石を打ったらすぐに計算機が打ってしまうので一体計算機がどこに置いたのかが分かりづらいことです。そこで OnThinkPutStone()関数を、
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;
}
}
}
}
とします。変更内容は、メッセージを発行する前に、1秒間を開けてから、これから置くマス目に別の色の石を表示し、更に0.3秒待つようにしたところです。NOTIFY_STONEは未定義ですので、石の値の定義のところに次のように定義しておきます。
#define NO_STONE 0
#define BLACK_STONE 1
#define WHITE_STONE 2
#define NOTIFY_STONE 3
また、これに合わせて WriteStone() も白と黒の石しか描画しないので変更しなければなりません。WriteStone() の石の色を設定をする部分を次のように変更します。
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;
ここまででなんとか一人で遊べるようになりました。しかしながらこの状態では計算機は最初にX方向Y方向を順に調べていき、最初に発見した石を打てる場所に打つので、人間が同じ手順で打つといつも同じ結果になってしまいます。要するに何にも考えていないのでまるでデタラメに打っているだけです。
次回からは少しずつ計算機を賢くしていきましょう。
第134回 プログラミングについて『たまにはオセロで遊ぼうよ!その6』
プログラミングについて 第134回目
『たまにはオセロで遊ぼうよ! その6』
今回も前回の続きです。リソースは何も変更がないので、プログラム本体のリストです。
#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 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();
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);
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;
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;
}
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,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();
x =player_stone;
player_stone= cpu_stone;
cpu_stone=x;
current_stone=player_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;
}
}
BEGIN_MESSAGE_MAP(Othello,CDialog)
ON_COMMAND(IDASK_ORDER,OnAskOrder)
ON_COMMAND(IDPUT_STONE,OnPutStone)
ON_WM_LBUTTONDOWN()
ON_WM_PAINT()
ON_WM_CLOSE()
END_MESSAGE_MAP()
OthelloApp OthelloApp;
今回からは計算機が相手になるようにしていきましょう。
まず次に打つのがプレイヤーなのか計算機なのかを表示することにします。関数の本体を先に作りましょう。まず Othello クラスの宣言に、
void WriteOrder();
を追加します。そして関数本体は、
void Othello::WriteOrder()
{
char tmp[81];
if(current_stone==player_stone){
sprintf(
tmp,"あなたの %s 石の番です",
current_stone==BLACK_STONE ? "●" : "○");
}
else{
strcpy(tmp,"お待ち下さい");
}
SetDlgItemText(IDMESSAGE,tmp);
}
というように current_stone が player_stone と同じか否かで表示内容を変えます。表示する場所は、リソースに予め配置しておいたID番号 IDMESSAGE のスタティックテキストです。
この関数を呼び出す場所は置く石の色が変るところですから、OnAskOrder と PutStoneになりますので、OnAskOrder の最後と PutStone の最後に WriteOrder(); を追加します。今のところ白も黒もプレイヤーが置くので、この状態で実行してみると表示されるメッセージは『あなたの ○ 石の番です』と『あなたの ● 石の番です』とが交互に切り替わります(人間が先攻を選んだときだけです)。
この状態でもちょっとは遊べますが、これではまだ不十分です。大きな不具合は、プレイ中にエスケープキーやリターンキーを押すとプログラムが中途半端な終了をしてしまうのです。これを最初に処理しておきましょう。やることは簡単で Othello のメッセージマップに、
ON_COMMAND(IDOK ,Default)
ON_COMMAND(IDCANCEL,Default)
追加しておけば完了です。リターンキーを押したときには IDOK が、エスケープキーを押したときには IDCANCEL のメッセージをWindowsが発行してきますので、CWndクラスの Default メンバー関数にその処理をさせればOKです。
次に解決しなければならない問題は、次に石を置く側に置く場所がないときの処理です。
オセロは相手が打てないときには自分が連続して打てることと、お互いが打てないときにはゲームが終了しますので、この機能を付けておかないとゲームが途中でどうにもならなくなってしまいます。そこで PutStone の
WriteStoneTotal(); と
x =player_stone;
player_stone= cpu_stone;
cpu_stone=x;
current_stone=player_stone;
の間でその処理を行います。挿入後は上の current_stone=player_stone; は不要になりますので削除します。
挿入する内容は、
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;
}
です。変数 loop は未定義ですので、この関数の最初に int loop; を追加しておきます。
最初のループ
for(loop=0;loop<=1;loop++){
if(current_stone==BLACK_STONE)current_stone=WHITE_STONE;
else current_stone=BLACK_STONE;
if(EnablePutStoneAnywhere())break;
}
の EnablePutStoneAnywhere() は現在は未定義ですが current_stone の石をどこかに打てるか否かを調べる関数です。ループは最大2回繰返されます。最初の一回目は今打った石と違う色に変えて検査をします。もしどこかに打てるのであればループを抜け出します。打てないときには再び石の色を変えて(今打った石の色になる)検査します。打てるのであればループを抜け出し、打てないのであればもう一度ループを繰返そうとしますが、これ以上繰返せないのでループは終了します。
ループが終了したとき、変数 loop が1よりも大きいときには、どちらの石も打てないことになるのでゲームはここで終了です。それが次のIF文です。
if(loop>1){
if(!WriteEndMessage()){
PostMessage(WM_CLOSE);
return;
}
InitializeBoxData();
WriteStoneTotal();
Redraw(&dc);
PostMessage(WM_COMMAND,IDASK_ORDER);
return;
}
WriteEndMessage() 関数も未定義ですが、ゲームの結果を表示し、もう一度行うか否かを問い合わせるものにします。この関数の戻り値が偽のときにはゲーム終了とし、真のときにはもう一度ゲームをすることにしますので、偽のときには WM_CLOSE メッセージを自分自身(Othello)に送り、真のときには必要な初期化を行って IDASK_ORDERメッセージを自分自身に送ります。
その未定義の関数 EnablePutStoneAnywhere() は、
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;
}
で、マス目を順番に検査していき打てることが分かったときには真を返し、打てないときには偽を返すようにします。
もう1つ WriteEndMessage() は、
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;
}
と石の数の差で表示内容を変え、もう一度プレイするか否かを問い合わせるようにします。もう一度プレイするときには真を、終了するときには偽を返すようにします。この2つの関数も Othello クラスの宣言に、
int EnablePutStoneAnywhere();
int WriteEndMessage();
と追加しておきます。
ここまでの状態で人間が2人で遊べるようになりました。
次は一人で遊べるように計算機が石を打つようにします。
この機能の追加も OnAskOrder() と PutStone() の最後に、
if(cpu_stone==current_stone)PostMessage(WM_COMMAND,IDTHINK_PUT_STONE);
を追加します。IDTHINK_PUT_STONE はresource.h に、
#define IDTHINK_PUT_STONE 1006
追加しておきます。また、PutStone() に動作テストのために仮に記述していた、
x =player_stone;
player_stone= cpu_stone;
cpu_stone=x;
は不要ですので削除します。
このままの状態ではプログラムを実行しても IDTHINK_PUT_STONE メッセージの処理をしていませんので計算機の順番になっても何事も起こりません。そこで、このメッセージの処理を行います。
Othello のメッセージマップに
ON_COMMAND(IDTHINK_PUT_STONE,OnThinkPutStone)
を追加し、Othello クラスの宣言に、
afx_msg void OnThinkPutStone();
を追加します。そして OnThinkPutStone() は、
afx_msg void Othello::OnThinkPutStone()
{
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)){
PostMessage(WM_COMMAND,IDPUT_STONE,(((cpu_stone<<8)|x)<<8)|y);
return;
}
}
}
}
とします。とりあえずのところはマス目を順番に検査していき、最初に石を打てるところを発見したらそこに石を打つことにします。メッセージの送り方は OnLButtonDown 関数での方法と同じですが、石の色は cpu_stone にセットされているものにします。
ここまで作るとどうにかこうにか一人で遊べるようになります。がしかし、実際に実行してみると色々と問題があることが分かると思います。1つは後攻を選んだとき、計算機が石を打つのですが、メッセージボックスが消去された後の灰色の部分が残っていて、そこに石を打ってしまうのです。これは OnAskOrder 内から IDTHINK_PUT_STONE メッセージを送っていますので、メッセージボックスが消去された後にWindowsが発行する WM_PAINT メッセージと順番が逆になってしまっているからです。
WM_COMMAND IDTHINK_PUT_STONE --- OnAskOrder() が発行。
WM_PAINT --- メッセージボックスが消去されたことで
Windowsが発行。
という順になってしまうからです。そこで OnAskOrder() の、
if(cpu_stone==current_stone)PostMessage(WM_COMMAND,IDTHINK_PUT_STONE);
を、
if(cpu_stone==current_stone){
PostMessage(WM_PAINT);
PostMessage(WM_COMMAND,IDTHINK_PUT_STONE);
}
と変更し、
WM_PAINT --- OnAskOrder() が発行。
WM_COMMAND IDTHINK_PUT_STONE --- OnAskOrder() が発行。
WM_PAINT --- メッセージボックスが消去されたことで
Windowsが発行。
というメッセージの順番になるようにします。これで1つは解決しました。
まだまだ問題があります。プレイヤーが数箇所のマス目を左ボタンで連続してクリックすると、計算機が石を置いた後にどんどんプレイヤーの石を置いていってしまうのです。これは、左ボタンをクリックしたときにWindowsから発行されるメッセージが、メッセージのバッファにどんどん行を成して溜まってしまうからなのです。これをなんとかしなければプレイするときに気を使わなければなりませんのでなんとかしなければなりません。
そこで PeekMessage という関数を使用します。この関数は現在のメッセージのバッファの内容を取得できるというものです。まず OnAskOrder の IDTHINK_PUT_STONE メッセージを発行する部分を、
if(player_stone==current_stone){
PurgeLButtonDownMessage();
}
else{
PostMessage(WM_PAINT);
PostMessage(WM_COMMAND,IDTHINK_PUT_STONE);
}
と変更し、PutStone 関数の IDTHINK_PUT_STONE メッセージを発行する部分も、
if(player_stone==current_stone)PurgeLButtonDownMessage();
else PostMessage(WM_COMMAND,IDTHINK_PUT_STONE);
のように変更します。
PurgeLButtonDownMessage() 関数は未定義ですので、Othello クラスの宣言に、
void PurgeLButtonDownMessage();
を追加します。そして関数本体は、
void Othello::PurgeLButtonDownMessage()
{
MSG msg;
while(PeekMessage(&msg,m_hWnd,WM_LBUTTONDOWN,WM_LBUTTONDOWN,PM_REMOVE));
}
といたって簡単なものになります。PeekMessage の詳細はコンパイラのオンラインヘルプを参照してもらうこととして、行っている内容は、メッセージバッファ内にWM_LBUTTONDOWN メッセージがなくなるまで繰返しています。最後の引数が PM_REMOVE としていますので、メッセージを取得するたびにWindowsはそのメッセージをメッセージバッファから削除していきます。
ここまで直してもまだ不十分です。人間が石を打ったらすぐに計算機が打ってしまうので一体計算機がどこに置いたのかが分かりづらいことです。そこで OnThinkPutStone()関数を、
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;
}
}
}
}
とします。変更内容は、メッセージを発行する前に、1秒間を開けてから、これから置くマス目に別の色の石を表示し、更に0.3秒待つようにしたところです。NOTIFY_STONEは未定義ですので、石の値の定義のところに次のように定義しておきます。
#define NO_STONE 0
#define BLACK_STONE 1
#define WHITE_STONE 2
#define NOTIFY_STONE 3
また、これに合わせて WriteStone() も白と黒の石しか描画しないので変更しなければなりません。WriteStone() の石の色を設定をする部分を次のように変更します。
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;
ここまででなんとか一人で遊べるようになりました。しかしながらこの状態では計算機は最初にX方向Y方向を順に調べていき、最初に発見した石を打てる場所に打つので、人間が同じ手順で打つといつも同じ結果になってしまいます。要するに何にも考えていないのでまるでデタラメに打っているだけです。
次回からは少しずつ計算機を賢くしていきましょう。






