基于ncurses开发俄罗斯方块:如何消除Tetromino自碰撞检测问题
俄罗斯方块碰撞检测避免自碰撞的解决方案
问题背景
用ncurses开发俄罗斯方块时,编写canPlaceTetromino方法判断Tetromino能否移动到新位置时,出现误检测自身碰撞的问题。该方法需要判断方块是否与边界或已放置的其他方块碰撞,但当前逻辑会把自身在colorbuffer中的单元当成障碍物。
相关信息:
- Tetromino数据来自
shapes4×4数组,非0值为方块单元,0为空位 gameBoardColours(colorbuffer)包含已放置的方块和当前正在下落的TetrominocurrentBrick是Brick类实例,记录了当前Tetromino每个单元在地图上的位置
现有代码
碰撞检测方法
bool canPlaceTetromino(int distanceX, int distanceY) { for (int y = 0; y < 4; ++y) { for (int x = 0; x < 4; ++x) { if (shapes[currentBrick.type][currentBrick.rotation][y][x] != 0) { int boardX = currentBrick.cellsPos[y][x].x + distanceX; int boardY = currentBrick.cellsPos[y][x].y + distanceY; if (boardX < 0 || boardX >= MAPWIDTH || boardY >= MAPHEIGHT || gameBoardColours[boardY][boardX] != 0) { return false; } } } } return true; }
Brick类定义
class Brick { public: Brick(); int type; int rotation; pos cellsPos[4][4]={}; void rotate(bool collided); void updateCellX(int difference); void updateCellY(int difference); };
解决方案
问题核心是gameBoardColours里包含了当前Tetromino的自身单元,检测时会把这些单元当成已放置的障碍物。以下是三种可行解决思路:
方案1:检测时排除自身单元
在判断gameBoardColours[boardY][boardX] != 0之前,先检查目标位置是否属于当前Tetromino的原始位置(未加偏移量的位置),如果是自身单元则跳过碰撞判断:
bool canPlaceTetromino(int distanceX, int distanceY) { for (int y = 0; y < 4; ++y) { for (int x = 0; x < 4; ++x) { if (shapes[currentBrick.type][currentBrick.rotation][y][x] != 0) { int originalX = currentBrick.cellsPos[y][x].x; int originalY = currentBrick.cellsPos[y][x].y; int boardX = originalX + distanceX; int boardY = originalY + distanceY; // 先判断边界碰撞 if (boardX < 0 || boardX >= MAPWIDTH || boardY >= MAPHEIGHT) { return false; } // 检查目标位置是否是当前方块的自身单元 bool isSelfCell = false; for (int sy = 0; sy < 4; ++sy) { for (int sx = 0; sx < 4; ++sx) { if (shapes[currentBrick.type][currentBrick.rotation][sy][sx] != 0) { if (currentBrick.cellsPos[sy][sx].x == boardX && currentBrick.cellsPos[sy][sx].y == boardY) { isSelfCell = true; break; } } } if (isSelfCell) break; } // 非自身单元且位置非空时判定碰撞 if (!isSelfCell && gameBoardColours[boardY][boardX] != 0) { return false; } } } } return true; }
方案2:分离当前方块与已放置方块的存储
更高效的方式是拆分存储结构:
- 用
fixedBoard二维数组仅存储已固定的落地方块 - 当前正在下落的Tetromino单独处理
这样碰撞检测时只需对比固定方块和边界,完全规避自碰撞:
// 假设fixedBoard是存储已固定方块的二维数组 bool canPlaceTetromino(int distanceX, int distanceY) { for (int y = 0; y < 4; ++y) { for (int x = 0; x < 4; ++x) { if (shapes[currentBrick.type][currentBrick.rotation][y][x] != 0) { int boardX = currentBrick.cellsPos[y][x].x + distanceX; int boardY = currentBrick.cellsPos[y][x].y + distanceY; if (boardX < 0 || boardX >= MAPWIDTH || boardY >= MAPHEIGHT || fixedBoard[boardY][boardX] != 0) { return false; } } } } return true; }
方案3:预存自身单元位置优化检测
在Brick类中添加方法获取当前方块的所有有效单元位置,检测时直接对比,减少嵌套循环的开销:
// 先给Brick类添加获取有效单元的方法 #include <vector> struct pos { int x; int y; }; std::vector<pos> Brick::getActiveCells() { std::vector<pos> cells; for (int y = 0; y < 4; ++y) { for (int x = 0; x < 4; ++x) { if (shapes[type][rotation][y][x] != 0) { cells.push_back(cellsPos[y][x]); } } } return cells; } // 修改碰撞检测方法 bool canPlaceTetromino(int distanceX, int distanceY) { auto selfCells = currentBrick.getActiveCells(); for (int y = 0; y < 4; ++y) { for (int x = 0; x < 4; ++x) { if (shapes[currentBrick.type][currentBrick.rotation][y][x] != 0) { int boardX = currentBrick.cellsPos[y][x].x + distanceX; int boardY = currentBrick.cellsPos[y][x].y + distanceY; if (boardX < 0 || boardX >= MAPWIDTH || boardY >= MAPHEIGHT) { return false; } // 快速判断是否为自身单元 bool isSelf = false; for (const auto& cell : selfCells) { if (cell.x == boardX && cell.y == boardY) { isSelf = true; break; } } if (!isSelf && gameBoardColours[boardY][boardX] != 0) { return false; } } } } return true; }
方案选择建议
- 快速修复选方案1或3,改动量小
- 长期维护项目推荐方案2,逻辑更清晰,后续扩展更方便
内容的提问来源于stack exchange,提问作者Fox1942
相关产品推荐
相关产品推荐

