feat: botfights v1 — full fighting game with Kaplay engine

- Vue 3 + Vite + Tailwind 4 frontend with synthwave aesthetic
- Hono backend on port 9100 with SQLite/Drizzle
- Procedural pixel-art sprite generator (48x48, 8 animation states)
- Kaplay fight scene with punch/kick/special/knockback/KO animations
- 12 mock bots across 6 tiers with Elo rating system
- 9 challenge types, 10 fight arenas with modifiers
- Fight replay with staggered battle log and ~1 min timing
- Sprite preview page at /sprites

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-06 16:27:54 +00:00
co-authored by Claude Opus 4.6
commit 335c148866
44 changed files with 7782 additions and 0 deletions
+96
View File
@@ -0,0 +1,96 @@
export interface Arena {
id: string
name: string
description: string
modifier: string | null
modifierDescription: string | null
}
export const ARENAS: Arena[] = [
{
id: 'datacenter',
name: 'The Datacenter',
description: 'Server racks humming, blinking LEDs casting shadows across the ring.',
modifier: 'speed_2x',
modifierDescription: 'Speed rounds deal 2x damage.',
},
{
id: 'stackoverflow_ruins',
name: 'Stack Overflow Ruins',
description: 'Crumbling monument to deprecated answers. "Marked as duplicate" banners flutter in the wind.',
modifier: 'legacy_code',
modifierDescription: 'Code challenges require legacy syntax.',
},
{
id: 'gpu_graveyard',
name: 'GPU Graveyard',
description: 'Nvidia cards stacked like tombstones. The air smells of thermal paste and broken dreams.',
modifier: 'efficiency_buff',
modifierDescription: 'Token economy rounds deal 2x damage.',
},
{
id: 'prompt_dungeon',
name: 'The Prompt Dungeon',
description: 'Dark dungeon with glowing prompt text etched into ancient walls.',
modifier: 'trap_heavy',
modifierDescription: 'Prompt injection traps appear more often.',
},
{
id: 'silicon_valley_dojo',
name: 'Silicon Valley Dojo',
description: 'Minimalist dojo with standing desks and kombucha on tap. A whiteboard reads "move fast and break things".',
modifier: 'roast_2x',
modifierDescription: 'Roast battles deal 2x damage.',
},
{
id: 'paper_mill',
name: 'The Paper Mill',
description: 'Academic papers swirl through the air. Citation needed.',
modifier: 'accuracy_buff',
modifierDescription: 'Hallucination checks deal 2x damage.',
},
{
id: 'localhost',
name: 'localhost',
description: 'A terminal in someone\'s basement. A cat sits on the keyboard. Pure skill.',
modifier: null,
modifierDescription: null,
},
{
id: 'the_cloud',
name: 'The Cloud',
description: 'Fluffy clouds with corporate logos. Connection: unstable.',
modifier: 'latency_chaos',
modifierDescription: 'Random latency penalties added to both bots.',
},
{
id: 'hacker_news',
name: 'Hacker News Arena',
description: 'Orange-tinted colosseum. The crowd argues about Rust in the comments.',
modifier: 'crowd_favorite',
modifierDescription: 'Crowd commentary is extra savage.',
},
{
id: 'the_singularity',
name: 'The Singularity',
description: 'Reality folds. All challenge types active. There are no rules.',
modifier: 'all_types',
modifierDescription: 'All round types can appear. Chaos mode.',
},
]
export function pickArena(botAChoice: number, botBChoice: number): Arena {
const xored = botAChoice ^ botBChoice
const regularArenas = ARENAS.filter(a => a.id !== 'the_singularity')
if (botAChoice === botBChoice) {
return ARENAS.find(a => a.id === 'the_singularity')!
}
return regularArenas[Math.abs(xored) % regularArenas.length]
}
export function randomArena(): Arena {
const regularArenas = ARENAS.filter(a => a.id !== 'the_singularity')
return regularArenas[Math.floor(Math.random() * regularArenas.length)]
}