2020-10-02 21:51:47 +02:00
import datetime
2020-09-28 03:40:13 +02:00
import math
from redbot . core import commands
from redbot . core import Config , checks
import discord
import re
2020-09-28 04:10:13 +02:00
LISTENING_CHANNEL = 620879085739966464
2020-09-28 03:40:13 +02:00
class SacrificeCog ( commands . Cog ) :
def __init__ ( self , bot ) :
self . bot = bot
self . config = Config . get_conf ( self , identifier = 154776548 )
default_global = {
" elo " : { } ,
2020-10-02 21:51:47 +02:00
" onevone_game_history " : { } , # {"0123456789": [{"date": now(), "opponent": "9876543210", "result": 1}]},
2020-09-28 03:40:13 +02:00
" tracked_msgs " : { }
}
self . config . register_global ( * * default_global )
@staticmethod
def is_valid_winloss ( s ) :
r = r " [wWlL]+ "
regx = re . compile ( r )
fm = regx . fullmatch ( s )
return fm
async def get_rank ( self , discord_id : str ) :
elos = await self . config . elo ( )
if discord_id not in elos :
return None , None
sorted_players = sorted ( elos , key = elos . get , reverse = True )
return sorted_players . index ( discord_id ) + 1 , len ( sorted_players )
@commands.Cog.listener ( )
async def on_raw_reaction_add ( self , payload ) :
tracked = await self . config . tracked_msgs ( )
if str ( payload . message_id ) in tracked . keys ( ) :
# fetch channel
2020-09-28 04:10:13 +02:00
channel = await self . bot . fetch_channel ( LISTENING_CHANNEL ) # channel
2020-09-28 03:40:13 +02:00
if not channel :
tracked . remove ( payload . message_id )
await self . config . tracked_msgs . set ( tracked )
return
# fetch msg
orig_msg = await channel . fetch_message ( payload . message_id )
if not orig_msg :
tracked . remove ( payload . message_id )
await self . config . tracked_msgs . set ( tracked )
return
opponents = orig_msg . mentions
if not opponents :
tracked . remove ( payload . message_id )
await self . config . tracked_msgs . set ( tracked )
return
opponent = opponents [ 0 ]
if str ( payload . user_id ) == str ( opponent . id ) and payload . emoji . name == " 👍 " :
outcome = orig_msg . content . split ( " " ) [ 2 ]
if SacrificeCog . is_valid_winloss ( outcome ) :
for c in outcome :
if c . lower ( ) == " w " :
await self . win ( str ( orig_msg . author . id ) , str ( opponent . id ) )
elif c . lower ( ) == " l " :
await self . win ( str ( opponent . id ) , str ( orig_msg . author . id ) )
new_elo = await self . get_elo ( str ( orig_msg . author . id ) )
new_elo_2 = await self . get_elo ( str ( opponent . id ) )
bot_msg = await channel . fetch_message ( tracked [ str ( payload . message_id ) ] )
await bot_msg . edit ( content = " ** %s **, your new ELO is: ** %s ** \n ** %s **, your new ELO is: ** %s ** " % (
orig_msg . author . mention , round ( new_elo ) , opponent . mention , round ( new_elo_2 ) )
)
del tracked [ str ( payload . message_id ) ]
await self . config . tracked_msgs . set ( tracked )
return
async def track_msg ( self , orig_message_id : str , bot_msg_id : str ) :
tracked = await self . config . tracked_msgs ( )
tracked [ orig_message_id ] = bot_msg_id
await self . config . tracked_msgs . set ( tracked )
@commands.command ( )
async def rank ( self , ctx , opponent : discord . Member = None , winloss : str = None ) :
2020-09-28 04:10:13 +02:00
if ctx . channel . id != LISTENING_CHANNEL :
return
2020-09-28 03:40:13 +02:00
author_id = str ( ctx . author . id )
if not opponent and not winloss :
2020-10-02 21:51:47 +02:00
# send elo and stats to player
2020-09-28 03:40:13 +02:00
rank , count = await self . get_rank ( author_id )
2020-10-02 21:51:47 +02:00
wins , total_games = await self . get_win_loss ( author_id )
if rank and total_games != 0 :
2020-09-28 03:40:13 +02:00
elo = await self . get_elo ( author_id )
2020-10-02 21:51:47 +02:00
await ctx . send ( " ** %s **, your ELO is: ** %s ** (ranked # %s / %s , %s wins/ %s games = %s win %% ) " % ( ctx . author . mention , round ( elo ) , rank , count , wins , total_games , round ( wins / total_games * 100 ) ) )
2020-09-28 03:40:13 +02:00
else :
await ctx . send ( " ** %s **, you are not ranked yet " % ctx . author . mention )
return
elif opponent and not winloss :
# show opponent rank
opponent_id = str ( opponent . id )
rank , count = await self . get_rank ( opponent_id )
2020-10-02 21:51:47 +02:00
wins , total_games = await self . get_win_loss ( opponent_id )
if rank and total_games != 0 :
2020-09-28 03:40:13 +02:00
elo = await self . get_elo ( opponent_id )
2020-10-02 21:51:47 +02:00
await ctx . send ( " ** %s ** ' ELO is: ** %s ** (ranked # %s / %s , %s wins/ %s games = %s win %% ) " % ( opponent . display_name , round ( elo ) , rank , count , wins , total_games , round ( wins / total_games * 100 ) ) )
2020-09-28 03:40:13 +02:00
else :
await ctx . send ( " ** %s ** is not ranked yet " % opponent . display_name )
return
elif opponent and winloss :
if not SacrificeCog . is_valid_winloss ( winloss ) :
await ctx . send ( " Invalid win/losses " )
return
2020-10-02 21:51:47 +02:00
if author_id == " 158370947097821185 " and str ( opponent . id ) == " 759952123713683516 " :
for c in winloss :
if c . lower ( ) == " w " :
await self . win ( author_id , str ( opponent . id ) )
elif c . lower ( ) == " l " :
await self . win ( str ( opponent . id ) , author_id )
await ctx . send ( " Ok master " )
else :
bot_msg = await ctx . send ( " Waiting for %s ' s :thumbsup: reaction to the **previous message**... " % opponent . display_name )
await self . track_msg ( str ( ctx . message . id ) , str ( bot_msg . id ) )
2020-09-28 03:40:13 +02:00
return
@staticmethod
def expection ( d ) :
2020-09-28 03:52:48 +02:00
# https://fr.wikipedia.org/wiki/Classement_Elo#Relation_entre_diff%C3%A9rence_de_points_Elo_et_probabilit%C3%A9_de_gain
return 1 / ( 1 + math . pow ( 10 , - d / 400 ) )
2020-09-28 03:40:13 +02:00
2020-10-02 21:51:47 +02:00
async def get_win_loss ( self , discord_id : str ) :
history = await self . config . onevone_game_history ( )
if discord_id not in history :
return 0 , 0
else :
user_history = history [ discord_id ]
results = [ match . result for match in user_history ]
return sum ( results ) , len ( results )
2020-09-28 03:40:13 +02:00
async def win ( self , winner_id : str , looser_id : str ) - > None :
elo_winner = await self . get_elo ( winner_id )
elo_looser = await self . get_elo ( looser_id )
diff = elo_winner - elo_looser
expection_winner = SacrificeCog . expection ( diff )
expection_looser = SacrificeCog . expection ( - diff )
k_winner = 40 # TODO: should be lowered after many games
k_looser = 40 # TODO: should be lowered after many games
new_elo_winner = elo_winner + k_winner * ( 1 - expection_winner )
new_elo_looser = elo_looser + k_looser * ( 0 - expection_looser )
2020-10-02 21:51:47 +02:00
await self . save_history ( winner_id , looser_id )
2020-09-28 03:40:13 +02:00
await self . save_elo ( { winner_id : new_elo_winner , looser_id : new_elo_looser } )
async def save_elo ( self , scores : dict ) - > None :
elos = await self . config . elo ( )
elos . update ( scores )
await self . config . elo . set ( elos )
2020-10-02 21:51:47 +02:00
async def save_history ( self , winner_id : str , looser_id : str ) :
history = await self . config . onevone_game_history ( )
winner_result = { " date " : datetime . datetime . now ( ) , " opponent " : looser_id , " result " : 1 }
looser_result = { " date " : datetime . datetime . now ( ) , " opponent " : winner_id , " result " : 0 }
if winner_id not in history :
history [ winner_id ] = [ winner_result ]
else :
history [ winner_id ] . append ( winner_result )
if looser_id not in history :
history [ looser_id ] = [ looser_result ]
else :
history [ looser_id ] . append ( looser_result )
await self . config . onevone_game_history . set ( history )
2020-09-28 03:40:13 +02:00
async def get_elo ( self , discord_id : str ) - > int :
elos = await self . config . elo ( )
if discord_id in elos :
return elos [ discord_id ]
else :
2020-09-28 03:52:48 +02:00
# set new elo = 1500
2020-09-28 03:40:13 +02:00
return 1500