Icy Tower - Game & Neuroevolution AI
A physics platformer written from scratch, then a network evolved to play it
- 179
- floors reached by the AI
- 2,000
- generations trained
- 0
- game engines or ML libraries used
What it does
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 403-weight network evolved by a genetic algorithm, which reached floor 179 after 2,000 generations.
How it works
Game
Fixed-timestep loop
An accumulator with a 250ms delta cap. Fixed timestep so physics does not change with frame rate, and the cap so a backgrounded tab does not try to simulate ten seconds in one frame.
Hand-rolled physics
AABB collision, gravity and friction written directly, with jump height coupled to horizontal speed. That coupling is the mechanic the original game rests on.
Object pooling
Platforms scrolling off the bottom are recycled to the top, so an endless tower runs in constant memory.
Three input modes
Keyboard, touch, or webcam. Hand position runs through an exponential moving average with a neutral deadzone; jumping fires on either an upward flick or a thumb-to-index pinch, each behind its own latch so one motion cannot trigger twice.
AI
A headless copy of the game
Training cannot run in a browser, so the physics is mirrored in Python and runs with no rendering.
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.
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.
Tech choices
The decisions that shaped it, and why they went that way.
- 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.
- 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 justify rather than configure.
- 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.
- 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.
- 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.
Built with
- Game
- Vanilla JavaScript (ES6+)HTML5 CanvasWeb Audio API
- Input
- MediaPipe HandsKeyboardTouch
- AI
- PythonKeras 3 on JAXNumPypygame
- Training UI
- FastAPIServer-Sent EventsGradio
What it produces

How the repo is laid out
Roughly the order you'd read it in: entry point first, then the parts doing the work.
Game
index.htmlCanvas page; loads the game and hand trackinggame.jsPhysics, sprites, combos, coins, ghost replaysAI
icy_tower.pyPython port of the game, headless for trainingneural_net.pyThe 14-16-8-3 network and weight serialisinggenetic_algorithm.pyElites, layer-wise crossover, adaptive mutationtrain_headless.pyThe 2,000-generation curriculum runbest_weights.jsonThe bridge: Python writes it, the browser reads it
Where it falls short
The whole game lives in a single game.js file. Splitting it into modules is the top item on its own roadmap and I have not done it. The sprites and sounds come from the original Icy Tower, so this stays a personal learning project rather than anything I would publish as my own artwork.