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