Sale: Get 60% Off on all Pro Plans. Buy Now

Design Snake Game

Medium Simulation

Problem

Design a snake game on a grid of given width and height with a list of food positions; implement a move function that moves the snake in the given direction, returns the game's score after the move, or -1 if the game is over due to collision or boundary crossing.

  • 1 ≤ width, height ≤ 10⁴
  • 0 ≤ food.length ≤ 10⁴
  • food[i].length == 2
  • 0 ≤ food[i][0] < height
  • 0 ≤ food[i][1] < width
  • direction is one of 'U', 'D', 'L', 'R'

Example

Input: width = 3, height = 2, food = [[1,2],[0,1]]; moves = ['R', 'D', 'R', 'U', 'L', 'U']
Output: [0, 0, 1, 1, 2, -1]

Initially, the snake is at (0,0). Move 'R' moves it to (0,1), no food eaten, score 0. Move 'D' to (1,1), no food, score 0. Move 'R' to (1,2), eats first food, snake grows, score 1. Move 'U' to (0,2), no food, score 1. Move 'L' to (0,1), eats second food, snake grows, score 2. Move 'U' to (-1,1), out of bounds, game over, returns -1.

Approach

Straightforward Solution

A naive approach might store the snake's body as a list and check for collisions by scanning the list, resulting in O(n) collision checks per move, which is inefficient for long snakes.

Core Observation

The snake's position and body must be tracked precisely to detect collisions with itself and boundaries. The snake moves by adding a new head and removing the tail unless it eats food, in which case it grows by not removing the tail. Efficient membership checks for the snake's body are essential to detect self-collisions in O(1) time.

Path to Optimal

Preview

The key insight is to use a deque to represent the snake's body for O(1) head and tail operations, and a hash set to track body positions for O(1) collision detection…

Full step-by-step walkthrough on Pro

Optimal Approach

Preview

Use a deque to store the snake's body coordinates with the head at the front and a set for constant-time membership checks. On each move, compute the new head position, remove the tail from the set and deque, check for collisions, then add the new head…

Full step-by-step walkthrough on Pro

Want the full reasoning chain?

Unlock the complete walkthrough, line-by-line analysis, and recall drill.

Unlock Pro

Time

O(1) per move

Each move involves constant-time operations: deque pop and append, set add and remove, and simple arithmetic for position updates. Collision checks are O(1) due to the set.

Space

O(n)

The snake's body can grow up to the size of the grid or the number of food items, so the deque and set store up to O(n) positions, where n is the number of moves or food items.

Pattern Spotlight

Simulation (Stateful Grid Movement with Collision Detection)

When simulating a moving object with a dynamic body on a grid, maintain both an ordered structure for the body and a hash set for quick collision checks, carefully updating these in the correct order to avoid false positives when the tail moves forward.

Solution

Python
1class SnakeGame:
2 def __init__(self, width: int, height: int, food: List[List[int]]):
3 self.width = width
4 self.height = height
5 self.food = food
6 self.food_index = 0
7 self.snake = deque([(0, 0)])
8 self.body = {(0, 0)}
9 self.score = 0
10 self.dirs = {
11 "U": (-1, 0),
12 "D": (1, 0),
13 "L": (0, -1),
14 "R": (0, 1)
15 }
16
17 def move(self, direction: str) -> int:
18 dr, dc = self.dirs[direction]
19 head_r, head_c = self.snake[0]
20 new_r, new_c = head_r + dr, head_c + dc
21 new_head = (new_r, new_c)
22
23 if not (0 <= new_r < self.height and 0 <= new_c < self.width):
24 return -1
25
26 tail = self.snake.pop()
27 self.body.remove(tail)
28
29 if new_head in self.body:
30 return -1
31
32 self.snake.appendleft(new_head)
33 self.body.add(new_head)
34
35 if self.food_index < len(self.food) and new_head == tuple(self.food[self.food_index]):
36 self.food_index += 1
37 self.score += 1
38 self.snake.append(tail)
39 self.body.add(tail)
40
41 return self.score

Step-by-Step Solution

1

Initialize Snake State and Movement Directions

3self.width = width
4self.height = height
5self.food = food
6self.food_index = 0
7self.snake = deque([(0, 0)])
8self.body = {(0, 0)}
9self.score = 0
10self.dirs = {
11 "U": (-1, 0),
12 "D": (1, 0),
13 "L": (0, -1),
14 "R": (0, 1)
15}

Objective

To set up the initial game state including grid dimensions, food list, snake body tracking, and movement direction mappings.

Key Insight

Representing the snake's body as both a deque and a set allows efficient updates and collision detection. The deque maintains the order of the snake's segments for head and tail operations, while the set enables O(1) membership checks. Predefining direction vectors simplifies move calculations and reduces errors.

Interview Quick-Check

Core Logic

Using a deque for the snake's body preserves order for head/tail operations, while a set tracks occupied positions for constant-time collision detection.

Common Pitfalls & Bugs

Failing to keep the set and deque in sync can cause incorrect collision detection or state corruption.

State & Boundaries

Initialize the snake at position (0, 0) with score zero and food index zero to track progress through the food list.

2

Compute New Head Position and Check Boundary Collision

To calculate the snake's next head position based on the input direction and verify it remains within grid boundaries.

3

Update Snake Body by Moving Tail and Detect Self-Collision

To simulate the snake moving forward by removing the tail, then check if the new head collides with the body.

4

Add New Head and Handle Food Consumption to Grow Snake

To add the new head position to the snake's body and grow the snake if food is eaten at the new head location.

5

Return Current Score or Game Over Indicator

To return the current score after a successful move or -1 if the game ended due to collision or boundary crossing.

4 more steps with full analysis available on Pro.

Line Analysis

This solution has 10 Critical lines interviewers watch for.

Line 24 Critical
return -1

Return -1 to indicate game over due to boundary collision.

Returning -1 signals to the caller that the game has ended because the snake moved out of bounds.

Line 29 Critical
if new_head in self.body:

Check if the new head position collides with the snake's body.

Detecting self-collision is critical to end the game when the snake bites itself.

Line 30 Critical
return -1

Return -1 to indicate game over due to self-collision.

Returning -1 signals the game has ended because the snake collided with itself.

Full line-by-line criticality + rationale for all 32 lines available on Pro.

Test Your Understanding

Why is the tail removed from the body set before checking for collisions with the new head position?

See the answer with Pro.

Related Problems

Simulation pattern

Don't just read it. Drill it.

Reconstruct Design Snake Game from memory until it sticks. AlgoDrill blanks out key lines and makes you fill them back in, step by step.

Unlock the Design Snake Game drill

or drill a free problem