Check for win condition

This commit is contained in:
Trevor Slocum 2023-09-07 23:35:27 -07:00
parent 042a9599b8
commit daa8bce760
8 changed files with 106 additions and 15 deletions

View file

@ -131,6 +131,9 @@ provide clients with the initial game state.
- Sent after sending `ok` when there are one or more legal moves still available to the player.
- Players must make moves using all available dice rolls before ending their turn.
- `win <player:text> wins!`
- Sent after a player bears their final checker off the board.
- `say <player:text> <message:line>`
- Chat message from another player.

View file

@ -1,6 +1,10 @@
package bgammon
import "sort"
import (
"sort"
"strconv"
"strings"
)
const (
SpaceHomePlayer = 0 // Current player's home.
@ -89,6 +93,20 @@ func CanBearOff(board []int, player int) bool {
return ok
}
func ParseSpace(space string) int {
i, err := strconv.Atoi(space)
if err != nil {
switch strings.ToLower(space) {
case "bar", "b":
return SpaceBarPlayer
case "off", "o", "home", "h":
return SpaceHomePlayer
}
return -1
}
return i
}
func compareMoveFunc(moves [][]int) func(i, j int) bool {
return func(i, j int) bool {
if moves[j][0] == moves[i][0] {

View file

@ -55,6 +55,8 @@ func (c *serverClient) sendEvent(e interface{}) {
ev.Type = bgammon.EventTypeFailedMove
case *bgammon.EventFailedOk:
ev.Type = bgammon.EventTypeFailedOk
case *bgammon.EventWin:
ev.Type = bgammon.EventTypeWin
default:
log.Panicf("unknown event type %+v", ev)
}
@ -111,6 +113,8 @@ func (c *serverClient) sendEvent(e interface{}) {
c.Write([]byte(fmt.Sprintf("failedmove %d/%d %s", ev.From, ev.To, ev.Reason)))
case *bgammon.EventFailedOk:
c.Write([]byte(fmt.Sprintf("failedok %s", ev.Reason)))
case *bgammon.EventWin:
c.Write([]byte(fmt.Sprintf("win %s wins!", ev.Player)))
default:
log.Panicf("unknown event type %+v", ev)
}

View file

@ -34,6 +34,10 @@ func newServerGame(id int) *serverGame {
}
func (g *serverGame) roll(player int) bool {
if g.Winner != 0 {
return false
}
if g.Turn == 0 {
if player == 1 {
if g.Roll1 != 0 {

View file

@ -30,7 +30,7 @@ type server struct {
commands chan serverCommand
gamesLock sync.RWMutex
clientsLock sync.RWMutex // TODO need RW?
clientsLock sync.Mutex
}
func newServer() *server {
@ -48,7 +48,7 @@ func newServer() *server {
}
func (s *server) listen(network string, address string) {
log.Printf("Listening for %s connections on %s...", network, address)
log.Printf("Listening for %s connections on %s...", strings.ToUpper(network), address)
listener, err := net.Listen(network, address)
if err != nil {
log.Fatalf("failed to listen on %s: %s", address, err)
@ -468,6 +468,7 @@ COMMANDS:
})
continue
}
ev := &bgammon.EventRolled{
Roll1: clientGame.Roll1,
Roll2: clientGame.Roll2,
@ -522,13 +523,13 @@ COMMANDS:
sendUsage()
continue COMMANDS
}
from, err := strconv.Atoi(string(split[0]))
if err != nil {
from := bgammon.ParseSpace(string(split[0]))
if from == -1 {
sendUsage()
continue COMMANDS
}
to, err := strconv.Atoi(string(split[1]))
if err != nil {
to := bgammon.ParseSpace(string(split[1]))
if to == -1 {
sendUsage()
continue COMMANDS
}
@ -557,14 +558,29 @@ COMMANDS:
})
continue
}
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) {
ev := &bgammon.EventMoved{
Moves: bgammon.FlipMoves(moves, client.playerNumber),
}
ev.Player = string(cmd.client.name)
client.sendEvent(ev)
clientGame.sendBoard(client)
if winEvent != nil {
client.sendEvent(winEvent)
}
})
case bgammon.CommandReset:
if clientGame == nil {
@ -607,12 +623,10 @@ COMMANDS:
legalMoves := clientGame.LegalMoves()
if len(legalMoves) != 0 {
playerNumber := 1
if clientGame.client2 == cmd.client {
playerNumber = 2
}
available := bgammon.FlipMoves(legalMoves, cmd.client.playerNumber)
bgammon.SortMoves(available)
cmd.client.sendEvent(&bgammon.EventFailedOk{
Reason: fmt.Sprintf("The following legal moves are available: %s", bgammon.FormatAndFlipMoves(legalMoves, playerNumber)),
Reason: fmt.Sprintf("The following legal moves are available: %s", bgammon.FormatMoves(available)),
})
continue
}

View file

@ -41,4 +41,5 @@ const (
EventTypeMoved = "moved"
EventTypeFailedMove = "failedmove"
EventTypeFailedOk = "failedok"
EventTypeWin = "win"
)

View file

@ -108,6 +108,10 @@ type EventFailedOk struct {
Reason string
}
type EventWin struct {
Event
}
func DecodeEvent(message []byte) (interface{}, error) {
e := &Event{}
err := json.Unmarshal(message, e)
@ -147,6 +151,8 @@ func DecodeEvent(message []byte) (interface{}, error) {
ev = &EventFailedMove{}
case EventTypeFailedOk:
ev = &EventFailedOk{}
case EventTypeWin:
ev = &EventWin{}
default:
return nil, fmt.Errorf("failed to decode event: unknown event type: %s", e.Type)
}

45
game.go
View file

@ -18,6 +18,7 @@ type Game struct {
Player1 Player
Player2 Player
Turn int
Winner int
Roll1 int
Roll2 int
Moves [][]int // Pending moves.
@ -39,6 +40,7 @@ func (g *Game) Copy() *Game {
Player1: g.Player1,
Player2: g.Player2,
Turn: g.Turn,
Winner: g.Winner,
Roll1: g.Roll1,
Roll2: g.Roll2,
Moves: make([][]int, len(g.Moves)),
@ -51,6 +53,10 @@ func (g *Game) Copy() *Game {
}
func (g *Game) NextTurn() {
if g.Winner != 0 {
return
}
nextTurn := 1
if g.Turn == 1 {
nextTurn = 2
@ -100,6 +106,10 @@ func (g *Game) iterateSpaces(from int, to int, f func(space int, spaceCount int)
// AddMoves adds moves to the game state. Adding a backwards move will remove the equivalent existing move.
func (g *Game) AddMoves(moves [][]int) bool {
if g.Winner != 0 {
return false
}
delta := 1
if g.Turn == 2 {
delta = -1
@ -139,6 +149,7 @@ VALIDATEMOVES:
return false
}
var checkWin bool
ADDMOVES:
for _, move := range addMoves {
l := gameCopy.LegalMoves()
@ -165,6 +176,10 @@ ADDMOVES:
gameCopy.Board[move[1]] += delta
}
if move[1] == SpaceHomePlayer || move[1] == SpaceHomeOpponent {
checkWin = true
}
gameCopy.Moves = append(gameCopy.Moves, []int{move[0], move[1]})
continue ADDMOVES
}
@ -191,11 +206,26 @@ ADDMOVES:
g.Board = gameCopy.Board
g.Moves = gameCopy.Moves
g.boardStates = gameCopy.boardStates
if checkWin {
var foundChecker bool
for space := 1; space <= 24; space++ {
log.Println(space)
if PlayerCheckers(g.Board[space], g.Turn) != 0 {
foundChecker = true
break
}
}
if !foundChecker {
g.Winner = g.Turn
}
}
return true
}
func (g *Game) LegalMoves() [][]int {
if g.Roll1 == 0 || g.Roll2 == 0 {
if g.Winner != 0 || g.Roll1 == 0 || g.Roll2 == 0 {
return nil
}
@ -665,13 +695,24 @@ func FlipMoves(moves [][]int, player int) [][]int {
return m
}
func FormatSpace(space int) []byte {
if space >= 1 && space <= 24 {
return []byte(strconv.Itoa(space))
} else if space == SpaceBarPlayer || space == SpaceBarOpponent {
return []byte("bar")
} else if space == SpaceHomePlayer || space == SpaceHomeOpponent {
return []byte("off")
}
return []byte("?")
}
func FormatMoves(moves [][]int) []byte {
var out bytes.Buffer
for i := range moves {
if i != 0 {
out.WriteByte(' ')
}
out.Write([]byte(fmt.Sprintf("%d/%d", moves[i][0], moves[i][1])))
out.Write([]byte(fmt.Sprintf("%s/%s", FormatSpace(moves[i][0]), FormatSpace(moves[i][1]))))
}
return out.Bytes()
}