use std::ops::Deref;
use regex::Regex;
use serde::{Deserialize, Serialize};
use serde_with::serde_as;
use thiserror::Error;
pub(crate) mod char;
pub use char::CharacterCode;
lazy_static::lazy_static! {
static ref BOARD_REGEX: regex::Regex = Regex::new("[^0-9,]").expect("failed to create regex");
}
pub const FLAGSHIP_ROWS: usize = 6;
pub const FLAGSHIP_COLS: usize = 22;
pub type TextBoard<const ROWS: usize, const COLS: usize> = [[char; COLS]; ROWS];
pub type Board<const ROWS: usize = FLAGSHIP_ROWS, const COLS: usize = FLAGSHIP_COLS> = [[u8; COLS]; ROWS];
#[serde_as]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct BoardData<const ROWS: usize = FLAGSHIP_ROWS, const COLS: usize = FLAGSHIP_COLS>(
#[serde_as(as = "[[_; COLS]; ROWS]")] pub Board<ROWS, COLS>,
);
impl<const ROWS: usize, const COLS: usize> Default for BoardData<ROWS, COLS> {
fn default() -> Self {
BoardData([[0; COLS]; ROWS])
}
}
impl<const ROWS: usize, const COLS: usize> From<TextBoard<ROWS, COLS>> for BoardData<ROWS, COLS> {
fn from(value: TextBoard<ROWS, COLS>) -> Self {
value.map(|row| row.map(|c| CharacterCode::from(c) as u8)).into()
}
}
impl<const ROWS: usize, const COLS: usize> Deref for BoardData<ROWS, COLS> {
type Target = Board<ROWS, COLS>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<const ROWS: usize, const COLS: usize> From<Board<ROWS, COLS>> for BoardData<ROWS, COLS> {
fn from(raw: Board<ROWS, COLS>) -> Self {
BoardData(raw)
}
}
impl<const ROWS: usize, const COLS: usize> PartialEq<BoardData<ROWS, COLS>> for Board<ROWS, COLS> {
fn eq(&self, other: &BoardData<ROWS, COLS>) -> bool {
self == &other.0
}
}
impl<const ROWS: usize, const COLS: usize> PartialEq<Board<ROWS, COLS>> for BoardData<ROWS, COLS> {
fn eq(&self, other: &Board<ROWS, COLS>) -> bool {
&self.0 == other
}
}
impl<const ROWS: usize, const COLS: usize> std::str::FromStr for BoardData<ROWS, COLS> {
type Err = BoardError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut board: Board<ROWS, COLS> = [[0; COLS]; ROWS];
let s = BOARD_REGEX.replace_all(s, "");
for (i, val) in s.split(',').enumerate() {
let row = i / FLAGSHIP_COLS;
let col = i % FLAGSHIP_COLS;
if row >= FLAGSHIP_ROWS || col >= FLAGSHIP_COLS && val.is_empty() {
continue;
}
if row >= FLAGSHIP_ROWS {
return Err(BoardError::TooManyRows);
}
if col >= FLAGSHIP_COLS {
return Err(BoardError::TooManyCols);
}
board[row][col] = val.parse().map_err(|_| BoardError::InvalidChar(val.to_string()))?;
}
Ok(BoardData(board))
}
}
impl<const ROWS: usize, const COLS: usize> From<BoardData<ROWS, COLS>> for Board<ROWS, COLS> {
fn from(val: BoardData<ROWS, COLS>) -> Self {
val.0
}
}
impl<const ROWS: usize, const COLS: usize> std::fmt::Display for BoardData<ROWS, COLS> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, " {}", "-".repeat(COLS * 2))?;
for row in self.0.iter() {
for (col_idx, col) in row.iter().enumerate() {
if col_idx == 0 {
write!(f, "|")?;
}
CharacterCode::from(*col).fmt(f)?;
if col_idx == COLS - 1 {
write!(f, "|")?;
}
}
writeln!(f)?;
}
writeln!(f, " {}", "-".repeat(COLS * 2))?;
Ok(())
}
}
#[derive(Error, Debug)]
pub enum BoardError {
#[error("too many rows in the input")]
TooManyRows,
#[error("too many columns in the input")]
TooManyCols,
#[error("invalid character in the input: {0}")]
InvalidChar(String),
#[error("regex error: {0}")]
Regex(#[from] regex::Error),
#[error("invalid length")]
InvalidLength,
}