Skip to content

Latest commit

 

History

History
257 lines (207 loc) · 8.13 KB

Protocol.org

File metadata and controls

257 lines (207 loc) · 8.13 KB

Hanabi server protocol specification

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.

Basics

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.

About types

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.

Starting the game

Connection

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>
}

Game start

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 game state

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>
}

Playing the game

Once the game has begun the players have access to the following actions:

Hint Color/Number Request

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 Card Request

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
}

Play Card Request

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.

Game over

A game is over after any one of 2 conditions is met:

  1. All error tokens are used up
  2. 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,
}

Error handling

todo