Start here
CEMI Fútbol is built on one idea: learning is seasoning, never the meal. A "Nerd Engine" sprinkles real facts as flavor after a shot, a save, or an elimination — it never blocks the game. Your job as the educator is simply to notice those moments out loud.
Why it works in class or at home
- Ages 8–16. The controls are one-finger simple; the ideas underneath scale from primary curiosity to secondary physics.
- No install, no barriers. Runs in any modern browser — Chrome, Firefox, Safari, Edge. No plugins, no downloads.
- Free and offline-capable. Once the page has loaded it keeps working without a connection, so it is fine for spotty Wi-Fi labs.
- Kid-safe. No account, name, or email is ever required to play. An optional profile is stored only in the browser.
- Four languages. English, Español, Français, Português — switchable at any time, which is itself a teaching tool (see Languages below).
The sixteen games: Golden Boot (swipe-physics penalties: tournament, 8-venue story tour, 2P), Gorilla on the Pitch (survive-and-score vs a gorilla family), Keepy-Uppy Infinito (juggle a ball forever — it has opinions), Anaconda Soccer (feed goals to shrink an ever-growing snake), Rumble Fútbol (1v1/2v2 party soccer with power-ups), Elemental League (water, earth, air, fire — the arena plays too), Cruce de Tráfico (cross eight lanes of traffic with your ball), Muerto FC (the last striker vs a horde that just wants a hug), Dodgeball FC (dodge their throws, score past their bodies), Gods vs Titans FC (pick your god, face a stone titan), FIREBALL FC (wizard duel: only fireballs may move the ball), Béisbol Gol (a robot pitches, you kick, the outfield is a goal), Dolphins vs Sharks (your pod plays football; the sharks eat loose balls), Dungeon Striker (descend a soccer dungeon — floors are matches, loot is boots), Endless Striker (three-lane dribble runner) and Endless Striker 3D (the same run, rebuilt in full 3D). Every activity below tells you which game it uses.
Jump to a game: Golden Boot · Gorilla on the Pitch · Keepy-Uppy Infinito · Anaconda Soccer · Rumble Fútbol · Elemental League · Cruce de Tráfico · Muerto FC · Dodgeball FC · Gods vs Titans FC · FIREBALL FC · Béisbol Gol · Dolphins vs Sharks · Dungeon Striker · Endless Striker · Endless Striker 3D
Install it like an app (recommended for classrooms): open the games hub once while online, then use your browser's Install / Add to Home Screen option (Chrome: menu → "Install app"; iPhone/iPad Safari: Share → "Add to Home Screen"). The site carries its own offline cache, so after that first visit every game you have opened keeps working with no connection at all — full screen, no browser bars. Teacher tip: before a class with unreliable Wi-Fi, open each game you plan to use once; from then on the lesson cannot be interrupted by the network. Only the global leaderboards need a connection.
Before, during & after play
You do not need to be a gamer to run these. Three light-touch habits turn play into learning.
Before
- Play one round yourself. Five minutes is enough to know how a swipe becomes a shot, and how the gorilla telegraphs its charge.
- Pick one subject lens. Decide today is a "physics day" or a "biology day" and choose one or two activities below — don't try to cover everything at once.
- Set the language. For a languages lesson, agree on which language the group will play in, or plan to switch mid-session.
During
- Read the facts out loud. When the Nerd Engine pops a line after a shot or an elimination, pause and ask "wait — is that true?" Curiosity beats correctness.
- Ask for a number. Golden Boot reports your shot's speed in km/h and its energy in joules. Have students say the numbers before the next shot.
- Encourage a log. A phone or a scrap of paper is enough. Several activities below are just structured observation logs.
After
- Round-robin reflection. Most surprising fact, hardest moment, best goal. Thirty seconds each.
- Verify one claim. Send students to check a single fact from the game against another source. Was the game right?
- Extend into the code. For older or curious students, the Programming guide turns the same games into a reading-real-code lesson.
Privacy & safety: two ways to play
CEMI Fútbol is designed for classrooms, so the safest option is also the default one. The account panel offers two clearly separated ways to play — and nobody is ever required to choose either one just to play.
Local mode — the default, made for kids
- A nickname is all there is. No email, no password, no real name, no birthday. Even the nickname is optional.
- Everything stays on the device. Nickname, records and achievements live only in that browser. Nothing is ever sent to any server — not quietly, not "anonymized", not at all.
- Clearing the browser erases it. That is the promise of the mode, not a flaw: the data lives nowhere else.
- An honest erase button. The panel includes an "erase my local data" button that wipes the profile and every local record on the spot (it asks for a second tap to confirm). Useful between class groups on shared computers.
- A look-only window on the world. One optional toggle lets a local player view the world leaderboards. It is strictly read-only: their own scores still never leave the device.
For the classroom: local mode means no signups, no accounts to create or manage, and no parental-consent paperwork — a whole class can play the moment the page loads.
Cloud mode — optional, for players who want to compete
- Sign in with Google or with an email and password. The player (or their family) chooses to switch; it never happens on its own.
- Progress syncs across devices. Signing in merges what is on the device with what is in the cloud, keeping the best of both — progress is never lost by switching computers.
- Leaderboards. Best scores appear on the global leaderboards under the chosen nickname.
- Reversible. Signing out returns the player to local mode at any time.
Nickname safety
Every nickname a player types is checked automatically against offensive, discriminatory, sexual and abusive words in all four of the game's languages. The check is not naive: it also catches the usual disguises — dots or underscores between letters, extra spacing, repeated letters, and swapping numbers for letters ("leetspeak"). A rejected name simply asks the player to pick another one. And the leaderboards double-check every name before showing it, with the server correcting anything that slips through — so an inappropriate name never appears on a classroom screen. If one is ever detected, it is quietly replaced by a harmless football nickname such as "Golazo#417".
Physics of the perfect shot
Golden Boot solves real ballistics for every shot. Swipe speed becomes launch speed, the swipe's end point becomes the aim on the goal plane, and a curved swipe adds spin. After the ball crosses the line, the Nerd Engine reports the actual numbers — km/h, joules, milliseconds, degrees — from games/golden-boot/shootout.js.
1 The Magnus effect: bend it like the code does
- Objective
- See that a spinning ball curves, and connect it to the Magnus force named in the game's facts.
- Instructions
- Play a Quick Match. First take five shots with a perfectly straight up-swipe. Then take five shots that hook sideways as you flick (a curved swipe). Watch the ball's flight path each time. Turn on "Loco Ball" and look for the Banana Spin effect, which multiplies the curve.
- Expected result
- Straight swipes fly straight; curved swipes bend in the air and can wrap around the keeper. Students should notice that curve is a separate control from power and aim. The Nerd Engine will surface the Roberto Carlos 1997 free kick — ~10 revolutions per second, curving almost 4 metres.
- Extension / Teacher's note
- Extension: Try the classroom demo — drop two sheets of paper, one flat and one lightly spun, and watch them fall differently. Note: in the code, spin is applied in the line
B.vx += (B.spin * B.vz * 0.5 + wind * 0.8) * dt— a sideways push that grows with forward speed, exactly like the real Magnus force.
2 Launch angle: over the bar vs. into the net
- Objective
- Discover the trade-off between shot height (angle) and staying on target.
- Instructions
- Deliberately blast ten shots high over the crossbar, then ten low daisy-cutters along the ground. Read the feedback line each time. On a "wide" or "over" miss, the game reports the miss angle in degrees; on a goal it reports the flight time in milliseconds.
- Expected result
- Students see that a steeper launch buys height but overshoots, while a flat shot is fast but easy to save low. The sweet spot is a modest angle into a corner. They can record the reported degrees for high vs. low misses and compare.
- Extension / Teacher's note
- Extension: Introduce the idea that on a real (drag-free) pitch, maximum range comes from a 45° launch — then ask why a penalty is never taken at 45°. Note: the crossbar sits at exactly 2.44 m (a rule unchanged since 1866); the game measures your miss against it.
3 Energy in joules: how hard did you hit it?
- Objective
- Read kinetic energy off a real formula and connect faster shots to more energy.
- Instructions
- Score five goals and write down the km/h and joule values the game reports. A regulation ball is 0.43 kg. The game computes energy as
0.5 × 0.43 × v²(that is ½mv²). Have students confirm one value with a calculator: convert km/h to m/s (divide by 3.6), then plug in. - Expected result
- A ~100 km/h shot (about 27.8 m/s) carries roughly 166 joules. Students should see energy grow with the square of speed — doubling the speed quadruples the energy. Their hand calculation should match the game's number.
- Extension / Teacher's note
- Extension: The Nerd Engine notes a pro penalty gives the keeper about 400 ms while a blink takes 300 ms. Ask: why does more energy make a save harder? Note: the exact line is
j: Math.round(0.5 * 0.43 * v0 * v0)ingames/golden-boot/shootout.js— students can read the physics straight from the source.
Gorilla biology & behavior
The gorilla in this game is not a cartoon. It knuckle-walks with real diagonal limb pairs, beats its chest as an honest warning before it charges, hunts the most isolated player like a real predator, and runs on stamina it must recover. Its behavior is a living observation subject.
4 Behavior observation log
- Objective
- Practice scientific observation by logging an animal's behavior states.
- Instructions
- One student plays; the rest are field biologists. Watch the gorilla and tally each behavior you see: patrolling (wandering), stalking (following a player), chest-beating (rearing up), charging (the fast rush), carrying a player off, and resting (tired). Note what happens just before each charge.
- Expected result
- Observers discover a repeating sequence: patrol → stalk → chest-beat → charge → tired. The chest-beat always precedes the charge — it is a warning, not the attack. Students learn that behavior can be broken into observable, predictable states.
- Extension / Teacher's note
- Extension: Compare with an ethogram — the real charts biologists use to catalog animal behaviors. Note: those states are literally the gorilla's finite state machine in
games/gorilla/gorilla.js(OFF → ENTER → PATROL → STALK → CHESTBEAT → CHARGE → CARRY → TIRED).
5 Honest signals: why bluff before you charge?
- Objective
- Understand honest signaling — costly displays that communicate real intent.
- Instructions
- Focus only on the chest-beating. Count how many chest-beats happen before a charge (the game plays a burst of them). Discuss: in the wild, why would a 200 kg animal announce its attack instead of ambushing?
- Expected result
- Students realize the display gives everyone a chance to flee, avoiding a costly fight that could injure the gorilla too. Most real gorilla charges are bluffs — chest-beating and a short rush that stops early. The signal is "honest" because only a genuinely strong animal can afford to make it.
- Extension / Teacher's note
- Extension: Find other honest signals in nature (a stotting gazelle, a peacock's tail, a rattlesnake's rattle). Note: in later waves the game shortens the warning window — read
setWaveingames/gorilla/gorilla.jsto see the telegraph time shrink as anger rises.
6 Speed & strength comparisons
- Objective
- Put gorilla facts into human-scale comparisons using the game's own data.
- Instructions
- Collect the biology facts the Nerd Engine shows between waves and turn each into a comparison. Examples to look for: silverback mass (~200 kg), arm span (~2.6 m — wider than the 2.44 m goal is tall), charge speed (up to ~40 km/h), and that gorillas share ~98% of human DNA — all while being herbivores.
- Expected result
- Students build a comparison chart: a silverback outweighs 2–3 adults, out-sprints Usain Bolt over a short dash, and reaches across a full goal — yet gets all that power from eating plants. The 98% DNA fact reframes "monster" into "close relative."
- Extension / Teacher's note
- Extension: Graph gorilla charge speed against a walking human, a sprinter, and a car in a school zone. Note: the charge speed lives in the
SPDtable ingames/gorilla/gorilla.jsas pixels per second (~40 px per metre) — students can convert it themselves.
Four languages, one game
Everything in both games is translated into English, Español, Français and Português, switchable any time. Because the same action produces the same word in every language, the game becomes a natural vocabulary machine.
7 GOAL in four tongues
- Objective
- Learn that the same concept has different words across languages, and notice patterns.
- Instructions
- Score a goal in each of the four languages, switching the language between shots. Write down the word that flashes: GOAL (en), GOL (es/pt), BUT (fr) — and note the game's fact that in German it is TOR. Say each one out loud.
- Expected result
- Students build a four-column word: GOAL / GOL / BUT / GOL. They notice Spanish and Portuguese share GOL, while French and English each go their own way. The Nerd Engine adds that Brazilian announcers hold the "O" in GOOOL for over 20 seconds.
- Extension / Teacher's note
- Extension: Have each student "commentate" a goal in a language that is not their first. Note: these words come from the shared fact pool in
cemi/facts.js— the same data structure teaches vocabulary and stores the game's trivia.
8 Football idioms around the world
- Objective
- Explore how each football culture invents its own colorful vocabulary.
- Instructions
- Trigger the language facts by taking shots that miss (the Nerd Engine surfaces language trivia on misses). Look for the "nutmeg" fact: a ball played through the legs is caño (pipe) in Argentina, petit pont (little bridge) in France, and caneta (pen) in Brazil.
- Expected result
- Students collect a small dictionary of idioms and discover that each language pictures the same move differently. Ask them to invent an English nickname for the same trick and defend it.
- Extension / Teacher's note
- Extension: Research one more football-mad language (Italian, German, Arabic) and add its word for nutmeg. Note: idioms are stored with a
languagetag incemi/facts.js; the game picks them by context so they feel fresh each session.
9 Play in your second language
- Objective
- Use immersion: navigate menus and instructions in a language you are learning.
- Instructions
- Set the game to a language the student is studying and play a full round without switching back. They must read the menus (PLAY / QUICK PLAY), the prompts ("SWIPE TO SHOOT"), and the fact lines in the target language.
- Expected result
- Students realize they can understand a lot from context even when they don't know every word — a core language-learning skill. Afterwards they list five new words they picked up.
- Extension / Teacher's note
- Extension: Pair a native speaker with a learner for a two-player match narrated in the target language. Note: every string lives in a four-language table (see
cemi/i18n.js), so the whole interface really is fully translated, not machine-swapped on the fly.
Math & Geography
The 32-nation tournament is a powers-of-two machine, the flags open a world map, and the leaderboards are a statistics dataset waiting to happen.
10 Brackets are powers of two
- Objective
- Understand knockout brackets as repeated halving — powers of two.
- Instructions
- Start a World Tournament (32 nations). Before playing, have students predict how many rounds it takes to win. Then play and count: Round of 32 → Round of 16 → Quarter-finals → Semi-finals → Final.
- Expected result
- Students find it takes exactly 5 rounds, because 32 = 2⁵ and each round halves the field (32 → 16 → 8 → 4 → 2 → 1 champion). They can also compute total matches played across the whole tournament (31).
- Extension / Teacher's note
- Extension: Ask how many rounds a 64- or 128-team cup would need, then generalize to log₂(N). Note: the round names are the ordered list
ROUND_KEYSincemi/tournament.js, and the bracket starts with 32 teams inalive.
11 32 flags, 32 countries
- Objective
- Connect flags to countries, capitals, and continents.
- Instructions
- Open the nation picker and browse the 32 flags. Each flag in the game is drawn from simple shapes, not an image. For each nation a student is dealt, have them name the country, its capital, and its continent. The game also shows how many World Cups each nation has won.
- Expected result
- Students build a table of country / capital / continent / titles. They notice the geographic spread (South America and Europe dominate the trophy count) and can rank nations by titles.
- Extension / Teacher's note
- Extension: Locate all 32 nations on a blank world map and shade them by continent. Note: the nations, their names in four languages, skill ratings, and World Cup
titlesall live as data incemi/nations.js— a clean dataset to read.
12 Leaderboard statistics
- Objective
- Practice collecting data and computing simple statistics.
- Instructions
- Have several students each play the Daily Challenge (a 10-shot gauntlet where corners and combos score points). Record everyone's score. Compute the mean, median, range, and mode of the class scores.
- Expected result
- Students produce a real dataset and its summary statistics, then discuss which measure best describes "typical" performance and how one very high score skews the mean but not the median.
- Extension / Teacher's note
- Extension: Because the daily challenge is the same for everyone on Earth today, compare your class median to friends elsewhere. Note: the fairness comes from a seeded RNG in
cemi/challenge.js— the same date produces the same challenge worldwide.
Programming — the code is open
Both games are zero-build, readable JavaScript with learning-oriented comments. The whole engine is meant to double as a programming lesson. These three activities are a taste; the full Programming guide has a file-by-file map and a live source viewer.
13 Read the physics you just played
- Objective
- See that a "game feeling" is really a few lines of math.
- Instructions
- Open the Programming guide and click the source link for
games/golden-boot/shootout.js. Find theupdatemethod and read the four lines that move the ball each frame (gravity pullsvydown, drag slowsvz, spin nudgesvx). - Expected result
- Students recognize the exact forces they felt while playing, written as short arithmetic on the ball's velocity. "The Magnus effect" stops being a mystery and becomes one readable line.
- Extension / Teacher's note
- Extension: Ask "what would you change to make the ball float like it's on the Moon?" (Hint: gravity.) Note: no setup required — the source viewer fetches the real file and shows it with line numbers in a pop-up.
14 Find the state machine
- Objective
- Recognize a finite state machine as the "brain" behind the gorilla.
- Instructions
- Open
games/gorilla/gorilla.jsin the source viewer and find the bigswitch (this.state)block. Match eachcase(PATROL, STALK, CHESTBEAT, CHARGE, CARRY, TIRED) to a behavior you observed in Activity 4. - Expected result
- Students map every behavior they watched onto a named block of code and see that "intelligence" here is just a set of rules for switching between states.
- Extension / Teacher's note
- Extension: Draw the state diagram as arrows between boxes — which states can lead to which? Note: the header comment of
games/gorilla/gorilla.jseven draws the same diagram in text.
15 Everyone gets the same day
- Objective
- Understand deterministic randomness — how a "random" daily challenge can be identical for everyone.
- Instructions
- Open
cemi/challenge.jsin the source viewer. ReaddayKey(today's date as a string) anddailyRng(which seeds a random-number generator from that date). Discuss: if the seed is the same for everyone today, what does that guarantee? - Expected result
- Students grasp that a seeded generator is repeatable: the same seed always produces the same sequence, so every player worldwide faces an identical challenge each day — fair to compare, yet different tomorrow.
- Extension / Teacher's note
- Extension: Ask why true randomness would make leaderboards unfair. Note: the generator is
mulberry32seeded byhashStr, both incemi/core.js— small, readable, and reused across the whole engine.
Ready for more? The Programming guide maps every engine file to what it teaches and shows students how to clone and run the whole thing.
The simplest physics in the whole catalog
A ball falls, you tap to kick it back up, and that is the entire simulation — gravity pulls it down, your tap sets a new upward speed. Keepy-Uppy Infinito is the best first stop for a student who has never read game code before, because its physics loop in games/keepy/main.js is only three lines long.
16 Gravity in three lines
- Objective
- Recognize the smallest possible working physics simulation.
- Instructions
- Play a run, watching the ball fall and bounce off your feet. Then find the per-frame update:
ball.vy += G * dt; ball.y += ball.vy * dt; ball.x += ball.vx * dt;. A kick just resets the vertical speed:b.vy = -kickV(c). - Expected result
- Students see there is no hidden "physics engine" — gravity is one number (
G) added to a speed every frame, and position is that speed added to a location every frame. A kick is nothing more than making the vertical speed negative (upward) again. - Extension / Teacher's note
- Extension: Ask what would happen if
Gwere doubled — would the ball fall faster or slower, and would that make the game easier or harder? Note: this same three-line pattern (velocity, then position, updated every frame bydt) is the physics core hiding inside every other game in this catalog — it just gets more decorations from there.
17 A catch window that shrinks as you improve
- Objective
- See a difficulty ramp built entirely from one shrinking number, with no change to speed or gravity.
- Instructions
- Play until you have juggled 20–30 touches in a row. Then read
band(n): it useslerpto shrink your forgiving "foot contact" zone from a generous 150 pixels down to a tight 66 pixels over your first 30 touches. - Expected result
- Students notice the game feels harder the longer they survive, purely because the timing window for a good touch keeps tightening — the ball never gets faster, the target just gets smaller. They can also find the combo rule: switching feet each touch grows your combo (up to x5), and repeating the same foot resets it to 1.
- Extension / Teacher's note
- Extension: Design a shrinking-window difficulty ramp for a different game (dodgeball's reaction time? a quiz's time limit?) using the same
lerp(generous, tight, progress)idea. Note: the combo bonus (KICK_STEP) adds power for every level of combo, so one constant tunes the entire skill ceiling of the game.
A predator that grows every time it eats
The anaconda in games/anaconda/main.js is a chain of points, each one following the point ahead of it at a fixed distance — the classic "Snake" body, reused as a predator. Every time it swallows the ball it pushes new points onto its own tail, growing in real time in front of you.
18 The snake is an array that grows
- Objective
- See that "growth" is just adding items to a list, and "following" is a simple distance rule.
- Instructions
- Find the segment-following loop in
updateSnake: each point is pulled toward the point before it whenever it drifts more thanSEG(5 px) away. Then find the eating moment:const tail = snake.pts[snake.pts.length - 1]; for (let i = 0; i < GROW; i++) snake.pts.push({ x: tail.x, y: tail.y });. - Expected result
- Students see the whole snake is one JavaScript array of
{x, y}points. Growing by six segments after a bite is literally.push()called six times — no drawing trick, no sprite swap, just more data in the same list. - Extension / Teacher's note
- Extension: Change
GROWfrom 6 to 20 and predict how many bites it would take before the snake fills the whole pitch. Note: this "each item follows the one ahead of it" rule is the same idea behind a real conga line, a real train, and — not coincidentally — the ancestor game this mechanic is named after.
19 A predator that cannot turn on a dime
- Objective
- Understand steering with a limited turn rate, and why that limit becomes an escape strategy.
- Instructions
- Read how the snake decides its heading:
const want = Math.atan2(ball.y - head.y, ball.x - head.x);always points straight at the ball, butsnake.ang += clamp(diff, -TURN * dt, TURN * dt)only lets the heading turn byTURNradians per second. - Expected result
- Students realize the snake always "knows" where the ball is but physically cannot spin toward it instantly — so cutting sharply inside its curve is a real, learnable way to escape, just like out-turning a real predator (or a car) with a wide turning circle.
- Extension / Teacher's note
- Extension: Change
TURNfrom2.6to6and predict whether escaping becomes easier or harder. Note: the tinywig = Math.sin(tGlob * 6) * 0.35term added to the heading is the same "never move in a perfectly straight line" trick used by the gorilla's gait and the muerto zombies' wobble — three different animals, one shared idea.
Eight power-ups, one small system
Rumble Fútbol seats up to four players (people or bots) around one screen and drops random power-ups onto the pitch. games/rumble/main.js is a tidy example of how professional games avoid writing eight separate features for eight different effects.
20 One data table, eight power-ups
- Objective
- See how a small list plus a
switchcan implement many different game effects. - Instructions
- Read the list
const PU = ['giant', 'freeze', 'speed', 'tele', 'bumper', 'goals', 'ice', 'fire'];, then findapplyPow(pu)and itsswitch (pu.type)block, where each case just flips one timer or value in the game state (for examplecase 'giant': ball.giantT = 8; break;). - Expected result
- Students see that every power-up is the same tiny object shape —
{type, x, y, ttl}— and picking one up is a single lookup, not eight different code paths. Adding a ninth power-up would mean adding one more list entry and one morecase. - Extension / Teacher's note
- Extension: Invent a ninth power-up and write the one
caseline it would need. Note: the "ice" power-up doesn't add new physics — it just lowers the existinggripvariable used every frame, from 10 down to 1.6, so players slide instead of stopping.
21 Four keyboards, one screen
- Objective
- Understand local multiplayer as input routing, not separate copies of the game.
- Instructions
- Find
KEYSETSingames/rumble/main.js: four named key groups (wasd,arrows,ijkl,tfgh). Then readhumanVec(p), which only ever reads the keys or the touch-stick that belong to that one player. - Expected result
- Students see that "four players on one keyboard" is really "four small labeled lists of keys," with each player's code only ever looking at its own list — the game loop itself never changes. Bots use the same movement function with computed targets instead of key presses.
- Extension / Teacher's note
- Extension: Design a fifth key set for a fifth player and say which physical keys would be free to use on a real keyboard. Note: look at
botVec()and notice the bot nearest the ball attacks while its teammate hangs back to "cover" usinglerp— a two-role team AI without any pathfinding at all.
When the field itself plays
Water, earth, air and fire each get a character AND a home arena in games/elemental/main.js — and the arena is not just scenery. Step into a lava pool and the ball speeds up; the wind in the air arena keeps shifting. The pitch is a second opponent.
22 Two tables, one shared key
- Objective
- See data modeling where two different tables are linked by the same identifier.
- Instructions
- Read
const ELEMENTS = { water: {...}, earth: {...}, air: {...}, fire: {...} };and, right below it,const ARENAS = { water: {...}, earth: {...}, ... };. Notice both tables use the exact same four keys. - Expected result
- Students recognize that a character's stats and its home arena's colors are two separate lookups sharing one id (
"water","fire"...) — the same "one recipe card" idea used for the gods in Gods vs Titans FC, doubled up into two linked cards per element. - Extension / Teacher's note
- Extension: Sketch the two data rows a fifth element ("ice") would need. Note: every element uses the same
speed/cd/powerKeyfields, so the whole roster is playable through one sharedupdateGod-style function, not four separate ones.
23 The pitch fights back
- Objective
- See environment-as-gameplay: the arena changes the physics, not just the art.
- Instructions
- Play an away match on the fire arena and step the ball into a lava pool. Then read the code:
if (arenaId === 'fire' && ball.air <= 0) { ... ball.vx *= 1.4; ball.vy *= 1.4; }insideupdateArena. - Expected result
- Students see that touching a themed hazard directly multiplies the ball's velocity — the field is not decoration, it is one more system reading and writing the same
ballobject every other part of the game uses. - Extension / Teacher's note
- Extension: Change the lava multiplier from
1.4to2.2and predict how much harder the fire arena becomes. Note: the rival's power-use decision is only rolled once every quarter second of real time (not once per rendered frame) — a small trick that keeps the daily challenge fair whether the game runs at 30fps or 144fps.
Decide the traffic once, draw it forever
Cruce de Tráfico hops your player and ball across eight lanes of streets, rivers and train tracks. games/cruce/main.js is a clean example of separating "roll the dice" from "compute the picture right now."
24 Traffic is decided once, drawn every frame
- Objective
- Separate a one-time random decision from an ongoing pure calculation.
- Instructions
- Read
makeRow(idx): a row's direction and speed are rolled once, the first time that row is needed (row.dir = rng.chance(0.5) ? 1 : -1; row.speed = rng.range(1.1, 1.9) * ...). Then readmovingItems(row), which computes each car's current position using onlytGlob(elapsed time) — no randomness at all. - Expected result
- Students see that once a row's rules are set, exactly where every car sits at any instant is pure math, not a fresh coin flip — a fast player and a slow player who reach the same row still see the identical traffic pattern.
- Extension / Teacher's note
- Extension: Explain why "roll once per row, in order of distance" is exactly what keeps the Daily Challenge fair for every player on Earth. Note: the ball that follows you uses the exact same trick — its bounce direction after being "punted" loose is picked from a list that was shuffled once, when the row was created, not re-rolled at the moment of impact.
25 Hopping with easing math
- Objective
- Understand eased motion — a hop that speeds up then slows down, instead of a straight-line snap.
- Instructions
- Read
posOf(e):const ek = 1 - (1 - k) * (1 - k);blends the drawn position between tiles, whileMath.sin(k * Math.PI)adds a little arc of height along the way. - Expected result
- Students see that the player's logical tile updates instantly, but the drawn position eases smoothly across
HOP_DUR(0.16 seconds) using a "fast start, slow finish" curve plus a sine-wave bounce — the standard recipe behind every satisfying tile hop. - Extension / Teacher's note
- Extension: Change
HOP_DURto a much larger number and predict how the game would feel. Note: one function,checkHazards(), decides life-or-death for three completely different lane types — street, train, and river — just by branching onrow.type.
A horde is one object, copied many times
In Muerto FC a lone striker scores while a horde of slow zombies grows larger after every goal. games/muerto/main.js shows how a scary difficulty ramp can be nothing more than the same object pushed into an array again and again.
26 A horde is data, not new code
- Objective
- See a difficulty ramp implemented purely by adding more copies of one object.
- Instructions
- Score a few goals and count the zombies. Then read
onGoal():for (let i = 0; i < SPAWN_PER_GOAL; i++) zombies.push(makeZombie(false)); if (goals % 3 === 0) zombies.push(makeZombie(true));. - Expected result
- Students see that every zombie is the exact same object shape with different numbers (speed, size), and the horde growing is simply the array getting longer. Every third goal adds one faster "runner" variant — still the same object, just different stats.
- Extension / Teacher's note
- Extension: Change
SPAWN_PER_GOALfrom 2 to 5 and predict how much faster the horde balloons. Note: the game's own in-play "Nerd fact" names this exact pattern out loud — designers call it data-driven design, the same idea behind the gods' stat table and the dungeon's floor table.
27 Chase, but make it wobble
- Objective
- Understand simple steering AI plus a wobble trick that makes straight-line chasing look organic.
- Instructions
- Read
updateZombies:const dirx = (you.x - z.x) / d, diry = (you.y - z.y) / d;points straight at the player, thenconst wob = Math.sin(z.walk * 2) * z.wob;nudges that direction sideways over time. - Expected result
- Students see that "chase AI" is just a unit vector toward a target, and the staggering, lurching walk comes from adding a small sideways sine wave on top of that vector — cheap math producing a believable zombie shuffle.
- Extension / Teacher's note
- Extension: Compare this wobble to the anaconda's
wigterm and the gorilla's gait — three different animals, the same "never move in a perfectly straight line" trick. Note: every zombie's speed is multiplied bydt, so the horde chases at the same real-world speed whether the game runs at 30fps or 144fps.
You can see exactly where it will land
In Dodgeball FC, three rivals throw dodgeballs whose landing spot is decided the instant they leave the hand — games/dodgeball/main.js turns a real physics fact (a thrown ball's landing point is fixed the moment it launches) into a fairness mechanic you can literally see coming.
28 The shadow never lies
- Objective
- Read a telegraphed projectile and see how a growing shadow honestly announces danger.
- Instructions
- Watch a throw's shadow grow as it flies. Then read
drawDodgeballFlight: the ball's straight-line position islerp(sx, tx, p)andlerp(sy, ty, p), while its height is faked withMath.sin(p * Math.PI) * 110— a curve, not real gravity. - Expected result
- Students see the target point
(tx, ty)is chosen the moment the ball is thrown, so the growing shadow on the floor is showing the real future landing spot the whole time — nothing is hidden from the player. - Extension / Teacher's note
- Extension: Explain why a designer would choose an honest, readable shadow over a "more realistic" hidden trajectory. Note: the arc height is a single sine wave, not a physics simulation — a good example of faking just enough physics to feel right without simulating it for real.
29 Why not roll the dice live?
- Objective
- Understand why some games pre-roll all of their randomness in advance instead of during play.
- Instructions
- Read the comment above
throwPlaninstartMatch(), then see how each rival simply reads down its own pre-rolled list:const plan = throwPlan[r.idx][throwIdx[r.idx]++ % throwPlan[r.idx].length];. - Expected result
- Students grasp that if the rivals rolled fresh random numbers while playing, a fast-moving player and a cautious player would consume the shared seed in a different order — breaking the promise that everyone gets the identical daily challenge. Pre-rolling the whole script first avoids that entirely.
- Extension / Teacher's note
- Extension: Compare this "pre-roll everything into a list" solution with Endless Striker's "use three separate dice" solution to the very same fairness problem. Note: a hit only stuns a rival if
speed > 170— a slow bump just bounces off, so the same collision code produces two very different outcomes depending on one number.
The words the gods left behind
Zeus, Hermes, Poseidon and Hades are more than character names in games/gods/main.js — every one of them left a word behind in modern English, Español, Français and Português. This game is a doorway into etymology: the study of where words come from.
30 Words the gods left behind
- Objective
- Discover that everyday vocabulary in four languages still carries ancient Greek mythology inside it.
- Instructions
- Pick each god in the roster — Zeus, Hermes, Poseidon, Hades — and the Titan they face. For each name, find one modern word that comes from it: the Titans gave us titanic / titánico / titanesque / titânico (huge, unstoppable); Hermes (a messenger god) gave us hermetic / hermético (sealed shut, from Hermes Trismegistus); the god Pan (not in the roster, but a good bonus) gave us panic / pánico / panique.
- Expected result
- Students build a small table crossing gods against languages and discover that ancient stories are still hiding inside words they use every week, without ever having met a Greek myth.
- Extension / Teacher's note
- Extension: Research one more Greek-derived word not listed here (try "chaos," "atlas," or "echo") and trace it to its myth. Note: the game's own strings file stores each god's name and power description as a four-language data row — exactly the same shape used for vocabulary in the Languages section above.
31 One recipe, four gods
- Objective
- Recognize data-driven character design: one function, four very different-feeling gods.
- Instructions
- Read the
GODStable:zeus: { color: '#ffd400', speed: 175, cd: 7, ... }, hermes: { color: '#9fd0ff', speed: 190, cd: 5, ... }, and so on. Every god is drawn and updated by the exact same functions. - Expected result
- Students see a "trading card" pattern: one shared piece of code reads a different row of stats depending on which god you picked. A special power simply fills a bar over
cdseconds and fires once it is full. - Extension / Teacher's note
- Extension: Change
zeus.cdfrom 7 to 2 and predict how much more often Zeus's lightning could strike. Note: this is the same "one recipe, many characters" pattern used again — twice — in Elemental League's linked element/arena tables.
An explosion is just a distance check
In FIREBALL FC nobody ever touches the ball — two wizards cast fireballs and use the blast to shove it toward the goal. games/fireball/main.js turns a blast radius into a few lines of readable math, plus a mana bar that forces players to budget their spells.
32 How close is "still close enough"?
- Objective
- Understand radius-based blast physics using nothing but distance and a clamp.
- Instructions
- Read
explode(b):const d = dist(b.x, b.y, ball.x, ball.y); if (d < R + 60) { const power = (b.big ? 460 : 330) * clamp(1 - d / (R + 60), 0.35, 1); ... }. - Expected result
- Students see that the ball only feels the blast if it is close enough, and the closer it is, the harder the push — a simple, readable falloff model for an explosion, using the same
dist()helper as every collision check in this catalog. - Extension / Teacher's note
- Extension: Change the blast radius
Rand predict how much bigger the "danger zone" around a fireball becomes. Note:Math.atan2finds the direction to push the ball, the same function the anaconda uses to aim itself at the ball.
33 Spending mana like allowance money
- Objective
- Understand a cooldown/resource-budget system that forces pacing instead of spamming.
- Instructions
- Read
cast(): casting costs a flat 25 mana and is refused below that, whilew.mana = Math.min(100, w.mana + 20 * dt)refills it slowly every frame. - Expected result
- Students realize a fixed cost plus a fixed regeneration rate quietly sets a "shots per minute" ceiling — four casts empty the bar, and it takes five seconds to refill from zero. The designer chose these two numbers on purpose.
- Extension / Teacher's note
- Extension: Change the cost from 25 to 10 and predict whether the duel becomes more fun or just chaotic. Note: the computer-controlled wizard aims using the same "normalized direction vector" trick you will see again in Elemental League and Dodgeball FC.
A whole game, scripted before the first pitch
A robot pitches, you swipe to kick — Béisbol Gol's games/beisbol/main.js pre-plans every one of its 54 pitches before the first one is even thrown, and fakes convincing curveballs with nothing but sine waves.
34 The whole match is scripted in advance
- Objective
- See seeded-RNG determinism applied to an entire match at once, not just one spawn.
- Instructions
- Read
startMatch(daily):rng = daily ? dailyRng('beisbol') : Math.random; for (let i = 0; i < INNINGS * PITCHES_PER; i++) plan.push(makePitch(Math.floor(i / PITCHES_PER), rng));— all 54 pitches are built into an array before play starts. - Expected result
- Students see that on Daily Challenge day, every player on Earth receives the identical list of pitch types, speeds and curves in the identical order — the strongest example yet of "same seed, same world for everyone."
- Extension / Teacher's note
- Extension: Compare this "whole match pre-rolled into a list" approach to Endless Striker's "three separate live dice" approach and Dodgeball FC's "pre-rolled per-rival lists" — three different, equally valid solutions researched to the exact same fairness problem. Note: the result is reported with
reportDaily('beisbol', ...), the same function every daily-challenge game in this catalog calls.
35 Faking a curveball with a sine wave
- Objective
- See how blending a straight line with a trig wave can fake a complex-looking flight path.
- Instructions
- Read
pitchPath(p):let y = lerp(RELEASE.y, targetY, p); if (pitch.type === 'curve') y += pitch.amp * Math.sin(p * Math.PI);— one extra sine hump for a curveball, three humps (Math.sin(p * Math.PI * 3)) for a floater. - Expected result
- Students realize
pjust counts from 0 (leaves the machine) to 1 (reaches the plate), and each pitch "type" is really just a different wave shape added on top of a straight blend — no real spin physics required. - Extension / Teacher's note
- Extension: Sketch what a new pitch type using
Math.cos(p * Math.PI * 2)would look like. Note: the goalkeeper predicts your shot's landing spot with the real ballistic formula0.5 * GRAVITY * tc * tc— the same "AI that predicts the future" idea used by Golden Boot's keeper.
Hunting where the ball will be
Water behaves differently from grass, and games/dolphins/main.js knows it: every swimmer — your pod and the sharks alike — shares one momentum function, and the hunting shark aims not at the ball, but at where the ball is about to be.
36 Swimming has momentum, grass doesn't
- Objective
- Feel the difference between water drag and ground friction, and see it as one shared function.
- Instructions
- Read
swimStep(p, tx, ty, accel, dt, maxV): velocity accelerates toward a target, gets capped atmaxV, thenp.vx *= Math.exp(-0.7 * dt)— an exponential drag that slowly bleeds speed away. - Expected result
- Students notice every swimmer glides and coasts instead of stopping on a dime — a real property of moving through water — and that all seven swimming characters (your pod plus the sharks) call this exact same function with different numbers.
- Extension / Teacher's note
- Extension: Change the drag constant
0.7to0.1and predict how "slippery" the water would feel. Note: reusing one physics function for seven different entities is the same "write it once" instinct behind Rumble's power-up switch and the gods' shared stat table.
37 Hunting where the ball will be
- Objective
- Understand predictive ("lead") pursuit — real predator behavior, not naive chasing.
- Instructions
- Find the shark closest to the ball (it becomes the
hunter; the restpatrolin slow circles), then read its target:const tx = ball.x + ball.vx * 0.25; const ty = ball.y + ball.vy * 0.25;. - Expected result
- Students see the hunting shark does not aim at the ball's current spot — it aims a little ahead, at where the ball's own speed will carry it. This is the same principle real predators use to intercept moving prey, and the same idea behind a quarterback throwing to a receiver's future position.
- Extension / Teacher's note
- Extension: Change the
0.25lead factor to0(chase the current spot) or1(aim far ahead) and predict how much easier or harder passing becomes. Note: the patrolling sharks circle usingMath.cos/Math.sinon a slowly advancing angle — the same trigonometric orbit trick used for menu decorations elsewhere in the engine.
Ten floors, one small table
Dungeon Striker sends you down ten themed floors, each a 45-second match against a different goalkeeper. games/dungeon/main.js shows the biggest procedural-generation and seeded-RNG lesson in the whole catalog — an entire ten-floor run is decided before you take your first step.
38 Ten floors, one small table
- Objective
- See an entire level progression built from one array of plain data rows.
- Instructions
- Read the
FLOORSarray: each row is just{ keepers: [...], hunt: 0.3, tint: '...' }. Floor 1's skeleton keeper, floor 2's twin slimes, floor 4's mimic — all ten floors live in this one table. - Expected result
- Students realize adding an eleventh floor means adding one more row to this table, not writing new gameplay code — the exact same "one recipe, many rows" idea already seen in the gods' stat table and Elemental League's linked tables, now describing whole levels instead of characters.
- Extension / Teacher's note
- Extension: Design a data row for an eleventh floor, picking a keeper type, a
huntvalue and a tint color. Note: the same table also drives the floor's rendered color tint — one row of data, two completely different jobs.
39 A whole run of loot, decided at floor one
- Objective
- See seeded-RNG determinism scaled up to an entire multi-floor run, plus a telegraphed AI state switch.
- Instructions
- Read
startRun(daily):rng = makeRng(daily ? dailyRng('dungeon') : Math.random);, then every loot offer for all nine choice-points and every floor's random "quirk" are drawn fromrngright there, before floor one even begins. Also findupdateHunt: a keeper that decides to hunt first shakes forwindupT = 0.5seconds before actually charging. - Expected result
- Students see the biggest example yet of "decide everything with the seed, up front" — bigger than Béisbol's single match, this is an entire ten-floor run — and recognize the half-second warning shake as the same honest, telegraphed-attack fairness pattern as the gorilla's chest-beat and dodgeball's growing shadow.
- Extension / Teacher's note
- Extension: Change floor one's
hunt: 0.3tohunt: 1and predict how much more aggressive the easiest floor suddenly becomes. Note: picked-up loot is just a counter (loot.wings,loot.blast...) multiplied into a stat wherever it matters, so grabbing the same item twice keeps compounding its effect with no extra code.
Three separate dice, so nothing steals from anything else
Endless Striker swerves you down a three-lane dribble run in games/striker/main.js. It is also the game that sets up the catalog's most advanced lesson: sometimes one shared game needs several completely independent seeded random-number generators.
40 Three separate dice, so nothing steals from anything else
- Objective
- Understand why a single fair game sometimes needs multiple independent seeded RNGs.
- Instructions
- Read
startRun():rng = makeRng(...); rivalRng = makeRng(...); curveRng = makeRng(...);— three separately-seeded generators, one each for obstacles, the rival parade, and the track's curve. - Expected result
- Students grasp that if everything shared one die, picking your country for the rival parade would accidentally reroll the obstacle course too — three separate, independently-seeded generators keep each promise ("same obstacles for everyone," "your country choice doesn't change the world") honest at the same time.
- Extension / Teacher's note
- Extension: Imagine merging the three generators back into one and predict exactly what would break. Note: this same "three separate seeded generators" idea is what makes the next lesson, Endless Striker 3D, able to reuse the identical daily challenge as this 2D game.
41 One touch, four different outcomes
- Objective
- See collision resolution gated by player state, instead of a plain yes/no hit.
- Instructions
- Read the collision block in
updatePlay(): it checksfireT(unstoppable), then whether you jumped or slid correctly, theninvulT(shield grace), thenshieldOn, before finally callingcrash(o)as a last resort. - Expected result
- Students see that touching an obstacle is not one rule — it is an ordered checklist of "was I in a state that saves me?" questions, and only if every one fails does the run actually end.
- Extension / Teacher's note
- Extension: Change the speed-ramp constant (the
2.2in the distance-to-speed formula) and predict how much sooner the game reaches its top speed. Note:games/striker/shared.jsdeclares itself the single source of truth for both this 2D game and its 3D twin — the next lesson shows exactly what that means.
The same game, a different set of paints
This is the catalog's flagship lesson. Endless Striker 3D is not a new game — its own header comment in games/striker3d/main.js says so directly: the same rules, the same numbers, the same daily seed as Endless Striker, rebuilt with a real 3D camera instead of a flat canvas.
42 The same game, a different set of paints
- Objective
- Prove that "3D" can mean "new renderer," not "new game," by finding shared code with your own eyes.
- Instructions
- Open
games/striker3d/main.jsand read its header comment. Then opengames/striker/shared.jsand find a constant such asROCKET_RANGE_M— then confirmgames/striker3d/main.jsimports that very same constant from that very same file. - Expected result
- Students see with their own eyes that item ranges, rival rotation, speed caps and even the daily seed are defined exactly once and imported by two completely different-looking games — one flat, one three-dimensional.
- Extension / Teacher's note
- Extension: Find a second constant shared between the two files. Note: this is the clearest possible answer to "what does 3D actually add?" — when the rules are provably identical, the only honest difference left is how the picture gets drawn.
43 A scene is a diorama, not a drawing
- Objective
- Understand the core idea of a 3D scene graph, in contrast to 2D canvas drawing.
- Instructions
- Read
buildWorld()ingames/striker3d/scene.js: aScene, aPerspectiveCamera, and aDirectionalLightare each created as objects and added into the scene, rather than drawn with pixel commands. - Expected result
- Students grasp that 3D graphics don't draw pixels directly the way
fillRectdoes — you arrange real objects (lights, cameras, shapes) into a "shoebox diorama," and the renderer takes a photo of that diorama from the camera's position, every single frame. - Extension / Teacher's note
- Extension: Find where the camera itself uses
lerpto ease toward the player's lane — the exact same easing trick from Endless Striker's lane movement, just applied to a camera instead of a runner. Note: old defenders are never thrown away —acquireDefender/releaseDefenderwipe and reuse them from a pool, because creating new 3D shapes constantly can stutter the game, a real lesson in why game engines recycle objects.