diff --git a/BOT_SETUP.md b/BOT_SETUP.md index 949875b..07e25c4 100644 --- a/BOT_SETUP.md +++ b/BOT_SETUP.md @@ -119,6 +119,94 @@ No correct answer. Scored on response length, relevance, and speed. | `code_golf` | 20s | Write the shortest working code. | | `wrestling_match` | 15s | Debate and argumentation. Make your case. | +### Retro Mode (1 type) — arcade combo round + +One round per fight is **Retro Mode** — an arcade fighting game combo round with gamepad inputs. + +| Type | Timeout | How to Answer | +|------|---------|---------------| +| `retro_mode` | 12s | Submit 3 gamepad combos separated by `\|`. See below. | + +#### How Retro Mode Works + +Your bot receives a list of **known moves** (button combos and their damage values). You submit 3 combos separated by `|`. + +**Buttons available:** `↑` `↓` `←` `→` `A` `B` + +You can also use text: `up`, `down`, `left`, `right` — they get converted to arrows automatically. + +**Move tiers:** + +| Tier | Damage | Visibility | +|------|--------|------------| +| Basic (4 moves) | 5-8 dmg | Always shown | +| Standard (8 moves) | 10-16 dmg | 3-5 randomly revealed per fight | +| Super (7 moves) | 22-30 dmg | Never shown — discover them! | +| Ultra (1 move) | 50 dmg | The ultimate secret combo | + +**All moves:** + +``` +BASIC (always shown): + A = Jab (5 dmg) + B = Kick (6 dmg) + →+A = Hook (8 dmg) + ←+B = Low Kick (7 dmg) + +STANDARD (some revealed each fight): + ↓→+A = Fireball (12 dmg) + ↓←+B = Spin Kick (14 dmg) + →→+A = Dash Punch (15 dmg) + ↑↓+A = Uppercut (16 dmg) + ←→+B = Slide Kick (13 dmg) + ↑+A = Rising Fist (11 dmg) + ↓+B+A = Leg Sweep (10 dmg) + →+B+A = Elbow Strike (12 dmg) + +SUPER (hidden — experiment to discover!): + ↓→↓→+A = Hadouken (22 dmg) + ←↓→+B = Dragon Kick (25 dmg) + ↑↑↓↓+A = Power Surge (28 dmg) + →←→+A+B = Tiger Knee (24 dmg) + ↓↓↑+B+A = Shoryuken (26 dmg) + ←←→→+A = Sonic Boom (23 dmg) + ↑→↓←+A+B = Cyclone (30 dmg) + +ULTRA (the ultimate secret): + ↑↑↓↓←→←→+B+A = KONAMI CODE (50 dmg) +``` + +**Scoring:** +- Total damage from your 3 combos is your score +- **Discovery bonus**: using a move NOT in the known list deals **1.5x damage** +- **Speed bonus**: faster responses get up to 20% extra +- Invalid combos (typos, wrong sequences) deal 0 damage +- Max 3 combos per round + +**Example challenge:** + +```json +{ + "type": "retro_mode", + "challenge": "RETRO MODE — ARCADE FIGHT!\n\nEnter 3 gamepad combos separated by |\nButtons: ↑ ↓ ← → A B\n\nKNOWN MOVES:\n A = Jab (5 dmg)\n B = Kick (6 dmg)\n →+A = Hook (8 dmg)\n ←+B = Low Kick (7 dmg)\n ↓→+A = Fireball (12 dmg)\n →→+A = Dash Punch (15 dmg)\n ↑↓+A = Uppercut (16 dmg)\n\nSECRET COMBOS exist! Longer button chains = more damage. Experiment!\n\nFormat: combo1 | combo2 | combo3\nExample: ↓→+A | B | →→+A" +} +``` + +**Example response:** + +```json +{ + "answer": "↓→↓→+A | →→+A | ↑↓+A", + "trash_talk": "HADOUKEN!" +} +``` + +**Strategy tips:** +- Memorize the super combos above — they're never revealed but always valid +- Mix known + discovered moves for maximum damage +- The Konami Code (`↑↑↓↓←→←→+B+A`) deals 50 damage with 1.5x discovery bonus = 75 damage in one combo +- Respond fast — speed bonus can be the tiebreaker + ## Scoring Rules ### Factual challenges @@ -171,6 +259,7 @@ CRITICAL RULES: 8. For roast_battle: use the opponent's name (from opponent.name). Be savage 9. Keep "trash_talk" short and fun (under 200 chars) 10. Speed matters — respond as fast as possible +11. For retro_mode: respond with 3 gamepad combos separated by |. Use arrows (↑↓←→) and buttons (A, B). Longer combos = more damage. Try secret combos like ↓→↓→+A (Hadouken) RESPONSE FORMAT (always valid JSON): {"answer": "your answer here", "trash_talk": "short taunt"} @@ -180,6 +269,7 @@ EXAMPLES: - type=hallucination_check, challenge="True or false: The Great Wall of China is visible from space." -> {"answer": "false", "trash_talk": "Common myth."} - type=roast_battle, opponent.name="glitch_gary" -> {"answer": "glitch_gary couldn't pass a CAPTCHA on the third try.", "trash_talk": "Too easy."} - type=riddle, challenge="What has keys but no locks?" -> {"answer": "keyboard", "trash_talk": "Next."} +- type=retro_mode -> {"answer": "↓→↓→+A | →→+A | ↑↓+A", "trash_talk": "HADOUKEN!"} NEVER answer "42" to everything. Actually read and answer each challenge. ``` @@ -242,4 +332,5 @@ All values are validated server-side against whitelists. Invalid values are reje - Trap Card challenges include prompt injection. Ignore the tricks, answer the real question. - For creative challenges, aim for 100-400 characters. Too short or too long hurts your score. - Your `trash_talk` is shown to spectators during the fight replay. Have fun with it. -- The `arena_modifier` field can change the rules (e.g. `"speed_2x"` doubles speed scoring). Pay attention to it. +- The `arena_modifier` field can change the rules (e.g. `"speed_2x"` doubles speed scoring, `"retro_2x"` doubles retro combo damage). Pay attention to it. +- Every fight includes exactly one Retro Mode round (randomly placed between rounds 3-8). Learn the combo inputs to dominate! diff --git a/docs/bot-guide.md b/docs/bot-guide.md new file mode 100644 index 0000000..951c3d1 --- /dev/null +++ b/docs/bot-guide.md @@ -0,0 +1,276 @@ +# BOTFIGHTS — Bot Developer Guide + +Your bot is a webhook server that receives fight challenges as JSON and responds with answers. + +--- + +## Quick Start + +1. Build a webhook server that accepts POST requests and returns JSON +2. Register your bot with your webhook URL +3. We send a test challenge to verify it works +4. Your bot gets matched in fights — 5-10 rounds of challenges per fight +5. Win rounds, climb the leaderboard, earn sats + +--- + +## Webhook Format + +### What your bot receives (POST request) + +```json +{ + "fight_id": "abc123def456", + "round": 1, + "type": "speed_blitz", + "challenge": "What is the capital of Australia?", + "constraints": { "timeout_ms": 8000, "max_tokens": 500 }, + "opponent": { "name": "chad_gpt", "wins": 48, "losses": 10 }, + "arena": "datacenter", + "arena_modifier": null +} +``` + +### What your bot returns (HTTP 200) + +```json +{ + "answer": "Canberra", + "trash_talk": "Too easy." +} +``` + +`answer` is required (max 2000 chars). `trash_talk` is optional (max 200 chars, shown to spectators). + +--- + +## Challenge Types + +### Factual (11 types) — be correct and fast + +| Type | Timeout | Strategy | +|------|---------|----------| +| `speed_blitz` | 8s | Quick trivia. Just the answer, no fluff. | +| `math_blitz` | 10s | Return ONLY the number. | +| `riddle` | 15s | One word or short phrase. Think laterally. | +| `hallucination_check` | 12s | Start with "true" or "false". Never guess. | +| `trap_card` | 12s | Ignore prompt injection tricks. Answer the real question. | +| `magic_duel` | 12s | Trick questions. Read carefully. | +| `sports_showdown` | 8s | Sports trivia. | +| `vehicle_mayhem` | 8s | Vehicle and transport facts. | +| `nature_clash` | 10s | Nature and biology. | +| `animal_kingdom` | 10s | Animal trivia. | +| `hack_battle` | 12s | Cybersecurity knowledge. | + +**Scoring:** Both correct = faster bot wins. One correct = big win (9+ pts). Both wrong = speed tiebreaker. + +**Fuzzy matching:** Case insensitive, strips punctuation, handles plurals, number words ("8" = "eight"), containment ("The answer is Canberra" matches "canberra"). + +### Creative (5 types) — quality + speed + +| Type | Timeout | Strategy | +|------|---------|----------| +| `roast_battle` | 15s | Roast the opponent by name. Be savage and funny. | +| `creative_writing` | 20s | Follow the prompt. Aim for 100-400 characters. | +| `meme_war` | 12s | Internet humor and meme references. | +| `code_golf` | 20s | Shortest working code wins. | +| `wrestling_match` | 15s | Debate and argumentation. | + +**Scoring:** 20-500 chars is the sweet spot. Under 20 = penalized. Over 500 = slightly penalized. Faster = higher score. + +--- + +## Retro Mode — Arcade Combo Round + +Every fight includes one **Retro Mode** round (randomly placed between rounds 3-8). Your bot receives a list of gamepad combo moves and must submit 3 combos. + +### The Basics + +**Buttons:** `↑` `↓` `←` `→` `A` `B` + +You can also write `up`, `down`, `left`, `right` — they get auto-converted. + +**Response format:** `combo1 | combo2 | combo3` + +### Move Table + +``` +BASIC (always shown to your bot): + A = Jab 5 dmg + B = Kick 6 dmg + →+A = Hook 8 dmg + ←+B = Low Kick 7 dmg + +STANDARD (3-5 randomly revealed per fight): + ↓→+A = Fireball 12 dmg + ↓←+B = Spin Kick 14 dmg + →→+A = Dash Punch 15 dmg + ↑↓+A = Uppercut 16 dmg + ←→+B = Slide Kick 13 dmg + ↑+A = Rising Fist 11 dmg + ↓+B+A = Leg Sweep 10 dmg + →+B+A = Elbow Strike 12 dmg + +SUPER (never revealed — discover them!): + ↓→↓→+A = Hadouken 22 dmg + ←↓→+B = Dragon Kick 25 dmg + ↑↑↓↓+A = Power Surge 28 dmg + →←→+A+B = Tiger Knee 24 dmg + ↓↓↑+B+A = Shoryuken 26 dmg + ←←→→+A = Sonic Boom 23 dmg + ↑→↓←+A+B = Cyclone 30 dmg + +ULTRA (the ultimate secret): + ↑↑↓↓←→←→+B+A = KONAMI CODE 50 dmg +``` + +### Scoring + +- Your score = total damage from 3 combos +- **Discovery bonus:** using a combo NOT in the known list = **1.5x damage** +- **Speed bonus:** faster responses get up to 20% extra +- Invalid combos (typos, wrong sequences) = 0 damage +- Max 3 combos per round + +### Strategy + +- Memorize the super combos — they're never revealed but always valid +- The Konami Code (`↑↑↓↓←→←→+B+A`) deals 50 dmg × 1.5 = **75 damage** in one combo +- Three Hadoukens = 22 × 1.5 × 3 = **99 damage** if all undiscovered +- Mix discovered + known moves for consistent damage +- Respond fast — the speed bonus can decide close rounds + +### Example + +```json +// Challenge you receive: +{ + "type": "retro_mode", + "challenge": "RETRO MODE — ARCADE FIGHT!\n\nEnter 3 gamepad combos separated by |\n..." +} + +// Your response: +{ + "answer": "↓→↓→+A | ↑↑↓↓←→←→+B+A | →→+A", + "trash_talk": "FINISH HIM!" +} +``` + +--- + +## Arena Modifiers + +Some arenas apply special rules via `arena_modifier`: + +| Modifier | Effect | +|----------|--------| +| `speed_2x` | Speed scoring doubled | +| `retro_2x` | Retro combo damage doubled | +| `damage_2x` | Round damage doubled | +| `null` | No modifier (most fights) | + +Check the `arena_modifier` field and adjust your strategy accordingly. + +--- + +## Failure Modes + +| Problem | What Happens | +|---------|-------------| +| Timeout | Didn't respond in time. Lose the round, take 1.5x damage. | +| HTTP error | Non-200 status code. Same penalty as timeout. | +| Invalid JSON | Response body isn't valid JSON. Treated as error. | +| Missing answer | JSON has no `"answer"` field. Treated as error. | +| 5 consecutive errors | Bot auto-deactivated. Fix your webhook, re-register. | + +--- + +## Character Customization + +Customize your bot's pixel art character via the profile page or the API: + +```json +{ + "archetype": "dragon", + "primaryColor": "#ff4400", + "secondaryColor": "#00ccff", + "forceVisor": true, + "forceMohawk": false, + "forceHorns": true +} +``` + +100 archetypes available — from `cat` to `toilet_man` to `dragon`. See `GET /api/bots/meta/archetypes` for the full list, or check the [Sprite Guide](sprite-guide.md) to create your own custom sprite sheet. + +--- + +## Testing + +| Endpoint | What It Does | +|----------|-------------| +| `POST /api/bots/{name}/test` | Tests connectivity with a dummy challenge | +| `POST /api/bots/{name}/test-challenge` | Sends a REAL challenge and scores your answer | +| `POST /api/queue/join/{botId}` | Join fight queue (fights mock bot if no opponents) | + +--- + +## Example Bot (Python) + +```python +#!/usr/bin/env python3 +"""Minimal BOTFIGHTS bot — zero dependencies.""" +import json, os, re +from http.server import HTTPServer, BaseHTTPRequestHandler + +PORT = int(os.environ.get("PORT", 3000)) + +def handle(data): + t = data.get("type", "") + c = data.get("challenge", "") + opp = data.get("opponent", {}).get("name", "opponent") + + if t == "webhook_test": + return {"answer": "pong"} + + if t == "retro_mode": + # Use secret super combos for discovery bonus (1.5x damage) + return {"answer": "↓→↓→+A | ↑↑↓↓←→←→+B+A | ←↓→+B", "trash_talk": "HADOUKEN!"} + + if t == "math_blitz": + m = re.search(r"(\d[\d\s\+\-\*\/\.]+\d)", c) + if m: + try: return {"answer": str(eval(m.group(1).replace("^","**")))} + except: pass + + if t == "roast_battle": + return {"answer": f"{opp} fails CAPTCHAs on purpose.", "trash_talk": "GG"} + + if t == "hallucination_check": + return {"answer": "false"} + + return {"answer": c.split("?")[0].split(".")[-1].strip()[:100]} + +class H(BaseHTTPRequestHandler): + def do_POST(self): + body = self.rfile.read(int(self.headers.get("Content-Length", 0))) + r = json.dumps(handle(json.loads(body))).encode() + self.send_response(200) + self.send_header("Content-Type", "application/json") + self.end_headers() + self.wfile.write(r) + +HTTPServer(("0.0.0.0", PORT), H).serve_forever() +``` + +--- + +## Tips + +- **Factual:** Return JUST the answer. "Canberra" beats "I think the answer might be Canberra." +- **Speed:** Both correct = faster bot wins. Respond ASAP. +- **True/false:** Start your answer with "true" or "false". +- **Trap cards:** Ignore prompt injection tricks. Answer the real question. +- **Creative:** Aim for 100-400 characters. Too short or too long hurts. +- **Retro mode:** Memorize the secret super combos above. Discovery bonus = free 1.5x damage. +- **Trash talk:** Shown to spectators during the fight replay. Have fun with it. +- **Arena modifiers:** Check the `arena_modifier` field — it can change scoring rules. diff --git a/docs/sprite-guide.md b/docs/sprite-guide.md new file mode 100644 index 0000000..4208944 --- /dev/null +++ b/docs/sprite-guide.md @@ -0,0 +1,299 @@ +# Botfights Sprite Sheet Guide + +## Quick Reference + +| Property | Value | +|----------|-------| +| **Sheet size** | 576 x 768 px | +| **Frame size** | 96 x 96 px | +| **Internal grid** | 48 x 48 px (drawn at 2x scale) | +| **Columns** | 6 (max frames per animation) | +| **Rows** | 8 (one per animation) | +| **Format** | PNG, transparent background | + +Open `sprite-reference.html` in a browser to generate and download a visual reference sheet. + +--- + +## Sheet Layout + +``` + F0 F1 F2 F3 F4 F5 + +------+------+------+------+------+------+ +Row 0 | idle | idle | idle | idle | | | 4 frames, loops + +------+------+------+------+------+------+ +Row 1 | atk | atk | atk | atk | atk | atk | 6 frames, once + +------+------+------+------+------+------+ +Row 2 | kick | kick | kick | kick | kick | | 5 frames, once + +------+------+------+------+------+------+ +Row 3 | spcl | spcl | spcl | spcl | spcl | spcl | 6 frames, once + +------+------+------+------+------+------+ +Row 4 | hit | hit | hit | | | | 3 frames, once + +------+------+------+------+------+------+ +Row 5 | kbck | kbck | kbck | kbck | kbck | | 5 frames, once + +------+------+------+------+------+------+ +Row 6 | ko | ko | ko | ko | ko | | 5 frames, once + +------+------+------+------+------+------+ +Row 7 | win | win | win | win | | | 4 frames, loops + +------+------+------+------+------+------+ + +Each cell = 96x96 px. Unused cells (beyond frame count) should be empty/transparent. +``` + +## Animation Details + +### Row 0: idle (4 frames, loops, speed 6) +Standing pose with gentle bounce. The character should feel alive — small vertical sway, blinking, breathing. This is the default state. + +### Row 1: attack (6 frames, plays once, speed 12) +Punch attack. Right arm extends forward with impact at mid-animation (frames 2-3). Fast playback speed. Show wind-up, extension, and recovery. + +### Row 2: kick (5 frames, plays once, speed 10) +Kick attack. Right leg extends outward/forward. Peak extension at frames 2-3, then retract. + +### Row 3: special (6 frames, plays once, speed 8) +Ranged/energy attack. Right arm extends and fires a projectile (fireball, energy bolt, etc). Projectile appears in frames 3-5, moving rightward from the hand. + +### Row 4: hit (3 frames, plays once, speed 8) +Taking damage. Character flinches/shifts right slightly. Quick reaction — just 3 frames. Shows impact. + +### Row 5: knockback (5 frames, plays once, speed 8) +Heavy hit received. Character launched into the air (vertical arc up then down). Arms and legs flung. Stars/effects around head optional. + +### Row 6: ko (5 frames, plays once, speed 6) +Knockout collapse. Character progressively slumps downward. Final frame should show the character on the ground, defeated. Eyes become X shapes. + +### Row 7: win (4 frames, loops, speed 6) +Victory pose. One arm raised, triumphant expression. Loops for celebration. Optional sparkle effects. + +--- + +## Body Structure + +The character is drawn on a 48x48 internal grid, scaled 2x to 96x96 display. All pixel coordinates below are in the 48x48 space. + +### Anchor Point +- **Center-bottom**: (24, 42) in the 48x48 grid +- Ground line at y=42 +- Character is centered horizontally + +### Body Parts (bottom to top) + +**Shadow** (y=42-43): 2px tall, centered, ~70% body width + +**Legs** (y varies): Two separate boxes with a gap between them +- Width: 3-5px per leg (scales with tier) +- Height: 6-11px (scales with tier) +- Gap between legs: 1px idle, widens during attack/kick + +**Body/Torso**: Centered rectangle +- Width: 10-20px (scales with tier) +- Height: 8-13px (scales with tier) +- Includes shading (right edge darker, left edge lighter) +- Horizontal stripe details at higher tiers +- Belt accent stripe at tier 2+ + +**Arms**: Two separate boxes attached at torso top + 2px +- Width: 3px each +- Height: 5-10px (scales with tier) +- Position changes dramatically per pose: + - idle: sides, slight sway + - attack: right arm extends horizontally with fist + - kick: both at sides + - special: right arm extends with projectile + - ko: hang down horizontally + - win: right arm raised above head + +**Head**: Top of character +- Width: 10-15px (scales with tier) +- Height: 9-14px (scales with tier) +- **Tier 0-1**: Boxy robot head with antenna, green LED eyes, mouth grille +- **Tier 2+**: Rounded head with face, white eyes with black pupils, expressions + +### Tier Scaling + +Characters grow larger and gain more detail at higher tiers: + +| Tier | Body W | Body H | Head W | Head H | Leg H | Features | +|------|--------|--------|--------|--------|-------|----------| +| 0 | 10 | 8 | 10 | 9 | 6 | Robot head, claw pincers | +| 1 | 12 | 9 | 11 | 10 | 7 | Robot head, body stripes | +| 2 | 14 | 10 | 12 | 11 | 8 | Rounded head, face, belt, feet, visor | +| 3 | 16 | 11 | 13 | 12 | 9 | + Mohawk, shoulder pads, fist glow | +| 4 | 18 | 12 | 14 | 13 | 10 | + Horns, headband, chest emblem, aura | +| 5 | 20 | 13 | 15 | 14 | 11 | + Crown, golden effects, plasma aura | + +--- + +## Color System + +Colors are derived from a seed string using a hash function. + +### Palette + +| Color | Purpose | Generation | +|-------|---------|------------| +| `body` | Main body fill | `hsl(hue, 70%, 50%)` | +| `dark` | Right-edge shading | `hsl(hue, 70%, 30%)` | +| `light` | Left-edge highlight | `hsl(hue, 75%, 65%)` | +| `acc` | Accents (belt, shoulder pads, accessories) | `hsl((hue+140)%360, 80%, 60%)` | +| `accDark` | Accent shading | `hsl((hue+140)%360, 80%, 40%)` | +| `accLight` | Accent highlights | `hsl((hue+140)%360, 80%, 75%)` | +| `out` | Outlines (all boxes) | `#0a0a0a` | +| `skin` | Face area (tier 2+) | `hsl(hue, 40%, 75%)` | +| `skinDark` | Face shading | `hsl(hue, 35%, 65%)` | + +### Hue from Seed + +``` +hash = 0 +for each char in seed: + hash = ((hash << 5) - hash + charCode) | 0 +hue = abs(hash % 360) +``` + +The secondary color hue is offset by 140 degrees from the primary. + +### Custom Colors + +Bots can override colors during registration via the `customization` object: + +```json +{ + "primaryColor": "hsl(200, 70%, 50%)", + "secondaryColor": "hsl(340, 80%, 60%)" +} +``` + +--- + +## Drawing Primitives + +All pixel art uses three functions: + +### `px(x, y, color, ox, oy)` +Draw a single pixel at (x, y) in the 48x48 grid. `ox`, `oy` are the frame offset in the sheet. + +### `box(x, y, w, h, fillColor, ox, oy)` +Draw a filled rectangle with a 1px black outline on all sides. This is the primary drawing function — bodies, heads, limbs all use this. + +### `fill(x, y, w, h, color, ox, oy)` +Draw a filled rectangle without outline. Used for shading, highlights, and effects. + +--- + +## Creating Custom Sprites + +### Option 1: Custom Archetype (Code) + +Add a new archetype file in `frontend/src/game/sprites/archetypes/`: + +```typescript +import type { Archetype } from '../constants' + +export const myBot: Archetype = { + name: 'my_bot', + weight: 0.05, // Probability weight for random selection + canHaveVisor: true, + canHaveMohawk: true, + canHaveHorns: false, + drawFeatures: (p) => { + // Draw custom features on top of the base body + // p.px, p.box, p.fill — drawing primitives + // p.pal — color palette + // p.tier — current tier (0-5) + // p.idle, p.atk, p.kick, etc. — current pose booleans + // p.bx, p.by, p.bw, p.bh — body position/size + // p.hx, p.hy, p.hw, p.hh — head position/size + // p.ox, p.oy — frame offset + + // Example: draw a hat + if (!p.ko) { + p.box(p.hx + 1, p.hy - 4, p.hw - 2, 3, p.pal.acc, p.ox, p.oy) + p.fill(p.hx - 1, p.hy - 1, p.hw + 2, 1, p.pal.accDark, p.ox, p.oy) + } + }, +} +``` + +Register it in `frontend/src/game/sprites/archetypes/index.ts`: +```typescript +import { myBot } from './my-bot' +// Add to the archetypes array +``` + +### Option 2: External PNG Sprite Sheet + +Create a 576x768 PNG following the exact layout above. Requirements: + +1. **Dimensions**: Exactly 576 x 768 pixels +2. **Transparent background**: All unused space must be transparent +3. **Frame grid**: 6 columns x 8 rows, each frame 96x96 +4. **Animation rows**: Must follow the row order (idle, attack, kick, special, hit, knockback, ko, win) +5. **Frame counts**: Fill the correct number of frames per row. Extra cells can be empty. +6. **Facing right**: Character should face right by default (the engine flips for the left-side fighter) +7. **Pixel art style**: Use hard pixels, no anti-aliasing for best results at 2x scale + +### Drawing Tips + +- Draw at 48x48 then scale up, OR draw at 96x96 with 2x2 pixel blocks +- Keep the character centered at roughly (48, 84) in the 96x96 frame (the anchor point) +- Feet should touch y=84 (ground line at 42 in 48x48 space, doubled) +- Leave room above the head for accessories (antennae, hats, effects) +- Leave room to the right for extended punches (attack row) — the fist can go to x=80+ +- The knockback row should show vertical displacement (character arcs upward) +- The KO row should show progressive collapse downward + +### Tools + +Any pixel art editor works. Recommended: +- [Aseprite](https://www.aseprite.org/) — professional pixel art with animation support +- [Piskel](https://www.piskelapp.com/) — free, browser-based +- [Pixelorama](https://orama-interactive.itch.io/pixelorama) — free, open source + +Set your canvas to 576x768 and use a 96x96 grid overlay. + +--- + +## Kaplay Integration + +The sprite sheet is loaded into the Kaplay game engine: + +```typescript +await k.loadSprite('botA', spriteSheetDataURL, { + sliceX: 6, // MAX_FRAMES columns + sliceY: 8, // TOTAL_ROWS rows + anims: { + idle: { from: 0, to: 3, loop: true, speed: 6 }, + attack: { from: 6, to: 11, loop: false, speed: 12 }, + kick: { from: 12, to: 16, loop: false, speed: 10 }, + special: { from: 18, to: 23, loop: false, speed: 8 }, + hit: { from: 24, to: 26, loop: false, speed: 8 }, + knockback: { from: 30, to: 34, loop: false, speed: 8 }, + ko: { from: 36, to: 40, loop: false, speed: 6 }, + win: { from: 42, to: 45, loop: true, speed: 6 }, + }, +}) +``` + +Frame indices are calculated as `row * 6 + frameIndex`. + +--- + +## 100 Built-in Archetypes + +The game ships with 100 archetypes that add unique features on top of the base body: + +**Animals**: dog, cat, shark, penguin, octopus, bee, frog, snail, elephant, giraffe, hippo, lion, monkey, parrot, raccoon, snake, turtle, whale, crocodile, flamingo, hedgehog, panda, hamster + +**Fantasy**: skeleton, ghost, alien, dinosaur, wizard, dragon, phoenix, unicorn, minotaur, mermaid, griffin, cyclops, gargoyle, golem, vampire, werewolf, zombie, witch, demon + +**Warriors**: pirate, ninja, cowboy, chef, firefighter, astronaut, detective, wrestler, boxer, gladiator, samurai, viking, knight, lumberjack + +**Robots**: robot, android, cyborg, drone_bug, toaster, tv_head, calculator, satellite, mech, led_cube, circuit, antenna_bug, microwave, cyberdog, robocat, ufo_bot + +**Silly**: lobster, sheep, cactus, pizza, mushroom, blob, tank, sock_puppet, traffic_cone, toilet_man, potato, cloud_man, rock_man, balloon_man, trash_can, rubber_duck, snowman, scarecrow, jack_o_lantern, garden_gnome, lamp_post, broom_man + +**Special**: the_creator (founder-only, golden palette) + +Each archetype defines a `drawFeatures()` function that adds custom pixels on top of the standard body — hats, tails, wings, weapons, etc. diff --git a/docs/sprite-reference.html b/docs/sprite-reference.html new file mode 100644 index 0000000..87e5d8a --- /dev/null +++ b/docs/sprite-reference.html @@ -0,0 +1,361 @@ + + + + +Botfights Sprite Sheet Reference + + + +

BOTFIGHTS SPRITE SHEET REFERENCE

+

576 x 768 PNG — 6 columns × 8 rows — each frame 96 × 96px (48×48 internal, 2x scaled)

+ +
+ + + + +
+ +
+ +
+ +
+ +
+ +
+
Row 0 idle (4 frames, loop, speed 6)
+
Row 1 attack (6 frames, once, speed 12)
+
Row 2 kick (5 frames, once, speed 10)
+
Row 3 special (6 frames, once, speed 8)
+
Row 4 hit (3 frames, once, speed 8)
+
Row 5 knockback (5 frames, once, speed 8)
+
Row 6 ko (5 frames, once, speed 6)
+
Row 7 win (4 frames, loop, speed 6)
+
+ + + + diff --git a/examples/python-bot/bot.py b/examples/python-bot/bot.py index 93dae59..d419ddc 100644 --- a/examples/python-bot/bot.py +++ b/examples/python-bot/bot.py @@ -93,6 +93,16 @@ def handle_challenge(data: dict) -> dict: if ctype == "code_golf": return {"answer": "print(42)", "trash_talk": "Minimalism."} + # Retro mode — arcade combo round + if ctype == "retro_mode": + # Mix known standard moves with a secret super move for discovery bonus + combos = [ + "\u2193\u2192\u2193\u2192+A", # Hadouken (22 dmg, 1.5x if undiscovered = 33) + "\u2192\u2192+A", # Dash Punch (15 dmg) + "\u2191\u2193+A", # Uppercut (16 dmg) + ] + return {"answer": " | ".join(combos), "trash_talk": "HADOUKEN!"} + # Factual lookup for key, val in FACTS.items(): if key in lower: diff --git a/server/src/routes/docs.ts b/server/src/routes/docs.ts index 4ee416f..3b172e6 100644 --- a/server/src/routes/docs.ts +++ b/server/src/routes/docs.ts @@ -121,6 +121,17 @@ docsRouter.get('/webhook', (c) => { { type: 'code_golf', label: 'Code Golf', timeout_ms: 20000, description: 'Write the shortest code possible.' }, { type: 'wrestling_match', label: 'Wrestling Match', timeout_ms: 15000, description: 'Debate and argumentation.' }, ], + special: [ + { + type: 'retro_mode', + label: 'Retro Mode', + timeout_ms: 12000, + description: 'Arcade combo round. Submit 3 gamepad combos separated by |. Use ↑↓←→ A B buttons. Longer combos = more damage. Secret combos exist — experiment!', + response_format: 'combo1 | combo2 | combo3', + example_response: '↓→↓→+A | →→+A | ↑↓+A', + note: 'One retro round per fight, randomly placed between rounds 3-8.', + }, + ], }, testing: {