Files
miku-discord/uno-online/BOT_INTEGRATION_GUIDE.md

571 lines
13 KiB
Markdown
Raw Permalink Normal View History

# 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.