使用C++一步步实现俄罗斯方块后续

 更新时间:2020年4月25日 17:29  点击:1684

一、实验简介

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 类

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]; //当前方块的shpe数组 4x4
  int next_box_shape[4][4];  //下一个方块的shpe数组 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);
    }
   }
 }
}

三、实验总结

到这里几个关键函数的介绍也就完成了,搞明白这些函数的功能并实现,再参考源码补全其他函数以及main函数就可以运行啦!当然俄罗斯方块的实现方法还有很多,每个人的思路和方法可能会不一样,或许你写出来的俄罗斯方块更简洁、更流畅! Enjoy it !:)

[!--infotagslink--]

相关文章

  • C++ STL标准库std::vector的使用详解

    vector是表示可以改变大小的数组的序列容器,本文主要介绍了C++STL标准库std::vector的使用详解,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2022-03-06
  • C++中取余运算的实现

    这篇文章主要介绍了C++中取余运算的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-02-23
  • 详解C++ string常用截取字符串方法

    这篇文章主要介绍了C++ string常用截取字符串方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25
  • C++调用C#的DLL程序实现方法

    本文通过例子,讲述了C++调用C#的DLL程序的方法,作出了以下总结,下面就让我们一起来学习吧。...2020-06-25
  • C++中四种加密算法之AES源代码

    本篇文章主要介绍了C++中四种加密算法之AES源代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。...2020-04-25
  • C++ 整数拆分方法详解

    整数拆分,指把一个整数分解成若干个整数的和。本文重点给大家介绍C++ 整数拆分方法详解,非常不错,感兴趣的朋友一起学习吧...2020-04-25
  • C++中 Sort函数详细解析

    这篇文章主要介绍了C++中Sort函数详细解析,sort函数是algorithm库下的一个函数,sort函数是不稳定的,即大小相同的元素在排序后相对顺序可能发生改变...2022-08-18
  • C++万能库头文件在vs中的安装步骤(图文)

    这篇文章主要介绍了C++万能库头文件在vs中的安装步骤(图文),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-02-23
  • 详解C++ bitset用法

    这篇文章主要介绍了C++ bitset用法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25
  • 浅谈C++中的string 类型占几个字节

    本篇文章小编并不是为大家讲解string类型的用法,而是讲解我个人比较好奇的问题,就是string 类型占几个字节...2020-04-25
  • C++ Eigen库计算矩阵特征值及特征向量

    这篇文章主要为大家详细介绍了C++ Eigen库计算矩阵特征值及特征向量,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-04-25
  • VSCode C++多文件编译的简单使用方法

    这篇文章主要介绍了VSCode C++多文件编译的简单使用方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-03-29
  • C++ pair的用法实例详解

    这篇文章主要介绍了C++ pair的用法实例详解的相关资料,需要的朋友可以参考下...2020-04-25
  • C++中的循环引用

    虽然C++11引入了智能指针的,但是开发人员在与内存的斗争问题上并没有解放,如果我门实用不当仍然有内存泄漏问题,其中智能指针的循环引用缺陷是最大的问题。下面通过实例代码给大家介绍c++中的循环引用,一起看看吧...2020-04-25
  • C++随机点名生成器实例代码(老师们的福音!)

    这篇文章主要给大家介绍了关于C++随机点名生成器的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25
  • C++如何删除map容器中指定值的元素详解

    map容器是C++ STL中的重要一员,删除map容器中value为指定元素的问题是我们经常与遇到的一个问题,下面这篇文章主要给大家介绍了关于利用C++如何删除map容器中指定值的元素的相关资料,需要的朋友可以参考借鉴,下面来一起看看吧。...2020-04-25
  • C++ 约瑟夫环问题案例详解

    这篇文章主要介绍了C++ 约瑟夫环问题案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下...2021-08-15
  • C++中cin的用法详细

    这篇文章主要介绍了C++中cin的用法详细,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25
  • 基于C++中常见编译错误的总结详解

    本篇文章是对C++中的常见编译错误进行了详细的分析介绍,需要的朋友参考下...2020-04-25
  • c++优先队列(priority_queue)用法详解

    这篇文章主要介绍了c++优先队列(priority_queue)用法详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25