A lightweight, dependency-free educational game built in pure modern JavaScript using the HTML5 Canvas API.
This is a multiplication practice game where the player controls a cannon at the bottom of the screen, firing numbered bullets at enemies displaying maths problems. Players must shoot the correct answer to destroy each enemy.
The goal was to build a robust "Game Engine" from scratch without external libraries, focusing on clean architecture, frame-rate independence, and state management.
The codebase currently resides in a single file (index.html) for portability, but is structured to be easily modularized.
We utilize a functional-inspired architecture where the game logic is separated from the game state.
- State is Isolated: All changing data (player position, score, enemy arrays) is contained in a single
gameStateobject. - Update is Logic-Only: The
update()function takes the current state, input, and time, and returns the modified state. - Draw is Pure: The
draw()function takes the state and renders it. It never modifies data.
We do not manually initialize the game before the loop starts. Instead, the update() function handles self-healing:
- If
gameStateisundefined, it creates it. - If
isGameOveris true and a restart is requested, it replaces the state with a fresh one. This removes the need for duplicate "reset" logic.
- Logic: Movement is calculated in Pixels Per Second, not Pixels Per Frame.
- Implementation: The
updatefunction accepts a rawtimestampfromrequestAnimationFrame, calculates thedeltaTimeinternally (comparing it tolastFrameTimestored in the state), and applies strictly time-based movement.
CONFIGObject: All "magic numbers" (colors, dimensions, base speeds) are extracted into a constant object at the top of the file.- Naming Convention: We strictly use verbose, explicit variable names (e.g.,
lastFrameTimeinstead oflastTime,deltaTimeinstead ofdt) to maximize readability.
{
score: Number,
highScore: Number, // Persisted via localStorage
isGameOver: Boolean,
lastFrameTime: Number, // Timestamp of the previous frame
secsSinceLastSpawn: Number, // Accumulator for enemy spawning
spawnIntervalSecs: Number, // Difficulty scaling
columns: Array[], // 5 columns of enemies
player: { x, y, width, height, targetX, sprite },
bullets: [ { x, y, width, height, value, sprite } ],
explosions: [ { x, y, age, type } ]
}Input is handled via a persistent object updated by Event Listeners, decoupled from the game loop.
{
moveLeftRequest: Boolean,
moveRightRequest: Boolean,
fireValue: String, // Number keys 0-12 (`, 1-9, 0, -, =)
restartRequest: Boolean // Enter key on Game Over
}Core Mechanics:
- Column-based movement: Player snaps between 5 columns using arrow keys
- Math problems: Enemies display multiplication equations with missing operands
- Numbered bullets: Keys 0-12 fire bullets with corresponding values
- Answer validation: Correct answers destroy enemies, wrong answers show red X effect
Visual Polish:
- Procedurally generated sprites: 3 enemy types, player cannon, bullets
- Explosion animations: Orange particles for successful hits, red X for misses
- Smooth interpolation: Player and enemies glide to target positions
Progression:
- Difficulty scaling: Spawn rate increases with score
- High score tracking: Persisted via localStorage, displayed in UI bar
- Game over: Triggered when enemies reach the player's row
Collision Detection: Standard AABB (Axis-Aligned Bounding Box)
If picking up this project, the following features are the next logical steps:
- Power-ups: Special bullets that clear entire columns or slow enemy descent
- Combo system: Reward consecutive correct answers with score multipliers
- Difficulty settings: Adjustable number ranges and operation types (+, -, ×, ÷)
- Practice mode: Focus on specific multiplication tables
- Statistics: Track accuracy, response time, and problem types
- Module split: Separate CONFIG, update, draw, and sprite generation into .js files
- Responsive canvas: Dynamically resize while maintaining aspect ratio
Simply open index.html in any modern web browser. No build steps or servers are required.
Or browse to https://scarytom.github.io/tta
- Arrow Left/Right: Move between columns
- Number keys: Fire bullets with values 0-12
`= 01-9= 1-90= 10-= 11== 12
- Enter: Restart after game over