Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Antonelli-Tech-Solutions/spades/llms.txt

Use this file to discover all available pages before exploring further.

During the playing phase, each player takes turns playing one card per trick in clockwise seat order. The server validates every card play server-side via the anti-cheat validator in server/anticheat/validate.js before mutating any game state — there is no way for a client to submit an illegal move and have it accepted. After each human card play, the server automatically resolves all consecutive bot turns, so the response you receive already reflects the state after bots have acted.

Card format

Cards are represented as JSON objects with suit and rank fields. Suits are full lowercase strings: "spades", "hearts", "diamonds", "clubs". Ranks are "2""9", "10", "J", "Q", "K", "A". Note that 10 is the two-character string "10", not a single-character code.Examples:
{ "suit": "spades",   "rank": "A"  }
{ "suit": "hearts",   "rank": "K"  }
{ "suit": "diamonds", "rank": "10" }
{ "suit": "clubs",    "rank": "2"  }

POST /api/tables/:tableId/play

Submit a card play for the requesting player’s turn. Required headers: x-session-id, x-player-id

Body

card
object
required
The card to play as a { suit, rank } object. The card must be in the player’s current hand and must be a legal play under the current game rules (see Legal play rules below).

Server-side validation

Before any state mutation the server checks (in order):
  1. The game must be in the playing phase.
  2. It must be the requesting player’s turn (currentPlayerSeat).
  3. The card must exist in the player’s hand.
  4. The card must be a legal play under current game rules (follow-suit obligation, Spades-breaking rules, and first-trick restrictions are all enforced).
A 409 is returned if checks 1 or 2 fail; a 400 is returned if checks 3 or 4 fail.

Bot turns

After the player’s card is accepted, the server resolves all consecutive bot turns before returning the response. The response body therefore reflects the game state after all bot plays have been applied — you do not need to poll while bots act.

Example

curl -X POST http://localhost:3000/api/tables/<tableId>/play \
  -H 'Content-Type: application/json' \
  -H 'x-session-id: <sessionId>' \
  -H 'x-player-id: <playerId>' \
  -d '{"card": {"suit": "spades", "rank": "A"}}'

Response codes

StatusMeaning
200Card accepted. Body: updated player view (see Game State). All bot turns auto-advanced before response.
400Card is not in the player’s hand, or the play is illegal (must follow suit, Spades not yet broken, first-trick Spade restriction).
401Missing or invalid session.
403Player is not seated at this table.
404Table not found.
409It is not this player’s turn to play, or the game is not in the playing phase.

The following rules govern which cards may be played on any given turn. These are enforced identically by the getLegalPlays function (used to compute validCards in the state response) and by isCardLegal (called during validateCardPlay before every state mutation).Follow-suit obligation If the player’s hand contains any cards matching the suit led in the current trick, the player must play one of those cards. They may not play a card of a different suit when they can follow suit.Spades cannot be led until broken A player may not lead a Spade to open a trick until Spades have been broken (i.e. a Spade has been played while unable to follow suit on a previous, non-first trick). Exception: if the player’s entire hand consists only of Spades, they may lead a Spade regardless of whether Spades are broken.First-trick Spade restriction On the very first trick of a hand, Spades cannot be led. Additionally, if a player cannot follow the led suit on the first trick, they may not play a Spade as long as they hold at least one non-Spade card. If their entire hand is Spades, they may play a Spade. Playing a Spade on the first trick (only possible when a player holds only Spades) does not break Spades.validCards shows all legal options The validCards array in the Game State response lists every card the current player is permitted to play. Clients may use this to highlight legal cards in the UI. The server re-validates independently — submitting a card not in validCards returns 400.

Anti-cheat

server/anticheat/validate.js exposes two validators called before any state mutation: validateBidTurn (for bids) and validateCardPlay (for card plays).validateCardPlay performs four checks in order:
  1. Game phase must be "playing".
  2. The requesting seat must match currentPlayerSeat (turn order enforcement).
  3. The submitted card must exist in the player’s hand (CARD_NOT_IN_HAND).
  4. The card must be legal under isCardLegal — which applies follow-suit, Spades-breaking, and first-trick restrictions (ILLEGAL_PLAY).
validateBidTurn checks that the game is in "bidding" phase and that the requesting seat is currentBidderSeat.Any violation throws a structured error with a machine-readable code (INVALID_ACTION, NOT_YOUR_TURN, CARD_NOT_IN_HAND, or ILLEGAL_PLAY) which the route handler maps to a 409 or 400 response. State is never mutated on a failed validation.

Trick resolution

After the 4th card is played to a trick the server resolves the winner immediately:
  • Highest Spade wins if any Spades were played in the trick.
  • Highest card of the led suit wins if no Spades were played.
  • Cards of other non-led suits never win regardless of rank.
The winning seat’s tricksWon count is incremented, the trick is appended to completedTricks, and the winner becomes currentPlayerSeat (they lead the next trick). If a Spade was played on any non-first trick and Spades were not yet broken, spadesbroken is set to true. A Spade played on the first trick (only possible when the player holds nothing but Spades) does not break Spades.

End of hand

After all 13 tricks have been played the server automatically runs end-of-hand scoring with no additional client action required:
  1. scoreHand — computes scoreDelta and newBags for each team based on bids, actual tricks won, and Nil/Blind Nil outcomes.
  2. applyBagPenalties — accumulates bags; every 10 accumulated bags deducts 100 points from that team’s score and resets the bag count for that group of 10.
  3. checkWinLoss — evaluates win and loss conditions:
    • A team reaching 250 or more points wins; if both teams cross 250 in the same hand the higher score wins (exact tie plays another hand).
    • A team falling to −250 or below loses immediately by the same tie-break rules.
If a win/loss condition is met the game moves to phase: "game_over" with gameOver: true and winner set to the winning team ("ns" or "ew"). Otherwise the dealer rotates clockwise, a new hand is dealt, and the game returns to phase: "bidding" automatically — no client action is needed to start the next hand.

Build docs developers (and LLMs) love