Added rust solver to the repository

This commit is contained in:
Lukas Wölfer
2025-08-08 19:16:08 +02:00
parent a9ca38e812
commit 5fdf1602eb
91 changed files with 7047 additions and 0 deletions

View File

@@ -0,0 +1,14 @@
[package]
name = "actions"
version = "0.1.0"
authors = ["Lukas Wölfer <lukas.woelfer@rwth-aachen.de>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
serde = {version="1.0.105",features=["derive"]}
serde_json = "1.0"
enum-iterator = "0.6.0"
board = {path = "../board"}

View File

@@ -0,0 +1,405 @@
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),
}
}
}

View File

@@ -0,0 +1,49 @@
#![warn(
clippy::all,
clippy::restriction,
clippy::pedantic,
clippy::nursery,
clippy::cargo
)]
#![allow(clippy::cargo)]
// Style choices
#![allow(
clippy::missing_docs_in_private_items,
clippy::needless_return,
clippy::get_unwrap,
clippy::indexing_slicing,
clippy::explicit_iter_loop
)]
// Way too pedantic
#![allow(clippy::integer_arithmetic)]
// Useless
#![allow(clippy::missing_inline_in_public_items, clippy::missing_const_for_fn)]
// Useful for production
#![allow(
clippy::use_debug,
clippy::print_stdout,
clippy::dbg_macro,
clippy::panic
)]
// Useful for improving code robustness
#![allow(
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::option_unwrap_used,
clippy::option_expect_used,
clippy::as_conversions,
// clippy::result_unwrap_used,
// clippy::wildcard_enum_match_arm
)]
#![allow(clippy::trivially_copy_pass_by_ref)]
#![allow(dead_code)]
mod base;
pub use base::*;
mod move_action;
pub use move_action::*;
#[cfg(test)]
mod tests;
pub mod possibilities;

View File

@@ -0,0 +1,178 @@
use board::{Board, CardType, FieldPosition, NumberCard, NumberCardColor};
use serde::{Deserialize, Serialize};
const COLOR_SEQUENCE: [NumberCardColor; 3] = [
NumberCardColor::Red,
NumberCardColor::Green,
NumberCardColor::Black,
];
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct Move {
start_card: CardType,
stack_len: u8,
pattern: u8,
pub source: FieldPosition,
pub destination: FieldPosition,
}
impl Move {
#[must_use]
fn alternate_card(bottom_suit: &NumberCardColor, bit: u8) -> NumberCardColor {
let pos = COLOR_SEQUENCE
.iter()
.position(|x| return x == bottom_suit)
.unwrap();
let shift_value = if bit == 0 { 0 } else { 1 };
return COLOR_SEQUENCE[(pos + shift_value + 1) % 3].clone();
}
#[must_use]
fn bit_card(last_card: &board::NumberCardColor, current_card: &board::NumberCardColor) -> u8 {
let last_pos = COLOR_SEQUENCE
.iter()
.position(|x| return x == last_card)
.unwrap();
let current_pos = COLOR_SEQUENCE
.iter()
.position(|x| return x == current_card)
.unwrap();
if (last_pos + 1) % 3 == current_pos {
return 0;
} else {
return 1;
}
}
#[must_use]
pub fn cards(&self) -> Vec<CardType> {
if let CardType::Number(NumberCard { value, .. }) = self.start_card {
let mut result = Vec::with_capacity(usize::from(self.stack_len));
result.push(self.start_card.clone());
for index in 1..self.stack_len {
let new_color = if let board::CardType::Number(board::NumberCard {
suit: last_suit,
..
}) = result.last().unwrap()
{
Self::alternate_card(last_suit, self.pattern & (1 << (index - 1)))
} else {
panic!("");
};
result.push(board::CardType::Number(board::NumberCard {
suit: new_color,
value: value - index,
}));
}
return result;
} else {
return vec![self.start_card.clone()];
}
}
#[must_use]
pub fn stack_len(&self) -> u8 {
return self.stack_len;
}
#[must_use]
pub fn new<'a>(
source: FieldPosition,
destination: FieldPosition,
cards: &'a [board::CardType],
) -> Self {
let mut pattern: u8 = 0;
let numbercard_filter = |card: &'a CardType| -> Option<&'a NumberCard> {
if let board::CardType::Number(numbercard) = card {
return Option::Some(numbercard);
} else {
return Option::None;
}
};
for (index, (last_card, card)) in (0_u8..).zip(
cards
.iter()
.filter_map(numbercard_filter)
.zip(cards.iter().skip(1).filter_map(numbercard_filter)),
) {
pattern |= Self::bit_card(&last_card.suit, &card.suit) << index;
debug_assert_eq!(card.value + 1, last_card.value);
}
return Self {
source,
destination,
start_card: cards[0].clone(),
stack_len: cards.len() as u8,
pattern,
};
}
}
impl super::BoardApplication for Move {
fn apply(&self, solboard: &mut Board) {
solboard.field[usize::from(self.source.column())].truncate(
solboard.field[usize::from(self.source.column())].len() - usize::from(self.stack_len()),
);
solboard.field[usize::from(self.destination.column())].append(&mut self.cards());
}
fn undo(&self, solboard: &mut Board) {
solboard.field[usize::from(self.destination.column())].truncate(
solboard.field[usize::from(self.destination.column())].len()
- usize::from(self.stack_len()),
);
solboard.field[usize::from(self.source.column())].append(&mut self.cards());
}
#[must_use]
fn can_apply(&self, _solboard: &Board) -> bool {
return true;
}
#[must_use]
fn can_undo(&self, _solboard: &Board) -> bool {
return true;
}
}
impl std::fmt::Display for Move {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let card_name = if self.stack_len() == 1 {
format!("{}", self.cards()[0])
} else {
format!("{} cards", self.stack_len())
};
return write!(
f,
"Move {} from {} to {}",
card_name, self.source, self.destination
);
}
}
#[test]
fn move_storage() {
let card_stack = vec![
board::CardType::Number(NumberCard {
value: 5,
suit: board::NumberCardColor::Red,
}),
board::CardType::Number(NumberCard {
value: 4,
suit: board::NumberCardColor::Black,
}),
board::CardType::Number(NumberCard {
value: 3,
suit: board::NumberCardColor::Green,
}),
];
let source = FieldPosition::new(0, 0);
let destination = FieldPosition::new(0, 1);
let my_move = Move::new(source.clone(), destination.clone(), &card_stack);
assert_eq!(my_move.cards(), card_stack);
let my_move = Move::new(source, destination, &card_stack[0..1]);
assert_eq!(
my_move.cards().iter().collect::<Vec<_>>(),
card_stack.iter().take(1).collect::<Vec<_>>()
)
}

View File

@@ -0,0 +1,373 @@
use board::{
Board, BunkerSlot, CardType, CardTypeNoHua, FieldPosition, NumberCard, NumberCardColor,
PositionNoGoal, SpecialCardType,
};
#[must_use]
pub fn bunkerize_actions(solboard: &Board) -> Vec<crate::All> {
let first_empty_bunker_index = solboard.bunker.iter().position(|x| match x {
BunkerSlot::Empty => return true,
_ => return false,
});
if let Option::Some(first_empty_bunker_index) = first_empty_bunker_index {
return solboard
.field
.iter()
.enumerate()
.filter_map(|(index, row)| {
return row
.last()
.filter(|card| {
if let CardType::Hua = card {
return false;
} else {
return true;
}
})
.map(|card| {
let field_position = FieldPosition::new(index as u8, (row.len() - 1) as u8);
return crate::All::Bunkerize(crate::Bunkerize {
field_position,
card: card.remove_hua(),
bunker_slot_index: first_empty_bunker_index as u8,
to_bunker: true,
});
});
})
.collect();
}
return Vec::new();
}
fn card_fits(source: &NumberCard, dest: &NumberCard) -> bool {
return (source.suit != dest.suit) && (source.value + 1 == dest.value);
}
fn fitting_field_number_position(card: &NumberCard, board: &Board) -> Option<FieldPosition> {
return board.field.iter().enumerate().find_map(|(index, row)| {
if let Option::Some(CardType::Number(top_card)) = row.last() {
if card_fits(card, top_card) {
return Option::Some(FieldPosition::new(index as u8, (row.len()) as u8));
}
}
return Option::None;
});
}
fn fitting_field_positions(card: &CardType, board: &Board) -> Vec<FieldPosition> {
let mut result = Vec::new();
if let CardType::Number(card) = card {
if let Option::Some(position) = fitting_field_number_position(card, board) {
result.push(position);
}
}
if let Option::Some(position) =
(0_u8..)
.zip(board.field.iter())
.find_map(|(column_index, slot)| {
if slot.is_empty() {
return Option::Some(FieldPosition::new(column_index, 0));
} else {
return Option::None;
}
})
{
result.push(position)
}
return result;
}
#[must_use]
pub fn debunkerize_actions(solboard: &Board) -> Vec<crate::All> {
let number_matching_cards =
(0_u8..)
.zip(solboard.bunker.iter())
.filter_map(|(bunker_slot_index, slot)| {
if let BunkerSlot::Stash(CardTypeNoHua::Number(card)) = slot {
return fitting_field_number_position(card, solboard).map(|field_position| {
return crate::All::Bunkerize(crate::Bunkerize {
card: CardTypeNoHua::Number(card.clone()),
field_position,
bunker_slot_index,
to_bunker: false,
});
});
} else {
return Option::None;
}
});
let empty_slot = solboard
.field
.iter()
.position(|row| return row.is_empty())
.map(|column_index| {
return FieldPosition::new(column_index as u8, 0);
});
if let Option::Some(field_position) = empty_slot {
let empty_slot_cards =
(0_u8..)
.zip(solboard.bunker.iter())
.filter_map(|(bunker_slot_index, slot)| {
if let BunkerSlot::Stash(card) = slot {
let result = crate::Bunkerize {
card: card.clone(),
bunker_slot_index,
field_position,
to_bunker: false,
};
return Option::Some(crate::All::Bunkerize(result));
} else {
return Option::None;
}
});
return number_matching_cards.chain(empty_slot_cards).collect();
} else {
return number_matching_cards.collect();
}
}
struct DragonTracker {
dragons: [(u8, [PositionNoGoal; 4]); 3],
}
impl DragonTracker {
fn new() -> Self {
return Self {
dragons: [(0, [PositionNoGoal::Bunker { slot_index: 0 }; 4]); 3],
};
}
fn dragon_to_id(dragon: &SpecialCardType) -> u8 {
return match dragon {
SpecialCardType::Zhong => 0,
SpecialCardType::Bai => 1,
SpecialCardType::Fa => 2,
};
}
fn id_to_dragon(id: u8) -> SpecialCardType {
return match id {
0 => SpecialCardType::Zhong,
1 => SpecialCardType::Bai,
2 => SpecialCardType::Fa,
_ => panic!("Dragon id too high"),
};
}
fn push(&mut self, dragon: &SpecialCardType, position: PositionNoGoal) {
let (ref mut count, ref mut cell) = self.dragons[usize::from(Self::dragon_to_id(dragon))];
cell[usize::from(*count)] = position;
*count += 1;
}
fn found_dragons(&self) -> impl Iterator<Item = (SpecialCardType, &[PositionNoGoal; 4])> {
return (0_u8..)
.zip(self.dragons.iter())
.filter_map(|(index, (count, positions))| {
if *count == 4 {
return Option::Some((Self::id_to_dragon(index), positions));
} else {
return Option::None;
}
});
}
}
#[must_use]
pub fn dragonkill_actions(solboard: &Board) -> Vec<crate::All> {
let mut dragon_position = DragonTracker::new();
for (position, card) in solboard.movable_cards() {
if let CardType::Special(card) = card {
dragon_position.push(&card, position);
}
}
let mut result: Vec<crate::All> = Vec::new();
for (card_type, positions) in dragon_position.found_dragons() {
let dragon_destination = solboard.bunker.iter().position(|x| {
return match x {
BunkerSlot::Empty => true,
BunkerSlot::Stash(CardTypeNoHua::Special(special_card_type)) => {
special_card_type == &card_type
}
_ => false,
};
});
if let Option::Some(dragon_destination) = dragon_destination {
let mut my_positions: [PositionNoGoal; 4] =
[PositionNoGoal::Bunker { slot_index: 0 }; 4];
my_positions.clone_from_slice(positions);
result.push(crate::All::DragonKill(crate::DragonKill {
card: card_type.clone(),
source: my_positions,
destination_slot_index: dragon_destination as u8,
}));
}
}
return result;
}
fn get_max_stack_count(board: &Board) -> [u8; 8] {
let mut result = [0; 8];
for (index, row) in result.iter_mut().zip(&board.field) {
let row_iterator = row.iter().rev();
let mut next_row_iterator = row.iter().rev();
if next_row_iterator.next().is_none() {
*index = 0;
continue;
}
*index = (row_iterator
.zip(next_row_iterator)
.take_while(|(card, bottom_card)| {
if let (CardType::Number(card), CardType::Number(bottom_card)) = (card, bottom_card)
{
return card_fits(card, bottom_card);
} else {
return false;
}
})
.count()
+ 1) as u8;
}
return result;
}
#[must_use]
pub fn field_move_actions(solboard: &Board) -> Vec<crate::All> {
let max_stack_counts: [u8; 8] = get_max_stack_count(solboard);
let required_size: u8 = max_stack_counts.iter().cloned().sum();
let mut result = Vec::with_capacity(usize::from(required_size));
for ((column_index, row), stack_size) in (0_u8..)
.zip(solboard.field.iter())
.zip(max_stack_counts.iter())
.filter(|(_, size)| return **size > 0)
{
for row_index in (row.len() - usize::from(*stack_size)) as u8..(row.len()) as u8 {
let my_stack = &row
.get(usize::from(row_index)..row.len())
.expect("Slicing failed");
for position in fitting_field_positions(
my_stack
.first()
.expect("Stack should at least have one entry"),
solboard,
) {
result.push(crate::All::Move(crate::Move::new(
FieldPosition::new(column_index, row_index),
position,
my_stack,
)));
}
}
}
return result;
}
#[must_use]
pub fn goal_move_actions(solboard: &Board) -> Vec<crate::All> {
let suit_to_id = |suit: &NumberCardColor| -> u8 {
return match suit {
NumberCardColor::Red => 0,
NumberCardColor::Green => 1,
NumberCardColor::Black => 2,
};
};
let first_empty_goal_slot_index = (0_u8..)
.zip(solboard.goal.iter())
.find_map(|(index, card)| {
if card.is_none() {
return Option::Some(index);
} else {
return Option::None;
}
})
.unwrap_or(3);
let mut goal_desired_pos = [(1_u8, first_empty_goal_slot_index); 3];
for (slot_id, card) in (0_u8..).zip(solboard.goal.iter()) {
match card {
Option::Some(NumberCard { value, suit }) => {
goal_desired_pos[usize::from(suit_to_id(suit))] = (*value + 1, slot_id);
}
Option::None => {}
};
}
let mut result = Vec::<crate::All>::new();
for (position, card) in solboard.movable_cards() {
if let CardType::Number(card) = card {
if goal_desired_pos[usize::from(suit_to_id(&card.suit))].0 == card.value {
result.push(crate::All::Goal(crate::Goal {
card: card.clone(),
source: position,
goal_slot_index: goal_desired_pos[usize::from(suit_to_id(&card.suit))].1,
}));
}
}
}
return result;
}
#[must_use]
pub fn huakill_actions(solboard: &Board) -> Vec<crate::All> {
for (slot_id, field_column) in (0_u8..).zip(solboard.field.iter()) {
if let Option::Some(CardType::Hua) = field_column.last() {
return vec![crate::All::HuaKill(crate::HuaKill {
field_position: FieldPosition::new(slot_id, (field_column.len() - 1) as u8),
})];
}
}
return Vec::new();
}
#[must_use]
pub fn all_actions(solboard: &Board) -> Vec<crate::All> {
return [
&huakill_actions(solboard)[..],
&dragonkill_actions(solboard)[..],
&goal_move_actions(solboard)[..],
&debunkerize_actions(solboard)[..],
&field_move_actions(solboard)[..],
&bunkerize_actions(solboard)[..],
]
.concat();
}
#[must_use]
pub fn filter_actions(solboard: &Board) -> Vec<crate::All> {
let action_list = all_actions(solboard);
let huakill_action = action_list.iter().find(|x| {
if let crate::All::HuaKill(_) = x {
return true;
} else {
return false;
}
});
if let Option::Some(action) = huakill_action {
return vec![action.clone()];
}
let mut goal_actions = action_list.iter().filter_map(|x| {
if let crate::All::Goal(x) = x {
return Option::Some(x);
} else {
return Option::None;
}
});
let minimum_goal = solboard
.goal
.iter()
.map(|x| match x {
Option::None => return 0,
Option::Some(card) => return card.value,
})
.min()
.unwrap();
if let Option::Some(minimum_goal_action) = goal_actions
.by_ref()
.min_by(|x, y| return x.card.value.cmp(&y.card.value))
{
if minimum_goal_action.card.value <= minimum_goal + 1 {
return vec![crate::All::Goal(minimum_goal_action.clone())];
}
}
return action_list.to_vec();
}

View File

@@ -0,0 +1,39 @@
use crate::possibilities::{all_actions, bunkerize_actions, dragonkill_actions};
use board::{BunkerSlot, CardTypeNoHua, SpecialCardType};
#[test]
pub fn dragonkill_test() -> Result<(), Box<dyn std::error::Error>> {
let mut x = board::load_test_board!("specific/dragonkill.json")?;
assert_eq!(dragonkill_actions(&x).len(), 1);
x.field[3].pop();
x.bunker[2] = BunkerSlot::Stash(CardTypeNoHua::Special(SpecialCardType::Zhong));
assert_eq!(dragonkill_actions(&x).len(), 1);
return Result::Ok(());
}
#[test]
pub fn bunkerize_test() -> Result<(), Box<dyn std::error::Error>> {
let x = board::load_test_board!("specific/dragonkill.json")?;
assert_eq!(bunkerize_actions(&x).len(), 5);
return Result::Ok(());
}
#[test]
pub fn all_actions_test() -> Result<(), Box<dyn std::error::Error>> {
let x = board::load_test_board!("specific/dragonkill.json")?;
let possible_actions = all_actions(&x);
assert_eq!(possible_actions.len(), 12);
assert_eq!(
possible_actions.iter().fold([0, 0, 0, 0, 0], |mut sum, x| {
match x {
crate::All::Bunkerize(_) => sum[0] += 1,
crate::All::Move(_) => sum[1] += 1,
crate::All::Goal(_) => sum[2] += 1,
crate::All::DragonKill(_) => sum[3] += 1,
_ => sum[4] += 1,
}
return sum;
}),
[5, 5, 1, 1, 0]
);
return Result::Ok(());
}