diff --git a/src/card.rs b/src/card.rs new file mode 100644 index 0000000..c177bc7 --- /dev/null +++ b/src/card.rs @@ -0,0 +1,64 @@ +use crate::{CardField, Icon}; + +#[derive(Debug, Default)] +// There are 60 cards in the base set +pub struct Card { + pub field: [CardField; 5], +} + +impl Card { + pub const fn empty() -> Self { + Self { + field: [CardField::Empty; 5], + } + } + pub fn from_dbg_string(text: &str) -> Self { + let mut result = Self::empty(); + let mut current_field = 0; + let mut char_iter = text.chars(); + let match_icon = |x: char| { + let field = match x { + 's' => Icon::Square, + 'c' => Icon::Circle, + 't' => Icon::Triangle, + 'l' => Icon::Color, + _ => return None, + }; + Some(field) + }; + while let Some(c) = char_iter.next() { + if let Some(f) = match_icon(c) { + result.field[current_field] = CardField::Icon(f); + current_field += 1; + continue; + } + let field = match c { + 'p' => { + let f = match_icon(char_iter.next().expect("No icon after point symbol")) + .expect("Incorrect icon after point symbol"); + CardField::PointsPer(f) + } + 'd' => { + let f1 = + match_icon(char_iter.next().expect("No first icon after double symbol")) + .expect("Incorrect first icon after double symbol"); + let f2 = match_icon( + char_iter + .next() + .expect("No second icon after double symbol"), + ) + .expect("Incorrect second icon after double symbol"); + CardField::DoubleIcon((f1, f2)) + } + '.' => CardField::Empty, + '-' => continue, + _ => panic!("Unknown character {c}"), + }; + + result.field[current_field] = field; + current_field += 1; + } + for c in text.chars() {} + result + } +} diff --git a/src/icon_bitfield.rs b/src/icon_bitfield.rs new file mode 100644 index 0000000..8e15fbe --- /dev/null +++ b/src/icon_bitfield.rs @@ -0,0 +1,117 @@ +use crate::Card; +use crate::CardField; +use crate::Icon; +use crate::Painting; + +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub(crate) struct IconBitfield { + circle: u8, + square: u8, + triangle: u8, + color: u8, + points: u8, +} + +impl IconBitfield { + pub(crate) fn get_icon_bits(&self, icon: Icon) -> u8 { + match icon { + Icon::Circle => self.circle, + Icon::Square => self.square, + Icon::Triangle => self.triangle, + Icon::Color => self.color, + } + } + pub(crate) fn get_point_bits(&self) -> u8 { + self.points + } + + pub(crate) fn add_icon(&mut self, icon: Icon, index: u8) { + let field = match icon { + Icon::Circle => &mut self.circle, + Icon::Square => &mut self.square, + Icon::Triangle => &mut self.triangle, + Icon::Color => &mut self.color, + }; + *field |= 1 << index; + } + pub fn add_field(&mut self, field: CardField, index: u8) { + debug_assert!(index < 5); + match field { + CardField::Icon(icon) => self.add_icon(icon, index), + CardField::DoubleIcon((icon1, icon2)) => { + self.add_icon(icon1, index); + self.add_icon(icon2, index); + } + CardField::PointsPer(_) => self.points |= 1 << index, + CardField::Empty => (), + } + } + pub fn new(card: &Card) -> Self { + let mut result = Self::default(); + (0u8..) + .zip(card.field.iter()) + .for_each(|(index, field)| result.add_field(field.clone(), index)); + result + } + + pub fn from_painting(painting: &Painting) -> Self { + let mut bits = Self::new(&painting[0]); + bits.layer_below(&Self::new(&painting[1])) + .layer_below(&Self::new(&painting[2])); + bits + } + + pub fn all_fields(&self) -> u8 { + self.circle | self.square | self.triangle | self.color | self.points + } + + /// Result of putting the other card below this one + pub fn layer_below(&mut self, other: &Self) -> &mut Self { + // 1 x 0 = 0 + // 0 x 0 = 0 + // 0 x 1 = 1 + // 1 x 1 = 0 + // Should be !a & b + + let bitmask = !self.all_fields() & other.all_fields(); + self.circle |= other.circle & bitmask; + self.square |= other.square & bitmask; + self.triangle |= other.triangle & bitmask; + self.color |= other.color & bitmask; + self.points |= other.points & bitmask; + self + } +} + +#[test] +fn test_add_field() { + let mut x: IconBitfield = Default::default(); + x.add_icon(Icon::Circle, 2); + let expected = IconBitfield { + circle: 0b00100, + ..Default::default() + }; + assert_eq!(x, expected); +} + +#[test] +fn test_layer_below() { + let mut a: IconBitfield = IconBitfield { + color: 0b00010, + points: 0b01000, + ..Default::default() + }; + let b = IconBitfield { + triangle: a.get_point_bits(), + square: 0b10000, + ..Default::default() + }; + let expected = IconBitfield { + color: a.get_icon_bits(Icon::Color), + points: a.get_point_bits(), + square: b.get_icon_bits(Icon::Square), + ..Default::default() + }; + a.layer_below(&b); + assert_eq!(a, expected); +} diff --git a/src/main.rs b/src/main.rs index 6036a2a..7a1c4d4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,31 +1,33 @@ +#![allow(dead_code)] +use crate::objective::Objective; + +mod card; +mod icon_bitfield; +mod objective; +use card::Card; + #[derive(Debug)] struct Shop { cards: [Card; 5], coins: [u8; 5], } -#[derive(Debug)] - +#[derive(Debug, Clone, Copy, PartialEq, Eq)] enum Icon { Circle, Square, Triangle, Color, } -#[derive(Debug)] +#[derive(Debug, Clone, Copy, Default)] enum CardField { Icon(Icon), DoubleIcon((Icon, Icon)), PointsPer(Icon), + #[default] Empty, } -#[derive(Debug)] - -// There are 60 cards in the base set -struct Card { - field: [CardField; 5], -} #[derive(Debug)] struct Player { @@ -34,6 +36,8 @@ struct Player { paintings: Vec<[Card; 3]>, } +type Painting = [Card; 3]; + #[derive(Debug)] struct Board { players: Vec, @@ -48,17 +52,4 @@ struct CriteriaCard { points: Vec, } -// There are 12 objectives/criteria in the base set -#[derive(Debug)] -enum Objective { - AllFields, - IconsUsed(Vec), - /// The count of all icons in the vec `.0` combined is exactly `.1` - ExactlyNIcons((Vec, u8)), - Neighbors(Vec<(Icon, Icon)>), - /// Both icons are on one card, but not adjacent - DistantNeighbors(Vec<(Icon, Icon)>), - NInARow(Icon, u8), -} - fn main() {} diff --git a/src/objective.rs b/src/objective.rs new file mode 100644 index 0000000..821d033 --- /dev/null +++ b/src/objective.rs @@ -0,0 +1,106 @@ +use crate::{Card, Icon, Painting, icon_bitfield::IconBitfield}; + +// There are 12 objectives/criteria in the base set +#[derive(Debug)] +pub enum Objective { + AllFields, + /// Must use every icon in the vector at least once + IconsUsed(Vec), + /// The count of all icons in the vec `.0` combined is exactly `.1` + ExactlyNIcons((Vec, u8)), + /// These icons are in fields exactly next to each other + /// One icon can only be the neighbor of one other icon + Neighbors(Vec<(Icon, Icon)>), + /// Both icons are on one card, but not adjacent + DistantNeighbors(Vec<(Icon, Icon)>), + NInARow(Icon, u8), +} + +impl Objective { + pub fn count_matches(&self, painting: &Painting) -> u8 { + match self { + Objective::AllFields => { + if IconBitfield::from_painting(painting).all_fields() == 0b11111 { + 1 + } else { + 0 + } + } + Objective::IconsUsed(icons) => { + let bits = IconBitfield::from_painting(painting); + if icons.iter().all(|i| bits.get_icon_bits(*i) > 0) { + 1 + } else { + 0 + } + } + Objective::ExactlyNIcons((icons, count)) => { + let bits = IconBitfield::from_painting(painting); + + if icons + .iter() + .map(|i| bits.get_icon_bits(*i).count_ones()) + .sum::() as u8 + == *count + { + 1 + } else { + 0 + } + } + Objective::Neighbors(items) => todo!(), + Objective::DistantNeighbors(items) => todo!(), + Objective::NInARow(icon, count) => { + let mut bits = IconBitfield::from_painting(painting).get_icon_bits(*icon); + let bitmask = (1 << count) - 1; + let mut res_count = 0; + while bits > 0 { + if (bits) & bitmask == bitmask { + res_count += 1; + bits >>= count; + } else { + bits >>= 1; + } + } + res_count + } + } + } +} + +#[cfg(test)] +mod tests { + use crate::{Card, Painting, objective::Objective}; + + #[test] + fn test_all_fields() { + let v: Painting = [ + Card::from_dbg_string("...ct"), + Card::from_dbg_string("s.s.."), + Card::from_dbg_string(".l.t."), + ]; + assert_eq!(Objective::AllFields.count_matches(&v), 1); + } + + #[test] + fn test_n_in_a_row() { + let v: Painting = [ + Card::from_dbg_string("...ll"), + Card::from_dbg_string("ll..."), + Card::from_dbg_string("..t.."), + ]; + assert_eq!( + Objective::NInARow(crate::Icon::Color, 2).count_matches(&v), + 2 + ); + let tricky_two: Painting = [ + Card::from_dbg_string("...ll"), + Card::from_dbg_string("tl..."), + Card::from_dbg_string("..lc."), + ]; + assert_eq!( + Objective::NInARow(crate::Icon::Color, 2).count_matches(&tricky_two), + 2 + ); + } +}