Add more JSON events
This commit is contained in:
parent
1c60039ed9
commit
2f09683339
7 changed files with 195 additions and 85 deletions
14
PROTOCOL.md
14
PROTOCOL.md
|
@ -1,4 +1,8 @@
|
|||
# Secification of bgammon protocol
|
||||
# Specification of bgammon.org protocol
|
||||
|
||||
Connect to `bgammon.org:1337` via TCP.
|
||||
|
||||
All commands and events are separated by newlines.
|
||||
|
||||
## User commands
|
||||
|
||||
|
@ -69,7 +73,7 @@ This command is not normally used, as the game state is provided in JSON format.
|
|||
|
||||
Disconnect from the server.
|
||||
|
||||
## Server responses
|
||||
## Events (server responses)
|
||||
|
||||
Data types:
|
||||
|
||||
|
@ -78,6 +82,10 @@ Data types:
|
|||
- `text` - alphanumeric without spaces
|
||||
- `line` - alphanumeric with spaces
|
||||
|
||||
All events are sent in either JSON or human-readable format. The human-readable
|
||||
format is documented here. The structure of each JSON message is available by referencing
|
||||
[this file](https://code.rocket9labs.com/tslocum/bgammon/src/branch/main/event.go).
|
||||
|
||||
### `hello <message:line>`
|
||||
|
||||
Initial welcome message sent by the server. It provides instructions on how to log in.
|
||||
|
@ -104,7 +112,7 @@ Game description.
|
|||
|
||||
End of games list.
|
||||
|
||||
### `joined <id:integer> <player:text> <name:line>`
|
||||
### `joined <id:integer> <player:text>`
|
||||
|
||||
Sent after successfully creating or joining a game, and when another player
|
||||
joins a game you are in.
|
||||
|
|
|
@ -1,16 +1,91 @@
|
|||
package main
|
||||
|
||||
import "code.rocket9labs.com/tslocum/bgammon"
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"code.rocket9labs.com/tslocum/bgammon"
|
||||
)
|
||||
|
||||
type serverClient struct {
|
||||
id int
|
||||
json bool
|
||||
name []byte
|
||||
account int
|
||||
connected int64
|
||||
lastActive int64
|
||||
lastPing int64
|
||||
commands <-chan []byte
|
||||
events chan<- []byte
|
||||
id int
|
||||
json bool
|
||||
name []byte
|
||||
account int
|
||||
connected int64
|
||||
lastActive int64
|
||||
lastPing int64
|
||||
commands <-chan []byte
|
||||
events chan<- []byte
|
||||
playerNumber int
|
||||
bgammon.Client
|
||||
}
|
||||
|
||||
func (c *serverClient) sendEvent(e interface{}) {
|
||||
if c.json {
|
||||
switch ev := e.(type) {
|
||||
case *bgammon.EventWelcome:
|
||||
ev.Type = "welcome"
|
||||
case *bgammon.EventNotice:
|
||||
ev.Type = "notice"
|
||||
case *bgammon.EventSay:
|
||||
ev.Type = "say"
|
||||
case *bgammon.EventList:
|
||||
ev.Type = "list"
|
||||
case *bgammon.EventJoined:
|
||||
ev.Type = "joined"
|
||||
case *bgammon.EventFailedJoin:
|
||||
ev.Type = "failedjoin"
|
||||
case *bgammon.EventBoard:
|
||||
ev.Type = "board"
|
||||
case *bgammon.EventRolled:
|
||||
ev.Type = "rolled"
|
||||
case *bgammon.EventMoved:
|
||||
ev.Type = "moved"
|
||||
}
|
||||
|
||||
buf, err := json.Marshal(e)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
c.events <- buf
|
||||
return
|
||||
}
|
||||
|
||||
switch ev := e.(type) {
|
||||
case *bgammon.EventWelcome:
|
||||
c.events <- []byte(fmt.Sprintf("welcome %s there are %d clients playing %d games.", ev.PlayerName, ev.Clients, ev.Games))
|
||||
case *bgammon.EventNotice:
|
||||
c.events <- []byte(fmt.Sprintf("notice %s", ev.Message))
|
||||
case *bgammon.EventSay:
|
||||
c.events <- []byte(fmt.Sprintf("say %s %s", ev.Player, ev.Message))
|
||||
case *bgammon.EventList:
|
||||
c.events <- []byte("liststart Games list:")
|
||||
for _, g := range ev.Games {
|
||||
password := 0
|
||||
if g.Password {
|
||||
password = 1
|
||||
}
|
||||
name := "Game"
|
||||
if g.Name != "" {
|
||||
name = g.Name
|
||||
}
|
||||
c.events <- []byte(fmt.Sprintf("game %d %d %d %s", g.ID, password, g.Players, name))
|
||||
}
|
||||
c.events <- []byte("listend End of games list.")
|
||||
case *bgammon.EventJoined:
|
||||
c.events <- []byte(fmt.Sprintf("joined %d %s", ev.GameID, ev.Player))
|
||||
case *bgammon.EventFailedJoin:
|
||||
c.events <- []byte(fmt.Sprintf("failedjoin %s", ev.Reason))
|
||||
case *bgammon.EventRolled:
|
||||
c.events <- []byte(fmt.Sprintf("rolled %s %d %d", ev.Player, ev.Roll1, ev.Roll2))
|
||||
case *bgammon.EventMoved:
|
||||
c.events <- []byte(fmt.Sprintf("moved %s %s", ev.Player, bgammon.FormatMoves(ev.Moves, c.playerNumber)))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *serverClient) sendNotice(message string) {
|
||||
c.sendEvent(&bgammon.EventNotice{
|
||||
Message: message,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ package main
|
|||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
|
@ -65,26 +64,21 @@ func (g *serverGame) sendBoard(client *serverClient) {
|
|||
}
|
||||
|
||||
if client.json {
|
||||
gameState := ServerGameState{
|
||||
ev := &bgammon.EventBoard{
|
||||
GameState: bgammon.GameState{
|
||||
Game: g.Game,
|
||||
Game: g.Game.Copy(),
|
||||
Available: g.LegalMoves(),
|
||||
},
|
||||
Board: g.Game.Board,
|
||||
}
|
||||
if playerNumber == 2 {
|
||||
log.Println(gameState.Board)
|
||||
log.Println(g.Game.Board)
|
||||
slices.Reverse(gameState.Board)
|
||||
/*log.Println(gameState.Board)
|
||||
log.Println(g.Game.Board)*/
|
||||
slices.Reverse(ev.Board)
|
||||
|
||||
log.Println(gameState.Board)
|
||||
log.Println(g.Game.Board)
|
||||
/*log.Println(gameState.Board)
|
||||
log.Println(g.Game.Board)*/
|
||||
}
|
||||
buf, err := json.Marshal(gameState)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to marshal json for %+v: %s", gameState, err)
|
||||
}
|
||||
client.events <- []byte(fmt.Sprintf("board %s", buf))
|
||||
client.sendEvent(ev)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -120,12 +114,18 @@ func (g *serverGame) addClient(client *serverClient) bool {
|
|||
if !ok {
|
||||
return
|
||||
}
|
||||
joinMessage := []byte(fmt.Sprintf("joined %d %s %s", g.id, client.name, g.name))
|
||||
client.events <- joinMessage
|
||||
|
||||
ev := &bgammon.EventJoined{
|
||||
GameID: g.id,
|
||||
}
|
||||
ev.Player = string(client.name)
|
||||
|
||||
client.sendEvent(ev)
|
||||
g.sendBoard(client)
|
||||
|
||||
opponent := g.opponent(client)
|
||||
if opponent != nil {
|
||||
opponent.events <- joinMessage
|
||||
opponent.sendEvent(ev)
|
||||
g.sendBoard(opponent)
|
||||
}
|
||||
}()
|
||||
|
@ -135,19 +135,23 @@ func (g *serverGame) addClient(client *serverClient) bool {
|
|||
case g.client1 != nil:
|
||||
g.client2 = client
|
||||
g.Player2.Name = string(client.name)
|
||||
client.playerNumber = 2
|
||||
ok = true
|
||||
case g.client2 != nil:
|
||||
g.client1 = client
|
||||
g.Player1.Name = string(client.name)
|
||||
client.playerNumber = 1
|
||||
ok = true
|
||||
default:
|
||||
i := rand.Intn(2)
|
||||
if i == 0 {
|
||||
g.client1 = client
|
||||
g.Player1.Name = string(client.name)
|
||||
client.playerNumber = 1
|
||||
} else {
|
||||
g.client2 = client
|
||||
g.Player2.Name = string(client.name)
|
||||
client.playerNumber = 2
|
||||
}
|
||||
ok = true
|
||||
}
|
||||
|
@ -163,6 +167,7 @@ func (g *serverGame) removeClient(client *serverClient) {
|
|||
if !ok {
|
||||
return
|
||||
}
|
||||
client.playerNumber = 0
|
||||
opponent := g.opponent(client)
|
||||
if opponent == nil {
|
||||
return
|
||||
|
|
|
@ -3,7 +3,6 @@ package main
|
|||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
|
@ -141,7 +140,6 @@ func (s *server) sendHello(c *serverClient) {
|
|||
}
|
||||
|
||||
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 {
|
||||
|
@ -209,7 +207,11 @@ COMMANDS:
|
|||
cmd.client.json = true
|
||||
}
|
||||
|
||||
s.sendWelcome(cmd.client)
|
||||
cmd.client.sendEvent(&bgammon.EventWelcome{
|
||||
PlayerName: string(cmd.client.name),
|
||||
Clients: len(s.clients),
|
||||
Games: len(s.games),
|
||||
})
|
||||
|
||||
log.Printf("login as %s - %s", username, password)
|
||||
continue
|
||||
|
@ -268,31 +270,16 @@ COMMANDS:
|
|||
}
|
||||
opponent.events <- []byte(fmt.Sprintf("say %s %s", cmd.client.name, bytes.Join(params, []byte(" "))))
|
||||
case bgammon.CommandList, "ls":
|
||||
if cmd.client.json {
|
||||
ev := bgammon.EventList{}
|
||||
for _, g := range s.games {
|
||||
ev.Games = append(ev.Games, bgammon.GameListing{
|
||||
ID: g.id,
|
||||
Password: len(g.password) != 0,
|
||||
Players: g.playerCount(),
|
||||
Name: string(g.name),
|
||||
})
|
||||
}
|
||||
buf, err := json.Marshal(ev)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
cmd.client.events <- buf
|
||||
continue
|
||||
}
|
||||
cmd.client.events <- []byte("liststart Games list:")
|
||||
players := 0
|
||||
password := 0
|
||||
name := "game name"
|
||||
ev := &bgammon.EventList{}
|
||||
for _, g := range s.games {
|
||||
cmd.client.events <- []byte(fmt.Sprintf("game %d %d %d %s", g.id, password, players, name))
|
||||
ev.Games = append(ev.Games, bgammon.GameListing{
|
||||
ID: g.id,
|
||||
Password: len(g.password) != 0,
|
||||
Players: g.playerCount(),
|
||||
Name: string(g.name),
|
||||
})
|
||||
}
|
||||
cmd.client.events <- []byte("listend End of games list.")
|
||||
cmd.client.sendEvent(ev)
|
||||
case bgammon.CommandCreate, "c":
|
||||
sendUsage := func() {
|
||||
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.")
|
||||
|
@ -319,8 +306,6 @@ COMMANDS:
|
|||
continue
|
||||
}
|
||||
|
||||
log.Printf("create game (password %s) name: %s", gamePassword, gameName)
|
||||
|
||||
g := newServerGame(<-s.newGameIDs)
|
||||
g.name = gameName
|
||||
g.password = gamePassword
|
||||
|
@ -330,12 +315,14 @@ COMMANDS:
|
|||
s.games = append(s.games, g) // TODO lock
|
||||
case bgammon.CommandJoin, "j":
|
||||
if clientGame != nil {
|
||||
cmd.client.events <- []byte("failedjoin Please leave the game you are in before joining another game.")
|
||||
cmd.client.sendEvent(&bgammon.EventFailedJoin{
|
||||
Reason: "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.")
|
||||
cmd.client.sendNotice("To join a public game specify its game ID. To join a private game, a password must also be specified.")
|
||||
}
|
||||
|
||||
if len(params) == 0 {
|
||||
|
@ -351,12 +338,16 @@ COMMANDS:
|
|||
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.")
|
||||
cmd.client.sendEvent(&bgammon.EventFailedJoin{
|
||||
Reason: "Invalid password.",
|
||||
})
|
||||
continue COMMANDS
|
||||
}
|
||||
|
||||
if !g.addClient(cmd.client) {
|
||||
cmd.client.events <- []byte("failedjoin Game is full.")
|
||||
cmd.client.sendEvent(&bgammon.EventFailedJoin{
|
||||
Reason: "Game is full.",
|
||||
})
|
||||
}
|
||||
continue COMMANDS
|
||||
}
|
||||
|
@ -390,15 +381,16 @@ COMMANDS:
|
|||
cmd.client.events <- []byte("notice It is not your turn to roll.")
|
||||
continue
|
||||
}
|
||||
ev := &bgammon.EventRolled{
|
||||
Roll1: clientGame.Roll1,
|
||||
Roll2: clientGame.Roll2,
|
||||
}
|
||||
ev.Player = string(cmd.client.name)
|
||||
clientGame.eachClient(func(client *serverClient) {
|
||||
roll1 := 0
|
||||
roll2 := 0
|
||||
if clientNumber == 1 {
|
||||
roll1 = clientGame.Roll1
|
||||
} else {
|
||||
roll2 = clientGame.Roll2
|
||||
client.sendEvent(ev)
|
||||
if !client.json {
|
||||
clientGame.sendBoard(client)
|
||||
}
|
||||
client.events <- []byte(fmt.Sprintf("rolled %s %d %d", cmd.client.name, roll1, roll2))
|
||||
})
|
||||
if clientGame.Turn == 0 && clientGame.Roll1 != 0 && clientGame.Roll2 != 0 {
|
||||
if clientGame.Roll1 > clientGame.Roll2 {
|
||||
|
@ -475,12 +467,12 @@ COMMANDS:
|
|||
}
|
||||
|
||||
clientGame.Moves = gameCopy.Moves
|
||||
ev := &bgammon.EventMoved{
|
||||
Moves: moves,
|
||||
}
|
||||
ev.Player = string(cmd.client.name)
|
||||
clientGame.eachClient(func(client *serverClient) {
|
||||
player := 1
|
||||
if clientGame.client2 == client {
|
||||
player = 2
|
||||
}
|
||||
client.events <- []byte(fmt.Sprintf("move %s %s", cmd.client.name, bgammon.FormatMoves(moves, player)))
|
||||
client.sendEvent(ev)
|
||||
if !client.json {
|
||||
clientGame.sendBoard(client)
|
||||
}
|
||||
|
|
31
event.go
31
event.go
|
@ -13,17 +13,24 @@ type Event struct {
|
|||
}
|
||||
|
||||
type EventWelcome struct {
|
||||
Event
|
||||
PlayerName string
|
||||
Clients int
|
||||
Games int
|
||||
}
|
||||
|
||||
type EventJoined struct {
|
||||
GameID int
|
||||
PlayerName string
|
||||
type EventNotice struct {
|
||||
Event
|
||||
Message string
|
||||
}
|
||||
|
||||
type EventSay struct {
|
||||
Event
|
||||
Message string
|
||||
}
|
||||
|
||||
type GameListing struct {
|
||||
Event
|
||||
ID int
|
||||
Password bool
|
||||
Players int
|
||||
|
@ -31,23 +38,33 @@ type GameListing struct {
|
|||
}
|
||||
|
||||
type EventList struct {
|
||||
Event
|
||||
Games []GameListing
|
||||
}
|
||||
|
||||
type EventSay struct {
|
||||
Message string
|
||||
type EventJoined struct {
|
||||
Event
|
||||
GameID int
|
||||
}
|
||||
|
||||
type EventFailedJoin struct {
|
||||
Event
|
||||
Reason string
|
||||
}
|
||||
|
||||
type EventBoard struct {
|
||||
Event
|
||||
GameState
|
||||
}
|
||||
|
||||
type EventRoll struct {
|
||||
type EventRolled struct {
|
||||
Event
|
||||
Roll1 int
|
||||
Roll2 int
|
||||
}
|
||||
|
||||
type EventMove struct {
|
||||
type EventMoved struct {
|
||||
Event
|
||||
Moves [][]int
|
||||
}
|
||||
|
||||
|
|
13
game.go
13
game.go
|
@ -31,6 +31,19 @@ func NewGame() *Game {
|
|||
}
|
||||
}
|
||||
|
||||
func (g *Game) Copy() *Game {
|
||||
newGame := &Game{
|
||||
Player1: g.Player1,
|
||||
Player2: g.Player2,
|
||||
Turn: g.Turn,
|
||||
Roll1: g.Roll1,
|
||||
Roll2: g.Roll2,
|
||||
}
|
||||
copy(newGame.Board, g.Board)
|
||||
copy(newGame.Moves, g.Moves)
|
||||
return newGame
|
||||
}
|
||||
|
||||
func (g *Game) turnPlayer() Player {
|
||||
switch g.Turn {
|
||||
case 2:
|
||||
|
|
|
@ -2,19 +2,19 @@ package bgammon
|
|||
|
||||
type GameState struct {
|
||||
*Game
|
||||
Player int
|
||||
Available [][]int // Legal moves.
|
||||
PlayerNumber int
|
||||
Available [][]int // Legal moves.
|
||||
}
|
||||
|
||||
func (g *GameState) OpponentPlayer() Player {
|
||||
if g.Player == 1 {
|
||||
if g.PlayerNumber == 1 {
|
||||
return g.Player2
|
||||
}
|
||||
return g.Player1
|
||||
}
|
||||
|
||||
func (g *GameState) LocalPlayer() Player {
|
||||
if g.Player == 1 {
|
||||
if g.PlayerNumber == 1 {
|
||||
return g.Player1
|
||||
}
|
||||
return g.Player2
|
||||
|
|
Loading…
Reference in a new issue