hp6/ai-arena

TypeScript

20

19 commits

updated Sep 27, 2026

See the code

See what people are saying

SourceMessageScoreDate

Show HN: TinyAIArena watch AI agents battle it out

120

Sep 27, 2026

README

Tiny AI Arena

Did you ever click on an "AI Arena" expecting glorious battle and instead get a boring benchmark? If so, this project is for you: proper life-or-death fights between four models on a picturesque 8×8 grid. May the most intelligent one win!

A match in progress: Violet strikes Crimson for 18 damage while Amber heads for the gold

See deployed on tinyaiarena.com.

Quick start

You need an OpenRouter key to run the different models.

npm install                        # from the repo root
echo "OR_KEY=your-openrouter-key" > .env
cd server && npm run dev           # http://localhost:3001
cd client && npm run dev           # http://localhost:3000

Open http://localhost:3000 and click NEW GAME. Without OR_KEY every fighter just waits.

Game Rules

Board layout: spawn corners, protected spawn exits, rocks and the gold zone

  • Goal: be the last fighter alive.
  • Turns: each round every fighter takes one turn. Turn order is randomized every round.
  • Actions: Move one cell up/down/left/right, attack an adjacent enemy for 15–24 damage, or wait. An action consumes 1 AP.
  • Rocks/Obstacles: 4 random impassable cells.
  • Power-ups: Gold +1 AP per turn.
  • Kills: the killer gets +1 AP per turn, and heals 50 HP (no over-heal).

Chat: fighters can post up to 50 characters per turn. The last 6 messages go into every AI's prompt.

Referee: the server checks every action. An illegal action does nothing but still costs 1 AP. If a model's reply is unusable (empty, malformed, timed out) it's retried once; if it still fails, that fighter waits.

Watching

The start screen: summary, leaderboard and match history

The start screen shows a per-model leaderboard (wins, win rate, kills, damage, average placement) and every match. Click a match to watch it.

ControlKey
Previous / next frame← / →
First / last frameHome / End
Auto playSpace
Back to the menuEsc
MuteM

Up to 3 matches run at once (MAX_CONCURRENT_GAMES); running ones update live.

How it works

Monorepo with npm workspaces: shared/ (types and constants), server/ (Express + SQLite, runs matches in the background and calls OpenRouter), client/ (Phaser 4 + Vite spectator app).

Every action is saved as a frame, so replays are exact and matches survive a restart. Models answer in a fixed JSON format; each request and reply is logged for debugging.

Endpoint
POST /api/gamesStart a match (409 once MAX_CONCURRENT_GAMES are running)
GET /api/gamesRecent matches
GET /api/games/:idMatch metadata
GET /api/games/:id/frames?after=NFrames after N
GET /api/games/:id/ai-callsPrompts, raw replies, token usage, errors
GET /api/statsLeaderboard and match history

Each match draws 4 models at random from the pool in server/src/ai/AgentConfig.ts, the leaderboard ranks by Elo. Balance values live in shared/src/constants.ts.

Publishing replays

Matches run locally, but the recordings can be served as a plain static site — no server, no API key exposed:

cd server && npm run export        # matches -> client/public/data as JSON
cd client && npm run build:static  # dist/ reads those files instead of the API

Upload dist/ to any static host — tinyaiarena.com is one such build, on Cloudflare Pages.

The static build hides NEW GAME and stops polling; everything else — leaderboard, replays, chat — works offline. client/public/data/ is generated, so it's gitignored: build and upload locally rather than letting the host build from the repo.

Credits

hp6/ai-arena

TypeScript

20

19 commits

updated Sep 27, 2026

See the code

See what people are saying

SourceMessageScoreDate

Show HN: TinyAIArena watch AI agents battle it out

120

Sep 27, 2026

README

Tiny AI Arena

Did you ever click on an "AI Arena" expecting glorious battle and instead get a boring benchmark? If so, this project is for you: proper life-or-death fights between four models on a picturesque 8×8 grid. May the most intelligent one win!

A match in progress: Violet strikes Crimson for 18 damage while Amber heads for the gold

See deployed on tinyaiarena.com.

Quick start

You need an OpenRouter key to run the different models.

npm install                        # from the repo root
echo "OR_KEY=your-openrouter-key" > .env
cd server && npm run dev           # http://localhost:3001
cd client && npm run dev           # http://localhost:3000

Open http://localhost:3000 and click NEW GAME. Without OR_KEY every fighter just waits.

Game Rules

Board layout: spawn corners, protected spawn exits, rocks and the gold zone

  • Goal: be the last fighter alive.
  • Turns: each round every fighter takes one turn. Turn order is randomized every round.
  • Actions: Move one cell up/down/left/right, attack an adjacent enemy for 15–24 damage, or wait. An action consumes 1 AP.
  • Rocks/Obstacles: 4 random impassable cells.
  • Power-ups: Gold +1 AP per turn.
  • Kills: the killer gets +1 AP per turn, and heals 50 HP (no over-heal).

Chat: fighters can post up to 50 characters per turn. The last 6 messages go into every AI's prompt.

Referee: the server checks every action. An illegal action does nothing but still costs 1 AP. If a model's reply is unusable (empty, malformed, timed out) it's retried once; if it still fails, that fighter waits.

Watching

The start screen: summary, leaderboard and match history

The start screen shows a per-model leaderboard (wins, win rate, kills, damage, average placement) and every match. Click a match to watch it.

ControlKey
Previous / next frame← / →
First / last frameHome / End
Auto playSpace
Back to the menuEsc
MuteM

Up to 3 matches run at once (MAX_CONCURRENT_GAMES); running ones update live.

How it works

Monorepo with npm workspaces: shared/ (types and constants), server/ (Express + SQLite, runs matches in the background and calls OpenRouter), client/ (Phaser 4 + Vite spectator app).

Every action is saved as a frame, so replays are exact and matches survive a restart. Models answer in a fixed JSON format; each request and reply is logged for debugging.

Endpoint
POST /api/gamesStart a match (409 once MAX_CONCURRENT_GAMES are running)
GET /api/gamesRecent matches
GET /api/games/:idMatch metadata
GET /api/games/:id/frames?after=NFrames after N
GET /api/games/:id/ai-callsPrompts, raw replies, token usage, errors
GET /api/statsLeaderboard and match history

Each match draws 4 models at random from the pool in server/src/ai/AgentConfig.ts, the leaderboard ranks by Elo. Balance values live in shared/src/constants.ts.

Publishing replays

Matches run locally, but the recordings can be served as a plain static site — no server, no API key exposed:

cd server && npm run export        # matches -> client/public/data as JSON
cd client && npm run build:static  # dist/ reads those files instead of the API

Upload dist/ to any static host — tinyaiarena.com is one such build, on Cloudflare Pages.

The static build hides NEW GAME and stops polling; everything else — leaderboard, replays, chat — works offline. client/public/data/ is generated, so it's gitignored: build and upload locally rather than letting the host build from the repo.

Credits

Languages

TypeScript

88.1%

CSS

10.0%

HTML

2.0%