这道题目要求我们模拟经典的贪吃蛇游戏,根据给定的移动指令和游戏地图,计算蛇最终的长度。作为华为OD机考双机位C卷的真题,它考察了数据结构应用、边界条件处理和算法实现能力。
核心需求可以拆解为:
最关键的实现决策是如何表示蛇的身体。常见方案有:
链表结构:
二维数组标记:
双端队列(deque):
python复制from collections import deque
class Game:
def __init__(self, grid):
self.snake = deque() # 蛇身体坐标队列
self.direction = 'L' # 初始方向向左
self.grid = grid # 游戏地图
self.init_snake() # 初始化蛇位置
方向转换需要处理转向指令(U/D/L/R)和直行指令(G):
python复制DIRECTIONS = {
'U': (-1, 0),
'D': (1, 0),
'L': (0, -1),
'R': (0, 1)
}
def change_direction(self, new_dir):
# 防止180度直接转向(如左转右)
if (self.direction, new_dir) not in [('U','D'), ('D','U'), ('L','R'), ('R','L')]:
self.direction = new_dir
需要检测三种碰撞情况:
python复制def check_collision(self, new_head):
# 边界检查
if not (0 <= new_head[0] < len(self.grid) and 0 <= new_head[1] < len(self.grid[0])):
return 'wall'
# 身体检查(排除尾部,因为尾部会移动)
if new_head in list(self.snake)[:-1]:
return 'body'
# 食物检查
if self.grid[new_head[0]][new_head[1]] == 'F':
return 'food'
return None
python复制def process_moves(self, moves):
for move in moves.split():
if move in ['U', 'D', 'L', 'R']:
self.change_direction(move)
elif move == 'G':
if not self.move_snake():
break # 游戏结束
return len(self.snake)
python复制def move_snake(self):
# 计算新头部位置
dx, dy = DIRECTIONS[self.direction]
new_head = (self.snake[0][0] + dx, self.snake[0][1] + dy)
# 碰撞检测
collision = self.check_collision(new_head)
if collision == 'wall' or collision == 'body':
return False # 游戏结束
# 更新蛇身体
self.snake.appendleft(new_head)
if collision != 'food':
self.snake.pop() # 没吃到食物,移除尾部
else:
self.grid[new_head[0]][new_head[1]] = 'E' # 吃掉食物
return True
python复制from collections import deque
class SnakeGame:
DIRECTIONS = {'U': (-1,0), 'D': (1,0), 'L': (0,-1), 'R': (0,1)}
def __init__(self, grid):
self.grid = grid
self.snake = deque()
self.direction = 'L'
self.init_snake()
def init_snake(self):
for i in range(len(self.grid)):
for j in range(len(self.grid[0])):
if self.grid[i][j] == 'H':
self.snake.append((i, j))
return
def change_direction(self, new_dir):
if (self.direction, new_dir) not in [('U','D'),('D','U'),('L','R'),('R','L')]:
self.direction = new_dir
def check_collision(self, pos):
if not (0 <= pos[0] < len(self.grid) and 0 <= pos[1] < len(self.grid[0])):
return 'wall'
if pos in list(self.snake)[:-1]:
return 'body'
if self.grid[pos[0]][pos[1]] == 'F':
return 'food'
return None
def move_snake(self):
dx, dy = self.DIRECTIONS[self.direction]
new_head = (self.snake[0][0] + dx, self.snake[0][1] + dy)
collision = self.check_collision(new_head)
if collision in ['wall', 'body']:
return False
self.snake.appendleft(new_head)
if collision != 'food':
self.snake.pop()
else:
self.grid[new_head[0]][new_head[1]] = 'E'
return True
def process_moves(self, moves):
for move in moves.split():
if move in ['U','D','L','R']:
self.change_direction(move)
elif move == 'G':
if not self.move_snake():
break
return len(self.snake)
# 使用示例
moves = "D G G"
n, m = 3, 3
grid = [
['F','F','F'],
['F','F','H'],
['E','F','E']
]
game = SnakeGame(grid)
print(game.process_moves(moves)) # 输出: 1
java复制import java.util.*;
public class SnakeGame {
private Deque<int[]> snake = new LinkedList<>();
private char[][] grid;
private char direction = 'L';
private final Map<Character, int[]> dirMap = Map.of(
'U', new int[]{-1,0},
'D', new int[]{1,0},
'L', new int[]{0,-1},
'R', new int[]{0,1}
);
public SnakeGame(char[][] grid) {
this.grid = grid;
initSnake();
}
private void initSnake() {
for(int i=0; i<grid.length; i++) {
for(int j=0; j<grid[0].length; j++) {
if(grid[i][j] == 'H') {
snake.addFirst(new int[]{i,j});
return;
}
}
}
}
public void changeDirection(char newDir) {
if(!( (direction=='U'&&newDir=='D') || (direction=='D'&&newDir=='U') ||
(direction=='L'&&newDir=='R') || (direction=='R'&&newDir=='L') )) {
direction = newDir;
}
}
private String checkCollision(int[] pos) {
if(pos[0]<0 || pos[0]>=grid.length || pos[1]<0 || pos[1]>=grid[0].length) {
return "wall";
}
for(int[] body : snake) {
if(body != snake.getLast() && pos[0]==body[0] && pos[1]==body[1]) {
return "body";
}
}
if(grid[pos[0]][pos[1]] == 'F') {
return "food";
}
return null;
}
private boolean moveSnake() {
int[] head = snake.getFirst();
int[] delta = dirMap.get(direction);
int[] newHead = new int[]{head[0]+delta[0], head[1]+delta[1]};
String collision = checkCollision(newHead);
if("wall".equals(collision) || "body".equals(collision)) {
return false;
}
snake.addFirst(newHead);
if(!"food".equals(collision)) {
snake.removeLast();
} else {
grid[newHead[0]][newHead[1]] = 'E';
}
return true;
}
public int processMoves(String moves) {
for(String move : moves.split(" ")) {
if(move.equals("U") || move.equals("D") || move.equals("L") || move.equals("R")) {
changeDirection(move.charAt(0));
} else if(move.equals("G")) {
if(!moveSnake()) break;
}
}
return snake.size();
}
public static void main(String[] args) {
String moves = "D G G";
char[][] grid = {
{'F','F','F'},
{'F','F','H'},
{'E','F','E'}
};
SnakeGame game = new SnakeGame(grid);
System.out.println(game.processMoves(moves)); // 输出: 1
}
}
方向处理错误:
碰撞检测遗漏:
食物不消失:
建议测试这些边界情况:
python复制# 测试用例1 - 初始碰撞
moves = "L G"
grid = [
['E','E','E'],
['E','H','E'],
['E','E','E']
] # 预期: 1 (向左撞墙)
# 测试用例2 - 身体增长
moves = "R G G U G G L G G D G G"
grid = [
['E','F','E'],
['E','H','E'],
['E','F','E']
] # 预期: 3 (吃两个食物)
# 测试用例3 - 自碰撞
moves = "R G U G L G D G"
grid = [
['E','E','E','E'],
['E','H','F','E'],
['E','F','F','E'],
['E','E','E','E']
] # 预期: 4 (形成环状自碰撞)
使用集合快速检测身体碰撞:
python复制# 在类中增加
self.body_set = set(self.snake)
# 移动时更新
if collision != 'food':
tail = self.snake.pop()
self.body_set.remove(tail)
self.body_set.add(new_head)
预计算方向向量:
python复制# 替代DIRECTIONS字典使用元组数组
self.dir_vec = [(-1,0), (1,0), (0,-1), (0,1)]
self.dir_map = {'U':0, 'D':1, 'L':2, 'R':3}
避免频繁的列表转换:
python复制# 将 list(self.snake)[:-1] 改为
for segment in itertools.islice(self.snake, 0, len(self.snake)-1):
if new_head == segment:
return 'body'
设N为网格行数,M为列数,K为移动步数:
时间复杂度:
空间复杂度:
使用body_set优化后,碰撞检测可降为O(1),整体复杂度优化到O(K)
实际游戏开发中还需要考虑:
这道题目虽然规则简单,但完整实现需要考虑各种边界情况,是检验编程基本功的优秀题目。在实现时建议先处理核心移动逻辑,再逐步添加碰撞检测等特性,通过单元测试确保各模块正确性。