...

Source file src/code.rocket9labs.com/tslocum/bgammon/pkg/server/server_command.go

Documentation: code.rocket9labs.com/tslocum/bgammon/pkg/server

     1  package server
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"log"
     7  	"strconv"
     8  	"strings"
     9  	"time"
    10  
    11  	"code.rocket9labs.com/tslocum/bgammon"
    12  )
    13  
    14  func (s *server) handleCommands() {
    15  	var cmd serverCommand
    16  COMMANDS:
    17  	for cmd = range s.commands {
    18  		if cmd.client == nil {
    19  			log.Panicf("nil client with command %s", cmd.command)
    20  		} else if cmd.client.terminating || cmd.client.Terminated() {
    21  			continue
    22  		}
    23  
    24  		cmd.command = bytes.TrimSpace(cmd.command)
    25  
    26  		firstSpace := bytes.IndexByte(cmd.command, ' ')
    27  		var keyword string
    28  		var startParameters int
    29  		if firstSpace == -1 {
    30  			keyword = string(cmd.command)
    31  			startParameters = len(cmd.command)
    32  		} else {
    33  			keyword = string(cmd.command[:firstSpace])
    34  			startParameters = firstSpace + 1
    35  		}
    36  		if keyword == "" {
    37  			continue
    38  		}
    39  		keyword = strings.ToLower(keyword)
    40  		params := bytes.Fields(cmd.command[startParameters:])
    41  
    42  		// Require users to send login command first.
    43  		if cmd.client.accountID == -1 {
    44  			resetCommand := keyword == bgammon.CommandResetPassword
    45  			if resetCommand {
    46  				if len(params) > 0 {
    47  					email := bytes.ToLower(bytes.TrimSpace(params[0]))
    48  					if len(email) > 0 {
    49  						err := resetAccount(s.mailServer, s.resetSalt, email)
    50  						if err != nil {
    51  							log.Fatalf("failed to reset password: %s", err)
    52  						}
    53  					}
    54  				}
    55  				cmd.client.Terminate("resetpasswordok")
    56  				continue
    57  			}
    58  
    59  			loginCommand := keyword == bgammon.CommandLogin || keyword == bgammon.CommandLoginJSON || keyword == "lj"
    60  			registerCommand := keyword == bgammon.CommandRegister || keyword == bgammon.CommandRegisterJSON || keyword == "rj"
    61  			if loginCommand || registerCommand {
    62  				if keyword == bgammon.CommandLoginJSON || keyword == bgammon.CommandRegisterJSON || keyword == "lj" || keyword == "rj" {
    63  					cmd.client.json = true
    64  				}
    65  
    66  				var username []byte
    67  				var password []byte
    68  				var randomUsername bool
    69  				if registerCommand {
    70  					sendUsage := func() {
    71  						cmd.client.Terminate("Please enter an email, username and password.")
    72  					}
    73  
    74  					var email []byte
    75  					if keyword == bgammon.CommandRegisterJSON || keyword == "rj" {
    76  						if len(params) < 4 {
    77  							sendUsage()
    78  							continue
    79  						}
    80  						email = params[1]
    81  						username = params[2]
    82  						password = bytes.Join(params[3:], []byte("_"))
    83  					} else {
    84  						if len(params) < 3 {
    85  							sendUsage()
    86  							continue
    87  						}
    88  						email = params[0]
    89  						username = params[1]
    90  						password = bytes.Join(params[2:], []byte("_"))
    91  					}
    92  					if onlyNumbers.Match(username) {
    93  						cmd.client.Terminate("Failed to register: Invalid username: must contain at least one non-numeric character.")
    94  						continue
    95  					}
    96  					password = bytes.ReplaceAll(password, []byte(" "), []byte("_"))
    97  					a := &account{
    98  						email:    email,
    99  						username: username,
   100  						password: password,
   101  					}
   102  					err := registerAccount(s.passwordSalt, a)
   103  					if err != nil {
   104  						cmd.client.Terminate(fmt.Sprintf("Failed to register: %s", err))
   105  						continue
   106  					}
   107  				} else {
   108  					s.clientsLock.Lock()
   109  
   110  					readUsername := func() bool {
   111  						if cmd.client.json {
   112  							if len(params) > 1 {
   113  								username = params[1]
   114  							}
   115  						} else {
   116  							if len(params) > 0 {
   117  								username = params[0]
   118  							}
   119  						}
   120  						if len(bytes.TrimSpace(username)) == 0 {
   121  							username = s.randomUsername()
   122  							randomUsername = true
   123  						} else if !alphaNumericUnderscore.Match(username) {
   124  							cmd.client.Terminate("Invalid username: must contain only letters, numbers and underscores.")
   125  							return false
   126  						}
   127  						if onlyNumbers.Match(username) {
   128  							cmd.client.Terminate("Invalid username: must contain at least one non-numeric character.")
   129  							return false
   130  						} else if s.clientByUsername(username) != nil || s.clientByUsername(append([]byte("Guest_"), username...)) != nil || (!randomUsername && !s.nameAllowed(username)) {
   131  							cmd.client.Terminate("That username is already in use.")
   132  							return false
   133  						}
   134  						return true
   135  					}
   136  					if !readUsername() {
   137  						s.clientsLock.Unlock()
   138  						continue
   139  					}
   140  					if len(params) > 2 {
   141  						password = bytes.ReplaceAll(bytes.Join(params[2:], []byte(" ")), []byte(" "), []byte("_"))
   142  					}
   143  
   144  					s.clientsLock.Unlock()
   145  				}
   146  
   147  				if len(password) > 0 {
   148  					a, err := loginAccount(s.passwordSalt, username, password)
   149  					if err != nil {
   150  						cmd.client.Terminate(fmt.Sprintf("Failed to log in: %s", err))
   151  						continue
   152  					} else if a == nil {
   153  						cmd.client.Terminate("No account was found with the provided username and password. To log in as a guest, do not enter a password.")
   154  						continue
   155  					}
   156  
   157  					var name []byte
   158  					if bytes.HasPrefix(a.username, []byte("bot_")) {
   159  						name = append([]byte("BOT_"), a.username[4:]...)
   160  					} else {
   161  						name = a.username
   162  					}
   163  					if s.clientByUsername(name) != nil {
   164  						cmd.client.Terminate("That username is already in use.")
   165  						continue
   166  					}
   167  
   168  					cmd.client.account = a
   169  					cmd.client.accountID = a.id
   170  					cmd.client.name = name
   171  					cmd.client.autoplay = a.autoplay
   172  				} else {
   173  					cmd.client.accountID = 0
   174  					if !randomUsername && !bytes.HasPrefix(username, []byte("BOT_")) && !bytes.HasPrefix(username, []byte("Guest_")) {
   175  						username = append([]byte("Guest_"), username...)
   176  					}
   177  					cmd.client.name = username
   178  				}
   179  
   180  				cmd.client.sendEvent(&bgammon.EventWelcome{
   181  					PlayerName: string(cmd.client.name),
   182  					Clients:    len(s.clients),
   183  					Games:      len(s.games),
   184  				})
   185  
   186  				log.Printf("Client %d logged in as %s", cmd.client.id, cmd.client.name)
   187  
   188  				// Send user settings.
   189  				if cmd.client.account != nil {
   190  					a := cmd.client.account
   191  					cmd.client.sendEvent(&bgammon.EventSettings{
   192  						AutoPlay:  a.autoplay,
   193  						Highlight: a.highlight,
   194  						Pips:      a.pips,
   195  						Moves:     a.moves,
   196  						Flip:      a.flip,
   197  						Advanced:  a.advanced,
   198  						Speed:     a.speed,
   199  					})
   200  				}
   201  
   202  				// Rejoin match in progress.
   203  				s.gamesLock.RLock()
   204  				for _, g := range s.games {
   205  					if g.terminated() || g.Winner != 0 {
   206  						continue
   207  					}
   208  
   209  					var rejoin bool
   210  					if bytes.Equal(cmd.client.name, g.allowed1) {
   211  						rejoin = g.rejoin1
   212  					} else if bytes.Equal(cmd.client.name, g.allowed2) {
   213  						rejoin = g.rejoin2
   214  					}
   215  					if rejoin {
   216  						g.addClient(cmd.client)
   217  						cmd.client.sendNotice(fmt.Sprintf("Rejoined match: %s", g.name))
   218  					}
   219  				}
   220  				s.gamesLock.RUnlock()
   221  				continue
   222  			}
   223  
   224  			cmd.client.Terminate("You must login before using other commands.")
   225  			continue
   226  		}
   227  
   228  		clientGame := s.gameByClient(cmd.client)
   229  		if clientGame != nil && clientGame.client1 != cmd.client && clientGame.client2 != cmd.client {
   230  			switch keyword {
   231  			case bgammon.CommandHelp, "h", bgammon.CommandJSON, bgammon.CommandList, "ls", bgammon.CommandBoard, "b", bgammon.CommandLeave, "l", bgammon.CommandReplay, bgammon.CommandSet, bgammon.CommandDisconnect, bgammon.CommandPong:
   232  				// These commands are allowed to be used by spectators.
   233  			default:
   234  				cmd.client.sendNotice("Command ignored: You are spectating this match.")
   235  				continue
   236  			}
   237  		}
   238  
   239  		switch keyword {
   240  		case bgammon.CommandHelp, "h":
   241  			// TODO get extended help by specifying a command after help
   242  			cmd.client.sendEvent(&bgammon.EventHelp{
   243  				Topic:   "",
   244  				Message: "Test help text",
   245  			})
   246  		case bgammon.CommandJSON:
   247  			sendUsage := func() {
   248  				cmd.client.sendNotice("To enable JSON formatted messages, send 'json on'. To disable JSON formatted messages, send 'json off'.")
   249  			}
   250  			if len(params) != 1 {
   251  				sendUsage()
   252  				continue
   253  			}
   254  			paramLower := strings.ToLower(string(params[0]))
   255  			switch paramLower {
   256  			case "on":
   257  				cmd.client.json = true
   258  				cmd.client.sendNotice("JSON formatted messages enabled.")
   259  			case "off":
   260  				cmd.client.json = false
   261  				cmd.client.sendNotice("JSON formatted messages disabled.")
   262  			default:
   263  				sendUsage()
   264  			}
   265  		case bgammon.CommandSay, "s":
   266  			if len(params) == 0 {
   267  				continue
   268  			}
   269  			if clientGame == nil {
   270  				cmd.client.sendNotice("Message not sent: You are not currently in a match.")
   271  				continue
   272  			}
   273  			opponent := clientGame.opponent(cmd.client)
   274  			if opponent == nil {
   275  				cmd.client.sendNotice("Message not sent: There is no one else in the match.")
   276  				continue
   277  			}
   278  			ev := &bgammon.EventSay{
   279  				Message: string(bytes.Join(params, []byte(" "))),
   280  			}
   281  			ev.Player = string(cmd.client.name)
   282  			opponent.sendEvent(ev)
   283  			if s.relayChat {
   284  				for _, spectator := range clientGame.spectators {
   285  					spectator.sendEvent(ev)
   286  				}
   287  			}
   288  		case bgammon.CommandList, "ls":
   289  			ev := &bgammon.EventList{}
   290  
   291  			s.gamesLock.RLock()
   292  			for _, g := range s.games {
   293  				listing := g.listing(cmd.client.name)
   294  				if listing == nil {
   295  					continue
   296  				}
   297  				ev.Games = append(ev.Games, *listing)
   298  			}
   299  			s.gamesLock.RUnlock()
   300  
   301  			cmd.client.sendEvent(ev)
   302  		case bgammon.CommandCreate, "c":
   303  			if clientGame != nil {
   304  				cmd.client.sendNotice("Failed to create match: Please leave the match you are in before creating another.")
   305  				continue
   306  			}
   307  
   308  			sendUsage := func() {
   309  				cmd.client.sendNotice("To create a public match please specify whether it is public or private, and also specify how many points are needed to win the match. When creating a private match, a password must also be provided.")
   310  			}
   311  			if len(params) < 2 {
   312  				sendUsage()
   313  				continue
   314  			}
   315  
   316  			var gamePassword []byte
   317  			gameType := bytes.ToLower(params[0])
   318  			var gameName []byte
   319  			var gamePoints []byte
   320  			switch {
   321  			case bytes.Equal(gameType, []byte("public")):
   322  				gamePoints = params[1]
   323  				if len(params) > 2 {
   324  					gameName = bytes.Join(params[2:], []byte(" "))
   325  				}
   326  			case bytes.Equal(gameType, []byte("private")):
   327  				if len(params) < 3 {
   328  					sendUsage()
   329  					continue
   330  				}
   331  				gamePassword = bytes.ReplaceAll(params[1], []byte("_"), []byte(" "))
   332  				gamePoints = params[2]
   333  				if len(params) > 3 {
   334  					gameName = bytes.Join(params[3:], []byte(" "))
   335  				}
   336  			default:
   337  				sendUsage()
   338  				continue
   339  			}
   340  
   341  			variant := bgammon.VariantBackgammon
   342  
   343  			// Backwards-compatible acey-deucey and tabula parameter. Acey-deucey added in v1.1.5. Tabula added in v1.2.2.
   344  			variantNone := bytes.HasPrefix(gameName, []byte("0 ")) || bytes.Equal(gameName, []byte("0"))
   345  			variantAcey := bytes.HasPrefix(gameName, []byte("1 ")) || bytes.Equal(gameName, []byte("1"))
   346  			variantTabula := bytes.HasPrefix(gameName, []byte("2 ")) || bytes.Equal(gameName, []byte("2"))
   347  			if variantNone || variantAcey || variantTabula {
   348  				if variantAcey {
   349  					variant = bgammon.VariantAceyDeucey
   350  				} else if variantTabula {
   351  					variant = bgammon.VariantTabula
   352  				}
   353  				if len(gameName) > 1 {
   354  					gameName = gameName[2:]
   355  				} else {
   356  					gameName = nil
   357  				}
   358  			}
   359  
   360  			points, err := strconv.Atoi(string(gamePoints))
   361  			if err != nil || points < 1 || points > 99 {
   362  				sendUsage()
   363  				continue
   364  			}
   365  
   366  			// Set default game name.
   367  			if len(bytes.TrimSpace(gameName)) == 0 {
   368  				abbr := "'s"
   369  				lastLetter := cmd.client.name[len(cmd.client.name)-1]
   370  				if lastLetter == 's' || lastLetter == 'S' {
   371  					abbr = "'"
   372  				}
   373  				gameName = []byte(fmt.Sprintf("%s%s match", cmd.client.name, abbr))
   374  			}
   375  
   376  			g := newServerGame(<-s.newGameIDs, variant)
   377  			g.name = gameName
   378  			g.Points = int8(points)
   379  			g.password = gamePassword
   380  			g.addClient(cmd.client)
   381  
   382  			s.gamesLock.Lock()
   383  			s.games = append(s.games, g)
   384  			s.gamesLock.Unlock()
   385  
   386  			cmd.client.sendNotice(fmt.Sprintf("Created match: %s", g.name))
   387  
   388  			if len(g.password) == 0 {
   389  				cmd.client.sendNotice("Note: Please be patient as you wait for another player to join the match. A chime will sound when another player joins. While you wait, join the bgammon.org community via Discord, Matrix or IRC at bgammon.org/community")
   390  			}
   391  		case bgammon.CommandJoin, "j":
   392  			if clientGame != nil {
   393  				cmd.client.sendEvent(&bgammon.EventFailedJoin{
   394  					Reason: "Please leave the match you are in before joining another.",
   395  				})
   396  				continue
   397  			}
   398  
   399  			sendUsage := func() {
   400  				cmd.client.sendNotice("To join a match please specify its ID or the name of a player in the match. To join a private match, a password must also be specified.")
   401  			}
   402  
   403  			if len(params) == 0 {
   404  				sendUsage()
   405  				continue
   406  			}
   407  
   408  			var joinGameID int
   409  			if onlyNumbers.Match(params[0]) {
   410  				gameID, err := strconv.Atoi(string(params[0]))
   411  				if err == nil && gameID > 0 {
   412  					joinGameID = gameID
   413  				}
   414  
   415  				if joinGameID == 0 {
   416  					sendUsage()
   417  					continue
   418  				}
   419  			} else {
   420  				paramLower := bytes.ToLower(params[0])
   421  				s.clientsLock.Lock()
   422  				for _, sc := range s.clients {
   423  					if bytes.Equal(paramLower, bytes.ToLower(sc.name)) {
   424  						g := s.gameByClient(sc)
   425  						if g != nil {
   426  							joinGameID = g.id
   427  						}
   428  						break
   429  					}
   430  				}
   431  				s.clientsLock.Unlock()
   432  
   433  				if joinGameID == 0 {
   434  					cmd.client.sendEvent(&bgammon.EventFailedJoin{
   435  						Reason: "Match not found.",
   436  					})
   437  					continue
   438  				}
   439  			}
   440  
   441  			s.gamesLock.Lock()
   442  			for _, g := range s.games {
   443  				if g.terminated() {
   444  					continue
   445  				}
   446  				if g.id == joinGameID {
   447  					providedPassword := bytes.ReplaceAll(bytes.Join(params[1:], []byte(" ")), []byte("_"), []byte(" "))
   448  					if len(g.password) != 0 && (len(params) < 2 || !bytes.Equal(g.password, providedPassword)) {
   449  						cmd.client.sendEvent(&bgammon.EventFailedJoin{
   450  							Reason: "Invalid password.",
   451  						})
   452  						s.gamesLock.Unlock()
   453  						continue COMMANDS
   454  					}
   455  
   456  					if bytes.HasPrefix(bytes.ToLower(cmd.client.name), []byte("bot_")) && ((g.client1 != nil && !bytes.HasPrefix(bytes.ToLower(g.client1.name), []byte("bot_"))) || (g.client2 != nil && !bytes.HasPrefix(bytes.ToLower(g.client2.name), []byte("bot_")))) {
   457  						cmd.client.sendEvent(&bgammon.EventFailedJoin{
   458  							Reason: "Bots are not allowed to join player matches. Please create a match instead.",
   459  						})
   460  						continue COMMANDS
   461  					}
   462  
   463  					spectator := g.addClient(cmd.client)
   464  					s.gamesLock.Unlock()
   465  					cmd.client.sendNotice(fmt.Sprintf("Joined match: %s", g.name))
   466  					if spectator {
   467  						cmd.client.sendNotice("You are spectating this match. Chat messages are not relayed.")
   468  					}
   469  					continue COMMANDS
   470  				}
   471  			}
   472  			s.gamesLock.Unlock()
   473  
   474  			cmd.client.sendEvent(&bgammon.EventFailedJoin{
   475  				Reason: "Match not found.",
   476  			})
   477  		case bgammon.CommandLeave, "l":
   478  			if clientGame == nil {
   479  				cmd.client.sendEvent(&bgammon.EventFailedLeave{
   480  					Reason: "You are not currently in a match.",
   481  				})
   482  				continue
   483  			}
   484  
   485  			if cmd.client.playerNumber == 1 {
   486  				clientGame.rejoin1 = false
   487  			} else {
   488  				clientGame.rejoin2 = false
   489  			}
   490  
   491  			clientGame.removeClient(cmd.client)
   492  		case bgammon.CommandDouble, "d":
   493  			if clientGame == nil {
   494  				cmd.client.sendNotice("You are not currently in a match.")
   495  				continue
   496  			} else if clientGame.Winner != 0 {
   497  				continue
   498  			}
   499  
   500  			if clientGame.Turn != cmd.client.playerNumber {
   501  				cmd.client.sendNotice("It is not your turn.")
   502  				continue
   503  			}
   504  
   505  			gameState := &bgammon.GameState{
   506  				Game:         clientGame.Game,
   507  				PlayerNumber: cmd.client.playerNumber,
   508  				Available:    clientGame.LegalMoves(false),
   509  			}
   510  			if !gameState.MayDouble() {
   511  				cmd.client.sendNotice("You may not double at this time.")
   512  				continue
   513  			}
   514  
   515  			if clientGame.DoublePlayer != 0 && clientGame.DoublePlayer != cmd.client.playerNumber {
   516  				cmd.client.sendNotice("You do not currently hold the doubling cube.")
   517  				continue
   518  			}
   519  
   520  			opponent := clientGame.opponent(cmd.client)
   521  			if opponent == nil {
   522  				cmd.client.sendNotice("You may not double until your opponent rejoins the match.")
   523  				continue
   524  			}
   525  
   526  			clientGame.DoubleOffered = true
   527  			clientGame.NextPartialTurn(opponent.playerNumber)
   528  
   529  			cmd.client.sendNotice(fmt.Sprintf("Double offered to opponent (%d points).", clientGame.DoubleValue*2))
   530  			clientGame.opponent(cmd.client).sendNotice(fmt.Sprintf("%s offers a double (%d points).", cmd.client.name, clientGame.DoubleValue*2))
   531  
   532  			clientGame.eachClient(func(client *serverClient) {
   533  				if client.json {
   534  					clientGame.sendBoard(client, false)
   535  				}
   536  			})
   537  		case bgammon.CommandResign:
   538  			if clientGame == nil {
   539  				cmd.client.sendNotice("You are not currently in a match.")
   540  				continue
   541  			} else if clientGame.Winner != 0 {
   542  				continue
   543  			}
   544  
   545  			gameState := &bgammon.GameState{
   546  				Game:         clientGame.Game,
   547  				PlayerNumber: cmd.client.playerNumber,
   548  				Available:    clientGame.LegalMoves(false),
   549  			}
   550  			if !gameState.MayResign() {
   551  				cmd.client.sendNotice("You may not resign at this time.")
   552  				continue
   553  			}
   554  
   555  			opponent := clientGame.opponent(cmd.client)
   556  			if opponent == nil {
   557  				cmd.client.sendNotice("You may not resign until your opponent rejoins the match.")
   558  				continue
   559  			}
   560  
   561  			clientGame.NextPartialTurn(opponent.playerNumber)
   562  
   563  			cmd.client.sendNotice("Declined double offer")
   564  			clientGame.opponent(cmd.client).sendNotice(fmt.Sprintf("%s declined double offer.", cmd.client.name))
   565  
   566  			clientGame.replay = append([][]byte{[]byte(fmt.Sprintf("i %d %s %s %d %d %d %d %d %d", clientGame.Started.Unix(), clientGame.Player1.Name, clientGame.Player2.Name, clientGame.Points, clientGame.Player1.Points, clientGame.Player2.Points, clientGame.Winner, clientGame.DoubleValue, clientGame.Variant))}, clientGame.replay...)
   567  
   568  			clientGame.replay = append(clientGame.replay, []byte(fmt.Sprintf("%d d %d 0", clientGame.Turn, clientGame.DoubleValue*2)))
   569  
   570  			var reset bool
   571  			if cmd.client.playerNumber == 1 {
   572  				clientGame.Player2.Points = clientGame.Player2.Points + clientGame.DoubleValue
   573  				if clientGame.Player2.Points >= clientGame.Points {
   574  					clientGame.Winner = 2
   575  					clientGame.Ended = time.Now()
   576  				} else {
   577  					reset = true
   578  				}
   579  			} else {
   580  				clientGame.Player1.Points = clientGame.Player2.Points + clientGame.DoubleValue
   581  				if clientGame.Player1.Points >= clientGame.Points {
   582  					clientGame.Winner = 1
   583  					clientGame.Ended = time.Now()
   584  				} else {
   585  					reset = true
   586  				}
   587  			}
   588  
   589  			var winEvent *bgammon.EventWin
   590  			if clientGame.Winner != 0 {
   591  				winEvent = &bgammon.EventWin{
   592  					Points: clientGame.DoubleValue,
   593  				}
   594  				if clientGame.Winner == 1 {
   595  					winEvent.Player = clientGame.Player1.Name
   596  				} else {
   597  					winEvent.Player = clientGame.Player2.Name
   598  				}
   599  
   600  				err := recordGameResult(clientGame, 4, clientGame.replay)
   601  				if err != nil {
   602  					log.Fatalf("failed to record game result: %s", err)
   603  				}
   604  
   605  				if !reset {
   606  					err := recordMatchResult(clientGame, matchTypeCasual)
   607  					if err != nil {
   608  						log.Fatalf("failed to record match result: %s", err)
   609  					}
   610  				}
   611  			}
   612  
   613  			if reset {
   614  				clientGame.Reset()
   615  				clientGame.replay = clientGame.replay[:0]
   616  			}
   617  
   618  			clientGame.eachClient(func(client *serverClient) {
   619  				clientGame.sendBoard(client, false)
   620  				if winEvent != nil {
   621  					client.sendEvent(winEvent)
   622  				}
   623  			})
   624  		case bgammon.CommandRoll, "r":
   625  			if clientGame == nil {
   626  				cmd.client.sendEvent(&bgammon.EventFailedRoll{
   627  					Reason: "You are not currently in a match.",
   628  				})
   629  				continue
   630  			} else if clientGame.Winner != 0 {
   631  				continue
   632  			}
   633  
   634  			opponent := clientGame.opponent(cmd.client)
   635  			if opponent == nil {
   636  				cmd.client.sendEvent(&bgammon.EventFailedRoll{
   637  					Reason: "You may not roll until your opponent rejoins the match.",
   638  				})
   639  				continue
   640  			}
   641  
   642  			if !clientGame.roll(cmd.client.playerNumber) {
   643  				cmd.client.sendEvent(&bgammon.EventFailedRoll{
   644  					Reason: "It is not your turn to roll.",
   645  				})
   646  				continue
   647  			}
   648  
   649  			clientGame.eachClient(func(client *serverClient) {
   650  				ev := &bgammon.EventRolled{
   651  					Roll1: clientGame.Roll1,
   652  					Roll2: clientGame.Roll2,
   653  					Roll3: clientGame.Roll3,
   654  				}
   655  				ev.Player = string(cmd.client.name)
   656  				if clientGame.Turn == 0 && client.playerNumber == 2 {
   657  					ev.Roll1, ev.Roll2 = ev.Roll2, ev.Roll1
   658  				}
   659  				client.sendEvent(ev)
   660  			})
   661  
   662  			// Re-roll automatically when players roll the same value when starting a game.
   663  			if clientGame.Turn == 0 && clientGame.Roll1 != 0 && clientGame.Roll2 != 0 {
   664  				reroll := func() {
   665  					clientGame.Roll1 = 0
   666  					clientGame.Roll2 = 0
   667  					if !clientGame.roll(clientGame.Turn) {
   668  						log.Fatal("failed to re-roll while starting game")
   669  					}
   670  
   671  					ev := &bgammon.EventRolled{
   672  						Roll1: clientGame.Roll1,
   673  						Roll2: clientGame.Roll2,
   674  						Roll3: clientGame.Roll3,
   675  					}
   676  					ev.Player = string(clientGame.Player1.Name)
   677  					if clientGame.Turn == 2 {
   678  						ev.Player = string(clientGame.Player2.Name)
   679  					}
   680  					clientGame.eachClient(func(client *serverClient) {
   681  						client.sendEvent(ev)
   682  					})
   683  				}
   684  
   685  				if clientGame.Roll1 > clientGame.Roll2 {
   686  					clientGame.Turn = 1
   687  					if clientGame.Variant != bgammon.VariantBackgammon {
   688  						reroll()
   689  					}
   690  				} else if clientGame.Roll2 > clientGame.Roll1 {
   691  					clientGame.Turn = 2
   692  					if clientGame.Variant != bgammon.VariantBackgammon {
   693  						reroll()
   694  					}
   695  				} else {
   696  					for {
   697  						clientGame.Roll1 = 0
   698  						clientGame.Roll2 = 0
   699  						if !clientGame.roll(1) {
   700  							log.Fatal("failed to re-roll to determine starting player")
   701  						}
   702  						if !clientGame.roll(2) {
   703  							log.Fatal("failed to re-roll to determine starting player")
   704  						}
   705  						clientGame.eachClient(func(client *serverClient) {
   706  							{
   707  								ev := &bgammon.EventRolled{
   708  									Roll1: clientGame.Roll1,
   709  								}
   710  								ev.Player = clientGame.Player1.Name
   711  								if clientGame.Turn == 0 && client.playerNumber == 2 {
   712  									ev.Roll1, ev.Roll2 = ev.Roll2, ev.Roll1
   713  								}
   714  								client.sendEvent(ev)
   715  							}
   716  							{
   717  								ev := &bgammon.EventRolled{
   718  									Roll1: clientGame.Roll1,
   719  									Roll2: clientGame.Roll2,
   720  								}
   721  								ev.Player = clientGame.Player2.Name
   722  								if clientGame.Turn == 0 && client.playerNumber == 2 {
   723  									ev.Roll1, ev.Roll2 = ev.Roll2, ev.Roll1
   724  								}
   725  								client.sendEvent(ev)
   726  							}
   727  						})
   728  						if clientGame.Roll1 > clientGame.Roll2 {
   729  							clientGame.Turn = 1
   730  							if clientGame.Variant != bgammon.VariantBackgammon {
   731  								reroll()
   732  							}
   733  							break
   734  						} else if clientGame.Roll2 > clientGame.Roll1 {
   735  							clientGame.Turn = 2
   736  							if clientGame.Variant != bgammon.VariantBackgammon {
   737  								reroll()
   738  							}
   739  							break
   740  						}
   741  					}
   742  				}
   743  			}
   744  
   745  			clientGame.NextPartialTurn(clientGame.Turn)
   746  
   747  			forcedMove := clientGame.playForcedMoves()
   748  			if forcedMove && len(clientGame.LegalMoves(false)) == 0 {
   749  				chooseRoll := clientGame.Variant == bgammon.VariantAceyDeucey && ((clientGame.Roll1 == 1 && clientGame.Roll2 == 2) || (clientGame.Roll1 == 2 && clientGame.Roll2 == 1)) && len(clientGame.Moves) == 2
   750  				if clientGame.Variant != bgammon.VariantAceyDeucey || !chooseRoll {
   751  					clientGame.recordEvent()
   752  					clientGame.nextTurn(false)
   753  					continue
   754  				}
   755  			}
   756  
   757  			clientGame.eachClient(func(client *serverClient) {
   758  				if clientGame.Turn != 0 || !client.json {
   759  					clientGame.sendBoard(client, false)
   760  				}
   761  			})
   762  		case bgammon.CommandMove, "m", "mv":
   763  			if clientGame == nil {
   764  				cmd.client.sendEvent(&bgammon.EventFailedMove{
   765  					Reason: "You are not currently in a match.",
   766  				})
   767  				continue
   768  			} else if clientGame.Winner != 0 {
   769  				clientGame.sendBoard(cmd.client, false)
   770  				continue
   771  			}
   772  
   773  			if clientGame.Turn != cmd.client.playerNumber {
   774  				cmd.client.sendEvent(&bgammon.EventFailedMove{
   775  					Reason: "It is not your turn to move.",
   776  				})
   777  				continue
   778  			}
   779  
   780  			opponent := clientGame.opponent(cmd.client)
   781  			if opponent == nil {
   782  				cmd.client.sendEvent(&bgammon.EventFailedMove{
   783  					Reason: "You may not move until your opponent rejoins the match.",
   784  				})
   785  				continue
   786  			}
   787  
   788  			sendUsage := func() {
   789  				cmd.client.sendEvent(&bgammon.EventFailedMove{
   790  					Reason: "Specify one or more moves in the form FROM/TO. For example: 8/4 6/4",
   791  				})
   792  			}
   793  
   794  			if len(params) == 0 {
   795  				sendUsage()
   796  				continue
   797  			}
   798  
   799  			var moves [][]int8
   800  			for i := range params {
   801  				split := bytes.Split(params[i], []byte("/"))
   802  				if len(split) != 2 {
   803  					sendUsage()
   804  					continue COMMANDS
   805  				}
   806  				from := bgammon.ParseSpace(string(split[0]))
   807  				if from == -1 {
   808  					sendUsage()
   809  					continue COMMANDS
   810  				}
   811  				to := bgammon.ParseSpace(string(split[1]))
   812  				if to == -1 {
   813  					sendUsage()
   814  					continue COMMANDS
   815  				}
   816  				if !bgammon.ValidSpace(from) || !bgammon.ValidSpace(to) {
   817  					cmd.client.sendEvent(&bgammon.EventFailedMove{
   818  						From:   from,
   819  						To:     to,
   820  						Reason: "Illegal move.",
   821  					})
   822  					continue COMMANDS
   823  				}
   824  
   825  				from, to = bgammon.FlipSpace(from, cmd.client.playerNumber, clientGame.Variant), bgammon.FlipSpace(to, cmd.client.playerNumber, clientGame.Variant)
   826  				moves = append(moves, []int8{from, to})
   827  			}
   828  
   829  			ok, expandedMoves := clientGame.AddMoves(moves, false)
   830  			if !ok {
   831  				cmd.client.sendEvent(&bgammon.EventFailedMove{
   832  					From:   0,
   833  					To:     0,
   834  					Reason: "Illegal move.",
   835  				})
   836  				continue
   837  			}
   838  
   839  			clientGame.eachClient(func(client *serverClient) {
   840  				ev := &bgammon.EventMoved{
   841  					Moves: bgammon.FlipMoves(expandedMoves, client.playerNumber, clientGame.Variant),
   842  				}
   843  				ev.Player = string(cmd.client.name)
   844  				client.sendEvent(ev)
   845  
   846  				clientGame.sendBoard(client, false)
   847  			})
   848  
   849  			clientGame.handleWin()
   850  		case bgammon.CommandReset:
   851  			if clientGame == nil {
   852  				cmd.client.sendNotice("You are not currently in a match.")
   853  				continue
   854  			} else if clientGame.Winner != 0 {
   855  				continue
   856  			}
   857  
   858  			if clientGame.Turn != cmd.client.playerNumber {
   859  				cmd.client.sendNotice("It is not your turn.")
   860  				continue
   861  			}
   862  
   863  			if len(clientGame.Moves) == 0 {
   864  				continue
   865  			}
   866  
   867  			l := len(clientGame.Moves)
   868  			undoMoves := make([][]int8, l)
   869  			for i, move := range clientGame.Moves {
   870  				undoMoves[l-1-i] = []int8{move[1], move[0]}
   871  			}
   872  			ok, _ := clientGame.AddMoves(undoMoves, false)
   873  			if !ok {
   874  				cmd.client.sendNotice("Failed to undo move: invalid move.")
   875  			} else {
   876  				clientGame.eachClient(func(client *serverClient) {
   877  					ev := &bgammon.EventMoved{
   878  						Moves: bgammon.FlipMoves(undoMoves, client.playerNumber, clientGame.Variant),
   879  					}
   880  					ev.Player = string(cmd.client.name)
   881  
   882  					client.sendEvent(ev)
   883  					clientGame.sendBoard(client, false)
   884  				})
   885  			}
   886  		case bgammon.CommandOk, "k":
   887  			if clientGame == nil {
   888  				cmd.client.sendNotice("You are not currently in a match.")
   889  				continue
   890  			} else if clientGame.Winner != 0 {
   891  				continue
   892  			}
   893  
   894  			opponent := clientGame.opponent(cmd.client)
   895  			if opponent == nil {
   896  				cmd.client.sendNotice("You must wait until your opponent rejoins the match before continuing the game.")
   897  				continue
   898  			}
   899  
   900  			if clientGame.DoubleOffered {
   901  				if clientGame.Turn != cmd.client.playerNumber {
   902  					opponent := clientGame.opponent(cmd.client)
   903  					if opponent == nil {
   904  						cmd.client.sendNotice("You may not accept the double until your opponent rejoins the match.")
   905  						continue
   906  					}
   907  
   908  					clientGame.DoubleOffered = false
   909  					clientGame.DoubleValue = clientGame.DoubleValue * 2
   910  					clientGame.DoublePlayer = cmd.client.playerNumber
   911  					clientGame.NextPartialTurn(opponent.playerNumber)
   912  
   913  					cmd.client.sendNotice("Accepted double.")
   914  					opponent.sendNotice(fmt.Sprintf("%s accepted double.", cmd.client.name))
   915  
   916  					clientGame.replay = append(clientGame.replay, []byte(fmt.Sprintf("%d d %d 1", clientGame.Turn, clientGame.DoubleValue)))
   917  					clientGame.eachClient(func(client *serverClient) {
   918  						clientGame.sendBoard(client, false)
   919  					})
   920  				} else {
   921  					cmd.client.sendNotice("Waiting for response from opponent.")
   922  				}
   923  				continue
   924  			} else if clientGame.Turn != cmd.client.playerNumber {
   925  				cmd.client.sendNotice("It is not your turn.")
   926  				continue
   927  			}
   928  
   929  			if clientGame.Roll1 == 0 || clientGame.Roll2 == 0 {
   930  				cmd.client.sendNotice("You must roll first.")
   931  				continue
   932  			}
   933  
   934  			legalMoves := clientGame.LegalMoves(false)
   935  			if len(legalMoves) != 0 {
   936  				available := bgammon.FlipMoves(legalMoves, cmd.client.playerNumber, clientGame.Variant)
   937  				bgammon.SortMoves(available)
   938  				cmd.client.sendEvent(&bgammon.EventFailedOk{
   939  					Reason: fmt.Sprintf("The following legal moves are available: %s", bgammon.FormatMoves(available)),
   940  				})
   941  				continue
   942  			}
   943  
   944  			if clientGame.Variant == bgammon.VariantAceyDeucey && ((clientGame.Roll1 == 1 && clientGame.Roll2 == 2) || (clientGame.Roll1 == 2 && clientGame.Roll2 == 1)) && len(clientGame.Moves) == 2 {
   945  				var doubles int
   946  				if len(params) > 0 {
   947  					doubles, _ = strconv.Atoi(string(params[0]))
   948  				}
   949  				if doubles < 1 || doubles > 6 {
   950  					cmd.client.sendEvent(&bgammon.EventFailedOk{
   951  						Reason: "Choose which doubles you want for your acey-deucey.",
   952  					})
   953  					continue
   954  				}
   955  
   956  				clientGame.recordEvent()
   957  				clientGame.nextTurn(true)
   958  				clientGame.Roll1, clientGame.Roll2 = int8(doubles), int8(doubles)
   959  				clientGame.Reroll = true
   960  
   961  				clientGame.eachClient(func(client *serverClient) {
   962  					ev := &bgammon.EventRolled{
   963  						Roll1:    clientGame.Roll1,
   964  						Roll2:    clientGame.Roll2,
   965  						Selected: true,
   966  					}
   967  					ev.Player = string(cmd.client.name)
   968  					client.sendEvent(ev)
   969  					clientGame.sendBoard(client, false)
   970  				})
   971  			} else if clientGame.Variant == bgammon.VariantAceyDeucey && clientGame.Reroll {
   972  				clientGame.recordEvent()
   973  				clientGame.nextTurn(true)
   974  				clientGame.Roll1, clientGame.Roll2 = 0, 0
   975  				if !clientGame.roll(cmd.client.playerNumber) {
   976  					cmd.client.Terminate("Server error")
   977  					opponent.Terminate("Server error")
   978  					continue
   979  				}
   980  				clientGame.Reroll = false
   981  
   982  				clientGame.eachClient(func(client *serverClient) {
   983  					ev := &bgammon.EventRolled{
   984  						Roll1: clientGame.Roll1,
   985  						Roll2: clientGame.Roll2,
   986  					}
   987  					ev.Player = string(cmd.client.name)
   988  					client.sendEvent(ev)
   989  					clientGame.sendBoard(client, false)
   990  				})
   991  			} else {
   992  				clientGame.recordEvent()
   993  				clientGame.nextTurn(false)
   994  			}
   995  		case bgammon.CommandRematch, "rm":
   996  			if clientGame == nil {
   997  				cmd.client.sendNotice("You are not currently in a match.")
   998  				continue
   999  			} else if clientGame.Winner == 0 {
  1000  				cmd.client.sendNotice("The match you are in is still in progress.")
  1001  				continue
  1002  			} else if clientGame.rematch == cmd.client.playerNumber {
  1003  				cmd.client.sendNotice("You have already requested a rematch.")
  1004  				continue
  1005  			} else if clientGame.client1 == nil || clientGame.client2 == nil {
  1006  				cmd.client.sendNotice("Your opponent left the match.")
  1007  				continue
  1008  			} else if clientGame.rematch != 0 && clientGame.rematch != cmd.client.playerNumber {
  1009  				s.gamesLock.Lock()
  1010  
  1011  				newGame := newServerGame(<-s.newGameIDs, clientGame.Variant)
  1012  				newGame.name = clientGame.name
  1013  				newGame.Points = clientGame.Points
  1014  				newGame.password = clientGame.password
  1015  				newGame.client1 = clientGame.client1
  1016  				newGame.client2 = clientGame.client2
  1017  				newGame.spectators = make([]*serverClient, len(clientGame.spectators))
  1018  				copy(newGame.spectators, clientGame.spectators)
  1019  				newGame.Player1.Name = clientGame.Player1.Name
  1020  				newGame.Player2.Name = clientGame.Player2.Name
  1021  				newGame.Player1.Points = clientGame.Player1.Points
  1022  				newGame.Player2.Points = clientGame.Player2.Points
  1023  				newGame.allowed1 = clientGame.allowed1
  1024  				newGame.allowed2 = clientGame.allowed2
  1025  				s.games = append(s.games, newGame)
  1026  
  1027  				clientGame.client1 = nil
  1028  				clientGame.client2 = nil
  1029  				clientGame.spectators = nil
  1030  
  1031  				s.gamesLock.Unlock()
  1032  
  1033  				{
  1034  					ev1 := &bgammon.EventJoined{
  1035  						GameID:       newGame.id,
  1036  						PlayerNumber: 1,
  1037  					}
  1038  					ev1.Player = newGame.Player1.Name
  1039  					ev2 := &bgammon.EventJoined{
  1040  						GameID:       newGame.id,
  1041  						PlayerNumber: 2,
  1042  					}
  1043  					ev2.Player = newGame.Player2.Name
  1044  					newGame.client1.sendEvent(ev1)
  1045  					newGame.client1.sendEvent(ev2)
  1046  					newGame.sendBoard(newGame.client1, false)
  1047  				}
  1048  
  1049  				{
  1050  					ev1 := &bgammon.EventJoined{
  1051  						GameID:       newGame.id,
  1052  						PlayerNumber: 1,
  1053  					}
  1054  					ev1.Player = newGame.Player2.Name
  1055  					ev2 := &bgammon.EventJoined{
  1056  						GameID:       newGame.id,
  1057  						PlayerNumber: 2,
  1058  					}
  1059  					ev2.Player = newGame.Player1.Name
  1060  					newGame.client2.sendEvent(ev1)
  1061  					newGame.client2.sendEvent(ev2)
  1062  					newGame.sendBoard(newGame.client2, false)
  1063  				}
  1064  
  1065  				for _, spectator := range newGame.spectators {
  1066  					newGame.sendBoard(spectator, false)
  1067  				}
  1068  			} else {
  1069  				clientGame.rematch = cmd.client.playerNumber
  1070  
  1071  				clientGame.opponent(cmd.client).sendNotice("Your opponent would like to play again. Type /rematch to accept.")
  1072  				cmd.client.sendNotice("Rematch offer sent.")
  1073  				continue
  1074  			}
  1075  		case bgammon.CommandBoard, "b":
  1076  			if clientGame == nil {
  1077  				cmd.client.sendNotice("You are not currently in a match.")
  1078  				continue
  1079  			}
  1080  
  1081  			clientGame.sendBoard(cmd.client, false)
  1082  		case bgammon.CommandPassword:
  1083  			if cmd.client.account == nil {
  1084  				cmd.client.sendNotice("Failed to change password: you are logged in as a guest.")
  1085  				continue
  1086  			} else if len(params) < 2 {
  1087  				cmd.client.sendNotice("Please specify your old and new passwords as follows: password <old> <new>")
  1088  				continue
  1089  			}
  1090  
  1091  			a, err := loginAccount(s.passwordSalt, cmd.client.name, params[0])
  1092  			if err != nil || a == nil || a.id == 0 {
  1093  				cmd.client.sendNotice("Failed to change password: incorrect existing password.")
  1094  				continue
  1095  			}
  1096  
  1097  			err = setAccountPassword(s.passwordSalt, a.id, string(bytes.Join(params[1:], []byte("_"))))
  1098  			if err != nil {
  1099  				cmd.client.sendNotice("Failed to change password.")
  1100  				continue
  1101  			}
  1102  			cmd.client.sendNotice("Password changed successfully.")
  1103  		case bgammon.CommandSet:
  1104  			if len(params) < 2 {
  1105  				cmd.client.sendNotice("Please specify the setting name and value as follows: set <name> <value>")
  1106  				continue
  1107  			}
  1108  
  1109  			name := string(bytes.ToLower(params[0]))
  1110  			settings := []string{"autoplay", "highlight", "pips", "moves", "flip", "advanced", "speed"}
  1111  			var found bool
  1112  			for i := range settings {
  1113  				if name == settings[i] {
  1114  					found = true
  1115  					break
  1116  				}
  1117  			}
  1118  			if !found {
  1119  				cmd.client.sendNotice("Please specify the setting name and value as follows: set <name> <value>")
  1120  				continue
  1121  			}
  1122  
  1123  			value, err := strconv.Atoi(string(params[1]))
  1124  			if err != nil || value < 0 || (name == "speed" && value > 3) || (name != "speed" && value > 1) {
  1125  				cmd.client.sendNotice("Invalid setting value provided.")
  1126  				continue
  1127  			}
  1128  
  1129  			if name == "autoplay" {
  1130  				cmd.client.autoplay = value == 1
  1131  			}
  1132  
  1133  			if cmd.client.account == nil {
  1134  				continue
  1135  			}
  1136  			_ = setAccountSetting(cmd.client.account.id, name, value)
  1137  		case bgammon.CommandReplay:
  1138  			var (
  1139  				id     int
  1140  				replay []byte
  1141  				err    error
  1142  			)
  1143  			if len(params) == 0 {
  1144  				if clientGame == nil || clientGame.Winner == 0 {
  1145  					cmd.client.sendNotice("Please specify the game as follows: replay <id>")
  1146  					continue
  1147  				}
  1148  				id = -1
  1149  				replay = bytes.Join(clientGame.replay, []byte("\n"))
  1150  			} else {
  1151  				id, err = strconv.Atoi(string(params[0]))
  1152  				if err != nil || id < 0 {
  1153  					cmd.client.sendNotice("Invalid replay ID provided.")
  1154  					continue
  1155  				}
  1156  				replay, err = replayByID(id)
  1157  				if err != nil {
  1158  					cmd.client.sendNotice("Invalid replay ID provided.")
  1159  					continue
  1160  				}
  1161  			}
  1162  			if len(replay) == 0 {
  1163  				cmd.client.sendNotice("No replay was recorded for that game.")
  1164  				continue
  1165  			}
  1166  			cmd.client.sendEvent(&bgammon.EventReplay{
  1167  				ID:      id,
  1168  				Content: replay,
  1169  			})
  1170  		case bgammon.CommandHistory:
  1171  			if len(params) == 0 {
  1172  				cmd.client.sendNotice("Please specify the player as follows: history <username>")
  1173  				continue
  1174  			}
  1175  			const historyPageSize = 50
  1176  
  1177  			page := 1
  1178  			if len(params) > 1 {
  1179  				p, err := strconv.Atoi(string(params[1]))
  1180  				if err == nil && p >= 1 {
  1181  					page = p
  1182  				}
  1183  			}
  1184  
  1185  			matches, err := matchHistory(string(params[0]))
  1186  			if err != nil {
  1187  				cmd.client.sendNotice("Invalid replay ID provided.")
  1188  				continue
  1189  			}
  1190  
  1191  			pages := (len(matches) / historyPageSize)
  1192  			if pages == 0 {
  1193  				pages = 1
  1194  			}
  1195  
  1196  			ev := &bgammon.EventHistory{
  1197  				Page:  page,
  1198  				Pages: pages,
  1199  			}
  1200  			if len(matches) > 0 && page <= pages {
  1201  				max := page * historyPageSize
  1202  				if max > len(matches) {
  1203  					max = len(matches)
  1204  				}
  1205  				ev.Matches = matches[(page-1)*historyPageSize : max]
  1206  			}
  1207  
  1208  			ev.Player = string(params[0])
  1209  			a, err := accountByUsername(string(params[0]))
  1210  			if err == nil && a != nil {
  1211  				ev.CasualBackgammonSingle = a.casual.backgammonSingle / 100
  1212  				ev.CasualBackgammonMulti = a.casual.backgammonMulti / 100
  1213  				ev.CasualAceyDeuceySingle = a.casual.aceySingle / 100
  1214  				ev.CasualAceyDeuceyMulti = a.casual.aceyMulti / 100
  1215  				ev.CasualTabulaSingle = a.casual.tabulaSingle / 100
  1216  				ev.CasualTabulaMulti = a.casual.tabulaMulti / 100
  1217  			}
  1218  			cmd.client.sendEvent(ev)
  1219  		case bgammon.CommandDisconnect:
  1220  			if clientGame != nil {
  1221  				clientGame.removeClient(cmd.client)
  1222  			}
  1223  			cmd.client.Terminate("Client disconnected")
  1224  		case bgammon.CommandPong:
  1225  			// Do nothing.
  1226  		case "endgame":
  1227  			if !allowDebugCommands {
  1228  				cmd.client.sendNotice("You are not allowed to use that command.")
  1229  				continue
  1230  			}
  1231  
  1232  			if clientGame == nil {
  1233  				cmd.client.sendNotice("You are not currently in a match.")
  1234  				continue
  1235  			}
  1236  
  1237  			clientGame.Turn = 1
  1238  			clientGame.Roll1 = 6
  1239  			clientGame.Roll2 = 1
  1240  			clientGame.Roll3 = 1
  1241  			clientGame.Variant = 2
  1242  			clientGame.Player1.Entered = true
  1243  			clientGame.Player2.Entered = true
  1244  			clientGame.Board = []int8{0, 0, 0, 0, 0, -3, 0, 0, -3, -2, -2, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 9, 3, 1, -5, 1, 1, 0}
  1245  
  1246  			log.Println(clientGame.Board[0:28])
  1247  
  1248  			clientGame.eachClient(func(client *serverClient) {
  1249  				clientGame.sendBoard(client, false)
  1250  			})
  1251  		default:
  1252  			log.Printf("Received unknown command from client %s: %s", cmd.client.label(), cmd.command)
  1253  			cmd.client.sendNotice(fmt.Sprintf("Unknown command: %s", cmd.command))
  1254  		}
  1255  	}
  1256  }
  1257  

View as plain text