This repository contains code for an internet game that is an adaptation of the card game I know as "Kaboom."
Table of contents:
- [Screenshots]
- Rules
- [Design]
- Instructions
Coming soon...
To read, start with Phase 0: pre-game. Then continue by clicking the hyperlinks in each subsection.
Each player is dealt 6 cards from a shuffled deck which they arrange into 2 rows and 3 columns face-down in front of them. Before the game begins, players may peek at their bottom row of cards.
Once every player is ready, the top most card of the draw pile is discarded face up and the first player proceeds with Phase 1: first move.
Or: at any point, a player may flip.
The player whose turn it is may choose one of three actions:
- declare "Kaboom", if no other player has already done so
- take the top discarded card
- draw a card
In declaring Kaboom, the player immediately ends their turn. Each other player is then allowed one turn in the normal order. Once play returns back to this player, the game is over and scores are tallied.
"Taking" is when the player chooses to take the most recently-discarded card instead of drawing a new card. The player then chooses a card of their own to replace, discarding the old card. "Keeping" is the same thing, except the player is "keeping" the card they drew.
After this, it is the next player's turn
The player draws a new card, revealing it only to themselves and may choose one of four actions:
- draw again, if the drawn card is the same rank as the top discarded card
- use the card's ability, if the card is a 7, 8, 9, 10, Jack, Queen, or black King
- discard, advancing to the next player's turn
- keep the card
Certain cards have abilities:
- 7 or 8: choose one of your own cards to peek at
- 9 or 10: choose one of any opponent's cards to peek at
- Jack or Queen: swap any two cards as long as they belong to different players
- black King: look at any two cards
After a player uses the ability (if they chose to do so), they may either discard and advance to the next player's turn, or keep the card.
Every player reveals all of their remaining cards, including penalty cards. Face cards are worth 10 points, Jokers are worth 0 points, and red Kings are worth -1 points.
If the player who declared Kaboom has the lowest score, they recieve 0 points for the round. Otherwise, they recieve an additional 10 points.
Flipping is what makes Kaboom interesting. A flip occurs when a player, at any point, takes a face-down player card and discards it face-up onto the discard pile. A flip is only valid if all of the following are true:
- The card they are flipping onto is not a card that a different player flipped
- It is not their turn, or if it is their turn, they have not progressed past the first move.
- The card they flip over has the same rank as the card on top
If any of these are false, the player must return the card they flipped back to its original position, face-down.
Otherwise, the card remains discarded. If the player flipped over a card that wasn't their own, they may give one of their own cards in place of the card they flipped.
After this is over, play returns to wherever it was prior to the flip.
Design of this game in software is made complicated by the fact that Kaboom is largely a sequential game except that any player may attempt to flip at any time. My server implementation accounts for this by using asychronous code.
The server has a "message-listener map": a dictionary mapping valid messages it could recieve from its clients to asychronous coroutines to call if that particular valid message is recieved. For example, during a player's first move, the map contains keys ""kaboom", "take", "draw", plus one "flip ..." for every card that is available to be flipped.
When a listener coroutine is called, the game logic takes control of Python's event loop, meaning that any client messages recieved during this time will not be read right away: this in lies the core complication. However, each listener coroutine is fast and immediately cedes control back to the event loop upon finishing so that messages can be processed again. This is accomplished by having every listener coroutine rewrite the message-listener map.
In other words, instead of waiting for the player to answer with which card to replace after they choose to "take" the top card, the server simplify changes which messages it is willing to accept by rewriting its message-listener map, notifies the client of these options, and then goes back to waiting for incoming messages. This is how my implementation satisfies both the sequential and asychronous nature of Kaboom.
The client consists of two asychronous routines: one that listens for messages from the server, and one that draws the game using pygame. The server mainly sends two types of messages: multiple_choice and spots messages. If it's a multiple_choice message, the server is asking the client to choose a move from a list of moves, such as in the first move.
If it's a spots message, the server is asking the client to pick a card, such as when using a 7 or 8. Given that most events in the game use one of these two message types, the client is able to handle most of the game by simply writing text to list options in a multiple_choice message, or highlight cards to select in a spots message.
As for the graphics side of things, I wanted to make use of the fact that each player's arrangement of cards should be about the same: a 2x3 grid of cards, rotated at 2-4 different places around the table. However, since pygame uses a "left-top" grid system (meaning that (0,0) is the top-left corner of the screen), simply applying a linear rotation to the coordinates of my cards would rotate them out of the viewport.
So, I specify all coordinates in terms of a regular, Cartesian [-1,1]x[-1,1] grid and convert to the left-top coordinate system just before drawing. This allows me to apply any linear rotations to the Cartesian coordinates and the point of rotation will be the center of the screen.
The only external dependency of this repository is pygame.
py [server.py|client.py] [ip] [port]