]> git.xonotic.org Git - xonotic/xonstat.git/blob - xonstat/util/xs_glicko.go
c34c3261b2d2eb3f4bffd681ae8ce8817c84c882
[xonotic/xonstat.git] / xonstat / util / xs_glicko.go
1 package main
2
3 import (
4         "encoding/json"
5         "flag"
6         "fmt"
7         "log"
8         "os"
9         "time"
10
11         "github.com/jmoiron/sqlx"
12         _ "github.com/lib/pq"
13 )
14
15 const DefaultStartGameID = 0
16 const DefaultEndGameID = -1
17 const DefaultRankingWindowDays = 7
18
19 type Config struct {
20         // database connection string
21         ConnStr string
22
23         // the starting game_id in the games table
24         StartGameID int
25
26         // the ending game_id in the games table
27         EndGameID int
28
29         // the number of days constituting the ranking window
30         RankingWindowDays int
31 }
32
33 func loadConfig(path string) (*Config, error) {
34         config := new(Config)
35
36         // defaults
37         config.ConnStr = "user=xonstat host=localhost dbname=xonstatdb sslmode=disable"
38         config.StartGameID = DefaultStartGameID
39         config.EndGameID = DefaultEndGameID
40         config.RankingWindowDays = DefaultRankingWindowDays
41
42         file, err := os.Open(path)
43         if err != nil {
44                 fmt.Println("Failed opening the file.")
45                 return config, err
46         }
47
48         decoder := json.NewDecoder(file)
49
50         // overwrite in-mem config with new values
51         err = decoder.Decode(config)
52         if err != nil {
53                 fmt.Println("Failed to decode the JSON.")
54                 return config, err
55         }
56
57         return config, nil
58 }
59
60 type Game struct {
61         GameID   int       `db:"game_id"`
62         GameType string    `db:"game_type_cd"`
63         ServerID int       `db:"server_id"`
64         Duration int       `db:"duration"`
65         CreateDt time.Time `db:"create_dt"`
66 }
67
68 type PlayerGameStat struct {
69         PlayerGameStatID int    `db:"player_game_stat_id"`
70         PlayerID         int    `db:"player_id"`
71         GameID           int    `db:"game_id"`
72         Nick             string `db:"stripped_nick"`
73         AliveTime        int    `db:"alivetime"`
74         Score            int    `db:"score"`
75 }
76
77 type GameProcessor struct {
78         config *Config
79         db     *sqlx.DB
80 }
81
82 func NewGameProcessor(config *Config) *GameProcessor {
83         processor := new(GameProcessor)
84
85         processor.config = config
86
87         db, err := sqlx.Connect("postgres", config.ConnStr)
88         if err != nil {
89                 log.Fatal(err)
90         }
91         processor.db = db
92
93         return processor
94 }
95
96 func (gp *GameProcessor) GamesInRange() []Game {
97         games := []Game{}
98
99         sql := `select game_id, game_type_cd, server_id, EXTRACT(EPOCH FROM duration) duration, 
100         create_dt from games where game_id between $1 and $2 order by game_id`
101
102         err := gp.db.Select(&games, sql, gp.config.StartGameID, gp.config.EndGameID)
103         if err != nil {
104                 log.Fatalf("Unable to select games: %s.\n", err)
105         }
106
107         return games
108 }
109
110 func (gp *GameProcessor) PlayerGameStats(gameID int) []PlayerGameStat {
111         pgstats := []PlayerGameStat{}
112
113         sql := `select player_game_stat_id, player_id, game_id, stripped_nick, 
114         EXTRACT(EPOCH from alivetime) alivetime, score from player_game_stats 
115         where game_id = $1 and player_id > 2 order by player_game_stat_id`
116
117         err := gp.db.Select(&pgstats, sql, gameID)
118         if err != nil {
119                 log.Fatalf("Unable to select player_game_stats for game %d: %s.\n", gameID, err)
120         }
121
122         return pgstats
123 }
124
125 func main() {
126         path := flag.String("config", "xs_glicko.json", "configuration file path")
127         start := flag.Int("start", DefaultStartGameID, "starting game_id")
128         end := flag.Int("end", DefaultEndGameID, "ending game_id")
129         days := flag.Int("days", DefaultRankingWindowDays, "number of days in the ranking window")
130         flag.Parse()
131
132         config, err := loadConfig(*path)
133         if err != nil {
134                 log.Fatalf("Unable to load config file: %s.\n", err)
135         }
136
137         if *start != DefaultStartGameID {
138                 config.StartGameID = *start
139         }
140
141         if *end != DefaultEndGameID {
142                 config.EndGameID = *end
143         }
144
145         if *days != DefaultRankingWindowDays {
146                 config.RankingWindowDays = *days
147         }
148
149         processor := NewGameProcessor(config)
150         for _, game := range processor.GamesInRange() {
151                 pgstats := processor.PlayerGameStats(game.GameID)
152                 fmt.Println(pgstats)
153         }
154 }