用c语言完成俄罗斯方块小游戏
这估计是你在编程学习过程中的第一个小游戏开发,怎么说呢,在这里只针对刚学程序设计的学生,就是说刚接触C语言没多久,有一点功底的学生看看,简陋的代码,简陋的实现,
by:BoBoo文睡不醒
注:该代码内注释加入个人理解,若有错误,多多包涵
由于不需要插件,绘制出来的玩意恐怕不够美观,多多理解
语言:C的不能再C。(没涉及啥很高深的玩意)
(高级一点的话用EasyX插件里面的玩意,那就很炫酷了)
涉及内容:光标移动,颜色,图形绘制、消去、覆盖和打印,键盘按键敲击的读取,计时器,文件的创建与读取
看似简单,实际上一点也不难
1. 游戏概述
俄罗斯方块是一款经典的益智游戏,主要功能包括:
- 显示游戏界面
- 随机生成方块并控制其移动和旋转
- 消除完整行
- 记录并保存最高分
- 游戏结束判断
本代码使用控制台操作,并通过系统函数控制光标位置、打印字符实现图形效果。
2. 游戏开发所需知识
在实现类似游戏之前,需掌握以下知识:
- 控制台操作:控制光标位置 (
CursorJump
) 和隐藏光标 (HideCursor
)。 - 颜色设置:通过
SetConsoleTextAttribute
改变文本颜色。 - 二维数组操作:表示游戏区域与方块信息。
- 随机数生成:使用
rand()
生成随机方块。 - 键盘事件:读取按键事件(如
_kbhit
和_getch
)。 - 文件操作:保存和读取游戏历史最高分。
3. 代码解读
3.1 数据结构
Face
- 表示整个游戏界面。
data
: 标记某处是否有方块(0无,1有)。color
: 存储方块颜色信息。
Block
- 存储7种方块的4种形态。
- 通过二维数组
space
表示方块形状。
c复制代码struct Face {
int data[ROW][COL + 10];
int color[ROW][COL + 10];
};
struct Block {
int space[4][4];
} block[7][4];
3.2 核心模块
(1) 界面初始化
使用InitInterface
绘制边框与提示信息:
- 通过
CursorJump
设置光标位置。 - 使用
printf("■")
绘制边框。 - 边框信息存入
face.data
。
c复制代码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("■");
}
}
}
}
(2) 方块初始化
InitBlockInfo
存储方块的7种形状和4种旋转状态:
- 每种形状的基本状态由
block[shape][0]
定义。 - 使用二维旋转公式生成其余三种形态。
c复制代码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];
}
}
(3) 方块绘制与移动
DrawBlock
:在指定位置打印方块。DrawSpace
:用空格覆盖指定位置,实现“擦除”效果。
c复制代码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 && y + i > 0) {
CursorJump(2 * (x + j), y + i);
printf("■");
}
}
}
}
(4) 键盘事件处理
通过_getch
检测按键:
LEFT
:左移RIGHT
:右移DOWN
:快速下落SPACE
:旋转
c复制代码switch (ch) {
case LEFT:
if (IsLegal(shape, form, x - 1, y)) x--;
break;
case RIGHT:
if (IsLegal(shape, form, x + 1, y)) x++;
break;
case SPACE:
if (IsLegal(shape, (form + 1) % 4, x, y)) form = (form + 1) % 4;
break;
}
(5) 行清除与得分判断
JudeFunc
实现:
- 检查行是否满。
- 若满,则消除行并将上方行下移。
- 更新得分。
c复制代码if (sum == COL - 2) {
grade += 10;
for (int j = 1; j < COL - 1; j++) {
face.data[i][j] = 0;
CursorJump(2 * j, i);
printf(" ");
}
}
(6) 游戏逻辑与结束判断
StartGame
控制方块下落、键盘响应与结束判断:
- 随机生成方块并控制其运动。
- 检测顶层是否有方块,若有则结束游戏。
(7) 文件操作
ReadGrade
与WriteGrade
负责读取和保存最高分:
- 通过
fopen
打开文件,保存最高分。 - 使用二进制方式存储成绩,防止文本存储带来的额外处理。
c复制代码FILE* pf = fopen("record.txt", "r");
fread(&max, sizeof(int), 1, pf);
4. 编写俄罗斯方块的步骤
- 明确需求:
- 游戏逻辑(下落、移动、旋转、消除)。
- 数据存储(方块形态、游戏区域)。
- 图形展示(控制台或GUI)。
- 搭建基础框架:
- 初始化游戏区域、边框与方块。
- 定义随机生成与旋转逻辑。
- 逐步完善:
- 实现消除与得分逻辑。
- 增加键盘事件处理。
- 添加文件操作功能。
- 优化与美化:
- 增加动画效果。
- 提高界面美观度(如使用插件或图形库)。
通过本代码,你可以学习到俄罗斯方块的实现核心逻辑,同时可以在此基础上尝试更高级的图形化实现,如使用EasyX插件或其他图形库,进一步提升游戏的视觉体验和功能丰富性。
您说:
对每一步进行详细的介绍
ChatGPT 说:
ChatGPT
编写俄罗斯方块游戏的详细步骤与说明
本文将从代码结构与逻辑出发,逐步拆解如何实现一个俄罗斯方块游戏,帮助理解并指导如何从零开始构建类似的游戏。
1. 明确需求与设计思路
俄罗斯方块的基本玩法包括以下几部分:
- 随机生成方块:从7种形状中随机生成一个方块,并在游戏区域顶部下落。
- 方块运动控制:通过键盘实现左右移动、加速下落和旋转操作。
- 碰撞检测与消行:检测方块是否碰到其他方块或到达底部,并判断是否需要消行。
- 得分与游戏结束:计算得分,判断是否达到游戏结束条件。
- 数据持久化:保存和读取最高分记录。
设计中还需考虑:
- 游戏的界面绘制。
- 使用二维数组存储游戏区域状态。
- 持续的方块生成与处理逻辑。
2. 搭建基础框架
2.1 确定游戏区域和方块结构
俄罗斯方块的核心在于使用二维数组存储数据状态:
-
Face
结构体:存储整个游戏区域状态。
data
数组:记录每个位置是否有方块。color
数组:记录每个位置的颜色。
-
Block
结构体:存储所有方块的形状及其4种旋转状态。
c复制代码#define ROW 29 // 游戏区域行数
#define COL 20 // 游戏区域列数
struct Face {
int data[ROW][COL + 10]; // 游戏区域方块标记
int color[ROW][COL + 10]; // 方块颜色
};
struct Block {
int space[4][4]; // 存储方块形状
} block[7][4]; // 7种形状,每种4种状态
2.2 初始化游戏区域与边框
绘制游戏界面的边框,展示基本信息(如得分、提示):
- 利用双重循环,遍历所有格子。
- 使用特定符号(如
■
)绘制边框。 - 在右侧区域打印提示文字。
代码实现:
c复制代码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 == 0 || i == ROW - 1) {
face.data[i][j] = 1; // 顶部和底部边框
printf(" ■");
} else {
face.data[i][j] = 0; // 非边框区域标记为空
}
}
}
// 打印右侧提示信息
CursorJump(2 * COL, 1);
printf("下一个方块:");
CursorJump(2 * COL + 4, ROW - 3);
printf("当前分数:%d", grade);
}
3. 初始化方块形状与逻辑
3.1 定义方块形状
俄罗斯方块共有7种形状,每种形状有4种旋转状态。通过二维数组定义每种形状的初始状态,并通过旋转公式生成其他状态。
代码实现:
c复制代码void InitBlockInfo() {
// 定义“T”形
for (int i = 0; i <= 2; i++) block[0][0].space[1][i] = 1;
block[0][0].space[2][1] = 1;
// 定义其他形状...
// 通过旋转公式生成每种形状的其他3种状态
for (int shape = 0; shape < 7; shape++) {
for (int form = 0; form < 3; form++) {
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
block[shape][form + 1].space[i][j] = block[shape][form].space[3 - j][i];
}
}
}
}
}
4. 方块的运动控制
4.1 键盘事件检测
通过_kbhit
检测键盘输入,并用_getch
捕获按键值:
LEFT
:左移。RIGHT
:右移。DOWN
:加速下落。SPACE
:旋转。
实现示例:
c复制代码char ch = getch();
switch (ch) {
case LEFT:
if (IsLegal(shape, form, x - 1, y)) x--;
break;
case RIGHT:
if (IsLegal(shape, form, x + 1, y)) x++;
break;
case SPACE:
if (IsLegal(shape, (form + 1) % 4, x, y)) form = (form + 1) % 4;
break;
case ESC:
exit(0); // 退出游戏
}
4.2 碰撞检测
使用IsLegal
函数判断方块是否可以移动或旋转:
- 检查方块每个格子是否会越界或与已有方块重叠。
- 返回
1
表示合法,0
表示不合法。
c复制代码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 &&
(x + j < 0 || x + j >= COL || y + i >= ROW || face.data[y + i][x + j] == 1)) {
return 0; // 非法
}
}
}
return 1; // 合法
}
5. 方块落地与消行
5.1 方块落地
每次方块下落时:
- 检查其下一步是否越界或碰到其他方块。
- 如果不能继续下落,则将当前方块写入
Face
结构。
5.2 行满判断与消除
当某一行填满时:
- 删除该行并将上方所有行下移。
- 更新分数。
代码实现:
c复制代码int JudeFunc() {
for (int i = ROW - 2; i > 4; i--) {
int sum = 0;
for (int j = 1; j < COL - 1; j++) sum += face.data[i][j];
if (sum == COL - 2) { // 满行
grade += 10;
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--) { // 上方行下移
for (int n = 1; n < COL - 1; n++) {
face.data[m][n] = face.data[m - 1][n];
face.color[m][n] = face.color[m - 1][n];
}
}
}
}
return 0;
}
6. 游戏结束与得分记录
6.1 游戏结束判断
若方块到达顶层,则游戏结束,提示玩家是否重开一局。
6.2 保存与读取最高分
通过文件操作保存最高分:
- 使用
fopen
以只读或写模式打开文件。 - 利用
fwrite
保存分数。
代码实现:
c复制代码void ReadGrade() {
FILE* pf = fopen("record.txt", "r");
if (pf == NULL) {
pf = fopen("record.txt", "w");
fwrite(&grade, sizeof(int), 1, pf);
}
fread(&max, sizeof(int), 1, pf);
fclose(pf);
}
7. 整合与优化
- 游戏主循环: 控制方块生成、运动、碰撞和消除,维护游戏逻辑流。
- 视觉优化: 增加更多颜色和图案设计,使游戏界面更美观。
以上是详细的游戏开发步骤,你可以在此基础上进行个性化扩展,比如增加背景音乐、提升图形效果,或者引入难度等级功能。
c(下面的代码经过了详细的注释,希望大家慢慢研究)
//作品:tetris(带注释)
// by:BoBoo文睡不醒
//注:该代码内注释加入个人理解,若有错误,多多包涵
//由于不需要插件,绘制出来的玩意恐怕不够美观,多多理解
//语言:C的不能再C。(没涉及啥很高深的玩意)(高级一点的话用EasyX插件里面的玩意,那就很炫酷了)
//涉及内容:光标移动,颜色,图形绘制、消去、覆盖和打印,键盘按键敲击的读取,计时器,文件的创建与读取
//看似简单,实际上一点也不难
//头文件部分:
#include <stdio.h>
#include <Windows.h>
//提供了一些在控制台程序中进行输入输出、控制光标位置、设置文本属性等方面的函数和常量
//在代码中使用了其中的一些函数比如SetConsoleCursorPosition()、SetConsoleTextAttribute()来控制光标位置和设置文本颜色
#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); //设置光标信息
}
//为了解清晰其中,关键结构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); //设置光标位置
}
//初始化界面
//主界面的外层的边框打印
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 || i == 0)//首末两行边框的打印
{
face.data[i][j] = 1; //标记该位置有方块
printf(" ■");//此处予以说明,为了整齐起见,每行方块之间加入空格。下方代码同理
}
else
face.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; //其他默认设置为白色(若需将颜色重置可把color变为7)
break;
}
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), c); //颜色设置
//注:SetConsoleTextAttribute是一个API(应用程序编程接口)(from百度,自行了解)
}
//画出方块
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 && y + i > 0) //如果该位置有方块
{
CursorJump(2 * (x + j), y + i); //光标跳转到指定位置
printf("■"); //输出方块
}
}
}
//由于不怎么会如何让打印的图形不影响边框(上边框),于是愚蠢的我决定再打印一遍上边框
//大家有更好的方法自行尝试
color(7);//不加的话第一行的颜色会与图形的颜色相同
for (int j = 0; j < COL + 10; j++)
{
if (j == 0 || j == COL - 1 || j == COL + 9)
{
CursorJump(2 * j, 0);
printf("■");
}
else
{
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 - 9);
if (grade > max)//接下来就是曼妙的人机交互部分
{
printf("恭喜你打破最高记录,最高记录更新为%d", grade);
WriteGrade();
}
else if (grade == max)
{
printf("与最高记录持平,加油再创佳绩", grade);
}
else
{
printf("请继续加油,当前与最高记录相差%d", max - grade);
}
CursorJump(2 * (COL / 3), ROW / 2 - 6);
printf("GAME OVER");
while (1)
{
char ch;
CursorJump(2 * (COL / 3), ROW / 2 - 3);
printf("再来一局?");
CursorJump(2 * (COL / 3), ROW / 2);
printf("输入y并敲击回车(y/n)进入下一局;");
CursorJump(2 * (COL / 3), ROW / 2 + 3);
printf("输入n并敲击回车(n/n)结束游戏;");
CursorJump(2 * (COL / 3), ROW / 2 + 6);
printf("敲击esc则退出游戏;");
if (kbhit() != 0)
{
printf("Y");
if (getch() == 27)
{
printf("Y");
system("cls"); //清空屏幕
color(7);
CursorJump(COL, ROW / 2);
printf(" 游戏结束 ");
CursorJump(COL, ROW / 2 + 2);
exit(0); //结束程序
}
//疑点:上面的if判断和执行代码无法实现,还未研究明白******
}
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 //键盘被敲击
{
//对敲击键盘的按键的判断
//(不理解为什么这个对,上面那个ESC的判断就有问题)******
char ch = getch(); //读取keycode
switch (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; //文件指针及时置空
}
//over
// //还是那句话
//看似简单,实际上一点也不难