From 28e2b6ccfdc38531871b0fb55d9fc7465f9a24ad Mon Sep 17 00:00:00 2001 From: Trevor Slocum Date: Thu, 27 Jun 2024 18:55:40 -0700 Subject: [PATCH] Allow players to resign games Resolves #16. --- PROTOCOL.md | 2 +- REPLAY.md | 14 +++++++- gamestate.go | 5 +-- pkg/server/locales/bgammon.pot | 10 ++++-- pkg/server/server_command.go | 61 +++++++++++++++++++++------------- 5 files changed, 62 insertions(+), 30 deletions(-) diff --git a/PROTOCOL.md b/PROTOCOL.md index 03299e7..591d30a 100644 --- a/PROTOCOL.md +++ b/PROTOCOL.md @@ -81,7 +81,7 @@ formatted responses are more easily parsed by computers. - Aliases: `d` - `resign` - - Decline double offer and resign game. + - Resign game. Resigning when a double is offered will decline the offer. - `roll` - Roll dice. diff --git a/REPLAY.md b/REPLAY.md index 4ac9ae1..b675a83 100644 --- a/REPLAY.md +++ b/REPLAY.md @@ -25,7 +25,13 @@ The index is always eight digits in length with leading zeroes. The first line of the game is the metadata. The timestamp specifies when the game started. -`i ` +`i ` + +The variant is specified as follows: + +- **0** Backgammon +- **1** Acey-deucey +- **2** Tabula #### Events @@ -55,6 +61,12 @@ When no moves are possible, only the roll is specified. `1 r 4-4` +##### Terminate + +When a player resigns voluntarily or abandons an unfinished game, the player terminating the game early is indicated. + +`1 t` + ## Example .match file ``` diff --git a/gamestate.go b/gamestate.go index 5dd2f96..45fb983 100644 --- a/gamestate.go +++ b/gamestate.go @@ -132,8 +132,9 @@ func (g *GameState) MayOK() bool { return g.Turn != 0 && g.Turn == g.PlayerNumber && g.Roll1 != 0 && len(g.Available) == 0 } -// MayResign returns whether the player may send the 'resign' command. -func (g *GameState) MayResign() bool { +// MayDecline returns whether the player may send the 'resign' command to +// decline a double offer. +func (g *GameState) MayDecline() bool { if g.Spectating || g.Winner != 0 { return false } diff --git a/pkg/server/locales/bgammon.pot b/pkg/server/locales/bgammon.pot index eda7d02..12aa8b0 100644 --- a/pkg/server/locales/bgammon.pot +++ b/pkg/server/locales/bgammon.pot @@ -10,6 +10,9 @@ msgstr "" msgid "%s accepted double." msgstr "" +msgid "%s resigned." +msgstr "" + msgid "%s declined double offer." msgstr "" @@ -34,7 +37,7 @@ msgstr "" msgid "Created match: %s" msgstr "" -msgid "Declined double offer" +msgid "Declined double offer." msgstr "" msgid "Double offered to opponent (%d points)." @@ -115,6 +118,9 @@ msgstr "" msgid "Rematch offer sent." msgstr "" +msgid "Resigned." +msgstr "" + msgid "Server error" msgstr "" @@ -160,7 +166,7 @@ msgstr "" msgid "You may not move until your opponent rejoins the match." msgstr "" -msgid "You may not resign at this time." +msgid "You may not resign until it is your turn." msgstr "" msgid "You may not resign until your opponent rejoins the match." diff --git a/pkg/server/server_command.go b/pkg/server/server_command.go index 04193cf..830a51d 100644 --- a/pkg/server/server_command.go +++ b/pkg/server/server_command.go @@ -559,41 +559,45 @@ COMMANDS: continue } - gameState := &bgammon.GameState{ - Game: clientGame.Game, - PlayerNumber: cmd.client.playerNumber, - Available: clientGame.LegalMoves(false), - } - if !gameState.MayResign() { - cmd.client.sendNotice(gotext.GetD(cmd.client.language, "You may not resign at this time.")) - continue - } - opponent := clientGame.opponent(cmd.client) if opponent == nil { cmd.client.sendNotice(gotext.GetD(cmd.client.language, "You may not resign until your opponent rejoins the match.")) continue } - clientGame.NextPartialTurn(opponent.playerNumber) + gameState := &bgammon.GameState{ + Game: clientGame.Game, + PlayerNumber: cmd.client.playerNumber, + Available: clientGame.LegalMoves(false), + } + var winner int8 + addReplayHeader := func() { + 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, winner, clientGame.DoubleValue, clientGame.Variant))}, clientGame.replay...) + } + if gameState.MayDecline() { + winner = opponent.playerNumber + clientGame.NextPartialTurn(opponent.playerNumber) - cmd.client.sendNotice(gotext.GetD(cmd.client.language, "Declined double offer")) - clientGame.opponent(cmd.client).sendNotice(fmt.Sprintf(gotext.GetD(clientGame.opponent(cmd.client).language, "%s declined double offer."), cmd.client.name)) + cmd.client.sendNotice(gotext.GetD(cmd.client.language, "Declined double offer.")) + clientGame.opponent(cmd.client).sendNotice(fmt.Sprintf(gotext.GetD(clientGame.opponent(cmd.client).language, "%s declined double offer."), cmd.client.name)) - 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...) + addReplayHeader() + clientGame.replay = append(clientGame.replay, []byte(fmt.Sprintf("%d d %d 0", clientGame.Turn, clientGame.DoubleValue*2))) + } else if gameState.Turn == 0 || gameState.Turn != cmd.client.playerNumber { + cmd.client.sendNotice(gotext.GetD(cmd.client.language, "You may not resign until it is your turn.")) + continue + } else { + winner = cmd.client.playerNumber + clientGame.NextPartialTurn(cmd.client.playerNumber) - clientGame.replay = append(clientGame.replay, []byte(fmt.Sprintf("%d d %d 0", clientGame.Turn, clientGame.DoubleValue*2))) + cmd.client.sendNotice(gotext.GetD(cmd.client.language, "Resigned.")) + clientGame.opponent(cmd.client).sendNotice(fmt.Sprintf(gotext.GetD(clientGame.opponent(cmd.client).language, "%s resigned."), cmd.client.name)) + + clientGame.replay = append(clientGame.replay, []byte(fmt.Sprintf("%d t", cmd.client.playerNumber))) + } var reset bool - if cmd.client.playerNumber == 1 { - clientGame.Player2.Points = clientGame.Player2.Points + clientGame.DoubleValue - if clientGame.Player2.Points >= clientGame.Points { - clientGame.Winner = 2 - clientGame.Ended = time.Now() - } else { - reset = true - } - } else { + if winner == 1 { clientGame.Player1.Points = clientGame.Player1.Points + clientGame.DoubleValue if clientGame.Player1.Points >= clientGame.Points { clientGame.Winner = 1 @@ -601,7 +605,16 @@ COMMANDS: } else { reset = true } + } else { + clientGame.Player2.Points = clientGame.Player2.Points + clientGame.DoubleValue + if clientGame.Player2.Points >= clientGame.Points { + clientGame.Winner = 2 + clientGame.Ended = time.Now() + } else { + reset = true + } } + addReplayHeader() var winEvent *bgammon.EventWin if clientGame.Winner != 0 {