#![allow(dead_code)]
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum BeardZone {
Mustache,
ChinBeard,
Cheeks,
Sideburns,
Neck,
}
impl BeardZone {
#[allow(dead_code)]
pub fn name(self) -> &'static str {
match self {
BeardZone::Mustache => "mustache",
BeardZone::ChinBeard => "chin_beard",
BeardZone::Cheeks => "cheeks",
BeardZone::Sideburns => "sideburns",
BeardZone::Neck => "neck",
}
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct BeardZoneEntry {
pub zone: BeardZone,
pub density: f32,
pub length: f32,
pub thickness: f32,
}
impl BeardZoneEntry {
#[allow(dead_code)]
pub fn new(zone: BeardZone) -> Self {
BeardZoneEntry {
zone,
density: 0.0,
length: 0.0,
thickness: 0.0,
}
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct BeardDensityState {
pub zones: Vec<BeardZoneEntry>,
pub global_density: f32,
}
#[allow(dead_code)]
pub fn default_beard_density_state() -> BeardDensityState {
let zones = vec![
BeardZoneEntry::new(BeardZone::Mustache),
BeardZoneEntry::new(BeardZone::ChinBeard),
BeardZoneEntry::new(BeardZone::Cheeks),
BeardZoneEntry::new(BeardZone::Sideburns),
BeardZoneEntry::new(BeardZone::Neck),
];
BeardDensityState {
zones,
global_density: 0.0,
}
}
#[allow(dead_code)]
pub fn bd_set_global(state: &mut BeardDensityState, v: f32) {
state.global_density = v.clamp(0.0, 1.0);
for z in &mut state.zones {
z.density = v.clamp(0.0, 1.0);
}
}
#[allow(dead_code)]
pub fn bd_set_zone_density(state: &mut BeardDensityState, zone: BeardZone, v: f32) {
for z in &mut state.zones {
if z.zone == zone {
z.density = v.clamp(0.0, 1.0);
return;
}
}
}
#[allow(dead_code)]
pub fn bd_set_zone_length(state: &mut BeardDensityState, zone: BeardZone, v: f32) {
for z in &mut state.zones {
if z.zone == zone {
z.length = v.clamp(0.0, 1.0);
return;
}
}
}
#[allow(dead_code)]
pub fn bd_reset(state: &mut BeardDensityState) {
for z in &mut state.zones {
z.density = 0.0;
z.length = 0.0;
z.thickness = 0.0;
}
state.global_density = 0.0;
}
#[allow(dead_code)]
pub fn bd_is_clean_shaven(state: &BeardDensityState) -> bool {
state.zones.iter().all(|z| z.density < 1e-6)
}
#[allow(dead_code)]
pub fn bd_average_density(state: &BeardDensityState) -> f32 {
if state.zones.is_empty() {
return 0.0;
}
let sum: f32 = state.zones.iter().map(|z| z.density).sum();
sum / state.zones.len() as f32
}
#[allow(dead_code)]
pub fn bd_to_json(state: &BeardDensityState) -> String {
format!(
r#"{{"global_density":{:.4},"zone_count":{}}}"#,
state.global_density,
state.zones.len()
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_is_clean_shaven() {
assert!(bd_is_clean_shaven(&default_beard_density_state()));
}
#[test]
fn set_global_density() {
let mut s = default_beard_density_state();
bd_set_global(&mut s, 0.8);
assert!((s.global_density - 0.8).abs() < 1e-5);
}
#[test]
fn set_global_sets_all_zones() {
let mut s = default_beard_density_state();
bd_set_global(&mut s, 0.5);
assert!(!bd_is_clean_shaven(&s));
}
#[test]
fn set_zone_density_clamps() {
let mut s = default_beard_density_state();
bd_set_zone_density(&mut s, BeardZone::Mustache, 2.0);
let z = s
.zones
.iter()
.find(|z| z.zone == BeardZone::Mustache)
.expect("should succeed");
assert!((z.density - 1.0).abs() < 1e-6);
}
#[test]
fn reset_clears_all() {
let mut s = default_beard_density_state();
bd_set_global(&mut s, 1.0);
bd_reset(&mut s);
assert!(bd_is_clean_shaven(&s));
}
#[test]
fn average_density_zero_default() {
let s = default_beard_density_state();
assert!(bd_average_density(&s).abs() < 1e-6);
}
#[test]
fn average_density_after_global_set() {
let mut s = default_beard_density_state();
bd_set_global(&mut s, 0.6);
assert!((bd_average_density(&s) - 0.6).abs() < 1e-5);
}
#[test]
fn zone_length_set() {
let mut s = default_beard_density_state();
bd_set_zone_length(&mut s, BeardZone::ChinBeard, 0.7);
let z = s
.zones
.iter()
.find(|z| z.zone == BeardZone::ChinBeard)
.expect("should succeed");
assert!((z.length - 0.7).abs() < 1e-5);
}
#[test]
fn to_json_has_global_density() {
assert!(bd_to_json(&default_beard_density_state()).contains("global_density"));
}
}