from abc import ABC, abstractclassmethod
from copy import deepcopy


class TwoPlayerGame(ABC):
    """
    Base class for... wait for it... two-players games !

    To define a new game, make a subclass of TwoPlayerGame, and define
    the following methods:

    - ``__init__(self, players, ...)`` : initialization of the game
    - ``possible_moves(self)`` : returns of all moves allowed
    - ``make_move(self, move)``: transforms the game according to the move
    - ``is_over(self)``: check whether the game has ended

    The following methods are optional:

    - ``show(self)`` : prints/displays the game
    - ``scoring``: gives a score to the current game (for the AI)
    - ``unmake_move(self, move)``: how to unmake a move (speeds up the AI)
    - ``ttentry(self)``: returns a string/tuple describing the game.
    - ``ttrestore(self, entry)``: use string/tuple from ttentry to restore a game.

    The __init__ method *must* do the following actions:

    - Store ``players`` (which must be a list of two Players) into
      self.players
    - Tell which player plays first with ``self.current_player = 1 # or 2``

    When defining ``possible_moves``, you must keep in mind that you
    are in the scope of the *current player*. More precisely, a
    subclass of TwoPlayerGame has the following attributes that
    indicate whose turn it is. These methods can be used but should not
    be overwritten:

    - ``self.player`` : the current player (e.g. ``Human_Player``)
    - ``self.opponent`` : the current Player's opponent (Player).
    - ``self.current_player``: the number (1 or 2) of the current player.
    - ``self.opponent_index``: the number (1 or 2) of the opponent.
    - ``self.nmove``: How many moves have been played so far ?

    For more, see the examples in the dedicated folder.

    Examples:
    ----------

    ::

        from easyAI import TwoPlayerGame, Human_Player

        class Sticks( TwoPlayerGame ):
            ''' In turn, the players remove one, two or three sticks from
                a pile. The player who removes the last stick loses '''

            def __init__(self, players):
                self.players = players
                self.pile = 20 # start with 20 sticks
                self.current_player = 1 # player 1 starts
            def possible_moves(self): return ['1','2','3']
            def make_move(self,move): self.pile -= int(move)
            def is_over(self): return self.pile <= 0

        p
        game = Sticks( [Human_Player(), Human_Player() ] )
        game.play()


    """

    @abstractclassmethod
    def possible_moves(self):
        pass

    @abstractclassmethod
    def make_move(self, move):
        pass

    @abstractclassmethod
    def is_over(self):
        pass

    def play(self, nmoves=1000, verbose=True):
        """
        Method for starting the play of a game to completion. If one of the
        players is a Human_Player, then the interaction with the human is via
        the text terminal.

        Parameters
        -----------

        nmoves:
          The limit of how many moves (plies) to play unless the game ends on
          it's own first.

        verbose:
          Setting verbose=True displays additional text messages.
        """

        history = []

        if verbose:
            self.show()

        for self.nmove in range(1, nmoves + 1):

            if self.is_over():
                break

            move = self.player.ask_move(self)
            history.append((deepcopy(self), move))
            self.make_move(move)

            if verbose:
                print(
                    "\nMove #%d: player %d plays %s :"
                    % (self.nmove, self.current_player, str(move))
                )
                self.show()

            self.switch_player()

        history.append(deepcopy(self))

        return history

    @property
    def opponent_index(self):
        return 2 if (self.current_player == 1) else 1

    @property
    def player(self):
        return self.players[self.current_player - 1]

    @property
    def opponent(self):
        return self.players[self.opponent_index - 1]

    def switch_player(self):
        self.current_player = self.opponent_index

    def copy(self):
        return deepcopy(self)

    def get_move(self):
        """
        Method for getting a move from the current player. If the player is an
        AI_Player, then this method will invoke the AI algorithm to choose the
        move. If the player is a Human_Player, then the interaction with the
        human is via the text terminal.
        """
        return self.player.ask_move(self)

    def play_move(self, move):
        """
        Method for playing one move with the current player. After making the move,
        the current player will change to the next player.

        Parameters
        -----------

        move:
          The move to be played. ``move`` should match an entry in the ``.possibles_moves()`` list.
        """
        result = self.make_move(move)
        self.switch_player()
        return result
