Add move command

This commit is contained in:
Trevor Slocum 2023-08-25 00:26:56 -07:00
parent 42b74236e6
commit 2408478221
6 changed files with 199 additions and 28 deletions

View file

@ -9,6 +9,7 @@ type serverClient struct {
account int
connected int64
lastActive int64
lastPing int64
commands <-chan []byte
events chan<- []byte
bgammon.Client

View file

@ -59,9 +59,13 @@ func (g *serverGame) roll(player int) bool {
func (g *serverGame) sendBoard(client *serverClient) {
if client.json {
buf, err := json.Marshal(g.Game)
gameState := bgammon.GameState{
Game: g.Game,
Available: g.LegalMoves(),
}
buf, err := json.Marshal(gameState)
if err != nil {
log.Fatalf("failed to marshal json for %+v: %s", g.Game, err)
log.Fatalf("failed to marshal json for %+v: %s", gameState, err)
}
client.events <- []byte(fmt.Sprintf("board %s", buf))
return
@ -120,6 +124,20 @@ func (g *serverGame) addClient(client *serverClient) bool {
return ok
}
func (g *serverGame) removeClient(client *serverClient) {
switch {
case g.client1 == client:
g.client1 = nil
case g.client2 == client:
g.client2 = nil
default:
return
}
// TODO game is considered paused when only one player is present
// once started, only the same player may join and continue the game
log.Println("removed client", client)
}
func (g *serverGame) opponent(client *serverClient) *serverClient {
if g.client1 == client {
return g.client2

View file

@ -22,6 +22,11 @@ func main() {
g.Roll1 = 3
g.Roll2 = 2
g.Turn = 1
log.Println("initial legal moves")
log.Printf("%+v", g.LegalMoves())
g.Moves = append(g.Moves, []int{6, 4})
log.Printf("Legal moves after %+v", g.Moves)
log.Printf("%+v", g.LegalMoves())
playerNumber := 1

View file

@ -83,9 +83,26 @@ func (s *server) handleConnection(conn net.Conn) {
s.sendHello(c)
go s.handlePingClient(c)
s.handleClientCommands(c)
}
func (s *server) handlePingClient(c *serverClient) {
// TODO only ping when there is no recent activity
t := time.NewTicker(time.Minute * 2)
for {
<-t.C
if len(c.name) == 0 {
c.Terminate("User did not send login command within 2 minutes.")
return
}
c.lastPing = time.Now().Unix()
c.events <- []byte(fmt.Sprintf("ping %d", c.lastPing))
}
}
func (s *server) handleClientCommands(c *serverClient) {
var command []byte
for command = range c.commands {
@ -197,6 +214,8 @@ COMMANDS:
}
}
clientGame := s.gameByClient(cmd.client)
switch keyword {
case bgammon.CommandHelp, "h":
// TODO get extended help by specifying a command after help
@ -226,12 +245,11 @@ COMMANDS:
if len(params) == 0 {
continue
}
g := s.gameByClient(cmd.client)
if g == nil {
if clientGame == nil {
cmd.client.events <- []byte("notice Message not sent. You are not currently in a game.")
continue
}
opponent := g.opponent(cmd.client)
opponent := clientGame.opponent(cmd.client)
if opponent == nil {
cmd.client.events <- []byte("notice Message not sent. There is no one else in the game.")
continue
@ -284,7 +302,7 @@ COMMANDS:
g.sendBoard(cmd.client)
case bgammon.CommandJoin, "j":
if s.gameByClient(cmd.client) != nil {
if clientGame != nil {
cmd.client.events <- []byte("failedjoin Please leave the game you are in before joining another game.")
continue
}
@ -320,53 +338,120 @@ COMMANDS:
}
}
case bgammon.CommandLeave, "l":
g := s.gameByClient(cmd.client)
if g == nil {
if clientGame == nil {
cmd.client.events <- []byte("failedleave You are not currently in a game.")
continue
}
if clientGame.client1 == cmd.client {
clientGame.client1 = nil
} else {
clientGame.client2 = nil
}
// TODO handle pausing or ending game
// TODO move to .removeClient
id := g.id
// TODO remove
cmd.client.events <- []byte(fmt.Sprintf("left %d", id))
leftMessage := []byte(fmt.Sprintf("left %s", cmd.client.name))
cmd.client.events <- leftMessage
opponent := clientGame.opponent(cmd.client)
if opponent != nil {
opponent.events <- leftMessage
}
case bgammon.CommandRoll, "r":
g := s.gameByClient(cmd.client)
if g == nil {
if clientGame == nil {
cmd.client.events <- []byte("notice You are not currently in a game.")
continue COMMANDS
continue
}
playerNumber := 1
if g.client2 == cmd.client {
if clientGame.client2 == cmd.client {
playerNumber = 2
}
if !g.roll(playerNumber) {
if !clientGame.roll(playerNumber) {
cmd.client.events <- []byte("notice It is not your turn to roll.")
} else {
g.eachClient(func(client *serverClient) {
client.events <- []byte(fmt.Sprintf("rolled %d %d", g.Roll1, g.Roll2))
clientGame.eachClient(func(client *serverClient) {
client.events <- []byte(fmt.Sprintf("rolled %d %d", clientGame.Roll1, clientGame.Roll2))
})
}
case bgammon.CommandMove, "m":
case bgammon.CommandMove, "m", "mv":
if clientGame == nil {
cmd.client.events <- []byte("notice You are not currently in a game.")
continue
}
sendUsage := func() {
cmd.client.events <- []byte("notice Specify one or more moves in the form FROM/TO. For example: 8/4 6/4")
}
if len(params) == 0 {
sendUsage()
continue
}
gameCopy := bgammon.Game{}
gameCopy = *clientGame.Game
gameCopy.Moves = [][]int{}
copy(gameCopy.Moves, clientGame.Moves)
var moves [][]int
for i := range params {
split := bytes.Split(params[i], []byte("/"))
if len(split) != 2 {
sendUsage()
continue COMMANDS
}
from, err := strconv.Atoi(string(split[0]))
if err != nil {
sendUsage()
continue COMMANDS
}
to, err := strconv.Atoi(string(split[1]))
if err != nil {
sendUsage()
continue COMMANDS
}
legalMoves := gameCopy.LegalMoves()
var found bool
for j := range legalMoves {
if legalMoves[j][0] == from && legalMoves[j][1] == to {
found = true
break
}
}
if !found {
cmd.client.events <- []byte(fmt.Sprintf("failedmove %d/%d Illegal move.", from, to))
continue COMMANDS
}
move := []int{from, to}
moves = append(moves, move)
gameCopy.Moves = append(gameCopy.Moves, move)
}
paramsText := bytes.Join(params, []byte(" "))
clientGame.Moves = gameCopy.Moves
clientGame.eachClient(func(client *serverClient) {
client.events <- []byte(fmt.Sprintf("move %s %s", cmd.client.name, paramsText))
clientGame.sendBoard(client)
})
case bgammon.CommandBoard, "b":
g := s.gameByClient(cmd.client)
if g == nil {
if clientGame == nil {
cmd.client.events <- []byte("notice You are not currently in a game.")
} else {
playerNumber := 1
if g.client2 == cmd.client {
if clientGame.client2 == cmd.client {
playerNumber = 2
}
scanner := bufio.NewScanner(bytes.NewReader(g.BoardState(playerNumber)))
scanner := bufio.NewScanner(bytes.NewReader(clientGame.BoardState(playerNumber)))
for scanner.Scan() {
cmd.client.events <- append([]byte("notice "), scanner.Bytes()...)
}
}
case bgammon.CommandDisconnect:
g := s.gameByClient(cmd.client)
if g != nil {
// todo remove client from game
if clientGame != nil {
clientGame.removeClient(cmd.client)
}
cmd.client.Terminate("Client disconnected")
default:

49
game.go
View file

@ -3,6 +3,7 @@ package bgammon
import (
"bytes"
"fmt"
"log"
"strconv"
)
@ -19,6 +20,7 @@ type Game struct {
Turn int
Roll1 int
Roll2 int
Moves [][]int // Pending moves.
}
func NewGame() *Game {
@ -71,6 +73,48 @@ func (g *Game) LegalMoves() [][]int {
return nil
}
rolls := []int{
g.Roll1,
g.Roll2,
}
if g.Roll1 == g.Roll2 { // Rolled doubles.
rolls = append(rolls, g.Roll1, g.Roll2)
}
haveDiceRoll := func(from, to int) bool {
// TODO diff needs to account for bar and home special spaces
diff := to - from
if diff < 0 {
diff *= -1
}
for _, roll := range rolls {
if roll == diff {
return true
}
}
return false
}
useDiceRoll := func(from, to int) {
// TODO diff needs to account for bar and home special spaces
diff := to - from
if diff < 0 {
diff *= -1
}
for i, roll := range rolls {
if roll == diff {
rolls = append(rolls[:i], rolls[i+1:]...)
return
}
}
log.Panicf("tried to use non-existent dice roll %d-%d, have %+v", from, to, rolls)
}
for _, move := range g.Moves {
log.Println("use move", move)
useDiceRoll(move[0], move[1])
}
var moves [][]int
for space := range g.Board {
if space == SpaceHomePlayer || space == SpaceHomeOpponent {
@ -87,6 +131,9 @@ func (g *Game) LegalMoves() [][]int {
// Enter from bar.
from, to := HomeRange(g.Turn)
g.iterateSpaces(from, to, func(homeSpace int, spaceCount int) {
if !haveDiceRoll(space, homeSpace) {
return
}
if spaceCount != g.Roll1 && spaceCount != g.Roll2 {
return
}
@ -109,7 +156,7 @@ func (g *Game) LegalMoves() [][]int {
}
g.iterateSpaces(space+dir, lastSpace, func(to int, spaceCount int) {
if spaceCount != g.Roll1 && spaceCount != g.Roll2 {
if !haveDiceRoll(space, to) {
return
}

View file

@ -2,5 +2,20 @@ package bgammon
type GameState struct {
*Game
Moves []int
Player int
Available [][]int // Legal moves.
}
func (g *GameState) OpponentPlayer() Player {
if g.Player == 1 {
return g.Player2
}
return g.Player1
}
func (g *GameState) LocalPlayer() Player {
if g.Player == 1 {
return g.Player1
}
return g.Player2
}