Skip to main content

Overview

The POST /game/{id}/play endpoint allows you to play moves in an active Blackjack game. You can either HIT (draw another card) or STAND (end your turn and let the dealer play).

Endpoint

POST /game/{id}/play

Path Parameters

id
string
required
The unique identifier of the game

Request Body

action
string
required
The move to play. Must be either HIT or STAND (case-insensitive)

Response

Returns a 200 OK status with the updated game state (same structure as GET /game/):
gameId
string
The game identifier
playerId
string
The player identifier
status
string
Updated game status after the move
playerScore
integer
Player’s current hand score
dealerScore
integer
Dealer’s score (partial if game still in progress, full if game ended)
playerCards
array
Array of card objects representing the player’s hand
dealerCards
array
Array of card objects representing the dealer’s hand (some cards may be hidden if game is in progress)

Move Actions

HIT

Draws one additional card from the deck and adds it to your hand. What happens:
  1. A card is drawn from the deck
  2. The card is added to your hand
  3. Your score is recalculated
  4. If your score exceeds 21, you bust and the game ends with status DEALER_WINS
  5. If you don’t bust, the game remains IN_PROGRESS and you can make another move
Example:
curl -X POST http://localhost:8080/game/550e8400-e29b-41d4-a716-446655440000/play \
  -H "Content-Type: application/json" \
  -d '{
    "action": "HIT"
  }'

STAND

Ends your turn and triggers the dealer’s play sequence. What happens:
  1. Your turn ends (no more cards drawn for you)
  2. The dealer automatically draws cards until reaching a score of 17 or higher
  3. The game outcome is determined:
    • If dealer busts (score > 21): status becomes PLAYER_WINS
    • If dealer score > player score: status becomes DEALER_WINS
    • If player score > dealer score: status becomes PLAYER_WINS
    • If scores are equal: status becomes PUSH (tie)
  4. Player ranking stats are updated (wins/losses)
Example:
curl -X POST http://localhost:8080/game/550e8400-e29b-41d4-a716-446655440000/play \
  -H "Content-Type: application/json" \
  -d '{
    "action": "STAND"
  }'

Game Flow Example

1. Initial State (after creating game)

{
  "gameId": "550e8400-e29b-41d4-a716-446655440000",
  "playerId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "status": "IN_PROGRESS",
  "playerScore": 15,
  "dealerScore": 10,
  "playerCards": [
    {"rank": "SEVEN", "suit": "HEARTS", "hidden": false},
    {"rank": "EIGHT", "suit": "DIAMONDS", "hidden": false}
  ],
  "dealerCards": [
    {"rank": "TEN", "suit": "CLUBS", "hidden": false},
    {"rank": null, "suit": null, "hidden": true}
  ]
}

2. After HIT (score still under 21)

{
  "gameId": "550e8400-e29b-41d4-a716-446655440000",
  "playerId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "status": "IN_PROGRESS",
  "playerScore": 19,
  "dealerScore": 10,
  "playerCards": [
    {"rank": "SEVEN", "suit": "HEARTS", "hidden": false},
    {"rank": "EIGHT", "suit": "DIAMONDS", "hidden": false},
    {"rank": "FOUR", "suit": "SPADES", "hidden": false}
  ],
  "dealerCards": [
    {"rank": "TEN", "suit": "CLUBS", "hidden": false},
    {"rank": null, "suit": null, "hidden": true}
  ]
}

3. After STAND (game ends)

{
  "gameId": "550e8400-e29b-41d4-a716-446655440000",
  "playerId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "status": "PLAYER_WINS",
  "playerScore": 19,
  "dealerScore": 17,
  "playerCards": [
    {"rank": "SEVEN", "suit": "HEARTS", "hidden": false},
    {"rank": "EIGHT", "suit": "DIAMONDS", "hidden": false},
    {"rank": "FOUR", "suit": "SPADES", "hidden": false}
  ],
  "dealerCards": [
    {"rank": "TEN", "suit": "CLUBS", "hidden": false},
    {"rank": "FIVE", "suit": "HEARTS", "hidden": false},
    {"rank": "TWO", "suit": "DIAMONDS", "hidden": false}
  ]
}

Game Status Transitions

Possible status values after playing a move:
  • IN_PROGRESS - Game continues, you can make another move
  • PLAYER_BUST - You exceeded 21 (transitions to DEALER_WINS)
  • DEALER_BUST - Dealer exceeded 21 (transitions to PLAYER_WINS)
  • PLAYER_WINS - You won (higher score or dealer bust)
  • DEALER_WINS - Dealer won (higher score or you bust)
  • PUSH - Tie (same final score)

Error Responses

400 Bad Request

Returned when the action is invalid:
{
  "timestamp": "2026-03-06T10:30:00Z",
  "status": 400,
  "error": "Bad Request",
  "message": "action must not be blank"
}

404 Not Found

Returned when the game doesn’t exist:
{
  "timestamp": "2026-03-06T10:30:00Z",
  "status": 404,
  "error": "Not Found",
  "message": "Game not found: 550e8400-e29b-41d4-a716-446655440000"
}

409 Conflict

Returned when trying to play a move on a finished game:
{
  "timestamp": "2026-03-06T10:30:00Z",
  "status": 409,
  "error": "Conflict",
  "message": "Game is not in progress"
}

Implementation Details

The move logic is handled by PlayMoveUseCase.java:24-32:
public Mono<GameStateResult> execute(PlayMoveCommand cmd) {
    MoveAction action = MoveAction.valueOf(cmd.action().toUpperCase());

    return gameRepo.findById(new GameId(cmd.gameId()))
            .switchIfEmpty(Mono.error(new GameNotFoundException(cmd.gameId())))
            .map(game -> applyMove(game, action))
            .flatMap(this::persistAndUpdateStats)
            .map(mapper::toResultForPlayer);
}
When the game ends, player stats are automatically updated (PlayMoveUseCase.java:41-54).

Next Steps

Build docs developers (and LLMs) love