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

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

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

アポロレポート

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

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

 今回も前回の続きです。リソースは何も変更がないので、プログラム本体のリストを掲載します。

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

 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 ;

   afx_msg void OnClose();
   afx_msg BOOL OnInitDialog();
   afx_msg void OnPaint();

       void Redraw(CDC *dc);
       void SetColor (CDC *dc,COLORREF outline_color,COLORREF fill_color);
       void FreeColor(CDC *dc);

  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;

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

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

  FreeColor(dc);
 }

 BEGIN_MESSAGE_MAP(Othello,CDialog)
  ON_WM_PAINT()
  ON_WM_CLOSE()
 END_MESSAGE_MAP()

 OthelloApp OthelloApp;

今回はマス目に石を設定し、マウス入力でマスの位置を取得するところまでを作りましょう。オセロゲームは最初、白と黒の石が中心部の2つずつ配置されていますのでマス目の初期化を行うことにします。

Othello クラスに、

 void InitializeBoxData();

の宣言を追加し、OnInitDialog 関数の最後にでも

 InitializeBoxData();

を追加しておきます。

そして、 InitializeBoxData 関数は、

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

とします。NO_STONE は白と黒の石も含めて予め、

 #define  NO_STONE 0
 #define BLACK_STONE 1
 #define WHITE_STONE 2

と定義しておきます。この関数は問題ありませんね。石の配置は一般的なオセロと逆かもしれませんが、動作説明には影響しませんので、このまま進めます。

次に石の描画です。Redraw メンバー関数の FreeColor の前に、

 for(x=0;x<MAX_BOX;x++){
  for(y=0;y<MAX_BOX;y++)WriteStone(dc,x,y);
 }

を追加します。WriteStone は未定義ですので、Othello クラスの宣言に、

 void WriteStone(CDC *dc,int x,int y);

を追加しておきます。そして WriteStone 関数は、

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

これもちょっとだけ説明しましょう。

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

マスの中心位置に石を描画します。

ここまでを実行すると、ますますオセロゲームらしくなってきました。次はマウスの左ボタンのクリックでマスの位置を検出する機能を付け加えましょう。

マウスの左ボタンを押すとWindowsから左ボタンを押したという WM_LBUTTONDOWNメッセージが送られてきますので、このメッセージを処理します。Othello クラスの宣言に、

 afx_msg void OnLButtonDown(UINT nflags,CPoint point);

を、メッセージマップに、

 ON_WM_LBUTTONDOWN()

を追加します。

OnLButtonDown メンバー関数は Othello クラスの基本クラス CWnd で予め定義されているものですので、追加する宣言も上のように同じ型と引数にしなければなりません。要するにこの関数をオーバーライドするのです。

LButtonDown 関数にどんな値が送られてくるのか確認するため、次のようにしてみます。

 afx_msg void Othello::OnLButtonDown(UINT nflags,CPoint point)
 {
  char tmp[31];

  sprintf(tmp,"%d %d\n",point.x,point.y);

  MessageBox(tmp);
 }

座標値は引数 point で渡されてきますので、この内容をメッセージボックスで表示してみます。実験してみるとクライアント座標系での座標値であることが分かります。Othello ウインドウの左上をクリックすると0に近い値になり、ウインドウの位置を移動してもこれが変わらないことから判断することができます。ということはどのマス目の位置でクリックしたかは簡単に分かります。そこで、

 afx_msg void Othello::OnLButtonDown(UINT nflags,CPoint point)
 {
  int x,y;
  char tmp[31];

  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){
     sprintf(tmp,"%d %d\n",x,y);
     MessageBox(tmp);
    }
   }
  }
 }

と書き換えて実行してみるとマス目のXとYのインデックス値が表示できます。問題はこの値をどのように扱うかです。ここですぐに石をひっくり返す方法もありますが、ちょっとエレガントではないので、この値をメッセージにして再び自分自身へ送るようにしましょう。

最初にメッセージのID番号を次のように resource.h に追加します。

 #define IDPUT_STONE           1004

メッセージを送るには PostMessage メンバー関数が使えます。この関数は、

 BOOL PostMessage(UINT message,WPARAM wParam=0,LPARAM lParam=0);

と定義されていますので、引数のメッセージには WM_COMMAND を設定します。更に、wParam にはリソースの定義した IDPUT_STONE を設定すると、メッセージマップでON_COMMAND(IDPUT_STONE,...) と記述してこのメッセージを処理することができます。
最後の引数は省略できますが、指定されたマス目の位置と石の色を指定しなければメッセージをもらっても意味がありません。最後の引数は LPARAM 型で、これは unsignedong の型ですので、この4バイトにうまく石の色、Xの位置、Yの位置を格納してしまいましょう。

石の色は、

 #define BLACK_STONE 1
 #define WHITE_STONE 2

と定義しましたので、1バイトあれば十分で、XとYの値も0~7しかありませんのでこれも1バイトずつしか必要ありません。そこで、

 LPARAM lparam;

 lparam=(((BLACK_STONE<<8)|x)<<8)|y);

というようにしましょう。この式は、石の種類を8ビット上にシフトしてXの値で論理和をとり、更に8ビットシフトしてYの値で論理和をとるという意味です。この方法が嫌いな方は、

 union{
  LPARAM lparam;
  char  values[4];
 }ulc;

 ulc.values[0]=BLACK_STONE;
 ulc.values[1]=x;
 ulc.values[2]=y;

というように共用体を作って代入する方法や、

 LPARAM lparam;

 ((char *)&lparam)[0]=BLACK_STONE;
 ((char *)&lparam)[1]=x;
 ((char *)&lparam)[2]=y;

などとキャストして行う方法もありますので試してみて下さい。

今仮にプレイヤーが押した石の色を黒としメッセージを送るように直すと、

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

となります。ちゃんとこのメッセージを受けられるかを試してみましょう。
Othello クラスの宣言に

 afx_msg void OnPutStone();

を追加し、メッセージマップに、

 ON_COMMAND(IDPUT_STONE,OnPutStone)

を追加します。そして OnPutStone メンバー関数は、

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

とします。GetCurrentMessage 関数は CWnd クラスのメンバー関数で現在処理中のメッセージを取得するものです。

実験をしてみると正常に動作しているようです。

だいぶ仕組みが出来てきましたので、最後にプレイヤーが先攻か後攻かを選択できるようにしましょう。

Othello クラスの宣言に次の3つを追加します。

 int  on_playing;
 int player_stone;
 int   cpu_stone;
 int current_stone;

on_playing はプレイ中か否かを示す変数に使います。
player_stone はプレイヤーの石の種類を示すのに使用します。
current_stone は現在どの色の石を打つのかを示すのに使用します。

この追加したメンバーのうち on_playing は OnInitDialog で、

 on_playing=0;

と初期化しておきます。

次に、

 #define IDASK_ORDER           1005

を resource.h に追加し、Othello クラスの宣言に、

 afx_msg void OnAskOrder();

を追加、更にメッセージマップに、

 ON_COMMAND(IDASK_ORDER,OnAskOrder)

を追加します。

そして、OnAskOrder 関数は、

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

とします。

今回はここまでです。また次回。

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

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