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"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "convert_case"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
[[package]]
name = "crossterm"
version = "0.24.0"
@ -63,6 +69,19 @@ dependencies = [
"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]]
name = "difflib"
version = "0.4.0"
@ -293,12 +312,27 @@ version = "0.6.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "semver"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2333e6df6d6598f2b1974829f853c2b4c5f4a6e503c10af918081aa6f8564e1"
[[package]]
name = "signal-hook"
version = "0.3.14"
@ -435,5 +469,6 @@ version = "2.0.0"
dependencies = [
"approx",
"crossterm",
"derive_more",
"mockall",
]

View File

@ -8,6 +8,7 @@ authors = ["Rico Riedel"]
[dependencies]
crossterm = "0.24"
derive_more = "0.99"
[dev-dependencies]
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)]
pub trait CharConverter {
fn convert(&self, level: f32) -> char;
fn convert(&self, level: f32) -> CharSample;
}
pub struct CharConverterImpl {
chars: String,
count: usize,
count: f32,
}
impl CharConverterImpl {
pub fn new(chars: String) -> Self {
let count = chars.chars().count();
let count = chars.chars().count() as f32;
Self { chars, count }
}
}
impl CharConverter for CharConverterImpl {
fn convert(&self, level: f32) -> char {
let len = self.count as f32;
let index = (level * len).rem_euclid(len) as usize;
fn convert(&self, level: f32) -> CharSample {
if level < 0.0 {
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::*;
#[test]
fn convert_negative_index() {
fn convert_keep() {
let converter = CharConverterImpl::new("abc".to_string());
assert_eq!('c', converter.convert(-0.2));
assert_eq!(CharSample::Keep, converter.convert(-0.1));
}
#[test]
fn convert_index_zero() {
let converter = CharConverterImpl::new("abc".to_string());
fn convert_draw() {
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]
fn convert() {
let converter = CharConverterImpl::new("abc".to_string());
fn convert_clear() {
let converter = CharConverterImpl::new("123".to_string());
assert_eq!('b', converter.convert(0.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));
assert_eq!(CharSample::Clear, converter.convert(1.0));
assert_eq!(CharSample::Clear, 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::level::{Level, LevelConverter};
use crossterm::style::Color;
mod char;
mod color;
mod level;
pub mod char;
pub mod color;
#[cfg_attr(test, mockall::automock)]
pub trait Converter {
fn char(&self, level: f32) -> char;
fn char(&self, level: f32) -> CharSample;
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,
color: T2,
level: T3,
}
impl<T1, T2, T3> ConverterImpl<T1, T2, T3> {
pub fn new(char: T1, color: T2, level: T3) -> Self {
Self { char, color, level }
}
}
impl<T1: CharConverter, T2: ColorConverter, T3: LevelConverter> Converter
for ConverterImpl<T1, T2, T3>
{
fn char(&self, level: f32) -> char {
impl<T1: CharConverter, T2: ColorConverter> Converter for ConverterImpl<T1, T2> {
fn char(&self, level: f32) -> CharSample {
self.char.convert(level)
}
fn color(&self, level: f32) -> Color {
self.color.convert(level)
}
fn level(&self, level: f32) -> Level {
self.level.convert(level)
}
}
#[cfg(test)]
@ -46,51 +32,34 @@ mod test {
use super::*;
use crate::convert::char::MockCharConverter;
use crate::convert::color::MockColorConverter;
use crate::convert::level::MockLevelConverter;
use mockall::predicate::eq;
use mockall::predicate::*;
#[test]
fn char() {
let mut char = MockCharConverter::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]
fn color() {
let char = MockCharConverter::new();
let mut color = MockColorConverter::new();
let level = MockLevelConverter::new();
color
.expect_convert()
.with(eq(2.0))
.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));
}
#[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 term;
mod vec;
mod sampler;
fn main() -> Result<(), error::Error> {
Ok(())

View File

@ -17,6 +17,7 @@ pub trait PatternFactory {
fn create(&self, config: &Config) -> Box<dyn Pattern>;
}
#[cfg_attr(test, mockall::automock)]
pub trait Pattern {
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;
#[derive(Copy, Clone, PartialEq, Debug)]
#[derive(Copy, Clone, PartialEq, Debug, Default)]
pub struct Vector {
pub x: f32,
pub y: f32,