From cfbdd632a928f6a5ad82577a8d4f5b527120e2b7 Mon Sep 17 00:00:00 2001 From: Trevor Slocum Date: Sun, 20 Aug 2023 21:21:49 -0700 Subject: [PATCH] Add protocol specification --- PROTOCOL.md | 82 ++++++++++++++++++++++++++++++++++++ cmd/bgammon-server/server.go | 74 ++++++++++++++++++++++++++++++-- command.go | 19 ++++++--- game.go | 4 +- player.go | 6 +++ 5 files changed, 174 insertions(+), 11 deletions(-) create mode 100644 PROTOCOL.md diff --git a/PROTOCOL.md b/PROTOCOL.md new file mode 100644 index 0000000..3ec7ac0 --- /dev/null +++ b/PROTOCOL.md @@ -0,0 +1,82 @@ +# Secification of bgammon protocol + +## User commands + +Format: `command [optional argument]` + +### `login [username] [password]` + +Log in to bgammon. A random username is assigned when none is provided. + +This must be the first command sent when a client connects to bgammon. + +### `help [command]` + +Request help for all commands, or optionally a specific command. + +### `list` + +List all games. + +### `create [public/private] [password]` + +List all games. + +### `join [ID] [password]` + +Join game. + +### `roll` + +Roll dice. + +### `move [from-to]...` + +Move checkers. + +### `reset` + +Reset pending checker movement. + +### `ok` + +Confirm checker movement and pass turn to next player. + +### `disconnect` + +Disconnect from the server. + +## Server responses + +Data types: + +- `integer` a whole number +- `boolean` - `0` (representing false) or `1` (representing true) +- `text` - alphanumeric without spaces +- `line` - alphanumeric with spaces + +### `hello ` + +Initial welcome message sent by the server. It provides instructions on how to log in. + +This message does not normally need to be displayed when using a graphical client. + +### `welcome there are clients playing games.` + +Initial message sent by the server. + +### `notice ` + +Server message. This should always be displayed to the user. + +### `liststart Games list:` + +Start of games list. + +### `game ` + +Game description. + +### `listend End of games list.` + +End of games list. \ No newline at end of file diff --git a/cmd/bgammon-server/server.go b/cmd/bgammon-server/server.go index 603a045..e8028b8 100644 --- a/cmd/bgammon-server/server.go +++ b/cmd/bgammon-server/server.go @@ -6,6 +6,7 @@ import ( "log" "math/rand" "net" + "strconv" "strings" "time" @@ -124,8 +125,18 @@ func (s *server) sendWelcome(c *serverClient) { c.events <- []byte(fmt.Sprintf("welcome %s there are %d clients playing %d games.", c.name, len(s.clients), len(s.games))) } +func (s *server) gameByClient(c *serverClient) *serverGame { + for _, g := range s.games { + if g.client1 == c || g.client2 == c { + return g + } + } + return nil +} + func (s *server) handleCommands() { var cmd serverCommand +COMMANDS: for cmd = range s.commands { if cmd.client == nil { log.Panicf("nil client with command %s", cmd.command) @@ -186,6 +197,11 @@ func (s *server) handleCommands() { } switch keyword { + case bgammon.CommandHelp: + // TODO get extended help by specifying a command after help + cmd.client.events <- []byte("helpstart Help text:") + cmd.client.events <- []byte("help Test help text") + cmd.client.events <- []byte("helpend End of help text.") case bgammon.CommandSay: if len(params) == 0 { continue @@ -196,13 +212,12 @@ func (s *server) handleCommands() { password := 0 name := "game name" for _, g := range s.games { - cmd.client.events <- []byte(fmt.Sprintf("game %d %d %d %s", g.id, players, password, name)) + cmd.client.events <- []byte(fmt.Sprintf("game %d %d %d %s", g.id, password, players, name)) } cmd.client.events <- []byte("listend End of games list.") case bgammon.CommandCreate: sendUsage := func() { - cmd.client.events <- []byte("notice To create a public game specify whether it is public or private.") - cmd.client.events <- []byte("notice When creating a private game, a password must be set.") + cmd.client.events <- []byte("notice To create a public game specify whether it is public or private. When creating a private game, a password must also be provided.") } if len(params) == 0 { sendUsage() @@ -232,10 +247,63 @@ func (s *server) handleCommands() { g.name = gameName g.password = gamePassword + if rand.Intn(2) == 0 { // Start as black. + g.client1 = cmd.client + g.Player1.Name = string(cmd.client.name) + } else { // Start as white. + g.client2 = cmd.client + g.Player2.Name = string(cmd.client.name) + } + s.games = append(s.games, g) + + players := 1 + password := 0 + if len(gamePassword) != 0 { + password = 1 + } + cmd.client.events <- []byte(fmt.Sprintf("joined %d %d %d %s", g.id, password, players, gameName)) case bgammon.CommandJoin: + if s.gameByClient(cmd.client) != nil { + cmd.client.events <- []byte("failedjoin Please leave the game you are in before joining another game.") + continue + } + + sendUsage := func() { + cmd.client.events <- []byte("notice To join a public game specify its game ID. To join a private game, a password must also be specified.") + } + + if len(params) == 0 { + sendUsage() + continue + } + gameID, err := strconv.Atoi(string(params[1])) + if err != nil || gameID < 1 { + sendUsage() + continue + } + + for _, g := range s.games { + if g.id == gameID { + if len(g.password) != 0 && (len(params) < 2 || !bytes.Equal(g.password, bytes.Join(params[2:], []byte(" ")))) { + cmd.client.events <- []byte("failedjoin Invalid password.") + continue COMMANDS + } + + log.Printf("join existing game %+v", g) + // cmd.client.events <- []byte(fmt.Sprintf("joined %d %d %d %s", g.id, players, password, gameName)) + continue COMMANDS + } + } + case bgammon.CommandLeave: case bgammon.CommandRoll: case bgammon.CommandMove: + case bgammon.CommandDisconnect: + g := s.gameByClient(cmd.client) + if g != nil { + // todo leave game + } + cmd.client.Terminate("Client disconnected") default: log.Printf("unknown command %s", keyword) } diff --git a/command.go b/command.go index f4e6da7..3d42308 100644 --- a/command.go +++ b/command.go @@ -5,11 +5,16 @@ package bgammon type Command string const ( - CommandLogin = "login" // Log in with username and password, or as a guest. - CommandSay = "say" // Send chat message. - CommandList = "list" // List available games. - CommandCreate = "create" // Create game. - CommandJoin = "join" // Join game. - CommandRoll = "roll" // Roll dice. - CommandMove = "move" // Move checkers. + CommandLogin = "login" // Log in with username and password, or as a guest. + CommandHelp = "help" // Print help information. + CommandSay = "say" // Send chat message. + CommandList = "list" // List available games. + CommandCreate = "create" // Create game. + CommandJoin = "join" // Join game. + CommandLeave = "leave" // Leave game. + CommandRoll = "roll" // Roll dice. + CommandMove = "move" // Move checkers. + CommandReset = "reset" // Reset checker movement. + CommandOk = "ok" // Confirm checker movement and pass turn to next player. + CommandDisconnect = "disconnect" // Disconnect from server. ) diff --git a/game.go b/game.go index 84e579f..eeabbf3 100644 --- a/game.go +++ b/game.go @@ -13,7 +13,9 @@ type Game struct { func NewGame() *Game { return &Game{ - Board: NewBoard(), + Board: NewBoard(), + Player1: NewPlayer(1), + Player2: NewPlayer(2), } } diff --git a/player.go b/player.go index 542df95..6c8baa3 100644 --- a/player.go +++ b/player.go @@ -4,3 +4,9 @@ type Player struct { Number int // 1 black, 2 white Name string } + +func NewPlayer(number int) *Player { + return &Player{ + Number: number, + } +}