use crate::error::ParseError::{BadPlay, BadString};
use crate::error::PlayError::DisjointTiles;
use crate::error::{BoardError, ParseError, PlayError};
use crate::game::logic::GameLogic;
use crate::game::state::GameState;
use crate::game::GameOutcome;
use crate::pieces::{Piece, PlacedPiece, Side};
use crate::tiles::Axis::{Horizontal, Vertical};
use crate::tiles::{Axis, AxisOffset, Coords, Tile};
use std::fmt::{Display, Formatter};
use std::str::FromStr;
use crate::board::state::BoardState;
use crate::collections::piecemap::PieceMap;
use crate::game::GameOutcome::{Draw, Win};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Play {
pub from: Tile,
pub movement: AxisOffset,
}
impl Play {
pub fn new(from: Tile, movement: AxisOffset) -> Self {
Self { from, movement }
}
pub fn from_tiles(src: Tile, dst: Tile) -> Result<Self, PlayError> {
let axis: Axis;
let displacement: i8;
if src.row == dst.row {
axis = Horizontal;
displacement = (dst.col as i8) - (src.col as i8);
} else if src.col == dst.col {
axis = Vertical;
displacement = (dst.row as i8) - (src.row as i8);
} else {
return Err(DisjointTiles)
};
Ok(Self::new(src, AxisOffset::new(axis, displacement)))
}
pub fn distance(&self) -> u8 {
self.movement.displacement.unsigned_abs()
}
pub fn to(&self) -> Tile {
let coords = self.to_coords();
Tile::new(coords.row as u8, coords.col as u8)
}
pub fn to_coords(&self) -> Coords {
Coords::from(self.from) + self.movement
}
}
impl FromStr for Play {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let tokens: Vec<&str> = s.split('-').collect();
if tokens.len() != 2 {
return Err(BadString(String::from(s)))
};
let m_res = Play::from_tiles(
Tile::from_str(tokens[0])?,
Tile::from_str(tokens[1])?
);
match m_res {
Ok(m) => Ok(m),
Err(e) => Err(BadPlay(e))
}
}
}
impl Display for Play {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}-{}", self.from, self.to())
}
}
#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ValidPlay { pub play: Play }
impl Display for ValidPlay {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.play.fmt(f)
}
}
#[derive(Eq, PartialEq, Debug, Default, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PlayEffects<B: BoardState> {
pub captures: B::PieceMap,
pub game_outcome: Option<GameOutcome>
}
#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PlayRecord<B: BoardState> {
pub side: Side,
pub play: Play,
pub effects: PlayEffects<B>
}
impl<B: BoardState> PlayRecord<B> {
pub fn eq_ignore_outcome(&self, other: &Self) -> bool {
self.side == other.side && self.play == other.play
}
}
impl<B: BoardState> Display for PlayRecord<B> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.play)?;
if !self.effects.captures.is_empty() {
write!(f, "x{}",
self.effects.captures.clone().into_iter().map(|pp: PlacedPiece |
pp.tile.to_string()).collect::<Vec<_>>().join("/"))?;
}
match self.effects.game_outcome {
Some(Win(_, side)) => {
if side == Side::Attacker {
write!(f, "++")?;
} else {
write!(f, "--")?;
}
},
Some(Draw(_)) => write!(f, "==")?,
None => {}
}
Ok(())
}
}
pub struct ValidPlayIterator<'a, 'b, B: BoardState> {
game_logic: &'a GameLogic<B>,
game_state: &'b GameState<B>,
start_tile: Tile,
piece: Piece,
movement: AxisOffset,
}
impl<'logic, 'state, B: BoardState> ValidPlayIterator<'logic, 'state, B> {
pub fn new(game_logic: &'logic GameLogic<B>, game_state: &'state GameState<B>, tile: Tile)
-> Result<Self, BoardError> {
if let Some(piece) = game_state.board.get_piece(tile) {
Ok(Self {
game_logic,
game_state,
start_tile: tile,
piece,
movement: AxisOffset { axis: Vertical, displacement: 1 }
})
} else {
Err(BoardError::NoPiece)
}
}
fn next_direction(&self) -> Option<AxisOffset> {
match self.movement.axis {
Vertical => {
if self.movement.displacement > 0 {
Some(AxisOffset { axis: Vertical, displacement: -1 })
} else {
Some(AxisOffset { axis: Horizontal, displacement: 1 })
}
},
Horizontal => {
if self.movement.displacement > 0 {
Some(AxisOffset { axis: Horizontal, displacement: -1 })
} else {
None
}
}
}
}
}
impl<'logic, 'state, B: BoardState> Iterator for ValidPlayIterator<'logic, 'state, B> {
type Item = ValidPlay;
fn next(&mut self) -> Option<Self::Item> {
loop {
let play = Play::new(self.start_tile, self.movement);
if let Ok(dest_tile) = self.game_logic.board_geo.coords_to_tile(play.to_coords()) {
self.movement.displacement +=
if self.movement.displacement.is_positive() { 1 } else { -1 };
let (can_occupy, can_pass) = self.game_logic.can_occupy_or_pass(
play, self.piece, self.game_state
);
if can_occupy {
return Some(ValidPlay {
play: Play::from_tiles(self.start_tile, dest_tile)
.expect("Tiles should be on same axis.")
})
} else if can_pass {
continue
} else {
self.movement = self.next_direction()?;
continue
}
} else {
self.movement = self.next_direction()?;
continue
}
}
}
}