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,15 +14,96 @@ 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 := ` | ||||||
|  | -- migrations/001_initial_schema.up.sql | ||||||
|  |  | ||||||
|  | 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, |     id INTEGER PRIMARY KEY AUTOINCREMENT, | ||||||
| 		room_id TEXT, |     room_id TEXT, -- nullable | ||||||
| 		username TEXT, |     username TEXT NOT NULL UNIQUE, | ||||||
|     password TEXT NOT NULL DEFAULT '', |     password TEXT NOT NULL DEFAULT '', | ||||||
| 		team TEXT, |     team TEXT NOT NULL DEFAULT '', -- 'red' or 'blue' | ||||||
| 		role TEXT, |     role TEXT NOT NULL DEFAULT '', -- 'guesser' or 'mime' | ||||||
| 		is_bot BOOLEAN |     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 ( | CREATE TABLE player_stats ( | ||||||
| @@ -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(schema) | 	_, err = db.Exec(schema001) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 	schema002 := ` | ||||||
|  | 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
	 Grail Finder
					Grail Finder