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; | ||||
|  | ||||
| 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 | ||||
| BEGIN | ||||
|     UPDATE player_stats | ||||
|   | ||||
| @@ -14,15 +14,96 @@ func setupPlayersTestDB(t *testing.T) (*sqlx.DB, func()) { | ||||
| 	db, err := sqlx.Connect("sqlite3", ":memory:") | ||||
| 	assert.NoError(t, err) | ||||
|  | ||||
| 	schema := ` | ||||
| 	CREATE TABLE IF NOT EXISTS players ( | ||||
| 	// Load schema from migration files | ||||
| 	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, | ||||
| 		room_id TEXT, | ||||
| 		username TEXT, | ||||
|     room_id TEXT, -- nullable | ||||
|     username TEXT NOT NULL UNIQUE, | ||||
|     password TEXT NOT NULL DEFAULT '', | ||||
| 		team TEXT, | ||||
| 		role TEXT, | ||||
| 		is_bot BOOLEAN | ||||
|     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 ( | ||||
| @@ -40,9 +121,32 @@ func setupPlayersTestDB(t *testing.T) (*sqlx.DB, func()) { | ||||
|     played_as_guesser INTEGER NOT NULL DEFAULT 0, | ||||
|     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) | ||||
|  | ||||
| 	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) { | ||||
| 	db, teardown := setupPlayersTestDB(t) | ||||
| 	defer teardown() | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Grail Finder
					Grail Finder