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 gridscore- 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 andcols(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
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()andfloor()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
whileloops to ensure valid conditions