Gaming Fundamentals & Snake
A lesson about the basics of game development, including functions, variables, loops, conditionals, more using Snake as an example and a project to work on.
Game Syntax Basics
This lesson aims to teach you a few basic fundamentals of game design using Javascript.
Agenda
- Show off the main components of the game
- Break down some syntax
- Show small components of the game and how they can be modified to create a better product
- Assign Hacks (homework)
Snake Game Outline
The snake game is composed of 2 main components on a Markdown page: style/display and logic.
Markdown is especially helpful for creating games in one file because supports HTML, which allows you to embed both CSS (styling) and Javascript (scripts/game logic).
Skeleton of the Game
Frontmatter
Frontmatter refers to a few lines of code at the beginning of a MarkDown web page that gives the search engine important infromation on how to run the site (title, subdomain, preset styles, etc.). It is very important for styling in MarkDown, as without it the engine would not know how to display the site and just use its defaults. Below is an example of the frontmatter used to run this site!
title: Gaming Fundamentals & Snake comments: true layout: post permalink: /snakegaming description: A lesson about the basics of game development, including functions, variables, loops, conditionals, more using Snake as an example and a project to work on. author: Evan S, West S, Ethan W, Nico D —
CSS Styling (controls the appearance of the page)
CSS defines how the game looks, including layout, colors, and which screens are visible. It also handles user interaction styles like hover effects.
canvas {
border-style: solid;
border-width: 10px;
border-color: #FFFFFF;
}
This gives the game canvas a visible white border, helping players clearly see the game area. CSS also hides or shows screens, like the menu and game-over screens, depending on the game state. Plenty of other parts of the page are defined and styled using CSS, like game over screens or text color for certain interactibles.
HTML Game Container
HTML organizes the elements that make up the game: the canvas, score display, menus, and settings. It provides the structure and interactive elements that the JavaScript controls during gameplay.
<div id="menu" class="py-4 text-light">
<p>Welcome to Snake, press <span
style="background-color: #FFFFFF; color: #000000">space</span> to begin</p>
<a id="new_game" class="link-alert">new game</a>
</div>
This creates the main menu with instructions and a button to start a new game. HTML allows users to interact with the game through buttons, links, and input options.
Game Initialization & Constants
The game sets up essential constants and variables during initialization. Game constants define screen states, block size, and DOM (Document Object Model, look this up) element references for efficient access.
// Canvas & Context
const canvas = document.getElementById("snake");
const ctx = canvas.getContext("2d");
// HTML Game IDs
const SCREEN_SNAKE = 0;
const screen_snake = document.getElementById("snake");
const ele_score = document.getElementById("score_value");
const speed_setting = document.getElementsByName("speed");
const wall_setting = document.getElementsByName("wall");
// HTML Screen IDs (div)
const SCREEN_MENU = -1, SCREEN_GAME_OVER = 1, SCREEN_SETTING = 2;
const screen_menu = document.getElementById("menu");
const screen_game_over = document.getElementById("gameover");
const screen_setting = document.getElementById("setting");
// HTML Event IDs (buttons)
const button_new_game = document.getElementById("new_game");
const button_new_game1 = document.getElementById("new_game1");
const button_new_game2 = document.getElementById("new_game2");
const button_setting_menu = document.getElementById("setting_menu");
const button_setting_menu1 = document.getElementById("setting_menu1");
// Game Control Variables
const BLOCK = 10; // size of block rendering
let SCREEN = SCREEN_MENU;
let snake;
let snake_dir;
let snake_next_dir;
let snake_speed;
let food = {x: 0, y: 0};
let score;
let wall;
Game State Management
The application uses a screen-based state system with constants to control which interface is visible. The showScreen()
function handles all screen transitions by manipulating DOM element visibility.
let showScreen = function(screen_opt){
SCREEN = screen_opt;
switch(screen_opt){
case SCREEN_SNAKE:
screen_snake.style.display = "block";
screen_menu.style.display = "none";
screen_setting.style.display = "none";
screen_game_over.style.display = "none";
break;
case SCREEN_GAME_OVER:
screen_snake.style.display = "block";
screen_menu.style.display = "none";
screen_setting.style.display = "none";
screen_game_over.style.display = "block";
break;
case SCREEN_SETTING:
screen_snake.style.display = "none";
screen_menu.style.display = "none";
screen_setting.style.display = "block";
screen_game_over.style.display = "none";
break;
}
}
Event Handling & Controls Setup
Keyboard input uses event listeners for arrow keys with direction validation to prevent immediate reversal. The spacebar serves as a universal “start/restart” key across different screens. This is how the game listens to your inputs.
window.onload = function(){
// HTML Events to Functions - Button clicks
button_new_game.onclick = function(){newGame();};
button_new_game1.onclick = function(){newGame();};
button_new_game2.onclick = function(){newGame();};
button_setting_menu.onclick = function(){showScreen(SCREEN_SETTING);};
button_setting_menu1.onclick = function(){showScreen(SCREEN_SETTING);};
// Speed setting event listeners
setSnakeSpeed(150);
for(let i = 0; i < speed_setting.length; i++){
speed_setting[i].addEventListener("click", function(){
for(let i = 0; i < speed_setting.length; i++){
if(speed_setting[i].checked){
setSnakeSpeed(speed_setting[i].value);
}
}
});
}
// Wall setting event listeners
setWall(1);
for(let i = 0; i < wall_setting.length; i++){
wall_setting[i].addEventListener("click", function(){
for(let i = 0; i < wall_setting.length; i++){
if(wall_setting[i].checked){
setWall(wall_setting[i].value);
}
}
});
}
// Spacebar detection for game start/restart
window.addEventListener("keydown", function(evt) {
if(evt.code === "Space" && SCREEN !== SCREEN_SNAKE)
newGame();
}, true);
}
Direction Control System
The direction system prevents the snake from immediately reversing into itself while allowing smooth direction changes. Arrow key codes are mapped to direction numbers for easy processing.
let changeDir = function(key){
// Direction mapping: 0=Up, 1=Right, 2=Down, 3=Left
switch(key) {
case 37: // left arrow
if (snake_dir !== 1) // not right
snake_next_dir = 3; // then switch left
break;
case 38: // up arrow
if (snake_dir !== 2) // not down
snake_next_dir = 0; // then switch up
break;
case 39: // right arrow
if (snake_dir !== 3) // not left
snake_next_dir = 1; // then switch right
break;
case 40: // down arrow
if (snake_dir !== 0) // not up
snake_next_dir = 2; // then switch down
break;
}
}
Game Initialization & Setup
The newGame()
function resets all game variables and initializes the snake’s starting position. This function is called whenever a new game begins, ensuring a clean slate.
let newGame = function(){
// Switch to game screen and focus for keyboard input
showScreen(SCREEN_SNAKE);
screen_snake.focus();
// Reset game state
score = 0;
altScore(score);
// Initialize snake with starting position
snake = [];
snake.push({x: 0, y: 15}); // Starting position
snake_next_dir = 1; // Moving right initially
// Place first food on canvas
addFood();
// Activate keyboard controls for canvas
canvas.onkeydown = function(evt) {
changeDir(evt.keyCode);
}
// Start the main game loop
mainLoop();
}
Core Game Loop Architecture
The mainLoop()
function implements the classic game loop pattern: update game state, check collisions, render graphics, then recursively call itself with setTimeout. This creates smooth, continuous gameplay at a controlled frame rate.
let mainLoop = function(){
// Get current head position
let _x = snake[0].x;
let _y = snake[0].y;
snake_dir = snake_next_dir; // Update direction from input
// Calculate new head position based on direction
// Direction 0=Up, 1=Right, 2=Down, 3=Left
switch(snake_dir){
case 0: _y--; break;
case 1: _x++; break;
case 2: _y++; break;
case 3: _x--; break;
}
// Move snake: remove tail, add new head
snake.pop();
snake.unshift({x: _x, y: _y});
// ... collision detection code here ...
// ... rendering code here ...
// Recursive call to continue game loop
setTimeout(mainLoop, snake_speed);
}
Snake Movement & Physics
Snake movement uses a coordinate system where the head gets a new position based on direction, and the tail is removed unless food is eaten. The snake is represented as an array of coordinate objects {x, y}
.
// Snake movement logic (from within mainLoop)
let _x = snake[0].x; // Current head x position
let _y = snake[0].y; // Current head y position
snake_dir = snake_next_dir; // Apply queued direction change
// Calculate new head position
switch(snake_dir){
case 0: _y--; break; // Up
case 1: _x++; break; // Right
case 2: _y++; break; // Down
case 3: _x--; break; // Left
}
// Move snake body
snake.pop(); // Remove tail segment
snake.unshift({x: _x, y: _y}); // Add new head position
// Wall wrapping logic (when walls are disabled)
if(wall === 0){
for(let i = 0, x = snake.length; i < x; i++){
if(snake[i].x < 0){
snake[i].x = snake[i].x + (canvas.width / BLOCK);
}
if(snake[i].x === canvas.width / BLOCK){
snake[i].x = snake[i].x - (canvas.width / BLOCK);
}
if(snake[i].y < 0){
snake[i].y = snake[i].y + (canvas.height / BLOCK);
}
if(snake[i].y === canvas.height / BLOCK){
snake[i].y = snake[i].y - (canvas.height / BLOCK);
}
}
}
Collision Detection System
The game implements multiple collision types: wall collisions (when enabled), self-collision detection, and food consumption detection. All use simple coordinate comparison functions.
// Wall collision detection (when walls are enabled)
if(wall === 1){
if (snake[0].x < 0 || snake[0].x === canvas.width / BLOCK ||
snake[0].y < 0 || snake[0].y === canvas.height / BLOCK){
showScreen(SCREEN_GAME_OVER);
return;
}
}
// Self-collision detection (snake hits itself)
for(let i = 1; i < snake.length; i++){
if (snake[0].x === snake[i].x && snake[0].y === snake[i].y){
showScreen(SCREEN_GAME_OVER);
return;
}
}
// Generic collision detection helper function
let checkBlock = function(x, y, _x, _y){
return (x === _x && y === _y);
}
// Food collision detection and handling
if(checkBlock(snake[0].x, snake[0].y, food.x, food.y)){
// Grow snake by adding segment
snake[snake.length] = {x: snake[0].x, y: snake[0].y};
// Increase score
altScore(++score);
// Generate new food
addFood();
// Render new food position
activeDot(food.x, food.y);
}
Food Generation & Scoring
Food spawns randomly on the grid using Math.floor(Math.random())
but includes validation to prevent spawning on the snake’s body. Each food consumption increases the score and snake length.
// Random food placement with collision avoidance
let addFood = function(){
// Generate random position within canvas bounds
food.x = Math.floor(Math.random() * ((canvas.width / BLOCK) - 1));
food.y = Math.floor(Math.random() * ((canvas.height / BLOCK) - 1));
// Check if food spawned on snake body
for(let i = 0; i < snake.length; i++){
if(checkBlock(food.x, food.y, snake[i].x, snake[i].y)){
addFood(); // Recursive call to try again
}
}
}
// Score update and display function
let altScore = function(score_val){
ele_score.innerHTML = String(score_val);
}
Canvas Rendering & Graphics
The rendering system uses HTML5 Canvas with simple rectangle drawing (fillRect
) to create the snake segments and food. The game redraws the entire canvas each frame with a blue background.
// Main rendering logic (from within mainLoop)
// Clear canvas and paint background
ctx.beginPath();
ctx.fillStyle = "royalblue";
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Draw all snake segments
for(let i = 0; i < snake.length; i++){
activeDot(snake[i].x, snake[i].y);
}
// Draw food
activeDot(food.x, food.y);
// Individual dot/block rendering function
let activeDot = function(x, y){
ctx.fillStyle = "#FFFFFF";
ctx.fillRect(x * BLOCK, y * BLOCK, BLOCK, BLOCK);
}
Settings & Configuration
The game includes customizable speed settings (slow/normal/fast) and wall toggle functionality, demonstrating how to implement user preferences in games. Settings are managed through radio button inputs with event listeners.
// Speed control functions
let setSnakeSpeed = function(speed_value){
snake_speed = speed_value;
}
// Speed values: 120=slow, 75=normal, 35=fast
// Wall setting function with visual feedback
let setWall = function(wall_value){
wall = wall_value;
// Change border color based on wall setting
if(wall === 0){
screen_snake.style.borderColor = "#606060"; // Gray for no walls
}
if(wall === 1){
screen_snake.style.borderColor = "#FFFFFF"; // White for walls
}
}
// Settings event handling (from window.onload)
for(let i = 0; i < speed_setting.length; i++){
speed_setting[i].addEventListener("click", function(){
for(let i = 0; i < speed_setting.length; i++){
if(speed_setting[i].checked){
setSnakeSpeed(speed_setting[i].value);
}
}
});
}
for(let i = 0; i < wall_setting.length; i++){
wall_setting[i].addEventListener("click", function(){
for(let i = 0; i < wall_setting.length; i++){
if(wall_setting[i].checked){
setWall(wall_setting[i].value);
}
}
});
}
Game Flow Summary
1. Initialization Phase:
- Load HTML structure with multiple screens
- Set up CSS styling and visual effects
- Initialize JavaScript variables and constants
- Bind event handlers to buttons and keyboard
2. Game Start Sequence:
- User presses spacebar or clicks “new game”
newGame()
function resets all variables- Snake spawns at starting position
- First food is randomly placed
- Main game loop begins
3. Game Loop Cycle:
- Read player input (arrow keys)
- Update snake position based on direction
- Check for collisions (walls, self, food)
- Handle collision consequences (game over, growth, score)
- Render updated game state to canvas
- Schedule next loop iteration with setTimeout
4. Game End:
- Collision detected triggers game over screen
- Player can restart or change settings
- Process repeats from initialization
Your Turn!
- Please Open “examplehacks.ipynb”.
- Complete 3 of our “easy” hacks.
- Complete 2 of our “hard” hacks.
- Complete the challenge hack OR Make a substantial change to the game on your own.