from collections import deque
class SnakeGame:
def __init__(self, width: int, height: int, food: list[list[int]]):
self.width = width
self.height = height
self.food = food
self.food_index = 0
self.snake = deque([(0, 0)]) # head at the right end
self.snake_set = set([(0, 0)])
self.dirs = {'U': (-1, 0), 'D': (1, 0), 'L': (0, -1), 'R': (0, 1)}
def move(self, direction: str) -> int:
head_row, head_col = self.snake[0]
dr, dc = self.dirs[direction]
new_row, new_col = head_row + dr, head_col + dc
# Check wall collision
if not (0 <= new_row < self.height and 0 <= new_col < self.width):
return -1
# Check self collision (remove tail from set if not eating food)
tail = self.snake[-1]
if self.food_index < len(self.food) and [new_row, new_col] == self.food[self.food_index]:
self.food_index += 1
grow = True
else:
grow = False
self.snake_set.remove(tail)
if (new_row, new_col) in self.snake_set:
return -1
self.snake.appendleft((new_row, new_col))
self.snake_set.add((new_row, new_col))
if not grow:
self.snake.pop()
else:
# keep tail (snake grows)
pass
return len(self.snake) - 1
class SnakeGame {
public:
int width, height, foodIndex;
vector<vector<int>> food;
deque<pair<int, int>> snake;
unordered_set<int> snakeSet;
SnakeGame(int width, int height, vector<vector<int>>& food) {
this->width = width;
this->height = height;
this->food = food;
foodIndex = 0;
snake.push_back({0, 0});
snakeSet.insert(0);
}
int move(string direction) {
int dr = 0, dc = 0;
if (direction == "U") dr = -1;
else if (direction == "D") dr = 1;
else if (direction == "L") dc = -1;
else if (direction == "R") dc = 1;
int headRow = snake.front().first, headCol = snake.front().second;
int newRow = headRow + dr, newCol = headCol + dc;
// Wall collision
if (newRow < 0 || newRow >= height || newCol < 0 || newCol >= width) return -1;
int tailRow = snake.back().first, tailCol = snake.back().second;
int newPos = newRow * width + newCol;
int tailPos = tailRow * width + tailCol;
bool grow = false;
if (foodIndex < food.size() && newRow == food[foodIndex][0] && newCol == food[foodIndex][1]) {
grow = true;
foodIndex++;
} else {
snakeSet.erase(tailPos);
}
if (snakeSet.count(newPos)) return -1;
snake.push_front({newRow, newCol});
snakeSet.insert(newPos);
if (!grow) {
snake.pop_back();
}
return snake.size() - 1;
}
};
import java.util.*;
class SnakeGame {
int width, height, foodIndex;
int[][] food;
Deque<int[]> snake;
Set<Integer> snakeSet;
public SnakeGame(int width, int height, int[][] food) {
this.width = width;
this.height = height;
this.food = food;
foodIndex = 0;
snake = new LinkedList<>();
snake.addFirst(new int[]{0, 0});
snakeSet = new HashSet<>();
snakeSet.add(0);
}
public int move(String direction) {
int dr = 0, dc = 0;
if (direction.equals("U")) dr = -1;
else if (direction.equals("D")) dr = 1;
else if (direction.equals("L")) dc = -1;
else if (direction.equals("R")) dc = 1;
int[] head = snake.peekFirst();
int newRow = head[0] + dr, newCol = head[1] + dc;
if (newRow < 0 || newRow >= height || newCol < 0 || newCol >= width) return -1;
int tailRow = snake.peekLast()[0], tailCol = snake.peekLast()[1];
int newPos = newRow * width + newCol;
int tailPos = tailRow * width + tailCol;
boolean grow = false;
if (foodIndex < food.length && newRow == food[foodIndex][0] && newCol == food[foodIndex][1]) {
grow = true;
foodIndex++;
} else {
snakeSet.remove(tailPos);
}
if (snakeSet.contains(newPos)) return -1;
snake.addFirst(new int[]{newRow, newCol});
snakeSet.add(newPos);
if (!grow) {
snake.pollLast();
}
return snake.size() - 1;
}
}
class SnakeGame {
constructor(width, height, food) {
this.width = width;
this.height = height;
this.food = food;
this.foodIndex = 0;
this.snake = [[0, 0]]; // head at index 0
this.snakeSet = new Set(['0,0']);
this.dirs = {'U': [-1, 0], 'D': [1, 0], 'L': [0, -1], 'R': [0, 1]};
}
move(direction) {
let [dr, dc] = this.dirs[direction];
let [headRow, headCol] = this.snake[0];
let newRow = headRow + dr, newCol = headCol + dc;
// Wall collision
if (newRow < 0 || newRow >= this.height || newCol < 0 || newCol >= this.width) return -1;
let tail = this.snake[this.snake.length - 1];
let grow = false;
if (this.foodIndex < this.food.length && newRow === this.food[this.foodIndex][0] && newCol === this.food[this.foodIndex][1]) {
this.foodIndex++;
grow = true;
} else {
this.snakeSet.delete(tail.join(','));
}
if (this.snakeSet.has([newRow, newCol].join(','))) return -1;
this.snake.unshift([newRow, newCol]);
this.snakeSet.add([newRow, newCol].join(','));
if (!grow) {
this.snake.pop();
}
return this.snake.length - 1;
}
}
You are asked to design a simple Snake game on a 2D grid. The snake starts at the top-left corner of the grid at position (0, 0)
. The grid has a specified width
and height
. There is a list of food
positions that the snake can eat to grow longer. Each time the snake eats food, its length increases by one, and the next food appears. The snake moves according to a sequence of commands: 'U'
(up), 'D'
(down), 'L'
(left), or 'R'
(right).
The game ends when the snake either runs into the wall or into itself. After each move, you must return the score, which is the number of food items eaten so far, or -1
if the game is over. The snake cannot move outside the grid or into its own body, except for the tail position if the tail is moving forward (since the tail leaves that spot).
Key constraints:
To solve this problem, we need to simulate the snake's movement while efficiently checking for collisions with the wall and itself. The naive approach would be to scan the entire snake's body to check for collisions after each move, but this would be too slow as the snake grows longer.
Therefore, we look for ways to optimize:
Let's break down the solution step by step:
This design ensures all operations are efficient, and the game simulation is accurate.
Suppose we have a 3x3 grid (width = 3
, height = 3
) and food at positions [[2,0],[0,0],[0,2],[2,2]]
.
(0,0)
. Score: 0.
(1,0)
. No food. Score: 0.
(2,0)
. Eats food. Snake grows to length 2. Score: 1.
(2,1)
. No food. Tail moves forward. Score: 1.
(1,1)
. No food. Score: 1.
(0,1)
. No food. Score: 1.
(0,0)
. Eats food. Snake grows to length 3. Score: 2.
At each step, we check for wall collisions, self-collisions, and whether the snake eats food, updating the state accordingly.
Brute-force approach: If we scanned the snake's entire body for collisions after every move, each move would be O(N) where N is the snake's length. As the snake grows, this becomes inefficient.
Optimized approach: By using a set for the snake's positions, checking for self-collision is O(1). Adding/removing from deque and set is also O(1). Thus, each move is processed in O(1) time.
Space complexity: Both the deque and set store each segment of the snake, so space is O(N), where N is the maximum possible length of the snake (up to the grid size).
We designed an efficient simulation of the Snake game using a double-ended queue to represent the snake's body and a hash set for fast collision detection. By carefully updating these structures as the snake moves and grows, we ensure all operations are fast and the game rules are enforced. This approach avoids the inefficiency of scanning the whole snake each time, making the solution elegant and scalable for large grids and long snakes.