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.
This commit is contained in:
230
uno-online/BOT_ACTION_SPEC.md
Normal file
230
uno-online/BOT_ACTION_SPEC.md
Normal file
@@ -0,0 +1,230 @@
|
||||
# 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
|
||||
```json
|
||||
{
|
||||
"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:**
|
||||
```json
|
||||
// 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
|
||||
```json
|
||||
{
|
||||
"action": "draw"
|
||||
}
|
||||
```
|
||||
- `action`: Must be `"draw"`
|
||||
- No other parameters needed
|
||||
|
||||
### 3. Call UNO
|
||||
```json
|
||||
{
|
||||
"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)
|
||||
```json
|
||||
{
|
||||
"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:**
|
||||
```json
|
||||
{
|
||||
"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:**
|
||||
```json
|
||||
{
|
||||
"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
|
||||
|
||||
```python
|
||||
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:
|
||||
```javascript
|
||||
socket.emit('botAction', {
|
||||
action: 'play',
|
||||
card: '4R',
|
||||
color: null
|
||||
})
|
||||
```
|
||||
|
||||
### Receive game state updates:
|
||||
```javascript
|
||||
socket.on('updateGameState', (state) => {
|
||||
// Process new game state
|
||||
})
|
||||
```
|
||||
Reference in New Issue
Block a user