Files
miku-discord/uno-online/BOT_ACTION_SPEC.md
koko210Serve 34b184a05a add: absorb uno-online as regular subdirectory
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.
2026-03-04 00:21:38 +02:00

5.3 KiB

Bot Action Specification for UNO Game

Overview

The bot (Miku) can interact with the UNO game by sending JSON action commands. The game state is provided in a structured JSON format, and the bot responds with action commands.

Action Types

1. Play a Card

{
  "action": "play",
  "card": "4R",
  "color": null
}
  • action: Must be "play"
  • card: The card code to play (e.g., "4R", "D2G", "skipB", "W", "D4W")
  • color: Optional. Only required for wild cards ("W" or "D4W"). Values: "R", "G", "B", or "Y"

Examples:

// Play a number card
{"action": "play", "card": "4R", "color": null}

// Play a Draw 2 card
{"action": "play", "card": "D2G", "color": null}

// Play a Skip card
{"action": "play", "card": "skipB", "color": null}

// Play a Wild card (must specify color)
{"action": "play", "card": "W", "color": "R"}

// Play a Wild Draw 4 (must specify color)
{"action": "play", "card": "D4W", "color": "B"}

2. Draw a Card

{
  "action": "draw"
}
  • action: Must be "draw"
  • No other parameters needed

3. Call UNO

{
  "action": "uno"
}
  • action: Must be "uno"
  • Call this when you have exactly 2 cards in hand and are about to play one
  • Important: Call UNO before playing your second-to-last card, or call it as a separate action

4. Combined Actions (Play + UNO)

{
  "action": "play",
  "card": "4R",
  "color": null,
  "callUno": true
}
  • Add "callUno": true when playing a card that will leave you with 1 card

Card Code Reference

Number Cards

  • Format: {number}{color}
  • Numbers: 0-9
  • Colors: R (Red), G (Green), B (Blue), Y (Yellow)
  • Examples: 0R, 5G, 9B, 3Y

Action Cards

  • Skip: skipR, skipG, skipB, skipY
  • Reverse: _R, _G, _B, _Y
  • Draw 2: D2R, D2G, D2B, D2Y
  • Wild: W
  • Wild Draw 4: D4W

Color Codes

  • R - Red
  • G - Green
  • B - Blue
  • Y - Yellow

Strategy Tips for Bot

When to Play Cards

  1. Check botContext.canPlay - if false, you must draw
  2. Look at player2.playableCards for available moves
  3. Match by color or number with currentCard

Priority Strategy

  1. Action cards first (Skip, Draw 2, Wild Draw 4) to disrupt opponent
  2. Match color if opponent has few cards (to block them)
  3. Match number to change color advantageously
  4. Save wild cards for critical moments

When to Call UNO

  • When you have exactly 2 cards and are about to play one
  • Must call UNO or you'll get 2 penalty cards!

When to Use Wild Cards

  • When you have no other playable cards
  • To change to a color you have many of
  • Wild Draw 4: When opponent has 1-2 cards (aggressive)

HTTP API Endpoint

Get Game State

GET http://localhost:5000/api/game/\{roomCode\}/state

Response:

{
  "success": true,
  "gameState": { /* full game state */ },
  "timestamp": "2026-01-27T..."
}

Submit Bot Action

POST http://localhost:5000/api/game/\{roomCode\}/action
Content-Type: application/json

{
  "action": "play",
  "card": "4R",
  "color": null
}

Response:

{
  "success": true,
  "message": "Action received and forwarded to game"
}

Error Handling

Invalid Actions

The game will ignore invalid actions:

  • Playing a card not in your hand
  • Playing an unplayable card
  • Not specifying color for wild cards
  • Playing out of turn

Validation

Before sending an action:

  1. Verify it's Player 2's turn (game.currentTurn === "Player 2")
  2. Check card is in player2.cards array
  3. Verify card is in player2.playableCards array
  4. For wild cards, always specify a valid color

Example Bot Decision Flow

def make_move(game_state):
    # Check if it's our turn
    if game_state['game']['currentTurn'] != 'Player 2':
        return None
    
    # Check if we can play
    if not game_state['botContext']['canPlay']:
        return {"action": "draw"}
    
    # Get playable cards
    playable = game_state['player2']['playableCards']
    
    if not playable:
        return {"action": "draw"}
    
    # Check if we need to call UNO
    our_cards = game_state['player2']['cardCount']
    call_uno = (our_cards == 2)
    
    # Priority: Draw 2 or Draw 4
    for card in playable:
        if card['type'] in ['draw2', 'draw4']:
            action = {"action": "play", "card": card['code']}
            if card['type'] == 'draw4':
                action['color'] = choose_best_color(game_state)
            if call_uno:
                action['callUno'] = True
            return action
    
    # Priority: Skip or Reverse
    for card in playable:
        if card['type'] in ['skip', 'reverse']:
            if call_uno:
                return {"action": "play", "card": card['code'], "callUno": True}
            return {"action": "play", "card": card['code']}
    
    # Play any available card
    card = playable[0]
    action = {"action": "play", "card": card['code']}
    if card['type'] == 'wild':
        action['color'] = choose_best_color(game_state)
    if call_uno:
        action['callUno'] = True
    return action

WebSocket Events (Alternative to HTTP)

If the bot maintains a WebSocket connection:

Emit bot action:

socket.emit('botAction', {
  action: 'play',
  card: '4R',
  color: null
})

Receive game state updates:

socket.on('updateGameState', (state) => {
  // Process new game state
})