Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import importlib
- import os
- import string
- from itertools import cycle
- from discord.ext import commands
- from .examples.example_game import GameBoardTicTacToe, PlayerTicTacToe, \
- is_move_valid, \
- make_move, get_messages, check_win, cleanup_game, \
- pick_random_mark
- from .important_types import WinCode, DrawCode, UserID, RoleID, GameTypeInfo, LiveGameInfo
- # typechecking stuff
- from .gamesession import GameSession
- from discord.ext.commands import Context
- from discord import Member
- from typing import List, Dict, Union, Iterable, Any, Callable, TypeVar, Sequence, Type, Tuple, Optional, cast, NewType, Set
- from . import games
- # ---
- class DiscordGamesCogError(Exception):
- pass
- class InvalidGameInfoError(DiscordGamesCogError):
- pass
- class DiscordGamesCog:
- def __init__(self, bot:Any) -> None:
- self.gameadmin_role_id = RoleID("403127699134218250")
- self.bot = bot
- self._games_filenames = self._load_games_filenames() # type: List[str]
- self.games = {} # type: Dict[str, GameTypeInfo]
- self.live_games = {} # type: Dict[str, LiveGameInfo]
- self.invalid_gamename_chars = {*string.punctuation, *string.whitespace, *string.printable} # type: Set[str]
- self.max_gamename_length = 10
- # Loading games stuff: START #
- # ======================================================================== #
- def _load_games_filenames(self) -> List[str]:
- # Finds .py files in ./games/ for use
- return games.get_mod_names()
- def _is_member_gameadmin(self, member:Member) -> bool:
- return any(RoleID(role.id) == self.gameadmin_role_id for role in member.roles)
- def _is_game_type_live(self, game_type:str) -> bool:
- for game_info in self.live_games.values():
- if game_info["game_type"] == game_type:
- return True
- return False
- def _import_helper(self, game_type:str, package:Optional[str]=None) -> None:
- mod_obj = importlib.import_module(game_type, package) # type: Any
- new_gametypeinfo = mod_obj.get_gametypeinfo() # type: GameTypeInfo
- self.games[game_type] = new_gametypeinfo
- # Reload game | Load game | Unload game
- @commands.command(pass_context=True)
- async def reloadgame(self, ctx:Context, game_type:str) -> None:
- if self._is_member_gameadmin(ctx.message.author):
- if game_type not in self.games:
- await self.bot.say("Game {} hasn't been loaded yet!".format(game_type))
- elif self._is_game_type_live(game_type):
- await self.bot.say("Game {} has game sessions in progress!".format(game_type))
- else:
- self._import_helper(game_type, package="discord_games.games")
- await self.bot.say("Game {} reloaded.".format(game_type))
- else:
- await self.bot.say("You don't have permission to do that!")
- @commands.command(pass_context=True)
- async def loadgame(self, ctx:Context, game_type:str) -> None:
- if self._is_member_gameadmin(ctx.message.author):
- if game_type not in self._games_filenames:
- await self.bot.say("Game {} is not a supported game!".format(game_type))
- elif game_type in self.games:
- await self.bot.say("Game {} is already loaded!".format(game_type))
- else:
- self._import_helper(game_type, package="discord_games.games")
- await self.bot.say("Game {} loaded.".format(game_type))
- else:
- await self.bot.say("You don't have permission to do that!")
- @commands.command(pass_context=True)
- async def unloadgame(self, ctx:Context, game_type:str) -> None:
- if self._is_member_gameadmin(ctx.message.author):
- if game_type not in self.games:
- await self.bot.say("Game {} hasn't been loaded yet!".format(game_type))
- elif self._is_game_type_live(game_type):
- await self.bot.say("Game {} has game sessions in progress!".format(game_type))
- else:
- del self.games[game_type]
- await self.bot.say("Game {} unloaded.".format(game_type))
- else:
- await self.bot.say("You don't have permission to do that!")
- # Reload all games | Load all games | Unload all games
- @commands.command(pass_context=True)
- async def reloadallgames(self, ctx:Context) -> None:
- if self._is_member_gameadmin(ctx.message.author):
- if len(self.live_games) != 0:
- # Unloading
- await self.bot.say("Unloading games...")
- for game_type in self.games.keys():
- del self.games[game_type]
- await self.bot.say("Games unloaded.")
- # Reloading
- await self.bot.say("Loading games...")
- for filename in self._games_filenames:
- self._import_helper(filename, package="discord_games.games")
- await self.bot.say("All games reloaded.")
- else:
- await self.bot.say("You can't reload all games if any games are live!")
- else:
- await self.bot.say("You don't have permission to do that!")
- @commands.command(pass_context=True)
- async def loadallgames(self, ctx:Context) -> None:
- if self._is_member_gameadmin(ctx.message.author):
- for filename in self._games_filenames:
- self._import_helper(filename, package="discord_games.games")
- await self.bot.say("All games loaded.")
- else:
- await self.bot.say("You don't have permission to do that!")
- @commands.command(pass_context=True)
- async def unloadallgames(self, ctx:Context) -> None:
- if self._is_member_gameadmin(ctx.message.author):
- if len(self.live_games) != 0:
- for game_type in self.games.keys():
- del self.games[game_type]
- await self.bot.say("All games unloaded.")
- else:
- await self.bot.say("You can't reload all games if any games are live!")
- else:
- await self.bot.say("You don't have permission to do that!")
- # Loading games stuff: End #
- # ======================================================================== #
- # The following commands only show the user information
- @commands.command(pass_context=False)
- async def showgames(self) -> None:
- lines = []
- lines.append("```")
- lines.append("Available games:")
- for gamename in self.games.keys():
- lines.append("\t * {}".format(gamename))
- lines.append("```")
- await self.bot.say("\n".join(lines))
- @commands.command(pass_context=False)
- async def showlobbies(self) -> None:
- lines = []
- lines.append("```")
- lines.append("Un-started lobbies:")
- for lobby_name, lobby_info in self.live_games.items():
- num_players = len(lobby_info["session"].players)
- max_players = lobby_info["max_players"]
- started = lobby_info["started"]
- lines.append("\t{lobby_name}: {num_players}/{max_players} players | started?: {started}".format(**locals()))
- lines.append("```")
- await self.bot.say("\n".join(lines))
- # Actual game stuff: Start #
- # ======================================================================== #
- # Helper functions to shorten functions where they're used in + to avoid duplicating code
- def _call_callables_in_args(self, args:Iterable[Any]) -> List[Any]:
- return [arg() if callable(arg) else arg for arg in args]
- def _call_callables_in_kwargs(self, kwargs:Dict[str, Any]) -> Dict[str, Any]:
- return {key:val() if callable(val) else val for key, val in kwargs.items()}
- def _get_invalid_chars(self, chars:str) -> str:
- invalid_chars_found = set()
- for char in chars:
- if char in self.invalid_gamename_chars:
- invalid_chars_found.add(char)
- return ", ".join(invalid_chars_found)
- def _is_gamename_valid(self, name:str) -> bool:
- return not any(char in self.invalid_gamename_chars for char in name)
- @commands.command(pass_context=True)
- async def newgame(self, ctx:Context, game_type:str, gamename:str) -> None:
- try:
- new_game_info = self.games[game_type] # type: GameTypeInfo
- except KeyError:
- await self.bot.say("{} is not a supported game!".format(gamename))
- else:
- if gamename in self.live_games:
- await self.bot.say("Gamesession with name '{}' is already taken!".format(gamename))
- elif self._is_gamename_valid(gamename):
- await self.bot.say("Gamesession '{}' has invalid chars '{}'".format(gamename, self._get_invalid_chars(gamename)))
- elif len(gamename) > self.max_gamename_length:
- await self.bot.say("Gamesession requested by {} has too long of a name".format(ctx.message.author))
- else:
- # Validity checks passed
- # Instantiating the `field_of_play` for the new game session
- field_of_play_info = new_game_info["field_of_play"]
- # Making sure any "saved" function calls in args and kwargs get called
- field_args = field_of_play_info["args"]
- field_kwargs = field_of_play_info["kwargs"]
- new_field_args = self._call_callables_in_args(field_args)
- new_field_kwargs = self._call_callables_in_kwargs(field_kwargs)
- new_field_of_play = field_of_play_info["cls"](*new_field_args, **new_field_kwargs)
- # Getting the other arguments to instantiate a new `GameSession`
- is_move_valid_func = new_game_info["is_move_valid_func"]
- make_move_func = new_game_info["make_move_func"]
- get_messages_func = new_game_info["get_messages_func"]
- win_checker_func = new_game_info["win_checker_func"]
- cleanup_func = new_game_info["cleanup_func"]
- self.live_games[gamename] = {
- "game_type": game_type,
- "max_players": self.games[game_type]["max_players"],
- "session": GameSession(new_field_of_play, is_move_valid_func, make_move_func, get_messages_func, win_checker_func, cleanup_func),
- "owner": ctx.message.author,
- "started": False
- }
- # Adding the creator of the new game session (to the game session)
- new_player_info = new_game_info["player"]
- new_player_cls = new_player_info["cls"]
- # Making sure any "saved" function calls in args and kwargs get called
- args = new_player_info["args"]
- kwargs = new_player_info["kwargs"]
- new_args = self._call_callables_in_args(args)
- new_kwargs = self._call_callables_in_kwargs(kwargs)
- game_session = self.live_games[gamename]["session"]
- game_session.add_player(ctx.message.author, new_player_cls, new_args, new_kwargs)
- await self.bot.say("Gamesession '{}' created".format(gamename))
- @commands.command(pass_context=True)
- async def editgame(self, ctx:Context, gamename:str, edits:str) -> None:
- if gamename not in self.live_games:
- await self.bot.say("Gamesession '{}' is not a live game!".format(gamename))
- elif ctx.message.author != self.live_games[gamename]["owner"]:
- await self.bot.say("You're not the owner of this gamesession {}!".format(ctx.message.author))
- else:
- # Validity checks passed
- # ... # Here you would edit the settings (if any) using `edits`, as well as checking `edits` for validity
- await self.bot.say("This doesn't do anything yet!")
- @commands.command(pass_context=True)
- async def joingame(self, ctx:Context, gamename:str) -> None:
- if gamename not in self.live_games:
- await self.bot.say("Gamesession '{}' is not a live game!".format(gamename))
- elif ctx.message.author in self.live_games[gamename]["session"].players:
- await self.bot.say("You're already in the game! ({})".format(gamename))
- elif len(self.live_games[gamename]["session"].players) == self.live_games[gamename]["max_players"]:
- await self.bot.say("Gamesession '{}' is full.".format(gamename))
- else:
- # Validity checks passed
- # Adding the new player to the game session
- player_info = self.games[self.live_games[gamename]["game_type"]]["player"] # Info about how to create new player objects for this game
- new_player_cls = player_info["cls"]
- # Making sure any "saved" function calls in args and kwargs get called
- args = player_info["args"]
- kwargs = player_info["kwargs"]
- new_args = self._call_callables_in_args(args)
- new_kwargs = self._call_callables_in_kwargs(kwargs)
- self.live_games[gamename]["session"].add_player(ctx.message.author, new_player_cls, new_args, new_kwargs)
- @commands.command(pass_context=True)
- async def startgame(self, ctx:Context, gamename:str) -> None:
- if gamename not in self.live_games:
- await self.bot.say("Gamesession '{}' is not a live game!".format(gamename))
- elif ctx.message.author != self.live_games[gamename]["owner"]:
- await self.bot.say("You're not the owner of this gamesession {}!".format(ctx.message.author))
- else:
- # Validity checks passed
- # Rewrite this mess
- game_info = self.games[self.live_games[gamename]["game_type"]]
- # Generate a turn order and set the first player turn (`current_player_turn`) to the first value yielded by `game_session.turn_order`
- game_session = self.live_games[gamename]["session"]
- game_session.turn_order = cycle(game_info["choose_turn_order_func"](game_session.players.keys()))
- game_session.current_player_turn = next(game_session.turn_order)
- self.live_games[gamename]["started"] = True # Let the function that handles commands for moves know that it should accept moves
- messages_to_say = game_session.get_messages(DrawCode.start_of_game)
- for msg in messages_to_say:
- await self.bot.say(msg)
- @commands.command(pass_context=True)
- async def play(self, ctx:Context, gamename:str, move_name:str, *args:str) -> None:
- if gamename not in self.live_games:
- await self.bot.say("Gamesession '{}' is not a live game!".format(gamename))
- elif ctx.message.author not in self.live_games[gamename]["session"].players:
- await self.bot.say("You're not in this gamesession {}!".format(ctx.message.author))
- elif not self.live_games[gamename]["started"]:
- await self.bot.say("Gamesession '{}' has not started yet!".format(gamename))
- elif move_name not in self.games[self.live_games[gamename]["game_type"]]:
- await self.bot.say("Move named '{}' is not a supported move for this game!".format(move_name))
- else:
- # Validity checks passed
- game_session = self.live_games[gamename]["session"]
- move_is_valid, reason_for_invalid = game_session.is_move_valid(move_name, args)
- if ctx.message.author == game_session.current_player_turn:
- if move_is_valid:
- messages_to_say = game_session.make_move(move_name, args)
- if messages_to_say is not None:
- for msg in messages_to_say:
- await self.bot.say(msg)
- win_code, winners = game_session.check_win()
- if win_code == WinCode.win:
- messages_to_say = game_session.cleanup_game(winners)
- for msg in messages_to_say:
- await self.bot.say(msg)
- elif win_code == WinCode.draw:
- messages_to_say = game_session.get_messages(DrawCode.game_draw)
- for msg in messages_to_say:
- await self.bot.say(msg)
- else: # has_won == WinCode.no_win
- messages_to_say = game_session.get_messages(DrawCode.no_win)
- for msg in messages_to_say:
- await self.bot.say(msg)
- else:
- await self.bot.say("Move is not valid for reason:\n\t{}".format(reason_for_invalid))
- else:
- await self.bot.say("It's not your turn {}!".format(ctx.message.author))
- # Actual game stuff: End #
- # ======================================================================== #
- # Utility stuff for testing: START: #
- # ======================================================================== #
- @commands.command(pass_context=True)
- async def showinternals(self, ctx:Context) -> None:
- if self._is_member_gameadmin(ctx.message.author):
- lines = []
- lines.append("```")
- lines.append("My internals:")
- for attr in vars(self):
- lines.append("\t{}={}".format(attr, self.__dict__[attr]))
- lines.append("```")
- msg = "\n".join(lines)
- await self.bot.say(msg)
- else:
- await self.bot.say("You don't have permission to do that!")
- # Utility stuff for testing: END: #
- # ======================================================================== #
Add Comment
Please, Sign In to add comment