Initial commit

This commit is contained in:
2022-03-31 19:44:24 +02:00
commit 5998d74a37
16 changed files with 1597 additions and 0 deletions

0
cambot/__init__.py Normal file
View File

394
cambot/codenames.py Normal file
View File

@@ -0,0 +1,394 @@
import random
import re
import os
from collections import Counter
from itertools import cycle
from enum import auto
from .emojis import *
from .utils import serialize, EnumZero
from .settings import *
class Teams(EnumZero):
RED = auto()
BLUE = auto()
NEUTRAL = auto()
BOMB = auto()
class Roles(EnumZero):
CHOOSER = auto()
GUESSER = auto()
class Recipients(EnumZero):
EVERYONE = auto()
CHOOSERS = auto()
class Modes(EnumZero):
COOPERATION = auto()
COMPETITION = auto()
class Statuses(EnumZero):
RED_CHOOSER = auto()
RED_GUESSER = auto()
BLUE_CHOOSER = auto()
BLUE_GUESSER = auto()
NEUTRAL_CHOOSER = auto()
NEUTRAL_GUESSER = auto()
CYCLES = {Modes.COOPERATION: cycle([Statuses.NEUTRAL_CHOOSER, Statuses.NEUTRAL_GUESSER]),
Modes.COMPETITION: cycle([Statuses.RED_CHOOSER, Statuses.RED_GUESSER, Statuses.BLUE_CHOOSER, Statuses.BLUE_GUESSER])}
CONFIGURATIONS = [Counter([Statuses.NEUTRAL_CHOOSER, Statuses.NEUTRAL_GUESSER]), # coop
Counter([Statuses.NEUTRAL_CHOOSER, Statuses.NEUTRAL_GUESSER, Statuses.NEUTRAL_GUESSER]), # coop
Counter([Statuses.RED_CHOOSER, Statuses.BLUE_CHOOSER, Statuses.NEUTRAL_GUESSER]),
Counter([Statuses.RED_CHOOSER, Statuses.RED_GUESSER, Statuses.BLUE_CHOOSER, Statuses.BLUE_GUESSER]),
Counter([Statuses.RED_CHOOSER, Statuses.RED_GUESSER, Statuses.BLUE_CHOOSER, Statuses.BLUE_GUESSER, Statuses.NEUTRAL_GUESSER]),
Counter([Statuses.RED_CHOOSER, Statuses.RED_GUESSER, Statuses.RED_GUESSER, Statuses.BLUE_CHOOSER, Statuses.BLUE_GUESSER, Statuses.BLUE_GUESSER])]
SYMBOLS = {Statuses.RED_CHOOSER: f"{REDDOT}{SPEAKING}",
Statuses.RED_GUESSER: f"{REDDOT}{GLASS}",
Statuses.BLUE_CHOOSER: f"{BLUEDOT}{SPEAKING}",
Statuses.BLUE_GUESSER: f"{BLUEDOT}{GLASS}",
Statuses.NEUTRAL_CHOOSER: f"{WHITEDOT}{SPEAKING}",
Statuses.NEUTRAL_GUESSER: f"{WHITEDOT}{GLASS}"}
class Game:
def __init__(self, channel):
self.channel = channel
self.reset(keep_players=False)
async def say(self, message):
await self.channel.send(message)
def get_player(self, client):
try:
player = next(p for p in self.players if p.client == client)
except StopIteration:
player = None
return player
def current_team(self):
return Teams(self.current_step.value // 2)
def current_role(self):
return Roles(self.current_step.value % 2)
def reset(self, keep_players=True):
self.playing = False
self.mode = None
self.steps = None
self.current_step = None
if not keep_players:
self.players = []
self.words = []
self.clue = None
self.number = None
self.turn = 0
class Player:
def __init__(self, client, team, role):
self.client = client
self.team = team
self.role = role
@property
def status(self):
return Statuses((2 * self.team.value) + self.role.value)
class Word:
def __init__(self, label, team):
self.label = label
self.team = team
self.guessed = False
async def maybe_join(game, message, team):
if game.playing:
await game.say(f"{message.author.mention} Une partie est en cours, impossible de rejoindre une équipe.")
return
if len([p for p in game.players if p.team == team]) > 0:
role = Roles.GUESSER
else:
role = Roles.CHOOSER
player = game.get_player(message.author)
if player:
if player.team == team:
await game.say(f"{message.author.mention} Tu fais déjà partie de cette équipe.")
return
else:
player.team = team
player.role = role
else:
player = Player(client=message.author, team=team, role=role)
game.players.append(player)
await game.say(f"{message.author.mention} Équipe {('rouge', 'bleue', 'neutre')[team.value]} rejointe, rôle : {('choisisseur', 'devineur')[role.value]}.")
async def maybe_change_role(game, message):
wannabe = game.get_player(message.author)
if not wannabe:
await game.say(f"{message.author.mention} Tu n'es dans aucune équipe.")
return
if game.playing:
await game.say(f"{message.author.mention} Une partie est en cours, impossible de changer de rôle.")
return
wannabe.role = Roles(int(not(wannabe.role.value)))
await game.say(f"{message.author.mention} est désormais {('choisisseur', 'devineur')[wannabe.role.value]} de l'équipe {('rouge', 'bleue', 'neutre')[wannabe.team.value]}.")
async def print_teams(game):
if not game.players:
await game.say(f"Aucun joueur inscrit.")
return
output = ""
for team in (Teams.RED, Teams.BLUE, Teams.NEUTRAL):
output += f"{(REDDOT, BLUEDOT, WHITEDOT)[team.value]} Équipe {('rouge', 'bleue', 'neutre')[team.value]} :\n"
for player in [p for p in game.players if p.team == team]:
output += f"--- {(SPEAKING, GLASS)[player.role.value]} {player.client.name} ({('choisisseur', 'devineur')[player.role.value]})\n"
await game.say(output)
async def print_configurations(game, with_error=False):
output = ""
if with_error:
output += "La configuration actuelle des joueurs ne correspond à aucune configuration de jeu possible.\n\n"
output += "__Configurations possibles__\n\n"
possible_nb_of_players = sorted(set(sum(c.values()) for c in CONFIGURATIONS))
for nb_players in possible_nb_of_players:
output += f"**{nb_players} joueur{'s' if nb_players > 1 else ''}**\n"
configurations = [c for c in CONFIGURATIONS if sum(c.values()) == nb_players]
for configuration in configurations:
statuses = list(configuration.elements())
competition = any(e in [0, 1, 2, 3] for e in statuses)
output += " + ".join([SYMBOLS[status] for status in statuses])
if competition:
output += f" (compétition {SWORDS})"
else:
output += f" (coopération {HANDSHAKE})"
output += "\n"
output += "\n"
await game.say(output)
async def print_grid(game, revealed=False, to=Recipients.EVERYONE):
output = ""
if to == Recipients.CHOOSERS:
output += "---------- Grille actuelle : ----------\n"
widths = []
for column in range(5):
words_in_column = [word for (idx, word) in enumerate(game.words) if idx % 5 == column]
widths.append(max(len(w.label) for w in words_in_column))
for y in range(5):
for x in range(5):
word = game.words[y*5 + x]
label = word.label + (" " * (widths[x] - len(word.label) + 5))
label = f"`{label}`"
if revealed and word.guessed:
label = f"~~{label}~~"
output += f"{(REDDOT, BLUEDOT, WHITEDOT, SKULL)[word.team.value] if (revealed or word.guessed) else QUESTION}{label}"
output += "\n"
if to == Recipients.EVERYONE:
await game.say(output)
elif to == Recipients.CHOOSERS:
choosers = [p for p in game.players if p.role == Roles.CHOOSER]
for chooser in choosers:
await chooser.client.send(output)
async def maybe_leave(game, message):
player = game.get_player(message.author)
if not player:
await game.say(f"{message.author.mention} Tu n'es dans aucune équipe.")
return
if game.playing:
await game.say(f"{message.author.mention} Une partie est en cours, impossible quitter l'équipe.")
return
team = player.team
game.players.remove(player)
await game.say(f"{message.author.mention} est parti de l'équipe {('rouge', 'bleue', 'neutre')[team.value]}.")
async def maybe_start(game):
if not game.players:
await game.say(f"Aucun joueur inscrit.")
return
configuration = Counter([p.status for p in game.players])
if not configuration in CONFIGURATIONS:
await print_configurations(game, with_error=True)
return
game.playing = True
if any(p.team in (Teams.RED, Teams.BLUE) for p in game.players):
game.mode = Modes.COMPETITION
else:
game.mode = Modes.COOPERATION
game.steps = CYCLES[game.mode]
with open(CODENAMES_WORDS, "r") as f:
lexicon = f.read().splitlines()
game.words = [Word(label=word, team=Teams.NEUTRAL) for word in random.sample(lexicon, 25)]
if game.mode == Modes.COMPETITION:
for idx in range(0, 9):
game.words[idx].team = Teams.RED
for idx in range(9, 17):
game.words[idx].team = Teams.BLUE
game.words[17].team = Teams.BOMB
elif game.mode == Modes.COOPERATION:
for idx in range(0, 12):
game.words[idx].team = Teams.BOMB
random.shuffle(game.words)
await proceed(game)
async def proceed(game):
if game.current_step:
scores = [len([w for w in game.words if w.guessed and w.team == team]) for team in (Teams.RED, Teams.BLUE, Teams.NEUTRAL)]
if any(w for w in game.words if w.guessed and w.team == Teams.BOMB):
if game.mode == Modes.COOPERATION:
await game.say(f"{SAD} La bombe a gagné. Score final des neutres : **{scores[2]}/12** en {game.turn} tours.")
elif game.mode == Modes.COMPETITION:
winners = int(not(game.current_team()))
await game.say(f"{PARTY} L'équipe {('rouge', 'bleue')[winners]} a gagné pour cause d'explosion de l'équipe adverse !")
game.reset()
return
for team in (Teams.RED, Teams.BLUE, Teams.NEUTRAL):
if [w for w in game.words if w.team == team] and not any(w for w in game.words if w.guessed == False and w.team == team):
await game.say(f"{PARTY} L'équipe {('rouge', 'bleue', 'neutre')[team.value]} a gagné ! Elle a trouvé tous ses mots.")
game.reset()
return
game.current_step = next(game.steps)
current_players = [p.client.mention for p in game.players if p.status == game.current_step]
if game.current_role() == Roles.CHOOSER:
game.turn += 1
if game.turn == 1:
await print_grid(game, revealed=False, to=Recipients.EVERYONE)
await print_grid(game, revealed=True, to=Recipients.CHOOSERS)
await game.say(f"{SYMBOLS[game.current_step]}"
f" C'est au choisisseur de l'équipe"
f" {('rouge', 'bleue', 'neutre')[game.current_team().value]}"
f" ({', '.join(current_players)}) de m'indiquer"
f" **en privé** un indice et un nombre de mots souhaité.")
return
if game.current_role() == Roles.GUESSER:
neutral_guessers = [p.client.mention for p in game.players if p.status == Statuses.NEUTRAL_GUESSER]
current_players += neutral_guessers
current_players = set(current_players)
plural = len(current_players) > 1
await game.say(f"{SYMBOLS[game.current_step]}"
f" C'est au{('','x')[plural]} devineur{('','s')[plural]} de l'équipe"
f" {('rouge', 'bleue', 'neutre')[game.current_team().value]}"
f" ({', '.join(current_players)}) de retrouver la sélection"
f" effectuée par le choisisseur. L'indice est **{game.clue}**."
f" Nombre de mots à trouver : **{game.number}**")
async def process_whisper(game, message):
player = game.get_player(message.author)
if game.current_step != player.status:
await message.author.send("Ce n'est pas à toi de me parler pour l'instant.")
return
content = message.content.strip()
if game.current_role() == Roles.CHOOSER:
if regex := re.search(r"^([^ ]+) (\d+)$", content, re.I):
maybe_number = int(regex.group(2))
number_max = len([w for w in game.words if w.guessed == False and w.team == game.current_team()])
if not (1 <= maybe_number <= number_max):
await message.author.send(f"Nombre invalide (doit être entre 1 et {number_max}).")
return
game.number = maybe_number
maybe_clue = regex.group(1)
if any(w for w in game.words if serialize(w.label) == serialize(maybe_clue)):
await message.author.send(f"L'indice ne peut pas être un des mots de la grille !")
return
game.clue = maybe_clue
await message.author.send(f"OK, ça continue sur {game.channel.mention} !")
await proceed(game)
return
else:
await message.author.send("Choisis secrètement un ou plusieurs mots, puis envoie (ici)"
" `<indice> <nombre>`, par exemple `caisson 3`."
" L'indice doit être un seul mot, et il ne doit pas être de"
" la même famille qu'un mot de la grille !")
return
if game.current_role() == Roles.GUESSER:
await message.author.send(f"Tu dois deviner le mot, ça se passe en public sur {game.channel.mention}.")
async def maybe_guess(game, message, content):
if not game.playing:
await game.say("Aucune partie en cours.")
return
player = game.get_player(message.author)
if not player:
await game.say(f"{message.author.mention} Tu n'es dans aucune équipe.")
return
if player.team != game.current_team() and player.team != Teams.NEUTRAL:
await game.say(f"{message.author.mention} Ce n'est pas à ton équipe de jouer.")
return
if player.role == Roles.CHOOSER:
await game.say(f"{message.author.mention} Tu es choisisseur, pas devineur.")
return
if game.current_role() == Roles.CHOOSER:
await game.say(f"{message.author.mention} Le choisisseur n'a pas encore donné son indice.")
return
try:
word = next(w for w in game.words if serialize(w.label) == serialize(content))
except StopIteration:
await game.say(f"{message.author.mention} Je ne reconnais pas ce mot dans la grille.")
return
if word.guessed:
await game.say(f"{message.author.mention} Ce mot a déjà été deviné et révélé.")
return
word.guessed = True
await print_grid(game, revealed=False, to=Recipients.EVERYONE)
if word.team == game.current_team():
await game.say(f"{SMILE} Bien ouej, c'est un mot de ton équipe !")
game.number -= 1
if game.number > 0:
await game.say(f"Nombre de mots à trouver encore : {game.number}")
else:
await game.say(f"Le nombre de mots indiqué par le choisisseur a été atteint, bravo ! Fin du tour.")
await proceed(game)
else:
game.number = 0
if word.team == Teams.NEUTRAL:
await game.say(f"{MEH} Oups, c'est un mot neutre... Fin du tour.")
elif word.team == Teams.BOMB:
await game.say(f"{BLOWN} **BOUM !** C'est {('une','la')[game.mode.value]} bombe ! Fin du jeu.")
else:
await game.say(f"{SAD} Mince... c'est un mot de l'équipe adverse... Fin du tour.")
await proceed(game)

16
cambot/emojis.py Normal file
View File

@@ -0,0 +1,16 @@
PUSHPIN = chr(128204)
REDDOT = chr(128308)
BLUEDOT = chr(128309)
WHITEDOT = chr(9898)
SPEAKING = chr(128483)
GLASS = chr(128269)
HANDSHAKE = chr(129309)
SWORDS = chr(9876)
EXPLOSION = chr(128165)
QUESTION = chr(10068)
SKULL = chr(9760)
SMILE = chr(128515)
MEH = chr(128533)
SAD = chr(128543)
BLOWN = chr(129327)
PARTY = chr(129395)

92
cambot/ephemeris.py Normal file
View File

@@ -0,0 +1,92 @@
import os
import requests
from datetime import datetime
from discord import Embed, Color
import re
import json
import locale
from bs4 import BeautifulSoup as bs
from .settings import *
from .saints import SAINTS
def citation():
try:
ts = datetime.now().strftime("%s")
req = requests.get(f"https://fr.wikiquote.org/wiki/Wikiquote:Accueil?r={ts}")
soup = bs(req.text, features="html.parser")
citation = soup.find(lambda tag:tag.name=="i" and "Lumière sur" in tag.text).parent.parent.parent.findAll("div")[1].text.strip()
return f"*{citation}*"
except Exception:
return "Impossible de trouver la citation du jour. Bouuuh !"
def saint():
today = datetime.now().strftime("%m/%d")
saint = SAINTS[today]
return saint
def weather_emoji(_id):
emojis = {re.compile(r"2.."):":thunder_cloud_rain:",
re.compile(r"3.."):":white_sun_rain_cloud:",
re.compile(r"5.."):":cloud_rain:",
re.compile(r"6.."):":snowflake:",
re.compile(r"7.."):":interrobang:",
re.compile(r"800"):":sunny:",
re.compile(r"801"):":white_sun_small_cloud:",
re.compile(r"80[23]"):":white_sun_cloud:",
re.compile(r"804"):":cloud:"}
try:
code = next(emoji[1] for emoji in emojis.items() if emoji[0].match(str(_id)))
except StopIteration:
code = ":negative_squared_cross_mark:"
return code
def weather(lat, lon):
r = requests.get(f"https://api.openweathermap.org/data/2.5/"
f"onecall?lat={lat}&lon={lon}&appid={OWM_KEY}&lang=fr&units=metric")
j = json.loads(r.text)
next_hours = []
for i in (2, 8, 14):
hourly = j["hourly"][i]
time = datetime.fromtimestamp(hourly["dt"]).strftime("%H:%M")
temp = round(hourly["temp"])
feels_like = hourly["feels_like"]
humidity = hourly["humidity"]
wind_speed = round(hourly["wind_speed"] * 3.6)
description = hourly["weather"][0]["description"]
emoji = weather_emoji(hourly["weather"][0]["id"])
next_hours.append({"time":time,
"emoji":emoji,
"description":description,
"temp":temp,
"wind_speed":wind_speed
})
return next_hours
def digest():
try:
locale.setlocale(locale.LC_ALL, "fr_CH.utf-8")
except locale.Error:
pass
now = datetime.now()
d = now.strftime("%A %-d %B")
c = citation()
s = saint()
w_strings = []
for w in weather(46.5196661, 6.6325467):
w_strings.append(f"{w['emoji']} {w['description']} ({w['temp']}°C, {w['wind_speed']} km/h)")
embed = Embed()
embed.title = "Bonjour !"
embed.description = f"Nous sommes le {d} ({s})\n\n{c}\n\u200B"
colors = [Color.red(), Color.gold(), Color.orange(), Color.blue(), Color.green(), Color.magenta(), Color.purple()]
embed.color = colors[now.weekday()]
embed.add_field(name="Matin", value=w_strings[0])
embed.add_field(name="Après-midi", value=w_strings[1])
embed.add_field(name="Soir", value=w_strings[2])
return embed
if __name__ == "__main__":
print(citation())

379
cambot/saints.py Normal file
View File

@@ -0,0 +1,379 @@
SAINTS = {
"01/01": "Jour de l'An",
"01/02": "Saint Basile",
"01/03": "Sainte Geneviève",
"01/04": "Saint Odilon",
"01/05": "Saint Edouard",
"01/06": "Saint André",
"01/07": "Saint Raymond",
"01/08": "Saint Lucien",
"01/09": "Sainte Alix",
"01/10": "Saint Guillaume",
"01/11": "Saint Paulin",
"01/12": "Sainte Tatiana",
"01/13": "Sainte Yvette",
"01/14": "Sainte Nina",
"01/15": "Saint Rémi",
"01/16": "Saint Marcel",
"01/17": "Sainte Roseline",
"01/18": "Sainte Prisca",
"01/19": "Saint Marius",
"01/20": "Saint Sébastien",
"01/21": "Sainte Agnès",
"01/22": "Saint Vincent",
"01/23": "Saint Barnard",
"01/24": "Saint François",
"01/25": "Saint Paul",
"01/26": "Sainte Paule",
"01/27": "Sainte Angèle",
"01/28": "Saint Thomas",
"01/29": "Saint Gildas",
"01/30": "Sainte Martine",
"01/31": "Sainte Marcelle",
"02/01": "Sainte Ella",
"02/02": "Saint Théophane",
"02/03": "Saint Blaise",
"02/04": "Sainte Véronique",
"02/05": "Sainte Agathe",
"02/06": "Saint Gaston",
"02/07": "Sainte Eugénie",
"02/08": "Sainte Jacqueline",
"02/09": "Sainte Apolline",
"02/10": "Saint Arnaud",
"02/11": "Saint Séverin",
"02/12": "Saint Félix",
"02/13": "Sainte Béatrice",
"02/14": "Saint Valentin",
"02/15": "Saint Claude",
"02/16": "Sainte Julienne",
"02/17": "Saint Alexis",
"02/18": "Sainte Bernadette",
"02/19": "Saint Gabin",
"02/20": "Sainte Aimée",
"02/21": "Saint Damien",
"02/22": "Sainte Isabelle",
"02/23": "Saint Lazare",
"02/24": "Saint Modeste",
"02/25": "Saint Roméo",
"02/26": "Saint Nestor",
"02/27": "Sainte Honorine",
"02/28": "Saint Romain",
"02/29": "Saint Auguste",
"03/01": "Saint Aubin",
"03/02": "Saint Charles",
"03/03": "Saint Gwenolé",
"03/04": "Saint Casimir",
"03/05": "Saint Olive",
"03/06": "Sainte Colette",
"03/07": "Sainte Félicité",
"03/08": "Saint Jean",
"03/09": "Sainte Françoise",
"03/10": "Saint Vivien",
"03/11": "Sainte Rosine",
"03/12": "Sainte Justine",
"03/13": "Saint Rodrigue",
"03/14": "Sainte Mathilde",
"03/15": "Sainte Louise",
"03/16": "Sainte Bénédicte",
"03/17": "Saint Patrick",
"03/18": "Saint Cyrille",
"03/19": "Saint Joseph",
"03/20": "Saint Herbert",
"03/21": "Sainte Clémence",
"03/22": "Sainte Léa",
"03/23": "Saint Victorien",
"03/24": "Sainte Catherine",
"03/25": "Saint Humbert",
"03/26": "Sainte Larissa",
"03/27": "Saint Habib",
"03/28": "Saint Gontran",
"03/29": "Sainte Gwladys",
"03/30": "Saint Amédée",
"03/31": "Saint Benjamin",
"04/01": "Saint Hugues",
"04/02": "Sainte Sandrine",
"04/03": "Saint Richard",
"04/04": "Saint Isidore",
"04/05": "Sainte Irène",
"04/06": "Saint Marcellin",
"04/07": "Saint Jean-Baptiste",
"04/08": "Sainte Julie",
"04/09": "Saint Gautier",
"04/10": "Saint Fulbert",
"04/11": "Saint Stanislas",
"04/12": "Saint Jules 1er",
"04/13": "Sainte Ida",
"04/14": "Saint Maxime",
"04/15": "Saint Paterne",
"04/16": "Saint Benoît",
"04/17": "Saint Etienne",
"04/18": "Saint Parfait",
"04/19": "Sainte Emma",
"04/20": "Sainte Odette",
"04/21": "Saint Anselme",
"04/22": "Saint Alexandre",
"04/23": "Saint Georges",
"04/24": "Saint Fidèle",
"04/25": "Saint Marc",
"04/26": "Sainte Alida",
"04/27": "Sainte Zita",
"04/28": "Sainte Valérie",
"04/29": "Sainte Catherine",
"04/30": "Saint Robert",
"05/01": "Saint Joseph",
"05/02": "Saint Boris",
"05/03": "Saint Jacques",
"05/04": "Saint Sylvain",
"05/05": "Sainte Judith",
"05/06": "Sainte Prudence",
"05/07": "Sainte Gisèle",
"05/08": "Saint Désiré",
"05/09": "Sainte Pacôme",
"05/10": "Sainte Solange",
"05/11": "Sainte Estelle",
"05/12": "Saint Achille",
"05/13": "Sainte Rolande",
"05/14": "Saint Matthias",
"05/15": "Sainte Denise",
"05/16": "Saint Honoré",
"05/17": "Saint Pascal",
"05/18": "Saint Eric",
"05/19": "Saint Yves",
"05/20": "Saint Bernardin",
"05/21": "Saint Constantin",
"05/22": "Saint Emile",
"05/23": "Saint Didier",
"05/24": "Saint Donatien",
"05/25": "Sainte Sophie",
"05/26": "Saint Bérenger",
"05/27": "Saint Augustin",
"05/28": "Saint Germain",
"05/29": "Saint Aymard",
"05/30": "Saint Ferdinand",
"05/31": "Sainte Perrine",
"06/01": "Saint Justin",
"06/02": "Sainte Blandine",
"06/03": "Saint Charles",
"06/04": "Sainte Clotilde",
"06/05": "Saint Igor",
"06/06": "Saint Norbert",
"06/07": "Saint Gilbert",
"06/08": "Saint Médard",
"06/09": "Sainte Diane",
"06/10": "Saint Landry",
"06/11": "Saint Barnabé",
"06/12": "Saint Guy",
"06/13": "Saint Antoine",
"06/14": "Saint Elisée",
"06/15": "Sainte Germaine",
"06/16": "Saint Jean-François",
"06/17": "Saint Hervé",
"06/18": "Saint Léonce",
"06/19": "Saint Romuald",
"06/20": "Saint Silvère",
"06/21": "Saint Rodolphe",
"06/22": "Saint Alban",
"06/23": "Sainte Audrey",
"06/24": "Saint Jean-Baptiste",
"06/25": "Saint Prosper",
"06/26": "Saint Anthelme",
"06/27": "Saint Fernand",
"06/28": "Saint Irénée",
"06/29": "Saint Pierre",
"06/30": "Saint Martial",
"07/01": "Saint Thierry",
"07/02": "Saint Martinien",
"07/03": "Saint Thomas",
"07/04": "Saint Florent",
"07/05": "Saint Antoine-Marie",
"07/06": "Sainte Marietta",
"07/07": "Saint Raoul",
"07/08": "Saint Thibaud",
"07/09": "Sainte Amandine",
"07/10": "Saint Ulric",
"07/11": "Saint Benoît",
"07/12": "Saint Olivier",
"07/13": "Saint Henri",
"07/14": "Saint Camille",
"07/15": "Saint Donald",
"07/16": "Sainte Elvire",
"07/17": "Sainte Charlotte",
"07/18": "Saint Frédéric",
"07/19": "Saint Arsène",
"07/20": "Sainte Marina",
"07/21": "Saint Victor",
"07/22": "Sainte Marie-Madeleine",
"07/23": "Sainte Brigitte",
"07/24": "Sainte Christine",
"07/25": "Saint Jacques",
"07/26": "Sainte Anne",
"07/27": "Sainte Nathalie",
"07/28": "Saint Samson",
"07/29": "Sainte Marthe",
"07/30": "Sainte Juliette",
"07/31": "Saint Ignace",
"08/01": "Saint Alphonse-Marie",
"08/02": "Saint Pierre-Julien",
"08/03": "Sainte Lydie",
"08/04": "Saint Jean-Marie",
"08/05": "Saint Abel",
"08/06": "Saint Octavien",
"08/07": "Saint Gaétan",
"08/08": "Saint Dominique",
"08/09": "Saint Amour",
"08/10": "Saint Laurent",
"08/11": "Sainte Claire",
"08/12": "Sainte Clarisse",
"08/13": "Saint Hippolyte",
"08/14": "Saint Evrard",
"08/15": "Sainte Marie",
"08/16": "Saint Armel",
"08/17": "Saint Hyacinthe",
"08/18": "Sainte Hélène",
"08/19": "Saint Jean-Eudes",
"08/20": "Saint Bernard",
"08/21": "Saint Christophe",
"08/22": "Saint Fabrice",
"08/23": "Sainte Rose",
"08/24": "Saint Barthélémy",
"08/25": "Saint Louis",
"08/26": "Sainte Natacha",
"08/27": "Sainte Monique",
"08/28": "Saint Augustin",
"08/29": "Sainte Sabine",
"08/30": "Saint Fiacre",
"08/31": "Saint Aristide",
"09/01": "Saint Gilles",
"09/02": "Sainte Ingrid",
"09/03": "Saint Grégoire",
"09/04": "Sainte Rosalie",
"09/05": "Sainte Raïssa",
"09/06": "Saint Bertrand",
"09/07": "Sainte Reine",
"09/08": "Saint Adrien",
"09/09": "Saint Alain",
"09/10": "Sainte Inès",
"09/11": "Saint Adelphe",
"09/12": "Saint Apollinaire",
"09/13": "Saint Aimé",
"09/14": "la Croix Glorieuse",
"09/15": "Saint Roland",
"09/16": "Sainte Edith",
"09/17": "Saint Renaud",
"09/18": "Sainte Nadège",
"09/19": "Sainte Emilie",
"09/20": "Saint Davy",
"09/21": "Saint Matthieu",
"09/22": "Saint Maurice",
"09/23": "Saint Constant",
"09/24": "Sainte Thècle",
"09/25": "Saint Hermann",
"09/26": "Sts Côme et Damien",
"09/27": "Saint Vincent",
"09/28": "Saint Venceslas",
"09/29": "Saint Michel",
"09/30": "Saint Jérôme",
"10/01": "Sainte Thérèse",
"10/02": "Saint Léger",
"10/03": "Saint Gérard",
"10/04": "Saint François",
"10/05": "Sainte Fleur",
"10/06": "Saint Bruno",
"10/07": "Saint Serge",
"10/08": "Sainte Pélagie",
"10/09": "Saint Denis",
"10/10": "Saint Ghislain",
"10/11": "Saint Firmin",
"10/12": "Saint Wilfrid",
"10/13": "Saint Géraud",
"10/14": "Saint Juste",
"10/15": "Sainte Thérèse",
"10/16": "Sainte Edwige",
"10/17": "Saint Baudouin",
"10/18": "Saint Luc",
"10/19": "Saint René",
"10/20": "Sainte Adeline",
"10/21": "Sainte Céline",
"10/22": "Sainte Elodie",
"10/23": "Saint Jean",
"10/24": "Saint Florentin",
"10/25": "Sainte Doria",
"10/26": "Saint Dimitri",
"10/27": "Sainte Emeline",
"10/28": "Saint Simon",
"10/29": "Saint Narcisse",
"10/30": "Sainte Bienvenue",
"10/31": "Saint Quentin",
"11/01": "La Toussaint",
"11/02": "Les Défunts",
"11/03": "Saint Hubert",
"11/04": "Saint Charles",
"11/05": "Sainte Sylvie",
"11/06": "Sainte Bertille",
"11/07": "Sainte Carine",
"11/08": "Saint Geoffroy",
"11/09": "Saint Théodore",
"11/10": "Saint Léon",
"11/11": "Saint Martin",
"11/12": "Saint Christian",
"11/13": "Saint Brice",
"11/14": "Saint Sidoine",
"11/15": "Saint Albert",
"11/16": "Sainte Marguerite",
"11/17": "Sainte Elisabeth",
"11/18": "Sainte Aude",
"11/19": "Saint Tanguy",
"11/20": "Saint Edmond",
"11/21": "Saint Albert",
"11/22": "Sainte Cécile",
"11/23": "Saint Clément",
"11/24": "Sainte Flora",
"11/25": "Sainte Catherine",
"11/26": "Sainte Delphine",
"11/27": "Saint Séverin",
"11/28": "Saint Jacques",
"11/29": "Saint Saturnin",
"11/30": "Saint André",
"12/01": "Sainte Florence",
"12/02": "Sainte Viviane",
"12/03": "Saint François-Xavier",
"12/04": "Sainte Barbara",
"12/05": "Saint Gérald",
"12/06": "Saint Nicolas",
"12/07": "Saint Ambroise",
"12/08": "Sainte Elfie",
"12/09": "Saint Pierre",
"12/10": "Saint Romaric",
"12/11": "Saint Daniel",
"12/12": "Sainte Jeanne-Françoise",
"12/13": "Sainte Lucie",
"12/14": "Sainte Odile",
"12/15": "Sainte Ninon",
"12/16": "Sainte Alice",
"12/17": "Saint Gaël",
"12/18": "Saint Gatien",
"12/19": "Saint Urbain",
"12/20": "Saint Théophile",
"12/21": "Saint Pierre",
"12/22": "Sainte Françoise-Xavière",
"12/23": "Saint Armand",
"12/24": "Sainte Adèle",
"12/25": "Jour de Noël",
"12/26": "Saint Etienne",
"12/27": "Saint Jean",
"12/28": "Saints Innocents",
"12/29": "Saint David",
"12/30": "Saint Roger",
"12/31": "Saint Sylvestre"
}

View File

@@ -0,0 +1,25 @@
from datetime import time
DISCORD_TOKEN = "xxx"
OWM_KEY = "xxx"
CHANNEL1 = 111111111111111111
CHANNEL2 = 222222222222222222
CHANNEL3 = 333333333333333333
EPHEMERIS_CHANNEL_IDS = (CHANNEL1,)
CODENAMES_CHANNEL_IDS = (CHANNEL1, CHANNEL2)
WORDLE_CHANNEL_IDS = (CHANNEL2, CHANNEL3)
WORDLE_VALID_WORDS = "cambot/wordlists/valid_words.txt"
WORDLE_TARGET_WORDS = "cambot/wordlists/target_words.txt"
WORDLE_POINTS = (1, 3, 6)
WORDLE_SLOWMODE = 0
WORDLE_MINLENGTH = 8
WORDLE_FORCE_ALTERNATION = True
HEARTBEAT = 60
EPHEMERIS = 0
WORDLE = 1
EVENTS = ((EPHEMERIS, time(8)),
(WORDLE, time(8)),
(WORDLE, time(20)))

9
cambot/utils.py Normal file
View File

@@ -0,0 +1,9 @@
import unidecode
from enum import Enum
def serialize(string):
return unidecode.unidecode(string.lower().strip().replace(" ", "").replace("-", "").replace("'", "").replace(".", ""))
class EnumZero(Enum):
def _generate_next_value_(name, start, count, last_values):
return count

98
cambot/wordle.py Normal file
View File

@@ -0,0 +1,98 @@
import random
from unidecode import unidecode
from collections import defaultdict
from .settings import *
with open(WORDLE_VALID_WORDS, "r") as f:
valid_words = set(f.read().splitlines())
with open(WORDLE_TARGET_WORDS, "r") as f:
target_words = set(f.read().splitlines())
valid_words = valid_words.union(target_words)
def validate(target, guess):
assert len(target) == len(guess)
copy = list(target)
output = [0] * len(target)
# Look for the green squares :
for idx in range(len(target)):
if target[idx] == guess[idx]:
output[idx] = 2
copy[idx] = None
# Look for the yellow squares :
for idx in range(len(target)):
if target[idx] == guess[idx]:
continue # ignore the letters that are green
if guess[idx] in copy:
left_most_yellow_idx = copy.index(guess[idx])
output[idx] = 1
copy[left_most_yellow_idx] = None # mark ONE of the corresponding letters as done
return output
class Game:
def __init__(self, channel):
self.channel = channel
self.target = None
self.winner = None
self.tries = 0
self.last_player = None
self.scores = None
self.tried = None
async def reset(self):
self.target = random.choice(tuple(x for x in target_words if len(x) >= WORDLE_MINLENGTH))
self.winner = None
self.tries = 0
self.last_player = None
self.scores = defaultdict(int)
self.tried = set()
await self.channel.edit(slowmode_delay=WORDLE_SLOWMODE)
await self.channel.send(f"Il y a un nouveau mot à deviner ! Il fait {len(self.target)} lettres.")
async def parse(self, message):
content = message.content.strip()
if (len(content) >= 1 and content[0] == "-") or self.winner or not self.target: # special char, or somebody won, or never initialized
return
guess = unidecode(content).upper()
if WORDLE_FORCE_ALTERNATION and message.author == self.last_player:
await self.channel.send(f"{message.author.mention} Laisse un peu jouer les autres !")
return
if len(guess) != len(self.target):
await self.channel.send(f"{message.author.mention} Le mot à deviner fait {len(self.target)} lettres (ça ne compte pas comme un tour).")
return
if guess not in valid_words:
await self.channel.send(f"{message.author.mention} `{guess}` n'est pas dans mon dictionnaire (ça ne compte pas comme un tour).")
return
self.tries += 1
self.last_player = message.author
result = validate(self.target, guess)
points = 0
for idx, letter in enumerate(guess):
if (idx, letter) in self.tried:
continue
points += WORDLE_POINTS[result[idx]]
self.tried.add((idx, letter))
output_word = " ".join(f":regional_indicator_{x.lower()}:" for x in guess)
output_squares = " ".join((":white_large_square:", ":yellow_square:", ":green_square:")[x] for x in result)
output = f"{message.author.mention} (essai {self.tries}, {points} point{'s' if points > 1 else ''})\n{output_word}\n{output_squares}"
self.scores[message.author] += points
if guess == self.target:
self.winner = message.author
await self.channel.edit(slowmode_delay=0)
output += "\n\n:trophy: YOUPI :trophy:\n\nScores :\n\n"
for idx, score in enumerate(sorted(self.scores.items(), key=lambda x:x[1], reverse=True)):
player, points = score
output += f"{idx+1}) {player.display_name} ({points} point{'s' if points > 1 else ''})\n"
await self.channel.send(output)