Add sampler

This commit is contained in:
Rico Riedel 2022-07-31 17:35:49 +02:00
parent 56e630204a
commit 8d75146744
No known key found for this signature in database
GPG Key ID: 75AC868575DE7B18
9 changed files with 228 additions and 132 deletions

35
Cargo.lock generated
View File

@ -38,6 +38,12 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "convert_case"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
[[package]] [[package]]
name = "crossterm" name = "crossterm"
version = "0.24.0" version = "0.24.0"
@ -63,6 +69,19 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "derive_more"
version = "0.99.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
dependencies = [
"convert_case",
"proc-macro2",
"quote",
"rustc_version",
"syn",
]
[[package]] [[package]]
name = "difflib" name = "difflib"
version = "0.4.0" version = "0.4.0"
@ -293,12 +312,27 @@ version = "0.6.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
[[package]]
name = "rustc_version"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [
"semver",
]
[[package]] [[package]]
name = "scopeguard" name = "scopeguard"
version = "1.1.0" version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "semver"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2333e6df6d6598f2b1974829f853c2b4c5f4a6e503c10af918081aa6f8564e1"
[[package]] [[package]]
name = "signal-hook" name = "signal-hook"
version = "0.3.14" version = "0.3.14"
@ -435,5 +469,6 @@ version = "2.0.0"
dependencies = [ dependencies = [
"approx", "approx",
"crossterm", "crossterm",
"derive_more",
"mockall", "mockall",
] ]

View File

@ -8,6 +8,7 @@ authors = ["Rico Riedel"]
[dependencies] [dependencies]
crossterm = "0.24" crossterm = "0.24"
derive_more = "0.99"
[dev-dependencies] [dev-dependencies]
mockall = "0.11" mockall = "0.11"

View File

@ -1,27 +1,40 @@
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum CharSample {
Keep,
Draw(char),
Clear,
}
#[cfg_attr(test, mockall::automock)] #[cfg_attr(test, mockall::automock)]
pub trait CharConverter { pub trait CharConverter {
fn convert(&self, level: f32) -> char; fn convert(&self, level: f32) -> CharSample;
} }
pub struct CharConverterImpl { pub struct CharConverterImpl {
chars: String, chars: String,
count: usize, count: f32,
} }
impl CharConverterImpl { impl CharConverterImpl {
pub fn new(chars: String) -> Self { pub fn new(chars: String) -> Self {
let count = chars.chars().count(); let count = chars.chars().count() as f32;
Self { chars, count } Self { chars, count }
} }
} }
impl CharConverter for CharConverterImpl { impl CharConverter for CharConverterImpl {
fn convert(&self, level: f32) -> char { fn convert(&self, level: f32) -> CharSample {
let len = self.count as f32; if level < 0.0 {
let index = (level * len).rem_euclid(len) as usize; CharSample::Keep
} else if level < 1.0 {
let index = (level * self.count) as usize;
let char = self.chars.chars().nth(index).unwrap();
self.chars.chars().nth(index).unwrap() CharSample::Draw(char)
} else {
CharSample::Clear
}
} }
} }
@ -30,37 +43,26 @@ mod test {
use super::*; use super::*;
#[test] #[test]
fn convert_negative_index() { fn convert_keep() {
let converter = CharConverterImpl::new("abc".to_string()); let converter = CharConverterImpl::new("abc".to_string());
assert_eq!('c', converter.convert(-0.2)); assert_eq!(CharSample::Keep, converter.convert(-0.1));
} }
#[test] #[test]
fn convert_index_zero() { fn convert_draw() {
let converter = CharConverterImpl::new("abc".to_string()); let converter = CharConverterImpl::new("xyz".to_string());
assert_eq!('a', converter.convert(0.0)); assert_eq!(CharSample::Draw('x'), converter.convert(0.0));
assert_eq!(CharSample::Draw('y'), converter.convert(0.5));
assert_eq!(CharSample::Draw('z'), converter.convert(0.9));
} }
#[test] #[test]
fn convert() { fn convert_clear() {
let converter = CharConverterImpl::new("abc".to_string()); let converter = CharConverterImpl::new("123".to_string());
assert_eq!('b', converter.convert(0.5)); assert_eq!(CharSample::Clear, converter.convert(1.0));
} assert_eq!(CharSample::Clear, converter.convert(1.5));
#[test]
fn convert_index_one() {
let converter = CharConverterImpl::new("abc".to_string());
assert_eq!('a', converter.convert(1.0));
}
#[test]
fn convert_index_above_one() {
let converter = CharConverterImpl::new("abc".to_string());
assert_eq!('b', converter.convert(1.5));
} }
} }

View File

@ -1,55 +0,0 @@
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum Level {
Keep,
Draw(f32),
Clear,
}
#[cfg_attr(test, mockall::automock)]
pub trait LevelConverter {
fn convert(&self, level: f32) -> Level;
}
#[derive(Default)]
pub struct LevelConverterImpl;
impl LevelConverter for LevelConverterImpl {
fn convert(&self, level: f32) -> Level {
if level < 0.0 {
Level::Keep
} else if level < 1.0 {
Level::Draw(level)
} else {
Level::Clear
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn convert_keep() {
let converter = LevelConverterImpl::default();
assert_eq!(Level::Keep, converter.convert(-0.1));
}
#[test]
fn convert_draw() {
let converter = LevelConverterImpl::default();
assert_eq!(Level::Draw(0.0), converter.convert(0.0));
assert_eq!(Level::Draw(0.5), converter.convert(0.5));
assert_eq!(Level::Draw(0.9), converter.convert(0.9));
}
#[test]
fn convert_clear() {
let converter = LevelConverterImpl::default();
assert_eq!(Level::Clear, converter.convert(1.0));
assert_eq!(Level::Clear, converter.convert(1.5));
}
}

View File

@ -1,44 +1,30 @@
use crate::convert::char::CharConverter; use crate::convert::char::{CharConverter, CharSample};
use crate::convert::color::ColorConverter; use crate::convert::color::ColorConverter;
use crate::convert::level::{Level, LevelConverter};
use crossterm::style::Color; use crossterm::style::Color;
mod char; pub mod char;
mod color; pub mod color;
mod level;
#[cfg_attr(test, mockall::automock)]
pub trait Converter { pub trait Converter {
fn char(&self, level: f32) -> char; fn char(&self, level: f32) -> CharSample;
fn color(&self, level: f32) -> Color; fn color(&self, level: f32) -> Color;
fn level(&self, level: f32) -> Level;
} }
pub struct ConverterImpl<T1, T2, T3> { #[derive(derive_more::Constructor)]
pub struct ConverterImpl<T1, T2> {
char: T1, char: T1,
color: T2, color: T2,
level: T3,
} }
impl<T1, T2, T3> ConverterImpl<T1, T2, T3> { impl<T1: CharConverter, T2: ColorConverter> Converter for ConverterImpl<T1, T2> {
pub fn new(char: T1, color: T2, level: T3) -> Self { fn char(&self, level: f32) -> CharSample {
Self { char, color, level }
}
}
impl<T1: CharConverter, T2: ColorConverter, T3: LevelConverter> Converter
for ConverterImpl<T1, T2, T3>
{
fn char(&self, level: f32) -> char {
self.char.convert(level) self.char.convert(level)
} }
fn color(&self, level: f32) -> Color { fn color(&self, level: f32) -> Color {
self.color.convert(level) self.color.convert(level)
} }
fn level(&self, level: f32) -> Level {
self.level.convert(level)
}
} }
#[cfg(test)] #[cfg(test)]
@ -46,51 +32,34 @@ mod test {
use super::*; use super::*;
use crate::convert::char::MockCharConverter; use crate::convert::char::MockCharConverter;
use crate::convert::color::MockColorConverter; use crate::convert::color::MockColorConverter;
use crate::convert::level::MockLevelConverter; use mockall::predicate::*;
use mockall::predicate::eq;
#[test] #[test]
fn char() { fn char() {
let mut char = MockCharConverter::new(); let mut char = MockCharConverter::new();
let color = MockColorConverter::new(); let color = MockColorConverter::new();
let level = MockLevelConverter::new();
char.expect_convert().with(eq(4.0)).return_const('M'); char.expect_convert()
.with(eq(4.0))
.return_const(CharSample::Draw('M'));
let converter = ConverterImpl::new(char, color, level); let converter = ConverterImpl::new(char, color);
assert_eq!('M', converter.char(4.0)); assert_eq!(CharSample::Draw('M'), converter.char(4.0));
} }
#[test] #[test]
fn color() { fn color() {
let char = MockCharConverter::new(); let char = MockCharConverter::new();
let mut color = MockColorConverter::new(); let mut color = MockColorConverter::new();
let level = MockLevelConverter::new();
color color
.expect_convert() .expect_convert()
.with(eq(2.0)) .with(eq(2.0))
.return_const(Color::Yellow); .return_const(Color::Yellow);
let converter = ConverterImpl::new(char, color, level); let converter = ConverterImpl::new(char, color);
assert_eq!(Color::Yellow, converter.color(2.0)); assert_eq!(Color::Yellow, converter.color(2.0));
} }
#[test]
fn level() {
let char = MockCharConverter::new();
let color = MockColorConverter::new();
let mut level = MockLevelConverter::new();
level
.expect_convert()
.with(eq(3.0))
.return_const(Level::Draw(2.0));
let converter = ConverterImpl::new(char, color, level);
assert_eq!(Level::Draw(2.0), converter.level(3.0));
}
} }

View File

@ -4,6 +4,7 @@ pub mod printer;
pub mod convert; pub mod convert;
pub mod term; pub mod term;
mod vec; mod vec;
mod sampler;
fn main() -> Result<(), error::Error> { fn main() -> Result<(), error::Error> {
Ok(()) Ok(())

View File

@ -17,6 +17,7 @@ pub trait PatternFactory {
fn create(&self, config: &Config) -> Box<dyn Pattern>; fn create(&self, config: &Config) -> Box<dyn Pattern>;
} }
#[cfg_attr(test, mockall::automock)]
pub trait Pattern { pub trait Pattern {
fn sample(&self, pos: Vector) -> f32; fn sample(&self, pos: Vector) -> f32;
} }

142
src/sampler.rs Normal file
View File

@ -0,0 +1,142 @@
use crate::convert::char::CharSample;
use crate::convert::Converter;
use crate::pattern::{Config, Pattern, PatternFactory};
use crate::vec::Vector;
use crossterm::style::Color;
use std::rc::Rc;
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum Sample {
Keep,
Draw { char: char, color: Color },
Clear,
}
pub trait SamplerFactory {
fn create(&self, config: &Config) -> Box<dyn Sampler>;
}
pub trait Sampler {
fn sample(&self, pos: Vector) -> Sample;
}
#[derive(derive_more::Constructor)]
pub struct SamplerFactoryImpl<T> {
char: Box<dyn PatternFactory>,
color: Box<dyn PatternFactory>,
converter: Rc<T>,
}
#[derive(derive_more::Constructor)]
pub struct SamplerImpl<T> {
char: Box<dyn Pattern>,
color: Box<dyn Pattern>,
converter: Rc<T>,
}
impl<T: Converter + 'static> SamplerFactory for SamplerFactoryImpl<T> {
fn create(&self, config: &Config) -> Box<dyn Sampler> {
Box::new(SamplerImpl::new(
self.char.create(config),
self.color.create(config),
self.converter.clone(),
))
}
}
impl<T: Converter> Sampler for SamplerImpl<T> {
fn sample(&self, pos: Vector) -> Sample {
match self.converter.char(self.char.sample(pos)) {
CharSample::Keep => Sample::Keep,
CharSample::Draw(char) => Sample::Draw {
char,
color: self.converter.color(self.color.sample(pos)),
},
CharSample::Clear => Sample::Clear,
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::convert::MockConverter;
use crate::pattern::MockPattern;
use mockall::predicate::eq;
#[test]
fn sample_keep() {
let color = MockPattern::new();
let mut char = MockPattern::new();
let mut converter = MockConverter::new();
char.expect_sample().return_const(3.0);
converter.expect_char().return_const(CharSample::Keep);
let sampler = SamplerImpl::new(Box::new(char), Box::new(color), Rc::new(converter));
assert_eq!(Sample::Keep, sampler.sample(Vector::default()));
}
#[test]
fn sample_draw() {
let mut char = MockPattern::new();
let mut color = MockPattern::new();
let mut converter = MockConverter::new();
char.expect_sample().return_const(3.0);
color.expect_sample().return_const(2.0);
converter.expect_char().return_const(CharSample::Draw('M'));
converter.expect_color().return_const(Color::Red);
let sampler = SamplerImpl::new(Box::new(char), Box::new(color), Rc::new(converter));
assert_eq!(
Sample::Draw {
char: 'M',
color: Color::Red
},
sampler.sample(Vector::default())
);
}
#[test]
fn sample_clear() {
let color = MockPattern::new();
let mut char = MockPattern::new();
let mut converter = MockConverter::new();
char.expect_sample().return_const(3.0);
converter.expect_char().return_const(CharSample::Clear);
let sampler = SamplerImpl::new(Box::new(char), Box::new(color), Rc::new(converter));
assert_eq!(Sample::Clear, sampler.sample(Vector::default()));
}
#[test]
fn sample_args_correct() {
let mut char = MockPattern::new();
let mut color = MockPattern::new();
let mut converter = MockConverter::new();
char.expect_sample()
.with(eq(Vector::new(4.0, 2.0)))
.return_const(6.0);
color
.expect_sample()
.with(eq(Vector::new(4.0, 2.0)))
.return_const(7.0);
converter
.expect_char()
.with(eq(6.0))
.return_const(CharSample::Draw('A'));
converter
.expect_color()
.with(eq(7.0))
.return_const(Color::Reset);
SamplerImpl::new(Box::new(char), Box::new(color), Rc::new(converter))
.sample(Vector::new(4.0, 2.0));
}
}

View File

@ -1,6 +1,6 @@
use std::ops::Sub; use std::ops::Sub;
#[derive(Copy, Clone, PartialEq, Debug)] #[derive(Copy, Clone, PartialEq, Debug, Default)]
pub struct Vector { pub struct Vector {
pub x: f32, pub x: f32,
pub y: f32, pub y: f32,