644 lines
16 KiB
Go
644 lines
16 KiB
Go
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]))
|
|
}
|
|
}
|
|
|
|
c.Board.SimplifyMoves()
|
|
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 bytes.HasPrefix(bl, []byte("you're now watching")) {
|
|
// Board state is not sent automatically when watching
|
|
log.Println("WRITE BOARDD")
|
|
c.Out <- []byte("board")
|
|
} else if logInOutRegexp.Match(b) {
|
|
continue
|
|
} else if dropsConnection.Match(b) {
|
|
continue
|
|
} else if startMatchRegexp.Match(b) {
|
|
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))
|
|
}
|
|
}
|