# UNO Game - Bot Integration Guide

## Overview

This UNO online game has been enhanced with a comprehensive bot integration system that exports the game state in JSON format at every turn. This allows external AI systems (like Miku Discord Bot) to play as Player 2.

## Table of Contents

1. [Game State JSON Structure](#game-state-json-structure)
2. [Card Format Codes](#card-format-codes)
3. [API Endpoints](#api-endpoints)
4. [Integration Flow](#integration-flow)
5. [Example Bot Decision Logic](#example-bot-decision-logic)

---

## Game State JSON Structure

The game exports a comprehensive state object with the following structure:

```json
{
  "game": {
    "isOver": false,
    "winner": null,
    "currentTurn": "Player 2",
    "turnNumber": 15
  },
  "currentCard": {
    "code": "5R",
    "type": "number",
    "value": 5,
    "color": "R",
    "colorName": "red",
    "displayName": "5 red",
    "currentColor": "R",
    "currentNumber": 5
  },
  "recentlyPlayed": [
    {
      "code": "5R",
      "type": "number",
      "value": 5,
      "color": "R",
      "colorName": "red",
      "displayName": "5 red",
      "position": 1
    }
  ],
  "player1": {
    "name": "Player 1",
    "cardCount": 7,
    "isCurrentTurn": false,
    "cards": []
  },
  "player2": {
    "name": "Player 2",
    "cardCount": 5,
    "isCurrentTurn": true,
    "cards": [
      {
        "code": "3R",
        "type": "number",
        "value": 3,
        "color": "R",
        "colorName": "red",
        "displayName": "3 red",
        "isPlayable": true
      },
      {
        "code": "7B",
        "type": "number",
        "value": 7,
        "color": "B",
        "colorName": "blue",
        "displayName": "7 blue",
        "isPlayable": false
      }
    ],
    "playableCards": [
      {
        "code": "3R",
        "type": "number",
        "value": 3,
        "color": "R",
        "colorName": "red",
        "displayName": "3 red",
        "isPlayable": true
      }
    ]
  },
  "deck": {
    "drawPileCount": 78,
    "playedPileCount": 15
  },
  "botContext": {
    "canPlay": true,
    "mustDraw": false,
    "hasUno": false,
    "isWinning": false,
    "actions": [
      {
        "action": "play_card",
        "card": {
          "code": "3R",
          "type": "number",
          "value": 3,
          "color": "R",
          "displayName": "3 red"
        },
        "requiresColorChoice": false
      }
    ]
  }
}
```

### Key Fields Explanation

#### `game` object
- `isOver`: Boolean indicating if the game has ended
- `winner`: Player name who won, or null if game is ongoing
- `currentTurn`: "Player 1" or "Player 2" - whose turn it is
- `turnNumber`: Approximate turn count (based on played cards)

#### `currentCard` object
- Complete information about the card currently on top of the pile
- `currentColor` and `currentNumber` are the active game rules

#### `player2.cards` array
- All cards in the bot's hand with full details
- Each card has `isPlayable` flag based on current game rules

#### `player2.playableCards` array
- Filtered list of only cards that can be played right now

#### `botContext` object
- `canPlay`: Can the bot play a card this turn?
- `mustDraw`: Must the bot draw a card?
- `hasUno`: Should the bot press the UNO button? (2 cards remaining)
- `isWinning`: Does the bot have only 1 card left?
- `actions`: Array of valid actions the bot can take

---

## Card Format Codes

Cards are represented as 2-4 character codes:

### Number Cards (0-9)
- Format: `[number][color]`
- Examples: `0R`, `5G`, `9B`, `3Y`
- Colors: R (Red), G (Green), B (Blue), Y (Yellow)

### Special Cards

#### Skip
- Format: `skip[color]`
- Examples: `skipR`, `skipG`, `skipB`, `skipY`
- Value code: 404

#### Reverse
- Format: `_[color]`
- Examples: `_R`, `_G`, `_B`, `_Y`
- Value code: 0

#### Draw 2
- Format: `D2[color]`
- Examples: `D2R`, `D2G`, `D2B`, `D2Y`
- Value code: 252

#### Wild
- Format: `W`
- Value code: 300
- No color until played

#### Draw 4 Wild
- Format: `D4W`
- Value code: 600
- No color until played

### Card Types

Each parsed card has a `type` field:
- `number`: Regular number cards (0-9)
- `skip`: Skip next player's turn
- `reverse`: Reverse play direction (in 2-player, acts as skip)
- `draw2`: Next player draws 2 cards
- `wild`: Change color
- `draw4_wild`: Change color and next player draws 4

---

## API Endpoints

### 1. Get Game State (HTTP)

**Endpoint:** `GET /api/game/:roomCode/state`

**Description:** Retrieve the current game state for a specific room.

**Example:**
```bash
curl http://localhost:5000/api/game/ABC123/state
```

**Response:**
```json
{
  "success": true,
  "gameState": { /* full game state object */ },
  "timestamp": "2026-01-25T10:30:00.000Z"
}
```

### 2. Submit Bot Action (HTTP)

**Endpoint:** `POST /api/game/:roomCode/action`

**Description:** Submit a bot's action (play card or draw card).

**Request Body (Play Card):**
```json
{
  "action": "play_card",
  "cardCode": "5R",
  "chosenColor": null
}
```

**Request Body (Play Wild Card):**
```json
{
  "action": "play_card",
  "cardCode": "W",
  "chosenColor": "R"
}
```

**Request Body (Draw Card):**
```json
{
  "action": "draw_card"
}
```

**Example:**
```bash
curl -X POST http://localhost:5000/api/game/ABC123/action \
  -H "Content-Type: application/json" \
  -d '{"action":"play_card","cardCode":"5R","chosenColor":null}'
```

### 3. Socket.IO Events

The game also supports real-time Socket.IO events:

#### Client → Server Events
- `botGameState`: Emitted by Player 2, contains full game state
- `botAction`: Bot submits an action
- `requestGameState`: Bot requests current state

#### Server → Client Events
- `botActionReceived`: Action received from HTTP API, forwarded to game
- `gameStateRequested`: Server requests updated state from game

---

## Integration Flow

### For Socket.IO Integration

1. **Connect as Player 2:**
   ```javascript
   const socket = io.connect('http://localhost:5000')
   socket.emit('join', {room: 'ABC123'}, (error) => {
     if (error) console.error(error)
   })
   ```

2. **Listen for game state updates:**
   ```javascript
   socket.on('updateGameState', (gameState) => {
     // Process game state and make decision
     console.log('Turn:', gameState.turn)
   })
   ```

3. **Request game state:**
   ```javascript
   socket.emit('requestGameState', (response) => {
     console.log(response)
   })
   ```

4. **Submit action:**
   ```javascript
   socket.emit('botAction', {
     action: 'play_card',
     cardCode: '5R'
   }, (response) => {
     console.log(response)
   })
   ```

### For HTTP API Integration (Recommended for Miku Bot)

1. **Get Room Code:** User shares the game room code

2. **Poll Game State:**
   ```python
   import requests
   
   room_code = "ABC123"
   response = requests.get(f"http://localhost:5000/api/game/{room_code}/state")
   game_state = response.json()['gameState']
   ```

3. **Check if it's bot's turn:**
   ```python
   if game_state['game']['currentTurn'] == 'Player 2':
       # Bot's turn - make decision
   ```

4. **Analyze playable cards:**
   ```python
   playable = game_state['player2']['playableCards']
   if len(playable) > 0:
       # Choose a card to play
       chosen_card = playable[0]['code']
   else:
       # Must draw
       action = {'action': 'draw_card'}
   ```

5. **Submit action:**
   ```python
   action = {
       'action': 'play_card',
       'cardCode': chosen_card,
       'chosenColor': 'R' if 'wild' in playable[0]['type'] else None
   }
   
   response = requests.post(
       f"http://localhost:5000/api/game/{room_code}/action",
       json=action
   )
   ```

---

## Example Bot Decision Logic

Here's a simple decision-making algorithm for a bot:

```python
def make_uno_decision(game_state):
    """
    Simple bot logic for playing UNO
    """
    bot_context = game_state['botContext']
    
    # Not our turn
    if not game_state['player2']['isCurrentTurn']:
        return None
    
    # Check if we must draw
    if bot_context['mustDraw']:
        return {
            'action': 'draw_card'
        }
    
    # Get playable cards
    playable = game_state['player2']['playableCards']
    
    if len(playable) == 0:
        return {
            'action': 'draw_card'
        }
    
    # Strategy: prioritize special cards, then high numbers
    chosen_card = None
    
    # 1. Try to play Draw 4 or Draw 2
    for card in playable:
        if card['type'] in ['draw4_wild', 'draw2']:
            chosen_card = card
            break
    
    # 2. Try to play Skip
    if not chosen_card:
        for card in playable:
            if card['type'] == 'skip':
                chosen_card = card
                break
    
    # 3. Play highest number card
    if not chosen_card:
        number_cards = [c for c in playable if c['type'] == 'number']
        if number_cards:
            chosen_card = max(number_cards, key=lambda x: x['value'])
    
    # 4. Play wild card as last resort
    if not chosen_card:
        chosen_card = playable[0]
    
    # Determine color for wild cards
    chosen_color = None
    if chosen_card['type'] in ['wild', 'draw4_wild']:
        # Count colors in hand
        colors = {}
        for card in game_state['player2']['cards']:
            if card.get('color'):
                colors[card['color']] = colors.get(card['color'], 0) + 1
        # Choose most common color
        chosen_color = max(colors, key=colors.get) if colors else 'R'
    
    return {
        'action': 'play_card',
        'cardCode': chosen_card['code'],
        'chosenColor': chosen_color
    }
```

---

## Miku Bot Integration Example

For integrating with the Miku Discord bot:

```python
# In Miku bot's commands folder, create uno_player.py

import requests
import time

class MikuUnoPlayer:
    def __init__(self, game_server_url, room_code):
        self.base_url = game_server_url
        self.room_code = room_code
    
    def get_game_state(self):
        """Fetch current game state"""
        url = f"{self.base_url}/api/game/{self.room_code}/state"
        response = requests.get(url)
        return response.json()['gameState']
    
    def play_turn(self):
        """Play one turn as Miku"""
        state = self.get_game_state()
        
        # Check if it's our turn
        if state['game']['currentTurn'] != 'Player 2':
            return "Not my turn yet!"
        
        # Use LLM to decide (pass game state to Miku's LLM)
        decision = self.make_llm_decision(state)
        
        # Submit action
        url = f"{self.base_url}/api/game/{self.room_code}/action"
        response = requests.post(url, json=decision)
        
        return response.json()
    
    def make_llm_decision(self, game_state):
        """
        Pass game state to Miku's LLM for decision-making
        """
        # Format prompt for LLM
        prompt = f"""
You are playing UNO. Here's the current game state:

Current card: {game_state['currentCard']['displayName']}
Your cards ({len(game_state['player2']['cards'])}):
{', '.join([c['displayName'] for c in game_state['player2']['cards']])}

Playable cards:
{', '.join([c['displayName'] for c in game_state['player2']['playableCards']])}

What should you do? Respond with JSON:
{{"action": "play_card", "cardCode": "5R"}}
or
{{"action": "draw_card"}}
"""
        
        # Query Miku's LLM
        from utils.llm import query_llama
        response = query_llama(prompt, model="llama3.1")
        
        # Parse LLM response to extract decision
        # ... parse JSON from response ...
        
        return decision
```

---

## Testing the Integration

### 1. Start the Game Server
```bash
cd /home/koko210Serve/docker/uno-online
npm install
npm start
```

### 2. Start the Client (in another terminal)
```bash
cd /home/koko210Serve/docker/uno-online/client
npm install
npm start
```

### 3. Create a Game
- Open browser to `http://localhost:3000`
- Click "CREATE GAME"
- Note the room code (e.g., "ABC123")

### 4. Test API Endpoints
```bash
# Get game state
curl http://localhost:5000/api/game/ABC123/state

# Submit action (after joining as Player 2)
curl -X POST http://localhost:5000/api/game/ABC123/action \
  -H "Content-Type: application/json" \
  -d '{"action":"draw_card"}'
```

### 5. Monitor Console Output
- Open browser console (F12)
- Look for `🎮 UNO GAME STATE` logs
- Copy JSON for testing with bot

---

## Next Steps for Miku Integration

1. **Create UNO command in Miku bot** (`/uno join [room_code]`)
2. **Implement polling loop** to check when it's Miku's turn
3. **Pass game state to LLM** with structured prompt
4. **Parse LLM response** to extract action decision
5. **Submit action via API**
6. **Add chat integration** to announce moves in Discord
7. **Handle edge cases** (disconnects, game over, etc.)

---

## Troubleshooting

### Game State Not Appearing
- Ensure you've joined as Player 2
- Check browser console for logs
- Verify Socket.IO connection

### API Returns 404
- Check that the room code is correct
- Ensure the game has started (both players joined)
- Verify game state has been emitted at least once

### Actions Not Working
- Ensure the action JSON format is correct
- Check that it's Player 2's turn
- Verify the card code exists in the bot's hand

---

## License

This integration system is part of the UNO Online project. See main README for license information.
