This document specifically deals with using and communicating with the hanabi server. To learn about the rules of hanabi the game try its wikipedia article.
Once started the server will begin listening to incoming websocket connections. Once a connection is established the client is free to send requests to the server. Each request will always be answered with an appropriate response. In fact any request sent from one client will result in the same response being sent to all client to ensure all player are kept up-to-date on current game events.
Should a request go unanswered something probably went wrong with the server.
The server goes to a great effort to first validate client input and to run various sanity checks before committing to any persistent game state changes. Should any client action result in the server sending an error response (the one exception to the rule of responses being sent to all players) it should still be possible to continue playing the game, provided the client side issue can be resolved. Error responses will always contain a clear explanation detailing what went wrong.
All communcation takes place via json. To simplify serialization in statically typed languages (the server is written
in rust, the reference client in java) each request and response contains a field msg_type
indicatong
the exact type of the message (easily mappable to an Enum). The possible values will be detailed in the following
sections.
To request to participate in a game the client must first send a ConnectionRequest
.
Other than its type it contains exactly one field - the player’s name:
ConnectionRequest {
msg_type: "CONNECTION_REQUEST",
name: String
}
If the player is already connected or if the game has already started the server will answer with an error response.
In case of success the server will return a ConnectionResponse
containing a list of the names
of all currently connected players:
ConnectionResponse {
msg_type: "CONNECTION_RESPONSE",
names: List<String>
}
To get a game going one of the connected players must send a GameStartRequest
which is otherwise
blank:
GameStartRequest {
msg_type: "GAME_START_REQUEST"
}
The server will answer with a GameStartResponse
containing the initial game state (explained in the next section):
GameStartResponse {
msg_type: "GAME_START_RESPONSE",
game_state: GameState
}
Trying to send anything other than a Connection
- or GameStartRequest
before the game has been started or sending
a ConnectionRequest
afterwards will both result in an error response.
The most basic building block is the Card
which consists of a Number
and a Color
and a unique id
used to tell the difference between two cards of the same color and number but with the player knowing
different things about them:
Color = "RED" | "GREEN" | "BLUE" | "WHITE" "YELLOW"
Number = "ONE" | "TWO" | "THREE" | "FOUR" | "FIVE"
Card {
color: Color,
number: Number
}
The next part is the CardKnowledge
, a player’s current insight into a card’s structure:
CardKnowledge {
knows_color: boolean,
knows_number: boolean,
knows_color_not: Set<Color>,
knows_number_not: Set<Number>
}
Both combine into a CardInHand
, a card currently held in a player’s hand:
CardInHand {
card: Card,
knowledge: CardKnowledge
}
Which leads us to the player which consists of a name and the currently held cards:
Player {
name: String,
cards: List<CardInHand>
}
These building blocks are all that is needed to construct the entire game state. Since all game logic is handled on the server the game state carries all information that’s needed to play and process the game, nameley:
- the amount of current (and maximum possible) error and hint tokens
- a map detailing the currently played cards for a color
- a list of all players
- a list of the cards left to be drawn
- a list of the cards that have been discarded
- the name of the next player to play
- the amount of turns left in the game (only included when it is known, that is after the deck has been emptied)
GameState {
hint_tokens: Int,
hint_tokens_max: Int,
err_tokens: Int,
played_cards: Map<Color, Number>,
players: List<Player>,
deck: List<Card>,
discarded_cards: List<Card>,
next_player: String,
turns_left: Option<Int>
}
Once the game has begun the players have access to the following actions:
Give another player a hint about their cards’ colors or numbers. These hints may also be “negative”, meaning it is e.g. possible to hint that all of a player’s cards are not of a specific color. A hint must contain the name of the targeted player as well as the hinted number/color:
HintColorRequest {
msg_type: "HINT_COLOR_REQUEST",
target_player: String,
color: Color
}
HintNumberRequest {
msg_type: "HINT_NUMBER_REQUEST",
target_player: String,
number: Number
}
The server will answer with a response containing the details of the hint action as well as the resulting game state:
HintColorResponse {
msg_type: "HINT_COLOR_RESPONSE",
hinting_player: String,
target_player: String,
hinted_color: Color,
game_state: GameState
}
HintNumberResponse {
msg_type: "HINT_NUMBER_RESPONSE",
hinting_player: String,
target_player: String,
hinted_number: Number,
game_state: GameState
}
Discard a card to regain hint tokens. Must contain the unique id of the card to be discarded:
DiscardCardRequest {
msg_type: "DISCARD_CARD_REQUEST",
discarded_card_id: Int
}
The server will answer with a response containing the details of the discard action, the card that was drawn to replace the played card (only if the deck is not yet empty), as well as the resulting game state:
DiscardCardResponse {
msg_type: "DISCARD_CARD_RESPONSE",
discarding_player: String,
discarded_card: Card,
drawn_card: Option<Card>,
game_state: GameState
}
Attempt to play a card on the field. Must contain the unique id of the card to be played:
PlayCardRequest {
msg_type: "PLAY_CARD_REQUEST",
played_card_id: Int
}
The server will answer with a response containing the details of the play action, an indication whether the play attempt was successful, the card that was drawn to replace the played card (only if the deck is not yet empty), as well as the resulting game state:
PlayCardResponse {
msg_type: "PLAY_CARD_RESPONSE",
playing_player: String,
played_card: Card,
drawn_card: Option<Card>,
success: Bool,
game_state: GameState
}
Playing a Five will regain one hint token id fewer than the maximum are currently in the game.
A game is over after any one of 2 conditions is met:
- All error tokens are used up
- The deck is empty and each player had their last turn
Once this happens instead of sending the responses specified in the previous section the server
will instead immediately send a GameOverResponse
containing the players’ score (the sum of the
highest played numbers for each color) before shutting down all connections:
GameOverResponse {
msg_type: "GAME_OVER_RESPONSE",
score: Int,
}
todo