English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
Одна. Обзор эксперимента
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 (при отправке письма, пожалуйста, замените # на @) для сообщения о нарушении,并提供 соответствующие доказательства. При подтверждении факта нарушения сайт немедленно удалят涉嫌侵权的内容.