diff options
author | Roman Gräf <romangraef@loves.dicksinhisan.us> | 2020-12-05 00:46:55 +0100 |
---|---|---|
committer | Roman Gräf <romangraef@loves.dicksinhisan.us> | 2020-12-05 00:46:55 +0100 |
commit | ab659c3f1f2d0cbb3705400622678125dd6f4369 (patch) | |
tree | c3cc613c57e79a33ae0678c34ecc7e9c8dacddb0 | |
parent | 987f3bc618574ea52ea551dd31d131a2d523d7dc (diff) | |
download | RedCog-MinecraftTrivia-ab659c3f1f2d0cbb3705400622678125dd6f4369.tar.gz RedCog-MinecraftTrivia-ab659c3f1f2d0cbb3705400622678125dd6f4369.tar.bz2 RedCog-MinecraftTrivia-ab659c3f1f2d0cbb3705400622678125dd6f4369.zip |
bug fixes and leaderboards
-rw-r--r-- | minecrafttrivia/game.py | 42 | ||||
-rw-r--r-- | minecrafttrivia/info.json | 31 | ||||
-rw-r--r-- | minecrafttrivia/recipe_provider.py | 28 | ||||
-rw-r--r-- | minecrafttrivia/trivia_interface_cog.py | 43 | ||||
-rw-r--r-- | minecrafttrivia/utils.py | 27 |
5 files changed, 116 insertions, 55 deletions
diff --git a/minecrafttrivia/game.py b/minecrafttrivia/game.py index 3a151e3..3e8f725 100644 --- a/minecrafttrivia/game.py +++ b/minecrafttrivia/game.py @@ -6,7 +6,9 @@ from datetime import datetime, timedelta from enum import Enum, auto import discord +from redbot.core import Config from redbot.core.bot import Red +from redbot.core.config import Group from . import constants, utils from .recipe_provider import DEFAULT_RECIPE_PROVIDER @@ -25,9 +27,10 @@ class OngoingGame(ABC): phase: GamePhase signup_message: discord.Message - def __init__(self, bot: Red, channel: discord.TextChannel): + def __init__(self, bot: Red, config: Config, channel: discord.TextChannel): self.participants = [] self.bot = bot + self.config: Group = config.guild(channel.guild) self.channel = channel self.phase = GamePhase.INACTIVE @@ -37,10 +40,10 @@ class OngoingGame(ABC): title="Signups opened for new game of Minecraft trivia", description="React to this message in order to join. You have 30 seconds to signup.", ) - embed.timestamp = datetime.now() + timedelta(seconds=30) + embed.timestamp = datetime.now() self.signup_message = await self.channel.send(embed=embed) await self.signup_message.add_reaction(constants.POSITIVE_REACTION) - await asyncio.sleep(30) + await asyncio.sleep(await self.config.join_timeout()) embed.description = "Signups are now closed. Wait for the game to finish to start a new one." await self.signup_message.edit(embed=embed) self.participants = await utils.get_participants((await self.channel.fetch_message(self.signup_message.id)).reactions) @@ -59,7 +62,7 @@ class OngoingGame(ABC): await self.channel.send(embed=embed) async def gameloop(self): - for i in range(5): + for i in range(2):#todo await self.single_round(i) await self.conclude_game() @@ -75,7 +78,7 @@ class OngoingGame(ABC): return False return True - until = datetime.now() + timedelta(seconds=60) + until = datetime.now() + timedelta(seconds=await self.config.guess_timeout()) while True: try: mes = await self.bot.wait_for('message', check=check, timeout=(until - datetime.now()).total_seconds()) @@ -108,11 +111,27 @@ class OngoingGame(ABC): class PointBasedGame(OngoingGame, ABC): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.points = {} + self.points: typing.Dict[discord.User, int] = {} + + async def conclude_game(self): + async with self.config.total_scores() as total_scores: + for user, points in self.points.items(): + uid = str(user.id) + if uid not in total_scores: + total_scores[uid] = 0 + total_scores[uid] += points + async with self.config.high_scores() as high_scores: + for user, points in self.points.items(): + uid = str(user.id) + if uid not in high_scores: + high_scores[uid] = points + else: + high_scores[uid] = max(high_scores[uid], points) + return await super().conclude_game() @property def ranks(self) -> typing.List[typing.Tuple[int, typing.Tuple[discord.User, int]]]: - return list(enumerate(reversed(sorted(self.points.items(), key=lambda x: x[1])))) + return utils.create_leaderboard(self.points) async def start_game(self): for u in self.participants: @@ -120,7 +139,7 @@ class PointBasedGame(OngoingGame, ABC): return await super().start_game() def leaderboard(self) -> str: - return "\n".join(f"**{rank + 1}.** {user.mention} - {points}" for rank, (user, points) in self.ranks) + return utils.format_leaderboard(self.ranks) class XDGame(PointBasedGame): @@ -184,5 +203,8 @@ class CraftingGame(PointBasedGame): return len(items_left_to_find) == 0 await self.wait_for_participant_messages(check) - embed.description += "\n\nAll items found" - await self.channel.edit(embed=embed) + for ingredient in items_left_to_find: + item = ingredient.allowed_items[0] + embed.description += f"\n{self.get_name(item)} ({ingredient.count}) - Not Found" + embed.description += "\n\nDone" + await message.edit(embed=embed) diff --git a/minecrafttrivia/info.json b/minecrafttrivia/info.json index cbb2c9d..94a0b21 100644 --- a/minecrafttrivia/info.json +++ b/minecrafttrivia/info.json @@ -1,16 +1,17 @@ { - "short": "Minecraft trivia minigames", - "author": [ - "romangraef89" - ], - "required_cogs": {}, - "requirements": [], - "tags": [ - "minecraft", - "minigame" - ], - "min_bot_version": "3.3.10", - "hidden": false, - "disabled": false, - "type": "COG" -}
\ No newline at end of file + "name": "MinecraftTrivia", + "short": "Minecraft trivia minigames", + "author": [ + "romangraef89" + ], + "required_cogs": {}, + "requirements": [], + "tags": [ + "minecraft", + "minigame" + ], + "min_bot_version": "3.3.10", + "hidden": false, + "disabled": false, + "type": "COG" +} diff --git a/minecrafttrivia/recipe_provider.py b/minecrafttrivia/recipe_provider.py index fe99d59..654192b 100644 --- a/minecrafttrivia/recipe_provider.py +++ b/minecrafttrivia/recipe_provider.py @@ -18,6 +18,7 @@ class Ingredient: @dataclass class CraftingRecipe: + name: str result: str ingredients: typing.List[Ingredient] @@ -53,7 +54,7 @@ class RecipeProvider: def load_all_recipes(self): for recipe_file in recipe_dir.iterdir(): with recipe_file.open() as fp: - self.load_recipe(fp) + self.load_recipe(recipe_file.name.split(".")[0], fp) def load_lang(self): with lang_file.open() as fp: @@ -76,14 +77,12 @@ class RecipeProvider: def follow_tags(self, tag: str) -> typing.List[str]: def internal(t): - if t == "": - print("x") for el in self.tags[t]: if el[0] == '#': for subel in internal(deminecraft(el[1:])): yield subel else: - yield el + yield deminecraft(el) return list(internal(tag)) @@ -99,14 +98,19 @@ class RecipeProvider: return self.follow_tags(deminecraft(obj['tag'])) raise RuntimeError("Invalid recipe") - def load_crafting_shapeless(self, data: dict) -> CraftingRecipe: + def load_crafting_shapeless(self, name: str, data: dict) -> CraftingRecipe: result = deminecraft(data['result']['item']) - ingredients = [] + ingredient_counts = defaultdict(int) + ingredients_content = {} for i in data['ingredients']: - ingredients.append(Ingredient(self.parse_ingredient(i), 1)) - return CraftingRecipe(result, ingredients) + ingredient_counts[str(i)] += 1 + ingredients_content[str(i)] = self.parse_ingredient(i) + ingredients = [] + for ingredient, count in ingredient_counts.items(): + ingredients.append(Ingredient(ingredients_content[ingredient], count)) + return CraftingRecipe(name, result, ingredients) - def load_crafting_shaped(self, data: dict) -> CraftingRecipe: + def load_crafting_shaped(self, name: str, data: dict) -> CraftingRecipe: item_counts = defaultdict(int) for row in data['pattern']: for cell in row: @@ -117,14 +121,14 @@ class RecipeProvider: obj = data['key'][item] ingredients.append(Ingredient(self.parse_ingredient(obj), count)) result = deminecraft(data['result']['item']) - return CraftingRecipe(result, ingredients) + return CraftingRecipe(name, result, ingredients) - def load_recipe(self, fp): + def load_recipe(self, name, fp): data = json.load(fp) loader = 'load_' + deminecraft(data['type']) if not hasattr(self, loader): return - recipe = getattr(self, loader)(data) + recipe = getattr(self, loader)(name, data) self.recipes.append(recipe) diff --git a/minecrafttrivia/trivia_interface_cog.py b/minecrafttrivia/trivia_interface_cog.py index 939bad0..b27f789 100644 --- a/minecrafttrivia/trivia_interface_cog.py +++ b/minecrafttrivia/trivia_interface_cog.py @@ -2,31 +2,54 @@ import typing import discord from discord.ext.commands import guild_only -from redbot.core import commands +from redbot.core import commands, Config from redbot.core.bot import Red -from .game import OngoingGame, CraftingGame +from . import utils +from .game import OngoingGame, CraftingGame, GamePhase class TriviaInterfaceCog(commands.Cog): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.active_games_per_channel: typing.Dict[int,] = {} + self.config = Config.get_conf(self, identifier=262200644) + self.config.register_global(version=0) + self.config.register_guild( + join_timeout=30, + guess_timeout=60, + total_scores={}, + high_scores={}, + ) + self.active_games_per_channel: typing.Dict[int, OngoingGame] = {} def get_game(self, channel: discord.TextChannel) -> typing.Optional[OngoingGame]: - return channel.id in self.active_games_per_channel and self.active_games_per_channel[channel.id] + game = channel.id in self.active_games_per_channel and self.active_games_per_channel[channel.id] + if game and game.phase != GamePhase.FINISHED: + return game def create_game(self, bot: Red, channel: discord.TextChannel) -> OngoingGame: - game = CraftingGame(bot, channel) + game = CraftingGame(bot, self.config, channel) self.active_games_per_channel[channel.id] = game return game @commands.command(aliases=["mctrivia", "mct"]) @guild_only() - async def minecrafttrivia(self, ctx: commands.GuildContext): + async def minecrafttrivia(self, ctx: commands.GuildContext, action: str = "new"): """Starts a game of minecraft trivia""" game = self.get_game(ctx.channel) - if game: - return await ctx.send("Game already started.") - game = self.create_game(ctx.bot, ctx.channel) - await game.start_signup() + if action == "new": + if game: + return await ctx.send("Game already started.") + game = self.create_game(ctx.bot, ctx.channel) + await game.start_signup() + elif action[:4] == "high": + high_scores = await self.config.guild(ctx.guild).high_scores() + await ctx.send(embed=discord.Embed( + title=f"MC Trivia Highscores for {ctx.guild.name}", + description=utils.format_leaderboard(utils.create_leaderboard(high_scores)))) + elif action[:4] == "lead": + total_scores = await self.config.guild(ctx.guild).total_scores() + await ctx.send(embed=discord.Embed( + title=f"MC Trivia Leaderboard for {ctx.guild.name}", + description=utils.format_leaderboard(utils.create_leaderboard(total_scores)) + )) diff --git a/minecrafttrivia/utils.py b/minecrafttrivia/utils.py index de80da0..8bb4730 100644 --- a/minecrafttrivia/utils.py +++ b/minecrafttrivia/utils.py @@ -6,11 +6,22 @@ from . import constants async def get_participants(reactions: typing.List[discord.Reaction]) -> typing.List[discord.User]: - for r in reactions: - if r.emoji == constants.POSITIVE_REACTION: - users = [] - async for u in r.users(): - if not u.bot: - users.append(u) - return users - return [] + for r in reactions: + if r.emoji == constants.POSITIVE_REACTION: + users = [] + async for u in r.users(): + if not u.bot: + users.append(u) + return users + return [] + + +def format_leaderboard(points: typing.List[typing.Tuple[int, typing.Tuple[typing.Union[discord.User, int], int]]]) -> str: + return "\n".join(f"**{rank + 1}.** {user.mention if hasattr(user, 'mention') else '<@' + str(user) + '>'} - {points}" for rank, (user, points) in points) + + +_T = typing.TypeVar("_T", discord.User, int) + + +def create_leaderboard(points: typing.Dict[_T, int]) -> typing.List[typing.Tuple[int, typing.Tuple[_T, int]]]: + return list(enumerate(reversed(sorted(points.items(), key=lambda x: x[1])))) |