Support flipping board

Resolves #17.
This commit is contained in:
Trevor Slocum 2024-01-05 12:15:47 -08:00
parent 2aa4c96f4c
commit bd5744ac55
9 changed files with 112 additions and 46 deletions

View file

@ -1,5 +1,6 @@
1.2.1:
- Add tutorial
- Support flipping board
- Change lobby button labels
1.2.0:

View file

@ -19,7 +19,6 @@ func parseFlags() *game.Game {
password string
serverAddress string
locale string
watch bool
tv bool
instant bool
debug int
@ -29,7 +28,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(&watch, "watch", false, "Watch random game")
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")
@ -50,7 +48,6 @@ func parseFlags() *game.Game {
g.Username = username
g.Password = password
g.ServerAddress = serverAddress
g.Watch = watch
g.TV = tv
g.Instant = instant

View file

@ -115,6 +115,7 @@ type board struct {
highlightCheckbox *etk.Checkbox
showPipCountCheckbox *etk.Checkbox
showMovesCheckbox *etk.Checkbox
flipBoardCheckbox *etk.Checkbox
accountGrid *etk.Grid
settingsGrid *etk.Grid
@ -145,6 +146,7 @@ type board struct {
highlightAvailable bool
showPipCount bool
showMoves bool
flipBoard bool
widget *BoardWidget
@ -158,6 +160,11 @@ const (
baseBoardVerticalSize = 25
)
var (
colorWhite = color.RGBA{255, 255, 255, 255}
colorBlack = color.RGBA{0, 0, 0, 255}
)
func NewBoard() *board {
b := &board{
barWidth: 100,
@ -177,8 +184,8 @@ func NewBoard() *board {
highlightSpaces: make([][]int8, 28),
spaceHighlight: ebiten.NewImage(1, 1),
foundMoves: make(map[int]bool),
opponentLabel: NewLabel(color.RGBA{255, 255, 255, 255}),
playerLabel: NewLabel(color.RGBA{0, 0, 0, 255}),
opponentLabel: NewLabel(colorWhite),
playerLabel: NewLabel(colorBlack),
opponentMovesLabel: etk.NewText(""),
playerMovesLabel: etk.NewText(""),
opponentPipCount: etk.NewText("0"),
@ -343,6 +350,20 @@ func NewBoard() *board {
}
movesLabel.SetVertical(messeji.AlignCenter)
b.flipBoardCheckbox = etk.NewCheckbox(b.toggleFlipBoardCheckbox)
b.flipBoardCheckbox.SetBorderColor(triangleA)
b.flipBoardCheckbox.SetCheckColor(triangleA)
b.flipBoardCheckbox.SetSelected(b.flipBoard)
flipBoardLabel := &ClickableText{
Text: etk.NewText(gotext.Get("Flip board")),
onSelected: func() {
b.flipBoardCheckbox.SetSelected(!b.flipBoardCheckbox.Selected())
b.toggleFlipBoardCheckbox()
},
}
flipBoardLabel.SetVertical(messeji.AlignCenter)
accountLabel := etk.NewText(gotext.Get("Account"))
accountLabel.SetVertical(messeji.AlignCenter)
@ -350,23 +371,25 @@ func NewBoard() *board {
checkboxGrid := etk.NewGrid()
checkboxGrid.SetColumnSizes(72, 20, -1)
checkboxGrid.SetRowSizes(-1, 20, -1, 20, -1, 20, -1)
checkboxGrid.SetRowSizes(-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)
{
grid := etk.NewGrid()
grid.AddChildAt(accountLabel, 0, 0, 1, 1)
grid.AddChildAt(b.accountGrid, 1, 0, 2, 1)
checkboxGrid.AddChildAt(grid, 0, 6, 3, 1)
checkboxGrid.AddChildAt(grid, 0, 8, 3, 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+72+20+72, 20, -1)
b.settingsGrid.SetRowSizes(72, 72+20+72+20+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)
@ -1066,6 +1089,20 @@ func (b *board) toggleMovesCheckbox() error {
return nil
}
func (b *board) toggleFlipBoardCheckbox() error {
b.flipBoard = b.flipBoardCheckbox.Selected()
b.setSpaceRects()
b.updateBackgroundImage()
flipBoard := 0
if b.flipBoard {
flipBoard = 1
}
b.Client.Out <- []byte(fmt.Sprintf("set flip %d", flipBoard))
b.Client.Out <- []byte("board")
return nil
}
func (b *board) newSprite(white bool) *Sprite {
s := &Sprite{}
s.colorWhite = white
@ -1632,7 +1669,7 @@ func (b *board) setRect(x, y, w, h int) {
if dialogWidth > game.screenW {
dialogWidth = game.screenW
}
dialogHeight := 72 + 72 + 20 + 72 + 20 + 72 + 20 + 72 + 20 + game.scale(baseButtonHeight)
dialogHeight := 72 + 72 + 20 + 72 + 20 + 72 + 20 + 72 + 20 + 72 + 20 + game.scale(baseButtonHeight)
if dialogHeight > game.screenH {
dialogHeight = game.screenH
}
@ -1916,7 +1953,7 @@ func (b *board) setSpaceRects() {
}
// Flip board.
if b.gameState.PlayerNumber == 1 {
if b.gameState.PlayerNumber == 1 && !b.flipBoard {
for i := 0; i < 6; i++ {
j, k, l, m := 1+i, 12-i, 13+i, 24-i
b.spaceRects[j], b.spaceRects[k], b.spaceRects[l], b.spaceRects[m] = b.spaceRects[k], b.spaceRects[j], b.spaceRects[m], b.spaceRects[l]
@ -1943,7 +1980,7 @@ func (b *board) bottomRow(space int) bool {
bottomEnd := 12
bottomBar := bgammon.SpaceBarPlayer
bottomHome := bgammon.SpaceHomePlayer
if b.gameState.PlayerNumber == 2 {
if b.flipBoard || b.gameState.PlayerNumber == 2 {
bottomStart = 1
bottomEnd = 12
}
@ -1994,6 +2031,42 @@ func (b *board) processState() {
}
b.lastPlayerNumber = b.gameState.PlayerNumber
if b.flipBoard || b.gameState.PlayerNumber == 2 {
if b.opponentLabel.activeColor != colorBlack {
b.opponentLabel.activeColor = colorBlack
b.opponentLabel.SetForegroundColor(colorBlack)
b.opponentPipCount.SetForegroundColor(colorBlack)
b.opponentMovesLabel.SetForegroundColor(colorBlack)
b.opponentLabel.lastActive = !b.opponentLabel.active
b.opponentLabel.updateBackground()
}
if b.playerLabel.activeColor != colorWhite {
b.playerLabel.activeColor = colorWhite
b.playerLabel.SetForegroundColor(colorWhite)
b.playerPipCount.SetForegroundColor(colorWhite)
b.playerMovesLabel.SetForegroundColor(colorWhite)
b.playerLabel.lastActive = !b.opponentLabel.active
b.playerLabel.updateBackground()
}
} else {
if b.opponentLabel.activeColor != colorWhite {
b.opponentLabel.activeColor = colorWhite
b.opponentLabel.SetForegroundColor(colorWhite)
b.opponentPipCount.SetForegroundColor(colorWhite)
b.opponentMovesLabel.SetForegroundColor(colorWhite)
b.opponentLabel.lastActive = !b.opponentLabel.active
b.opponentLabel.updateBackground()
}
if b.playerLabel.activeColor != colorBlack {
b.playerLabel.activeColor = colorBlack
b.playerLabel.SetForegroundColor(colorBlack)
b.playerPipCount.SetForegroundColor(colorBlack)
b.playerMovesLabel.SetForegroundColor(colorBlack)
b.playerLabel.lastActive = !b.opponentLabel.active
b.playerLabel.updateBackground()
}
}
var showGrid *etk.Grid
if !b.gameState.Spectating && !b.availableStale {
if b.gameState.MayRoll() {
@ -2024,6 +2097,9 @@ func (b *board) processState() {
spaceValue := b.gameState.Board[space]
white := spaceValue < 0
if b.flipBoard {
white = !white
}
abs := spaceValue
if abs < 0 {
@ -2072,7 +2148,7 @@ func (b *board) processState() {
b.updateOpponentLabel()
b.updatePlayerLabel()
if b.gameState.Turn != 1 {
if b.gameState.Turn != b.gameState.PlayerNumber {
return
}
@ -2233,14 +2309,7 @@ func (b *board) _movePiece(sprite *Sprite, from int, to int, speed int, pause bo
sprite.y = y
sprite.toStart = time.Time{}
/*homeSpace := b.ClientWebSocket.Board.PlayerHomeSpace()
if b.gameState.Turn != b.gameState.Player {
homeSpace = 25 - homeSpace
}
if to != homeSpace {*/
b.spaceSprites[to] = append(b.spaceSprites[to], sprite)
/*}*/
for i, s := range b.spaceSprites[from] {
if s == sprite {
b.spaceSprites[from] = append(b.spaceSprites[from][:i], b.spaceSprites[from][i+1:]...)
@ -2569,7 +2638,7 @@ func (bw *BoardWidget) HandleMouse(cursor image.Point, pressed bool, clicked boo
// TODO allow grabbing multiple pieces by grabbing further down the stack
if !handled && b.playerTurn() && clicked && (b.lastDragClick.IsZero() || time.Since(b.lastDragClick) >= 50*time.Millisecond) {
s, space := b.spriteAt(cx, cy)
if s != nil && s.colorWhite == (b.gameState.PlayerNumber == 2) && space != bgammon.SpaceHomeOpponent && (space != bgammon.SpaceHomePlayer || !game.Board.gameState.Acey || !game.Board.gameState.Player1.Entered) {
if s != nil && s.colorWhite == (b.flipBoard || b.gameState.PlayerNumber == 2) && space != bgammon.SpaceHomeOpponent && (space != bgammon.SpaceHomePlayer || !game.Board.gameState.Acey || !game.Board.gameState.Player1.Entered) {
b.startDrag(s, space, false)
handled = true
}

View file

@ -41,7 +41,7 @@ import (
"golang.org/x/text/language"
)
const version = "v1.2.0p1"
const version = "v1.2.1"
const DefaultServerAddress = "wss://ws.bgammon.org"
@ -569,8 +569,7 @@ type Game struct {
register bool
loggedIn bool
Watch bool
TV bool
TV bool
Client *Client
@ -1328,6 +1327,7 @@ func (g *Game) handleEvent(e interface{}) {
}
case *bgammon.EventBoard:
g.Board.Lock()
g.Board.stateLock.Lock()
*g.Board.gameState = ev.GameState
*g.Board.gameState.Game = *ev.GameState.Game
@ -1364,8 +1364,10 @@ func (g *Game) handleEvent(e interface{}) {
}
g.Board.availableStale = false
g.Board.stateLock.Unlock()
g.Board.processState()
g.Board.Unlock()
setViewBoard(true)
case *bgammon.EventRolled:
g.Board.Lock()
@ -1413,6 +1415,7 @@ func (g *Game) handleEvent(e interface{}) {
if ev.Player == g.Client.Username && !g.Board.gameState.Spectating {
return
}
g.Board.Lock()
g.Unlock()
for _, move := range ev.Moves {
@ -1456,6 +1459,10 @@ func (g *Game) handleEvent(e interface{}) {
b.showPipCountCheckbox.SetSelected(b.showPipCount)
b.showMoves = ev.Moves
b.showMovesCheckbox.SetSelected(b.showMoves)
b.flipBoard = ev.Flip
b.flipBoardCheckbox.SetSelected(b.flipBoard)
b.setSpaceRects()
b.updateBackgroundImage()
b.processState()
b.updatePlayerLabel()
b.updateOpponentLabel()
@ -1952,11 +1959,6 @@ func (g *Game) Connect() {
time.Sleep(time.Second)
c.Out <- []byte("tv")
}()
} else if g.Watch {
go func() {
time.Sleep(time.Second)
c.Out <- []byte("watch")
}()
}
connectTime := time.Now()
@ -2019,11 +2021,6 @@ func (g *Game) ConnectLocal(conn net.Conn) {
time.Sleep(time.Second)
c.Out <- []byte("tv")
}()
} else if g.Watch {
go func() {
time.Sleep(time.Second)
c.Out <- []byte("watch")
}()
}
go c.connectTCP(conn)

View file

@ -91,7 +91,7 @@ type lobby struct {
func NewLobby() *lobby {
mainButtons = []*lobbyButton{
{gotext.Get("Refresh matches")},
{gotext.Get("Create new match")},
{gotext.Get("Create match")},
{gotext.Get("Join match")},
}
@ -322,12 +322,6 @@ func (l *lobby) selectButton(buttonIndex int) func() error {
l.createGamePassword.Field.SetText("")
l.rebuildButtonsGrid()
scheduleFrame()
/*case lobbyButtonWatch:
if l.selected < 0 || l.selected >= len(l.games) {
return
}
l.c.Out <- []byte(fmt.Sprintf("watch %d", l.games[l.selected].ID))
setViewBoard(true)*/
case lobbyButtonJoin:
if l.selected < 0 || l.selected >= len(l.games) {
return nil

View file

@ -112,6 +112,9 @@ msgstr ""
msgid "Failed to submit moves: %s"
msgstr ""
msgid "Flip board"
msgstr ""
msgid "Good Luck, Have Fun"
msgstr ""

View file

@ -34,6 +34,7 @@ func (w *tutorialWidget) hide() {
game.lobby.showCreateGame = false
game.setRoot(listGamesFrame)
setViewBoard(false)
game.Board.gameState.PlayerNumber = 0
w.grid.Clear()
}
@ -81,6 +82,10 @@ func (w *tutorialWidget) setPage(page int) {
case 3:
game.lobby.showCreateGame = false
game.setRoot(listGamesFrame)
game.Board.gameState.PlayerNumber = 1
if game.needLayoutBoard {
game.layoutBoard()
}
setViewBoard(true)
title = gotext.Get("Board")
message = gotext.Get("You have the black checkers. You can move a checker by either clicking it or dragging it.")

4
go.mod
View file

@ -3,12 +3,12 @@ module code.rocket9labs.com/tslocum/boxcars
go 1.17
require (
code.rocket9labs.com/tslocum/bgammon v0.0.0-20240103025410-1146b1c5c390
code.rocket9labs.com/tslocum/bgammon v0.0.0-20240105195235-fb58a25c67de
code.rocket9labs.com/tslocum/bgammon-tabula-bot v0.0.0-20231230070310-ccf9dd359e84
code.rocket9labs.com/tslocum/etk v0.0.0-20231225090418-db70da18e067
code.rocket9labs.com/tslocum/tabula v0.0.0-20240102002109-32165ed8d7ec
code.rocketnine.space/tslocum/kibodo v1.0.3-0.20231214093410-c8a7dcdbc544
code.rocketnine.space/tslocum/messeji v1.0.6-0.20231218071755-e4087431ad9f
code.rocketnine.space/tslocum/messeji v1.0.6-0.20240105201028-fc0897906b05
github.com/hajimehoshi/ebiten/v2 v2.6.3
github.com/leonelquinteros/gotext v1.5.3-0.20231003122255-12a99145a351
github.com/llgcode/draw2d v0.0.0-20231212091825-f55e0c776b44

8
go.sum
View file

@ -1,7 +1,7 @@
code.rocket9labs.com/tslocum/bei v0.0.0-20240104011455-722dd38546ef h1:pE3r8hvM5fWnbtHoaY6TL/mlixMATbMo9y16u2F0cXU=
code.rocket9labs.com/tslocum/bei v0.0.0-20240104011455-722dd38546ef/go.mod h1:tS60/VNAJphKvDBkSLQhKALa15msIAuWWfEKNc4oFZc=
code.rocket9labs.com/tslocum/bgammon v0.0.0-20240103025410-1146b1c5c390 h1:hnmyhy/coq51rFCvReDk12ZOli5JjWrZm8/52ibbAoI=
code.rocket9labs.com/tslocum/bgammon v0.0.0-20240103025410-1146b1c5c390/go.mod h1:6NKX9J8GqNltV66SCqPwJpA8tGLY/rsE9bsMzCcHUbg=
code.rocket9labs.com/tslocum/bgammon v0.0.0-20240105195235-fb58a25c67de h1:qCHSTtVclOAUW2x1nX72XWDaR4za2/J6q7GceCxhEX8=
code.rocket9labs.com/tslocum/bgammon v0.0.0-20240105195235-fb58a25c67de/go.mod h1:qoXFACgj2k4y5996rU5+GxZ2x9De1ZyA3+GraUJpFj8=
code.rocket9labs.com/tslocum/bgammon-tabula-bot v0.0.0-20231230070310-ccf9dd359e84 h1:OG0cqxT6PRDJYEUKE7Wof+REP9vvPau6ycGMT5VW/X8=
code.rocket9labs.com/tslocum/bgammon-tabula-bot v0.0.0-20231230070310-ccf9dd359e84/go.mod h1:OPxtBXWd9keQ8LekCXT6bdfVqIVN3CqvM/lnekib4D8=
code.rocket9labs.com/tslocum/etk v0.0.0-20231225090418-db70da18e067 h1:DmzeAyFImu/z58bv3hPLybYoFEviCt4ugEp7SM0Y+qM=
@ -10,8 +10,8 @@ code.rocket9labs.com/tslocum/tabula v0.0.0-20240102002109-32165ed8d7ec h1:Ugo9B7
code.rocket9labs.com/tslocum/tabula v0.0.0-20240102002109-32165ed8d7ec/go.mod h1:ZvMo2xto5GUODvzLiTNEg5DjHoKULK+HGt8wfYHHq78=
code.rocketnine.space/tslocum/kibodo v1.0.3-0.20231214093410-c8a7dcdbc544 h1:G+yE90iQh2Pmzd6otFl7LEGVI+a/HRCnehcU01CjXyo=
code.rocketnine.space/tslocum/kibodo v1.0.3-0.20231214093410-c8a7dcdbc544/go.mod h1:FEGJwIgz3VmyN5khErdyEVX0YBG/VIPwogvNApc9TTQ=
code.rocketnine.space/tslocum/messeji v1.0.6-0.20231218071755-e4087431ad9f h1:/6Mpu+9TfQyee7uJ1epZEEfGGCT67bvZKpVhWy8T7cs=
code.rocketnine.space/tslocum/messeji v1.0.6-0.20231218071755-e4087431ad9f/go.mod h1:hA/frrbchjCX75HUG/GH97X3eH8xw++AKC2F+z+uXyA=
code.rocketnine.space/tslocum/messeji v1.0.6-0.20240105201028-fc0897906b05 h1:2nhD6fTuIJU94hIFD9hoSx+Jwr2WKxGLFv6jxgdmEIU=
code.rocketnine.space/tslocum/messeji v1.0.6-0.20240105201028-fc0897906b05/go.mod h1:cznUGfvC7BKbc5sx4I36XpLsF0ar3TPJYZlrND0IlDQ=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=