Sketch 2026-03-04 02:18

This is a classic Snake game where players control a growing snake using arrow keys to eat food and increase their score. The game ends when the snake hits a wall or itself, and can be restarted by pressing 'R'.

🧪 Try This!

Experiment with the code by making these changes:

  1. Make the snake move faster — Higher frameRate values make draw() run more frequently, so the snake updates position more often and appears to move faster.
  2. Change the grid size to make the game harder — A smaller grid means the snake moves smaller distances and the game becomes more precise and challenging.
  3. Change the snake's color to blue — The fill() function sets the color for shapes drawn afterward—RGB values control the exact color.
  4. Make the food spawn faster by removing collision checks — If you remove the while loop that checks for snake overlap, food can spawn on the snake (but the game still works).
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch implements the timeless Snake game using p5.js fundamentals. A player-controlled snake moves across a grid, eating pink food squares to grow longer and rack up points. The game combines keyboard input handling, collision detection (both with walls and the snake's own body), and dynamic array management to track the snake's segments. It demonstrates how simple rules—move forward, eat to grow, die on collision—create an engaging interactive experience.

The code is organized into a setup() function that initializes the snake and game state, a draw() function that runs the game loop, and specialized helper functions for movement, collision checking, food placement, and game-over handling. By studying this sketch, you'll learn how to manage arrays of game objects, use vectors for direction and position, implement keyboard controls with validation, and structure a complete game with win/lose conditions.

⚙️ How It Works

  1. When the sketch loads, setup() creates a canvas, initializes a snake array with three segments positioned at the center, and picks a random food location that doesn't overlap the snake.
  2. Every frame (10 times per second by default), draw() clears the background and checks if the game is over—if so, it displays the game-over screen and stops.
  3. updateSnake() moves the snake forward by creating a new head in the current direction, adding it to the front of the array, and removing the tail (unless food was just eaten, which causes growth).
  4. The sketch checks three collision conditions: if the head reaches food, the score increases and the snake grows; if the head hits a wall boundary, the game ends; if the head touches any other segment of the snake's body, the game ends.
  5. The snake and food are drawn as colored rectangles on a grid, and the current score displays in the top-left corner.
  6. Keyboard input (arrow keys and 'R' for restart) is handled by keyPressed(), which updates the direction vector while preventing the snake from reversing directly into itself.

🎓 Concepts You'll Learn

Game loop with draw()Array management for linked segmentsVector-based position and directionCollision detection (walls and self)Keyboard input handlingGame state management

📝 Code Breakdown

setup()

setup() runs once when the sketch starts. It prepares the canvas, initializes the snake array with three segments, and places the first food. Notice how the snake's starting position is calculated to snap to the grid—this ensures every movement stays aligned to the scale.

function setup() {
  createCanvas(windowWidth, windowHeight);
  // Tip: Use windowWidth/windowHeight for responsive canvas
  
  direction = createVector(0, 0); // Initial direction (not moving)
  frameRate(10); // Snake speed (frames per second)

  // Initialize snake
  snake = [];
  let headX = floor(width / 2 / scale) * scale;
  let headY = floor(height / 2 / scale) * scale;
  snake.push(createVector(headX, headY)); // Head
  // Add a few segments for starting length
  snake.push(createVector(headX - scale, headY));
  snake.push(createVector(headX - 2 * scale, headY));

  // Place initial food
  pickLocation();
}
Line-by-line explanation (9 lines)

🔧 Subcomponents:

for-loop Snake segment creation snake.push(createVector(headX, headY)); // Head snake.push(createVector(headX - scale, headY)); snake.push(createVector(headX - 2 * scale, headY));

Creates three initial segments: a head and two body segments arranged horizontally to the left

createCanvas(windowWidth, windowHeight);
Creates a canvas that fills the entire browser window, making the game responsive to window size
direction = createVector(0, 0); // Initial direction (not moving)
Initializes the direction vector to (0, 0), meaning the snake doesn't move until the player presses an arrow key
frameRate(10); // Snake speed (frames per second)
Sets the draw() loop to run 10 times per second, controlling how fast the snake moves
snake = [];
Creates an empty array that will store all the snake's segments as vectors
let headX = floor(width / 2 / scale) * scale;
Calculates the snake's starting x position at the horizontal center, snapped to the nearest grid point
let headY = floor(height / 2 / scale) * scale;
Calculates the snake's starting y position at the vertical center, snapped to the nearest grid point
snake.push(createVector(headX, headY)); // Head
Adds the head segment at the center position to the beginning of the snake array
snake.push(createVector(headX - scale, headY)); snake.push(createVector(headX - 2 * scale, headY));
Adds two more segments to the left of the head, creating a horizontal snake of length 3
pickLocation();
Calls the function that randomly places food somewhere on the grid

draw()

draw() is the game loop—it runs 10 times per second. Every frame, it checks collisions, updates movement, and redraws everything. The early return when gameIsOver is true prevents the game from continuing after you've lost.

🔬 This loop draws the entire snake. What happens if you change rect(snake[i].x, snake[i].y, scale, scale) to circle(snake[i].x + scale/2, snake[i].y + scale/2, scale)? Why does the snake look rounder?

  for (let i = 0; i < snake.length; i++) {
    fill(0, 255, 0); // Green snake
    if (i === 0) {
      fill(0, 200, 0); // Darker green for head
    }
    noStroke();
    rect(snake[i].x, snake[i].y, scale, scale);
  }
function draw() {
  background(220);

  if (gameIsOver) {
    displayGameOver();
    return; // Stop draw loop if game is over
  }

  // Update snake position
  updateSnake();

  // Check for food collision
  if (snake[0].x === food.x && snake[0].y === food.y) {
    score++;
    pickLocation();
    growSnake();
  }

  // Check for wall collision
  checkWallCollision();

  // Check for self-collision
  checkSelfCollision();

  // Draw snake
  for (let i = 0; i < snake.length; i++) {
    fill(0, 255, 0); // Green snake
    if (i === 0) {
      fill(0, 200, 0); // Darker green for head
    }
    noStroke();
    rect(snake[i].x, snake[i].y, scale, scale);
  }

  // Draw food
  fill(255, 0, 100); // Pink food
  noStroke();
  rect(food.x, food.y, scale, scale);

  // Display score
  fill(0);
  textSize(24);
  textAlign(LEFT, TOP);
  text("Score: " + score, 10, 10);
}
Line-by-line explanation (19 lines)

🔧 Subcomponents:

conditional Game over state check if (gameIsOver) { displayGameOver(); return; // Stop draw loop if game is over }

If the game has ended, display the game-over screen and exit early, skipping all game logic

conditional Food collision and growth if (snake[0].x === food.x && snake[0].y === food.y) { score++; pickLocation(); growSnake(); }

Checks if the snake's head touches food; if so, increases score, places new food, and grows the snake

for-loop Draw all snake segments for (let i = 0; i < snake.length; i++) { fill(0, 255, 0); // Green snake if (i === 0) { fill(0, 200, 0); // Darker green for head } noStroke(); rect(snake[i].x, snake[i].y, scale, scale); }

Loops through every segment in the snake array and draws each as a green rectangle, with the head in darker green

background(220);
Clears the canvas with a light gray background, erasing the previous frame so the snake appears to move instead of leaving a trail
if (gameIsOver) {
Checks whether the game state is over
displayGameOver();
Calls the function that draws the game-over message and final score
return; // Stop draw loop if game is over
Exits the draw() function early, skipping all game updates and preventing further movement
updateSnake();
Calls the function that moves the snake forward in the current direction by one grid cell
if (snake[0].x === food.x && snake[0].y === food.y) {
Checks if the head's position (snake[0]) exactly matches the food's position
score++;
Increases the score by 1 point
pickLocation();
Randomly places new food on the grid
growSnake();
Calls the function that makes the snake grow (though the actual growth happens in updateSnake)
checkWallCollision();
Checks if the head has moved beyond the canvas boundaries and ends the game if so
checkSelfCollision();
Checks if the head has collided with any other segment of the snake's body
for (let i = 0; i < snake.length; i++) {
Loops through each segment in the snake array, counting from the head (index 0) to the tail
fill(0, 255, 0); // Green snake
Sets the fill color to bright green for all segments
if (i === 0) {
Checks if this is the head segment (the first element in the array)
fill(0, 200, 0); // Darker green for head
Changes the fill color to a darker green specifically for the head, making it visually distinct
rect(snake[i].x, snake[i].y, scale, scale);
Draws a square at the segment's position with width and height both equal to scale
fill(255, 0, 100); // Pink food
Sets the fill color to pink for the food
rect(food.x, food.y, scale, scale);
Draws the food as a pink square at the same grid size as the snake segments
text("Score: " + score, 10, 10);
Displays the current score in the top-left corner of the canvas

updateSnake()

updateSnake() is the movement engine. It creates a new head in the direction the player is moving, adds it to the front of the array with unshift(), and removes the tail with pop() unless food was just eaten. This simple push-and-pop mechanism creates the illusion of smooth motion.

🔬 This code controls growth: the tail is only removed if food wasn't eaten. What happens if you change snake.pop() to not execute—e.g., always keep the tail? Try removing the line entirely to make the snake grow every frame.

  // Remove the tail segment (unless we just ate food)
  if (!checkFoodCollision()) { // Check if the new head is on food
    snake.pop();
  }
function updateSnake() {
  // Only move if a direction is set
  if (direction.x === 0 && direction.y === 0) {
    return;
  }

  // Create a new head position
  let head = snake[0].copy();
  head.x += direction.x * scale;
  head.y += direction.y * scale;

  // Add the new head to the beginning of the snake
  snake.unshift(head);

  // Remove the tail segment (unless we just ate food)
  if (!checkFoodCollision()) { // Check if the new head is on food
    snake.pop();
  }
}
Line-by-line explanation (8 lines)

🔧 Subcomponents:

conditional Direction validation if (direction.x === 0 && direction.y === 0) { return; }

Prevents movement until the player presses an arrow key

calculation New head position let head = snake[0].copy(); head.x += direction.x * scale; head.y += direction.y * scale;

Copies the current head and adds the direction multiplied by scale to get the next grid position

conditional Conditional tail removal if (!checkFoodCollision()) { // Check if the new head is on food snake.pop(); }

Removes the tail only if the head didn't land on food—if it did, the snake grows by one segment

if (direction.x === 0 && direction.y === 0) {
Checks if the direction is (0, 0), meaning no arrow key has been pressed yet
return;
Exits the function early, so the snake doesn't move until a direction is set
let head = snake[0].copy();
Creates a copy of the current head vector—.copy() ensures we don't modify the original head
head.x += direction.x * scale;
Moves the new head horizontally: if direction.x is 1, move right by scale; if -1, move left by scale; if 0, don't move horizontally
head.y += direction.y * scale;
Moves the new head vertically in the same way
snake.unshift(head);
Adds the new head to the beginning of the snake array, pushing all other segments back one position
if (!checkFoodCollision()) { // Check if the new head is on food
Calls checkFoodCollision() and only proceeds if it returns false (no food was eaten)
snake.pop();
Removes the last segment (tail) from the snake array, keeping the length constant

checkFoodCollision()

This helper function checks if the snake's head has reached the food. It's used both in draw() to trigger growth and in updateSnake() to prevent tail removal when food is eaten.

function checkFoodCollision() {
  return snake[0].x === food.x && snake[0].y === food.y;
}
Line-by-line explanation (1 lines)
return snake[0].x === food.x && snake[0].y === food.y;
Returns true if the head's x and y coordinates exactly match the food's position, false otherwise. The && operator requires both conditions to be true.

growSnake()

growSnake() is currently empty because the actual growth logic is handled in updateSnake(). When food is eaten, we simply skip the snake.pop() line, leaving an extra segment. You could add explicit growth logic here, like snake.push(snake[snake.length - 1].copy()), to be more explicit.

function growSnake() {
  // When food is eaten, the snake naturally grows because we don't pop the tail
  // You could also explicitly add a new segment at the tail if desired
}
Line-by-line explanation (1 lines)
// When food is eaten, the snake naturally grows because we don't pop the tail
This comment explains that growth is actually handled in updateSnake()—by not removing the tail when food is eaten, the snake gets one segment longer

pickLocation()

pickLocation() ensures food never spawns on top of the snake by using a while loop that keeps trying random positions until it finds one that doesn't collide. This is a common game-dev pattern called 'rejection sampling'.

🔬 This code picks random grid positions. What happens if you remove the '* scale' multiplications so food can appear at any pixel position instead of aligning to the grid?

  // Keep picking until food is not on the snake
  while (true) {
    let x = floor(random(cols)) * scale;
    let y = floor(random(rows)) * scale;
    food = createVector(x, y);
function pickLocation() {
  let cols = floor(width / scale);
  let rows = floor(height / scale);

  // Keep picking until food is not on the snake
  while (true) {
    let x = floor(random(cols)) * scale;
    let y = floor(random(rows)) * scale;
    food = createVector(x, y);

    let onSnake = false;
    for (let i = 0; i < snake.length; i++) {
      if (food.x === snake[i].x && food.y === snake[i].y) {
        onSnake = true;
        break;
      }
    }
    if (!onSnake) {
      break; // Found a valid location
    }
  }
}
Line-by-line explanation (13 lines)

🔧 Subcomponents:

calculation Grid dimensions let cols = floor(width / scale); let rows = floor(height / scale);

Calculates how many grid cells fit horizontally and vertically on the canvas

while-loop Valid food placement while (true) { let x = floor(random(cols)) * scale; let y = floor(random(rows)) * scale; food = createVector(x, y); let onSnake = false; for (let i = 0; i < snake.length; i++) { if (food.x === snake[i].x && food.y === snake[i].y) { onSnake = true; break; } } if (!onSnake) { break; // Found a valid location } }

Repeatedly picks random grid positions until one is found that doesn't overlap with the snake

let cols = floor(width / scale);
Divides the canvas width by the grid cell size to find how many columns fit
let rows = floor(height / scale);
Divides the canvas height by the grid cell size to find how many rows fit
while (true) {
Starts an infinite loop that will continue until a valid food position is found
let x = floor(random(cols)) * scale;
Picks a random integer from 0 to cols-1, then multiplies by scale to get a grid-aligned x coordinate
let y = floor(random(rows)) * scale;
Picks a random integer from 0 to rows-1, then multiplies by scale to get a grid-aligned y coordinate
food = createVector(x, y);
Creates a vector at the random position and stores it in the global food variable
let onSnake = false;
Initializes a flag to track whether this food position overlaps with the snake
for (let i = 0; i < snake.length; i++) {
Loops through every segment of the snake to check if any overlap with the food
if (food.x === snake[i].x && food.y === snake[i].y) {
Checks if this snake segment's position exactly matches the food position
onSnake = true;
Sets the flag to true, indicating that food overlaps with the snake
break;
Exits the for loop early since we've already found an overlap—no need to check further
if (!onSnake) {
Checks if onSnake is false, meaning the food position is valid
break; // Found a valid location
Exits the while loop, ending the function and keeping the current food position

checkWallCollision()

checkWallCollision() is called every frame to detect if the snake has hit a boundary. If any part of the head is outside the canvas, the game ends immediately.

function checkWallCollision() {
  let head = snake[0];
  if (head.x < 0 || head.x >= width || head.y < 0 || head.y >= height) {
    gameOver();
  }
}
Line-by-line explanation (3 lines)

🔧 Subcomponents:

conditional Canvas boundary detection if (head.x < 0 || head.x >= width || head.y < 0 || head.y >= height) {

Checks if the head has moved beyond any of the four canvas edges

let head = snake[0];
Stores a reference to the snake's head (the first segment) in a local variable for easier reading
if (head.x < 0 || head.x >= width || head.y < 0 || head.y >= height) {
Checks if the head's position is outside the canvas: x < 0 (left edge), x >= width (right edge), y < 0 (top edge), or y >= height (bottom edge). The || operator means any one of these conditions triggers the if block.
gameOver();
Calls the gameOver() function to end the game

checkSelfCollision()

checkSelfCollision() detects when the snake crashes into its own body. By starting the loop at index 1, we avoid checking the head against itself. Once a collision is found, we break out of the loop since there's no need to check further.

function checkSelfCollision() {
  let head = snake[0];
  for (let i = 1; i < snake.length; i++) {
    if (head.x === snake[i].x && head.y === snake[i].y) {
      gameOver();
      break;
    }
  }
}
Line-by-line explanation (5 lines)

🔧 Subcomponents:

for-loop Body segment collision check for (let i = 1; i < snake.length; i++) { if (head.x === snake[i].x && head.y === snake[i].y) { gameOver(); break; } }

Loops through all body segments (starting from index 1, skipping the head) and checks if any overlap with the head

let head = snake[0];
Stores a reference to the snake's head for comparison
for (let i = 1; i < snake.length; i++) {
Loops through all segments starting from index 1 (the first body segment after the head). Starting at 1 prevents the head from colliding with itself.
if (head.x === snake[i].x && head.y === snake[i].y) {
Checks if the head's position exactly matches this body segment's position
gameOver();
Ends the game if a collision is detected
break;
Exits the loop early since the game is already over

gameOver()

gameOver() is called when the snake hits a wall or itself. It sets the game state flag and stops the draw loop to freeze gameplay.

function gameOver() {
  gameIsOver = true;
  noLoop(); // Stop the draw loop
}
Line-by-line explanation (2 lines)
gameIsOver = true;
Sets the global gameIsOver flag to true, signaling that the game has ended
noLoop(); // Stop the draw loop
Calls p5.js's noLoop() function, which stops draw() from running, freezing the game state

displayGameOver()

displayGameOver() is called from draw() when gameIsOver is true. It displays three lines of text at different sizes and positions, overlaying the game over state on the canvas.

function displayGameOver() {
  fill(255, 0, 0); // Red text
  textSize(64);
  textAlign(CENTER, CENTER);
  text("GAME OVER", width / 2, height / 2 - 40);
  textSize(32);
  text("Score: " + score, width / 2, height / 2 + 20);
  textSize(24);
  text("Press R to Restart", width / 2, height / 2 + 80);
}
Line-by-line explanation (8 lines)
fill(255, 0, 0); // Red text
Sets the text color to red
textSize(64);
Sets the font size for the next text() call to 64 pixels
textAlign(CENTER, CENTER);
Aligns the text to be centered both horizontally and vertically
text("GAME OVER", width / 2, height / 2 - 40);
Displays 'GAME OVER' in large text at the center of the canvas, positioned slightly above the vertical center
textSize(32);
Reduces the font size to 32 pixels for the next text
text("Score: " + score, width / 2, height / 2 + 20);
Displays the final score at medium size, centered
textSize(24);
Reduces the font size to 24 pixels for the restart instruction
text("Press R to Restart", width / 2, height / 2 + 80);
Displays instructions to press R, positioned below the score

keyPressed()

keyPressed() is called whenever a key is pressed. It updates the direction vector based on arrow key input, but includes safety checks to prevent the snake from reversing directly into itself—a common pitfall in snake games. The 'R' key handling lets players restart after a game over.

🔬 These conditions prevent the snake from reversing (e.g., direction.x !== 1 stops a left move when moving right). What happens if you remove these reversal checks, like changing 'direction.x !== 1' to 'true'? Can you make the snake crash into itself?

  if (keyCode === LEFT_ARROW && direction.x !== 1) {
    direction.set(-1, 0);
  } else if (keyCode === RIGHT_ARROW && direction.x !== -1) {
    direction.set(1, 0);
  } else if (keyCode === UP_ARROW && direction.y !== 1) {
    direction.set(0, -1);
  } else if (keyCode === DOWN_ARROW && direction.y !== -1) {
    direction.set(0, 1);
function keyPressed() {
  // Arrow keys control direction, but prevent immediate reversal
  // e.g., if moving right, can't immediately move left
  if (keyCode === LEFT_ARROW && direction.x !== 1) {
    direction.set(-1, 0);
  } else if (keyCode === RIGHT_ARROW && direction.x !== -1) {
    direction.set(1, 0);
  } else if (keyCode === UP_ARROW && direction.y !== 1) {
    direction.set(0, -1);
  } else if (keyCode === DOWN_ARROW && direction.y !== -1) {
    direction.set(0, 1);
  } else if (keyCode === 82 && gameIsOver) { // 'R' key to restart
    restartGame();
  }
}
Line-by-line explanation (7 lines)

🔧 Subcomponents:

conditional Arrow key direction handling if (keyCode === LEFT_ARROW && direction.x !== 1) { direction.set(-1, 0); } else if (keyCode === RIGHT_ARROW && direction.x !== -1) { direction.set(1, 0); } else if (keyCode === UP_ARROW && direction.y !== 1) { direction.set(0, -1); } else if (keyCode === DOWN_ARROW && direction.y !== -1) { direction.set(0, 1); }

Updates the direction vector based on arrow key input while preventing the snake from reversing into itself

conditional Restart key handling } else if (keyCode === 82 && gameIsOver) { // 'R' key to restart restartGame(); }

Detects when 'R' is pressed after game over and resets the game

if (keyCode === LEFT_ARROW && direction.x !== 1) {
Checks if the left arrow was pressed AND the snake is not currently moving right (direction.x !== 1). This prevents the snake from instantly reversing and colliding with itself.
direction.set(-1, 0);
Sets the direction vector to (-1, 0), meaning move left (negative x) with no vertical movement
} else if (keyCode === RIGHT_ARROW && direction.x !== -1) {
Checks if the right arrow was pressed AND the snake is not moving left, then sets direction to (1, 0)
} else if (keyCode === UP_ARROW && direction.y !== 1) {
Checks if the up arrow was pressed AND the snake is not moving down, then sets direction to (0, -1)
} else if (keyCode === DOWN_ARROW && direction.y !== -1) {
Checks if the down arrow was pressed AND the snake is not moving up, then sets direction to (0, 1)
} else if (keyCode === 82 && gameIsOver) { // 'R' key to restart
Checks if the 'R' key (keyCode 82) was pressed AND the game is currently over
restartGame();
Calls the restartGame() function to reset the game state and resume play

restartGame()

restartGame() is called when the player presses 'R' after a game over. It mirrors the initialization in setup(), resetting all game state variables and restarting the draw loop with loop().

function restartGame() {
  score = 0;
  gameIsOver = false;
  direction.set(0, 0); // Reset direction
  frameRate(10); // Reset speed

  // Reinitialize snake
  snake = [];
  let headX = floor(width / 2 / scale) * scale;
  let headY = floor(height / 2 / scale) * scale;
  snake.push(createVector(headX, headY));
  snake.push(createVector(headX - scale, headY));
  snake.push(createVector(headX - 2 * scale, headY));

  pickLocation(); // Place new food
  loop(); // Resume the draw loop
}
Line-by-line explanation (10 lines)

🔧 Subcomponents:

calculation Game state initialization score = 0; gameIsOver = false; direction.set(0, 0); // Reset direction frameRate(10); // Reset speed

Resets all game variables to their starting values

calculation Snake reinitialization snake = []; let headX = floor(width / 2 / scale) * scale; let headY = floor(height / 2 / scale) * scale; snake.push(createVector(headX, headY)); snake.push(createVector(headX - scale, headY)); snake.push(createVector(headX - 2 * scale, headY));

Creates a new snake array with three segments centered on the canvas

score = 0;
Resets the score to zero
gameIsOver = false;
Sets gameIsOver back to false, allowing the game to run again
direction.set(0, 0); // Reset direction
Resets the direction vector to (0, 0) so the snake doesn't move until a key is pressed
frameRate(10); // Reset speed
Resets the frame rate to 10, ensuring the snake moves at the original speed
snake = [];
Creates a new empty snake array, discarding the old segments
let headX = floor(width / 2 / scale) * scale;
Calculates the center x position for the new snake head
let headY = floor(height / 2 / scale) * scale;
Calculates the center y position for the new snake head
snake.push(createVector(headX, headY)); snake.push(createVector(headX - scale, headY)); snake.push(createVector(headX - 2 * scale, headY));
Adds three new segments to create the starting snake at the center of the canvas
pickLocation(); // Place new food
Calls pickLocation() to place food on the grid
loop(); // Resume the draw loop
Calls p5.js's loop() function to resume the draw loop, which was stopped by noLoop() when the game ended

windowResized()

windowResized() is a p5.js callback that runs whenever the browser window is resized. In this sketch, it resizes the canvas and re-centers the snake and food if the game is active, ensuring the game remains playable at any window size.

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
  // Called when preview panel is resized
  // Re-center snake and food if the canvas size changes drastically
  if (!gameIsOver) {
    let headX = floor(width / 2 / scale) * scale;
    let headY = floor(height / 2 / scale) * scale;
    snake[0].set(headX, headY);
    pickLocation();
  }
}
Line-by-line explanation (5 lines)

🔧 Subcomponents:

conditional Responsive canvas resizing if (!gameIsOver) { let headX = floor(width / 2 / scale) * scale; let headY = floor(height / 2 / scale) * scale; snake[0].set(headX, headY); pickLocation(); }

Re-centers the snake's head and places new food if the window is resized during active gameplay

resizeCanvas(windowWidth, windowHeight);
Calls p5.js's resizeCanvas() function to update the canvas dimensions to match the new window size
if (!gameIsOver) {
Only re-centers the snake if the game is active (not in a game-over state)
let headX = floor(width / 2 / scale) * scale; let headY = floor(height / 2 / scale) * scale;
Recalculates the center position based on the new canvas dimensions
snake[0].set(headX, headY);
Moves the snake's head to the new center position
pickLocation();
Places food at a new random location on the resized canvas

📦 Key Variables

snake array

An array of p5.Vector objects representing each segment of the snake's body. The first element (snake[0]) is the head.

let snake = [];
food object

A p5.Vector storing the current position of the food on the grid. Updated by pickLocation().

let food;
score number

Tracks the player's score, incremented by 1 each time food is eaten.

let score = 0;
scale number

The size of each grid cell in pixels. Controls the size of snake segments, food, and movement distance per frame.

let scale = 20;
direction object

A p5.Vector storing the current movement direction. x and y are each -1, 0, or 1.

let direction;
gameIsOver boolean

A flag indicating whether the game has ended. When true, the game-over screen is displayed instead of normal gameplay.

let gameIsOver = false;

🔧 Potential Improvements (5)

Here are some ways this code could be enhanced:

BUG keyPressed() and updateSnake() interaction

If the player presses two arrow keys quickly (e.g., UP then LEFT), the second input can reverse the snake into itself before the next updateSnake() call, causing immediate self-collision. This happens because the direction is set before the next movement occurs.

💡 Store the next intended direction separately and only apply it in updateSnake() after checking for valid moves. This is called 'input buffering' and is standard in Snake games.

BUG checkWallCollision()

The collision boundary check uses >= for the right edge but < 0 for the left. For consistency and clarity, both should check against the grid-aligned boundaries to avoid off-by-one errors as the snake grows.

💡 Consider using a more explicit boundary check: if (head.x + scale > width || head.x < 0 || head.y + scale > height || head.y < 0) to account for the snake segment's size.

PERFORMANCE pickLocation() while loop

As the snake grows longer (especially near 400+ segments), the while loop in pickLocation() may iterate many times before finding a valid spot, slowing down the game.

💡 Consider stopping the game when the snake reaches a certain length, or use a more efficient algorithm (e.g., maintain a list of occupied cells instead of looping through the entire snake each time).

STYLE checkSelfCollision() and checkWallCollision()

Both functions set head = snake[0] at the start. This logic could be simplified or merged since they're called sequentially.

💡 Consider creating a single 'updateGameState()' function that checks all collisions at once, making the code more modular and easier to maintain.

FEATURE restartGame() and setup()

Snake initialization code is duplicated in both setup() and restartGame(). Any changes to starting conditions must be updated in two places.

💡 Extract the snake initialization into a separate helper function like initializeSnake() and call it from both setup() and restartGame() to reduce code duplication.

🔄 Code Flow

Code flow showing setup, draw, updatesnake, checkfoodcollision, growsnake, picklocation, checkwallcollision, checkselfcollision, gameover, displaygameover, keypressed, restartgame, windowresized

💡 Click on function names in the diagram to jump to their code

graph TD start[Start] --> setup[setup] setup --> draw[draw loop] draw --> game-over-check[Game Over Check] game-over-check -->|if gameIsOver| displaygameover[displayGameOver] game-over-check -->|else| updatesnake[updateSnake] updatesnake --> food-collision[Food Collision] food-collision -->|if food eaten| growsnake[growSnake] food-collision -->|else| growth-logic[Growth Logic] updatesnake --> head-calculation[Head Calculation] updatesnake --> direction-check[Direction Check] updatesnake --> snake-drawing-loop[Snake Drawing Loop] snake-drawing-loop -->|for each segment| draw draw --> boundary-check[Boundary Check] boundary-check -->|if hit wall| gameover[gameOver] draw --> self-collision-loop[Self Collision Loop] self-collision-loop -->|if hit self| gameover draw --> keypressed[keyPressed] keypressed --> direction-input[Direction Input] direction-input -->|if arrow key pressed| updatesnake keypressed --> restart-input[Restart Input] restart-input -->|if 'R' pressed| restartgame[restartGame] restartgame --> state-reset[State Reset] state-reset --> snake-reinit[Snake Reinitialization] snake-reinit --> setup draw --> windowresized[windowResized] windowresized --> canvas-resize[Canvas Resize] canvas-resize -->|if game active| setup click setup href "#fn-setup" click draw href "#fn-draw" click updatesnake href "#fn-updatesnake" click checkfoodcollision href "#fn-checkfoodcollision" click growsnake href "#fn-growsnake" click picklocation href "#fn-picklocation" click checkwallcollision href "#fn-checkwallcollision" click checkselfcollision href "#fn-checkselfcollision" click gameover href "#fn-gameover" click displaygameover href "#fn-displaygameover" click keypressed href "#fn-keypressed" click restartgame href "#fn-restartgame" click windowresized href "#fn-windowresized" click snake-initialization href "#sub-snake-initialization" click game-over-check href "#sub-game-over-check" click food-collision href "#sub-food-collision" click snake-drawing-loop href "#sub-snake-drawing-loop" click direction-check href "#sub-direction-check" click head-calculation href "#sub-head-calculation" click growth-logic href "#sub-growth-logic" click grid-calculation href "#sub-grid-calculation" click food-placement-loop href "#sub-food-placement-loop" click boundary-check href "#sub-boundary-check" click self-collision-loop href "#sub-self-collision-loop" click direction-input href "#sub-direction-input" click restart-input href "#sub-restart-input" click state-reset href "#sub-state-reset" click snake-reinit href "#sub-snake-reinit" click canvas-resize href "#sub-canvas-resize"

❓ Frequently Asked Questions

What visual elements are featured in the p5.js sketch titled 'Sketch 2026-03-04 02:18'?

The sketch visually represents a snake game, featuring a green snake that grows as it consumes pink food, with a score displayed in the corner.

How can users interact with this snake game sketch?

Users can control the direction of the snake using keyboard inputs, guiding it to eat food while avoiding walls and its own body.

What creative coding concepts does this p5.js sketch illustrate?

This sketch demonstrates concepts such as game loop mechanics, collision detection, and dynamic object growth in a simple grid-based environment.

Preview

Sketch 2026-03-04 02:18 - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of Sketch 2026-03-04 02:18 - Code flow showing setup, draw, updatesnake, checkfoodcollision, growsnake, picklocation, checkwallcollision, checkselfcollision, gameover, displaygameover, keypressed, restartgame, windowresized
Code Flow Diagram