一、前言
代码繁杂,很多地方可以优化,用的都是一些早期学习的知识,主要是思路分享。
二、思路
表格
初始各个格子状态为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;
}