PuzzleEngine is a single-HTML game framework that powers three distinct puzzle games — Match-3, Sudoku, and Falling Blocks (Tetris-style) — from a unified core architecture. The games feel like different "skins" or "modules" running on the same engine, sharing identical lifecycle methods, telemetry, and level progression systems.
Every game module implements the exact same interface:
init(levelConfig) → Set up the game board and state from a config object
start() → Begin gameplay (bind input, start timers)
reset() → Handled by engine: destroy + init + start
destroy() → Tear down DOM, unbind events, stop loops
The GameEngine class is the only entity that calls these methods. Games never self-start or self-reset — they respond to the engine's orchestration. This inversion of control means adding a 4th game requires zero changes to the engine; you simply implement the four methods and call engine.registerGame('newGame', NewGameModule).
A single Telemetry class handles all event tracking. Every game reports the same three events with an identical data structure:
| Event | When Fired |
|---|---|
Tutorial_Complete |
Player dismisses the first-time tutorial |
Level_Start |
Engine starts a new level |
Level_Fail |
Player loses (out of moves/mistakes/space) |
Event schema (universal):
{
"eventName": "Level_Start",
"gameId": "match3",
"level": 1,
"score": 0,
"timestamp": "2025-01-15T12:00:00.000Z",
"metadata": { "description": "Tutorial" }
}Games don't instantiate their own trackers or define custom events — they call this.engine.failLevel() or this.engine.winLevel() and the engine handles telemetry dispatch. The tutorial flow is also engine-managed: it shows a tutorial on first play and fires Tutorial_Complete through the same telemetry pipeline.
All difficulty curves live in a single LEVEL_DATA object at the top of the file:
{
"match3": [
{ "level": 1, "gridSize": 6, "moves": 25, "targetScore": 400, "colors": 4 },
...
],
"sudoku": [
{ "level": 1, "clues": 45, "maxMistakes": 5 },
...
],
"tetris": [
{ "level": 1, "speed": 800, "linesTarget": 5, "width": 10, "height": 20 },
...
]
}Each game's init() receives the current level's config object. The game module doesn't know what level it's on or how progression works — the engine manages level indexing, advancement, and retry. A designer can tweak difficulty by editing this JSON without touching any game logic.
The GameEngine class owns:
- Level state (
currentLevels— tracks each game's progress) - Tutorial state (
tutorialShown— one-time per game) - Transitions (win → next level, fail → retry, reset)
- UI overlays (tutorial dialogs, game-over screens)
Games are stateless modules in the sense that they don't manage their own lifecycle. They hold gameplay state (board, score, etc.) but the engine tells them when to exist.
┌─────────────────────────────────────────────────┐
│ GameEngine │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Telemetry│ │Level Data│ │ UI/Views │ │
│ │ (shared) │ │ (JSON) │ │ (shared) │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ registerGame() initGame() startGame() │
│ resetGame() nextLevel() failLevel() │
│ winLevel() │
├──────────┬──────────┬───────────────────────────┤
│ Match-3 │ Sudoku │ Falling Blocks │
│ Module │ Module │ Module │
│ │ │ │
│ init() │ init() │ init() │
│ start() │ start() │ start() │
│ destroy()│ destroy()│ destroy() │
└──────────┴──────────┴───────────────────────────┘
- Adding a new game: Create a module object with
init/start/destroy, add level data toLEVEL_DATA, register it. ~0 lines of engine code change. - Adding a new telemetry event: Add one method to
Telemetry, call it from the engine. All games get it automatically. - Changing difficulty: Edit the JSON. No code changes.
- Shared UI: Game-over screens, tutorials, the telemetry panel, and navigation are all engine-level — games don't reimplement them.
This is a single-file project by design (per the assignment spec). In a production version, you would split:
/engine/ GameEngine.js, Telemetry.js
/games/ match3.js, sudoku.js, tetris.js
/config/ levels.json
/ui/ views, overlays, components
index.html Shell / entry point
The single-file approach demonstrates that the architecture holds even without a build system.