tabula/bei.go

207 lines
4.7 KiB
Go

package tabula
import (
"bufio"
"bytes"
"fmt"
"log"
"net"
"strconv"
"code.rocket9labs.com/tslocum/bei"
)
type BEIServer struct {
}
func NewBEIServer() *BEIServer {
return &BEIServer{}
}
func (s *BEIServer) handleConnection(conn net.Conn) {
analysis := make([]*Analysis, 0, AnalysisBufferSize)
var beiCommand bool
scanner := bufio.NewScanner(conn)
for scanner.Scan() {
if scanner.Err() != nil {
log.Printf("error: failed to read from client: %s", scanner.Err())
conn.Close()
return
}
if !beiCommand && !bytes.Equal(scanner.Bytes(), []byte("bei")) {
log.Printf("error: failed to read from client: failed to receive bei command")
conn.Close()
return
}
switch {
case bytes.Equal(scanner.Bytes(), []byte("bei")):
buf, err := bei.EncodeEvent(&bei.EventOkBEI{
Version: 1,
ID: map[string]string{
"name": "tabula",
},
})
if err != nil {
log.Fatalf("error: failed to encode event: %s", err)
}
conn.Write(buf)
conn.Write([]byte("\n"))
beiCommand = true
case bytes.HasPrefix(scanner.Bytes(), []byte("move ")):
b, err := parseState(scanner.Bytes()[5:])
if err != nil {
log.Println(err)
conn.Close()
return
}
available, _ := b.Available(1)
b.Analyze(available, &analysis, false)
var move *bei.Move
if len(analysis) > 0 {
move = &bei.Move{}
for _, m := range analysis[0].Moves {
if m[0] == 0 && m[1] == 0 {
break
}
move.Play = append(move.Play, &bei.Play{From: int(m[0]), To: int(m[1])})
}
}
result := &bei.EventOkMove{
Moves: []*bei.Move{},
}
if move != nil {
result.Moves = append(result.Moves, move)
}
buf, err := bei.EncodeEvent(result)
if err != nil {
log.Fatalf("error: failed to encode event: %s", err)
}
conn.Write(buf)
conn.Write([]byte("\n"))
case bytes.HasPrefix(scanner.Bytes(), []byte("choose ")):
b, err := parseState(scanner.Bytes()[7:])
if err != nil {
log.Println(err)
conn.Close()
return
}
if b[SpaceVariant] != VariantAceyDeucey {
log.Println("error: failed to choose roll: state does not represent acey-deucey game")
conn.Close()
return
}
roll := b.ChooseDoubles(&analysis)
if roll < 1 || roll > 6 {
log.Printf("error: failed to read from client: invalid roll: %d", roll)
conn.Close()
return
}
buf, err := bei.EncodeEvent(&bei.EventOkChoose{
Rolls: []*bei.ChooseRoll{
{
Roll: roll,
},
},
})
if err != nil {
log.Fatalf("error: failed to encode event: %s", err)
}
conn.Write(buf)
conn.Write([]byte("\n"))
default:
log.Printf("error: received unexpected command from client: %s", scanner.Bytes())
conn.Close()
return
}
}
if scanner.Err() != nil {
log.Printf("error: failed to read from client: %s", scanner.Err())
conn.Close()
return
}
}
func (s *BEIServer) Listen(address string) {
listener, err := net.Listen("tcp", address)
if err != nil {
log.Fatalf("failed to listen on %s: %s", address, err)
}
log.Printf("Listening for connections on %s...", address)
for {
conn, err := listener.Accept()
if err != nil {
log.Fatalf("failed to listen on %s: %s", address, err)
}
go s.handleConnection(conn)
}
}
func (s *BEIServer) ListenLocal() chan net.Conn {
conns := make(chan net.Conn)
go s.handleLocal(conns)
return conns
}
func (s *BEIServer) handleLocal(conns chan net.Conn) {
for {
local, remote := net.Pipe()
conns <- local
go s.handleConnection(remote)
}
}
func parseState(buf []byte) (Board, error) {
var stateInts []int
for _, v := range bytes.Split(buf, []byte(",")) {
i, err := strconv.Atoi(string(v))
if err != nil {
return Board{}, fmt.Errorf("error: failed to read from client: failed to decode state: %s", err)
}
stateInts = append(stateInts, i)
}
state, err := bei.DecodeState(stateInts)
if err != nil {
return Board{}, fmt.Errorf("error: failed to read from client: failed to decode state: %s", err)
}
b := Board{}
for i, v := range state.Board {
b[i] = int8(v)
}
b[SpaceRoll1] = int8(state.Roll1)
b[SpaceRoll2] = int8(state.Roll2)
if int8(state.Variant) != VariantTabula && state.Roll1 == state.Roll2 {
b[SpaceRoll3], b[SpaceRoll4] = int8(state.Roll1), int8(state.Roll2)
} else {
b[SpaceRoll3] = int8(state.Roll3)
}
if int8(state.Variant) != VariantBackgammon {
b[SpaceVariant] = int8(state.Variant)
if state.Entered1 {
b[SpaceEnteredPlayer] = 1
}
if state.Entered2 {
b[SpaceEnteredOpponent] = 1
}
} else {
b[SpaceEnteredPlayer] = 1
b[SpaceEnteredOpponent] = 1
}
if Verbose {
var logMessage []byte
for _, v := range b {
logMessage = append(logMessage, []byte(fmt.Sprintf("%4d", int(v)))...)
}
log.Println(string(logMessage))
}
return b, nil
}