Spades Tutorial Part 2¶
Playing a card¶
When a player tries to play a card from his hand (by clicking), the frontend will send a message like play_card player_1 td
to the backend indicating that player_1
is attempting to play the card named td
. (player_1
is there for historical reasons and not actually needed – use PLAYER_NAME
to find out who is sending the message instead.)
When this happens, the backend will look for and attempt to execute a RECEIVER
named play_card
with two arguments. Let’s go ahead and define that receiver now.
We need to accomplish a few things:
- for the player sending the message, check that it is actually their turn. If not, we should
REJECT
the message as an illegal action. - check that the card specified in the message actually exists in the given player’s hand. (We need to validate the input here because unscrupulous users could hijack the frontend and send any kind of message they want.)
- check that the given card follows suit.
- if it does follow suit, we need to go ahead and actually play it.
Whose turn is it¶
1 2 3 4 5 | RECEIVE play_card $dummy $card_name:
IF NOT EQUAL $PLAYER_NAME GET_POSITION_ELT GET_PROP 'play_order' 0:
REJECT CONCAT "not player turn: " $PLAYER_NAME;
END_IF
END_RECEIVE
|
- As we said, the first argument is not needed, so we have called it
$dummy
. - Instead we are checking whether
PLAYER_NAME
is equal to the first element ofplay_order
, which we’re using to keep track of play order. - If the check fails, we immediately
REJECT
with a helpful message to let the frontend know what went wrong.
Check the card exists in player hand¶
1 2 3 4 5 6 7 8 9 | RECEIVE play_card $dummy $card_name:
IF NOT EQUAL $PLAYER_NAME GET_POSITION_ELT GET_PROP 'play_order' 0:
REJECT CONCAT "not player turn: " $PLAYER_NAME;
END_IF
ASSIGN 'player_hand' GET_CONTAINER CONCAT $PLAYER_NAME "_hand";
IF NOT CONTAINS_BY_NAME $player_hand $card_name:
REJECT CONCAT "card not in player hand: " $card_name;
END_IF
END_RECEIVE
|
Check the card follows suit¶
This one is a bit trickier. A card is said to follow suit if it is the same suit as what was led. In Spades, you must follow suit unless you are leading or unable to follow suit. So there are a few possible ways for a play to be valid:
- It’s the leading card (no cards have been played yet this trick).
- It is the same suit as what was led.
- The player has no cards of the same suit as the leading suit.
We are using the PROP suit_led
to keep track of what suit was led. If nothing has been played yet, this will be NULL
.
Let’s create a few helper functions to make this logic easier:
DEF get_suit $card:
RETURN TOKEN_GET_PROP $card 'suit';
END_DEF
DEF follows_suit $card:
RETURN EQUAL get_suit $card GET_PROP 'suit_led';
END_DEF
We are now ready to check the follows suit logic:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | RECEIVE play_card $dummy $card_name:
IF NOT EQUAL $PLAYER_NAME GET_POSITION_ELT GET_PROP 'play_order' 0:
REJECT CONCAT "not player turn: " $PLAYER_NAME;
END_IF
ASSIGN 'player_hand' GET_CONTAINER CONCAT $PLAYER_NAME "_hand";
IF NOT CONTAINS_BY_NAME $player_hand $card_name:
REJECT CONCAT "card not in player hand: " $card_name;
END_IF
# get the number of cards in player hand of the led suit
ASSIGN 'ct' 0;
ASSIGN 'i' PLUS SIZE $player_hand -1;
WHILE GREATER_OR_EQUAL $i 0:
IF follows_suit GET_BY_POSITION $player_hand $i:
ASSIGN 'ct' PLUS $ct 1;
END_IF
ASSIGN 'i' PLUS $i -1;
END_WHILE
IF NOT
OR
OR
IS_NULL GET_PROP 'suit_led'
follows_suit GET_TOKEN $card_name
EQUAL $ct 0:
REJECT CONCAT "must follow suit: " $card_name;
END_IF
END_RECEIVE
|
Here we used a loop to iterate over the player’s hand and count how many of their cards are of the led suit. That total is saved to the variable $ct
. Finally we combine all the logic together to decide whether to reject the received message.
Updating game state to reflect card was played¶
If we’ve made it this far, that means that the card was a legal play, so we should go ahead and make the play.
To play a card, we must remove it from the player’s hand and place it into a special container for the current play. We don’t discard the card right away as it is helpful for the other players to see what has currently been played.
If this is all ok, we finish by ACCEPT
ing the message.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | RECEIVE play_card $dummy $card_name:
IF NOT EQUAL $PLAYER_NAME GET_POSITION_ELT GET_PROP 'play_order' 0:
REJECT CONCAT "not player turn: " $PLAYER_NAME;
END_IF
ASSIGN 'player_hand' GET_CONTAINER CONCAT $PLAYER_NAME "_hand";
IF NOT CONTAINS_BY_NAME $player_hand $card_name:
REJECT CONCAT "card not in player hand: " $card_name;
END_IF
# get the number of cards in player hand of the led suit
ASSIGN 'ct' 0;
ASSIGN 'i' PLUS SIZE $player_hand -1;
WHILE GREATER_OR_EQUAL $i 0:
IF follows_suit GET_BY_POSITION $player_hand $i:
ASSIGN 'ct' PLUS $ct 1;
END_IF
ASSIGN 'i' PLUS $i -1;
END_WHILE
IF NOT
OR
OR
IS_NULL GET_PROP 'suit_led'
follows_suit GET_TOKEN $card_name
EQUAL $ct 0:
REJECT CONCAT "must follow suit: " $card_name;
END_IF
# ok to play card
ASSIGN 'player_play' GET_CONTAINER CONCAT $PLAYER_NAME '_play';
ASSIGN 'card_token' POP_BY_NAME $player_hand $card_name;
IF IS_NULL $card_token:
REJECT "Something went wrong";
END_IF
APPEND $player_play $card_token;
ACCEPT;
END_RECEIVE
|
Now, we are able to play cards from our hand and follow the rules of following suit.