Build your own version of the classic arcade game, Snake — where a hungry snake slithers around the board, gobbling up apples and growing longer with each bite! This project will guide you step-by-step through building the game from the ground up using HTML, CSS, JavaScript, and jQuery.
Along the way, you’ll practice essential programming skills like keyboard input handling, data modeling with objects and arrays, screen updates using helper functions, and collision detection logic.
- How to build a full project from start to finish using HTML, CSS, and JavaScript
- How to model game data using arrays and objects
- How to handle real-time input with keyboard events
- How to draw and update elements dynamically with jQuery
- How to break code into helper functions for clarity and reuse
Table of Contents
- Overview
- Project Grading
- Part 1 - HTML & CSS
- Part 2 - Modeling Data & jQuery
- Part 3 - Completing the Game's Logic
- TODO 7: Change the snake's direction
- TODO 8: Make the head move
- TODO 9: Check for collisions with the wall
- TODO 10: Check for collisions with the apple
- TODO 11: Move the body
- TODO 12: Handle Apple Collisions
- TODO 13: Check for snake collisions with itself
- TODO 14: Make sure our Apple is placed only in available positions
- 🌟 Extra Challenges
- 📤 Push Your Work
Your goal is to build a playable Snake game that runs in the browser. You'll start by building a static HTML and CSS layout, then use JavaScript and jQuery to animate the snake, track score, detect collisions, and spawn apples. Once your game works, try out the optional challenges to add new features like color changes or competitive gameplay!
- Become comfortable writing and linking together HTML, CSS, and JavaScript files
- Practice using jQuery to create and manipulate elements on the screen
- Store and update data using arrays and objects
- Use keyboard inputs to control behavior in real-time
- Organize code into clearly defined helper functions
To save your progress on GitHub, use the following commands in your terminal:
git add -A
git commit -m "finished setup for snake"
git push
Your project is graded based on completion of each major step. Make sure each TODO is fully implemented and functional before moving on to the next!
Section / TODO | Description | Points |
---|---|---|
Part 1 | Set up HTML, CSS, and JavaScript | 7 |
TODO 4 | Create the apple | 4 |
TODO 5 | Create the snake | 4 |
TODO 6 | Create and configure the game loop | 5 |
TODO 7 | Handle keyboard input and set direction | 10 |
TODO 8 | Move the snake’s head based on direction | 10 |
TODO 9 | Detect wall collisions | 10 |
TODO 10 | Detect apple collisions | 10 |
TODO 11 | Add new snake segment on apple collision | 10 |
TODO 12 | Make the snake’s body follow the head | 10 |
TODO 13 | Detect collisions with the snake itself | 10 |
TODO 14 | Ensure apple spawns only in empty positions | 10 |
- Learn how the various files in a program are linked together in an index.html file
- Use jQuery to dynamically create HTML elements and modify their properties
- Practice using Objects and Arrays to model game components
- Learn about keyboard inputs
- Learn about Asynchronous function calls
- Learn how to organize code in a program
🎯 Goal: Set up the main structure of the page, including the board and score display elements.
-
Open your
index.html
file. -
Inside the
<body>
section, add the following three elements:- A
<div>
with the IDboard
- An
<h1>
with the IDscore
and the text “Score: 0” - Another
<h1>
with the IDhighScore
and the text “High Score: 0”
- A
<div id="board"></div>
<h1 id="score">Score: 0</h1>
<h1 id="highScore">High Score: 0</h1>
- Save your file, and preview it in the browser by opening
index.html
or using Live Server.
You’re creating the static parts of the game — the things that will appear on screen and stay in place while the game runs. The board will eventually contain the snake and apples, and the score displays will update as you play.
The snake and apple elements will be dynamic — they’ll be added later using JavaScript.
- "Score: 0" and "High Score: 0" should both appear at the top of the page.
- The board itself won't be visible yet — that's coming up in the next step.
- If you don’t see the scores:
- Double-check your HTML syntax
- Make sure the IDs and text match exactly
🎯 Goal: Link your stylesheet and make the game board visible with a border.
-
Open your
index.html
file. -
Inside the
<head>
section, add the following line to connect your CSS:
<link rel="stylesheet" type="text/css" href="index.css" />
- Save your file, then preview your game in the browser.
Without linking the CSS file, your page will load — but it won’t look like anything! By adding this line, you're telling the browser to also load your styles so the layout and visuals appear as intended.
- You should now see:
- A square game board with a border
- The board placed below the score displays
- The board should be empty (no snake or apple yet)
- If the board doesn’t appear:
- Make sure your
<link>
tag is inside the<head>
- Double-check the filename: it must be exactly
index.css
- Make sure your
🎯 Goal: Link your JavaScript and jQuery files so your game logic can run properly.
-
Open your
index.html
file. -
At the very bottom of the file, just before the closing
</html>
tag, add this line to connect your JavaScript:
<script src="index.js"></script>
⏳ Why put it at the bottom?
JavaScript often needs to interact with HTML elements — and if those elements haven’t loaded yet, the code will break. Putting the script at the end ensures the page loads first, then runs the JavaScript.
- Still in the
<head>
section, add one more script tag to load jQuery:
<script src="jquery.min.js"></script>
💡 This must come before your
index.js
file loads — otherwise your code will try to use jQuery before it exists.
- Save your file and reload the page in the browser.
If your game isn’t working as expected, check these common problems:
Problem | Fix |
---|---|
❌ $ is not defined |
Make sure jQuery is loaded before index.js |
❌ Page looks unstyled | Check your <link> to index.css in the <head> section |
❌ Nothing happens when pressing keys | Make sure index.js is linked after the HTML, at the bottom |
❌ Snake or apple don’t appear later on | Open the console and check for typos or JavaScript errors |
- Open your page in the browser and then open the Console (right-click → Inspect → Console).
- You should see no jQuery-related errors.
- If you see something like
"$ is not defined"
:- Confirm that
jquery.min.js
is linked correctly before yourindex.js
- Double-check for typos in the script tag paths
- Confirm that
🎯 Goal: Show an apple on a random empty square.
- Add
const apple = {};
at the top ofindex.js
(inside Game Variables). - Write a helper
makeApple()
that:- Builds a new
<div>
with classapple
and adds it to#board
. - Finds a free spot using
getRandomAvailablePosition()
. - Stores that row / column on the
apple
object. - Calls
repositionSquare(apple)
to move it on screen.
- Builds a new
- In
init()
(look for// TODO 4-2
) callmakeApple();
.
-
Declare an empty
apple
object near the top of yourindex.js
file.🔍 Search for the section labeled
"Game Variables"
and add:var apple = {};
-
Create the
makeApple()
function in the helper functions section. This function creates the apple element and gives it a random location on the board.Paste the following inside the function body:
/* Create an HTML element for the apple using jQuery. Then find a random * position on the board that is not occupied and position the apple there. */ function makeApple() { // make the apple jQuery Object and append it to the board apple.element = $("<div>").addClass("apple").appendTo(board); // get a random available row/column on the board var randomPosition = getRandomAvailablePosition(); // initialize the row/column properties on the Apple Object apple.row = randomPosition.row; apple.column = randomPosition.column; // position the apple on the screen repositionSquare(apple); }
-
Call the
makeApple()
function inside theinit()
function where TODO 4-2 is noted.
You’re using jQuery to create a new <div>
with the class apple
, then randomly placing it on the board. This makes the apple appear visually and stores its location in code so you can detect when the snake reaches it.
The helper function getRandomAvailablePosition()
handles the logic to avoid putting the apple on top of the snake — we'll improve it later in the project.
- A small red square (the apple) should now appear somewhere on your game board.
- Refreshing the page should place the apple in a new random spot.
- If nothing appears:
- Check your
makeApple()
function for typos - Confirm the function is being called in
init()
- Make sure the apple has the correct CSS class (
apple
)
- Check your
🎯 Goal: Build the starting snake on the board.
- Near the top of
index.js
addconst snake = {};
. - Create a helper
makeSnakeSquare(row, column)
that:- Builds a
<div>
with classsnake
and positions it usingrepositionSquare()
. - Pushes the square into
snake.body
and updatessnake.tail
. - Gives the first square the id
snake-head
.
- Builds a
- Inside
init()
(look for// TODO 5-2
) set up the first three squares:
snake.body = [];
makeSnakeSquare(10,10);
makeSnakeSquare(10,9);
makeSnakeSquare(10,8);
snake.head = snake.body[0];
Check: You should see a three-square snake in the middle of the board.
-
Declare an empty
snake
object in the"Game Variables"
section at the top of yourindex.js
file:
const snake = {};
2. **Create the `makeSnakeSquare(row, column)` function** in the **helper functions** section. This function builds one segment of the snake and places it on the board.
Fill in the function like this:
```js
function makeSnakeSquare(row, column) {
// initialize a new snakeSquare Object
const snakeSquare = {};
// make the snakeSquare element and add it to the board
snakeSquare.element = $("<div>").addClass("snake").appendTo(board);
// assign the row and column position
snakeSquare.row = row;
snakeSquare.column = column;
// set the snake’s position visually
repositionSquare(snakeSquare);
// if this is the head, give it a unique ID
if (snake.body.length === 0) {
snakeSquare.element.attr("id", "snake-head");
}
// add the square to the snake’s body and update the tail
snake.body.push(snakeSquare);
snake.tail = snakeSquare;
}
🧠
makeSnakeSquare()
handles everything needed to create a new body piece, place it on the screen, and track its position in code.
-
Initialize the snake inside the
init()
function (at TODO 5-2):snake.body = []; // Start with an empty body makeSnakeSquare(10, 10); // Create the first square in the middle of the board makeSnakeSquare(10, 9); // Create a second square to the left of the first makeSnakeSquare(10, 8); // Create a third square to the left of the second snake.head = snake.body[0]; // Mark the first segment as the head
CLICK TO READ about the Snake's data
We can refer to each part of the snake as a
snakeSquare
Object which will have the following properties:
snakeSquare.row
: A reference to the row where thesnakeSquare
currently exists.snakeSquare.column
: A reference to the column where thesnakeSquare
currently exists.snakeSquare.direction
: A reference to the direction that this particularsnakeSquare
is currently moving in.Because the Snake is made up of multiple
snakeSquares
that are in a particular order, we can model the Snake's body as an Array. It will also be useful to have a quick reference for the head and tail of the snake.This data will be stored in the
snake
Object:
snake.body
: An Array containing allsnakeSquare
Objects.snake.head
: Reference to the jQuerysnakeSquare
Object at the head of the snake. The same assnake.body[0]
but easier to read.snake.tail
: Reference to the jQuerysnakeSquare
Object at the end of the snake. The same assnake.body[snake.body.length - 1]
but easier to read.Most of this will be handled automatically, but first you'll have to create and call the functions that do that.
- Three green squares should appear on the board — these are the body segments of your snake. The head is a darker green square.
- If you refresh the page, they should appear in the same spot (head in row 10, column 10).
- If nothing appears:
- Confirm that
makeSnakeSquare()
is being called ininit()
three times. - Make sure the CSS class
snake
exists and has visible styles.
- Confirm that
🎯 Goal: Start the game's update cycle so the snake can move and the screen can refresh in real time.
- In your
init()
function, find the comment labeled// TODO 5a
and add the following line:
updateInterval = setInterval(update, 100);
🌀 This starts a game loop — a function that runs repeatedly on a timer. In this case, the
update()
function will run every 100 milliseconds (10 times per second).
- Find the
update()
function in your code (it should already be declared), and fill in its body like this:
function update() {
if (started) {
moveSnake();
}
if (hasHitWall() || hasCollidedWithSnake()) {
endGame();
}
if (hasCollidedWithApple()) {
handleAppleCollision();
}
}
Every frame of your game — just like in an animation — the following happens:
- The snake updates its position.
- The game checks for any collisions.
- The screen redraws based on new positions.
The setInterval()
function makes that happen repeatedly and smoothly, giving us a live game experience.
- Open your browser and use Live Server to preview your game.
- Open the Developer Tools Console (Right-click → Inspect → Console).
- Inside the
update()
function, temporarily add this line at the top:
console.log("updating...");
-
Refresh the browser. You should see “updating...” printed every 100 milliseconds — this means your game loop is working!
-
Once you've confirmed it works, delete the
console.log
so your console doesn’t get flooded during gameplay.
Congratulations! You've completed the foundation of your Snake game. Here's what you accomplished:
✅ Data Modeling: Created Objects to represent the snake and apple with properties like position and direction
✅ Game Loop: Set up setInterval()
to create smooth animation by calling update()
10 times per second
✅ Visual Elements: Used jQuery to create and position HTML elements for the snake and apple
✅ Game State: Established the basic structure for tracking score and game components
What's working now:
- Apple appears randomly on the board
- Snake head is visible and positioned correctly
- Game loop is running (even though movement isn't implemented yet)
Coming up in Part 3: You'll add movement, collision detection, and all the game logic that makes Snake actually playable!
🎯 Goal: Detect keyboard input and update the snake's direction accordingly.
-
Find the
handleKeyDown(event)
function in the helper functions section near the bottom ofindex.js
. -
Inside the function, add the following lines:
activeKey = event.which;
console.log(activeKey);
⌨️ This saves the key code of the last key pressed and logs it to the console so you can check if input is working correctly.
-
Open your game in the browser and press the arrow keys. In the console, you should see numbers like:
37
for Left38
for Up39
for Right40
for Down
-
Find the function
checkForNewDirection()
. This is where you’ll update the snake's movement based on which arrow key was pressed. -
Add this
if
statement to check for the LEFT arrow key:
if (activeKey === KEY.LEFT) {
snake.head.direction = "left";
}
- Now it's your turn!
👉 Using the pattern above, addelse if
statements to check for the other three keys:KEY.RIGHT
,KEY.UP
, andKEY.DOWN
.
Each one should setsnake.head.direction
to the matching direction string.
- Press any arrow key while your game is running.
- You should see the direction change in the console (e.g.
"left"
,"up"
, etc.). - If you see key codes but not directions:
- Make sure
checkForNewDirection()
is being called insidemoveSnake()
- Double-check the
KEY
values and thatactiveKey
is being updated
- Make sure
- Once it’s working, comment out the
console.log()
line insidehandleKeyDown()
to keep your console clean.
console.log() is your debugging superpower! Here are smart ways to use it throughout this project:
Position Debugging:
console.log("Snake head at:", snake.head.row, snake.head.column); console.log("Apple at:", apple.row, apple.column);Direction Debugging:
console.log("Snake direction:", snake.head.direction); console.log("Active key pressed:", activeKey);Collision Debugging:
console.log("Hit wall?", hasHitWall()); console.log("Hit apple?", hasCollidedWithApple());Body Movement Debugging:
// In your loop, add: console.log( "Moving piece", i, "from", snakeSquare.row, snakeSquare.column, "to", nextRow, nextColumn );Pro Tip: Add console.log statements when something isn't working, then remove them once it's fixed!
🎯 Goal: Update the snake's head position on each frame based on its current direction.
-
Find the
moveSnake()
function and locate the comment// TODO 7
. -
Add this
if
statement directly under the comment to handle movement to the left:
if (snake.head.direction === "left") {
snake.head.column = snake.head.column - 1;
}
🧠 This decreases the column number by 1, moving the head one square to the left. (Remember: columns increase from left to right.)
- Now it's your turn!
👉 Add three moreelse if
statements to move the head in the other directions:"right"
should increase the column by 1"up"
should decrease the row by 1"down"
should increase the row by 1
🔁 Don’t forget: The
row
value increases as you go down the screen, andcolumn
increases as you go right.
- Leave the call to
repositionSquare(snake.head);
at the end so the snake head updates on screen.
- Open your game in the browser and use the arrow keys to control the snake.
- The snake should move one square per update in whichever direction you press.
- If it only moves left:
- Check that your new
else if
blocks are insidemoveSnake()
- Make sure
snake.head.direction
is spelled correctly in each condition
- Check that your new
If your snake isn’t moving as expected, try this:
console.log("Head is at:", snake.head.row, snake.head.column);
console.log("Direction:", snake.head.direction);
Watch the console as you press arrow keys — you should see the direction update and the position change accordingly. If you don't, then check where those values are being set and updated in your code.
🎯 Goal: End the game if the snake moves off the board.
-
Find the
hasHitWall()
function. This function should returntrue
if the snake’s head is outside the bounds of the game board, andfalse
otherwise. -
Inside the function, check whether the head's row or column is beyond the board limits:
snake.head.row
should be between0
andROWS - 1
snake.head.column
should be between0
andCOLUMNS - 1
-
If either value is outside that range, return
true
to signal a collision. Otherwise, returnfalse
.
🧱 This function is used in the game loop to detect when the snake hits a wall — and if it does, the game ends using
endGame()
.
- The board is a grid with a fixed number of rows (
ROWS
) and columns (COLUMNS
). - If
snake.head.row
is less than 0 or greater than or equal toROWS
, the snake is off the top or bottom edge. - If
snake.head.column
is less than 0 or greater than or equal toCOLUMNS
, the snake is off the left or right edge.
- Run your game and drive the snake into all four walls.
- The game should stop, and the snake should no longer move.
- If the game keeps going:
- Make sure
hasHitWall()
returnstrue
when the head is out of bounds - Confirm that
hasHitWall()
is being called inside theupdate()
function
- Make sure
🎯 Goal: Detect when the snake’s head reaches the apple’s position.
-
Find the
hasCollidedWithApple()
function. -
Inside this function, compare the head’s row and column to the apple’s row and column.
-
If both the row and the column match, return
true
.
Otherwise, returnfalse
.
🍎 This tells the game when the snake has reached the apple so we can grow the snake and reposition the apple in the next step.
- Run your game and try to guide the snake toward the apple.
- When the snake touches the apple, the apple should move to a new position and the score should increase.
- If the apple doesn’t move:
- Make sure
hasCollidedWithApple()
is being called inside theupdate()
function - Check that the row and column comparisons are correct
- Make sure
🎯 Goal: Update each segment of the snake’s body so it follows the head.
-
Find the
moveSnake()
function, where the snake’s movement logic is happening. -
Just before moving the head, add this loop to update the rest of the body:
for ( /* code to loop through the indexes of the snake.body Array*/ ) {
var snakeSquare = "???";
var nextSnakeSquare = "???";
var nextRow = "???";
var nextColumn = "???";
var nextDirection = "???";
snakeSquare.direction = nextDirection;
snakeSquare.row = nextRow;
snakeSquare.column = nextColumn;
repositionSquare(snakeSquare);
}
🧠 You are making each square “chase” the one ahead of it — just like how a real snake’s body moves.
- Replace each
???
with the correct property name so the movement copies over correctly.
When moving the body, you need to loop backward through the snake.body
array. This is because each segment needs to take the position of the segment in front of it. If you loop forward, you’ll overwrite positions before they can be used!
Below are two images. The first shows the snake movement with correct backward looping, and the second shows what happens if you loop forward. Notice how the snake seems to shrink in the incorrect version!
- Run your game and press arrow keys to move.
- You should see:
- The snake’s head moves one square in the chosen direction
- The body segments follow the exact path of the head
- If the snake “breaks apart” or overlaps weirdly:
- Double-check that you're looping backward
- Make sure you're updating the
.row
,.column
, and.direction
correctly
🎯 Goal: Add a new snake segment in the correct position after eating an apple.
-
Find the
handleAppleCollision()
function.
Scroll down until you find the line starting withvar row = snake.tail.row;
— that’s your starting point. -
Replace the default
0
values with logic that calculates the correctrow
andcolumn
based on the tail's current direction.Use conditionals like this:
if (snake.tail.direction === "left") {
column++; // Place the new piece to the RIGHT of the tail
}
// Add similar checks for "right", "up", and "down"
🧠 You’re placing the new segment behind the tail — so think in the opposite direction of where the tail is moving, and think if it is moving across rows or columns.
- After calculating the new coordinates, the code should already call
makeSnakeSquare(row, column);
for you.
- Run your game and move the snake into an apple.
- You should see:
- A new green segment appear right behind the tail
- The apple jump to a new position
- If you still see a piece appear at (0, 0):
- Double-check that your logic is actually changing the
row
andcolumn
- Confirm that
snake.tail.direction
is correct and has all four conditionals
- Double-check that your logic is actually changing the
🎯 Goal: End the game if the snake runs into its own body.
-
Find the
hasCollidedWithSnake()
function. -
Inside this function, loop through all the squares in
snake.body
except the head (which is at index0
). -
If the head’s row and column match any other square’s row and column, return
true
.
Otherwise, returnfalse
.
🧠 This check tells us if the head has “crashed” into the body — if so, the game will end via the
update()
loop.
- You’ll need to compare each square’s
.row
and.column
withsnake.head.row
andsnake.head.column
. - Be sure to skip index 0, since that’s the head itself — you don’t want to compare it to itself!
- Run your game and eat a few apples to grow the snake.
- Try turning the snake into its own body.
- You should see:
- The game immediately ends
- The snake stops moving
- The “Game Over” message appears
- If the game doesn’t end:
- Make sure you’re looping through
snake.body
starting at index1
- Check that you’re comparing both the
row
andcolumn
- Make sure you’re looping through
🎯 Goal: Make sure the apple never spawns on top of the snake.
-
Find the
getRandomAvailablePosition()
function. -
You’ll see this basic structure:
var spaceIsAvailable;
var randomPosition = {};
while (!spaceIsAvailable) {
randomPosition.column = Math.floor(Math.random() * COLUMNS);
randomPosition.row = Math.floor(Math.random() * ROWS);
spaceIsAvailable = true;
}
return randomPosition;
-
Your job is to add a check inside the
while
loop to make sure thatrandomPosition
is not already occupied by the snake.🍎 If it is on the snake, set
spaceIsAvailable = false;
so the loop tries again.
- Use a
for
loop inside thewhile
loop to check each square insnake.body
. - Compare each body square’s
row
andcolumn
with therandomPosition
object'srow
andcolumn
. - If both match for any snake square, that means the spot is taken — set
spaceIsAvailable = false;
🧠 This is similar to how you checked for head collisions — but now you're comparing the apple’s spot against every part of the snake (including the head).
This step is tricky to test unless the snake is already large or you’ve played for a long time.
📣 Before moving on, ask your instructor to review your logic.
Make sure your loop checks every piece of the snake and correctly handles collisions.
Pro tip: Once you’ve finished, try logging potential collisions in the console to confirm they’re being caught.
What if your snake changed color each time it grew?
In this challenge, you’ll make your snake cycle through a set of colors — red, orange, yellow, green, blue, and purple — one square at a time. Each new segment should appear in the next color, and then the pattern repeats.
This will turn your snake into a moving rainbow!
- Create a list (array) called
colors
:
["red", "orange", "yellow", "green", "blue", "purple"];
- Add a new variable (like
colorIndex
) to track which color comes next - After you call
makeSnakeSquare()
, immediately apply the background color using:
snake.tail.element.css("backgroundColor", colors[colorIndex]);
- After each new square is created, increase
colorIndex
. If it goes past the end of the list, loop it back to 0.
Let’s make things a bit more interesting.
In this challenge, your snake will start with normal movement — but once your score hits a multiple of 10, the controls will flip:
- Up becomes down
- Left becomes right
- Chaos becomes your new best friend
The controls should continue to swap every time the score passes another multiple of 10.
- Track the current score using the existing score variable
- Add a new boolean variable called
isReversed
and set it tofalse
initially - Inside of the
handleAppleCollision()
function, check if the score is a multiple of 10:- If it is, toggle
isReversed
to its opposite value
- If it is, toggle
- Update the
handleKeyDown()
function to account for theisReversed
state- If
isReversed
istrue
, reverse the direction logic - If
isReversed
isfalse
, use the normal direction logic
- If
When you're completely finished:
- Save all your files
- Open your terminal
- Add, commit, and push your code:
git add .
git commit -m "Completed Snake game"
git push
✅ Confirm that your changes are visible on GitHub.
🧠 Reminder: Always write clear commit messages so your collaborators (and future you!) know what changed.
🎉 Congratulations on finishing the Snake game! 🎉
You've built a fully functional game from scratch using HTML, CSS, and JavaScript. You've also learned how to use jQuery to manipulate HTML elements and how to organize your code using functions.