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

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

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

アポロレポート

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

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

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


 #include <afxwin.h>
 #include "res\resource.h"
 #include "dummy.h"

 #define MAX_BOX      8
 #define STONE_SIZE_RATIO 0.8

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

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

  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("オセロゲーム");
 }

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

 OthelloApp OthelloApp;

 afx_msg BOOL Othello::OnInitDialog()
 {
  RECT rect;
  double xsize,ysize,t;
  int x,y;

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

今回は盤面を描画しましょう。盤面を最初に描画するにはWindowsから Othelloクラスに送られてくる再描画メッセージ WM_PAINT を処理するようにします。

Othello クラスの宣言に、

 afx_msg void OnPaint();

を追加します。次に Othello クラスのメッセージマップに、

 ON_WM_PAINT()

を追加します。この2つを追加すると、Othello クラスの OnPaint メンバー関数は、CDialog クラスのものを使用するのではなく、独自のものを記述する(オーバーライド)できるようになります。そして OnPaint は、

 afx_msg void Othello::OnPaint()
 {
  CPaintDC dc(this);

  Redraw(&dc);
 }

です。CPaintDC は以前にも説明したように OnPaint 関数には必ず定義しておいて下さい。この関数の中で再描画を行なってもいいのですが、別の関数を呼び出すことで再描画を行なった方が将来便利になると思いますので、Redraw 関数にそれを任せることにします。この関数はまだ宣言していませんので、Othello クラスの宣言に、

 void Redraw(CDC *dc);

を追加します。Redraw メンバー関数を書く前にデバイスコンテキストについてちょっと説明しておきましょう。デバイスコンテキストとは、パソコンの画面の他、プリンタやスキャナ等の周辺機器の特性を認識していて、Windows上での描画とそれらの機器とのインターフェイスを行なってくれるものです。通常はパソコンの画面の任意のウインドウに対して描画を行ないますが、その描画先をプリンタのデバイスコンテキストにすると、印刷を行なうことができるようになります。Othello クラスは基本的には CWnd クラスから派生していますので、CWnd は自分自身のデバイスコンテキストを持っています。そのデバイスコンテキストは OnPaint メンバー関数で定義したCPaintDC などを使って使用することができます。

CPaintDCも1つのクラスで、CDC クラスから派生したものです。デバイスコンテキストへのポインタは CWnd クラスでは、GetDC() メンバー関数を使用して取得できますがCPaintDC クラスはオブジェクトの生成時にこれを自動的に行い、消滅時に ReleaseDCメンバー関数を呼び出してデバイスコンテキストを解放する動作を自動的に行なってくれるものです。その他に CPaintDC クラスは別のことも行ないますが、ここでは説明は省略します。

いまデバイスコンテキストへのポインタを取得したとします。ここでは説明のために、dc がデバイスコンテキストへのポインタとします。このデバイスコンテキストへの描画は(オセロゲームでは直線の描画、矩形の塗りつぶし、円の描画等になります)には、描画する図の色を設定してから行なうことになります。線の描画は CPen クラスのオブジェクトを、塗りつぶしには CBrush クラスのオブジェクトを使用することで行います。例えば直線を描画するときには、

 CDC *dc;
 CPen pen,*old_pen;

   :
   :

 pen.CreatePen(PS_SOLID,0,RGB(255,0,0)); //線幅0で赤の実線のペンを生成。
 old_pen=dc->SelectObject(&pen);     //デバイスコンテキストにペンを設定す
                      //る。このとき設定前のペンへのポイン
                      //タが返されますのでこのポインタを保
                      //持しておきます。

 dc->MoveTo(0,0);             //カレントポジションを移動。
 dc->LineTo(100,100);           //カレントポジションから直線を描画。

 dc->SelectObject(old_pen);        //描画が終了したので、デバイスコンテ
                      //キストのペンを元のペンへのポインタ
                      //に戻す。

 pen.DeleteObject();           //生成したペンのオブジェクトの内容を
                      //削除します。

という一連の操作になります。Windowsでの描画はこのように『あれを作って』『それを選択して』『描画して』『選択を元に戻して』『作ったものを削除する』という面倒な操作が必要ですので、何種類かの色の図形を描画するとなるとダラダラとそれらを記述していかなければなりません。そこで、ペンやブラシの生成と選択、描画後の選択解除を別のメンバー関数にやらせることにしてプログラムをスッキリさせるようにした方が記述するときの労力が減少するはずです。

そこで、othello クラスに、

 CPen  pen ,*old_pen ;
 CBrush brush,*old_brush;
 int  pen_brush_using ;

のデータと、

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

の関数を追加します。pen_brush_using はペンとブラシが使用中であるか否かを保持する目的に使用しますので、初期値は OnInitDialog 関数内で、

 pen_brush_using=0;

と初期化しておきます。

SetColor は、

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

FreeColor は、

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

と作っておきます。すると先程のダラダラと記述したものは、

 CDC *dc;

   :
   :

 SetColor(dc,RGB(255,0,0),RGB(255,0,0));

 dc->MoveTo(0,0);
 dc->LineTo(100,100);

 FreeColor(dc);

といたって簡潔になります。

それでは Redraw を作りましょう。

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

この関数で色の定数 DGREEN_COLOR や BLACK_COLOR は

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

として予め定義しておきます。そのうちに必要になると思いますので、白と赤の色も定義しておきます。

この関数も少し説明しておきましょう。

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

は色を黒にして各マスの輪郭を描画しています。盤面の輪郭を描画してから縦横7本ずつの線を描画してもいいのですが、ちょっと無駄のようですがマスの位置が正しく設定されているかの確認も兼ねてそれぞれのマスの輪郭を描画することにしました。

 FreeColor(dc);

は、描画が終わったのでペンとブラシの選択の解除を行なっています。

実行するとやっとオセロゲームらしくなったはずです。

今回はここまでにしましょう。

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

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