use crate::{GameStatus, MnkBoard, MnkGame, PlaceError, PlayError, Space};
use std::fmt;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct GravityGame<const R: usize, const C: usize, const K: usize>(MnkGame<R, C, K>);
impl<const R: usize, const C: usize, const K: usize> GravityGame<R, C, K> {
#[must_use]
pub const fn new() -> Self {
Self(MnkGame::new())
}
#[must_use]
pub const fn board(&self) -> &MnkBoard<R, C, K> {
self.0.board()
}
#[must_use]
pub const fn status(&self) -> GameStatus {
self.0.status()
}
pub fn play_in(&mut self, column: usize) -> Result<(), PlayError> {
let row = lowest_row_in(self.board(), column)?;
self.0.play_at(row, column)
}
}
fn lowest_row_in<const R: usize, const C: usize, const K: usize>(
board: &MnkBoard<R, C, K>,
column: usize,
) -> Result<usize, PlayError> {
if column >= C {
return Err(PlayError::PlaceError(PlaceError::OutOfBounds {
row: 0,
column,
}));
}
let mut rows = (0..R).rev();
rows.find(|&r| {
board
.get(r, column)
.expect("column guard should guarantee in-bounds")
== &Space::Empty
})
.ok_or_else(|| PlayError::RuleError {
message: format!("column {column} is full"),
})
}
impl<const R: usize, const C: usize, const K: usize> Default for GravityGame<R, C, K> {
fn default() -> Self {
Self::new()
}
}
impl<const R: usize, const C: usize, const K: usize> From<GravityGame<R, C, K>>
for MnkBoard<R, C, K>
{
fn from(game: GravityGame<R, C, K>) -> Self {
game.0.into()
}
}
impl<const R: usize, const C: usize, const K: usize> From<GravityGame<R, C, K>>
for MnkGame<R, C, K>
{
fn from(game: GravityGame<R, C, K>) -> Self {
game.0
}
}
impl<const R: usize, const C: usize, const K: usize> fmt::Display for GravityGame<R, C, K> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[cfg(test)]
mod test_lowest_row_in {
use super::*;
use crate::Player;
use std::assert_matches;
#[test]
fn empty_yields_r_minus_one() {
let one_one: MnkBoard<1, 1, 1> = MnkBoard::new();
assert_eq!(lowest_row_in(&one_one, 0), Ok(0));
let two_one: MnkBoard<2, 1, 1> = MnkBoard::new();
assert_eq!(lowest_row_in(&two_one, 0), Ok(1));
let two_two: MnkBoard<2, 2, 1> = MnkBoard::new();
assert_eq!(lowest_row_in(&two_two, 1), Ok(1));
}
#[test]
fn non_empty_yields_lowest() {
let mut two_one: MnkBoard<2, 1, 1> = MnkBoard::new();
_ = two_one.place(Player::X, 1, 0);
assert_eq!(lowest_row_in(&two_one, 0), Ok(0));
let mut two_two: MnkBoard<2, 2, 1> = MnkBoard::new();
_ = two_two.place(Player::X, 1, 1);
assert_eq!(lowest_row_in(&two_two, 1), Ok(0));
}
#[test]
fn out_of_bounds_fails() {
let one_one: MnkBoard<1, 1, 1> = MnkBoard::new();
assert_matches!(
lowest_row_in(&one_one, 1),
Err(PlayError::PlaceError(PlaceError::OutOfBounds {
column: 1,
row: _
}))
);
}
#[test]
fn full_column_fails() {
let mut one_one: MnkBoard<1, 1, 1> = MnkBoard::new();
_ = one_one.place(Player::X, 0, 0);
assert_matches!(
lowest_row_in(&one_one, 0),
Err(PlayError::RuleError { message: _ })
);
}
}