Variant ratings: Constant sum of rating

Whenever a variants gains/loses points now,
we ensure that the sum of all variant ratings of that type stays
constant by adjusting all other variants in the opposite direction.
This commit is contained in:
Maximilian Keßler 2023-12-22 11:20:58 +01:00
parent 707c1814ef
commit 4b760e5f84
Signed by: max
GPG key ID: BCC5A619923C0BA5
3 changed files with 45 additions and 6 deletions

View file

@ -263,14 +263,14 @@ CREATE TABLE variant_base_ratings (
DROP TABLE IF EXISTS variant_ratings CASCADE; DROP TABLE IF EXISTS variant_ratings CASCADE;
CREATE TABLE variant_ratings ( CREATE TABLE variant_ratings (
/** This should reference the game that triggered the elo update */ /** This should reference the game that triggered the elo update */
league_id INTEGER NOT NULL REFERENCES games (league_id), league_id INTEGER NOT NULL REFERENCES games (league_id),
variant_id SMALLINT NOT NULL REFERENCES variants (id), variant_id SMALLINT NOT NULL REFERENCES variants (id),
num_players SMALLINT NOT NULL, num_players SMALLINT NOT NULL,
change REAL NOT NULL, change REAL NOT NULL,
value_after REAL NOT NULL, value_after REAL NOT NULL,
PRIMARY KEY (league_id, variant_id) PRIMARY KEY (league_id, variant_id, num_players)
); );
DROP TABLE IF EXISTS user_base_ratings CASCADE; DROP TABLE IF EXISTS user_base_ratings CASCADE;

View file

@ -43,7 +43,7 @@ FORBIDDEN_GAME_OPTIONS = [
# Cache time (in seconds) for history requests of players # Cache time (in seconds) for history requests of players
# In case of frequent reruns (especially during development), we do not want to stress the server too much. # In case of frequent reruns (especially during development), we do not want to stress the server too much.
USER_HISTORY_CACHE_TIME = 5 * 60 USER_HISTORY_CACHE_TIME = 60 * 60
# Fraction of seeds which is assumed to be unwinnable # Fraction of seeds which is assumed to be unwinnable
UNWINNABLE_SEED_FRACTION = 0.02 UNWINNABLE_SEED_FRACTION = 0.02

View file

@ -196,13 +196,52 @@ def process_rating_of_next_game() -> bool:
# Calculate changes in rating # Calculate changes in rating
user_changes, variant_change = compute_rating_changes(user_ratings, games_played, variant_rating, score == 5 * num_suits) user_changes, variant_change = compute_rating_changes(user_ratings, games_played, variant_rating, score == 5 * num_suits)
# Update database for variants # Update database for the played variant
cur.execute("INSERT INTO variant_ratings (league_id, variant_id, num_players, change, value_after) " cur.execute("INSERT INTO variant_ratings (league_id, variant_id, num_players, change, value_after) "
"VALUES (%s, %s, %s, %s, %s)", "VALUES (%s, %s, %s, %s, %s)",
(league_id, variant_id, num_players, variant_change, variant_rating + variant_change) (league_id, variant_id, num_players, variant_change, variant_rating + variant_change)
) )
# Note: We do not commit here, only after players have been processed as well # Note: We do not commit here, only after players have been processed as well
# Now, we get a list of all other variants of the same rating type, we will also need to update these.
cur.execute(
"SELECT DISTINCT ON (variants.id, variant_base_ratings.num_players)"
" variants.id,"
" variant_base_ratings.num_players,"
" COALESCE(variant_ratings.value_after, variant_base_ratings.rating) AS current_rating "
"FROM variants "
"LEFT OUTER JOIN variant_base_ratings"
" ON variants.id = variant_base_ratings.variant_id "
"LEFT OUTER JOIN variant_ratings "
" ON variant_ratings.variant_id = variant_base_ratings.variant_id"
" AND variant_ratings.num_players = variant_base_ratings.num_players "
"WHERE"
" variants.rating_type = %s"
" AND NOT"
" (variants.id = %s AND variant_base_ratings.num_players = %s)"
"GROUP BY ("
" variants.id, variant_base_ratings.num_players, variant_base_ratings.rating,"
" variant_ratings.league_id, variant_ratings.value_after, variant_base_ratings.num_players"
" ) "
"ORDER BY variants.id, variant_base_ratings.num_players, league_id DESC",
(rating_type, variant_id, num_players)
)
# This ensures that the sum of ratings of variants of the same type stays constant (up to rounding).
variants_of_same_rating_type = cur.fetchall()
other_variant_change = -variant_change / len(variants_of_same_rating_type)
other_variant_vals = []
for (other_variant_id, other_num_players, current_rating) in variants_of_same_rating_type:
other_variant_vals.append((league_id, other_variant_id, other_num_players, other_variant_change, current_rating + other_variant_change))
# This updates the variants now
psycopg2.extras.execute_values(
cur,
"INSERT INTO variant_ratings (league_id, variant_id, num_players, change, value_after) "
"VALUES %s",
other_variant_vals
)
user_ratings_vals = [] user_ratings_vals = []
for user_id, change in user_changes.items(): for user_id, change in user_changes.items():
user_ratings_vals.append((league_id, user_id, rating_type, change, user_ratings[user_id] + change)) user_ratings_vals.append((league_id, user_id, rating_type, change, user_ratings[user_id] + change))