pythonalgorithmrecursiontreeothello

How to implement tree made from possible moves in game Othello (Reversi)


I am trying to make a tree of possible moves for the game Othello, on which I will later use a minimax algorithm. The game is played in Player vs AI mode where the player has "1" on the board and the AI "2". This is my current function for getting the best move for the AI:

def findMoveForAI(board, player, depth, start):
    best_score_for_move = -float('inf')
    play_x = play_y = -1
    moves = validMoves(board, player)
    if not moves:
        return (play_x , play_y)
    for x, y in moves:
        # this is where I am trying to make a tree
        (temp, total_fillped) = PlayMove(copy.deepcopy(board), x, y, player)
        move_eval = AlphaBeta(temp, player, depth, -999999999999, 999999999999, True, start)
        if move_eval > best_score_for_move  :
            best_score_for_move = move_eval 
            play_x = x; play_y= y
    return (play_x , play_y)

Also, this is how I initialize board:

board = [['.' for x in range(8)] for y in range(8)]

At the place I marked in the code, I am trying to make a tree for every possible move for the AI in that moment, and then do minimax on it and get the best possible move.

I have class TreeNode and class Tree for building the tree:

class TreeNode(object):

    def __init__(self, data):
        self.parent = None
        self.children = []
        self.data = data

    def is_root(self):
        return self.parent is None

    def is_leaf(self):
        return len(self.children) == 0

    def add_child(self, x):
        x.parent = self
        self.children.append(x)


class Tree(object):
    def __init__(self):
        self.root = None

This is what I tried for making the tree:

def makeTree(tree, board, player, depth):
    if depth > 0:
        new_player = change_player(player)
        possible_moves = validMoves(board, new_player)
        for x, y in possible_moves:
            new_board = PlayMove(copy.deepcopy(board), x, y, new_player)[0]
            child_tree = makeTree(tree, new_board, new_player, depth - 1)
            tree.add_child(child_tree)
    return tree

But now this creates many Tree instances, while I would need one tree and many nodes. How I can resolve this with this recursive approach?


Solution

  • You would need your recursive function to return a TreeNode instance, not a Tree instance. The top level call will then return the root node, which should then be assigned to the root attribute of a single Tree instance.

    I would also suggest creating an Edge class, so you can store the information about the move that was played in the parent board in order to get to the child board.

    If I understand correctly you want to separate the minimax/alphabeta algorithm from the actual game rules, and first create the tree of states (specific to the game), and then feed that to a generic minimax/alphabeta algorithm which can then be ignorant about the game rules, and just focus on the information in the tree.

    Here is an idea for an implementation:

    class Tree:
        def __init__(self):
            self.root = None
    
    class TreeNode:
    
        def __init__(self, board, player, value=None):
            self.parent = None
            self.children = []
            self.board = board
            self.player = player
            self.value = value  # Initially only provided for leaf nodes
    
        def is_root(self):
            return self.parent is None
    
        def is_leaf(self):
            return len(self.children) == 0
    
        def add_edge(self, edge):
            edge.child.parent = self
            self.children.append(edge)
    
        def to_list(self):  # to ease debugging...
            return [self.board, [edge.child.to_list() for edge in self.children]]
    
    class Edge:
        def __init__(self, x, y, child):
            self.x = x
            self.y = y
            self.child = child
    
        
    def makeTree(board, player, depth):
    
        def makeNode(board, player, depth):
            if depth == 0:  # Create a leaf with a heuristic value
                return TreeNode(board, player, heuristic(board, player))
            
            node = TreeNode(board, player)
            new_player = change_player(player)
            possible_moves = validMoves(board, new_player)
            for x, y in possible_moves:
                new_board = PlayMove(copy.deepcopy(board), x, y, new_player)[0]
                node.add_edge(Edge(x, y, makeNode(new_board, new_player, depth - 1)))
            return node
    
        tree = Tree()
        tree.root = makeNode(board, player, depth)
        return tree
    

    Your findMoveForAI and AlphaBeta functions would no longer get a board and player as argument, nor would they call PlayMove. Instead they would just traverse the tree. findMoveForAI would get the tree instance as argument, and AlphaBeta would get a node as argument. The values would bubble up the tree as this executes, based on the values that are stored in the leaves of the tree.

    So findMoveForAI could look like this:

    def findMoveForAI(tree):
        best_score_for_move = -float('inf')
        play_x = play_y = -1
        for x, y, child in tree.root.children:
            move_eval = AlphaBeta(child, depth, -999999999999, 999999999999)
            if move_eval > best_score_for_move:
                best_score_for_move = move_eval 
                play_x = x
                play_y = y
        return (play_x , play_y)
    

    And the driver code would have these two steps:

    DEPTH = 3
    # ...
    tree = makeTree(board, player, DEPTH) 
    best_move = findMoveForAI(tree)
    # ...