From 13ddb3b25b0bb097a39979eb6e40c2bfde211571 Mon Sep 17 00:00:00 2001 From: spla Date: Mon, 21 Dec 2020 10:23:04 +0100 Subject: [PATCH] New feature! Added Elo rating system! --- README.md | 4 +- app/locales/ca.txt | 1 + app/locales/en.txt | 1 + app/locales/es.txt | 1 + app/locales/fr.txt | 1 + db-setup.py | 2 +- mastochess.py | 133 +++++++++++++++++++++++++++++++++++++++------ 7 files changed, 123 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 69003f0..936a9d8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Mastodon Chess Play with other fediverse users a Chess game! Mastodon Chess control games, players and boards and even it post, graphically, every move to both players! Mastodon Chess (mastochess) uses [python-chess](https://python-chess.readthedocs.io/en/latest/) library. +Mastodon Chess uses Elo rating system to calculate the relative skill levels of fediverse players! ### How to play: @@ -99,4 +100,5 @@ Within Python Virtual Environment: 04.12.2020 - New feature! Now players can claim a draw. 05.12.2020 - New feature! Add panel stats. 19.12.2020 - New feature! Now you can configure bot's language! -19.12.2020 - New feature! Added french language! +19.12.2020 - New feature! Added french language! +21.12.2020 - New feature! Added Elo rating system! diff --git a/app/locales/ca.txt b/app/locales/ca.txt index e86bfe2..36ecd28 100644 --- a/app/locales/ca.txt +++ b/app/locales/ca.txt @@ -71,3 +71,4 @@ post_my_panel_str: panell (publica les estadístiques) locale_change_successfully: llengua canviada amb èxit a locale_not_changed: encara no és suportada :-( change_lang_str: conf ca (per a configurar el bot en català) +panel_elo_rating_str: Elo diff --git a/app/locales/en.txt b/app/locales/en.txt index 70cb02d..8718dd1 100644 --- a/app/locales/en.txt +++ b/app/locales/en.txt @@ -71,3 +71,4 @@ post_my_panel_str: panel (post player stats) locale_change_successfully: language sucessfully changed to locale_not_changed: is not supported yet :-( change_lang_str: conf en (to configure the bot in english) +panel_elo_rating_str: Elo diff --git a/app/locales/es.txt b/app/locales/es.txt index 82e8b74..fea1af5 100644 --- a/app/locales/es.txt +++ b/app/locales/es.txt @@ -71,3 +71,4 @@ post_my_panel_str: panel (publica el panel de datos) locale_change_successfully: idioma cambiado con éxito a locale_not_changed: no es soportado aún :-( change_lang_str: conf es (para configurar el bot en castellano) +panel_elo_rating_str: Elo diff --git a/app/locales/fr.txt b/app/locales/fr.txt index 56f027f..de86e82 100644 --- a/app/locales/fr.txt +++ b/app/locales/fr.txt @@ -71,3 +71,4 @@ post_my_panel_str: panneau (publie les statistiques) locale_change_successfully: langue modifiée avec succès en locale_not_changed: n'est pas encore pris en charge :-( change_lang_str: conf fr (pour configurer le bot en français) +panel_elo_rating_str: Elo diff --git a/db-setup.py b/db-setup.py index bf8763c..10c03f0 100644 --- a/db-setup.py +++ b/db-setup.py @@ -169,7 +169,7 @@ if __name__ == '__main__': create_table(db, db_user, table, sql) table = "players" - sql = "create table "+table+" (player_id bigint PRIMARY KEY, player_name varchar(40), lang varchar(2))" + sql = "create table "+table+" (player_id bigint PRIMARY KEY, player_name varchar(40), lang varchar(2), elo_rating float)" create_table(db, db_user, table, sql) ############################################################ diff --git a/mastochess.py b/mastochess.py index cc88d15..b848417 100644 --- a/mastochess.py +++ b/mastochess.py @@ -20,7 +20,7 @@ import chess.svg from cairosvg import svg2png import chess.pgn from PIL import Image, ImageFont, ImageDraw -import lichess.api +import math def cleanhtml(raw_html): cleanr = re.compile('<.*?>') @@ -31,7 +31,48 @@ def unescape(s): s = s.replace("'", "'") return s -def create_panel(username, played_games, wins): +# Function to calculate the Probability +def Probability(rating1, rating2): + + return 1.0 * 1.0 / (1 + 1.0 * math.pow(10, 1.0 * (rating1 - rating2) / 400)) + +# Function to calculate Elo rating +# K is a constant. +# d determines whether +# Player A wins or Player B. +def EloRating(Ra, Rb, K, d): + + # To calculate the Winning + # Probability of Player B + Pb = Probability(Ra, Rb) + + # To calculate the Winning + # Probability of Player A + Pa = Probability(Rb, Ra) + + # Case -1 When Player A wins + # Updating the Elo Ratings + if (d == 1) : + Ra = Ra + K * (1 - Pa) + Rb = Rb + K * (0 - Pb) + + # Case -2 When Player B wins + # Updating the Elo Ratings + else : + Ra = Ra + K * (0 - Pa) + Rb = Rb + K * (1 - Pb) + + Ra = round(Ra, 6) + Rb = round(Rb, 6) + print("Updated Ratings:-") + print("Ra =", Ra," Rb =", Rb) + + return(Ra, Rb) + + # This code is contributed by + # Smitha Dinesh Semwal + +def create_panel(username, rating, played_games, wins): if played_games > 0 and wins > 0: @@ -77,10 +118,12 @@ def create_panel(username, played_games, wins): panel_games_str = get_locale("panel_games_str", player_lang) panel_wins_str = get_locale("panel_wins_str", player_lang) panel_ratio_str = get_locale("panel_ratio_str", player_lang) + panel_elo_rating_str = get_locale("panel_elo_rating_str", player_lang) - draw.text((y+70,x+120), panel_games_str + ': ' + str(played_games), font=fnt, fill=(255,255,255,220)) #fill=(255,255,255,255)) ## full opacity - draw.text((y+70,x+170), panel_wins_str + ': ' + str(wins), font=fnt, fill=(255,255,255,220)) #fill=(255,255,255,255)) ## full opacity - draw.text((y+70,x+220), panel_ratio_str + ': ' + str(ratio) + '%', font=fnt, fill=(255,255,255,220)) + draw.text((y+70,x+80), panel_games_str + ': ' + str(played_games), font=fnt, fill=(255,255,255,220)) #fill=(255,255,255,255)) ## full opacity + draw.text((y+70,x+130), panel_wins_str + ': ' + str(wins), font=fnt, fill=(255,255,255,220)) #fill=(255,255,255,255)) ## full opacity + draw.text((y+70,x+180), panel_ratio_str + ': ' + str(ratio) + '%', font=fnt, fill=(255,255,255,220)) + draw.text((y+70,x+230), panel_elo_rating_str + ': ' + str(round(rating)), font=fnt, fill=(255,255,255,220)) fnt = ImageFont.truetype('app/fonts/DroidSans.ttf', 15, layout_engine=ImageFont.LAYOUT_BASIC) @@ -978,6 +1021,8 @@ def claim_draw(username): def close_game(username, checkmate): + d = 0 + try: conn = None @@ -996,6 +1041,30 @@ def close_game(username, checkmate): black_player = row[1] + cur.execute("select elo_rating from players where player_name = (%s)", (white_player,)) + + row = cur.fetchone() + + if row[0] != None: + + white_rating = row[0] + + else: + + white_rating = 1500 + + cur.execute("select elo_rating from players where player_name = (%s)", (black_player,)) + + row = cur.fetchone() + + if row[0] != None: + + black_rating = row[0] + + else: + + black_rating = 1500 + cur.close() except (Exception, psycopg2.DatabaseError) as error: @@ -1028,16 +1097,28 @@ def close_game(username, checkmate): winner = username + if winner == white_player: + + d = 1 + else: if query_word == search_end and username == white_user and stalemate == False: winner = black_user + d = 2 + elif query_word == search_end and username == black_user and stalemate == False: winner = white_user + d = 1 + + K = 30 + + new_white_rating, new_black_rating = EloRating(white_rating, black_rating, K, d) + try: conn = None @@ -1050,6 +1131,10 @@ def close_game(username, checkmate): cur.execute("update stats set winner=(%s), finished=(%s), updated_at=(%s) where game_id=(%s)", (winner, finished, now, game_id)) + cur.execute("update players set elo_rating=(%s) where player_name=(%s)", (new_white_rating, white_user)) + + cur.execute("update players set elo_rating=(%s) where player_name=(%s)", (new_black_rating, black_user)) + conn.commit() cur.close() @@ -1094,9 +1179,21 @@ def get_stats(player): wins = row[0] + cur.execute("select elo_rating from players where player_name = (%s)", (player,)) + + row = cur.fetchone() + + if row[0] != None: + + rating = row[0] + + else: + + rating = 1500 + cur.close() - return (played_games, wins) + return (rating, played_games, wins) except (Exception, psycopg2.DatabaseError) as error: @@ -1745,9 +1842,9 @@ if __name__ == '__main__': elif query_word == search_panel: - played_games, wins = get_stats(username) + rating, played_games, wins = get_stats(username) - create_panel(username, played_games, wins) + create_panel(username, rating, played_games, wins) toot_text = '@'+username @@ -1969,11 +2066,11 @@ if __name__ == '__main__': toot_text += '\n' + winned_games + "\n" - played_games, wins = get_stats(username) + rating, played_games, wins = get_stats(username) wins_of_many = get_locale("wins_of_many", player_lang1) - toot_text += username + ': ' + str(wins) + ' ' + wins_of_many + ' ' + str(played_games) + "\n" + toot_text += username + ': ' + str(wins) + ' ' + wins_of_many + ' ' + str(played_games) + ' (Elo: ' + str(round(rating)) + ')' + '\n' player_lang2 = get_lang(playing_user) @@ -1981,7 +2078,7 @@ if __name__ == '__main__': toot_text += "\n@"+playing_user + ': ' + well_done + "\n" - played_games, wins = get_stats(playing_user) + rating, played_games, wins = get_stats(playing_user) winned_games = get_locale("winned_games", player_lang2) @@ -1989,7 +2086,7 @@ if __name__ == '__main__': toot_text += '\n\n' + winned_games + "\n" - toot_text += playing_user + ': ' + str(wins) + ' ' + wins_of_many + ' ' + str(played_games) + "\n" + toot_text += playing_user + ': ' + str(wins) + ' ' + wins_of_many + ' ' + str(played_games) + ' (Elo: ' + str(round(rating)) + ')' + '\n' elif check == False and stalemate == True: @@ -1999,11 +2096,11 @@ if __name__ == '__main__': toot_text += '\n' + winned_games + "\n" - played_games, wins = get_stats(username) + rating, played_games, wins = get_stats(username) toot_text += username + ': ' + str(wins) + ' ' + wins_of_many + ' ' + str(played_games) + "\n" - played_games, wins = get_stats(playing_user) + rating, played_games, wins = get_stats(playing_user) toot_text += playing_user + ': ' + str(wins) + ' ' + wins_of_many + ' ' + str(played_games) + "\n" @@ -2258,7 +2355,7 @@ if __name__ == '__main__': toot_text += '\n' + winned_games + "\n" - played_games, wins = get_stats(white_player) + rating, played_games, wins = get_stats(white_player) toot_text += white_player + ': ' + str(wins) + ' ' + wins_of_many + ' ' + str(played_games) + "\n" @@ -2274,7 +2371,7 @@ if __name__ == '__main__': toot_text += '\n' + winned_games + "\n" - played_games, wins = get_stats(black_player) + rating, played_games, wins = get_stats(black_player) toot_text += black_player + ': ' + str(wins) + ' ' + wins_of_many + ' ' + str(played_games) + "\n" @@ -2288,9 +2385,9 @@ if __name__ == '__main__': elif query_word == search_panel: - played_games, wins = get_stats(username) + rating, played_games, wins = get_stats(username) - create_panel(username, played_games, wins) + create_panel(username, rating, played_games, wins) toot_text = '@'+username