Added Spoutnik
This commit is contained in:
141
wordle.py
Normal file
141
wordle.py
Normal file
@@ -0,0 +1,141 @@
|
||||
import random
|
||||
import re
|
||||
from unidecode import unidecode
|
||||
from collections import defaultdict
|
||||
from datetime import datetime
|
||||
from math import ceil
|
||||
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
|
||||
self.last_datetime = None
|
||||
self.resetters = set()
|
||||
|
||||
async def reset(self):
|
||||
output = ""
|
||||
if self.target and not self.winner:
|
||||
output = f"Le mot précédent était : `{self.target}`\n\n"
|
||||
self.target = random.choice(tuple(x for x in target_words if WORDLE_MAXLENGTH >= len(x) >= WORDLE_MINLENGTH))
|
||||
self.winner = None
|
||||
self.tries = 0
|
||||
self.last_player = None
|
||||
self.scores = defaultdict(int)
|
||||
self.tried = set()
|
||||
self.last_datetime = None
|
||||
self.resetters = set()
|
||||
await self.channel.edit(slowmode_delay=WORDLE_SLOWMODE)
|
||||
output += f"Il y a un nouveau mot à deviner ! Il fait {len(self.target)} lettres."
|
||||
await self.channel.send(output)
|
||||
|
||||
async def parse(self, message):
|
||||
guess = unidecode(message.content.strip()).upper()
|
||||
|
||||
# if it's the special keyword 'reset', consider resetting
|
||||
if guess == "RESET" and WORDLE_RESETTERS_NEEDED != 0:
|
||||
if message.author in self.resetters:
|
||||
return
|
||||
self.resetters.add(message.author)
|
||||
await self.channel.send(f"Réinitialisation : {len(self.resetters)}/{WORDLE_RESETTERS_NEEDED}")
|
||||
if len(self.resetters) >= WORDLE_RESETTERS_NEEDED:
|
||||
await self.reset()
|
||||
return
|
||||
|
||||
# if somebody has already won, return silently
|
||||
if self.winner:
|
||||
return
|
||||
|
||||
# same if the game was never initialized
|
||||
if not self.target:
|
||||
return
|
||||
|
||||
# same if the message is not comprised of letters (with or without accents) only
|
||||
if not re.match(r"^[A-Z]*$", guess):
|
||||
return
|
||||
|
||||
# same if the guess is obviously not the same length
|
||||
ratio = 2/3
|
||||
if len(guess) <= len(self.target) * ratio or len(self.target) <= len(guess) * ratio:
|
||||
return
|
||||
|
||||
# check for errors and react accordingly
|
||||
error = False
|
||||
if WORDLE_FORCE_ALTERNATION and message.author == self.last_player and (elapsed := (datetime.now() - self.last_datetime).total_seconds()) < WORDLE_ALTERNATION_TIMEOUT:
|
||||
await message.add_reaction("\N{BUSTS IN SILHOUETTE}")
|
||||
error = True
|
||||
remaining = ceil(WORDLE_ALTERNATION_TIMEOUT - elapsed)
|
||||
await self.channel.send(f"{message.author.mention} : tu pourras rejouer dans {remaining} seconde{'s' if remaining > 1 else ''}")
|
||||
if len(guess) != len(self.target):
|
||||
await message.add_reaction("\N{LEFT RIGHT ARROW}")
|
||||
error = True
|
||||
if guess not in valid_words:
|
||||
await message.add_reaction("\N{EXCLAMATION QUESTION MARK}")
|
||||
error = True
|
||||
if error:
|
||||
return
|
||||
|
||||
# everything is OK, validate the proposal
|
||||
self.tries += 1
|
||||
self.last_player = message.author
|
||||
self.last_datetime = datetime.now()
|
||||
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)
|
||||
self.resetters = set()
|
||||
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