From 7944c5fd252b976d62c8e559adb40e3e69a87a69 Mon Sep 17 00:00:00 2001 From: Trevor Slocum Date: Fri, 15 Nov 2024 19:45:52 -0800 Subject: [PATCH] Add player versus player match statistics --- go.mod | 8 ++--- go.sum | 19 +++++----- pkg/server/database.go | 61 +++++++++++++++++++++++++++++++++ pkg/server/database_disabled.go | 4 +++ pkg/server/server.go | 4 +-- pkg/server/server_full.go | 12 ++++++- 6 files changed, 92 insertions(+), 16 deletions(-) diff --git a/go.mod b/go.mod index e60c174..8541ae0 100644 --- a/go.mod +++ b/go.mod @@ -13,8 +13,8 @@ require ( github.com/jackc/pgx/v5 v5.7.1 github.com/jlouis/glicko2 v1.0.0 github.com/matcornic/hermes/v2 v2.1.0 - golang.org/x/crypto v0.28.0 - golang.org/x/text v0.19.0 + golang.org/x/crypto v0.29.0 + golang.org/x/text v0.20.0 ) require ( @@ -40,6 +40,6 @@ require ( github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect github.com/vanng822/css v1.0.1 // indirect github.com/vanng822/go-premailer v1.22.0 // indirect - golang.org/x/net v0.30.0 // indirect - golang.org/x/sys v0.26.0 // indirect + golang.org/x/net v0.31.0 // indirect + golang.org/x/sys v0.27.0 // indirect ) diff --git a/go.sum b/go.sum index 272e5af..6244de5 100644 --- a/go.sum +++ b/go.sum @@ -114,8 +114,8 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= -golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= -golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= +golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= @@ -134,16 +134,17 @@ golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= -golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= -golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= +golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= +golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190225065934-cc5685c2db12/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -160,8 +161,8 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -183,8 +184,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= diff --git a/pkg/server/database.go b/pkg/server/database.go index 96ef301..f4e9aad 100644 --- a/pkg/server/database.go +++ b/pkg/server/database.go @@ -1274,6 +1274,67 @@ func accountStats(name string, matchType int, variant int8, tz *time.Location) ( return result, nil } +func playerVsPlayerStats(tz *time.Location) (*serverStatsResult, error) { + dbLock.Lock() + defer dbLock.Unlock() + + tx, err := begin() + if err != nil { + return nil, err + } + defer tx.Commit(context.Background()) + + condition := "player1 NOT LIKE 'Guest_%' AND player1 NOT LIKE 'BOT_%' AND player2 NOT LIKE 'Guest_%' AND player2 NOT LIKE 'BOT_%'" + + var earliestGame int64 + rows, err := tx.Query(context.Background(), "SELECT MIN(started) FROM game WHERE "+condition) + if err != nil { + return nil, err + } + for rows.Next() { + if err != nil { + continue + } + err = rows.Scan(&earliestGame) + } + if err != nil { + return nil, err + } + + result := &serverStatsResult{} + m := midnight(time.Unix(earliestGame, 0).In(tz)) + earliest := time.Date(m.Year(), m.Month(), 1, 0, 0, 0, 0, m.Location()) + rangeStart, rangeEnd := earliest.Unix(), earliest.AddDate(0, 1, 0).Unix() + var games int + for { + rows, err := tx.Query(context.Background(), "SELECT COUNT(*) FROM game WHERE started >= $1 AND started < $2 AND "+condition, rangeStart, rangeEnd) + if err != nil { + return nil, err + } + for rows.Next() { + if err != nil { + continue + } + err = rows.Scan(&games) + } + if err != nil { + return nil, err + } + + result.History = append(result.History, &serverStatsEntry{ + Date: time.Unix(rangeStart, 0).Format("2006-01"), + Games: games, + }) + + earliest = time.Date(earliest.Year(), earliest.Month()+1, 1, 0, 0, 0, 0, m.Location()) + rangeStart, rangeEnd = earliest.Unix(), time.Date(earliest.Year(), earliest.Month()+1, 1, 0, 0, 0, 0, m.Location()).Unix() + if rangeStart >= time.Now().Unix() { + break + } + } + return result, nil +} + func ratingColumn(matchType int, variant int8, multiPoint bool) string { var columnStart = "casual_" if matchType == matchTypeRated { diff --git a/pkg/server/database_disabled.go b/pkg/server/database_disabled.go index 7ddf075..7337bc6 100644 --- a/pkg/server/database_disabled.go +++ b/pkg/server/database_disabled.go @@ -106,3 +106,7 @@ func cumulativeStats(tz *time.Location) (*serverStatsResult, error) { func accountStats(name string, tz *time.Location) (*accountStatsResult, error) { return &accountStatsResult{}, nil } + +func playerVsPlayerStats(tz *time.Location) (*serverStatsResult, error) { + return &serverStatsResult{}, nil +} diff --git a/pkg/server/server.go b/pkg/server/server.go index 573c9a7..b7ef00c 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -73,8 +73,8 @@ type server struct { gamesCacheTime time.Time gamesCacheLock sync.Mutex - statsCache [5][]byte - statsCacheTime [5]time.Time + statsCache [6][]byte + statsCacheTime [6]time.Time statsCacheLock sync.Mutex leaderboardCache [12][]byte diff --git a/pkg/server/server_full.go b/pkg/server/server_full.go index a12ffc9..e03dd11 100644 --- a/pkg/server/server_full.go +++ b/pkg/server/server_full.go @@ -83,6 +83,7 @@ func (s *server) listenWebSocket(address string) { handle("/stats-total.json", s.handleStatsFunc(2)) handle("/stats-tabula.json", s.handleStatsFunc(3)) handle("/stats-wildbg.json", s.handleStatsFunc(4)) + handle("/stats-player.json", s.handleStatsFunc(5)) handle("/stats/{username:[A-Za-z0-9_\\-]+}.json", s.handleAccountStatsFunc(matchTypeCasual, bgammon.VariantBackgammon)) handle("/stats/{username:[A-Za-z0-9_\\-]+}/backgammon.json", s.handleAccountStatsFunc(matchTypeCasual, bgammon.VariantBackgammon)) handle("/stats/{username:[A-Za-z0-9_\\-]+}/acey.json", s.handleAccountStatsFunc(matchTypeCasual, bgammon.VariantAceyDeucey)) @@ -258,7 +259,7 @@ func (s *server) cachedStats(statsType int) []byte { if err != nil { log.Fatalf("failed to serialize tabula statistics: %s", err) } - default: + case 4: stats, err := accountStats("BOT_wildbg", matchTypeCasual, bgammon.VariantBackgammon, s.tz) if err != nil { log.Fatalf("failed to fetch wildbg statistics: %s", err) @@ -267,6 +268,15 @@ func (s *server) cachedStats(statsType int) []byte { if err != nil { log.Fatalf("failed to serialize wildbg statistics: %s", err) } + default: // 5 + stats, err := playerVsPlayerStats(s.tz) + if err != nil { + log.Fatalf("failed to fetch player statistics: %s", err) + } + s.statsCache[statsType], err = json.Marshal(stats) + if err != nil { + log.Fatalf("failed to marshal %+v: %s", stats, err) + } } return s.statsCache[statsType]