c/c++实现俄罗斯方块

一、前言

代码繁杂,很多地方可以优化,用的都是一些早期学习的知识,主要是思路分享。

二、思路

表格

初始各个格子状态为0

先打印个你想要的表格,我的表格图示:

俄罗斯方块

初始化俄罗斯方块,记下坐标,存储到一个二维数组里,并初始状态为1

后续输出,只打印表格中状态为1的坐标,当然这里还没有把俄罗斯方块的状态转换为表格的状态

旋转

我的思路是初始俄罗斯方块每种方块时找一个中心点,存储在数组b[n][1]对应的位置,方便后续旋转操作,中心点已知,旋转前点坐标已知,那么就可以通过数学公式计算旋转后坐标

移动

分为左右移动,就简单的x坐标+-

状态转化(核心思路)

打印时,只打印状态为1的坐标,我这里由两部分需要打印,一个是表格内各个坐标,一个是俄罗斯方块的坐标。

初始表格各个坐标状态为0,初始俄罗斯方块状态为1。

俄罗斯方块每次移动后,设置原来的俄罗方块状态为0,新的俄罗斯方块状态为1。

等俄罗斯方块到底部停止运动后,将俄罗斯方块状态转换为表格状态,俄罗斯方块进行初始化。

三、代码实现

Tetris.h

#pragma once

#include <windows.h>	//WINAPI相关操作,srand,sleep,system等
#include<stdio.h>		//标准输入输出(print,scanf,perror等)
#include<assert.h>		//assert()等
#include <locale.h>		//本地环境设置,主要是为了宽字符的打印
#include <stdbool.h>	//true,false



#define KEY_PRESS(vk)  ((GetAsyncKeyState(vk)&1)?1:0)//用来判断按键是否被按

#define WALL L'□'	//墙
#define BLOCK L'■' //块
#define TIME 1000	//睡眠时间

typedef struct BlockCoordinate
{
	int x;//确定一个中心(x,y)
	int y;
	int a;
	//然后由中心旋转,确定俄罗斯方块此时的状态
}BC;

typedef struct System
{
	BC a[250];//200个格子
	BC b[7][4];//7种俄罗斯方块,每种都是有4个块
	int type;//俄罗斯方块种类
}Ss;

enum GAME_STATUS	//枚举
{
	OK,				//正常
	KILL_BY_SELF,	//正常失败
	END_NORMAL		//正常退出
};

enum GAME_OPERATE
{
	RENEW,	//重新生成俄罗斯方块
	CLEAR,	//清除并得分
	NORMAL	//正常状态
};

typedef struct Tetris
{
	Ss s;						//格子状态over(1)和out(0)
	enum GAME_STATUS _Status;	//游戏状态
	enum GAME_OPERATE _Operate;	//操作状态
	enum DIRECTION _Dir;		//俄罗斯方块的方向
	int _Socre;					//游戏分数
	int _SleepTime;				//休眠时间
	int _Row;					//要清除的行
}Tetris, * pTetris;


//暂停
void Pause();

//游戏初始化
void GameInit(pTetris t);

//设置光标位置
void SetPos(short x, short y);

//打印欢迎界⾯
void WelcomeToGame();

//创建地图
void CreateMap();

//初始俄罗斯方块位置
void BlockInit(pTetris t);

//将over的块打印出来
void OverPrint(pTetris t);//over为1,out为0

//先初始化over的块
void OverInit(pTetris t);

//游戏运行
void GameRun(pTetris t);

//初始俄罗斯方块信息
void TetrisInit(pTetris t);

//转化,消除俄罗斯方块
void BlockTurn(pTetris t);
void FBlockTurn(pTetris t);

//俄罗斯方块移动
void BlockMove(pTetris t);

//俄罗斯方块左旋转,右旋转
void BlockLeftSpin(pTetris t);
void BlockRightSpin(pTetris t);

//左移右移
void RightMove(pTetris t);
void LeftMove(pTetris t);

//下移上移
void DownMove(pTetris t);
void UpMove(pTetris t);

//独家秘方
bool ax1(pTetris t);
bool ax2(pTetris t);
bool ax3(pTetris t);
bool ax33(pTetris t);
bool ax4(pTetris t);

//方块清除
void BlockClear(pTetris t);

//所有格子下移一位,消除最后一行用
void BlockClearRow(pTetris t);

//检测是否失败
void TetrisCheck(pTetris t);

//游戏退出时的维护
void GameEnd(pTetris t);

Tetris.c

#define _CRT_SECURE_NO_WARNINGS 1

#include"Tetris.h"

void Pause()
{
	SetPos(38, 20);
	system("pause");
}

//封装⼀个设置光标位置的函数
void SetPos(short x, short y)
{
	HANDLE houtput = NULL;
	houtput = GetStdHandle(STD_OUTPUT_HANDLE);//获取标准输出的句柄

	COORD pos = { x, y };//设置光标位置
	SetConsoleCursorPosition(houtput, pos);
}

void WelcomeToGame()
{
	SetPos(38, 12);
	wprintf(L"欢迎来到俄罗斯方块小游戏\n");
	SetPos(38, 20);
	system("pause");
	system("cls");
	SetPos(24, 12);
	wprintf(L"用 ← . → 来控制方块的移动,按空格暂停,按A左转,按D右转,按F3加速,F4减速\n");
	Pause();
	system("cls");
}

void CreateMap()
{
	//整体框架
	//10列20行,为了方便输出,我设置24行,预备的方块在顶上4行
	//也就是1+10+1=12列,1+4+1+20+1=27行
	//那么第6行为0-3 4-7 8-11

	int i = 0;
	//上(00,00)-(22,00)
	SetPos(0, 0);
	for (i = 0; i <= 11; i++)
	{
		wprintf(L"%lc", WALL);
	}
	//中(00,05)-(06,05)和(16,05)-(22,05)
	SetPos(0, 5);
	for (i = 0; i <= 3; i++)
	{
		wprintf(L"%lc", WALL);
	}
	SetPos(16, 5);
	for (i = 8; i <= 11; i++)
	{
		wprintf(L"%lc", WALL);
	}
	//左(00,00)-(00,26)
	for (i = 0; i <= 26; i++)
	{
		SetPos(0, i);
		wprintf(L"%lc", WALL);
	}
	//右(22,00)-(22,26)
	for (i = 0; i <= 26; i++)
	{
		SetPos(22, i);
		wprintf(L"%lc", WALL);
	}
	//下(00,26)-(22,26)
	SetPos(0, 26);
	for (i = 0; i <= 11; i++)
	{
		wprintf(L"%lc", WALL);
	}
}

void BlockInit(pTetris t)
{
	t->s.type = 0;
	//俄罗斯方块有长条,大方块,左三角,右三角,中三角,左S,右S,共7种
	//长条
	//0*00
	//0*00
	//0*00
	//0*00
	t->s.b[0][0].x = 10;
	t->s.b[0][0].y = 1;
	t->s.b[0][1].x = 10;
	t->s.b[0][1].y = 2;
	t->s.b[0][2].x = 10;
	t->s.b[0][2].y = 3;
	t->s.b[0][3].x = 10;
	t->s.b[0][3].y = 4;
	//大方块
	//0000
	//**00
	//**00
	//0000
	t->s.b[1][0].x = 8;
	t->s.b[1][0].y = 2;
	t->s.b[1][1].x = 10;
	t->s.b[1][1].y = 2;
	t->s.b[1][2].x = 8;
	t->s.b[1][2].y = 3;
	t->s.b[1][3].x = 10;
	t->s.b[1][3].y = 3;
	//左三角
	//0000
	//**00
	//0*00
	//0*00
	t->s.b[2][0].x = 8;
	t->s.b[2][0].y = 2;
	t->s.b[2][1].x = 10;
	t->s.b[2][1].y = 2;
	t->s.b[2][2].x = 10;
	t->s.b[2][2].y = 3;
	t->s.b[2][3].x = 10;
	t->s.b[2][3].y = 4;
	//右三角
	//0000
	//0**0
	//0*00
	//0*00
	t->s.b[3][0].x = 12;
	t->s.b[3][0].y = 2;
	t->s.b[3][1].x = 10;
	t->s.b[3][1].y = 2;
	t->s.b[3][2].x = 10;
	t->s.b[3][2].y = 3;
	t->s.b[3][3].x = 10;
	t->s.b[3][3].y = 4;
	//中三角
	//0000
	//***0
	//0*00
	//0000
	t->s.b[4][0].x = 8;
	t->s.b[4][0].y = 2;
	t->s.b[4][1].x = 10;
	t->s.b[4][1].y = 2;
	t->s.b[4][2].x = 12;
	t->s.b[4][2].y = 3;
	t->s.b[4][3].x = 10;
	t->s.b[4][3].y = 3;
	//左S
	//0000
	//**00
	//0**0
	//0000
	t->s.b[5][0].x = 8;
	t->s.b[5][0].y = 2;
	t->s.b[5][1].x = 10;
	t->s.b[5][1].y = 2;
	t->s.b[5][2].x = 12;
	t->s.b[5][2].y = 3;
	t->s.b[5][3].x = 10;
	t->s.b[5][3].y = 3;
	//右S
	//0000
	//0**0
	//**00
	//0000
	t->s.b[6][0].x = 12;
	t->s.b[6][0].y = 2;
	t->s.b[6][1].x = 10;
	t->s.b[6][1].y = 2;
	t->s.b[6][2].x = 8;
	t->s.b[6][2].y = 3;
	t->s.b[6][3].x = 10;
	t->s.b[6][3].y = 3;
	//这里把每个a[n][2]坐标设为一致,是为了把它当做旋转的中心点,方便后续旋转

	//n==x/2+10*(y-1)-1
}

void BlockTurn(pTetris t)
{
	int n = t->s.type;
	for (int i = 0; i < 4; i++)
	{
		t->s.a[(t->s.b[n][i].x) / 2 + (t->s.b[n][i].y - 1) * 10 - 1].a = 1;
	}
}
void FBlockTurn(pTetris t)
{
	int n = t->s.type;
	for (int i = 0; i < 4; i++)
	{
		t->s.a[(t->s.b[n][i].x) / 2 + (t->s.b[n][i].y - 1) * 10 - 1].a = 0;
	}
}

void TetrisInit(pTetris t)
{
	//OverInit(t);//初始块的状态out(0)

	BlockInit(t);//初始俄罗斯方块的种类

	t->_Status = OK;
}

void GameInit(pTetris t)
{
	//1.控制台窗⼝⼤⼩的设置
	system("mode con cols=100 lines=40");


	//2.控制台窗⼝名字的设置
	system("title 俄罗斯方块");


	//3.⿏标光标的隐藏.
	HANDLE houtput = NULL;
	houtput = GetStdHandle(STD_OUTPUT_HANDLE);//获得标准输出设备的句柄

	CONSOLE_CURSOR_INFO cursor_info = { 0 };//定义一个光标信息的结构体

	GetConsoleCursorInfo(houtput, &cursor_info);//获取控制台上的光标信息

	cursor_info.bVisible = false;//设置控制台上的光标的状态为隐藏

	SetConsoleCursorInfo(houtput, &cursor_info);//设置控制台上的光标信息


	//4.打印欢迎界⾯
	WelcomeToGame();


	//5.创建地图
	CreateMap();


	//6初始分数,睡眠时间等
	t->_SleepTime = TIME;
	t->_Socre = 0;
	t->_Row = 0;

}

void OverPrint(pTetris t)
{
	int n = 0;
	for (n = 0; n < 250; n++)
	{
		if (n == 40 || n == 41 || n == 42 || n == 47 || n == 48 || n == 49)
		{
			continue;
		}
		SetPos(t->s.a[n].x, t->s.a[n].y);
		if(t->s.a[n].a==1)
		{
			wprintf(L"%lc", BLOCK);
		}
		else
		{
			wprintf(" ");
		}
	}
}

void OverInit(pTetris t)
{//2-20,6-15
	int i, j, n = 0;
	for (j = 1; j <= 25; j++)
	{
		for (i = 2; i <= 20; i += 2)
		{
			t->s.a[n].a = 0;
			t->s.a[n].x = i;
			t->s.a[n].y = j;
			n++;
		}
	}
}

void LeftMove(pTetris t)//左移
{//x--,y不变,然后消除原俄罗斯方块,转换新俄罗斯方块
	int n = t->s.type;
	for (int i = 0; i < 4; i++)
	{
		t->s.b[n][i].x -= 2;
	}
}
void RightMove(pTetris t)//右移
{
	int n = t->s.type;
	for (int i = 0; i < 4; i++)
	{
		t->s.b[n][i].x += 2;
	}
}
void DownMove(pTetris t)
{
	int n = t->s.type;
	for (int i = 0; i < 4; i++)
	{
		t->s.b[n][i].y++;
	}
}
void UpMove(pTetris t)
{
	int n = t->s.type;
	for (int i = 0; i < 4; i++)
	{
		t->s.b[n][i].y--;
	}
}
void BlockLeftSpin(pTetris t)
{//x2=2b-2y1+a;y2=b+x1/2-a/2
	int n = t->s.type;
	for (int i = 0; i < 4; i++)
	{
		int x = t->s.b[n][i].x;
		int y = t->s.b[n][i].y;
		if (i == 1)
		{
			continue;
		}//转换成数学问题,勾股定理
		t->s.b[n][i].x = -(2 * t->s.b[n][1].y) + (2 * y) + (t->s.b[n][1].x);
		t->s.b[n][i].y = (t->s.b[n][1].y) + (t->s.b[n][1].x / 2) - (x / 2);
	}
	if (!ax3(t) || !ax4(t))
	{
		BlockRightSpin(t);
	}
}
void BlockRightSpin(pTetris t)
{//x2=2b-2y1+a;y2=b+x1/2-a/2
	int n = t->s.type;
	for (int i = 0; i < 4; i++)
	{
		int x = t->s.b[n][i].x;
		int y = t->s.b[n][i].y;
		if (i == 1)
		{
			continue;
		}
		t->s.b[n][i].x = (2 * t->s.b[n][1].y) - (2 * y) + (t->s.b[n][1].x);
		t->s.b[n][i].y = (t->s.b[n][1].y) - (t->s.b[n][1].x / 2) + (x / 2);
	}
	if (!ax3(t) || !ax4(t))
	{
		BlockLeftSpin(t);
	}
}
bool ax1(pTetris t)//判断右边是否到底,到底返回false
{
	int n = t->s.type;
	for (int i = 0; i < 4; i++)
	{
		if (t->s.b[n][i].x >= 20)
		{
			return false;
		}
	}
	return true;
}
bool ax2(pTetris t)//判断左边是否到底,到底返回false
{
	int n = t->s.type;
	for (int i = 0; i < 4; i++)
	{
		if (t->s.b[n][i].x <= 2)
		{
			return false;
		}
	}
	return true;
}
bool ax3(pTetris t)//判断是否出界,出界返回false
{
	int n = t->s.type;
	for (int i = 0; i < 4; i++)
	{
		if (t->s.b[n][i].y < 6
			|| t->s.b[n][i].y > 25
			|| t->s.b[n][i].x < 2
			|| t->s.b[n][i].x > 20)
		{
			return false;
		}
	}
	return true;
}
bool ax33(pTetris t)
{
	int n = t->s.type;
	for (int i = 0; i < 4; i++)
	{
		if (t->s.b[n][i].y > 25
			|| t->s.b[n][i].x < 2
			|| t->s.b[n][i].x > 20)
		{
			return false;
		}
	}
	return true;
}
bool ax4(pTetris t)//判断是否与块重合,重合返回false
{
	int n = t->s.type;
	for (int i = 0; i < 4; i++)
	{
		if (t->s.a[(t->s.b[n][i].x) / 2 + (t->s.b[n][i].y - 1) * 10 - 1].a == 1)
			return false;
	}
	return true;
}
void BlockMove(pTetris t)
{
	if (KEY_PRESS(VK_RIGHT))//判断→是否被按
	{
		if (ax1(t) && ax3(t))//判断右边是否到边界且是否在范围内
		{
			FBlockTurn(t);
			RightMove(t);
			BlockTurn(t);
		}
	}
	else if (KEY_PRESS(VK_LEFT))//判断←是否被按
	{
		if (ax2(t) && ax3(t))//判断左边是否到边界且是否在范围内
		{
			FBlockTurn(t);
			LeftMove(t);
			BlockTurn(t);
		}
	}
	else if (KEY_PRESS(65) && ax3(t))//判断A键是否被按且是否在范围内
	{
		FBlockTurn(t);
		BlockLeftSpin(t);
		BlockTurn(t);
	}
	else if (KEY_PRESS(68) && ax3(t))//判断D键是否被按且是否在范围内
	{
		FBlockTurn(t);
		BlockRightSpin(t);
		BlockTurn(t);
	}

	FBlockTurn(t);
	DownMove(t);//下移


	if (!ax33(t) || !ax4(t))
	{
		UpMove(t);
		BlockTurn(t);
		t->_Operate = RENEW;
	}
}

void AllDownMove(pTetris t)//所有范围内格子下移
{//n == x / 2 + 10 * (y - 1) - 1
	//x:2-20  y:6-25
	int i, j = 0;
	for (i = 2; i <= 20; i += 2)
	{
		for (j = 25; j >= 6; j--)
		{
			t->s.a[(i / 2) + 10 * (j - 1) - 1].a
				= t->s.a[(i / 2) + 10 * ((j - 1) - 1) - 1].a;
		}
	}
}

void BlockClearRow(pTetris t)
{//
	int i, j = 0;
	for (i = 2; i <= 20; i += 2)
	{
		for (j = (t->_Row + 1) / 10; j >= 5; j--)
		{
			t->s.a[(i / 2) + 10 * (j - 1) - 1].a
				= t->s.a[(i / 2) + 10 * ((j - 1) - 1) - 1].a;
		}
	}
	t->_Socre += 1;
}

void BlockClear(pTetris t)
{
	int  i, j = 0;
	for (i = 50; i <= 249; i++)
	{
		if (t->s.a[i].a == 0)
		{
			i = i - (i % 10) + 10;
		}
		if (i % 10 == 9)
		{
			t->_Row = i;//对应行,赋的值为对应行末尾编号
			BlockClearRow(t);
		}
	}
}

void TetrisCheck(pTetris t)
{
	for (int i = 40; i <= 49; i++)
	{
		if (t->s.a[i].a == 1)
		{
			t->_Status = KILL_BY_SELF;
		}
	}
}

void GameRun(pTetris t)
{
	int a;
	OverInit(t);//初始块的状态out(0)
	do
	{
		//打印各种信息
		SetPos(40, 10);
		printf("总分数:%2d\n", t->_Socre);
		SetPos(40, 12);
		printf("用 ← . → 来控制方块的移动\n");
		SetPos(40, 13);
		printf("按空格暂停,按A左转,按D右转,按F3加速,F4减速\n");

		//确认各种状态
		if (KEY_PRESS(VK_SPACE))//判断空格是否被按
		{
			Pause();
		}
		else if (KEY_PRESS(VK_ESCAPE))//判断ESC是否被按
		{
			t->_Status = END_NORMAL;//正常退出
		}
		else if (KEY_PRESS(VK_F3))//判断F3
		{
			if (t->_SleepTime > 200)
			{
				t->_SleepTime -= 100;
			}
		}
		else if (KEY_PRESS(VK_F4))//判断F4
		{
			if (t->_SleepTime <= 1000)
			{
				t->_SleepTime += 100;
			}
		}
		else if (KEY_PRESS(VK_ESCAPE))//判断ESC
		{
			t->_Status = END_NORMAL;//正常退出
		}

		//生成俄罗斯方块
		if (t->_Operate == RENEW)//如果操作状态为重新生成
		{
			TetrisInit(t);//初始化俄罗斯方块信息
			t->s.type = rand() % 7;//0-6
			t->_Operate = NORMAL;

			//检测时机选在这里是因为:RENEW时所有状态更新,方块状态为最新
			//这时候若检测到在y=5这一行有状态为1,就为失败
			TetrisCheck(t);
		}
		
		//当一行满的时候,检测并清除
		BlockClear(t);

		//把俄罗斯方块信息转化为块上的信息
		BlockTurn(t);

		//打印现在状态下游戏界面
		OverPrint(t);

		//俄罗斯方块移动
		BlockMove(t);


		Sleep(t->_SleepTime);
		SetPos(38, 20);//单纯想把按任意键继续删掉
		printf("                    ");
	} while (t->_Status == OK);
}

//维护
void GameEnd(pTetris t)
{
	SetPos(38, 20);
	switch (t->_Status)
	{
	case KILL_BY_SELF:
		printf("失败了菜鸡,游戏结束\n");
		break;
	case END_NORMAL:
		printf(" 主动退出 ,游戏结束\n");
		break;
	}
}

Start.c

#define _CRT_SECURE_NO_WARNINGS 1

#include"Tetris.h"

void env()
{
	setlocale(LC_ALL, "");//设置本地环境

	system("color 70");//设置颜色

	srand((unsigned int)time(NULL));//设置伪随机值

}

void game()
{
	Tetris t = { 0 };

	GameInit(&t);//游戏初始化

	GameRun(&t);//游戏运行

	GameEnd(&t);//游戏维护
}

int main()
{
	env();//设置环境

	game();//开始游戏

	SetPos(30, 30);//为了不把我的游戏界面覆盖

	return 0;
}

四、结果展示

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值