Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,27 @@ loading in future.
src/settings.rs

That's it. Now you can start to make your own custom genetic code.
The `giles` genotype (in genotypes/giles.rs) is a fully worked example of this
The `giles` genotype (in genotypes/giles/) is a fully worked example of this
that you can look at.

## Inspecting evolved creatures

Because a `giles` genome is just a block of byte-code, there is tooling to read
what evolution actually produced:

- A disassembler and assembler live in `eyes2_lib::giles::asm`
(`disassemble` / `assemble`), converting between raw genome bytes and a
readable listing. `GilesGenotype::disassemble()` dumps a creature's code and
`GilesGenotype::from_genome()` builds a creature from hand-written assembly.
- In the TUI, press `i` to open the inspector. It shows the selected creature's
registers (IP, accumulator, I/O registers, energy, breed/mutation rate) and a
disassembly of its genome with the current instruction highlighted. Use `n`/`p`
to cycle through creatures and `.` to single-step the world one tick at a time.

A genotype can opt into the inspector by implementing the `Genotype::inspect()`
method (returning its registers and a listing); genotypes that don't just show
no detail.

# Still to do

- DONE Save and Restore of worlds and individual creatures
Expand Down
10 changes: 7 additions & 3 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,13 @@ to create the framework for evolving creatures:
- NIRVANA: implement multi host architecture and deploy with kubernetes
- I think I won't do this. Distributing across processes for the trivial work a creature does
will not scale
- STRETCH provide a debug architecture
- needs to implement a GUI for representing the state of the creature
- e.g. assembler / disassembler and debugger
- DONE provide a debug architecture
- DONE a GUI for representing the state of the creature (the TUI inspector,
press `i` - shows the selected creature's registers and disassembled code,
`n`/`p` to cycle creatures, `.` to single-step)
- DONE assembler / disassembler (eyes2_lib::giles::asm)
- the disassembler doubles as the "debugger" code view; full breakpoint style
debugging is not implemented

Stage 2
-------
Expand Down
31 changes: 30 additions & 1 deletion eyes2-lib/src/entity/creature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

use crate::utils::move_pos;

use super::genotype::genotype::GenotypeActions;
use super::genotype::genotype::{GenotypeActions, GenotypeInspect};
use super::vision::{look_world, Vision};
use super::Genotype;
use super::Update;
Expand All @@ -35,6 +35,21 @@ use fastrand::Rng as FastRng;
use serde::Deserialize;
use serde::Serialize;

/// A snapshot of a creature (and its genotype) for the TUI inspector.
#[derive(Debug, Clone)]
pub struct CreatureInspect {
/// the creature's unique id
pub id: u64,
/// the sigil used to render it in the world
pub sigil: char,
/// its (x, y) position in the world
pub coord: (i32, i32),
/// its current energy
pub energy: i32,
/// inspectable internal state of its genotype, if any
pub genotype: Option<GenotypeInspect>,
}

#[derive(Serialize, Deserialize, Clone)]
pub struct Creature {
// the unique id of the creature used to identify it in the world
Expand Down Expand Up @@ -96,6 +111,9 @@ impl Creature {
}

pub fn set_config(&mut self, config: Settings) {
// keep the genotype's copy of the settings in step (it is skipped during
// serialization, so this restores it after a world is loaded)
self.genotype.set_config(config.clone());
self.config = config;
}

Expand Down Expand Up @@ -147,6 +165,17 @@ impl Creature {
self.sigil
}

/// Produce a snapshot of this creature for the TUI inspector.
pub fn inspect(&self) -> CreatureInspect {
CreatureInspect {
id: self.id,
sigil: self.sigil,
coord: (self.coord.x, self.coord.y),
energy: self.energy,
genotype: self.genotype.inspect(),
}
}

pub fn vision(&mut self, vision: Vision) {
self.genotype.vision(vision);
}
Expand Down
38 changes: 38 additions & 0 deletions eyes2-lib/src/entity/genotype/genotype.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,32 @@ pub enum BadGenomeError {
InvalidGenome,
}

/// One line of a genotype's program listing, for the inspector.
#[derive(Debug, Clone)]
pub struct InspectLine {
/// the address of the line within the genome
pub addr: usize,
/// the rendered instruction text
pub text: String,
}

/// A genotype-agnostic snapshot of a creature's "brain" for the TUI inspector.
///
/// Genotypes that have inspectable internal state (such as the `giles` byte-code
/// VM) return one of these from [`Genotype::inspect`]; the GUI renders it
/// without needing to know anything about the specific genotype.
#[derive(Debug, Clone)]
pub struct GenotypeInspect {
/// a short name for the kind of genotype, e.g. "giles"
pub kind: &'static str,
/// labelled register / state values, e.g. ("R", "0x2a")
pub state: Vec<(String, String)>,
/// the program listing (may be empty for genotypes without code)
pub listing: Vec<InspectLine>,
/// the index into `listing` of the instruction about to execute, if any
pub active: Option<usize>,
}

// Every creature has a Genotype which defines their behaviour. It is
// expected that the Genotype will be defined by a genome, and that the
// genome (with mutations as appropriate) will be passed to the
Expand All @@ -27,6 +53,12 @@ pub trait Genotype: DynClone + Send {
// the canonical energy level in in Creature itself)
fn set_energy(&mut self, energy: i32);

// update the global settings held by the genotype. This is called by the
// world after deserialization so that a loaded genotype uses the loaded
// world's settings rather than the defaults filled in by `#[serde(skip)]`.
// Genotypes that do not read the config can ignore this.
fn set_config(&mut self, _config: Settings) {}

// return the sigil used to represent this creature in the world
fn get_sigil(&self) -> char {
'D'
Expand All @@ -36,6 +68,12 @@ pub trait Genotype: DynClone + Send {
// the last Look(Direction) action. The value is a 1D array of 4
// Cells. With the nearest cell the first in the array.
fn vision(&mut self, _vision: Vision) {}

// Return a snapshot of internal state for the TUI inspector, or None for
// genotypes that have nothing interesting to show.
fn inspect(&self) -> Option<GenotypeInspect> {
None
}
}
clone_trait_object!(Genotype);

Expand Down
Loading