You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
776 lines
20 KiB
776 lines
20 KiB
package fibs |
|
|
|
import ( |
|
"bytes" |
|
"context" |
|
"fmt" |
|
"io" |
|
"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" |
|
"nhooyr.io/websocket" |
|
) |
|
|
|
// Debug controls the level of debug information to print. |
|
var Debug = 0 |
|
|
|
const whoInfoSize = 12 |
|
|
|
const ( |
|
whoInfoDataName = iota |
|
whoInfoDataOpponent |
|
whoInfoDataWatching |
|
whoInfoDataReady |
|
whoInfoDataAway |
|
whoInfoDataRating |
|
whoInfoDataExperience |
|
whoInfoDataIdleTime |
|
whoInfoDataLoginTime |
|
whoInfoDataHostname |
|
whoInfoDataClientName |
|
whoInfoDataEmail |
|
) |
|
|
|
var DefaultProxyAddress = "" |
|
|
|
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 |
|
wsProxyAddress string // WebSocket->TCP proxy address |
|
|
|
tcpConn io.Writer |
|
wsConn *websocket.Conn |
|
|
|
Board *Board |
|
|
|
tvMode bool |
|
} |
|
|
|
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, |
|
wsProxyAddress: DefaultProxyAddress, |
|
|
|
Username: username, |
|
Password: password, |
|
|
|
who: make(map[string]*WhoInfo), |
|
|
|
notified: make(map[string]bool), |
|
} |
|
|
|
c.Board = NewBoard(c) |
|
|
|
go c.eventLoop() |
|
|
|
return c |
|
} |
|
|
|
func (c *Client) handleWrite() { |
|
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") |
|
tv := []byte("tv") |
|
reset := []byte("reset") |
|
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), tv) { |
|
c.tvMode = !c.tvMode |
|
if c.tvMode { |
|
l("Now watching backgammon TV") |
|
c.WatchRandomGame() |
|
} else { |
|
l("Stopped watching backgammon TV") |
|
} |
|
continue |
|
} else if bytes.Equal(bytes.ToLower(b), reset) { |
|
c.Board.ResetPreMoves() |
|
c.Event <- &EventDraw{} |
|
l("Reset pre-moves") |
|
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) { |
|
homeBoardStart, homeBoardEnd := c.Board.homeBoardSpaces() |
|
|
|
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("Player home spaces: %d-%d", |
|
homeBoardStart, homeBoardEnd) |
|
lf("Player can bear off: %t", c.Board.allPlayerPiecesInHomeBoard()) |
|
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() |
|
|
|
if c.wsProxyAddress != "" { |
|
err := c.wsConn.Write(context.Background(), websocket.MessageBinary, p) |
|
if err != nil { |
|
log.Fatalf("Transmission problem: %s", err) |
|
} |
|
buffer.Reset() |
|
continue |
|
} |
|
|
|
n, err := oi.LongWrite(c.tcpConn, 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) handleRead(r io.Reader) { |
|
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 |
|
} |
|
} |
|
} |
|
} |
|
|
|
// CallTELNET is called when a connection is made with the server. |
|
func (c *Client) CallTELNET(ctx telnet.Context, w telnet.Writer, r telnet.Reader) { |
|
c.tcpConn = w |
|
|
|
go c.handleRead(r) |
|
|
|
c.handleWrite() |
|
} |
|
|
|
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) callWebSocket() { |
|
ctx := context.Background() |
|
|
|
var err error |
|
c.wsConn, _, err = websocket.Dial(ctx, c.wsProxyAddress, nil) |
|
if err != nil { |
|
log.Fatal("dial error", err) |
|
} |
|
defer c.wsConn.Close(websocket.StatusInternalError, "the sky is falling") |
|
|
|
//c.wsConn.Close(websocket.StatusNormalClosure, "") |
|
|
|
r, w := io.Pipe() |
|
|
|
go c.handleRead(r) |
|
|
|
go func() { |
|
for { |
|
_, data, err := c.wsConn.Read(context.Background()) |
|
if err != nil { |
|
if err.Error() != "EOF" { |
|
lf("** Disconnected: %s", err) |
|
} else { |
|
l("** Disconnected") |
|
} |
|
} |
|
w.Write(data) |
|
} |
|
}() |
|
|
|
c.handleWrite() |
|
|
|
// TODO write CloseMessage when closing WS |
|
} |
|
|
|
func (c *Client) Connect() error { |
|
connectionType := "Telnet" |
|
if c.wsProxyAddress != "" { |
|
connectionType = fmt.Sprintf("WebSocket proxy (%s)", c.wsProxyAddress) |
|
} |
|
l(fmt.Sprintf("Connecting via %s to %s...", connectionType, c.serverAddress)) |
|
|
|
go c.keepAlive() |
|
|
|
if c.wsProxyAddress != "" { |
|
go c.callWebSocket() |
|
return nil |
|
} |
|
|
|
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 winsThisMatchRegexp = regexp.MustCompile(`^\w+ wins the [0-9]+ point match .*`) |
|
var newGameRegexp = regexp.MustCompile(`^Starting a new game with .*`) |
|
var inviteResumeGameRegexp = regexp.MustCompile(`^*\* You invited \w+ to resume a saved match\..*`) |
|
var joinedGameRegexp = regexp.MustCompile(`^\w+ has joined you\..*`) |
|
|
|
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:])) |
|
c.Event <- &EventBoardState{S: c.Board.s, V: c.Board.v} |
|
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 |
|
} |
|
c.Event <- EventBoardState{S: c.Board.s, V: c.Board.v} |
|
} 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.v[StatePlayerDice1] = dice[0] |
|
c.Board.v[StatePlayerDice2] = dice[1] |
|
} else { |
|
c.Board.v[StateTurn] = c.Board.v[StatePlayerColor] * -1 |
|
c.Board.v[StateOpponentDice1] = dice[0] |
|
c.Board.v[StateOpponentDice2] = dice[1] |
|
} |
|
|
|
c.Event <- &EventBoardState{S: c.Board.s, V: c.Board.v} |
|
|
|
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])) |
|
} |
|
} |
|
|
|
c.Board.SimplifyMoves() |
|
|
|
c.Board.v[StateTurn] = player * -1 |
|
if c.Board.v[StateTurn] == c.Board.v[StatePlayerColor] { |
|
c.Board.v[StatePlayerDice1] = 0 |
|
c.Board.v[StatePlayerDice2] = 0 |
|
} else { |
|
c.Board.v[StateOpponentDice1] = 0 |
|
c.Board.v[StateOpponentDice2] = 0 |
|
} |
|
|
|
c.Board.Draw() |
|
|
|
bs := string(b) |
|
if strings.HasSuffix(bs, " .") { |
|
bs = bs[:len(bs)-2] + "." |
|
} |
|
lg(bs) |
|
continue |
|
} else if string(b) == "Value of 'boardstyle' set to 3." { |
|
continue |
|
} else if string(b) == "It's your turn to move." || strings.TrimSpace(string(b)) == "It's your turn to roll or double." || strings.TrimSpace(string(b)) == "It's your turn. Please roll or double" { |
|
c.Board.v[StateTurn] = c.Board.v[StatePlayerColor] |
|
c.Board.Draw() |
|
if strings.TrimSpace(string(b)) == "It's your turn to roll or double." || strings.TrimSpace(string(b)) == "It's your turn. Please roll or double" { |
|
c.Out <- []byte("roll") // TODO Delay and allow configuring |
|
} |
|
} else if cantMoveRegexp.Match(b) { |
|
match := cantMoveRegexp.FindSubmatch(b) |
|
|
|
u := string(match[1]) |
|
if u == c.Username || u == c.Board.s[0] || u == "You" { |
|
//c.Board.opponentDice[0] = 0 |
|
//c.Board.opponentDice[1] = 0 |
|
c.Board.v[StateTurn] = c.Board.v[StatePlayerColor] * -1 |
|
} else if u == c.Board.s[1] { |
|
//c.Board.playerDice[0] = 0 |
|
//c.Board.playerDice[1] = 0 |
|
c.Board.v[StateTurn] = c.Board.v[StatePlayerColor] |
|
} |
|
} else if pleaseMoveRegexp.Match(b) { |
|
match := pleaseMoveRegexp.FindSubmatch(b) |
|
|
|
n, err := strconv.Atoi(string(match[1])) |
|
if err == nil { |
|
c.Board.v[StateMovablePieces] = n |
|
} |
|
} else if joinedGameRegexp.Match(b) { |
|
// Board state is not sent automatically when joining resumed game |
|
c.Out <- []byte("board") |
|
} else if bytes.HasPrefix(bl, []byte("you're now watching")) { |
|
// Board state is not sent automatically when watching |
|
c.Out <- []byte("board") |
|
} else if inviteResumeGameRegexp.Match(b) { |
|
// Board state is not always sent automatically when joining resumed game |
|
go func() { |
|
time.Sleep(500 * time.Millisecond) |
|
c.Out <- []byte("board") |
|
}() |
|
} else if logInOutRegexp.Match(b) { |
|
continue |
|
} else if dropsConnection.Match(b) { |
|
continue |
|
} else if startMatchRegexp.Match(b) { |
|
continue |
|
} else if winsThisMatchRegexp.Match(b) { |
|
if c.tvMode { |
|
go func() { |
|
time.Sleep(5 * time.Second) |
|
if !c.tvMode { |
|
return |
|
} |
|
c.WatchRandomGame() |
|
}() |
|
} |
|
continue |
|
} else if winsMatchRegexp.Match(b) { |
|
continue |
|
} else if newGameRegexp.Match(b) { |
|
c.Board.ResetMoves() |
|
c.Board.resetSelection() |
|
// TODO reset premove |
|
continue |
|
} |
|
|
|
if gameBufferRegexp.Match(b) || cantMoveRegexp.Match(b) || |
|
doublesRegexp.Match(b) || acceptDoubleRegexp.Match(b) || |
|
bytes.HasPrefix(bl, []byte("you're now watching")) || bytes.HasPrefix(bl, []byte("** you stop watching")) || |
|
strings.HasPrefix(string(b), "Bearing off:") || strings.HasPrefix(string(b), "The only possible move") { |
|
lg(string(b)) |
|
|
|
if !bytes.HasPrefix(bl, []byte("you're now watching")) && !bytes.HasPrefix(bl, []byte("** you stop watching")) { |
|
continue |
|
} |
|
} |
|
|
|
l(string(b)) |
|
} |
|
}
|
|
|