Allow displaying previous moves on board

This commit is contained in:
Trevor Slocum 2023-12-09 11:19:08 -08:00
parent 67380585cf
commit e530ba7fa1
5 changed files with 142 additions and 54 deletions

View file

@ -1,5 +1,6 @@
1.1.8:
- Allow bearing checkers off by double clicking
- Allow displaying previous moves on board
- Fade out dice rolls instead of hiding them
1.1.7:

View file

@ -1,6 +1,7 @@
package game
import (
"bytes"
"fmt"
"image"
"image/color"
@ -61,6 +62,9 @@ type board struct {
playerRoll1, playerRoll2 int
playerRollStale bool
opponentMoves [][]int
playerMoves [][]int
debug int // Print and draw debug information
Client *Client
@ -85,8 +89,8 @@ type board struct {
opponentLabel *Label
playerLabel *Label
opponentMoves *etk.Text
playerMoves *etk.Text
opponentMovesLabel *etk.Text
playerMovesLabel *etk.Text
opponentPipCount *etk.Text
playerPipCount *etk.Text
@ -97,8 +101,9 @@ type board struct {
menuGrid *etk.Grid
showPipCountCheckbox *etk.Checkbox
highlightCheckbox *etk.Checkbox
showPipCountCheckbox *etk.Checkbox
showMovesCheckbox *etk.Checkbox
settingsGrid *etk.Grid
matchStatusGrid *etk.Grid
@ -121,8 +126,9 @@ type board struct {
lineHeight int
lineOffset int
showPipCount bool
highlightAvailable bool
showPipCount bool
showMoves bool
widget *BoardWidget
@ -157,8 +163,8 @@ func NewBoard() *board {
foundMoves: make(map[int]bool),
opponentLabel: NewLabel(color.RGBA{255, 255, 255, 255}),
playerLabel: NewLabel(color.RGBA{0, 0, 0, 255}),
opponentMoves: etk.NewText(""),
playerMoves: etk.NewText(""),
opponentMovesLabel: etk.NewText(""),
playerMovesLabel: etk.NewText(""),
opponentPipCount: etk.NewText("0"),
playerPipCount: etk.NewText("0"),
buttonsGrid: etk.NewGrid(),
@ -191,20 +197,20 @@ func NewBoard() *board {
t.SetScrollBarVisible(false)
}
centerText(b.opponentMoves)
centerText(b.playerMoves)
centerText(b.opponentMovesLabel)
centerText(b.playerMovesLabel)
centerText(b.opponentPipCount)
centerText(b.playerPipCount)
b.opponentMoves.SetHorizontal(messeji.AlignStart)
b.playerMoves.SetHorizontal(messeji.AlignEnd)
b.opponentMovesLabel.SetHorizontal(messeji.AlignStart)
b.playerMovesLabel.SetHorizontal(messeji.AlignEnd)
b.opponentPipCount.SetHorizontal(messeji.AlignEnd)
b.playerPipCount.SetHorizontal(messeji.AlignStart)
b.opponentMoves.SetForegroundColor(color.RGBA{255, 255, 255, 255})
b.playerMoves.SetForegroundColor(color.RGBA{0, 0, 0, 255})
b.opponentMovesLabel.SetForegroundColor(color.RGBA{255, 255, 255, 255})
b.playerMovesLabel.SetForegroundColor(color.RGBA{0, 0, 0, 255})
b.opponentPipCount.SetForegroundColor(color.RGBA{255, 255, 255, 255})
b.playerPipCount.SetForegroundColor(color.RGBA{0, 0, 0, 255})
@ -224,20 +230,6 @@ func NewBoard() *board {
settingsLabel := etk.NewText(gotext.Get("Settings"))
settingsLabel.SetHorizontal(messeji.AlignCenter)
b.showPipCountCheckbox = etk.NewCheckbox(b.togglePipCountCheckbox)
b.showPipCountCheckbox.SetBorderColor(triangleA)
b.showPipCountCheckbox.SetCheckColor(triangleA)
b.showPipCountCheckbox.SetSelected(b.showPipCount)
pipCountLabel := &ClickableText{
Text: etk.NewText(gotext.Get("Show pip count")),
onSelected: func() {
b.showPipCountCheckbox.SetSelected(!b.showPipCountCheckbox.Selected())
b.togglePipCountCheckbox()
},
}
pipCountLabel.SetVertical(messeji.AlignCenter)
b.highlightCheckbox = etk.NewCheckbox(b.toggleHighlightCheckbox)
b.highlightCheckbox.SetBorderColor(triangleA)
b.highlightCheckbox.SetCheckColor(triangleA)
@ -252,16 +244,46 @@ func NewBoard() *board {
}
highlightLabel.SetVertical(messeji.AlignCenter)
b.showPipCountCheckbox = etk.NewCheckbox(b.togglePipCountCheckbox)
b.showPipCountCheckbox.SetBorderColor(triangleA)
b.showPipCountCheckbox.SetCheckColor(triangleA)
b.showPipCountCheckbox.SetSelected(b.showPipCount)
pipCountLabel := &ClickableText{
Text: etk.NewText(gotext.Get("Show pip count")),
onSelected: func() {
b.showPipCountCheckbox.SetSelected(!b.showPipCountCheckbox.Selected())
b.togglePipCountCheckbox()
},
}
pipCountLabel.SetVertical(messeji.AlignCenter)
b.showMovesCheckbox = etk.NewCheckbox(b.toggleMovesCheckbox)
b.showMovesCheckbox.SetBorderColor(triangleA)
b.showMovesCheckbox.SetCheckColor(triangleA)
b.showMovesCheckbox.SetSelected(b.showMoves)
movesLabel := &ClickableText{
Text: etk.NewText(gotext.Get("Show moves")),
onSelected: func() {
b.showMovesCheckbox.SetSelected(!b.showMovesCheckbox.Selected())
b.toggleMovesCheckbox()
},
}
movesLabel.SetVertical(messeji.AlignCenter)
checkboxGrid := etk.NewGrid()
checkboxGrid.SetRowSizes(-1, 20, -1)
checkboxGrid.AddChildAt(b.showPipCountCheckbox, 0, 0, 1, 1)
checkboxGrid.AddChildAt(pipCountLabel, 1, 0, 4, 1)
checkboxGrid.AddChildAt(b.highlightCheckbox, 0, 2, 1, 1)
checkboxGrid.AddChildAt(highlightLabel, 1, 2, 4, 1)
checkboxGrid.SetRowSizes(-1, 20, -1, 20, -1)
checkboxGrid.AddChildAt(b.highlightCheckbox, 0, 0, 1, 1)
checkboxGrid.AddChildAt(highlightLabel, 1, 0, 4, 1)
checkboxGrid.AddChildAt(b.showPipCountCheckbox, 0, 2, 1, 1)
checkboxGrid.AddChildAt(pipCountLabel, 1, 2, 4, 1)
checkboxGrid.AddChildAt(b.showMovesCheckbox, 0, 4, 1, 1)
checkboxGrid.AddChildAt(movesLabel, 1, 4, 4, 1)
b.settingsGrid.SetBackground(color.RGBA{40, 24, 9, 255})
b.settingsGrid.SetColumnSizes(20, -1, -1, 20)
b.settingsGrid.SetRowSizes(72, 72+20+72, 20, -1)
b.settingsGrid.SetRowSizes(72, 72+20+72+20+72, 20, -1)
b.settingsGrid.AddChildAt(settingsLabel, 1, 0, 2, 1)
b.settingsGrid.AddChildAt(checkboxGrid, 1, 1, 2, 1)
b.settingsGrid.AddChildAt(etk.NewBox(), 1, 2, 1, 1)
@ -323,13 +345,13 @@ func NewBoard() *board {
{
f := etk.NewFrame()
f.AddChild(b.opponentMoves)
f.AddChild(b.opponentMovesLabel)
f.AddChild(b.opponentPipCount)
f.AddChild(b.opponentLabel)
f.AddChild(b.opponentLabel)
f.AddChild(b.playerLabel)
f.AddChild(b.playerPipCount)
f.AddChild(b.playerMoves)
f.AddChild(b.playerMovesLabel)
f.AddChild(b.uiGrid)
b.frame.AddChild(f)
}
@ -421,8 +443,8 @@ func (b *board) fontUpdated() {
b.timerLabel.SetFont(b.fontFace, fontMutex)
b.clockLabel.SetFont(b.fontFace, fontMutex)
b.opponentMoves.SetFont(bufferFont, fontMutex)
b.playerMoves.SetFont(bufferFont, fontMutex)
b.opponentMovesLabel.SetFont(bufferFont, fontMutex)
b.playerMovesLabel.SetFont(bufferFont, fontMutex)
b.opponentPipCount.SetFont(bufferFont, fontMutex)
b.playerPipCount.SetFont(bufferFont, fontMutex)
@ -664,6 +686,11 @@ func (b *board) selectResign() error {
return nil
}
func (b *board) toggleHighlightCheckbox() error {
b.highlightAvailable = b.highlightCheckbox.Selected()
return nil
}
func (b *board) togglePipCountCheckbox() error {
b.showPipCount = b.showPipCountCheckbox.Selected()
b.updatePlayerLabel()
@ -671,8 +698,9 @@ func (b *board) togglePipCountCheckbox() error {
return nil
}
func (b *board) toggleHighlightCheckbox() error {
b.highlightAvailable = b.highlightCheckbox.Selected()
func (b *board) toggleMovesCheckbox() error {
b.showMoves = b.showMovesCheckbox.Selected()
b.processState()
return nil
}
@ -1241,7 +1269,7 @@ func (b *board) setRect(x, y, w, h int) {
if dialogWidth > game.screenW {
dialogWidth = game.screenW
}
dialogHeight := 72 + 72 + 20 + 72 + 20 + game.scale(baseButtonHeight)
dialogHeight := 72 + 72 + 20 + 72 + 20 + 72 + 20 + game.scale(baseButtonHeight)
if dialogHeight > game.screenH {
dialogHeight = game.screenH
}
@ -1286,8 +1314,8 @@ func (b *board) setRect(x, y, w, h int) {
} else if b.w >= 100 {
padding = 5
}
b.opponentMoves.SetPadding(padding)
b.playerMoves.SetPadding(padding / 2)
b.opponentMovesLabel.SetPadding(padding)
b.playerMovesLabel.SetPadding(padding / 2)
b.opponentPipCount.SetPadding(padding / 2)
b.playerPipCount.SetPadding(padding)
}
@ -1328,8 +1356,8 @@ func (b *board) updateOpponentLabel() {
return
}
{
newRect := image.Rect(int(b.horizontalBorderSize), y-bounds.Dy(), x, y+bounds.Dy()*2)
b.opponentMoves.SetRect(newRect)
newRect := image.Rect(int(b.horizontalBorderSize), y-bounds.Dy()*2, x, y+bounds.Dy()*4)
b.opponentMovesLabel.SetRect(newRect)
}
{
newRect := r.Inset(-padding)
@ -1342,6 +1370,14 @@ func (b *board) updateOpponentLabel() {
b.opponentPipCount.SetRect(newRect)
}
var moves []byte
if len(b.opponentMoves) != 0 {
moves = bytes.ReplaceAll(bgammon.FormatMoves(b.opponentMoves), []byte(" "), []byte("\n"))
}
if b.opponentMovesLabel.Text() != string(moves) {
b.opponentMovesLabel.SetText(string(moves))
}
if b.showPipCount {
b.opponentPipCount.SetVisible(true)
pipCount := strconv.Itoa(b.gameState.Pips(player.Number))
@ -1388,8 +1424,8 @@ func (b *board) updatePlayerLabel() {
return
}
{
newRect := image.Rect(x+bounds.Dx(), y-bounds.Dy(), int(b.horizontalBorderSize)+b.innerW, y+bounds.Dy()*2)
b.playerMoves.SetRect(newRect)
newRect := image.Rect(x+bounds.Dx(), y-bounds.Dy()*2, int(b.horizontalBorderSize)+b.innerW, y+bounds.Dy()*4)
b.playerMovesLabel.SetRect(newRect)
}
{
newRect := r.Inset(-padding)
@ -1404,6 +1440,14 @@ func (b *board) updatePlayerLabel() {
}
}
var moves []byte
if len(b.playerMoves) != 0 {
moves = bytes.ReplaceAll(bgammon.FormatMoves(b.playerMoves), []byte(" "), []byte("\n"))
}
if b.playerMovesLabel.Text() != string(moves) {
b.playerMovesLabel.SetText(string(moves))
}
if b.showPipCount {
b.playerPipCount.SetVisible(true)
pipCount := strconv.Itoa(b.gameState.Pips(player.Number))
@ -1653,6 +1697,14 @@ func (b *board) processState() {
b._positionCheckers()
if b.showMoves && b.gameState.Turn == 1 {
b.playerMoves = expandMoves(b.gameState.Moves)
} else if b.showMoves && b.gameState.Turn == 2 {
b.opponentMoves = expandMoves(b.gameState.Moves)
} else {
b.playerMoves, b.opponentMoves = nil, nil
}
b.updateOpponentLabel()
b.updatePlayerLabel()
@ -2062,3 +2114,29 @@ func allMoves(in *bgammon.Game, from int, to int) []int {
}
return moves
}
func expandMoves(moves [][]int) [][]int {
var expanded bool
for _, m := range moves {
expandedMoves, ok := game.Board.gameState.ExpandMove(m, m[0], nil, true)
if !ok {
return moves
}
if len(expandedMoves) != 1 {
expanded = true
break
}
}
if !expanded {
return moves
}
var newMoves [][]int
for _, m := range moves {
expandedMoves, ok := game.Board.gameState.ExpandMove(m, m[0], nil, true)
if !ok {
return moves
}
newMoves = append(newMoves, expandedMoves...)
}
return newMoves
}

View file

@ -970,6 +970,8 @@ func (g *Game) handleEvent(e interface{}) {
g.Board.opponentRoll1, g.Board.opponentRoll2 = 0, 0
g.Board.playerRollStale = false
g.Board.opponentRollStale = false
g.Board.playerMoves = nil
g.Board.opponentMoves = nil
g.Board.processState()
g.Board.Unlock()
setViewBoard(true)
@ -1087,6 +1089,13 @@ func (g *Game) handleEvent(e interface{}) {
g.Board.movePiece(move[0], move[1])
}
g.Lock()
if g.Board.showMoves {
if g.Board.gameState.Turn == 1 {
g.Board.playerMoves = expandMoves(g.Board.gameState.Moves)
} else if g.Board.gameState.Turn == 2 {
g.Board.opponentMoves = expandMoves(g.Board.gameState.Moves)
}
}
g.Board.Unlock()
case *bgammon.EventFailedMove:
g.Client.Out <- []byte("board") // Refresh game state.

6
go.mod
View file

@ -3,8 +3,8 @@ module code.rocket9labs.com/tslocum/boxcars
go 1.17
require (
code.rocket9labs.com/tslocum/bgammon v0.0.0-20231203064241-5abce61fabb1
code.rocket9labs.com/tslocum/bgammon-tabula-bot v0.0.0-20231207002804-464a7b1265ff
code.rocket9labs.com/tslocum/bgammon v0.0.0-20231208220405-8c4b5112ba77
code.rocket9labs.com/tslocum/bgammon-tabula-bot v0.0.0-20231209094518-612426a93d9e
code.rocket9labs.com/tslocum/etk v0.0.0-20231120184929-cba07aae6454
code.rocketnine.space/tslocum/kibodo v1.0.2
code.rocketnine.space/tslocum/messeji v1.0.6-0.20231128010227-689683b75174
@ -18,7 +18,7 @@ require (
)
require (
code.rocket9labs.com/tslocum/tabula v0.0.0-20231207014630-bbec0a941eda // indirect
code.rocket9labs.com/tslocum/tabula v0.0.0-20231209094435-959d43321c78 // indirect
github.com/ebitengine/oto/v3 v3.1.0 // indirect
github.com/ebitengine/purego v0.5.1 // indirect
github.com/gobwas/httphead v0.1.0 // indirect

12
go.sum
View file

@ -1,11 +1,11 @@
code.rocket9labs.com/tslocum/bgammon v0.0.0-20231203064241-5abce61fabb1 h1:DU4h2XiS2bz4FdaeHRfYTBNwhWuaYGmnb/NA0VgsVMc=
code.rocket9labs.com/tslocum/bgammon v0.0.0-20231203064241-5abce61fabb1/go.mod h1:u3nbSwxWnwOXbCNPQD4s3abGTfvA4/gi9U626f7ZN9Q=
code.rocket9labs.com/tslocum/bgammon-tabula-bot v0.0.0-20231207002804-464a7b1265ff h1:FhQTN1yQH9Na5h7gYYWpGwbbGZnp6paCqNGSw4LK7jE=
code.rocket9labs.com/tslocum/bgammon-tabula-bot v0.0.0-20231207002804-464a7b1265ff/go.mod h1:ZZMqz1qLpKY2ZS59vPrc6Rx1fcI/KN40JswsEQIksgA=
code.rocket9labs.com/tslocum/bgammon v0.0.0-20231208220405-8c4b5112ba77 h1:MnAW9Icj5mDOaFnyT3BcH/xRoa0stZEJjs+hQg0lyj0=
code.rocket9labs.com/tslocum/bgammon v0.0.0-20231208220405-8c4b5112ba77/go.mod h1:u3nbSwxWnwOXbCNPQD4s3abGTfvA4/gi9U626f7ZN9Q=
code.rocket9labs.com/tslocum/bgammon-tabula-bot v0.0.0-20231209094518-612426a93d9e h1:l9VIZMa9Zpw7oh6jwQ42qTl+fTRERuMIz9OW+5GFIE0=
code.rocket9labs.com/tslocum/bgammon-tabula-bot v0.0.0-20231209094518-612426a93d9e/go.mod h1:HzEVxqMCixYFDVGAesdp9bc9onxNzCEJUf+eyDRMZWw=
code.rocket9labs.com/tslocum/etk v0.0.0-20231120184929-cba07aae6454 h1:R+KAg2+ORkMT/bcljmlno1nr21OBOxo6hY0Qa2qOk+Y=
code.rocket9labs.com/tslocum/etk v0.0.0-20231120184929-cba07aae6454/go.mod h1:bDrMbdTIhIy9egS6ISZat3Cw1dusd/Raeiq8vKHo9b4=
code.rocket9labs.com/tslocum/tabula v0.0.0-20231207014630-bbec0a941eda h1:4AUhF3+uRGROFrHB65U3OgmTvAACzMDAPcV0vs320/E=
code.rocket9labs.com/tslocum/tabula v0.0.0-20231207014630-bbec0a941eda/go.mod h1:XtS6M8qcK0fnUn4k5I7h4JcZ2sz2WEAXOcF2DLaQBME=
code.rocket9labs.com/tslocum/tabula v0.0.0-20231209094435-959d43321c78 h1:5E+Av3zTjMndkoz9n7QSl0icA1eBjnzFXtpbVADhXy8=
code.rocket9labs.com/tslocum/tabula v0.0.0-20231209094435-959d43321c78/go.mod h1:XtS6M8qcK0fnUn4k5I7h4JcZ2sz2WEAXOcF2DLaQBME=
code.rocketnine.space/tslocum/kibodo v1.0.2 h1:0RfvVz+IUku8MFx9wvDb+p8byns5gAjQLUo4ZenWP44=
code.rocketnine.space/tslocum/kibodo v1.0.2/go.mod h1:mAYs1JKFnWlRFzo9BtteAlwjKdk1MIKgEyhQaPdeQDI=
code.rocketnine.space/tslocum/messeji v1.0.6-0.20231128010227-689683b75174 h1:jxE3JcaE4ovMWaZHLA6MvYgkmdkdbEEUv943Bp+MoxU=