"""Contains Game, HumanPlayer, AIType, AIEasy and AIMiniMax classes.

AIType is parent class for AIEasy and AIMiniMax child classes.
"""

import random

from lib import gui
from lib.minimax_algorithm import MiniMax


class Game(object):
    """Tracks state of the game board, players move history and executes both player's moves."""
    
    def __init__(self, mode):
        self.mode = mode

        self.game_over = False
        self.winner = None # Will be 0=draw, 1=player, 2=Ai

        self.move_no = 1

        # 0=empty, 1=player, 2=AI
        self.board_matrix = [0,0,0,0,0,0,0,0,0]
        
        # Matrix of ways of winning
        self.win_matrix = [[0,1,2],# rows
                          [3,4,5],
                          [6,7,8],
                          [0,3,6],# columns
                          [1,4,7],
                          [2,5,8],
                          [0,4,8],# diagonals
                          [6,4,2],
                          ]
        
        if self.mode == 'easy':
            self.players = (HumanPlayer(self), AIEasy(self))
        elif self.mode == 'impossible':
            self.players = (HumanPlayer(self), AIMiniMax(self))
        
    def update(self, player_move):
        """Execute player's move and allow computer to take turn. Check for win after each turn."""
        if not player_move == None:
            self.players[0].update(player_move)
            self.move_no += 1
            self.check_win()
        if self.winner == None:
            self.players[1].update()
            self.move_no +=1
            self.check_win()

    def check_win(self):
        """Check for win, loose or draw."""
        # Check against win matrix if game is won or lost
        for player in [1,2]:
            for line in self.win_matrix:
                chain_len = 0
                for idx in line:
                    if self.board_matrix[idx] == player:
                        chain_len += 1
                        if chain_len == 3:
                            self.winner = player
                            return
                            
        # If empty sapce present then game is not over, otherwise its a draw
        for sqr in self.board_matrix:
            if sqr == 0:
                self.winner = None
                return
        self.winner = 0
        
     
class HumanPlayer(object):
    """Track and execute human player's moves."""

    def __init__(self, game):
        self.game = game
        self.moves = []

    def update(self, player_move):
        """Take player's move and update board matrix and moves list."""
        self.game.board_matrix[player_move] = 1
        self.moves.append(player_move)


class AIType(object):
    """Parent class for types of AI difficulty."""
    
    def __init__(self, game):
        self.game = game
        self.board_matrix = self.game.board_matrix
        self.win_matrix = self.game.win_matrix
        self.moves = []
        self.players = (1,2)

    def random(self):
        """Random method selects a move to make randomly from available squares on game board"""
        avail_squares = []
        for idx, sqr in enumerate(self.board_matrix):
            if sqr == 0:
                avail_squares.append(idx)
        nxt_move = random.choice(avail_squares)
        return nxt_move


class AIEasy(AIType):
    """Class for selecting and tracking moves when Easy difficulty setting selected in game menu"""
        
    def update(self):
        """Selects move by completing winning row, blocking user's winning row or otherwise
        randomly selecting an available tile. Then updates game board and list of moves."""
        nxt_move = self.win_or_block()
        if nxt_move == None:
            nxt_move = self.random()
        
        #Making move
        self.board_matrix[nxt_move] = 2
        self.moves.append(nxt_move)

    # If one move from winning or losing complete line to win or block.
    def win_or_block(self):
        """Checks for rows of 2 X's or O's where game could be won or user blocked from winning."""
        for p in reversed(self.players): #Reversed to check for winning moves before blocking moves.
            for line in self.win_matrix:
                chain_len = 0
                empty_sqr = None
                for idx in line:
                    if self.board_matrix[idx] == 0:
                        empty_sqr = idx
                    elif self.board_matrix[idx] == p:
                        chain_len += 1
                if chain_len == 2 and empty_sqr:
                    nxt_move = empty_sqr
                    return nxt_move
        return None


class AIMiniMax(AIType):
    """Class for managing computer's moves when impossible difficulty selected in game menu"""
    
    def __init__(self, game):
        AIType.__init__(self, game)
        
        self.minimax = MiniMax(self.win_matrix)
        self.moves_until_win = None
    
    def update(self):
        """Select move using minimax algorithm, update game board and list of moves."""
        if self.game.move_no == 1:
            nxt_move = self.random()
        else:
            minimax_score = self.minimax.minimax_scores(self.board_matrix)
            nxt_move = self.minimax.choice
            
            # If there is a guarenteed path to winnig then calculate remaining moves until win.
            if minimax_score > 0:
                self.moves_until_win = (9 - minimax_score)/2
        
        #Making move
        self.board_matrix[nxt_move] = 2
        self.moves.append(nxt_move)

"""Contains Game, HumanPlayer, AIType, AIEasy and AIMiniMax classes.

AIType is parent class for AIEasy and AIMiniMax child classes.
"""

import random

from lib import gui
from lib.minimax_algorithm import MiniMax


class Game(object):
    """Tracks state of the game board, players move history and executes both player's moves."""
    
    def __init__(self, mode):
        self.mode = mode

        self.game_over = False
        self.winner = None # Will be 0=draw, 1=player, 2=Ai

        self.move_no = 1

        # 0=empty, 1=player, 2=AI
        self.board_matrix = [0,0,0,0,0,0,0,0,0]
        
        # Matrix of ways of winning
        self.win_matrix = [[0,1,2],# rows
                          [3,4,5],
                          [6,7,8],
                          [0,3,6],# columns
                          [1,4,7],
                          [2,5,8],
                          [0,4,8],# diagonals
                          [6,4,2],
                          ]
        
        if self.mode == 'easy':
            self.players = (HumanPlayer(self), AIEasy(self))
        elif self.mode == 'impossible':
            self.players = (HumanPlayer(self), AIMiniMax(self))
        
    def update(self, player_move):
        """Execute player's move and allow computer to take turn. Check for win after each turn."""
        if not player_move == None:
            self.players[0].update(player_move)
            self.move_no += 1
            self.check_win()
        if self.winner == None:
            self.players[1].update()
            self.move_no +=1
            self.check_win()

    def check_win(self):
        """Check for win, loose or draw."""
        # Check against win matrix if game is won or lost
        for player in [1,2]:
            for line in self.win_matrix:
                chain_len = 0
                for idx in line:
                    if self.board_matrix[idx] == player:
                        chain_len += 1
                        if chain_len == 3:
                            self.winner = player
                            return
                            
        # If empty sapce present then game is not over, otherwise its a draw
        for sqr in self.board_matrix:
            if sqr == 0:
                self.winner = None
                return
        self.winner = 0
        
     
class HumanPlayer(object):
    """Track and execute human player's moves."""

    def __init__(self, game):
        self.game = game
        self.moves = []

    def update(self, player_move):
        """Take player's move and update board matrix and moves list."""
        self.game.board_matrix[player_move] = 1
        self.moves.append(player_move)


class AIType(object):
    """Parent class for types of AI difficulty."""
    
    def __init__(self, game):
        self.game = game
        self.board_matrix = self.game.board_matrix
        self.win_matrix = self.game.win_matrix
        self.moves = []
        self.players = (1,2)

    def random(self):
        """Random method selects a move to make randomly from available squares on game board"""
        avail_squares = []
        for idx, sqr in enumerate(self.board_matrix):
            if sqr == 0:
                avail_squares.append(idx)
        nxt_move = random.choice(avail_squares)
        return nxt_move


class AIEasy(AIType):
    """Class for selecting and tracking moves when Easy difficulty setting selected in game menu"""
        
    def update(self):
        """Selects move by completing winning row, blocking user's winning row or otherwise
        randomly selecting an available tile. Then updates game board and list of moves."""
        nxt_move = self.win_or_block()
        if nxt_move == None:
            nxt_move = self.random()
        
        #Making move
        self.board_matrix[nxt_move] = 2
        self.moves.append(nxt_move)

    # If one move from winning or losing complete line to win or block.
    def win_or_block(self):
        """Checks for rows of 2 X's or O's where game could be won or user blocked from winning."""
        for p in reversed(self.players): #Reversed to check for winning moves before blocking moves.
            for line in self.win_matrix:
                chain_len = 0
                empty_sqr = None
                for idx in line:
                    if self.board_matrix[idx] == 0:
                        empty_sqr = idx
                    elif self.board_matrix[idx] == p:
                        chain_len += 1
                if chain_len == 2 and empty_sqr:
                    nxt_move = empty_sqr
                    return nxt_move
        return None


class AIMiniMax(AIType):
    """Class for managing computer's moves when impossible difficulty selected in game menu"""
    
    def __init__(self, game):
        AIType.__init__(self, game)
        
        self.minimax = MiniMax(self.win_matrix)
        self.moves_until_win = None
    
    def update(self):
        """Select move using minimax algorithm, update game board and list of moves."""
        if self.game.move_no == 1:
            nxt_move = self.random()
        else:
            minimax_score = self.minimax.minimax_scores(self.board_matrix)
            nxt_move = self.minimax.choice
            
            # If there is a guarenteed path to winnig then calculate remaining moves until win.
            if minimax_score > 0:
                self.moves_until_win = (9 - minimax_score)/2
        
        #Making move
        self.board_matrix[nxt_move] = 2
        self.moves.append(nxt_move)

Contains Game, HumanPlayer, AIType, AIEasy and AIMiniMax classes.  AIType is parent class for AIEasy and AIMiniMax child classes. Tracks state of the game board, players move history and executes both player's moves. # Will be 0=draw, 1=player, 2=Ai # 0=empty, 1=player, 2=AI # Matrix of ways of winning # rows # columns # diagonals Execute player's move and allow computer to take turn. Check for win after each turn. Check for win, loose or draw. # Check against win matrix if game is won or lost # If empty sapce present then game is not over, otherwise its a draw Track and execute human player's moves. Take player's move and update board matrix and moves list. Parent class for types of AI difficulty. Random method selects a move to make randomly from available squares on game board Class for selecting and tracking moves when Easy difficulty setting selected in game menu Selects move by completing winning row, blocking user's winning row or otherwise         randomly selecting an available tile. Then updates game board and list of moves. #Making move # If one move from winning or losing complete line to win or block. Checks for rows of 2 X's or O's where game could be won or user blocked from winning. #Reversed to check for winning moves before blocking moves. Class for managing computer's moves when impossible difficulty selected in game menu Select move using minimax algorithm, update game board and list of moves. # If there is a guarenteed path to winnig then calculate remaining moves until win. #Making move