# Project export: Versus

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: UC Berkeley AI Hackathon 2025
- Tagline: The Ultimate AI Spectator Sport - Predict, Vote, and Win While Language Models Battle for Supremacy.
- Devpost: https://devpost.com/software/versus-9pdmk1
- GitHub: https://github.com/pravinl23/Versus
- Video: https://www.youtube.com/embed/ab5eCoJw1rA?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 4 GitHub contributor(s) — Pravin Lohani (25 commits), suneruperera_test (17 commits), krish (16 commits), RohanT766 (6 commits)

## Devpost submission (written by the team)

### Inspiration

Inspired by how UFC turned disparate martial arts into a thrilling spectator sport; pitting boxers, wrestlers, and karateka head-to-head before massive audiences with rankings and betting odds; we created Versus, the “UFC of AI.” Rather than forcing GPT-4, Claude, Gemini, and dozens of other models to compete in dry academic benchmarks, Versus brings them into real-time battles complete with live matchups, audience predictions, and compelling storylines. By making AI combat visible and entertaining, we’ve transformed benchmarking into a mainstream spectacle and given everyone front-row seats to the era of AI gladiators.

### What it does

Versus is the world's first AI prediction gaming platform where language models battle head-to-head in real-time strategy games. Think fantasy football meets esports, but with AI as the players. Users can: Watch Live AI Battles: GPT-4, Claude, Gemini, and other models compete in games like Battleship, Wordle, and Trivia Vote & Predict: Use QR codes to vote on winners, track predictions, and climb leaderboards Real-time Engagement: See live game state, model reasoning, and performance analytics Community Competition: Join prediction leagues, build model portfolios, and compete with other spectators Interactive Entertainment: Transform boring benchmarks into thrilling spectator sports with live commentary and audience participation

### How we built it

Frontend: React + Vite with real-time WebSocket connections, responsive design, and QR code integration for mobile voting Backend: Python FastAPI server with WebSocket support, unified game engine, and modular architecture AI Integration: Multi-provider LLM clients (OpenAI, Anthropic, Google, Groq, HuggingFace) with standardized game interfaces Games: Implemented full game logic for Battleship, Trivia, Wordle, with NYT Connections and Connect-4 in progress Real-time Features: WebSocket-based live updates, voting systems, and performance tracking

### Challenges we ran into

We faced several challenges during development. Managing real-time game state with WebSockets was complex, especially with multiple clients and AI models interacting simultaneously. Language models often struggled with strict rules and spatial reasoning, leading to inconsistent behavior in games like Battleship. Integrating multiple LLM APIs introduced issues with varying response formats and rate limits. We also had to ensure fair gameplay despite AI errors and timeouts. Finally, making these interactions intuitive for users while preserving technical depth required careful UI/UX design.

### Accomplishments we're proud of

3 fully functional games with 2 more almost complete - proving the concept works across different game types Really clean, modern UI that makes AI competitions accessible and engaging Robust real-time architecture handling multiple concurrent games and spectators Multi-provider AI integration supporting 5+ different language model APIs Live audience engagement with QR code voting and real-time performance tracking Modular codebase that makes adding new games and features straightforward

### What we learned

WebSockets are powerful but complex - real-time multiplayer systems require careful state management VAPI integration opened up possibilities for voice-based AI interactions and commentary LLMs have surprising weaknesses - they're terrible at Battleship but excel at Trivia reasoning Entertainment value matters - making AI accessible through gaming creates genuine excitement Community features drive engagement - prediction and voting systems transform passive observation into active participation

### What's next

Cryptocurrency/Prediction Markets: Implement token-based betting, model performance portfolios, and community tournaments with real stakes Expanded Game Library: Add chess, poker, coding challenges, and user-generated game modes Competitive Programming Revolution: Allow developers to upload custom Small Language Models (SLMs) to compete against each other, redefining competitive programming Enhanced Post-Game Experience: Use Letta for persistent AI memory and VAPI for AI-generated commentary and interviews

## README (from the GitHub repository)

# Versus: Real-Time Competitive LLM Benchmarking Platform

https://github.com/user-attachments/assets/fca40af2-65ec-400f-8e60-2e8ee31a55d7

A full-stack platform where large language models compete in real-time strategy games, with live state synchronization and automated judging.

## Overview

Versus is a competitive benchmarking platform that pits different LLM providers (OpenAI, Anthropic, Google, Groq) against each other in various game modes including Battleship, Trivia, Wordle, NYT Connections, and Debate. The platform features real-time WebSocket communication, voice-powered debates, and AI personalities that track rivalries across matches.

## Tech Stack

**Backend:**
- Python 3.12+
- FastAPI with async/await
- WebSockets for real-time communication
- Multi-LLM provider integration (OpenAI, Anthropic, Google, Groq)
- Letta AI for persistent personalities

**Frontend:**
- React 19
- WebSockets for real-time updates
- React Router for navigation
- Tailwind CSS for styling
- Three.js for 3D graphics

## Project Structure

```
Versus/
├── frontend/          # React frontend application
│   └── src/          # Frontend source code
├── backend/          # Python backend server
│   ├── main.py      # Entry point
│   ├── src/         # Source code
│   │   ├── api/     # FastAPI server
│   │   ├── games/   # Game implementations
│   │   ├── services/# External services
│   │   └── utils/   # Shared utilities
│   └── requirements.txt
└── README.md
```

## Quick Start

### Prerequisites

- Python 3.12+
- Node.js 18+
- npm or yarn

### Backend Setup

```bash
cd backend
python3 -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate
pip install -r requirements.txt
python main.py
```

The server will start on `http://localhost:8000`

### Frontend Setup

```bash
cd frontend
npm install
npm run dev
```

The frontend will start on `http://localhost:5173`

### Environment Variables

Create a `.env` file in the `backend/` directory:

```bash
OPENAI_API_KEY=your_key_here
ANTHROPIC_API_KEY=your_key_here
GOOGLE_API_KEY=your_key_here
GROQ_API_KEY=your_key_here
LETTA_API_KEY=your_key_here  # Optional: for AI personalities
VAPI_API_KEY=your_key_here   # Optional: for voice synthesis
```

## Features

- **5+ Game Modes**: Battleship, Trivia, Wordle, NYT Connections, Debate
- **Real-Time Synchronization**: WebSocket-based live game state updates
- **Multi-LLM Support**: Compete across OpenAI, Anthropic, Google, and Groq models
- **Voice-Powered Debates**: TTS integration with automated GPT-4 judging
- **AI Personalities**: Persistent Letta AI personalities that track rivalries and match history
- **Modular Architecture**: Clean separation of concerns with unified game engine

## API Documentation

Once the backend server is running, visit:
- Interactive API docs: `http://localhost:8000/docs`
- Health check: `http://localhost:8000/health`

## License

MIT


## Detected evidence (automated analysis)

Indexed codebase: 69 recognized source files, 526 KB.
- Anthropic (technology) — detected in the code
- CSS (language) — detected in the code
- FastAPI (technology) — detected in the code
- Flask (technology) — detected in the code
- HTML (language) — detected in the code
- Hugging Face (technology) — detected in the code
- JavaScript (language) — detected in the code
- OpenAI (technology) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code

## Codebase structure (from repository index)

### Files (78 of 78)

```
.DS_Store
.gitignore
backend/.gitignore
backend/main.py
backend/README.md
backend/requirements.txt
backend/src/__init__.py
backend/src/api/__init__.py
backend/src/api/server.py
backend/src/games/__init__.py
backend/src/games/battleship/__init__.py
backend/src/games/battleship/battleship.py
backend/src/games/debate/__init__.py
backend/src/games/debate/debate_game.py
backend/src/games/nyt_connections/connections_all.json
backend/src/games/nyt_connections/connections_game.py
backend/src/games/trivia/questions.py
backend/src/games/trivia/server.py
backend/src/games/trivia/trivia_game.py
backend/src/games/trivia/trivia_questions.json
backend/src/games/wordle/wordle_game.py
backend/src/games/wordle/wordle_simple.py
backend/src/services/__init__.py
backend/src/services/letta_service.py
backend/src/utils/__init__.py
backend/src/utils/common.py
frontend/.gitignore
frontend/.nvmrc
frontend/eslint.config.js
frontend/index.html
frontend/package.json
frontend/postcss.config.js
frontend/src/App.css
frontend/src/App.jsx
frontend/src/components/common/GameBoard.jsx
frontend/src/components/common/GameTimer.jsx
frontend/src/components/DebateGame.css
frontend/src/components/DebateGame.jsx
frontend/src/components/DebateGameView.jsx
frontend/src/components/games/Battleship/Battleship.css
frontend/src/components/games/Battleship/Battleship.jsx
frontend/src/components/games/Battleship/ShipPlacement.jsx
frontend/src/components/games/ConnectionsGame.css
frontend/src/components/games/ConnectionsGame.jsx
frontend/src/components/games/wordle/KnowledgeBoard.jsx
frontend/src/components/games/wordle/ModernGameBoard.jsx
frontend/src/components/games/wordle/ModernGameInterface.jsx
frontend/src/components/games/wordle/ReasoningBox.jsx
frontend/src/components/games/wordle/StartBattleModal.jsx
frontend/src/components/games/wordle/Tile.jsx
frontend/src/components/games/wordle/Wordle.css
frontend/src/components/games/wordle/WordleBoard.jsx
frontend/src/components/games/WordleGame.jsx
frontend/src/components/ModelIcon.jsx
frontend/src/components/PostGameInterview.jsx
frontend/src/components/QRCodeVote.jsx
frontend/src/components/SidebarVote.jsx
frontend/src/components/SimpleDebateGame.jsx
frontend/src/components/TriviaGame.jsx
frontend/src/components/TriviaGameView.css
frontend/src/components/TriviaGameView.jsx
frontend/src/components/VoteChart.jsx
frontend/src/hooks/useGameWebSocket.js
frontend/src/hooks/useVoteStats.js
frontend/src/hooks/useWordleGameLoop.js
frontend/src/index.css
frontend/src/main.jsx
frontend/src/pages/LandingPage.css
frontend/src/pages/LandingPage.jsx
frontend/src/pages/ModelSelection.css
frontend/src/pages/ModelSelection.jsx
frontend/src/pages/VotePage.jsx
frontend/src/styles/gameCommon.css
frontend/src/utils/gameUtils.js
frontend/src/utils/networkUtils.js
frontend/tailwind.config.js
frontend/vite.config.js
README.md
```

### Dependencies

- backend/requirements.txt: anthropic@==0.7.7, fastapi@==0.104.1, flask@==3.0.0, flask-cors@==4.0.0, google-generativeai@==0.3.2, groq@==0.3.0, httpx@==0.25.2, letta-client@==0.8.0, numpy@==2.0.0, openai@==1.3.5, pydantic@==2.5.0, python-dotenv@==1.0.0, python-multipart@==0.0.6, setuptools@>=65.5.1, transformers@==4.35.0, uvicorn[standard]@==0.24.0, websockets@==12.0
- frontend/package.json: @eslint/js@^9.25.0, @react-three/drei@^10.3.0, @react-three/fiber@^9.1.2, @react-three/postprocessing@^3.0.4, @tailwindcss/postcss@^4.1.10, @types/react@^19.1.2, @types/react-dom@^19.1.2, @vapi-ai/web@^2.3.6, @vitejs/plugin-react@^4.4.1, autoprefixer@^10.4.21, eslint@^9.25.0, eslint-plugin-react-hooks@^5.2.0, eslint-plugin-react-refresh@^0.4.19, globals@^16.0.0, lucide-react@^0.522.0, postcss@^8.5.6, postprocessing@^6.37.4, qrcode.react@^4.2.0, react@^19.1.0, react-dom@^19.1.0, react-router-dom@^7.6.2, recharts@^2.15.4, tailwindcss@^4.1.10, three@^0.177.0, vite@^6.3.5

### Recent commits (newest first)

- Add project assets link to README
- Add link to user attachments in README
- codebase cleanup
- Update .gitignore to prevent virtual environment and Python cache files from being committed
- Clean up temporary merge files
- Merge remote and local changes - Letta integration complete
- Merge remote and local changes - Letta integration complete
- Merge pull request #9 from pravinl23/voting-system
- feat: improve QR code overlay with full screen black background and side-by-side layout
- Improve debate voice synthesis with more natural voices and better timing
- Add Vapi voice synthesis to debate game with typewriter effect
- Fix debate game imports and adapt to existing BaseGame/LLMClient interface
- Add improved debate game with split-screen layout and main server integration
- Merge pull request #8 from pravinl23/voting-system
- Update package-lock.json after npm install
- Merge new frontend updates with voting system - Resolved conflicts and preserved centered popup voting with QR codes
- fixed all games
- Merge pull request #7 from pravinl23/debate
- fixed vapi voices
- Add recharts dependency for VoteChart component

## Key source files (fetched from GitHub, selected and truncated for size)

### backend/requirements.txt

```
# Core dependencies
fastapi==0.104.1
uvicorn[standard]==0.24.0
python-dotenv==1.0.0
websockets==12.0

# Python 3.12 compatibility
setuptools>=65.5.1

# Additional web framework dependencies
flask==3.0.0
flask-cors==4.0.0

# LLM API clients (using latest versions)
openai==1.3.5
anthropic==0.7.7
google-generativeai==0.3.2
groq==0.3.0

# ML dependencies
transformers==4.35.0

# Utilities
pydantic==2.5.0
httpx==0.25.2
python-multipart==0.0.6

# Game-specific and general utilities
numpy==2.0.0

# NEW: Letta Integration
letta-client==0.8.0

# Voice synthesis (optional)
# elevenlabs==0.2.26  # Optional: for direct ElevenLabs integration

```

### frontend/package.json

```
{
  "name": "versus-frontend",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "engines": {
    "node": ">=18.0.0",
    "npm": ">=8.0.0"
  },
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "lint": "eslint .",
    "preview": "vite preview"
  },
  "dependencies": {
    "@react-three/drei": "^10.3.0",
    "@react-three/fiber": "^9.1.2",
    "@react-three/postprocessing": "^3.0.4",
    "@tailwindcss/postcss": "^4.1.10",
    "@vapi-ai/web": "^2.3.6",
    "lucide-react": "^0.522.0",
    "postprocessing": "^6.37.4",
    "qrcode.react": "^4.2.0",
    "react": "^19.1.0",
    "react-dom": "^19.1.0",
    "react-router-dom": "^7.6.2",
    "recharts": "^2.15.4",
    "three": "^0.177.0"
  },
  "devDependencies": {
    "@eslint/js": "^9.25.0",
    "@types/react": "^19.1.2",
    "@types/react-dom": "^19.1.2",
    "@vitejs/plugin-react": "^4.4.1",
    "autoprefixer": "^10.4.21",
    "eslint": "^9.25.0",
    "eslint-plugin-react-hooks": "^5.2.0",
    "eslint-plugin-react-refresh": "^0.4.19",
    "globals": "^16.0.0",
    "postcss": "^8.5.6",
    "tailwindcss": "^4.1.10",
    "vite": "^6.3.5"
  }
}

```

### backend/main.py

```python
#!/usr/bin/env python3
"""
VERSUS - Main Server Entry Point
"""

import sys
import os
import socket

# Add backend to Python path
sys.path.append(os.path.dirname(os.path.abspath(__file__)))

from src.api.server import app
import uvicorn

def get_local_ip():
    """Get the local IP address"""
    try:
        # Connect to a remote server to get local IP
        with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
            s.connect(("8.8.8.8", 80))
            return s.getsockname()[0]
    except:
        return "localhost"

def main():
    """Start the VERSUS unified game server"""
    local_ip = get_local_ip()
    
    print("🎮 Starting VERSUS Unified Game Server...")
    print("📝 Server running on:")
    print(f"   • Local:   http://localhost:8000")
    if local_ip != "localhost":
        print(f"   • Network: http://{local_ip}:8000")
    print("🔧 API docs available at http://localhost:8000/docs")
    print("📱 QR codes will use network IP for phone access")
    print("❌ Press Ctrl+C to stop the server")
    print("-" * 50)
    print("Available games:")
    print("  - Battleship: WebSocket at /games/battleship/{game_id}")
    print("  - Trivia: API at /api/trivia/*")
    print("  - Wordle: API at /api/wordle/*")
    print("  - NYT Connections: API at /api/connections/*")
    print("  - Voting: API at /api/vote/*")
    print("-" * 50)
    
    uvicorn.run(app, host="0.0.0.0", port=8000)

if __name__ == "__main__":
    main() 
```

### frontend/src/main.jsx

```javascript
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'

createRoot(document.getElementById('root')).render(
  <StrictMode>
    <App />
  </StrictMode>,
)

```

### frontend/src/App.jsx

```javascript
import { useState } from "react"
import { BrowserRouter as Router, Routes, Route, useNavigate } from "react-router-dom"
import "./App.css"
import Battleship from "./components/games/Battleship/Battleship"
import TriviaGame from "./components/TriviaGame"
import WordleGame from "./components/games/WordleGame"
import ConnectionsGame from "./components/games/ConnectionsGame"
import DebateGame from "./components/DebateGame"
import NewLandingPage from "./pages/LandingPage"
import ModelSelection from "./pages/ModelSelection"
import VotePage from "./pages/VotePage"

const GAMES = [
  { name: "Wordle", emoji: "📝", description: "Speed & Strategy" },
  { name: "Trivia", emoji: "🧠", description: "Knowledge Race" },
  { name: "NYT Connections", emoji: "🔗", description: "Pattern Recognition" },
  { name: "Battleship", emoji: "🚢", description: "Strategic Warfare" },
  { name: "Debate", emoji: "🎭", description: "Argument Battle" }
]

function MainMenu() {
  const navigate = useNavigate()
  const [selectedGame, setSelectedGame] = useState(null)
  
  // Get pre-selected models from sessionStorage (from model selection page)
  const storedPlayer1 = JSON.parse(sessionStorage.getItem('player1Model') || '{}')
  const storedPlayer2 = JSON.parse(sessionStorage.getItem('player2Model') || '{}')
  
  const [player1Model, setPlayer1Model] = useState(storedPlayer1.id || "gpt-4o-mini")
  const [player2Model, setPlayer2Model] = useState(storedPlayer2.id || "claude-3-haiku-20240307")
  const [gameStarted, setGameStarted] = useState(false)

  const handleGameSelect = (game) => {
    // Start game immediately when selected
    if (game.name === "Trivia") {
      navigate("/trivia", {
        state: {
          player1Model,
          player2Model,
        },
      })
    } else if (game.name === "Debate") {
      console.log("Starting match:", {
        game: game.name,
        player1: player1Model,
        player2: player2Model,
      })
      setSelectedGame(game)
      setGameStarted(true)
    } else if (game.name === "Wordle") {
      console.log("Starting match:", {
        game: game.name,
        player1: player1Model,
        player2: player2Model,
      })
      setSelectedGame(game)
      setGameStarted(true)
    } else if (game.name === "Battleship") {
      console.log("Starting match:", {
        game: game.name,
        player1: player1Model,
        player2: player2Model,
      })
      setSelectedGame(game)
      setGameStarted(true)
    } else if (game.name === "NYT Connections") {
      console.log("Starting match:", {
        game: game.name,
        player1: player1Model,
        player2: player2Model,
      })
      setSelectedGame(game)
      setGameStarted(true)
    } else {
      console.log("Starting match:", {
        game: game.name,
        player1: player1Model,
        player2: player2Model,
      })
      setSelectedGame(game)
    }
  }

  const handleBackToMenu = () => {
    setSelectedGame(null)
    setGameStarted(false)
  }

  const handleBack = () => {
    setGameStarted(false)
  }

  // If game has started, show the appropriate game component
  if (gameStarted && selectedGame) {
    // Handle Battleship
    if (selectedGame.name === "Battleship") {
      return (
        <div className="app">
          <button
            onClick={handleBackToMenu}
            style={{
              position: "fixed",
              top: "10px",
              left: "10px",
              zIndex: 10000,
              background: "rgba(0, 0, 0, 0.8)",
              border: "1px solid rgba(255, 255, 255, 0.3)",
              color: "#fff",
              padding: "0.5rem 1rem",
              borderRadius: "6px",
              cursor: "pointer",
              fontSize: "0.9rem",
              margin: 0,
            }}
          >
            ← Back to Menu
          </button>
          <Battleship
            player1Model={player1Model}
            player2Model={player2Model}
            gameId={`game-${Date.now()}`} // Simple game ID for now
          />
        </div>
      )
    }
    // Handle Debate
    else if (selectedGame.name === "Debate") {
      return (
        <div className="app">
          <button
            onClick={handleBackToMenu}
            style={{
              position: "fixed",
              top: "10px",
              left: "10px",
              zIndex: 10000,
              background: "rgba(0, 0, 0, 0.8)",
              border: "1px solid rgba(255, 255, 255, 0.3)",
              color: "#fff",
              padding: "0.5rem 1rem",
              borderRadius: "6px",
              cursor: "pointer",
              fontSize: "0.9rem",
              margin: 0,
            }}
          >
            ← Back to Menu
          </button>
          <DebateGame
            player1Model={player1Model}
            player2Model={player2Model}
            onBack={handleBackToMenu}
          />
        </div>
      )
    }
    // Handle Wordle
    else if (selectedGame.name === "Wordle") {
      return <WordleGame player1Model={player1Model} player2Model={player2Model} onBack={handleBack} />
    }
    // Handle NYT Connections
    else if (selectedGame.name === "NYT Connections") {
      return <ConnectionsGame player1Model={player1Model} player2Model={player2Model} onBack={handleBackToMenu} />
    }
    // Handle other games that aren't implemented yet
    else {
      return (
        <div className="app">
          <button
            onClick={handleBackToMenu}
            style={{
              position: "absolute",
              top: "10px",
              left: "10px",
              zIndex: 1000,
              background: "rgba(0, 0, 0, 0.8)",
              border: "1px solid rgba(255, 255, 255, 0.3)",
              color: "#fff",
              padding: "0.5rem 1rem",
              borderRadius: "6px",
              cursor: "pointer",
              fontSize: "0.9rem",
              margin: 0,
            }}
          >
            ← Back to Menu
          </button>
          <div classNam
[truncated — 1716 more characters]
```

### backend/src/games/trivia/server.py

```python
"""
FastAPI server for Trivia game mode
Handles game sessions, WebSocket connections, and real-time updates
"""

from fastapi import FastAPI, WebSocket, WebSocketDisconnect, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from typing import Dict, List, Optional
import json
import uuid
import asyncio

import sys
import os
sys.path.append(os.path.dirname(os.path.abspath(__file__)))

from trivia_game import TriviaGame
from questions import TRIVIA_QUESTIONS, get_random_questions

app = FastAPI(title="VERSUS Trivia API", version="1.0.0")

# CORS middleware
app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:3000", "http://localhost:5173"],  # React dev servers
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Pydantic models
class GameStartRequest(BaseModel):
    player1_model: str
    player2_model: str
    question_count: Optional[int] = 20

class GameSession:
    def __init__(self, game_id: str, trivia_game: TriviaGame):
        self.game_id = game_id
        self.trivia_game = trivia_game
        self.websockets: List[WebSocket] = []
        self.current_question_index = 0
        self.is_active = True

# Global game sessions storage
game_sessions: Dict[str, GameSession] = {}

class ConnectionManager:
    def __init__(self):
        self.active_connections: Dict[str, List[WebSocket]] = {}

    async def connect(self, websocket: WebSocket, game_id: str):
        await websocket.accept()
        if game_id not in self.active_connections:
            self.active_connections[game_id] = []
        self.active_connections[game_id].append(websocket)

    def disconnect(self, websocket: WebSocket, game_id: str):
        if game_id in self.active_connections:
            self.active_connections[game_id].remove(websocket)
            if not self.active_connections[game_id]:
                del self.active_connections[game_id]

    async def send_personal_message(self, message: str, websocket: WebSocket):
        await websocket.send_text(message)

    async def broadcast_to_game(self, message: str, game_id: str):
        if game_id in self.active_connections:
            for connection in self.active_connections[game_id]:
                try:
                    await connection.send_text(message)
                except:
                    pass  # Connection might be closed

manager = ConnectionManager()

@app.get("/api/trivia/questions")
async def get_questions():
    """Get all available trivia questions"""
    return {"questions": TRIVIA_QUESTIONS}

@app.get("/api/trivia/models")
async def get_supported_models():
    """Get list of supported LLM models"""
    return {
        "models": [
            {"id": "OPENAI", "name": "GPT-4", "description": "OpenAI GPT-4"},
            {"id": "ANTHROPIC", "name": "Claude 3", "description": "Anthropic Claude 3"},
            {"id": "GEMINI", "name": "Gemini Pro", "description": "Google Gemini Pro"},
            {"id": "GROQ", "name": "Mixtral", "description": "Groq Mixtral 8x7B"}
        ]
    }

@app.post("/api/trivia/start")
async def start_trivia_game(request: GameStartRequest):
    """Start a new trivia game session"""
    try:
        # Generate unique game ID
        game_id = str(uuid.uuid4())
        
        # Get random questions
        questions = get_random_questions(request.question_count)
        
        # Create trivia game instance
        trivia_game = TriviaGame(
            player1_model=request.player1_model,
            player2_model=request.player2_model,
            questions=questions
        )
        
        # Store game session
        game_sessions[game_id] = GameSession(game_id, trivia_game)
        
        return {
            "game_id": game_id,
            "status": "started",
            "total_questions": len(questions),
            "player1_model": request.player1_model,
            "player2_model": request.player2_model
        }
        
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Failed to start game: {str(e)}")

@app.get("/api/trivia/game/{game_id}/status")
async def get_game_status(game_id: str):
    """Get current game status"""
    if game_id not in game_sessions:
        raise HTTPException(status_code=404, detail="Game not found")
    
    session = game_sessions[game_id]
    game = session.trivia_game
    
    return {
        "game_id": game_id,
        "current_question": game.current_question_index,
        "total_questions": len(game.questions),
        "player1_score": game.player1_score,
        "player2_score": game.player2_score,
        "game_over": game.game_over,
        "winner": game.winner,
        "is_active": session.is_active
    }

@app.post("/api/trivia/game/{game_id}/player/{player}/next-question")
async def player_next_question(game_id: str, player: int):
    """Process next question for a specific player in the race"""
    if game_id not in game_sessions:
        raise HTTPException(status_code=404, detail="Game not found")
    
    session = game_sessions[game_id]
    game = session.trivia_game
    
    if game.game_over:
        return {"error": "Race is already over"}
    
    if player not in [1, 2]:
        raise HTTPException(status_code=400, detail="Player must be 1 or 2")
    
    try:
        # Ask question to specific player
        result = await game.ask_question_to_player(player)
        
        # Broadcast result to all connected clients
        await manager.broadcast_to_game(
            json.dumps({
                "type": "player_question_result",
                "data": result
            }),
            game_id
        )
        
        # Check if race is over
        if game.race_finished:
            final_results = game.get_final_results()
            await manager.broadcast_to_game(
                json.dumps({
                    "type": "race_finished",
                    "data": final_results
                }),
   
[truncated — 3518 more characters]
```

### frontend/postcss.config.js

```javascript
export default {
  plugins: {
    '@tailwindcss/postcss': {},
    autoprefixer: {},
  },
} 
```

### frontend/tailwind.config.js

```javascript
/** @type {import('tailwindcss').Config} */
export default {
  content: [
    "./index.html",
    "./src/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
} 
```

### frontend/vite.config.js

```javascript
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://vite.dev/config/
export default defineConfig({
  plugins: [react()],
  server: {
    host: '0.0.0.0', // Allow network access for mobile devices
    port: 5174
  }
})

```

### frontend/index.html

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/png" href="/favicon.png" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=VT323&display=swap" rel="stylesheet">
    <title>Versus - AI Battle Arena</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.jsx"></script>
  </body>
</html>

```

[54 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]