"""
This module contains the implementation of the TreeExploration class, which is responsible for managing a search
for the best move in a given chess position using a tree-based approach.
The TreeExploration class is used to create and manage a tree structure that represents the possible moves and
their evaluations in a chess position. It provides methods for exploring the tree, selecting the best move,
and printing information during the move computation.
The module also includes helper functions for creating a TreeExploration object and its dependencies.
Classes:
- TreeExploration: Manages the search for the best move using a tree-based approach.
Functions:
- create_tree_exploration: Creates a TreeExploration object with the specified dependencies.
"""
import queue
import random
from dataclasses import dataclass
from typing import Callable
import chipiron.environments.chess_env.board as boards
from chipiron.environments.chess_env.move.imove import moveKey
from chipiron.players.move_selector.move_selector import MoveRecommendation
from chipiron.players.move_selector.treevalue.progress_monitor.progress_monitor import (
AllStoppingCriterionArgs,
ProgressMonitor,
create_stopping_criterion,
)
from chipiron.players.move_selector.treevalue.recommender_rule.recommender_rule import (
recommend_move_after_exploration_generic,
)
from chipiron.players.move_selector.treevalue.search_factory import NodeSelectorFactory
from chipiron.utils.communication.gui_messages import PlayerProgressMessage
from chipiron.utils.dataclass import IsDataclass
from chipiron.utils.logger import chipiron_logger
from . import node_selector as node_sel
from . import recommender_rule
from . import tree_manager as tree_man
from . import trees
from .trees.factory import MoveAndValueTreeFactory
[docs]@dataclass
class TreeExplorationResult:
move_recommendation: MoveRecommendation
tree: trees.MoveAndValueTree
[docs]@dataclass
class TreeExploration:
"""
Tree Exploration is an object to manage one best move search.
Attributes:
- tree: The tree structure representing the possible moves and their evaluations.
- tree_manager: The manager for the tree structure.
- node_selector: The selector for choosing nodes and moves to open in the tree.
- recommend_move_after_exploration: The recommender rule for selecting the best move after the tree exploration.
- stopping_criterion: The stopping criterion for determining when to stop the tree exploration.
Methods:
- print_info_during_move_computation: Prints information during the move computation.
- explore: Explores the tree to find the best move.
"""
# TODO Not sure why this class is not simply the TreeAndValuePlayer Class
# but might be useful when dealing with multi round and time , no?
tree: trees.MoveAndValueTree
tree_manager: tree_man.AlgorithmNodeTreeManager
node_selector: node_sel.NodeSelector
recommend_move_after_exploration: recommender_rule.AllRecommendFunctionsArgs
stopping_criterion: ProgressMonitor
notify_percent_function: Callable[[int], None] | None
[docs] def print_info_during_move_computation(
self, random_generator: random.Random
) -> None:
"""
Prints information during the move computation.
Args:
- random_generator: The random number generator.
Returns:
- None
"""
current_best_move: str
if self.tree.root_node.minmax_evaluation.best_move_sequence:
current_best_move = str(
self.tree.root_node.minmax_evaluation.best_move_sequence[0]
)
else:
current_best_move = "?"
if random_generator.random() < 0.11:
chipiron_logger.info(f"fen: {self.tree.root_node.board.fen}")
str_progress = self.stopping_criterion.get_string_of_progress(self.tree)
chipiron_logger.info(
f"{str_progress} | current best move: {current_best_move} | current white value: {self.tree.root_node.minmax_evaluation.value_white_minmax})"
)
# ,end='\r')
self.tree.root_node.minmax_evaluation.print_moves_sorted_by_value_and_exploration()
self.tree_manager.print_best_line(tree=self.tree)
[docs] def explore(self, random_generator: random.Random) -> TreeExplorationResult:
"""
Explores the tree to find the best move.
Args:
- random_generator: The random number generator.
Returns:
- MoveRecommendation: The recommended move and its evaluation.
"""
# by default the first tree expansion is the creation of the tree node
tree_expansions: tree_man.TreeExpansions = tree_man.TreeExpansions()
tree_expansion: tree_man.TreeExpansion = tree_man.TreeExpansion(
child_node=self.tree.root_node,
parent_node=None,
board_modifications=None,
creation_child_node=True,
move=None,
)
tree_expansions.add_creation(tree_expansion=tree_expansion)
loop: int = 0
while self.stopping_criterion.should_we_continue(tree=self.tree):
loop = loop + 1
assert not self.tree.root_node.is_over()
# print info
self.print_info_during_move_computation(random_generator=random_generator)
# choose the moves and nodes to open
opening_instructions: node_sel.OpeningInstructions
opening_instructions = self.node_selector.choose_node_and_move_to_open(
tree=self.tree, latest_tree_expansions=tree_expansions
)
# make sure we do not break the stopping criterion
opening_instructions_subset: node_sel.OpeningInstructions
opening_instructions_subset = (
self.stopping_criterion.respectful_opening_instructions(
opening_instructions=opening_instructions, tree=self.tree
)
)
# open the nodes
tree_expansions = self.tree_manager.open_instructions(
tree=self.tree, opening_instructions=opening_instructions_subset
)
# self.node_selector.communicate_expansions()
self.tree_manager.update_backward(tree_expansions=tree_expansions)
self.tree_manager.update_indices(tree=self.tree)
if loop % 10 == 0:
self.stopping_criterion.notify_percent_progress(
tree=self.tree, notify_percent_function=self.notify_percent_function
)
# trees.save_raw_data_to_file(tree=self.tree)
# self.tree_manager.print_some_stats(tree=self.tree)
# for move, child in self.tree.root_node.moves_children.items():
# chipiron_logger.info(f'{move} {self.tree.root_node.moves_children[move].minmax_evaluation.get_value_white()}'
# f' {child.minmax_evaluation.over_event.get_over_tag()}')
# chipiron_logger.info(f'evaluation for white: {self.tree.root_node.minmax_evaluation.get_value_white()}')
best_move_key: moveKey = recommend_move_after_exploration_generic(
self.recommend_move_after_exploration,
tree=self.tree,
random_generator=random_generator,
)
self.tree_manager.print_best_line(
tree=self.tree
) # todo maybe almost best chosen line no?
best_move_uci = self.tree.root_node.board.get_uci_from_move_key(
move_key=best_move_key
)
move_recommendation: MoveRecommendation = MoveRecommendation(
move=best_move_uci, evaluation=self.tree.evaluate()
)
tree_exploration_result: TreeExplorationResult = TreeExplorationResult(
move_recommendation=move_recommendation, tree=self.tree
)
return tree_exploration_result
[docs]def create_tree_exploration(
node_selector_create: NodeSelectorFactory,
starting_board: boards.IBoard,
tree_manager: tree_man.AlgorithmNodeTreeManager,
tree_factory: MoveAndValueTreeFactory,
stopping_criterion_args: AllStoppingCriterionArgs,
recommend_move_after_exploration: recommender_rule.AllRecommendFunctionsArgs,
queue_progress_player: queue.Queue[IsDataclass] | None,
) -> TreeExploration:
"""
Creates a TreeExploration object with the specified dependencies.
Args:
- node_selector_create: The factory function for creating the node selector.
- starting_board: The starting chess board position.
- tree_manager: The manager for the tree structure.
- tree_factory: The factory for creating the tree structure.
- stopping_criterion_args: The arguments for creating the stopping criterion.
- recommend_move_after_exploration: The recommender rule for selecting the best move after the tree exploration.
Returns:
- TreeExploration: The created TreeExploration object.
"""
# creates the tree
move_and_value_tree: trees.MoveAndValueTree = tree_factory.create(
starting_board=starting_board
)
# creates the node selector
node_selector: node_sel.NodeSelector = node_selector_create()
stopping_criterion: ProgressMonitor = create_stopping_criterion(
args=stopping_criterion_args, node_selector=node_selector
)
def notify_percent_function(progress_percent: int) -> None:
if queue_progress_player is not None:
queue_progress_player.put(
PlayerProgressMessage(
progress_percent=progress_percent, player_color=starting_board.turn
)
)
tree_exploration: TreeExploration = TreeExploration(
tree=move_and_value_tree,
tree_manager=tree_manager,
stopping_criterion=stopping_criterion,
node_selector=node_selector,
recommend_move_after_exploration=recommend_move_after_exploration,
notify_percent_function=notify_percent_function,
)
return tree_exploration