use std::{
fmt::Debug,
hash::Hash,
marker::Sized,
ops::{Add, Range, Sub},
};
use hashbrown::HashMap;
use crate::utils::dedup;
use self::color::ColorId;
pub trait Color
where
Self: Debug
+ Eq
+ Hash
+ Default
+ Copy
+ Send
+ Sync
+ Add<Output = Self>
+ Sub<Output = Result<Self, String>>,
{
fn blank() -> Self;
fn is_solved(self) -> bool;
fn memoize_rate() -> bool {
false
}
fn solution_rate(self, all_colors: &[ColorId]) -> f64;
fn variants(self) -> Vec<Self>
where
Self: Sized;
fn as_color_id(self) -> Option<ColorId>;
fn from_color_ids(ids: &[ColorId]) -> Self;
}
pub trait Block
where
Self: Debug + Eq + Hash + Default + Copy + Send + Sync,
{
type Color: Color;
fn from_str_and_color(s: &str, color: Option<ColorId>) -> Self {
let size = s.parse().expect("Non-integer block size given");
Self::from_size_and_color(size, color)
}
fn from_size_and_color(size: usize, color: Option<ColorId>) -> Self;
fn partial_sums(desc: &[Self]) -> Vec<usize>
where
Self: Sized;
fn size(self) -> usize;
fn color(self) -> Self::Color;
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct Description<T: Block> {
pub vec: Vec<T>,
}
impl<T> Description<T>
where
T: Block,
{
pub fn new(mut vec: Vec<T>) -> Self {
let zero = T::default();
vec.retain(|x| *x != zero);
Self { vec }
}
fn from_line(line: &[ColorId], blank_code: ColorId) -> Self {
let size = line.len();
let mut description = vec![];
let mut index = 0;
while index < size {
let block_begin = index;
let color_number = line[index];
index += line[index..]
.iter()
.take_while(|&&x| x == color_number)
.count();
let block_size = index - block_begin;
if (block_size > 0) && (color_number != blank_code) {
let block = T::from_size_and_color(block_size, Some(color_number));
description.push(block);
}
}
Self::new(description)
}
pub fn colors(&self) -> impl Iterator<Item = ColorId> + '_ {
self.vec
.iter()
.filter_map(|block| block.color().as_color_id())
}
}
pub fn clues_from_solution<B>(
solution_matrix: &[Vec<ColorId>],
blank_code: ColorId,
) -> (Vec<Description<B>>, Vec<Description<B>>)
where
B: Block,
{
let height = solution_matrix.len();
if height == 0 {
return (vec![], vec![]);
}
let width = solution_matrix[0].len();
if width == 0 {
return (vec![], vec![]);
}
let columns = (0..width)
.map(|col_index| {
let column: Vec<_> = (0..height)
.map(|row_index| solution_matrix[row_index][col_index])
.collect();
Description::from_line(&column, blank_code)
})
.collect();
let rows = solution_matrix
.iter()
.map(|row| Description::from_line(row, blank_code))
.collect();
(columns, rows)
}
impl<B> Description<B>
where
B: Block,
{
pub fn block_starts(&self) -> Vec<usize> {
self.vec
.iter()
.zip(Block::partial_sums(&self.vec))
.map(|(block, end)| end - block.size())
.collect()
}
fn min_space(&self) -> usize {
if self.vec.is_empty() {
return 0;
}
*Block::partial_sums(&self.vec)
.last()
.expect("Partial sums should be non-empty")
}
pub fn positions_number(&self, line_length: usize) -> usize {
let min_space = self.min_space();
assert!(line_length >= min_space);
line_length - min_space + 1
}
pub fn color_ranges(&self, line_length: usize) -> HashMap<ColorId, Range<usize>> {
let start_indexes = self.block_starts();
let sums = B::partial_sums(&self.vec);
let slack_space = self.positions_number(line_length) - 1;
let line_colors: Vec<_> = dedup(self.colors());
line_colors
.into_iter()
.map(|color| {
let mut first_index = None;
let mut last_index = None;
for (block_index, block) in self.vec.iter().enumerate() {
if block.color().as_color_id() == Some(color) {
if first_index == None {
first_index = Some(block_index);
}
last_index = Some(block_index);
}
}
let first_index = first_index.expect("First position not found");
let last_index = last_index.expect("First position not found");
let first_pos = start_indexes[first_index];
let last_pos = sums[last_index] + slack_space;
(color, first_pos..last_pos)
})
.collect()
}
}
pub mod color {
use super::HashMap;
#[derive(Debug, PartialEq, Clone)]
#[allow(variant_size_differences)]
pub enum ColorValue {
CommonName(String),
RgbTriplet(u8, u8, u8),
HexValue3(u16),
HexValue6(u32),
}
impl ColorValue {
pub fn parse(value: &str) -> Self {
#[allow(unused_imports)]
use crate::utils::Stripper;
let value = value.strip_prefix('#').unwrap_or(value);
if value.len() == 3 {
let hex3 = u16::from_str_radix(value, 16);
if let Ok(hex3) = hex3 {
return Self::HexValue3(hex3);
}
}
if value.len() == 6 {
let hex6 = u32::from_str_radix(value, 16);
if let Ok(hex6) = hex6 {
return Self::HexValue6(hex6);
}
}
let rgb: Vec<_> = value.split(',').collect();
if rgb.len() == 3 {
let rgb: Vec<u8> = rgb
.iter()
.filter_map(|component| component.trim().parse().ok())
.collect();
if rgb.len() == 3 {
return Self::RgbTriplet(rgb[0], rgb[1], rgb[2]);
}
}
Self::CommonName(value.to_string())
}
#[allow(clippy::cast_possible_truncation)]
pub fn to_rgb(&self) -> (u8, u8, u8) {
const MULTIPLIER: u8 = 0x11;
match self {
Self::RgbTriplet(r, g, b) => (*r, *g, *b),
Self::HexValue3(hex3) => {
let (r, gb) = (hex3 >> 8, *hex3 as u8);
let (g, b) = (gb >> 4, gb % (1 << 4));
(r as u8 * MULTIPLIER, g * MULTIPLIER, b * MULTIPLIER)
}
Self::HexValue6(hex6) => {
let (r, gb) = (hex6 >> 16, *hex6 as u16);
let (g, b) = (gb >> 8, gb as u8);
(r as u8, g as u8, b)
}
Self::CommonName(name) => match name.to_lowercase().as_str() {
"black" => (0, 0, 0),
"white" => (255, 255, 255),
"red" => (255, 0, 0),
"lime" => (0, 255, 0),
"blue" => (0, 0, 255),
"yellow" => (255, 255, 0),
"cyan" | "aqua" => (0, 255, 255),
"magenta" | "fuchsia" => (255, 0, 255),
"silver" => (192, 192, 192),
"gray" => (128, 128, 128),
"maroon" => (128, 0, 0),
"olive" => (128, 128, 0),
"green" => (0, 128, 0),
"purple" => (128, 0, 128),
"teal" => (0, 128, 128),
"navy" => (0, 0, 128),
_unknown_color => (0, 0, 0),
},
}
}
}
pub type ColorId = u32;
#[derive(Debug, Clone)]
pub struct ColorDesc {
id: ColorId,
name: String,
value: ColorValue,
symbol: char,
}
impl ColorDesc {
pub fn symbol(&self) -> String {
self.symbol.to_string()
}
pub fn name(&self) -> &str {
if let ColorValue::CommonName(value) = &self.value {
value
} else {
&self.name
}
}
pub fn rgb_value(&self) -> (u8, u8, u8) {
self.value.to_rgb()
}
}
#[derive(Debug, Clone)]
pub struct ColorPalette {
vec: HashMap<String, ColorDesc>,
symbols: Vec<char>,
default_color: Option<String>,
}
impl ColorPalette {
pub const WHITE_ID: ColorId = 1;
pub fn with_white_and_black(white_name: &str, black_name: &str) -> Self {
let mut this = Self::with_white(white_name);
this.color_with_name_value_and_symbol(black_name, ColorValue::HexValue3(0x000), 'X');
this.set_default(black_name).unwrap();
this
}
pub fn with_white(white_name: &str) -> Self {
let mut this = Self::new();
this.color_with_name_value_symbol_and_id(
white_name,
ColorValue::HexValue3(0xFFF),
'.',
Self::WHITE_ID,
);
this
}
fn new() -> Self {
Self::with_colors(HashMap::new())
}
fn with_colors(colors: HashMap<String, ColorDesc>) -> Self {
let symbols = (0_u8..0xFF)
.filter_map(|ch| {
if ch.is_ascii_punctuation() {
Some(ch.into())
} else {
None
}
})
.collect();
Self {
vec: colors,
symbols,
default_color: None,
}
}
pub fn set_default(&mut self, color_name: &str) -> Result<(), String> {
if self.vec.contains_key(color_name) {
self.default_color = Some(color_name.to_string());
return Ok(());
}
Err(format!(
"Cannot set default color {}: not in Palette",
color_name
))
}
#[allow(dead_code)]
#[allow(clippy::option_as_ref_deref)]
pub fn get_default(&self) -> Option<&str> {
self.default_color.as_ref().map(String::as_str)
}
pub fn id_by_name(&self, name: &str) -> Option<ColorId> {
self.vec.get(name).map(|desc| desc.id)
}
pub fn desc_by_id(&self, id: ColorId) -> Option<ColorDesc> {
self.vec
.values()
.find(|color_desc| color_desc.id == id)
.cloned()
}
fn color_with_name_value_symbol_and_id(
&mut self,
name: &str,
value: ColorValue,
symbol: char,
id: ColorId,
) {
let new = ColorDesc {
id,
name: name.to_string(),
value,
symbol,
};
let _color = self.vec.entry(name.to_string()).or_insert(new);
}
pub fn color_with_name_value_and_symbol(
&mut self,
name: &str,
value: ColorValue,
symbol: char,
) {
let current_max = self.vec.values().map(|color| color.id).max();
let id = current_max.map_or(1, |val| val * 2);
self.color_with_name_value_symbol_and_id(name, value, symbol, id);
}
pub fn color_with_name_and_value(&mut self, name: &str, value: ColorValue) {
let occupied_symbols: Vec<_> = self.vec.values().map(|color| color.symbol).collect();
let &next_symbol = self
.symbols
.iter()
.find(|available_symbol| !occupied_symbols.contains(available_symbol))
.expect("Cannot create color: No more symbols available.");
self.color_with_name_value_and_symbol(name, value, next_symbol);
}
}
}
#[cfg(test)]
mod tests {
use crate::block::{binary::BinaryBlock, multicolor::ColoredBlock};
use super::*;
#[test]
fn block_starts_empty_binary() {
let d = Description::new(Vec::<BinaryBlock>::new());
assert!(d.block_starts().is_empty())
}
#[test]
fn block_starts_empty_colored() {
let d = Description::new(Vec::<ColoredBlock>::new());
assert!(d.block_starts().is_empty())
}
#[test]
fn block_starts_single_binary() {
let d = Description::new(vec![BinaryBlock(5)]);
assert_eq!(d.block_starts(), vec![0])
}
#[test]
fn block_starts_single_colored() {
let d = Description::new(vec![ColoredBlock::from_size_and_color(5, 1)]);
assert_eq!(d.block_starts(), vec![0])
}
#[test]
fn block_starts_binary() {
let d = Description::new(vec![BinaryBlock(5), BinaryBlock(2), BinaryBlock(3)]);
assert_eq!(d.block_starts(), vec![0, 6, 9])
}
#[test]
fn block_starts_colored() {
let d = Description::new(vec![
ColoredBlock::from_size_and_color(5, 1),
ColoredBlock::from_size_and_color(1, 1),
ColoredBlock::from_size_and_color(3, 2),
]);
assert_eq!(d.block_starts(), vec![0, 6, 7])
}
#[test]
fn color_ranges_binary() {
let d = Description::new(vec![BinaryBlock(5), BinaryBlock(2), BinaryBlock(3)]);
let ranges = d.color_ranges(12);
assert!(ranges.is_empty())
}
#[test]
fn color_ranges_colored() {
let d = Description::new(vec![
ColoredBlock::from_size_and_color(5, 1),
ColoredBlock::from_size_and_color(1, 4),
ColoredBlock::from_size_and_color(3, 2),
ColoredBlock::from_size_and_color(2, 1),
]);
let mut ranges: Vec<_> = d.color_ranges(13).into_iter().collect();
ranges.sort_by_key(|(k, _v)| *k);
assert_eq!(ranges, vec![(1, 0..13), (2, 6..11), (4, 5..8)])
}
}
#[cfg(test)]
mod color_value_tests {
use super::color::ColorValue;
#[test]
fn construct() {
assert_eq!(ColorValue::parse("0F0"), ColorValue::HexValue3(240));
assert_eq!(ColorValue::parse("#0F0"), ColorValue::HexValue3(240));
assert_eq!(ColorValue::parse("0000FF"), ColorValue::HexValue6(255));
assert_eq!(ColorValue::parse("#0000FF"), ColorValue::HexValue6(255));
assert_eq!(
ColorValue::parse("white"),
ColorValue::CommonName("white".to_string())
);
assert_eq!(
ColorValue::parse("200, 16,0 "),
ColorValue::RgbTriplet(200, 16, 0)
);
assert_eq!(
ColorValue::parse("200, X, 16"),
ColorValue::CommonName("200, X, 16".to_string())
);
}
#[test]
fn rgb() {
assert_eq!(ColorValue::parse("0F0").to_rgb(), (0, 255, 0));
assert_eq!(ColorValue::parse("0000FF").to_rgb(), (0, 0, 255));
assert_eq!(ColorValue::parse("red").to_rgb(), (255, 0, 0));
assert_eq!(ColorValue::parse("YELLOW").to_rgb(), (255, 255, 0));
assert_eq!(ColorValue::parse("teal").to_rgb(), (0, 128, 128));
assert_eq!(ColorValue::parse("unknown").to_rgb(), (0, 0, 0));
assert_eq!(ColorValue::parse("200, 16,0 ").to_rgb(), (200, 16, 0));
assert_eq!(
ColorValue::parse("55bb88").to_rgb(),
ColorValue::parse("5b8").to_rgb()
);
}
}