diff --git a/README.md b/README.md index 14740bb..6d30952 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,11 @@ **Framework** for testing **AI** system in basic or logic games. +## Documentation + +The documentation is available at [Read the Docs](https://iarena.readthedocs.io/en/latest/). + + ## Architecture ### Interfaces diff --git a/docs/index.rst b/docs/index.rst index d3ebd6b..4ea2529 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -7,6 +7,7 @@ :hidden: /src/getting_started + /src/installation .. toctree:: diff --git a/docs/src/players/players.rst b/docs/src/players/players.rst index f13ee4f..fdd8176 100644 --- a/docs/src/players/players.rst +++ b/docs/src/players/players.rst @@ -46,17 +46,25 @@ The player can create its own movement or choose one from the list of possible m .. code-block:: python - def play( - self, - position: IPosition) -> IMovement: + from IArena.interfaces.IPlayer import IPlayer + from IArena.games.Hanoi import HanoiRules, HanoiMovement, HanoiPosition + + class MyPlayer(IPlayer): + + def play( + self, + position: HanoiPosition) -> HanoiMovement: - # Create next movement from scratch - movement = IMovement(...) + # Create next movement from scratch + movement = IMovement(...) - # Choose one from the list of possible movements - rules = position.get_rules() - possible_movements = rules.possible_movements(position) - movement = possible_movements[...] + # Choose one from the list of possible movements + rules = position.get_rules() + possible_movements = rules.possible_movements(position) + movement = possible_movements[...] + + # Return the movement + return movement ----- @@ -73,7 +81,7 @@ If you prefer to see step by step the game playing by the player, use ``Broadcas .. code-block:: python from IArena.arena.GenericGame import GenericGame # or BroadcastGame - from IArena.games.Hanoi import HanoiRules, HanoiMovement, HanoiPosition + from IArena.games.Hanoi import HanoiRules rules = HanoiRules() my_player = MyPlayer() @@ -85,8 +93,9 @@ If you prefer to see step by step the game playing by the player, use ``Broadcas score = arena.play() +================= Multiplayer games -^^^^^^^^^^^^^^^^^ +================= In games with more than 1 player, you would need another player to play against. There are several generic players implemented, please check :ref:`random_player`. @@ -107,3 +116,76 @@ There are several generic players implemented, please check :ref:`random_player` players=[my_player, other_player] ) score = arena.play() + +You may want to repeat the game several times to get a better score. +For this purpose just use a loop and accumulate the score. + +.. code-block:: python + + from IArena.arena.GenericGame import GenericGame # or BroadcastGame + from IArena.games.Coins import CoinsRules, CoinsMovement, CoinsPosition + from IArena.players.players import RandomPlayer + + rules = CoinsRules() + my_player = MyPlayer() + other_player = RandomPlayer() + + score = 0 + for _ in range(50): + arena = GenericGame( + rules=rules, + players=[my_player, other_player] + ) + score += arena.play() + + for _ in range(50): + arena = GenericGame( + rules=rules, + players=[other_player, my_player] + ) + score += arena.play() + + print(score) + + +================ +Heuristic Player +================ + +Other way to create a player is to give a heuristic for a position, rather than a movement. +The already implement player ``HeuristicPlayer`` does check every possible future position, +by trying every possible movement in a position, +and calculates the score of the position by the method ``heuristic``. +Then, it decides the movement with the lower heuristic. + +.. code-block:: python + + from IArena.arena.GenericGame import GenericGame # or BroadcastGame + from IArena.games.Hanoi import HanoiRules, HanoiMovement, HanoiPosition + from IArena.players.HeuristicPlayer import HeuristicPlayer + + class MyPlayer(HeuristicPlayer): + + def heuristic(self, position: HanoiPosition) -> float: + # Add code to calculate the heuristic of the position + return 0.0 + + rules = HanoiRules() + my_player = MyPlayer() + + arena = GenericGame( + rules=rules, + players=[my_player] + ) + + +.. note:: + + Remember that the best movement is the one with the lowest heuristic. + +Multiplayer game heuristic player +--------------------------------- + +One thing to take into account is that creating an heuristic for a multiplayer game, +the heuristic must be calculated as the position given is the one that the opponent will play. +Because the positions in which it applies are the next ones, and not the current ones. diff --git a/src/IArena/players/HeuristicPlayer.py b/src/IArena/players/HeuristicPlayer.py new file mode 100644 index 0000000..2c3ae27 --- /dev/null +++ b/src/IArena/players/HeuristicPlayer.py @@ -0,0 +1,33 @@ + +from IArena.interfaces.IPosition import IPosition +from IArena.interfaces.IMovement import IMovement +from IArena.interfaces.IPlayer import IPlayer +from IArena.utils.decorators import override, pure_virtual + +class HeuristicPlayer(IPlayer): + + @override + def play( + self, + position: IPosition) -> IMovement: + # Get rules of the game + rules = position.get_rules() + + # Get all movements + movements = rules.possible_movements(position) + + # Calculate heuristic for all possible positions from every possible movement + values = [ + self.heuristic( + rules.next_position(movement, position)) + for movement + in movements] + + # Return the best movement + return movements[values.index(min(values))] + + @pure_virtual + def heuristic( + self, + position: IPosition) -> float: + pass diff --git a/src/IArena/players/players.py b/src/IArena/players/players.py index aad324c..5b9b193 100644 --- a/src/IArena/players/players.py +++ b/src/IArena/players/players.py @@ -1,52 +1,40 @@ import random -# from typing import override from IArena.interfaces.IPosition import IPosition -from IArena.interfaces.IGameRules import IGameRules from IArena.interfaces.IMovement import IMovement -from IArena.interfaces.PlayerIndex import PlayerIndex from IArena.interfaces.IPlayer import IPlayer from IArena.utils.decorators import override -class DummyPlayer(IPlayer): - def __init__( - self, - rules: IGameRules, - player_index: PlayerIndex): - self.rules_ = rules - self.player_index_ = player_index - - -class FirstPlayer(DummyPlayer): +class FirstPlayer(IPlayer): @override def play( self, position: IPosition) -> IMovement: - return self.rules_.possible_movements(position)[0] + return position.get_rules().possible_movements(position)[0] -class LastPlayer(DummyPlayer): +class LastPlayer(IPlayer): @override def play( self, position: IPosition) -> IMovement: - return self.rules_.possible_movements(position)[-1] + return position.get_rules().possible_movements(position)[-1] -class RandomPlayer(DummyPlayer): +class RandomPlayer(IPlayer): @override def play( self, position: IPosition) -> IMovement: - return random.choice(self.rules_.possible_movements(position)) + return random.choice(position.get_rules().possible_movements(position)) -class PlayablePlayer(DummyPlayer): +class PlayablePlayer(IPlayer): SeparatorN = 40 @@ -55,7 +43,7 @@ def play( self, position: IPosition) -> IMovement: - possibilities = list(self.rules_.possible_movements(position)) + possibilities = list(position.get_rules().possible_movements(position)) print ("=" * PlayablePlayer.SeparatorN) print (f"Next player: {position.next_player()}")