Add move command
This commit is contained in:
parent
42b74236e6
commit
2408478221
6 changed files with 199 additions and 28 deletions
|
@ -9,6 +9,7 @@ type serverClient struct {
|
|||
account int
|
||||
connected int64
|
||||
lastActive int64
|
||||
lastPing int64
|
||||
commands <-chan []byte
|
||||
events chan<- []byte
|
||||
bgammon.Client
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
49
game.go
|
@ -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
|
||||
}
|
||||
|
||||
|
|
17
gamestate.go
17
gamestate.go
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue