
Problem Statement
The goal is to design a "Snake Game" typically found on older cell phones and simple gaming consoles, programmed to run on a device with a defined screen size labeled by its width and height. Initially, the game places a snake of unit length at the upper left corner of the screen. As the game progresses, pieces of food, specified by coordinates within the food array, appear on the screen one after another. Each time the snake consumes a piece of food, its length increases by one unit, as does the player's score.
However, the game imposes a natural limit to the snake's unfettered eating: food pieces will never appear on a block currently occupied by the snake's body. The gameplay continues with the snake moving in specified directions until it either exits the boundary of the play area or collides into itself, at which point the game concludes.
The functionality for this game is encapsulated in the SnakeGame class, which initializes the game setting with the specified screen and food positions and processes the movement commands, returning the current score or indicating the game's end.
Examples
Example 1
Input
["SnakeGame", "move", "move", "move", "move", "move", "move"] [[3, 2, [[1, 2], [0, 1]]], ["R"], ["D"], ["R"], ["U"], ["L"], ["U"]]
Output
[null, 0, 0, 1, 1, 2, -1]
Explanation
SnakeGame snakeGame = new SnakeGame(3, 2, [[1, 2], [0, 1]]);
snakeGame.move("R"); // return 0
snakeGame.move("D"); // return 0
snakeGame.move("R"); // return 1, snake eats the first piece of food. The second piece of food appears at (0, 1).
snakeGame.move("U"); // return 1
snakeGame.move("L"); // return 2, snake eats the second food. No more food appears.
snakeGame.move("U"); // return -1, game over because snake collides with borderConstraints
1 <= width, height <= 1041 <= food.length <= 50food[i].length == 20 <= ri < height0 <= ci < widthdirection.length == 1directionis'U','D','L', or'R'.- At most
104calls will be made tomove.
Approach and Intuition
In approaching the design of the Snake Game, it's helpful to understand both the operational mechanics provided in the problem statement and the game’s structure from common gameplay experiences:
Initialization:
- The
SnakeGameobject is initialized withwidth,height, and thefoodcoordinates. - The snake starts at position
(0,0)with an initial length of 1.
- The
Game Mechanics:
- The game accepts a
movecommand, which specifies the direction of the snake's next move (one of 'U' for up, 'D' for down, 'L' for left, and 'R' for right). - The snake moves one block per command in the specified direction.
- The game accepts a
Food Consumption and Growth:
- If the snake's new head position after a move matches the location of the next piece of food, the snake grows in length by one unit and the corresponding piece of food is considered eaten. The next piece will then appear according to the sequence provided in the
foodarray. - The game keeps track of how many pieces of food have been eaten using an index that gets incremented with each consumed piece.
- If the snake's new head position after a move matches the location of the next piece of food, the snake grows in length by one unit and the corresponding piece of food is considered eaten. The next piece will then appear according to the sequence provided in the
Checking Game Termination Conditions:
- If the snake attempts to move outside the confines of the screen's width or height, the game ends.
- If the snake moves into any part of its own body, the game also ends.
- The move method returns
-1if the game is over; otherwise, it returns the current score, which equates to the number of food items eaten.
Example Walkthrough:
- Consider the given example with initial settings and a series of moves.
- Track the snake’s head, ensure it does not collide with the wall or itself, manage food consumption accurately, and update the game state and score accordingly.
This strategy ensures the accurate simulation of the Snake Game, providing clear rules for movement, growth, and game-ending scenarios.
Solutions
- Java
- Python
class GameOfSnake {
HashMap<Pair<Integer, Integer>, Boolean> snakePositionMap;
Deque<Pair<Integer, Integer>> snakeBody;
int[][] foodItems;
int foodPointer;
int maxWidth;
int maxHeight;
public GameOfSnake(int maxWidth, int maxHeight, int[][] foodItems) {
this.maxWidth = maxWidth;
this.maxHeight = maxHeight;
this.foodItems = foodItems;
this.snakePositionMap = new HashMap<Pair<Integer, Integer>, Boolean>();
this.snakePositionMap.put(new Pair<Integer, Integer>(0,0), true);
this.snakeBody = new LinkedList<Pair<Integer, Integer>>();
this.snakeBody.offerLast(new Pair<Integer, Integer>(0,0));
}
public int adjustSnake(String direction) {
Pair<Integer, Integer> headCell = this.snakeBody.peekFirst();
int nextRow = headCell.getKey();
int nextCol = headCell.getValue();
switch (direction) {
case "U":
nextRow--;
break;
case "D":
nextRow++;
break;
case "L":
nextCol--;
break;
case "R":
nextCol++;
break;
}
Pair<Integer, Integer> newHeadPosition = new Pair<Integer, Integer>(nextRow, nextCol);
Pair<Integer, Integer> tailCell = this.snakeBody.peekLast();
boolean hitsBoundary = nextRow < 0 || nextRow >= this.maxHeight || nextCol < 0 || nextCol >= this.maxWidth;
boolean selfCollision = this.snakePositionMap.containsKey(newHeadPosition) && !(newHeadPosition.getKey() == tailCell.getKey() && newHeadPosition.getValue() == tailCell.getValue());
if (hitsBoundary || selfCollision) {
return -1;
}
if ((this.foodPointer < this.foodItems.length)
&& (this.foodItems[this.foodPointer][0] == nextRow)
&& (this.foodItems[this.foodPointer][1] == nextCol)) {
this.foodPointer++;
} else {
this.snakeBody.pollLast();
this.snakePositionMap.remove(tailCell);
}
this.snakeBody.addFirst(newHeadPosition);
this.snakePositionMap.put(newHeadPosition, true);
return this.snakeBody.size() - 1;
}
}
The provided Java code implements a Snake game class, GameOfSnake, managing the snake's movements and game logic, ensuring efficient interaction with game elements like food, boundaries, and the snake itself. Here's a breakdown:
A HashMap,
snakePositionMap, keeps track of positions occupied by the snake. Atrueindicates the spot is occupied by the snake.A Deque,
snakeBody, holds the snake's body coordinates, facilitating O(1) time complexity for appending or removing cells from the snake's body, enabling efficient growth and movement.An integer array,
foodItems, and a food pointer,foodPointer, manage the food positions and the current target food piece.The grid dimensions
maxWidthandmaxHeightdefine the playing area.
The constructor initializes the game with the snake starting at position (0,0), food positioned based on the foodItems parameter, and sets up the game dimensions.
The adjustSnake method updates the snake's head position based on the input direction ("U" for up, "D" for down, "L" for left, "R" for right). It then:
- Calculates the new position for the snake's head.
- Checks for boundary hits or collisions with its body aside from the tail piece, which is allowed as it moves.
If a food item is at the new head position, foodPointer increments, and the snake grows. If not, the snake moves by adding a new head and removing the tail, maintaining its size.
The game ends (returns -1) if there's a collision or the snake hits a boundary. Otherwise, it returns the current size of the snake reduced by one, which represents the score (length of the snake minus initial size).
This implementation highlights effective use of data structures for gameplay mechanics, demonstrating how to manage the game's state efficiently.
class SnakeGame:
def __init__(self, width: int, height: int, food: List[List[int]]):
self.snake_list = collections.deque([(0,0)])
self.position_set = {(0,0) : 1}
self.game_width = width
self.game_height = height
self.food_positions = food
self.food_counter = 0
self.direction_map = {'U': [-1, 0], 'L': [0, -1], 'R': [0, 1], 'D': [1, 0]}
def move(self, direction: str) -> int:
head_new = (self.snake_list[0][0] + self.direction_map[direction][0], self.snake_list[0][1] + self.direction_map[direction][1])
outside_bounds = head_new[0] < 0 or head_new[0] >= self.game_height or head_new[1] < 0 or head_new[1] >= self.game_width
self_bite = head_new in self.position_set and head_new != self.snake_list[-1]
if outside_bounds or self_bite:
return -1
target_food = self.food_positions[self.food_counter] if self.food_counter < len(self.food_positions) else None
if self.food_counter < len(self.food_positions) and target_food[0] == head_new[0] and target_food[1] == head_new[1]:
self.food_counter += 1
else:
tail = self.snake_list.pop()
del self.position_set[tail]
self.snake_list.appendleft(head_new)
self.position_set[head_new] = 1
return len(self.snake_list) - 1
Implementing a "Snake Game" in Python involves setting up the game dimensions, tracking the positions of the snake and food, and handling the movement based on user inputs.
Initial Setup:
- Initialize the game with dimensions
widthandheight. - A deque
snake_listbegins with the snake's start position. - Create a dictionary
position_setto track snake's body positions for quick look-up. - List
food_positionsstores the coordinates of the food items. - A counter
food_counterto manage the consumption of food.
- Initialize the game with dimensions
Movement Mechanics:
- Define
direction_mapwith vectors representing possible movements (U, D, L, R). - The
movefunction updates the snake's head to the new position based on the direction. - Check if the new head position is outside the game boundary or bites itself.
- If the snake's head reaches a food position, increment the
food_counter. - If no food is consumed, remove the tail segment; this simulates the snake moving forward.
- Add the new head position to
snake_listand updateposition_set. - Return the current length of the snake minus one, to account for the zero-indexed list.
- Define
Ensure to manage the snake's state accurately after each move, with the game ending when the snake goes out of bounds or bites itself. The snake grows by staying at its current length instead of removing the tail when it consumes food.