c语言——俄罗斯方块

简介: c语言——俄罗斯方块

一、游戏效果



二. 游戏背景

俄罗斯方块是久负盛名的游戏,它也和贪吃蛇,扫雷等游戏位列经典游戏的⾏列。

《俄罗斯方块》(Tetris,俄文:Тетрис)是一款由俄罗斯人阿列克谢·帕基特诺夫于1984年6月发明的休闲游戏。

该游戏曾经被多家公司代理过。经过多轮诉讼后,该游戏的代理权最终被任天堂获得。 任天堂对于俄罗斯方块来说意义重大,因为将它与GB搭配在一起后,获得了巨大的成功。

《俄罗斯方块》的基本规则是移动、旋转和摆放游戏自动输出的各种方块,使之排列成完整的一行或多行并且消除得分。

三、游戏开发日志

基本逻辑结构与贪吃蛇一致

四、游戏实现

我们有了贪吃蛇的知识储备及了解WIN32 API,理解下面内容也就不难了。

4.1  游戏逻辑主体

#include"tetris.h"
int main()
{
  //修改当前地区为本地模式,为了⽀持中⽂宽字符的打印
  setlocale(LC_ALL, "");
  srand((unsigned int)time(NULL));
  char ch=0;
  do
  {
    GameInit();
    GameRun();
    GameOver();
    system("cls");
    SetPos(20, 12);
    printf("再来一局吗?(Y/N):\n");
    SetPos(22, 15);
    ch = getchar();
    
    getchar();//清理\n
  } while (ch == 'y' || ch == 'Y');
  SetPos(0, 29);
  
  return 0;
}

4.2  游戏初始化的实现

4.2.1  设置窗口大小、名称及隐藏光标

//设置控制台窗⼝的⼤⼩,30⾏,30列
  system("mode con cols=60 lines=30");
  //设置cmd窗⼝名称
  system("title  俄罗斯方块");
  //获取标准输出的句柄(⽤来标识不同设备的数值)
  HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
  //影藏光标操作
  CONSOLE_CURSOR_INFO CursorInfo;
  GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息
  CursorInfo.bVisible = false; //隐藏控制台光标
  SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态

4.2.2 打印欢迎界面

void WelcomeToGame()
{
  SetPos(20, 10);
  printf("欢迎来到俄罗斯方块!");
  SetPos(20, 23);//让按任意键继续的出现的位置好看点
  system("pause");
  system("cls");
  SetPos(14, 10);
  printf("↑为变换.↓为加速.←为左移.→为右移\n");
  
  SetPos(20, 23);//让按任意键继续的出现的位置好看点
  system("pause");
  system("cls");
}

4.2.3  初始化地图

void CreateMap()
{
  memset(IF.pos, 0, sizeof(int)*30*60);//对数组初始化(如果不初始化,FAIL后第二次循环会IF.pos会保留上次数据)
  int i = 0;
  //墙体轮廓
  SetPos(40, 0);
  for (i = 40; i < 60; i += 2)
  {
    
    wprintf(L"%lc", WALL);
  }
  SetPos(0,29);
  for (i = 0; i < 60; i += 2)
  {
    IF.pos[i][29] = 1;
    wprintf(L"%lc", WALL);
  }
  //x是0,y从1开始增⻓
  for (i = 0; i < 30; i++)
  {
    IF.pos[0][i] = 1;//标记此处有方块
    SetPos(0, i);
    wprintf(L"%c", WALL);
  }
  
  for (i = 0; i < 30; i++)
  {
    IF.pos[38][i] = 1;
    SetPos(38, i);
    wprintf(L"%c", WALL);
  }
  for (i = 0; i < 30; i++)
  {
    IF.pos[58][i] = 1;
    SetPos(58, i);
    wprintf(L"%c", WALL);
  }
  SetPos(40, 10);
  for (i = 40; i < 60; i += 2)
  {
    IF.pos[i][10] = 1;
    wprintf(L"%lc", WALL);
  }
  //提示字体
  SetPos(40, 1);
  printf("下一个方块:");
  SetPos(44, 12);
  printf("左移:←");
  SetPos(44, 14);
  printf("右移:→");
  SetPos(44, 16);
  printf("加速:↓");
  SetPos(44, 18);
  printf("变换:↑");
  SetPos(44, 20);
  printf("暂停: 空格");
  SetPos(44, 22);
  printf("退出: Esc");
  SetPos(44, 24);
  printf("重新开始:R");
  
    SetPos(44, 26);
  printf("当前分数:%d", grade);
  
}

注解:

1)一个宽字符占2个字节,我们设置 cols=60 lines=30,实际就是长宽各30个字节大小

2)如上图所示,游戏展示区长为20,高为30,右上部分为下一个方块提示区域,正下面为游戏操作提示区

3)横轴为x轴,向右坐标逐渐增大,向下为y轴,坐标逐渐增大。(x,y)确定宽字符(小方块)的位置,因为宽字符(小方块)占2个字节宽度,所以x轴坐标没有奇数值

4)为了方便检测下落方块是否靠墙、落地及碰到其它方块,我们定义一个大数组pos[60][30],一一对应地图上每个宽字符(小方块)的位置,用大数组pos[60][30]记录墙体位置及落地的方块位置

typedef struct InterFace
{
  int pos[60][30]; //用于标记整个界面有方块的位置(1为有,0为无)
  
}InterFace;
InterFace IF;

4.2.4 初始化方块信息

void InitBlockInfo()
{
  //'T'字型
  int i = 0;
  for (i = 0; i < 3; i++)
  {
    block[0][0].space[1][i] = 1;
  }
  block[0][0].space[2][1] = 1;
  
  //‘L'字型
  for (i = 1; i < 4; i++)
  {
    block[1][0].space[i][1] = 1;
  }
  block[1][0].space[3][2] = 1;
  //'J'字形
  for (i = 1; i < 4; i++)
  {
    block[2][0].space[i][2] = 1;
  }
  block[2][0].space[3][1] = 1;
  //’Z'字型
  for (i = 0; i < 2; i++)
  {
    block[3][0].space[1][i] = 1;
  }
  for (i = 1; i < 3; i++)
  {
    block[3][0].space[2][i] = 1;
  }
  //‘S'字型
  for (i = 1; i < 3; i++)
  {
    block[4][0].space[1][i] = 1;
  }
  for (i = 0; i < 2; i++)
  {
    block[4][0].space[2][i] = 1;
  }
  //'田’字型
  int j = 0;
  for (i = 1; i < 3; i++)
  {
    for (j = 1; j < 3; j++)
    {
      block[5][0].space[i][j] = 1;
    }
  }
  //长条型
  for (i = 0; i < 4; i++)
  {
    block[6][0].space[i][1] = 1;
  }
  int m = 0;
  int n = 0;
  int tmp[4][4];
  for (i = 0; i < 7; i++)
  {
    block[i][0].speed = 200;//初始化下落速度
    block[i][0].blockstate = NORMAL;
  }
  for (i = 0; i < LINES; i++)
  {
    for (j = 0; j < COLS-1; j++)
    {
      for (m = 0; m < 4; m++)
      {
        for (n = 0; n < 4; n++)
        {
          tmp[m][n] = block[i][j].space[m][n];
        }
      }
      for (m = 0; m < 4; m++)
      {
        for (n = 0; n < 4; n++)
        {
          block[i][j + 1].space[n][3-m] = tmp[m][n];
        }
      }
      block[i][j + 1].speed = 200;//初始化下落速度
      block[i][j + 1].blockstate =NORMAL;//初始化方块状态
    }
  }
  
}

注解:

1)我们定义结构体二位数组,用于存储7种基本形状方块的各自的4种形态的信息,共28种。

#define  COLS 4
#define LINES  7
enum BlockState{CHANGE,LEFT,RIGHT,DOWN,NORMAL};
typedef struct Block
{
  int space[4][4];
  int speed;
  enum BlockState blockstate;
}Block; 
Block block[LINES][COLS] ;//结构体二位数组,用于存储7种基本形状方块的各自的4种形态的信息,共28种

2)首先初始化7种基本形状方块,然后基本形状方块通过顺时针旋转依次得到各自剩下的三种形态。

4.3  游戏运行的实现

void GameRun()
{
  
  int i = rand() % 7, j = rand() % 4; //随机获取方块的形状
  do
  {
    int x = 18;
    int y = 0;
    int next_i =  rand() % 7, next_j = rand() % 4;
    PrintBlock(next_i, next_j, 46, 4);//绘制提示框图形
    PrintBlock(i, j, 18, 0);//绘制方块初始位置
    while(IsLegal(i, j, x, y ))//不合法,说明到底了
    {
      
      if (KEY_PRESS(VK_UP))
      {
        block[i][j].blockstate = CHANGE;
      }
      else if (KEY_PRESS(VK_DOWN))
      {
        block[i][j].blockstate = DOWN;
      }
      else if (KEY_PRESS(VK_LEFT))
      {
        block[i][j].blockstate = LEFT;//左移
      }
      else if (KEY_PRESS(VK_RIGHT))
      {
        block[i][j].blockstate = RIGHT;//右移
      }
      else if (KEY_PRESS(VK_SPACE))
      {
        pause();//暂停
      }
      else if (KEY_PRESS(VK_ESCAPE))
      {
        state = END;//退出
        break;
      }
      else if (KEY_PRESS(R))
      {
        state = RESTART;//重新开始
        break;
      }
      else//什么都没按
      {
        block[i][j].blockstate = NORMAL;
      }
      BlockMove(block, &i, &j, &x,&y);
      PrintBlock(i, j, x, y);//绘制方块位置
      Sleep(block[i][j].speed);//每一帧休息的时间
    }
    i = next_i;
    j = next_j;
    PrintSpace(i,j, 46, 4);//覆盖提示框图形
  } while (state ==RUN);
}

4.3.1  合法判断(碰撞检测)

//碰撞检测
int IsLegal(int i, int j, int x, int y)
{
  for (int m = 0; m < 4; m++)
  {
    for (int n = 0; n< 4; n++)
    {
      //如果方块落下的位置本来就已经有方块了,则不合法
      if ((block[i][j].space[m][n] == 1) && (IF.pos[x + n * 2][y + m] == 1))
        return 0; //不合法
    }
  }
  return 1; //合法
}

4.3.2  覆盖方块

void  PrintSpace(int i, int j, int  x, int y)//覆盖指定位置方块
{
  for (int m = 0; m< 4; m++)
  {
    for (int n = 0; n < 4; n++)
    {
      if (block[i][j].space[m][n] == 1)
      {
        SetPos(x+2*n,y+m); //光标跳转到指定位置
        printf("  "); //打印空格覆盖(两个空格)
      }
    }
  }
}

4.3.3  绘制方块

void PrintBlock(int i, int j, int x, int y)
{
int m = 0;
int n = 0;
for (m = 0; m < 4; m++)
{
  for (n = 0; n < 4; n++)
  {
    if (block[i][j].space[m][n] == 1)
    {
      //IF.pos[x+n*2][y+m] = 1;//记录该位置有方块
      SetPos(x + n * 2, y + m);
      wprintf(L"%c", BIOCK);
    }
  }
}
}

4.3.4   判断满行消除

int JudeIsErase()
{
  int sign = 0;
  int i = 0;
  
  for (i = 28; i > 3; i--)
  {
    int count = 0;
    for (int j = 2; j < 38; j += 2)
    {
      if (IF.pos[j][i] == 1)
      {
        count++;
      }
    }
    if (count == 18)//整行全是方块
    {
      sign = 1;
      grade += 10;
      SetPos(44, 26);
      printf("当前分数:%d", grade);//更新当前分数
      for (int a = 2; a < 38; a += 2)
      {
        SetPos(a, i);
        printf("  ");
        IF.pos[a][i] = 0;
      }
      //将该行以上全部下移一格
      for (int m = i; m > 3; m--)
      {
        
        for (int n = 2; n < 38; n += 2)
        {
          
          IF.pos[n][m] = IF.pos[n][m - 1]; //将上一行方块移到下一行
          if (IF.pos[n][m]==1) //上一行移下来的是方块,打印方块
          {
            SetPos(n, m); //光标跳转到该位置
            wprintf(L"%c", BIOCK); //打印方块
          }
          else //上一行移下来的是空格,打印空格
          {
            SetPos(n, m); //光标跳转到该位置
            printf("  "); //打印空格(两个空格)
          }
        }
        
      }
    }
  }
  return sign;
}

注:

1)用大数组pos[60][30],从下往上判断是否满行,如果满行,则将将该行以上全部下移一格,这样就达到消行的目的

2)若第十四第十三行都满行了,当检测到第十四行满行,将第十三行及以上全部下移一格,再次循环,从现十三行(原十二行)开始检测,此时会漏掉现十四行(原十三行),所以引进标识变量sign,只要有消行,则变为1,当sign=0时,表示全部方块没有满行了

4.4.5   判断游戏失败

void JudeIsOver()
{
  for (int i = 2; i < 38; i+=2)
  {
    if (IF.pos[i][1] == 1) //第一行有方块存在
    {
      state = FAIL;
      
      
    }
  }
  
}

4.4.6  方块移动

void BlockMove(Block block[][COLS], int *i, int *j, int *x, int *y)
{
  switch (block[*i][*j].blockstate)
  {
  case CHANGE:
    if (IsLegal(*i, ((*j) + 1) % 4, *x, (*y )+1) == 1) //判断方块变化后(变化的同时下降一格)是否碰撞
    {
      //方块旋转后合法才进行以下操作
      PrintSpace(*i, *j, *x, *y); //用空格覆盖当前方块所在位置
      (*j) = ((*j) + 1) % 4;
      (*y)++;
      
      
      //PrintBlock(i, j, x, y);//绘制下一个变化的方块
      block[*i][*j].blockstate = NORMAL;
      block[*i][*j].speed = 200;
    }
    //else //方块再下落就碰撞了(已经到达底部)
    //{
    //  //将当前方块的信息录入IF当中
    //  for (int m = 0; m< 4; m++)
    //  {
    //    for (int n = 0; n< 4; n++)
    //    {
    //      if (block[*i][*j].space[m][n] == 1)
    //      {
    //        IF.pos[(*x )+n*2][(*y )+m] = 1; //将该位置标记为有方块
    //        
    //      }
    //    }
    //  }
    //  while (JudeIsErase()); //判断此次方块下落是否得分
    //  JudeIsOver();
  
    //}
    break;
  case LEFT:
    if (IsLegal(*i, *j,(*x )-2, *y ) == 1) //判断方块左移后是否合法
    {
      //方块左移后合法才进行以下操作
      PrintSpace(*i, *j, *x, *y); //用空格覆盖当前方块所在位置
    
      (*x) = (*x) - 2;
      block[*i][*j].blockstate = NORMAL;
      block[*i][*j].speed = 200;
      
      //PrintBlock(i, j, x, y);//绘制下一个变化的方块
    }
    break;
  case RIGHT:
    if (IsLegal(*i, *j, (*x) + 2, *y) == 1) //判断方块右移后是否合法
    {
      //方块右移后合法才进行以下操作
      PrintSpace(*i, *j, *x, *y); //用空格覆盖当前方块所在位置
      (*x) = (*x) + 2;
      block[*i][*j].blockstate = NORMAL;
      block[*i][*j].speed = 200;
      //PrintBlock(i, j, x, y);//绘制下一个变化的方块
    }
    break;
  case DOWN:
    if (IsLegal(*i, *j,* x , (*y) + 1) == 1) //判断方块下移后是否合法
    {
      //方块下移后合法才进行以下操作
      PrintSpace(*i,* j, *x,* y); //用空格覆盖当前方块所在位置
      (*y)++;
      block[*i][*j].blockstate = NORMAL;
      if (block[*i][*j].speed>50)//避免连续按,变成负数,会出现bug
      {
        block[*i][*j].speed -= 50;
      }
      
    }
    else //碰撞
    {
      //将当前方块的信息录入IF当中
      for (int m = 0; m < 4; m++)
      {
        for (int n = 0; n < 4; n++)
        {
          if (block[*i][*j].space[m][n] == 1)
          {
            IF.pos[(*x) + n * 2][(*y) + m] = 1; //将该位置标记为有方块
          }
        }
      }
      while (JudeIsErase()); //判断此次方块下落是否得分
      JudeIsOver();
    }
    break;
  case NORMAL:
    if (IsLegal(*i, *j,*x, (*y) + 1) == 1) //方块再下落合法
    {
      //方块右移后合法才进行以下操作
      PrintSpace(*i, *j, *x,* y); //用空格覆盖当前方块所在位置
      
      (*y)++;
      block[*i][*j].blockstate = NORMAL;
      block[*i][*j].speed=200;
      
    }
    else //碰撞
    {
      //将当前方块的信息录入IF当中
      for (int m = 0; m < 4; m++)
      {
        for (int n = 0; n < 4; n++)
        {
          if (block[*i][*j].space[m][n] == 1)
          {
            IF.pos[(*x) + n * 2][(*y) + m] = 1; //将该位置标记为有方块
          }
        }
      }
      while (JudeIsErase()); //判断此次方块下落是否得分
      JudeIsOver();
    }
    break;
  }
  
}

注:

1)这里我们根据方块状态判断方块的下一步位置和形状,需要改变参数,所以传的是地址

2)只有在正常下落和加速下落才需要描述不合法时(碰撞时)状况,因为此时已经到底了。变换方块时不需要描述不合法(碰撞时)状况(因为'L'型,在变换时容易与边界发生碰撞,若此时将方块位置记录在pos[60][30]的大数组中,导致跳出该循环,进行下一个方块判断,导致该方块悬浮在半空的BUG)

4.4   游戏结束的实现

enum GameState{ RUN = 1,END,FAIL,RESTART };
enum GameState state;
void GameOver()
{
  switch (state)
  {
  case END:
    SetPos(14, 15);
    printf("您主动退出游戏\n");
    SetPos(14, 18);
    system("pause");
    break;
  case FAIL:
    Sleep(1000); //留给玩家反应时间
    system("cls"); //清空屏幕
    SetPos(20, 10);
    printf("小垃圾,游戏结束!\n");
    SetPos(20, 14);
    printf("你的最终得分为: %d ", grade);
    SetPos(20, 18);
    system("pause");
    break;
  case RESTART:
    system("cls"); //清空屏幕
    SetPos(26, 10);
    printf("重新开始!\n");
    SetPos(22, 14);
    printf("小垃圾,你准备好了吗?\n");
    Sleep(2000);
    main();
    break;
  }
}

注:

1)对游戏的三个状态分别进行描述。

五、完整代码

5.1  tetris.h

#include<Windows.h>
#include <locale.h>
#include<stdbool.h>
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<windows.h>
#define WALL  L'□'
#define BIOCK  L'■' 
#define  COLS 4
#define LINES  7
#define R 0x52  
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK)&0x1) ? 1 : 0)
enum GameState{ RUN = 1,END,FAIL,RESTART };
enum GameState state;
enum BlockState{CHANGE,LEFT,RIGHT,DOWN,NORMAL};
int grade;
typedef struct InterFace
{
  int pos[60][30]; //用于标记整个界面有方块的位置(1为有,0为无)
  
}InterFace;
InterFace IF;
typedef struct Block
{
  int space[4][4];
  int speed;
  enum BlockState blockstate;
}Block; 
Block block[LINES][COLS] ;//结构体二位数组,用于存储7种基本形状方块的各自的4种形态的信息,共28种
void GameInit();
void CreateMap();
void InitBlockInfo();
void GameRun();
void PrintBlock(int i,int j,int x,int y);//ij 为方块形状,x,y为打印坐标
void BlockMove(Block block[][COLS], int *i, int *j, int *x, int *y);
void  PrintSpace(int i, int j, int  x, int y);
int JudeIsErase();
void JudeIsOver();
void GameOver();

5.2   tetris.c

#include"tetris.h"
//设置光标的坐标
void SetPos(short x, short y)
{
  COORD pos = { x, y };
  HANDLE hOutput = NULL;
  //获取标准输出的句柄(⽤来标识不同设备的数值)
  hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
  //设置标准输出上光标的位置为pos
  SetConsoleCursorPosition(hOutput, pos);
  
}
void WelcomeToGame()
{
  SetPos(20, 10);
  printf("欢迎来到俄罗斯方块!");
  SetPos(20, 23);//让按任意键继续的出现的位置好看点
  system("pause");
  system("cls");
  SetPos(14, 10);
  printf("↑为变换.↓为加速.←为左移.→为右移\n");
  
  SetPos(20, 23);//让按任意键继续的出现的位置好看点
  system("pause");
  system("cls");
}
void CreateMap()
{
  memset(IF.pos, 0, sizeof(int)*30*60);//对数组初始化(如果不初始化,FAIL后第二次循环会IF.pos会保留上次数据)
  int i = 0;
  //墙体轮廓
  SetPos(40, 0);
  for (i = 40; i < 60; i += 2)
  {
    
    wprintf(L"%lc", WALL);
  }
  SetPos(0,29);
  for (i = 0; i < 60; i += 2)
  {
    IF.pos[i][29] = 1;
    wprintf(L"%lc", WALL);
  }
  //x是0,y从1开始增⻓
  for (i = 0; i < 30; i++)
  {
    IF.pos[0][i] = 1;//标记此处有方块
    SetPos(0, i);
    wprintf(L"%c", WALL);
  }
  
  for (i = 0; i < 30; i++)
  {
    IF.pos[38][i] = 1;
    SetPos(38, i);
    wprintf(L"%c", WALL);
  }
  for (i = 0; i < 30; i++)
  {
    IF.pos[58][i] = 1;
    SetPos(58, i);
    wprintf(L"%c", WALL);
  }
  SetPos(40, 10);
  for (i = 40; i < 60; i += 2)
  {
    IF.pos[i][10] = 1;
    wprintf(L"%lc", WALL);
  }
  //提示字体
  SetPos(40, 1);
  printf("下一个方块:");
  SetPos(44, 12);
  printf("左移:←");
  SetPos(44, 14);
  printf("右移:→");
  SetPos(44, 16);
  printf("加速:↓");
  SetPos(44, 18);
  printf("变换:↑");
  SetPos(44, 20);
  printf("暂停: 空格");
  SetPos(44, 22);
  printf("退出: Esc");
  SetPos(44, 24);
  printf("重新开始:R");
  /*SetPos(44, 26);
  printf("最高纪录:%d", max);*/
  SetPos(44, 26);
  printf("当前分数:%d", grade);
  /*getchar();*/
}
void InitBlockInfo()
{
  //'T'字型
  int i = 0;
  for (i = 0; i < 3; i++)
  {
    block[0][0].space[1][i] = 1;
  }
  block[0][0].space[2][1] = 1;
  
  //‘L'字型
  for (i = 1; i < 4; i++)
  {
    block[1][0].space[i][1] = 1;
  }
  block[1][0].space[3][2] = 1;
  //'J'字形
  for (i = 1; i < 4; i++)
  {
    block[2][0].space[i][2] = 1;
  }
  block[2][0].space[3][1] = 1;
  //’Z'字型
  for (i = 0; i < 2; i++)
  {
    block[3][0].space[1][i] = 1;
  }
  for (i = 1; i < 3; i++)
  {
    block[3][0].space[2][i] = 1;
  }
  //‘S'字型
  for (i = 1; i < 3; i++)
  {
    block[4][0].space[1][i] = 1;
  }
  for (i = 0; i < 2; i++)
  {
    block[4][0].space[2][i] = 1;
  }
  //'田’字型
  int j = 0;
  for (i = 1; i < 3; i++)
  {
    for (j = 1; j < 3; j++)
    {
      block[5][0].space[i][j] = 1;
    }
  }
  //长条型
  for (i = 0; i < 4; i++)
  {
    block[6][0].space[i][1] = 1;
  }
  int m = 0;
  int n = 0;
  int tmp[4][4];
  for (i = 0; i < 7; i++)
  {
    block[i][0].speed = 200;//初始化下落速度
    block[i][0].blockstate = NORMAL;
  }
  for (i = 0; i < LINES; i++)
  {
    for (j = 0; j < COLS-1; j++)
    {
      for (m = 0; m < 4; m++)
      {
        for (n = 0; n < 4; n++)
        {
          tmp[m][n] = block[i][j].space[m][n];
        }
      }
      for (m = 0; m < 4; m++)
      {
        for (n = 0; n < 4; n++)
        {
          block[i][j + 1].space[n][3 - m] = tmp[m][n];
        }
      }
      block[i][j + 1].speed = 200;//初始化下落速度
      block[i][j + 1].blockstate =NORMAL;//初始化方块状态
    }
  }
  
}
void GameInit()
{
  //设置控制台窗⼝的⼤⼩,30⾏,30列
  system("mode con cols=60 lines=30");
  //设置cmd窗⼝名称
  system("title  俄罗斯方块");
  //获取标准输出的句柄(⽤来标识不同设备的数值)
  HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
  //影藏光标操作
  CONSOLE_CURSOR_INFO CursorInfo;
  GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息
  CursorInfo.bVisible = false; //隐藏控制台光标
  SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态
  //打印欢迎界⾯
  WelcomeToGame();
  //初始化地图
  CreateMap();
  //初始化方块信息
  InitBlockInfo();
  //初始化游戏状态
  state = RUN;
}
void PrintBlock(int i, int j, int x, int y)
{
int m = 0;
int n = 0;
for (m = 0; m < 4; m++)
{
  for (n = 0; n < 4; n++)
  {
    if (block[i][j].space[m][n] == 1)
    {
      //IF.pos[x+n*2][y+m] = 1;//记录该位置有方块
      SetPos(x + n * 2, y + m);
      wprintf(L"%c", BIOCK);
    }
  }
}
}
void pause()//暂停
{
  while (1)
  {
    Sleep(300);
    if (KEY_PRESS(VK_SPACE))
    {
      break;
    }
  }
}
//碰撞检测
int IsLegal(int i, int j, int x, int y)
{
  for (int m = 0; m < 4; m++)
  {
    for (int n = 0; n< 4; n++)
    {
      //如果方块落下的位置本来就已经有方块了,则不合法
      if ((block[i][j].space[m][n] == 1) && (IF.pos[x + n * 2][y + m] == 1))
        return 0; //不合法
    }
  }
  return 1; //合法
}
void  PrintSpace(int i, int j, int  x, int y)//覆盖指定位置方块
{
  for (int m = 0; m< 4; m++)
  {
    for (int n = 0; n < 4; n++)
    {
      if (block[i][j].space[m][n] == 1)
      {
        SetPos(x+2*n,y+m); //光标跳转到指定位置
        printf("  "); //打印空格覆盖(两个空格)
      }
    }
  }
}
int JudeIsErase()
{
  int sign = 0;
  int i = 0;
  
  for (i = 28; i > 3; i--)
  {
    int count = 0;
    for (int j = 2; j < 38; j += 2)
    {
      if (IF.pos[j][i] == 1)
      {
        count++;
      }
    }
    if (count == 18)//整行全是方块
    {
      sign = 1;
      grade += 10;
      SetPos(44, 26);
      printf("当前分数:%d", grade);//更新当前分数
      for (int a = 2; a < 38; a += 2)
      {
        SetPos(a, i);
        printf("  ");
        IF.pos[a][i] = 0;
      }
      //将该行以上全部下移一格
      for (int m = i; m > 3; m--)
      {
        
        for (int n = 2; n < 38; n += 2)
        {
          
          IF.pos[n][m] = IF.pos[n][m - 1]; //将上一行方块移到下一行
          if (IF.pos[n][m]==1) //上一行移下来的是方块,打印方块
          {
            SetPos(n, m); //光标跳转到该位置
            wprintf(L"%c", BIOCK); //打印方块
          }
          else //上一行移下来的是空格,打印空格
          {
            SetPos(n, m); //光标跳转到该位置
            printf("  "); //打印空格(两个空格)
          }
        }
        
      }
    }
  }
  return sign;
}
void JudeIsOver()
{
  for (int i = 2; i < 38; i+=2)
  {
    if (IF.pos[i][1] == 1) //第一行有方块存在
    {
      state = FAIL;
      
      
    }
  }
  
}
void BlockMove(Block block[][COLS], int *i, int *j, int *x, int *y)
{
  switch (block[*i][*j].blockstate)
  {
  case CHANGE:
    if (IsLegal(*i, ((*j) + 1) % 4, *x, (*y )+1) == 1) //判断方块变化后(变化的同时下降一格)是否碰撞
    {
      //方块旋转后合法才进行以下操作
      PrintSpace(*i, *j, *x, *y); //用空格覆盖当前方块所在位置
      (*j) = ((*j) + 1) % 4;
      (*y)++;
      
      
      //PrintBlock(i, j, x, y);//绘制下一个变化的方块
      block[*i][*j].blockstate = NORMAL;
      block[*i][*j].speed = 200;
    }
    //else //方块再下落就碰撞了(已经到达底部)
    //{
    //  //将当前方块的信息录入IF当中
    //  for (int m = 0; m< 4; m++)
    //  {
    //    for (int n = 0; n< 4; n++)
    //    {
    //      if (block[*i][*j].space[m][n] == 1)
    //      {
    //        IF.pos[(*x )+n*2][(*y )+m] = 1; //将该位置标记为有方块
    //        
    //      }
    //    }
    //  }
    //  while (JudeIsErase()); //判断此次方块下落是否得分
    //  JudeIsOver();
  
    //}
    break;
  case LEFT:
    if (IsLegal(*i, *j,(*x )-2, *y ) == 1) //判断方块左移后是否合法
    {
      //方块左移后合法才进行以下操作
      PrintSpace(*i, *j, *x, *y); //用空格覆盖当前方块所在位置
    
      (*x) = (*x) - 2;
      block[*i][*j].blockstate = NORMAL;
      block[*i][*j].speed = 200;
      
      //PrintBlock(i, j, x, y);//绘制下一个变化的方块
    }
    break;
  case RIGHT:
    if (IsLegal(*i, *j, (*x) + 2, *y) == 1) //判断方块右移后是否合法
    {
      //方块右移后合法才进行以下操作
      PrintSpace(*i, *j, *x, *y); //用空格覆盖当前方块所在位置
      (*x) = (*x) + 2;
      block[*i][*j].blockstate = NORMAL;
      block[*i][*j].speed = 200;
      //PrintBlock(i, j, x, y);//绘制下一个变化的方块
    }
    break;
  case DOWN:
    if (IsLegal(*i, *j,* x , (*y) + 1) == 1) //判断方块下移后是否合法
    {
      //方块下移后合法才进行以下操作
      PrintSpace(*i,* j, *x,* y); //用空格覆盖当前方块所在位置
      (*y)++;
      block[*i][*j].blockstate = NORMAL;
      if (block[*i][*j].speed>50)//避免连续按,变成负数,会出现bug
      {
        block[*i][*j].speed -= 50;
      }
      
    }
    else //碰撞
    {
      //将当前方块的信息录入IF当中
      for (int m = 0; m < 4; m++)
      {
        for (int n = 0; n < 4; n++)
        {
          if (block[*i][*j].space[m][n] == 1)
          {
            IF.pos[(*x) + n * 2][(*y) + m] = 1; //将该位置标记为有方块
          }
        }
      }
      while (JudeIsErase()); //判断此次方块下落是否得分
      JudeIsOver();
    }
    break;
  case NORMAL:
    if (IsLegal(*i, *j,*x, (*y) + 1) == 1) //方块再下落合法
    {
      //方块右移后合法才进行以下操作
      PrintSpace(*i, *j, *x,* y); //用空格覆盖当前方块所在位置
      
      (*y)++;
      block[*i][*j].blockstate = NORMAL;
      block[*i][*j].speed=200;
      
    }
    else //碰撞
    {
      //将当前方块的信息录入IF当中
      for (int m = 0; m < 4; m++)
      {
        for (int n = 0; n < 4; n++)
        {
          if (block[*i][*j].space[m][n] == 1)
          {
            IF.pos[(*x) + n * 2][(*y) + m] = 1; //将该位置标记为有方块
          }
        }
      }
      while (JudeIsErase()); //判断此次方块下落是否得分
      JudeIsOver();
    }
    break;
  }
  
}
void GameRun()
{
  
  int i = rand() % 7, j = rand() % 4; //随机获取方块的形状
  do
  {
    int x = 18;
    int y = 0;
    int next_i =  rand() % 7, next_j = rand() % 4;
    PrintBlock(next_i, next_j, 46, 4);//绘制提示框图形
    PrintBlock(i, j, 18, 0);//绘制方块初始位置
    while(IsLegal(i, j, x, y ))//不合法,说明到底了
    {
      
      if (KEY_PRESS(VK_UP))
      {
        block[i][j].blockstate = CHANGE;
      }
      else if (KEY_PRESS(VK_DOWN))
      {
        block[i][j].blockstate = DOWN;
      }
      else if (KEY_PRESS(VK_LEFT))
      {
        block[i][j].blockstate = LEFT;//左移
      }
      else if (KEY_PRESS(VK_RIGHT))
      {
        block[i][j].blockstate = RIGHT;//右移
      }
      else if (KEY_PRESS(VK_SPACE))
      {
        pause();//暂停
      }
      else if (KEY_PRESS(VK_ESCAPE))
      {
        state = END;//退出
        break;
      }
      else if (KEY_PRESS(R))
      {
        state = RESTART;//重新开始
        break;
      }
      else//什么都没按
      {
        block[i][j].blockstate = NORMAL;
      }
      BlockMove(block, &i, &j, &x,&y);
      PrintBlock(i, j, x, y);//绘制方块位置
      Sleep(block[i][j].speed);//每一帧休息的时间
    }
    i = next_i;
    j = next_j;
    PrintSpace(i,j, 46, 4);//覆盖提示框图形
  } while (state ==RUN);
}
void GameOver()
{
  switch (state)
  {
  case END:
    SetPos(14, 15);
    printf("您主动退出游戏\n");
    SetPos(14, 18);
    system("pause");
    break;
  case FAIL:
    Sleep(1000); //留给玩家反应时间
    system("cls"); //清空屏幕
    SetPos(20, 10);
    printf("小垃圾,游戏结束!\n");
    SetPos(20, 14);
    printf("你的最终得分为: %d ", grade);
    SetPos(20, 18);
    system("pause");
    break;
  case RESTART:
    system("cls"); //清空屏幕
    SetPos(26, 10);
    printf("重新开始!\n");
    SetPos(22, 14);
    printf("小垃圾,你准备好了吗?\n");
    Sleep(2000);
    main();
    break;
  }
}

5.3  test.c

#include"tetris.h"
int main()
{
  //修改当前地区为本地模式,为了⽀持中⽂宽字符的打印
  setlocale(LC_ALL, "");
  srand((unsigned int)time(NULL));
  char ch=0;
  do
  {
    GameInit();
    GameRun();
    GameOver();
    system("cls");
    SetPos(20, 12);
    printf("再来一局吗?(Y/N):\n");
    SetPos(22, 15);
    ch = getchar();
    
    getchar();//清理\n
  } while (ch == 'y' || ch == 'Y');
  SetPos(0, 29);
  
  return 0;
}
相关文章
|
1月前
|
C语言
俄罗斯方块-----C语言
俄罗斯方块-----C语言
16 0
俄罗斯方块-----C语言
|
5月前
|
存储 C语言
C语言实战 | “俄罗斯方块”游戏重构
摘要(Markdown格式): 在之前的游戏中,全局变量的过度使用导致存储浪费和低代码通用性。以“贪吃蛇”为例,显示功能依赖全局变量,限制了函数的复用。通过参数传递代替全局变量,如在“俄罗斯方块”等游戏中控制物体运动的函数,可提升代码重用性和模块化。重构过程中,即使小到变量命名和代码精简的改进,也能逐步带来程序质量的显著提升。
31 0
|
6月前
|
Linux C语言
俄罗斯方块(c语言)
俄罗斯方块(c语言)
|
C语言
C语言小游戏------俄罗斯方块
C语言写的俄罗斯方块小游戏
262 0
|
28天前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
31 3
|
19天前
|
存储 缓存 C语言
【c语言】简单的算术操作符、输入输出函数
本文介绍了C语言中的算术操作符、赋值操作符、单目操作符以及输入输出函数 `printf` 和 `scanf` 的基本用法。算术操作符包括加、减、乘、除和求余,其中除法和求余运算有特殊规则。赋值操作符用于给变量赋值,并支持复合赋值。单目操作符包括自增自减、正负号和强制类型转换。输入输出函数 `printf` 和 `scanf` 用于格式化输入和输出,支持多种占位符和格式控制。通过示例代码详细解释了这些操作符和函数的使用方法。
32 10
|
12天前
|
存储 算法 程序员
C语言:库函数
C语言的库函数是预定义的函数,用于执行常见的编程任务,如输入输出、字符串处理、数学运算等。使用库函数可以简化编程工作,提高开发效率。C标准库提供了丰富的函数,满足各种需求。
|
18天前
|
机器学习/深度学习 C语言
【c语言】一篇文章搞懂函数递归
本文详细介绍了函数递归的概念、思想及其限制条件,并通过求阶乘、打印整数每一位和求斐波那契数等实例,展示了递归的应用。递归的核心在于将大问题分解为小问题,但需注意递归可能导致效率低下和栈溢出的问题。文章最后总结了递归的优缺点,提醒读者在实际编程中合理使用递归。
44 7
|
18天前
|
存储 编译器 程序员
【c语言】函数
本文介绍了C语言中函数的基本概念,包括库函数和自定义函数的定义、使用及示例。库函数如`printf`和`scanf`,通过包含相应的头文件即可使用。自定义函数需指定返回类型、函数名、形式参数等。文中还探讨了函数的调用、形参与实参的区别、return语句的用法、函数嵌套调用、链式访问以及static关键字对变量和函数的影响,强调了static如何改变变量的生命周期和作用域,以及函数的可见性。
25 4