Simple genetic simulator, 1
This commit is contained in:
commit
b323eef0e2
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/target
|
217
Cargo.lock
generated
Normal file
217
Cargo.lock
generated
Normal file
@ -0,0 +1,217 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "colornamer"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbf1702f41ec8b3a350e44afdcbae669b761712bd9dd839299775f428b811957"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||
|
||||
[[package]]
|
||||
name = "genetics"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"colornamer",
|
||||
"itertools",
|
||||
"rand",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.172"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
||||
dependencies = [
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "5.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97"
|
||||
dependencies = [
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
|
||||
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"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.14.2+wasi-0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
|
||||
dependencies = [
|
||||
"wit-bindgen-rt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rt"
|
||||
version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
10
Cargo.toml
Normal file
10
Cargo.toml
Normal file
@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "genetics"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
colornamer = "1.0.1"
|
||||
itertools = "0.14.0"
|
||||
rand = "0.9.1"
|
||||
serde = {version = "1.0.219", features=["derive"]}
|
18
src/choice_function.rs
Normal file
18
src/choice_function.rs
Normal file
@ -0,0 +1,18 @@
|
||||
use rand::{rng, seq::IndexedRandom, Rng};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum ChoiceFunction {
|
||||
First,
|
||||
Last,
|
||||
Random,
|
||||
}
|
||||
|
||||
impl ChoiceFunction {
|
||||
pub fn choose<'a, T>(&self, xs: &'a [T]) -> &'a T {
|
||||
match self {
|
||||
ChoiceFunction::First => &xs[0],
|
||||
ChoiceFunction::Last => &xs[xs.len() - 1],
|
||||
ChoiceFunction::Random => xs.choose(&mut rng()).unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
65
src/creature/coloration.rs
Normal file
65
src/creature/coloration.rs
Normal file
@ -0,0 +1,65 @@
|
||||
use colornamer::{ColorNamer, Colors};
|
||||
|
||||
use crate::genetics::{Genetic, Transcriber};
|
||||
|
||||
use super::stats::Stat;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Coloration {
|
||||
pub eyes: Color,
|
||||
pub body: Color,
|
||||
}
|
||||
|
||||
impl Coloration {
|
||||
pub fn profile(&self) -> String {
|
||||
return format!(
|
||||
"Eyes: {}\nBody: {}\n",
|
||||
self.eyes.to_name(),
|
||||
self.body.to_name()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl Genetic for Coloration {
|
||||
fn transcribe(&mut self, t: Transcriber) {
|
||||
self.eyes.transcribe(t.nest("eyes"));
|
||||
self.body.transcribe(t.nest("body"));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct Color {
|
||||
pub r: Stat<'.', '!', 0, 8>,
|
||||
pub g: Stat<'.', '!', 0, 8>,
|
||||
pub b: Stat<'.', '!', 0, 8>,
|
||||
pub pallor: Stat<'.', '!', 0, 8>,
|
||||
}
|
||||
|
||||
impl Genetic for Color {
|
||||
fn transcribe(&mut self, t: Transcriber) {
|
||||
self.r.transcribe(t.nest("r"));
|
||||
self.g.transcribe(t.nest("g"));
|
||||
self.b.transcribe(t.nest("b"));
|
||||
self.pallor.transcribe(t.nest("pallor"));
|
||||
}
|
||||
}
|
||||
|
||||
impl Color {
|
||||
fn to_name(&self) -> String {
|
||||
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 hex_color = format!("#{:02x}{:02x}{:02x}", r, g, b);
|
||||
return format!(
|
||||
"{} ({})",
|
||||
colornamer.name_hex_color(&hex_color).unwrap(),
|
||||
hex_color
|
||||
);
|
||||
}
|
||||
}
|
62
src/creature/mod.rs
Normal file
62
src/creature/mod.rs
Normal file
@ -0,0 +1,62 @@
|
||||
mod coloration;
|
||||
mod species;
|
||||
mod stats;
|
||||
|
||||
use coloration::Coloration;
|
||||
use species::Parts;
|
||||
use stats::Stats;
|
||||
|
||||
use crate::genetics::{Genetic, Genotype, Transcriber};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Creature {
|
||||
// pub name: String,
|
||||
pub genotype: Genotype,
|
||||
}
|
||||
|
||||
impl Creature {
|
||||
pub fn profile(&self) -> String {
|
||||
let phenotype: Phenotype = self.genotype.load();
|
||||
|
||||
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));
|
||||
}
|
||||
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"));
|
||||
}
|
||||
}
|
64
src/creature/species.rs
Normal file
64
src/creature/species.rs
Normal file
@ -0,0 +1,64 @@
|
||||
use crate::genetics::{Genetic, Transcriber};
|
||||
|
||||
use super::stats::Stat;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Parts {
|
||||
stare: Stat<'e', 'E', 0, 6>,
|
||||
fangs: Stat<'f', 'F', 0, 6>,
|
||||
wings: Stat<'w', 'W', 0, 6>,
|
||||
}
|
||||
|
||||
impl Parts {
|
||||
pub fn profile(&self) -> String {
|
||||
format!("Species: {:?}\n", self.species())
|
||||
}
|
||||
|
||||
pub fn has_stare(&self) -> bool {
|
||||
return self.stare.value() >= 5;
|
||||
}
|
||||
|
||||
pub fn has_fangs(&self) -> bool {
|
||||
return self.fangs.value() >= 5;
|
||||
}
|
||||
|
||||
pub fn has_wings(&self) -> bool {
|
||||
return self.wings.value() >= 5;
|
||||
}
|
||||
|
||||
pub fn species(&self) -> Species {
|
||||
let mut ix = 0;
|
||||
if self.has_stare() {
|
||||
ix += 4;
|
||||
}
|
||||
if self.has_fangs() {
|
||||
ix += 2;
|
||||
}
|
||||
if self.has_wings() {
|
||||
ix += 1;
|
||||
}
|
||||
|
||||
use Species::*;
|
||||
return [Bunny, Pigeon, Rat, Dragon, Hare, Crow, Snake, Bat][ix];
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
Pigeon,
|
||||
Rat,
|
||||
Dragon,
|
||||
Hare,
|
||||
Crow,
|
||||
Snake,
|
||||
Bat,
|
||||
}
|
57
src/creature/stats.rs
Normal file
57
src/creature/stats.rs
Normal file
@ -0,0 +1,57 @@
|
||||
use crate::genetics::{Genetic, Transcriber};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Stats {
|
||||
pub iq: Stat<'.', '!', 5, 10>, // 5-15
|
||||
pub eq: Stat<'.', '!', 5, 10>, // 5-15
|
||||
}
|
||||
|
||||
impl Stats {
|
||||
pub fn profile(&self) -> String {
|
||||
format!("IQ: {}\nEQ: {}\n", self.iq.value(), self.eq.value())
|
||||
}
|
||||
}
|
||||
|
||||
impl Genetic for Stats {
|
||||
fn transcribe(&mut self, t: Transcriber) {
|
||||
self.iq.transcribe(t.nest("IQ"));
|
||||
self.eq.transcribe(t.nest("EQ"));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Stat<const OFF: char, const ON: char, const MIN: usize, const RANGE: usize> {
|
||||
values: [bool; RANGE],
|
||||
}
|
||||
|
||||
impl<const OFF: char, const ON: char, const MIN: usize, const RANGE: usize>
|
||||
Stat<OFF, ON, MIN, RANGE>
|
||||
{
|
||||
pub fn value(&self) -> usize {
|
||||
let mut value = MIN;
|
||||
for v in self.values.iter() {
|
||||
value += *v as usize;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
impl<const OFF: char, const ON: char, const MIN: usize, const RANGE: usize> Default
|
||||
for Stat<OFF, ON, MIN, RANGE>
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
values: [false; RANGE],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<const OFF: char, const ON: char, const MIN: usize, const RANGE: usize> Genetic
|
||||
for Stat<OFF, ON, MIN, RANGE>
|
||||
{
|
||||
fn transcribe(&mut self, mut t: Transcriber) {
|
||||
for v in self.values.iter_mut() {
|
||||
t.express(v, &[(OFF, false), (ON, true)]);
|
||||
}
|
||||
}
|
||||
}
|
199
src/genetics.rs
Normal file
199
src/genetics.rs
Normal file
@ -0,0 +1,199 @@
|
||||
use std::{
|
||||
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;
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize, Debug)]
|
||||
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 {
|
||||
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 {
|
||||
let root = RootTranscriber::Generator(Generator {
|
||||
choice_function,
|
||||
progress: BTreeMap::new(),
|
||||
});
|
||||
let root_cell = Rc::new(RefCell::new(root));
|
||||
let transcriber = Transcriber {
|
||||
root: root_cell.clone(),
|
||||
chromosome: String::from("base"),
|
||||
};
|
||||
let mut base = T::default();
|
||||
base.transcribe(transcriber);
|
||||
match &*root_cell.borrow() {
|
||||
RootTranscriber::Generator(generator) => {
|
||||
let chromosomes = generator.progress.clone();
|
||||
return Genotype { chromosomes };
|
||||
}
|
||||
_ => panic!("a generator should not able to turn into a loader"),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn load<T: Genetic>(&self) -> T {
|
||||
let mut progress = BTreeMap::new();
|
||||
for (key, (lhs, rhs)) in self.chromosomes.iter() {
|
||||
progress.insert(
|
||||
key.clone(),
|
||||
(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 transcriber = Transcriber {
|
||||
root: root_cell.clone(),
|
||||
chromosome: String::from("base"),
|
||||
};
|
||||
let mut base = T::default();
|
||||
base.transcribe(transcriber);
|
||||
return base;
|
||||
}
|
||||
}
|
||||
|
||||
enum RootTranscriber {
|
||||
Generator(Generator),
|
||||
Loader(Loader),
|
||||
}
|
||||
|
||||
impl RootTranscriber {
|
||||
pub fn express<T: Clone>(
|
||||
&mut self,
|
||||
chromosome: &str,
|
||||
field: &mut T,
|
||||
possibilities: &[(char, T)],
|
||||
) {
|
||||
match self {
|
||||
RootTranscriber::Generator(generator) => {
|
||||
generator.express(chromosome, field, possibilities)
|
||||
}
|
||||
RootTranscriber::Loader(loader) => loader.express(chromosome, field, possibilities),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Transcriber {
|
||||
root: Rc<RefCell<RootTranscriber>>,
|
||||
chromosome: String,
|
||||
}
|
||||
|
||||
impl Transcriber {
|
||||
pub fn express<T: Clone>(&mut self, field: &mut T, possibilities: &[(char, T)]) {
|
||||
self.root
|
||||
.borrow_mut()
|
||||
.express(&self.chromosome, field, possibilities)
|
||||
}
|
||||
|
||||
pub fn nest(&self, suffix: &str) -> Transcriber {
|
||||
return Transcriber {
|
||||
root: self.root.clone(),
|
||||
chromosome: format!("{}.{}", self.chromosome, suffix),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Genetic: Default {
|
||||
fn transcribe(&mut self, t: Transcriber);
|
||||
}
|
||||
|
||||
struct Generator {
|
||||
choice_function: ChoiceFunction,
|
||||
progress: BTreeMap<String, Chromosomes>,
|
||||
}
|
||||
|
||||
impl Generator {
|
||||
pub fn express<T>(&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;
|
||||
|
||||
let progress = self
|
||||
.progress
|
||||
.entry(chromosome.to_string())
|
||||
.or_insert((String::new(), String::new()));
|
||||
|
||||
progress.0.push(lhs);
|
||||
progress.1.push(rhs);
|
||||
}
|
||||
}
|
||||
|
||||
struct Loader {
|
||||
progress: BTreeMap<String, (usize, Vec<char>, Vec<char>)>,
|
||||
}
|
||||
|
||||
impl Loader {
|
||||
pub fn express<T: Clone>(
|
||||
&mut self,
|
||||
chromosome: &str,
|
||||
field: &mut T,
|
||||
possibilities: &[(char, T)],
|
||||
) {
|
||||
let (mut lhs, mut rhs) = ('?', '?');
|
||||
if let Some((ix, lhs_buf, rhs_buf)) = self.progress.get_mut(chromosome) {
|
||||
lhs = lhs_buf.get(*ix).cloned().unwrap_or('?');
|
||||
rhs = rhs_buf.get(*ix).cloned().unwrap_or('?');
|
||||
*ix += 1;
|
||||
}
|
||||
|
||||
let mut expressed = lhs; // lhs wins if not dominated
|
||||
if dominance(rhs) > dominance(lhs) {
|
||||
expressed = rhs;
|
||||
}
|
||||
for (allele, value) in possibilities.iter() {
|
||||
if *allele == expressed {
|
||||
*field = value.clone();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn dominance(allele: char) -> u8 {
|
||||
if allele.is_ascii_lowercase() {
|
||||
return 1;
|
||||
}
|
||||
if allele.is_ascii_uppercase() {
|
||||
return 2;
|
||||
}
|
||||
return 0;
|
||||
}
|
74
src/main.rs
Normal file
74
src/main.rs
Normal file
@ -0,0 +1,74 @@
|
||||
use choice_function::ChoiceFunction;
|
||||
use creature::{Creature, Phenotype};
|
||||
use genetics::Genotype;
|
||||
use rand::{rng, seq::SliceRandom};
|
||||
|
||||
mod choice_function;
|
||||
mod creature;
|
||||
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() {
|
||||
const N_ITERATIONS: usize = 200;
|
||||
const N_CREATURES: usize = 100;
|
||||
const N_SURVIVORS: usize = 90;
|
||||
|
||||
let mut pool = vec![];
|
||||
for _ in 0..N_CREATURES {
|
||||
pool.push(generate_creature());
|
||||
}
|
||||
|
||||
for iteration in 0..N_ITERATIONS {
|
||||
report(iteration, &pool);
|
||||
|
||||
// kill N creatures
|
||||
let mut survivors = pool.clone();
|
||||
survivors.sort_by(|x, y| fitness(x).partial_cmp(&fitness(y)).unwrap());
|
||||
survivors.reverse();
|
||||
survivors.drain(N_SURVIVORS..N_CREATURES);
|
||||
|
||||
let mut pool2 = vec![];
|
||||
let mut survivors_queue = vec![];
|
||||
while survivors_queue.len() < N_CREATURES * 2 {
|
||||
survivors_queue.extend(survivors.clone());
|
||||
}
|
||||
survivors_queue.shuffle(&mut rng());
|
||||
|
||||
for _ in 0..N_CREATURES {
|
||||
let lhs = survivors_queue.pop().unwrap();
|
||||
let rhs = survivors_queue.pop().unwrap();
|
||||
pool2.push(lhs.breed(&rhs));
|
||||
}
|
||||
pool = pool2;
|
||||
}
|
||||
}
|
||||
|
||||
fn report(iteration: usize, pool: &[Creature]) {
|
||||
println!("== Iteration {} ==", iteration + 1);
|
||||
println!("{}", pool[0].profile());
|
||||
println!("");
|
||||
}
|
||||
|
||||
fn fitness(creature: &Creature) -> f64 {
|
||||
let phenotype: Phenotype = creature.genotype.load();
|
||||
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;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
fn main() {
|
||||
evolve();
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user