Add game logic
This commit is contained in:
parent
cfbdd632a9
commit
42b74236e6
10 changed files with 686 additions and 57 deletions
72
PROTOCOL.md
72
PROTOCOL.md
|
@ -10,6 +10,12 @@ Log in to bgammon. A random username is assigned when none is provided.
|
|||
|
||||
This must be the first command sent when a client connects to bgammon.
|
||||
|
||||
### `json [on/off]`
|
||||
|
||||
Turn JSON formatted messages on or off. JSON messages are not sent by default.
|
||||
|
||||
Graphical clients should send the `json on` command immediately after sending `login`.
|
||||
|
||||
### `help [command]`
|
||||
|
||||
Request help for all commands, or optionally a specific command.
|
||||
|
@ -22,7 +28,7 @@ List all games.
|
|||
|
||||
List all games.
|
||||
|
||||
### `join [ID] [password]`
|
||||
### `join [id] [password]`
|
||||
|
||||
Join game.
|
||||
|
||||
|
@ -42,6 +48,18 @@ Reset pending checker movement.
|
|||
|
||||
Confirm checker movement and pass turn to next player.
|
||||
|
||||
### `say <message>`
|
||||
|
||||
Send a chat message.
|
||||
|
||||
This command can only be used after creating or joining a game.
|
||||
|
||||
### `board`
|
||||
|
||||
Print current game state in human-readable form.
|
||||
|
||||
This command is not normally used, as the game state is provided in JSON format.
|
||||
|
||||
### `disconnect`
|
||||
|
||||
Disconnect from the server.
|
||||
|
@ -73,10 +91,58 @@ Server message. This should always be displayed to the user.
|
|||
|
||||
Start of games list.
|
||||
|
||||
### `game <ID:integer> <password:boolean> <players:integer> <name:line>`
|
||||
### `game <id:integer> <password:boolean> <players:integer> <name:line>`
|
||||
|
||||
Game description.
|
||||
|
||||
### `listend End of games list.`
|
||||
|
||||
End of games list.
|
||||
End of games list.
|
||||
|
||||
### `joined <id:integer> <player:text> <name:line>`
|
||||
|
||||
Sent after successfully creating or joining a game, and when another player
|
||||
joins a game you are in.
|
||||
|
||||
The server will always send a `board` response immediately after `joined` to
|
||||
provide clients with the initial game state.
|
||||
|
||||
### `failedjoin <message:line>`
|
||||
|
||||
Sent after failing to join a game.
|
||||
|
||||
### `parted <gameID:integer> <gameID:integer>`
|
||||
|
||||
Sent after leaving a game.
|
||||
|
||||
### `json <message:line>`
|
||||
|
||||
Server confirmation of client requested JSON formatting.
|
||||
|
||||
This message does not normally need to be displayed when using a graphical client.
|
||||
|
||||
### `board <json:line>`
|
||||
|
||||
Game state in JSON format.
|
||||
|
||||
This message is only sent to clients that have explicitly enabled JSON formatted messages.
|
||||
|
||||
```
|
||||
type Player struct {
|
||||
Number int // 1 black, 2 white
|
||||
Name string
|
||||
}
|
||||
|
||||
type Game struct {
|
||||
Board []int
|
||||
Player1 Player
|
||||
Player2 Player
|
||||
Turn int
|
||||
Roll1 int
|
||||
Roll2 int
|
||||
}
|
||||
```
|
||||
|
||||
### `say <player:text> <message:line>`
|
||||
|
||||
Chat message from another player.
|
||||
|
|
21
board.go
21
board.go
|
@ -4,6 +4,9 @@ package bgammon
|
|||
// all state sent to white, and input received from white is reversed
|
||||
// handle this transparently by translating at the message level rather than each time spaces are used
|
||||
|
||||
// HomePlayer is the real Player1 home, HomeOpponent is the real Player2 home
|
||||
// HomeBoardPlayer (Player1) ranges 1-6, HomeBoardOpponent (Player2) ranges 24-19 (visible to them as 1-6)
|
||||
|
||||
// 1-24 for 24 spaces, 2 spaces for bar, 2 spaces for home
|
||||
const (
|
||||
SpaceHomePlayer = 0
|
||||
|
@ -12,14 +15,20 @@ const (
|
|||
SpaceHomeOpponent = 27
|
||||
)
|
||||
|
||||
const numBoardSpaces = 28
|
||||
const BoardSpaces = 28
|
||||
|
||||
type Board struct {
|
||||
Space []int // Positive values represent player 1 (black), negative values represent player 2 (white).
|
||||
func NewBoard() []int {
|
||||
space := make([]int, BoardSpaces)
|
||||
space[24], space[1] = 2, -2
|
||||
space[19], space[6] = -5, 5
|
||||
space[17], space[8] = -3, 3
|
||||
space[13], space[12] = 5, -5
|
||||
return space
|
||||
}
|
||||
|
||||
func NewBoard() *Board {
|
||||
return &Board{
|
||||
Space: make([]int, numBoardSpaces),
|
||||
func HomeRange(player int) (from int, to int) {
|
||||
if player == 2 {
|
||||
return 24, 19
|
||||
}
|
||||
return 1, 6
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import "code.rocket9labs.com/tslocum/bgammon"
|
|||
|
||||
type serverClient struct {
|
||||
id int
|
||||
json bool
|
||||
name []byte
|
||||
account int
|
||||
connected int64
|
||||
|
|
|
@ -31,11 +31,11 @@ func newSocketClient(conn net.Conn, commands chan<- []byte, events <-chan []byte
|
|||
func (c *socketClient) readCommands() {
|
||||
var scanner = bufio.NewScanner(c.conn)
|
||||
for scanner.Scan() {
|
||||
log.Printf("READ COMMAND %s", scanner.Text())
|
||||
|
||||
buf := make([]byte, len(scanner.Bytes()))
|
||||
copy(buf, scanner.Bytes())
|
||||
c.commands <- buf
|
||||
|
||||
log.Printf("<- %s", scanner.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -44,7 +44,8 @@ func (c *socketClient) writeEvents() {
|
|||
for event = range c.events {
|
||||
c.conn.Write(event)
|
||||
c.conn.Write([]byte("\n"))
|
||||
log.Printf("write event %s", event)
|
||||
|
||||
log.Printf("-> %s", event)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,15 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"code.rocket9labs.com/tslocum/bgammon"
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"code.rocket9labs.com/tslocum/bgammon"
|
||||
)
|
||||
|
||||
type serverGame struct {
|
||||
|
@ -11,8 +18,9 @@ type serverGame struct {
|
|||
lastActive int64
|
||||
name []byte
|
||||
password []byte
|
||||
client1 bgammon.Client
|
||||
client2 bgammon.Client
|
||||
client1 *serverClient
|
||||
client2 *serverClient
|
||||
r *rand.Rand
|
||||
*bgammon.Game
|
||||
}
|
||||
|
||||
|
@ -23,5 +31,100 @@ func newServerGame(id int) *serverGame {
|
|||
created: now,
|
||||
lastActive: now,
|
||||
Game: bgammon.NewGame(),
|
||||
r: rand.New(rand.NewSource(time.Now().UnixNano() + rand.Int63n(1000000))),
|
||||
}
|
||||
}
|
||||
|
||||
func (g *serverGame) roll(player int) bool {
|
||||
if g.Turn == 0 {
|
||||
if player == 1 {
|
||||
if g.Roll1 != 0 {
|
||||
return false
|
||||
}
|
||||
g.Roll1 = g.r.Intn(6) + 1
|
||||
return true
|
||||
} else {
|
||||
if g.Roll2 != 0 {
|
||||
return false
|
||||
}
|
||||
g.Roll2 = g.r.Intn(6) + 1
|
||||
return true
|
||||
}
|
||||
} else if player != g.Turn || g.Roll1 != 0 || g.Roll2 != 0 {
|
||||
return false
|
||||
}
|
||||
g.Roll1, g.Roll2 = g.r.Intn(6)+1, g.r.Intn(6)+1
|
||||
return true
|
||||
}
|
||||
|
||||
func (g *serverGame) sendBoard(client *serverClient) {
|
||||
if client.json {
|
||||
buf, err := json.Marshal(g.Game)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to marshal json for %+v: %s", g.Game, err)
|
||||
}
|
||||
client.events <- []byte(fmt.Sprintf("board %s", buf))
|
||||
return
|
||||
}
|
||||
|
||||
playerNumber := 1
|
||||
if g.client2 == client {
|
||||
playerNumber = 2
|
||||
}
|
||||
scanner := bufio.NewScanner(bytes.NewReader(g.BoardState(playerNumber)))
|
||||
for scanner.Scan() {
|
||||
client.events <- append([]byte("notice "), scanner.Bytes()...)
|
||||
}
|
||||
}
|
||||
|
||||
func (g *serverGame) eachClient(f func(client *serverClient)) {
|
||||
if g.client1 != nil {
|
||||
f(g.client1)
|
||||
}
|
||||
if g.client2 != nil {
|
||||
f(g.client2)
|
||||
}
|
||||
}
|
||||
|
||||
func (g *serverGame) addClient(client *serverClient) bool {
|
||||
var ok bool
|
||||
defer func() {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
joinMessage := []byte(fmt.Sprintf("joined %d %s %s", g.id, client.name, g.name))
|
||||
client.events <- joinMessage
|
||||
opponent := g.opponent(client)
|
||||
if opponent != nil {
|
||||
opponent.events <- joinMessage
|
||||
}
|
||||
}()
|
||||
switch {
|
||||
case g.client1 != nil && g.client2 != nil:
|
||||
ok = false
|
||||
case g.client1 != nil:
|
||||
g.client2 = client
|
||||
ok = true
|
||||
case g.client2 != nil:
|
||||
g.client1 = client
|
||||
ok = true
|
||||
default:
|
||||
i := rand.Intn(2)
|
||||
if i == 0 {
|
||||
g.client1 = client
|
||||
} else {
|
||||
g.client2 = client
|
||||
}
|
||||
ok = true
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
func (g *serverGame) opponent(client *serverClient) *serverClient {
|
||||
if g.client1 == client {
|
||||
return g.client2
|
||||
} else if g.client2 == client {
|
||||
return g.client1
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"code.rocket9labs.com/tslocum/bgammon"
|
||||
)
|
||||
|
@ -13,5 +16,23 @@ func main() {
|
|||
s := newServer()
|
||||
go s.listen("tcp", "127.0.0.1:1337")
|
||||
|
||||
g := newServerGame(1)
|
||||
g.Board[bgammon.SpaceBarPlayer] = 3
|
||||
g.Board[bgammon.SpaceBarOpponent] = -2
|
||||
g.Roll1 = 3
|
||||
g.Roll2 = 2
|
||||
g.Turn = 1
|
||||
log.Printf("%+v", g.LegalMoves())
|
||||
|
||||
playerNumber := 1
|
||||
|
||||
go func() {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
scanner := bufio.NewScanner(bytes.NewReader(g.BoardState(playerNumber)))
|
||||
for scanner.Scan() {
|
||||
log.Printf("%s", append([]byte("notice "), scanner.Bytes()...))
|
||||
}
|
||||
|
||||
}()
|
||||
select {}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
|
@ -166,7 +167,7 @@ COMMANDS:
|
|||
|
||||
// Require users to send login command first.
|
||||
if cmd.client.account == -1 {
|
||||
if keyword == bgammon.CommandLogin {
|
||||
if keyword == bgammon.CommandLogin || keyword == "l" {
|
||||
var username []byte
|
||||
var password []byte
|
||||
switch len(params) {
|
||||
|
@ -197,16 +198,46 @@ COMMANDS:
|
|||
}
|
||||
|
||||
switch keyword {
|
||||
case bgammon.CommandHelp:
|
||||
case bgammon.CommandHelp, "h":
|
||||
// TODO get extended help by specifying a command after help
|
||||
cmd.client.events <- []byte("helpstart Help text:")
|
||||
cmd.client.events <- []byte("help Test help text")
|
||||
cmd.client.events <- []byte("helpend End of help text.")
|
||||
case bgammon.CommandSay:
|
||||
case bgammon.CommandJSON:
|
||||
sendUsage := func() {
|
||||
cmd.client.events <- []byte("notice To enable JSON formatted messages, send 'json on'. To disable JSON formatted messages, send 'json off'.")
|
||||
}
|
||||
if len(params) != 1 {
|
||||
sendUsage()
|
||||
continue
|
||||
}
|
||||
paramLower := strings.ToLower(string(params[0]))
|
||||
switch paramLower {
|
||||
case "on":
|
||||
cmd.client.json = true
|
||||
cmd.client.events <- []byte("json JSON formatted messages enabled.")
|
||||
case "off":
|
||||
cmd.client.json = false
|
||||
cmd.client.events <- []byte("json JSON formatted messages disabled.")
|
||||
default:
|
||||
sendUsage()
|
||||
}
|
||||
case bgammon.CommandSay, "s":
|
||||
if len(params) == 0 {
|
||||
continue
|
||||
}
|
||||
case bgammon.CommandList:
|
||||
g := s.gameByClient(cmd.client)
|
||||
if g == nil {
|
||||
cmd.client.events <- []byte("notice Message not sent. You are not currently in a game.")
|
||||
continue
|
||||
}
|
||||
opponent := g.opponent(cmd.client)
|
||||
if opponent == nil {
|
||||
cmd.client.events <- []byte("notice Message not sent. There is no one else in the game.")
|
||||
continue
|
||||
}
|
||||
opponent.events <- []byte(fmt.Sprintf("say %s %s", cmd.client.name, bytes.Join(params, []byte(" "))))
|
||||
case bgammon.CommandList, "ls":
|
||||
cmd.client.events <- []byte("liststart Games list:")
|
||||
players := 0
|
||||
password := 0
|
||||
|
@ -215,7 +246,7 @@ COMMANDS:
|
|||
cmd.client.events <- []byte(fmt.Sprintf("game %d %d %d %s", g.id, password, players, name))
|
||||
}
|
||||
cmd.client.events <- []byte("listend End of games list.")
|
||||
case bgammon.CommandCreate:
|
||||
case bgammon.CommandCreate, "c":
|
||||
sendUsage := func() {
|
||||
cmd.client.events <- []byte("notice To create a public game specify whether it is public or private. When creating a private game, a password must also be provided.")
|
||||
}
|
||||
|
@ -246,24 +277,13 @@ COMMANDS:
|
|||
g := newServerGame(<-s.newGameIDs)
|
||||
g.name = gameName
|
||||
g.password = gamePassword
|
||||
|
||||
if rand.Intn(2) == 0 { // Start as black.
|
||||
g.client1 = cmd.client
|
||||
g.Player1.Name = string(cmd.client.name)
|
||||
} else { // Start as white.
|
||||
g.client2 = cmd.client
|
||||
g.Player2.Name = string(cmd.client.name)
|
||||
if !g.addClient(cmd.client) {
|
||||
log.Panicf("failed to add client to newly created game %+v %+v", g, cmd.client)
|
||||
}
|
||||
s.games = append(s.games, g) // TODO lock
|
||||
|
||||
s.games = append(s.games, g)
|
||||
|
||||
players := 1
|
||||
password := 0
|
||||
if len(gamePassword) != 0 {
|
||||
password = 1
|
||||
}
|
||||
cmd.client.events <- []byte(fmt.Sprintf("joined %d %d %d %s", g.id, password, players, gameName))
|
||||
case bgammon.CommandJoin:
|
||||
g.sendBoard(cmd.client)
|
||||
case bgammon.CommandJoin, "j":
|
||||
if s.gameByClient(cmd.client) != nil {
|
||||
cmd.client.events <- []byte("failedjoin Please leave the game you are in before joining another game.")
|
||||
continue
|
||||
|
@ -277,7 +297,7 @@ COMMANDS:
|
|||
sendUsage()
|
||||
continue
|
||||
}
|
||||
gameID, err := strconv.Atoi(string(params[1]))
|
||||
gameID, err := strconv.Atoi(string(params[0]))
|
||||
if err != nil || gameID < 1 {
|
||||
sendUsage()
|
||||
continue
|
||||
|
@ -290,18 +310,63 @@ COMMANDS:
|
|||
continue COMMANDS
|
||||
}
|
||||
|
||||
log.Printf("join existing game %+v", g)
|
||||
// cmd.client.events <- []byte(fmt.Sprintf("joined %d %d %d %s", g.id, players, password, gameName))
|
||||
if !g.addClient(cmd.client) {
|
||||
cmd.client.events <- []byte("failedjoin Game is full.")
|
||||
continue COMMANDS
|
||||
}
|
||||
|
||||
g.sendBoard(cmd.client)
|
||||
continue COMMANDS
|
||||
}
|
||||
}
|
||||
case bgammon.CommandLeave:
|
||||
case bgammon.CommandRoll:
|
||||
case bgammon.CommandMove:
|
||||
case bgammon.CommandLeave, "l":
|
||||
g := s.gameByClient(cmd.client)
|
||||
if g == nil {
|
||||
cmd.client.events <- []byte("failedleave You are not currently in a game.")
|
||||
continue
|
||||
}
|
||||
|
||||
id := g.id
|
||||
// TODO remove
|
||||
cmd.client.events <- []byte(fmt.Sprintf("left %d", id))
|
||||
case bgammon.CommandRoll, "r":
|
||||
g := s.gameByClient(cmd.client)
|
||||
if g == nil {
|
||||
cmd.client.events <- []byte("notice You are not currently in a game.")
|
||||
continue COMMANDS
|
||||
}
|
||||
|
||||
playerNumber := 1
|
||||
if g.client2 == cmd.client {
|
||||
playerNumber = 2
|
||||
}
|
||||
if !g.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))
|
||||
})
|
||||
}
|
||||
case bgammon.CommandMove, "m":
|
||||
case bgammon.CommandBoard, "b":
|
||||
g := s.gameByClient(cmd.client)
|
||||
if g == nil {
|
||||
cmd.client.events <- []byte("notice You are not currently in a game.")
|
||||
} else {
|
||||
playerNumber := 1
|
||||
if g.client2 == cmd.client {
|
||||
playerNumber = 2
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(bytes.NewReader(g.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 leave game
|
||||
// todo remove client from game
|
||||
}
|
||||
cmd.client.Terminate("Client disconnected")
|
||||
default:
|
||||
|
|
|
@ -7,6 +7,7 @@ type Command string
|
|||
const (
|
||||
CommandLogin = "login" // Log in with username and password, or as a guest.
|
||||
CommandHelp = "help" // Print help information.
|
||||
CommandJSON = "json" // Enable or disable JSON formatted messages.
|
||||
CommandSay = "say" // Send chat message.
|
||||
CommandList = "list" // List available games.
|
||||
CommandCreate = "create" // Create game.
|
||||
|
@ -16,5 +17,6 @@ const (
|
|||
CommandMove = "move" // Move checkers.
|
||||
CommandReset = "reset" // Reset checker movement.
|
||||
CommandOk = "ok" // Confirm checker movement and pass turn to next player.
|
||||
CommandBoard = "board" // Print current board state in human-readable form.
|
||||
CommandDisconnect = "disconnect" // Disconnect from server.
|
||||
)
|
||||
|
|
385
game.go
385
game.go
|
@ -1,11 +1,21 @@
|
|||
package bgammon
|
||||
|
||||
import "math/rand"
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var boardTopBlack = []byte("+13-14-15-16-17-18-+---+19-20-21-22-23-24-+")
|
||||
var boardBottomBlack = []byte("+12-11-10--9--8--7-+---+-6--5--4--3--2--1-+")
|
||||
|
||||
var boardTopWhite = []byte("+24-23-22-21-20-19-+---+18-17-16-15-14-13-+")
|
||||
var boardBottomWhite = []byte("+-1--2--3--4--5--6-+---+-7--8--9-10-11-12-+")
|
||||
|
||||
type Game struct {
|
||||
Board *Board
|
||||
Player1 *Player
|
||||
Player2 *Player
|
||||
Board []int
|
||||
Player1 Player
|
||||
Player2 Player
|
||||
Turn int
|
||||
Roll1 int
|
||||
Roll2 int
|
||||
|
@ -19,16 +29,367 @@ func NewGame() *Game {
|
|||
}
|
||||
}
|
||||
|
||||
func (g *Game) roll(r rand.Rand, player int) {
|
||||
if player != g.Turn || g.Roll1 != 0 || g.Roll2 != 0 {
|
||||
return
|
||||
func (g *Game) turnPlayer() Player {
|
||||
switch g.Turn {
|
||||
case 2:
|
||||
return g.Player2
|
||||
default:
|
||||
return g.Player1
|
||||
}
|
||||
g.Roll1, g.Roll2 = r.Intn(6)+1, r.Intn(6)+1
|
||||
}
|
||||
|
||||
func (g *Game) LegalMoves() []int {
|
||||
// todo get current player based on turn and enumerate spaces and roll to get available moves
|
||||
// sent to clients and used to validate moves
|
||||
var moves []int
|
||||
func (g *Game) opponentPlayer() Player {
|
||||
switch g.Turn {
|
||||
case 2:
|
||||
return g.Player1
|
||||
default:
|
||||
return g.Player2
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Game) iterateSpaces(from int, to int, f func(space int, spaceCount int)) {
|
||||
if from == to {
|
||||
return
|
||||
}
|
||||
|
||||
i := 1
|
||||
if to > from {
|
||||
for space := from; space <= to; space++ {
|
||||
f(space, i)
|
||||
i++
|
||||
}
|
||||
} else {
|
||||
for space := from; space >= to; space-- {
|
||||
f(space, i)
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Game) LegalMoves() [][]int {
|
||||
if g.Roll1 == 0 || g.Roll2 == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var moves [][]int
|
||||
for space := range g.Board {
|
||||
if space == SpaceHomePlayer || space == SpaceHomeOpponent {
|
||||
continue
|
||||
}
|
||||
|
||||
checkers := g.Board[space]
|
||||
playerCheckers := numPlayerCheckers(checkers, g.Turn)
|
||||
if playerCheckers == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if space == SpaceBarPlayer || space == SpaceBarOpponent {
|
||||
// Enter from bar.
|
||||
from, to := HomeRange(g.Turn)
|
||||
g.iterateSpaces(from, to, func(homeSpace int, spaceCount int) {
|
||||
if spaceCount != g.Roll1 && spaceCount != g.Roll2 {
|
||||
return
|
||||
}
|
||||
opponentCheckers := numOpponentCheckers(g.Board[homeSpace], g.Turn)
|
||||
if opponentCheckers <= 1 {
|
||||
moves = append(moves, []int{space, homeSpace})
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// Move normally.
|
||||
lastSpace := 1
|
||||
dir := -1
|
||||
if g.Turn == 2 {
|
||||
lastSpace = 24
|
||||
dir = 1
|
||||
}
|
||||
|
||||
if space == lastSpace {
|
||||
continue // TODO check if all pieces in home
|
||||
}
|
||||
|
||||
g.iterateSpaces(space+dir, lastSpace, func(to int, spaceCount int) {
|
||||
if spaceCount != g.Roll1 && spaceCount != g.Roll2 {
|
||||
return
|
||||
}
|
||||
|
||||
if to == SpaceHomePlayer || to == SpaceHomeOpponent {
|
||||
return // TODO
|
||||
}
|
||||
|
||||
opponentCheckers := numOpponentCheckers(g.Board[to], g.Turn)
|
||||
if opponentCheckers <= 1 {
|
||||
movable := 1
|
||||
if g.Roll1 == g.Roll2 {
|
||||
movable = playerCheckers
|
||||
if movable > 4 {
|
||||
movable = 4
|
||||
}
|
||||
}
|
||||
for i := 0; i < movable; i++ {
|
||||
moves = append(moves, []int{space, to})
|
||||
//log.Printf("ADD MOVE %d-%d", space, to)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
return moves
|
||||
}
|
||||
|
||||
func (g *Game) RenderSpace(player int, space int, spaceValue int, legalMoves [][]int) []byte {
|
||||
var playerColor = "x"
|
||||
var opponentColor = "o"
|
||||
if player == 2 {
|
||||
playerColor = "o"
|
||||
opponentColor = "x"
|
||||
}
|
||||
|
||||
var pieceColor string
|
||||
value := g.Board[space]
|
||||
if space == SpaceBarPlayer {
|
||||
pieceColor = playerColor
|
||||
} else if space == SpaceBarOpponent {
|
||||
pieceColor = opponentColor
|
||||
} else {
|
||||
if value < 0 {
|
||||
pieceColor = "x"
|
||||
} else if value > 0 {
|
||||
pieceColor = "o"
|
||||
} else {
|
||||
pieceColor = playerColor
|
||||
}
|
||||
}
|
||||
|
||||
abs := value
|
||||
if value < 0 {
|
||||
abs = value * -1
|
||||
}
|
||||
|
||||
top := space > 12
|
||||
if player == 2 {
|
||||
top = !top
|
||||
}
|
||||
|
||||
firstDigit := 4
|
||||
secondDigit := 5
|
||||
if !top {
|
||||
firstDigit = 5
|
||||
secondDigit = 4
|
||||
}
|
||||
|
||||
var firstNumeral string
|
||||
var secondNumeral string
|
||||
if abs > 5 {
|
||||
if abs > 9 {
|
||||
firstNumeral = "1"
|
||||
} else {
|
||||
firstNumeral = strconv.Itoa(abs)
|
||||
}
|
||||
if abs > 9 {
|
||||
secondNumeral = strconv.Itoa(abs - 10)
|
||||
}
|
||||
|
||||
if spaceValue == firstDigit && (!top || abs > 9) {
|
||||
pieceColor = firstNumeral
|
||||
} else if spaceValue == secondDigit && abs > 9 {
|
||||
pieceColor = secondNumeral
|
||||
} else if top && spaceValue == secondDigit {
|
||||
pieceColor = firstNumeral
|
||||
}
|
||||
}
|
||||
|
||||
if abs > 5 {
|
||||
abs = 5
|
||||
}
|
||||
|
||||
var r []byte
|
||||
if abs > 0 && spaceValue <= abs {
|
||||
r = []byte(pieceColor)
|
||||
} else {
|
||||
r = []byte(" ")
|
||||
}
|
||||
return append(append([]byte(" "), r...), ' ')
|
||||
}
|
||||
|
||||
func (g *Game) BoardState(player int) []byte {
|
||||
var t bytes.Buffer
|
||||
|
||||
playerRating := "0"
|
||||
opponentRating := "0"
|
||||
|
||||
var white bool
|
||||
if player == 2 {
|
||||
white = true
|
||||
}
|
||||
|
||||
var opponentName = g.Player2.Name
|
||||
var playerName = g.Player1.Name
|
||||
if playerName == "" {
|
||||
playerName = "Waiting..."
|
||||
}
|
||||
if opponentName == "" {
|
||||
opponentName = "Waiting..."
|
||||
}
|
||||
if white {
|
||||
playerName, opponentName = opponentName, playerName
|
||||
}
|
||||
|
||||
var playerColor = "x"
|
||||
var opponentColor = "o"
|
||||
if white {
|
||||
playerColor = "o"
|
||||
opponentColor = "x"
|
||||
}
|
||||
|
||||
if white {
|
||||
t.Write(boardTopWhite)
|
||||
} else {
|
||||
t.Write(boardTopBlack)
|
||||
}
|
||||
t.WriteString(" ")
|
||||
t.WriteByte('\n')
|
||||
|
||||
legalMoves := g.LegalMoves()
|
||||
space := func(row int, col int) []byte {
|
||||
spaceValue := row + 1
|
||||
if row > 5 {
|
||||
spaceValue = 5 - (row - 6)
|
||||
}
|
||||
|
||||
if col == -1 {
|
||||
if row <= 4 {
|
||||
return g.RenderSpace(player, SpaceBarOpponent, spaceValue, legalMoves)
|
||||
}
|
||||
return g.RenderSpace(player, SpaceBarPlayer, spaceValue, legalMoves)
|
||||
}
|
||||
|
||||
var index int
|
||||
if !white {
|
||||
if row < 6 {
|
||||
col = 12 - col
|
||||
} else {
|
||||
col = 11 - col
|
||||
}
|
||||
|
||||
index = col
|
||||
if row > 5 {
|
||||
index = 11 - col + 13
|
||||
}
|
||||
} else {
|
||||
index = col + 3
|
||||
if row > 5 {
|
||||
index = 11 - col + 15
|
||||
}
|
||||
}
|
||||
if white {
|
||||
index = BoardSpaces - 1 - index
|
||||
}
|
||||
|
||||
if row == 5 {
|
||||
return []byte(" ")
|
||||
}
|
||||
|
||||
return g.RenderSpace(player, index, spaceValue, legalMoves)
|
||||
}
|
||||
|
||||
for i := 0; i < 11; i++ {
|
||||
t.WriteRune(VerticalBar)
|
||||
t.Write([]byte(""))
|
||||
for j := 0; j < 12; j++ {
|
||||
t.Write(space(i, j))
|
||||
|
||||
if j == 5 {
|
||||
t.WriteRune(VerticalBar)
|
||||
t.Write(space(i, -1))
|
||||
t.WriteRune(VerticalBar)
|
||||
}
|
||||
}
|
||||
|
||||
t.Write([]byte("" + string(VerticalBar) + " "))
|
||||
|
||||
if i == 0 {
|
||||
t.Write([]byte(opponentColor + " " + opponentName + " (" + opponentRating + ")"))
|
||||
if g.Board[SpaceHomeOpponent] != 0 {
|
||||
v := g.Board[SpaceHomeOpponent]
|
||||
if v < 0 {
|
||||
v *= -1
|
||||
}
|
||||
t.Write([]byte(fmt.Sprintf(" %d off", v)))
|
||||
}
|
||||
} else if i == 2 {
|
||||
if g.Turn != player && g.Roll1 > 0 {
|
||||
t.Write([]byte(fmt.Sprintf(" %d %d ", g.Roll1, g.Roll2)))
|
||||
} else {
|
||||
t.Write([]byte(fmt.Sprintf(" - - ")))
|
||||
}
|
||||
} else if i == 8 {
|
||||
if g.Turn == player && g.Roll1 > 0 {
|
||||
t.Write([]byte(fmt.Sprintf(" %d %d ", g.Roll1, g.Roll2)))
|
||||
} else {
|
||||
t.Write([]byte(fmt.Sprintf(" - - ")))
|
||||
}
|
||||
} else if i == 10 {
|
||||
t.Write([]byte(playerColor + " " + playerName + " (" + playerRating + ")"))
|
||||
if g.Board[SpaceHomePlayer] != 0 {
|
||||
v := g.Board[SpaceHomePlayer]
|
||||
if v < 0 {
|
||||
v *= -1
|
||||
}
|
||||
t.Write([]byte(fmt.Sprintf(" %d off", v)))
|
||||
}
|
||||
}
|
||||
|
||||
t.Write([]byte(" "))
|
||||
t.WriteByte('\n')
|
||||
}
|
||||
|
||||
if white {
|
||||
t.Write(boardBottomWhite)
|
||||
} else {
|
||||
t.Write(boardBottomBlack)
|
||||
}
|
||||
t.WriteString(" \n")
|
||||
|
||||
return t.Bytes()
|
||||
}
|
||||
|
||||
func spaceDiff(from int, to int) int {
|
||||
diff := to - from
|
||||
if diff < 0 {
|
||||
return diff * -1
|
||||
}
|
||||
return diff
|
||||
}
|
||||
|
||||
func numPlayerCheckers(checkers int, player int) int {
|
||||
if player == 1 {
|
||||
if checkers > 0 {
|
||||
return checkers
|
||||
}
|
||||
return 0
|
||||
} else {
|
||||
if checkers < 0 {
|
||||
return checkers * -1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func numOpponentCheckers(checkers int, player int) int {
|
||||
if player == 2 {
|
||||
if checkers > 0 {
|
||||
return checkers
|
||||
}
|
||||
return 0
|
||||
} else {
|
||||
if checkers < 0 {
|
||||
return checkers * -1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
VerticalBar rune = '\u2502' // │
|
||||
)
|
||||
|
|
|
@ -5,8 +5,8 @@ type Player struct {
|
|||
Name string
|
||||
}
|
||||
|
||||
func NewPlayer(number int) *Player {
|
||||
return &Player{
|
||||
func NewPlayer(number int) Player {
|
||||
return Player{
|
||||
Number: number,
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue