Fix: rating update
This commit is contained in:
@ -2,7 +2,7 @@ ALTER TABLE player_stats
|
|||||||
ADD COLUMN rating REAL NOT NULL DEFAULT 1000.0;
|
ADD COLUMN rating REAL NOT NULL DEFAULT 1000.0;
|
||||||
|
|
||||||
CREATE TRIGGER update_player_rating
|
CREATE TRIGGER update_player_rating
|
||||||
BEFORE UPDATE OF games_played, games_won ON player_stats
|
AFTER UPDATE OF games_played, games_won ON player_stats
|
||||||
WHEN NEW.games_played = OLD.games_played + 1
|
WHEN NEW.games_played = OLD.games_played + 1
|
||||||
BEGIN
|
BEGIN
|
||||||
UPDATE player_stats
|
UPDATE player_stats
|
||||||
|
@ -14,18 +14,99 @@ func setupPlayersTestDB(t *testing.T) (*sqlx.DB, func()) {
|
|||||||
db, err := sqlx.Connect("sqlite3", ":memory:")
|
db, err := sqlx.Connect("sqlite3", ":memory:")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
schema := `
|
// Load schema from migration files
|
||||||
CREATE TABLE IF NOT EXISTS players (
|
schema001 := `
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
-- migrations/001_initial_schema.up.sql
|
||||||
room_id TEXT,
|
|
||||||
username TEXT,
|
|
||||||
password TEXT NOT NULL DEFAULT '',
|
|
||||||
team TEXT,
|
|
||||||
role TEXT,
|
|
||||||
is_bot BOOLEAN
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE player_stats (
|
CREATE TABLE rooms (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
creator_name TEXT NOT NULL,
|
||||||
|
team_turn TEXT NOT NULL DEFAULT '',
|
||||||
|
this_turn_limit INTEGER NOT NULL DEFAULT 0,
|
||||||
|
opened_this_turn INTEGER NOT NULL DEFAULT 0,
|
||||||
|
blue_counter INTEGER NOT NULL DEFAULT 0,
|
||||||
|
red_counter INTEGER NOT NULL DEFAULT 0,
|
||||||
|
red_turn BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
mime_done BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
is_running BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
is_over BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
team_won TEXT NOT NULL DEFAULT '',
|
||||||
|
room_link TEXT NOT NULL DEFAULT ''
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE players (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
room_id TEXT, -- nullable
|
||||||
|
username TEXT NOT NULL UNIQUE,
|
||||||
|
password TEXT NOT NULL DEFAULT '',
|
||||||
|
team TEXT NOT NULL DEFAULT '', -- 'red' or 'blue'
|
||||||
|
role TEXT NOT NULL DEFAULT '', -- 'guesser' or 'mime'
|
||||||
|
is_bot BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
FOREIGN KEY (room_id) REFERENCES rooms(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE word_cards (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
room_id TEXT NOT NULL,
|
||||||
|
word TEXT NOT NULL,
|
||||||
|
color TEXT NOT NULL DEFAULT '',
|
||||||
|
revealed BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
mime_view BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
FOREIGN KEY (room_id) REFERENCES rooms(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE card_marks (
|
||||||
|
card_id INTEGER NOT NULL,
|
||||||
|
username TEXT NOT NULL,
|
||||||
|
FOREIGN KEY (card_id) REFERENCES word_cards(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (username) REFERENCES players(username) ON DELETE CASCADE,
|
||||||
|
PRIMARY KEY (card_id, username)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE actions (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
room_id TEXT NOT NULL,
|
||||||
|
actor TEXT NOT NULL,
|
||||||
|
actor_color TEXT NOT NULL DEFAULT '',
|
||||||
|
action_type TEXT NOT NULL,
|
||||||
|
word TEXT NOT NULL DEFAULT '',
|
||||||
|
word_color TEXT NOT NULL DEFAULT '',
|
||||||
|
number_associated TEXT NOT NULL DEFAULT '', -- for clues
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (room_id) REFERENCES rooms(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE settings (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
room_id TEXT NOT NULL,
|
||||||
|
language TEXT NOT NULL DEFAULT 'en',
|
||||||
|
room_pass TEXT NOT NULL DEFAULT '',
|
||||||
|
turn_time INTEGER NOT NULL DEFAULT 60, -- seconds
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (room_id) REFERENCES rooms(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE sessions(
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
lifetime INTEGER NOT NULL DEFAULT 3600,
|
||||||
|
token_key TEXT NOT NULL DEFAULT '' UNIQUE, -- encoded value
|
||||||
|
username TEXT NOT NULL,
|
||||||
|
FOREIGN KEY (username) REFERENCES players(username) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE journal(
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
entry TEXT NOT NULL DEFAULT '',
|
||||||
|
username TEXT NOT NULL,
|
||||||
|
room_id TEXT NOT NULL,
|
||||||
|
FOREIGN KEY (username) REFERENCES players(username) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (room_id) REFERENCES rooms(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE player_stats (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
username TEXT NOT NULL UNIQUE,
|
username TEXT NOT NULL UNIQUE,
|
||||||
games_played INTEGER NOT NULL DEFAULT 0,
|
games_played INTEGER NOT NULL DEFAULT 0,
|
||||||
@ -40,9 +121,32 @@ func setupPlayersTestDB(t *testing.T) (*sqlx.DB, func()) {
|
|||||||
played_as_guesser INTEGER NOT NULL DEFAULT 0,
|
played_as_guesser INTEGER NOT NULL DEFAULT 0,
|
||||||
FOREIGN KEY (username) REFERENCES players(username) ON DELETE CASCADE
|
FOREIGN KEY (username) REFERENCES players(username) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
`
|
||||||
|
_, err = db.Exec(schema001)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
`
|
schema002 := `
|
||||||
_, err = db.Exec(schema)
|
ALTER TABLE player_stats
|
||||||
|
ADD COLUMN rating REAL NOT NULL DEFAULT 1000.0;
|
||||||
|
|
||||||
|
CREATE TRIGGER update_player_rating
|
||||||
|
AFTER UPDATE OF games_played, games_won ON player_stats
|
||||||
|
WHEN NEW.games_played = OLD.games_played + 1
|
||||||
|
BEGIN
|
||||||
|
UPDATE player_stats
|
||||||
|
SET rating = OLD.rating +
|
||||||
|
32.0 * (
|
||||||
|
CASE
|
||||||
|
WHEN NEW.games_won = OLD.games_won + 1
|
||||||
|
THEN 1.0 - 0.5 -- Win term: 0.5
|
||||||
|
ELSE 0.0 - 0.5 -- Loss term: -0.5
|
||||||
|
END
|
||||||
|
) +
|
||||||
|
0.05 * (1000.0 - OLD.rating)
|
||||||
|
WHERE id = OLD.id;
|
||||||
|
END;
|
||||||
|
`
|
||||||
|
_, err = db.Exec(schema002)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
return db, func() {
|
return db, func() {
|
||||||
@ -50,6 +154,39 @@ func setupPlayersTestDB(t *testing.T) (*sqlx.DB, func()) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPlayerStatsRatingUpdate(t *testing.T) {
|
||||||
|
db, teardown := setupPlayersTestDB(t)
|
||||||
|
defer teardown()
|
||||||
|
|
||||||
|
username := "test_player_rating"
|
||||||
|
_, err := db.Exec(`INSERT INTO players (username) VALUES (?)`, username)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = db.Exec(`INSERT INTO player_stats (username, games_played, games_won, rating) VALUES (?, 0, 0, 1000.0)`, username)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Simulate a win
|
||||||
|
_, err = db.Exec(`UPDATE player_stats SET games_played = 1, games_won = 1 WHERE username = ?`, username)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
var ratingAfterWin float64
|
||||||
|
err = db.Get(&ratingAfterWin, `SELECT rating FROM player_stats WHERE username = ?`, username)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
// Expected: 1000 + 32 * (1 - 0.5) + 0.05 * (1000 - 1000) = 1000 + 16 = 1016
|
||||||
|
assert.InDelta(t, 1016.0, ratingAfterWin, 0.001)
|
||||||
|
|
||||||
|
// Simulate a loss
|
||||||
|
_, err = db.Exec(`UPDATE player_stats SET games_played = 2, games_won = 1 WHERE username = ?`, username)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
var ratingAfterLoss float64
|
||||||
|
err = db.Get(&ratingAfterLoss, `SELECT rating FROM player_stats WHERE username = ?`, username)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
// Expected: 1016 + 32 * (0 - 0.5) + 0.05 * (1000 - 1016) = 1016 - 16 + 0.05 * (-16) = 1000 - 0.8 = 999.2
|
||||||
|
assert.InDelta(t, 999.2, ratingAfterLoss, 0.001)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
func TestPlayersRepo_AddPlayer(t *testing.T) {
|
func TestPlayersRepo_AddPlayer(t *testing.T) {
|
||||||
db, teardown := setupPlayersTestDB(t)
|
db, teardown := setupPlayersTestDB(t)
|
||||||
defer teardown()
|
defer teardown()
|
||||||
|
Reference in New Issue
Block a user