针对推箱子这类题目,数据维护(即:如何优雅地处理“移动后原来格子变回什么”以及“移动到新格子变成什么”)确实是最大的痛点。

针对上机考试(求稳、求快、逻辑不炸),我认为最佳实践不是用复杂的 if-else 去判断,而是采用 “双层地图法” (Two-Layer Approach)

如果一定要坚持你偏好的 “单层地图法”(复合数值),也有对应的最佳辅助函数写法

我把这两种方案都列出来,你可以根据自己的习惯选择。


方案一:双层地图法(强烈推荐,逻辑最稳)

这是我最推荐的方案。它的核心思想是:把“不动的”和“会动的”分开存。

  • Layer 1 (Static): 存放 墙、目标点、空地。这些东西在游戏开始后永远不会变。
  • Layer 2 (Dynamic): 存放 人、箱子。这些东西会跑来跑去。

1. 数据结构

// Layer 1: 背景层 (只存 0-空地, 1-墙, 3-目标)
// 初始化后只读,永远不修改!
std::vector<std::vector<int>> staticMap; 
 
// Layer 2: 物体层 (存 0-无物体, 2-箱子, 4-人)
// 移动时只修改这个
std::vector<std::vector<int>> dynamicMap; 

2. 为什么这是最佳实践?

当你移动人物时,你完全不需要关心脚下有没有目标点

  • 人走开?直接把 dynamicMap 的该位置设为 0
  • 人进入?直接把 dynamicMap 的该位置设为 4
  • 原来的目标点呢? 它安安静静地躺在 staticMap 里,根本没人动它。

3. 代码实现 (Move Logic)

你看,代码里完全没有“恢复现场”的逻辑,清爽极了:

void MainWindow::movePlayer(int dx, int dy) {
    int nx = px + dx;
    int ny = py + dy;
 
    // 1. 检查墙 (看 staticMap)
    if (staticMap[nx][ny] == 1) return; 
 
    // 2. 检查箱子 (看 dynamicMap)
    if (dynamicMap[nx][ny] == 2) { 
        // 推箱子逻辑...
        // 只需要改 dynamicMap,不用管 staticMap
        dynamicMap[nnx][nny] = 2; // 箱子移到新位置
        dynamicMap[nx][ny] = 0;   // 旧位置箱子没了
    }
    
    // 3. 移动人
    dynamicMap[px][py] = 0; // 原位置清空 (不用管是不是目标点,staticMap记着呢)
    dynamicMap[nx][ny] = 4; // 新位置设定人
    px = nx; py = ny;
}

4. 绘图逻辑 (Paint Logic)

绘图时,把两层叠加起来看就行了:

// 先画 staticMap (背景)
if (staticMap[i][j] == 3) drawTarget();
else if (staticMap[i][j] == 1) drawWall();
 
// 再画 dynamicMap (前景)
if (dynamicMap[i][j] == 2) {
    // 进阶:如果 staticMap[i][j] == 3,说明箱子在目标上,画绿色箱子
    if (staticMap[i][j] == 3) drawGreenBox();
    else drawBrownBox();
}
else if (dynamicMap[i][j] == 4) drawPlayer();

方案二:单层地图法 + 状态还原函数(符合你直觉的方案)

如果你觉得维护两个二维数组麻烦,坚持用一个 map(包含 0-6 所有状态),那么最佳实践是:封装“进入”和“离开”的逻辑,坚决不手写 if-else

你需要写两个辅助函数:enterCellleaveCell

1. 辅助函数定义

// 当一个物体(objType) 离开 当前格子(currentVal) 后,格子变成了什么?
// objType: 4(人) 或 2(箱子)
// currentVal: 当前格子里的数值 (比如 6-人在目标, 4-人在空地)
int getAfterLeave(int currentVal) {
    if (currentVal == 6 || currentVal == 5) return 3; // 只要是在目标上的,离开后变回目标(3)
    return 0; // 否则变回空地
}
 
// 当一个物体(objType) 进入 目标格子(targetVal) 后,格子变成了什么?
// targetVal: 目标格原本的样子 (0-空地, 3-目标)
int getAfterEnter(int targetVal, int objType) {
    if (targetVal == 3) {
        // 如果进的是目标点
        if (objType == 4) return 6; // 人(4) -> 人在目标(6)
        if (objType == 2) return 5; // 箱子(2) -> 箱子在目标(5)
    }
    // 如果进的是空地
    return objType; // 保持原样 (4或2)
}

2. 代码实现 (Move Logic)

有了这两个函数,你的移动逻辑就变成了公式化的操作,不会出 bug:

void MainWindow::movePlayer(int dx, int dy) {
    int nx = px + dx;
    int ny = py + dy;
    
    int &nextCell = map[nx][ny]; // 下一步的格子
    int &currCell = map[px][py]; // 当前脚下的格子
 
    // --- 撞墙 ---
    if (nextCell == 1) return;
 
    // --- 遇到箱子 (2或5) ---
    if (nextCell == 2 || nextCell == 5) {
        int nnx = nx + dx;
        int nny = ny + dy;
        int &farCell = map[nnx][nny]; // 箱子后面
 
        // 箱子能推吗?(后面必须是 0-空地 或 3-目标)
        if (farCell == 0 || farCell == 3) {
            // 1. 箱子离开 nextCell,进入 farCell
            farCell = getAfterEnter(farCell, 2); // 箱子进后面
            nextCell = getAfterLeave(nextCell);  // 箱子离开中间(此时nextCell暂时变回了空地或目标)
            
            // 2. 人离开 currCell,进入 nextCell
            // 注意:此时 nextCell 已经是箱子离开后的状态了,直接让人进去
            nextCell = getAfterEnter(nextCell, 4); 
            currCell = getAfterLeave(currCell);
            
            px = nx; py = ny;
        }
        return;
    }
 
    // --- 遇到空地或目标 (0或3) ---
    if (nextCell == 0 || nextCell == 3) {
        nextCell = getAfterEnter(nextCell, 4); // 人进新坑
        currCell = getAfterLeave(currCell);    // 人出旧坑
        px = nx; py = ny;
    }
}

总结建议

  • 如果你想脑子最省事:选 方案一(双层地图)。因为你不需要去想“离开后变什么”这个问题,逻辑天然解耦。只要判定 staticMap 有没有墙,然后只改 dynamicMap 就行了。
  • 如果你想代码最紧凑:选 方案二(单层地图),但务必getAfterLeave 这种逻辑抽离出来。千万不要在 movePlayer 里写一堆 if (val == 5) ... else if (val == 6) ...,考场上一紧张绝对会漏掉一种情况(比如箱子推走后,目标点没复原)。

鉴于只有 2 小时,我会稍微倾向于 方案一,因为它的 paintEventmove 逻辑几乎不会发生“状态丢失”的 Bug。