...

Source file src/code.rocketnine.space/tslocum/fibs/client.go

Documentation: code.rocketnine.space/tslocum/fibs

     1  package fibs
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"io"
     8  	"log"
     9  	"math/rand"
    10  	"regexp"
    11  	"strconv"
    12  	"strings"
    13  	"time"
    14  
    15  	"github.com/reiver/go-oi"
    16  	"github.com/reiver/go-telnet"
    17  	"golang.org/x/text/language"
    18  	"golang.org/x/text/message"
    19  	"nhooyr.io/websocket"
    20  )
    21  
    22  // Debug controls the level of debug information to print.
    23  var Debug = 0
    24  
    25  const whoInfoSize = 12
    26  
    27  const (
    28  	whoInfoDataName = iota
    29  	whoInfoDataOpponent
    30  	whoInfoDataWatching
    31  	whoInfoDataReady
    32  	whoInfoDataAway
    33  	whoInfoDataRating
    34  	whoInfoDataExperience
    35  	whoInfoDataIdleTime
    36  	whoInfoDataLoginTime
    37  	whoInfoDataHostname
    38  	whoInfoDataClientName
    39  	whoInfoDataEmail
    40  )
    41  
    42  var DefaultProxyAddress = ""
    43  
    44  var (
    45  	TypeWelcome      = []byte("1")
    46  	TypeOwnInfo      = []byte("2")
    47  	TypeMOTD         = []byte("3")
    48  	TypeEndMOTD      = []byte("4")
    49  	TypeWhoInfo      = []byte("5")
    50  	TypeEndWhoInfo   = []byte("6")
    51  	TypeLogin        = []byte("7")
    52  	TypeLogout       = []byte("8")
    53  	TypeMsg          = []byte("9")
    54  	TypeMsgDelivered = []byte("10")
    55  	TypeMsgSaved     = []byte("11")
    56  	TypeSay          = []byte("12")
    57  	TypeShout        = []byte("13")
    58  	TypeWhisper      = []byte("14")
    59  	TypeKibitz       = []byte("15")
    60  	TypeYouSay       = []byte("16")
    61  	TypeYouShout     = []byte("17")
    62  	TypeYouWhisper   = []byte("18")
    63  	TypeYouKibitz    = []byte("19")
    64  	TypeBoardState   = []byte("board:")
    65  )
    66  
    67  var numberPrinter = message.NewPrinter(language.English)
    68  
    69  type WhoInfo struct {
    70  	Username   string
    71  	Opponent   string
    72  	Watching   string
    73  	Ready      bool
    74  	Away       bool
    75  	Rating     int
    76  	Experience int
    77  	Idle       int
    78  	LoginTime  int
    79  	ClientName string
    80  }
    81  
    82  func (w *WhoInfo) String() string {
    83  	opponent := "In the lobby"
    84  	if w.Opponent != "" && w.Opponent != "-" {
    85  		opponent = "playing against " + w.Opponent
    86  	}
    87  	clientName := ""
    88  	if w.ClientName != "" && w.ClientName != "-" {
    89  		clientName = " using " + w.ClientName
    90  	}
    91  	return fmt.Sprintf("%s (rated %d with %d exp) is %s%s", w.Username, w.Rating, w.Experience, opponent, clientName)
    92  }
    93  
    94  type Client struct {
    95  	In    chan []byte
    96  	Out   chan []byte
    97  	Event chan interface{}
    98  
    99  	Username string
   100  	Password string
   101  
   102  	loggedin bool
   103  	motd     []byte
   104  	rawMode  bool
   105  
   106  	who map[string]*WhoInfo
   107  
   108  	notified map[string]bool
   109  
   110  	serverAddress  string
   111  	wsProxyAddress string // WebSocket->TCP proxy address
   112  
   113  	tcpConn io.Writer
   114  	wsConn  *websocket.Conn
   115  
   116  	Board *Board
   117  
   118  	tvMode bool
   119  }
   120  
   121  func NewClient(serverAddress string, username string, password string) *Client {
   122  	c := &Client{
   123  		In:    make(chan []byte, 100),
   124  		Out:   make(chan []byte, 100),
   125  		Event: make(chan interface{}, 100),
   126  
   127  		serverAddress:  serverAddress,
   128  		wsProxyAddress: DefaultProxyAddress,
   129  
   130  		Username: username,
   131  		Password: password,
   132  
   133  		who: make(map[string]*WhoInfo),
   134  
   135  		notified: make(map[string]bool),
   136  	}
   137  
   138  	c.Board = NewBoard(c)
   139  
   140  	go c.eventLoop()
   141  
   142  	return c
   143  }
   144  
   145  func (c *Client) handleWrite() {
   146  	var buffer bytes.Buffer
   147  	var p []byte
   148  
   149  	crlfBuffer := [2]byte{'\r', '\n'}
   150  	crlf := crlfBuffer[:]
   151  
   152  	help := []byte("help")
   153  	who := []byte("who")
   154  	quit := []byte("quit")
   155  	bye := []byte("bye")
   156  	watch := []byte("watch")
   157  	tv := []byte("tv")
   158  	reset := []byte("reset")
   159  	textBoard := []byte("textboard")
   160  	about := []byte("about")
   161  	show := []byte("show")
   162  	average := []byte("average")
   163  	dicetest := []byte("dicetest")
   164  	boardstate := []byte("boardstate")
   165  	stat := []byte("stat")
   166  	for b := range c.Out {
   167  		if bytes.Equal(bytes.ToLower(b), watch) {
   168  			c.WatchRandomGame()
   169  			continue
   170  		} else if bytes.Equal(bytes.ToLower(b), tv) {
   171  			c.tvMode = !c.tvMode
   172  			if c.tvMode {
   173  				l("Now watching backgammon TV")
   174  				c.WatchRandomGame()
   175  			} else {
   176  				l("Stopped watching backgammon TV")
   177  			}
   178  			continue
   179  		} else if bytes.Equal(bytes.ToLower(b), reset) {
   180  			c.Board.ResetPreMoves()
   181  			c.Event <- &EventDraw{}
   182  			l("Reset pre-moves")
   183  			continue
   184  		} else if bytes.Equal(bytes.ToLower(b), who) {
   185  			for username := range c.who {
   186  				lf("%s", c.who[username])
   187  			}
   188  			continue
   189  		} else if bytes.Equal(bytes.ToLower(b), textBoard) {
   190  			go func() {
   191  				c.Out <- []byte("set boardstyle 2")
   192  				time.Sleep(time.Second)
   193  				c.Out <- []byte("board")
   194  				time.Sleep(time.Second)
   195  				c.Out <- []byte("set boardstyle 3")
   196  			}()
   197  			continue
   198  		} else if bytes.HasPrefix(bytes.ToLower(b), help) || bytes.HasPrefix(bytes.ToLower(b), about) || bytes.HasPrefix(bytes.ToLower(b), average) ||
   199  			bytes.HasPrefix(bytes.ToLower(b), dicetest) || bytes.HasPrefix(bytes.ToLower(b), show) || bytes.HasPrefix(bytes.ToLower(b), stat) {
   200  			c.rawMode = true
   201  			go func() {
   202  				time.Sleep(time.Second)
   203  				c.rawMode = false
   204  			}()
   205  		} else if bytes.Equal(bytes.ToLower(b), boardstate) {
   206  			homeBoardStart, homeBoardEnd := c.Board.PlayerHomeSpaces()
   207  
   208  			lf("Board state:  %s", c.Board.GetState())
   209  			lf("Player color:  %d", c.Board.v[StatePlayerColor])
   210  			lf("Direction:  %d", c.Board.v[StateDirection])
   211  			lf("Current turn:  %d", c.Board.v[StateTurn])
   212  			lf("Player home spaces:  %d-%d",
   213  				homeBoardStart, homeBoardEnd)
   214  			lf("Player can bear off:  %t", c.Board.PlayerPieceAreHome())
   215  			lf("Moves:  %+v", c.Board.moves)
   216  			lf("Pre-moves:  %+v", c.Board.premove)
   217  			continue
   218  		} else if bytes.Equal(bytes.ToLower(b), quit) || bytes.Equal(bytes.ToLower(b), bye) {
   219  			// TODO match command, not exact string
   220  			c.rawMode = true
   221  		}
   222  
   223  		if Debug > 0 {
   224  			l("-> " + string(bytes.TrimSpace(b)))
   225  		}
   226  
   227  		buffer.Write(b)
   228  		buffer.Write(crlf)
   229  
   230  		p = buffer.Bytes()
   231  
   232  		if c.wsProxyAddress != "" {
   233  			err := c.wsConn.Write(context.Background(), websocket.MessageBinary, p)
   234  			if err != nil {
   235  				log.Fatalf("Transmission problem: %s", err)
   236  			}
   237  			buffer.Reset()
   238  			continue
   239  		}
   240  
   241  		n, err := oi.LongWrite(c.tcpConn, p)
   242  		if nil != err {
   243  			break
   244  		}
   245  		if expected, actual := int64(len(p)), n; expected != actual {
   246  			log.Fatalf("Transmission problem: tried sending %d bytes, but actually only sent %d bytes.", expected, actual)
   247  		}
   248  
   249  		buffer.Reset()
   250  	}
   251  }
   252  
   253  func (c *Client) handleRead(r io.Reader) {
   254  	var b = &bytes.Buffer{}
   255  	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.
   256  	p := buffer[:]
   257  
   258  	var motd bool // Parse all messages as MOTD text until MsgEndMOTD
   259  
   260  	for {
   261  		// Read 1 byte.
   262  		n, err := r.Read(p)
   263  		if n <= 0 && nil == err {
   264  			continue
   265  		} else if n <= 0 && nil != err {
   266  			if err.Error() != "EOF" {
   267  				lf("** Disconnected: %s", err)
   268  			} else {
   269  				l("** Disconnected")
   270  			}
   271  			return
   272  		}
   273  
   274  		b.WriteByte(p[0])
   275  
   276  		if p[0] == '\n' {
   277  			buf := make([]byte, b.Len())
   278  			copy(buf, b.Bytes())
   279  
   280  			if Debug > 0 {
   281  				l("<- " + string(bytes.TrimSpace(buf)))
   282  			}
   283  
   284  			if c.loggedin {
   285  				if !motd {
   286  					buf = bytes.TrimSpace(buf)
   287  					if len(buf) == 0 {
   288  						b.Reset()
   289  						continue
   290  					}
   291  				}
   292  
   293  				if c.rawMode {
   294  					c.In <- append([]byte("** "), buf...)
   295  					b.Reset()
   296  					continue
   297  				}
   298  
   299  				if bytes.HasPrefix(b.Bytes(), TypeMOTD) && !motd {
   300  					motd = true
   301  					c.motd = append(c.motd, buf[1:]...)
   302  					b.Reset()
   303  					continue
   304  				} else if bytes.HasPrefix(b.Bytes(), TypeEndMOTD) && motd {
   305  					motd = false
   306  					c.motd = bytes.TrimSpace(c.motd)
   307  					c.In <- append([]byte("3 "), c.motd...)
   308  					b.Reset()
   309  					continue
   310  				} else if motd {
   311  					c.motd = append(c.motd, buf...)
   312  					b.Reset()
   313  					continue
   314  				}
   315  
   316  				c.In <- buf
   317  			}
   318  			b.Reset()
   319  		}
   320  
   321  		if !c.loggedin {
   322  			bt := strings.TrimSpace(b.String())
   323  			if bt == "login:" {
   324  				b.Reset()
   325  				c.Out <- []byte(fmt.Sprintf("login bgammon 1008 %s %s", c.Username, c.Password))
   326  				c.loggedin = true
   327  			}
   328  		}
   329  	}
   330  }
   331  
   332  // CallTELNET is called when a connection is made with the server.
   333  func (c *Client) CallTELNET(ctx telnet.Context, w telnet.Writer, r telnet.Reader) {
   334  	c.tcpConn = w
   335  
   336  	go c.handleRead(r)
   337  
   338  	c.handleWrite()
   339  }
   340  
   341  func (c *Client) keepAlive() {
   342  	t := time.NewTicker(5 * time.Minute)
   343  	for range t.C {
   344  		c.Out <- []byte("set boardstyle 3")
   345  	}
   346  }
   347  
   348  func (c *Client) updateWhoInfo(b []byte) {
   349  	s := bytes.Split(b, []byte(" "))
   350  	if len(s) != whoInfoSize {
   351  		return
   352  	}
   353  
   354  	info := &WhoInfo{
   355  		Username: string(s[whoInfoDataName]),
   356  	}
   357  
   358  	r := s[whoInfoDataRating]
   359  	if bytes.ContainsRune(r, '.') {
   360  		r = s[whoInfoDataRating][:bytes.IndexByte(s[whoInfoDataRating], '.')]
   361  	}
   362  	rating, err := strconv.Atoi(string(r))
   363  	if err != nil {
   364  		rating = 0
   365  	}
   366  	info.Rating = rating
   367  
   368  	experience, err := strconv.Atoi(string(s[whoInfoDataExperience]))
   369  	if err != nil {
   370  		experience = 0
   371  	}
   372  	info.Experience = experience
   373  
   374  	opponent := ""
   375  	if len(s[whoInfoDataOpponent]) > 1 || s[whoInfoDataOpponent][0] != '-' {
   376  		opponent = string(s[whoInfoDataOpponent])
   377  	}
   378  	info.Opponent = opponent
   379  
   380  	watching := ""
   381  	if len(s[whoInfoDataWatching]) > 1 || s[whoInfoDataWatching][0] != '-' {
   382  		watching = string(s[whoInfoDataWatching])
   383  	}
   384  	info.Watching = watching
   385  
   386  	ready := false
   387  	if string(s[whoInfoDataReady]) == "1" {
   388  		ready = true
   389  	}
   390  	info.Ready = ready
   391  
   392  	clientName := ""
   393  	if len(s[whoInfoDataClientName]) > 1 || s[whoInfoDataClientName][0] != '-' {
   394  		clientName = string(s[whoInfoDataClientName])
   395  	}
   396  	info.ClientName = clientName
   397  
   398  	status := "Unavailable"
   399  	if info.Opponent != "" {
   400  		status = "vs. " + info.Opponent
   401  	} else if info.Ready {
   402  		status = "Available"
   403  	}
   404  
   405  	itemText := info.Username + strings.Repeat(" ", 18-len(info.Username))
   406  
   407  	ratingString := numberPrinter.Sprintf("%d", info.Rating)
   408  	itemText += ratingString + strings.Repeat(" ", 8-len(ratingString))
   409  
   410  	experienceString := numberPrinter.Sprintf("%d", info.Experience)
   411  	itemText += experienceString + strings.Repeat(" ", 12-len(experienceString))
   412  
   413  	itemText += status
   414  
   415  	c.who[string(s[whoInfoDataName])] = info
   416  }
   417  
   418  func (c *Client) GetAllWhoInfo() []*WhoInfo {
   419  	w := make([]*WhoInfo, len(c.who))
   420  	var i int
   421  	for _, whoInfo := range c.who {
   422  		w[i] = whoInfo
   423  		i++
   424  	}
   425  	return w
   426  }
   427  
   428  func (c *Client) LoggedIn() bool {
   429  	// TODO lock
   430  	return c.loggedin
   431  }
   432  
   433  func (c *Client) WatchRandomGame() {
   434  	var options []string
   435  	for username, whoInfo := range c.who {
   436  		if username != "" && whoInfo.Opponent != "" &&
   437  			!strings.Contains(username, "Bot") && !strings.Contains(whoInfo.Opponent, "Bot") {
   438  			options = append(options, username, whoInfo.Opponent)
   439  		}
   440  	}
   441  	if len(options) == 0 {
   442  		for username, whoInfo := range c.who {
   443  			if username != "" && whoInfo.Opponent != "" {
   444  				options = append(options, username, whoInfo.Opponent)
   445  			}
   446  		}
   447  		if len(options) == 0 {
   448  			return
   449  		}
   450  	}
   451  
   452  	option := options[rand.Intn(len(options))]
   453  	c.Out <- []byte("watch " + option)
   454  }
   455  
   456  func (c *Client) callWebSocket() {
   457  	ctx := context.Background()
   458  
   459  	var err error
   460  	c.wsConn, _, err = websocket.Dial(ctx, c.wsProxyAddress, nil)
   461  	if err != nil {
   462  		log.Fatal("dial error", err)
   463  	}
   464  	defer c.wsConn.Close(websocket.StatusInternalError, "the sky is falling")
   465  
   466  	//c.wsConn.Close(websocket.StatusNormalClosure, "")
   467  
   468  	r, w := io.Pipe()
   469  
   470  	go c.handleRead(r)
   471  
   472  	go func() {
   473  		for {
   474  			_, data, err := c.wsConn.Read(context.Background())
   475  			if err != nil {
   476  				if err.Error() != "EOF" {
   477  					lf("** Disconnected: %s", err)
   478  				} else {
   479  					l("** Disconnected")
   480  				}
   481  			}
   482  			w.Write(data)
   483  		}
   484  	}()
   485  
   486  	c.handleWrite()
   487  
   488  	// TODO write CloseMessage when closing WS
   489  }
   490  
   491  func (c *Client) Connect() error {
   492  	connectionType := "Telnet"
   493  	if c.wsProxyAddress != "" {
   494  		connectionType = fmt.Sprintf("WebSocket proxy (%s)", c.wsProxyAddress)
   495  	}
   496  	l(fmt.Sprintf("Connecting via %s to %s...", connectionType, c.serverAddress))
   497  
   498  	go c.keepAlive()
   499  
   500  	if c.wsProxyAddress != "" {
   501  		go c.callWebSocket()
   502  		return nil
   503  	}
   504  
   505  	err := telnet.DialToAndCall(c.serverAddress, c)
   506  	if err != nil {
   507  		lf("** Disconnected: %s", err)
   508  	}
   509  	return err
   510  }
   511  
   512  func (c *Client) boardStateUpdated() {
   513  	s := make([]string, len(c.Board.s))
   514  	v := make([]int, len(c.Board.v))
   515  	copy(s, c.Board.s)
   516  	copy(v, c.Board.v)
   517  	c.Event <- &EventBoardState{S: s, V: v}
   518  }
   519  
   520  func (c *Client) eventLoop() {
   521  	var setBoardStyle bool
   522  	var turnRegexp = regexp.MustCompile(`^turn: (\w+)\.`)
   523  	var movesRegexp = regexp.MustCompile(`^(\w+) moves (.*)`)
   524  	var rollsRegexp = regexp.MustCompile(`^(\w+) rolls? (.*)`)
   525  	var logInOutRegexp = regexp.MustCompile(`^\w+ logs [In|Out]\.`)
   526  	var startMatchRegexp = regexp.MustCompile(`^\w+ and \w+ start a .*`)
   527  	var winsMatchRegexp = regexp.MustCompile(`^\w+ wins a [0-9]+ point match against .*`)
   528  	var winsThisMatchRegexp = regexp.MustCompile(`^\w+ wins the [0-9]+ point match .*`)
   529  	var newGameRegexp = regexp.MustCompile(`^Starting a new game with .*`)
   530  	var inviteResumeGameRegexp = regexp.MustCompile(`^*\* You invited \w+ to resume a saved match\..*`)
   531  	var joinedGameRegexp = regexp.MustCompile(`^\w+ has joined you\..*`)
   532  
   533  	var gameBufferRegexp = regexp.MustCompile(`^\w+ (makes|roll|rolls|rolled|move|moves) .*`)
   534  	var pleaseMoveRegexp = regexp.MustCompile(`^Please move ([0-9]) pieces?.`)
   535  	var invalidMoveRegexp = regexp.MustCompile(`^\*\* You can't move .*`)
   536  	var cantMoveRegexp = regexp.MustCompile(`^(\w+) can't move\.`)
   537  	var doublesRegexp = regexp.MustCompile(`^\w+ doubles.`)
   538  	var acceptDoubleRegexp = regexp.MustCompile(`^\w+ accepts the double.`)
   539  
   540  	for b := range c.In {
   541  		b = bytes.Replace(b, []byte{7}, []byte{}, -1)
   542  		b = bytes.TrimSpace(b)
   543  		bl := bytes.ToLower(b)
   544  
   545  		// Select 10+ first to read prefixes correctly
   546  		if bytes.HasPrefix(b, TypeMsgSaved) {
   547  			lf("Message to %s saved", b[3:])
   548  			continue
   549  		} else if bytes.HasPrefix(b, TypeMsgDelivered) {
   550  			lf("Message to %s delivered", b[3:])
   551  			continue
   552  		} else if bytes.HasPrefix(b, TypeSay) {
   553  			s := bytes.SplitN(b[3:], []byte(" "), 2)
   554  			lf("%s says: %s", s[0], s[1])
   555  			continue
   556  		} else if bytes.HasPrefix(b, TypeShout) {
   557  			s := bytes.SplitN(b[3:], []byte(" "), 2)
   558  			lf("%s shouts: %s", s[0], s[1])
   559  			continue
   560  		} else if bytes.HasPrefix(b, TypeWhisper) {
   561  			s := bytes.SplitN(b[3:], []byte(" "), 2)
   562  			lf("%s whispers: %s", s[0], s[1])
   563  			continue
   564  		} else if bytes.HasPrefix(b, TypeKibitz) {
   565  			s := bytes.SplitN(b[3:], []byte(" "), 2)
   566  			lf("%s kibitzes: %s", s[0], s[1])
   567  			continue
   568  		} else if bytes.HasPrefix(b, TypeYouSay) {
   569  			s := bytes.SplitN(b[3:], []byte(" "), 2)
   570  			lf("You say to %s: %s", s[0], s[1])
   571  			continue
   572  		} else if bytes.HasPrefix(b, TypeYouShout) {
   573  			lf("You shout: %s", b[3:])
   574  			continue
   575  		} else if bytes.HasPrefix(b, TypeYouWhisper) {
   576  			lf("You whisper: %s", b[3:])
   577  			continue
   578  		} else if bytes.HasPrefix(b, TypeYouKibitz) {
   579  			lf("You kibitz: %s", b[3:])
   580  			continue
   581  		} else if bytes.HasPrefix(b, TypeWelcome) {
   582  			s := bytes.Split(b[2:], []byte(" "))
   583  			loginTimestamp, err := strconv.Atoi(string(s[1]))
   584  			if err != nil {
   585  				loginTimestamp = 0
   586  			}
   587  			loginTime := time.Unix(int64(loginTimestamp), 0)
   588  			lf("Welcome, %s! Last login at %s", s[0], loginTime)
   589  			continue
   590  		} else if bytes.HasPrefix(b, TypeOwnInfo) {
   591  			// TODO Print own info
   592  			continue
   593  		} else if bytes.HasPrefix(b, TypeMOTD) {
   594  			for _, line := range bytes.Split(c.motd, []byte("\n")) {
   595  				l(strings.ReplaceAll(string(line), "|", " "))
   596  			}
   597  			if !setBoardStyle {
   598  				c.Out <- []byte("set boardstyle 3")
   599  				setBoardStyle = true
   600  			}
   601  			continue
   602  		} else if bytes.HasPrefix(b, TypeWhoInfo) {
   603  			c.updateWhoInfo(b[2:])
   604  			continue
   605  		} else if bytes.HasPrefix(b, TypeEndWhoInfo) {
   606  			who := make([]*WhoInfo, len(c.who))
   607  			i := 0
   608  			for _, whoInfo := range c.who {
   609  				who[i] = whoInfo
   610  				i++
   611  			}
   612  
   613  			c.Event <- &EventWho{
   614  				Who: who,
   615  			}
   616  			continue
   617  		} else if bytes.HasPrefix(b, TypeLogin) || bytes.HasPrefix(b, TypeLogout) {
   618  			b = b[2:]
   619  			b = b[bytes.IndexByte(b, ' ')+1:]
   620  			continue
   621  		} else if bytes.HasPrefix(b, TypeMsg) {
   622  			lf("Received message: %s", b[3:])
   623  			continue
   624  		} else if bytes.HasPrefix(b, TypeBoardState) {
   625  			c.Board.SetState(string(bytes.SplitN(b, []byte(" "), 2)[0][6:]))
   626  			c.boardStateUpdated()
   627  			continue
   628  		} else if turnRegexp.Match(b) {
   629  			turn := turnRegexp.FindSubmatch(b)
   630  			if string(turn[1]) == c.Username || string(turn[1]) == c.Board.s[0] || string(turn[1]) == "You" {
   631  				c.Board.v[StateTurn] = c.Board.v[StatePlayerColor]
   632  			} else {
   633  				c.Board.v[StateTurn] = c.Board.v[StatePlayerColor] * -1
   634  			}
   635  			c.boardStateUpdated()
   636  		} else if rollsRegexp.Match(b) {
   637  			roll := rollsRegexp.FindSubmatch(b)
   638  			periodIndex := bytes.IndexRune(roll[2], '.')
   639  			if periodIndex > -1 {
   640  				roll[2] = roll[2][:periodIndex]
   641  			}
   642  			s := bytes.Split(roll[2], []byte(" "))
   643  			var dice [2]int
   644  			var i int
   645  			for _, m := range s {
   646  				v, err := strconv.Atoi(string(m))
   647  				if err == nil {
   648  					dice[i] = v
   649  					i++
   650  				}
   651  			}
   652  
   653  			if string(roll[1]) == c.Board.s[0] || string(roll[1]) == "You" {
   654  				c.Board.v[StateTurn] = c.Board.v[StatePlayerColor]
   655  				c.Board.v[StatePlayerDice1] = dice[0]
   656  				c.Board.v[StatePlayerDice2] = dice[1]
   657  			} else {
   658  				c.Board.v[StateTurn] = c.Board.v[StatePlayerColor] * -1
   659  				c.Board.v[StateOpponentDice1] = dice[0]
   660  				c.Board.v[StateOpponentDice2] = dice[1]
   661  			}
   662  
   663  			c.Board.ResetMoves()
   664  
   665  			c.boardStateUpdated()
   666  		} else if movesRegexp.Match(b) {
   667  			c.boardStateUpdated()
   668  
   669  			match := movesRegexp.FindSubmatch(b)
   670  
   671  			player := c.Board.v[StatePlayerColor]
   672  			if string(match[1]) == c.Board.s[1] {
   673  				player *= -1
   674  			}
   675  
   676  			c.Board.ResetMoves()
   677  
   678  			from := -1
   679  			to := -1
   680  
   681  			s := bytes.Split(match[2], []byte(" "))
   682  			for _, m := range s {
   683  				move := bytes.Split(m, []byte("-"))
   684  				if len(move) == 2 {
   685  					from = c.Board.parseMoveString(player, string(move[0]))
   686  					to = c.Board.parseMoveString(player, string(move[1]))
   687  					if from >= 0 && to >= 0 {
   688  						c.Event <- &EventMove{
   689  							Player: player,
   690  							From:   from,
   691  							To:     to,
   692  						}
   693  					}
   694  					c.Board.Move(player, string(move[0]), string(move[1]))
   695  				}
   696  			}
   697  
   698  			c.Board.SimplifyMoves()
   699  
   700  			c.boardStateUpdated()
   701  
   702  			bs := string(b)
   703  			if strings.HasSuffix(bs, " .") {
   704  				bs = bs[:len(bs)-2] + "."
   705  			}
   706  			lg(bs)
   707  
   708  			continue
   709  		} else if string(b) == "Value of 'boardstyle' set to 3." {
   710  			continue
   711  		} 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" {
   712  			c.Board.v[StateTurn] = c.Board.v[StatePlayerColor]
   713  			c.Board.Draw()
   714  			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" {
   715  				c.Out <- []byte("roll") // TODO Delay and allow configuring
   716  			}
   717  		} else if invalidMoveRegexp.Match(b) {
   718  			c.Board.ResetPreMoves()
   719  			c.Board.Draw()
   720  		} else if cantMoveRegexp.Match(b) {
   721  			match := cantMoveRegexp.FindSubmatch(b)
   722  
   723  			u := string(match[1])
   724  			if u == c.Username || u == c.Board.s[0] || u == "You" {
   725  				//c.Board.opponentDice[0] = 0
   726  				//c.Board.opponentDice[1] = 0
   727  				c.Board.v[StateTurn] = c.Board.v[StatePlayerColor] * -1
   728  			} else if u == c.Board.s[1] {
   729  				//c.Board.playerDice[0] = 0
   730  				//c.Board.playerDice[1] = 0
   731  				c.Board.v[StateTurn] = c.Board.v[StatePlayerColor]
   732  			}
   733  		} else if pleaseMoveRegexp.Match(b) {
   734  			match := pleaseMoveRegexp.FindSubmatch(b)
   735  
   736  			n, err := strconv.Atoi(string(match[1]))
   737  			if err == nil {
   738  				c.Board.v[StateMovablePieces] = n
   739  			}
   740  		} else if joinedGameRegexp.Match(b) {
   741  			// Board state is not sent automatically when joining resumed game
   742  			c.Out <- []byte("board")
   743  		} else if bytes.HasPrefix(bl, []byte("you're now watching")) {
   744  			// Board state is not sent automatically when watching
   745  			c.Out <- []byte("board")
   746  		} else if inviteResumeGameRegexp.Match(b) {
   747  			// Board state is not always sent automatically when joining resumed game
   748  			go func() {
   749  				time.Sleep(500 * time.Millisecond)
   750  				c.Out <- []byte("board")
   751  			}()
   752  		} else if logInOutRegexp.Match(b) {
   753  			continue
   754  		} else if startMatchRegexp.Match(b) {
   755  			continue
   756  		} else if winsThisMatchRegexp.Match(b) {
   757  			if c.tvMode {
   758  				go func() {
   759  					time.Sleep(5 * time.Second)
   760  					if !c.tvMode {
   761  						return
   762  					}
   763  					c.WatchRandomGame()
   764  				}()
   765  			}
   766  			continue
   767  		} else if winsMatchRegexp.Match(b) {
   768  			continue
   769  		} else if newGameRegexp.Match(b) {
   770  			c.Board.ResetMoves()
   771  			c.Board.ResetPreMoves()
   772  			c.Board.resetSelection()
   773  			continue
   774  		}
   775  
   776  		if gameBufferRegexp.Match(b) || cantMoveRegexp.Match(b) ||
   777  			doublesRegexp.Match(b) || acceptDoubleRegexp.Match(b) ||
   778  			bytes.HasPrefix(bl, []byte("you're now watching")) || bytes.HasPrefix(bl, []byte("** you stop watching")) ||
   779  			strings.HasPrefix(string(b), "Bearing off:") || strings.HasPrefix(string(b), "The only possible move") {
   780  			lg(string(b))
   781  
   782  			if !bytes.HasPrefix(bl, []byte("you're now watching")) && !bytes.HasPrefix(bl, []byte("** you stop watching")) {
   783  				continue
   784  			}
   785  		}
   786  
   787  		l(string(b))
   788  	}
   789  }
   790  

View as plain text