You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于ncurses开发俄罗斯方块:如何消除Tetromino自碰撞检测问题

俄罗斯方块碰撞检测避免自碰撞的解决方案

问题背景

用ncurses开发俄罗斯方块时,编写canPlaceTetromino方法判断Tetromino能否移动到新位置时,出现误检测自身碰撞的问题。该方法需要判断方块是否与边界或已放置的其他方块碰撞,但当前逻辑会把自身在colorbuffer中的单元当成障碍物。

相关信息:

  • Tetromino数据来自shapes4×4数组,非0值为方块单元,0为空位
  • gameBoardColours(colorbuffer)包含已放置的方块和当前正在下落的Tetromino
  • currentBrick是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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 22:23:11