Documentation ¶
Overview ¶
Package game implements the main game controller and contains representations for a current game state. Every server instance is an instance of a single game coordinator, which is responsible for multiplexing over multiple different game instances with associated game state. Each game contains at least two players, each of which has an associated websocket connection.
To achieve this efficiently, a tiered flow of data is used. The GameCoordinator is a singleton and owns all game instances for a particular game server. Each game coordinator will always be handling zero or more games at once. Each game further contains at least two player instances at a time. Players are owned by a Game and handled by that instance alone.
Index ¶
- Constants
- Variables
- func FormatMessage(command string, data interface{}) string
- func ParseMessage(msg string, data interface{}) (string, error)
- func Score(correct bool, base, streak int, taken, allowed time.Duration) int64
- func StringMessage(msg string) (verb string, data string, err error)
- type Action
- type AddPlayer
- type Answer
- type Client
- func (c Client) Close()
- func (c Client) CloseReason(why string)
- func (c Client) Open()
- func (c Client) Read(buf []byte) (int, error)
- func (c Client) ReadMessage(data interface{}) (string, error)
- func (c Client) ReadString() (string, string, error)
- func (c Client) Send(msg string)
- func (c Client) SendMessage(verb string, body interface{})
- func (c Client) Write(msg []byte) (int, error)
- type ConnectHost
- type ConnectPlayer
- type ConnectionUpdate
- type Coordinator
- type EndAnswer
- type EndGame
- type Game
- type Host
- type KickPlayer
- type Leaderboard
- type NextQuestion
- type Pin
- type Player
- type PlayerInfo
- type SendResults
- type StartAnswer
- type StartGame
- type State
- type StateFunc
- type Status
Constants ¶
const ( CommandGameCount = "gcount" CommandQuestionCount = "count" CommandNewQuestion = "ques" CommandAnswerAck = "ansack" CommandQuestionOver = "qend" CommandSeeResults = "res" CommandFinalResults = "fres" CommandNewPlayer = "plr" CommandRemovePlayer = "rmplr" CommandDisconPlayer = "dcplr" CommandStartAck = "sack" CommandQuestionAck = "quack" CommandNewAnswer = "nans" )
WebSocket server message commands. Each websocket message is formatted as "<verb> <body>", where body is any valid JSON value, including literals (raw numbers or string literals) or composites (arrays and objects). This allows arbitrary Go data to be passed to the client. The client expects each field in the passed data to be lower case.
const ( MessageIdenfity = "ident" MessageAcknowledge = "ack" MessageAnswer = "ans" MessageKick = "kick" MessageCountdown = "count" MessageStartGame = "start" MessageNextQuestion = "next" MessageAnswerNow = "sans" MessageQuestionEnd = "time" )
WebSocket client message commands. Client equivalent of server message commands. See documentation for server message commands for more details on format.
const ( // MaxMessagesize is the maximum size of a client message in bytes. MaxMessagesize = 4096 // PongInterval is the time between keepalive pings. PongInterval = time.Second * 15 // PongTimeout is the maximum time allowed waiting for a keepalive pong. PongTimeout = time.Second * 10 )
Client mechanism constants.
const ( MinGamePin = 1111111111 MaxGamePin = 4294967295 )
Gameplay constants with no need for configuration.
const ( // Game is waiting for the host to connect. GameHostWaiting = iota // Game is waiting for sufficient players. GameWaiting // Game is currently live. GameRunning // Game is dead and waiting to be reaped. GameDead )
Possible game states.
const ( MaxGameTime = time.Minute * 45 MinPlayers = 3 BasePoints = 1000 StreakBonus = 100 MaxStreakBonus = 500 )
Gameplay constants.
Variables ¶
var ( ErrorConnectionClosed = fmt.Errorf("client: connection closed") ErrorBadMessageType = fmt.Errorf("client: binary message received") ErrorMalformedMessage = fmt.Errorf("client: invalid message syntax") )
Client errors.
Functions ¶
func FormatMessage ¶
FormatMessage returns the client-readable form of the message consisting of the verb "command" and arguments from data in JSON form.
func ParseMessage ¶
ParseMessage parses a formatted websocket message, exctracting the attached JSON data and the command verb, returning the verb and unmarshalling the data into "data". Any encontered errors are returned and are usually fatal to the client.
func Score ¶
Score returns the number of points that should be awarded for an answer. The rules which govern this are as follows:
- No points are awarded for an incorrect answer
- As the time taken to answer tends toward zero, points tend toward 2*base.
- 100 additional points are awarded for each correct answer in a row (streak), up to a maximum of 500 bonus streak points
- At half time, no bonus points are awarded. After half time, bonus points are negative up to -base (no points awarded at all, not counting bonuses)
This function does all calculations as floating points, but truncates the result in the end. If taken > allowed or taken < 0, Score panics (these must never be allowed to happen).
Types ¶
type Action ¶
type Action interface {
Perform(game *Game)
}
A Action is some action which can be sent to the game runner goroutine to perform an action on live game data, synchronised with the rest of the game. As these run on the runner thread, there is no need for any kind of locking. The only condition is that these actions MUST NEVER set game.sf to nil. Use game.GameEnding instead.
type AddPlayer ¶
AddPlayer allocates a new slot on the server for one player to join and returns an ID for this player, which is the index into the players array. This ID will then be used by the websocket to request to join the game.
type Client ¶
type Client struct { Connected bool Ctx context.Context Cancel context.CancelFunc // contains filtered or unexported fields }
A Client represents a connection from a generic client which is capable of both receiving and sending messages, handling pings and graceful disconnections.
func (Client) Close ¶
func (c Client) Close()
Close gracefully tears down the connection with a generic teardown message for the client.
func (Client) CloseReason ¶
CloseReason gracefully tears down the connection with the specified teardown message for the client.
func (Client) Open ¶
func (c Client) Open()
Open sets up the websocket connection for reading as a client. Among other things, it sets up the read deadline and PING subsystem handlers.
It is NOT necessary to call Open to use the client, and is often undesirable if you are not planning on taking ownership of this connection yet.
func (Client) Read ¶
Read reads the next client message into buf, handling any formatting errors and errors. Errors returned are fatal to the application and cannot be recovered. The application read loop must terminate after the first error.
func (Client) ReadMessage ¶
ReadMessage reads a single message from the client and parses according to the message passing scheme. Data arguments are unmarshalled into data. The command verb and any possible errors are returned.
func (Client) ReadString ¶
ReadString reads a single message from the client and returns the verb, data and any errors encountered. Errors returned are usually fatal to the client.
func (Client) Send ¶
Send transmits a message to the client through the websocket connection, blocking either until the message has sent or the connection terminates. This call CANNOT block forever.
func (Client) SendMessage ¶
SendMessage formats a message using FormatMessage and sends to the client.
type ConnectHost ¶
func (ConnectHost) Perform ¶
func (c ConnectHost) Perform(game *Game)
type ConnectPlayer ¶
ConnectPlayer initializes a player's connection, performs the startup handshake asynchronously. When this is complete, submits a new action to the game runner to update the player's state.
func (ConnectPlayer) Perform ¶
func (c ConnectPlayer) Perform(game *Game)
type ConnectionUpdate ¶
ConnectionUpdate submits a new connection state to the game loop.
Used to inform the game loop of a disconnection or re-connection, if appropriate. This does not remove the player from the player roster, but does make it possible for the player to re-connect and resume.
func (ConnectionUpdate) Perform ¶
func (c ConnectionUpdate) Perform(game *Game)
type Coordinator ¶
type Coordinator struct {
// contains filtered or unexported fields
}
Coordinator is responsible for managing all ongoing games in order to receive and delegate incoming events.
func NewCoordinator ¶
func NewCoordinator(maxGameTime time.Duration) Coordinator
NewCoordinator allocates and returns a new game coordinator with a blank initial game map.
func (*Coordinator) CreateGame ¶
func (c *Coordinator) CreateGame(q quiz.Quiz) Game
CreateGame creates a new game blank game with no players waiting for a host connection, generating a random PIN by continually regenerating a random PIN until a free one is found. If the maximum concurrent games are running, blocks until one is available (which hopefully should occur *very* rarely).
func (Coordinator) GameExists ¶
func (c Coordinator) GameExists(pin Pin) bool
GameExists checks if a game with the specified PIN is present in the game map.
type EndGame ¶
EndGame shuts down the game runner, thereby terminating the current game.
If the shutdown is clean, the state is merely shifted to the GameEnding state, which allows for the final leaderboard to be shown. If the shutdown was NOT clean, the state is immediately set to nil and the game runner shuts down on the spot. This is usually used when the host disconnects.
type Game ¶
type Game struct { PIN Pin quiz.Quiz Action chan Action Request chan chan State // contains filtered or unexported fields }
Game is a single instance of a running game.
func (*Game) AcceptAnswers ¶
AcceptAnswers is active when the game is idle accepting answers until
- Every player has answered
- The time runs out (host will notify us)
- The host manually skips the question (host will notify us)
func (*Game) GameEnding ¶
GameEnding is the state while we are showing the game end screen and results summary, after which the game runner can shut down. It accepts one more message, which is the host communicating that it is finished.
func (*Game) GameTerminate ¶
GameTerminate terminates the game loop on next iteration.
func (*Game) Question ¶
Question is active when the game is showing a question but BEFORE we are accepting answers. At the point where the countdown is finished, a snapshot of the current player state is taken such that new players do not disrupt the existing players' game.
func (*Game) Run ¶
func (game *Game) Run()
Run enters into the main game loop for this game instance, listening for events and mutating internal game state. Run blocks the calling goroutine until the game has terminated, at which point it will inform the GameCoordinator through the reaper channel it was initialised with.
func (*Game) WaitForHost ¶
WaitForHost is the state while the host is still in the process of connecting.
type Host ¶
type Host struct {
Client
}
The Host of a game is the client which receives incoming question texts and which handles time synchronisation for the rest of the game.
type KickPlayer ¶
type KickPlayer struct {
ID int
}
KickPlayer disconnects and bans a player ID from this game. This means the player will be disconnected and will be prevented from rejoining.
func (KickPlayer) Perform ¶
func (k KickPlayer) Perform(game *Game)
type Leaderboard ¶
type Leaderboard []PlayerInfo
Leaderboard represents a sorted leaderboard of players, sorted by score. It is designed for use with sort.Interface.
func NewLeaderboard ¶
func NewLeaderboard(plrs []Player) (l Leaderboard)
NewLeaderboard copies all players from plrs into a new leaderboard.
func (Leaderboard) Len ¶
func (l Leaderboard) Len() int
func (Leaderboard) Less ¶
func (l Leaderboard) Less(i, j int) bool
func (Leaderboard) Swap ¶
func (l Leaderboard) Swap(i, j int)
type NextQuestion ¶
type NextQuestion struct{}
func (NextQuestion) Perform ¶
func (n NextQuestion) Perform(game *Game)
type Pin ¶
type Pin uint32
Pin is the 10-digit PIN which can be used to join a game. It is constrained at an int32, as that is the smallest int type which provides enough space without allowing any higher inputs.
type Player ¶
type Player struct { Client ID int Nick string Score int64 Correct int Streak int Banned bool // contains filtered or unexported fields }
A Player is one registered player as part of a running game. Each player is a wrapper around a single websocket connection combined with a runner thread, which handles user-focused events, such as receiving answers or returning feedback.
All exported fields may be accessed concurrently, but all unexported - especially "conn" - are exclusively held by the player runner, which translates messages from the client into actions for the game runner and handles ping timeouts. These must never be used, except by the player runner thread.
func (Player) Info ¶
func (p Player) Info() PlayerInfo
Info extracts a player's information into a PlayerInfo struct, stuitable for websocket transmission to a client.
type PlayerInfo ¶
type PlayerInfo struct { ID int `json:"id"` Nick string `json:"name"` Score int64 `json:"score"` Correct int `json:"correct"` Streak int `json:"streak"` }
PlayerInfo is a message object, mirroring the PlayerData interface on the client. It should only be used for formatted transmission over a websocket.
type SendResults ¶
type SendResults struct{}
func (SendResults) Perform ¶
func (s SendResults) Perform(_ *Game)
type StartAnswer ¶
type StartAnswer struct{}
func (StartAnswer) Perform ¶
func (s StartAnswer) Perform(game *Game)
type StartGame ¶
type StartGame struct {
Count int
}
StartGame either begins a game or game countdown. If Count is > 0, the countdown state is activated. Else the game is immediately started.
type State ¶
type State struct { Status Status Host *Host Players []Player CurrentQuestion int // contains filtered or unexported fields }
State is the current state of an ongoing, running game instance. This is separated into a separate struct for ease of passing around combined game state to other objects, as well as to separate methods which act on the game itself and its state.