diff --git a/crates/games/src/sprouts/mod.rs b/crates/games/src/sprouts/mod.rs index 374255b..9e1b42a 100644 --- a/crates/games/src/sprouts/mod.rs +++ b/crates/games/src/sprouts/mod.rs @@ -1,11 +1,81 @@ #![doc = include_str!("./README.md")] -use game_solver::game::Game; -use petgraph::matrix_graph::MatrixGraph; +use game_solver::{game::{Game, StateType}, player::ImpartialPlayer}; +use petgraph::{csr::DefaultIx, matrix_graph::{MatrixGraph, NodeIndex}, Undirected}; +use thiserror::Error; + +type Ix = DefaultIx; + +#[derive(Debug, Clone)] +pub struct SproutsMove { + from: NodeIndex, + to: NodeIndex +} + +type SproutsGraph = MatrixGraph<(), (), Undirected, Option<()>>; #[derive(Clone)] -pub struct Sprouts(MatrixGraph<(), ()>); +pub struct Sprouts(SproutsGraph); + +impl Sprouts { + pub fn new(node_count: usize) -> Self { + let mut graph = MatrixGraph::new_undirected(); + + for _ in 0..node_count { + graph.add_node(()); + } + + Self(graph) + } +} + +#[derive(Error, Debug, Clone)] +pub enum SproutsMoveError { + #[error("chosen index {0} from move {1:?} is out of bounds.")] + MoveOutOfBounds(Ix, SproutsMove), + #[error("chosen index {0} from move {1:?} references a dead sprout.")] + DeadSprout(Ix, SproutsMove), + #[error("a move for {0:?} has already been made")] + SproutsConnected(SproutsMove) +} + +impl Game for Sprouts { + type Move = SproutsMove; + type Iter<'a> = std::vec::IntoIter; + + type Player = ImpartialPlayer; + type MoveError = SproutsMoveError; + + const STATE_TYPE: Option = Some(StateType::Normal); + + fn max_moves(&self) -> Option { + // TODO: i actually want to find what the proper paper is, but + // https://en.wikipedia.org/wiki/Sprouts_(game)#Maximum_number_of_moves + // is where this is from. + Some(self.0.node_count()) + } + + fn move_count(&self) -> usize { + self.0.edge_count() + } + + fn make_move(&mut self, m: &Self::Move) -> Result<(), Self::MoveError> { + if self.0.has_edge(m.from, m.to) { + return Err(SproutsMoveError::SproutsConnected(m.clone())); + } + + Ok(()) + } + + fn player(&self) -> Self::Player { + ImpartialPlayer::Next + } -// impl Game for Sprouts { + fn possible_moves(&self) -> Self::Iter<'_> { + + } -// } + fn state(&self) -> game_solver::game::GameState { + Self::STATE_TYPE.unwrap().state(self) + } +}