Add remaining JSON events
This commit is contained in:
parent
ce59fe7598
commit
7a7141d189
10 changed files with 324 additions and 190 deletions
14
PROTOCOL.md
14
PROTOCOL.md
|
@ -68,6 +68,11 @@ formatted responses are more easily parsed by computers.
|
|||
- This command is not normally used, as the game state is provided in JSON format.
|
||||
- *Aliases:* `b`
|
||||
|
||||
- `pong <message>`
|
||||
- Sent in response to server `ping` event to prevent the connection from timing out.
|
||||
- Whether the client sends a `pong` command, or any other command, clients
|
||||
must write some data to the server at least once every ten minutes.
|
||||
|
||||
- `disconnect`
|
||||
- Disconnect from the server.
|
||||
|
||||
|
@ -106,9 +111,9 @@ This document lists events in human-readable format.
|
|||
- `listend End of games list.`
|
||||
- End of games list.
|
||||
|
||||
- `joined <id:integer> <player:text>`
|
||||
- `joined <id:integer> <playerNumber:integer> <playerName:text>`
|
||||
- Sent after successfully creating or joining a game, and when another player
|
||||
joins a game you are in.
|
||||
joins a game you are in.
|
||||
- The server will always send a `board` response immediately after `joined` to
|
||||
provide clients with the initial game state.
|
||||
|
||||
|
@ -128,3 +133,8 @@ provide clients with the initial game state.
|
|||
|
||||
- `say <player:text> <message:line>`
|
||||
- Chat message from another player.
|
||||
|
||||
- `ping <message:text>`
|
||||
- Sent to clients to prevent their connection from timing out.
|
||||
- Whether the client replies with a `pong` command, or any other command,
|
||||
clients must write some data to the server at least once every ten minutes.
|
|
@ -1,6 +1,7 @@
|
|||
package bgammon
|
||||
|
||||
type Client interface {
|
||||
HandleReadWrite()
|
||||
Write(message []byte)
|
||||
Terminate(reason string)
|
||||
Terminated() bool
|
||||
|
|
|
@ -22,10 +22,13 @@ type serverClient struct {
|
|||
}
|
||||
|
||||
func (c *serverClient) sendEvent(e interface{}) {
|
||||
// JSON formatted messages.
|
||||
if c.json {
|
||||
switch ev := e.(type) {
|
||||
case *bgammon.EventWelcome:
|
||||
ev.Type = bgammon.EventTypeWelcome
|
||||
case *bgammon.EventHelp:
|
||||
ev.Type = bgammon.EventTypeHelp
|
||||
case *bgammon.EventPing:
|
||||
ev.Type = bgammon.EventTypePing
|
||||
case *bgammon.EventNotice:
|
||||
|
@ -38,12 +41,20 @@ func (c *serverClient) sendEvent(e interface{}) {
|
|||
ev.Type = bgammon.EventTypeJoined
|
||||
case *bgammon.EventFailedJoin:
|
||||
ev.Type = bgammon.EventTypeFailedJoin
|
||||
case *bgammon.EventLeft:
|
||||
ev.Type = bgammon.EventTypeLeft
|
||||
case *bgammon.EventBoard:
|
||||
ev.Type = bgammon.EventTypeBoard
|
||||
case *bgammon.EventRolled:
|
||||
ev.Type = bgammon.EventTypeRolled
|
||||
case *bgammon.EventFailedRoll:
|
||||
ev.Type = bgammon.EventTypeFailedRoll
|
||||
case *bgammon.EventMoved:
|
||||
ev.Type = bgammon.EventTypeMoved
|
||||
case *bgammon.EventFailedMove:
|
||||
ev.Type = bgammon.EventTypeFailedMove
|
||||
case *bgammon.EventFailedOk:
|
||||
ev.Type = bgammon.EventTypeFailedOk
|
||||
default:
|
||||
log.Panicf("unknown event type %+v", ev)
|
||||
}
|
||||
|
@ -56,9 +67,14 @@ func (c *serverClient) sendEvent(e interface{}) {
|
|||
return
|
||||
}
|
||||
|
||||
// Human-readable messages.
|
||||
switch ev := e.(type) {
|
||||
case *bgammon.EventWelcome:
|
||||
c.Write([]byte(fmt.Sprintf("welcome %s there are %d clients playing %d games.", ev.PlayerName, ev.Clients, ev.Games)))
|
||||
case *bgammon.EventHelp:
|
||||
c.Write([]byte("helpstart Help text:"))
|
||||
c.Write([]byte(fmt.Sprintf("help %s", ev.Message)))
|
||||
c.Write([]byte("helpend End of help text."))
|
||||
case *bgammon.EventPing:
|
||||
c.Write([]byte(fmt.Sprintf("ping %s", ev.Message)))
|
||||
case *bgammon.EventNotice:
|
||||
|
@ -80,13 +96,21 @@ func (c *serverClient) sendEvent(e interface{}) {
|
|||
}
|
||||
c.Write([]byte("listend End of games list."))
|
||||
case *bgammon.EventJoined:
|
||||
c.Write([]byte(fmt.Sprintf("joined %d %s", ev.GameID, ev.Player)))
|
||||
c.Write([]byte(fmt.Sprintf("joined %d %d %s", ev.GameID, ev.PlayerNumber, ev.Player)))
|
||||
case *bgammon.EventFailedJoin:
|
||||
c.Write([]byte(fmt.Sprintf("failedjoin %s", ev.Reason)))
|
||||
case *bgammon.EventLeft:
|
||||
c.Write([]byte(fmt.Sprintf("left %d %d %s", ev.GameID, ev.PlayerNumber, ev.Player)))
|
||||
case *bgammon.EventRolled:
|
||||
c.Write([]byte(fmt.Sprintf("rolled %s %d %d", ev.Player, ev.Roll1, ev.Roll2)))
|
||||
case *bgammon.EventFailedRoll:
|
||||
c.Write([]byte(fmt.Sprintf("failedroll %s", ev.Reason)))
|
||||
case *bgammon.EventMoved:
|
||||
c.Write([]byte(fmt.Sprintf("moved %s %s", ev.Player, bgammon.FormatMoves(ev.Moves, c.playerNumber))))
|
||||
case *bgammon.EventFailedMove:
|
||||
c.Write([]byte(fmt.Sprintf("failedmove %d/%d %s", ev.From, ev.To, ev.Reason)))
|
||||
case *bgammon.EventFailedOk:
|
||||
c.Write([]byte(fmt.Sprintf("failedok %s", ev.Reason)))
|
||||
default:
|
||||
log.Panicf("unknown event type %+v", ev)
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ package main
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"sync"
|
||||
|
@ -27,11 +26,18 @@ func newSocketClient(conn net.Conn, commands chan<- []byte, events chan []byte)
|
|||
events: events,
|
||||
commands: commands,
|
||||
}
|
||||
go c.readCommands()
|
||||
go c.writeEvents()
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *socketClient) HandleReadWrite() {
|
||||
if c.terminated {
|
||||
return
|
||||
}
|
||||
|
||||
go c.writeEvents()
|
||||
c.readCommands()
|
||||
}
|
||||
|
||||
func (c *socketClient) Write(message []byte) {
|
||||
if c.terminated {
|
||||
return
|
||||
|
@ -105,14 +111,12 @@ func (c *socketClient) Terminate(reason string) {
|
|||
return
|
||||
}
|
||||
c.terminated = true
|
||||
c.conn.Write([]byte(fmt.Sprintf("Connection closed: %s\n", reason)))
|
||||
c.conn.Close()
|
||||
go func() {
|
||||
time.Sleep(5 * time.Second)
|
||||
c.wgEvents.Wait()
|
||||
close(c.events)
|
||||
close(c.commands)
|
||||
log.Println("FINISHED CLEANUP")
|
||||
}()
|
||||
}
|
||||
|
||||
|
|
|
@ -3,10 +3,8 @@ package main
|
|||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"code.rocket9labs.com/tslocum/bgammon"
|
||||
|
@ -58,33 +56,35 @@ func (g *serverGame) roll(player int) bool {
|
|||
}
|
||||
|
||||
func (g *serverGame) sendBoard(client *serverClient) {
|
||||
playerNumber := 1
|
||||
if g.client2 == client {
|
||||
playerNumber = 2
|
||||
}
|
||||
|
||||
if client.json {
|
||||
ev := &bgammon.EventBoard{
|
||||
GameState: bgammon.GameState{
|
||||
Game: g.Game.Copy(),
|
||||
Available: g.LegalMoves(),
|
||||
Game: g.Game,
|
||||
PlayerNumber: client.playerNumber,
|
||||
Available: g.LegalMoves(),
|
||||
},
|
||||
}
|
||||
if playerNumber == 2 {
|
||||
/*log.Println(gameState.Board)
|
||||
log.Println(g.Game.Board)*/
|
||||
slices.Reverse(ev.Board)
|
||||
|
||||
/*log.Println(gameState.Board)
|
||||
log.Println(g.Game.Board)*/
|
||||
// Reverse spaces for white.
|
||||
if client.playerNumber == 2 {
|
||||
log.Println(g.Game.Board)
|
||||
|
||||
ev.GameState.Game = ev.GameState.Copy()
|
||||
for space := 1; space <= 24; space++ {
|
||||
ev.Board[space] = g.Game.Board[24-space+1]
|
||||
}
|
||||
|
||||
log.Println(g.Game.Board)
|
||||
log.Println("AFTER")
|
||||
}
|
||||
|
||||
client.sendEvent(ev)
|
||||
return
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(bytes.NewReader(g.BoardState(playerNumber)))
|
||||
scanner := bufio.NewScanner(bytes.NewReader(g.BoardState(client.playerNumber)))
|
||||
for scanner.Scan() {
|
||||
client.Write(append([]byte("notice "), scanner.Bytes()...))
|
||||
client.sendNotice(string(scanner.Bytes()))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -109,14 +109,15 @@ func (g *serverGame) eachClient(f func(client *serverClient)) {
|
|||
}
|
||||
|
||||
func (g *serverGame) addClient(client *serverClient) bool {
|
||||
var ok bool
|
||||
var playerNumber int
|
||||
defer func() {
|
||||
if !ok {
|
||||
if playerNumber == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
ev := &bgammon.EventJoined{
|
||||
GameID: g.id,
|
||||
GameID: g.id,
|
||||
PlayerNumber: playerNumber,
|
||||
}
|
||||
ev.Player = string(client.name)
|
||||
|
||||
|
@ -126,66 +127,86 @@ func (g *serverGame) addClient(client *serverClient) bool {
|
|||
opponent := g.opponent(client)
|
||||
if opponent != nil {
|
||||
opponent.sendEvent(ev)
|
||||
g.sendBoard(opponent)
|
||||
if !opponent.json {
|
||||
g.sendBoard(opponent)
|
||||
}
|
||||
}
|
||||
}()
|
||||
switch {
|
||||
case g.client1 != nil && g.client2 != nil:
|
||||
ok = false
|
||||
// Do not assign player number.
|
||||
case g.client1 != nil:
|
||||
g.client2 = client
|
||||
g.Player2.Name = string(client.name)
|
||||
client.playerNumber = 2
|
||||
ok = true
|
||||
playerNumber = 2
|
||||
case g.client2 != nil:
|
||||
g.client1 = client
|
||||
g.Player1.Name = string(client.name)
|
||||
client.playerNumber = 1
|
||||
ok = true
|
||||
playerNumber = 1
|
||||
default:
|
||||
i := rand.Intn(2)
|
||||
if i == 0 {
|
||||
g.client1 = client
|
||||
g.Player1.Name = string(client.name)
|
||||
client.playerNumber = 1
|
||||
playerNumber = 1
|
||||
} else {
|
||||
g.client2 = client
|
||||
g.Player2.Name = string(client.name)
|
||||
client.playerNumber = 2
|
||||
playerNumber = 2
|
||||
}
|
||||
ok = true
|
||||
}
|
||||
return ok
|
||||
return playerNumber != 0
|
||||
}
|
||||
|
||||
func (g *serverGame) removeClient(client *serverClient) {
|
||||
// TODO game is considered paused when only one player is present
|
||||
// once started, only the same player may join and continue the game
|
||||
log.Println("remove client", client)
|
||||
ok := true
|
||||
var playerNumber int
|
||||
defer func() {
|
||||
if !ok {
|
||||
if playerNumber == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
ev := &bgammon.EventLeft{
|
||||
GameID: g.id,
|
||||
PlayerNumber: client.playerNumber,
|
||||
}
|
||||
ev.Player = string(client.name)
|
||||
|
||||
client.sendEvent(ev)
|
||||
if !client.json {
|
||||
g.sendBoard(client)
|
||||
}
|
||||
|
||||
var opponent *serverClient
|
||||
if playerNumber == 1 && g.client2 != nil {
|
||||
opponent = g.client2
|
||||
} else if playerNumber == 2 && g.client1 != nil {
|
||||
opponent = g.client1
|
||||
}
|
||||
if opponent != nil {
|
||||
opponent.sendEvent(ev)
|
||||
if !opponent.json {
|
||||
g.sendBoard(opponent)
|
||||
}
|
||||
}
|
||||
|
||||
client.playerNumber = 0
|
||||
opponent := g.opponent(client)
|
||||
if opponent == nil {
|
||||
return
|
||||
}
|
||||
opponent.Write([]byte(fmt.Sprintf("left %d %s %s", g.id, client.name, g.name)))
|
||||
if !opponent.json {
|
||||
g.sendBoard(opponent)
|
||||
}
|
||||
}()
|
||||
switch {
|
||||
case g.client1 == client:
|
||||
g.client1 = nil
|
||||
g.Player1.Name = ""
|
||||
playerNumber = 1
|
||||
case g.client2 == client:
|
||||
g.client2 = nil
|
||||
g.Player2.Name = ""
|
||||
playerNumber = 2
|
||||
default:
|
||||
ok = false
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,43 +1,35 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"code.rocket9labs.com/tslocum/bgammon"
|
||||
)
|
||||
|
||||
func main() {
|
||||
b := bgammon.NewBoard()
|
||||
log.Printf("%+v", b)
|
||||
/*
|
||||
b := bgammon.NewBoard()
|
||||
g := newServerGame(1)
|
||||
g.Board[bgammon.SpaceBarPlayer] = 3
|
||||
g.Board[bgammon.SpaceBarOpponent] = -2
|
||||
g.Roll1 = 1
|
||||
g.Roll2 = 3
|
||||
g.Turn = 2
|
||||
log.Println("initial legal moves")
|
||||
log.Printf("%+v", g.LegalMoves())
|
||||
|
||||
//g.Moves = append(g.Moves, []int{6, 4})
|
||||
log.Printf("Legal moves after %+v", g.Moves)
|
||||
log.Printf("%+v", g.LegalMoves())
|
||||
|
||||
playerNumber := 2
|
||||
|
||||
go func() {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
scanner := bufio.NewScanner(bytes.NewReader(g.BoardState(playerNumber)))
|
||||
for scanner.Scan() {
|
||||
log.Printf("%s", append([]byte("notice "), scanner.Bytes()...))
|
||||
}
|
||||
|
||||
}()
|
||||
select {}
|
||||
*/
|
||||
|
||||
s := newServer()
|
||||
go s.listen("tcp", "127.0.0.1:1337")
|
||||
|
||||
g := newServerGame(1)
|
||||
g.Board[bgammon.SpaceBarPlayer] = 3
|
||||
g.Board[bgammon.SpaceBarOpponent] = -2
|
||||
g.Roll1 = 1
|
||||
g.Roll2 = 3
|
||||
g.Turn = 2
|
||||
log.Println("initial legal moves")
|
||||
log.Printf("%+v", g.LegalMoves())
|
||||
|
||||
//g.Moves = append(g.Moves, []int{6, 4})
|
||||
log.Printf("Legal moves after %+v", g.Moves)
|
||||
log.Printf("%+v", g.LegalMoves())
|
||||
|
||||
playerNumber := 2
|
||||
|
||||
go func() {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
scanner := bufio.NewScanner(bytes.NewReader(g.BoardState(playerNumber)))
|
||||
for scanner.Scan() {
|
||||
log.Printf("%s", append([]byte("notice "), scanner.Bytes()...))
|
||||
}
|
||||
|
||||
}()
|
||||
s.listen("tcp", "127.0.0.1:1337")
|
||||
select {}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
|
@ -9,6 +8,7 @@ import (
|
|||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"code.rocket9labs.com/tslocum/bgammon"
|
||||
|
@ -28,6 +28,8 @@ type server struct {
|
|||
newGameIDs chan int
|
||||
newClientIDs chan int
|
||||
commands chan serverCommand
|
||||
|
||||
clientsLock sync.RWMutex // TODO need RW?
|
||||
}
|
||||
|
||||
func newServer() *server {
|
||||
|
@ -63,6 +65,33 @@ func (s *server) handleListener(listener net.Listener) {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *server) addClient(c *serverClient) {
|
||||
s.clientsLock.Lock()
|
||||
defer s.clientsLock.Unlock()
|
||||
|
||||
s.clients = append(s.clients, c)
|
||||
}
|
||||
|
||||
func (s *server) removeClient(c *serverClient) {
|
||||
go func() {
|
||||
g := s.gameByClient(c)
|
||||
if g != nil {
|
||||
g.removeClient(c)
|
||||
}
|
||||
c.Terminate("")
|
||||
}()
|
||||
|
||||
s.clientsLock.Lock()
|
||||
defer s.clientsLock.Unlock()
|
||||
|
||||
for i, sc := range s.clients {
|
||||
if sc == c {
|
||||
s.clients = append(s.clients[:i], s.clients[i+1:]...)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) handleConnection(conn net.Conn) {
|
||||
log.Printf("new conn %+v", conn)
|
||||
|
||||
|
@ -80,12 +109,16 @@ func (s *server) handleConnection(conn net.Conn) {
|
|||
commands: commands,
|
||||
Client: newSocketClient(conn, commands, events),
|
||||
}
|
||||
log.Println("socket client", c)
|
||||
|
||||
s.sendHello(c)
|
||||
s.addClient(c)
|
||||
|
||||
go s.handlePingClient(c)
|
||||
s.handleClientCommands(c)
|
||||
go s.handleClientCommands(c)
|
||||
|
||||
c.HandleReadWrite()
|
||||
|
||||
// Remove client.
|
||||
s.removeClient(c)
|
||||
}
|
||||
|
||||
func (s *server) handlePingClient(c *serverClient) {
|
||||
|
@ -242,13 +275,13 @@ COMMANDS:
|
|||
switch keyword {
|
||||
case bgammon.CommandHelp, "h":
|
||||
// TODO get extended help by specifying a command after help
|
||||
cmd.client.Write([]byte("helpstart Help text:"))
|
||||
cmd.client.Write([]byte("help Test help text"))
|
||||
cmd.client.Write([]byte("helpend End of help text."))
|
||||
// TODO JSON format
|
||||
cmd.client.sendEvent(&bgammon.EventHelp{
|
||||
Topic: "",
|
||||
Message: "Test help text",
|
||||
})
|
||||
case bgammon.CommandJSON:
|
||||
sendUsage := func() {
|
||||
cmd.client.Write([]byte("notice To enable JSON formatted messages, send 'json on'. To disable JSON formatted messages, send 'json off'."))
|
||||
cmd.client.sendNotice("To enable JSON formatted messages, send 'json on'. To disable JSON formatted messages, send 'json off'.")
|
||||
}
|
||||
if len(params) != 1 {
|
||||
sendUsage()
|
||||
|
@ -258,10 +291,10 @@ COMMANDS:
|
|||
switch paramLower {
|
||||
case "on":
|
||||
cmd.client.json = true
|
||||
cmd.client.Write([]byte("json JSON formatted messages enabled.")) // TODO send in JSON format
|
||||
cmd.client.sendNotice("JSON formatted messages enabled.")
|
||||
case "off":
|
||||
cmd.client.json = false
|
||||
cmd.client.Write([]byte("json JSON formatted messages disabled."))
|
||||
cmd.client.sendNotice("JSON formatted messages disabled.")
|
||||
default:
|
||||
sendUsage()
|
||||
}
|
||||
|
@ -270,15 +303,19 @@ COMMANDS:
|
|||
continue
|
||||
}
|
||||
if clientGame == nil {
|
||||
cmd.client.Write([]byte("notice Message not sent. You are not currently in a game."))
|
||||
cmd.client.sendNotice("Message not sent. You are not currently in a game.")
|
||||
continue
|
||||
}
|
||||
opponent := clientGame.opponent(cmd.client)
|
||||
if opponent == nil {
|
||||
cmd.client.Write([]byte("notice Message not sent. There is no one else in the game."))
|
||||
cmd.client.sendNotice("Message not sent. There is no one else in the game.")
|
||||
continue
|
||||
}
|
||||
opponent.Write([]byte(fmt.Sprintf("say %s %s", cmd.client.name, bytes.Join(params, []byte(" ")))))
|
||||
ev := &bgammon.EventSay{
|
||||
Message: string(bytes.Join(params, []byte(" "))),
|
||||
}
|
||||
ev.Player = string(cmd.client.name)
|
||||
opponent.sendEvent(ev)
|
||||
case bgammon.CommandList, "ls":
|
||||
ev := &bgammon.EventList{}
|
||||
for _, g := range s.games {
|
||||
|
@ -292,7 +329,7 @@ COMMANDS:
|
|||
cmd.client.sendEvent(ev)
|
||||
case bgammon.CommandCreate, "c":
|
||||
sendUsage := func() {
|
||||
cmd.client.Write([]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."))
|
||||
cmd.client.sendNotice("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()
|
||||
|
@ -364,31 +401,25 @@ COMMANDS:
|
|||
}
|
||||
case bgammon.CommandLeave, "l":
|
||||
if clientGame == nil {
|
||||
cmd.client.Write([]byte("failedleave You are not currently in a game."))
|
||||
cmd.client.sendEvent(&bgammon.EventFailedLeave{
|
||||
Reason: "You are not currently in a game.",
|
||||
})
|
||||
continue
|
||||
}
|
||||
if clientGame.client1 == cmd.client {
|
||||
clientGame.client1 = nil
|
||||
} else {
|
||||
clientGame.client2 = nil
|
||||
}
|
||||
// TODO handle pausing or ending game
|
||||
// TODO move to .removeClient
|
||||
|
||||
leftMessage := []byte(fmt.Sprintf("left %s", cmd.client.name))
|
||||
cmd.client.Write(leftMessage)
|
||||
opponent := clientGame.opponent(cmd.client)
|
||||
if opponent != nil {
|
||||
opponent.Write(leftMessage)
|
||||
}
|
||||
clientGame.removeClient(cmd.client)
|
||||
case bgammon.CommandRoll, "r":
|
||||
if clientGame == nil {
|
||||
cmd.client.Write([]byte("notice You are not currently in a game."))
|
||||
cmd.client.sendEvent(&bgammon.EventFailedRoll{
|
||||
Reason: "You are not currently in a game.",
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
if !clientGame.roll(clientNumber) {
|
||||
cmd.client.Write([]byte("notice It is not your turn to roll."))
|
||||
cmd.client.sendEvent(&bgammon.EventFailedRoll{
|
||||
Reason: "It is not your turn to roll.",
|
||||
})
|
||||
continue
|
||||
}
|
||||
ev := &bgammon.EventRolled{
|
||||
|
@ -414,17 +445,23 @@ COMMANDS:
|
|||
}
|
||||
case bgammon.CommandMove, "m", "mv":
|
||||
if clientGame == nil {
|
||||
cmd.client.Write([]byte("failedmove You are not currently in a game."))
|
||||
cmd.client.sendEvent(&bgammon.EventFailedMove{
|
||||
Reason: "You are not currently in a game.",
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
if clientGame.Turn != clientNumber {
|
||||
cmd.client.Write([]byte("failedmove It is not your turn to move."))
|
||||
cmd.client.sendEvent(&bgammon.EventFailedMove{
|
||||
Reason: "It is not your turn to move.",
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
sendUsage := func() {
|
||||
cmd.client.Write([]byte("failedmove Specify one or more moves in the form FROM/TO. For example: 8/4 6/4"))
|
||||
cmd.client.sendEvent(&bgammon.EventFailedMove{
|
||||
Reason: "Specify one or more moves in the form FROM/TO. For example: 8/4 6/4",
|
||||
})
|
||||
}
|
||||
|
||||
if len(params) == 0 {
|
||||
|
@ -467,7 +504,11 @@ COMMANDS:
|
|||
}
|
||||
if !found {
|
||||
log.Printf("available legal moves: %s", bgammon.FormatMoves(legalMoves, clientNumber))
|
||||
cmd.client.Write([]byte(fmt.Sprintf("failedmove %d/%d Illegal move.", originalFrom, originalTo)))
|
||||
cmd.client.sendEvent(&bgammon.EventFailedMove{
|
||||
From: originalFrom,
|
||||
To: originalTo,
|
||||
Reason: "Illegal move.",
|
||||
})
|
||||
continue COMMANDS
|
||||
}
|
||||
|
||||
|
@ -476,20 +517,26 @@ COMMANDS:
|
|||
gameCopy.Moves = append(gameCopy.Moves, move)
|
||||
}
|
||||
|
||||
clientGame.Moves = gameCopy.Moves
|
||||
if !clientGame.AddMoves(moves) {
|
||||
log.Panicf("FAILED TO ADD MOVES")
|
||||
cmd.client.sendEvent(&bgammon.EventFailedMove{
|
||||
From: 0,
|
||||
To: 0,
|
||||
Reason: "Illegal move.",
|
||||
})
|
||||
continue
|
||||
}
|
||||
ev := &bgammon.EventMoved{
|
||||
Moves: moves,
|
||||
}
|
||||
ev.Player = string(cmd.client.name)
|
||||
clientGame.eachClient(func(client *serverClient) {
|
||||
client.sendEvent(ev)
|
||||
if !client.json {
|
||||
clientGame.sendBoard(client)
|
||||
}
|
||||
clientGame.sendBoard(client)
|
||||
})
|
||||
case bgammon.CommandOk, "k":
|
||||
if clientGame == nil {
|
||||
cmd.client.Write([]byte("notice You are not currently in a game."))
|
||||
cmd.client.sendNotice("You are not currently in a game.")
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -499,21 +546,20 @@ COMMANDS:
|
|||
if clientGame.client2 == cmd.client {
|
||||
playerNumber = 2
|
||||
}
|
||||
cmd.client.Write([]byte(fmt.Sprintf("failedok You still have the following legal moves available: %s", bgammon.FormatMoves(legalMoves, playerNumber))))
|
||||
cmd.client.sendEvent(&bgammon.EventFailedOk{
|
||||
Reason: fmt.Sprintf("The following legal moves are available: %s", bgammon.FormatMoves(legalMoves, playerNumber)),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
log.Println("legal to pass turn")
|
||||
case bgammon.CommandBoard, "b":
|
||||
if clientGame == nil {
|
||||
cmd.client.Write([]byte("notice You are not currently in a game."))
|
||||
cmd.client.sendNotice("You are not currently in a game.")
|
||||
continue
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(bytes.NewReader(clientGame.BoardState(clientNumber)))
|
||||
for scanner.Scan() {
|
||||
cmd.client.Write(append([]byte("notice "), scanner.Bytes()...))
|
||||
}
|
||||
clientGame.sendBoard(cmd.client)
|
||||
case bgammon.CommandDisconnect:
|
||||
if clientGame != nil {
|
||||
clientGame.removeClient(cmd.client)
|
||||
|
|
|
@ -27,13 +27,18 @@ type EventType string
|
|||
|
||||
const (
|
||||
EventTypeWelcome = "welcome"
|
||||
EventTypeHelp = "help"
|
||||
EventTypePing = "ping"
|
||||
EventTypeNotice = "notice"
|
||||
EventTypeSay = "say"
|
||||
EventTypeList = "list"
|
||||
EventTypeJoined = "joined"
|
||||
EventTypeFailedJoin = "failedjoin"
|
||||
EventTypeLeft = "left"
|
||||
EventTypeBoard = "board"
|
||||
EventTypeRolled = "rolled"
|
||||
EventTypeFailedRoll = "failedroll"
|
||||
EventTypeMoved = "moved"
|
||||
EventTypeFailedMove = "failedmove"
|
||||
EventTypeFailedOk = "failedok"
|
||||
)
|
||||
|
|
125
event.go
125
event.go
|
@ -19,6 +19,12 @@ type EventWelcome struct {
|
|||
Games int
|
||||
}
|
||||
|
||||
type EventHelp struct {
|
||||
Event
|
||||
Topic string
|
||||
Message string
|
||||
}
|
||||
|
||||
type EventPing struct {
|
||||
Event
|
||||
Message string
|
||||
|
@ -49,7 +55,8 @@ type EventList struct {
|
|||
|
||||
type EventJoined struct {
|
||||
Event
|
||||
GameID int
|
||||
GameID int
|
||||
PlayerNumber int
|
||||
}
|
||||
|
||||
type EventFailedJoin struct {
|
||||
|
@ -57,6 +64,17 @@ type EventFailedJoin struct {
|
|||
Reason string
|
||||
}
|
||||
|
||||
type EventLeft struct {
|
||||
Event
|
||||
GameID int
|
||||
PlayerNumber int
|
||||
}
|
||||
|
||||
type EventFailedLeave struct {
|
||||
Event
|
||||
Reason string
|
||||
}
|
||||
|
||||
type EventBoard struct {
|
||||
Event
|
||||
GameState
|
||||
|
@ -68,89 +86,74 @@ type EventRolled struct {
|
|||
Roll2 int
|
||||
}
|
||||
|
||||
type EventFailedRoll struct {
|
||||
Event
|
||||
Reason string
|
||||
}
|
||||
|
||||
type EventMoved struct {
|
||||
Event
|
||||
Moves [][]int
|
||||
}
|
||||
|
||||
type EventFailedMove struct {
|
||||
Event
|
||||
From int
|
||||
To int
|
||||
Reason string
|
||||
}
|
||||
|
||||
type EventFailedOk struct {
|
||||
Event
|
||||
Reason string
|
||||
}
|
||||
|
||||
func DecodeEvent(message []byte) (interface{}, error) {
|
||||
e := &Event{}
|
||||
err := json.Unmarshal(message, e)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ev interface{}
|
||||
switch e.Type {
|
||||
case EventTypeWelcome:
|
||||
ev := &EventWelcome{}
|
||||
err = json.Unmarshal(message, ev)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ev, nil
|
||||
ev = &EventWelcome{}
|
||||
case EventTypeHelp:
|
||||
ev = &EventHelp{}
|
||||
case EventTypePing:
|
||||
ev := &EventPing{}
|
||||
err = json.Unmarshal(message, ev)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ev, nil
|
||||
ev = &EventPing{}
|
||||
case EventTypeNotice:
|
||||
ev := &EventNotice{}
|
||||
err = json.Unmarshal(message, ev)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ev, nil
|
||||
ev = &EventNotice{}
|
||||
case EventTypeSay:
|
||||
ev := &EventSay{}
|
||||
err = json.Unmarshal(message, ev)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ev, nil
|
||||
ev = &EventSay{}
|
||||
case EventTypeList:
|
||||
ev := &EventList{}
|
||||
err = json.Unmarshal(message, ev)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ev, nil
|
||||
ev = &EventList{}
|
||||
case EventTypeJoined:
|
||||
ev := &EventJoined{}
|
||||
err = json.Unmarshal(message, ev)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ev, nil
|
||||
ev = &EventJoined{}
|
||||
case EventTypeFailedJoin:
|
||||
ev := &EventFailedJoin{}
|
||||
err = json.Unmarshal(message, ev)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ev, nil
|
||||
ev = &EventFailedJoin{}
|
||||
case EventTypeLeft:
|
||||
ev = &EventLeft{}
|
||||
case EventTypeBoard:
|
||||
ev := &EventBoard{}
|
||||
err = json.Unmarshal(message, ev)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ev, nil
|
||||
ev = &EventBoard{}
|
||||
case EventTypeRolled:
|
||||
ev := &EventRolled{}
|
||||
err = json.Unmarshal(message, ev)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ev, nil
|
||||
ev = &EventRolled{}
|
||||
case EventTypeFailedRoll:
|
||||
ev = &EventFailedRoll{}
|
||||
case EventTypeMoved:
|
||||
ev := &EventMoved{}
|
||||
err = json.Unmarshal(message, ev)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ev, nil
|
||||
ev = &EventMoved{}
|
||||
case EventTypeFailedMove:
|
||||
ev = &EventFailedMove{}
|
||||
case EventTypeFailedOk:
|
||||
ev = &EventFailedOk{}
|
||||
default:
|
||||
return nil, fmt.Errorf("failed to decode event: unknown event type: %s", e.Type)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(message, ev)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ev, nil
|
||||
}
|
||||
|
|
30
game.go
30
game.go
|
@ -83,6 +83,35 @@ func (g *Game) iterateSpaces(from int, to int, f func(space int, spaceCount int)
|
|||
}
|
||||
}
|
||||
|
||||
func (g *Game) AddMoves(moves [][]int) bool {
|
||||
original := make([]int, len(g.Board))
|
||||
copy(original, g.Board)
|
||||
ADDMOVES:
|
||||
for _, move := range moves {
|
||||
l := g.LegalMoves()
|
||||
for _, lm := range l {
|
||||
if lm[0] == move[0] && lm[1] == move[1] {
|
||||
delta := 1
|
||||
if g.Turn == 2 {
|
||||
delta = -1
|
||||
}
|
||||
lm[0] -= delta
|
||||
opponentCheckers := numOpponentCheckers(g.Board[lm[1]], g.Turn)
|
||||
if opponentCheckers == 1 {
|
||||
lm[1] = delta
|
||||
} else {
|
||||
lm[1] += delta
|
||||
}
|
||||
continue ADDMOVES
|
||||
}
|
||||
}
|
||||
g.Board = original
|
||||
return false
|
||||
}
|
||||
g.Moves = append(g.Moves, moves...)
|
||||
return true
|
||||
}
|
||||
|
||||
func (g *Game) LegalMoves() [][]int {
|
||||
if g.Roll1 == 0 || g.Roll2 == 0 {
|
||||
return nil
|
||||
|
@ -126,7 +155,6 @@ func (g *Game) LegalMoves() [][]int {
|
|||
}
|
||||
|
||||
for _, move := range g.Moves {
|
||||
log.Println("use move", move)
|
||||
useDiceRoll(move[0], move[1])
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue