diff --git a/Cargo.lock b/Cargo.lock index aa93cfd..73caab4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -43,7 +43,6 @@ dependencies = [ "colornamer", "itertools", "rand", - "serde", ] [[package]] @@ -141,26 +140,6 @@ dependencies = [ "getrandom", ] -[[package]] -name = "serde" -version = "1.0.219" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.219" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "syn" version = "2.0.100" diff --git a/Cargo.toml b/Cargo.toml index 306dd95..7ecd2c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,4 +7,3 @@ edition = "2021" colornamer = "1.0.1" itertools = "0.14.0" rand = "0.9.1" -serde = {version = "1.0.219", features=["derive"]} diff --git a/src/choice_function.rs b/src/choice_function.rs index c583eb3..6aa38ee 100644 --- a/src/choice_function.rs +++ b/src/choice_function.rs @@ -1,4 +1,4 @@ -use rand::{rng, seq::IndexedRandom, Rng}; +use rand::{rng, seq::IndexedRandom}; #[derive(Clone, Copy)] pub enum ChoiceFunction { @@ -9,6 +9,9 @@ pub enum ChoiceFunction { impl ChoiceFunction { pub fn choose<'a, T>(&self, xs: &'a [T]) -> &'a T { + if xs.len() == 0 { + panic!("requires at least one item") + } match self { ChoiceFunction::First => &xs[0], ChoiceFunction::Last => &xs[xs.len() - 1], diff --git a/src/creature/coloration.rs b/src/creature/coloration.rs index 9c0845a..732a4f7 100644 --- a/src/creature/coloration.rs +++ b/src/creature/coloration.rs @@ -27,12 +27,12 @@ impl Genetic for Coloration { } } -#[derive(Clone, Copy, Default)] +#[derive(Clone, Default)] pub struct Color { pub r: Stat<'.', '!', 0, 8>, pub g: Stat<'.', '!', 0, 8>, pub b: Stat<'.', '!', 0, 8>, - pub pallor: Stat<'.', '!', 0, 8>, + pub p: Stat<'.', '!', 0, 8>, } impl Genetic for Color { @@ -40,21 +40,25 @@ impl Genetic for Color { self.r.transcribe(t.nest("r")); self.g.transcribe(t.nest("g")); self.b.transcribe(t.nest("b")); - self.pallor.transcribe(t.nest("pallor")); + self.p.transcribe(t.nest("p")); } } impl Color { - fn to_name(&self) -> String { + pub fn to_rgb(&self) -> (u8, u8, u8) { let mut r = self.r.value() * 255 / 8; let mut g = self.g.value() * 255 / 8; let mut b = self.b.value() * 255 / 8; - let pallor_amt = self.pallor.value() * 255 / 8; - r = (r * (255 - pallor_amt) + 255 * pallor_amt) / 255; - g = (g * (255 - pallor_amt) + 255 * pallor_amt) / 255; - b = (b * (255 - pallor_amt) + 255 * pallor_amt) / 255; - // let colornamer = ColorNamer::new(Colors::NTC); - let colornamer = ColorNamer::new(Colors::X11); + let p = self.p.value() * 255 / 8; + + r = (r * (255 - p) + 0xd0 * p) / 255; + g = (g * (255 - p) + 0xd0 * p) / 255; + b = (b * (255 - p) + 0xd8 * p) / 255; + return (r as u8, g as u8, b as u8); + } + pub fn to_name(&self) -> String { + let colornamer = ColorNamer::new(Colors::NTC); + let (r, g, b) = self.to_rgb(); let hex_color = format!("#{:02x}{:02x}{:02x}", r, g, b); return format!( "{} ({})", diff --git a/src/creature/gender.rs b/src/creature/gender.rs new file mode 100644 index 0000000..284046e --- /dev/null +++ b/src/creature/gender.rs @@ -0,0 +1,28 @@ +use crate::genetics::{Genetic, Transcriber}; + +#[derive(Clone, Copy, Debug, Default)] +pub enum Gender { + Male, + Female, + #[default] + Other, +} + +impl Genetic for Gender { + fn transcribe(&mut self, mut t: Transcriber) { + t.express( + self, + &[ + ('m', Gender::Male), + ('f', Gender::Female), + ('O', Gender::Other), + ], + ); + } +} + +impl Gender { + pub fn profile(&self) -> String { + format!("Gender: {:?}\n", self) + } +} diff --git a/src/creature/mod.rs b/src/creature/mod.rs index 5831c97..9008216 100644 --- a/src/creature/mod.rs +++ b/src/creature/mod.rs @@ -1,62 +1,47 @@ mod coloration; +mod gender; +mod phenotype; mod species; mod stats; -use coloration::Coloration; -use species::Parts; -use stats::Stats; +use phenotype::Phenotype; -use crate::genetics::{Genetic, Genotype, Transcriber}; +use crate::{choice_function::ChoiceFunction, genetics::Genotype}; -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct Creature { - // pub name: String, - pub genotype: Genotype, + genotype: Genotype, } impl Creature { - pub fn profile(&self) -> String { - let phenotype: Phenotype = self.genotype.load(); + pub fn generate(choice_function: ChoiceFunction) -> Self { + Creature { + genotype: Genotype::generate::(choice_function), + } + } - let mut profile = phenotype.profile(); // format!("Name: {}\n{}", self.name, phenotype.profile()); - profile.push_str("\nGenome:\n"); - for (chromosome, (lhs, rhs)) in self.genotype.chromosomes.iter() { - profile.push_str(&format!(" {}[l]: {}\n", chromosome, lhs)); - profile.push_str(&format!(" {}[r]: {}\n", chromosome, rhs)); + pub fn breed(&self, choice_function: ChoiceFunction, other: &Creature) -> Creature { + return Creature { + genotype: self.genotype.breed(choice_function, &other.genotype), + }; + } + + pub fn phenotype(&self) -> Phenotype { + let phenotype: Phenotype = self.genotype.load(); + return phenotype; + } + + pub fn profile(&self) -> String { + let phenotype: Phenotype = self.phenotype(); + let mut profile = phenotype.profile(); + if true { + profile.push_str("\nGenome:\n"); + + for (chromosome, (lhs, rhs)) in self.genotype.chromosomes.iter() { + profile.push_str(&format!(" {}[l]: {}\n", chromosome, lhs)); + profile.push_str(&format!(" {}[r]: {}\n", chromosome, rhs)); + } } return profile; } - - pub fn breed(&self, other: &Creature) -> Creature { - return Creature { - // name: String::from("Pyrex"), - genotype: self.genotype.breed(&other.genotype), - }; - } -} - -#[derive(Default)] -pub struct Phenotype { - pub stats: Stats, - pub coloration: Coloration, - pub parts: Parts, -} - -impl Phenotype { - pub fn profile(&self) -> String { - format!( - "{}{}{}", - self.stats.profile(), - self.coloration.profile(), - self.parts.profile() - ) - } -} - -impl Genetic for Phenotype { - fn transcribe(&mut self, t: Transcriber) { - self.stats.transcribe(t.nest("stats")); - self.coloration.transcribe(t.nest("coloration")); - self.parts.transcribe(t.nest("parts")); - } } diff --git a/src/creature/phenotype.rs b/src/creature/phenotype.rs new file mode 100644 index 0000000..dc2ed6c --- /dev/null +++ b/src/creature/phenotype.rs @@ -0,0 +1,32 @@ +use crate::genetics::{Genetic, Transcriber}; + +use super::{coloration::Coloration, gender::Gender, species::Parts, stats::Stats}; + +#[derive(Default)] +pub struct Phenotype { + pub gender: Gender, + pub parts: Parts, + pub stats: Stats, + pub coloration: Coloration, +} + +impl Phenotype { + pub fn profile(&self) -> String { + format!( + "{}{}{}{}", + self.gender.profile(), + self.parts.profile(), + self.stats.profile(), + self.coloration.profile() + ) + } +} + +impl Genetic for Phenotype { + fn transcribe(&mut self, t: Transcriber) { + self.gender.transcribe(t.nest("gender")); + self.parts.transcribe(t.nest("parts")); + self.stats.transcribe(t.nest("stats")); + self.coloration.transcribe(t.nest("coloration")); + } +} diff --git a/src/creature/species.rs b/src/creature/species.rs index ed32d16..772a1e4 100644 --- a/src/creature/species.rs +++ b/src/creature/species.rs @@ -43,14 +43,6 @@ impl Parts { } } -impl Genetic for Parts { - fn transcribe(&mut self, t: Transcriber) { - self.stare.transcribe(t.nest("stare")); - self.fangs.transcribe(t.nest("fangs")); - self.wings.transcribe(t.nest("wings")); - } -} - #[derive(Clone, Copy, Debug)] pub enum Species { Bunny, @@ -62,3 +54,11 @@ pub enum Species { Snake, Bat, } + +impl Genetic for Parts { + fn transcribe(&mut self, t: Transcriber) { + self.stare.transcribe(t.nest("stare")); + self.fangs.transcribe(t.nest("fangs")); + self.wings.transcribe(t.nest("wings")); + } +} diff --git a/src/creature/stats.rs b/src/creature/stats.rs index 32ae912..5bf8e60 100644 --- a/src/creature/stats.rs +++ b/src/creature/stats.rs @@ -51,7 +51,7 @@ impl Gene { fn transcribe(&mut self, mut t: Transcriber) { for v in self.values.iter_mut() { - t.express(v, &[(OFF, false), (ON, true)]); + t.express(v, &[(OFF, false), (ON, true)]) } } } diff --git a/src/genetics/mod.rs b/src/genetics/mod.rs new file mode 100644 index 0000000..c0e4d71 --- /dev/null +++ b/src/genetics/mod.rs @@ -0,0 +1,59 @@ +mod transcriber; + +use std::collections::{BTreeMap, HashSet}; + +use itertools::{EitherOrBoth, Itertools}; +pub use transcriber::Transcriber; + +use crate::choice_function::{self, ChoiceFunction}; + +#[derive(Clone, Debug)] +pub struct Genotype { + pub chromosomes: BTreeMap, +} + +type Chromosomes = (String, String); + +pub trait Genetic: Default { + fn transcribe(&mut self, t: Transcriber); +} + +fn merge(choice_function: ChoiceFunction, one: &str, two: &str) -> String { + let mut results = String::new(); + for pair in one.chars().zip_longest(two.chars()) { + results.push(match pair { + EitherOrBoth::Both(l, r) => *choice_function.choose(&[l, r]), + EitherOrBoth::Left(l) => l, + EitherOrBoth::Right(r) => r, + }); + } + results +} + +impl Genotype { + pub fn breed(&self, choice_function: ChoiceFunction, other: &Genotype) -> Genotype { + let mut chromosomes: HashSet = HashSet::new(); + chromosomes.extend(self.chromosomes.keys().cloned()); + chromosomes.extend(other.chromosomes.keys().cloned()); + + let mut result: BTreeMap = BTreeMap::new(); + let placeholder = (String::new(), String::new()); + + for k in chromosomes { + let mine = self.chromosomes.get(&k).unwrap_or(&placeholder); + let theirs = self.chromosomes.get(&k).unwrap_or(&placeholder); + + /* + let mine_merged = merge(choice_function, &mine.0, &mine.1); + let theirs_merged = merge(choice_function, &theirs.0, &theirs.1); + result.insert(k, (mine_merged, theirs_merged)); + */ + let my_choice = (*choice_function.choose(&[&mine.0, &mine.1])).clone(); + let their_choice = (*choice_function.choose(&[&theirs.0, &theirs.1])).clone(); + result.insert(k, (my_choice, their_choice)); + } + return Genotype { + chromosomes: result, + }; + } +} diff --git a/src/genetics.rs b/src/genetics/transcriber.rs similarity index 52% rename from src/genetics.rs rename to src/genetics/transcriber.rs index d577d9a..940f65c 100644 --- a/src/genetics.rs +++ b/src/genetics/transcriber.rs @@ -1,74 +1,29 @@ -use std::{ - cell::RefCell, - collections::{BTreeMap, HashSet}, - rc::Rc, -}; - -use itertools::{EitherOrBoth, Itertools}; -use rand::{seq::IndexedRandom, RngCore}; -use serde::{Deserialize, Serialize}; +use std::{cell::RefCell, collections::BTreeMap}; use crate::choice_function::ChoiceFunction; -#[derive(Clone, Deserialize, Serialize, Debug)] -pub struct Genotype { - pub chromosomes: BTreeMap, -} -type Chromosomes = (String, String); - -fn merge(rng: &mut impl RngCore, one: &str, two: &str) -> String { - let mut results = String::new(); - for pair in one.chars().zip_longest(two.chars()) { - results.push(match pair { - EitherOrBoth::Both(l, r) => *[l, r].choose(rng).unwrap(), - EitherOrBoth::Left(l) => l, - EitherOrBoth::Right(r) => r, - }) - } - return results; -} +use super::{Chromosomes, Genetic, Genotype}; impl Genotype { - pub fn breed(&self, other: &Genotype) -> Genotype { - let mut rng = rand::rng(); - let mut chromosomes: HashSet = HashSet::new(); - chromosomes.extend(self.chromosomes.keys().cloned()); - chromosomes.extend(other.chromosomes.keys().cloned()); - - let mut result: BTreeMap = BTreeMap::new(); - let placeholder = (String::new(), String::new()); - for k in chromosomes { - let mine = self.chromosomes.get(&k).unwrap_or(&placeholder); - let theirs = self.chromosomes.get(&k).unwrap_or(&placeholder); - - let mine_merged = merge(&mut rng, &mine.0, &mine.1); - let theirs_merged = merge(&mut rng, &theirs.0, &theirs.1); - result.insert(k, (mine_merged, theirs_merged)); - } - return Genotype { - chromosomes: result, - }; - } - pub fn generate(choice_function: ChoiceFunction) -> Self { let root = RootTranscriber::Generator(Generator { choice_function, progress: BTreeMap::new(), }); - let root_cell = Rc::new(RefCell::new(root)); + let root_cell = RefCell::new(root); let transcriber = Transcriber { - root: root_cell.clone(), + root: &root_cell, chromosome: String::from("base"), }; let mut base = T::default(); base.transcribe(transcriber); - match &*root_cell.borrow() { + match root_cell.into_inner() { RootTranscriber::Generator(generator) => { - let chromosomes = generator.progress.clone(); + let chromosomes = generator.progress; return Genotype { chromosomes }; } - _ => panic!("a generator should not able to turn into a loader"), - }; + _ => panic!("a generator should not be able to turn into anything else"), + } } pub fn load(&self) -> T { @@ -76,13 +31,14 @@ impl Genotype { for (key, (lhs, rhs)) in self.chromosomes.iter() { progress.insert( key.clone(), + // as a note: we could just store the character iterator! (0, Vec::from_iter(lhs.chars()), Vec::from_iter(rhs.chars())), ); } let root = RootTranscriber::Loader(Loader { progress }); - let root_cell = Rc::new(RefCell::new(root)); + let root_cell = RefCell::new(root); let transcriber = Transcriber { - root: root_cell.clone(), + root: &root_cell, chromosome: String::from("base"), }; let mut base = T::default(); @@ -104,21 +60,20 @@ impl RootTranscriber { possibilities: &[(char, T)], ) { match self { - RootTranscriber::Generator(generator) => { - generator.express(chromosome, field, possibilities) + RootTranscriber::Generator(generator) => generator.express(chromosome, possibilities), + RootTranscriber::Loader(loader) => { + loader.express(chromosome, field, possibilities); } - RootTranscriber::Loader(loader) => loader.express(chromosome, field, possibilities), } } } -#[derive(Clone)] -pub struct Transcriber { - root: Rc>, +pub struct Transcriber<'r> { + root: &'r RefCell, chromosome: String, } -impl Transcriber { +impl Transcriber<'_> { pub fn express(&mut self, field: &mut T, possibilities: &[(char, T)]) { self.root .borrow_mut() @@ -127,33 +82,28 @@ impl Transcriber { pub fn nest(&self, suffix: &str) -> Transcriber { return Transcriber { - root: self.root.clone(), + root: self.root, chromosome: format!("{}.{}", self.chromosome, suffix), }; } } - -pub trait Genetic: Default { - fn transcribe(&mut self, t: Transcriber); -} - struct Generator { choice_function: ChoiceFunction, progress: BTreeMap, } impl Generator { - pub fn express(&mut self, chromosome: &str, _field: &mut T, possibilities: &[(char, T)]) { - let lhs = self.choice_function.choose(possibilities).0; - let rhs = self.choice_function.choose(possibilities).0; + pub fn express(&mut self, chromosome: &str, possibilities: &[(char, T)]) { + let lhs = self.choice_function.choose(possibilities); + let rhs = self.choice_function.choose(possibilities); let progress = self .progress - .entry(chromosome.to_string()) + .entry(chromosome.to_string()) // SLOW! .or_insert((String::new(), String::new())); - progress.0.push(lhs); - progress.1.push(rhs); + progress.0.push(lhs.0); + progress.1.push(rhs.0); } } @@ -175,7 +125,7 @@ impl Loader { *ix += 1; } - let mut expressed = lhs; // lhs wins if not dominated + let mut expressed = lhs; if dominance(rhs) > dominance(lhs) { expressed = rhs; } diff --git a/src/main.rs b/src/main.rs index ea43751..34cba6a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,53 +1,48 @@ use choice_function::ChoiceFunction; -use creature::{Creature, Phenotype}; -use genetics::Genotype; +use creature::Creature; use rand::{rng, seq::SliceRandom}; mod choice_function; mod creature; mod genetics; -fn generate_creature() -> Creature { - let genotype: Genotype = Genotype::generate::(ChoiceFunction::Random); - let creature = Creature { - // name: String::from("Pyrex"), - genotype, - }; - return creature; -} - fn evolve() { + let cf = ChoiceFunction::Random; const N_ITERATIONS: usize = 200; const N_CREATURES: usize = 100; - const N_SURVIVORS: usize = 90; + const N_RANDOMS: usize = 50; + const N_SURVIVORS: usize = 40; let mut pool = vec![]; for _ in 0..N_CREATURES { - pool.push(generate_creature()); + pool.push(Creature::generate(cf)); } for iteration in 0..N_ITERATIONS { report(iteration, &pool); - // kill N creatures - let mut survivors = pool.clone(); + let mut survivors = pool; survivors.sort_by(|x, y| fitness(x).partial_cmp(&fitness(y)).unwrap()); survivors.reverse(); survivors.drain(N_SURVIVORS..N_CREATURES); - let mut pool2 = vec![]; + for _ in 0..N_RANDOMS { + survivors.push(Creature::generate(cf)); + } + let mut survivors_queue = vec![]; while survivors_queue.len() < N_CREATURES * 2 { survivors_queue.extend(survivors.clone()); } survivors_queue.shuffle(&mut rng()); + let mut offspring = vec![]; for _ in 0..N_CREATURES { let lhs = survivors_queue.pop().unwrap(); let rhs = survivors_queue.pop().unwrap(); - pool2.push(lhs.breed(&rhs)); + offspring.push(lhs.breed(cf, &rhs)); } - pool = pool2; + pool = offspring; } } @@ -58,13 +53,10 @@ fn report(iteration: usize, pool: &[Creature]) { } fn fitness(creature: &Creature) -> f64 { - let phenotype: Phenotype = creature.genotype.load(); + let phenotype = creature.phenotype(); let mut value = 0.0; - if phenotype.parts.has_wings() { - value += 0.5; - } - value -= phenotype.coloration.body.r.value() as f64 / 4.0; value += (phenotype.stats.iq.value() as f64) / 10.0; + value += (phenotype.stats.eq.value() as f64) / 10.0; return value; } diff --git a/thumbnail.png b/thumbnail.png new file mode 100644 index 0000000..d896a6a Binary files /dev/null and b/thumbnail.png differ diff --git a/thumbnail.xcf b/thumbnail.xcf new file mode 100644 index 0000000..9e6a1ba Binary files /dev/null and b/thumbnail.xcf differ diff --git a/thumbnail2.png b/thumbnail2.png new file mode 100644 index 0000000..a52ff6d Binary files /dev/null and b/thumbnail2.png differ diff --git a/thumbnail2.xcf b/thumbnail2.xcf new file mode 100644 index 0000000..34a411c Binary files /dev/null and b/thumbnail2.xcf differ diff --git a/thumbnail3.xcf b/thumbnail3.xcf new file mode 100644 index 0000000..35509b6 Binary files /dev/null and b/thumbnail3.xcf differ diff --git a/thumbnail4.png b/thumbnail4.png new file mode 100644 index 0000000..d9c1a78 Binary files /dev/null and b/thumbnail4.png differ diff --git a/thumbnail4.xcf b/thumbnail4.xcf new file mode 100644 index 0000000..3185c20 Binary files /dev/null and b/thumbnail4.xcf differ