use core::num::NonZeroU8;
use crate::replay::PieceColor;
pub const TOTAL_PIECE_COUNT: usize = 29;
pub const ZLIB_HEADER_FIRST_BYTE: u8 = b'x';
pub const BASE64_ZLIB_FIRST_BYTE: u8 = b'e';
pub const UNCOMPRESSED_FIRST_BYTE: u8 = b'{';
pub const METADATA_EVENTDATA_SEPARATOR: u8 = b'\n';
pub static METADATA_DATE_FORMAT: &str = "%Y/%m/%d %H:%M:%S";
pub const DEFAULT_SKIN_COLORS_U8: [u8; TOTAL_PIECE_COUNT] = [
1, 7, 11, 3, 14, 4, 9, 1, 7, 2, 6, 10, 2, 13, 5, 9, 15, 4, 11, 3, 12, 2, 16, 8, 4, 10, 13, 2, 8,
];
pub const DEFAULT_SKIN_COLORS: [PieceColor; TOTAL_PIECE_COUNT] = {
let mut arr = [PieceColor::Invisible; TOTAL_PIECE_COUNT];
let mut i = 0;
while i < arr.len() {
arr[i] = PieceColor::try_from_u8(DEFAULT_SKIN_COLORS_U8[i]).unwrap();
i += 1;
}
arr
};
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(
feature = "strum",
derive(strum::EnumIter, strum::IntoStaticStr, strum::VariantArray)
)]
#[cfg_attr(all(test, feature = "strum"), derive(strum::EnumCount))]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(usize)]
pub enum Piece {
Z = 0,
S = 1,
J = 2,
L = 3,
T = 4,
O = 5,
I = 6,
Z5 = 7,
S5 = 8,
P = 9,
Q = 10,
F = 11,
E = 12,
T5 = 13,
U = 14,
V = 15,
W = 16,
X = 17,
J5 = 18,
L5 = 19,
R = 20,
Y = 21,
N = 22,
H = 23,
I5 = 24,
I3 = 25,
C = 26,
I2 = 27,
O1 = 28,
}
impl Piece {
#[must_use]
pub const fn get_index(self) -> usize {
self as usize
}
#[must_use]
pub const fn mino_count(self) -> NonZeroU8 {
use Piece::{
C, E, F, H, I, I2, I3, I5, J, J5, L, L5, N, O, O1, P, Q, R, S, S5, T, T5, U, V, W, X,
Y, Z, Z5,
};
const ONE: NonZeroU8 = NonZeroU8::MIN;
const TWO: NonZeroU8 = NonZeroU8::new(2).unwrap();
const THREE: NonZeroU8 = NonZeroU8::new(3).unwrap();
const FOUR: NonZeroU8 = NonZeroU8::new(4).unwrap();
const FIVE: NonZeroU8 = NonZeroU8::new(5).unwrap();
match self {
O1 => ONE,
I2 => TWO,
C | I3 => THREE,
Z | S | J | L | T | O | I => FOUR,
Z5 | S5 | P | Q | F | E | T5 | U | V | W | X | J5 | L5 | R | Y | N | H | I5 => FIVE,
}
}
#[must_use]
pub const fn is_monomino(self) -> bool {
self.mino_count().get() == 1
}
#[must_use]
pub const fn is_domino(self) -> bool {
self.mino_count().get() == 2
}
#[must_use]
#[doc(alias = "is_trimino")]
#[doc(alias = "is_tromino")]
pub const fn is_triomino(self) -> bool {
self.mino_count().get() == 3
}
#[must_use]
#[doc(alias = "is_tetramino")]
pub const fn is_tetromino(self) -> bool {
self.mino_count().get() == 4
}
#[must_use]
#[doc(alias = "is_pentamino")]
pub const fn is_pentomino(self) -> bool {
self.mino_count().get() == 5
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "strum")]
const _CONST_ASSERT_TOTAL_PIECE_COUNT_MISMATCH: () = {
use strum::EnumCount;
assert!(
Piece::COUNT == TOTAL_PIECE_COUNT,
"TOTAL_PIECE_COUNT should match amount of variants in `Pieces` enum"
);
};
#[test]
#[cfg(feature = "strum")]
fn piece_enum_has_no_holes() {
use strum::IntoEnumIterator;
let mut prev_piece_usize = None;
for piece in Piece::iter() {
let piece_usize = piece as usize;
assert!(piece_usize < TOTAL_PIECE_COUNT);
if let Some(prev) = prev_piece_usize {
assert_eq!(prev + 1, piece_usize);
}
prev_piece_usize = Some(piece_usize);
}
}
#[test]
#[cfg(feature = "strum")]
fn piece_enum_is_only_one_polyomino() {
use strum::IntoEnumIterator;
for piece in Piece::iter() {
let mut order = None;
let mut insert = |n: u8| {
if let Some(old) = order.replace(n) {
panic!(
"Assertion failed: {piece} already {old} but also {n}?",
piece = <&str>::from(&piece)
);
}
};
if piece.is_monomino() {
insert(1);
}
if piece.is_domino() {
insert(2);
}
if piece.is_triomino() {
insert(3);
}
if piece.is_tetromino() {
insert(4);
}
if piece.is_pentomino() {
insert(5);
}
assert!(
order.is_some(),
"Assertion failed: {piece} order is undefined?",
piece = <&str>::from(&piece)
);
assert_eq!(order, Some(piece.mino_count().get()));
}
}
}