Include player rating in match listing

This commit is contained in:
Trevor Slocum 2024-01-13 15:53:57 -08:00
parent 3b818ab272
commit eda9754b06
8 changed files with 137 additions and 36 deletions

View file

@ -50,6 +50,7 @@ type GameListing struct {
Password bool
Points int8
Players int8
Rating int
Name string
}

View file

@ -1,10 +1,11 @@
package server
type account struct {
id int
email []byte
username []byte
password []byte
id int
email []byte
username []byte
password []byte
autoplay bool
highlight bool
pips bool
@ -12,4 +13,7 @@ type account struct {
flip bool
advanced bool
speed int8
casual *clientRating
competitive *clientRating
}

View file

@ -11,11 +11,68 @@ import (
"code.rocket9labs.com/tslocum/bgammon"
)
type clientRating struct {
backgammonSingle int
backgammonMulti int
aceySingle int
aceyMulti int
tabulaSingle int
tabulaMulti int
}
func (r *clientRating) getRating(variant int8, multiPoint bool) int {
switch variant {
case bgammon.VariantBackgammon:
if !multiPoint {
return r.backgammonSingle
}
return r.backgammonMulti
case bgammon.VariantAceyDeucey:
if !multiPoint {
return r.aceySingle
}
return r.aceyMulti
case bgammon.VariantTabula:
if !multiPoint {
return r.tabulaSingle
}
return r.tabulaMulti
default:
log.Panicf("unknown variant: %d", variant)
return 0
}
}
func (r *clientRating) setRating(variant int8, multiPoint bool, rating int) {
switch variant {
case bgammon.VariantBackgammon:
if !multiPoint {
r.backgammonSingle = rating
return
}
r.backgammonMulti = rating
case bgammon.VariantAceyDeucey:
if !multiPoint {
r.aceySingle = rating
return
}
r.aceyMulti = rating
case bgammon.VariantTabula:
if !multiPoint {
r.tabulaSingle = rating
}
r.tabulaMulti = rating
default:
log.Panicf("unknown variant: %d", variant)
}
}
type serverClient struct {
id int
json bool
name []byte
account int
account *account
accountID int
connected int64
active int64
lastPing int64

View file

@ -343,9 +343,12 @@ func loginAccount(passwordSalt string, username []byte, password []byte) (*accou
}
defer tx.Commit(context.Background())
a := &account{}
a := &account{
casual: &clientRating{},
competitive: &clientRating{},
}
var autoplay, highlight, pips, moves, flip, advanced int
err = tx.QueryRow(context.Background(), "SELECT id, email, username, password, autoplay, highlight, pips, moves, flip, advanced, speed FROM account WHERE username = $1 OR email = $2", bytes.ToLower(bytes.TrimSpace(username)), bytes.ToLower(bytes.TrimSpace(username))).Scan(&a.id, &a.email, &a.username, &a.password, &autoplay, &highlight, &pips, &moves, &flip, &advanced, &a.speed)
err = tx.QueryRow(context.Background(), "SELECT id, email, username, password, autoplay, highlight, pips, moves, flip, advanced, speed, casual_backgammon_single, casual_backgammon_multi, casual_acey_single, casual_acey_multi, casual_tabula_single, casual_tabula_multi, rated_backgammon_single, rated_backgammon_multi, rated_acey_single, rated_acey_multi, rated_tabula_single, rated_tabula_multi FROM account WHERE username = $1 OR email = $2", bytes.ToLower(bytes.TrimSpace(username)), bytes.ToLower(bytes.TrimSpace(username))).Scan(&a.id, &a.email, &a.username, &a.password, &autoplay, &highlight, &pips, &moves, &flip, &advanced, &a.speed, &a.casual.backgammonSingle, &a.casual.backgammonMulti, &a.casual.aceySingle, &a.casual.aceyMulti, &a.casual.tabulaSingle, &a.casual.tabulaMulti, &a.competitive.backgammonSingle, &a.competitive.backgammonMulti, &a.competitive.aceySingle, &a.competitive.aceyMulti, &a.competitive.tabulaSingle, &a.competitive.tabulaMulti)
if err != nil {
return nil, nil
} else if len(a.password) == 0 {
@ -435,7 +438,7 @@ func setAccountSetting(id int, name string, value int) error {
return err
}
func recordGameResult(g *bgammon.Game, winType int8, account1 int, account2 int, replay [][]byte) error {
func recordGameResult(g *serverGame, winType int8, replay [][]byte) error {
dbLock.Lock()
defer dbLock.Unlock()
@ -454,19 +457,19 @@ func recordGameResult(g *bgammon.Game, winType int8, account1 int, account2 int,
}
defer tx.Commit(context.Background())
_, err = tx.Exec(context.Background(), "INSERT INTO game (variant, started, ended, player1, account1, player2, account2, points, winner, wintype, replay) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)", g.Variant, g.Started.Unix(), ended.Unix(), g.Player1.Name, account1, g.Player2.Name, account2, g.Points, g.Winner, winType, bytes.Join(replay, []byte("\n")))
_, err = tx.Exec(context.Background(), "INSERT INTO game (variant, started, ended, player1, account1, player2, account2, points, winner, wintype, replay) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)", g.Variant, g.Started.Unix(), ended.Unix(), g.Player1.Name, g.account1, g.Player2.Name, g.account2, g.Points, g.Winner, winType, bytes.Join(replay, []byte("\n")))
if err != nil {
return err
}
if account1 != 0 {
_, err = tx.Exec(context.Background(), "UPDATE account SET active = $1 WHERE id = $2", time.Now().Unix(), account1)
if g.account1 != 0 {
_, err = tx.Exec(context.Background(), "UPDATE account SET active = $1 WHERE id = $2", time.Now().Unix(), g.account1)
if err != nil {
return err
}
}
if account2 != 0 {
_, err = tx.Exec(context.Background(), "UPDATE account SET active = $1 WHERE id = $2", time.Now().Unix(), account2)
if g.account2 != 0 {
_, err = tx.Exec(context.Background(), "UPDATE account SET active = $1 WHERE id = $2", time.Now().Unix(), g.account2)
if err != nil {
return err
}
@ -474,11 +477,11 @@ func recordGameResult(g *bgammon.Game, winType int8, account1 int, account2 int,
return nil
}
func recordMatchResult(g *bgammon.Game, matchType int, account1 int, account2 int) error {
func recordMatchResult(g *serverGame, matchType int) error {
dbLock.Lock()
defer dbLock.Unlock()
if db == nil || g.Started.IsZero() || g.Winner == 0 || account1 == 0 || account2 == 0 || account1 == account2 {
if db == nil || g.Started.IsZero() || g.Winner == 0 || g.account1 == 0 || g.account2 == 0 || g.account1 == g.account2 {
return nil
}
@ -491,14 +494,14 @@ func recordMatchResult(g *bgammon.Game, matchType int, account1 int, account2 in
columnName := ratingColumn(matchType, g.Variant, g.Points != 1)
var rating1i int
err = tx.QueryRow(context.Background(), "SELECT "+columnName+" FROM account WHERE id = $1", account1).Scan(&rating1i)
err = tx.QueryRow(context.Background(), "SELECT "+columnName+" FROM account WHERE id = $1", g.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)
err = tx.QueryRow(context.Background(), "SELECT "+columnName+" FROM account WHERE id = $1", g.account2).Scan(&rating2i)
if err != nil {
return err
}
@ -512,12 +515,30 @@ func recordMatchResult(g *bgammon.Game, matchType int, account1 int, account2 in
rating2New, _, _ := glicko2.Rank(rating2, 50, 0.06, []glicko2.Opponent{ratingPlayer{rating1, 30, 0.06, outcome2}}, 0.6)
active := time.Now().Unix()
_, err = tx.Exec(context.Background(), "UPDATE account SET "+columnName+" = $1, active = $2 WHERE id = $3", int(rating1New*100), active, account1)
_, err = tx.Exec(context.Background(), "UPDATE account SET "+columnName+" = $1, active = $2 WHERE id = $3", int(rating1New*100), active, g.account1)
if err != nil {
return err
}
_, err = tx.Exec(context.Background(), "UPDATE account SET "+columnName+" = $1, active = $2 WHERE id = $3", int(rating2New*100), active, account2)
return err
_, err = tx.Exec(context.Background(), "UPDATE account SET "+columnName+" = $1, active = $2 WHERE id = $3", int(rating2New*100), active, g.account2)
if err != nil {
return err
}
if g.client1 != nil && g.client1.account != nil {
if matchType == matchTypeCasual {
g.client1.account.casual.setRating(g.Variant, g.Points > 1, int(rating1New*100))
} else {
g.client1.account.competitive.setRating(g.Variant, g.Points > 1, int(rating1New*100))
}
}
if g.client2 != nil && g.client2.account != nil {
if matchType == matchTypeCasual {
g.client2.account.casual.setRating(g.Variant, g.Points > 1, int(rating2New*100))
} else {
g.client2.account.competitive.setRating(g.Variant, g.Points > 1, int(rating2New*100))
}
}
return nil
}
func matchInfo(id int) (timestamp int64, player1 string, player2 string, replay []byte, err error) {

View file

@ -51,11 +51,11 @@ func replayByID(id int) ([]byte, error) {
return nil, nil
}
func recordGameResult(g *bgammon.Game, winType int8, account1 int, account2 int, replay [][]byte) error {
func recordGameResult(g *serverGame, winType int8, replay [][]byte) error {
return nil
}
func recordMatchResult(g *bgammon.Game, matchType int, account1 int, account2 int) error {
func recordMatchResult(g *serverGame, matchType int) error {
return nil
}

View file

@ -159,7 +159,12 @@ func (g *serverGame) roll(player int8) bool {
// Store account IDs.
if g.Started.IsZero() && g.Roll1 != 0 && g.Roll2 != 0 {
g.Started = time.Now()
g.account1, g.account2 = g.client1.account, g.client2.account
if g.client1.account != nil {
g.account1 = g.client1.account.id
}
if g.client2.account != nil {
g.account2 = g.client2.account.id
}
}
return true
} else if player != g.Turn || g.Roll1 != 0 || g.Roll2 != 0 {
@ -468,6 +473,17 @@ func (g *serverGame) listing(playerName []byte) *bgammon.GameListing {
playerCount = g.playerCount()
}
var rating int
if g.client1 != nil && g.client1.account != nil {
rating = g.client1.account.casual.getRating(g.Variant, g.Points > 1)
}
if g.client2 != nil && g.client2.account != nil {
r := g.client2.account.casual.getRating(g.Variant, g.Points > 1)
if r > rating {
rating = r
}
}
name := string(g.name)
switch g.Variant {
case bgammon.VariantAceyDeucey:
@ -481,6 +497,7 @@ func (g *serverGame) listing(playerName []byte) *bgammon.GameListing {
Points: g.Points,
Password: len(g.password) != 0,
Players: playerCount,
Rating: rating / 100,
Name: name,
}
}
@ -655,13 +672,13 @@ func (g *serverGame) handleWin() bool {
if g.Variant != bgammon.VariantBackgammon {
winType = 1
}
err := recordGameResult(g.Game, winType, g.client1.account, g.client2.account, g.replay)
err := recordGameResult(g, winType, g.replay)
if err != nil {
log.Fatalf("failed to record game result: %s", err)
}
if !reset {
err := recordMatchResult(g.Game, matchTypeCasual, g.client1.account, g.client2.account)
err := recordMatchResult(g, matchTypeCasual)
if err != nil {
log.Fatalf("failed to record match result: %s", err)
}

View file

@ -192,7 +192,7 @@ func (s *server) handleWebSocket(w http.ResponseWriter, r *http.Request) {
c := &serverClient{
id: <-s.newClientIDs,
account: -1,
accountID: -1,
connected: now,
active: now,
commands: commands,
@ -257,7 +257,7 @@ func (s *server) handleTerminatedGames() {
if g.forefeit == 1 {
g.Winner = 2
}
err := recordMatchResult(g.Game, matchTypeCasual, g.account1, g.account2)
err := recordMatchResult(g, matchTypeCasual)
if err != nil {
log.Fatalf("failed to record match result: %s", err)
}
@ -297,7 +297,7 @@ func (s *server) handleConnection(conn net.Conn) {
c := &serverClient{
id: <-s.newClientIDs,
account: -1,
accountID: -1,
connected: now,
active: now,
commands: commands,

View file

@ -40,7 +40,7 @@ COMMANDS:
params := bytes.Fields(cmd.command[startParameters:])
// Require users to send login command first.
if cmd.client.account == -1 {
if cmd.client.accountID == -1 {
resetCommand := keyword == bgammon.CommandResetPassword
if resetCommand {
if len(params) > 0 {
@ -165,7 +165,8 @@ COMMANDS:
continue
}
cmd.client.account = a.id
cmd.client.account = a
cmd.client.accountID = a.id
cmd.client.name = name
cmd.client.autoplay = a.autoplay
cmd.client.sendEvent(&bgammon.EventSettings{
@ -178,7 +179,7 @@ COMMANDS:
Speed: a.speed,
})
} else {
cmd.client.account = 0
cmd.client.accountID = 0
if !randomUsername && !bytes.HasPrefix(username, []byte("BOT_")) && !bytes.HasPrefix(username, []byte("Guest_")) {
username = append([]byte("Guest_"), username...)
}
@ -588,13 +589,13 @@ COMMANDS:
winEvent.Player = clientGame.Player2.Name
}
err := recordGameResult(clientGame.Game, 4, clientGame.client1.account, clientGame.client2.account, clientGame.replay)
err := recordGameResult(clientGame, 4, clientGame.replay)
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)
err := recordMatchResult(clientGame, matchTypeCasual)
if err != nil {
log.Fatalf("failed to record match result: %s", err)
}
@ -1068,7 +1069,7 @@ COMMANDS:
clientGame.sendBoard(cmd.client, false)
case bgammon.CommandPassword:
if cmd.client.account == 0 {
if cmd.client.account == nil {
cmd.client.sendNotice("Failed to change password: you are logged in as a guest.")
continue
} else if len(params) < 2 {
@ -1118,10 +1119,10 @@ COMMANDS:
cmd.client.autoplay = value == 1
}
if cmd.client.account == 0 {
if cmd.client.account == nil {
continue
}
_ = setAccountSetting(cmd.client.account, name, value)
_ = setAccountSetting(cmd.client.account.id, name, value)
case bgammon.CommandReplay:
var (
id int