use board::{ Board, BunkerSlot, CardType, CardTypeNoHua, FieldPosition, NumberCard, PositionNoGoal, SpecialCardType, }; use serde::{Deserialize, Serialize}; pub(super) trait BoardApplication { fn apply(&self, solboard: &mut Board); fn undo(&self, solboard: &mut Board); fn can_apply(&self, solboard: &Board) -> bool; fn can_undo(&self, solboard: &Board) -> bool; fn checked_apply(&self, solboard: &mut Board) -> bool { if self.can_apply(solboard) { self.apply(solboard); return true; } return false; } fn checked_undo(&self, solboard: &mut Board) -> bool { if self.can_undo(solboard) { self.undo(solboard); return true; } return false; } } fn can_pop_top(solboard: &Board, position: &PositionNoGoal, card: &CardType) -> bool { match position { PositionNoGoal::Field(fieldpos) => { if solboard.field[usize::from(fieldpos.column())] .last() .expect("Trying to pop top of empty field stack") != card { return false; } } PositionNoGoal::Bunker { slot_index } => { if solboard.bunker[usize::from(*slot_index)] != BunkerSlot::Stash(card.remove_hua()) { return false; } } }; return true; } fn pop_top(solboard: &mut Board, position: &PositionNoGoal, card: &CardType) { debug_assert!(can_pop_top(solboard, position, card)); match position { PositionNoGoal::Field(fieldpos) => { solboard .field .get_mut(usize::from(fieldpos.column())) .expect("Column index fucked") .pop(); } PositionNoGoal::Bunker { slot_index } => { solboard.bunker[usize::from(*slot_index)] = BunkerSlot::Empty; } } } #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] pub struct Goal { pub card: NumberCard, pub source: PositionNoGoal, pub goal_slot_index: u8, } impl BoardApplication for Goal { fn can_apply(&self, solboard: &Board) -> bool { match &solboard.goal[usize::from(self.goal_slot_index)] { Option::Some(NumberCard { value, suit }) => { if self.card.value != *value + 1 { return false; } if self.card.suit != *suit { return false; } } Option::None => { if self.card.value != 1 { return false; } } } if !can_pop_top(solboard, &self.source, &CardType::Number(self.card.clone())) { return false; } return true; } fn can_undo(&self, _solboard: &Board) -> bool { return true; } fn apply(&self, solboard: &mut Board) { pop_top(solboard, &self.source, &CardType::Number(self.card.clone())); *solboard .goal .get_mut(usize::from(self.goal_slot_index)) .expect("Slot index fucked") = Option::Some(self.card.clone()); } fn undo(&self, solboard: &mut Board) { match &self.source { PositionNoGoal::Field(position) => { solboard .field .get_mut(usize::from(position.column())) .expect("Column index fucked") .push(CardType::Number(self.card.clone())); } PositionNoGoal::Bunker { slot_index } => { solboard.bunker[usize::from(*slot_index)] = BunkerSlot::Stash(CardTypeNoHua::Number(self.card.clone())); } } if self.card.value == 1 { solboard.goal[usize::from(self.goal_slot_index)] = Option::None; } else { solboard.goal[usize::from(self.goal_slot_index)] = Option::Some(NumberCard { suit: self.card.suit.clone(), value: self.card.value - 1, }); } } } impl std::fmt::Display for Goal { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { return write!( f, "Goal {} from {} to slot #{}", self.card, self.source, self.goal_slot_index ); } } #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] pub struct DragonKill { pub card: SpecialCardType, pub source: [PositionNoGoal; 4], pub destination_slot_index: u8, } impl BoardApplication for DragonKill { fn apply(&self, solboard: &mut Board) { for position in &self.source { pop_top(solboard, position, &CardType::Special(self.card.clone())); } solboard.bunker[usize::from(self.destination_slot_index)] = BunkerSlot::Blocked(Option::Some(self.card.clone())); } fn undo(&self, solboard: &mut Board) { solboard.bunker[usize::from(self.destination_slot_index)] = BunkerSlot::Empty; for position in &self.source { match &position { PositionNoGoal::Field(field_position) => { solboard.field[usize::from(field_position.column())] .push(CardType::Special(self.card.clone())); } PositionNoGoal::Bunker { slot_index } => { solboard.bunker[usize::from(*slot_index)] = BunkerSlot::Stash(CardTypeNoHua::Special(self.card.clone())); } } } } fn can_apply(&self, solboard: &Board) -> bool { if self.destination_slot_index >= 3 { return false; } let previous_slot_empty = solboard .bunker .iter() .take(self.destination_slot_index.saturating_sub(1).into()) .all(|x| { if let BunkerSlot::Empty = x { return true; } else { return false; } }); if previous_slot_empty { return false; } return true; } fn can_undo(&self, _solboard: &Board) -> bool { return true; } } impl std::fmt::Display for DragonKill { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { return write!( f, "Kill {} to bunker #{} from {}, {}, {}, {}", self.card, self.destination_slot_index, self.source[0], self.source[1], self.source[2], self.source[3], ); } } #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] pub struct Bunkerize { pub card: CardTypeNoHua, pub bunker_slot_index: u8, pub field_position: FieldPosition, pub to_bunker: bool, } impl Bunkerize { fn can_move_to_bunker(&self, solboard: &Board) -> bool { if self.field_position.row() + 1 != solboard.field[usize::from(self.field_position.column())].len() as u8 { return false; } if self.card.add_hua() != *solboard.field[usize::from(self.field_position.column())] .last() .unwrap() { return false; } if solboard.bunker[usize::from(self.bunker_slot_index)] != BunkerSlot::Empty { return false; } return true; } fn move_to_bunker(&self, solboard: &mut Board) { debug_assert!(self.can_move_to_bunker(solboard)); solboard.field[usize::from(self.field_position.column())].pop(); solboard.bunker[usize::from(self.bunker_slot_index)] = BunkerSlot::Stash(self.card.clone()); } fn can_move_from_bunker(&self, solboard: &Board) -> bool { if solboard.bunker[usize::from(self.bunker_slot_index)] != BunkerSlot::Stash(self.card.clone()) { return false; } if self.field_position.row() != solboard.field[usize::from(self.field_position.column())].len() as u8 { return false; } return true; } fn move_from_bunker(&self, solboard: &mut Board) { debug_assert!(self.can_move_from_bunker(solboard)); solboard.field[usize::from(self.field_position.column())].push(self.card.add_hua()); solboard.bunker[usize::from(self.bunker_slot_index)] = BunkerSlot::Empty; } } impl BoardApplication for Bunkerize { fn apply(&self, solboard: &mut Board) { if self.to_bunker { self.move_to_bunker(solboard); } else { self.move_from_bunker(solboard); } } fn undo(&self, solboard: &mut Board) { if self.to_bunker { self.move_from_bunker(solboard); } else { self.move_to_bunker(solboard); } } fn can_apply(&self, solboard: &Board) -> bool { if self.to_bunker { return self.can_move_to_bunker(solboard); } else { return self.can_move_from_bunker(solboard); } } fn can_undo(&self, solboard: &Board) -> bool { if self.to_bunker { return self.can_move_from_bunker(solboard); } else { return self.can_move_to_bunker(solboard); } } } impl std::fmt::Display for Bunkerize { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { if self.to_bunker { return write!( f, "Move {} from {} to bunker #{}", self.card, self.field_position, self.bunker_slot_index, ); } else { return write!( f, "Move {} from bunker #{} to {}", self.card, self.bunker_slot_index, self.field_position, ); } } } #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] pub struct HuaKill { pub field_position: FieldPosition, } impl BoardApplication for HuaKill { fn can_apply(&self, solboard: &Board) -> bool { if solboard.field[usize::from(self.field_position.column())].last() != Option::Some(&CardType::Hua) { return false; } if solboard.field[usize::from(self.field_position.column())].len() != (self.field_position.row() + 1) as usize { return false; } return true; } fn apply(&self, solboard: &mut Board) { debug_assert!(self.can_apply(solboard)); solboard.field[usize::from(self.field_position.column())].pop(); solboard.hua_set = true; } fn can_undo(&self, solboard: &Board) -> bool { if solboard.field[usize::from(self.field_position.column())].len() != self.field_position.row() as usize { return false; } return true; } fn undo(&self, solboard: &mut Board) { debug_assert!(self.can_undo(solboard)); solboard.field[usize::from(self.field_position.column())].push(CardType::Hua); solboard.hua_set = false; } } impl std::fmt::Display for HuaKill { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { return write!(f, "Kill hua from {}", self.field_position); } } #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] pub enum All { Bunkerize(Bunkerize), DragonKill(DragonKill), Goal(Goal), HuaKill(HuaKill), Move(super::Move), } impl std::fmt::Display for All { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { Self::Bunkerize(x) => return write!(f, "{}", x), Self::DragonKill(x) => return write!(f, "{}", x), Self::Goal(x) => return write!(f, "{}", x), Self::HuaKill(x) => return write!(f, "{}", x), Self::Move(x) => return write!(f, "{}", x), } } } impl All { pub fn apply(&self, solboard: &mut Board) { match self { Self::HuaKill(obj) => { obj.apply(solboard); } Self::DragonKill(obj) => { obj.apply(solboard); } Self::Goal(obj) => { obj.apply(solboard); } Self::Bunkerize(obj) => { obj.apply(solboard); } Self::Move(obj) => obj.apply(solboard), } } pub fn undo(&self, solboard: &mut Board) { match self { Self::HuaKill(obj) => { obj.undo(solboard); } Self::DragonKill(obj) => { obj.undo(solboard); } Self::Goal(obj) => { obj.undo(solboard); } Self::Bunkerize(obj) => { obj.undo(solboard); } Self::Move(obj) => obj.undo(solboard), } } }