Add doubling cube

This commit is contained in:
Trevor Slocum 2023-10-20 13:51:32 -07:00
parent f96a4fdb1a
commit 8da2829b6a
6 changed files with 169 additions and 14 deletions

View file

@ -45,6 +45,13 @@ formatted responses are more easily parsed by computers.
- Join match by match ID or by player.
- Aliases: `j`
- `double`
- Offer double to opponent.
- Aliases: `d`
- `resign`
- Decline double offer and resign game.
- `roll`
- Roll dice.
- Aliases: `r`
@ -58,7 +65,7 @@ formatted responses are more easily parsed by computers.
- Aliases: `r`
- `ok`
- Confirm checker movement and pass turn to next player.
- Accept double offer or confirm checker movement and pass turn to next player.
- Aliases: `k`
- `rematch`

View file

@ -617,6 +617,88 @@ COMMANDS:
}
clientGame.removeClient(cmd.client)
case bgammon.CommandDouble, "d":
if clientGame == nil {
cmd.client.sendNotice("You are not currently in a match.")
continue
}
if clientGame.Turn != cmd.client.playerNumber {
cmd.client.sendNotice("It is not your turn.")
continue
}
gameState := &bgammon.GameState{
Game: clientGame.Game,
PlayerNumber: cmd.client.playerNumber,
Available: clientGame.LegalMoves(),
}
if !gameState.MayDouble() {
cmd.client.sendNotice("You may not double at this time.")
continue
}
clientGame.DoubleOffered = true
cmd.client.sendNotice(fmt.Sprintf("Double offered to opponent (%d points).", clientGame.Points*2))
clientGame.opponent(cmd.client).sendNotice(fmt.Sprintf("%s offers a double (%d points).", cmd.client.name, clientGame.Points*2))
clientGame.eachClient(func(client *serverClient) {
if client.json {
clientGame.sendBoard(client)
}
})
case bgammon.CommandResign:
if clientGame == nil {
cmd.client.sendNotice("You are not currently in a match.")
continue
}
gameState := &bgammon.GameState{
Game: clientGame.Game,
PlayerNumber: cmd.client.playerNumber,
Available: clientGame.LegalMoves(),
}
if !gameState.MayResign() {
cmd.client.sendNotice("You may not resign at this time.")
continue
}
cmd.client.sendNotice("Declined double offer")
clientGame.opponent(cmd.client).sendNotice(fmt.Sprintf("%s declined double offer.", cmd.client.name))
log.Println("RESIGN VALUE: ", clientGame.DoubleValue) // TODO
if cmd.client.playerNumber == 1 {
clientGame.Player2.Points += clientGame.DoubleValue
if clientGame.Player2.Points >= clientGame.Points {
clientGame.Winner = 2
} else {
clientGame.Reset()
}
} else {
clientGame.Player1.Points += clientGame.DoubleValue
if clientGame.Player1.Points >= clientGame.Points {
clientGame.Winner = 1
} else {
clientGame.Reset()
}
}
var winEvent *bgammon.EventWin
if clientGame.Winner != 0 {
winEvent = &bgammon.EventWin{}
if clientGame.Winner == 1 {
winEvent.Player = clientGame.Player1.Name
} else {
winEvent.Player = clientGame.Player2.Name
}
}
clientGame.eachClient(func(client *serverClient) {
clientGame.sendBoard(client)
if winEvent != nil {
client.sendEvent(winEvent)
}
})
case bgammon.CommandRoll, "r":
if clientGame == nil {
cmd.client.sendEvent(&bgammon.EventFailedRoll{
@ -783,6 +865,21 @@ COMMANDS:
continue
}
if clientGame.DoubleOffered && clientGame.Turn != cmd.client.playerNumber {
opponent := clientGame.opponent(cmd.client)
if opponent == nil {
cmd.client.sendNotice("You may not accept the double until your opponent rejoins the match.")
continue
}
clientGame.DoubleOffered = false
clientGame.DoubleValue *= 2
clientGame.DoublePlayer = opponent.playerNumber
cmd.client.sendNotice("Accepted double.")
opponent.sendNotice(fmt.Sprintf("%s accepted double.", cmd.client.name))
continue
}
legalMoves := clientGame.LegalMoves()
if len(legalMoves) != 0 {
available := bgammon.FlipMoves(legalMoves, cmd.client.playerNumber)

View file

@ -14,6 +14,8 @@ const (
CommandCreate = "create" // Create match.
CommandJoin = "join" // Join match.
CommandLeave = "leave" // Leave match.
CommandDouble = "double" // Offer double to opponent.
CommandResign = "resign" // Decline double offer and resign game.
CommandRoll = "roll" // Roll dice.
CommandMove = "move" // Move checkers.
CommandReset = "reset" // Reset checker movement.

47
game.go
View file

@ -22,30 +22,39 @@ type Game struct {
Roll1 int
Roll2 int
Moves [][]int // Pending moves.
Points int
Points int // Points required to win the match.
DoubleValue int // Doubling cube value.
DoublePlayer int // Player that currently posesses the doubling cube.
DoubleOffered bool // Whether the current player is offering a double.
boardStates [][]int // One board state for each move to allow undoing a move.
}
func NewGame() *Game {
return &Game{
Board: NewBoard(),
Player1: NewPlayer(1),
Player2: NewPlayer(2),
Board: NewBoard(),
Player1: NewPlayer(1),
Player2: NewPlayer(2),
Points: 1,
DoubleValue: 1,
}
}
func (g *Game) Copy() *Game {
newGame := &Game{
Board: make([]int, len(g.Board)),
Player1: g.Player1,
Player2: g.Player2,
Turn: g.Turn,
Winner: g.Winner,
Roll1: g.Roll1,
Roll2: g.Roll2,
Moves: make([][]int, len(g.Moves)),
boardStates: make([][]int, len(g.boardStates)),
Board: make([]int, len(g.Board)),
Player1: g.Player1,
Player2: g.Player2,
Turn: g.Turn,
Winner: g.Winner,
Roll1: g.Roll1,
Roll2: g.Roll2,
Moves: make([][]int, len(g.Moves)),
Points: g.Points,
DoubleValue: g.DoubleValue,
DoublePlayer: g.DoublePlayer,
boardStates: make([][]int, len(g.boardStates)),
}
copy(newGame.Board, g.Board)
copy(newGame.Moves, g.Moves)
@ -68,6 +77,18 @@ func (g *Game) NextTurn() {
g.boardStates = g.boardStates[:0]
}
func (g *Game) Reset() {
g.Board = NewBoard()
g.Turn = 0
g.Roll1 = 0
g.Roll2 = 0
g.Moves = nil
g.DoubleValue = 1
g.DoublePlayer = 0
g.DoubleOffered = false
g.boardStates = nil
}
func (g *Game) turnPlayer() Player {
switch g.Turn {
case 2:

View file

@ -56,8 +56,19 @@ func (g *GameState) SpaceAt(x int, y int) int {
return space
}
// MayDouble returns whether the player may send the 'double' command.
func (g *GameState) MayDouble() bool {
if g.Winner != 0 {
return false
}
return g.Turn != 0 && g.Turn == g.PlayerNumber && g.Roll1 == 0 && !g.DoubleOffered && (g.DoublePlayer == 0 || g.DoublePlayer == g.PlayerNumber)
}
// MayRoll returns whether the player may send the 'roll' command.
func (g *GameState) MayRoll() bool {
if g.Winner != 0 || g.DoubleOffered {
return false
}
switch g.Turn {
case 0:
if g.PlayerNumber == 1 {
@ -78,10 +89,26 @@ func (g *GameState) MayRoll() bool {
// MayOK returns whether the player may send the 'ok' command.
func (g *GameState) MayOK() bool {
if g.Winner != 0 {
return false
} else if g.Turn != 0 && g.Turn != g.PlayerNumber && g.DoubleOffered {
return true
}
return g.Turn != 0 && g.Turn == g.PlayerNumber && g.Roll1 != 0 && len(g.Available) == 0
}
// MayResign returns whether the player may send the 'resign' command.
func (g *GameState) MayResign() bool {
if g.Winner != 0 {
return false
}
return g.Turn != 0 && g.Turn != g.PlayerNumber && g.DoubleOffered
}
// MayReset returns whether the player may send the 'reset' command.
func (g *GameState) MayReset() bool {
if g.Winner != 0 {
return false
}
return g.Turn != 0 && g.Turn == g.PlayerNumber && len(g.Moves) > 0
}

View file

@ -3,6 +3,7 @@ package bgammon
type Player struct {
Number int // 1 black, 2 white
Name string
Points int
}
func NewPlayer(number int) Player {