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.