aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRoman Gräf <romangraef@loves.dicksinhisan.us>2020-12-05 00:46:55 +0100
committerRoman Gräf <romangraef@loves.dicksinhisan.us>2020-12-05 00:46:55 +0100
commitab659c3f1f2d0cbb3705400622678125dd6f4369 (patch)
treec3cc613c57e79a33ae0678c34ecc7e9c8dacddb0
parent987f3bc618574ea52ea551dd31d131a2d523d7dc (diff)
downloadRedCog-MinecraftTrivia-ab659c3f1f2d0cbb3705400622678125dd6f4369.tar.gz
RedCog-MinecraftTrivia-ab659c3f1f2d0cbb3705400622678125dd6f4369.tar.bz2
RedCog-MinecraftTrivia-ab659c3f1f2d0cbb3705400622678125dd6f4369.zip
bug fixes and leaderboards
-rw-r--r--minecrafttrivia/game.py42
-rw-r--r--minecrafttrivia/info.json31
-rw-r--r--minecrafttrivia/recipe_provider.py28
-rw-r--r--minecrafttrivia/trivia_interface_cog.py43
-rw-r--r--minecrafttrivia/utils.py27
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]))))