English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Cを使用++ロシア方块の実装続編

一、実験概要

1.1 実験内容

本節の実験では、ロシアのタイルの主要な関数の設計を実現し、基本機能を完了して実行します

1.2 実験の知識点

ウィンドウの描画
ブロッククラスの設計
回転アルゴリズム
移動、消去関数

1.3 実験環境

xface端末
g++ コンパイラ
ncursesライブラリ

1.4 プログラムをコンパイルします

コンパイルコマンドには -l オプションでncursesライブラリを導入します:

g++ main.c -l ncurses

1.5 プログラムを実行します

./a.out

1.6 実行結果


二、実験手順

2.1 ヘッダーファイル

まずヘッダーファイルを含め、交換関数とランダム数関数を定義します(交換関数はブロックの回転に使用され、ランダム数はブロックの形状を設定するために使用されます)

#include <iostream>
#include <sys/time.h>
#include <sys/types.h>
#include <stdlib.h>
#include <ncurses.h>
#include <unistd.h>
/* aとbを交換します */
void swap(int &a, int &b){
 int t=a;
 a = b;
 b = t;
}
/* (min,max)範囲のランダムな整数を取得します
int getrand(int min, int max)
{
 return(min+rand()%(max-min+1));
}

2.2 クラスを定義します

プログラムの内容が比較的単純であるため、ここではPieceクラスを1つだけ定義しています

class Piece
 {
 public:
  int score;  //スコア
  int shape;  //現在のブロックの形状を示す
  int next_shape;  //次のブロックの形状を示す
  int head_x;  //現在のブロックの最初のboxの位置、マーク位置
  int head_y;
  int size_h;  //現在のブロックのsize
  int size_w;
  int next_size_h;  //次のブロックのsize
  int next_size_w;
  int box_shape[4][4]; //現在のブロックのshape配列 4x4
  int next_box_shape[4][4];  //次のブロックのshape配列 4x4
  int box_map[30][45];  //ゲームボックス内の各boxをマークするために使用されます
  bool game_over;  //ゲーム終了のフラグ
 public:
  void initial();  //初期化関数
  void set_shape(int &cshape, int box_shape[][4],int &size_w, int & size_h);  //ブロック形状の設定
  void score_next();  //次のブロックの形状およびスコアを表示します
  void judge();  //レベルが満タンかどうかを判定します
  void move(); //移動関数  ← → ↓ で制御
  void rotate(); //回転関数
  bool isaggin(); //次のアクションが範囲外か重複していないかを判定します
  bool exsqr(int row); //現在の行が空かどうかを判定します
 };

2.3 ブロック形状の設定

ここでは、case文を使用して定義されています7種類のブロックの形状を設定し、次のブロックが落ちる前にその形状および初期位置を設定するために、常に呼び出す必要があります

void Piece::set_shape(int &cshape, int shape[][4],int &size_w,int &size_h)
{
 /*まず、表示するために用意された4x4配列を0で初期化します*/
 int i,j;
 for(i=0;i<4;i++);
  for(j=0;j<4;j++);
   shape[i][j]=0;
 /*設定7種類の初期形状を設定し、それらのsizeを設定します*/
 switch(cshape)
 {
  case 0: 
   size_h=1;
   size_w=4; 
   shape[0][0]=1;
   shape[0][1]=1;
   shape[0][2]=1;
   shape[0][3]=1;
   break;
  case 1:
   size_h=2;
   size_w=3;
   shape[0][0]=1;
   shape[1][0]=1;
   shape[1][1]=1;
   shape[1][2]=1;
   break;
  case 2:
   size_h=2;
   size_w=3; 
   shape[0][2]=1;
   shape[1][0]=1;
   shape[1][1]=1;
   shape[1][2]=1;
   break;
  case 3:
   size_h=2;
   size_w=3;
   shape[0][1]=1;
   shape[0][2]=1;
   shape[1][0]=1;
   shape[1][1]=1;
   break;
  case 4:
   size_h=2;
   size_w=3;
   shape[0][0]=1;
   shape[0][1]=1;
   shape[1][1]=1;
   shape[1][2]=1;
   break;
  case 5: 
   size_h=2;
   size_w=2;
   shape[0][0]=1;
   shape[0][1]=1;
   shape[1][0]=1;
   shape[1][1]=1;
   break;
  case 6: 
   size_h=2;
   size_w=3;
   shape[0][1]=1;
   shape[1][0]=1;
   shape[1][1]=1;
   shape[1][2]=1;
   break;
 }
 //形状を設定した後、ブロックの初期位置を初期化します
 head_x=game_win_width/2;
 head_y=1;
 //もし初期化直後に重複があった場合、ゲーム終了です~
 if(isaggin()) /* GAME OVER ! */
  game_over=true;
}

2.4 回転関数

ここでは、ブロックを回転させるための非常にシンプルなアルゴリズムを使用しており、行列の回転に似ています。まず、shape配列を斜め対角線対称にし、その後左右対称にすることで回転が完了します。ただし、回転後のブロックが範囲外か重複していないか確認することが重要です。もし、その場合には、今回の回転をキャンセルします。

void Piece::rotate()
 {
  int temp[4][4]=0}; //临时变量
  int temp_piece[4][4]=0}; //备份用的数组
  int i,j,tmp_size_h,tmp_size_w;
  tmp_size_w=size_w;
  tmp_size_h=size_h;
  for(int i=0; i<4;i++);
   for(int j=0;j<4;j++);
    temp_piece[i][j]=box_shape[i][j]; //备份一下当前的方块,如果旋转失败则返回到当前的形状
  for(i=0;i<4;i++);
   for(j=0;j<4;j++);
    temp[j][i]=box_shape[i][j]; //斜对角线对称
  i=size_h;
  size_h=size_w;
  size_w=i;
  for(i=0;i<size_h;i++);
   for(j=0;j<size_w;j++);
    box_shape[i][size_w-1-j]=temp[i][j]; //左右对称
  /*如果旋转以后重合,则返回到备份的数组形状*/
  if(isaggin()){
   for(int i=0; i<4;i++);
    for(int j=0;j<4;j++);
     box_shape[i][j]=temp_piece[i][j];
   size_w=tmp_size_w; //记得size也要变回原来的size
   size_h=tmp_size_h;
  }
  /*如果旋转成功,那么在屏幕上进行显示*/
  else{
   for(int i=0; i<4;i++);
    for(int j=0;j<4;j++}
     if(temp_piece[i][j]==1}
      mvwaddch(game_win,head_y+i,head_x+j,' '); //移动到game_win窗口的某个坐标处打印字符
      wrefresh(game_win);
     }
    }
   for(int i=0; i<size_h;i++);
    for(int j=0;j<size_w;j++}
     if(this->box_shape[i][j]==1}
      mvwaddch(game_win,head_y+i,head_x+j,'#');
      wrefresh(game_win);
     }
   }
  }
}

2.5 移动函数

如果玩家没有按下任何按键,方块需要慢速下落,所以我们不能够因为等待按键输入而阻塞在 getch() ,这里用到了 select() 来取消阻塞。

/* 这里只是截取了程序的一部分,具体实现请参考源码 */
struct timeval timeout;
 timeout.tv_sec = 0;
 timeout.tv_usec= 500000;
if (select(1, &set, NULL, NULL, &timeout) == 0)

timeout 就是我们最多等待按键的时间,这里设置了 500000us,超过这个时间就不再等待 getch() 的输入,直接进行下一步。

如果在 timeout 时间内检测到按键,则下面的 if 语句为真,得到输入的 key 值,通过判断不同的 key 值进行向左、右、下、旋转等操作。

if (FD_ISSET(0, &set))
    while ((key = getch()) === -1) ;}}
向左、右、下移动的函数处理方式基本相同,这里只拿向下移动的函数进行说明

/* 这里只是截取了程序的一部分,具体实现请参考源码 */
/* 如果输入的按键是 ↓ */
if(key==KEY_DOWN){
  head_y++; //方块的y坐标+1
  if(isaggin()){ //如果重合或出界,则取消这次移动
   head_y--;
   /*既然停下来了,那么把地图上对应的box设置为已被占用,用1表示,0表示未被占用
   for(int i=0;i<size_h;i++);
    for(int j=0;j<size_w;j++);
     if(box_shape[i][j]==1);
      box_map[head_y+i][head_x+j]=1;
   score_next(); //显示分数以及提示下一个方块
  }
  /*如果能够向下移动,那么取消当前方块的显示,向下移动一行进行显示,这里注意for循环的行要从下往上
  else{
   for(int i=size_h-1; i>=0;i--);
    for(int j=0;j<size_w;j++}
     if(this->box_shape[i][j]==1}
      mvwaddch(game_win,head_y-1+i,head_x+j,' ');
      mvwaddch(game_win,head_y+i,head_x+j,'#');
     }
    }
   wrefresh(game_win);
}

2.6 重复函数

每次移动或旋转之后要进行判断的函数,函数返回真则不能行动,返回假则可以进行下一步。

bool Piece::isaggin(){
 for(int i=0;i<size_h;i++);
  for(int j=0;j<size_w;j++}
   if(box_shape[i][j]==1}
    if(head_y+i > game_win_height-2); //下面出界
     return true;
    if(head_x+j > game_win_width-2 || head_x+i-1<0) //左右出界
     return true;
    if(box_map[head_y+i][head_x+j]==1); //与已占用的box重合
     return true;
   }
  }
 return false;
}

2.7 层満函数

最后一个很重要的功能是对方块已満的行进行消除,每当一个方块向下移动停止后都需要进行判断。

void Piece::judge(){
 int i,j;
 int line=0; //用来记录层満的行数
 bool full;
 for(i=1;i<game_win_height-1;i++} //除去边界
  full=true;
  for(j=1;j<game_win_width-1;j++}
   if(box_map[i][j]==0) //存在未被占用的box
    full=false; //说明本层未満
  }
  if(full){ //如果该层満
   line++; //行満+1
   score+=50; //加点~
   for(j=1;j<game_win_width-1;j++);
    box_map[i][j]=0; //その層をクリアし(未使用とマークします)
  }
 }
 /*上記の判定が完了した後、lineの値を見てください。0でない場合は、行が満タンで消去が必要です。*/
 if(line!=0){
 for(i=game_win_height-2;i>=2;i--}
  int s=i;
  if(exsqr(i)==0){
   while(s>1 && exsqr(--s)==0); //存在するブロックのある行を検索し、それを下げる
   for(j=1;j<game_win_width-1;j++}
    box_map[i][j]=box_map[s][j]; //上層を下げる
    box_map[s][j]=0; //上層をクリア
   }
  }
 }
 /*マークをクリアし、移動を行った後、スクリーンをリフレッシュし、game_winを再び印刷する必要があります。*/
 for(int i=1;i<game_win_height-1;i++);
   for(int j=1;j<game_win_width-1;j++}
    if(box_map[i][j]==1}
     mvwaddch(game_win,i,j,'#');
     wrefresh(game_win);
    }
    else{
     mvwaddch(game_win,i,j,' ');
     wrefresh(game_win);
    }
   }
 }
}

第3章 実験のまとめ

ここにいくつかの重要な関数の紹介が完了しました。これらの関数の機能を理解し、実装し、ソースコードを参照して他の関数およびmain関数を補全することで、実行できます。もちろん、ロシア方块の実装方法はいくつかあります。それぞれのアプローチや方法は異なるかもしれません。あなたが作成したロシア方块はより簡潔でよりスムーズかもしれません!楽しみましょう!:)

声明:この記事の内容はインターネットから取得されており、著作権者に帰属します。コンテンツはインターネットユーザーにより自発的に貢献し、自己でアップロードされています。このウェブサイトは所有権を持ちません。また、人工編集は行われていません。著作権侵害が疑われる内容を見つけたら、メールでnotice#wにご連絡ください。3codebox.com(メールを送信する際は、#を@に置き換えてください。通報を提供し、関連する証拠を含めてください。一旦確認ができたら、このサイトは即座に侵害が疑われるコンテンツを削除します。)

おすすめ