Initial commit
This commit is contained in:
0
cambot/__init__.py
Normal file
0
cambot/__init__.py
Normal file
394
cambot/codenames.py
Normal file
394
cambot/codenames.py
Normal 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
16
cambot/emojis.py
Normal 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
92
cambot/ephemeris.py
Normal 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
379
cambot/saints.py
Normal 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"
|
||||
}
|
||||
25
cambot/settings_example.py
Normal file
25
cambot/settings_example.py
Normal 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
9
cambot/utils.py
Normal 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
98
cambot/wordle.py
Normal 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)
|
||||
Reference in New Issue
Block a user