diff --git a/client.go b/client.go index be78e5b..970656d 100644 --- a/client.go +++ b/client.go @@ -10,6 +10,7 @@ import ( "net" "os" "regexp" + "sort" "strings" "time" @@ -149,8 +150,7 @@ func (c *Client) handleWebSocketRead(conn *websocket.Conn) { ev, err := bgammon.DecodeEvent(msg) if err != nil { log.Printf("error: failed to parse message: %s", msg) - conn.Close(websocket.StatusNormalClosure, "Read error") - return + continue } c.Events <- ev @@ -252,8 +252,7 @@ func (c *Client) handleTCPRead(conn *net.TCPConn) { ev, err := bgammon.DecodeEvent(scanner.Bytes()) if err != nil { log.Printf("error: failed to parse message: %s", scanner.Bytes()) - conn.Close() - return + continue } c.Events <- ev @@ -376,14 +375,37 @@ func (c *Client) handleEvents() { *Game = ev.GameState *Game.Game = *ev.GameState.Game + g := Game - if Game.DoubleOffered && Game.DoublePlayer != Game.PlayerNumber { - panic("TODO") + if Game.DoubleOffered { + if g.Turn == Game.PlayerNumber { + continue + } + moves, err := analyze(Game.Game) + if err != nil { + c.Out <- []byte("say failed to communicate with gnubg service") + time.Sleep(100 * time.Millisecond) + log.Fatalf("failed to communicate with gnubg service: %s", err) + } else if moves[0][0] == -1 && moves[0][1] == -1 { + c.Out <- []byte("ok") + } else { + c.Out <- []byte("resign") + } + c.lastActivity = time.Now() + continue } if Game.Roll1 == 0 && len(Game.Player2.Name) != 0 && (Game.Turn == 0 || Game.Turn == Game.PlayerNumber) && Game.MayRoll() { if Game.MayDouble() { - panic("TODO") + moves, err := analyze(Game.Game) + if err != nil { + c.Out <- []byte("say failed to communicate with gnubg service") + time.Sleep(100 * time.Millisecond) + log.Fatalf("failed to communicate with gnubg service: %s", err) + } else if moves[0][0] == -2 && moves[0][1] == -2 { + c.Out <- []byte("double") + continue + } } c.Out <- []byte("r") c.rolled = true @@ -411,12 +433,45 @@ func (c *Client) handleEvents() { continue } + var mv [][2]int8 for i := 0; i < 4; i++ { from, to := moves[i][0], moves[i][1] if from == 0 && to == 0 { break } else if from == 0 || from == 25 { + mv = append(mv, [2]int8{from, to}) + } else if to == 0 || to == 25 { + diff := bgammon.SpaceDiff(from, to, g.Variant) + if diff > 6 { + c := from - g.Roll1 + last := from + for c > to { + if bgammon.OpponentCheckers(g.Board[c], 1) <= 1 { + mv = append(mv, [2]int8{last, c}) + last = c + } + c -= g.Roll1 + } + mv = append(mv, [2]int8{last, to}) + } else { + mv = append(mv, [2]int8{from, to}) + } + } else { + mv = append(mv, [2]int8{from, to}) + } + } + sort.Slice(mv, func(i int, j int) bool { + if mv[i][0] == mv[j][0] { + return mv[i][1] > mv[j][1] + } + return mv[i][0] > mv[j][0] + }) + for _, m := range mv { + from, to := m[0], m[1] + if from == 0 || from == 25 { c.Out <- []byte(fmt.Sprintf("mv bar/%d", to)) + } else if to == 0 || to == 25 { + c.Out <- []byte(fmt.Sprintf("mv %d/off", from)) } else { c.Out <- []byte(fmt.Sprintf("mv %d/%d", from, to)) } diff --git a/gnubg.go b/gnubg.go index 70d527e..11ac202 100644 --- a/gnubg.go +++ b/gnubg.go @@ -8,6 +8,7 @@ import ( "log" "os/exec" "strconv" + "strings" "time" "code.rocket9labs.com/tslocum/bgammon" @@ -224,9 +225,11 @@ func analyze(g *bgammon.Game) ([4][2]int8, error) { scanner := bufio.NewScanner(stdout) for scanner.Scan() { buf := scanner.Bytes() - if bytes.Contains(buf, []byte("The dice have been set")) { + if bytes.Contains(buf, []byte("The dice have been set")) || bytes.Contains(buf, []byte("The cube has been set")) { setDice = true - } else if setDice && (bytes.Contains(buf, []byte("gnubg moves")) || bytes.Contains(buf, []byte("gnubg offers to resign"))) { + } else if setDice && moves == nil && (bytes.Contains(buf, []byte("gnubg moves")) || bytes.Contains(buf, []byte("gnubg doubles")) || bytes.Contains(buf, []byte("gnubg accepts")) || bytes.Contains(buf, []byte("gnubg refuses the cube")) || bytes.Contains(buf, []byte("gnubg offers to resign"))) { + moves = buf + } else if setDice && moves == nil && bytes.Contains(buf, []byte("Rolled")) { moves = buf } log.Println(string(buf)) @@ -240,7 +243,23 @@ func analyze(g *bgammon.Game) ([4][2]int8, error) { } }() - stdin.Write([]byte(fmt.Sprintf("set automatic roll off\nset automatic move off\nnew game\nset board %s\nset turn 0\nset dice %d %d\nplay\n", gnubgPosition(g), g.Roll1, g.Roll2))) + var extra []string + if g.DoublePlayer != 0 { + extra = append(extra, fmt.Sprintf("set cube owner %d", g.DoublePlayer-1)) + } + if g.DoubleValue != 0 { + extra = append(extra, fmt.Sprintf("set cube value %d", g.DoubleValue)) + } + if g.DoubleOffered { + extra = append(extra, "double") + } else if g.Roll1 != 0 && g.Roll2 != 0 { + extra = append(extra, fmt.Sprintf("set dice %d %d", g.Roll1, g.Roll2)) + extra = append(extra, "play") + } else { + extra = append(extra, "play") + } + + stdin.Write([]byte(fmt.Sprintf("set automatic roll off\nset automatic move off\nnew game\nset beavers 0\nset score %d %d %d\nset board %s\nset turn %d\n%s\n", g.Player1.Points, g.Player2.Points, g.Points, gnubgPosition(g), g.Turn-1, strings.Join(extra, "\n")))) t := time.NewTicker(50 * time.Millisecond) for { <-t.C @@ -256,11 +275,26 @@ func analyze(g *bgammon.Game) ([4][2]int8, error) { return [4][2]int8{}, err } - resign := bytes.Contains(moves, []byte("gnubg offers to resign")) - if resign { + acceptCube := bytes.Contains(moves, []byte("gnubg accepts")) + if acceptCube { return [4][2]int8{{-1, -1}}, nil } + double := bytes.Contains(moves, []byte("gnubg doubles")) + if double { + return [4][2]int8{{-2, -2}}, nil + } + + rollDice := bytes.Contains(moves, []byte("Rolled")) + if rollDice { + return [4][2]int8{{-3, -3}}, nil + } + + resign := bytes.Contains(moves, []byte("gnubg offers to resign")) || bytes.Contains(moves, []byte("gnubg refuses the cube")) + if resign { + return [4][2]int8{{-4, -4}}, nil + } + movesWord := bytes.Index(moves, []byte("moves")) if movesWord == -1 { return [4][2]int8{}, fmt.Errorf("failed to parse gnubg moves: %s", moves) diff --git a/go.mod b/go.mod index 56f6b40..7e91ab5 100644 --- a/go.mod +++ b/go.mod @@ -5,11 +5,12 @@ go 1.23 toolchain go1.23.2 require ( - code.rocket9labs.com/tslocum/bgammon v0.0.0-20241118091417-1188379900a1 + code.rocket9labs.com/tslocum/bgammon v0.0.0-20241120221233-00e4e4da52c2 nhooyr.io/websocket v1.8.17 ) require ( code.rocket9labs.com/tslocum/bei v0.0.0-20240108012722-6db380cc190b // indirect - code.rocket9labs.com/tslocum/tabula v0.0.0-20241024013344-d112a9463c51 // indirect + code.rocket9labs.com/tslocum/tabula v0.0.0-20241123064436-a9529056c6e2 // indirect + golang.org/x/text v0.20.0 // indirect ) diff --git a/go.sum b/go.sum index 41469c5..a269adb 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,10 @@ code.rocket9labs.com/tslocum/bei v0.0.0-20240108012722-6db380cc190b h1:Y0a14Kf/hSYepSmp4ZfDeE4CZZGBGBS97CNjCbKJm0c= code.rocket9labs.com/tslocum/bei v0.0.0-20240108012722-6db380cc190b/go.mod h1:tS60/VNAJphKvDBkSLQhKALa15msIAuWWfEKNc4oFZc= -code.rocket9labs.com/tslocum/bgammon v0.0.0-20241118091417-1188379900a1 h1:X0sTclnDOUSv0EOxwwBYWUnrq7kXFpfSvlDI/H3T7Fk= -code.rocket9labs.com/tslocum/bgammon v0.0.0-20241118091417-1188379900a1/go.mod h1:FzdCJoZoG/6nkxLl6OufdfFTXggr5ZArtl7eJ/CCcj0= -code.rocket9labs.com/tslocum/tabula v0.0.0-20241024013344-d112a9463c51 h1:JkjRKoSSmtHFSVIOAZV6e+Z+7r0mIxClsDpJxC/ZlOs= -code.rocket9labs.com/tslocum/tabula v0.0.0-20241024013344-d112a9463c51/go.mod h1:WEJXESKXqrMFLAArikQ79lpRibNeeE1C0VruxXYMF5M= +code.rocket9labs.com/tslocum/bgammon v0.0.0-20241120221233-00e4e4da52c2 h1:sRRgE5J+OsvLgHgJeO4pI2yraGO+6VwdfoXF4Zc+fA0= +code.rocket9labs.com/tslocum/bgammon v0.0.0-20241120221233-00e4e4da52c2/go.mod h1:FzdCJoZoG/6nkxLl6OufdfFTXggr5ZArtl7eJ/CCcj0= +code.rocket9labs.com/tslocum/tabula v0.0.0-20241123064436-a9529056c6e2 h1:yqzMXKgBx+3/e4DbZNwcuNNjVpRTGF0+FKvO55F81og= +code.rocket9labs.com/tslocum/tabula v0.0.0-20241123064436-a9529056c6e2/go.mod h1:+yChyzCoVWgw6wMQrfh/9EQsB3bMkM21i/33C9e72+c= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= nhooyr.io/websocket v1.8.17 h1:KEVeLJkUywCKVsnLIDlD/5gtayKp8VoCkksHCGGfT9Y= nhooyr.io/websocket v1.8.17/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c= diff --git a/main.go b/main.go index 11fcb61..485314d 100644 --- a/main.go +++ b/main.go @@ -23,6 +23,16 @@ func main() { } //moves, err := parseMoves([]byte("10/off(2) 7/2(2)")) + /*g := bgammon.NewGame(bgammon.VariantBackgammon) + for i := range g.Board { + g.Board[i] = 0 + } + g.Board[1] = 1 + g.Board[24] = -1 + g.Turn = 2 + g.DoublePlayer = 2 + g.DoubleOffered = true + log.Fatal(analyze(g))*/ c := newClient(serverAddress, username, password, points)