Skip to main content

Step 4: Food & Growth

Time to add food for the worm to eat! When the worm eats food, it grows longer and new food appears randomly.

Food Variables

Add variables to track the food position and score:

// Grid settings
let cellSize = 20;
let cols, rows;

// Worm
let worm = [];
let xSpeed = 1;
let ySpeed = 0;

// Food
let food;

// Score
let score = 0;

What these do:

  • food - Object storing food's x,y position on the grid
  • score - Tracks how many food items eaten

Creating Random Food

Add a function to place food at a random location:

function createFood() {
food = {
x: floor(random(cols)),
y: floor(random(rows))
};
}

Breaking it down:

Random Position

x: floor(random(cols))
  • random(cols) generates a number between 0 and cols (0 to 20)
  • floor() rounds down to get a whole number
  • Example: random(20) might give 15.7, floor() makes it 15

Why Floor?

Grid positions must be integers (whole numbers). We can't have a segment at position 15.7!

Initialize Food in setup()

Call createFood() when the game starts:

function setup() {
createCanvas(400, 400);
frameRate(10);

// Calculate grid dimensions
cols = width / cellSize;
rows = height / cellSize;

// Initialize worm in the center
let startX = floor(cols / 2);
let startY = floor(rows / 2);
worm.push({x: startX, y: startY});

// Create first food
createFood();
}

Drawing the Food

Add food rendering in draw():

function draw() {
background(50);

// Update worm position
updateWorm();

// Draw grid lines (optional)
stroke(80);
strokeWeight(1);
for (let i = 0; i < cols; i++) {
line(i * cellSize, 0, i * cellSize, height);
}
for (let i = 0; i < rows; i++) {
line(0, i * cellSize, width, i * cellSize);
}

// Draw the food
fill(255, 0, 0); // Red color
noStroke();
rect(food.x * cellSize, food.y * cellSize, cellSize, cellSize);

// Draw the worm
fill(0, 255, 0); // Green color
for (let i = 0; i < worm.length; i++) {
let segment = worm[i];
rect(segment.x * cellSize, segment.y * cellSize, cellSize, cellSize);
}

// Draw score
fill(255);
textSize(20);
textAlign(LEFT);
text('Score: ' + score, 10, 25);
}

Color coding:

  • Red (255, 0, 0) = Food
  • Green (0, 255, 0) = Worm
  • White (255) = Score text

Checking for Food Collision

Update updateWorm() to check if the worm ate the food:

function updateWorm() {
// Get the current head position
let head = worm[worm.length - 1];

// Calculate new head position
let newHead = {
x: head.x + xSpeed,
y: head.y + ySpeed
};

// Add new head to the front
worm.push(newHead);

// Check if worm ate the food
if (newHead.x === food.x && newHead.y === food.y) {
score++;
createFood();
// DON'T remove the tail - worm grows!
} else {
// Remove the tail (normal movement)
worm.shift();
}
}

Breaking it down:

Collision Detection

if (newHead.x === food.x && newHead.y === food.y)
  • Checks if worm's head is at the same position as food
  • === checks for exact equality
  • Both x AND y must match

When Food is Eaten

score++;
createFood();
// DON'T remove the tail
  • Increase score by 1
  • Create new food at random location
  • Key: We don't call worm.shift()!
  • Without removing the tail, the worm gains one segment

Normal Movement

else {
worm.shift();
}
  • If no food eaten, remove tail as usual
  • Worm stays same length
tip

How growth works:

  • Every frame: Add new head (worm.push)
  • Normal: Remove tail (worm.shift) → Same length
  • Eating: Keep tail → Length increases by 1!

This creates smooth growth without extra logic!

Visualizing Growth

Before eating (length = 3):

Food: [F]
Worm: [3][2][1]

When head reaches food:

[F][3][2][1]

After eating (length = 4):

New food appears at random location
[4][3][2][1]
Tail kept! Worm is longer!

Complete Code So Far

// Grid settings
let cellSize = 20;
let cols, rows;

// Worm
let worm = [];
let xSpeed = 1;
let ySpeed = 0;

// Food
let food;

// Score
let score = 0;

function setup() {
createCanvas(400, 400);
frameRate(10);

// Calculate grid dimensions
cols = width / cellSize;
rows = height / cellSize;

// Initialize worm in the center
let startX = floor(cols / 2);
let startY = floor(rows / 2);
worm.push({x: startX, y: startY});

// Create first food
createFood();
}

function draw() {
background(50);

// Update worm position
updateWorm();

// Draw grid lines (optional)
stroke(80);
strokeWeight(1);
for (let i = 0; i < cols; i++) {
line(i * cellSize, 0, i * cellSize, height);
}
for (let i = 0; i < rows; i++) {
line(0, i * cellSize, width, i * cellSize);
}

// Draw the food
fill(255, 0, 0);
noStroke();
rect(food.x * cellSize, food.y * cellSize, cellSize, cellSize);

// Draw the worm
fill(0, 255, 0);
for (let i = 0; i < worm.length; i++) {
let segment = worm[i];
rect(segment.x * cellSize, segment.y * cellSize, cellSize, cellSize);
}

// Draw score
fill(255);
textSize(20);
textAlign(LEFT);
text('Score: ' + score, 10, 25);
}

function updateWorm() {
// Get the current head position
let head = worm[worm.length - 1];

// Calculate new head position
let newHead = {
x: head.x + xSpeed,
y: head.y + ySpeed
};

// Add new head to the front
worm.push(newHead);

// Check if worm ate the food
if (newHead.x === food.x && newHead.y === food.y) {
score++;
createFood();
// DON'T remove the tail - worm grows!
} else {
// Remove the tail (normal movement)
worm.shift();
}
}

function createFood() {
food = {
x: floor(random(cols)),
y: floor(random(rows))
};
}

function keyPressed() {
// Right arrow
if (keyCode === RIGHT_ARROW && xSpeed !== -1) {
xSpeed = 1;
ySpeed = 0;
}
// Left arrow
else if (keyCode === LEFT_ARROW && xSpeed !== 1) {
xSpeed = -1;
ySpeed = 0;
}
// Down arrow
else if (keyCode === DOWN_ARROW && ySpeed !== -1) {
xSpeed = 0;
ySpeed = 1;
}
// Up arrow
else if (keyCode === UP_ARROW && ySpeed !== 1) {
xSpeed = 0;
ySpeed = -1;
}
}

Test It Out!

Run the game and try eating food:

  • ✅ Red square (food) appears randomly
  • ✅ When worm reaches food, score increases
  • ✅ Worm gets longer each time
  • ✅ New food appears after eating
  • ✅ Score displayed in top left

Potential Issue: Food on Worm

Currently, food might appear on top of the worm! Here's an improved createFood():

function createFood() {
let validPosition = false;

while (!validPosition) {
food = {
x: floor(random(cols)),
y: floor(random(rows))
};

// Check if food overlaps with worm
validPosition = true;
for (let i = 0; i < worm.length; i++) {
if (food.x === worm[i].x && food.y === worm[i].y) {
validPosition = false;
break;
}
}
}
}

This ensures food never spawns on the worm!

What You Learned

  • How to use random() and floor() for random positions
  • How to implement collision detection with ===
  • The clever growth trick (skip tail removal)
  • How conditional logic can change game behavior
  • How to display text on canvas
  • How to check if two grid positions match
  • Using while loops to ensure valid conditions