commit
ad9ece3682
10 changed files with 1561 additions and 0 deletions
@ -0,0 +1,15 @@
@@ -0,0 +1,15 @@
|
||||
# fibs |
||||
[](https://docs.rocketnine.space/code.rocketnine.space/tslocum/fibs) |
||||
[](https://liberapay.com/rocketnine.space) |
||||
|
||||
[FIBS](https://fibs.com) (online backgammon) client library |
||||
|
||||
**Currently in pre-alpha state. Here be dragons.** |
||||
|
||||
## Documentation |
||||
|
||||
Documentation is available via [godoc](https://docs.rocketnine.space/code.rocketnine.space/tslocum/fibs). |
||||
|
||||
## Support |
||||
|
||||
Please share issues and suggestions [here](https://code.rocketnine.space/tslocum/fibs/issues). |
@ -0,0 +1,758 @@
@@ -0,0 +1,758 @@
|
||||
package fibs |
||||
|
||||
import ( |
||||
"bytes" |
||||
"fmt" |
||||
"log" |
||||
"sort" |
||||
"strconv" |
||||
"strings" |
||||
"sync" |
||||
) |
||||
|
||||
// TODO Add PlayerName, etc
|
||||
|
||||
const ( |
||||
BoxDrawingsLightVertical = '|' |
||||
) |
||||
|
||||
const ( |
||||
StateLength = iota |
||||
StatePlayerScore |
||||
StateOpponentScore |
||||
StateBoardSpace0 |
||||
) |
||||
|
||||
const ( |
||||
StateTurn = 29 + iota |
||||
StatePlayerDice1 |
||||
StatePlayerDice2 |
||||
StateOpponentDice1 |
||||
StateOpponentDice2 |
||||
StateDoublingValue |
||||
StatePlayerMayDouble |
||||
StateOpponentMayDouble |
||||
StateWasDoubled |
||||
StatePlayerColor |
||||
StateDirection |
||||
StateObsoleteHome |
||||
StateObsoleteBar |
||||
StatePlayerHome |
||||
StateOpponentHome |
||||
StatePlayerBar |
||||
StateOpponentBar |
||||
StateMovablePieces |
||||
StateObsoletePlayerForced |
||||
StateObsoleteOpponentForced |
||||
StateRedoubles |
||||
) |
||||
|
||||
const ( |
||||
SpaceUnknown = -1 |
||||
) |
||||
|
||||
const initialState = "FIBS:Welcome:5:0:2:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:-1:0:0:0:0:1:1:1:0:1:-1:0:25:0:0:0:0:4:0:0:0" |
||||
|
||||
var boardTopWhite = []byte("+13-14-15-16-17-18-+---+19-20-21-22-23-24-+") |
||||
var boardBottomWhite = []byte("+12-11-10--9--8--7-+---+-6--5--4--3--2--1-+") |
||||
|
||||
var boardTopBlack = []byte("+-1--2--3--4--5--6-+---+-7--8--9-10-11-12-+") |
||||
var boardBottomBlack = []byte("+24-23-22-21-20-19-+---+18-17-16-15-14-13-+") |
||||
|
||||
type Board struct { |
||||
client *Client |
||||
|
||||
state string |
||||
|
||||
s []string |
||||
v []int |
||||
|
||||
moves [][2]int |
||||
movesColor int |
||||
|
||||
validMoves map[int][]int |
||||
|
||||
from map[int]int |
||||
to map[int]int |
||||
|
||||
playerDice [2]int |
||||
opponentDice [2]int |
||||
|
||||
selected [2]int |
||||
|
||||
premove [][2]int |
||||
premovefrom map[int]int |
||||
premoveto map[int]int |
||||
|
||||
dragFromX int |
||||
dragFromY int |
||||
|
||||
sync.Mutex |
||||
} |
||||
|
||||
func NewBoard(client *Client) *Board { |
||||
b := &Board{ |
||||
client: client, |
||||
} |
||||
|
||||
b.ResetMoves() |
||||
b.ResetPreMoves() |
||||
|
||||
b.SetState(initialState) |
||||
|
||||
// TODO
|
||||
/* |
||||
b.v[StatePlayerColor] = -1 |
||||
b.v[StateBoardSpace0+11] = 12 |
||||
b.v[StateBoardSpace0+9] = 7 |
||||
b.v[StateBoardSpace0+13] = -13 |
||||
b.v[StateBoardSpace0+24] = -3 |
||||
b.v[StatePlayerBar] = 3 |
||||
b.Update() |
||||
*/ |
||||
|
||||
return b |
||||
} |
||||
|
||||
// TODO refactor
|
||||
func (b *Board) GetIntState() []int { |
||||
return b.v |
||||
} |
||||
|
||||
func (b *Board) resetSelection() { |
||||
b.selected[0] = 0 |
||||
b.selected[1] = 0 |
||||
} |
||||
|
||||
func (b *Board) autoSendMoves() { |
||||
movable := 2 |
||||
if b.playerDice[0] > 0 && b.playerDice[0] == b.playerDice[1] { |
||||
movable = 4 |
||||
} |
||||
if b.v[StateMovablePieces] > 0 { |
||||
movable = b.v[StateMovablePieces] |
||||
} |
||||
if len(b.premove) == 1 { |
||||
abs := b.premove[0][1] - b.premove[0][0] |
||||
direction := 1 |
||||
if abs < 0 { |
||||
abs *= -1 |
||||
direction = -1 |
||||
} |
||||
if b.playerDice[0] == b.playerDice[1] { |
||||
for expandDoubles := 4; expandDoubles >= 2; expandDoubles-- { |
||||
if abs != b.playerDice[0]*expandDoubles { |
||||
continue |
||||
} |
||||
|
||||
from, _ := b.premove[0][0], b.premove[0][1] |
||||
|
||||
b.premove = nil |
||||
for i := 1; i <= expandDoubles; i++ { |
||||
b.premove = append(b.premove, [2]int{from + ((b.playerDice[0]*i - 1) * direction), from + ((b.playerDice[0] * i) * direction)}) |
||||
} |
||||
break |
||||
} |
||||
} |
||||
} |
||||
if len(b.premove) < movable { |
||||
return |
||||
} |
||||
|
||||
moveCommand := []byte("move") |
||||
for j := 0; j < 2; j++ { |
||||
for i := range b.premove { |
||||
var from string |
||||
if b.premove[i][0] == 0 || b.premove[i][0] == 25 { |
||||
from = "bar" |
||||
} else { |
||||
from = strconv.Itoa(b.premove[i][0]) |
||||
} |
||||
|
||||
if (j == 0) != (from == "bar") { |
||||
continue // Always send bar moves first
|
||||
} |
||||
|
||||
var to string |
||||
if b.premove[i][1] == b.playerHomeSpace() { |
||||
to = "off" |
||||
} else { |
||||
to = strconv.Itoa(b.premove[i][1]) |
||||
} |
||||
|
||||
moveCommand = append(moveCommand, []byte(" "+from+"-"+to)...) |
||||
} |
||||
|
||||
} |
||||
|
||||
b.ResetPreMoves() |
||||
|
||||
b.client.Out <- moveCommand |
||||
} |
||||
|
||||
func (b *Board) GetState() string { |
||||
var s = strings.Join(b.s, ":") |
||||
for i := range b.v { |
||||
s += ":" + strconv.Itoa(b.v[i]) |
||||
} |
||||
return s |
||||
} |
||||
|
||||
func (b *Board) SetState(state string) { |
||||
b.Lock() |
||||
|
||||
lastTurn := 0 |
||||
if len(b.v) > 0 { |
||||
lastTurn = b.v[StateTurn] |
||||
} |
||||
|
||||
b.s = strings.Split(state, ":") |
||||
|
||||
b.v = make([]int, len(b.s)-2) |
||||
var err error |
||||
for i := 0; i < 46; i++ { |
||||
b.v[i], err = strconv.Atoi(b.s[i+2]) |
||||
if err != nil { |
||||
log.Fatal(err) |
||||
} |
||||
} |
||||
|
||||
if b.v[StateTurn] != lastTurn { |
||||
if lastTurn == b.v[StatePlayerColor] { |
||||
b.playerDice = [2]int{0, 0} |
||||
} else { |
||||
b.opponentDice = [2]int{0, 0} |
||||
} |
||||
} |
||||
|
||||
if b.v[StatePlayerDice1] > 0 { |
||||
b.playerDice = [2]int{b.v[StatePlayerDice1], b.v[StatePlayerDice2]} |
||||
} |
||||
if b.v[StateOpponentDice1] > 0 { |
||||
b.opponentDice = [2]int{b.v[StateOpponentDice1], b.v[StateOpponentDice2]} |
||||
} |
||||
|
||||
b.Unlock() |
||||
b.Draw() |
||||
} |
||||
|
||||
func (b *Board) Draw() { |
||||
// TODO
|
||||
} |
||||
|
||||
func (b *Board) renderSpace(index int, spaceValue int) []byte { |
||||
var playerColor = "x" |
||||
var opponentColor = "o" |
||||
if b.v[StatePlayerColor] == 1 { |
||||
playerColor = "o" |
||||
opponentColor = "x" |
||||
} |
||||
|
||||
var pieceColor string |
||||
value := b.v[StateBoardSpace0+index] |
||||
if index == b.playerBarSpace() { |
||||
value = b.v[StatePlayerBar] |
||||
pieceColor = playerColor |
||||
} else if index == 25-b.playerBarSpace() { |
||||
value = b.v[StateOpponentBar] |
||||
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 := index <= 12 |
||||
if b.v[StatePlayerColor] == 1 { |
||||
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 |
||||
} |
||||
} |
||||
|
||||
// TODO
|
||||
if abs > 5 { |
||||
abs = 5 |
||||
} |
||||
|
||||
var r []byte |
||||
foregroundColor := "#FFFFFF" |
||||
backgroundColor := "#000000" |
||||
if index != 0 && index != 25 { |
||||
if true { // default theme
|
||||
if index%2 == 0 { |
||||
backgroundColor = "#303030" |
||||
} else { |
||||
backgroundColor = "#101010" |
||||
} |
||||
} else { // rainbow
|
||||
foregroundColor = "#000000" |
||||
switch index % 6 { |
||||
case 1: |
||||
backgroundColor = "#FF0000" |
||||
case 2: |
||||
backgroundColor = "#FFA500" |
||||
case 3: |
||||
backgroundColor = "#FFFF00" |
||||
case 4: |
||||
backgroundColor = "#008000" |
||||
case 5: |
||||
backgroundColor = "#0000FF" |
||||
case 0: |
||||
backgroundColor = "#4B0082" |
||||
} |
||||
} |
||||
} |
||||
// Highlight legal moves
|
||||
highlightSpace := b.validMove(b.selected[0], index) |
||||
highlightSpace = false // TODO Make configurable, disable by default
|
||||
//+(b.playerDice[0]*b.v[StatePlayerColor]) ||b.selected[0] == index+(b.playerDice[1]*b.v[StatePlayerColor])) && b.selected[1] > 0
|
||||
if b.selected[1] > 0 && highlightSpace && index != 25 && index != 0 { |
||||
foregroundColor = "black" |
||||
backgroundColor = "yellow" |
||||
} |
||||
if abs > 0 && spaceValue <= abs { |
||||
r = []byte(pieceColor) |
||||
} else { |
||||
r = []byte(" ") |
||||
} |
||||
|
||||
rightArrowFrom := (b.v[StateDirection] == b.movesColor) == (index > 12) |
||||
if b.selected[0] == index && b.selected[1] > 0 && spaceValue <= abs && spaceValue > abs-b.selected[1] { |
||||
r = []byte("*") |
||||
} else if b.premovefrom[index] > 0 && spaceValue > (abs+b.premoveto[index])-b.premovefrom[index] && spaceValue <= abs+b.premoveto[index] { |
||||
if index == 25-b.playerBarSpace() { |
||||
r = []byte("โพ") |
||||
} else if index == b.playerBarSpace() { |
||||
r = []byte("โด") |
||||
} else if rightArrowFrom { |
||||
r = []byte("โธ") |
||||
} else { |
||||
r = []byte("โ") |
||||
} |
||||
foregroundColor = "yellow" |
||||
} else if b.premoveto[index] > 0 && spaceValue > abs && spaceValue <= abs+(b.premoveto[index]) { |
||||
r = []byte(playerColor) |
||||
foregroundColor = "yellow" |
||||
} else if b.from[index] > 0 && spaceValue > abs && spaceValue <= abs+b.from[index] { |
||||
if rightArrowFrom { |
||||
r = []byte("โธ") |
||||
} else { |
||||
r = []byte("โ") |
||||
} |
||||
if b.movesColor == b.v[StatePlayerColor] { |
||||
foregroundColor = "yellow" |
||||
} else { |
||||
foregroundColor = "green" |
||||
} |
||||
} else if b.to[index] > 0 && spaceValue > abs-(b.to[index]+b.from[index]) { |
||||
if b.movesColor == b.v[StatePlayerColor] { |
||||
foregroundColor = "yellow" |
||||
} else { |
||||
foregroundColor = "green" |
||||
} |
||||
} |
||||
|
||||
return append(append([]byte(fmt.Sprintf("[\"space-%d\"][%s:%s:b] ", index, foregroundColor, backgroundColor)), r...), []byte(" [-:-:-][\"\"]")...) |
||||
} |
||||
|
||||
func (b *Board) ResetMoves() { |
||||
b.moves = nil |
||||
b.movesColor = 0 |
||||
b.validMoves = make(map[int][]int) |
||||
b.from = make(map[int]int) |
||||
b.to = make(map[int]int) |
||||
} |
||||
|
||||
func (b *Board) ResetPreMoves() { |
||||
b.premove = nil |
||||
b.premovefrom = make(map[int]int) |
||||
b.premoveto = make(map[int]int) |
||||
} |
||||
|
||||
func (b *Board) allPlayerPiecesInHomeBoard() bool { |
||||
homeBoardStart := 1 |
||||
homeBoardEnd := 6 |
||||
if b.v[StateDirection] == -1 { |
||||
homeBoardStart = 19 |
||||
homeBoardEnd = 24 |
||||
} |
||||
hasPlayerPiece := func(index int) bool { |
||||
if index < 0 || index > 25 { |
||||
return false |
||||
} |
||||
return (b.v[StatePlayerColor] == 1 && b.v[StateBoardSpace0+index] > 0) || |
||||
(b.v[StatePlayerColor] == -1 && b.v[StateBoardSpace0+index] < 0) |
||||
} |
||||
for i := 1; i < 24; i++ { |
||||
if i >= homeBoardStart && i <= homeBoardEnd { |
||||
continue |
||||
} |
||||
if hasPlayerPiece(i) { |
||||
return false |
||||
} |
||||
} |
||||
return true |
||||
} |
||||
|
||||
func (b *Board) spaceAvailable(index int) bool { |
||||
if index < 0 || index > 25 { |
||||
return false |
||||
} |
||||
if index == 0 || index == 25 { |
||||
return b.allPlayerPiecesInHomeBoard() |
||||
} |
||||
return (b.v[StatePlayerColor] == 1 && b.v[StateBoardSpace0+index] >= -1) || |
||||
(b.v[StatePlayerColor] == -1 && b.v[StateBoardSpace0+index] <= 1) |
||||
} |
||||
|
||||
func (b *Board) GetValidMoves(from int) []int { |
||||
if validMoves, ok := b.validMoves[from]; ok { |
||||
return validMoves |
||||
} |
||||
|
||||
var validMoves []int |
||||
defer func() { |
||||
b.validMoves[from] = validMoves |
||||
}() |
||||
|
||||
if b.v[StateTurn] != b.v[StatePlayerColor] || b.playerDice[0] == 0 || b.playerDice[1] == 0 { |
||||
return validMoves |
||||
} |
||||
|
||||
// TODO consider opponent blocking midway In full move
|
||||
trySpaces := [][]int{ |
||||
{b.playerDice[0]}, |
||||
{b.playerDice[1]}, |
||||
{b.playerDice[0], b.playerDice[1]}, |
||||
{b.playerDice[1], b.playerDice[0]}, |
||||
} |
||||
if b.playerDice[0] == b.playerDice[1] { |
||||
trySpaces = append(trySpaces, |
||||
[]int{b.playerDice[0], b.playerDice[0], b.playerDice[0]}, |
||||
[]int{b.playerDice[0], b.playerDice[0], b.playerDice[0], b.playerDice[0]}) |
||||
} |
||||
|
||||
if b.allPlayerPiecesInHomeBoard() { |
||||
homeSpace := b.playerHomeSpace() |
||||
spacesHome := from - homeSpace |
||||
if spacesHome < 0 { |
||||
spacesHome *= -1 |
||||
} |
||||
if spacesHome <= b.playerDice[0] || spacesHome <= b.playerDice[1] { |
||||
trySpaces = append(trySpaces, []int{spacesHome}) |
||||
} |
||||
} |
||||
foundMoves := make(map[int]bool) |
||||
CHECKSPACES: |
||||
for i := range trySpaces { |
||||
checkSpace := 0 |
||||
for _, space := range trySpaces[i] { |
||||
checkSpace += space |
||||
if !b.spaceAvailable(from + (checkSpace * b.v[StateDirection])) { |
||||
continue CHECKSPACES |
||||
} |
||||
} |
||||
space := from + (checkSpace * b.v[StateDirection]) |
||||
if _, value := foundMoves[space]; !value { |
||||
foundMoves[space] = true |
||||
validMoves = append(validMoves, space) |
||||
} |
||||
} |
||||
|
||||
sort.Ints(validMoves) |
||||
|
||||
return validMoves |
||||
} |
||||
|
||||
func (b *Board) playerBarSpace() int { |
||||
return 25 - b.playerHomeSpace() |
||||
} |
||||
|
||||
func (b *Board) playerHomeSpace() int { |
||||
if b.v[StateDirection] == -1 { |
||||
return 0 |
||||
} |
||||
return 25 |
||||
} |
||||
|
||||
func (b *Board) validMove(f int, t int) bool { |
||||
if b.v[StateTurn] != b.v[StatePlayerColor] || b.playerDice[0] == 0 || b.playerDice[1] == 0 { |
||||
return false |
||||
} |
||||
|
||||
if t == b.playerHomeSpace() { |
||||
// TODO bear off logic, only allow high roll
|
||||
return b.allPlayerPiecesInHomeBoard() |
||||
} |
||||
|
||||
validMoves := b.GetValidMoves(f) |
||||
for i := range validMoves { |
||||
if validMoves[i] == t { |
||||
return true |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
func (b *Board) parseMoveString(player int, s string) int { |
||||
space, err := strconv.Atoi(s) |
||||
if err != nil { |
||||
space = SpaceUnknown |
||||
if s == "bar" { |
||||
barSpace := b.playerBarSpace() |
||||
if b.v[StatePlayerColor] == player { |
||||
space = barSpace |
||||
} else { |
||||
space = 25 - barSpace |
||||
} |
||||
} else if s == "off" { |
||||
space = b.playerHomeSpace() |
||||
} |
||||
} |
||||
return space |
||||
} |
||||
|
||||
func (b *Board) Move(player int, f string, t string) { |
||||
from := b.parseMoveString(player, f) |
||||
to := b.parseMoveString(player, t) |
||||
|
||||
if from == SpaceUnknown || to == SpaceUnknown { |
||||
// TODO debug print ("error: failed to parse move: player %d, from %s, to %s", player, f, t)
|
||||
return |
||||
} |
||||
|
||||
b.moves = append(b.moves, [2]int{from, to}) |
||||
b.movesColor = player |
||||
|
||||
b.from[from]++ |
||||
b.to[to]++ |
||||
|
||||
b.v[StateTurn] = player * -1 |
||||
|
||||
b.validMoves = make(map[int][]int) |
||||
b.ResetPreMoves() |
||||
} |
||||
|
||||
func (b *Board) SimplifyMoves() { |
||||
for i := range b.moves { |
||||
for j := range b.moves { |
||||
if b.moves[i][1] == b.moves[j][0] { |
||||
// Same to space as from space
|
||||
b.moves[j][0] = b.moves[i][0] // Set from space to earlier from space
|
||||
b.moves = append(b.moves[:i], b.moves[i+1:]...) |
||||
b.SimplifyMoves() |
||||
return |
||||
} else if b.moves[i][0] == b.moves[j][1] { |
||||
// Same to space as from space
|
||||
b.moves[j][1] = b.moves[i][1] // Set to space to earlier to space
|
||||
b.moves = append(b.moves[:i], b.moves[i+1:]...) |
||||
b.SimplifyMoves() |
||||
return |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
func (b *Board) addPreMove(from int, to int) bool { |
||||
if !b.validMove(from, to) { |
||||
return false |
||||
} |
||||
b.premove = append(b.premove, [2]int{from, to}) |
||||
b.premovefrom[from]++ |
||||
b.premoveto[to]++ |
||||
return true |
||||
} |
||||
|
||||
func (b *Board) Render() []byte { |
||||
b.Lock() |
||||
|
||||
var white bool |
||||
if b.v[StatePlayerColor] == 1 { |
||||
white = true |
||||
} |
||||
|
||||
var opponentName = b.s[1] |
||||
var playerName = b.s[0] |
||||
|
||||
var playerColor = "x" |
||||
var opponentColor = "o" |
||||
if white { |
||||
playerColor = "o" |
||||
opponentColor = "x" |
||||
} |
||||
|
||||
var t bytes.Buffer |
||||
t.WriteString("[\"space-off\"] [\"\"]\n") |
||||
t.WriteString("[\"space-off\"] [\"\"]\n") |
||||
t.WriteString("[\"space-off\"] ") |
||||
if white { |
||||
t.Write(boardTopWhite) |
||||
} else { |
||||
t.Write(boardTopBlack) |
||||
} |
||||
t.WriteString("[\"\"] ") |
||||
t.WriteByte('\n') |
||||
|
||||
space := func(i int, j int) []byte { |
||||
spaceValue := i + 1 |
||||
if i > 5 { |
||||
spaceValue = 5 - (i - 6) |
||||
} |
||||
|
||||
if j == -1 { |
||||
if i <= 4 { |
||||
return b.renderSpace(25-b.playerBarSpace(), spaceValue) |
||||
} |
||||
return b.renderSpace(b.playerBarSpace(), spaceValue) |
||||
} |
||||
|
||||
var index int |
||||
if !white { |
||||
if i < 6 { |
||||
j = 12 - j |
||||
} else { |
||||
j = 11 - j |
||||
} |
||||
|
||||
index = 12 + j |
||||
if i > 5 { |
||||
index = 12 - j |
||||
} |
||||
} else { |
||||
index = 12 + j |
||||
if i > 5 { |
||||
index = 11 - j |
||||
} |
||||
} |
||||
if !white { |
||||
index = 24 - index |
||||
} |
||||
index++ // increment to get actual space number (0 is bar)
|
||||
|
||||
if i == 5 { |
||||
return []byte("[-:#000000] [-:-]") |
||||
} |
||||
|
||||
return b.renderSpace(index, spaceValue) |
||||
} |
||||
|
||||
for i := 0; i < 11; i++ { |
||||
t.Write([]byte("[\"space-off\"]")) |
||||
|
||||
if i == 5 && b.v[StateDoublingValue] > 1 { |
||||
t.WriteString(fmt.Sprintf("%2d ", b.v[StateDoublingValue])) |
||||
if b.v[StatePlayerMayDouble] == 1 { |
||||
t.WriteByte('v') |
||||
} else { |
||||
t.WriteByte('^') |
||||
} |
||||
} else { |
||||
t.WriteByte(' ') |
||||
t.WriteByte(' ') |
||||
t.WriteByte(' ') |
||||
t.WriteByte(' ') |
||||
} |
||||
|
||||
t.WriteRune(BoxDrawingsLightVertical) |
||||
t.Write([]byte("[\"\"]")) |
||||
for j := 0; j < 12; j++ { |
||||
t.Write(space(i, j)) |
||||
|
||||
if j == 5 { |
||||
t.WriteRune(BoxDrawingsLightVertical) |
||||
t.Write(space(i, -1)) |
||||
t.WriteRune(BoxDrawingsLightVertical) |
||||
} |
||||
} |
||||
|
||||
t.Write([]byte("[\"space-off\"]" + string(BoxDrawingsLightVertical) + " ")) |
||||
|
||||
playerRollColor := "yellow" |
||||
playerBold := "b" |
||||
opponentRollColor := "white" |
||||
opponentBold := "" |
||||
if b.v[StateTurn] != b.v[StatePlayerColor] { |
||||
playerRollColor = "white" |
||||
opponentRollColor = "green" |
||||
playerBold = "" |
||||
opponentBold = "b" |
||||
} |
||||
|
||||
if i == 0 { |
||||
t.Write([]byte("[" + opponentRollColor + "::" + opponentBold + "]" + opponentColor + " " + opponentName + " (" + b.s[4] + ")")) |
||||
if b.v[StateOpponentHome] > 0 { |
||||
t.Write([]byte(fmt.Sprintf(" %d off", b.v[StateOpponentHome]))) |
||||
} |
||||
t.Write([]byte("[-::-]")) |
||||
} else if i == 2 { |
||||
if b.opponentDice[0] > 0 { |
||||
t.Write([]byte(fmt.Sprintf(" [%s::%s]%d %d[-::-] ", opponentRollColor, opponentBold, b.opponentDice[0], b.opponentDice[1]))) |
||||
} else { |
||||
t.Write([]byte(fmt.Sprintf(" [%s]- -[-] ", opponentRollColor))) |
||||
} |
||||
} else if i == 8 { |
||||
if b.playerDice[0] > 0 { |
||||
t.Write([]byte(fmt.Sprintf(" [%s::%s]%d %d[-::-] ", playerRollColor, playerBold, b.playerDice[0], b.playerDice[1]))) |
||||
} else { |
||||
t.Write([]byte(fmt.Sprintf(" [%s]- -[-] ", playerRollColor))) |
||||
} |
||||
} else if i == 10 { |
||||
t.Write([]byte("[" + playerRollColor + "::" + playerBold + "]" + playerColor + " " + playerName + " (" + b.s[3] + ")")) |
||||
if b.v[StatePlayerHome] > 0 { |
||||
t.Write([]byte(fmt.Sprintf(" %d off", b.v[StatePlayerHome]))) |
||||
} |
||||
t.Write([]byte("[-::-]")) |
||||
} |
||||
|
||||
t.Write([]byte("[\"\"] ")) |
||||
t.WriteByte('\n') |
||||
} |
||||
|
||||
t.WriteString("[\"space-off\"] ") |
||||
if white { |
||||
t.Write(boardBottomWhite) |
||||
} else { |
||||
t.Write(boardBottomBlack) |
||||
} |
||||
t.WriteString(" [\"\"]\n") |
||||
t.WriteString("[\"space-off\"] [\"\"]") |
||||
|
||||
b.Unlock() |
||||
|
||||
return t.Bytes() |
||||
} |
@ -0,0 +1,62 @@
@@ -0,0 +1,62 @@
|
||||
package fibs |
||||
|
||||
import ( |
||||
"fmt" |
||||
"testing" |
||||
) |
||||
|
||||
func TestBoard_GetValidMoves(t *testing.T) { |
||||
client := NewClient("", "", "") |
||||
|
||||
b := NewBoard(client) |
||||
|
||||
b.v[StatePlayerColor] = 1 |
||||
b.v[StateTurn] = 1 |
||||
b.v[StateDirection] = -1 |
||||
b.v[StateBoardSpace0+11] = 3 |
||||
b.v[StateBoardSpace0+9] = 7 |
||||
b.v[StateBoardSpace0+13] = -4 |
||||
b.v[StateBoardSpace0+24] = -3 |
||||
b.v[StatePlayerBar] = 1 |
||||
|
||||
testCases := []struct { |
||||
roll [2]int |
||||
from int |
||||
moves []int |
||||
}{ |
||||
{ |
||||
roll: [2]int{1, 5}, |
||||
from: 25, |
||||
moves: []int{19, 20}, |
||||
}, |
||||
{ |
||||
roll: [2]int{1, 5}, |
||||
from: 1, |
||||
moves: []int{}, |
||||
}, |
||||
} |
||||
for _, c := range testCases { |
||||
c := c |
||||
t.Run(fmt.Sprintf("With-%d-%d-From-%s", c.roll[0], c.roll[1], fmt.Sprintf("%02d", c.from)), func(t *testing.T) { |
||||
b.playerDice = c.roll |
||||
b.Draw() |
||||
|
||||
validMoves := b.GetValidMoves(c.from) |
||||
if !equalInts(validMoves, c.moves) { |
||||
t.Errorf("unexpected valid moves: expected %+v, got %+v\n%s", c.moves, validMoves, b.Render()) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func equalInts(a, b []int) bool { |
||||
if len(a) != len(b) { |
||||
return false |
||||
} |
||||
for i := range a { |
||||
if a[i] != b[i] { |
||||
return false |
||||
} |
||||
} |
||||
return true |
||||
} |
@ -0,0 +1,643 @@
@@ -0,0 +1,643 @@
|
||||
package fibs |
||||
|
||||
import ( |
||||
"bytes" |
||||
"fmt" |
||||
"log" |
||||
"math/rand" |
||||
"regexp" |
||||
"strconv" |
||||
"strings" |
||||
"time" |
||||
|
||||
"github.com/reiver/go-oi" |
||||
"github.com/reiver/go-telnet" |
||||
"golang.org/x/text/language" |
||||
"golang.org/x/text/message" |
||||
) |
||||
|
||||
const debug = 1 // TODO
|
||||
|
||||
const whoInfoSize = 12 |
||||
|
||||
const ( |
||||
whoInfoDataName = iota |
||||
whoInfoDataOpponent |
||||
whoInfoDataWatching |
||||
whoInfoDataReady |
||||
whoInfoDataAway |
||||
whoInfoDataRating |
||||
whoInfoDataExperience |
||||
whoInfoDataIdleTime |
||||
whoInfoDataLoginTime |
||||
whoInfoDataHostname |
||||
whoInfoDataClientName |
||||
whoInfoDataEmail |
||||
) |
||||
|
||||
var ( |
||||
TypeWelcome = []byte("1") |
||||
TypeOwnInfo = []byte("2") |
||||
TypeMOTD = []byte("3") |
||||
TypeEndMOTD = []byte("4") |
||||
TypeWhoInfo = []byte("5") |
||||
TypeEndWhoInfo = []byte("6") |
||||
TypeLogin = []byte("7") |
||||
TypeLogout = []byte("8") |
||||
TypeMsg = []byte("9") |
||||
TypeMsgDelivered = []byte("10") |
||||
TypeMsgSaved = []byte("11") |
||||
TypeSay = []byte("12") |
||||
TypeShout = []byte("13") |
||||
TypeWhisper = []byte("14") |
||||
TypeKibitz = []byte("15") |
||||
TypeYouSay = []byte("16") |
||||
TypeYouShout = []byte("17") |
||||
TypeYouWhisper = []byte("18") |
||||
TypeYouKibitz = []byte("19") |
||||
TypeBoardState = []byte("board:") |
||||
) |
||||
|
||||
var numberPrinter = message.NewPrinter(language.English) |
||||
|
||||
type WhoInfo struct { |
||||
Username string |
||||
Opponent string |
||||
Watching string |
||||
Ready bool |
||||
Away bool |
||||
Rating int |
||||
Experience int |
||||
Idle int |
||||
LoginTime int |
||||
ClientName string |
||||
} |
||||
|
||||
func (w *WhoInfo) String() string { |
||||
opponent := "In the lobby" |
||||
if w.Opponent != "" && w.Opponent != "-" { |
||||
opponent = "playing against " + w.Opponent |
||||
} |
||||
clientName := "" |
||||
if w.ClientName != "" && w.ClientName != "-" { |
||||
clientName = " using " + w.ClientName |
||||
} |
||||
return fmt.Sprintf("%s (rated %d with %d exp) is %s%s", w.Username, w.Rating, w.Experience, opponent, clientName) |
||||
} |
||||
|
||||
type Client struct { |
||||
In chan []byte |
||||
Out chan []byte |
||||
Event chan interface{} |
||||
|
||||
Username string |
||||
Password string |
||||
|
||||
loggedin bool |
||||
motd []byte |
||||
rawMode bool |
||||
|
||||
who map[string]*WhoInfo |
||||
|
||||
notified map[string]bool |
||||
|
||||
serverAddress string |
||||
|
||||
Board *Board |
||||
} |
||||
|
||||
func NewClient(serverAddress string, username string, password string) *Client { |
||||
c := &Client{ |
||||
In: make(chan []byte, 100), |
||||
Out: make(chan []byte, 100), |
||||
Event: make(chan interface{}, 100), |
||||
|
||||
serverAddress: serverAddress, |
||||
|
||||
Username: username, |
||||
Password: password, |
||||
|
||||
who: make(map[string]*WhoInfo), |
||||
|
||||
notified: make(map[string]bool), |
||||
} |
||||
|
||||
c.Board = NewBoard(c) |
||||
|
||||
go c.eventLoop() |
||||
|
||||
return c |
||||
} |
||||
|
||||
// CallTELNET is called when a connection is made with the server.
|
||||
func (c *Client) CallTELNET(ctx telnet.Context, w telnet.Writer, r telnet.Reader) { |
||||
go func() { |
||||
var b = &bytes.Buffer{} |
||||
var buffer [1]byte // Seems like the length of the buffer needs to be small, otherwise will have to wait for buffer to fill up.
|
||||
p := buffer[:] |
||||
|
||||
var motd bool // Parse all messages as MOTD text until MsgEndMOTD
|
||||
|
||||
for { |
||||
// Read 1 byte.
|
||||
n, err := r.Read(p) |
||||
if n <= 0 && nil == err { |
||||
continue |
||||
} else if n <= 0 && nil != err { |
||||
if err.Error() != "EOF" { |
||||
lf("** Disconnected: %s", err) |
||||
} else { |
||||
l("** Disconnected") |
||||
} |
||||
return |
||||
} |
||||
|
||||
b.WriteByte(p[0]) |
||||
|
||||
if p[0] == '\n' { |
||||
buf := make([]byte, b.Len()) |
||||
copy(buf, b.Bytes()) |
||||
|
||||
if debug > 0 { |
||||
l("<- " + string(bytes.TrimSpace(buf))) |
||||
} |
||||
|
||||
if c.loggedin { |
||||
if !motd { |
||||
buf = bytes.TrimSpace(buf) |
||||
if len(buf) == 0 { |
||||
b.Reset() |
||||
continue |
||||
} |
||||
} |
||||
|
||||
if c.rawMode { |
||||
c.In <- append([]byte("** "), buf...) |
||||
b.Reset() |
||||
continue |
||||
} |
||||
|
||||
if bytes.HasPrefix(b.Bytes(), TypeMOTD) && !motd { |
||||
motd = true |
||||
c.motd = append(c.motd, buf[1:]...) |
||||
b.Reset() |
||||
continue |
||||
} else if bytes.HasPrefix(b.Bytes(), TypeEndMOTD) && motd { |
||||
motd = false |
||||
c.motd = bytes.TrimSpace(c.motd) |
||||
c.In <- append([]byte("3 "), c.motd...) |
||||
b.Reset() |
||||
continue |
||||
} else if motd { |
||||
c.motd = append(c.motd, buf...) |
||||
b.Reset() |
||||
continue |
||||
} |
||||
|
||||
c.In <- buf |
||||
} |
||||
b.Reset() |
||||
} |
||||
|
||||
if !c.loggedin { |
||||
bt := strings.TrimSpace(b.String()) |
||||
if bt == "login:" { |
||||
b.Reset() |
||||
c.Out <- []byte(fmt.Sprintf("login bgammon 1008 %s %s", c.Username, c.Password)) |
||||
c.loggedin = true |
||||
} |
||||
} |
||||
} |
||||
}() |
||||
|
||||
var buffer bytes.Buffer |
||||
var p []byte |
||||
|
||||
var crlfBuffer [2]byte = [2]byte{'\r', '\n'} |
||||
crlf := crlfBuffer[:] |
||||
|
||||
help := []byte("help") |
||||
who := []byte("who") |
||||
quit := []byte("quit") |
||||
bye := []byte("bye") |
||||
watch := []byte("watch") |
||||
about := []byte("about") |
||||
show := []byte("show") |
||||
average := []byte("average") |
||||
dicetest := []byte("dicetest") |
||||
boardstate := []byte("boardstate") |
||||
stat := []byte("stat") |
||||
for b := range c.Out { |
||||
if bytes.Equal(bytes.ToLower(b), watch) { |
||||
c.WatchRandomGame() |
||||
continue |
||||
} else if bytes.Equal(bytes.ToLower(b), who) { |
||||
for username := range c.who { |
||||
lf("%s", c.who[username]) |
||||
} |
||||
continue |
||||
} else if bytes.HasPrefix(bytes.ToLower(b), help) || bytes.HasPrefix(bytes.ToLower(b), about) || bytes.HasPrefix(bytes.ToLower(b), average) || |
||||
bytes.HasPrefix(bytes.ToLower(b), dicetest) || bytes.HasPrefix(bytes.ToLower(b), show) || bytes.HasPrefix(bytes.ToLower(b), stat) { |
||||
c.rawMode = true |
||||
go func() { |
||||
time.Sleep(time.Second) |
||||
c.rawMode = false |
||||
}() |
||||
} else if bytes.Equal(bytes.ToLower(b), boardstate) { |
||||
lf("Board state: %s", c.Board.GetState()) |
||||
lf("Player color: %d", c.Board.v[StatePlayerColor]) |
||||
lf("Direction: %d", c.Board.v[StateDirection]) |
||||
lf("Current turn: %d", c.Board.v[StateTurn]) |
||||
lf("Moves: %+v", c.Board.moves) |
||||
lf("Pre-moves: %+v", c.Board.premove) |
||||
continue |
||||
} else if bytes.Equal(bytes.ToLower(b), quit) || bytes.Equal(bytes.ToLower(b), bye) { |
||||
// TODO match command, not exact string
|
||||
c.rawMode = true |
||||
} |
||||
|
||||
if debug > 0 { |
||||
l("-> " + string(bytes.TrimSpace(b))) |
||||
} |
||||
|
||||
buffer.Write(b) |
||||
buffer.Write(crlf) |
||||
|
||||
p = buffer.Bytes() |
||||
|
||||
n, err := oi.LongWrite(w, p) |
||||
if nil != err { |
||||
break |
||||
} |
||||
if expected, actual := int64(len(p)), n; expected != actual { |
||||
log.Fatalf("Transmission problem: tried sending %d bytes, but actually only sent %d bytes.", expected, actual) |
||||
} |
||||
|
||||
buffer.Reset() |
||||
} |
||||
} |
||||
|
||||
func (c *Client) keepAlive() { |
||||
t := time.NewTicker(5 * time.Minute) |
||||
for range t.C { |
||||
c.Out <- []byte("set boardstyle 3") |
||||
} |
||||
} |
||||
|
||||
func (c *Client) updateWhoInfo(b []byte) { |
||||
s := bytes.Split(b, []byte(" ")) |
||||
if len(s) != whoInfoSize { |
||||
return |
||||
} |
||||
|
||||
info := &WhoInfo{ |
||||
Username: string(s[whoInfoDataName]), |
||||
} |
||||
|
||||
r := s[whoInfoDataRating] |
||||
if bytes.ContainsRune(r, '.') { |
||||
r = s[whoInfoDataRating][:bytes.IndexByte(s[whoInfoDataRating], '.')] |
||||
} |
||||
rating, err := strconv.Atoi(string(r)) |
||||
if err != nil { |
||||
rating = 0 |
||||
} |
||||
info.Rating = rating |
||||
|
||||
experience, err := strconv.Atoi(string(s[whoInfoDataExperience])) |
||||
if err != nil { |
||||
experience = 0 |
||||
} |
||||
info.Experience = experience |
||||
|
||||
opponent := "" |
||||
if len(s[whoInfoDataOpponent]) > 1 || s[whoInfoDataOpponent][0] != '-' { |
||||
opponent = string(s[whoInfoDataOpponent]) |
||||
} |
||||
info.Opponent = opponent |
||||
|
||||
watching := "" |
||||
if len(s[whoInfoDataWatching]) > 1 || s[whoInfoDataWatching][0] != '-' { |
||||
watching = string(s[whoInfoDataWatching]) |
||||
} |
||||
info.Watching = watching |
||||
|
||||
ready := false |
||||
if string(s[whoInfoDataReady]) == "1" { |
||||
ready = true |
||||
} |
||||
info.Ready = ready |
||||
|
||||
clientName := "" |
||||
if len(s[whoInfoDataClientName]) > 1 || s[whoInfoDataClientName][0] != '-' { |
||||
clientName = string(s[whoInfoDataClientName]) |
||||
} |
||||
info.ClientName = clientName |
||||
|
||||
status := "Unavailable" |
||||
if info.Opponent != "" { |
||||
status = "vs. " + info.Opponent |
||||
} else if info.Ready { |
||||
status = "Available" |
||||
} |
||||
|
||||
itemText := info.Username + strings.Repeat(" ", 18-len(info.Username)) |
||||
|
||||
ratingString := numberPrinter.Sprintf("%d", info.Rating) |
||||
itemText += ratingString + strings.Repeat(" ", 8-len(ratingString)) |
||||
|
||||
experienceString := numberPrinter.Sprintf("%d", info.Experience) |
||||
itemText += experienceString + strings.Repeat(" ", 12-len(experienceString)) |
||||
|
||||
itemText += status |
||||
|
||||
c.who[string(s[whoInfoDataName])] = info |
||||
|
||||
// TODO who info event
|
||||
} |
||||
|
||||
func (c *Client) GetAllWhoInfo() []*WhoInfo { |
||||
w := make([]*WhoInfo, len(c.who)) |
||||
var i int |
||||
for _, whoInfo := range c.who { |
||||
w[i] = whoInfo |
||||
i++ |
||||
} |
||||
return w |
||||
} |
||||
|
||||
func (c *Client) LoggedIn() bool { |
||||
// TODO lock
|
||||
return c.loggedin |
||||
} |
||||
|
||||
func (c *Client) WatchRandomGame() { |
||||
var options []string |
||||
for username, whoInfo := range c.who { |
||||
if username != "" && whoInfo.Opponent != "" && |
||||
!strings.Contains(username, "Bot") && !strings.Contains(whoInfo.Opponent, "Bot") { |
||||
options = append(options, username, whoInfo.Opponent) |
||||
} |
||||
} |
||||
if len(options) == 0 { |
||||
for username, whoInfo := range c.who { |
||||
if username != "" && whoInfo.Opponent != "" { |
||||
options = append(options, username, whoInfo.Opponent) |
||||
} |
||||
} |
||||
if len(options) == 0 { |
||||
return |
||||
} |
||||
} |
||||
|
||||
option := options[rand.Intn(len(options))] |
||||
c.Out <- []byte("watch " + option) |
||||
} |
||||
|
||||
func (c *Client) Connect() error { |
||||
l(fmt.Sprintf("Connecting to %s...", c.serverAddress)) |
||||
|
||||
go c.keepAlive() |
||||
|
||||
err := telnet.DialToAndCall(c.serverAddress, c) |
||||
if err != nil { |
||||
lf("** Disconnected: %s", err) |
||||
} |
||||
return err |
||||
} |
||||
|
||||
func (c *Client) eventLoop() { |
||||
var setBoardStyle bool |
||||
var turnRegexp = regexp.MustCompile(`^turn: (\w+)\.`) |
||||
var movesRegexp = regexp.MustCompile(`^(\w+) moves (.*)`) |
||||
var rollsRegexp = regexp.MustCompile(`^(\w+) rolls? (.*)`) |
||||
var logInOutRegexp = regexp.MustCompile(`^\w+ logs [In|Out]\.`) |
||||
var dropsConnection = regexp.MustCompile(`^\w+ drops connection\.`) |
||||
var startMatchRegexp = regexp.MustCompile(`^\w+ and \w+ start a .*`) |
||||
var winsMatchRegexp = regexp.MustCompile(`^\w+ wins a [0-9]+ point match against .*`) |
||||
var newGameRegexp = regexp.MustCompile(`^Starting a new game with .*`) |
||||
|
||||
var gameBufferRegexp = regexp.MustCompile(`^\w+ (makes|roll|rolls|rolled|move|moves) .*`) |
||||
var pleaseMoveRegexp = regexp.MustCompile(`^Please move ([0-9]) pieces?.`) |
||||
var cantMoveRegexp = regexp.MustCompile(`^(\w+) can't move.`) |
||||
var doublesRegexp = regexp.MustCompile(`^\w+ doubles.`) |
||||
var acceptDoubleRegexp = regexp.MustCompile(`^\w+ accepts the double.`) |
||||
|
||||
for b := range c.In { |
||||
b = bytes.Replace(b, []byte{7}, []byte{}, -1) |
||||
b = bytes.TrimSpace(b) |
||||
bl := bytes.ToLower(b) |
||||
|
||||
// Select 10+ first to read prefixes correctly
|
||||
if bytes.HasPrefix(b, TypeMsgSaved) { |
||||
lf("Message to %s saved", b[3:]) |
||||
continue |
||||
} else if bytes.HasPrefix(b, TypeMsgDelivered) { |
||||
lf("Message to %s delivered", b[3:]) |
||||
continue |
||||
} else if bytes.HasPrefix(b, TypeSay) { |
||||
s := bytes.SplitN(b[3:], []byte(" "), 2) |
||||
lf("%s says: %s", s[0], s[1]) |
||||
continue |
||||
} else if bytes.HasPrefix(b, TypeShout) { |
||||
s := bytes.SplitN(b[3:], []byte(" "), 2) |
||||
lf("%s shouts: %s", s[0], s[1]) |
||||
continue |
||||
} else if bytes.HasPrefix(b, TypeWhisper) { |
||||
s := bytes.SplitN(b[3:], []byte(" "), 2) |
||||
lf("%s whispers: %s", s[0], s[1]) |
||||
continue |
||||
} else if bytes.HasPrefix(b, TypeKibitz) { |
||||
s := bytes.SplitN(b[3:], []byte(" "), 2) |
||||
lf("%s kibitzes: %s", s[0], s[1]) |
||||
continue |
||||
} else if bytes.HasPrefix(b, TypeYouSay) { |
||||
s := bytes.SplitN(b[3:], []byte(" "), 2) |
||||
lf("You say to %s: %s", s[0], s[1]) |
||||
continue |
||||
} else if bytes.HasPrefix(b, TypeYouShout) { |
||||
lf("You shout: %s", b[3:]) |
||||
continue |
||||
} else if bytes.HasPrefix(b, TypeYouWhisper) { |
||||
lf("You whisper: %s", b[3:]) |
||||
continue |
||||
} else if bytes.HasPrefix(b, TypeYouKibitz) { |
||||
lf("You kibitz: %s", b[3:]) |
||||
continue |
||||
} else if bytes.HasPrefix(b, TypeWelcome) { |
||||
s := bytes.Split(b[2:], []byte(" ")) |
||||
loginTimestamp, err := strconv.Atoi(string(s[1])) |
||||
if err != nil { |
||||
loginTimestamp = 0 |
||||
} |
||||
loginTime := time.Unix(int64(loginTimestamp), 0) |
||||
lf("Welcome, %s! Last login at %s", s[0], loginTime) |
||||
continue |
||||
} else if bytes.HasPrefix(b, TypeOwnInfo) { |
||||
// TODO Print own info
|
||||
continue |
||||
} else if bytes.HasPrefix(b, TypeMOTD) { |
||||
for _, line := range bytes.Split(c.motd, []byte("\n")) { |
||||
l(string(line)) |
||||
} |
||||
if !setBoardStyle { |
||||
c.Out <- []byte("set boardstyle 3") |
||||
setBoardStyle = true |
||||
} |
||||
continue |
||||
} else if bytes.HasPrefix(b, TypeWhoInfo) { |
||||
c.updateWhoInfo(b[2:]) |
||||
// TODO who window
|
||||
continue |
||||
} else if bytes.HasPrefix(b, TypeEndWhoInfo) { |
||||
// TODO draw who info event
|
||||
continue |
||||
} else if bytes.HasPrefix(b, TypeLogin) || bytes.HasPrefix(b, TypeLogout) { |
||||
b = b[2:] |
||||
b = b[bytes.IndexByte(b, ' ')+1:] |
||||
l(string(b)) |
||||
// TODO enable showing log In and Out messages
|
||||
// TODO remove from who
|
||||
continue |
||||
} else if bytes.HasPrefix(b, TypeMsg) { |
||||
lf("Received message: %s", b[3:]) |
||||
continue |
||||
} else if bytes.HasPrefix(b, TypeBoardState) { |
||||
c.Board.SetState(string(bytes.SplitN(b, []byte(" "), 2)[0][6:])) |
||||
continue |
||||
} else if turnRegexp.Match(b) { |
||||
turn := turnRegexp.FindSubmatch(b) |
||||
if string(turn[1]) == c.Username || string(turn[1]) == c.Board.s[0] || string(turn[1]) == "You" { |
||||
c.Board.v[StateTurn] = c.Board.v[StatePlayerColor] |
||||
} else { |
||||
c.Board.v[StateTurn] = c.Board.v[StatePlayerColor] * -1 |
||||
} |
||||
} else if rollsRegexp.Match(b) { |
||||
roll := rollsRegexp.FindSubmatch(b) |
||||
periodIndex := bytes.IndexRune(roll[2], '.') |
||||
if periodIndex > -1 { |
||||
roll[2] = roll[2][:periodIndex] |
||||
} |
||||
s := bytes.Split(roll[2], []byte(" ")) |
||||
var dice [2]int |
||||
var i int |
||||
for _, m := range s { |
||||
v, err := strconv.Atoi(string(m)) |
||||
if err == nil { |
||||
dice[i] = v |
||||
i++ |
||||
} |
||||
} |
||||
|
||||
if string(roll[1]) == c.Board.s[0] || string(roll[1]) == "You" { |
||||
c.Board.v[StateTurn] = c.Board.v[StatePlayerColor] |
||||
c.Board.playerDice = dice |
||||
} else { |
||||
c.Board.v[StateTurn] = c.Board.v[StatePlayerColor] * -1 |
||||
c.Board.opponentDice = dice |
||||
} |
||||
|
||||
c.Board.ResetMoves() |
||||
|
||||
c.Board.Draw() |
||||
} else if movesRegexp.Match(b) { |
||||
match := movesRegexp.FindSubmatch(b) |
||||
|
||||
player := c.Board.v[StatePlayerColor] |
||||
if string(match[1]) == c.Board.s[1] { |
||||
player *= -1 |
||||
} |
||||
|
||||
c.Board.ResetMoves() |
||||
|
||||
from := -1 |
||||
to := -1 |
||||
|
||||
s := bytes.Split(match[2], []byte(" ")) |
||||
for _, m := range s { |
||||
move := bytes.Split(m, []byte("-")) |
||||
if len(move) == 2 { |
||||
from = c.Board.parseMoveString(player, string(move[0])) |
||||
to = c.Board.parseMoveString(player, string(move[1])) |
||||
if from >= 0 && to >= 0 { |
||||
c.Event <- &EventMove{ |
||||
Player: player, |
||||
From: from, |
||||
To: to, |
||||
} |
||||
} |
||||
|
||||
c.Board.Move(player, string(move[0]), string(move[1])) |
||||
} |