use crate::error::ParseError::{BadChar, EmptyString};
use crate::error::ParseError;
use crate::tiles::Axis::{Horizontal, Vertical};
use std::fmt::{Debug, Display, Formatter};
use std::ops::Add;
use std::str::FromStr;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct AxisOffset {
pub axis: Axis,
pub displacement: i8,
}
impl AxisOffset {
pub fn new(axis: Axis, displacement: i8) -> Self {
Self { axis, displacement }
}
pub fn manhattan_dist(&self) -> u8 {
self.displacement.unsigned_abs()
}
}
#[derive(Debug, Copy, Clone)]
pub struct RowColOffset{
row: i8,
col: i8
}
impl RowColOffset {
pub fn new(row: i8, col: i8) -> Self {
Self { row, col }
}
pub fn manhattan_dist(&self) -> u8 {
self.row.unsigned_abs() + self.col.unsigned_abs()
}
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub struct Coords {
pub row: i8,
pub col: i8
}
impl Coords {
pub fn new(row: i8, col: i8) -> Self {
Self { row, col }
}
pub fn row_col_offset_from(&self, other: Coords) -> RowColOffset {
RowColOffset {
row: self.row - other.row,
col: self.col - other.col
}
}
}
impl From<Tile> for Coords {
fn from(t: Tile) -> Self {
Self {
row: t.row as i8,
col: t.col as i8
}
}
}
impl Add<RowColOffset> for Coords {
type Output = Self;
fn add(self, rhs: RowColOffset) -> Self {
Self {
row: self.row + rhs.row,
col: self.col + rhs.col
}
}
}
impl Add<AxisOffset> for Coords {
type Output = Self;
fn add(self, rhs: AxisOffset) -> Self {
match rhs.axis {
Vertical => Coords::new(self.row + rhs.displacement, self.col),
Horizontal => Coords::new(self.row, self.col + rhs.displacement),
}
}
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Tile {
pub row: u8,
pub col: u8
}
impl Tile {
pub fn new(row: u8, col: u8) -> Self {
Self { row, col }
}
pub fn posn_on_axis(&self, axis: Axis) -> u8 {
match axis {
Vertical => self.row,
Horizontal => self.col
}
}
}
impl Debug for Tile {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "Tile(row={}, col={})", self.row, self.col)
}
}
impl Display for Tile {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}{}", (self.col + 97) as char, self.row + 1)
}
}
impl FromStr for Tile {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let col = if let Some(&byte) = s.as_bytes().first() {
if !(97..=122).contains(&byte) {
return Err(BadChar(byte as char))
}
byte - 97
} else {
return Err(EmptyString)
};
Ok(Tile::new(s[1..].parse::<u8>()? - 1, col))
}
}
impl From<Tile> for (u8, u8) {
fn from(value: Tile) -> Self {
(value.row, value.col)
}
}
#[derive(Eq, PartialEq, Debug, Clone, Copy, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Axis {
Vertical = 0,
Horizontal = 0x80
}
impl Axis {
pub fn other(&self) -> Axis {
match self {
Vertical => Horizontal,
Horizontal => Vertical
}
}
}
pub struct TileIterator {
side_len: u8,
current_row: u8,
current_col: u8
}
impl TileIterator {
pub(crate) fn new(side_len: u8) -> Self {
Self {
side_len,
current_row: 0,
current_col: 0
}
}
}
impl Iterator for TileIterator {
type Item = Tile;
fn next(&mut self) -> Option<Self::Item> {
if self.current_row >= self.side_len {
return None
}
let tile = Tile::new(self.current_row, self.current_col);
if self.current_col >= self.side_len - 1 {
self.current_row += 1;
self.current_col = 0;
} else {
self.current_col += 1;
}
Some(tile)
}
}
#[cfg(test)]
mod tests {
use crate::error::ParseError::{BadChar, BadInt, BadPlay, BadString, EmptyString};
use crate::error::PlayError;
use crate::play::Play;
use crate::tiles::Axis::{Horizontal, Vertical};
use crate::tiles::Tile;
use std::str::FromStr;
#[test]
fn test_tile_creation() {
for r in 0..16 {
for c in 0..16 {
let t = Tile::new(r, c);
assert_eq!(t.row, r);
assert_eq!(t.col, c);
}
}
}
#[test]
fn test_moves() {
let p_res = Play::from_tiles(Tile::new(2, 4), Tile::new(2, 6));
assert!(p_res.is_ok());
let p = p_res.unwrap();
assert_eq!(p.from, Tile::new(2, 4));
assert_eq!(p.movement.axis, Horizontal);
assert_eq!(p.movement.displacement, 2);
assert_eq!(p.to(), Tile::new(2, 6));
let p_res = Play::from_tiles(Tile::new(2, 3), Tile::new(5, 3));
assert!(p_res.is_ok());
let p = p_res.unwrap();
assert_eq!(p.from, Tile::new(2, 3));
assert_eq!(p.movement.axis, Vertical);
assert_eq!(p.movement.displacement, 3);
assert_eq!(p.to(), Tile::new(5, 3));
let p_res = Play::from_tiles(Tile::new(1, 4), Tile::new(1, 1));
assert!(p_res.is_ok());
let p = p_res.unwrap();
assert_eq!(p.from, Tile::new(1, 4));
assert_eq!(p.movement.axis, Horizontal);
assert_eq!(p.movement.displacement, -3);
assert_eq!(p.distance(), 3);
assert_eq!(p.to(), Tile::new(1, 1));
let p_res = Play::from_tiles(Tile::new(7, 5), Tile::new(0, 5));
assert!(p_res.is_ok());
let p = p_res.unwrap();
assert_eq!(p.from, Tile::new(7, 5));
assert_eq!(p.movement.axis, Vertical);
assert_eq!(p.movement.displacement, -7);
assert_eq!(p.to(), Tile::new(0, 5));
let m_res = Play::from_tiles(Tile::new(2, 3), Tile::new(3, 6));
assert!(m_res.is_err());
}
#[test]
fn test_parsing_tiles() {
let parsed_t = Tile::from_str("a8");
let t = Tile::new(7, 0);
assert!(parsed_t.is_ok());
assert_eq!(parsed_t.unwrap(), t);
assert_eq!(t.to_string(), "a8");
let parsed_t = Tile::from_str("f14");
let t = Tile::new(13, 5);
assert!(parsed_t.is_ok());
assert_eq!(parsed_t.unwrap(), t);
assert_eq!(t.to_string(), "f14");
assert_eq!(Tile::from_str(""), Err(EmptyString));
assert_eq!(Tile::from_str("[53"), Err(BadChar('[')));
assert!(matches!(Tile::from_str("a!!"), Err(BadInt(_))));
}
#[test]
fn test_parsing_moves() {
let parsed_m = Play::from_str("a8-a11");
let m = Play::from_tiles(
Tile::new(7, 0),
Tile::new(10, 0)
).unwrap();
assert!(parsed_m.is_ok());
assert_eq!(parsed_m.unwrap(), m);
assert_eq!(m.to_string(), "a8-a11");
let parsed_m = Play::from_str("f5-d5");
let m = Play::from_tiles(
Tile::new(4, 5),
Tile::new(4, 3)
).unwrap();
assert!(parsed_m.is_ok());
assert_eq!(parsed_m.unwrap(), m);
assert_eq!(m.to_string(), "f5-d5");
let parsed_m = Play::from_str("f5-d6");
assert_eq!(parsed_m, Err(BadPlay(PlayError::DisjointTiles)));
let parsed_m = Play::from_str("f5-d7-d6");
assert_eq!(parsed_m, Err(BadString(String::from("f5-d7-d6"))));
let parsed_m = Play::from_str("f5-d]");
assert!(matches!(parsed_m, Err(BadInt(_))));
let parsed_m = Play::from_str("!5-d5");
assert_eq!(parsed_m, Err(BadChar('!')));
}
}