Add remaining JSON events

This commit is contained in:
Trevor Slocum 2023-08-27 14:10:18 -07:00
parent ce59fe7598
commit 7a7141d189
10 changed files with 324 additions and 190 deletions

View file

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

View file

@ -1,6 +1,7 @@
package bgammon
type Client interface {
HandleReadWrite()
Write(message []byte)
Terminate(reason string)
Terminated() bool

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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
View file

@ -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
View file

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