diff --git a/game.go b/game.go index 9798bd4..3340424 100644 --- a/game.go +++ b/game.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "log" + "math" "strconv" "time" @@ -47,6 +48,10 @@ type Game struct { Reroll bool // Used in acey-deucey. + partialTurn int8 + partialTime time.Time + partialHandled bool + boardStates [][]int8 // One board state for each move to allow undoing a move. enteredStates [][2]bool // Player 1 entered state and Player 2 entered state for each move. @@ -96,6 +101,10 @@ func (g *Game) Copy(shallow bool) *Game { DoubleOffered: g.DoubleOffered, Reroll: g.Reroll, + + partialTurn: g.partialTurn, + partialTime: g.partialTime, + partialHandled: g.partialHandled, } copy(newGame.Board, g.Board) copy(newGame.Moves, g.Moves) @@ -108,6 +117,50 @@ func (g *Game) Copy(shallow bool) *Game { return newGame } +func (g *Game) PartialTurn() int8 { + return g.partialTurn +} + +func (g *Game) PartialTime() int { + var delta time.Duration + if g.partialTime.IsZero() { + delta = time.Since(g.Started) + } else { + delta = time.Since(g.partialTime) + } + if delta <= 30*time.Second { + return 0 + } + return int(math.Floor(delta.Seconds())) +} + +func (g *Game) PartialHandled() bool { + return g.partialHandled +} + +func (g *Game) SetPartialHandled(handled bool) { + g.partialHandled = handled +} + +func (g *Game) NextPartialTurn(player int8) { + if g.Started.IsZero() || g.Winner != 0 { + return + } + + delta := g.PartialTime() + if delta > 0 { + switch g.partialTurn { + case 1: + g.Player1.Inactive += delta + case 2: + g.Player2.Inactive += delta + } + } + + g.partialTurn = player + g.partialTime = time.Now() +} + func (g *Game) NextTurn(reroll bool) { if g.Winner != 0 { return @@ -121,6 +174,8 @@ func (g *Game) NextTurn(reroll bool) { g.Turn = nextTurn } + g.NextPartialTurn(g.Turn) + g.Roll1, g.Roll2, g.Roll3 = 0, 0, 0 g.Moves = g.Moves[:0] g.boardStates = g.boardStates[:0] @@ -128,6 +183,8 @@ func (g *Game) NextTurn(reroll bool) { } func (g *Game) Reset() { + g.Player1.Inactive = 0 + g.Player2.Inactive = 0 if g.Variant != VariantBackgammon { g.Player1.Entered = false g.Player2.Entered = false @@ -144,6 +201,8 @@ func (g *Game) Reset() { g.Reroll = false g.boardStates = nil g.enteredStates = nil + g.partialTurn = 0 + g.partialTime = time.Time{} } func (g *Game) turnPlayer() Player { diff --git a/pkg/server/game.go b/pkg/server/game.go index 036c348..56d9950 100644 --- a/pkg/server/game.go +++ b/pkg/server/game.go @@ -23,6 +23,7 @@ type serverGame struct { allowed2 []byte account1 int account2 int + inactive int8 forefeit int8 rematch int8 rejoin1 bool @@ -130,6 +131,7 @@ func (g *serverGame) playForcedMoves() bool { return true } } + g.NextPartialTurn(g.Turn) return true } diff --git a/pkg/server/server.go b/pkg/server/server.go index aa72987..95c7735 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -23,6 +23,8 @@ import ( const clientTimeout = 40 * time.Second +const inactiveLimit = 600 // 10 minutes. + var allowDebugCommands bool var ( @@ -134,7 +136,7 @@ func NewServer(tz string, dataSource string, mailServer string, passwordSalt str go s.handleNewGameIDs() go s.handleNewClientIDs() go s.handleCommands() - go s.handleTerminatedGames() + go s.handleGames() return s } @@ -242,20 +244,62 @@ func (s *server) removeClient(c *serverClient) { } } -func (s *server) handleTerminatedGames() { +func (s *server) handleGames() { t := time.NewTicker(time.Minute) for range t.C { s.gamesLock.Lock() i := 0 for _, g := range s.games { + if !g.PartialHandled() && g.Player1.Rating != 0 && g.Player2.Rating != 0 { + partialTurn := g.PartialTurn() + if partialTurn != 0 { + total := g.PartialTime() + switch partialTurn { + case 1: + total += g.Player1.Inactive + case 2: + total += g.Player2.Inactive + } + if total >= inactiveLimit { + g.inactive = partialTurn + g.SetPartialHandled(true) + if !g.terminated() { + var player *serverClient + var opponent *serverClient + switch partialTurn { + case 1: + player = g.client1 + opponent = g.client2 + case 2: + player = g.client2 + opponent = g.client1 + } + if player != nil { + player.sendNotice("You have been inactive for more than ten minutes. If your opponent leaves the match they will receive a win.") + } + if opponent != nil { + opponent.sendNotice("Your opponent has been inactive for more than ten minutes. You may continue playing or leave the match at any time and receive a win.") + } + } + } + } + } + if !g.terminated() { s.games[i] = g i++ - } else if g.forefeit != 0 && g.Winner == 0 { - g.Winner = 1 - if g.forefeit == 1 { - g.Winner = 2 + } else if g.Winner == 0 && (g.inactive != 0 || g.forefeit != 0) { + if g.inactive != 0 { + g.Winner = 1 + if g.inactive == 1 { + g.Winner = 2 + } + } else { + g.Winner = 1 + if g.forefeit == 1 { + g.Winner = 2 + } } err := recordMatchResult(g, matchTypeCasual) if err != nil { diff --git a/pkg/server/server_command.go b/pkg/server/server_command.go index 9cbc31b..b1933ef 100644 --- a/pkg/server/server_command.go +++ b/pkg/server/server_command.go @@ -519,6 +519,7 @@ COMMANDS: } clientGame.DoubleOffered = true + clientGame.NextPartialTurn(opponent.playerNumber) cmd.client.sendNotice(fmt.Sprintf("Double offered to opponent (%d points).", clientGame.DoubleValue*2)) clientGame.opponent(cmd.client).sendNotice(fmt.Sprintf("%s offers a double (%d points).", cmd.client.name, clientGame.DoubleValue*2)) @@ -552,6 +553,8 @@ COMMANDS: continue } + clientGame.NextPartialTurn(opponent.playerNumber) + cmd.client.sendNotice("Declined double offer") clientGame.opponent(cmd.client).sendNotice(fmt.Sprintf("%s declined double offer.", cmd.client.name)) @@ -734,6 +737,8 @@ COMMANDS: } } + clientGame.NextPartialTurn(clientGame.Turn) + forcedMove := clientGame.playForcedMoves() if forcedMove && len(clientGame.LegalMoves(false)) == 0 { chooseRoll := clientGame.Variant == bgammon.VariantAceyDeucey && ((clientGame.Roll1 == 1 && clientGame.Roll2 == 2) || (clientGame.Roll1 == 2 && clientGame.Roll2 == 1)) && len(clientGame.Moves) == 2 @@ -898,6 +903,7 @@ COMMANDS: clientGame.DoubleOffered = false clientGame.DoubleValue = clientGame.DoubleValue * 2 clientGame.DoublePlayer = cmd.client.playerNumber + clientGame.NextPartialTurn(opponent.playerNumber) cmd.client.sendNotice("Accepted double.") opponent.sendNotice(fmt.Sprintf("%s accepted double.", cmd.client.name)) diff --git a/player.go b/player.go index 25e5395..37bdf56 100644 --- a/player.go +++ b/player.go @@ -1,11 +1,12 @@ package bgammon type Player struct { - Number int8 // 1 black, 2 white - Name string - Rating int - Points int8 - Entered bool // Whether all checkers have entered the board. (Acey-deucey) + Number int8 // 1 black, 2 white + Name string + Rating int + Points int8 + Entered bool // Whether all checkers have entered the board. (Acey-deucey) + Inactive int // Inactive time. (Seconds) } func NewPlayer(number int8) Player {