use alloc::collections::btree_map::{BTreeMap, Entry};
use alloc::vec::Vec;
use core::cmp::Ordering;
use core::iter;
use crate::error::Error;
use crate::limits::{MAX_DIMENSIONS, MAX_DIMENSION_SIZE, MAX_SQUARE_COUNT};
use crate::player::Player;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Qi<P, S> {
dims: [usize; MAX_DIMENSIONS],
ndim: usize,
board: Vec<Option<P>>,
first_hand: BTreeMap<P, usize>,
second_hand: BTreeMap<P, usize>,
first_style: S,
second_style: S,
turn: Player,
board_pieces: usize,
hand_pieces: usize,
}
impl<P, S> Qi<P, S> {
pub fn new(shape: &[usize], first_style: S, second_style: S) -> Result<Self, Error> {
if shape.is_empty() {
return Err(Error::EmptyShape);
}
if shape.len() > MAX_DIMENSIONS {
return Err(Error::TooManyDimensions);
}
let mut dims = [0usize; MAX_DIMENSIONS];
let mut total: usize = 1;
for (slot, &size) in dims.iter_mut().zip(shape.iter()) {
if size == 0 {
return Err(Error::DimensionTooSmall);
}
if size > MAX_DIMENSION_SIZE {
return Err(Error::DimensionTooLarge);
}
*slot = size;
total = total.checked_mul(size).ok_or(Error::TooManySquares)?;
}
if total > MAX_SQUARE_COUNT {
return Err(Error::TooManySquares);
}
let board = iter::repeat_with(|| None).take(total).collect();
Ok(Self {
dims,
ndim: shape.len(),
board,
first_hand: BTreeMap::new(),
second_hand: BTreeMap::new(),
first_style,
second_style,
turn: Player::First,
board_pieces: 0,
hand_pieces: 0,
})
}
pub fn board_diff(
mut self,
changes: impl IntoIterator<Item = (usize, Option<P>)>,
) -> Result<Self, Error> {
let total = self.board.len();
for (index, square) in changes {
if index >= total {
return Err(Error::IndexOutOfRange);
}
let was_occupied = self.board[index].is_some();
let now_occupied = square.is_some();
self.board[index] = square;
match (was_occupied, now_occupied) {
(false, true) => self.board_pieces += 1,
(true, false) => self.board_pieces -= 1,
_ => {}
}
}
self.check_cardinality()?;
Ok(self)
}
#[must_use]
pub fn toggle(mut self) -> Self {
self.turn = self.turn.flip();
self
}
#[must_use]
pub fn with_turn(mut self, turn: Player) -> Self {
self.turn = turn;
self
}
#[must_use]
pub fn shape(&self) -> &[usize] {
&self.dims[..self.ndim]
}
#[must_use]
pub const fn dimension_count(&self) -> usize {
self.ndim
}
#[must_use]
pub fn square_count(&self) -> usize {
self.board.len()
}
#[must_use]
pub const fn piece_count(&self) -> usize {
self.board_pieces + self.hand_pieces
}
#[must_use]
pub const fn board_piece_count(&self) -> usize {
self.board_pieces
}
#[must_use]
pub const fn hand_piece_count(&self) -> usize {
self.hand_pieces
}
#[must_use]
pub const fn turn(&self) -> Player {
self.turn
}
#[must_use]
pub const fn first_style(&self) -> &S {
&self.first_style
}
#[must_use]
pub const fn second_style(&self) -> &S {
&self.second_style
}
#[must_use]
pub fn board(&self) -> &[Option<P>] {
&self.board
}
#[must_use]
pub fn square(&self, index: usize) -> Option<&P> {
self.board.get(index).and_then(Option::as_ref)
}
pub fn first_hand(&self) -> impl Iterator<Item = (&P, usize)> {
self.first_hand.iter().map(|(piece, &count)| (piece, count))
}
pub fn second_hand(&self) -> impl Iterator<Item = (&P, usize)> {
self.second_hand
.iter()
.map(|(piece, &count)| (piece, count))
}
fn check_cardinality(&self) -> Result<(), Error> {
if self.board_pieces.saturating_add(self.hand_pieces) > self.board.len() {
return Err(Error::TooManyPieces);
}
Ok(())
}
}
impl<P: Ord, S> Qi<P, S> {
pub fn first_hand_diff(
mut self,
changes: impl IntoIterator<Item = (P, i32)>,
) -> Result<Self, Error> {
apply_hand_diff(&mut self.first_hand, &mut self.hand_pieces, changes)?;
self.check_cardinality()?;
Ok(self)
}
pub fn second_hand_diff(
mut self,
changes: impl IntoIterator<Item = (P, i32)>,
) -> Result<Self, Error> {
apply_hand_diff(&mut self.second_hand, &mut self.hand_pieces, changes)?;
self.check_cardinality()?;
Ok(self)
}
#[must_use]
pub fn first_hand_count(&self, piece: &P) -> usize {
self.first_hand.get(piece).copied().unwrap_or(0)
}
#[must_use]
pub fn second_hand_count(&self, piece: &P) -> usize {
self.second_hand.get(piece).copied().unwrap_or(0)
}
}
fn apply_hand_diff<P: Ord>(
hand: &mut BTreeMap<P, usize>,
hand_pieces: &mut usize,
changes: impl IntoIterator<Item = (P, i32)>,
) -> Result<(), Error> {
for (piece, delta) in changes {
match delta.cmp(&0) {
Ordering::Equal => {} Ordering::Greater => {
let added = magnitude(delta);
let slot = hand.entry(piece).or_insert(0);
*slot = slot.saturating_add(added);
*hand_pieces = hand_pieces.saturating_add(added);
}
Ordering::Less => {
let removed = magnitude(delta);
match hand.entry(piece) {
Entry::Occupied(mut entry) => {
let current = *entry.get();
if removed > current {
return Err(Error::HandUnderflow);
}
let remaining = current - removed;
if remaining == 0 {
entry.remove();
} else {
*entry.get_mut() = remaining;
}
*hand_pieces -= removed;
}
Entry::Vacant(_) => return Err(Error::HandUnderflow),
}
}
}
}
Ok(())
}
fn magnitude(delta: i32) -> usize {
usize::try_from(delta.unsigned_abs()).unwrap_or(usize::MAX)
}