- Communication protocol documentation
- NetEvents - Network level client-server connection management
- Application level client-server connection management
- VCEvents - Player setup game phase
- VCEvents - Turns game phase
- MVEvents - State events
- UpdateActionToken
- UpdateActivateLeader
- UpdateCurrentPlayer
- UpdateDevCardGrid
- UpdateDevSlot
- UpdateFaithPoints
- UpdateGame
- UpdateGameEnd
- UpdateLastRound
- UpdateLeadersHand
- UpdateLeadersHandCount
- UpdateMarket
- UpdatePlayerStatus
- UpdateResourceContainer
- UpdateSetupDone
- UpdateVaticanSection
- UpdateVictoryPoints
- Shared Errors
This document describes the client-server communication protocol used by the implementation of the Masters of Renaissance game written by group AM49.
All messages are encoded using the GSON library and follow therefore the JSON specification, language-wise.
Every value shown in the messages is to be taken as an example, having been written only to show the messages'
structure.
Important details about state update messages in MVEvents - State events.
UpdateAction
messages confirm the success of the requested action and mark the end of the state update messages
stream.
Error messages are unicast to the client that sent the illegal request.
Two data structures that are often used inside messages are:
- Resource maps - correlate a string, representing a resource type, with an integer, corresponding to its quantity
- Resource container maps - correlate a container ID (expressed as an integer) with a resource map, corresponding to the resource(s) to add to/remove from it
To make the protocol more resilient and situation-aware, NetEvents
have been separated from application-level events.
The player, when starting the client, can choose whether to connect to the server (singleplayer and multiplayer
playmodes) or playing locally (singleplayer only).
In both cases the messages sent are the same and happen in the exact same way: the network side of the project has been
implemented to allow changing the transport layer transparently to both the client and the server.
Upon connection, a handshake takes place:
┌────────┒ ┌────────┒
│ Client ┃ │ Server ┃
┕━━━┯━━━━┛ ┕━━━━┯━━━┛
│ │
│ ReqWelcome │
┝━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━►│
│ │
│ ResWelcome │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
ReqWelcome (client)
{
"type": "ReqWelcome"
}
ResWelcome (server)
{
"type": "ResWelcome"
}
The event of the connection closing can happen for three reasons:
- The player quits the game
- The client crashes
- The server crashes
Quitting the client sends one final application-level message to the server (
see Quitting the game), allowing the server to distinguish between the situations. After the
server has executed the quit routine, the sockets can now be closed.
A ReqGoodbye
event is sent by the client:
{ "type": "ReqGoodbye" }
A ResGoodbye
message is sent by the server as an acknowledgement:
{
"type": "ResGoodbye"
}
Only then the network sockets is closed by both parties.
This message is used internally by the network layer to notify the application layer of the fact that the connection with the server was closed by the server itself.
ErrServerUnavailable
{ "type": "ErrServerUnavailable" }
The player is allowed to connect back to the server and join the game they were previously in.
The reconnection routine at the network level is exactly the same as a normal connection.
The server checks whether the clients are still alive using both normal messages and heartbeat messages (the timeout
resets with any kind of message).
Heartbeat messages are sent at regular intervals.
┌────────┒ ┌────────┒
│ Client ┃ │ Server ┃
┕━━━┯━━━━┛ ┕━━━━┯━━━┛
│ │
│ ReqHeartbeat │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
│ ResHeartbeat │
┝━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━►│
│ │
ReqHeartbeat (server)
{
"type": "ReqHeartbeat"
}
ResHeartbeat (client)
{
"type": "ResHeartbeat"
}
Broken (unparsable) messages are signaled with a ErrProtocol
messsage containing information about the error itself:
{
"type": "ErrProtocol",
"msg": "Error parsing the message: invalid token 'a'"
}
A summary of the requirements highlighting the relevant parts is reported below:
- On player connection: the player is automatically added to the game currently being filled. If there is no game in its starting phase, a new one is created.
- The player starting a new game chooses how many players have to join before the game can start.
- The game starts as soon as the specified number of players (given by the first player to join) is reached.
- The server manages the players' turns as per the game's rules. The server must handle a player disconnecting or leaving the game; if there are no players left the game will terminate and all players have to be notified.
The following specification for the additional feature "Multiple Games" is taken into account in the communication protocol's design:
- Only one waiting room is used to manage the players who join the server.
- Players who disconnect can successively reconnect to continue the game. While a player is not connected, the game continues skipping the player's turns.
Given those requirements, the communication at connection time has been modeled the following way.
After establishing a connection with the server, the client will ask the player to input a nickname of their choice. The entry is sent to the server, and, if acceptable (unique among the registered nicknames, not empty, not already set), is accepted. Else, the server will signal the error, restarting the process.
Information about the player being the first of the game is included in the response sent to the client. This is necessary to handle the choice of the number of players.
┌────────┒ ┌────────┒
│ Client ┃ │ Server ┃
┕━━━┯━━━━┛ ┕━━━━┯━━━┛
│ │
│ ReqJoin │
┝━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━►│
│ │ ╭───────────────────────╮
│ ├─┤ check nickname unique │
│ UpdateBookedSeats │ ╰───────────────────────╯
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
│ ErrNickname │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
ReqJoin (client)
{
"type": "ReqJoin",
"nickname": "NicknameA"
}
UpdateBookedSeats (server)
{
"type": "UpdateBookedSeats",
"bookedSeats":1,
"canPrepareNewGame":"NicknameA"
}
ErrNickname (server)
{
"type": "ErrNickname",
"reason": "TAKEN"
}
The UpdateBookedSeats
message gives the client information about who is the first in the waiting list (and therefore
can choose the new game's player count) and the quantity of players in the waiting list. When the game is waiting for
players to join before its start, sending notifications allows the players who already joined to know how many empty
seats are left, therefore getting a sense for how much waiting time there is left.
When a player is assigned by the server as the first of a new game, they have to decide the number of players required to start it.
┌────────┒ ┌────────┒
│ Client ┃ │ Server ┃
┕━━━┯━━━━┛ ┕━━━━┯━━━┛
╭────────────╮ │ │
│ user input ├─┤ │
╰────────────╯ │ ReqNewGame │
┝━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━►│
│ │ ╭─────────────╮
│ ├─┤ try setting │
│ UpdateJoinGame │ ╰─────────────╯
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
│ ErrAction │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
ReqNewGame (client)
{
"type": "ReqNewGame",
"playersCount": 3
}
UpdateJoinGame (server)
{
"type": "UpdateJoinGame",
"playersCount": 3
}
ErrNewGame (server)
{
"type": "ErrNewGame",
"isInvalidPlayersCount": false
}
The UpdateJoinGame
message signals to the first playersCount
waiting clients that a new game is undergoing creation
and the number of players included. Any other client that joins the server and is assigned to the starting game will
also receive the message.
If a ReqNewGame
is received from a client that is not allowed to request a new
game, ErrNewGame.isInvalidPlayersCount
is set to false.
See the Game start section for information on how the protocol defines the initial data transfer after joining a game.
When the player quits the game, the client will send a ReqQuit
message:
ReqQuit (client)
{
"type":"ReqQuit"
}
The server will then execute an internal routine that will allow the player to reconnect at a later time and join back
the game they were in, notifying at the same time the other players of the game of the event (
see UpdatePlayerStatus).
A ResQuit
message is sent to the client to notify it about the closing routine being completed.
ResQuit (server)
{
"type":"ResQuit"
}
The network-level closing routine will then start (see Closing the connection).
A player can reconnect to a game they left, if it's still ongoing.
This is done at connection time by choosing the same nickname as they previously had.
When reconnecting, the server will send all the necessary game data for the client to cache ( see UpdateGame). The client will therefore be able to judge what phase of the game is currently in place (setup, turns, who the current player is, etc.).
When the game starts, the server instantiates its internal model.
It will then send the game data to the clients (see UpdateGame), and the player setup phase will start.
During this phase the players will have to choose the quantity of leader cards and resources specified in the configuration file.
As per the game's rules, the players have to decide manually what leader cards they want to keep for the game's duration.
The client is sent the IDs of the leader cards they can choose from, and will send back a subset of them.
Errors related to this action are:
ErrAction
message, with reasonLATE_SETUP_ACTION
- the request message is sent too late (the setup phase is already concluded)ErrObjectNotOwned
- the request message contains IDs that are not in the player's card listErrInitialChoice
- the request message contains too few IDs or the leader cards have alerady been chosen by the player
┌────────┒ ┌────────┒
│ Client ┃ │ Server ┃
┕━━━┯━━━━┛ ┕━━━━┯━━━┛
│ │
╭────────────╮ │ │
│ user input ├─┤ │
╰────────────╯ │ ReqChooseLeaders │
┝━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━►│
│ │ ╭──────────────────╮
│ ├─┤ try exec / check │
│ *state update messages* │ ╰──────────────────╯
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
│ UpdateAction │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
│ ErrAction │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
│ ErrObjectNotOwned │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
│ ErrInitialChoice │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
ReqChooseLeaders (client)
{
"type": "ReqChooseLeaders",
"leaders": [ 3, 15 ]
}
UpdateAction (server)
{
"type": "UpdateAction",
"action": "CHOOSE_LEADERS",
"player": "NicknameA"
}
ErrInitialChoice (server)
{
"type": "ErrInitialChoice",
"isLeadersChoice": true,
"missingLeadersCount": 2
}
After choosing the leader cards the players is prompted to choose their starting resources, following the configuration file's settings.
The client is sent the quantity of resources and the resource types the player has to choose from (
see UpdateGame).
Every player will have to choose the resources before the game's turns can start, thereby concluding the setup phase.
The shelves
field is a standard resource container map
Error messages are fired in these situations:
ErrAction
message, with reasonLATE_SETUP_ACTION
- the request message is sent too late (the setup phase is already concluded)ErrObjectNotOwned
- the request message contains resource container IDs that are not owned the playerErrNoSuchEntity
- a resource type that does not exist is specifiedErrInitialChoice
- the resurces were already chosenErrResourceTransfer
- a number of resources exceeding the shelf's capacity is requested to be added to a shelf
┌────────┒ ┌────────┒
│ Client ┃ │ Server ┃
┕━━━┯━━━━┛ ┕━━━━┯━━━┛
│ │
╭────────────╮ │ │
│ user input ├─┤ │
╰────────────╯ │ ReqChooseResources │
┝━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━►│
│ │ ╭──────────────────╮
│ ├─┤ try exec / check │
│ *state update messages* │ ╰──────────────────╯
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
│ UpdateAction │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
│ ErrAction │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
│ ErrObjectNotOwned │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
│ ErrNoSuchEntity │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
│ ErrInitialChoice │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
│ ErrResourceTransfer │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
ReqChooseResources (client)
{
"type": "ReqChooseResources",
"shelves": [
{ "1": { "Coin": 1 } },
{ "0": { "Shield": 1 } }
]
}
UpdateAction (server)
{
"type": "UpdateAction",
"action": "CHOOSE_RESOURCES",
"player": "NicknameA"
}
ErrAction (server)
{
"type": "ErrChooseResources",
"msg": "Cannot choose 2 starting resources, only 1 available."
}
ErrInitialChoice (server)
{
"type": "ErrInitialChoice",
"isLeadersChoice": false,
"missingLeadersCount": 0
}
After all players have gone through the setup phase, the server will start the turn loop.
The messages in this section can be differentiated into:
- Main actions, of which the player has to make only one during the turn
- Secondary actions, which can be repeated within the player's turn
- State messages, which update the local caches' state to game the server's
During their turn, the player has to choose which action to take among these:
- Getting resources from the market
- Buying a development card
- Activating the production
Since the player may want to make a secondary move after the main action, the server waits for a TurnEnd
message
before switching to the next player.
The following needs to be specified:
- Which row/column the player wants to take the resources from
- For each replaceable resource, which resource type to convert it to (if applicable)
- For each resource (its type considered after the leaders' processing), which shelf to use to store it
- What resources, among the ones taken from the market, to discard
Discarding is simply handled by specifying a lower quantity of resources to add to a shelf. This also easily games the rule for which only the resources given by the market can be discarded.
The replacements
field specifies how the resource conversion should be handled. Since the player knows what type of
resource the leader converts to, they can easily select them by specifying, for each type of resource they want as
output, how many replaceable resources (of the available ones) to use.
The replacement
field is a standard resource map
The shelves
field is a standard resource container map
Errors may arise from fitting the resources in the shelves, either by specifying the wrong shelf or by not discarding enough resources. See Shared Errors for more details on the errors that may be generated by the request.
┌────────┒ ┌────────┒
│ Client ┃ │ Server ┃
┕━━━┯━━━━┛ ┕━━━━┯━━━┛
╭────────────╮ │ │
│ user input ├─┤ │
╰────────────╯ │ ReqTakeFromMarket │
┝━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━►│
│ │ ╭──────────────────╮
│ ├─┤ try exec / check │
│ *state update messages* │ ╰──────────────────╯
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
│ UpdateAction │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
│ ErrAction │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
│ ErrObjectNotOwned │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
│ ErrNoSuchEntity │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
│ ErrResourceReplacement │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
│ ErrReplacedTransRecipe │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
│ ErrResourceTransfer │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
ReqTakeFromMarket (client)
{
"type": "ReqTakeFromMarket",
"isRow": true,
"index": 0,
"replacements": { "Coin": 2 },
"shelves": [
{ "1": { "Coin": 2 } },
{ "3": { "Shield": 1 } }
]
}
UpdateAction (server)
{
"type": "UpdateAction",
"action": "TAKE_MARKET_RESOURCES",
"player": "NicknameA"
}
The following information is needed when buying a development card:
- The row and column of the card to identify it in the grid
- The slot to place the card into
- For each resource that has to be paid, the shelf (or strongbox) to take it from
The resContainers
field is a standard resource container map
An ErrBuyDevCard
message signals:
- the selected color/level refer to an empty deck of cards (
isStackEmpty
is set to true) - the card's level does not allow the card to be placed in the selected slot (
isStackEmpty
is set to false)
An ErrCardRequirements
message signals which cost requirements the player is missing.
The missingResources
field is a standard resource map
Other possible errors include not identifying a valid card/slot, not satisfying placement requirements (the card's level
is not one above the level of the card it is being placed onto) and not specifying correctly the resource transaction's
details.
See Shared Errors for more details on the errors that may be generated by the request.
┌────────┒ ┌────────┒
│ Client ┃ │ Server ┃
┕━━━┯━━━━┛ ┕━━━━┯━━━┛
╭────────────╮ │ │
│ user input ├─┤ │
╰────────────╯ │ ReqBuyDevCard │
┝━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━►│
│ │ ╭──────────────────╮
│ ├─┤ try exec / check │
│ *state update messages* │ ╰──────────────────╯
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
│ UpdateAction │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
│ ErrAction │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
│ ErrObjectNotOwned │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
│ ErrNoSuchEntity │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
│ ErrResourceReplacement │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
│ ErrReplacedTransRecipe │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
│ ErrResourceTransfer │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
│ ErrBuyDevCard │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
│ ErrCardRequirements │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
ReqBuyDevCard (client)
{
"type": "ReqBuyDevCard",
"level": 1,
"color": "Blue",
"devSlot": 2,
"resContainers": [
{ "1": { "Coin": 2 } },
{ "3": { "Shield": 1 } }
]
}
UpdateAction (server)
{
"type": "UpdateAction",
"action": "BUY_DEVELOPMENT_CARD",
"player": "NicknameA"
}
ErrBuyDevCard (server)
{
"type": "ErrBuyDevCard",
"isStackEmpty": true
}
ErrCardRequirements (server)
{
"type": "ErrCardRequirements",
"missingDevCards": null,
"missingResources": { "Coin": 1, "Shield": 2 }
}
The following information is needed when activating productions:
- Which productions to activate
- For each production, a
- Each non-storable resource chosen as a replacement in the input
- The storable and non-storable output replacements
The inputContainers
field is a standard resource container map detailing all (default and
replaced) resources in input. The non-storable replacements have to be specified by themselves in
the inputNonStorableRep
field (which is a standard resource map). The outputRep
field
specifies the replacements pertaining to the output side of the productions (it's a
standard resource map since all output goes to the player's strongbox).
Errors may originate from issues with referencing inexistent/not owned objects, resource replacements and resource
transfers.
See Shared Errors for more details on a specific error.
┌────────┒ ┌────────┒
│ Client ┃ │ Server ┃
┕━━━┯━━━━┛ ┕━━━━┯━━━┛
╭────────────╮ │ │
│ user input ├─┤ │
╰────────────╯ │ ReqActivateProductions │
┝━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━►│
│ │ ╭──────────────────╮
│ ├─┤ try exec / check │
│ *state update messages* │ ╰──────────────────╯
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
│ UpdateAction │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
│ ErrAction │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
│ ErrObjectNotOwned │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
│ ErrNoSuchEntity │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
│ ErrResourceReplacement │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
│ ErrReplacedTransRecipe │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
│ ErrResourceTransfer │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
ReqActivateProductions (client)
{
"type": "ReqActivateProductions",
"prodRequests": [
{
"id": 0,
"outputRep": { "Faith": 1 },
"inputContainers": [
{ "1": { "Coin": 1 } },
{ "2": { "Coin": 2 } },
{ "0": { "Shield": 2 } }
]
}, {
"id": 3,
"outputRep": { "Faith": 1 },
"inputContainers": [
{ "0": { "Stone": 2 } }
],
"inputNonStorableRep": { "Faith": 1 }
}
]
}
UpdateAction (server)
{
"type": "UpdateAction",
"action": "ACTIVATE_PRODUCTION",
"player": "NicknameA"
}
Since the server cannot at any point assume that the player has finished choosing their moves ( see secondary actions), an explicit message has to be sent.
If a player ends their turn early (without having done a mandatory action) an ErrAction
with "reason": "EARLY_TURN_END"
is generated.
┌────────┒ ┌────────┒
│ Client ┃ │ Server ┃
┕━━━┯━━━━┛ ┕━━━━┯━━━┛
╭────────────╮ │ │
│ user input ├─┤ │
╰────────────╯ │ ReqEndTurn │
┝━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━►│
│ │ ╭──────╮
│ ├─┤ exec │
│ *state update messages* │ ╰──────╯
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
│ UpdateAction │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
│ ErrAction │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
ReqEndTurn (client)
{ "type": "ReqEndTurn" }
UpdateAction (server)
{
"type": "UpdateAction",
"action": "END_TURN",
"player": "NicknameA"
}
Secondary moves can be performed as often as the player wants and at any point of the turn. They are:
- Swapping the content of the player's shelves
- Activating/discarding a leader card
During their turn, the player can decide to reorder the content of their warehouse's shelves and leader depots.
This is technically only useful when taking resources from the market, as no other action refills the shelves, but it was left as an always-possible operation to improve the gameplay experience.
┌────────┒ ┌────────┒
│ Client ┃ │ Server ┃
┕━━━┯━━━━┛ ┕━━━━┯━━━┛
╭────────────╮ │ │
│ user input ├─┤ │
╰────────────╯ │ ReqSwapShelves │
┝━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━►│
│ │ ╭──────────────────╮
│ ├─┤ try exec / check │
│ *state update messages* │ ╰──────────────────╯
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
│ UpdateAction │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
│ ErrAction │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
│ ErrObjectNotOwned │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
│ ErrResourceTransfer │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
ReqSwapShelves (client)
{
"type": "ReqSwapShelves",
"shelf1": 0,
"shelf2": 3
}
UpdateAction (server)
{
"type": "UpdateAction",
"action": "SWAP_SHELVES",
"player": "NicknameA"
}
During their turn, in addition to one of the main three actions, a player can choose to discard or activate their leader cards.
To activate or discard a leader the server needs to know which card the player wants to act on and which action to perform.
┌────────┒ ┌────────┒
│ Client ┃ │ Server ┃
┕━━━┯━━━━┛ ┕━━━━┯━━━┛
╭────────────╮ │ │
│ user input ├─┤ │
╰────────────╯ │ ReqLeaderAction │
┝━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━►│
│ │ ╭──────────────────╮
│ ├─┤ try exec / check │
│ *state update messages* │ ╰──────────────────╯
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
│ UpdateAction │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
│ ErrAction │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
│ ErrObjectNotOwned │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
│ ErrCardRequirements │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
ReqLeaderAction (client)
{
"type": "ReqLeaderAction",
"leader": 0,
"isActivate": true
}
UpdateAction (server)
{
"type": "UpdateAction",
"action": "ACTIVATE_LEADER",
"player": "NicknameA"
}
ErrCardRequirements (server)
{
"type": "ErrCardRequirements",
"missingDevCards": [
{
"color": "Blue",
"level": 1,
"quantity": 2
}
],
"missingResources": null
}
If a leader is activated while already active no error is raised, since it's not a critical event.
┌────────┒ ┌────────┒
│ Client ┃ │ Server ┃
┕━━━┯━━━━┛ ┕━━━━┯━━━┛
╭────────────╮ │ │
│ user input ├─┤ │
╰────────────╯ │ ReqLeaderAction │
┝━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━►│
│ │ ╭──────────────────╮
│ ├─┤ try exec / check │
│ *state update messages* │ ╰──────────────────╯
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
│ UpdateAction │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
│ ErrAction │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
│ ErrObjectNotOwned │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
│ ErrActiveLeaderDiscarded │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
ReqLeaderAction (client)
{
"type": "ReqLeaderAction",
"leader": 0,
"isActivate": false
}
UpdateAction (server)
{
"type": "UpdateAction",
"action": "DISCARD_LEADER",
"player": "NicknameA"
}
ErrActiveLeaderDiscarded (server)
{
"type": "ErrActiveLeaderDiscarded"
}
The ErrActiveLeaderDiscarded
error message is sent by the server when a player tries to discard an active leader card.
These messages are used to update the clients' caches so that the data is synchronized with the server's.
State update messages are sent autonomously by the updated entities. Clients cannot assume that a state update message
will be sent or the timing it will be sent with (ordering with respect to other messages, etc.).
State update messages are broadcast unless specified.
IDs reference the data given in game start.
Notifies clients of the activation of an action token, sending its ID.
┌────────┒ ┌────────┒
│ Client ┃ │ Server ┃
┕━━━┯━━━━┛ ┕━━━━┯━━━┛
│ │
│ UpdateActionToken │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
UpdateActionToken (server)
{
"type": "UpdateActionToken",
"actionToken": 6
}
Notifies clients when a leader card is activated.
┌────────┒ ┌────────┒
│ Client ┃ │ Server ┃
┕━━━┯━━━━┛ ┕━━━━┯━━━┛
│ │
│ UpdateActivateLeader │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
UpdateActivateLeader (server)
{
"type": "UpdateActivateLeader",
"leader": 1
}
Notifies clients of who the current player is from the moment the message is sent.
┌────────┒ ┌────────┒
│ Client ┃ │ Server ┃
┕━━━┯━━━━┛ ┕━━━━┯━━━┛
│ │
│ UpdateCurrentPlayer │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
UpdateCurrentPlayer (server)
{
"type": "UpdateCurrentPlayer",
"nickname": "NicknameA"
}
Notifies the clients of an update in the development card grid's state.
The list of IDs are the respective top cards of each deck, the level of which is the card's position in the list and the color of which is specified in the enclosing map.
┌────────┒ ┌────────┒
│ Client ┃ │ Server ┃
┕━━━┯━━━━┛ ┕━━━━┯━━━┛
│ │
│ UpdateDevCardGrid │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
UpdateDevCardGrid (server)
{
"type": "UpdateDevCardGrid",
"topCards": {
"levelsCount": 3,
"colorsCount": 4,
"grid": {
"Purple": [
[ 2, 0, 1, 3 ]
],
"Green": [
[ 8, 7, 6, 4 ]
]
}
}
}
Notifies the clients of cards being in a player's board's development card slot.
┌────────┒ ┌────────┒
│ Client ┃ │ Server ┃
┕━━━┯━━━━┛ ┕━━━━┯━━━┛
│ │
│ UpdateDevSlot │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
UpdateDevSlot (server)
{
"type": "UpdateDevSlot",
"player": "NicknameA",
"slot": 0,
"cards": [ 7, 9 ]
}
Notifies clients of a player progressing on the faith track.
┌────────┒ ┌────────┒
│ Client ┃ │ Server ┃
┕━━━┯━━━━┛ ┕━━━━┯━━━┛
│ │
│ UpdateFaithTrack │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
UpdateFaithTrack (server)
{
"type": "UpdateFaithTrack",
"isBlackCross": false,
"faithPoints": 14,
"player": "NicknameA"
}
As the game starts, the server notifies all players of the event.
The server sends the game's state to be cached by the clients. Caching parts of the game's state allows the clients to
answer requests without the server's intervention.
Caching allows partial checks to be preemptively (but not exclusively) done client side: if the player specifies an
index that's out of bounds, the client is able to catch the error before sending the request to the server, reducing
network and server loads and improving the game's responsiveness.
The game's model has been parameterized to allow for flexibility. The parameters are set via a configuration file, which also contains serialized game data (e.g. cards, resources, etc).
Clients have a default configuration file embedded to allow for local single player games. Both clients and server also support loading custom configuration files.
Since the file on a server may be different from the one embedded in a client, all game elements need to be sent at the start of a game, ensuring proper synchronization between the clients and the server, both in terms of IDs and actual game data.
Since the connection and reconnection phases are very delicate, a modular approach was discarded in favor of a
monolithic message.
If a modular approach were to be chosen, the clients' state-switching logic would become unmanageable, needing to manage
asynchronous and independent state messages to handle transitions that depend on the presence of the data itself (some
of these transitions would in fact be impossible to model if the data was not sent all in the same message).
┌────────┒ ┌────────┒
│ Client ┃ │ Server ┃
┕━━━┯━━━━┛ ┕━━━━┯━━━┛
│ │
│ UpdateGame │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
UpdateGame (server)
{
"type": "UpdateGame",
"players": [
{
"nickname": "NicknameA",
"baseProduction": 0,
"warehouseShelves": [ 0, 1, 2 ],
"strongbox": 3,
"setup": {
"chosenLeadersCount": 2,
"initialResources": 1,
"initialExcludedResources": [ "Faith" ],
"hasChosenLeaders": false,
"hasChosenResources": false
},
"leadersHand": [ 3, 5, 7, 15 ],
"leadersHandCount": 4,
"devSlots": [ [], [], [] ],
"faithPoints": 0,
"victoryPoints": 0,
"active":true
}
],
"devCardColors": [
{
"name": "Blue",
"ansiColor": "\u001b[34m"
},
{
"name": "Green",
"ansiColor": "\u001b[32m"
}
],
"resourceTypes": [
{
"name": "Servant",
"ansiColor": "\u001B[95m",
"isStorable": true,
"isGiveableToPlayer": true,
"isTakeableFromPlayer": true
}, {
"name": "Zero",
"ansiColor": "\u001B[37m",
"isStorable": false,
"isGiveableToPlayer": false,
"isTakeableFromPlayer": false
}
],
"leaderCards": [
{
"resourceType": "Servant",
"leaderType": "DiscountLeader",
"devCardRequirement": {
"entries": [
{ "color": "Yellow", "quantity": 1, "level": 2 }
]
},
"isActive": false,
"containerId": -1,
"discount": 1,
"id": 0,
"victoryPoints": 2,
"production": -1
}
],
"developmentCards": [
{
"color": "Green",
"cost": {
"requirements": { "Shield": 1 }
},
"level": 1,
"id": 0,
"victoryPoints": 1,
"production": 5
}
],
"resContainers": [
{
"id": 4,
"content": {},
"boundedResType": "Stone",
"size": 2
}, {
"id": 3,
"content": {},
"size": -1
}
],
"productions": [
{
"id": 0,
"input": {},
"inputBlanks": 2,
"inputBlanksExclusions": [],
"output": {},
"outputBlanks": 1,
"outputBlanksExclusions": [ "Faith" ],
"discardableOutput": false
}
],
"actionTokens": [
{
"id": 0,
"kind": "ActionTokenBlackMoveOneShuffle"
}, {
"id": 3,
"kind": "ActionTokenDiscardTwo",
"discardedDevCardColor": "Blue"
}
],
"faithTrack": {
"vaticanSections": {
"16": {
"id": 2,
"faithPointsBeginning": 12,
"faithPointsEnd": 16,
"victoryPoints": 3,
"activated": false,
"bonusGivenPlayers": []
}
},
"yellowTiles": [
{
"faithPoints": 12,
"victoryPoints": 6
}
]
},
"market": {
"grid": [
[ "Stone", "Shield", "Zero", "Faith" ],
[ "Zero", "Servant", "Zero", "Zero" ]
],
"replaceableResType": "Zero",
"slide": "Servant"
},
"devCardGrid": {
"levelsCount": 3,
"colorsCount": 4,
"topCards": {
"Yellow": [ null, 11, 31, 39 ],
"Purple": [ null, 5, 29, 33 ]
}
},
"isSetupDone": false,
"devSlotsCount": 3,
"currentPlayer": "NicknameA",
"inkwellPlayer": "NicknameA",
"blackPoints": 0,
"lastRound": false,
"ended": false,
"isMandatoryActionDone": false
}
Notifies the clients of the end of the game, detailing the winner player.
┌────────┒ ┌────────┒
│ Client ┃ │ Server ┃
┕━━━┯━━━━┛ ┕━━━━┯━━━┛
│ │
│ UpdateGameEnd │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
UpdateGameEnd (server)
{
"type": "UpdateGameEnd",
"winner": "NicknameA"
}
Notifies the clients of the current round being the last.
┌────────┒ ┌────────┒
│ Client ┃ │ Server ┃
┕━━━┯━━━━┛ ┕━━━━┯━━━┛
│ │
│ UpdateLastRound │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
UpdateLastRound (server)
{
"type": "UpdateLastRound"
}
A player's leader cards are hidden from the other players until activated. This message is therefore sent only to the
owner of the cards, whom can see their IDs.
The other players will receive a UpdateLeadersHandCount event, which only contains the number
of cards owned by the other player.
Upon receiving a UpdateLeadersHand event, the non-owner players will know that the
currently-playing player has activated a card and its ID. They can therefore add the ID to the current player's leader
cards hand.
┌────────┒ ┌────────┒
│ Client ┃ │ Server ┃
┕━━━┯━━━━┛ ┕━━━━┯━━━┛
│ │
│ UpdateLeadersHand │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
UpdateLeadersHand (server)
{
"type": "UpdateLeadersHand",
"player": "NicknameA",
"leaders": [ 3, 5 ]
}
Given the explanation of UpdateLeadersHand, this message contains the number of leader card a
player is holding.
This allows for the cards' IDs to remain hidden while informing clients of events such as a card being discarded.
┌────────┒ ┌────────┒
│ Client ┃ │ Server ┃
┕━━━┯━━━━┛ ┕━━━━┯━━━┛
│ │
│ UpdateLeadersHandCount │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
UpdateLeadersHandCount (server)
{
"type": "UpdateLeadersHandCount",
"player": "NicknameA",
"leadersHandCount": 1
}
This message holds the current state of the game's market.
It specifies what resource type is to be accounted for as replaceable, since the configuration file allows for it to be changed.
┌────────┒ ┌────────┒
│ Client ┃ │ Server ┃
┕━━━┯━━━━┛ ┕━━━━┯━━━┛
│ │
│ UpdateMarket │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
UpdateMarket (server)
{
"type": "UpdateMarket",
"market": {
"grid": [
[ "Coin", "Servant", "Stone", "Shield" ],
[ "Shield", "Faith", "Zero", "Zero" ]
],
"replaceableResType": "Zero",
"slide": "Stone"
}
}
Notifications about players connecting/disconnecting from a game are sent via this message.
┌────────┒ ┌────────┒
│ Client ┃ │ Server ┃
┕━━━┯━━━━┛ ┕━━━━┯━━━┛
│ │
│ UpdatePlayerStatus │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
UpdatePlayerStatus (server)
{
"type": "UpdatePlayerStatus",
"nickname": "NicknameA",
"isActive": true
}
This message details the current state of the resource container with the specified ID.
Strongboxes have size set to -1.
If there is no binding resource the boundedResType
field is not specified.
┌────────┒ ┌────────┒
│ Client ┃ │ Server ┃
┕━━━┯━━━━┛ ┕━━━━┯━━━┛
│ │
│ UpdateResourceContainer │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
UpdateResourceContainer (server)
{
"type": "UpdateResourceContainer",
"resContainer": {
"id": 3,
"content": {
"Coin": 3
},
"boundedResType": "Coin",
"size": 1
}
}
When every player has chosen their leader cards and starting resources the conclusion of the setup phase is notified to the clients.
┌────────┒ ┌────────┒
│ Client ┃ │ Server ┃
┕━━━┯━━━━┛ ┕━━━━┯━━━┛
│ │
│ UpdateSetupDone │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
UpdateSetupDone (server)
{
"type": "UpdateSetupDone"
}
This message contains the list of players who benefit from the vatican section's bonus points.
┌────────┒ ┌────────┒
│ Client ┃ │ Server ┃
┕━━━┯━━━━┛ ┕━━━━┯━━━┛
│ │
│ UpdateVaticanSection │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
UpdateVaticanSection (server)
{
"type": "UpdateVaticanSection",
"id": 60,
"bonusGivenPlayers": "NicknameA"
}
This message contains the current quantity of victory points for the specified player.
The victory points' quantity is updated in real time.
┌────────┒ ┌────────┒
│ Client ┃ │ Server ┃
┕━━━┯━━━━┛ ┕━━━━┯━━━┛
│ │
│ UpdateVictoryPoints │
│◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ │
UpdateVictoryPoints (server)
{
"type": "UpdateVictoryPoints",
"player": "NicknameA",
"victoryPoints": 20
}
Error messages that are sent in multiple occasions are reported here.
Any successive mentions of these messages refer to this section for syntax and examples.
This message signals to the client that the action is being requested at the wrong time.
The reason
field offers a more detailed explanation:
LATE_SETUP_ACTION
- a setup request is sent after the setup phase is concludedEARLY_MANDATORY_ACTION
- an action request (non-setup) is sent during the setup phaseLATE_MANDATORY_ACTION
- a action request (non-setup) is sent for the second time during a player's turnEARLY_TURN_END
- a request to end the player's turn is sent before a mandatory action requestGAME_ENDED
- an action request is sent after the game's endNOT_CURRENT_PLAYER
- the player requesting the action is not the current player
ErrAction (server)
{
"type": "ErrAction",
"reason": "LATE_SETUP_ACTION"
}
This message signals the absence of an entity to game an ID with.
The originalEntity
field describes the kind of entity the request pertained to, which include:
MARKET_INDEX
- index of a market's row/column does not existLEADER
- leader card with referenced ID does not existDEVCARD
- development card with referenced ID does not existCOLOR
- referenced color does not existRESOURCE
- referenced resource type does not exist
The message also reports the id
or the code
string of the missing object.
ErrNoSuchEntity (server)
{
"type": "ErrNoSuchEntity",
"originalEntity": "LEADER",
"id": 50,
"code": null
}
When a request message from a client references the ID of an object that is not owned by the player,
an ErrObjectNotOwned
message is sent by the server, detailing the erroneus ID and the object's kind.
ErrObjectNotOwned (server)
{
"type": "ErrObjectNotOwned",
"id": "NicknameA",
"objectType": "LeaderCard"
}
This message signals a discrepancy between the available and specified numbers of resources to be put in a container.
The isInput
field indicates where in the recipe (input/output) the error happened. The replacedCount
field details
the number of available resources in the transaction after the replacements have been factored in.
The shelvesChoiceResCount
field details the number of resources requested to be put in the container.
The isIllegalDiscardedOut
field specifies whether the discrepancy is to be reconduced to illegally discarded resources
in output.
ErrReplacedTransRecipe (server)
{
"type": "ErrReplacedTransRecipe",
"isInput": true,
"resType": "Coin",
"replacedCount": 3,
"shelvesChoiceResCount": 4,
"isIllegalDiscardedOut": false
}
This message signals an error when validating a resource transaction request.
The issue might lie in either of the shelf maps or the replacement maps.
The isInput
field distinguishes between the transaction's input and output resources, while the isReplacement
field
distinguishes between replacements and shelf maps.
The reason
field details the reason for which the request was denied, and can be one of:
NEGATIVE_VALUES
- maps contain negative resource quantitiesILLEGAL_STORABLE
- a storable resource is specified when non-storable are allowed onlyILLEGAL_NON_STORABLE
- same as above, but invertedEXCLUDED
- a forbidden resource is used as a replacement
ErrResourceReplacement (server)
{
"type": "ErrResourceReplacement",
"isInput": true,
"isReplacement": false,
"reason": "ILLEGAL_NON_STORABLE"
}
This error signals an error with a resource transfer request.
Reasons included in the message are:
BOUNDED_RESTYPE_DIFFER
- a resource is trying to be added/removed to a shelf that's bound to another resource typeNON_STORABLE
- a non-storable resource is trying to be added/removed to a resource containerCAPACITY_REACHED
- the resource transfer requests that the number of resulting resources in the container is either less than zero or greater than the container's capacityDUPLICATE_BOUNDED_RESOURCE
- a resource is trying to be added to a shelf while there is another shelf bound to the same resource type
ErrResourceTransfer (server)
{
"type": "ErrResourceTransfer",
"resType": "Coin",
"isAdded": true,
"reason": "CAPACITY_REACHED"
}