tome_lib/PROTOCOL.md

7.9 KiB

So You Wanna Write a Snengame client or server

High level considerations

Snengame is written under the following assumptions:

  1. Everything is calculated server side
  2. A client does not have to poll the server or use a stateful connection
  3. Game actions are ultimately implemented using the GameCommand format

The default server and clients are written using JSON over Websockets.

GameCommand

Go Struct representation:

type Command struct {
	PlayerID int     `json:"player_id"`
	Type     CmdType `json:"type"`
	Cmd      string  `json:"cmd"`
}

Plaintext Representation:

<PlayerID> <Type> <Command>

Example Command:

2 a p 0

PlayerID represents which player is issuing the command. It can be either 1 (for "Sentinal") or 2 (for "Scourge")

Type is the command type. The following are accepted: a: Action Command s: State Command d: Debug command

The remainder of the GameCommand is a string containing the actual command. The following are valid commands:

State Commands:

State commands always return a "player-safe" view of the board known as a GameView. This will be described shortly.

b: ready up or begin the game
s: start turn
e: end turn
g: return the current GameView.

Debug Commands:

Client-server specific.

d: If enabled, run a debug function

Action Commands:

Action commands return either an empty list of cards or the relevant list of cards affected (i.e. the current players hand)

s: Look at the top X cards of the current players deck, where x is their Life total. Returns nothing if unable to scry (i.e. the player can not currently draw a card). Otherwise return a list of zero to three cards representing what was seen.
d <x>: Draw the the card at position X in the result return from a Scry. Returns nothing if the player is unable to draw. Otherwise return the current players hand
p <x> <y>: Play the card at position X in the current players hand to position Y on the current player's side of the board. Return nothing if the card cannot be played, otherwise return the current players side of the board.
m <x> <y>: Move the card at position X to position Y, with both positions being on the current players side of the board. Return nothing if the card cannot be moved, otherwise return the current players side of the board.
a <x> <y>: Attack with the card at position X on the current players side of the board at position Y on the opponents side of the board. Returns nothing if the attack can not be made, or the current players side of the board.
t <x> <y>: Apply a targeted effect to card on board X (Either SentinalID or ScourgeID) at position Y. Returns the effected board on success and nothing on failure. Requires a card with a targeted effect to be played first

GameView

A "GameView" is a player-safe version of the current game state. It contains all the information a client needs to show the current game state, without revealing any unwanted information about the players opponent.

TODO: Describe all the options available in a GameView. Until then, see internal/game/game_view.go

GameCommand Results

The results of a GameCommand are returned in the following format:

Go struct representation:

type CommandResult struct {
	PlayerID     int       `json:"player_id"`
	ResultType   CmdType   `json:"result_type"`
	StateResult  *GameView `json:"state_result,omitempty"`
	ActionResult *Deck     `json:"action_result,omitempty"`
	DebugResult  *Game     `json:"debug_result,omitempty"`
}

PlayerID is the same as in a GameCommand ResultType is the same as in a GameCommand, with the addition of the "e" type. This indicates an error handling the command StateResult contains a GameView as described above. ActionResult is a structure containing a list of Cards, which represent the result of the action command DebugResult can be implemented however the client prefers.

See internal/game/cmd.go for more details on GameCommands.

Session Commands

Session commands are used by the default server and client to communicate actions and game state. They are implemented as JSON structures over Websockets

Go struct representation:

type SessionCommand struct {
	ID          uuid.UUID     `json:"player_id"`
	MatchID     uuid.UUID     `json:"match_id"`
	Command     SessionCmd    `json:"command"`
	GameCommand *game.Command `json:"game_command,omitempty"`
	Data        string        `json:"data,omitempty"`
}

ID is the player ID in standard UUID format. It is generated by the client, however in the future it may be generated by some type of authentication system

MatchID is the UUID of the match being played. It is NOT generated by the client; the server returns it when a game is found. All SessionCommands except for "query" MUST have a MatchID

Command is a string representing a command from the following list:

query: Tell the server you are looking for a game. Blocks until the server responds with a found game.
join: Attempt to join the game with the attached MatchID
leave: Attempt to leave the game with the attached MatchID. If no game is found and the player is queueing, remove from queue. Always succeeds.
poll: Check if the server has any "broadcast" responses. Opts into polling.
play: Attempt to run the attached GameCommand
ready: Check if both players have joined the game with attached MatchID
load_deck: attempt to set the players deck to the information stored in the Data section

GameCommand is a game command following the conventions described above.

Data is a section for miscelaneous arguments

Session Command results

The server responds with JSON in the following format:

Go struct representation

type SessionCommandResult struct {
	ID         uuid.UUID           `json:"player_id"`
	MatchID    uuid.UUID           `json:"match_id"`
	Result     SessionResp         `json:"result"`
	GameResult *game.CommandResult `json:"game_result,omitempty"`
}

ID is the player ID of the sender, as described above.

MatchID is the match ID of the sender, as described above.

GameResult is the result of the GameCommand (if one was sent) as described above

Result is a string representing the result of the SessionCommand. The following are valid Results that are expected to be acted upon by the client

"found": A game has been created on the server. The client should use the attached MatchID
"joined p1": The client was succesful in joining the game as Player 1 ("Sentinal")
"joined p2": The client was succesful in joining the game as Player 2 ("Scourge")
"left": The client was succesful in leaving the game
"played": The server recognized a game command was sent. This does NOT mean the command was succesful; the client should check the GameResult for that information
"game ready": Both players have joined the game and game commands can be sent
"generic error": An error has occured.
"deck loaded": deck loaded succesfully
"load deck error": error either loading the deck or parsing the deck

The following Results are called "Broadcasts" and are sent by the server to ease the work done by the clients. None of them are required to be handled, but it will most likely be useful for clients to handle at least some of them.

If a client opt's into polling it is expected to poll constantly. Clients can feel free to discard any Broadcasts they don't want to handle.

All of the following are returned from the "poll" session command.

"Sentinal turn": Player 2 has ended their turn.
"Scourge turn": Player 1 has ended their turn
"Scourge wins": Player 2 has won
"Sentinal wins": Player 1 has won
"update": An action has been taken that has altered the board or game state in some way, besides starting or ending a turn.
"Sentinal joined": Player 1 has joined the game
"Scourge joined": Player 2 has joined the game
"Sentinal player is ready": Player 1 is ready to play the game
"Scourge player is ready": Player 2 is ready to play the game
"Sentinal player has left": Player 1 has left the game
"Scourge player has left": Player 2 has left the game

For more information on session commands, see internal/coordinator/session.go