title: The Tower Learns You
emoji: πΌ
colorFrom: purple
colorTo: gray
sdk: docker
app_port: 7860
pinned: false
short_description: A roguelite where a small LLM generates & adapts the game
tags:
- build-small-hackathon
- thousand-token-wood
- off-brand
- llama-champion
- llama-cpp
- roguelite
- game
- gradio
- track:wood
- sponsor:openai
- achievement:offgrid
- achievement:offbrand
- achievement:llama
πΌ The Tower Learns You
A proof-of-concept roguelite where a language model is load-bearing β not decoration.
Strip the AI out and there is no game content. The run, the entire skill pool, the bosses, the loot, your class evolutions, and the endgame are all generated by a local LLM β and reshaped around the way you actually play.
βΆ Demo
https://github.com/user-attachments/assets/db5c6ad2-02bf-4fd7-8a0f-b50d60218d7b
Video not loading? Watch / download the 60-second trailer Β» β every frame is real, unedited game output: the same
renderHTML the live app produces, captured from authentic engine state.
π Built for the Build Small Hackathon β track: Thousand Token Wood. π¬ Demo video: https://github.com/user-attachments/assets/db5c6ad2-02bf-4fd7-8a0f-b50d60218d7b π£ Social post: https://x.com/lurkingof/status/2066288605937742024
π§ββοΈ Judging / running locally β use the GitHub
mainbranchFor the full load-bearing-AI experience (incl. AI-generated skills via a local llama.cpp model), clone and run github.com/vknt-m/tower-learns-you (
mainbranch) β see Run it below. This hosted Space runs the same game against HF Inference Providers; the localmainbuild is the reference setup for testing/judging.
Why this exists
Most games use AI for flavor text or matchmaking. This is an experiment in the opposite: making the model responsible for the parts that normally ship hard-coded. The Python engine owns the rules (damage, AP, turn order, balance); the model owns the content and adaptation, behind strict typed contracts. The result is a game whose identity is regenerated every run and bent by your own behavior.
What the AI actually drives
- Run identity β theme, title, and the provisional archetype the floor sequence is built around.
- The entire skill pool β 16 skills per run, each named, described, and costed (validated against the same rules authored skills follow).
- Equipment language β weapon / armor / relic naming drawn from a per-run loot lexicon.
- Class evolutions β at the mid and late thresholds you evolve into an AI-authored class with a unique signature skill, shaped by how you've been fighting.
- Boss identities & decks β each guardian's name, epithet, weaknesses, and move deck.
- Live boss adaptation β the final boss reads your patterns and adjusts its deck mid-fight to counter you.
- Turn narration & ascension passives β moment-to-moment flavor and the permanent perks carried between runs.
All of it is derived from your behavioral history: action ratios, element and skill preferences, defensive tendencies, and healing choices.
Why it doesn't break
The model proposes; Python disposes. Every generated object passes through Pydantic schemas and Python registries before it can touch game state, so the model can author content but can never change a formula, invent a mechanic, or mutate state directly. If the model is slow, unavailable, or returns something invalid, the engine falls back to deterministic authored content and the UI flags it β the game is always playable, with or without a model.
The loop
Shape an initiate β choose a starter skill β climb ten floors of turn-based combat (spend AP, cast skills, read enemy intent) β claim loot and learn new skills β evolve at the thresholds β defeat the adaptive final boss β ascend and carry permanent power into a freshly generated run.
Stack
- Frontend β a custom HTML / CSS / JS client served by Gradio 6
gr.Server(no Blocks UI). The Python game logic and HTML rendering are reused as REST endpoints. - Engine β deterministic, seeded Python: combat, loot, progression, balance.
- Model gateway β pluggable backends:
mock(deterministic, no credentials),local_openai(loopback llama.cpp),hf_inference(Hugging Face).
flowchart LR
UI[Custom gr.Server frontend] --> Engine[Deterministic Python engine]
Engine --> Core[Combat Β· Loot Β· Progression]
Engine --> GW[Typed model gateway]
GW --> Mock[Mock β deterministic]
GW --> Local[Loopback llama.cpp]
GW --> HF[HF Inference]
GW --> Guard[Pydantic schemas + registries]
Run it
The default mock backend needs no credentials and no GPU:
python -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install -r requirements-server.txt
python tools\catalog_assets.py # builds Assets/manifest.json (first run only)
python server_app.py
Open http://127.0.0.1:7861/.
To run against a real model, set MODEL_BACKEND to local_openai (loopback llama.cpp) or
hf_inference (with HF_TOKEN and HF_MODEL_ID). The local launcher binds to 127.0.0.1 only and
exposes the model nothing but read-only inspection and typed submit_* proposal tools β never shell,
filesystem, or direct state access.
Status
This is a proof of concept / demo, not a finished game. The full ten-floor loop β AI generation, behavioral adaptation, evolutions, and ascension β works end-to-end on both the deterministic mock backend and a local model. Balance and content breadth are intentionally illustrative.
Tests
.\.venv\Scripts\python.exe -m pytest -q
Assets & license
Image and audio assets live under Assets/, licensed CC BY 4.0 with attribution to vknt-m
β see ASSET_LICENSE.md and ATTRIBUTION.md. The
trailer music and bgm is royalty-free.