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

Продолжение реализации Tetris на C++

Одна. Обзор эксперимента

1.1 Содержание эксперимента

В этом разделе эксперимента мы реализуем дизайн основных функций игры Tetris, завершим основные функции и запустим.

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

class Piece
 {
 public:
  int score; //Очки
  int shape; //Показывает форму текущего блока
  int next_shape; //Показывает форму следующего блока
  int head_x; //Положение первого box текущего блока, пометка позиции
  int head_y;
  int size_h; //Размер текущего блока
  int size_w;
  int next_size_h; //Размер следующего блока
  int next_size_w;
  int box_shape[4][4]; //Массив формы текущего блока 4x4
  int next_box_shape[4][4]; //Массив формы следующего блока 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 массив, используемый для представления, нулями*/
 int i,j;
 for(i=0;i<4;i++)
  for(j=0;j<4;j++)
   shape[i][j] = 0;
 /*Установить 7 начальных форм и установить их размер*/
 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=true;
}

2.4 Функция вращения

здесь используется очень простой алгоритм для вращения фигуры, подобный вращению матрицы, сначала производим симметрию по диагонали, затем симметрию слева направо, и вращение завершено, необходимо проверить, не выходит ли фигура за пределы или не совпадает ли после вращения, если да, то отменяем это вращение.

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

Третий раздел: обобщение эксперимента

Вот и заканчивается описание нескольких ключевых функций, чтобы понять функциональность этих функций и реализовать их, затем можно дополнить другие функции и функцию main, чтобы запустить! Конечно, существует множество методов реализации игры "Тетрис",思路和方法可能因人而异, возможно, вы напишите более простую и плавную игру "Тетрис"! Наслаждайтесь ею! :)

Заявление: контент этой статьи был получен из Интернета, авторские права принадлежат соответствующему автору, контент был предоставлен пользователями Интернета по своей инициативе, этот сайт не имеет права собственности, не был отредактирован вручную, и не несет ответственности за соответствующие юридические вопросы. Если вы обнаружите контент,涉嫌侵犯版权, пожалуйста, отправьте письмо по адресу: notice#oldtoolbag.com (при отправке письма, пожалуйста, замените # на @) для сообщения о нарушении,并提供 соответствующие доказательства. При подтверждении факта нарушения сайт немедленно удалят涉嫌侵权的内容.

Давай посмотрим!