# Icy Tower - Game & AI

> A game written from scratch, then a network evolved to play it

A browser platformer built with no game engine, plus a neural network taught to climb it by a genetic algorithm, reaching floor 179 after 2,000 generations.

- Year: 2025 - 2026
- Tags: Vanilla JS, Canvas, MediaPipe, Genetic Algorithms
- Page: https://www.rondahan.com/projects/icy-tower
- Playable build: https://www.rondahan.com/games/icy-tower/index.html
- Game repo: https://github.com/rondahan04/icy_tower
- AI repo: https://github.com/rondahan04/icy_neural_network

## Numbers

- **179** - floors reached by the AI
- **2,000** - generations trained
- **0** - game engines or ML libraries used

## What it is

A browser remake of Icy Tower built without a game engine, and a neural network taught to play it. The game has hand-written physics, a webcam hand-tracking control mode, ghost replays of previous runs and a coin economy with unlockable characters. The AI side is a neural network evolved by a genetic algorithm, which reached floor 179 after 2,000 generations.

## How it works

1. **Game - The clock runs at a fixed rate** - The game always takes the same size step, whatever the frame rate, so a fast machine and a slow one play the same. If the tab goes to the background the step is capped, otherwise coming back would replay ten seconds at once.
2. **Game - Movement written by hand** - Gravity, friction, and how the player lands on a platform are all written directly. The rule that matters: the faster you are running, the higher you jump. Everything else in the game is built on that one line.
3. **Game - Platforms get reused** - A platform that scrolls off the bottom is moved back to the top instead of being thrown away, so the tower can go on forever without the memory growing.
4. **Game - Three ways to play** - Keyboard, touch, or webcam. In webcam mode your hand steers, and a quick flick up or a pinch makes you jump. The hand position is smoothed first, or every tremor would read as a move.
5. **AI - The game again, with nothing to look at** - Training needs to play thousands of games a minute, which no browser will do. So the game was rebuilt in Python and run headless: no window, no drawing, just the numbers.
6. **AI - Evolve, do not train** - Population of 200, top 10% carried forward untouched, layer-wise crossover that swaps whole kernel and bias pairs, and a mutation sigma that doubles after five generations without improvement.
7. **AI - One bridge file** - Evolved weights serialise to JSON that both runtimes read, so the network trained in Python is the same one that plays in the browser.

## Flow

The two repos are one system. The Python port exists so training can run without a browser, and best_weights.json is the only thing that crosses between them.

- game.js (the brain of the game)
- ported to → icy_tower.py (same brain, but headless)
- 2,000 generations → genetic_algorithm.py (200 pop, 10% elites) + neural_net.py (the player's decision-maker)
- writes → best_weights.json (the bridge)
- read back by → Browser AI mode (same weights, live)

## Technical notes

1. **No engine, deliberately** - Phaser or Matter.js would have abstracted away exactly the part that was interesting: the momentum-to-jump-height coupling and the collision handling.
2. **Neuroevolution rather than reinforcement learning** - No gradients and no RL library. The genetic algorithm is written by hand, which made every design choice something I had to configure.
3. **Taught one skill at a time** - Training all of it at once went nowhere, so it was split into stages. Roughly the first 500 generations only rewarded jumping. The next 500 added moving left and right, which is what makes a jump go anywhere. Only then did the score start counting floors climbed. Each stage starts from the winners of the last one, so nothing is relearned from scratch.
4. **Fitness shaped to give early signal** - Height times 0.5 plus floor times 1000. Generation zero contains nobody who can land on a platform, so a floor-only score gives every agent zero and evolution has nothing to select on.
5. **One seed per generation** - Every agent in a generation plays an identical tower. Otherwise the fittest agent is just the one that got easy platforms, and the run selects for luck.
6. **Layer-wise crossover** - Children inherit whole layers from each parent instead of a random mix of individual weights. Splicing mid-layer destroys whatever that layer had learned.

## Stack

- **Game:** Vanilla JavaScript (ES6+), HTML5 Canvas, Web Audio API
- **Input:** MediaPipe Hands, Keyboard, Touch
- **AI:** Python, Keras 3 on JAX, NumPy, pygame
- **Training UI:** FastAPI, Server-Sent Events, Gradio

## Repo layout

- `index.html` - Canvas page; loads the game and hand tracking
- `game.js` - Physics, sprites, combos, coins, ghost replays
- `icy_tower.py` - Python port of the game, headless for training
- `neural_net.py` - The 14-16-8-3 network and weight serialising
- `genetic_algorithm.py` - Elites, layer-wise crossover, adaptive mutation
- `train_headless.py` - The 2,000-generation curriculum run
- `best_weights.json` - The bridge: Python writes it, the browser reads it

## Honestly

The whole game lives in a single 4,400-line game.js file. Splitting it into modules is the top item on its own roadmap and I have not done it.

---

Machine-readable: [llms.txt](https://www.rondahan.com/llms.txt) · [sitemap.xml](https://www.rondahan.com/sitemap.xml) · Markdown variants at [/md](https://www.rondahan.com/md) or via `Accept: text/markdown`.
