
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 border
Constraints
1 <= width, height <= 104
1 <= food.length <= 50
food[i].length == 2
0 <= ri < height
0 <= ci < width
direction.length == 1
direction
is'U'
,'D'
,'L'
, or'R'
.- At most
104
calls 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
SnakeGame
object is initialized withwidth
,height
, and thefood
coordinates. - The snake starts at position
(0,0)
with an initial length of 1.
- The
Game Mechanics:
- The game accepts a
move
command, 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
food
array. - 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
-1
if 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. Atrue
indicates 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
maxWidth
andmaxHeight
define 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
width
andheight
. - A deque
snake_list
begins with the snake's start position. - Create a dictionary
position_set
to track snake's body positions for quick look-up. - List
food_positions
stores the coordinates of the food items. - A counter
food_counter
to manage the consumption of food.
- Initialize the game with dimensions
Movement Mechanics:
- Define
direction_map
with vectors representing possible movements (U, D, L, R). - The
move
function 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_list
and 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.
No comments yet.