プリント基板設計・シミュレーション

TOP > アポロレポート > コラム > 第133回 プログラミングについて『たまにはオセロで遊ぼうよ!その5』
コラム
2026/07/17

第133回 プログラミングについて『たまにはオセロで遊ぼうよ!その5』

アポロレポート

           プログラミングについて 第133回目

           『たまにはオセロで遊ぼうよ! その5』

 今回も前回の続きです。変更があった 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

 [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

 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;

   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);

  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;
 }

 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,(((BLACK_STONE<<8)|x)<<8)|y);
     return;
    }
   }
  }
 }

 afx_msg void Othello::OnPutStone()
 {
  LPARAM lparam;
  int  stone,x,y;

  char tmp[21];

  lparam=GetCurrentMessage()->lParam;
  y  =lparam & 0x000000ff; lparam=lparam>>8;
  x  =lparam & 0x000000ff; lparam=lparam>>8;
  stone=lparam & 0x000000ff;

  sprintf(tmp,"%d %d %d\n",stone,x,y);
  MessageBox(tmp);
 }

 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;
 }

 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;

だいぶ長くなってきましたが頑張りましょう。前回は左ボタンをクリックしたときに発行するメッセージの石の色を固定していましたのでここで修正しておきましょう。
OnLButtonDown 関数の

 PostMessage(WM_COMMAND,IDPUT_STONE,(((BLACK_STONE<<8)|x)<<8)|y);



 PostMessage(WM_COMMAND,IDPUT_STONE,(((player_stone<<8)|x)<<8)|y);

と直します。

次はマス目が指定されたときの処理をする OnPutStone 関数を直します。

 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;

  MessageBox("Ok");
 }

ちょっと説明をしておきましょう。

 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;

は、未定義の関数ですが、指定されたマス目に石を置けるかどうかをチェックする関数で、置けないときにはこのメッセージを無視します。

 MessageBox("Ok");

は、正しく動作しているか否かを確認するために仮にメッセージボックスを表示するためのものです。

次に未定義だった IsEnablePutStone 関数を Othello クラスの宣言に追加します。

 int IsEnablePutStone(
    int stone,int xput,int yput,int check_this=1,int drc= -1);

引数の宣言に int check_this=1 などとしているのは、引数を省略したときのディフォルトの値を設定するためです。

IsEnablePutStone 関数を作る前に1つ用意しておくデータがあります。それは、置けるか否かを検査するときに検査する方向を予め定義しておくことです。

 #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 };

そして IsEnablePutStone 関数です。

 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;
 }

これも説明をしておきましょう。

 if(check_this && box_data[xput][yput].stone!=NO_STONE)return 0;

は、check_this が真のときに指定されたマス目に既に石があるか否かをチェックし、あるときには当然置けないので偽を返します。このようにしたのは実際に石を置いたときのチェック時に指定したマス目をチェックしないようにするのが目的です。

 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   ; }

は、引数が省略されたときには drc には -1 が設定されますので、このときには8方向すべてをチェックします。それ以外のときには指定された方向のみをチェックします。このようにしたのも実際に石を置くときのための準備です。

 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+=xdrc_table[drc],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;
   }
  }
 }

は、指定されたマス目から放射状に8方向または1方向について置けるかどうかをチェックしているものです。チェックは指定されたマス目の次のマス目から開始し、順に調べていき置けると分かった時点で真を返します。

ここまでのプログラムを実行してみて下さい。石を置けるところを指定するとメッセージボックスが表示されると思います。

石を反転させる前にやっておくことがありました。白と黒の石の数を表示する機能を付けることを忘れていました。Othello クラスの宣言に次のものを追加し、

 int total_black_stone;
 int total_white_stone;

 void WriteStoneTotal();

InitializeBox 関数の最後に

 total_black_stone=2;
 total_white_stone=2;

 WriteStoneTotal();

を追加します。

WriteStoneTotal は、

 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);
 }

とします。

それでは石を置いて反転しましょう。OnPutStone の MessageBox("Ok"); と仮にしておいたところを、

 PutStone(stone,xput,yput);

と書き換えます。この関数は未定義ですので、Othello クラスの宣言に、

 void PutStone(int stone,int xput,int yput);

を追加します。そしてこの関数は、

 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)++;

  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)--;
   }
  }

  Redraw(&dc);

  WriteStoneTotal();

  x      =player_stone;
  player_stone=  cpu_stone;
  cpu_stone=x;

  current_stone=player_stone;
 }

これも説明しましょう。

 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)++;

指定されたマスに石を置き、その色の総数を1つ増やします。

 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)--;
  }
 }

 Redraw(&dc);

は結果を確認するために仮に全体を再描画します。

 WriteStoneTotal();

現在の石の総数を更新します。

 x      =player_stone;
 player_stone=  cpu_stone;
 cpu_stone=x;

 current_stone=player_stone;

これは、動作テストのために相手の石もプレイヤーが置けるように石の色を変えるようにしています。

 ここまでのプログラムを実行してみると一応は正常に動作しているようです。1つ気に入らないのは全部をひっくり返した後に再描画を行なっているので面白くないことです。やはり1つ1つ順番にひっくり返っていくのを見るのもこのゲームの楽しみの1つですので、これを直しましょう。上の Redraw(&dc); を削除し、石をひっくり返した後に次のものを挿入します。

指定されたマスに置いたときには、

 WriteStone(&dc,xput,yput);
 Delay(0.3);

を相手の石をひっくり返したときには、

 WriteStone(&dc,x,y);
 Delay(0.3);

を挿入します。Delay は未定義ですので Othello クラスの宣言に、

 void Delay(double delta_time);

を挿入します。この関数は『ハノイの塔』でも使ったものですので特には説明を省略します。

 void Othello::Delay(double delta_time)
 {
  DWORD to,dt;

  dt=(int)(delta_time*1000.0);

  to=GetTickCount();

  while(1){
   if(GetTickCount()-to>dt)break;
  }
 }

今回はここまでです。

そのお悩み、
アポロ技研に話してみませんか?

アポロ技研でワンランク上の物創りへ!
そのお悩み、アポロ技研に話してみませんか?