Source code for core.enums
from typing import Final
from enum import StrEnum, Flag, auto
from functools import reduce
[docs]
class Command(StrEnum):
"""
Available command.
"""
INFO = "info"
"""
| Displays the identifier string of the engine and list of its capabilities.
| See https://github.com/jonthysell/Mzinga/wiki/UniversalHiveProtocol#info.
"""
HELP = "help"
"""
| Displays the list of available commands.
| If a command is specified, displays the help for that command.
"""
OPTIONS = "options"
"""
| Displays the available options for the engine.
| See https://github.com/jonthysell/Mzinga/wiki/UniversalHiveProtocol#options.
"""
NEWGAME = "newgame"
"""
| Starts a game. The game type and state depend on the command arguments.
| See https://github.com/jonthysell/Mzinga/wiki/UniversalHiveProtocol#newgame.
"""
VALIDMOVES = "validmoves"
"""
| Displays a list of every valid move in the current game.
| See https://github.com/jonthysell/Mzinga/wiki/UniversalHiveProtocol#validmoves.
"""
BESTMOVE = "bestmove"
"""
| Search for the best move for the current game.
| See https://github.com/jonthysell/Mzinga/wiki/UniversalHiveProtocol#bestmove.
"""
PLAY = "play"
"""
| Plays the specified MoveString in the current game.
| See https://github.com/jonthysell/Mzinga/wiki/UniversalHiveProtocol#play.
"""
PASS = "pass"
"""
| Plays a passing move in the current game.
| See https://github.com/jonthysell/Mzinga/wiki/UniversalHiveProtocol#pass.
"""
UNDO = "undo"
"""
| Undoes the specified amount of moves in the current game.
| See https://github.com/jonthysell/Mzinga/wiki/UniversalHiveProtocol#undo.
"""
EXIT = "exit"
"""
Exits the engine.
"""
[docs]
class Option(StrEnum):
"""
Engine option.
"""
STRATEGY_WHITE = "StrategyWhite"
"""
AI strategy for player white.
"""
STRATEGY_BLACK = "StrategyBlack"
"""
AI strategy for player black.
"""
MAX_BRANCHING_FACTOR = "MaxBranchingFactor"
"""
Maximum amount of moves that can be considered for each node by Negamax agents.
"""
NUM_THREADS = "NumThreads"
"""
Available threads to parallelize AI thinking.
"""
[docs]
class OptionType(StrEnum):
"""
Option type.
"""
BOOL = "bool"
"""
Boolean type.
"""
INT = "int"
"""
Integer type.
"""
FLOAT = "double"
"""
Decimal type.
"""
ENUM = "enum"
"""
Enum type.
"""
[docs]
class Strategy(StrEnum):
"""
Possible AI strategy.
"""
RANDOM = "Random"
"""
Random strategy.
"""
NEGAMAX = "Negamax"
"""
Negamax with alpha-beta pruning strategy.
"""
[docs]
class PlayerColor(StrEnum):
"""
Player color.
"""
WHITE = "White"
"""
White.
"""
BLACK = "Black"
"""
Black.
"""
@property
def code(self) -> str:
"""
Short string representation.
:rtype: str
"""
return self[0].lower()
@property
def opposite(self):
"""
Opposite color.
:rtype: PlayerColor
"""
return _EnumUtils.PLAYER_COLOR_OPPOSITES[self]
[docs]
class GameState(StrEnum):
"""
Game state.
"""
NOT_STARTED = "NotStarted"
"""
Game not started yet.
"""
IN_PROGRESS = "InProgress"
"""
Game in progress.
"""
DRAW = "Draw"
"""
Game ended with a draw.
"""
WHITE_WINS = "WhiteWins"
"""
Game ended with victory for white.
"""
BLACK_WINS = "BlackWins"
"""
Game ended with victory for black.
"""
[docs]
@classmethod
def parse(cls, state: str):
"""
Parses a GameStateString.
:param state: GameStateString.
:type state: str
:return: GameState.
:rtype: GameState
"""
return GameState(state) if state else GameState.NOT_STARTED
[docs]
class GameType(Flag):
"""
| Game type.
| Determines the available pieces.
"""
BASE = auto()
"""
Base GameType, without expansions.
"""
M = auto()
"""
Mosquito GameType, with the Mosquito expansion.
"""
L = auto()
"""
Ladybug GameType, with the Ladybug expansion.
"""
P = auto()
"""
Pillbug GameType, with the Pillbug expansion.
"""
[docs]
@classmethod
def parse(cls, game_type: str):
"""
| Parses a GameTypeString.
| The GameTypeString always needs to include the Base GameType.
:param type: GameTypeString.
:type type: str
:raises ValueError: If it's not a valid GameTypeString.
:return: GameType.
:rtype: GameType
"""
if game_type:
base, *expansions = game_type.upper().split("+")
try:
if GameType[base] != GameType.BASE or expansions == [""] or len(expansions) > 1 and game_type.find("+") >= 0: raise KeyError()
return reduce(lambda type, expansion: type | expansion, [GameType[expansion] for expansion in (expansions[0] if expansions else "")], GameType[base])
except KeyError as e:
raise ValueError(f"'{game_type}' is not a valid GameType") from e
return GameType.BASE
@property
def tag(self) -> str:
return (self.name or "").title()
@property
def index(self) -> int:
subset = [flag for flag in GameType if flag != GameType.BASE]
index = sum(1 << i for i, flag in enumerate(subset) if flag in self)
return index
def __str__(self) -> str:
return "".join(gametype.tag + ("+" if gametype is GameType.BASE and len(self) > 1 else "") for gametype in self)
def __repr__(self):
return self.__str__()
[docs]
class BugType(StrEnum):
"""
Bug type.
"""
QUEEN_BEE = "Q"
"""
| Queen Bee.
| Moves by 1 cell.
| Must be placed after the first turn and by the fourth.
| Surrounding this piece with other pieces is the win condition.
"""
SPIDER = "S"
"""
| Spider.
| Moves by exactly 3 cells around the hive.
"""
BEETLE = "B"
"""
| Beetle.
| Moves by 1 cell, but can also go above the hive.
| Pieces below the Beetle cannot move and the cell is now considered of the Beetle's color.
| Can go above other pieces already above the hive, piling up.
"""
GRASSHOPPER = "G"
"""
| Grasshopper.
| Moves in a straight by jumping over other pieces.
"""
SOLDIER_ANT = "A"
"""
| Soldier Ant.
| Moves by any amount of cells around the hive.
"""
MOSQUITO = "M"
"""
| Mosquito.
| Available with GameType expansion M.
| Moves by coping adiacent pieces' moveset.
| If it goes above the hive by coping a Beetle, it can keep moving like a Beetle until it goes back down.
| If it's only neighboring piece is another Mosquito, it cannot move.
"""
LADYBUG = "L"
"""
| Ladybug.
| Available with GameType expansion L.
| Moves by exactly 3 cells, like a Spider, but the first 2 steps must be above the hive.
"""
PILLBUG = "P"
"""
| Pillbug.
| Available with GameType expansion P.
| Moves by 1 cell.
| Instead of moving itself, it can move a neighboring piece above itself, and then back down on a neighboring empty cell.
"""
[docs]
class Direction(StrEnum):
"""
| Hexagonal multi-layered grid direction.
| The grid is assumed to be oriented with cells point-up.
"""
RIGHT = "|-"
"""
Right side of the hex: bug-
"""
UP_RIGHT = "|/"
"""
Up right side of the hex: bug/
"""
UP_LEFT = "\\|"
"""
Up left side of the hex: \\bug
"""
LEFT = "-|"
"""
Left side of the hex: -bug
"""
DOWN_LEFT = "/|"
"""
Down left side of the hex: /bug
"""
DOWN_RIGHT = "|\\"
"""
Down right side of the hex: bug\\
"""
[docs]
@classmethod
def lefts(cls):
"""
Returns all left directions.
:return: List of left directions.
:rtype: list[Direction]
"""
return _EnumUtils.DIRECTION_LEFTS
[docs]
@classmethod
def rights(cls):
"""
Returns all right directions.
:return: List of right directions.
:rtype: list[Direction]
"""
return _EnumUtils.DIRECTION_RIGHTS
def __str__(self) -> str:
return self.replace("|", "")
def __repr__(self):
return self.__str__()
@property
def opposite(self):
"""
Opposite direction.
:rtype: Direction
"""
return _EnumUtils.DIRECTION_OPPOSITES[self]
@property
def clockwise(self):
"""
Direction to the right (clockwise).
:rtype: Direction
"""
return _EnumUtils.DIRECTION_CLOCKWISES[self]
@property
def anticlockwise(self):
"""
Direction to the left (anticlockwise).
:rtype: Direction
"""
return _EnumUtils.DIRECTION_ANTICLOCKWISES[self]
@property
def delta_index(self) -> int:
"""
Direction index.
:rtype: int
"""
return _EnumUtils.DIRECTION_DELTA_INDICES[self]
@property
def is_right(self) -> bool:
"""
Whether it's a right direction.
:rtype: bool
"""
return self in Direction.rights()
@property
def is_left(self) -> bool:
"""
Whether it's a left direction.
:rtype: bool
"""
return self in Direction.lefts()
class _EnumUtils:
"""
Internal utility class to make some enum methods and properties run in constant time.
"""
PLAYER_COLOR_OPPOSITES: Final[dict[PlayerColor, PlayerColor]] = {color: PlayerColor.BLACK if color is PlayerColor.WHITE else PlayerColor.WHITE for color in PlayerColor}
"""
Map of player colors to their opposite.
"""
DIRECTION_LEFTS: Final[list[Direction]] = [direction for direction in Direction if direction is Direction.LEFT or direction is Direction.UP_LEFT or direction is Direction.DOWN_LEFT]
"""
List of left directions.
"""
DIRECTION_RIGHTS: Final[list[Direction]] = [direction for direction in Direction if direction is Direction.RIGHT or direction is Direction.UP_RIGHT or direction is Direction.DOWN_RIGHT]
"""
List of right directions.
"""
DIRECTION_DELTA_INDICES: Final[dict[Direction, int]] = {direction: list(Direction).index(direction) for direction in Direction}
"""
Map of directions to their indices.
"""
DIRECTION_OPPOSITES: Final[dict[Direction, Direction]] = {direction: list(Direction)[(list(Direction).index(direction) + 3) % 6] for direction in list(Direction)}
"""
Map of directions to their opposite.
"""
DIRECTION_CLOCKWISES: Final[dict[Direction, Direction]] = {direction: list(Direction)[(list(Direction).index(direction) + 5) % 6] for direction in list(Direction)}
"""
Map of directions to their clockwise (right) neighboring direction.
"""
DIRECTION_ANTICLOCKWISES: Final[dict[Direction, Direction]] = {direction: list(Direction)[(list(Direction).index(direction) + 1) % 6] for direction in list(Direction)}
"""
Map of directions to their anitclockwise (left) neighboring direction.
"""