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

TOP > アポロレポート > コラム > 第93回 プログラミングについて『テトリスゲームを作ろう! その1』
コラム
2024/12/24

第93回 プログラミングについて『テトリスゲームを作ろう! その1』

アポロレポート
実は、以前何気なく書いてしまった『そのうちはテトリスゲームを....』というのがどうも長い間引っかかっていました。UNIX上では過去に作って遊んでいたことがあったのですが、パソコン上でというものは作ってはいませんでした。いつかはいつかはと思っているうちに日が経ってしまい自分の言葉を反故にする訳にもいかないので、とうとう作る決心をしました。
 作るのはゲームということで基本的には仕事とは関係ありませんので、自宅で作れればいいのですが私の自宅にはパソコンがないのです。ということで結局のところは会社で肩身の狭い思いをして作ったのですが、ソースプログラムをいじっているときはまだごまかしもできるのですが、動作確認ということになると『テトリスゲームをやっている』とバレてしまうのでした。適当に言い訳をしておきましたがなんともばつの悪いものです。

 前置きはそれくらいにしておいて本題に入りましょう。テトリスという名前はもともとテトロミノという言葉が語源になっています。テトロミノのテトロは4という意味です。よく港などにテトラポットというコンクリートのブロックがありますね。あれは角が4つあるのでそういう名前になったのだろうと思います。
 中学か高校の理科で、モノ、ジ、トリ、テトラ、ペンタ、ヘキサ、ヘクタ、オクタ、ノナン、デカン、ドデカン、エイコサンなどとお経のように暗記した記憶はありませんか。モノは1、ジは2、トリは3といった具合に数を表しています。モノラル、ステレオ、トライアングル、ペンタゴン、ペンタックス(多分プリズムの5角形が根源)など
はこの読み方から付けられている名前です。
 さてそのテトロミノというものはどんなものでしょうか。まずドミノから考えてみましょう。日本ではドミノ倒しで有名なドミノなのですが、ドミノのドというのは上の数字の呼び方からすると、ジのことで即ち2を意味しています。何が2かと言えば、正方形の数で、2つ組み合わせてできる形をドミノと言います。1個だったらモノミノとかミノとかでも言うのでしょうか。2つの正方形を組み合わせた形は、

 □□

しかありません。3個をのものはトロミノと言い、

 □□□ □□
     □

の2種類の組み合わせしかありません。4個を組み合わせたテトロミノは、

 □□□□ □□□ □□□ □□  □□
       □    □  □□ □□

の5種類になります。5個を組み合わせたものはペントロミノと言うのですが、その組み合わせは考えてみて下さい。

 さて、そのテトロミノをベースにしてテトリスというゲームが出来たのですが、上の図以外にゲームとしては、

□□□  □□
□   □□

の2種類が加わります。基本の5種類の形をどう回転させてもできない形です。これで合計7種類の形状ができました。テトリスゲームはこのブロックを上から落としていき、落ちている途中で左右方向の移動と回転を行い積み上げていくもので、1列が完成したらそれが得点となり、その完成した1列は消去されるという単純なゲームです。

 まずは71回目にお話しした最低限のウインドウを開くプログラムです。今回もVisual C++で書きますので御了承願います。

 #include <afxwin.h>

 class MyApp : public CWinApp
 {
  public:
   virtual BOOL InitInstance();
 };

 class MyWnd : public CFrameWnd
 {
  public:
   MyWnd();
 };

 class MyApp MyApp;

 BOOL MyApp::InitInstance()
 {
  m_pMainWnd = new class MyWnd;

  return TRUE;
 }

 MyWnd::MyWnd()
 {
  Create(NULL,"",WS_OVERLAPPEDWINDOW | WS_VISIBLE ,rectDefault);
 }

このプログラムの MyApp を TetrisApp に、MyWnd を Tetris に変更します。

 #include <afxwin.h>

 class TetrisApp : public CWinApp
 {
  public:
   virtual BOOL InitInstance();
 };

 class Tetris : public CFrameWnd
 {
  public:
   Tetris();
 };

 class TetrisApp TetrisApp;

 BOOL TetrisApp::InitInstance()
 {
  m_pMainWnd = new class Tetris;

  return TRUE;
 }

 Tetris::Tetris()
 {
  Create(NULL,"",WS_OVERLAPPEDWINDOW | WS_VISIBLE ,rectDefault);
 }

名称の変更だけですので特に問題はありませんね。コンパイルとリンクの方法は71回目を参考にして下さい。このプログラムだけでまずはウインドウが開きます。このウインドウは Tetris::Tetris コンストラクタの Create 関数で rectDefault としていますので勝手な位置に勝手なサイズで開いてしまいます。位置とサイズを固定にするために基本となる大きさを決めましょう。
 基本となる大きさには、テトロミノの構成要素の1つの正方形の大きさ、縦と横の格子数があります。ここでは、

 正方形の大きさを 8
 縦の格子数  を 20 横の格子数  を 10
とします。これだけでもいいのですが、格子の大きさを正方形(ブロック)の大きさよりも若干大き目にして1つ1つのブロックが分かるようにするために、

 格子の大きさ を 10

とします。そしてもう1つ、得点を表示するためにウインドウの上に少し空白を開けておくためにそのマージン数を格子3つ分をとることにしましょう。

 上のマージン数を 3

として次の定数を定義します。

 #define BLOCK_SIZE  8    //ブロックの大きさ
 #define GRID_SIZE 10    //格子の大きさ
 #define GRID_SUMY 22    //縦の格子数
 #define GRID_SUMX 10    //横の格子数
 #define TOP_MARGIN  3    //上のマージン数

そして、Tetris::Tetris を次のように変更します。

 Tetris::Tetris()
 {
  RECT rect;
  int xsize,ysize;
                          //(1)
  Create(
   NULL,"",
   WS_BORDER | WS_CAPTION | WS_OVERLAPPED | WS_MINIMIZEBOX | WS_SYSMENU,
   rectDefault);
                          //(2)
  GetWindowRect(&rect);
  xsize=rect.right -rect.left;
  ysize=rect.bottom-rect.top ;

  GetClientRect(&rect);
  xsize-=(rect.right -rect.left);
  ysize-=(rect.bottom-rect.top );

  xsize+=GRID_SIZE* GRID_SUMX;
  ysize+=GRID_SIZE*(GRID_SUMY+TOP_MARGIN);
                          //(3)
  SystemParametersInfo(SPI_GETWORKAREA,0,&rect,0);
                          //(4)
  MoveWindow(
   (rect.left+rect.right )/2-xsize/2,
   (rect.top +rect.bottom)/2-ysize/2,
   xsize,ysize);
                          //(5)
  ShowWindow(SW_SHOW);
 }

上のプログラムで、

(1)はウインドウの属性を設定しています。

WS_VISIBLE を指定しないことで初期状態で非表示し、

WS_OVERLAPPEDWINDOW を止めて WS_CAPTION、WS_OVERLAPPED、WS_MINIMIZEBOX、WS_SYSMENU を別個に指定します。この指定で、ウインドウは
アイコン化はできますが最大表示にしたり、マウスでウインドウのサイズを変更できなくなります(移動は可能)。

(2)ではウインドウのサイズを決定しています。Visual C++のクラスライブラリには2種類のウインドウサイズを取得するものがあります。
1つは、GetWindowRect でウインドウのスクリーン座標の位置とサイズを取得するもの、もう1つはウインドウ内のクライアント領域(通常、図を描画す領域)の位置とサイズを取得する GetClientRect というものがあります。

まず GetWindowRect でウインドウの位置とサイズを取得し、

   GetWindowRect(&rect);
   xsize=rect.right -rect.left;
   ysize=rect.bottom-rect.top ;

とすることで、ウインドウの横と縦の大きさを xsize、ysize に代入しておきます。次に、

   GetClientRect(&rect);
   xsize-=(rect.right -rect.left);
   ysize-=(rect.bottom-rect.top );

でクライアント領域の横と縦の大きさをそれぞれ xsize、ysize から引き算します。これで一体何が求まるかと言えば、X方向ではウインドウの枠の幅2つ分とY方向ではタイトル部分の高さと下の枠の幅の合計が求まるのです。結構色々と試してみたのですが、この方法が一番安直な方法みたいです。

次にクライアント領域のサイズを xsize、ysize に加算するとウインドウ全体の大きさが決定できます。

   xsize+=GRID_SIZE* GRID_SUMX;
   ysize+=GRID_SIZE*(GRID_SUMY+TOP_MARGIN);

ここまではウインドウの大きさしか決定できていないので、次に位置を決定します。

(3)では、SystemParametersInfo というあまり見慣れない関数を呼び出しているのですが、この関数は現在の Windowsの色々なパラメータを取得できる関数で
、この場合はワークエリアのサイズを取得しています。
ワークエリアというのは、Windows95 やWindows NTのスクリーンでのツールバー以外の領域のことです。この値はWindowsのプログラムを作るときには結構大切なもので、新しく生成したウインドウがツールバーの下に隠れてしまわないように考慮しなければなりません。

   SystemParametersInfo(SPI_GETWORKAREA,0,&rect,0);

(4)ではウインドウの位置とサイズを実際に設定します。位置はウインドウの中心がワークエリアの中心になるようにしています。

  MoveWindow(
   (rect.left+rect.right )/2-xsize/2,
   (rect.top +rect.bottom)/2-ysize/2,
   xsize,ysize);

(5)ではこのウインドウは初期状態では非表示としていたので、表示するようにしています。初期状態から可視状態としておくと、プログラムが起動されると一度ディフォルトの位置とサイズでウインドウが表示されてから、改めてサイズと位置が変更されるのが見えてしまうのです。

   ShowWindow(SW_SHOW);

 これでウインドウが目的の位置と大きさで表示できるようになりました。ウインドウのタイトルを付けたいときには、

 SetWindowText("Tetris");

などとして、このコンストラクタに追加して下さい。

 今回はここまでとしましょう。また次回。

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

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