Bot-Man-Toe/pyclient/games/game.py

139 lines
4.6 KiB
Python

"""
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 {}