c语言——俄罗斯方块

慈云数据 6个月前 (05-14) 技术支持 67 0

一、游戏效果



二. 游戏背景

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

《俄罗斯方块》(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  
 

注解: 

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  

注解: 

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.3.2  覆盖方块

void  PrintSpace(int i, int j, int  x, int y)//覆盖指定位置方块
{
	for (int m = 0; m 

4.3.3  绘制方块

void PrintBlock(int i, int j, int x, int y)
{
int m = 0;
int n = 0;
for (m = 0; m  

 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  3; m--)
			{
				
				for (int n = 2; n  
 
 

注:

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

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

4.4.5   判断游戏失败

void JudeIsOver()
{
	for (int i = 2; i  
 

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; m50)//避免连续按,变成负数,会出现bug
			{
				block[*i][*j].speed -= 50;
			}
			
		}
		else //碰撞
		{
			//将当前方块的信息录入IF当中
			for (int m = 0; m  
 
 

注:

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
#include 
#include
#include
#include
#include
#include
#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  3; i--)
	{
		int count = 0;
		for (int j = 2; j  3; m--)
			{
				
				for (int n = 2; n 50)//避免连续按,变成负数,会出现bug
			{
				block[*i][*j].speed -= 50;
			}
			
		}
		else //碰撞
		{
			//将当前方块的信息录入IF当中
			for (int m = 0; m  

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;
}
微信扫一扫加客服

微信扫一扫加客服

点击启动AI问答
Draggable Icon