parent
a2fb60cf35
commit
5ec74dd3db
6 changed files with 127 additions and 13 deletions
1
go.mod
1
go.mod
|
@ -7,6 +7,7 @@ require (
|
|||
github.com/gobwas/ws v1.3.1
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/jackc/pgx/v5 v5.5.1
|
||||
github.com/jlouis/glicko2 v1.0.0
|
||||
github.com/matcornic/hermes/v2 v2.1.0
|
||||
)
|
||||
|
||||
|
|
2
go.sum
2
go.sum
|
@ -53,6 +53,8 @@ github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk
|
|||
github.com/jaytaylor/html2text v0.0.0-20180606194806-57d518f124b0/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
|
||||
github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056 h1:iCHtR9CQyktQ5+f3dMVZfwD2KWJUgm7M0gdL9NGr8KA=
|
||||
github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
|
||||
github.com/jlouis/glicko2 v1.0.0 h1:pSl/OTRclxdrhtqoqJpvO41GoUL1dmaTnxC2F6+W+y8=
|
||||
github.com/jlouis/glicko2 v1.0.0/go.mod h1:5dzlxjhVPPLk+wiUwwF2oVyDwsNXMgnw7WrLRxuejBs=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
|
|
|
@ -21,22 +21,27 @@ import (
|
|||
"code.rocket9labs.com/tslocum/bgammon"
|
||||
"github.com/alexedwards/argon2id"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jlouis/glicko2"
|
||||
"github.com/matcornic/hermes/v2"
|
||||
)
|
||||
|
||||
const databaseSchema = `
|
||||
CREATE TABLE account (
|
||||
id serial PRIMARY KEY,
|
||||
created bigint NOT NULL,
|
||||
confirmed bigint NOT NULL DEFAULT 0,
|
||||
active bigint NOT NULL,
|
||||
reset bigint NOT NULL DEFAULT 0,
|
||||
email text NOT NULL,
|
||||
username text NOT NULL,
|
||||
password text NOT NULL,
|
||||
highlight smallint NOT NULL DEFAULT 1,
|
||||
pips smallint NOT NULL DEFAULT 1,
|
||||
moves smallint NOT NULL DEFAULT 0
|
||||
id serial PRIMARY KEY,
|
||||
created bigint NOT NULL,
|
||||
confirmed bigint NOT NULL DEFAULT 0,
|
||||
active bigint NOT NULL,
|
||||
reset bigint NOT NULL DEFAULT 0,
|
||||
email text NOT NULL,
|
||||
username text NOT NULL,
|
||||
password text NOT NULL,
|
||||
casualsingle integer NOT NULL DEFAULT 150000,
|
||||
casualmulti integer NOT NULL DEFAULT 150000,
|
||||
ratedsingle integer NOT NULL DEFAULT 150000,
|
||||
ratedmulti integer NOT NULL DEFAULT 150000,
|
||||
highlight smallint NOT NULL DEFAULT 1,
|
||||
pips smallint NOT NULL DEFAULT 1,
|
||||
moves smallint NOT NULL DEFAULT 0
|
||||
);
|
||||
CREATE TABLE game (
|
||||
id serial PRIMARY KEY,
|
||||
|
@ -437,6 +442,68 @@ func recordGameResult(g *bgammon.Game, winType int, account1 int, account2 int,
|
|||
return err
|
||||
}
|
||||
|
||||
func recordMatchResult(g *bgammon.Game, matchType int, account1 int, account2 int) error {
|
||||
dbLock.Lock()
|
||||
defer dbLock.Unlock()
|
||||
|
||||
if db == nil || g.Started.IsZero() || g.Winner == 0 || account1 == 0 || account2 == 0 || account1 == account2 {
|
||||
return nil
|
||||
}
|
||||
|
||||
tx, err := begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Commit(context.Background())
|
||||
|
||||
var columnName string
|
||||
switch matchType {
|
||||
case matchTypeCasual:
|
||||
if g.Points == 1 {
|
||||
columnName = "casualsingle"
|
||||
} else {
|
||||
columnName = "casualmulti"
|
||||
}
|
||||
case matchTypeRated:
|
||||
if g.Points == 1 {
|
||||
columnName = "ratedsingle"
|
||||
} else {
|
||||
columnName = "ratedmulti"
|
||||
}
|
||||
default:
|
||||
log.Panicf("unknown match type: %d", matchType)
|
||||
}
|
||||
|
||||
var rating1i int
|
||||
err = tx.QueryRow(context.Background(), "SELECT "+columnName+" FROM account WHERE id = $1", account1).Scan(&rating1i)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rating1 := float64(rating1i) / 100
|
||||
|
||||
var rating2i int
|
||||
err = tx.QueryRow(context.Background(), "SELECT "+columnName+" FROM account WHERE id = $1", account2).Scan(&rating2i)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rating2 := float64(rating2i) / 100
|
||||
|
||||
outcome1, outcome2 := 1.0, 0.0
|
||||
if g.Winner == 2 {
|
||||
outcome1, outcome2 = 0.0, 1.0
|
||||
}
|
||||
rating1New, _, _ := glicko2.Rank(rating1, 50, 0.06, []glicko2.Opponent{ratingPlayer{rating2, 30, 0.06, outcome1}}, 0.6)
|
||||
rating2New, _, _ := glicko2.Rank(rating2, 50, 0.06, []glicko2.Opponent{ratingPlayer{rating1, 30, 0.06, outcome2}}, 0.6)
|
||||
|
||||
_, err = tx.Exec(context.Background(), "UPDATE account SET "+columnName+" = $1 WHERE id = $2", int(rating1New*100), account1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.Exec(context.Background(), "UPDATE account SET "+columnName+" = $1 WHERE id = $2", int(rating2New*100), account2)
|
||||
return err
|
||||
}
|
||||
|
||||
func matchInfo(id int) (timestamp int64, player1 string, player2 string, replay []byte, err error) {
|
||||
dbLock.Lock()
|
||||
defer dbLock.Unlock()
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
package server
|
||||
|
||||
const (
|
||||
matchTypeCasual = iota
|
||||
matchTypeRated
|
||||
)
|
||||
|
||||
type serverStatsEntry struct {
|
||||
Date string
|
||||
Games int
|
||||
|
|
|
@ -55,6 +55,10 @@ func recordGameResult(g *bgammon.Game, winType int, account1 int, account2 int,
|
|||
return nil
|
||||
}
|
||||
|
||||
func recordMatchResult(g *bgammon.Game, matchType int, account1 int, account2 int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func matchHistory(username string) ([]*bgammon.HistoryMatch, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
|
|
@ -1059,6 +1059,13 @@ COMMANDS:
|
|||
if err != nil {
|
||||
log.Fatalf("failed to record game result: %s", err)
|
||||
}
|
||||
|
||||
if !reset {
|
||||
err := recordMatchResult(clientGame.Game, matchTypeCasual, clientGame.client1.account, clientGame.client2.account)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to record match result: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if reset {
|
||||
|
@ -1365,7 +1372,12 @@ COMMANDS:
|
|||
log.Fatalf("failed to record game result: %s", err)
|
||||
}
|
||||
|
||||
if reset {
|
||||
if !reset {
|
||||
err := recordMatchResult(clientGame.Game, matchTypeCasual, clientGame.client1.account, clientGame.client2.account)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to record match result: %s", err)
|
||||
}
|
||||
} else {
|
||||
clientGame.Reset()
|
||||
clientGame.replay = clientGame.replay[:0]
|
||||
}
|
||||
|
@ -1767,7 +1779,7 @@ COMMANDS:
|
|||
clientGame.Turn = 1
|
||||
clientGame.Roll1 = 6
|
||||
clientGame.Roll2 = 6
|
||||
clientGame.Board = []int{1, 0, -2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, -1, 1, -1}
|
||||
clientGame.Board = []int{1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -2, 0, 0, 0, 0}
|
||||
|
||||
clientGame.eachClient(func(client *serverClient) {
|
||||
clientGame.sendBoard(client)
|
||||
|
@ -1786,3 +1798,26 @@ func RandInt(max int) int {
|
|||
}
|
||||
return int(i.Int64())
|
||||
}
|
||||
|
||||
type ratingPlayer struct {
|
||||
r float64
|
||||
rd float64
|
||||
sigma float64
|
||||
outcome float64
|
||||
}
|
||||
|
||||
func (p ratingPlayer) R() float64 {
|
||||
return p.r
|
||||
}
|
||||
|
||||
func (p ratingPlayer) RD() float64 {
|
||||
return p.rd
|
||||
}
|
||||
|
||||
func (p ratingPlayer) Sigma() float64 {
|
||||
return p.sigma
|
||||
}
|
||||
|
||||
func (p ratingPlayer) SJ() float64 {
|
||||
return p.outcome
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue