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