The open-engine philosophy
CEMI Fútbol is written to be opened up. It runs directly in the browser as native ES modules — no bundler, no transpiler, no node_modules. Every file is small, single-purpose, and commented so a curious student can follow it.
- Zero-build. The browser loads the modules straight from disk. What you read is exactly what runs.
- Vanilla JS on HTML5 canvas. No hidden magic — the game is drawn one frame at a time with plain
<canvas>calls. - Comments as curriculum. The code explains its own physics, biology and structure in the comments, in plain language.
- A shared engine. Both games sit on the same
cemi/engine, so a lesson learned in one game transfers to the other.
Try it now: click cemi/core.js. The real file opens in a viewer with line numbers. Every monospace path on this page works the same way.
The code map: what each file teaches
The engine lives in cemi/; each game lives in games/. Click any path to read it.
Engine — cemi/
| File | What it does | What it teaches |
|---|---|---|
cemi/core.js | Game loop, scene manager, responsive canvas, helper math (clamp, lerp, mulberry32, hashStr). | Game loops, delta-time updates, seeded RNG, utility functions. |
cemi/input.js | Unified pointer/touch/keyboard; swipes carry a full trail so games read direction, speed AND curvature. | Event handling, vectors, reading gestures from raw points. |
cemi/audio.js | All-synth WebAudio sound effects — zero audio files. | Procedural audio, oscillators, envelopes. |
cemi/fx.js | Particles, confetti, screen-shake, floating text. | Particle systems, animation, "game juice". |
cemi/facts.js | The Nerd Engine: context-tagged facts that never repeat until the pool is exhausted. | Data-driven content, sets, non-repeating random selection. |
cemi/i18n.js | English / Español / Français / Português with t('key', {vars}) everywhere. | Internationalization, key/value data structures, string templating. |
cemi/nations.js | 32 nations; flags are DRAWN from simple shapes, no image files. | Data modeling, procedural vector graphics. |
cemi/tournament.js | Knockout brackets: pick a nation, survive 5 rounds to the cup. | Arrays, shuffling, tournament trees, powers of two. |
cemi/challenge.js | Seeded daily/weekly challenges — the same world for everyone today. | Determinism, hashing, date keys, seeds. |
cemi/profile.js | Optional, kid-safe local profile — never required to play. | localStorage, privacy-by-design, default objects. |
cemi/backend.js | One Store interface, two implementations (local + Firebase), offline-first. | Interfaces/polymorphism, graceful degradation. |
cemi/ui.js | Shared DOM UI and a line-art SVG icon library (never emojis). | DOM building, SVG, component thinking. |
Golden Boot — games/golden-boot/
| File | What it does | What it teaches |
|---|---|---|
games/golden-boot/main.js | Boots the game, owns the menu/tournament/results screens. | App structure, wiring modules together. |
games/golden-boot/shootout.js | The physics scene: gravity + drag + Magnus spin, keeper AI, scoring. | Projectile motion, vectors, forces, prediction, energy. |
games/golden-boot/render.js | Two renderers over one state: retro 16-bit and modern broadcast, with a 2.5D projection. | 3D-to-2D projection, separation of state and view. |
games/golden-boot/strings.js | Game text in four languages + its Nerd Engine facts pack. | i18n data, templated feedback with real numbers. |
Gorilla on the Pitch — games/gorilla/
| File | What it does | What it teaches |
|---|---|---|
games/gorilla/main.js | Sets up the pitch, teammates, waves, and the survive-and-score loop. | Game state, entities, win/lose conditions. |
games/gorilla/gorilla.js | The gorilla: a finite state machine + procedural quadruped animation. | State machines, AI targeting, procedural animation, trigonometry. |
games/gorilla/strings.js | Game text in four languages + real gorilla-biology facts. | Data-driven content, biology as data. |
Four guided code-reading activities
Each activity opens a real file in the source viewer. No editor and no setup needed — reading comes before writing.
1 The gravity constant — and Moon ball
- Objective
- Locate a physical constant in code and reason about a change without running it.
- Instructions
- Open
games/golden-boot/shootout.js. Search for9.81— Earth's gravity in m/s². Find where it pulls the ball down each frame (B.vy -= g * dt). Now imagine replacing it with the Moon's gravity, 1.62. - Expected result
- Students predict that on the Moon the ball would barely arc down — shots would sail long and float, keepers would have far more time, and "over the bar" would happen constantly. They can point to the exact line responsible.
- Extension / Teacher's note
- Extension: The game also has a "beach ball" effect that uses
g = 3.6. Find it and explain why a beach ball floats. Note: reasoning about a change in your head ("what-if") is a real debugging skill — no need to edit anything.
2 How the keeper reads a shot
- Objective
- See how simple AI can look smart by predicting the future.
- Instructions
- In
games/golden-boot/shootout.js, findpredictCrossing. Read how it fast-forwards the ball's flight in a loop to guess where it will cross the goal line, then look at_keeperDecideto see how difficulty makes the keeper "read" the shot more often. - Expected result
- Students discover the keeper isn't cheating — it runs a cheap copy of the same physics to predict the crossing point, then dives there (with some deliberate error so it's beatable). Higher difficulty simply lowers the error.
- Extension / Teacher's note
- Extension: Discuss the trade-off: a perfect predictor would be unbeatable and no fun. Where is the randomness that keeps it fair? Note: the same forward-simulation trick powers real game and robotics AI.
3 Walking without a sprite sheet
- Objective
- Understand procedural animation — motion generated by math, not drawn frames.
- Instructions
- Open
games/gorilla/gorilla.js. Find_gaitand read how the phase advances with distance travelled, not time (this.phase += (sp * dt) / this.stride), so the gorilla's legs plant and step realistically. Then find theLIMBStable and read the comment about diagonal limb pairs. - Expected result
- Students see that a convincing knuckle-walk is produced entirely by trigonometry and timing — the four limbs step in diagonal pairs, the body bobs twice per stride and sways once. No animation frames exist at all.
- Extension / Teacher's note
- Extension: Find
_renderProceduraland thebob/swayvalues — change one in your head and predict the effect. Note: the file supports an optional sprite sheet too; the procedural path is the fallback and the teaching case.
4 One data structure, four languages
- Objective
- See how a single data shape can hold every translation of the game.
- Instructions
- Open
cemi/i18n.jsand read theSTRtable: each key maps to{ en, es, fr, pt }. Then opencemi/facts.jsand see the exact same shape used for the Nerd Engine's facts. Find thet()function and read how{vars}get substituted into a string. - Expected result
- Students recognize one reusable pattern — a key with four language values — powering the entire multilingual interface and the trivia. They understand why adding a language means adding one field, not rewriting the game.
- Extension / Teacher's note
- Extension: Trace how
t('gb_nerd_goal', {kmh, j, ms})turns into the sentence you read after a goal. Note: the physics feedback strings ingames/golden-boot/strings.jsuse the same templating with real computed numbers.
Clone it and run it
Because there is no build step, running the whole project locally takes three commands. You only need Git and Python 3 (which ships with macOS and Linux, and is a quick install on Windows).
- Clone the repository:
git clone https://github.com/<owner>/futbol.gitcd futbol - Start the tiny dev server (needed because ES modules require HTTP, not
file://):python3 scripts/dev-server.py 8321 - Open it in a browser:
http://localhost:8321
The dev server (scripts/dev-server.py) is a ~20-line Python script that serves the files with no-cache headers, so every edit shows up on reload. Change a number in games/golden-boot/shootout.js, refresh, and watch the game change. That immediate loop — edit, save, refresh, see — is the whole point.
Note: the source viewer on these pages fetches files over HTTP too, so it works best when the docs are opened through that same local server (or wherever the site is hosted), not from a bare file:// path.