Video version

This commit is contained in:
Pyrex 2025-04-20 18:39:52 -07:00
parent b323eef0e2
commit 4df78304d0
19 changed files with 218 additions and 187 deletions

21
Cargo.lock generated
View File

@ -43,7 +43,6 @@ dependencies = [
"colornamer", "colornamer",
"itertools", "itertools",
"rand", "rand",
"serde",
] ]
[[package]] [[package]]
@ -141,26 +140,6 @@ dependencies = [
"getrandom", "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]] [[package]]
name = "syn" name = "syn"
version = "2.0.100" version = "2.0.100"

View File

@ -7,4 +7,3 @@ edition = "2021"
colornamer = "1.0.1" colornamer = "1.0.1"
itertools = "0.14.0" itertools = "0.14.0"
rand = "0.9.1" rand = "0.9.1"
serde = {version = "1.0.219", features=["derive"]}

View File

@ -1,4 +1,4 @@
use rand::{rng, seq::IndexedRandom, Rng}; use rand::{rng, seq::IndexedRandom};
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub enum ChoiceFunction { pub enum ChoiceFunction {
@ -9,6 +9,9 @@ pub enum ChoiceFunction {
impl ChoiceFunction { impl ChoiceFunction {
pub fn choose<'a, T>(&self, xs: &'a [T]) -> &'a T { pub fn choose<'a, T>(&self, xs: &'a [T]) -> &'a T {
if xs.len() == 0 {
panic!("requires at least one item")
}
match self { match self {
ChoiceFunction::First => &xs[0], ChoiceFunction::First => &xs[0],
ChoiceFunction::Last => &xs[xs.len() - 1], ChoiceFunction::Last => &xs[xs.len() - 1],

View File

@ -27,12 +27,12 @@ impl Genetic for Coloration {
} }
} }
#[derive(Clone, Copy, Default)] #[derive(Clone, Default)]
pub struct Color { pub struct Color {
pub r: Stat<'.', '!', 0, 8>, pub r: Stat<'.', '!', 0, 8>,
pub g: Stat<'.', '!', 0, 8>, pub g: Stat<'.', '!', 0, 8>,
pub b: Stat<'.', '!', 0, 8>, pub b: Stat<'.', '!', 0, 8>,
pub pallor: Stat<'.', '!', 0, 8>, pub p: Stat<'.', '!', 0, 8>,
} }
impl Genetic for Color { impl Genetic for Color {
@ -40,21 +40,25 @@ impl Genetic for Color {
self.r.transcribe(t.nest("r")); self.r.transcribe(t.nest("r"));
self.g.transcribe(t.nest("g")); self.g.transcribe(t.nest("g"));
self.b.transcribe(t.nest("b")); self.b.transcribe(t.nest("b"));
self.pallor.transcribe(t.nest("pallor")); self.p.transcribe(t.nest("p"));
} }
} }
impl Color { 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 r = self.r.value() * 255 / 8;
let mut g = self.g.value() * 255 / 8; let mut g = self.g.value() * 255 / 8;
let mut b = self.b.value() * 255 / 8; let mut b = self.b.value() * 255 / 8;
let pallor_amt = self.pallor.value() * 255 / 8; let p = self.p.value() * 255 / 8;
r = (r * (255 - pallor_amt) + 255 * pallor_amt) / 255;
g = (g * (255 - pallor_amt) + 255 * pallor_amt) / 255; r = (r * (255 - p) + 0xd0 * p) / 255;
b = (b * (255 - pallor_amt) + 255 * pallor_amt) / 255; g = (g * (255 - p) + 0xd0 * p) / 255;
// let colornamer = ColorNamer::new(Colors::NTC); b = (b * (255 - p) + 0xd8 * p) / 255;
let colornamer = ColorNamer::new(Colors::X11); 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); let hex_color = format!("#{:02x}{:02x}{:02x}", r, g, b);
return format!( return format!(
"{} ({})", "{} ({})",

28
src/creature/gender.rs Normal file
View File

@ -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)
}
}

View File

@ -1,62 +1,47 @@
mod coloration; mod coloration;
mod gender;
mod phenotype;
mod species; mod species;
mod stats; mod stats;
use coloration::Coloration; use phenotype::Phenotype;
use species::Parts;
use stats::Stats;
use crate::genetics::{Genetic, Genotype, Transcriber}; use crate::{choice_function::ChoiceFunction, genetics::Genotype};
#[derive(Clone, Debug)] #[derive(Clone)]
pub struct Creature { pub struct Creature {
// pub name: String, genotype: Genotype,
pub genotype: Genotype,
} }
impl Creature { impl Creature {
pub fn profile(&self) -> String { pub fn generate(choice_function: ChoiceFunction) -> Self {
let phenotype: Phenotype = self.genotype.load(); Creature {
genotype: Genotype::generate::<Phenotype>(choice_function),
}
}
let mut profile = phenotype.profile(); // format!("Name: {}\n{}", self.name, phenotype.profile()); pub fn breed(&self, choice_function: ChoiceFunction, other: &Creature) -> Creature {
profile.push_str("\nGenome:\n"); return Creature {
for (chromosome, (lhs, rhs)) in self.genotype.chromosomes.iter() { genotype: self.genotype.breed(choice_function, &other.genotype),
profile.push_str(&format!(" {}[l]: {}\n", chromosome, lhs)); };
profile.push_str(&format!(" {}[r]: {}\n", chromosome, rhs)); }
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; 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"));
}
} }

32
src/creature/phenotype.rs Normal file
View File

@ -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"));
}
}

View File

@ -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)] #[derive(Clone, Copy, Debug)]
pub enum Species { pub enum Species {
Bunny, Bunny,
@ -62,3 +54,11 @@ pub enum Species {
Snake, Snake,
Bat, 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"));
}
}

View File

@ -51,7 +51,7 @@ impl<const OFF: char, const ON: char, const MIN: usize, const RANGE: usize> Gene
{ {
fn transcribe(&mut self, mut t: Transcriber) { fn transcribe(&mut self, mut t: Transcriber) {
for v in self.values.iter_mut() { for v in self.values.iter_mut() {
t.express(v, &[(OFF, false), (ON, true)]); t.express(v, &[(OFF, false), (ON, true)])
} }
} }
} }

59
src/genetics/mod.rs Normal file
View File

@ -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<String, Chromosomes>,
}
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<String> = HashSet::new();
chromosomes.extend(self.chromosomes.keys().cloned());
chromosomes.extend(other.chromosomes.keys().cloned());
let mut result: BTreeMap<String, Chromosomes> = 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,
};
}
}

View File

@ -1,74 +1,29 @@
use std::{ use std::{cell::RefCell, collections::BTreeMap};
cell::RefCell,
collections::{BTreeMap, HashSet},
rc::Rc,
};
use itertools::{EitherOrBoth, Itertools};
use rand::{seq::IndexedRandom, RngCore};
use serde::{Deserialize, Serialize};
use crate::choice_function::ChoiceFunction; use crate::choice_function::ChoiceFunction;
#[derive(Clone, Deserialize, Serialize, Debug)] use super::{Chromosomes, Genetic, Genotype};
pub struct Genotype {
pub chromosomes: BTreeMap<String, Chromosomes>,
}
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;
}
impl Genotype { impl Genotype {
pub fn breed(&self, other: &Genotype) -> Genotype {
let mut rng = rand::rng();
let mut chromosomes: HashSet<String> = HashSet::new();
chromosomes.extend(self.chromosomes.keys().cloned());
chromosomes.extend(other.chromosomes.keys().cloned());
let mut result: BTreeMap<String, Chromosomes> = 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<T: Genetic>(choice_function: ChoiceFunction) -> Self { pub fn generate<T: Genetic>(choice_function: ChoiceFunction) -> Self {
let root = RootTranscriber::Generator(Generator { let root = RootTranscriber::Generator(Generator {
choice_function, choice_function,
progress: BTreeMap::new(), progress: BTreeMap::new(),
}); });
let root_cell = Rc::new(RefCell::new(root)); let root_cell = RefCell::new(root);
let transcriber = Transcriber { let transcriber = Transcriber {
root: root_cell.clone(), root: &root_cell,
chromosome: String::from("base"), chromosome: String::from("base"),
}; };
let mut base = T::default(); let mut base = T::default();
base.transcribe(transcriber); base.transcribe(transcriber);
match &*root_cell.borrow() { match root_cell.into_inner() {
RootTranscriber::Generator(generator) => { RootTranscriber::Generator(generator) => {
let chromosomes = generator.progress.clone(); let chromosomes = generator.progress;
return Genotype { chromosomes }; 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<T: Genetic>(&self) -> T { pub fn load<T: Genetic>(&self) -> T {
@ -76,13 +31,14 @@ impl Genotype {
for (key, (lhs, rhs)) in self.chromosomes.iter() { for (key, (lhs, rhs)) in self.chromosomes.iter() {
progress.insert( progress.insert(
key.clone(), key.clone(),
// as a note: we could just store the character iterator!
(0, Vec::from_iter(lhs.chars()), Vec::from_iter(rhs.chars())), (0, Vec::from_iter(lhs.chars()), Vec::from_iter(rhs.chars())),
); );
} }
let root = RootTranscriber::Loader(Loader { progress }); let root = RootTranscriber::Loader(Loader { progress });
let root_cell = Rc::new(RefCell::new(root)); let root_cell = RefCell::new(root);
let transcriber = Transcriber { let transcriber = Transcriber {
root: root_cell.clone(), root: &root_cell,
chromosome: String::from("base"), chromosome: String::from("base"),
}; };
let mut base = T::default(); let mut base = T::default();
@ -104,21 +60,20 @@ impl RootTranscriber {
possibilities: &[(char, T)], possibilities: &[(char, T)],
) { ) {
match self { match self {
RootTranscriber::Generator(generator) => { RootTranscriber::Generator(generator) => generator.express(chromosome, possibilities),
generator.express(chromosome, field, possibilities) RootTranscriber::Loader(loader) => {
loader.express(chromosome, field, possibilities);
} }
RootTranscriber::Loader(loader) => loader.express(chromosome, field, possibilities),
} }
} }
} }
#[derive(Clone)] pub struct Transcriber<'r> {
pub struct Transcriber { root: &'r RefCell<RootTranscriber>,
root: Rc<RefCell<RootTranscriber>>,
chromosome: String, chromosome: String,
} }
impl Transcriber { impl Transcriber<'_> {
pub fn express<T: Clone>(&mut self, field: &mut T, possibilities: &[(char, T)]) { pub fn express<T: Clone>(&mut self, field: &mut T, possibilities: &[(char, T)]) {
self.root self.root
.borrow_mut() .borrow_mut()
@ -127,33 +82,28 @@ impl Transcriber {
pub fn nest(&self, suffix: &str) -> Transcriber { pub fn nest(&self, suffix: &str) -> Transcriber {
return Transcriber { return Transcriber {
root: self.root.clone(), root: self.root,
chromosome: format!("{}.{}", self.chromosome, suffix), chromosome: format!("{}.{}", self.chromosome, suffix),
}; };
} }
} }
pub trait Genetic: Default {
fn transcribe(&mut self, t: Transcriber);
}
struct Generator { struct Generator {
choice_function: ChoiceFunction, choice_function: ChoiceFunction,
progress: BTreeMap<String, Chromosomes>, progress: BTreeMap<String, Chromosomes>,
} }
impl Generator { impl Generator {
pub fn express<T>(&mut self, chromosome: &str, _field: &mut T, possibilities: &[(char, T)]) { pub fn express<T>(&mut self, chromosome: &str, possibilities: &[(char, T)]) {
let lhs = self.choice_function.choose(possibilities).0; let lhs = self.choice_function.choose(possibilities);
let rhs = self.choice_function.choose(possibilities).0; let rhs = self.choice_function.choose(possibilities);
let progress = self let progress = self
.progress .progress
.entry(chromosome.to_string()) .entry(chromosome.to_string()) // SLOW!
.or_insert((String::new(), String::new())); .or_insert((String::new(), String::new()));
progress.0.push(lhs); progress.0.push(lhs.0);
progress.1.push(rhs); progress.1.push(rhs.0);
} }
} }
@ -175,7 +125,7 @@ impl Loader {
*ix += 1; *ix += 1;
} }
let mut expressed = lhs; // lhs wins if not dominated let mut expressed = lhs;
if dominance(rhs) > dominance(lhs) { if dominance(rhs) > dominance(lhs) {
expressed = rhs; expressed = rhs;
} }

View File

@ -1,53 +1,48 @@
use choice_function::ChoiceFunction; use choice_function::ChoiceFunction;
use creature::{Creature, Phenotype}; use creature::Creature;
use genetics::Genotype;
use rand::{rng, seq::SliceRandom}; use rand::{rng, seq::SliceRandom};
mod choice_function; mod choice_function;
mod creature; mod creature;
mod genetics; mod genetics;
fn generate_creature() -> Creature {
let genotype: Genotype = Genotype::generate::<Phenotype>(ChoiceFunction::Random);
let creature = Creature {
// name: String::from("Pyrex"),
genotype,
};
return creature;
}
fn evolve() { fn evolve() {
let cf = ChoiceFunction::Random;
const N_ITERATIONS: usize = 200; const N_ITERATIONS: usize = 200;
const N_CREATURES: usize = 100; const N_CREATURES: usize = 100;
const N_SURVIVORS: usize = 90; const N_RANDOMS: usize = 50;
const N_SURVIVORS: usize = 40;
let mut pool = vec![]; let mut pool = vec![];
for _ in 0..N_CREATURES { for _ in 0..N_CREATURES {
pool.push(generate_creature()); pool.push(Creature::generate(cf));
} }
for iteration in 0..N_ITERATIONS { for iteration in 0..N_ITERATIONS {
report(iteration, &pool); report(iteration, &pool);
// kill N creatures let mut survivors = pool;
let mut survivors = pool.clone();
survivors.sort_by(|x, y| fitness(x).partial_cmp(&fitness(y)).unwrap()); survivors.sort_by(|x, y| fitness(x).partial_cmp(&fitness(y)).unwrap());
survivors.reverse(); survivors.reverse();
survivors.drain(N_SURVIVORS..N_CREATURES); 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![]; let mut survivors_queue = vec![];
while survivors_queue.len() < N_CREATURES * 2 { while survivors_queue.len() < N_CREATURES * 2 {
survivors_queue.extend(survivors.clone()); survivors_queue.extend(survivors.clone());
} }
survivors_queue.shuffle(&mut rng()); survivors_queue.shuffle(&mut rng());
let mut offspring = vec![];
for _ in 0..N_CREATURES { for _ in 0..N_CREATURES {
let lhs = survivors_queue.pop().unwrap(); let lhs = survivors_queue.pop().unwrap();
let rhs = 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 { fn fitness(creature: &Creature) -> f64 {
let phenotype: Phenotype = creature.genotype.load(); let phenotype = creature.phenotype();
let mut value = 0.0; 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.iq.value() as f64) / 10.0;
value += (phenotype.stats.eq.value() as f64) / 10.0;
return value; return value;
} }

BIN
thumbnail.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 KiB

BIN
thumbnail.xcf Normal file

Binary file not shown.

BIN
thumbnail2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

BIN
thumbnail2.xcf Normal file

Binary file not shown.

BIN
thumbnail3.xcf Normal file

Binary file not shown.

BIN
thumbnail4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 425 KiB

BIN
thumbnail4.xcf Normal file

Binary file not shown.