""" This module contains the base class for any game. If you'd like to create a new game, please copy this module and fill in all the necessary methods in order to have a functioning game. """ from __future__ import annotations from enum import Enum, auto from typing import Any class FinishState(Enum): draw = auto() loss = auto() win = auto() class Game: """ Base class for all games. A game is always a snapshot of a game that is paused because it waits for a user to make a move. """ def action_name(self) -> str: """ Return the type of action to take. Please only use alphanumeric characters. This helps the player understand what they are supposed to do. For example, in Risk, you can use this to explain whether they are expected to "recruit" or "attack" or "move" with their troops. If all moves require the same action (such as with tic-tac-toe), you can leave this to return an empty string. """ return "" def as_seen_by(self, player : int) -> dict[str, Any]: """ From the perspective of a given player, return the game's state. Note that it is very common in games for players to not have ALL the details available. For example, in card games, you are generally unable to see the cards in your opponents' hands. The game's state is formatted in a way that makes it easy to convert it to JSON or a tensor. :param player: Player. Counting starts from one. :type player: int :return: The game's state from the perspective of the player. :rtype: dict[str, Any] """ return {} @classmethod def empty(cls) -> "Game": """ Create a new game. Use this method to shuffle the cards, to arrange pieces on the board, and set up the state in a way where the first player can start to make its move. :return: A game that's ready to start. :rtype: Game """ return cls() def game_name(self) -> str: """ Return a non-empty string that uniquely identifies the game. When creating a new unspecified game, please respect the Java package naming convention. """ return "default-game" def move_default(self) -> "Game": """ Fallback move option for when a user isn't available or cannot decide. Program this move to be predictable, and resemble an attempted skip as much as possible. For example:" - In Go, place a tile in an empty space near the top-left corner - In Monopoly, choose not to buy anything - In poker, check. (Or fold after a raise.) - In a maze, choose the left-most path """ # NOTE: Games are meant to be immutable values! # As such, you should always consider creating a deep copy of oneself, # do not simply return self if it might update. return self def move(self, payload : dict[str, Any] | None = None) -> "Game": """ Make a move on behalf of whose turn it is. :param payload: Dictionary containing the player's response. :type payload: dict[str, Any] | None :return: A new instance of the game where a new action is required. :rtype: Game """ if not isinstance(payload, dict): return self.move_default() # NOTE: Games are meant to be immutable values! # As such, you should always consider creating a deep copy of oneself, # do not simply return self if it might update. return self def player_to_move(self) -> int: """ Return which player is currently meant to move. :return: Which player to move. Counting starts from one. :rtype: int """ return 1 def to_dict(self) -> dict[str, Any]: """ Return a dictionary containing the full game state. :return: The current game's state. :rtype: dict[str, Any] """ return {} def winner(self) -> dict[int, FinishState] | None: """ Determine whether the game has ended. :return: A list detailing which players have won, lost or drawn - or None if the game hasn't finished. :rtype: dict[int, FinishState] | None """ return {}