C语言俄罗斯方块开发日志
11.27
准备工作
在电子书《深入体验C语言项目开发》中看到俄罗斯方块的开发,受到启发,想要挑战自己开发俄罗斯方块小游戏。
了解到一个图形库“easyx.h”,计划借助此库完成开发。
下载并安装好库之后,简单了解了一下easyx这个库的功能。
尝试了基础的简单画图.
11.28
获取按键操作
在我使用_getch()函数模拟获取按键的过程中,发现一个巨大的问题:
绘图的界面和控制台不是同一个窗口,我只能在控制台获取按键,因此,我在获取按键之前,必须先让鼠标聚焦在控制台上,不能隐藏掉控制台界面,只显示绘画的图形界面,相当的不优雅。
在B站上观摩他人实现俄罗斯方块的过程,发现,可以使用easyx库中自带的ExMessage完成获取按键的过程。
根据示例代码,初次尝试,无法正常获取键盘输入,但是鼠标操作正常,不知道原因,重新观看B站视频教程的实现过程。
发现问题,问题原因:消息类型判错错误。
cExMessage key; while (1) { key = getmessage(EX_KEY); switch(key.vkcode) // 消息类型应为 vkcode(virtual keyboard),示例为 message误认为是全局消息, { ... } }重新学习easyx库相关内容
解决了按键点一次运行执行多次的问题,自认为前期工作准备完成,能够绘图,能够获取按键信息,接下来就是游戏思路的设计以及代码的实现。
完成了绘画游戏界面
11.29
遇到问题
怎么旋转方块
不同的文件之中怎么共享变量
二维数组的相关操作不熟悉
12.13
从头开始。
流程分析
绘制流程图
[图片缺失:image-20241213015509454]
相关代码分析:
数据结构:
int x=10,int y=18 游戏格数
int map[x][y] 二维坐标地图
int curBlock[4][4] 当前方块数据
int nextBlock[4][4] 下一个方块数据
int blocks[7][4][4] 所有方块初始数据
int blockID 方块种类判断[0-6]
int curPos[x][y] 当前方块的坐标
int speed 游戏刷新间隔|方块下落速度
函数分析:
void draw_gui(void) 绘画gui
void draw_game_area(void) 绘画游戏区域
void draw_next_area(void) 绘画下一个方块区域
void draw_info_area(void) 绘画游戏信息区域
void draw_block(int** curBlock, int** curPos) 绘画新坐标下的方块
void clean_block(int** preBlock, int** prePos) 清除旧坐标下的方块
void game_ing(void) 游戏算法实现
int** block_generate(void) 生成一个新方块(给一个随机数,改变方块的初始方向),返回方块数据curBlock
int** block_falling(void) 方块下落,返回坐标curPos
int** block_change_shape(int** curBlock) 改变方块样式,返回方块数据curBlock
bool block_check_barrier(int** curPos) 检查是否碰壁,返回bool
void block_clean_line(void) 清除满行
2025.2.4
再再战俄罗斯方块
计划:
- 更改底层代码,C语言转C++.因此浏览一遍C++文档.面向对象内容加上一些常用的库.顺便复习编程语言
- 重新学习easyx绘图库.
- 一个一个小环节的写,调试.先让一部分跑起来,再丰富内容.
第一步:让单方块动起来
#include <iostream>
#include <easyx.h>
#include <graphics.h>
using namespace std;
int main()
{
initgraph(400, 700);
for (int i = 1; i <= 10; i++)
{
clearrectangle(10, (i - 1) * 20, 30, i * 20);
fillrectangle(10, i * 20, 30, (i + 1) * 20);
Sleep(1000);
}
system("pause");
return 0;
}第二步:让完整方块动起来
#include <iostream>
#include <easyx.h>
#include <graphics.h>
using namespace std;
const int BLOCK_SIZE = 30; // 一个方块的边长大小
const int X = 10; // 横向方块数量
const int Y = 18; // 纵向方块数量
const int BLOCK_TYPES = 20; // 不同方块类型的个数
class Tetris
{
public:
int block_id = 2;
int block_total_type = 20;
int block_data[BLOCK_TYPES][16] = {
// 仅仅给出三个作为示例,后面再补充方块
{
0, 0, 0, 0,
0, 1, 1, 0,
0, 1, 1, 0,
0, 0, 0, 0
},
{
0, 0, 0, 0,
1, 1, 1, 1,
0, 0, 0, 0,
0, 0, 0, 0
},
{
0, 1, 0, 0,
0, 1, 0, 0,
0, 1, 0, 0,
0, 1, 0, 0
}
};
int map[X][Y] = { 0 }; // 地图方块数据,0: 未被占用, 1: 被占用
int block_pos[2] = { 0 }; // 4*4方块在地图中的坐标 [0]: x ... [1]: y
void init(void);
void set_block_pos_to_start(int block_id, int* block_pos);
void block_down(int block_id, int* block_pos);
};
int main()
{
Tetris game;
game.init();
game.set_block_pos_to_start(game.block_id, game.block_pos);
while (true)
{
game.block_down(game.block_id, game.block_pos);
Sleep(1000);
}
Sleep(4000);
return 0;
}
// 初始化游戏界面
void Tetris::init(void)
{
// 初始化桌布大小
initgraph((X + 2) * BLOCK_SIZE, (Y + 1) * BLOCK_SIZE);
// 画边界障碍
setfillcolor(RGB(56, 56, 56)); // 设置边界为灰色
POINT pts_barrier[] = { {0.5 * BLOCK_SIZE,0}, {BLOCK_SIZE, 0},
{BLOCK_SIZE, Y * BLOCK_SIZE}, {(X + 1) * BLOCK_SIZE,Y * BLOCK_SIZE},
{(X + 1) * BLOCK_SIZE,0}, {(X + 1.5) * BLOCK_SIZE,0},
{(X + 1.5) * BLOCK_SIZE,(Y + 0.5) * BLOCK_SIZE}, {0.5 * BLOCK_SIZE,(Y + 0.5) * BLOCK_SIZE} };
fillpolygon(pts_barrier, 8); // 起点开始8个顺时针顶点坐标 (x, y)
}
// 刷新方块的时候,获取方块的起始位置,使下一刻就能显示出方块
void Tetris::set_block_pos_to_start(int block_id, int* block_pos)
{
block_pos[0] = 3;
block_pos[1] = -4;
bool is_block = false;
for (int row = 3; row >= 0; row--)
{
if (is_block)
{
break;
}
for (int col = 0; col <= 3; col++)
{
if (block_data[block_id][row * 4 + col] == 1)
{
if (is_block)
{
break;
}
block_pos[1] += (3 - row);
is_block = true;
}
}
}
}
void Tetris::block_down(int block_id, int* block_pos)
{
setorigin(BLOCK_SIZE, 0);
for (int row = 0; row < 4; row++)
{
for (int col = 0; col < 4; col++)
{
if (block_data[block_id][row * 4 + col] == 1)
{
clearrectangle((block_pos[0] + col) * BLOCK_SIZE,
(block_pos[1] + row) * BLOCK_SIZE,
(block_pos[0] + col + 1) * BLOCK_SIZE,
(block_pos[1] + row + 1) * BLOCK_SIZE);
}
}
}
block_pos[1]++;
for (int row = 0; row < 4; row++)
{
for (int col = 0; col < 4; col++)
{
if (block_data[block_id][row * 4 + col] == 1)
{
fillrectangle((block_pos[0] + col) * BLOCK_SIZE,
(block_pos[1] + row) * BLOCK_SIZE,
(block_pos[0] + col + 1) * BLOCK_SIZE,
(block_pos[1] + row + 1) * BLOCK_SIZE);
}
}
}
}2.5
第三步:让多个方块堆叠起来
#include <iostream>
#include <easyx.h>
#include <graphics.h>
using namespace std;
const int BLOCK_SIZE = 30; // 一个方块的边长大小
const int X = 10; // 横向方块数量
const int Y = 18; // 纵向方块数量
const int BLOCK_TYPES = 20; // 不同方块类型的个数 共19种
int speed = 100;
class Tetris
{
public:
int block_id = rand() % 19;
int block_total_type = 20;
int block_data[BLOCK_TYPES][16] = {
// 仅仅给出三个作为示例,后面再补充方块
{ // 1
0, 0, 0, 0,
0, 1, 1, 0,
0, 1, 1, 0,
0, 0, 0, 0
},
{ // 2
0, 0, 0, 0,
1, 1, 1, 1,
0, 0, 0, 0,
0, 0, 0, 0
},
{ // 3
0, 1, 0, 0,
0, 1, 0, 0,
0, 1, 0, 0,
0, 1, 0, 0
},
{ // 4
0, 0, 0, 0,
0, 1, 0, 0,
1, 1, 1, 0,
0, 0, 0, 0
},
{ // 5
0, 0, 0, 0,
0, 1, 0, 0,
0, 1, 1, 0,
0, 1, 0, 0
},
{ // 6
0, 0, 0, 0,
0, 0, 0, 0,
1, 1, 1, 0,
0, 1, 0, 0
},
{ // 7
0, 0, 0, 0,
0, 1, 0, 0,
1, 1, 0, 0,
0, 1, 0, 0
},
{ // 8
0, 0, 0, 0,
0, 0, 0, 0,
0, 1, 1, 0,
1, 1, 0, 0
},
{ // 9
0, 0, 0, 0,
1, 0, 0, 0,
1, 1, 0, 0,
0, 1, 0, 0
},
{ // 10
0, 0, 0, 0,
0, 0, 0, 0,
1, 1, 0, 0,
0, 1, 1, 0
},
{ // 11
0, 0, 0, 0,
0, 1, 0, 0,
1, 1, 0, 0,
1, 0, 0, 0
},
{ // 12
0, 0, 0, 0,
0, 1, 0, 0,
0, 1, 1, 1,
0, 0, 0, 0
},
{ // 13
0, 0, 0, 0,
0, 1, 1, 0,
0, 1, 0, 0,
0, 1, 0, 0
},
{ // 14
0, 0, 0, 0,
0, 1, 1, 1,
0, 0, 0, 1,
0, 0, 0, 0
},
{ // 15
0, 0, 0, 0,
0, 0, 1, 0,
0, 0, 1, 0,
0, 1, 1, 0
},
{ // 16
0, 0, 0, 0,
0, 0, 0, 1,
0, 1, 1, 1,
0, 0, 0, 0
},
{ // 17
0, 0, 0, 0,
0, 0, 1, 0,
0, 0, 1, 0,
0, 0, 1, 1
},
{ // 18
0, 0, 0, 0,
0, 1, 1, 1,
0, 1, 0, 0,
0, 0, 0, 0
},
{ // 19
0, 0, 0, 0,
0, 0, 1, 1,
0, 0, 0, 1,
0, 0, 0, 1
}
};
int map[X][Y] = { 0 }; // 地图方块数据,0: 未被占用, 1: 被占用
int block_pos[2] = { 0 }; // 4*4方块在地图中的坐标 [0]: x ... [1]: y
void init(void);
void set_block_pos_to_start(void);
void block_down(void);
void update_map(void);
bool is_stop(void);
bool is_end_game(void);
int get_random_block(void);
};
int main()
{
srand(time(0));
Tetris game;
game.init();
game.block_id = game.get_random_block();
game.set_block_pos_to_start();
while (!game.is_end_game())
{
while (!game.is_stop())
{
game.block_down();
Sleep(speed);
}
// 地图上更新方块数据
game.update_map();
// 新一轮
game.block_id = game.get_random_block();
game.set_block_pos_to_start();
Sleep(speed);
}
cout << "!!!!!!!!!!!!!!!!!!!!!!!" << endl << "Game ends! 3s later auto close!" << endl;
Sleep(3000);
return 0;
}
// 初始化游戏界面
void Tetris::init(void)
{
// 初始化桌布大小
initgraph((X + 2) * BLOCK_SIZE, (Y + 1) * BLOCK_SIZE, EX_SHOWCONSOLE);
// 画边界障碍
setfillcolor(RGB(56, 56, 56)); // 设置边界为灰色
POINT pts_barrier[] = { {0.5 * BLOCK_SIZE,0}, {BLOCK_SIZE, 0},
{BLOCK_SIZE, Y * BLOCK_SIZE}, {(X + 1) * BLOCK_SIZE,Y * BLOCK_SIZE},
{(X + 1) * BLOCK_SIZE,0}, {(X + 1.5) * BLOCK_SIZE,0},
{(X + 1.5) * BLOCK_SIZE,(Y + 0.5) * BLOCK_SIZE}, {0.5 * BLOCK_SIZE,(Y + 0.5) * BLOCK_SIZE} };
fillpolygon(pts_barrier, 8); // 起点开始8个顺时针顶点坐标 (x, y)
cout << "Initialized game successfully!" << endl;
}
// 刷新方块的时候,获取方块的起始位置,使下一刻就能显示出方块
void Tetris::set_block_pos_to_start(void)
{
// 初始化起点
this->block_pos[0] = 3;
this->block_pos[1] = -4;
// 往下微调Y坐标
bool is_block = false;
for (int row = 3, col = 0; row >= 0; col++)
{
if (is_block)
{
break;
}
if (col >= 4)
{
row--;
col = 0;
}
// 获取最底层实体方块的Y轴
if (1 == this->block_data[this->block_id][row * 4 + col])
{
this->block_pos[1] += 3 - row; // 往下移动的格数[0, 3]使下一刻就能显示出方块
is_block = true;
printf("Block_id: %d\tis setted to start spot!\tPos: [%d, %d]\n",
this->block_id, this->block_pos[0], this->block_pos[1]);
}
}
}
// 方块下落一格
void Tetris::block_down(void)
{
setorigin(BLOCK_SIZE, 0); // 设置 原点O 的位置
for (int row = 3, col = 0; row >= 0; col++) // 从下往上绘画
{
if (col >= 4)
{
row--;
col = 0;
}
if (row < 0)
{
break;
}
if (1 == this->block_data[this->block_id][row * 4 + col])
{
// 清除当前位置的方块
clearrectangle((this->block_pos[0] + col) * BLOCK_SIZE,
(this->block_pos[1] + row) * BLOCK_SIZE,
(this->block_pos[0] + col + 1) * BLOCK_SIZE,
(this->block_pos[1] + row + 1) * BLOCK_SIZE);
// 绘画下面位置的方块
fillrectangle((this->block_pos[0] + col) * BLOCK_SIZE,
(this->block_pos[1] + row + 1) * BLOCK_SIZE,
(this->block_pos[0] + col + 1) * BLOCK_SIZE,
(this->block_pos[1] + row + 2) * BLOCK_SIZE);
}
}
this->block_pos[1]++;
printf("Block_id: %d\tis downing!\tPos: [%d, %d]\n",
this->block_id, this->block_pos[0], this->block_pos[1]);
}
// 更新地图方块数据,方块停止运动后占用地图
void Tetris::update_map(void)
{
for (int row = 3, col = 0; row >= 0; col++)
{
if (col >= 4)
{
col = 0;
row--;
}
if (row < 0)
{
break;
}
this->map[this->block_pos[0] + col][this->block_pos[1] + row] += this->block_data[this->block_id][row * 4 + col];
}
}
// 方块是否停止运动(到达最底层,碰到其他方块)
bool Tetris::is_stop(void)
{
for (int row = 3, col = 0; row >= 0; col++)
{
if (col >= 4)
{
col = 0;
row--;
}
// 最后一个循环为 row=-1, col=0 依旧会执行会出bug
if (row < 0)
{
return false;
}
// 遍历到实体方块
if (1 == this->block_data[this->block_id][row * 4 + col])
{
// 方块到达最底层
if (Y - 1 <= this->block_pos[1] + row)
{ // 坐标是从0开始计数,同数组,当为 Y-1
printf("Block_id: %d\tarrived at the buttom!\tPos: [%d, %d]\n",
this->block_id, this->block_pos[0], this->block_pos[1]);
return true;
}
// 方块下面碰到其他方块
if (1 == this->map[this->block_pos[0] + col][this->block_pos[1] + row + 1])
{
printf("Block_id: %d\thits another block!\tPos: [%d, %d]\n",
this->block_id, this->block_pos[0], this->block_pos[1]);
return true;
}
}
}
}
// 是否结束游戏,方块堆满了
bool Tetris::is_end_game(void)
{
for (int row = 3, col = 0; row >= 0; col++)
{
if (col >= 4)
{
col = 0;
row--;
}
if (row < 0)
{
return false;
}
if (1 == this->map[this->block_pos[0] + col][0] && -1 == this->block_pos[1] + row)
{ // 第0层对应的方块被占用即结束
return true;
}
}
}
// 随机生成方块ID用于随机方块
int Tetris::get_random_block(void)
{
return rand() % 19;
}问题:一刻方块只能移动一次,不能一次移动两下,Sleep时间太长了,无法同时处理间隔内有两次移动。每下降一格才能平移一格,最大平移距离与最大垂直距离成正比。
2.27
正式版1.0
#include <iostream>
#include <easyx.h>
#include <time.h>
using namespace std;
#define X first
#define Y second
class Tetris
{
public:
Tetris(int blocksX, int blocksY, int blockSize)
{
block.blocksX = blocksX;
block.blocksY = blocksY;
block.size = blockSize;
UpdateBlockData();
render.blockSpace = blockSize / 11;
render.barrierPadding = blockSize * 2 / 7;
render.barrierEllipse = 10;
render.windowPadding = blockSize * 3 / 5;
UpdateDrawLength();
}
IMAGE picBlocks[7];
int colorId = 0;
int map[20][20];
int FPS = 1000 / 60;
int cntFPS = 0;
clock_t startTime;
clock_t endTime;
clock_t costTime;
struct Render
{
pair<int, int> blockStart;
int blockSpace;
int windowPadding;
int windowWidth;
int windowHeight;
int barrierPadding;
int barrierWidth;
int barrierHeight;
int barrierEllipse;
int spotBarrierLeft;
int spotBarrierTop;
int spotBlockLeft;
int spotBlockTop;
} render;
struct Block
{
int blocksX = 10, blocksY = 18;
int size = 35;
int id = 1, nextId = 2;
pair<int, int> pos[5];
pair<int, int> data[20][5];
} block;
void SetBlockData(int blockId, int blockCnt, int posX, int posY);
void UpdateBlockData(void);
void UpdateDrawLength(void);
void UpdateMap(void);
void DrawWindow(void);
void DrawBackgound(void);
void DrawBarrier(void);
void DrawMap(void);
void DrawBlock(int posX, int posY);
void DrawBlocks(void);
void DrawGameEnd(void);
void EventBlockMove(int moveX, int moveY);
void EventBlockGenerate(void);
void EventBlockChange(void);
void EventKeyDown(void);
void EventCleanFullLine(void);
bool CheckBarrierTop(void);
bool CheckBarrierButtom(void);
bool CheckBarrierLeft(void);
bool CheckBarrierRight(void);
bool CheckGameEnd(void);
bool CheckBlockValid(pair<int, int>* pos);
bool CheckBlockDownBlock(void);
bool CheckBlockLeftBlock(void);
bool CheckBlockRightBlock(void);
};
int main()
{
int blocksX = 10, blocksY = 18, blockSize = 35;
Tetris game(blocksX, blocksY, blockSize);
loadimage(&game.picBlocks[0], _T("assets/brown.png"), blockSize, blockSize);
loadimage(&game.picBlocks[1], _T("assets/deep_blue.png"), blockSize, blockSize);
loadimage(&game.picBlocks[2], _T("assets/green.png"), blockSize, blockSize);
loadimage(&game.picBlocks[3], _T("assets/purple.png"), blockSize, blockSize);
loadimage(&game.picBlocks[4], _T("assets/red.png"), blockSize, blockSize);
loadimage(&game.picBlocks[5], _T("assets/shallow_blue.png"), blockSize, blockSize);
loadimage(&game.picBlocks[6], _T("assets/yellow.png"), blockSize, blockSize);
game.EventBlockGenerate();
game.EventBlockGenerate();
game.DrawWindow();
while (true)
{
game.startTime = clock();
BeginBatchDraw();
cleardevice();
game.EventCleanFullLine();
game.EventKeyDown();
game.DrawBackgound();
game.DrawBarrier();
if (game.CheckBarrierTop()) break;
if (game.CheckBlockDownBlock() || game.CheckBarrierButtom())
{
game.UpdateMap();
game.EventBlockGenerate();
}
if (game.cntFPS >= 15)
{
game.EventBlockMove(0, 1);
game.cntFPS = 0;
}
game.DrawMap();
game.DrawBlocks();
EndBatchDraw();
if (game.CheckGameEnd()) break;
game.cntFPS++;
game.endTime = clock();
game.costTime = (game.endTime - game.startTime) % game.FPS;
if (game.costTime < game.FPS)
{
Sleep(game.FPS - game.costTime);
}
}
game.DrawGameEnd();
Sleep(2000);
return 0;
}
void Tetris::SetBlockData(int blockId, int blockCnt, int posX, int posY)
{
block.data[blockId][blockCnt].X = posX;
block.data[blockId][blockCnt].Y = posY;
}
void Tetris::UpdateBlockData(void)
{
int centerPos = block.blocksX / 2;
SetBlockData(1, 1, centerPos, 0);
SetBlockData(1, 2, centerPos + 1, 0);
SetBlockData(1, 3, centerPos, -1);
SetBlockData(1, 4, centerPos + 1, -1);
SetBlockData(2, 1, centerPos - 1, 0);
SetBlockData(2, 2, centerPos, 0);
SetBlockData(2, 3, centerPos + 1, 0);
SetBlockData(2, 4, centerPos + 2, 0);
SetBlockData(3, 1, centerPos, 0);
SetBlockData(3, 2, centerPos, -1);
SetBlockData(3, 3, centerPos, -2);
SetBlockData(3, 4, centerPos, -3);
SetBlockData(4, 1, centerPos, 0);
SetBlockData(4, 2, centerPos + 1, 0);
SetBlockData(4, 3, centerPos + 1, -1);
SetBlockData(4, 4, centerPos + 2, -1);
SetBlockData(5, 1, centerPos + 1, 0);
SetBlockData(5, 2, centerPos, -1);
SetBlockData(5, 3, centerPos + 1, -1);
SetBlockData(5, 4, centerPos, -2);
SetBlockData(6, 1, centerPos, 0);
SetBlockData(6, 2, centerPos + 1, 0);
SetBlockData(6, 3, centerPos - 1, -1);
SetBlockData(6, 4, centerPos, -1);
SetBlockData(7, 1, centerPos, 0);
SetBlockData(7, 2, centerPos, -1);
SetBlockData(7, 3, centerPos + 1, -1);
SetBlockData(7, 4, centerPos + 1, -2);
SetBlockData(8, 1, centerPos - 1, 0);
SetBlockData(8, 2, centerPos, 0);
SetBlockData(8, 3, centerPos + 1, 0);
SetBlockData(8, 4, centerPos, -1);
SetBlockData(9, 1, centerPos, 0);
SetBlockData(9, 2, centerPos, -1);
SetBlockData(9, 3, centerPos + 1, -1);
SetBlockData(9, 4, centerPos, -2);
SetBlockData(10, 1, centerPos, 0);
SetBlockData(10, 2, centerPos - 1, -1);
SetBlockData(10, 3, centerPos, -1);
SetBlockData(10, 4, centerPos + 1, -1);
SetBlockData(11, 1, centerPos, 0);
SetBlockData(11, 2, centerPos - 1, -1);
SetBlockData(11, 3, centerPos, -1);
SetBlockData(11, 4, centerPos, -2);
SetBlockData(12, 1, centerPos, 0);
SetBlockData(12, 2, centerPos + 1, 0);
SetBlockData(12, 3, centerPos + 2, 0);
SetBlockData(12, 4, centerPos, -1);
SetBlockData(13, 1, centerPos, 0);
SetBlockData(13, 2, centerPos, -1);
SetBlockData(13, 3, centerPos, -2);
SetBlockData(13, 4, centerPos + 1, -2);
SetBlockData(14, 1, centerPos + 2, 0);
SetBlockData(14, 2, centerPos, -1);
SetBlockData(14, 3, centerPos + 1, -1);
SetBlockData(14, 4, centerPos + 2, -1);
SetBlockData(15, 1, centerPos, 0);
SetBlockData(15, 2, centerPos + 1, 0);
SetBlockData(15, 3, centerPos + 1, -1);
SetBlockData(15, 4, centerPos + 1, -2);
SetBlockData(16, 1, centerPos, 0);
SetBlockData(16, 2, centerPos + 1, 0);
SetBlockData(16, 3, centerPos + 2, 0);
SetBlockData(16, 4, centerPos + 2, -1);
SetBlockData(17, 1, centerPos, 0);
SetBlockData(17, 2, centerPos + 1, 0);
SetBlockData(17, 3, centerPos, -1);
SetBlockData(17, 4, centerPos, -2);
SetBlockData(18, 1, centerPos, 0);
SetBlockData(18, 2, centerPos, -1);
SetBlockData(18, 3, centerPos + 1, -1);
SetBlockData(18, 4, centerPos + 2, -1);
SetBlockData(19, 1, centerPos + 1, 0);
SetBlockData(19, 2, centerPos + 1, -1);
SetBlockData(19, 3, centerPos, -2);
SetBlockData(19, 4, centerPos + 1, -2);
}
void Tetris::UpdateDrawLength(void)
{
render.barrierWidth = render.barrierPadding * 2 + block.blocksX * block.size+ (block.blocksX - 1) * render.blockSpace;
render.barrierHeight = render.barrierPadding * 2 + block.blocksY * block.size + (block.blocksY - 1) * render.blockSpace;
render.windowWidth = render.windowPadding * 2 + render.barrierWidth + render.blockSpace + 0;
render.windowHeight = render.windowPadding * 2 + render.barrierHeight;
render.spotBarrierLeft = render.windowPadding;
render.spotBarrierTop = render.spotBarrierLeft;
render.spotBlockLeft = render.windowPadding + render.barrierPadding;
render.spotBlockTop = render.spotBlockLeft;
}
void Tetris::UpdateMap(void)
{
for (int cnt = 1; cnt <= 4; cnt++)
{
map[block.pos[cnt].X][block.pos[cnt].Y] = 1;
}
}
void Tetris::DrawWindow()
{
initgraph(render.windowWidth, render.windowHeight);
}
void Tetris::DrawBackgound(void)
{
IMAGE bk;
loadimage(&bk, _T("assets/gameBK.png"), 1239, 850);
putimage(0, 0, &bk);
}
void Tetris::DrawBarrier(void)
{
// draw bk
setfillcolor(RGB(46, 48, 54));
setlinecolor(BLACK);
fillroundrect(render.spotBarrierLeft, render.spotBarrierTop,
render.spotBarrierLeft + render.barrierWidth, render.spotBarrierTop + render.barrierHeight,
render.barrierEllipse, render.barrierEllipse);
// draw blocks
setfillcolor(RGB(54, 57, 63));
setlinecolor(RGB(54, 57, 63));
for (int x = 1, y = 1; ; y++)
{
if (y > block.blocksY) y = 1, x++;
if (x > block.blocksX) break;
int right = render.spotBlockLeft + x * block.size + (x - 1) * render.blockSpace;
int buttom = render.spotBlockTop + y * block.size + (y - 1) * render.blockSpace;
fillroundrect(right - block.size, buttom - block.size, right, buttom,
render.barrierEllipse / 2, render.barrierEllipse / 2);
}
}
void Tetris::DrawMap(void)
{
for (int x = 1, y= 1; ; y++)
{
if (y > block.blocksY) y = 1, x++;
if (x > block.blocksX) break;
if (map[x][y] == 1) DrawBlock(x, y);
}
}
void Tetris::DrawBlock(int posX, int posY)
{
int left = render.spotBlockLeft + (posX - 1) * (block.size + render.blockSpace);
int top = render.spotBlockTop + (posY - 1) * (block.size + render.blockSpace);
putimage(left, top, &picBlocks[colorId]);
}
void Tetris::DrawBlocks(void)
{
for (int cnt = 1; cnt <= 4; cnt++)
{
if (block.pos[cnt].Y >= 1) DrawBlock(block.pos[cnt].X, block.pos[cnt].Y);
}
}
void Tetris::DrawGameEnd(void)
{
settextcolor(GREEN);
setbkmode(TRANSPARENT);
settextstyle(70, 0, _T("微软雅黑"));
LOGFONT f;
gettextstyle(&f);
f.lfQuality = ANTIALIASED_QUALITY;
f.lfWeight = FW_BLACK;
//
RECT r = { 0, 0, render.windowWidth, render.windowHeight };
drawtext(_T("Game over!"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
}
void Tetris::EventBlockMove(int moveX, int moveY)
{
for (int cnt = 1; cnt <= 4; cnt++)
{
block.pos[cnt].X += moveX;
block.pos[cnt].Y += moveY;
}
}
void Tetris::EventBlockGenerate(void)
{
srand(int(time(0)));
block.id = block.nextId;
block.nextId = rand() % 19 + 1;
colorId = rand() % 7;
for (int cnt = 1; cnt <= 4; cnt++)
{
int x = block.data[block.id][cnt].X;
int y = block.data[block.id][cnt].Y;
block.pos[cnt] = { x, y };
}
}
void Tetris::EventBlockChange(void)
{
int newBlockId;
int moveBlocksX;
int moveBlocksY;
pair<int, int> newPos[5];
// calc down distance
moveBlocksX = block.pos[1].X - block.data[block.id][1].X;
moveBlocksY = block.pos[1].Y - block.data[block.id][1].Y;
// next id
switch (block.id)
{
case 2:
case 4:
case 6:
case 8:
case 9:
case 10:
case 12:
case 13:
case 14:
case 16:
case 17:
case 18:
newBlockId = block.id + 1;
break;
case 3:
case 5:
case 7:
newBlockId = block.id - 1;
break;
case 11:
case 15:
case 19:
newBlockId = block.id - 3;
break;
default:
newBlockId = block.id;
}
// calc newPos
for (int cnt = 1; cnt <= 4; cnt++)
{
newPos[cnt].X = block.data[newBlockId][cnt].X + moveBlocksX;
newPos[cnt].Y = block.data[newBlockId][cnt].Y + moveBlocksY;
}
// check newPos valid
if (CheckBlockValid(newPos))
{// update pos
block.id = newBlockId;
for (int cnt = 1; cnt <= 4; cnt++)
{
block.pos[cnt].X = newPos[cnt].X;
block.pos[cnt].Y = newPos[cnt].Y;
}
}
}
void Tetris::EventKeyDown(void)
{
ExMessage key;
do
{
if (!peekmessage(&key, EX_KEY)) break;
if (key.message != WM_KEYDOWN) break;
if (key.vkcode == VK_LEFT && !CheckBlockLeftBlock() && !CheckBarrierLeft())
EventBlockMove(-1, 0);
else if (key.vkcode == VK_RIGHT && !CheckBlockRightBlock() && !CheckBarrierRight())
EventBlockMove(1, 0);
else if (key.vkcode == VK_DOWN)
{
if (!CheckBlockDownBlock() || !CheckBarrierButtom())
EventBlockMove(0, 1);
}
else if (key.vkcode == VK_UP)
EventBlockChange();
else cout << "Unkonwn key down" << endl;
flushmessage(-1);
} while (false);
}
void Tetris::EventCleanFullLine(void)
{
for (int y = block.blocksY; y >= 1; y--)
{
bool isFull = true;
for (int x = 1; x <= block.blocksX; x++)
{
if (map[x][y] != 1)
{
isFull = false;
break;
}
}
if (isFull)
{
for (int yy = y; yy >= 1; yy--)
{
for (int xx = 1; xx <= block.blocksX; xx++)
{
map[xx][yy] = map[xx][yy - 1];
}
}
y++;
}
}
}
bool Tetris::CheckBarrierTop(void)
{
for (int cnt = 1; cnt <= 4; cnt++)
{
if (block.pos[cnt].Y == 0 &&
map[block.pos[cnt].X][1] == 1)
return true;
}
return false;
}
bool Tetris::CheckBarrierButtom(void)
{
for (int cnt = 1; cnt <= 4; cnt++)
{
if (block.pos[cnt].Y >= block.blocksY) return true;
}
return false;
}
bool Tetris::CheckBarrierLeft(void)
{
for (int cnt = 1; cnt <= 4; cnt++)
{
if (block.pos[cnt].X <= 1) return true;
}
return false;
}
bool Tetris::CheckBarrierRight(void)
{
for (int cnt = 1; cnt <= 4; cnt++)
{
if (block.pos[cnt].X >= block.blocksX) return true;
}
return false;
}
bool Tetris::CheckGameEnd(void)
{
for (int x = 1; x <= block.blocksX; x++)
{
if (map[x][0] == 1) return true;
}
return false;
}
bool Tetris::CheckBlockValid(pair<int, int>* pos)
{
// check block hit block
for (int cnt = 1; cnt <= 4; cnt++)
{
int x = pos[cnt].X;
int y = pos[cnt].Y;
if (x < 1 || x > block.blocksX) return false;
if (y < 1 || y > block.blocksY) return false;
if (map[x][y] == 1) return false;
}
return true;
}
bool Tetris::CheckBlockDownBlock(void)
{
for (int cnt = 1; cnt <= 4; cnt++)
{
if (map[block.pos[cnt].X][block.pos[cnt].Y + 1] == 1) return true;
}
return false;
}
bool Tetris::CheckBlockLeftBlock(void)
{
for (int cnt = 1; cnt <= 4; cnt++)
{
int x = block.pos[cnt].X - 1;
int y = block.pos[cnt].Y;
if (map[x][y] == 1) return true;
}
return false;
}
bool Tetris::CheckBlockRightBlock(void)
{
for (int cnt = 1; cnt <= 4; cnt++)
{
int x = block.pos[cnt].X + 1;
int y = block.pos[cnt].Y;
if (map[x][y] == 1) return true;
}
return false;
}