From 5191ecb83de9d6af528fc4bf8f0342c3e88740d5 Mon Sep 17 00:00:00 2001 From: Trevor Slocum Date: Fri, 15 Dec 2023 19:22:59 -0800 Subject: [PATCH] Record replays --- REPLAY.md | 27 ++++++++--------- pkg/server/database.go | 14 ++++++--- pkg/server/database_disabled.go | 2 +- pkg/server/game.go | 6 ++++ pkg/server/server.go | 51 ++++++++++++++++++++++++++++----- 5 files changed, 73 insertions(+), 27 deletions(-) diff --git a/REPLAY.md b/REPLAY.md index 06b3902..2023d91 100644 --- a/REPLAY.md +++ b/REPLAY.md @@ -15,40 +15,37 @@ The index table consists of one or more lines in the following format: `bgammon-replay ` Games are in chronological order. -The index specifies the index of the first byte of the first line of a game. -The index is always eight digits with leading zeroes. +The index specifies the position of the first byte of the first line of a game. +The index is always eight digits in length with leading zeroes. ### Game #### Metadata -The first line of the file specifies the metadata. +The first line of the game is the metadata. `i ` -#### Index table - -The index table consists of one or more lines in the following format: - -`g ` - -The index specifies the index of the first byte of each line corresponding to each turn in the game. -The index is always eight digits with leading zeroes. - #### Events +The remaining lines of the game are the events. + +Events are in the following format: + +` ` + ##### Double Accepted: -`d 2 1` +`1 d 2 1` Declined: -`d 2 0` +`1 d 2 0` ##### Roll and move Moves are always specified from player 1's perspective. -`r 5-3 13/8 24/21` +`1 r 5-3 13/8 24/21` diff --git a/pkg/server/database.go b/pkg/server/database.go index 67ed689..fdbd525 100644 --- a/pkg/server/database.go +++ b/pkg/server/database.go @@ -48,7 +48,8 @@ CREATE TABLE game ( account2 integer NOT NULL, points integer NOT NULL, winner integer NOT NULL, - wintype integer NOT NULL + wintype integer NOT NULL, + replay TEXT NOT NULL DEFAULT '' ); ` @@ -387,11 +388,16 @@ func setAccountSetting(id int, name string, value int) error { return err } -func recordGameResult(g *bgammon.Game, winType int, account1 int, account2 int) error { - if db == nil || g.Started.IsZero() || g.Ended.IsZero() || g.Winner == 0 { +func recordGameResult(g *bgammon.Game, winType int, account1 int, account2 int, replay [][]byte) error { + if db == nil || g.Started.IsZero() || g.Winner == 0 { return nil } + ended := g.Ended + if ended.IsZero() { + ended = time.Now() + } + tx, err := begin() if err != nil { return err @@ -402,7 +408,7 @@ func recordGameResult(g *bgammon.Game, winType int, account1 int, account2 int) if g.Acey { acey = 1 } - _, err = tx.Exec(context.Background(), "INSERT INTO game (acey, started, ended, player1, account1, player2, account2, points, winner, wintype) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)", acey, g.Started.Unix(), g.Ended.Unix(), g.Player1.Name, account1, g.Player2.Name, account2, g.Points, g.Winner, winType) + _, err = tx.Exec(context.Background(), "INSERT INTO game (acey, started, ended, player1, account1, player2, account2, points, winner, wintype, replay) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)", acey, g.Started.Unix(), ended.Unix(), g.Player1.Name, account1, g.Player2.Name, account2, g.Points, g.Winner, winType, bytes.Join(replay, []byte("\n"))) return err } diff --git a/pkg/server/database_disabled.go b/pkg/server/database_disabled.go index 104baa0..8fde718 100644 --- a/pkg/server/database_disabled.go +++ b/pkg/server/database_disabled.go @@ -43,7 +43,7 @@ func setAccountSetting(id int, name string, value int) error { return nil } -func recordGameResult(g *bgammon.Game, winType int, account1 int, account2 int) error { +func recordGameResult(g *bgammon.Game, winType int, account1 int, account2 int, replay [][]byte) error { return nil } diff --git a/pkg/server/game.go b/pkg/server/game.go index f58a906..d628f59 100644 --- a/pkg/server/game.go +++ b/pkg/server/game.go @@ -8,6 +8,11 @@ import ( "code.rocket9labs.com/tslocum/bgammon" ) +type replayEvent struct { + Player int + Event []byte +} + type serverGame struct { id int created int64 @@ -22,6 +27,7 @@ type serverGame struct { rematch int rejoin1 bool rejoin2 bool + replay [][]byte *bgammon.Game } diff --git a/pkg/server/server.go b/pkg/server/server.go index d3e51f6..4ef0ff2 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -992,13 +992,22 @@ COMMANDS: cmd.client.sendNotice("Declined double offer") clientGame.opponent(cmd.client).sendNotice(fmt.Sprintf("%s declined double offer.", cmd.client.name)) + acey := 0 + if clientGame.Acey { + acey = 1 + } + clientGame.replay = append([][]byte{[]byte(fmt.Sprintf("i %s %s %d %d %d %d %d %d", clientGame.Player1.Name, clientGame.Player2.Name, clientGame.Points, clientGame.Player1.Points, clientGame.Player2.Points, clientGame.Winner, clientGame.DoubleValue, acey))}, clientGame.replay...) + + clientGame.replay = append(clientGame.replay, []byte(fmt.Sprintf("%d d %d 0", clientGame.Turn, clientGame.DoubleValue*2))) + + 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 { - clientGame.Reset() + reset = true } } else { clientGame.Player1.Points = clientGame.Player2.Points + clientGame.DoubleValue @@ -1006,7 +1015,7 @@ COMMANDS: clientGame.Winner = 1 clientGame.Ended = time.Now() } else { - clientGame.Reset() + reset = true } } @@ -1021,11 +1030,17 @@ COMMANDS: winEvent.Player = clientGame.Player2.Name } - err := recordGameResult(clientGame.Game, 4, clientGame.client1.account, clientGame.client2.account) + err := recordGameResult(clientGame.Game, 4, clientGame.client1.account, clientGame.client2.account, clientGame.replay) if err != nil { log.Fatalf("failed to record game result: %s", err) } } + + if reset { + clientGame.Reset() + clientGame.replay = clientGame.replay[:0] + } + clientGame.eachClient(func(client *serverClient) { clientGame.sendBoard(client) if winEvent != nil { @@ -1273,14 +1288,23 @@ COMMANDS: } } + acey := 0 + if clientGame.Acey { + acey = 1 + } + clientGame.replay = append([][]byte{[]byte(fmt.Sprintf("i %s %s %d %d %d %d %d %d", clientGame.Player1.Name, clientGame.Player2.Name, clientGame.Points, clientGame.Player1.Points, clientGame.Player2.Points, clientGame.Winner, winPoints, acey))}, clientGame.replay...) + + clientGame.replay = append(clientGame.replay, []byte(fmt.Sprintf("%d r %d-%d %s", clientGame.Turn, clientGame.Roll1, clientGame.Roll2, bgammon.FormatMoves(clientGame.Moves)))) + winEvent = &bgammon.EventWin{ Points: winPoints * clientGame.DoubleValue, } + var reset bool if clientGame.Winner == 1 { winEvent.Player = clientGame.Player1.Name clientGame.Player1.Points = clientGame.Player1.Points + winPoints*clientGame.DoubleValue if clientGame.Player1.Points < clientGame.Points { - clientGame.Reset() + reset = true } else { clientGame.Ended = time.Now() } @@ -1288,7 +1312,7 @@ COMMANDS: winEvent.Player = clientGame.Player2.Name clientGame.Player2.Points = clientGame.Player2.Points + winPoints*clientGame.DoubleValue if clientGame.Player2.Points < clientGame.Points { - clientGame.Reset() + reset = true } else { clientGame.Ended = time.Now() } @@ -1298,10 +1322,15 @@ COMMANDS: if clientGame.Acey { winType = 1 } - err := recordGameResult(clientGame.Game, winType, clientGame.client1.account, clientGame.client2.account) + err := recordGameResult(clientGame.Game, winType, clientGame.client1.account, clientGame.client2.account, clientGame.replay) if err != nil { log.Fatalf("failed to record game result: %s", err) } + + if reset { + clientGame.Reset() + clientGame.replay = clientGame.replay[:0] + } } clientGame.eachClient(func(client *serverClient) { @@ -1378,6 +1407,7 @@ COMMANDS: cmd.client.sendNotice("Accepted double.") opponent.sendNotice(fmt.Sprintf("%s accepted double.", cmd.client.name)) + clientGame.replay = append(clientGame.replay, []byte(fmt.Sprintf("%d d %d 1", clientGame.Turn, clientGame.DoubleValue))) clientGame.eachClient(func(client *serverClient) { clientGame.sendBoard(client) }) @@ -1405,6 +1435,10 @@ COMMANDS: continue } + recordEvent := func() { + clientGame.replay = append(clientGame.replay, []byte(fmt.Sprintf("%d r %d-%d %s", clientGame.Turn, clientGame.Roll1, clientGame.Roll2, bgammon.FormatMoves(clientGame.Moves)))) + } + if clientGame.Acey && ((clientGame.Roll1 == 1 && clientGame.Roll2 == 2) || (clientGame.Roll1 == 2 && clientGame.Roll2 == 1)) && len(clientGame.Moves) == 2 { var doubles int if len(params) > 0 { @@ -1417,6 +1451,7 @@ COMMANDS: continue } + recordEvent() clientGame.NextTurn(true) clientGame.Roll1, clientGame.Roll2 = doubles, doubles clientGame.Reroll = true @@ -1431,6 +1466,7 @@ COMMANDS: client.sendEvent(ev) }) } else if clientGame.Acey && clientGame.Reroll { + recordEvent() clientGame.NextTurn(true) clientGame.Roll1, clientGame.Roll2 = 0, 0 if !clientGame.roll(cmd.client.playerNumber) { @@ -1450,6 +1486,7 @@ COMMANDS: clientGame.sendBoard(client) }) } else { + recordEvent() clientGame.NextTurn(false) if clientGame.Winner == 0 { gameState := &bgammon.GameState{ @@ -1629,7 +1666,7 @@ COMMANDS: clientGame.Turn = 2 clientGame.Roll1 = 4 clientGame.Roll2 = 4 - clientGame.Board = []int{1, -4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 1, -1, 1, -1} + clientGame.Board = []int{1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -2, 0, -1, 0, 0} clientGame.eachClient(func(client *serverClient) { clientGame.sendBoard(client)