sacrifice-bot/cogs/sacrifice/sacrifice.py

183 lines
7.8 KiB
Python

import datetime
import math
from redbot.core import commands
from redbot.core import Config, checks
import discord
import re
LISTENING_CHANNEL = 620879085739966464
class SacrificeCog(commands.Cog):
def __init__(self, bot):
self.bot = bot
self.config = Config.get_conf(self, identifier=154776548)
default_global = {
"elo": {},
"onevone_game_history": {}, # {"0123456789": [{"date": now(), "opponent": "9876543210", "result": 1}]},
"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
channel = await self.bot.fetch_channel(LISTENING_CHANNEL) # channel
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):
if ctx.channel.id != LISTENING_CHANNEL:
return
author_id = str(ctx.author.id)
if not opponent and not winloss:
# send elo and stats to player
rank, count = await self.get_rank(author_id)
wins, total_games = await self.get_win_loss(author_id)
if rank and total_games != 0:
elo = await self.get_elo(author_id)
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)))
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)
wins, total_games = await self.get_win_loss(opponent_id)
if rank and total_games != 0:
elo = await self.get_elo(opponent_id)
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)))
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
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))
return
@staticmethod
def expection(d):
# 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))
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)
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)
await self.save_history(winner_id, looser_id)
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)
async def save_history(self, winner_id: str, looser_id: str):
history = await self.config.onevone_game_history()
winner_result = {"date": datetime.datetime.now().isoformat(), "opponent": looser_id, "result": 1}
looser_result = {"date": datetime.datetime.now().isoformat(), "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)
async def get_elo(self, discord_id: str) -> int:
elos = await self.config.elo()
if discord_id in elos:
return elos[discord_id]
else:
# set new elo = 1500
return 1500