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

TOP > アポロレポート > コラム > 第98回 プログラミングについて『テトリスゲームを作ろう! その6』
コラム
2025/01/31

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

アポロレポート
今回は絶対に完成させましょう。

残る機能は落下するテトロミノを回転させたり、左右に移動させたりすることです。キー入力を処理するためにメッセージマップに ON_CHAR() を追加し、

 BEGIN_MESSAGE_MAP(Tetris,CFrameWnd)
  ON_COMMAND(ID_IDLE,OnIdle)
  ON_COMMAND(ID_PLAY,OnStartPlay)
  ON_WM_PAINT()
  ON_WM_CHAR()
 END_MESSAGE_MAP()

のようにします。また Tetris クラスには、

afx_msg void OnChar(UINT nChar,UINT nRepCnt,UINT nFlags);

を追加します。このメンバー関数は Tetris クラスの基本クラスである CWnd クラスのメンバー関数で、このメンバー関数をオーバーライドします。

この OnChar 関数は、

 afx_msg void Tetris::OnChar(UINT nChar,UINT nRepCnt,UINT nFlags)
 {
  int dy,dx,dr;

  if(block_flag==0)return;

  dy=dx=dr=0;
                        //(1)
     if(nChar=='4')dx= -1;
  else if(nChar=='5')dr= 1;
  else if(nChar=='6')dx= 1;
  else if(nChar==' ')dy= 1;
  else        return;

  if(dy){                  //(2)
   while(DownBlock())Delay(0.02);
  }
  else if(CheckEnable(0,dx,dr)){       //(3)
   WriteBlock(0);

   if(dx)block_xpos+=dx;
   else RotationBlock(block_matrix);

   WriteBlock(1);
  }

  old_time=GetTickCount();          //(4)
 }

 とりあえず私の使っているパソコンはデスクトップですので、テンキーの4、5、6とスペースバーを使用することにします。

 4       左に移動
 5       回転
 6       右に移動
 Spaceバー 一気に落下

ということにします。

(1)では押されたキーの値(文字)を調べて4、5、6またはスペースバーのときにはそれぞれのアクション用に変数に値を設定し、それ以外のときには呼び出し側に戻るようにします。

(2)はスペースバーが押されたときで、一気にテトロミノを落下させます。

   while(DownBlock())Delay(0.02);

は DownBlock メンバー関数がもうこれ以上落下させられないという値(0)になるまで1つ1つ下に移動させていきます。このときも少し表示の遅延を行なった方がワクワクします。

(3)は左右の移動か回転のときです。CheckEnable 関数で移動または回転ができるか否かを検査し、可能なときには落下中のテトロミノを消去した後、左右の移動であればテトロミノの位置を示す変数にその変化量を加え、回転のときには RotationBlock 関数で回転させます。その後テトロミノを再描画します。

 実はこれだけで1ヶ月に渡って作ってきたテトリスゲームが完成してしまいました。
プログラムの全文です。

 #include <afxwin.h>

 #define BLOCK_SIZE  8
 #define GRID_SIZE 10
 #define GRID_SUMY 22
 #define GRID_SUMX 10
 #define TOP_MARGIN  3

 #define INITIAL_SPEED 500
 #define  DELTA_SPEED 20

 #define ID_IDLE 100
 #define ID_PLAY 101

 #define FLOATING  RGB( 0, 0, 0)
 #define FIXED    RGB(127, 0, 0)
 #define BACKGROUND RGB(255,255,255)
 #define COMPLETE  RGB(255, 0, 0)

 class TetrisApp : public CWinApp
 {
  private:
   virtual BOOL OnIdle(LONG lCount);

  public:
   virtual BOOL InitInstance();
 };

 class Tetris : public CFrameWnd
 {
  private:
   int playing;
   int point ;

   DWORD old_time;
   DWORD speed;

   int  grid_matrix[GRID_SUMY][GRID_SUMX];
   int  block_matrix[5][5];
   int  block_flag,block_ypos,block_xpos;

   afx_msg void OnIdle();
   afx_msg void OnPaint();
   afx_msg void OnChar(UINT nChar,UINT nRepCnt,UINT nFlags);
   afx_msg void OnStartPlay();
       void Redraw();
       void WriteBlock(int wflag);
       void WriteBlockTask(COLORREF color,int *yary,int *xary,int lary);
       void WritePoint();
       void MakeBlock();
       void RotationBlock(int block[5][5]);
       void Delay(double delta_time);
       int CheckEnable(int delta_ypos,int delta_xpos,int delta_drct);
       int DownBlock();
       void SetGridMatrix();
       void CheckComplete();

  public:
   Tetris();

  DECLARE_MESSAGE_MAP()
 };

 class TetrisApp TetrisApp;

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

  return TRUE;
 }

 BOOL TetrisApp::OnIdle(LONG lCount)
 {
  m_pMainWnd->PostMessage(WM_COMMAND,ID_IDLE);

  return TRUE;
 }

 BEGIN_MESSAGE_MAP(Tetris,CFrameWnd)
  ON_COMMAND(ID_IDLE,OnIdle)
  ON_COMMAND(ID_PLAY,OnStartPlay)
  ON_WM_PAINT()
  ON_WM_CHAR()
 END_MESSAGE_MAP()

 Tetris::Tetris()
 {
  RECT rect;
  int xsize,ysize;

  Create(
   NULL,"",
   WS_BORDER | WS_CAPTION | WS_OVERLAPPED | WS_MINIMIZEBOX | WS_SYSMENU,
   rectDefault);

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

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

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

  ShowWindow(SW_SHOW);

  playing = -1;
  old_time= 0;
  speed  =INITIAL_SPEED;
 }

 afx_msg void Tetris::OnIdle()
 {
  DWORD cur_time;

  if(!playing)return;

  cur_time=GetTickCount();
  if(cur_time-old_time>=speed){
   old_time=cur_time;

   if(block_flag==0)MakeBlock();
   else       DownBlock();
  }
 }

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

  if(playing== -1){
   playing=0;

   if(MessageBox("開始しますか?","開始",MB_YESNO)==IDNO){
     PostQuitMessage(0);
   }
   else{
    PostMessage(WM_COMMAND,ID_PLAY);
   }
  }
 }

 afx_msg void Tetris::OnStartPlay()
 {
  int y,x;

  for(y=0;y<GRID_SUMY;y++){
   for(x=0;x<GRID_SUMX;x++)grid_matrix[y][x]=0;
  }

  block_flag=0;
  point   =0;
  playing  =1;
  speed   =INITIAL_SPEED;

  Redraw();
 }

 void Tetris::Redraw()
 {
  int yary[GRID_SUMX*GRID_SUMY],xary[GRID_SUMX*GRID_SUMY],lary;
  int y,x;
  int loop,color;

  for(loop=0;loop<=2;loop++){
   lary=0;
   for(y=0;y<GRID_SUMY;y++){
    for(x=0;x<GRID_SUMX;x++){
     if(grid_matrix[y][x]==loop){
      yary[lary]=y;
      xary[lary]=x;
      lary++;
     }
    }
   }

      if(loop==0)color=BACKGROUND;
   else if(loop==1)color=FIXED   ;
   else      color=COMPLETE ;
   WriteBlockTask(color,yary,xary,lary);
  }

  WritePoint();

  if(block_flag)WriteBlock(1);
 }

 void Tetris::WriteBlockTask(COLORREF color,int *yary,int *xary,int lary)
 {
  CClientDC dc(this);
  CPen  pen (PS_SOLID,0,color),*old_pen ;
  CBrush brush(      color),*old_brush;
  int i;
  int xpos,ypos;

  old_pen =dc.SelectObject(&pen );
  old_brush=dc.SelectObject(&brush);

  for(i=0;i<lary;i++){
   ypos=yary[i]*GRID_SIZE+GRID_SIZE*TOP_MARGIN+GRID_SIZE/2;
   xpos=xary[i]*GRID_SIZE           +GRID_SIZE/2;

   dc.Rectangle(
    xpos-BLOCK_SIZE/2 ,ypos-BLOCK_SIZE/2 ,
    xpos+BLOCK_SIZE/2+1,ypos+BLOCK_SIZE/2+1);
  }

  dc.SelectObject(old_pen );
  dc.SelectObject(old_brush);

   pen.DeleteObject();
  brush.DeleteObject();
 }

 void Tetris::WriteBlock(int wflag)
 {
  int yary[25],xary[25];
  int lary;
  int y,x;
  COLORREF color;

  lary=0;
  for(y=0;y<5;y++){
   for(x=0;x<5;x++){
    if(!block_matrix[y][x])continue;

    yary[lary]=y+block_ypos-2;
    xary[lary]=x+block_xpos-2;
    lary++;
   }
  }

  if(wflag)color=FLOATING;
  else   color=BACKGROUND;

  WriteBlockTask(color,yary,xary,lary);
 }

 void Tetris::WritePoint()
 {
  CClientDC dc(this);
  char text[21];

  if(point==0)strcpy (text,"       ");
  else    sprintf(text,"Point=%d",point);

  dc.TextOut(0,0,text,strlen(text));
 }

 void Tetris::MakeBlock()
 {
  int type;
  int y,x;

  srand(GetTickCount());

  type=rand()%7+1;

  for(y=0;y<5;y++){
   for(x=0;x<5;x++)block_matrix[y][x]=0;
  }

  if(type==1){
   block_matrix[2][1]=1;
   block_matrix[2][2]=1;
   block_matrix[2][3]=1;
   block_matrix[2][4]=1;
  }
  else if(type==2){
   block_matrix[2][1]=1;
   block_matrix[2][2]=1;
   block_matrix[2][3]=1;
   block_matrix[3][2]=1;
  }
  else if(type==3){
   block_matrix[2][1]=1;
   block_matrix[2][2]=1;
   block_matrix[2][3]=1;
   block_matrix[3][3]=1;
  }
  else if(type==4){
   block_matrix[2][1]=1;
   block_matrix[2][2]=1;
   block_matrix[2][3]=1;
   block_matrix[3][1]=1;
  }
  else if(type==5){
   block_matrix[2][1]=1;
   block_matrix[2][2]=1;
   block_matrix[3][2]=1;
   block_matrix[3][3]=1;
  }
  else if(type==6){
   block_matrix[2][2]=1;
   block_matrix[2][3]=1;
   block_matrix[3][1]=1;
   block_matrix[3][2]=1;
  }
  else if(type==7){
   block_matrix[2][2]=1;
   block_matrix[2][3]=1;
   block_matrix[3][2]=1;
   block_matrix[3][3]=1;
  }

  block_xpos=GRID_SUMX/2;
  block_ypos=2;
  block_flag=1;

  if(CheckEnable(0,0,0)){
   WriteBlock(1);
  }
  else{
   playing=0;

   if(MessageBox(
      "ゲーム終了\n"
      "もう一度プレイしますか?",
      "再開",MB_YESNO)==IDYES){
    PostMessage(WM_COMMAND,ID_PLAY);
   }
   else{
    PostQuitMessage(0);
   }
  }
 }

 void Tetris::RotationBlock(int block[5][5])
 {
  int tmp[5][5];
  int loop;
  int y,x;

  for(loop=0;loop<=1;loop++){
   for(y=0;y<5;y++){
    for(x=0;x<5;x++){
     if(loop==0) tmp[y ][x]=block[y][x];
     else    block[4-x][y]= tmp[y][x];
    }
   }
  }
 }

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

  dt=(int)(delta_time*1000.0);

  to=GetTickCount();

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

 int Tetris::CheckEnable(int delta_ypos,int delta_xpos,int delta_drct)
 {
  int tmp[5][5];
  int y,x,xx,yy;

  for(y=0;y<5;y++){
   for(x=0;x<5;x++)tmp[y][x]=block_matrix[y][x];
  }

  for(;delta_drct>0;delta_drct--)RotationBlock(tmp);

  for(y=0;y<5;y++){
   for(x=0;x<5;x++){
    if(!tmp[y][x])continue;

    yy=block_ypos+y-2+delta_ypos;
    xx=block_xpos+x-2+delta_xpos;

    if(yy< 0 || yy>=GRID_SUMY)return 0;
    if(xx< 0 || xx>=GRID_SUMX)return 0;

    if(grid_matrix[yy][xx])return 0;
   }
  }

  return 1;
 }

 int Tetris::DownBlock()
 {
  WriteBlock(0);

  if(CheckEnable(1,0,0)){
   block_ypos++;
   WriteBlock(1);
   return 1;
  }
  else{
   block_flag=0;
   SetGridMatrix();
   old_time=0;
   return 0;
  }
 }

 void Tetris::SetGridMatrix()
 {
  int y,x;

  for(y=0;y<5;y++){
   for(x=0;x<5;x++){
    if(block_matrix[y][x]){
     grid_matrix[y+block_ypos-2][x+block_xpos-2]=1;
    }
   }
  }

  Redraw();

  CheckComplete();
 }

 void Tetris::CheckComplete()
 {
  int y,x,yy,flag,loop;

  for(loop=0;loop<=1;loop++){
   flag=0;
   for(y=GRID_SUMY-1;y>=0;y--){
    for(x=0;x<GRID_SUMX;x++){
     if(!grid_matrix[y][x])break;
    }

    if(x<GRID_SUMX)continue;

    if(loop==0){
     flag=1;
     for(x=0;x<GRID_SUMX;x++)grid_matrix[y][x]=2;
    }
    else{
     for(yy=y;yy>=0;yy--){
      for(x=0;x<GRID_SUMX;x++){
       if(yy>0)grid_matrix[yy][x]=grid_matrix[yy-1][x];
       else  grid_matrix[yy][x]=0;
      }
     }

     if(++point%10==9)speed-=DELTA_SPEED;

     Redraw();

     Delay(0.5);

     y++;
    }
   }

   if(flag){
    Redraw();
    Delay(0.5);
   }
  }
 }

 afx_msg void Tetris::OnChar(UINT nChar,UINT nRepCnt,UINT nFlags)
 {
  int dy,dx,dr;

  if(block_flag==0)return;

  dy=dx=dr=0;

     if(nChar=='4')dx= -1;
  else if(nChar=='5')dr= 1;
  else if(nChar=='6')dx= 1;
  else if(nChar==' ')dy= 1;
  else        return;

  if(dy){
   while(DownBlock())Delay(0.02);
  }
  else if(CheckEnable(0,dx,dr)){
   WriteBlock(0);

   if(dx)block_xpos+=dx;
   else RotationBlock(block_matrix);

   WriteBlock(1);
  }

  old_time=GetTickCount();
 }

最初の予想より行数が多くなったのですが、500行程度ですのでまあ許容範囲というところでしょう。私は普段、仕事に使うプログラムばかりを書いていますので、ゲームというのは殆ど書く機会はありません。今回このゲームを作ってみて結構ワクワクしました。どうしても短い時間でゲームを作ろうというので手の込んだものは作れないのですが、昔はオセロ(弱いけれど)ゲームとか、ちょっとしたシューティングゲームを作って遊んだものです。

 とりあえず何かの参考になれば幸いです。また仕事や勉強で気が滅入ったときにはちょっと遊んでみるのもいいと思います。

 それではまた次回。

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

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