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

Uso de C++Implementación posterior del Tetris paso a paso

I. Introducción al experimento

1.1 Contenido del experimento

En esta sección del experimento implementaremos el diseño de las funciones principales del Tetris, completaremos las funciones básicas y ejecutaremos.

1.2 Conocimientos del experimento

Dibujo de la ventana
Diseño de la clase de bloques
Algoritmo de rotación
Funciones de movimiento y eliminación

1.3 Ambiente de experimento

xface terminal
g++ Compilador
Biblioteca ncurses

1.4 Compilar el programa

El comando de compilación debe incluir -l Opciones para incluir la biblioteca ncurses:

g++ main.c -l ncurses

1.5 Ejecutar el programa

./a.out

1.6 Resultados de la ejecución


II. Pasos del experimento

2.1 Archivos de cabecera

Primero incluir los archivos de cabecera y definir una función de intercambio y una función de número aleatorio, que se utilizan posteriormente (la función de intercambio se utiliza para girar los bloques y el número aleatorio se utiliza para establecer la forma de los bloques)

#include <iostream>
#include <sys/time.h>
#include <sys/types.h>
#include <stdlib.h>
#include <ncurses.h>
#include <unistd.h>
/* Intercambiar a y b */
void swap(int &a, int &b){
 int t=a;
 a = b;
 b = t;
}
/* Obtener un entero aleatorio en el rango (min, max)
int getrand(int min, int max)
{
 return(min+rand()%(max-min+1));
}

2.2 Definir clase

Debido a que el contenido del programa es relativamente simple, aquí solo se define una clase Piece

class Piece
 {
 public:
  int score;  //Puntos
  int shape;  //Representa la forma del bloque actual
  int next_shape;  //Representa la forma del siguiente bloque
  int head_x;  //La posición del primer box del bloque actual, marcador de posición
  int head_y;
  int size_h;  //El tamaño actual del bloque
  int size_w;
  int next_size_h;  //El tamaño del siguiente bloque
  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}; //Variable temporal
  int temp_piece[4][4]= {0}; //Array de respaldo
  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]; //Hacer una copia del cuadro actual, si la rotación falla, regresa a la forma actual
  for(i=0;i<4;i++);
   for(j=0;j<4;j++);
    temp[j][i]=box_shape[i][j]; //Simetría diagonal
  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]; //Simetría bilateral
  /*Si hay superposición después de la rotación, regresa a la forma guardada en el array de respaldo*/
  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; //Recuerda que el tamaño también debe regresar al tamaño original
   size_h=tmp_size_h;
  }
  /*Si la rotación tiene éxito, se muestra en la pantalla*/
  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,' '); //Moverse a alguna coordenada en la ventana game_win para imprimir caracteres
      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 Función de movimiento

Si el jugador no presiona ninguna tecla, el cuadro debe caer lentamente, por lo que no podemos bloquearnos esperando la entrada de getch(), aquí se utiliza select() para cancelar el bloqueo.

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

El tiempo de espera es el tiempo máximo que esperamos a que se pulse una tecla, aquí se ha configurado 500000us, si se supera este tiempo, ya no se espera la entrada de getch(), y se procede directamente al siguiente paso.

Si se detecta una tecla pulsada dentro del tiempo de espera, la condición if siguiente es verdadera, se obtiene el valor de la clave de entrada, y se realizan operaciones como desplazamiento a la izquierda, derecha, abajo y rotación mediante la evaluación de diferentes valores de clave.

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; //Vaciar esa capa (marcar como no utilizada)
  }
 }
 /*Después de la verificación anterior, revise el valor de line. Si no es 0, significa que hay una capa llena que necesita ser eliminada*/
 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); //Buscar las filas donde existen bloques y moverlas hacia abajo
   for(j=1;j<game_win_width-1;j++){
    box_map[i][j]=box_map[s][j]; //Mover la capa superior hacia abajo
    box_map[s][j]=0; //Borrar la capa superior
   }
  }
 }
 /*Después de vaciar y mover las marcas, debe actualizarse la pantalla nuevamente, imprima game_win de nuevo*/
 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);
    }
   }
 }
}

Tercero, resumen del experimento

Con esto, la introducción a varias funciones clave se ha completado. Entender las funciones de estas funciones y lograr su implementación, luego referirse al código fuente para completar otras funciones y la función main, luego puede ejecutarse. ¡Por supuesto, hay muchos métodos para implementar Tetris, y cada persona puede tener enfoques y métodos diferentes, tal vez su Tetris sea más simple y más fluido! ¡Disfrútelo! :)

Declaración: el contenido de este artículo se obtiene de la red, es propiedad del autor original, el contenido se contribuye y sube por los usuarios de Internet, este sitio no posee los derechos de propiedad, no se ha realizado una edición humana y no asume ninguna responsabilidad legal. Si encuentra contenido sospechoso de infracción de derechos de autor, le invitamos a enviar un correo electrónico a: notice#oldtoolbag.com (al enviar un correo electrónico, reemplace # con @ para denunciar y proporcione evidencia. Una vez confirmado, este sitio eliminará inmediatamente el contenido sospechoso de infracción de derechos de autor.)

Te gustará