Add checker animation speed setting

Resolves #20.
This commit is contained in:
Trevor Slocum 2024-01-11 18:45:43 -08:00
parent 64ca68b4ee
commit 23e4f3f2a4
7 changed files with 135 additions and 64 deletions

View file

@ -1,6 +1,7 @@
1.2.4:
- Play forced moves automatically
- Add checker animation speed setting
- Support advanced checker movement
- Play forced moves automatically
1.2.3:
- Fix replaying tabula games

View file

@ -20,7 +20,6 @@ func parseFlags() *game.Game {
serverAddress string
locale string
tv bool
instant bool
debug int
touch bool
)
@ -28,7 +27,6 @@ func parseFlags() *game.Game {
flag.StringVar(&password, "password", "", "Password")
flag.StringVar(&serverAddress, "address", game.DefaultServerAddress, "Server address")
flag.StringVar(&locale, "locale", "", "Use specified locale for translations")
flag.BoolVar(&instant, "instant", false, "Instant checker moves (for bot versus bot matches)")
flag.BoolVar(&tv, "tv", false, "Watch random games continuously")
flag.BoolVar(&touch, "touch", false, "Force touch input related interface elements to be displayed")
flag.IntVar(&debug, "debug", 0, "Print debug information and serve pprof on specified port")
@ -52,7 +50,6 @@ func parseFlags() *game.Game {
g.Password = password
g.ServerAddress = serverAddress
g.TV = tv
g.Instant = instant
if touch {
g.EnableTouchInput()

View file

@ -119,6 +119,7 @@ type board struct {
showMovesCheckbox *etk.Checkbox
flipBoardCheckbox *etk.Checkbox
advancedMovementCheckbox *etk.Checkbox
selectSpeed *etk.Select
accountGrid *etk.Grid
settingsGrid *etk.Grid
@ -146,6 +147,7 @@ type board struct {
lineHeight int
lineOffset int
speed int8
highlightAvailable bool
showPipCount bool
showMoves bool
@ -212,6 +214,7 @@ func NewBoard() *board {
chatGrid: etk.NewGrid(),
floatChatGrid: etk.NewGrid(),
floatInputGrid: etk.NewGrid(),
speed: bgammon.SpeedMedium,
showPipCount: true,
highlightAvailable: true,
widget: NewBoardWidget(),
@ -382,33 +385,58 @@ func NewBoard() *board {
}
advancedMovementLabel.SetVertical(messeji.AlignCenter)
accountLabel := etk.NewText(gotext.Get("Account"))
accountLabel.SetVertical(messeji.AlignCenter)
b.recreateAccountGrid()
checkboxGrid := etk.NewGrid()
checkboxGrid.SetColumnSizes(72, 20, -1)
checkboxGrid.SetRowSizes(-1, 20, -1, 20, -1, 20, -1, 20, -1, 20, -1)
checkboxGrid.AddChildAt(b.highlightCheckbox, 0, 0, 1, 1)
checkboxGrid.AddChildAt(highlightLabel, 2, 0, 1, 1)
checkboxGrid.AddChildAt(b.showPipCountCheckbox, 0, 2, 1, 1)
checkboxGrid.AddChildAt(pipCountLabel, 2, 2, 1, 1)
checkboxGrid.AddChildAt(b.showMovesCheckbox, 0, 4, 1, 1)
checkboxGrid.AddChildAt(movesLabel, 2, 4, 1, 1)
checkboxGrid.AddChildAt(b.flipBoardCheckbox, 0, 6, 1, 1)
checkboxGrid.AddChildAt(flipBoardLabel, 2, 6, 1, 1)
gridY := 8
if !AutoEnableTouchInput {
checkboxGrid.AddChildAt(b.advancedMovementCheckbox, 0, 8, 1, 1)
checkboxGrid.AddChildAt(advancedMovementLabel, 2, 8, 1, 1)
gridY += 2
checkboxGrid.SetRowSizes(-1, 20, -1, 20, -1, 20, -1, 20, -1, 20, -1, 20, -1)
} else {
checkboxGrid.SetRowSizes(-1, 20, -1, 20, -1, 20, -1, 20, -1, 20, -1)
}
{
accountLabel := etk.NewText(gotext.Get("Account"))
accountLabel.SetVertical(messeji.AlignCenter)
grid := etk.NewGrid()
grid.AddChildAt(accountLabel, 0, 0, 1, 1)
grid.AddChildAt(b.accountGrid, 1, 0, 2, 1)
checkboxGrid.AddChildAt(grid, 0, gridY, 3, 1)
checkboxGrid.AddChildAt(grid, 0, 0, 3, 1)
}
{
speedLabel := etk.NewText(gotext.Get("Speed"))
speedLabel.SetVertical(messeji.AlignCenter)
b.selectSpeed = etk.NewSelect(game.itemHeight(), b.confirmSelectSpeed)
b.selectSpeed.SetHighlightColor(color.RGBA{191, 156, 94, 255})
b.selectSpeed.AddOption(gotext.Get("Slow"))
b.selectSpeed.AddOption(gotext.Get("Medium"))
b.selectSpeed.AddOption(gotext.Get("Fast"))
b.selectSpeed.AddOption(gotext.Get("Instant"))
b.selectSpeed.SetSelectedItem(int(bgammon.SpeedMedium))
grid := etk.NewGrid()
grid.AddChildAt(speedLabel, 0, 0, 1, 1)
grid.AddChildAt(b.selectSpeed, 1, 0, 2, 1)
checkboxGrid.AddChildAt(grid, 0, 2, 3, 1)
}
cGrid := func(checkbox *etk.Checkbox) *etk.Grid {
g := etk.NewGrid()
g.SetColumnSizes(7, -1)
g.AddChildAt(checkbox, 1, 0, 1, 1)
return g
}
checkboxGrid.AddChildAt(cGrid(b.highlightCheckbox), 0, 4, 1, 1)
checkboxGrid.AddChildAt(highlightLabel, 2, 4, 1, 1)
checkboxGrid.AddChildAt(cGrid(b.showPipCountCheckbox), 0, 6, 1, 1)
checkboxGrid.AddChildAt(pipCountLabel, 2, 6, 1, 1)
checkboxGrid.AddChildAt(cGrid(b.showMovesCheckbox), 0, 8, 1, 1)
checkboxGrid.AddChildAt(movesLabel, 2, 8, 1, 1)
checkboxGrid.AddChildAt(cGrid(b.flipBoardCheckbox), 0, 10, 1, 1)
checkboxGrid.AddChildAt(flipBoardLabel, 2, 10, 1, 1)
if !AutoEnableTouchInput {
checkboxGrid.AddChildAt(cGrid(b.advancedMovementCheckbox), 0, 12, 1, 1)
checkboxGrid.AddChildAt(advancedMovementLabel, 2, 12, 1, 1)
}
gridSize := 72 + 20 + 72 + 20 + 72 + 20 + 72 + 20 + 72
@ -417,7 +445,7 @@ func NewBoard() *board {
}
b.settingsGrid.SetBackground(color.RGBA{40, 24, 9, 255})
b.settingsGrid.SetColumnSizes(20, -1, -1, 20)
b.settingsGrid.SetRowSizes(72, gridSize, 20, -1)
b.settingsGrid.SetRowSizes(72, -1, 20, game.scale(baseButtonHeight))
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)
@ -545,6 +573,11 @@ func NewBoard() *board {
f := etk.NewFrame()
f.AddChild(b.menuGrid)
f.AddChild(b.settingsGrid)
children := b.selectSpeed.Children()
if len(children) == 0 {
log.Panicf("failed to find speed selection list")
}
f.AddChild(children[0])
f.AddChild(b.changePasswordGrid)
f.AddChild(b.leaveGameGrid)
b.frame.AddChild(f)
@ -802,6 +835,7 @@ func (b *board) showSettings() error {
func (b *board) showChangePassword() error {
b.settingsGrid.SetVisible(false)
b.selectSpeed.SetMenuVisible(false)
b.changePasswordGrid.SetVisible(true)
etk.SetFocus(b.changePasswordOld)
return nil
@ -810,6 +844,7 @@ func (b *board) showChangePassword() error {
func (b *board) hideMenu() error {
b.menuGrid.SetVisible(false)
b.settingsGrid.SetVisible(false)
b.selectSpeed.SetMenuVisible(false)
b.changePasswordGrid.SetVisible(false)
b.changePasswordOld.Field.SetText("")
b.changePasswordNew.Field.SetText("")
@ -820,6 +855,7 @@ func (b *board) toggleMenu() error {
if b.menuGrid.Visible() {
b.menuGrid.SetVisible(false)
b.settingsGrid.SetVisible(false)
b.selectSpeed.SetMenuVisible(false)
} else {
b.menuGrid.SetVisible(true)
}
@ -890,7 +926,7 @@ func (b *board) _selectUndo() {
b.Client.Out <- []byte(fmt.Sprintf("mv %d/%d", lastMove[1], lastMove[0]))
playSoundEffect(effectMove)
b.movePiece(lastMove[1], lastMove[0])
b.movePiece(lastMove[1], lastMove[0], false)
b.gameState.Moves = b.gameState.Moves[:l-1]
}
@ -914,6 +950,15 @@ func (b *board) selectChangePassword() error {
return b.hideMenu()
}
func (b *board) confirmSelectSpeed(index int) (accept bool) {
if index < int(bgammon.SpeedSlow) || index > int(bgammon.SpeedInstant) {
return false
}
b.speed = int8(index)
b.Client.Out <- []byte(fmt.Sprintf("set speed %d", b.speed))
return true
}
func (b *board) selectReplayStart() error {
if !game.replay {
return nil
@ -2367,6 +2412,16 @@ func (b *board) processState() {
func (b *board) _movePiece(sprite *Sprite, from int8, to int8, speed int8, pause bool) {
moveTime := (650 * time.Millisecond) / time.Duration(speed)
pauseTime := 250 * time.Millisecond
switch b.speed {
case bgammon.SpeedSlow:
moveTime += moveTime / 2
case bgammon.SpeedFast:
moveTime /= 2
pauseTime /= 2
case bgammon.SpeedInstant:
moveTime = 0
pauseTime = 0
}
b.moving = sprite
@ -2384,7 +2439,7 @@ func (b *board) _movePiece(sprite *Sprite, from int8, to int8, speed int8, pause
// Center piece in space
x += (w - int(b.spaceWidth)) / 2
if !game.Instant {
if moveTime != 0 {
sprite.toX = x
sprite.toY = y
sprite.toTime = moveTime
@ -2406,7 +2461,7 @@ func (b *board) _movePiece(sprite *Sprite, from int8, to int8, speed int8, pause
}
b.moving = nil
if game.Instant {
if pauseTime == 0 {
return
} else if pause {
time.Sleep(pauseTime)
@ -2416,7 +2471,7 @@ func (b *board) _movePiece(sprite *Sprite, from int8, to int8, speed int8, pause
}
// movePiece returns when finished moving the piece.
func (b *board) movePiece(from int8, to int8) {
func (b *board) movePiece(from int8, to int8, pause bool) {
pieces := b.spaceSprites[from]
if len(pieces) == 0 {
log.Printf("ERROR: NO SPRITE FOR MOVE %d/%d", from, to)
@ -2432,13 +2487,13 @@ func (b *board) movePiece(from int8, to int8) {
}
}
b._movePiece(sprite, from, to, 1, moveAfter == nil)
b._movePiece(sprite, from, to, 1, pause && moveAfter == nil)
if moveAfter != nil {
bar := bgammon.SpaceBarPlayer
if b.gameState.Turn == b.gameState.PlayerNumber {
bar = bgammon.SpaceBarOpponent
}
b._movePiece(moveAfter, to, bar, 1, true)
b._movePiece(moveAfter, to, bar, 1, pause)
}
}
@ -2708,6 +2763,10 @@ func NewBoardWidget() *BoardWidget {
}
func (bw *BoardWidget) finishClick(cursor image.Point, double bool) {
game.Board.Lock()
game.Lock()
game.Board.Unlock()
defer game.Unlock()
if game.Board.draggingSpace == -1 || len(game.Board.gameState.Available) == 0 {
return
}
@ -2753,6 +2812,15 @@ func (bw *BoardWidget) finishClick(cursor image.Point, double bool) {
if len(useMove) == 0 {
return
}
playSoundEffect(effectMove)
game.Unlock()
game.Board.Lock()
game.Board.movePiece(useMove[0], useMove[1], false)
game.Board.gameState.AddLocalMove([]int8{useMove[0], useMove[1]})
game.Board.gameState.Moves = append(game.Board.gameState.Moves, []int8{useMove[0], useMove[1]})
game.Board.processState()
game.Board.Unlock()
game.Lock()
game.Client.Out <- []byte(fmt.Sprintf("mv %d/%d", useMove[0], useMove[1]))
return
}
@ -2777,6 +2845,17 @@ FINDMOVE:
if len(useMoves) == 0 {
return
}
game.Unlock()
game.Board.Lock()
for _, move := range useMoves {
playSoundEffect(effectMove)
game.Board.movePiece(move[0], move[1], false)
game.Board.gameState.AddMoves([][]int8{{move[0], move[1]}}, true)
game.Board.gameState.Moves = append(game.Board.gameState.Moves, []int8{move[0], move[1]})
game.Board.processState()
}
game.Board.Unlock()
game.Lock()
for _, move := range useMoves {
game.Client.Out <- []byte(fmt.Sprintf("mv %d/%d", move[0], move[1]))
}
@ -2796,6 +2875,9 @@ func (bw *BoardWidget) HandleMouse(cursor image.Point, pressed bool, clicked boo
if b.dragging == nil {
if b.advancedMovement && clicked {
if b.moving != nil {
return false, nil
}
const doubleClickDuration = 250 * time.Millisecond
space := b.spaceAt(cx, cy)
if space != -1 {
@ -2806,8 +2888,6 @@ func (bw *BoardWidget) HandleMouse(cursor image.Point, pressed bool, clicked boo
b.lastDragClick = setTime
go func() {
time.Sleep(doubleClickDuration)
game.Lock()
defer game.Unlock()
if !b.lastDragClick.Equal(setTime) {
return
}
@ -2816,7 +2896,7 @@ func (bw *BoardWidget) HandleMouse(cursor image.Point, pressed bool, clicked boo
}()
return true, nil
}
bw.finishClick(cursor, true)
go bw.finishClick(cursor, true)
b.lastDragClick = time.Now()
return true, nil
}

View file

@ -41,7 +41,7 @@ import (
"golang.org/x/text/language"
)
const version = "v1.2.3p1"
const version = "v1.2.3p3"
const DefaultServerAddress = "wss://ws.bgammon.org"
@ -498,6 +498,7 @@ func setViewBoard(view bool) {
game.Board.menuGrid.SetVisible(false)
game.Board.settingsGrid.SetVisible(false)
game.Board.selectSpeed.SetMenuVisible(false)
game.Board.leaveGameGrid.SetVisible(false)
statusBuffer.SetRect(statusBuffer.Rect())
@ -621,8 +622,6 @@ type Game struct {
bufferWidth int
Instant bool
connectGridY int
showConnectStatusBuffer bool
@ -1082,11 +1081,7 @@ func NewGame() *Game {
searchButton := etk.NewButton(gotext.Get("Search"), g.selectHistorySearch)
itemHeight := 48
if defaultFontSize == extraLargeFontSize {
itemHeight = 72
}
g.lobby.historyList = etk.NewList(itemHeight, g.lobby.selectHistory)
g.lobby.historyList = etk.NewList(game.itemHeight(), g.lobby.selectHistory)
g.lobby.historyList.SetColumnSizes(int(float64(indentA)*1.25), int(float64(indentB)*1.25)-int(float64(indentA)*1.25), -1)
g.lobby.historyList.SetHighlightColor(color.RGBA{79, 55, 30, 255})
@ -1253,8 +1248,8 @@ func (g *Game) setBufferRects() {
createGameContainer.SetRowSizes(-1, statusBufferHeight, g.lobby.buttonBarHeight)
joinGameContainer.SetRowSizes(-1, statusBufferHeight, g.lobby.buttonBarHeight)
historyContainer.SetRowSizes(g.lobby.itemHeight, 2, -1, statusBufferHeight, g.lobby.buttonBarHeight)
listGamesContainer.SetRowSizes(g.lobby.itemHeight, 2, -1, statusBufferHeight, g.lobby.buttonBarHeight)
historyContainer.SetRowSizes(g.itemHeight(), 2, -1, statusBufferHeight, g.lobby.buttonBarHeight)
listGamesContainer.SetRowSizes(g.itemHeight(), 2, -1, statusBufferHeight, g.lobby.buttonBarHeight)
}
func (g *Game) handleAutoRefresh() {
@ -1444,21 +1439,14 @@ func (g *Game) handleEvent(e interface{}) {
case *bgammon.EventMoved:
lg(gotext.Get("%s moved %s", ev.Player, bgammon.FormatMoves(ev.Moves)))
if ev.Player == g.Client.Username && !g.Board.gameState.Spectating && !g.Board.gameState.Forced {
var delta int8
for _, move := range ev.Moves {
delta = move[1] - move[0]
break
}
if (delta < 0) == (g.Board.gameState.Variant == bgammon.VariantTabula) {
return
}
return
}
g.Board.Lock()
g.Unlock()
for _, move := range ev.Moves {
playSoundEffect(effectMove)
g.Board.movePiece(move[0], move[1])
g.Board.movePiece(move[0], move[1], true)
}
g.Lock()
if g.Board.showMoves {
@ -1491,6 +1479,10 @@ func (g *Game) handleEvent(e interface{}) {
case *bgammon.EventSettings:
b := g.Board
b.Lock()
if ev.Speed >= bgammon.SpeedSlow && ev.Speed <= bgammon.SpeedInstant {
b.speed = ev.Speed
b.selectSpeed.SetSelectedItem(int(b.speed))
}
b.highlightAvailable = ev.Highlight
b.highlightCheckbox.SetSelected(b.highlightAvailable)
b.showPipCount = ev.Pips
@ -2230,6 +2222,7 @@ func (g *Game) handleInput(keys []ebiten.Key) error {
g.Board.menuGrid.SetVisible(false)
} else if g.Board.settingsGrid.Visible() {
g.Board.settingsGrid.SetVisible(false)
g.Board.selectSpeed.SetMenuVisible(false)
} else if g.Board.changePasswordGrid.Visible() {
g.Board.hideMenu()
} else if g.Board.leaveGameGrid.Visible() {
@ -2806,6 +2799,13 @@ func acceptInput(text string) (handled bool) {
return true
}
func (g *Game) itemHeight() int {
if defaultFontSize == extraLargeFontSize {
return 72
}
return 48
}
func (g *Game) EnableTouchInput() {
if g.TouchInput {
return

View file

@ -53,8 +53,6 @@ type lobby struct {
lastClick time.Time
itemHeight int
selected int
c *Client
@ -111,14 +109,9 @@ func NewLobby() *lobby {
{gotext.Get("View replay")},
}
itemHeight := 48
if defaultFontSize == extraLargeFontSize {
itemHeight = 72
}
l := &lobby{
refresh: true,
buttonsGrid: etk.NewGrid(),
itemHeight: itemHeight,
}
indentA, indentB := lobbyIndentA, lobbyIndentB
@ -126,7 +119,7 @@ func NewLobby() *lobby {
indentA, indentB = int(float64(indentA)*1.3), int(float64(indentB)*1.3)
}
matchList := etk.NewList(l.itemHeight, l.selectMatch)
matchList := etk.NewList(game.itemHeight(), l.selectMatch)
matchList.SetSelectionMode(etk.SelectRow)
matchList.SetColumnSizes(indentA, indentB-indentA, -1)
matchList.SetHighlightColor(color.RGBA{79, 55, 30, 255})

4
go.mod
View file

@ -3,9 +3,9 @@ module code.rocket9labs.com/tslocum/boxcars
go 1.17
require (
code.rocket9labs.com/tslocum/bgammon v0.0.0-20240111193402-262ad93e51c2
code.rocket9labs.com/tslocum/bgammon v0.0.0-20240112024704-5410496772f5
code.rocket9labs.com/tslocum/bgammon-tabula-bot v0.0.0-20240111193502-f01ac821f8ba
code.rocket9labs.com/tslocum/etk v0.0.0-20240111201928-bde57bc0aca8
code.rocket9labs.com/tslocum/etk v0.0.0-20240112010933-fb51eb32adda
code.rocket9labs.com/tslocum/tabula v0.0.0-20240108183445-695ea428ae21
code.rocketnine.space/tslocum/kibodo v1.0.3-0.20240110043547-31f31eb07497
code.rocketnine.space/tslocum/messeji v1.0.6-0.20240109205105-4ffeffdd2441

8
go.sum
View file

@ -1,11 +1,11 @@
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-20240111193402-262ad93e51c2 h1:H51VlgdBfZbqnRCqjgBY5WLSs2FeUVznBGZahrfU90M=
code.rocket9labs.com/tslocum/bgammon v0.0.0-20240111193402-262ad93e51c2/go.mod h1:65vhSKgeQb6ccjUm5NJlbBdwuAH3VSFoSApZ/aVG3+4=
code.rocket9labs.com/tslocum/bgammon v0.0.0-20240112024704-5410496772f5 h1:yTbLlxhcYdb3Zxr2gBgL9yj5zwmolgNNVO0KlnFBTsE=
code.rocket9labs.com/tslocum/bgammon v0.0.0-20240112024704-5410496772f5/go.mod h1:65vhSKgeQb6ccjUm5NJlbBdwuAH3VSFoSApZ/aVG3+4=
code.rocket9labs.com/tslocum/bgammon-tabula-bot v0.0.0-20240111193502-f01ac821f8ba h1:9KpLZT8DAOV0Uk5rJZfNVdfn3MTGBM8gy3MEx3qM8aY=
code.rocket9labs.com/tslocum/bgammon-tabula-bot v0.0.0-20240111193502-f01ac821f8ba/go.mod h1:ZzucsodM8kqL3y3GYzDYrEUNrDlLlzMux7WVmJ06ZBI=
code.rocket9labs.com/tslocum/etk v0.0.0-20240111201928-bde57bc0aca8 h1:5Qu0E2FEAuxi15hp6MQgRYttBJbNN1LhtiGRVros1qY=
code.rocket9labs.com/tslocum/etk v0.0.0-20240111201928-bde57bc0aca8/go.mod h1:RrN0szXD27BMmCX63/t9SbD1CYDHXaf72y8IozXSzyg=
code.rocket9labs.com/tslocum/etk v0.0.0-20240112010933-fb51eb32adda h1:Ho2Jb3mRmpz1zLVlqeo7tmTgZK0ruL/Sr8BEy4bMnEw=
code.rocket9labs.com/tslocum/etk v0.0.0-20240112010933-fb51eb32adda/go.mod h1:RrN0szXD27BMmCX63/t9SbD1CYDHXaf72y8IozXSzyg=
code.rocket9labs.com/tslocum/tabula v0.0.0-20240108183445-695ea428ae21 h1:1VG88tdhCSVv7wGoIKQe8A8KfBXJsdz5pDsyP4ymDwk=
code.rocket9labs.com/tslocum/tabula v0.0.0-20240108183445-695ea428ae21/go.mod h1:WEJXESKXqrMFLAArikQ79lpRibNeeE1C0VruxXYMF5M=
code.rocketnine.space/tslocum/kibodo v1.0.3-0.20240110043547-31f31eb07497 h1:QpzLvcDV7DsaeFKrQZcHkDfq1PqsHcwUVnRXRKBAxe0=