#[cfg(feature = "serialization")]
use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter, Result};
use weasel::{
battle_rules, battle_rules_with_space, rules::empty::*, BattleRules, Entities, Entity,
EntityId, EventQueue, EventTrigger, PositionClaim, RemoveEntity, Rounds, SpaceRules,
WeaselError, WeaselResult, WriteMetrics,
};
const BATTLEFIELD_LENGTH: usize = 5;
#[derive(Default)]
pub(crate) struct CustomSpaceRules {}
impl SpaceRules<CustomRules> for CustomSpaceRules {
type Position = Square;
type SpaceSeed = BattlefieldSeed;
type SpaceModel = Battlefield;
type SpaceAlteration = Vec<Square>;
fn generate_model(&self, seed: &Option<Self::SpaceSeed>) -> Self::SpaceModel {
Battlefield::from_seed(*seed)
}
fn check_move(
&self,
model: &Self::SpaceModel,
_claim: PositionClaim<CustomRules>,
position: &Self::Position,
) -> WeaselResult<(), CustomRules> {
if model.is_free(position) {
Ok(())
} else {
Err(WeaselError::UserError("position occupied".to_string()))
}
}
fn move_entity(
&self,
model: &mut Self::SpaceModel,
claim: PositionClaim<CustomRules>,
position: Option<&Self::Position>,
_metrics: &mut WriteMetrics<CustomRules>,
) {
if let Some(position) = position {
match claim {
PositionClaim::Spawn(id) => model.insert(position, *id),
PositionClaim::Movement(entity) => model.insert(position, *entity.entity_id()),
}
} else {
if let PositionClaim::Movement(entity) = claim {
model.free(entity.position());
}
}
}
fn translate_entity(
&self,
_model: &Self::SpaceModel,
new_model: &mut Self::SpaceModel,
entity: &mut dyn Entity<CustomRules>,
_event_queue: &mut Option<EventQueue<CustomRules>>,
_metrics: &mut WriteMetrics<CustomRules>,
) {
new_model.insert(entity.position(), *entity.entity_id());
}
fn alter_space(
&self,
_entities: &Entities<CustomRules>,
_rounds: &Rounds<CustomRules>,
model: &mut Self::SpaceModel,
alteration: &Self::SpaceAlteration,
event_queue: &mut Option<EventQueue<CustomRules>>,
_metrics: &mut WriteMetrics<CustomRules>,
) {
for trap_position in alteration {
model.place_trap(trap_position);
if let Some(entity_id) = model.get(trap_position) {
RemoveEntity::trigger(event_queue, entity_id).fire();
}
}
}
}
battle_rules_with_space! { CustomSpaceRules }
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
pub(crate) struct Square {
pub x: usize,
pub y: usize,
}
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
pub(crate) enum BattlefieldSeed {
OneDimension,
TwoDimensions,
}
#[derive(Default, Clone, Copy)]
pub(crate) struct BattlefieldCell {
entity: Option<EntityId<CustomRules>>,
trap: bool,
}
#[allow(clippy::large_enum_variant)]
pub(crate) enum Battlefield {
Empty,
OneDimension([BattlefieldCell; BATTLEFIELD_LENGTH]),
TwoDimensions([[BattlefieldCell; BATTLEFIELD_LENGTH]; BATTLEFIELD_LENGTH]),
}
impl Battlefield {
fn from_seed(seed: Option<BattlefieldSeed>) -> Self {
if let Some(seed) = seed {
match seed {
BattlefieldSeed::OneDimension => {
Self::OneDimension([BattlefieldCell::default(); BATTLEFIELD_LENGTH])
}
BattlefieldSeed::TwoDimensions => Self::TwoDimensions(
[[BattlefieldCell::default(); BATTLEFIELD_LENGTH]; BATTLEFIELD_LENGTH],
),
}
} else {
Self::Empty
}
}
fn is_free(&self, position: &Square) -> bool {
match self {
Self::Empty => false,
Self::OneDimension(squares) => squares[position.x].entity.is_none(),
Self::TwoDimensions(squares) => squares[position.y][position.x].entity.is_none(),
}
}
fn insert(&mut self, position: &Square, entity: EntityId<CustomRules>) {
match self {
Self::Empty => {}
Self::OneDimension(squares) => squares[position.x].entity = Some(entity),
Self::TwoDimensions(squares) => squares[position.y][position.x].entity = Some(entity),
}
}
fn free(&mut self, position: &Square) {
match self {
Self::Empty => {}
Self::OneDimension(squares) => squares[position.x].entity = None,
Self::TwoDimensions(squares) => squares[position.y][position.x].entity = None,
}
}
fn place_trap(&mut self, position: &Square) {
match self {
Self::Empty => {}
Self::OneDimension(squares) => squares[position.x].trap = true,
Self::TwoDimensions(squares) => squares[position.y][position.x].trap = true,
}
}
fn get(&self, position: &Square) -> Option<EntityId<CustomRules>> {
match self {
Self::Empty => None,
Self::OneDimension(squares) => squares[position.x].entity,
Self::TwoDimensions(squares) => squares[position.y][position.x].entity,
}
}
}
impl Display for Battlefield {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
match self {
Self::Empty => write!(f, "[]"),
Self::OneDimension(squares) => {
for col in squares {
write!(f, "|")?;
if let Some(entity) = col.entity {
print_creature_id(f, entity)?;
} else if col.trap {
write!(f, "X")?;
} else {
write!(f, " ")?;
}
}
write!(f, "|")?;
writeln!(f)?;
Ok(())
}
Self::TwoDimensions(squares) => {
for (_, row) in squares.iter().rev().enumerate() {
for (_, col) in row.iter().enumerate() {
write!(f, "|")?;
if let Some(entity) = col.entity {
print_creature_id(f, entity)?;
} else if col.trap {
write!(f, "X")?;
} else {
write!(f, " ")?;
}
}
write!(f, "|")?;
writeln!(f)?;
}
Ok(())
}
}
}
}
fn print_creature_id(f: &mut Formatter<'_>, id: EntityId<CustomRules>) -> Result {
match id {
EntityId::Creature(id) => write!(f, "{}", id),
#[allow(unreachable_patterns)]
_ => panic!("not expecting anything else than a creature"),
}
}