Change package /src/chesspp to just /chesspp

This commit is contained in:
2024-01-29 00:56:24 +01:00
parent 31a219ea87
commit 8fcedbbc8c
27 changed files with 124 additions and 124 deletions

View File

@@ -1,111 +1,111 @@
import chess import chess
import random import random
import numpy as np import numpy as np
from chesspp import eval from chesspp import eval
from chesspp import util from chesspp import util
class ClassicMcts: class ClassicMcts:
def __init__(self, board: chess.Board, color: chess.Color, parent=None, move: chess.Move | None = None, def __init__(self, board: chess.Board, color: chess.Color, parent=None, move: chess.Move | None = None,
random_state: int | None = None): random_state: int | None = None):
self.random = random.Random(random_state) self.random = random.Random(random_state)
self.board = board self.board = board
self.color = color self.color = color
self.parent = parent self.parent = parent
self.move = move self.move = move
self.children = [] self.children = []
self.visits = 0 self.visits = 0
self.legal_moves = list(board.legal_moves) self.legal_moves = list(board.legal_moves)
self.untried_actions = self.legal_moves self.untried_actions = self.legal_moves
self.score = 0 self.score = 0
def _expand(self) -> 'ClassicMcts': def _expand(self) -> 'ClassicMcts':
""" """
Expands the node, i.e., choose an action and apply it to the board Expands the node, i.e., choose an action and apply it to the board
:return: :return:
""" """
move = self.random.choice(self.untried_actions) move = self.random.choice(self.untried_actions)
self.untried_actions.remove(move) self.untried_actions.remove(move)
next_board = self.board.copy() next_board = self.board.copy()
next_board.push(move) next_board.push(move)
child_node = ClassicMcts(next_board, color=self.color, parent=self, move=move) child_node = ClassicMcts(next_board, color=self.color, parent=self, move=move)
self.children.append(child_node) self.children.append(child_node)
return child_node return child_node
def _rollout(self, rollout_depth: int = 20) -> int: def _rollout(self, rollout_depth: int = 20) -> int:
""" """
Rolls out the node by simulating a game for a given depth. Rolls out the node by simulating a game for a given depth.
Sometimes this step is called 'simulation' or 'playout'. Sometimes this step is called 'simulation' or 'playout'.
:return: the score of the rolled out game :return: the score of the rolled out game
""" """
copied_board = self.board.copy() copied_board = self.board.copy()
steps = 1 steps = 1
for i in range(rollout_depth): for i in range(rollout_depth):
if copied_board.is_game_over(): if copied_board.is_game_over():
break break
m = util.pick_move(copied_board) m = util.pick_move(copied_board)
copied_board.push(m) copied_board.push(m)
steps += 1 steps += 1
return eval.score_manual(copied_board) // steps return eval.score_manual(copied_board) // steps
def _backpropagate(self, score: float) -> None: def _backpropagate(self, score: float) -> None:
""" """
Backpropagates the results of the rollout Backpropagates the results of the rollout
:param score: :param score:
:return: :return:
""" """
self.visits += 1 self.visits += 1
# TODO: maybe use score + num of moves together (a win in 1 move is better than a win in 20 moves) # TODO: maybe use score + num of moves together (a win in 1 move is better than a win in 20 moves)
self.score += score self.score += score
if self.parent: if self.parent:
self.parent._backpropagate(score) self.parent._backpropagate(score)
def is_fully_expanded(self) -> bool: def is_fully_expanded(self) -> bool:
return len(self.untried_actions) == 0 return len(self.untried_actions) == 0
def _best_child(self) -> 'ClassicMcts': def _best_child(self) -> 'ClassicMcts':
""" """
Picks the best child according to our policy Picks the best child according to our policy
:return: the best child :return: the best child
""" """
# NOTE: maybe clamp the score between [-1, +1] instead of [-inf, +inf] # NOTE: maybe clamp the score between [-1, +1] instead of [-inf, +inf]
choices_weights = [(c.score / c.visits) + np.sqrt(((2 * np.log(self.visits)) / c.visits)) choices_weights = [(c.score / c.visits) + np.sqrt(((2 * np.log(self.visits)) / c.visits))
for c in self.children] for c in self.children]
best_child_index = np.argmax(choices_weights) if self.color == chess.WHITE else np.argmin(choices_weights) best_child_index = np.argmax(choices_weights) if self.color == chess.WHITE else np.argmin(choices_weights)
return self.children[best_child_index] return self.children[best_child_index]
def _select_leaf(self) -> 'ClassicMcts': def _select_leaf(self) -> 'ClassicMcts':
""" """
Selects a leaf node. Selects a leaf node.
If the node is not expanded is will be expanded. If the node is not expanded is will be expanded.
:return: Leaf node :return: Leaf node
""" """
current_node = self current_node = self
while not current_node.board.is_game_over(): while not current_node.board.is_game_over():
if not current_node.is_fully_expanded(): if not current_node.is_fully_expanded():
return current_node._expand() return current_node._expand()
else: else:
current_node = current_node._best_child() current_node = current_node._best_child()
return current_node return current_node
def build_tree(self, samples: int = 1000) -> 'ClassicMcts': def build_tree(self, samples: int = 1000) -> 'ClassicMcts':
""" """
Runs the MCTS with the given number of samples Runs the MCTS with the given number of samples
:param samples: number of simulations :param samples: number of simulations
:return: best node containing the best move :return: best node containing the best move
""" """
for i in range(samples): for i in range(samples):
# selection & expansion # selection & expansion
# rollout # rollout
# backpropagate score # backpropagate score
node = self._select_leaf() node = self._select_leaf()
score = node._rollout() score = node._rollout()
node._backpropagate(score) node._backpropagate(score)
return self._best_child() return self._best_child()

View File

@@ -1,13 +1,13 @@
import chess import chess
import random import random
from chesspp.i_strategy import IStrategy from chesspp.i_strategy import IStrategy
class RandomStrategy(IStrategy): class RandomStrategy(IStrategy):
def __init__(self, random_state: random.Random): def __init__(self, random_state: random.Random):
self.random_state = random_state self.random_state = random_state
def pick_next_move(self, board: chess.Board) -> chess.Move | None: def pick_next_move(self, board: chess.Board) -> chess.Move | None:
if len(list(board.legal_moves)) == 0: if len(list(board.legal_moves)) == 0:
return None return None
return self.random_state.choice(list(board.legal_moves)) return self.random_state.choice(list(board.legal_moves))

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 777 B

After

Width:  |  Height:  |  Size: 777 B

View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

Before

Width:  |  Height:  |  Size: 748 B

After

Width:  |  Height:  |  Size: 748 B

View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB