悠悠楠杉
用C++实现俄罗斯方块:二维数组与键盘控制的经典复刻
二、核心数据结构设计
1. 二维数组构建游戏地图
cpp
const int MAP_HEIGHT = 20;
const int MAP_WIDTH = 10;
int gameMap[MAP_HEIGHT][MAP_WIDTH] = {0}; // 0表示空,1-7表示不同方块
这个二维数组是整个游戏的"上帝视角",每个元素存储着对应格子的状态。初始化时全为0,当方块落到底部时,相应位置会更新为方块编号。
2. 方块对象的巧思
cpp
struct Tetromino {
int shape[4][4]; // 4x4旋转模板
int posX, posY; // 当前坐标
int type; // 方块类型
};
每种俄罗斯方块都有4种旋转形态,用4x4矩阵表示。例如T型方块:
cpp
int tShape[4][4] = {
{0,1,0,0},
{1,1,1,0},
{0,0,0,0},
{0,0,0,0}
};
三、关键算法实现
1. 旋转算法:矩阵变换的艺术
cpp
void rotate(Tetromino &block) {
int temp[4][4];
for(int i=0; i<4; ++i)
for(int j=0; j<4; ++j)
temp[j][3-i] = block.shape[i][j];
if(!checkCollision(block)) // 碰撞检测
memcpy(block.shape, temp, sizeof(temp));
}
这个看似简单的转置加镜像操作,实则暗藏玄机。我花了3个小时调试才发现边缘旋转时的数组越界问题——这也正是游戏开发的魅力所在。
2. 碰撞检测:游戏规则的守护者
cpp
bool checkCollision(const Tetromino &block) {
for(int i=0; i<4; ++i) {
for(int j=0; j<4; ++j) {
if(block.shape[i][j]) {
int x = block.posX + j;
int y = block.posY + i;
if(x<0 || x>=MAP_WIDTH || y>=MAP_HEIGHT || gameMap[y][x])
return true;
}
}
}
return false;
}
四、键盘控制实现
1. Windows平台下的_getch()方案
cpp
include <conio.h>
void handleInput() {
if(kbhit()) {
switch(getch()) {
case 'a': moveLeft(); break;
case 'd': moveRight(); break;
case 's': moveDown(); break;
case 'w': rotate(); break;
case ' ': hardDrop(); break;
}
}
}
2. 移动逻辑的防抖处理
在实际测试中,发现连续按键会导致方块"飞走"。通过添加计时器控制,确保每次移动至少有100ms间隔:
cpp
clock_t lastMove = 0;
void moveLeft() {
if(clock() - lastMove > 100) {
currentBlock.posX--;
if(checkCollision(currentBlock))
currentBlock.posX++;
lastMove = clock();
}
}
五、游戏主循环的架构设计
cpp
while(!gameOver) {
drawMap(); // 渲染地图
handleInput(); // 处理输入
updateGame(); // 更新逻辑
Sleep(50); // 控制帧率
}
这个看似简单的循环却需要精细调校。经过多次测试,将帧率控制在20FPS(50ms延迟)既能保证流畅度,又不会让方块下落过快。
六、开发中的经验教训
内存越界的幽灵:在实现消行功能时,忘记处理数组上边界导致游戏崩溃。解决方法是在所有数组访问前添加边界检查。
方块卡墙问题:旋转时如果紧贴墙壁会导致穿模。最终解决方案是先尝试水平移动再旋转。
随机数陷阱:使用rand()直接生成方块导致连续出现相同方块。改进方案:
cpp int nextType = rand() % 7; while(nextType == currentType) nextType = rand() % 7;
七、完整代码结构示意
├── main.cpp // 程序入口
├── game.h // 游戏逻辑声明
├── render.cpp // 控制台绘制模块
├── input.cpp // 输入处理模块
└── tetrominos.h // 方块数据定义
建议采用面向对象方式重构,将游戏状态、渲染逻辑、输入控制分离到不同类中,这会让后续添加新功能(如存档、特效)更容易。