# Project export: Boardify

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: TreeHacks 2026
- Tagline: Play your favorite board games with friends Anytime, Anywhere
- Devpost: https://devpost.com/software/boardify
- GitHub: https://github.com/AlexiaHyn/Boardify
- Demo: http://boardify-gold.vercel.app/
- Video: https://www.youtube.com/embed/NDjyJeRQWgs?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 3 GitHub contributor(s) — Daren Tan (17 commits), AlexiaHyn (16 commits), Ngoc Phuc Khang Nguyen (7 commits)

## Devpost submission (written by the team)

### Inspiration

We are international students who bonded over board games on the first day of the hackathon. It was a great way to break the ice, but we quickly realized a problem: once we leave, we can’t keep playing together because most quality online board game platforms are behind paywalls or require complex setups. We wanted to democratize this experience. We built Boardify to let friends anywhere play any board game—whether it’s a classic they miss from home or a brand-new game they just invented—instantly and for free.

### What it does

Boardify is an AI-powered game engine that turns text descriptions into fully playable, multiplayer online card games in seconds. Input: You type a game name (e.g., "Uno", "Exploding Kittens") or a completely new idea (e.g., "A card game about hacking where you steal GPUs"). Generation: Our AI agent researches the official rules (or improvises new ones), designs the deck, and generates the game logic. Visualization: It generates custom card assets and artwork on the fly. Play: It deploys a live multiplayer room where you and your friends can join and play immediately in the browser.

### How we built it

We built a sophisticated "Universal Game Engine" that decouples game rules from the core logic, allowing AI to script the game behavior dynamically. Frontend: Built with React and deployed on Vercel for a responsive, real-time interface. AI Pipeline (hosted on Modal): Research: We use Perplexity Sonar to search the web for comprehensive, up-to-date rules for existing games. Logic Generation: We use Anthropic Claude 3.5 Sonnet to convert those rules into a strict, proprietary JSON Game Schema (defining deck structure, turn phases, and win conditions) and a Python Plugin (handling complex mechanics like "stacking draw cards" or "blocking actions"). Visuals: We use Flux to generate thematic artwork for the card faces and backgrounds based on the game's theme. Research: We use Perplexity Sonar to search the web for comprehensive, up-to-date rules for existing games. Logic Generation: We use Anthropic Claude 3.5 Sonnet to convert those rules into a strict, proprietary JSON Game Schema (defining deck structure, turn phases, and win conditions) and a Python Plugin (handling complex mechanics like "stacking draw cards" or "blocking actions"). Visuals: We use Flux to generate thematic artwork for the card faces and backgrounds based on the game's theme. Backend & Infrastructure: The entire backend runs on Modal. We use Modal's sandboxing capabilities to safely execute and validate the AI-generated Python code before the game starts, ensuring the game is bug-free and playable.

### Challenges we ran into

The "Universal" Problem: Designing a single JSON schema that could describe every possible card game was incredibly difficult. We had to abstract mechanics like "reverse turn order," "skip next player," and "reaction cards" (like Nope in Exploding Kittens) into a generalized event system. AI Hallucinations: Early on, the AI would generate rules that didn't make sense (e.g., a deck with 0 cards). We solved this by implementing a validation step in a Modal sandbox that "test-runs" the generated game configuration to catch errors before the user sees them. Real-time State Sync: Managing the game state for multiplayer sessions where rules change dynamically required a robust WebSocket architecture.

### Accomplishments we're proud of

The Hybrid Engine: We're proud that we didn't just make a "text adventure." We built a real game engine where the AI writes actual Python code (plugins) to handle edge cases the JSON schema can't cover. This allows for complex logic like "challenging a Wild Draw 4" in Uno. Instant Deployment: Going from a text prompt to a playable multiplayer link in under a minute is a huge technical feat involving three different AI models working in concert. Sandboxed Safety: Successfully using Modal to sandbox the AI-generated code means our platform is secure, even though it executes arbitrary code generated by an LLM.

### What we learned

Structured Output is Key: We learned that asking an LLM to "write code" is messy, but asking it to "fill out this specific JSON schema" yields incredibly reliable results. Infrastructure Matters: Using serverless GPUs via Modal allowed us to scale our heavy AI workloads (like Flux image generation) without managing complex infrastructure. Community: We learned the value of finding mentors early! Their feedback helped us narrow our scope from "all board games" to "card-based games," which made the project achievable in 36 hours.

### What's next

Board Support: Expanding our "Universal Engine" to support spatial boards (like Chess or Catan), not just card stacks. Voice Mode: Adding a voice interface so you can argue about the rules with the AI dealer, just like in real life. Persistent Campaigns: Allowing the AI to remember past games and evolve the rules over a series of sessions (Legacy-style games).

## README (from the GitHub repository)

# 🃏 Card Game Engine

A **fully generic multiplayer card game engine** built with FastAPI + WebSockets on the backend and Next.js 14 on the frontend. Any card game can be defined in a single JSON file — the engine reads it and runs a working multiplayer version.

Currently ships with a **complete, playable Exploding Kittens** implementation.

---

## 🗂 Project Structure

```
game-engine/
├── backend/
│   ├── app/
│   │   ├── main.py                  ← FastAPI entry point (game-agnostic)
│   │   ├── models/
│   │   │   └── game.py              ← Domain models (Card, Player, GameState…)
│   │   ├── schemas/
│   │   │   └── requests.py          ← API request/response schemas
│   │   ├── routers/
│   │   │   ├── rooms.py             ← HTTP REST endpoints
│   │   │   └── websocket.py         ← WebSocket real-time endpoint
│   │   ├── services/
│   │   │   ├── game_loader.py       ← Reads JSON → builds game state
│   │   │   ├── room_manager.py      ← In-memory rooms + broadcast
│   │   │   └── engines/
│   │   │       ├── exploding_kittens.py  ← EK-specific rules
│   │   │       └── generic.py            ← Fallback engine
│   │   └── games/
│   │       └── exploding_kittens.json   ← 🎮 Game definition
│   ├── requirements.txt
│   └── Dockerfile
│
├── frontend/
│   ├── src/
│   │   ├── app/
│   │   │   ├── page.tsx             ← Home: create / join room
│   │   │   ├── join/page.tsx        ← Join via code or link
│   │   │   └── room/[roomCode]/page.tsx  ← Active game room
│   │   ├── components/
│   │   │   ├── game/
│   │   │   │   ├── GameRoom.tsx     ← Main orchestrator
│   │   │   │   ├── GameCard.tsx     ← Generic card component
│   │   │   │   ├── GameTable.tsx    ← Table, deck, discard, opponents
│   │   │   │   ├── PlayerHand.tsx   ← Local player's hand
│   │   │   │   ├── GameLog.tsx      ← Scrolling event log
│   │   │   │   ├── PendingActionPanel.tsx  ← Modals for Favor, Nope, etc.
│   │   │   │   └── SeeTheFutureModal.tsx
│   │   │   └── lobby/
│   │   │       └── Lobby.tsx        ← Waiting room UI
│   │   ├── hooks/
│   │   │   ├── useGameSocket.ts     ← Auto-reconnecting WebSocket
│   │   │   └── useGameActions.ts    ← Action dispatch helpers
│   │   ├── lib/
│   │   │   └── api.ts               ← Typed API client
│   │   └── types/
│   │       └── game.ts              ← TypeScript interfaces
│   ├── package.json
│   └── Dockerfile
│
└── docker-compose.yml
```

---

## 🚀 Quick Start

### Without Docker

**Backend:**
```bash
cd backend
pip install -r requirements.txt
uvicorn app.main:app --reload --port 8000
```

**Frontend:**
```bash
cd frontend
cp .env.local.example .env.local   # edit if your backend isn't on :8000
npm install
npm run dev
```

Open [http://localhost:3000](http://localhost:3000).

### With Docker Compose
```bash
docker compose up --build
```

---

## 🎮 How to Play (Exploding Kittens)

1. **Create a room** — enter your name, choose Exploding Kittens, click Create.
2. **Share the link** — click "Copy Invite Link" in the lobby and send it to friends (or share the 6-digit room code directly).
3. **Friends join** — they visit `/join?room=XXXXXX` or paste the link, enter their name.
4. **Host starts** — once 2–5 players are in the lobby, the host clicks "Start Game".
5. **Play!**

### Card interactions
| Card | How to use |
|------|-----------|
| Action cards (Skip, Attack, Shuffle, See Future) | Click the card in your hand |
| Favor | Click Favor → choose a target player |
| Cat combos (Taco, Rainbow, Beard, etc.) | Click first cat, then click a matching cat, then choose a target |
| Nope | When a Nope button appears, click it to cancel an action |
| Defuse | Automatically used when you draw an Exploding Kitten |
| Bomb placement | After defusing, drag the slider to choose where to reinsert the bomb |

---

## ➕ Adding a New Game (e.g. Uno)

1. Create `backend/app/games/uno.json` following the schema below.
2. *(Optional)* Create `backend/app/services/engines/uno.py` for game-specific rules. If absent, the generic engine is used.
3. That's it — the game appears in the frontend dropdown automatically.

### JSON Game Definition Schema

```json
{
  "id": "my_game",
  "name": "My Card Game",
  "description": "...",

  "rules": {
    "minPlayers": 2,
    "maxPlayers": 8,
    "handSize": 7,
    "turnStructure": {
      "phases": [
        { "id": "play", "name": "Play a Card", "description": "...", "isOptional": false },
        { "id": "draw", "name": "Draw", "description": "...", "isOptional": true }
      ],
      "canPassTurn": true,
      "mustPlayCard": false,
      "drawCount": 1
    },
    "winCondition": {
      "type": "empty_hand",
      "description": "First player to empty their hand wins!"
    },
    "specialRules": []
  },

  "cards": [
    {
      "id": "card_key",          // unique identifier
      "name": "Card Name",
      "type": "action",          // action | defense | reaction | special | combo
      "subtype": "card_key",     // used by the engine for effect logic
      "emoji": "🃏",
      "description": "What this card does",
      "effects": [
        {
          "type": "skip",        // effect type for engine logic
          "target": "self",      // self | others | choose | all
          "description": "...",
          "metadata": {}
        }
      ],
      "isPlayable": true,
      "isReaction": false,       // true = can be played out of turn
      "count": 4,                // copies in the deck
      "metadata": {}
    }
  ],

  "ui": {
    "tableBackground": "#1a472a",
    "turnPrompt": "It's your turn!",
    "winMessage": "🎉 {playerName} wins!",
    "actionLabels": {
      "draw_card": "Draw",
      "play_card": "Play"
    }
  }
}
```

### Custom Engine Module (optional)

If you need game-specific rules (e.g. Uno's draw-2, reverse, wild), create:
`backend/app/services/engines/my_game.py`

It must export two functions:

```python
def setup_game(state: GameState) -> None:
    """Deal cards, set up zones, set state.phase = 'playing'."""
    ...

def apply_action(state: GameState, action: ActionRequest) -> tuple[bool, str, list[str]]:
    """Returns (success, error_message, triggered_effects)."""
    ...
```

---

## 🔌 API Reference

| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/games` | List available game types |
| POST | `/api/rooms/create` | Create a new room |
| POST | `/api/rooms/{code}/join` | Join a room |
| POST | `/api/rooms/{code}/start?player_id=X` | Start the game (host only) |
| POST | `/api/rooms/{code}/action` | Send a game action |
| GET | `/api/rooms/{code}/state` | Get current game state |
| WS | `/ws/{code}/{playerId}` | Real-time state updates |

### Action types (Exploding Kittens)

| type | Extra fields | Description |
|------|-------------|-------------|
| `draw_card` | — | Draw the top card of the deck |
| `play_card` | `cardId`, optional `targetPlayerId`, optional `metadata.comboPairId` | Play a card from hand |
| `nope` | `cardId` | Play a Nope to cancel a pending action |
| `select_target` | `targetPlayerId` or `metadata.cardId` | Resolve Favor target |
| `insert_exploding` | `metadata.position` | Place bomb back in deck after defusing |

---

## 🏗 Architecture Notes

- **State is fully server-side.** The client never modifies state locally — it only sends actions and receives broadcasts.
- **Per-player state masking.** Each WebSocket broadcast sends a personalised view with other players' hands hidden.
- **Game engines are pluggable.** `game_loader._get_engine(game_type)` dynamically imports the engine by name, falling back to `generic.py`.
- **JSON-driven card data.** Card definitions, rules, and UI strings all live in the JSON file. The engine modules handle only the imperative logic.
- **Pydantic v2 models** throughout — all state is validated and serialised consistently.

---

## 🧩 Extending the Generic Engine

The `generic.py` engine handles:
- Drawing cards
- Playing cards (discard + log)
- `empty_hand` win condition

For more

[README truncated for size]

## Detected evidence (automated analysis)

Indexed codebase: 59 recognized source files, 425 KB.
- Anthropic (technology) — detected in the code
- CSS (language) — detected in the code
- FastAPI (technology) — detected in the code
- JavaScript (language) — detected in the code
- Next.js (technology) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code

## Codebase structure (from repository index)

### Files (78 of 78)

```
.vscode/settings.json
game-engine/backend/.env.example
game-engine/backend/.gitignore
game-engine/backend/app/__init__.py
game-engine/backend/app/games/crazy_eights.json
game-engine/backend/app/games/exploding_kittens.json
game-engine/backend/app/games/go_fish.json
game-engine/backend/app/games/uno_draw_five.json
game-engine/backend/app/games/uno_plus_1000.json
game-engine/backend/app/games/uno.json
game-engine/backend/app/main.py
game-engine/backend/app/models/__init__.py
game-engine/backend/app/models/game.py
game-engine/backend/app/routers/__init__.py
game-engine/backend/app/routers/rooms.py
game-engine/backend/app/routers/websocket.py
game-engine/backend/app/schemas/__init__.py
game-engine/backend/app/schemas/requests.py
game-engine/backend/app/services/__init__.py
game-engine/backend/app/services/engines/__init__.py
game-engine/backend/app/services/engines/exploding_kittens.py
game-engine/backend/app/services/engines/game_plugin_base.py
game-engine/backend/app/services/engines/generic.py
game-engine/backend/app/services/engines/go_fish.py
game-engine/backend/app/services/engines/INTEGRATION_COMPLETE.md
game-engine/backend/app/services/engines/plugin_loader.py
game-engine/backend/app/services/engines/PLUGIN_SYSTEM_README.md
game-engine/backend/app/services/engines/SETUP_GUIDE.md
game-engine/backend/app/services/engines/universal.py
game-engine/backend/app/services/engines/uno_draw_five.py
game-engine/backend/app/services/engines/uno_plus_1000.py
game-engine/backend/app/services/engines/uno.py
game-engine/backend/app/services/game_generator.py
game-engine/backend/app/services/game_loader.py
game-engine/backend/app/services/modal_app.py
game-engine/backend/app/services/room_manager.py
game-engine/backend/app/showcases/exploding_kittens/exploding_kittens.json
game-engine/backend/app/showcases/exploding_kittens/exploding_kittens.py
game-engine/backend/app/showcases/uno/uno.json
game-engine/backend/app/showcases/uno/uno.py
game-engine/backend/Dockerfile
game-engine/backend/manage_games.py
game-engine/backend/README_MODAL.md
game-engine/backend/requirements.txt
game-engine/docker-compose.yml
game-engine/frontend/.env.local.example
game-engine/frontend/.env.production
game-engine/frontend/.gitignore
game-engine/frontend/DEPLOY_VERCEL.md
game-engine/frontend/Dockerfile
game-engine/frontend/next-env.d.ts
game-engine/frontend/next.config.js
game-engine/frontend/package.json
game-engine/frontend/postcss.config.js
game-engine/frontend/src/app/globals.css
game-engine/frontend/src/app/layout.tsx
game-engine/frontend/src/app/page.tsx
game-engine/frontend/src/app/room/[roomCode]/page.tsx
game-engine/frontend/src/app/room/page.tsx
game-engine/frontend/src/app/showcase/page.tsx
game-engine/frontend/src/components/game/DefaultActionButtons.tsx
game-engine/frontend/src/components/game/GameCard.tsx
game-engine/frontend/src/components/game/GameLog.tsx
game-engine/frontend/src/components/game/GameRoom.tsx
game-engine/frontend/src/components/game/GameTable.tsx
game-engine/frontend/src/components/game/PendingActionPanel.tsx
game-engine/frontend/src/components/game/PlayerHand.tsx
game-engine/frontend/src/components/game/SeeTheFutureModal.tsx
game-engine/frontend/src/components/lobby/Lobby.tsx
game-engine/frontend/src/hooks/useGameActions.ts
game-engine/frontend/src/hooks/useGameSocket.ts
game-engine/frontend/src/lib/api.ts
game-engine/frontend/src/lib/games.ts
game-engine/frontend/src/types/game.ts
game-engine/frontend/tailwind.config.js
game-engine/frontend/tsconfig.json
game-engine/README.md
README.md
```

### Dependencies

- game-engine/backend/requirements.txt: anthropic@>=0.39.0, fastapi@>=0.110.0, httpx@>=0.27.0, modal@>=0.66.0, pydantic@>=2.0.0, python-multipart@>=0.0.9, uvicorn[standard]@>=0.29.0, websockets@>=12.0
- game-engine/frontend/package.json: @types/node@^20.12.12, @types/react@^18.3.3, @types/react-dom@^18.3.0, autoprefixer@^10.4.19, next@14.2.3, postcss@^8.4.38, react@^18.3.0, react-dom@^18.3.0, tailwindcss@^3.4.3, typescript@^5.4.5

### Recent commits (newest first)

- Merge pull request #11 from AlexiaHyn/revert-9-game-entities
- Revert "Update card UI"
- Merge pull request #9 from AlexiaHyn/game-entities
- Merge branch 'main' into game-entities
- Merge pull request #10 from AlexiaHyn/lol
- fix to fetch data
- Merge pull request #8 from AlexiaHyn/ai-image-gen
- migrate exploding kittens to the new game engine
- fix to prioritise main file logic
- Add colour customization
- sync prod state
- fixed nope in EK
- Merge pull request #7 from AlexiaHyn/game-entities
- Fixed see the future bug
- updated exploding kittens half working
- iterate on image gen prompt
- manage games
- working image gen
- Merge pull request #6 from AlexiaHyn/game-entities
- setup for vercel deployment

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

### game-engine/frontend/DEPLOY_VERCEL.md

```markdown
# Deploy Frontend to Vercel

## Prerequisites

- ✅ Backend deployed and accessible via public URL (Heroku, Railway, Render, etc.)
- ✅ Backend supports CORS (already configured in `app/main.py`)
- ✅ Backend supports WebSocket connections

---

## Option 1: Deploy via Vercel Dashboard (Easiest)

### Step 1: Update Production Environment Variables

Edit `frontend/.env.production` with your backend URL:

```env
NEXT_PUBLIC_API_URL=https://your-backend-url.herokuapp.com
NEXT_PUBLIC_WS_URL=wss://your-backend-url.herokuapp.com
```

**Important:** Use `https://` for API and `wss://` for WebSocket!

### Step 2: Push to GitHub

```bash
# From project root
git add .
git commit -m "Prepare frontend for Vercel deployment"
git push
```

### Step 3: Deploy on Vercel

1. Go to https://vercel.com
2. Click **"Add New..." → "Project"**
3. **Import Git Repository**:
   - Connect your GitHub account (if not already)
   - Select your repository: `Boardify`
4. **Configure Project**:
   - Framework Preset: **Next.js** (auto-detected)
   - Root Directory: Click **"Edit"** → Set to `game-engine/frontend`
   - Build Command: `npm run build` (default)
   - Output Directory: `.next` (default)
   - Install Command: `npm install` (default)
5. **Environment Variables**:
   - Click **"Environment Variables"**
   - Add variable:
     - Name: `NEXT_PUBLIC_API_URL`
     - Value: `https://your-backend-url.herokuapp.com`
   - Add variable:
     - Name: `NEXT_PUBLIC_WS_URL`
     - Value: `wss://your-backend-url.herokuapp.com`
6. Click **"Deploy"**

### Step 4: Wait for Deployment

Vercel will:
- ✅ Install dependencies
- ✅ Build your Next.js app
- ✅ Deploy to global CDN
- ✅ Provide a production URL: `https://boardify-xxx.vercel.app`

**Deployment takes ~2-3 minutes.**

---

## Option 2: Deploy via Vercel CLI (Advanced)

### Step 1: Install Vercel CLI

```bash
npm install -g vercel
```

### Step 2: Login to Vercel

```bash
vercel login
```

This opens a browser for authentication.

### Step 3: Deploy

```bash
cd game-engine/frontend

# First deployment (interactive)
vercel

# Follow prompts:
# - Set up and deploy? Yes
# - Which scope? (Your team)
# - Link to existing project? No
# - Project name? boardify-frontend
# - Directory? ./ (current)
# - Override settings? No

# Set environment variables
vercel env add NEXT_PUBLIC_API_URL production
# Paste your backend URL: https://your-backend.herokuapp.com

vercel env add NEXT_PUBLIC_WS_URL production
# Paste your WebSocket URL: wss://your-backend.herokuapp.com

# Deploy to production
vercel --prod
```

### Step 4: Get Your URL

After deployment, Vercel shows:
```
✅ Production: https://boardify-frontend.vercel.app
```

---

## Option 3: Auto-Deploy from GitHub (Recommended)

Once you've deployed via Option 1 or 2, Vercel automatically redeploys when you push to GitHub.

**Enable Auto-Deploy:**

1. In Vercel dashboard → Your project
2. Settings → Git
3. Production Branch: `main` (or your default branch)
4. **Automatic Deployments**: Enabled ✅

[truncated — 4629 more characters]
```

### game-engine/backend/README_MODAL.md

```markdown
# Modal Setup Guide for Boardify Backend

> **Windows Users:** Use `python -m modal` instead of `modal` for all commands throughout this guide.

## Overview

This backend uses [Modal](https://modal.com) for serverless AI-powered card game generation. Modal provides:

- **Serverless Python functions** - Run expensive AI operations on-demand without managing infrastructure
- **GPU/CPU scaling** - Automatically scales based on load
- **Sandboxed execution** - Validate generated games safely
- **Secret management** - Secure API key handling

## What Modal Does in Boardify

The backend includes a Modal app (`app/services/modal_app.py`) that:

1. **Research game rules** - Uses Perplexity Sonar API to fetch comprehensive card game rules
2. **Generate game JSON** - Uses Claude to create game definition files from rules
3. **Generate plugins** - Uses Claude to create Python plugin files for game-specific logic
4. **Validate games** - Runs sandboxed validation to ensure JSON is correct and playable

---

## Prerequisites

- Python 3.11+ installed
- Modal account (free tier available)
- Anthropic API key (for Claude)
- Perplexity API key (for Sonar research)

---

## Setup Instructions

### 1. Install Modal

**If using a virtual environment (recommended):**

```bash
# Navigate to your project root
cd game-engine

# Activate your virtual environment first
source .venv/Scripts/activate  # Windows Git Bash
# OR
.venv\Scripts\activate  # Windows CMD
# OR
source .venv/bin/activate  # Unix/Mac

# Install Modal in the venv
pip install modal

# Or install all backend dependencies at once
cd backend
pip install -r requirements.txt
```

**If NOT using a virtual environment:**

```bash
pip install modal

# Or install all dependencies:
cd game-engine/backend
pip install -r requirements.txt
```

**Windows Note:** On Windows, Modal commands need to be run with `python -m modal` instead of just `modal`.

**Important:** Modal must be installed in the same Python environment you use to run the backend. If you get "No module named modal" errors, make sure your virtual environment is activated.

### 2. Authenticate with Modal

Run the Modal setup command to authenticate:

**Unix/Mac:**
```bash
modal setup
```

**Windows:**
```bash
python -m modal setup
```

This will:
- Open a browser window to log in to Modal
- Create a Modal token on your machine (~/.modal.toml or %USERPROFILE%\.modal.toml on Windows)
- Connect your local environment to Modal's infrastructure

### 3. Configure Environment Variables

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

```bash
cd game-engine/backend
cp .env.example .env
```

Edit `.env` and add your API keys:

```env
ANTHROPIC_API_KEY=sk-ant-xxxxx
PERPLEXITY_API_KEY=pplx-xxxxx
```

**Get API Keys:**
- **Anthropic API**: https://console.anthropic.com/
- **Perplexity API**: https://www.perplexity.ai/settings/api

### 4. Deploy the Modal App

Deploy the Modal functions to Modal's cloud:

**Unix/Mac:**
```bash
modal deploy app/services/modal_app.py
`
[truncated — 9143 more characters]
```

### game-engine/docker-compose.yml

```yaml
version: '3.9'

services:
  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile
    ports:
      - "8000:8000"
    volumes:
      - ./backend/app/games:/app/app/games   # hot-reload game JSON files
    environment:
      - PYTHONUNBUFFERED=1
    restart: unless-stopped

  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    environment:
      - NEXT_PUBLIC_API_URL=http://localhost:8000
      - NEXT_PUBLIC_WS_URL=ws://localhost:8000
    depends_on:
      - backend
    restart: unless-stopped

```

### game-engine/frontend/Dockerfile

```
FROM node:20-alpine AS base

WORKDIR /app
COPY package*.json ./
RUN npm ci

COPY . .
RUN npm run build

EXPOSE 3000
CMD ["npm", "start"]

```

### game-engine/backend/requirements.txt

```
fastapi>=0.110.0
uvicorn[standard]>=0.29.0
pydantic>=2.0.0
websockets>=12.0
python-multipart>=0.0.9
modal>=0.66.0
anthropic>=0.39.0
httpx>=0.27.0

```

### game-engine/backend/Dockerfile

```
FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 8000

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]

```

### game-engine/frontend/package.json

```
{
  "name": "card-game-engine-frontend",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "next": "14.2.3",
    "react": "^18.3.0",
    "react-dom": "^18.3.0"
  },
  "devDependencies": {
    "@types/node": "^20.12.12",
    "@types/react": "^18.3.3",
    "@types/react-dom": "^18.3.0",
    "autoprefixer": "^10.4.19",
    "postcss": "^8.4.38",
    "tailwindcss": "^3.4.3",
    "typescript": "^5.4.5"
  }
}

```

### game-engine/backend/app/main.py

```python
"""
Card Game Engine – FastAPI entry point.
Generic: load any card game by pointing at its JSON definition.
"""
from pathlib import Path

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles

from app.routers import rooms, websocket

app = FastAPI(
    title="Card Game Engine API",
    description="Generic multiplayer card game engine. Load any card game via JSON.",
    version="3.0.0",
)

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_methods=["*"],
    allow_headers=["*"],
)

# Serve generated card images from /static/
_static_dir = Path(__file__).resolve().parent.parent / "static"
_static_dir.mkdir(parents=True, exist_ok=True)
app.mount("/static", StaticFiles(directory=str(_static_dir)), name="static")

app.include_router(rooms.router)
app.include_router(websocket.router)


@app.get("/health")
def health():
    from app.services.room_manager import ROOMS
    return {"status": "ok", "active_rooms": len(ROOMS)}

```

### game-engine/frontend/src/app/layout.tsx

```typescript
import type { Metadata } from 'next';
import { Cinzel, Crimson_Pro } from 'next/font/google';
import './globals.css';

const cinzel = Cinzel({
  subsets: ['latin'],
  variable: '--font-display',
  weight: ['400', '500', '600', '700'],
});

const crimsonPro = Crimson_Pro({
  subsets: ['latin'],
  variable: '--font-body',
  weight: ['300', '400', '500', '600', '700'],
  style: ['normal', 'italic'],
});

export const metadata: Metadata = {
  title: 'Boardify – Conjure Your Next Great Game',
  description:
    'Describe a board game idea and watch it materialize into a complete blueprint, powered by AI.',
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en" className={`${cinzel.variable} ${crimsonPro.variable}`}>
      <body className="antialiased">{children}</body>
    </html>
  );
}

```

### game-engine/frontend/src/app/page.tsx

```typescript
"use client";

import { useState, useEffect } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { generateGame, type GenerateProgressEvent } from "@/lib/api";
import { addGame } from "@/lib/games";

/* ─── Example prompts for typing animation ─────────────────────────── */

const EXAMPLE_PROMPTS = [
	"Exploding Kittens with 5 diffuser cards",
	"A bluffing game for 4 players with hidden roles",
	"Uno but every card has a spell effect",
	"Cooperative survival horror card game",
	"Poker meets deck-building with fantasy creatures",
	"A fast 2-player dueling card game",
];

/* ─── Typing Animation Hook ───────────────────────────────────────── */

function useTypingAnimation(
	phrases: string[],
	typingSpeed = 45,
	deletingSpeed = 25,
	pauseMs = 2200,
) {
	const [display, setDisplay] = useState("");
	const [phraseIdx, setPhraseIdx] = useState(0);
	const [charIdx, setCharIdx] = useState(0);
	const [isDeleting, setIsDeleting] = useState(false);

	useEffect(() => {
		const current = phrases[phraseIdx] ?? "";

		if (!isDeleting && charIdx <= current.length) {
			if (charIdx === current.length) {
				const timeout = setTimeout(() => setIsDeleting(true), pauseMs);
				return () => clearTimeout(timeout);
			}
			const timeout = setTimeout(() => {
				setDisplay(current.slice(0, charIdx + 1));
				setCharIdx((c) => c + 1);
			}, typingSpeed);
			return () => clearTimeout(timeout);
		}

		if (isDeleting && charIdx > 0) {
			const timeout = setTimeout(() => {
				setDisplay(current.slice(0, charIdx - 1));
				setCharIdx((c) => c - 1);
			}, deletingSpeed);
			return () => clearTimeout(timeout);
		}

		if (isDeleting && charIdx === 0) {
			setIsDeleting(false);
			setPhraseIdx((i) => (i + 1) % phrases.length);
		}
	}, [
		charIdx,
		isDeleting,
		phraseIdx,
		phrases,
		typingSpeed,
		deletingSpeed,
		pauseMs,
	]);

	return display;
}

/* ─── Compass Rose Loading SVG ────────────────────────────────────── */

function CompassRose() {
	return (
		<div className="relative flex items-center justify-center my-10">
			<div className="absolute h-36 w-36 rounded-full border border-[var(--color-gold-dim)] opacity-20 animate-[radialPulse_2.5s_ease-in-out_infinite]" />
			<div className="absolute h-44 w-44 rounded-full border border-[var(--color-gold-dim)] opacity-10 animate-[radialPulse_2.5s_ease-in-out_infinite_0.5s]" />
			<svg
				width="120"
				height="120"
				viewBox="0 0 120 120"
				fill="none"
				className="animate-[compassSpin_8s_linear_infinite]"
			>
				<circle
					cx="60"
					cy="60"
					r="50"
					stroke="var(--color-gold)"
					strokeWidth="1.5"
					strokeDasharray="314"
					className="animate-[drawCircle_2s_ease-out_forwards]"
					opacity="0.6"
				/>
				<circle
					cx="60"
					cy="60"
					r="35"
					stroke="var(--color-gold-dim)"
					strokeWidth="1"
					strokeDasharray="220"
					className="animate-[drawCircle_2.5s_ease-out_forwards]"
					opacity="0.4"
				/>
				<line
					x1="60"
					y1="60"
					x2="60"
					y2="15"
					stroke="var(--color-gold)"
					strokeWidth="2"
					strokeLinecap="round"
					strokeDasharray="45"
					className="animate-[drawNeedle_1.5s_ease-out_0.5s_forwards]"
					opacity="0.8"
				/>
				<line
					x1="60"
					y1="60"
					x2="60"
					y2="105"
					stroke="var(--color-gold-dim)"
					strokeWidth="1.5"
					strokeLinecap="round"
					strokeDasharray="45"
					className="animate-[drawNeedle_1.5s_ease-out_0.7s_forwards]"
					opacity="0.5"
				/>
				<line
					x1="60"
					y1="60"
					x2="105"
					y2="60"
					stroke="var(--color-gold-dim)"
					strokeWidth="1.5"
					strokeLinecap="round"
					strokeDasharray="45"
					className="animate-[drawNeedle_1.5s_ease-out_0.9s_forwards]"
					opacity="0.5"
				/>
				<line
					x1="60"
					y1="60"
					x2="15"
					y2="60"
					stroke="var(--color-gold-dim)"
					strokeWidth="1.5"
					strokeLinecap="round"
					strokeDasharray="45"
					className="animate-[drawNeedle_1.5s_ease-out_1.1s_forwards]"
					opacity="0.5"
				/>
				<line
					x1="60"
					y1="60"
					x2="92"
					y2="28"
					stroke="var(--color-gold-dim)"
					strokeWidth="1"
					strokeLinecap="round"
					strokeDasharray="45"
					className="animate-[drawNeedle_1.5s_ease-out_1.3s_forwards]"
					opacity="0.3"
				/>
				<line
					x1="60"
					y1="60"
					x2="28"
					y2="92"
					stroke="var(--color-gold-dim)"
					strokeWidth="1"
					strokeLinecap="round"
					strokeDasharray="45"
					className="animate-[drawNeedle_1.5s_ease-out_1.5s_forwards]"
					opacity="0.3"
				/>
				<circle
					cx="60"
					cy="60"
					r="3"
					fill="var(--color-gold)"
					className="animate-[fadeIn_0.5s_ease-out_0.3s_both]"
				/>
			</svg>
		</div>
	);
}

/* ─── Decorative Diamond ──────────────────────────────────────────── */

function Diamond({ className = "" }: { className?: string }) {
	return (
		<svg width="8" height="8" viewBox="0 0 8 8" className={className}>
			<path d="M4 0L8 4L4 8L0 4Z" fill="currentColor" />
		</svg>
	);
}

/* ─── Page ────────────────────────────────────────────────────────── */

export default function HomePage() {
	const router = useRouter();
	const [prompt, setPrompt] = useState("");
	const typedText = useTypingAnimation(EXAMPLE_PROMPTS);

	const [inputFocused, setInputFocused] = useState(false);
	const showAnimatedPlaceholder = !inputFocused && prompt.length === 0;

	// AI generation state
	const [generating, setGenerating] = useState(false);
	const [genMessage, setGenMessage] = useState("");
	const [error, setError] = useState("");

	// True while generating or showing the success message before redirect
	const hideChrome = generating || !!genMessage;

	const handleGenerate = async () => {
		if (!prompt.trim()) {
			setError("Enter a game idea to generate");
			return;
		}
		setGenerating(true);
		setGenMessage("Starting generation…");
		setError("");
		try {
			const res = await generateGame(
				prompt.trim(),
				(evt: GenerateProgressEvent) => {
					setGenMessage(e
[truncated — 8742 more characters]
```

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