import math import numpy as np class Node: def __init__(self, game, args, state, parent = None, action = None): self.game = game self.args = args self.state = state self.parent = parent self.action = action self.children = list() self.expandable_moves = self.game.get_moves(self.state) self.visits = 0 self.total_value = 0 def leaf_or_not(self): return (len(self.children) > 0 and len(self.expandable_moves) == 0) def search(self): best_child = None best_ucb = -np.inf for child in self.children: ucb = self.get_ucb(child) if best_ucb < ucb: best_ucb = ucb best_child = child return best_child def get_ucb(self, child): q_value = 1 - ((child.total_value / child.visits) + 1) / 2 return q_value + self.args["EXPLORATION_CONSTANT"] * math.sqrt(math.log(self.visits) / child.visits) def expand(self): rand_move = np.random.choice(self.expandable_moves) self.expandable_moves.remove(rand_move) child = self.game.make_move(self.state.copy(), rand_move, 1) child = self.game.change_perspective(child) child = Node(self.game, self.args, child, self, rand_move) self.children.append(child) return child def simulate(self): is_terminal, value = self.game.know_terminal_value(self.state, self.action) value = self.game.get_opponent_value(value) if is_terminal: return value state = self.state.copy() player = 1 while True: possible_moves = self.game.get_moves(state) rand_move = np.random.choice(possible_moves) state = self.game.make_move(state, rand_move, player) is_terminal, value = self.game.know_terminal_value(state, rand_move) if is_terminal: if player == -1: value = self.game.get_opponent_value(value) return value player = self.game.get_opponent(player) def backpropagate(self,value): self.total_value += value self.visits += 1 value = self.game.get_opponent_value(value) if self.parent is not None: self.parent.backpropagate(value) class MCTS: def __init__(self, game, args): self.game = game self.args = args def search(self, node): root = Node(self.game, self.args, node) for _ in range(self.args["NO_OF_SEARCHES"]): node = root while node.leaf_or_not(): node = node.search() is_terminal, value = self.game.know_terminal_value(node.state, node.action) value = self.game.get_opponent_value(value) if not is_terminal: node = node.expand() value = node.simulate() node.backpropagate(value) move_probability = np.zeros(self.game.possible_state) for children in root.children: move_probability[children.action] = children.visits move_probability /= np.sum(move_probability) return move_probability