diff --git a/board.go b/board.go index 8735d44..c9c0f31 100644 --- a/board.go +++ b/board.go @@ -4,7 +4,6 @@ import ( "bytes" "fmt" "log" - "sort" "strconv" "strings" "sync" @@ -73,12 +72,13 @@ type Board struct { moves [][2]int movesColor int - validMoves map[int][]int + validMoves map[int][][]int from map[int]int to map[int]int - selected [2]int + selectedNum int + selectedSpace int premove [][2]int Premovefrom map[int]int @@ -130,8 +130,8 @@ func (b *Board) GetIntState() []int { } func (b *Board) resetSelection() { - b.selected[0] = 0 - b.selected[1] = 0 + b.selectedSpace = 0 + b.selectedNum = 0 } func (b *Board) autoSendMoves() { @@ -142,29 +142,6 @@ func (b *Board) autoSendMoves() { if b.v[StateMovablePieces] > 0 { movable = b.v[StateMovablePieces] } - if len(b.premove) == 1 { - abs := b.premove[0][1] - b.premove[0][0] - direction := 1 - if abs < 0 { - abs *= -1 - direction = -1 - } - if b.v[StatePlayerDice1] == b.v[StatePlayerDice2] { - for expandDoubles := 4; expandDoubles >= 2; expandDoubles-- { - if abs != b.v[StatePlayerDice1]*expandDoubles { - continue - } - - from, _ := b.premove[0][0], b.premove[0][1] - - b.premove = nil - for i := 1; i <= expandDoubles; i++ { - b.premove = append(b.premove, [2]int{from + ((b.v[StatePlayerDice1]*i - 1) * direction), from + ((b.v[StatePlayerDice1] * i) * direction)}) - } - break - } - } - } if len(b.premove) < movable { return } @@ -370,10 +347,10 @@ func (b *Board) renderSpace(index int, spaceValue int) []byte { } } // Highlight legal moves - highlightSpace := b.ValidMove(b.selected[0], index) + highlightSpace := b.ValidMove(b.selectedSpace, index) highlightSpace = false // TODO Make configurable, disable by default - //+(b.v[StatePlayerDice1]*b.v[StatePlayerColor]) ||b.selected[0] == index+(b.v[StatePlayerDice2]*b.v[StatePlayerColor])) && b.selected[1] > 0 - if b.selected[1] > 0 && highlightSpace && index != 25 && index != 0 { + //+(b.v[StatePlayerDice1]*b.v[StatePlayerColor]) ||b.selectedSpace == index+(b.v[StatePlayerDice2]*b.v[StatePlayerColor])) && b.selectedNum > 0 + if b.selectedNum > 0 && highlightSpace && index != 25 && index != 0 { foregroundColor = "black" backgroundColor = "yellow" } @@ -384,7 +361,7 @@ func (b *Board) renderSpace(index int, spaceValue int) []byte { } rightArrowFrom := (b.v[StateDirection] == b.movesColor) == (index > 12) - if b.selected[0] == index && b.selected[1] > 0 && spaceValue <= abs && spaceValue > abs-b.selected[1] { + if b.selectedSpace == index && b.selectedNum > 0 && spaceValue <= abs && spaceValue > abs-b.selectedNum { r = []byte("*") } else if b.Premovefrom[index] > 0 && spaceValue > (abs+b.Premoveto[index])-b.Premovefrom[index] && spaceValue <= abs+b.Premoveto[index] { if index == 25-b.PlayerBarSpace() { @@ -425,7 +402,7 @@ func (b *Board) renderSpace(index int, spaceValue int) []byte { func (b *Board) ResetMoves() { b.moves = nil b.movesColor = 0 - b.validMoves = make(map[int][]int) + b.validMoves = make(map[int][][]int) b.from = make(map[int]int) b.to = make(map[int]int) } @@ -453,9 +430,15 @@ func (b *Board) allPlayerPiecesInHomeBoard() bool { return false } value := b.v[StateBoardSpace0+index] + + // Include pre-moves mod := b.v[StatePlayerColor] value -= b.client.Board.Premovefrom[index] * mod - return value != 0 + + if b.v[StatePlayerColor] == -1 { + return value < 0 + } + return value > 0 } for i := 1; i < 24; i++ { if i >= homeBoardStart && i <= homeBoardEnd { @@ -479,12 +462,12 @@ func (b *Board) spaceAvailable(index int) bool { (b.v[StatePlayerColor] == -1 && b.v[StateBoardSpace0+index] <= 1) } -func (b *Board) GetValidMoves(from int) []int { +func (b *Board) GetValidMoves(from int) [][]int { if validMoves, ok := b.validMoves[from]; ok { return validMoves } - var validMoves []int + var validMoves [][]int defer func() { b.validMoves[from] = validMoves }() @@ -493,7 +476,6 @@ func (b *Board) GetValidMoves(from int) []int { return validMoves } - // TODO consider opponent blocking midway In full move trySpaces := [][]int{ {b.v[StatePlayerDice1]}, {b.v[StatePlayerDice2]}, @@ -529,12 +511,10 @@ CHECKSPACES: space := from + (checkSpace * b.v[StateDirection]) if _, value := foundMoves[space]; !value { foundMoves[space] = true - validMoves = append(validMoves, space) + validMoves = append(validMoves, trySpaces[i]) } } - sort.Ints(validMoves) - return validMoves } @@ -560,8 +540,16 @@ func (b *Board) ValidMove(f int, t int) bool { } validMoves := b.GetValidMoves(f) +CHECKVALID: for i := range validMoves { - if validMoves[i] == t { + checkSpace := 0 + for _, space := range validMoves[i] { + checkSpace += space + if !b.spaceAvailable(f + (checkSpace * b.v[StateDirection])) { + continue CHECKVALID + } + } + if f+(checkSpace*b.v[StateDirection]) == t { return true } } @@ -603,7 +591,7 @@ func (b *Board) Move(player int, f string, t string) { b.v[StateTurn] = player * -1 - b.validMoves = make(map[int][]int) + b.validMoves = make(map[int][][]int) b.ResetPreMoves() } @@ -627,18 +615,77 @@ func (b *Board) SimplifyMoves() { } } -func (b *Board) AddPreMove(from int, to int) bool { +func (b *Board) GetSelection() (num int, space int) { + return b.selectedNum, b.selectedSpace +} + +func (b *Board) SetSelection(num int, space int) { + b.selectedNum, b.selectedSpace = num, space +} + +func (b *Board) ResetSelection() { + b.selectedNum, b.selectedSpace = 0, 0 +} + +func (b *Board) addPreMove(from int, to int, num int) bool { // Allow bearing off when the player moves their own pieces on to the bar if to == 0 || to == 25 { to = b.PlayerHomeSpace() } + + // Expand combined move + moves := b.client.Board.GetValidMoves(from) + +CHECKPREMOVES: + for i := range moves { + checkSpace := 0 + for _, space := range moves[i] { + checkSpace += space + lf("CHECK %d %d", checkSpace, from+(checkSpace*b.v[StateDirection])) + if !b.spaceAvailable(from + (checkSpace * b.v[StateDirection])) { + continue CHECKPREMOVES + } + } + lf("SECOND PHASE %+v %d", moves[i], num) + if (from+(checkSpace*b.v[StateDirection]) == to) && len(moves[i]) > 1 { + lf("SECOND.5 PHASE %+v %d", moves[i], num) + for j := 0; j < num; j++ { + checkSpace = 0 + lastSpace := 0 + for _, space := range moves[i] { + checkSpace += space + lf("THIRD PHASE %d %d", from+(lastSpace*b.v[StateDirection]), from+(checkSpace*b.v[StateDirection])) + + if !b.addPreMove(from+(lastSpace*b.v[StateDirection]), from+(checkSpace*b.v[StateDirection]), 1) { + return false + } + lastSpace = checkSpace + } + } + return true + } + } + if !b.ValidMove(from, to) { return false } - b.premove = append(b.premove, [2]int{from, to}) - b.Premovefrom[from]++ - b.Premoveto[to]++ + for i := 0; i < num; i++ { + b.premove = append(b.premove, [2]int{from, to}) + b.Premovefrom[from]++ + b.Premoveto[to]++ + } + lf("ADD %+v", b.premove) + return true +} + +func (b *Board) AddPreMove(from int, to int) bool { + if !b.addPreMove(from, to, b.selectedNum) { + return false + } + lf("FINAL %+v", b.premove) + + b.resetSelection() b.autoSendMoves() return true } diff --git a/board_test.go b/board_test.go index 16446e8..d46184d 100644 --- a/board_test.go +++ b/board_test.go @@ -42,8 +42,10 @@ func TestBoard_GetValidMoves(t *testing.T) { b.Draw() validMoves := b.GetValidMoves(c.from) - if !equalInts(validMoves, c.moves) { - t.Errorf("unexpected valid moves: expected %+v, got %+v\n%s", c.moves, validMoves, b.Render()) + for i := range validMoves { + if !equalInts(validMoves[i], c.moves) { + t.Errorf("unexpected valid moves: expected %+v, got %+v\n%s", c.moves, validMoves, b.Render()) + } } }) } diff --git a/client.go b/client.go index a9360ee..3b3a104 100644 --- a/client.go +++ b/client.go @@ -19,7 +19,8 @@ import ( "nhooyr.io/websocket" ) -const debug = 1 // TODO +// Debug controls the level of debug information to print. +var Debug = 0 const whoInfoSize = 12 @@ -209,7 +210,7 @@ func (c *Client) handleWrite() { c.rawMode = true } - if debug > 0 { + if Debug > 0 { l("-> " + string(bytes.TrimSpace(b))) } @@ -266,7 +267,7 @@ func (c *Client) handleRead(r io.Reader) { buf := make([]byte, b.Len()) copy(buf, b.Bytes()) - if debug > 0 { + if Debug > 0 { l("<- " + string(bytes.TrimSpace(buf))) } @@ -475,16 +476,8 @@ func (c *Client) callWebSocket() { }() c.handleWrite() - /* TODO - err := conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) - if err != nil { - log.Println("write close:", err) - return nil - } - select { - case <-done: - case <-time.After(time.Second): - }*/ + + // TODO write CloseMessage when closing WS } func (c *Client) Connect() error { @@ -519,6 +512,7 @@ func (c *Client) eventLoop() { var winsMatchRegexp = regexp.MustCompile(`^\w+ wins a [0-9]+ point match against .*`) var winsThisMatchRegexp = regexp.MustCompile(`^\w+ wins the [0-9]+ point match .*`) var newGameRegexp = regexp.MustCompile(`^Starting a new game with .*`) + var inviteResumeGameRegexp = regexp.MustCompile(`^*\* You invited \w+ to resume a saved match\..*`) var joinedGameRegexp = regexp.MustCompile(`^\w+ has joined you\..*`) var gameBufferRegexp = regexp.MustCompile(`^\w+ (makes|roll|rolls|rolled|move|moves) .*`) @@ -734,6 +728,12 @@ func (c *Client) eventLoop() { } else if bytes.HasPrefix(bl, []byte("you're now watching")) { // Board state is not sent automatically when watching c.Out <- []byte("board") + } else if inviteResumeGameRegexp.Match(b) { + // Board state is not always sent automatically when joining resumed game + go func() { + time.Sleep(500 * time.Millisecond) + c.Out <- []byte("board") + }() } else if logInOutRegexp.Match(b) { continue } else if dropsConnection.Match(b) {