CoolFace
Apppublic

angshuman-nandy/LLM-Battleships

sourceHugging Faceupdated 5mo agoView on Hugging Face
0likes
App README

LLM Battleships

A Battleship game where two LLM agents play each other — or you play against one — viewable live in a browser. Configure providers, board size, and ship placement strategy, then watch the AIs duel in real time.

Deployable as a single Docker container on Hugging Face Spaces (free CPU tier).


How to Play

LLM vs LLM (spectator)

Both players are AI models. You pick the provider, model, and API key for each, choose a board size, and watch them play. Both boards are fully visible — see where ships are placed and watch cells turn red as they're hit.

Human vs LLM

You are Player 1. The LLM is Player 2.

  1. 1.Place your ships — click a ship from the list to select it, hover over the board to preview placement, click to place. Press Rotate to toggle horizontal/vertical. Press Confirm when done.
  2. 2.Fire — click any unrevealed cell on the enemy board to fire. Your turn indicator shows when it's your go.
  3. 3.Win — sink all enemy ships before yours are sunk.

Board legend

ColourMeaning
Dark blueEmpty sea
GreenYour ship
RedHit
Dark redSunk ship
Gray dotMiss

Placement modes

Each player's ships can be placed by one of four strategies:

ModeDescription
llmThe battle LLM places its own ships
third_agentA separate LLM (different model/provider) places ships
humanYou click ships onto the board interactively
randomServer generates a valid random layout instantly

Tech Stack

LayerTechnology
BackendPython 3.11, FastAPI, uvicorn
FrontendReact 18, TypeScript, Vite
Real-timeServer-Sent Events (SSE)
LLM providersAnthropic SDK, OpenAI SDK
Data validationPydantic v2
ContainerDocker multi-stage build
HostingHugging Face Spaces (port 7860)
StateIn-memory only — no database

Architecture

Browser (React + TypeScript)
│
│  HTTP REST              SSE stream
│  POST /api/game/create  GET /sse/{game_id}
│  POST /api/game/start   ─────────────────────────────────────┐
│  POST /api/game/fire                                          │
│  GET  /api/game/state                                         │
│                                                               │
▼                                                               │
┌─────────────────────────────────────────────────────────────┐ │
│  FastAPI  (backend/main.py)                                 │ │
│                                                             │ │
│  ┌─────────────────┐    ┌──────────────────────────────┐   │ │
│  │  routes_game.py │    │       routes_sse.py           │   │ │
│  │  ─ create       │    │  asyncio.Queue per game       │───┼─┘
│  │  ─ start        │    │  ping every 15s               │   │
│  │  ─ place        │    └──────────────────────────────┘   │
│  │  ─ fire         │                                        │
│  │  ─ state        │                                        │
│  └────────┬────────┘                                        │
│           │                                                  │
│           ▼                                                  │
│  ┌─────────────────────────────────────────────────────┐    │
│  │  session_store.py  (module-level singleton)         │    │
│  │                                                     │    │
│  │  _games      dict[game_id → GameState]              │    │
│  │  _queues     dict[game_id → asyncio.Queue]  ────────┼────┘
│  │  _tasks      dict[game_id → asyncio.Task]           │
│  │  _human_events / _placement_events                  │
│  └─────────────────────────────────────────────────────┘
│           │
│           ▼
│  ┌─────────────────────────────────────────────────────┐
│  │  engine.py  (GameEngine — runs as asyncio.Task)     │
│  │                                                     │
│  │  start_game()                                       │
│  │    └─ placement_phase()                             │
│  │         ├─ asyncio.gather(resolve_p1, resolve_p2)   │
│  │         └─ emit all_placements_done                 │
│  │    └─ turn_loop()                                   │
│  │         ├─ human turn → await human_event           │
│  │         └─ LLM turn  → llm.choose_shot() + retry   │
│  └──────────────────┬──────────────────────────────────┘
│                     │
│           ┌─────────▼─────────┐
│           │   llm/            │
│           │   ─ base.py       │  Forced tool use on every call
│           │   ─ anthropic_    │  Max 3 retries on bad coords
│           │     wrapper.py    │
│           │   ─ openai_       │
│           │     wrapper.py    │
│           │   ─ factory.py    │
│           └───────────────────┘
└─────────────────────────────────────────────────────────────┘

Frontend state flow
───────────────────
useSSE  ──event──▶  useGameState (reducer)  ──▶  BoardPair / MoveLog
                       │
                       ├─ all_placements_done → re-fetch /state (boards now live)
                       ├─ shot_fired          → update grid cell + blink animation
                       ├─ shot_fired (sunk)   → re-fetch /state (all cells marked)
                       └─ game_over           → re-fetch /state (final snapshot)

Local Development

Prerequisites

  • —Python 3.11+
  • —Node.js 20+

Backend

bash
cd LLM-battleships
cp .env.example .env          # fill in your API keys
pip install -r backend/requirements.txt
uvicorn backend.main:app --reload --port 8000

Frontend (separate terminal)

bash
cd frontend
npm install
npm run dev                   # → http://localhost:5173 (proxies /api to :8000)

Set ENV=development in .env to enable CORS between the two dev servers.

Environment variables (.env)

ENV=development

ANTHROPIC_API_KEY=sk-ant-...
OPENAI_API_KEY=sk-...

Keys set here pre-fill the setup form in the browser. Leave blank to enter them manually in the UI. API keys are never logged or stored server-side.


Docker

bash
docker build -t battleships .
docker run -p 7860:7860 battleships
# → http://localhost:7860

The multi-stage Dockerfile builds the React frontend first, then copies the static output into the Python image alongside the backend. No separate frontend server is needed at runtime — FastAPI serves the built assets.


Hugging Face Spaces

Push the repo as a Docker Space. HF exposes port 7860 automatically. Users supply their own API keys via the UI — never hardcode them in the image.

Space type: Docker
Port:       7860

Supported LLM Providers

ProviderNotes
Anthropicclaude-3-5-haiku
OpenAIgpt-4o-mini

All LLM calls use forced tool use — structured JSON responses only, no free-text parsing.


Board Sizes & Fleets

BoardFleet
5×5Destroyer (2), Submarine (3)
7×7Destroyer (2), Cruiser (3), Battleship (4)
10×10 – 15×15Carrier (5), Battleship (4), Cruiser (3), Submarine (3), Destroyer (2)