Game Development

Interactive games built with HTML5 Canvas and JavaScript

Space Invaders

How to Play

Move with / (or A/D), shoot with Space. Press Enter to start or restart.

Press Enter to Play

Defend Earth!

Move with ←/→ or A/D, shoot with Space. Clear all invaders to win the level.

Coding Concepts Used

  • Variables & constants for game state (score, lives, level, speeds).
  • Functions for drawing, updating, spawning, and collision checks.
  • Objects & ES6 classes (Player, Invader, Bullet) to encapsulate data + behavior.
  • Arrays for managing bullets and enemies, including filtering and mapping.
  • Event listeners for keyboard input; a keys map for pressed/released state.
  • Game loop using requestAnimationFrame with delta time for smooth motion.
  • Collision detection with axis-aligned bounding boxes (AABB).
  • State machine-style flags (running, paused, gameOver, level).
  • Math for movement, grid stepping, descent, and speed ramp by level.
  • Canvas 2D API for rendering shapes, text, and a starfield background.
  • Timing via simple cooldowns to limit fire rate and invader step rate.
  • Immutability patterns (e.g., filtering arrays) to remove off-screen or hit entities.
  • Encapsulation & composition to keep code modular and readable.

Space Invaders Coding Concepts — Student Worksheet (line‑by‑line explained)

Game Summary

You will explore a simple Space Invaders game coded in HTML, CSS, and JavaScript. The aim is to link programming concepts to actual examples in the game.

Every code line below has a plain‑English comment at the end of the line.

1. Variables & Constants

Definition: Store data that can change (variables) or stay the same (constants).

Taken from the game:

const W = 560, H = 640; // W and H are the canvas width and height
const PLAYER_SPEED = 320;      // how fast the player moves (pixels per second)
const BULLET_SPEED = 520;      // how fast bullets move (pixels per second)
const INVADER_STEP_X = 18;     // how far invaders move sideways each step
const INVADER_STEP_Y = 24;     // how far invaders drop down on a bounce
const INVADER_BASE_RATE = 0.65; // time between invader steps at level 1 (in seconds)
const FIRE_COOLDOWN = 220;     // time between player shots (in milliseconds)

const state = {                 // one object holding the whole game state
  running: false,              // is the game currently running?
  gameOver: false,             // has the player lost?
  level: 1,                    // current level number
  score: 0,                    // player score
  invaders: [],                // list of enemy invaders
  bullets: [],                 // list of all bullets (player and enemy)
  invaderDir: 1,               // direction invaders move: 1 right, -1 left
  invaderTimer: 0,             // countdown until the next invader step
  invaderRate: INVADER_BASE_RATE, // current step rate (can get faster)
  stars: Array.from({length: 120}, () => ({ // make 120 random stars for the background
    x: Math.random()*W,        // random x position on screen
    y: Math.random()*H,        // random y position on screen
    s: Math.random()*2+0.5     // random star size
  }))
};

2. Functions

Definition: Reusable blocks of code that perform a specific task.

Taken from the game (level setup):

function resetLevel(level = 1) {            // set up a level (default to level 1)
  state.level = level;                      // store the level number
  state.invaders = [];                      // clear any old invaders
  const cols = 10, rows = 5;                // how many invaders across and down
  const startX = 40, startY = 80, spacingX = 46, spacingY = 36; // grid layout numbers
  for (let r = 0; r < rows; r++) {          // loop over each row
    for (let c = 0; c < cols; c++) {        // loop over each column
      state.invaders.push(                   // add an invader to the list
        new Invader(startX + c*spacingX,     // x position based on column
                    startY + r*spacingY,     // y position based on row
                    r)                       // store the row index (used for colour/score)
      );
    }
  }
  state.invaderDir = 1;                     // start moving to the right
  state.invaderRate = Math.max(0.20,        // never go faster than 0.20s per step
    INVADER_BASE_RATE * (1 - (level-1)*0.12) // higher levels step faster
  );
  state.invaderTimer = state.invaderRate;   // reset the step timer
  state.bullets = [];                        // clear any bullets
  player.x = W/2 - player.w/2;               // centre the player horizontally
  player.y = H - 70;                          // set the player near the bottom
  player.cooldown = 0;                        // allow immediate shooting
  player.invincibleTimer = 0;                 // remove any temporary invincibility
}

3. Objects & ES6 Classes

Definition: Structures for grouping data and related functions.

Taken from the game (Player class):

class Player extends Entity {                 // Player is a type of Entity (inherits size/position)
  constructor(){
    super(W/2-18, H-70, 36, 18);             // call Entity with starting x,y and width,height
    this.cooldown = 0;                        // time until the player can shoot again (ms)
    this.lives = 3;                           // number of lives
    this.invincibleTimer = 0;                 // short time after hit where player can't be hit
  }
  update(dt){                                 // update player each frame (dt = time since last frame in seconds)
    const left = keys.get('arrowleft') || keys.get('a');   // is left key held?
    const right = keys.get('arrowright') || keys.get('d'); // is right key held?
    let dx = (right?1:0) - (left?1:0);        // convert keys to direction: -1 left, 0 still, 1 right
    this.x = clamp(this.x + dx * PLAYER_SPEED * dt, 8, W - this.w - 8); // move and keep inside screen
    this.cooldown = Math.max(0, this.cooldown - dt*1000);  // count down the shoot cooldown
    this.invincibleTimer = Math.max(0, this.invincibleTimer - dt*1000); // count down invincibility
  }
  canFire(){ return this.cooldown <= 0; }     // player can shoot if cooldown finished
  fire(){                                     // create a new bullet from the player
    this.cooldown = FIRE_COOLDOWN;            // reset the cooldown
    return new Bullet(this.x + this.w/2 - 2,  // bullet x starts at centre of the ship
                      this.y - 10,            // bullet y starts just above the ship
                      -1);                    // -1 means bullet travels upwards
  }
}

4. Arrays

Definition: Lists that hold multiple values.

Taken from the game (managing entities):

state.invaders.push(                          // add a new invader to the list
  new Invader(startX + c*spacingX,           // x position for this column
              startY + r*spacingY,           // y position for this row
              r)                              // store the row index
);

state.bullets = state.bullets.filter(b =>    // replace bullets with a filtered list
  !b.offscreen()                              // keep only bullets still on screen
);                                           // (removes bullets that went off the top/bottom)

5. Event Listeners

Definition: Code that reacts when something happens (like a key press).

Taken from the game (keyboard handling):

const keys = new Map();                       // a map to remember which keys are pressed
addEventListener('keydown', (e) => {          // when any key is pressed down
  if (["ArrowLeft","ArrowRight"," ","Space","Enter","a","d","A","D"].includes(e.key)) // if it's a game key
    e.preventDefault();                       // stop the page from scrolling etc.
  keys.set(e.key.toLowerCase(), true);        // remember this key is now pressed
});
addEventListener('keyup', (e) =>              // when any key is released
  keys.set(e.key.toLowerCase(), false)        // remember this key is no longer pressed
);

addEventListener('keydown', (e) => {          // when a key is pressed
  if (e.key === 'Enter'){                     // if the key was Enter
    if (!state.running) { restartGame(); }    // start (or restart) the game if it's not running
  }
});

6. Game Loop

Definition: A loop that keeps updating and drawing the game every frame.

Taken from the game:

let last = performance.now();                 // remember the time of the previous frame
function tick(){                              // one frame of the game
  const t = performance.now();                // current time (ms)
  const dt = Math.min(0.033, (t - last) / 1000); // seconds since last frame, capped for stability
  last = t;                                   // update last time to now

  drawStarfield(dt);                          // draw the moving star background

  if (state.running){                         // only update gameplay if running
    player.update(dt);                        // move the player based on keys
    const shoot = keys.get(' ') || keys.get('space'); // is spacebar held?
    const bulletCount = state.bullets.filter(b=>b.dir<0).length; // how many player bullets exist
    if (shoot && player.canFire() && bulletCount < MAX_PLAYER_BULLETS){ // allowed to fire now?
      state.bullets.push(player.fire());      // create and store a new bullet
    }

    updateInvaders(dt);                       // move enemies and maybe shoot
    state.bullets.forEach(b=>b.update(dt));   // move all bullets

    // collisions & drawing happen elsewhere (see sections 7 and 10) // info comment
  } else {
    overlay.classList.remove('hidden');       // show the start/game over message
  }

  requestAnimationFrame(tick);                // ask the browser to call tick() again next frame
}

7. Collision Detection

Definition: Checking if two objects overlap.

Taken from the game:

class Entity {                                 // base class for anything with a box shape
  constructor(x, y, w, h) { this.x=x; this.y=y; this.w=w; this.h=h; } // store position and size
  get left(){return this.x}                   // left side x value
  get right(){return this.x+this.w}          // right side x value
  get top(){return this.y}                   // top y value
  get bottom(){return this.y+this.h}         // bottom y value
  intersects(o){                              // do two boxes overlap?
    return !(this.right < o.left              // no overlap if this is completely left of other
          || this.left > o.right              // or completely right of other
          || this.bottom < o.top              // or completely above other
          || this.top > o.bottom);            // or completely below other
  }
}

for (const b of state.bullets){               // check each bullet
  if (b.dir < 0){                             // only player bullets (they move up)
    for (const inv of state.invaders){        // check against each invader
      if (inv.alive && b.intersects(inv)){    // if invader is alive and bullet hits it
        inv.alive = false;                    // mark invader as destroyed
        b.y = -99;                            // move bullet off-screen so it will be removed
        state.score += inv.value + state.level*2; // increase the score for this hit
      }
    }
  }
}

8. State Machine‑style Flags

Definition: Variables that control which part of the game is running.

Taken from the game:

function restartGame(){                        // start a new run
  state.running = true;                        // the game is now running
  state.gameOver = false;                      // clear game over flag
  state.score = 0;                             // reset score
  player.lives = 3;                            // give the player 3 lives
  resetLevel(1);                               // build level 1 enemies
  overlay.classList.add('hidden');             // hide the overlay message
}

if (!state.running){                           // if the game is not running
  overlay.querySelector('h2').textContent =    // set the big title text
    state.gameOver ? 'Game Over' : 'Defend Earth!'; // show different text if lost or not
  overlay.querySelector('.badge').textContent = // set the small badge text
    state.gameOver ? 'Press Enter to Restart' : 'Press Enter to Play'; // what the player should do
  overlay.classList.remove('hidden');          // make sure overlay is visible
}

9. Math

Definition: Using calculations for movement, positioning, and scaling.

Taken from the game:

const clamp = (v, min, max) => Math.min(max, Math.max(min, v)); // keep a value between min and max
this.x = clamp(                              // set the player's x position to a safe value
  this.x + dx * PLAYER_SPEED * dt,           // new position based on speed, direction, and time
  8,                                         // don't go beyond 8 pixels from the left edge
  W - this.w - 8                             // don't go beyond 8 pixels from the right edge
);                                            // end of clamp

10. Canvas 2D API

Definition: Commands for drawing shapes, text, and images.

Taken from the game:

g.fillStyle = '#9be564';                      // choose a green colour for the ship
g.fillRect(this.x, this.y, this.w, 6);        // draw the bottom part of the ship
g.fillRect(this.x+6, this.y-6, this.w-12, 6); // draw the middle part (smaller)
g.fillRect(this.x+12, this.y-12, this.w-24, 6); // draw the top part (even smaller)

g.fillStyle = '#e6f1ff';                      // choose a light colour for text
g.font = '14px ui-monospace, Menlo, monospace'; // choose the text font and size
g.fillText(`Score: ${state.score}`, 12, 20);  // draw the score text at the top-left

11. Timing

Definition: Controlling when events happen (cooldowns, step timers).

Taken from the game:

player.cooldown = Math.max(0, player.cooldown - dt*1000); // tick down the shoot cooldown (ms)
if (shoot && player.canFire()){               // if space is pressed and cooldown finished
  state.bullets.push(player.fire());          // create a new bullet and add it to the list
}

state.invaderTimer -= dt;                     // subtract time from the invader step timer (s)
if (state.invaderTimer <= 0){                 // if it reached zero or less
  state.invaderTimer = state.invaderRate;     // reset the timer to the current rate
  // here we move invaders sideways, and drop down if they hit the edge // info comment
}

12. Immutability Patterns

Definition: Creating new arrays instead of changing them directly.

Taken from the game:

state.bullets = state.bullets.filter(b =>     // replace bullets with a filtered version
  !b.offscreen()                              // only keep bullets that are still visible
);                                            // result: removed off‑screen bullets

const live = state.invaders.filter(v => v.alive); // make a list of invaders that are still alive

13. Encapsulation & Composition

Definition: Keeping related code together and building bigger features from smaller ones.

Taken from the game:

class Bullet extends Entity {                   // Bullet is also an Entity (has a box)
  constructor(x,y,dir){                        // build a bullet at x,y with a direction
    super(x,y,4,12);                           // bullet has a width of 4 and height of 12
    this.dir = dir;                            // direction: -1 up, +1 down
  }
  update(dt){                                  // move the bullet each frame
    this.y += this.dir * BULLET_SPEED * dt;    // change its y based on direction and speed
  }
}

fire(){                                        // a method inside Player that creates bullets
  this.cooldown = FIRE_COOLDOWN;               // set shoot delay before next shot
  return new Bullet(                           // return a brand new Bullet object
    this.x + this.w/2 - 2,                     // start at the centre of the ship (x)
    this.y - 10,                               // start just above the ship (y)
    -1                                         // this bullet will travel upwards
  );
}

Your Turn — Challenge Task

Open the game code. For each concept above, find another example and rewrite it with a simple end‑of‑line comment.

Extra Credit

Suggest one improvement to the game (e.g., sound effects, more levels, new enemy types) and list which coding concepts you'd use to build it (e.g., arrays for multiple sounds, timing for cooldowns, classes for new enemies).

← Back to Home Page