ncpig 0.6.1

Non-Cooperative Perfect Information Games, and algorithms to play them.
Documentation
//! Monte Carlo Tree Search playout strategies.

use std::error::Error;
use std::fmt::Debug;

use super::Node;
use crate::prelude::{Game, Random, SearchChoose};
use crate::search::random::RandomError;

/// MCTS playout strategy.
///
/// Play out moves to the game starting from the given node.
pub trait Playout<const N: usize, G: Game<N>, T>
where
    G::State: Clone,
    G::Player: Clone,
    Self::Error: From<G::Error>,
    T: Debug,
{
    /// Errors returned by the playout strategy.
    type Error: Error;

    /// Play out one move.
    fn one_move(&self, game: &G, state: &G::State) -> Result<G::State, Self::Error>;

    /// Play out moves to the game from the given starting position.
    fn until_end(
        &self,
        game: &G,
        node: &Node<N, G::State, T>,
    ) -> Result<[G::Score; N], Self::Error> {
        log::debug!("running the playout strategy from {:?}", node);
        let mut state = node.state.clone();
        while !game.is_complete(&state)? {
            state = self.one_move(game, &state)?;
        }

        Ok(game
            .players()
            .iter()
            .map(|p| game.score(p, &state))
            .collect::<Result<Vec<_>, _>>()?
            .try_into()
            .unwrap_or_else(|_| panic!("Couldn't collect to an {N}-length array???")))
    }
}

impl<const N: usize, G: Game<N>, T> Playout<N, G, T> for Random
where
    G::Action: Clone + Debug,
    G::State: Clone,
    G::Player: Clone,
    T: Debug,
{
    type Error = RandomError<N, G>;

    fn one_move(&self, game: &G, state: &G::State) -> Result<G::State, Self::Error> {
        let Some(action) = self.choose_action(game, state)? else {
            return Ok(state.clone());
        };
        Ok(game.do_action(&action, state.clone())?)
    }
}