俄罗斯方块(C语言实现)

article/2025/9/25 2:25:22

文章目录

  • 游戏说明
  • 游戏效果展示
  • 游戏代码
  • 游戏代码详解
    • 游戏框架构建
    • 隐藏光标
    • 光标跳转
    • 初始化界面
    • 初始化方块信息
    • 颜色设置
    • 画出方块
    • 空格覆盖
    • 合法性判断
    • 判断得分与结束
    • 游戏主体逻辑函数
    • 从文件读取最高分
    • 更新最高分到文件
    • 主函数

游戏说明

俄罗斯方块相信大家都知道,这里就不再介绍什么游戏背景了,我这里对本代码实现的俄罗斯方块作一些说明

  1. 按方向键的左右键可实现方块的左右移动。
  2. 按方向键的下键可实现方块的加速下落。
  3. 按空格键可实现方块的顺时针旋转。
  4. 按Esc键可退出游戏。
  5. 按S键可暂停游戏,暂停游戏后按任意键继续游戏。
  6. 按R键可重新开始游戏。

除此之外,本游戏还拥有计分系统,可保存玩家的历史最高记录。

游戏效果展示

在这里插入图片描述

游戏代码

博友们可以将以下代码复制到自己的编译器当中运行:

#include <stdio.h>
#include <Windows.h>
#include <stdlib.h>
#include <time.h>
#include <conio.h>#define ROW 29 //游戏区行数
#define COL 20 //游戏区列数#define DOWN 80 //方向键:下
#define LEFT 75 //方向键:左
#define RIGHT 77 //方向键:右#define SPACE 32 //空格键
#define ESC 27 //Esc键struct Face
{int data[ROW][COL + 10]; //用于标记指定位置是否有方块(1为有,0为无)int color[ROW][COL + 10]; //用于记录指定位置的方块颜色编码
}face;struct Block
{int space[4][4];
}block[7][4]; //用于存储7种基本形状方块的各自的4种形态的信息,共28种//隐藏光标
void HideCursor();
//光标跳转
void CursorJump(int x, int y);
//初始化界面
void InitInterface();
//初始化方块信息
void InitBlockInfo();
//颜色设置
void color(int num);
//画出方块
void DrawBlock(int shape, int form, int x, int y);
//空格覆盖
void DrawSpace(int shape, int form, int x, int y);
//合法性判断
int IsLegal(int shape, int form, int x, int y);
//判断得分与结束
int JudeFunc();
//游戏主体逻辑函数
void StartGame();
//从文件读取最高分
void ReadGrade();
//更新最高分到文件
void WriteGrade();int max, grade; //全局变量
int main()
{
#pragma warning (disable:4996) //消除警告max = 0, grade = 0; //初始化变量system("title 俄罗斯方块"); //设置cmd窗口的名字system("mode con lines=29 cols=60"); //设置cmd窗口的大小HideCursor(); //隐藏光标ReadGrade(); //从文件读取最高分到max变量	InitInterface(); //初始化界面InitBlockInfo(); //初始化方块信息srand((unsigned int)time(NULL)); //设置随机数生成的起点StartGame(); //开始游戏return 0;
}//隐藏光标
void HideCursor()
{CONSOLE_CURSOR_INFO curInfo; //定义光标信息的结构体变量curInfo.dwSize = 1;  //如果没赋值的话,隐藏光标无效curInfo.bVisible = FALSE; //将光标设置为不可见HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); //获取控制台句柄SetConsoleCursorInfo(handle, &curInfo); //设置光标信息
}
//光标跳转
void CursorJump(int x, int y)
{COORD pos; //定义光标位置的结构体变量pos.X = x; //横坐标设置pos.Y = y; //纵坐标设置HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); //获取控制台句柄SetConsoleCursorPosition(handle, pos); //设置光标位置
}
//初始化界面
void InitInterface()
{color(7); //颜色设置为白色for (int i = 0; i < ROW; i++){for (int j = 0; j < COL + 10; j++){if (j == 0 || j == COL - 1 || j == COL + 9){face.data[i][j] = 1; //标记该位置有方块CursorJump(2 * j, i);printf("■");}else if (i == ROW - 1){face.data[i][j] = 1; //标记该位置有方块printf("■");}elseface.data[i][j] = 0; //标记该位置无方块}}for (int i = COL; i < COL + 10; i++){face.data[8][i] = 1; //标记该位置有方块CursorJump(2 * i, 8);printf("■");}CursorJump(2 * COL, 1);printf("下一个方块:");CursorJump(2 * COL + 4, ROW - 19);printf("左移:←");CursorJump(2 * COL + 4, ROW - 17);printf("右移:→");CursorJump(2 * COL + 4, ROW - 15);printf("加速:↓");CursorJump(2 * COL + 4, ROW - 13);printf("旋转:空格");CursorJump(2 * COL + 4, ROW - 11);printf("暂停: S");CursorJump(2 * COL + 4, ROW - 9);printf("退出: Esc");CursorJump(2 * COL + 4, ROW - 7);printf("重新开始:R");CursorJump(2 * COL + 4, ROW - 5);printf("最高纪录:%d", max);CursorJump(2 * COL + 4, ROW - 3);printf("当前分数:%d", grade);
}
//初始化方块信息
void InitBlockInfo()
{//“T”形for (int i = 0; i <= 2; i++)block[0][0].space[1][i] = 1;block[0][0].space[2][1] = 1;//“L”形for (int i = 1; i <= 3; i++)block[1][0].space[i][1] = 1;block[1][0].space[3][2] = 1;//“J”形for (int i = 1; i <= 3; i++)block[2][0].space[i][2] = 1;block[2][0].space[3][1] = 1;for (int i = 0; i <= 1; i++){//“Z”形block[3][0].space[1][i] = 1;block[3][0].space[2][i + 1] = 1;//“S”形block[4][0].space[1][i + 1] = 1;block[4][0].space[2][i] = 1;//“O”形block[5][0].space[1][i + 1] = 1;block[5][0].space[2][i + 1] = 1;}//“I”形for (int i = 0; i <= 3; i++)block[6][0].space[i][1] = 1;int temp[4][4];for (int shape = 0; shape < 7; shape++) //7种形状{for (int form = 0; form < 3; form++) //4种形态(已经有了一种,这里每个还需增加3种){//获取第form种形态for (int i = 0; i < 4; i++){for (int j = 0; j < 4; j++){temp[i][j] = block[shape][form].space[i][j];}}//将第form种形态顺时针旋转,得到第form+1种形态for (int i = 0; i < 4; i++){for (int j = 0; j < 4; j++){block[shape][form + 1].space[i][j] = temp[3 - j][i];}}}}
}
//颜色设置
void color(int c)
{switch (c){case 0:c = 13; //“T”形方块设置为紫色break;case 1:case 2:c = 12; //“L”形和“J”形方块设置为红色break;case 3:case 4:c = 10; //“Z”形和“S”形方块设置为绿色break;case 5:c = 14; //“O”形方块设置为黄色break;case 6:c = 11; //“I”形方块设置为浅蓝色break;default:c = 7; //其他默认设置为白色break;}SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), c); //颜色设置//注:SetConsoleTextAttribute是一个API(应用程序编程接口)
}
//画出方块
void DrawBlock(int shape, int form, int x, int y)
{for (int i = 0; i < 4; i++){for (int j = 0; j < 4; j++){if (block[shape][form].space[i][j] == 1) //如果该位置有方块{CursorJump(2 * (x + j), y + i); //光标跳转到指定位置printf("■"); //输出方块}}}
}
//空格覆盖
void DrawSpace(int shape, int form, int x, int y)
{for (int i = 0; i < 4; i++){for (int j = 0; j < 4; j++){if (block[shape][form].space[i][j] == 1) //如果该位置有方块{CursorJump(2 * (x + j), y + i); //光标跳转到指定位置printf("  "); //打印空格覆盖(两个空格)}}}
}
//合法性判断
int IsLegal(int shape, int form, int x, int y)
{for (int i = 0; i < 4; i++){for (int j = 0; j < 4; j++){//如果方块落下的位置本来就已经有方块了,则不合法if ((block[shape][form].space[i][j] == 1) && (face.data[y + i][x + j] == 1))return 0; //不合法}}return 1; //合法
}
//判断得分与结束
int JudeFunc()
{//判断是否得分for (int i = ROW - 2; i > 4; i--){int sum = 0; //记录第i行的方块个数for (int j = 1; j < COL - 1; j++){sum += face.data[i][j]; //统计第i行的方块个数}if (sum == 0) //该行没有方块,无需再判断其上的层次(无需再继续判断是否得分)break; //跳出循环if (sum == COL - 2) //该行全是方块,可得分{grade += 10; //满一行加10分color(7); //颜色设置为白色CursorJump(2 * COL + 4, ROW - 3); //光标跳转到显示当前分数的位置printf("当前分数:%d", grade); //更新当前分数for (int j = 1; j < COL - 1; j++) //清除得分行的方块信息{face.data[i][j] = 0; //该位置得分后被清除,标记为无方块CursorJump(2 * j, i); //光标跳转到该位置printf("  "); //打印空格覆盖(两个空格)}//把被清除行上面的行整体向下挪一格for (int m = i; m >1; m--){sum = 0; //记录上一行的方块个数for (int n = 1; n < COL - 1; n++){sum += face.data[m - 1][n]; //统计上一行的方块个数face.data[m][n] = face.data[m - 1][n]; //将上一行方块的标识移到下一行face.color[m][n] = face.color[m - 1][n]; //将上一行方块的颜色编号移到下一行if (face.data[m][n] == 1) //上一行移下来的是方块,打印方块{CursorJump(2 * n, m); //光标跳转到该位置color(face.color[m][n]); //颜色设置为还方块的颜色printf("■"); //打印方块}else //上一行移下来的是空格,打印空格{CursorJump(2 * n, m); //光标跳转到该位置printf("  "); //打印空格(两个空格)}}if (sum == 0) //上一行移下来的全是空格,无需再将上层的方块向下移动(移动结束)return 1; //返回1,表示还需调用该函数进行判断(移动下来的可能还有满行)}}}//判断游戏是否结束for (int j = 1; j < COL - 1; j++){if (face.data[1][j] == 1) //顶层有方块存在(以第1行为顶层,不是第0行){Sleep(1000); //留给玩家反应时间system("cls"); //清空屏幕color(7); //颜色设置为白色CursorJump(2 * (COL / 3), ROW / 2 - 3);if (grade>max){printf("恭喜你打破最高记录,最高记录更新为%d", grade);WriteGrade();}else if (grade == max){printf("与最高记录持平,加油再创佳绩", grade);}else{printf("请继续加油,当前与最高记录相差%d", max - grade);}CursorJump(2 * (COL / 3), ROW / 2);printf("GAME OVER");while (1){char ch;CursorJump(2 * (COL / 3), ROW / 2 + 3);printf("再来一局?(y/n):");scanf("%c", &ch);if (ch == 'y' || ch == 'Y'){system("cls");main();}else if (ch == 'n' || ch == 'N'){CursorJump(2 * (COL / 3), ROW / 2 + 5);exit(0);}else{CursorJump(2 * (COL / 3), ROW / 2 + 4);printf("选择错误,请再次选择");}}}}return 0; //判断结束,无需再调用该函数进行判断
}
//游戏主体逻辑函数
void StartGame()
{int shape = rand() % 7, form = rand() % 4; //随机获取方块的形状和形态while (1){int t = 0;int nextShape = rand() % 7, nextForm = rand() % 4; //随机获取下一个方块的形状和形态int x = COL / 2 - 2, y = 0; //方块初始下落位置的横纵坐标color(nextShape); //颜色设置为下一个方块的颜色DrawBlock(nextShape, nextForm, COL + 3, 3); //将下一个方块显示在右上角while (1){color(shape); //颜色设置为当前正在下落的方块DrawBlock(shape, form, x, y); //将该方块显示在初始下落位置if (t == 0){t = 15000; //这里t越小,方块下落越快(可以根据此设置游戏难度)}while (--t){if (kbhit() != 0) //若键盘被敲击,则退出循环break;}if (t == 0) //键盘未被敲击{if (IsLegal(shape, form, x, y + 1) == 0) //方块再下落就不合法了(已经到达底部){//将当前方块的信息录入face当中//face:记录界面的每个位置是否有方块,若有方块还需记录该位置方块的颜色。for (int i = 0; i < 4; i++){for (int j = 0; j < 4; j++){if (block[shape][form].space[i][j] == 1){face.data[y + i][x + j] = 1; //将该位置标记为有方块face.color[y + i][x + j] = shape; //记录该方块的颜色数值}}}while (JudeFunc()); //判断此次方块下落是否得分以及游戏是否结束break; //跳出当前死循环,准备进行下一个方块的下落}else //未到底部{DrawSpace(shape, form, x, y); //用空格覆盖当前方块所在位置y++; //纵坐标自增(下一次显示方块时就相当于下落了一格了)}}else //键盘被敲击{char ch = getch(); //读取keycodeswitch (ch){case DOWN: //方向键:下if (IsLegal(shape, form, x, y + 1) == 1) //判断方块向下移动一位后是否合法{//方块下落后合法才进行以下操作DrawSpace(shape, form, x, y); //用空格覆盖当前方块所在位置y++; //纵坐标自增(下一次显示方块时就相当于下落了一格了)}break;case LEFT: //方向键:左if (IsLegal(shape, form, x - 1, y) == 1) //判断方块向左移动一位后是否合法{//方块左移后合法才进行以下操作DrawSpace(shape, form, x, y); //用空格覆盖当前方块所在位置x--; //横坐标自减(下一次显示方块时就相当于左移了一格了)}break;case RIGHT: //方向键:右if (IsLegal(shape, form, x + 1, y) == 1) //判断方块向右移动一位后是否合法{//方块右移后合法才进行以下操作DrawSpace(shape, form, x, y); //用空格覆盖当前方块所在位置x++; //横坐标自增(下一次显示方块时就相当于右移了一格了)}break;case SPACE: //空格键if (IsLegal(shape, (form + 1) % 4, x, y + 1) == 1) //判断方块旋转后是否合法{//方块旋转后合法才进行以下操作DrawSpace(shape, form, x, y); //用空格覆盖当前方块所在位置y++; //纵坐标自增(总不能原地旋转吧)form = (form + 1) % 4; //方块的形态自增(下一次显示方块时就相当于旋转了)}break;case ESC: //Esc键system("cls"); //清空屏幕color(7);CursorJump(COL, ROW / 2);printf("  游戏结束  ");CursorJump(COL, ROW / 2 + 2);exit(0); //结束程序case 's':case 'S':  //暂停system("pause>nul"); //暂停(按任意键继续)break;case 'r':case 'R': //重新开始system("cls"); //清空屏幕main(); //重新执行主函数}}}shape = nextShape, form = nextForm; //获取下一个方块的信息DrawSpace(nextShape, nextForm, COL + 3, 3); //将右上角的方块信息用空格覆盖}
}
//从文件读取最高分
void ReadGrade()
{FILE* pf = fopen("俄罗斯方块最高得分记录.txt", "r"); //以只读方式打开文件if (pf == NULL) //打开文件失败{pf = fopen("俄罗斯方块最高得分记录.txt", "w"); //以只写方式打开文件(文件不存在可以自动创建该文件)fwrite(&grade, sizeof(int), 1, pf); //将max写入文件(此时max为0),即将最高历史得分初始化为0}fseek(pf, 0, SEEK_SET); //使文件指针pf指向文件开头fread(&max, sizeof(int), 1, pf); //读取文件中的最高历史得分到max当中fclose(pf); //关闭文件pf = NULL; //文件指针及时置空
}
//更新最高分到文件
void WriteGrade()
{FILE* pf = fopen("俄罗斯方块最高得分记录.txt", "w"); //以只写方式打开文件if (pf == NULL) //打开文件失败{printf("保存最高得分记录失败\n");exit(0);}fwrite(&grade, sizeof(int), 1, pf); //将本局游戏得分写入文件当中(更新最高历史得分)fclose(pf); //关闭文件pf = NULL; //文件指针及时置空
}

游戏代码详解

游戏框架构建

首先我们定义一下界面的大小,我们这里定义游戏区的行数和列数。

#define ROW 29 //游戏区行数
#define COL 20 //游戏区列数

我这里将方块堆积的区域称为游戏区,将按键提示以及方块提示的区域称为提示区。
在这里插入图片描述
我们还需要一个结构体,该结构体记录界面的每个位置是否有方块,若有方块还需记录该位置方块的颜色。

struct Face
{int data[ROW][COL + 10]; //用于标记指定位置是否有方块(1为有,0为无)int color[ROW][COL + 10]; //用于记录指定位置的方块颜色编码
}face;

其次,我们还需要一个结构体,该结构体当中存储着一个4行4列的二维数组,这个二维数组就用于存储单个方块的基本信息。(众所周知,4行4列的二维数组可以容纳下游戏当中的每一种方块)

而俄罗斯方块当中有7种基本形状的方块,而每种方块通过旋转后又可以得到3种方块,共28种。

因此,我们可以用该结构体定义一个7行4列的二维数组存储这28个方块的信息。

struct Block
{int space[4][4];
}block[7][4]; //用于存储7种基本形状方块的各自的4种形态的信息,共28种

做到这里框架已经基本构建好了,为了提高代码的可读性,我们再根据需要用到的按键的键码值对其进行宏定义。

#define DOWN 80 //方向键:下
#define LEFT 75 //方向键:左
#define RIGHT 77 //方向键:右#define SPACE 32 //空格键
#define ESC 27 //Esc键

隐藏光标

光标的作用在于提醒使用者,你接下来的输入将会在该位置出现。但在进行游戏时我们并不需要用到光标,光标在那里一闪一闪的显然是不行的,这时我们需要将光标隐藏。

//隐藏光标
void HideCursor()
{CONSOLE_CURSOR_INFO curInfo; //定义光标信息的结构体变量curInfo.dwSize = 1;  //如果没赋值的话,隐藏光标无效curInfo.bVisible = FALSE; //将光标设置为不可见HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); //获取控制台句柄SetConsoleCursorInfo(handle, &curInfo); //设置光标信息
}

其中,关键结构CONSOLE_CURSOR_INFO在其头文件当中的内容如下:
在这里插入图片描述
设置光标信息函数在其头文件中的声明如下:
在这里插入图片描述

光标跳转

在屏幕上进行输出时,我们需要光标先移动到目标位置再进行输出,因此,光标跳转函数也是必不可少的。

//光标跳转
void CursorJump(int x, int y)
{COORD pos; //定义光标位置的结构体变量pos.X = x; //横坐标设置pos.Y = y; //纵坐标设置HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); //获取控制台句柄SetConsoleCursorPosition(handle, pos); //设置光标位置
}

其中,关键结构COORD在其头文件当中的内容如下:
在这里插入图片描述
设置光标位置函数在其头文件中的声明如下:
在这里插入图片描述

初始化界面

初始化界面完成基本信息的打印,包括由白色方块构成的边界和按键提示语句。
在这里插入图片描述
对照最终效果图片,看着代码很好理解,但是需要注意两点

  1. 一个小方块在cmd命令窗口当中占两个单位的横坐标、一个单位的纵坐标。
  2. 光标跳转函数CursorJump接收的是光标将要跳至的横纵坐标。

例如,想要将光标跳转到 i 行 j 列(这里所说的行和列都是以一个方块为单位),就等价于让光标跳转到坐标(2*j,i)处。

//初始化界面
void InitInterface()
{color(7); //颜色设置为白色for (int i = 0; i < ROW; i++){for (int j = 0; j < COL + 10; j++){if (j == 0 || j == COL - 1 || j == COL + 9){face.data[i][j] = 1; //标记该位置有方块CursorJump(2 * j, i);printf("■");}else if (i == ROW - 1){face.data[i][j] = 1; //标记该位置有方块printf("■");}elseface.data[i][j] = 0; //标记该位置无方块}}for (int i = COL; i < COL + 10; i++){face.data[8][i] = 1; //标记该位置有方块CursorJump(2 * i, 8);printf("■");}CursorJump(2 * COL, 1);printf("下一个方块:");CursorJump(2 * COL + 4, ROW - 19);printf("左移:←");CursorJump(2 * COL + 4, ROW - 17);printf("右移:→");CursorJump(2 * COL + 4, ROW - 15);printf("加速:↓");CursorJump(2 * COL + 4, ROW - 13);printf("旋转:空格");CursorJump(2 * COL + 4, ROW - 11);printf("暂停: S");CursorJump(2 * COL + 4, ROW - 9);printf("退出: Esc");CursorJump(2 * COL + 4, ROW - 7);printf("重新开始:R");CursorJump(2 * COL + 4, ROW - 5);printf("最高纪录:%d", max);CursorJump(2 * COL + 4, ROW - 3);printf("当前分数:%d", grade);
}

初始化方块信息

上面说到俄罗斯方块有7种基本形状,便是以下7种:
在这里插入图片描述
我们先将这7种基本形状的方块信息存储在各自的第0种形态处,如下:
在这里插入图片描述
然后取第0种形态顺时针旋转后得到第1种形态,取第1种形态顺时针旋转后得到第2种形态,取第2种形态顺时针旋转后得到第3种形态。这7种形状都按此方法操作,最终得到全部28种方块信息,如下:
在这里插入图片描述
在旋转过程中,一个方块顺时针旋转一次后其位置变换规律如下:
在这里插入图片描述

//初始化方块信息
void InitBlockInfo()
{//“T”形for (int i = 0; i <= 2; i++)block[0][0].space[1][i] = 1;block[0][0].space[2][1] = 1;//“L”形for (int i = 1; i <= 3; i++)block[1][0].space[i][1] = 1;block[1][0].space[3][2] = 1;//“J”形for (int i = 1; i <= 3; i++)block[2][0].space[i][2] = 1;block[2][0].space[3][1] = 1;for (int i = 0; i <= 1; i++){//“Z”形block[3][0].space[1][i] = 1;block[3][0].space[2][i + 1] = 1;//“S”形block[4][0].space[1][i + 1] = 1;block[4][0].space[2][i] = 1;//“O”形block[5][0].space[1][i + 1] = 1;block[5][0].space[2][i + 1] = 1;}//“I”形for (int i = 0; i <= 3;i++)block[6][0].space[i][1] = 1;int temp[4][4];for (int shape = 0; shape < 7; shape++) //7种形状{for (int form = 0; form < 3; form++) //4种形态(已经有了一种,这里每个还需增加3种){//获取第form种形态for (int i = 0; i < 4; i++){for (int j = 0; j < 4; j++){temp[i][j] = block[shape][form].space[i][j];}}//将第form种形态顺时针旋转,得到第form+1种形态for (int i = 0; i < 4; i++){for (int j = 0; j < 4; j++){block[shape][form + 1].space[i][j] = temp[3 - j][i];}}}}
}

颜色设置

这里的颜色设置函数所接收的参数c(0~6),代表7种形状的方块,每种方块对应自己的颜色,所对应的颜色可以自己设置。
在这里插入图片描述

//颜色设置
void color(int c)
{switch (c){case 0:c = 13; //“T”形方块设置为紫色break;case 1:case 2:c = 12; //“L”形和“J”形方块设置为红色break;case 3:case 4:c = 10; //“Z”形和“S”形方块设置为绿色break;case 5:c = 14; //“O”形方块设置为黄色break;case 6:c = 11; //“I”形方块设置为浅蓝色break;default:c = 7; //其他默认设置为白色break;}SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), c); //颜色设置//注:SetConsoleTextAttribute是一个API(应用程序编程接口)
}

设置颜色函数在其头文件中的声明如下:
在这里插入图片描述

画出方块

方块的信息有了,接下来就是将方块在屏幕上显示出来。该函数的作用是,将第shape种形状的第form种形态的方块打印在屏幕的指定位置处。
在这里插入图片描述
所给x和y,指的是方块信息当中第一行第一列的方块的打印位置。

//画出方块
void DrawBlock(int shape, int form, int x, int y)
{for (int i = 0; i < 4; i++){for (int j = 0; j < 4; j++){if (block[shape][form].space[i][j] == 1) //如果该位置有方块{CursorJump(2 * (x + j), y + i); //光标跳转到指定位置printf("■"); //输出方块}}}
}

空格覆盖

无论是游戏区方块的移动,还是提示区右上角下一个方块的显示,都需要方块位置的变换,而在变化之前肯定是要先将之前打印的方块用空格进行覆盖,然后再打印变化后的方块。

在覆盖方块时特别需要注意的是,要覆盖一个小方块需要用两个空格。

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

合法性判断

其实在方块移动过程中,无时无刻都在判断方块下一次变化后的位置是否合法,只有合法才会允许该变化的进行。

所谓非法,就是指该方块进行了该变化后落在了本来就有方块的位置。

//合法性判断
int IsLegal(int shape, int form, int x, int y)
{for (int i = 0; i < 4; i++){for (int j = 0; j < 4; j++){//如果方块落下的位置本来就已经有方块了,则不合法if ((block[shape][form].space[i][j] == 1) && (face.data[y + i][x + j] == 1))return 0; //不合法}}return 1; //合法
}

判断得分与结束

判断得分:
从下往上判断,若某一行方块全满,则将改行方块数据清空,并将该行上方的方块全部下移,下移结束后返回1,表示还需再次调用该函数进行判断,因为被下移的行并没有进行判断,可能还存在满行。

判断结束:

  1. 直接判断游戏区最上面的一行当中是否有方块存在,若存在方块,则游戏结束。
  2. 游戏结束后,除了给出游戏结束提示语之外,如果玩家本局游戏分数大于历史最高记录,则需要更新最高分到文件当中。
  3. 游戏结束后询问玩家是否再来一局。
//判断得分与结束
int JudeFunc()
{//判断是否得分for (int i = ROW - 2; i > 4; i--){int sum = 0; //记录第i行的方块个数for (int j = 1; j < COL - 1; j++){sum += face.data[i][j]; //统计第i行的方块个数}if (sum == 0) //该行没有方块,无需再判断其上的层次(无需再继续判断是否得分)break; //跳出循环if (sum == COL - 2) //该行全是方块,可得分{grade += 10; //满一行加10分color(7); //颜色设置为白色CursorJump(2 * COL + 4, ROW - 3); //光标跳转到显示当前分数的位置printf("当前分数:%d", grade); //更新当前分数for (int j = 1; j < COL - 1; j++) //清除得分行的方块信息{face.data[i][j] = 0; //该位置得分后被清除,标记为无方块CursorJump(2 * j, i); //光标跳转到该位置printf("  "); //打印空格覆盖(两个空格)}//把被清除行上面的行整体向下挪一格for (int m = i; m >1; m--){sum = 0; //记录上一行的方块个数for (int n = 1; n < COL - 1; n++){sum += face.data[m - 1][n]; //统计上一行的方块个数face.data[m][n] = face.data[m - 1][n]; //将上一行方块的标识移到下一行face.color[m][n] = face.color[m - 1][n]; //将上一行方块的颜色编号移到下一行if (face.data[m][n] == 1) //上一行移下来的是方块,打印方块{CursorJump(2 * n, m); //光标跳转到该位置color(face.color[m][n]); //颜色设置为还方块的颜色printf("■"); //打印方块}else //上一行移下来的是空格,打印空格{CursorJump(2 * n, m); //光标跳转到该位置printf("  "); //打印空格(两个空格)}}if (sum == 0) //上一行移下来的全是空格,无需再将上层的方块向下移动(移动结束)return 1; //返回1,表示还需调用该函数进行判断(移动下来的可能还有满行)}}}//判断游戏是否结束for (int j = 1; j < COL - 1; j++){if (face.data[1][j] == 1) //顶层有方块存在(以第1行为顶层,不是第0行){Sleep(1000); //留给玩家反应时间system("cls"); //清空屏幕color(7); //颜色设置为白色CursorJump(2 * (COL / 3), ROW / 2 - 3);if (grade>max){printf("恭喜你打破最高记录,最高记录更新为%d", grade);WriteGrade();}else if (grade == max){printf("与最高记录持平,加油再创佳绩", grade);}else{printf("请继续加油,当前与最高记录相差%d", max - grade);}CursorJump(2 * (COL / 3), ROW / 2);printf("GAME OVER");while (1){char ch;CursorJump(2 * (COL / 3), ROW / 2 + 3);printf("再来一局?(y/n):");scanf("%c", &ch);if (ch == 'y' || ch == 'Y'){system("cls");main();}else if (ch == 'n' || ch == 'N'){CursorJump(2 * (COL / 3), ROW / 2 + 5);exit(0);}else{CursorJump(2 * (COL / 3), ROW / 2 + 4);printf("选择错误,请再次选择");}}}}return 0; //判断结束,无需再调用该函数进行判断
}

游戏主体逻辑函数

主体逻辑:

  1. 在打印当前下落的方块前,先随机获取下一次将要下落的方块,并打印到提示区的右上角。
  2. 将当前下落的方块首先打印到游戏区顶部,给定一定的时间间隔,若在该时间内键盘未被敲击,则方块下落一格,方块下落前需先判断下落后的合法性。
  3. 若在给定时间间隔内键盘被敲击,则根据所敲击的按键给出相应反馈。
  4. 若方块落到底部,则调用“判断得分与结束”函数进行判断。
  5. 若游戏未结束,则循环进行以上步骤。
//游戏主体逻辑函数
void StartGame()
{	int shape = rand() % 7, form = rand() % 4; //随机获取方块的形状和形态while (1){int t = 0;int nextShape = rand() % 7, nextForm = rand() % 4; //随机获取下一个方块的形状和形态int x = COL / 2 - 2, y = 0; //方块初始下落位置的横纵坐标color(nextShape); //颜色设置为下一个方块的颜色DrawBlock(nextShape, nextForm, COL + 3, 3); //将下一个方块显示在右上角while (1){color(shape); //颜色设置为当前正在下落的方块DrawBlock(shape, form, x, y); //将该方块显示在初始下落位置if (t == 0){t = 15000; //这里t越小,方块下落越快(可以根据此设置游戏难度)}while (--t){if (kbhit() != 0) //若键盘被敲击,则退出循环break;}if (t == 0) //键盘未被敲击{if (IsLegal(shape, form, x, y + 1) == 0) //方块再下落就不合法了(已经到达底部){//将当前方块的信息录入face当中//face:记录界面的每个位置是否有方块,若有方块还需记录该位置方块的颜色。for (int i = 0; i < 4; i++){for (int j = 0; j < 4; j++){if (block[shape][form].space[i][j] == 1){face.data[y + i][x + j] = 1; //将该位置标记为有方块face.color[y + i][x + j] = shape; //记录该方块的颜色数值}}}while (JudeFunc()); //判断此次方块下落是否得分以及游戏是否结束break; //跳出当前死循环,准备进行下一个方块的下落}else //未到底部{DrawSpace(shape, form, x, y); //用空格覆盖当前方块所在位置y++; //纵坐标自增(下一次显示方块时就相当于下落了一格了)}}else //键盘被敲击{char ch = getch(); //读取keycodeswitch (ch){case DOWN: //方向键:下if (IsLegal(shape, form, x, y + 1) == 1) //判断方块向下移动一位后是否合法{//方块下落后合法才进行以下操作DrawSpace(shape, form, x, y); //用空格覆盖当前方块所在位置y++; //纵坐标自增(下一次显示方块时就相当于下落了一格了)}break;case LEFT: //方向键:左if (IsLegal(shape, form, x - 1, y) == 1) //判断方块向左移动一位后是否合法{//方块左移后合法才进行以下操作DrawSpace(shape, form, x, y); //用空格覆盖当前方块所在位置x--; //横坐标自减(下一次显示方块时就相当于左移了一格了)}break;case RIGHT: //方向键:右if (IsLegal(shape, form, x + 1, y) == 1) //判断方块向右移动一位后是否合法{//方块右移后合法才进行以下操作DrawSpace(shape, form, x, y); //用空格覆盖当前方块所在位置x++; //横坐标自增(下一次显示方块时就相当于右移了一格了)}break;case SPACE: //空格键if (IsLegal(shape, (form + 1) % 4, x, y + 1) == 1) //判断方块旋转后是否合法{//方块旋转后合法才进行以下操作DrawSpace(shape, form, x, y); //用空格覆盖当前方块所在位置y++; //纵坐标自增(总不能原地旋转吧)form = (form + 1) % 4; //方块的形态自增(下一次显示方块时就相当于旋转了)}break;case ESC: //Esc键system("cls"); //清空屏幕color(7);CursorJump(COL, ROW / 2);printf("  游戏结束  ");CursorJump(COL, ROW / 2 + 2);exit(0); //结束程序case 's':case 'S':  //暂停system("pause>nul"); //暂停(按任意键继续)break;case 'r':case 'R': //重新开始system("cls"); //清空屏幕main(); //重新执行主函数}}}shape = nextShape, form = nextForm; //获取下一个方块的信息DrawSpace(nextShape, nextForm, COL + 3, 3); //将右上角的方块信息用空格覆盖}
}

注意: 这里只是概括性的说明了俄罗斯方块的主体逻辑,代码当中还有大量注释以供大家理解。

从文件读取最高分

首先需要使用fopen函数打开“俄罗斯方块最高记录.txt”文件,若是第一次运行该代码,则会自动创建该文件,并将历史最高记录设置为0,之后读取文件当中的历史最高记录存储在max变量当中,并关闭文件即可。

//从文件读取最高分
void ReadGrade()
{FILE* pf = fopen("俄罗斯方块最高得分记录.txt", "r"); //以只读方式打开文件if (pf == NULL) //打开文件失败{pf = fopen("俄罗斯方块最高得分记录.txt", "w"); //以只写方式打开文件(文件不存在可以自动创建该文件)fwrite(&grade, sizeof(int), 1, pf); //将max写入文件(此时max为0),即将最高历史得分初始化为0}fseek(pf, 0, SEEK_SET); //使文件指针pf指向文件开头fread(&max, sizeof(int), 1, pf); //读取文件中的最高历史得分到max当中fclose(pf); //关闭文件pf = NULL; //文件指针及时置空
}

更新最高分到文件

首先使用fopen函数打开“俄罗斯方块最高记录.txt”,然后将本局游戏的分数grade写入文件当中即可(覆盖式)。

//更新最高分到文件
void WriteGrade()
{FILE* pf = fopen("俄罗斯方块最高得分记录.txt", "w"); //以只写方式打开文件if (pf == NULL) //打开文件失败{printf("保存最高得分记录失败\n");exit(0);}fwrite(&grade, sizeof(int), 1, pf); //将本局游戏得分写入文件当中(更新最高历史得分)fclose(pf); //关闭文件pf = NULL; //文件指针及时置空
}

主函数

主函数里面就是依次调用以上函数,但有三点需要说明

  1. 全局变量grade需要在主函数内初始化为0,不能在全局范围初始化为0,因为当玩家按下R键进行重玩时我们需要将当前分数grade重新设置为0。
  2. 随机数的生成起点建议设置在主函数当中。
  3. 主函数当中的#pragma语句是用于消除类似以下警告的:

在这里插入图片描述

int max, grade; //全局变量
int main()
{
#pragma warning (disable:4996) //消除警告max = 0, grade = 0; //初始化变量system("title 俄罗斯方块"); //设置cmd窗口的名字system("mode con lines=29 cols=60"); //设置cmd窗口的大小HideCursor(); //隐藏光标ReadGrade(); //从文件读取最高分到max变量	InitInterface(); //初始化界面InitBlockInfo(); //初始化方块信息srand((unsigned int)time(NULL)); //设置随机数生成的起点StartGame(); //开始游戏return 0;
}

http://chatgpt.dhexx.cn/article/p2vbxviw.shtml

相关文章

IDEA 设置类注释模板

效果展示 步骤&#xff1a;File-->settings-->Editor-->File and Code Templates-->Files 选择Class文件&#xff08;当然你要设置接口的还也可以选择Interface文件&#xff09;

设置idea类注释模板

1.File-->Settings...&#xff0c;或者直接按快捷键CtrlAltS. 2. Editor-->File and Code Templates-->Includes-->File Header,根据右下角Description的提示&#xff0c;添加自己需要的注释。 3.在右侧空白处设置自己的模板&#xff0c;若提示cant parse class.可…

IDEA 创建类注释模板设置

1.idea类注释 打开&#xff1a;file->setting->Editor->Filr and Code Templates->Includes->File Header 类注释模板&#xff1a; /** * title: ${NAME}* projectName ${PROJECT_NAME}* description: TODO* author ${USER}* date ${DATE}${TIME}*/ 2.方法注释…

java idea 配置注释模板

java idea 配置注释模板 最近项目组在加强代码规范管理。代码优雅从代码注释开始。奥力给&#xff01;&#xff01;&#xff01; 类注释的 File >> Settings >> Editor >> File and code Templates >> Files >> class 上图中的模板内容如下&…

idea类注释模板,方法注释模板。

idea设置--实时模板 新建组 类模板 /*** description $description$* author YJJ* date $date$ $time$* version 1.0*/ 方法模板 ** * description $description$ $params$ $return$* author YJJ* date $date$ $time$*/ params脚本&#xff1a; groovyScript("def r…

IDEA代码注释模板

存在问题: 每次写代码&#xff0c;新建类都没有注释&#xff0c;这对一个拥有代码强迫症患者来说&#xff0c;是多么不爽的一件事&#xff0c;不利于开发和后期问题排查 举个例子&#xff1a; 上次写代码的时候&#xff0c;这一段只有我和上帝知道&#xff0c;而现在&#xff0…

idea添加注释模板

自动添加描述 class // 创建时间有的也没有 仅仅就是class的描述信息&#xff0c;看个人公司情况吧&#xff0c;描述信息最好创建时间有一个空行&#xff0c;我忘记了/** * 描述信息 * * create: ${YEAR}-${MONTH}-${DAY} ${HOUR}:${MINUTE} */method // 一会要写入模板的内…

【idea生成类注释模板快捷键设置】

idea类注释模板快捷键 设置在方法上生成注释的快捷键1&#xff0c;ctrlaltS打开idea设置设置模板中的参数取值来源使用效果展示 设置在方法上生成注释的快捷键 1&#xff0c;ctrlaltS打开idea设置 设置模板中的参数取值来源 使用效果展示

IntelliJ IDEA类和方法注释模板配置

1、设置文件注释 设置Java类的注释模板&#xff0c;创建Java类自动生成该注释模板。 先看文件注释效果&#xff1a; 1.1、选择 IntelliJ IDEA —> 选择 Preferences... 打开IDEA属性设置面板 1.2、选择 File Code Templates—>选择 Includes 选项卡—>选择 File He…

IDEA配置方法类注释模板

IDEA配置方法类注释模板 IDEA20.1配置方法注释注释的种类注释使用规范 摘自阿里开发手册 IDEA方法模板注释一、打开idea 点击菜单File->Setting-Editor->Live Template二、选择Live Template 添加快键键缩写注释模板代码三、配置Edit variables四、配置入参和返回字段的d…

IDEA类和方法注释模板配置

1、类的注释模板配置 File-->Settings-->Editor-->File and Code Templates-->File --->Class: 模板代码下所示&#xff1a; /*** ClassName: ${NAME}* Author : ${USER}* Date :${DATE} ${TIME}* Description: TODO* Version :1.0*/ 上述${NAME}为类名&…

IDEA方法注释模板设置

IDEA类和方法注释模板设置 1、File–>Settings–>Editor–>Live Templates &#xff08;1&#xff09;新建组&#xff1a;命名为userDefine &#xff08;2&#xff09;新建模板&#xff1a;选中你刚才创建的组&#xff0c;然后右上角新建模板&#xff0c;命名为* &…

IDEA注释模板配置

最初&#xff0c;从Eclipse转到IDEA&#xff0c;总是不习惯IDEA的注释方式&#xff0c;所以在网上找来找去加上自己的测试&#xff0c;终于形成一套相对详细的配置流程。之前每次用到都靠收藏里的别人文章应付&#xff0c;但是在配置新的开发环境或者有新的小组成员加入时总要用…

IDEA类文档注释模板设置

IDEA类文档注释模板设置&#xff1a; 在【File and Code Templates】页面设置类&#xff08;Class&#xff09;的文档注释 . 注意&#xff1a;public class ${NAME} {}一定不能删 /***ClassName ${NAME}*Description TODO*Author ${USER}*Date ${DATE} ${TIME}*Version 1.0*…

IDEA 创建类注释模板

一、引言 在写Java代码过程中&#xff0c;当我们创建类的时候往往需要在类上写一些注释信息&#xff0c;而这些注释信息就主要是几个注释点&#xff0c;如果全部都手写就比较浪费时间了&#xff0c;这个时间后我们就可以通过使用注释模板添加自己的自定义的注释在类或者方法上。…

IDEA 设置注释模板顺序

配置注释模板Live Templates时&#xff0c;模板展示顺序只会按照首字母顺序进行排序&#xff0c;若想按照自己配置的模板进行排序&#xff0c;可按照下图关闭设置即可。

IDEA设置注释模板

目录 一、设置类注释模板 二、设置方法注释模板 一、设置类注释模板 打开File->setting->File and Code Templates,选择Includes&#xff0c;选中File Header.如下图所示&#xff1a; 勾选"Enable Live Templates",在内容框中填入注释模板信息。 /*** 创建…

【IDEA类注释模板和方法注释模板】

IDEA类注释模板和方法注释模板 前言一、设置IDEA 中的类注释模板二、定义方法注释模板每天一个小知识 前言 在我们的开发过程中。良好的代码习惯总是重要的&#xff08;ps&#xff1a;编程两大痛苦&#xff1a;别人不写注释文档&#xff0c;自己写注释文档&#xff09;&#x…

idea类注释模板快捷键设置

类 在idea 中java类创建的时候就会带上注释模板&#xff0c;所以不用快捷键也可以的 变量 描述 ${DATE} 当前系统日期 ${DAY} 当月的当天 ${DS} 美元符号。此变量用于转义美元字符&#xff0c;因此不会将其视为模板变量的前缀。 ${END} 完成编辑变量后的插入位置 ${HOUR}…

idea 设置类的注释模板

在Idea工具中&#xff0c;点击File >> Settings >> Editor >> File and Code Templates >> Files&#xff0c;选择class &#xff08;或者直接按快捷键&#xff1a;Ctrl Alt S&#xff09; 在右侧根据需要输入模板内容&#xff1a; /***Author: JYC…