-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.py
278 lines (200 loc) · 10.5 KB
/
main.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
'''
This file contains the Discord interaction, implemented using the Disnake library.
'''
from typing import Callable
from dotenv import load_dotenv
load_dotenv()
import os
import traceback
import disnake
from disnake.ext import commands
from wordy_types import EndResult
from game_store import get_info_for_user, set_info_for_user, write_to_disk
from wordy_chat import begin_game, enter_guess, get_emotes_for_colorblind, render_result
from dictionary import languages, get_acceptable_words_for, get_alphabet_for, get_solution_words_for
bot = commands.Bot(command_prefix="/", description="Wordy Guessing Game", help_command=None,
activity=disnake.Game(name='with dictionaries'))
def generate_game_commands(languages: dict):
'''For each language we support, generate a prefix command and a slash command.'''
for lang_code, lang in languages.items():
def make_commands(lang_code):
async def handle_lang_prefix(ctx: commands.Context, guess: str):
nonlocal lang_code
await handle_new_guess(guess, lang_code, ctx.author, ctx.reply)
async def handle_lang_slash(inter, guess:str):
nonlocal lang_code
await handle_new_guess(guess, lang_code, inter.author, inter.response.send_message)
return handle_lang_prefix, handle_lang_slash
handle_lang_prefix, handle_lang_slash = make_commands(lang_code)
bot.command(name=lang['command'], help=lang['help'])(handle_lang_prefix)
bot.slash_command(name=lang['command'], description=lang['help'])(handle_lang_slash)
# Fixed prefix commands
HELP_TEXT_PRE = """**Wordy is a Wordle-like clone that supports multiple languages.**
Choose the command fitting to the language you want to use and guess a word. If Wordy returns a gray icon ⬛ the letter does not exist. If it returns a yellow icon 🟨 the letter exists but is on the wrong spot. If Wordy returns a green icon 🟩 the letter is on the correct spot.
In colorblind mode 🟦 represents the right letter on the wrong position and 🟧 the right letter on the right position.
**To enter a guess (games are started automatically):** ```
"""
HELP_TEXT_POST = """```
To give up (or to switch languages) use `/surrender`.
To toggle colorblind mode on or off use `/colorblind`.
*Wordy saves your DiscordID together with your Discord name and your game stats to provide game statistics.*
"""
@bot.command(name='surrender', help="Give up and reveal the word!")
async def surrender_prefix(ctx: commands.Context):
await handle_surrender(ctx.author, ctx.reply)
@bot.command(name='help', help="Get your stats")
async def help_prefix(ctx: commands.Context):
await handle_help(ctx.reply)
@bot.command(name='stats', help="Get your stats")
async def stats_prefix(ctx: commands.Context):
await handle_stats(ctx.author, ctx.reply)
@bot.command(name='colorblind', help="Toggle colorblind mode on or off")
async def colorblind_prefix(ctx: commands.Context):
await handle_colorblind(ctx.author, ctx.reply)
@bot.command(name='show', help="Show current board state")
async def show_prefix(ctx: commands.Context):
await handle_show(ctx.author, ctx.reply)
# Fixed slash commands
@bot.slash_command(name='surrender', description="Give up and reveal the word!")
async def surrender_slash(inter):
await handle_surrender(inter.user, inter.response.send_message)
@bot.slash_command(name='help', description="How to play Wordy")
async def help_slash(inter):
await handle_help(inter.response.send_message)
@bot.slash_command(name='stats', description="Get your stats")
async def stats_slash(inter):
await handle_stats(inter.user, inter.response.send_message)
@bot.slash_command(name='colorblind', description="Toggle colorblind mode on or off")
async def colorblind_slash(inter):
await handle_colorblind(inter.user, inter.response.send_message)
@bot.slash_command(name='show', description="Show current board state")
async def show_slash(inter):
await handle_show(inter.user, inter.response.send_message)
# Common functionality
async def handle_show(user: disnake.User|disnake.Member, reply: Callable):
# Show the current board state
player = get_info_for_user(user.id)
if player.current_game is None:
await reply("You haven't started a game yet!")
return
# Render the results
description = "Your board:\n"
description += "```"
for result,word in zip(player.current_game.results, player.current_game.board_state):
description += f"{render_result(result, player.settings.colorblind)} {word}\n"
description += "```"
await reply(description)
async def handle_colorblind(user: disnake.User|disnake.Member, reply: Callable):
# Toggle colorblind state for the user
player = get_info_for_user(user.id)
player.username = str(user)
player.settings.colorblind = not player.settings.colorblind
set_info_for_user(user.id, player)
write_to_disk()
emote_absent, emote_present, emote_correct = get_emotes_for_colorblind(player.settings.colorblind)
description = f"""Colorblind mode is turned {'ON' if player.settings.colorblind else 'OFF'} for you.
Your icon colors are: absent letter {emote_absent}, wrong position {emote_present}, and correct {emote_correct}."""
await reply(description)
async def handle_help(reply: Callable):
description = HELP_TEXT_PRE
for lang in languages.values():
command = f"/{lang['command']} <guess>"
description += f"{command:<19} {lang['help']}\n"
description += HELP_TEXT_POST
await reply(description)
async def handle_stats(user: disnake.User|disnake.Member, reply: Callable):
try:
player = get_info_for_user(user.id)
stats = player.stats
except Exception as ex:
print(f"Failed to fetch stats for {user}:")
traceback.print_exc()
await reply(f"Sorry, we're unable to fetch your stats right now 😿")
return
embed = disnake.Embed(title=f"{user.name}'s stats", color=0x00ff00)
embed.set_thumbnail(url=user.avatar.url if user.avatar else None)
embed.add_field(name="🏆 Won", value=stats.wins, inline=True)
embed.add_field(name="☠️ Lost", value=stats.losses, inline=True)
embed.add_field(name="🏳️ Surrendered", value=stats.surrenders, inline=False)
embed.add_field(name='═══════════════════════', value="Games played in different languages:", inline=False)
for lang,lang_count in player.stats.games.items():
flag = 'gb' if lang == 'en' else lang
embed.add_field(name=f"{lang} :flag_{flag}:", value=f"{lang_count}", inline=True)
await reply(embed=embed)
async def handle_surrender(user: disnake.User | disnake.Member, reply: Callable):
player = get_info_for_user(user.id)
player.username = str(user)
if player.current_game is None:
await reply("You haven't started a game yet!")
return
player.stats.surrenders += 1
lang_games = player.stats.games.get(player.current_game.lang, 0)
player.stats.games[player.current_game.lang] = lang_games + 1
answer = player.current_game.answer
player.current_game = None
set_info_for_user(user.id, player)
await reply(f"You coward! 🙄\nYour word was `{answer}`!")
async def handle_new_guess(guess: str, lang: str, user: disnake.User|disnake.Member, reply: Callable):
# Validate input
if not guess:
await reply(f"To play Wordy simply type `/wordy <guess>` to start or continue your own personal game.")
return
guess = guess.lower()
guess = guess.removeprefix('guess:')
if len(guess) != 5:
await reply("Guess must be 5 letters long")
return
# Make sure the word is valid
if guess not in get_solution_words_for(lang) and guess not in get_acceptable_words_for(lang):
await reply("That's not a valid word!")
return
# Gather text to return to the user
description = ''
# Make sure we have a game running, starting a new one if not
player = get_info_for_user(user.id)
player.username = str(user)
if not player.current_game or player.current_game.state != EndResult.PLAYING:
description += "Starting a new game...\n"
player.current_game = begin_game(player, lang)
# Make sure the user isn't switching languages
if player.current_game.lang != lang:
await reply('You are already playing in a different language! Use `/surrender` to end it.')
return
# Make sure the user hasn't already guessed this word
if guess in player.current_game.board_state:
await reply("You've already guessed that word!")
return
# Make sure the guess uses only letters from the language's dictionary
dictionary = get_alphabet_for(lang)
if any(char not in dictionary for char in guess):
await reply(f"You can only use the following letters: `{dictionary}`")
return
# Process the guess
enter_guess(guess, player.current_game)
# Render the results
description += "Your results so far:\n"
description += "```"
for result,word in zip(player.current_game.results, player.current_game.board_state):
description += f"{render_result(result, player.settings.colorblind)} {word}\n"
description += "```"
# See if the game is over
if player.current_game.state == EndResult.WIN:
description += f"\nCongratulations! 🎉\nCompleted in {len(player.current_game.board_state)} guesses!\n"
player.stats.wins += 1
elif player.current_game.state == EndResult.LOSE:
description += f"\nNo more guesses! 😭\nYour word was `{player.current_game.answer}`!\n"
player.stats.losses += 1
# Send the response
embed = disnake.Embed(title="Wordy", description=description.strip('\n'))
await reply(embed=embed)
# If the game is done, record stats and remove game data
if player.current_game.state != EndResult.PLAYING:
lang_games = player.stats.games.get(player.current_game.lang, 0)
player.stats.games[player.current_game.lang] = lang_games + 1
player.current_game = None
# Store the updated player info
set_info_for_user(user.id, player)
write_to_disk()
if __name__ == "__main__":
generate_game_commands(languages)
bot.run(os.getenv("DISCORD_TOKEN"))