Record game results to database
This commit is contained in:
parent
34de22fae1
commit
b4bbf10287
5 changed files with 148 additions and 3 deletions
84
cmd/bgammon-server/database.go
Normal file
84
cmd/bgammon-server/database.go
Normal 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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
9
go.mod
|
@ -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
23
go.sum
|
@ -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=
|
||||
|
|
Loading…
Reference in a new issue