Add protocol specification

This commit is contained in:
Trevor Slocum 2023-08-20 21:21:49 -07:00
parent b0ffa930b7
commit cfbdd632a9
5 changed files with 174 additions and 11 deletions

82
PROTOCOL.md Normal file
View file

@ -0,0 +1,82 @@
# Secification of bgammon protocol
## User commands
Format: `command <required argument> [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> [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 <message:line>`
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 <name:text> there are <clients:integer> clients playing <games:integer> games.`
Initial message sent by the server.
### `notice <message:line>`
Server message. This should always be displayed to the user.
### `liststart Games list:`
Start of games list.
### `game <ID:integer> <password:boolean> <players:integer> <name:line>`
Game description.
### `listend End of games list.`
End of games list.

View file

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

View file

@ -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.
)

View file

@ -13,7 +13,9 @@ type Game struct {
func NewGame() *Game {
return &Game{
Board: NewBoard(),
Board: NewBoard(),
Player1: NewPlayer(1),
Player2: NewPlayer(2),
}
}

View file

@ -4,3 +4,9 @@ type Player struct {
Number int // 1 black, 2 white
Name string
}
func NewPlayer(number int) *Player {
return &Player{
Number: number,
}
}