Record game results to database

This commit is contained in:
Trevor Slocum 2023-11-21 16:16:33 -08:00
parent 34de22fae1
commit b4bbf10287
5 changed files with 148 additions and 3 deletions

View file

@ -0,0 +1,84 @@
package main
import (
"context"
"log"
"code.rocket9labs.com/tslocum/bgammon"
"github.com/jackc/pgx/v5"
)
const databaseSchema = `
CREATE TABLE game (
id serial PRIMARY KEY,
started bigint NOT NULL,
ended bigint NOT NULL,
winner integer NOT NULL,
player1 text NOT NULL,
player2 text NOT NULL
);
`
func connectDB(dataSource string) (*pgx.Conn, error) {
var err error
db, err := pgx.Connect(context.Background(), dataSource)
if err != nil {
return nil, err
}
return db, nil
}
func begin(db *pgx.Conn) (pgx.Tx, error) {
tx, err := db.Begin(context.Background())
if err != nil {
return nil, err
}
_, err = tx.Exec(context.Background(), "SET SCHEMA 'bgammon'")
if err != nil {
return nil, err
}
return tx, nil
}
func testDBConnection(db *pgx.Conn) error {
_, err := db.Exec(context.Background(), "SELECT 1=1")
return err
}
func initDB(db *pgx.Conn) {
tx, err := begin(db)
if err != nil {
log.Fatalf("failed to initialize database: %s", err)
}
defer tx.Commit(context.Background())
var result int
err = tx.QueryRow(context.Background(), "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'bgammon' AND table_name = 'game'").Scan(&result)
if err != nil {
log.Fatal(err)
} else if result > 0 {
return // Database has been initialized.
}
_, err = tx.Exec(context.Background(), databaseSchema)
if err != nil {
log.Fatalf("failed to initialize database: %s", err)
}
log.Println("Initialized database schema")
}
func recordGameResult(conn *pgx.Conn, g bgammon.Game) error {
if g.Started.IsZero() || g.Ended.IsZero() || g.Winner == 0 {
return nil
}
tx, err := begin(conn)
if err != nil {
return err
}
defer tx.Commit(context.Background())
_, err = tx.Exec(context.Background(), "INSERT INTO game (started, ended, winner, player1, player2) VALUES ($1, $2, $3, $4, $5)", g.Started.Unix(), g.Ended.Unix(), g.Winner, g.Player1.Name, g.Player2.Name)
return err
}

View file

@ -12,12 +12,14 @@ func main() {
var (
tcpAddress string
wsAddress string
dataSource string
debug int
debugCommands bool
rollStatistics bool
)
flag.StringVar(&tcpAddress, "tcp", "localhost:1337", "TCP listen address")
flag.StringVar(&wsAddress, "ws", "localhost:1338", "WebSocket listen address")
flag.StringVar(&dataSource, "db", "", "Database data source (postgres://username:password@localhost:5432/database_name")
flag.IntVar(&debug, "debug", 0, "print debug information and serve pprof on specified port")
flag.BoolVar(&debugCommands, "debug-commands", false, "allow players to use restricted commands")
flag.BoolVar(&rollStatistics, "statistics", false, "print dice roll statistics and exit")
@ -42,7 +44,7 @@ func main() {
allowDebugCommands = debugCommands
}
s := newServer()
s := newServer(dataSource)
if tcpAddress != "" {
s.listen("tcp", tcpAddress)
}

View file

@ -16,6 +16,7 @@ import (
"time"
"code.rocket9labs.com/tslocum/bgammon"
"github.com/jackc/pgx/v5"
)
const clientTimeout = 40 * time.Second
@ -47,9 +48,11 @@ type server struct {
gamesCache []byte
gamesCacheTime time.Time
gamesCacheLock sync.Mutex
db *pgx.Conn
}
func newServer() *server {
func newServer(dataSource string) *server {
const bufferSize = 10
s := &server{
newGameIDs: make(chan int),
@ -57,6 +60,24 @@ func newServer() *server {
commands: make(chan serverCommand, bufferSize),
welcome: []byte("hello Welcome to bgammon.org! Please log in by sending the 'login' command. You may specify a username, otherwise you will be assigned a random username. If you specify a username, you may also specify a password. Have fun!"),
}
if dataSource != "" {
var err error
s.db, err = connectDB(dataSource)
if err != nil {
log.Fatalf("failed to connect to database: %s", err)
}
err = testDBConnection(s.db)
if err != nil {
log.Fatalf("failed to test database connection: %s", err)
}
initDB(s.db)
log.Println("Connected to database successfully")
}
go s.handleNewGameIDs()
go s.handleNewClientIDs()
go s.handleCommands()
@ -784,6 +805,10 @@ COMMANDS:
} else {
winEvent.Player = clientGame.Player2.Name
}
if s.db != nil {
recordGameResult(s.db, *clientGame.Game)
}
}
clientGame.eachClient(func(client *serverClient) {
clientGame.sendBoard(client)
@ -964,6 +989,10 @@ COMMANDS:
clientGame.Ended = time.Now()
}
}
if s.db != nil {
recordGameResult(s.db, *clientGame.Game)
}
}
clientGame.eachClient(func(client *serverClient) {

9
go.mod
View file

@ -2,10 +2,17 @@ module code.rocket9labs.com/tslocum/bgammon
go 1.20
require github.com/gobwas/ws v1.3.1
require (
github.com/gobwas/ws v1.3.1
github.com/jackc/pgx/v5 v5.5.0
)
require (
github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
golang.org/x/crypto v0.15.0 // indirect
golang.org/x/sys v0.14.0 // indirect
golang.org/x/text v0.14.0 // indirect
)

23
go.sum
View file

@ -1,9 +1,32 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.3.1 h1:Qi34dfLMWJbiKaNbDVzM9x27nZBjmkaW6i4+Ku+pGVU=
github.com/gobwas/ws v1.3.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.5.0 h1:NxstgwndsTRy7eq9/kqYc/BZh5w2hHJV86wjvO+1xPw=
github.com/jackc/pgx/v5 v5.5.0/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA=
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=