UNO card game web app (Node.js/React) with Miku bot integration. Previously an independent git repo (fork of mizanxali/uno-online). Removed .git/ and absorbed into main repo for unified tracking. Includes bot integration code: botActionExecutor, cardParser, gameStateBuilder, and server-side bot action support. 37 files, node_modules excluded via local .gitignore.
13 KiB
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
- Game State JSON Structure
- Card Format Codes
- API Endpoints
- Integration Flow
- Example Bot Decision Logic
Game State JSON Structure
The game exports a comprehensive state object with the following structure:
{
"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 endedwinner: Player name who won, or null if game is ongoingcurrentTurn: "Player 1" or "Player 2" - whose turn it isturnNumber: Approximate turn count (based on played cards)
currentCard object
- Complete information about the card currently on top of the pile
currentColorandcurrentNumberare the active game rules
player2.cards array
- All cards in the bot's hand with full details
- Each card has
isPlayableflag 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 turnreverse: Reverse play direction (in 2-player, acts as skip)draw2: Next player draws 2 cardswild: Change colordraw4_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:
curl http://localhost:5000/api/game/ABC123/state
Response:
{
"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):
{
"action": "play_card",
"cardCode": "5R",
"chosenColor": null
}
Request Body (Play Wild Card):
{
"action": "play_card",
"cardCode": "W",
"chosenColor": "R"
}
Request Body (Draw Card):
{
"action": "draw_card"
}
Example:
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 statebotAction: Bot submits an actionrequestGameState: Bot requests current state
Server → Client Events
botActionReceived: Action received from HTTP API, forwarded to gamegameStateRequested: Server requests updated state from game
Integration Flow
For Socket.IO Integration
-
Connect as Player 2:
const socket = io.connect('http://localhost:5000') socket.emit('join', {room: 'ABC123'}, (error) => { if (error) console.error(error) }) -
Listen for game state updates:
socket.on('updateGameState', (gameState) => { // Process game state and make decision console.log('Turn:', gameState.turn) }) -
Request game state:
socket.emit('requestGameState', (response) => { console.log(response) }) -
Submit action:
socket.emit('botAction', { action: 'play_card', cardCode: '5R' }, (response) => { console.log(response) })
For HTTP API Integration (Recommended for Miku Bot)
-
Get Room Code: User shares the game room code
-
Poll Game State:
import requests room_code = "ABC123" response = requests.get(f"http://localhost:5000/api/game/{room_code}/state") game_state = response.json()['gameState'] -
Check if it's bot's turn:
if game_state['game']['currentTurn'] == 'Player 2': # Bot's turn - make decision -
Analyze playable cards:
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'} -
Submit action:
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:
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:
# 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
cd /home/koko210Serve/docker/uno-online
npm install
npm start
2. Start the Client (in another terminal)
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
# 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 STATElogs - Copy JSON for testing with bot
Next Steps for Miku Integration
- Create UNO command in Miku bot (
/uno join [room_code]) - Implement polling loop to check when it's Miku's turn
- Pass game state to LLM with structured prompt
- Parse LLM response to extract action decision
- Submit action via API
- Add chat integration to announce moves in Discord
- 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.