├── EvoFighters ├── __init__.py ├── banner_51.ascii ├── banner_90.ascii ├── banner_79.ascii ├── grammar.bnf ├── scripts │ └── make_tree.py ├── SaveData.py ├── Eval.py ├── Parsing.py ├── .pylintrc ├── Utils.py ├── Creatures.py └── Arena.py ├── evofighters_rust ├── .projectile ├── .rustfmt.toml ├── Cargo.toml ├── src │ ├── util.rs │ ├── main.rs │ ├── stats.rs │ ├── rng.rs │ ├── sim.rs │ ├── saver.rs │ ├── cli.rs │ ├── eval.rs │ ├── parsing.rs │ ├── simplify.rs │ ├── dna.rs │ ├── creatures.rs │ └── arena.rs └── Cargo.lock ├── .gitignore ├── tests └── basic_test.py ├── setup.py └── README.md /EvoFighters/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /evofighters_rust/.projectile: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /evofighters_rust/.rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 80 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *\.pyc 2 | todo.org 3 | grammar.txt 4 | .#* 5 | *.egg-info/* 6 | *.save 7 | *.emacs.desktop* 8 | *.coverage 9 | *htmlcov* 10 | target/ 11 | *.xz 12 | -------------------------------------------------------------------------------- /EvoFighters/banner_51.ascii: -------------------------------------------------------------------------------- 1 | __ ___ 2 | / / / / / 3 | (___ ___ (___ ___ (___ (___ ___ ___ ___ 4 | | \ )| )| | | )| )| |___)| )|___ 5 | |__ \/ |__/ | | |__/ | / |__ |__ | __/ 6 | __/ 7 | -------------------------------------------------------------------------------- /EvoFighters/banner_90.ascii: -------------------------------------------------------------------------------- 1 | '||''''| '||' '|' ..|''|| '||''''| || '|| . 2 | || . '|. .' .|' || || . ... ... . || .. .||. .... ... .. .... 3 | ||''| || | || || ||''| || || || ||' || || .|...|| ||' '' ||. ' 4 | || ||| '|. || || || |'' || || || || || . '|.. 5 | .||.....| | ''|...|' .||. .||. '||||. .||. ||. '|.' '|...' .||. |'..|' 6 | .|....' 7 | -------------------------------------------------------------------------------- /EvoFighters/banner_79.ascii: -------------------------------------------------------------------------------- 1 | ________ ________ _ __ _ 2 | |_ __ | |_ __ |(_) [ | / |_ 3 | | |_ \_|_ __ .--. | |_ \_|__ .--./)| |--. `| |-'.---. _ .--. .--. 4 | | _| _[ \ [ ]/ .'`\ \ | _| [ | / /'`\;| .-. | | | / /__\\[ `/'`\]( (`\] 5 | _| |__/ |\ \/ / | \__. |_| |_ | | \ \._//| | | | | |,| \__., | | `'.'. 6 | |________| \__/ '.__.'|_____| [___].',__`[___]|__]\__/ '.__.'[___] [\__) ) 7 | ( ( __)) 8 | -------------------------------------------------------------------------------- /tests/basic_test.py: -------------------------------------------------------------------------------- 1 | import EvoFighters.Arena as A 2 | import cPickle as pickle 3 | 4 | def test_fight(): 5 | A.set_verbosity(0) 6 | a = A.Creature() 7 | b = A.Creature() 8 | A.fight(a, b) 9 | print repr(a) 10 | print repr(b) 11 | 12 | def test_pickle(): 13 | a = A.Creature() 14 | ap = pickle.dumps(a,2) 15 | b = pickle.loads(ap) 16 | print a 17 | print b 18 | 19 | def test_repr(): 20 | a = A.Creature() 21 | print str(a) 22 | print repr(a) 23 | 24 | if __name__ == '__main__': 25 | test_fight() 26 | #test_pickle() 27 | #test_repr() 28 | 29 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup, find_packages 4 | 5 | setup(name='EvoFighters', 6 | version='0.5', 7 | description='RPG fighters who evolve to get better', 8 | author='Josh Kuhn', 9 | license='GPLv3', 10 | author_email='deontologician@gmail.com', 11 | packages=find_packages(), 12 | install_requires=[ 13 | 'blessings==1.5.1', 14 | ], 15 | entry_points={ 16 | 'console_scripts': [ 17 | 'EvoFighters = EvoFighters.Arena:main', 18 | ], 19 | }, 20 | package_data = { 21 | '': ['*.ascii'], 22 | } 23 | ) 24 | -------------------------------------------------------------------------------- /evofighters_rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | 3 | name = "evofighters_rust" 4 | version = "0.0.1" 5 | authors = [ "Josh Kuhn " ] 6 | 7 | [dependencies] 8 | clap = "2.30.0" 9 | derive_builder = "0.5.1" 10 | enum_primitive = "0.1.1" 11 | lazy_static = "1.0" 12 | num = "0.1.41" 13 | num_cpus = "1.0.0" 14 | rand = "0.4" 15 | serde = "1.0" 16 | serde_derive = "1.0" 17 | serde_json = "1.0" 18 | time = "0.1.32" 19 | twox-hash = "1.1.0" 20 | xz2 = "0.1.4" 21 | 22 | [profile.release] 23 | debug = true 24 | opt-level = 3 25 | rpath = false 26 | lto = true 27 | 28 | [features] 29 | log_info = [] 30 | log_debug = [] 31 | log_trace = [] 32 | 33 | [[bin]] 34 | name = "evofighters" 35 | path = "src/main.rs" 36 | -------------------------------------------------------------------------------- /evofighters_rust/src/util.rs: -------------------------------------------------------------------------------- 1 | // TODO: add the stars to different debug statements 2 | #[macro_export] 3 | macro_rules! trace { 4 | ($($arg:tt)*) => ( 5 | if cfg!(feature = "log_trace") { 6 | println!($($arg)*); 7 | }) 8 | } 9 | 10 | #[macro_export] 11 | macro_rules! debug { 12 | ($($arg:tt)*) => ( 13 | if cfg!(feature = "log_trace") || 14 | cfg!(feature = "log_debug") { 15 | println!($($arg)*); 16 | }) 17 | } 18 | 19 | #[macro_export] 20 | macro_rules! info { 21 | ($($arg:tt)*) => ( 22 | if cfg!(feature = "log_info") || 23 | cfg!(feature = "log_debug") || 24 | cfg!(feature = "log_trace") { 25 | println!($($arg)*); 26 | }) 27 | } 28 | -------------------------------------------------------------------------------- /evofighters_rust/src/main.rs: -------------------------------------------------------------------------------- 1 | #![feature(box_patterns)] 2 | #![feature(nll)] 3 | 4 | extern crate clap; 5 | #[macro_use] 6 | extern crate derive_builder; 7 | #[macro_use] 8 | extern crate enum_primitive; 9 | extern crate num; 10 | extern crate num_cpus; 11 | extern crate rand; 12 | extern crate serde; 13 | #[macro_use] 14 | extern crate serde_derive; 15 | extern crate serde_json; 16 | extern crate time; 17 | extern crate twox_hash; 18 | extern crate xz2; 19 | 20 | #[macro_use] 21 | mod util; 22 | 23 | mod arena; 24 | mod cli; 25 | mod creatures; 26 | mod dna; 27 | mod eval; 28 | mod parsing; 29 | mod rng; 30 | mod saver; 31 | mod stats; 32 | mod sim; 33 | mod simplify; 34 | 35 | fn main() { 36 | let app = cli::parse_args(); 37 | cli::execute_command(&app); 38 | } 39 | -------------------------------------------------------------------------------- /evofighters_rust/src/stats.rs: -------------------------------------------------------------------------------- 1 | #[derive(Copy, Clone, Serialize, Deserialize, Debug, Default)] 2 | pub struct GlobalStatistics { 3 | pub mutations: usize, 4 | pub children_born: usize, 5 | pub feeders_eaten: usize, 6 | pub kills: usize, 7 | pub rounds: usize, 8 | } 9 | 10 | impl GlobalStatistics { 11 | pub fn new() -> GlobalStatistics { 12 | GlobalStatistics::default() 13 | } 14 | 15 | pub fn absorb(&mut self, other: GlobalStatistics) { 16 | self.mutations += other.mutations; 17 | self.children_born += other.children_born; 18 | self.feeders_eaten += other.feeders_eaten; 19 | self.kills += other.kills; 20 | self.rounds += other.rounds; 21 | } 22 | } 23 | 24 | #[derive(Copy, Clone, Debug, Serialize, Deserialize, Default)] 25 | pub struct CreatureStats { 26 | pub kills: usize, 27 | pub num_children: usize, 28 | pub survived: usize, 29 | pub eaten: usize, 30 | } 31 | -------------------------------------------------------------------------------- /EvoFighters/grammar.bnf: -------------------------------------------------------------------------------- 1 | This is not a code file, just documentation of the grammar 2 | My best guess is that this is an LL0 grammar 3 | 4 | CONDITIONAL :: always ACT 5 | | in_range VAL VAL VAL ACT ACT 6 | | less_than VAL VAL ACT ACT 7 | | greater_than VAL VAL ACT ACT 8 | | equal_to VAL VAL ACT ACT 9 | | not_equal_to VAL VAL ACT ACT 10 | | me_last_act ACT ACT ACT #Note: 1st ACT cannot be "subcondition" 11 | | target_last_act ACT ACT ACT #Note: 1st ACT cannot be "subcondition" 12 | 13 | VAL :: literal INT 14 | | random 15 | | me ATTR 16 | | target ATTR 17 | 18 | ACT :: subcondition CONDITIONAL 19 | | attack DMG 20 | | defend DMG 21 | | signal SIG 22 | | use 23 | | take 24 | | wait 25 | | flee 26 | | mate 27 | 28 | # ATTR is all terminal 29 | ATTR :: energy 30 | | signal 31 | | generation 32 | | kills 33 | | survived 34 | | num_children 35 | | top_item 36 | 37 | # ITEM is all terminal 38 | ITEM :: food 39 | | good_food 40 | | better_food 41 | | excellent_food 42 | 43 | # SIG is all terminal 44 | SIG :: red 45 | | yellow 46 | | blue 47 | | purple 48 | | orange 49 | | green 50 | 51 | # DMG is all terminal 52 | DMG :: fire 53 | | ice 54 | | electricity 55 | -------------------------------------------------------------------------------- /evofighters_rust/src/rng.rs: -------------------------------------------------------------------------------- 1 | use rand::{Rand, Rng, SeedableRng, XorShiftRng}; 2 | use rand::distributions; 3 | use rand::distributions::range::SampleRange; 4 | use creatures::{Creature, CreatureID}; 5 | 6 | #[derive(Debug, Clone)] 7 | pub struct RngState { 8 | rng: XorShiftRng, 9 | } 10 | 11 | impl Default for RngState { 12 | fn default() -> RngState { 13 | RngState::new(11, 17, 23, 51) 14 | } 15 | } 16 | 17 | impl RngState { 18 | pub fn new(a: u32, b: u32, c: u32, d: u32) -> RngState { 19 | RngState { 20 | rng: SeedableRng::from_seed([a, b, c, d]), 21 | } 22 | } 23 | 24 | pub fn from_creatures(a: &Creature, b: &Creature) -> RngState { 25 | let a_p = CreatureID::parents_to_u32(a.parents); 26 | let b_p = CreatureID::parents_to_u32(b.parents); 27 | RngState::new(a_p, b_p, a.hash(), b.hash()) 28 | } 29 | 30 | pub fn spawn(&mut self) -> RngState { 31 | RngState::new(self.rand(), self.rand(), self.rand(), self.rand()) 32 | } 33 | 34 | pub fn rand(&mut self) -> T { 35 | self.rng.gen() 36 | } 37 | 38 | pub fn rand_range( 39 | &mut self, 40 | low: T, 41 | high: T, 42 | ) -> T { 43 | if low == high { 44 | low 45 | } else { 46 | self.rng.gen_range(low, high) 47 | } 48 | } 49 | 50 | pub fn normal_sample(&mut self, mean: f64, std_dev: f64) -> f64 { 51 | use rand::distributions::IndependentSample; 52 | let normal = distributions::Normal::new(mean, std_dev); 53 | normal.ind_sample(&mut self.rng) 54 | } 55 | 56 | pub fn rand_weighted_bool(&mut self, n: u32) -> bool { 57 | self.rng.gen_weighted_bool(n) 58 | } 59 | 60 | pub fn shuffle(&mut self, values: &mut [T]) { 61 | self.rng.shuffle(values) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /EvoFighters/scripts/make_tree.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | '''Creates a family tree from the currently living generation''' 3 | 4 | from SaveData import SaveData 5 | import pygraphviz as pgv 6 | import sys 7 | from collections import Counter 8 | import argparse 9 | 10 | def join(creatures, dead): 11 | for c in creatures: 12 | dead[c.name] = c 13 | return dead 14 | 15 | def segment(alive, dead): 16 | candidates = {alive[0].name : alive[0]}#{ c.name : c for c in alive } 17 | combined = join(alive, dead) 18 | vetted = {} 19 | layer = 0 20 | while candidates: 21 | print >>sys.stderr, 'Got {} candidates to work through'.format(len(candidates)) 22 | counter = Counter(c.fullname for c in candidates.values()) 23 | print 'Gene summary for layer {} ({} creatures):'.format(layer, len(candidates)) 24 | for val, count in counter.most_common(): 25 | print count, 'x', val 26 | print '============' 27 | old_candidates = candidates.copy() 28 | candidates = {} 29 | for c in old_candidates.values(): 30 | if c.parents: 31 | p1, p2 = c.parents 32 | if p1 not in vetted and p1 not in old_candidates and p1 in combined: 33 | candidates[p1] = combined[p1] 34 | if p2 not in vetted and p2 not in old_candidates and p2 in combined: 35 | candidates[p2] = combined[p2] 36 | vetted[c.name] = c 37 | layer += 1 38 | return vetted 39 | 40 | def create_graph(vetted): 41 | print 'Creating graph...' 42 | G = pgv.AGraph(directed = True) 43 | total = len(vetted) 44 | last_percent = 0 45 | for i, c in enumerate(vetted.values()): 46 | percent = int((float(i) / total) * 100) 47 | if percent > last_percent: 48 | last_percent = percent 49 | print '\r{:3}%'.format(percent), 50 | sys.stdout.flush() 51 | if c.parents is not None: 52 | p1, p2 = c.parents 53 | G.add_edge(p1, c.name) 54 | G.add_edge(p2, c.name) 55 | else: 56 | G.add_edge('First Generation', c.name) 57 | return G 58 | 59 | 60 | def layout_and_draw(G): 61 | print 'Laying out...' 62 | G.layout(prog = 'dot') 63 | print 'Drawing...' 64 | G.draw('family_tree.svg') 65 | 66 | def parse_args(argv): 67 | parser = argparse.ArgumentParser(description = 'A script to do various analysis') 68 | 69 | def main(argv): 70 | #args = parse_args(argv) 71 | if len(argv) > 1: 72 | filename = argv[1] 73 | else: 74 | filename = 'evofighters.save' 75 | print 'Opening {}...'.format(filename) 76 | with open(filename, 'r') as fd: 77 | sd = SaveData.loadfrom(fd) 78 | print 'Segmenting based on survivors...' 79 | vetted = segment(sd.creatures, sd.dead) 80 | sd = None 81 | 82 | G = create_graph(vetted) 83 | vetted = None 84 | 85 | layout_and_draw(G) 86 | 87 | if __name__ == '__main__': 88 | main(sys.argv) 89 | 90 | -------------------------------------------------------------------------------- /evofighters_rust/src/sim.rs: -------------------------------------------------------------------------------- 1 | use std::thread; 2 | use std::sync::mpsc::channel; 3 | use num_cpus; 4 | 5 | use arena::Arena; 6 | use saver::{Saver, Settings}; 7 | use creatures::Creatures; 8 | use saver::OwnedCheckpoint; 9 | use stats::GlobalStatistics; 10 | 11 | /// Simulation is the coordinating object that manages all of the 12 | /// different threads used to run the sim. It decides how many workers 13 | /// to create, and handles exit signals etc. 14 | /// 15 | /// There are X `Worker` threads, where X is the number of physical cores: 16 | /// * `inbox` - a `Receiver` to receive new work to do 17 | /// * `outbox` - a `Sender` channel to send completed work through 18 | /// * `metrics` - a `Sender` channel to send metrics through 19 | /// * `should_exit` - a `Receiver` to get graceful shutdown messages 20 | /// 21 | /// There is one `Saver` thread 22 | /// * `checkpoints` - a `Receiver` of checkpoints to save to disk 23 | /// * `metrics` - a `Receiver` of metrics to save to disk 24 | /// * `should_exit` - a `Receiver` to get graceful shutdown messages 25 | /// 26 | /// On the main thread: 27 | /// * `worker_out` - a `Vec` with channels to send work to `Worker`s 28 | /// * `worker_in` - a `Vec` with channels to receive completed work 29 | /// * `metrics` - a `Sender` channel to send metrics through 30 | /// * `should_exit` - a `Sender` to send graceful shutdown messages 31 | pub struct Simulation { 32 | filename: String, 33 | arena: Arena, 34 | settings: Settings, 35 | } 36 | 37 | impl Simulation { 38 | pub fn new(filename: &str, settings: Settings) -> Simulation { 39 | let arena = match Saver::load(filename) { 40 | Ok(checkpoint) => { 41 | println!("Loading from file {}", filename); 42 | Arena::from_checkpoint(checkpoint, filename) 43 | } 44 | Err(_) => { 45 | println!("Creating initial population"); 46 | let population: Creatures = 47 | Creatures::new(settings.max_population_size); 48 | println!("Created {} creatures", settings.max_population_size); 49 | Arena::new(population, filename, settings) 50 | } 51 | }; 52 | 53 | Simulation { 54 | filename: filename.to_owned(), 55 | settings, 56 | arena, 57 | } 58 | } 59 | 60 | pub fn load_or_create(&mut self) -> OwnedCheckpoint { 61 | println!("Attempting to load checkpoint from {}...", self.filename); 62 | match Saver::load(&self.filename) { 63 | Ok(checkpoint) => { 64 | println!( 65 | "Success. {} creatures loaded.", 66 | checkpoint.creatures.len() 67 | ); 68 | checkpoint 69 | } 70 | Err(_) => { 71 | let creatures = 72 | Creatures::new(self.settings.max_population_size); 73 | println!( 74 | "Created {} creatures.", 75 | self.settings.max_population_size 76 | ); 77 | OwnedCheckpoint { 78 | creatures, 79 | settings: self.settings, 80 | stats: GlobalStatistics::default(), 81 | } 82 | } 83 | } 84 | } 85 | 86 | pub fn simulate(&mut self) { 87 | self.arena.simulate() 88 | } 89 | 90 | pub fn full_simulate(&mut self) { 91 | let physical_cores = num_cpus::get_physical(); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /evofighters_rust/src/saver.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::Error; 3 | 4 | use serde_json; 5 | 6 | use xz2::write::XzEncoder; 7 | use xz2::read::XzDecoder; 8 | 9 | use creatures::{Creatures, DeserializableCreatures}; 10 | use stats::GlobalStatistics; 11 | 12 | #[derive(Debug, Deserialize, Serialize, Copy, Clone, Builder)] 13 | pub struct Settings { 14 | #[builder(default = "0.10")] 15 | pub mutation_rate: f64, 16 | 17 | #[builder(default = "120_000")] 18 | pub max_population_size: usize, 19 | 20 | #[builder(default = "30.0")] 21 | pub metric_fps: f64, 22 | } 23 | 24 | impl Default for Settings { 25 | fn default() -> Self { 26 | SettingsBuilder::default().build().unwrap() 27 | } 28 | } 29 | 30 | #[derive(Debug)] 31 | pub struct Saver { 32 | filename: String, 33 | settings: Settings, 34 | } 35 | 36 | impl Saver { 37 | pub const COMPRESSION_LEVEL: u32 = 9; 38 | 39 | pub fn new(filename: &str) -> Saver { 40 | Saver { 41 | filename: filename.to_owned(), 42 | settings: Settings::default(), 43 | } 44 | } 45 | 46 | /// Save the current file to disk 47 | pub fn save( 48 | &mut self, 49 | creatures: &Creatures, 50 | stats: &GlobalStatistics, 51 | ) -> Result<(), Error> { 52 | let contents = Checkpoint { 53 | creatures, 54 | stats: stats.to_owned(), 55 | settings: self.settings.to_owned(), 56 | }; 57 | // Create a writer 58 | let compressor = XzEncoder::new( 59 | File::create(&self.filename)?, 60 | Saver::COMPRESSION_LEVEL, 61 | ); 62 | serde_json::to_writer(compressor, &contents)?; 63 | Ok(()) 64 | } 65 | 66 | /// Load a savefile from disk 67 | pub fn load(filename: &str) -> Result { 68 | // Create reader 69 | let decompressor = XzDecoder::new(File::open(filename)?); 70 | let d_checkpoint: DeserializableCheckpoint = 71 | serde_json::from_reader(decompressor)?; 72 | Ok(d_checkpoint.into_owned_checkpoint()) 73 | } 74 | } 75 | 76 | /// This checkpoint can be written to disk without needing to take 77 | /// ownership or clone the entire creatures array. It's private 78 | /// because nobody but the Saver should create one of these 79 | /// structures. 80 | #[derive(Serialize)] 81 | struct Checkpoint<'a> { 82 | creatures: &'a Creatures, 83 | stats: GlobalStatistics, 84 | settings: Settings, 85 | } 86 | 87 | /// This checkpoint owns its creatures array. It's public because when 88 | /// you load a file this is what's returned. 89 | pub struct OwnedCheckpoint { 90 | pub creatures: Creatures, 91 | pub stats: GlobalStatistics, 92 | pub settings: Settings, 93 | } 94 | 95 | /// This checkpoint is what's deserialized from disk. Several of the 96 | /// substructures need to be "hydrated" because parts of their 97 | /// in-memory structures are redundant and aren't serialized to save 98 | /// space. Those redundant portions can be recalculated on the fly 99 | /// from the data that is saved to disk, then an `OwnedCheckpoint` can 100 | /// be returned. 101 | #[derive(Deserialize)] 102 | struct DeserializableCheckpoint { 103 | pub creatures: DeserializableCreatures, 104 | pub stats: GlobalStatistics, 105 | pub settings: Settings, 106 | } 107 | 108 | impl DeserializableCheckpoint { 109 | fn into_owned_checkpoint(self) -> OwnedCheckpoint { 110 | let DeserializableCheckpoint { 111 | creatures: deserialized_creatures, 112 | stats, 113 | settings, 114 | } = self; 115 | OwnedCheckpoint { 116 | creatures: deserialized_creatures.into_creatures(), 117 | stats, 118 | settings, 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /evofighters_rust/src/cli.rs: -------------------------------------------------------------------------------- 1 | use clap; 2 | use dna; 3 | use sim; 4 | use simplify; 5 | use saver::SettingsBuilder; 6 | 7 | pub fn parse_args() -> clap::ArgMatches<'static> { 8 | clap::App::new( 9 | r" __ ___ 10 | / / / / / 11 | (___ ___ (___ ___ (___ (___ ___ ___ ___ 12 | | \ )| )| | | )| )| |___)| )|___ 13 | |__ \/ |__/ | | |__/ | / |__ |__ | __/ 14 | __/ ", 15 | ).version("1.0") 16 | .author("Josh Kuhn ") 17 | .about("Evolving fighting bots") 18 | .arg( 19 | clap::Arg::with_name("savefile") 20 | .short("f") 21 | .long("file") 22 | .default_value("evofighters.evo") 23 | .value_name("SAVEFILE") 24 | .help("Name of save file") 25 | .takes_value(true) 26 | .global(true), 27 | ) 28 | .arg( 29 | clap::Arg::with_name("mutation_rate") 30 | .short("m") 31 | .long("mutation-rate") 32 | .value_name("MUTATION_RATE") 33 | .help("Chance of a new creature having a mutation") 34 | .takes_value(true) 35 | .global(true), 36 | ) 37 | .arg( 38 | clap::Arg::with_name("max_population_size") 39 | .short("p") 40 | .long("max-population-size") 41 | .value_name("MAX_POP_SIZE") 42 | .help("Maximum population to allow") 43 | .takes_value(true) 44 | .global(true), 45 | ) 46 | .arg( 47 | clap::Arg::with_name("metric_fps") 48 | .short("f") 49 | .long("fps") 50 | .value_name("FPS") 51 | .help("Framerate at which to emit metrics") 52 | .takes_value(true) 53 | .global(true), 54 | ) 55 | .subcommand( 56 | clap::SubCommand::with_name("simulate") 57 | .about("Main command. Runs an evofighters simulation"), 58 | ) 59 | .subcommand( 60 | clap::SubCommand::with_name("cycle-check") 61 | .about("Does a cycle detection on the given bases") 62 | .arg( 63 | clap::Arg::with_name("bases") 64 | .required(true) 65 | .multiple(true) 66 | .value_name("BASE"), 67 | ), 68 | ) 69 | .get_matches() 70 | } 71 | 72 | pub fn execute_command(app: &clap::ArgMatches) { 73 | match app.subcommand() { 74 | ("cycle-check", Some(check)) => { 75 | cycle_check(check.values_of("bases").unwrap()) 76 | } 77 | _ => run_simulation(app), 78 | } 79 | } 80 | 81 | pub fn run_simulation(app: &clap::ArgMatches) { 82 | let filename = app.value_of("savefile").unwrap(); 83 | let mut sb = SettingsBuilder::default(); 84 | if let Some(mr) = app.value_of("mutation_rate") { 85 | sb.mutation_rate(mr.parse().unwrap()); 86 | } 87 | if let Some(pop_size) = app.value_of("max_population_size") { 88 | sb.max_population_size(pop_size.parse().unwrap()); 89 | } 90 | if let Some(metric_fps) = app.value_of("metric_fps") { 91 | sb.metric_fps(metric_fps.parse().unwrap()); 92 | } 93 | let settings = sb.build().unwrap(); 94 | sim::Simulation::new(filename, settings).simulate(); 95 | } 96 | 97 | pub fn cycle_check(bases: clap::Values) { 98 | let dna_args: dna::DNA = dna::DNA::from( 99 | bases 100 | .map(|x| x.parse().expect("Well that wasn't an integer")) 101 | .collect::>(), 102 | ); 103 | match simplify::cycle_detect(&dna_args) { 104 | Ok(_thought_cycle) => println!("Got a cycle!"), 105 | Err(failure) => println!("Failed to get a cycle: {:?}", failure), 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /EvoFighters/SaveData.py: -------------------------------------------------------------------------------- 1 | import cPickle as pickle 2 | import ConfigParser 3 | import os 4 | import re 5 | 6 | from EvoFighters.Creatures import Creature 7 | from EvoFighters.Utils import print_helper 8 | 9 | 10 | _int_rgx = re.compile(r'\d+') 11 | _float_rgx = re.compile(r'[\d\.]+') 12 | _bool_rgx = re.compile(r'yes|no|true|false|on|off', flags=re.I) 13 | 14 | 15 | def _make_bool(possibool): 16 | if possibool.lower() in ('on', 'true', 'yes'): 17 | return True 18 | elif possibool.lower() in ('off', 'false', 'no'): 19 | return False 20 | else: 21 | raise ValueError('Not Possibool!') 22 | 23 | 24 | class Settings(object): 25 | '''Settings. By default loaded from the .settings file, but saved 26 | individually for each generation file''' 27 | 28 | __slots__ = ('max_pop_size', 'fps', 'save_filename', 'save_dir', 29 | 'config_dir', 'mutation_rate', 'mating_cost', 'max_inv_size', 30 | 'winner_life_bonus', 'save_interval', 'verbosity', 31 | 'max_thinking_steps', 'max_tree_depth') 32 | 33 | def __init__(self): 34 | self.max_pop_size = 12000 35 | self.fps = 1 36 | self.config_dir = '~/.config/EvoFighters' 37 | self.save_dir = 'saves' 38 | self.save_filename = 'default' 39 | self.winner_life_bonus = 5 40 | self.save_interval = 90 41 | self.verbosity = 0 42 | self.mutation_rate = 0.10 # higher = more mutations 43 | self.mating_cost = 40 44 | self.max_inv_size = 5 45 | # how many steps they are allowed to use to construct a thought 46 | self.max_thinking_steps = 200 47 | # How deeply nested a thought tree is allowed to be 48 | self.max_tree_depth = 15 49 | 50 | def __str__(self): 51 | lines = ['{} = {!r}'.format(key, getattr(self, key)) 52 | for key in self.__slots__] 53 | return 'Settings:\n ' + '\n '.join(lines) 54 | 55 | @property 56 | def save_file(self): 57 | return os.path.join( 58 | os.path.expanduser(self.config_dir), 59 | self.save_dir, 60 | self.save_filename, 61 | ) 62 | 63 | @property 64 | def config_file(self): 65 | return os.path.join( 66 | os.path.expanduser(self.config_dir), 'config') 67 | 68 | def set_from_strings(self, keyvals): 69 | '''Parse int/float/bool from string values''' 70 | for name, value in keyvals: 71 | if _int_rgx.match(value): 72 | setattr(self, name, int(value)) 73 | elif _float_rgx.match(value): 74 | setattr(self, name, float(value)) 75 | elif _bool_rgx.match(value): 76 | setattr(self, name, _make_bool(value)) 77 | else: 78 | setattr(self, name, value) 79 | 80 | @staticmethod 81 | def from_config(): 82 | config_dir = os.path.expanduser('~/.config/EvoFighters') 83 | if not os.path.exists(config_dir): 84 | os.makedirs(config_dir) 85 | filename = os.path.join(config_dir, 'config') 86 | s = Settings() 87 | if not os.path.exists(filename): 88 | return s 89 | config = ConfigParser.ConfigParser() 90 | with open(filename, 'r') as config_file: 91 | config.readfp(config_file) 92 | s.set_from_strings(config.items('global')) 93 | return s 94 | 95 | def write_config(self): 96 | config = ConfigParser.ConfigParser() 97 | config.add_section('global') 98 | for name in self.__slots__: 99 | config.set('global', name, getattr(self, name)) 100 | with open(self.config_file, 'w') as config_file: 101 | config.write(config_file) 102 | 103 | 104 | class SaveData(object): 105 | 'Holds data about the run to save to disk' 106 | 107 | def __init__(self, creatures, feeder_count, num_encounters, count, 108 | dead, settings=None): 109 | self.creatures = creatures 110 | self.feeder_count = feeder_count 111 | self.num_encounters = num_encounters 112 | self.count = count 113 | self.dead = dead 114 | self.settings = settings or Settings.from_config() 115 | 116 | def save(self): 117 | '''Saves a generation to a file, with the generation number 118 | for starting up again''' 119 | print('Saving progress to file.') 120 | self.count = Creature.count 121 | save_dir = os.path.dirname(self.settings.save_file) 122 | if not os.path.exists(save_dir): 123 | os.makedirs(save_dir) 124 | with open(self.settings.save_file, 'w+') as savefile: 125 | pickle.dump(self, file=savefile, protocol=2) 126 | print('Finished saving.') 127 | 128 | @staticmethod 129 | def loadfrom(savefile): 130 | '''Loads savedata from `savefile`''' 131 | sd = pickle.load(savefile) 132 | Creature.count = sd.count 133 | return sd 134 | 135 | def print1(self, *args, **kwargs): 136 | if self.settings.verbosity >= 1: 137 | print_helper(*args, prefix='***',**kwargs) 138 | 139 | def print2(self, *args, **kwargs): 140 | if self.settings.verbosity >= 2: 141 | print_helper(*args, prefix='**', **kwargs) 142 | 143 | def print3(self, *args, **kwargs): 144 | if self.settings.verbosity >= 3: 145 | print_helper(*args, prefix='*', **kwargs) 146 | 147 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ``` 2 | __ ___ 3 | / / / / / 4 | (___ ___ (___ ___ (___ (___ ___ ___ ___ 5 | | \ )| )| | | )| )| |___)| )|___ 6 | |__ \/ |__/ | | |__/ | / |__ |__ | __/ 7 | __/ 1.0 8 | Josh Kuhn 9 | Evolving fighting bots 10 | 11 | USAGE: 12 | evofighters [OPTIONS] [SUBCOMMAND] 13 | 14 | FLAGS: 15 | -h, --help Prints help information 16 | -V, --version Prints version information 17 | 18 | OPTIONS: 19 | -f, --file Name of save file [default: evofighters.evo] 20 | 21 | SUBCOMMANDS: 22 | cycle-check Does a cycle detection on the given bases 23 | help Prints this message or the help of the given subcommand(s) 24 | simulate Main command. Runs an evofighters simulation 25 | ``` 26 | 27 | ## What is this? 28 | 29 | This is a simulation I've written on and off over several years. It's 30 | fundamentally about little bots with a very simple fighting 31 | system. They attack, defend, pick up items, and occasionally try to 32 | mate with each other. 33 | 34 | When they mate, they combine their dna similar to how sexually reproducing 35 | organisms in real life do, randomly selecting genes from each of the 36 | two parents. There's a chance of different kinds of mutations like 37 | gene swaps, transcription errors and the like. 38 | 39 | Once you have replication, a competitive environment, and mutations, 40 | you get evolution. It's not super sensitive to starting conditions, it 41 | just works and is kind of fun. 42 | 43 | ## How it works 44 | 45 | There are some rough phases the simulation occurs in for each creature: 46 | 47 | 1. Creating a new creature, which randomly chooses between each pair 48 | of genes from the parents. It also has a chance of random mutation. 49 | 2. Compiling/Parsing, which iterates through the bases of the 50 | creature's dna, and builds an ast with the creature's program for 51 | how to behave in an encounter. The parsing process is very 52 | forgiving: if the next base isn't a valid value for the next term, 53 | we just skip it and go on to the next base. If we reach the end of 54 | the dna, we wrap around to the beginning. If parsing takes too many 55 | steps we abort. 56 | 3. Evaluation, when the creature is in an actual encounter with 57 | another creature. The parsed ast is evaluated in the context of the 58 | fight (some variables depend on the opponent, some are random, so 59 | can't be done ahead of time). The outcome of this is a decision for 60 | which action the creature should take. 61 | 4. The creature takes an action. The 3 most interesting actions are 62 | attacking, defending and mating. Mating requires some reciprocity 63 | and some spare energy (otherwise you could create something from 64 | nothing and mating constantly would be the best strategy to pass on 65 | genes). Fighting allows you to gain energy if you win, but if the 66 | opponent is smart and defends correctly against you, you will 67 | expend resources fighting and not get anything in return. 68 | 69 | There is an initial population of creatures, with a bootstrapped DNA 70 | that parses out to "Turn 1: Try to mate. Turn 2: Flee". This is a 71 | reasonable starting point since it ensures mating gets off the ground 72 | (can't evolve if you don't reproduce), but it also ensures short 73 | fights since they flee right afterward (and don't waste all of their 74 | energy trying to mate over and over again). 75 | 76 | For each encounter, two random creatures are selected from the 77 | population, and pitted against each other. The fight is given a 78 | randomized maximum number of rounds to last. Then the creatures go at 79 | it, fighting or mating as their dna instructs them. 80 | 81 | ## History 82 | 83 | I initially wrote a version of this in python. Being a simulation 84 | though, it needed to run fast, so I made it work in pypy. I left the 85 | project alone for a while. 86 | 87 | When I came back about 3 years later, I rewrote the main portion of it 88 | in pre-1.0 rust. It used several experimental extensions which were 89 | never standardized. But I made some advances over the initial python 90 | version. Mostly in speed, but also I wrote a clever compiler that 91 | allowed precompiling and simplifying a creatures genes. Previously, 92 | every time a fight was run, some evaluation/parsing occurred. Then I 93 | left the project alone for a while. 94 | 95 | When I came back about 3 years later, I fixed almost all of the 96 | pre-1.0 problems and got it almost working in standard rust. The only 97 | issue was that standard rust still didn't have box pattern syntax, 98 | which was really useful for digging through deep recursive data 99 | structures. So, we're on nightly rust, and only using two language 100 | extensions (`box_syntax` and `nll` which is actually expected to be 101 | standardized). 102 | 103 | ## Contributing 104 | 105 | This is mostly a fun project for me. I don't actually think it's 106 | useful for anyone else, nor is it very interesting to watch. The fun 107 | of it is developing it and seeing what kinds of strategies the 108 | creatures come up with after millions of generations. 109 | 110 | So, since the goal isn't actually to create useful software, but to 111 | enjoy the process, I don't really forsee a need to accept 112 | contributions. That being said, if you want to fork this, be my guest, 113 | and if you feel like contributing, feel free to open up an issue and 114 | we can talk! 115 | -------------------------------------------------------------------------------- /evofighters_rust/src/eval.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::cmp::{max, min, PartialEq, PartialOrd}; 3 | 4 | use dna::{ast, lex, Gene}; 5 | use creatures::Creature; 6 | use rng::RngState; 7 | 8 | // PerformableAction is the result of evaluating a thought tree 9 | #[derive(Debug, Copy, PartialEq, Eq, Clone, Serialize, Deserialize)] 10 | pub enum PerformableAction { 11 | Attack(lex::DamageType), 12 | Defend(lex::DamageType), 13 | Signal(lex::Signal), 14 | Eat, 15 | Take, 16 | Wait, 17 | Flee, 18 | Mate, 19 | NoAction, 20 | } 21 | 22 | impl fmt::Display for PerformableAction { 23 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 24 | match *self { 25 | PerformableAction::Attack(dmg) => { 26 | write!(f, "attack with damage type: {:?}", dmg) 27 | } 28 | PerformableAction::Defend(dmg) => { 29 | write!(f, "defend against damage type: {:?}", dmg) 30 | } 31 | PerformableAction::Signal(sig) => { 32 | write!(f, "signal with the color: {:?}", sig) 33 | } 34 | PerformableAction::Eat => write!(f, "use an item in his inventory"), 35 | PerformableAction::Take => write!(f, "take something from target"), 36 | PerformableAction::Wait => write!(f, "wait"), 37 | PerformableAction::Flee => write!(f, "flee the encounter"), 38 | PerformableAction::Mate => write!(f, "mate with the target"), 39 | PerformableAction::NoAction => write!(f, "(no action)"), 40 | } 41 | } 42 | } 43 | 44 | pub fn evaluate( 45 | me: &Creature, 46 | other: &Creature, 47 | tree: &ast::Condition, 48 | ) -> PerformableAction { 49 | match *tree { 50 | ast::Condition::Always(ref action) => eval_action(me, other, action), 51 | ast::Condition::RangeCompare { 52 | ref value, 53 | ref bound_a, 54 | ref bound_b, 55 | ref affirmed, 56 | ref denied, 57 | } => { 58 | let a = eval_value(me, other, bound_a); 59 | let b = eval_value(me, other, bound_b); 60 | let check_val = eval_value(me, other, value); 61 | if min(a, b) <= check_val && check_val <= max(a, b) { 62 | trace!("{} was between {} and {}", check_val, a, b); 63 | eval_action(me, other, affirmed) 64 | } else { 65 | trace!("{} was not between {} and {}", check_val, a, b); 66 | eval_action(me, other, denied) 67 | } 68 | } 69 | ast::Condition::BinCompare { 70 | ref operation, 71 | ref lhs, 72 | ref rhs, 73 | ref affirmed, 74 | ref denied, 75 | } => { 76 | let op: fn(&usize, &usize) -> bool = match *operation { 77 | ast::BinOp::LT => PartialOrd::lt, 78 | ast::BinOp::GT => PartialOrd::gt, 79 | ast::BinOp::EQ => PartialEq::eq, 80 | ast::BinOp::NE => PartialEq::ne, 81 | }; 82 | let evaled_lhs = eval_value(me, other, lhs); 83 | let evaled_rhs = eval_value(me, other, rhs); 84 | if op(&evaled_lhs, &evaled_rhs) { 85 | trace!( 86 | "{:?}({}) was {} {:?}({})", 87 | lhs, 88 | evaled_lhs, 89 | operation, 90 | rhs, 91 | evaled_rhs 92 | ); 93 | eval_action(me, other, affirmed) 94 | } else { 95 | trace!( 96 | "{:?}({}) was not {} {:?}({})", 97 | lhs, 98 | evaled_lhs, 99 | operation, 100 | rhs, 101 | evaled_rhs 102 | ); 103 | eval_action(me, other, denied) 104 | } 105 | } 106 | ast::Condition::ActionCompare { 107 | ref actor_type, 108 | ref action, 109 | ref affirmed, 110 | ref denied, 111 | } => { 112 | let (actor, actor_str) = match *actor_type { 113 | ast::ActorType::Me => (me, "my"), 114 | ast::ActorType::Other => (other, "other's"), 115 | }; 116 | let my_action = eval_action(me, other, action); 117 | if my_action == actor.last_action { 118 | trace!( 119 | "{}'s last action was {:?}", 120 | actor_str, 121 | actor.last_action 122 | ); 123 | eval_action(me, other, affirmed) 124 | } else { 125 | trace!( 126 | "{}'s last action was not {:?}", 127 | actor_str, 128 | actor.last_action 129 | ); 130 | eval_action(me, other, denied) 131 | } 132 | } 133 | } 134 | } 135 | 136 | fn eval_action( 137 | me: &Creature, 138 | other: &Creature, 139 | action: &ast::Action, 140 | ) -> PerformableAction { 141 | match *action { 142 | ast::Action::Attack(dmg) => PerformableAction::Attack(dmg), 143 | ast::Action::Defend(dmg) => PerformableAction::Defend(dmg), 144 | ast::Action::Signal(sig) => PerformableAction::Signal(sig), 145 | ast::Action::Eat => PerformableAction::Eat, 146 | ast::Action::Take => PerformableAction::Take, 147 | ast::Action::Wait => PerformableAction::Wait, 148 | ast::Action::Flee => PerformableAction::Flee, 149 | ast::Action::Mate => PerformableAction::Mate, 150 | ast::Action::Subcondition(ref sub) => evaluate(me, other, sub), 151 | } 152 | } 153 | 154 | fn eval_value(me: &Creature, other: &Creature, val: &ast::Value) -> usize { 155 | match *val { 156 | ast::Value::Literal(x) => x as usize, 157 | ast::Value::Random => RngState::from_creatures(me, other) 158 | .rand_range(0, Gene::MAX_MEANINGFUL_VALUE as usize), 159 | ast::Value::Me(attr) => me.attr(attr), 160 | ast::Value::Other(attr) => other.attr(attr), 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /EvoFighters/Eval.py: -------------------------------------------------------------------------------- 1 | '''This module handles evaluating the parse trees that Parsing creates''' 2 | 3 | from Parsing import COND, ACT, ATTR, VAL 4 | from Utils import dmg_repr, sig_repr 5 | import operator as Op 6 | from random import randint 7 | 8 | sd = None # Set by Arena 9 | 10 | class PerformableAction(object): 11 | '''Represents a concrete, comparable action that a creature intends to carry 12 | out''' 13 | def __init__(self, typ, arg): 14 | self.typ = typ 15 | self.arg = arg 16 | 17 | def __eq__(self, other): 18 | return self.typ == other.typ and self.arg == other.arg 19 | 20 | def __repr__(self): 21 | return str(self) 22 | 23 | def __str__(self): 24 | if self.typ == ACT['attack']: 25 | return "attack with damage type: {}".format(dmg_repr(self.arg)) 26 | elif self.typ == ACT['defend']: 27 | return "defend against damage type: {}".format(dmg_repr(self.arg)) 28 | elif self.typ == ACT['signal']: 29 | return "signal with the color {0}".format(sig_repr(self.arg)) 30 | elif self.typ == ACT['use']: 31 | return "use an item in his inventory" 32 | elif self.typ == ACT['take']: 33 | return "take something from target" 34 | elif self.typ == ACT['wait']: 35 | return "wait" 36 | elif self.typ == ACT['flee']: 37 | return "flee the encounter" 38 | elif self.typ == ACT['mate']: 39 | return "mate with target" 40 | else: 41 | return "do unknown action: ({}, {})".format(self.typ, self.arg) 42 | 43 | 44 | class InvalidInstructionError(Exception): 45 | '''Thrown whenever an invalid instruction is evaluated''' 46 | pass 47 | 48 | 49 | def evaluate(me, tree): 50 | '''Eval takes information from the creature and a thought and returns an 51 | action to perform''' 52 | cond_typ = tree[0] 53 | if cond_typ == COND['always']: 54 | return eval_act(me, tree[1]) 55 | elif cond_typ == COND['in_range']: 56 | val1 = get_val(me, tree[1]) 57 | val2 = get_val(me, tree[2]) 58 | check_val = get_val(me, tree[3]) 59 | if min(val1, val2) <= check_val <= max(val1, val2): 60 | sd.print3('{val_repr} was between {} and {}', val1, val2, val_repr = tree[3]) 61 | return eval_act(me, tree[4]) 62 | else: 63 | sd.print3('{val_repr} was not between {} and {}', val1, val2, val_repr = tree[1]) 64 | return eval_act(me, tree[5]) 65 | elif COND['less_than'] <= cond_typ <= COND['not_equal_to']: 66 | if cond_typ == COND['less_than']: 67 | op_str = '<' 68 | op = Op.lt 69 | elif cond_typ == COND['greater_than']: 70 | op_str = '>' 71 | op = Op.gt 72 | elif cond_typ == COND['equal_to']: 73 | op_str = '==' 74 | op = Op.eq 75 | elif cond_typ == COND['not_equal_to']: 76 | op_str = '!=' 77 | op = Op.ne 78 | val1 = get_val(me, tree[1]) 79 | val2 = get_val(me, tree[2]) 80 | if op(val1, val2): 81 | sd.print3('{val_repr} was {} {val_repr2}', op_str, val_repr = tree[1], 82 | val_repr2 = tree[2]) 83 | return eval_act(me, tree[3]) 84 | else: 85 | sd.print3('{val_repr} was not {} {val_repr2}', op_str, val_repr = tree[1], 86 | val_repr2 = tree[2]) 87 | return eval_act(me, tree[4]) 88 | elif cond_typ in [COND['me_last_act'], COND['target_last_act']]: 89 | if cond_typ == COND['me_last_act']: 90 | actor = me 91 | else: 92 | actor = me.target 93 | act1 = eval_act(me, tree[1]) 94 | if act1 == actor.last_action: 95 | sd.print3('{who.name}\'s last action was {act_repr}', who = actor, 96 | act_repr = tree[1]) 97 | return eval_act(me, tree[2]) 98 | else: 99 | sd.print3('{who.name}\'s last action was not {act_repr} ({fst} vs. {snd})', 100 | who = actor, act_repr = tree[1], fst = act1, 101 | snd = actor.last_action) 102 | return eval_act(me, tree[3]) 103 | else: 104 | raise InvalidInstructionError("Couldn't understand condition: {0}". 105 | format(cond_typ)) 106 | 107 | def get_val(me, tree): 108 | '''Evaluates a VAL node in a thought tree''' 109 | val_typ = tree[0] 110 | if val_typ == VAL['literal']: 111 | return tree[1] 112 | elif val_typ == VAL['random']: 113 | ret = randint(-1, 9) 114 | return ret 115 | elif val_typ == VAL['me']: 116 | return get_attr(me, tree[1]) 117 | elif val_typ == VAL['target']: 118 | return get_attr(me.target, tree[1]) 119 | 120 | def get_attr(who, attr_typ): 121 | '''Returns the value of the attribute on "who" ''' 122 | if attr_typ == ATTR['energy']: 123 | return who.energy 124 | elif attr_typ == ATTR['signal']: 125 | return who.signal 126 | elif attr_typ == ATTR['generation']: 127 | return who.generation 128 | elif attr_typ == ATTR['kills']: 129 | return who.kills 130 | elif attr_typ == ATTR['survived']: 131 | return who.survived 132 | elif attr_typ == ATTR['num_children']: 133 | return who.num_children 134 | elif attr_typ == ATTR['top_item']: 135 | return who.top_item if who.has_items else -1 136 | 137 | def eval_act(me, tree): 138 | '''Returns an action suitable for performing (PerformableAction)''' 139 | act_typ = tree[0] 140 | if act_typ in (ACT['attack'], ACT['defend'], ACT['signal']): 141 | return PerformableAction(act_typ, tree[1]) 142 | elif act_typ in (ACT['use'], ACT['take'], 143 | ACT['wait'], ACT['flee'], ACT['mate']): 144 | return PerformableAction(act_typ, None) 145 | elif act_typ == ACT['subcondition']: 146 | return evaluate(me, tree[1]) 147 | else: 148 | raise InvalidInstructionError("Didn't understand action: {0}"\ 149 | .format(act_typ)) 150 | 151 | if __name__ == '__main__': 152 | from Parsing import Parser, TooMuchThinkingError 153 | from Creatures import Creature 154 | last_action = PerformableAction(ACT['wait'], None) 155 | for _ in xrange(1000): 156 | a = Creature() 157 | b = Creature() 158 | a.target = b 159 | a.last_action = last_action 160 | try: 161 | p = Parser(a.dna) 162 | last_action = evaluate(a, next(p).tree) 163 | str(last_action) 164 | except TooMuchThinkingError: 165 | continue 166 | 167 | -------------------------------------------------------------------------------- /evofighters_rust/src/parsing.rs: -------------------------------------------------------------------------------- 1 | use std::iter::Iterator; 2 | use std::option::Option::*; 3 | use std::convert::From; 4 | use num::FromPrimitive; 5 | 6 | use dna::{ast, lex, DNAIter, DNA}; 7 | 8 | #[derive(Debug, PartialEq, Eq, Copy, Clone)] 9 | pub enum Failure { 10 | DNAEmpty, 11 | TookTooLong, 12 | ParseTreeTooDeep, 13 | } 14 | 15 | #[derive(Debug, Clone)] 16 | pub struct Decision { 17 | pub tree: ast::Condition, 18 | pub offset: usize, 19 | pub icount: usize, 20 | pub skipped: usize, 21 | } 22 | 23 | pub struct Indecision { 24 | pub reason: Failure, 25 | pub icount: usize, 26 | pub skipped: usize, 27 | pub offset: usize, 28 | } 29 | 30 | impl From for Failure { 31 | fn from(indecision: Indecision) -> Failure { 32 | indecision.reason 33 | } 34 | } 35 | 36 | pub enum Thought { 37 | Dec(Decision), 38 | Ind(Indecision), 39 | } 40 | 41 | impl Thought { 42 | fn feeder_decision() -> Thought { 43 | Thought::Dec(Decision { 44 | tree: ast::Condition::Always(ast::Action::Wait), 45 | icount: 0, 46 | skipped: Parser::MAX_THINKING_STEPS + 1, 47 | offset: 0, 48 | }) 49 | } 50 | 51 | pub fn offset(&self) -> usize { 52 | match *self { 53 | Thought::Dec(Decision { offset, .. }) 54 | | Thought::Ind(Indecision { offset, .. }) => offset, 55 | } 56 | } 57 | 58 | pub fn into_result(self) -> Result { 59 | match self { 60 | Thought::Dec(a) => Ok(a), 61 | Thought::Ind(b) => Err(b), 62 | } 63 | } 64 | } 65 | 66 | type ParseResult = Result; 67 | 68 | #[derive(Debug, Clone)] 69 | pub struct Parser { 70 | icount: usize, 71 | skipped: usize, 72 | depth: usize, 73 | for_feeder: bool, 74 | dna_stream: DNAIter, 75 | } 76 | 77 | impl Parser { 78 | const MAX_THINKING_STEPS: usize = 100; 79 | const MAX_TREE_DEPTH: usize = 20; 80 | 81 | /// Handles parsing from dna and returning a parse tree which 82 | /// represents a creature's thought process in making 83 | /// encounter decisions 84 | pub fn new(dna: &DNA, offset: usize) -> Parser { 85 | Parser { 86 | icount: 0, 87 | skipped: 0, 88 | dna_stream: dna.base_stream(offset), 89 | depth: 0, 90 | for_feeder: false, 91 | } 92 | } 93 | 94 | pub fn current_offset(&self) -> usize { 95 | self.dna_stream.offset() 96 | } 97 | 98 | fn next_valid(&mut self, minimum: i8) -> ParseResult { 99 | let mut next_i8 = self.dna_stream.next(); 100 | let mut next_val: Option = next_i8.and_then(FromPrimitive::from_i8); 101 | self.icount += 1; 102 | while next_val.is_none() || next_i8.unwrap() < minimum { 103 | next_i8 = self.dna_stream.next(); 104 | next_val = next_i8.and_then(FromPrimitive::from_i8); 105 | self.skipped += 1; 106 | if self.icount + self.skipped > Parser::MAX_THINKING_STEPS { 107 | return Err(Failure::TookTooLong); 108 | } 109 | } 110 | Ok(next_val.unwrap()) 111 | } 112 | 113 | fn parse_condition(&mut self) -> ParseResult { 114 | if self.depth > Parser::MAX_TREE_DEPTH { 115 | return Err(Failure::ParseTreeTooDeep); 116 | } 117 | Ok(match self.next_valid(0)? { 118 | lex::Condition::Always => { 119 | ast::Condition::Always(self.parse_action()?) 120 | } 121 | lex::Condition::InRange => ast::Condition::RangeCompare { 122 | value: self.parse_value()?, 123 | bound_a: self.parse_value()?, 124 | bound_b: self.parse_value()?, 125 | affirmed: self.parse_action()?, 126 | denied: self.parse_action()?, 127 | }, 128 | cnd @ lex::Condition::LessThan 129 | | cnd @ lex::Condition::GreaterThan 130 | | cnd @ lex::Condition::EqualTo 131 | | cnd @ lex::Condition::NotEqualTo => ast::Condition::BinCompare { 132 | operation: match cnd { 133 | lex::Condition::LessThan => ast::BinOp::LT, 134 | lex::Condition::GreaterThan => ast::BinOp::GT, 135 | lex::Condition::EqualTo => ast::BinOp::EQ, 136 | lex::Condition::NotEqualTo => ast::BinOp::NE, 137 | _ => panic!("Not possible"), 138 | }, 139 | lhs: self.parse_value()?, 140 | rhs: self.parse_value()?, 141 | affirmed: self.parse_action()?, 142 | denied: self.parse_action()?, 143 | }, 144 | actor @ lex::Condition::MyLastAction 145 | | actor @ lex::Condition::OtherLastAction => { 146 | ast::Condition::ActionCompare { 147 | actor_type: match actor { 148 | lex::Condition::MyLastAction => ast::ActorType::Me, 149 | lex::Condition::OtherLastAction => { 150 | ast::ActorType::Other 151 | } 152 | _ => panic!("Not possible"), 153 | }, 154 | action: self.parse_action()?, 155 | affirmed: self.parse_action()?, 156 | denied: self.parse_action()?, 157 | } 158 | } 159 | }) 160 | } 161 | 162 | fn parse_action(&mut self) -> ParseResult { 163 | Ok(match self.next_valid(0)? { 164 | lex::Action::Subcondition => { 165 | self.depth += 1; 166 | let subcond = ast::Action::Subcondition(Box::new( 167 | self.parse_condition()?, 168 | )); 169 | self.depth -= 1; 170 | subcond 171 | } 172 | lex::Action::Attack => ast::Action::Attack(self.next_valid(0)?), 173 | lex::Action::Defend => ast::Action::Defend(self.next_valid(0)?), 174 | lex::Action::Signal => ast::Action::Signal(self.next_valid(0)?), 175 | lex::Action::Eat => ast::Action::Eat, 176 | lex::Action::Take => ast::Action::Take, 177 | lex::Action::Mate => ast::Action::Mate, 178 | lex::Action::Wait => ast::Action::Wait, 179 | lex::Action::Flee => ast::Action::Flee, 180 | }) 181 | } 182 | 183 | fn parse_value(&mut self) -> ParseResult { 184 | Ok(match self.next_valid(0)? { 185 | lex::Value::Literal => ast::Value::Literal(self.next_valid(0)?), 186 | lex::Value::Random => ast::Value::Random, 187 | lex::Value::Me => ast::Value::Me(self.next_valid(0)?), 188 | lex::Value::Other => ast::Value::Other(self.next_valid(0)?), 189 | }) 190 | } 191 | } 192 | 193 | impl Iterator for Parser { 194 | type Item = Thought; 195 | fn next(&mut self) -> Option { 196 | if self.for_feeder { 197 | return Some(Thought::feeder_decision()); 198 | } 199 | let value = Some(match self.parse_condition() { 200 | Err(msg) => Thought::Ind(Indecision { 201 | icount: self.icount, 202 | skipped: self.skipped, 203 | reason: msg, 204 | offset: self.current_offset(), 205 | }), 206 | Ok(tree) => Thought::Dec(Decision { 207 | icount: self.icount, 208 | skipped: self.skipped, 209 | tree: tree, 210 | offset: self.current_offset(), 211 | }), 212 | }); 213 | // Reset counts so the creatures get a new budget next time! 214 | self.icount = 0; 215 | self.skipped = 0; 216 | value 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /EvoFighters/Parsing.py: -------------------------------------------------------------------------------- 1 | '''Handles parsing of streams of integers into instructions for the 2 | EvoFighters''' 3 | 4 | from collections import namedtuple 5 | 6 | sd = None # set by Arenas 7 | 8 | 9 | COND = dict(always = 0, 10 | in_range = 1, 11 | less_than = 2, 12 | greater_than = 3, 13 | equal_to = 4, 14 | not_equal_to = 5, 15 | me_last_act = 6, 16 | target_last_act = 7, 17 | ) 18 | 19 | VAL = dict(literal = 0, # a number straight from the dna 20 | random = 1, # a randomly selected number 21 | me = 2, # one of my attributes (1 arg) 22 | target = 3, # one of my enemies attributes (1 arg) 23 | ) 24 | 25 | ACT = dict(subcondition = 0, # indicates a subconditional 26 | attack = 1, # attacks target with specified attack type (1 arg) 27 | mate = 2, # attempt to mate with target 28 | defend = 3, # defends with specified defense type (1 arg) 29 | use = 4, # uses the top item in the inventory 30 | signal = 5, # sets a flag on the creature (1 arg) 31 | take = 6, # takes top item in enemies inventory 32 | wait = 7, # do nothing 33 | flee = 8, # escape the fight 34 | ) 35 | 36 | ATTR = dict(energy = 0, # energy level 37 | signal = 1, # current signal 38 | generation = 2, # generation number 39 | kills = 3, # how many targets killed 40 | survived = 4, # how many encounters survived 41 | num_children = 5, # how many children 42 | top_item = 6, # value of top inventory item 43 | ) 44 | 45 | ITEM = dict(food = 0, 46 | good_food = 1, 47 | better_food = 2, 48 | excellent_food = 3, 49 | ) 50 | 51 | SIG = dict(red = 0, 52 | yellow = 1, 53 | blue = 2, 54 | purple = 3, 55 | orange = 4, 56 | green = 5, 57 | ) 58 | 59 | DMG = dict(fire = 0, 60 | ice = 1, 61 | electricity = 2, 62 | ) 63 | 64 | class ParseError(Exception): 65 | 'Thrown when a parse goes wrong' 66 | pass 67 | 68 | 69 | class TooMuchThinkingError(Exception): 70 | 'Thrown when a creatures takes too many steps to produce a tree' 71 | def __init__(self, msg, icount, skipped): 72 | Exception.__init__(self, msg) 73 | self.icount = icount 74 | self.skipped = skipped 75 | 76 | 77 | Thought = namedtuple('Thought', 'tree icount skipped') 78 | 79 | class Parser(object): 80 | '''Handles parsing from dna and returning a parse tree which represents a 81 | creature's tthought process in making encounter decisions''' 82 | 83 | WAIT_THOUGHT = (COND['always'], (ACT['wait'],)) 84 | 85 | def __init__(self, dna): 86 | self._icount = 0 87 | self._progress = 0 88 | self._skipped = 0 89 | self._dna = dna 90 | self._len = len(dna) 91 | self._depth = 0 92 | 93 | def next(self): 94 | '''Parses a dna iterator (Must not be the dna list!) into a tree 95 | structure that represents a creature's thought process in making 96 | encounter decisions''' 97 | self._icount = 0 98 | self._skipped = 0 99 | self._depth = 0 100 | return Thought(tree = self._cond(), 101 | icount = self._icount, 102 | skipped = self._skipped) 103 | 104 | def _cond(self): 105 | '''Parses a conditional node''' 106 | #get the condition type symbol 107 | cond_typ = self._get_next_valid(COND) 108 | 109 | if cond_typ == COND['always']: 110 | return (COND['always'], 111 | self._act()) 112 | elif cond_typ == COND['in_range']: 113 | return (COND['in_range'], 114 | self._val, 115 | self._val, 116 | self._val, 117 | self._act(), 118 | self._act()) 119 | elif COND['less_than'] <= cond_typ <= COND['not_equal_to']: 120 | return (cond_typ, 121 | self._val, 122 | self._val, 123 | self._act(), 124 | self._act()) 125 | elif cond_typ in [COND['me_last_act'], COND['target_last_act']]: 126 | return(cond_typ, 127 | self._act(nosub = True), 128 | self._act(), 129 | self._act()) 130 | 131 | @property 132 | def _val(self): 133 | '''Parses a value node''' 134 | val_typ = self._get_next_valid(VAL) 135 | 136 | if val_typ == VAL['literal']: 137 | self._icount += 1 138 | return (VAL['literal'], 139 | self._dna[self._progress % self._len]) 140 | elif val_typ == VAL['random']: 141 | return (VAL['random'],) 142 | elif val_typ in [VAL['me'], VAL['target']]: 143 | return (val_typ, 144 | self._get_next_valid(ATTR)) 145 | 146 | def _act(self, nosub = False): 147 | '''Parses an action node''' 148 | if self._depth > sd.settings.max_tree_depth: 149 | raise TooMuchThinkingError('Recursion depth exceeded', 150 | icount=0, 151 | skipped=sd.settings.max_thinking_steps) 152 | act_typ = self._get_next_valid(ACT, minimum = 1 if nosub else 0) 153 | 154 | if act_typ == ACT['subcondition']: 155 | # you're thinking, "increment a variable before a function call and 156 | # decrement it afterward? Why not use the call stack?!" Well, I'll 157 | # tell you why not: I don't want to thread a depth argument through 158 | # all of the other parser method calls. That's why this is a class! 159 | self._depth += 1 160 | retval = (ACT['subcondition'], 161 | self._cond()) 162 | self._depth -= 1 163 | return retval 164 | elif act_typ in [ACT['attack'], ACT['defend']]: 165 | return (act_typ, 166 | self._get_next_valid(DMG)) 167 | elif act_typ == ACT['signal']: 168 | return (ACT['signal'], 169 | self._get_next_valid(SIG)) 170 | elif act_typ in (ACT['use'], ACT['take'], ACT['mate'], 171 | ACT['wait'], ACT['flee']): 172 | return (act_typ,) 173 | 174 | def _get_next_valid(self, typ, minimum = 0): 175 | '''Gets the next valid integer in the range allowed by the given 176 | type. Adds on to the `count` passed in. mini and maxi allow one to 177 | restrict the range further than `typ` does''' 178 | next_val = self._dna[self._progress % self._len] 179 | self._icount += 1 180 | self._progress += 1 181 | # we want to return a count 1 less than the number required, since we 182 | # dont want to penalize for required parser symbols. Therefore if the 183 | # while condition succeeds the first time through, count is not 184 | # incremented 185 | while not( minimum <= next_val < len(typ) ): 186 | next_val = self._dna[self._progress % self._len] 187 | self._skipped += 1 188 | if self._icount + self._skipped > sd.settings.max_thinking_steps: 189 | raise TooMuchThinkingError('Recursion depth exceeded', 190 | icount = 0, 191 | skipped=sd.settings.max_thinking_steps) 192 | return next_val 193 | 194 | 195 | 196 | 197 | if __name__ == '__main__': 198 | from random import randint 199 | from EvoFighters.Utils import show_thought 200 | for _ in xrange(500): 201 | p = Parser([randint(-1, 9) for _ in xrange(50)]) 202 | show_thought(next(p).tree) 203 | -------------------------------------------------------------------------------- /EvoFighters/.pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | # Specify a configuration file. 4 | #rcfile= 5 | 6 | # Python code to execute, usually for sys.path manipulation such as 7 | # pygtk.require(). 8 | #init-hook= 9 | 10 | # Profiled execution. 11 | profile=no 12 | 13 | # Add files or directories to the blacklist. They should be base names, not 14 | # paths. 15 | ignore=.git,__init__.py 16 | 17 | # Pickle collected data for later comparisons. 18 | persistent=yes 19 | 20 | # List of plugins (as comma separated values of python modules names) to load, 21 | # usually to register additional checkers. 22 | load-plugins= 23 | 24 | 25 | [MESSAGES CONTROL] 26 | 27 | # Enable the message, report, category or checker with the given id(s). You can 28 | # either give multiple identifier separated by comma (,) or put this option 29 | # multiple time. 30 | #enable= 31 | 32 | # Disable the message, report, category or checker with the given id(s). You 33 | # can either give multiple identifier separated by comma (,) or put this option 34 | # multiple time (only on the command line, not in the configuration file where 35 | # it should appear only once). 36 | disable=C0103,R0912,R0902,R0911,R0903 37 | 38 | 39 | [REPORTS] 40 | 41 | # Set the output format. Available formats are text, parseable, colorized, msvs 42 | # (visual studio) and html 43 | output-format=text 44 | 45 | # Include message's id in output 46 | include-ids=no 47 | 48 | # Put messages in a separate file for each module / package specified on the 49 | # command line instead of printing them on stdout. Reports (if any) will be 50 | # written in a file name "pylint_global.[txt|html]". 51 | files-output=no 52 | 53 | # Tells whether to display a full report or only the messages 54 | reports=yes 55 | 56 | # Python expression which should return a note less than 10 (10 is the highest 57 | # note). You have access to the variables errors warning, statement which 58 | # respectively contain the number of errors / warnings messages and the total 59 | # number of statements analyzed. This is used by the global evaluation report 60 | # (RP0004). 61 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) 62 | 63 | # Add a comment according to your evaluation note. This is used by the global 64 | # evaluation report (RP0004). 65 | comment=no 66 | 67 | 68 | [VARIABLES] 69 | 70 | # Tells whether we should check for unused import in __init__ files. 71 | init-import=no 72 | 73 | # A regular expression matching the beginning of the name of dummy variables 74 | # (i.e. not used). 75 | dummy-variables-rgx=_|dummy 76 | 77 | # List of additional names supposed to be defined in builtins. Remember that 78 | # you should avoid to define new builtins when possible. 79 | additional-builtins= 80 | 81 | 82 | [MISCELLANEOUS] 83 | 84 | # List of note tags to take in consideration, separated by a comma. 85 | notes=FIXME,XXX,TODO 86 | 87 | 88 | [FORMAT] 89 | 90 | # Maximum number of characters on a single line. 91 | max-line-length=80 92 | 93 | # Maximum number of lines in a module 94 | max-module-lines=1000 95 | 96 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 97 | # tab). 98 | indent-string=' ' 99 | 100 | 101 | [BASIC] 102 | 103 | # Required attributes for module, separated by a comma 104 | required-attributes= 105 | 106 | # List of builtins function names that should not be used, separated by a comma 107 | bad-functions=map,filter,apply,input 108 | 109 | # Regular expression which should only match correct module names 110 | module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ 111 | 112 | # Regular expression which should only match correct module level names 113 | const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ 114 | 115 | # Regular expression which should only match correct class names 116 | class-rgx=[A-Z_][a-zA-Z0-9]+$ 117 | 118 | # Regular expression which should only match correct function names 119 | function-rgx=[a-z_][a-z0-9_]{2,30}$ 120 | 121 | # Regular expression which should only match correct method names 122 | method-rgx=[a-z_][a-z0-9_]{2,30}$ 123 | 124 | # Regular expression which should only match correct instance attribute names 125 | attr-rgx=[a-z_][a-z0-9_]{2,30}$ 126 | 127 | # Regular expression which should only match correct argument names 128 | argument-rgx=[a-z_][a-z0-9_]{2,30}$ 129 | 130 | # Regular expression which should only match correct variable names 131 | variable-rgx=[a-z_][a-z0-9_]{2,30}$ 132 | 133 | # Regular expression which should only match correct list comprehension / 134 | # generator expression variable names 135 | inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ 136 | 137 | # Good variable names which should always be accepted, separated by a comma 138 | good-names=i,j,k,ex,Run,_ 139 | 140 | # Bad variable names which should always be refused, separated by a comma 141 | bad-names=foo,bar,baz,toto,tutu,tata 142 | 143 | # Regular expression which should only match functions or classes name which do 144 | # not require a docstring 145 | no-docstring-rgx=__.*__ 146 | 147 | 148 | [TYPECHECK] 149 | 150 | # Tells whether missing members accessed in mixin class should be ignored. A 151 | # mixin class is detected if its name ends with "mixin" (case insensitive). 152 | ignore-mixin-members=yes 153 | 154 | # List of classes names for which member attributes should not be checked 155 | # (useful for classes with attributes dynamically set). 156 | ignored-classes=SQLObject 157 | 158 | # When zope mode is activated, add a predefined set of Zope acquired attributes 159 | # to generated-members. 160 | zope=no 161 | 162 | # List of members which are set dynamically and missed by pylint inference 163 | # system, and so shouldn't trigger E0201 when accessed. Python regular 164 | # expressions are accepted. 165 | generated-members=REQUEST,acl_users,aq_parent 166 | 167 | 168 | [SIMILARITIES] 169 | 170 | # Minimum lines number of a similarity. 171 | min-similarity-lines=4 172 | 173 | # Ignore comments when computing similarities. 174 | ignore-comments=yes 175 | 176 | # Ignore docstrings when computing similarities. 177 | ignore-docstrings=yes 178 | 179 | 180 | [IMPORTS] 181 | 182 | # Deprecated modules which should not be used, separated by a comma 183 | deprecated-modules=regsub,string,TERMIOS,Bastion,rexec 184 | 185 | # Create a graph of every (i.e. internal and external) dependencies in the 186 | # given file (report RP0402 must not be disabled) 187 | import-graph= 188 | 189 | # Create a graph of external dependencies in the given file (report RP0402 must 190 | # not be disabled) 191 | ext-import-graph= 192 | 193 | # Create a graph of internal dependencies in the given file (report RP0402 must 194 | # not be disabled) 195 | int-import-graph= 196 | 197 | 198 | [CLASSES] 199 | 200 | # List of interface methods to ignore, separated by a comma. This is used for 201 | # instance to not check methods defines in Zope's Interface base class. 202 | ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by 203 | 204 | # List of method names used to declare (i.e. assign) instance attributes. 205 | defining-attr-methods=__init__,__new__,setUp 206 | 207 | # List of valid names for the first argument in a class method. 208 | valid-classmethod-first-arg=cls 209 | 210 | 211 | [DESIGN] 212 | 213 | # Maximum number of arguments for function / method 214 | max-args=5 215 | 216 | # Argument names that match this expression will be ignored. Default to name 217 | # with leading underscore 218 | ignored-argument-names=_.* 219 | 220 | # Maximum number of locals for function / method body 221 | max-locals=15 222 | 223 | # Maximum number of return / yield for function / method body 224 | max-returns=6 225 | 226 | # Maximum number of branch for function / method body 227 | max-branchs=12 228 | 229 | # Maximum number of statements in function / method body 230 | max-statements=50 231 | 232 | # Maximum number of parents for a class (see R0901). 233 | max-parents=7 234 | 235 | # Maximum number of attributes for a class (see R0902). 236 | max-attributes=7 237 | 238 | # Minimum number of public methods for a class (see R0903). 239 | min-public-methods=2 240 | 241 | # Maximum number of public methods for a class (see R0904). 242 | max-public-methods=20 243 | 244 | 245 | [EXCEPTIONS] 246 | 247 | # Exceptions that will emit a warning when being caught. Defaults to 248 | # "Exception" 249 | overgeneral-exceptions=Exception 250 | -------------------------------------------------------------------------------- /evofighters_rust/src/simplify.rs: -------------------------------------------------------------------------------- 1 | // For simplifying thought trees 2 | 3 | use std::cmp::{max, min, PartialEq, PartialOrd}; 4 | use std::rc::Rc; 5 | 6 | use dna::{ast, DNA}; 7 | use parsing; 8 | use parsing::Decision; 9 | 10 | /// Simplifies a condition tree by replacing it with another condition 11 | /// tree that takes less (or at least not more) time to execute at 12 | /// runtime. 13 | /// 14 | /// Evolutionary algorithms are good at generating code that "works" 15 | /// but does so by being very redundant, or by doing straightforward 16 | /// things in roundabout ways. This is essentially a compiler that 17 | /// reduces these "bushy" trees to "trim and fit" versions that are 18 | /// equivalent. 19 | /// 20 | /// As a bonus, the behavior of the simplified trees is often easier 21 | /// to interpret by a human. 22 | pub fn simplify( 23 | Decision { 24 | tree, 25 | offset, 26 | icount, 27 | skipped, 28 | }: Decision, 29 | ) -> Decision { 30 | let stage_1_cond = eval_static_conditionals(tree); 31 | // Next, evaluate redundant Always -> Subcondition branches 32 | let stage_2_cond = eval_redundant_conditions(stage_1_cond); 33 | Decision { 34 | tree: stage_2_cond, 35 | offset, 36 | icount, 37 | skipped, 38 | } 39 | } 40 | 41 | /// Evaluates static conditionals at compile time. 42 | /// 43 | /// Static conditionals always evaluate to the same thing, so they can 44 | /// be compiled to one branch or the other before we ever run 45 | /// anything, saving a check at runtime. 46 | fn eval_static_conditionals(cond: ast::Condition) -> ast::Condition { 47 | // Evaluate in line anywhere in the tree that contains only literals. 48 | use dna::ast::Condition::{ActionCompare, Always, BinCompare, RangeCompare}; 49 | use dna::ast::Value::Literal; 50 | use dna::ast::BinOp::{EQ, GT, LT, NE}; 51 | match cond { 52 | Always(act) => Always(esc_action(act)), 53 | RangeCompare { 54 | value: Literal(check_val), 55 | bound_a: Literal(a), 56 | bound_b: Literal(b), 57 | affirmed, 58 | denied, 59 | } => { 60 | if min(a, b) <= check_val && check_val <= max(a, b) { 61 | Always(esc_action(affirmed)) 62 | } else { 63 | Always(esc_action(denied)) 64 | } 65 | } 66 | RangeCompare { 67 | value, 68 | bound_a, 69 | bound_b, 70 | affirmed, 71 | denied, 72 | } => RangeCompare { 73 | value: value, 74 | bound_a: bound_a, 75 | bound_b: bound_b, 76 | affirmed: esc_action(affirmed), 77 | denied: esc_action(denied), 78 | }, 79 | BinCompare { 80 | operation, 81 | lhs: Literal(lhs), 82 | rhs: Literal(rhs), 83 | affirmed, 84 | denied, 85 | } => { 86 | let esc_affirmed = esc_action(affirmed); 87 | let esc_denied = esc_action(denied); 88 | if esc_affirmed == esc_denied { 89 | Always(esc_affirmed) 90 | } else { 91 | let op: fn(&usize, &usize) -> bool = match operation { 92 | LT => PartialOrd::lt, 93 | GT => PartialOrd::gt, 94 | EQ => PartialEq::eq, 95 | NE => PartialEq::ne, 96 | }; 97 | if op(&(lhs as usize), &(rhs as usize)) { 98 | Always(esc_affirmed) 99 | } else { 100 | Always(esc_denied) 101 | } 102 | } 103 | } 104 | BinCompare { 105 | operation, 106 | lhs, 107 | rhs, 108 | affirmed, 109 | denied, 110 | } => { 111 | let esc_affirmed = esc_action(affirmed); 112 | let esc_denied = esc_action(denied); 113 | if esc_affirmed == esc_denied { 114 | Always(esc_affirmed) 115 | } else { 116 | BinCompare { 117 | operation: operation, 118 | lhs: lhs, 119 | rhs: rhs, 120 | affirmed: esc_affirmed, 121 | denied: esc_denied, 122 | } 123 | } 124 | } 125 | ActionCompare { 126 | actor_type, 127 | action, 128 | affirmed, 129 | denied, 130 | } => { 131 | let esc_affirmed = esc_action(affirmed); 132 | let esc_denied = esc_action(denied); 133 | if esc_affirmed == esc_denied { 134 | Always(esc_affirmed) 135 | } else { 136 | ActionCompare { 137 | actor_type: actor_type, 138 | action: esc_action(action), 139 | affirmed: esc_affirmed, 140 | denied: esc_denied, 141 | } 142 | } 143 | } 144 | } 145 | } 146 | 147 | fn esc_action(act: ast::Action) -> ast::Action { 148 | use dna::ast::Action::Subcondition; 149 | use dna::ast::Condition::Always; 150 | match act { 151 | Subcondition(box Always(act)) => esc_action(act), 152 | Subcondition(box cond) => { 153 | Subcondition(Box::new(eval_static_conditionals(cond))) 154 | } 155 | otherwise => otherwise, 156 | } 157 | } 158 | 159 | /// Simplifies redundant conditionals. 160 | /// 161 | /// For example, "Always(Always(action))" is equivalent to 162 | /// "Always(action)". 163 | fn eval_redundant_conditions(cond: ast::Condition) -> ast::Condition { 164 | use dna::ast::Condition::{ActionCompare, Always, BinCompare, RangeCompare}; 165 | use dna::ast::Action::Subcondition; 166 | match cond { 167 | Always(Subcondition(box cond)) => eval_redundant_conditions(cond), 168 | Always(act) => Always(erc_action(act)), 169 | RangeCompare { 170 | value, 171 | bound_a, 172 | bound_b, 173 | affirmed, 174 | denied, 175 | } => RangeCompare { 176 | value: value, 177 | bound_a: bound_a, 178 | bound_b: bound_b, 179 | affirmed: erc_action(affirmed), 180 | denied: erc_action(denied), 181 | }, 182 | BinCompare { 183 | operation, 184 | lhs, 185 | rhs, 186 | affirmed, 187 | denied, 188 | } => BinCompare { 189 | operation: operation, 190 | lhs: lhs, 191 | rhs: rhs, 192 | affirmed: erc_action(affirmed), 193 | denied: erc_action(denied), 194 | }, 195 | ActionCompare { 196 | actor_type, 197 | action, 198 | affirmed, 199 | denied, 200 | } => ActionCompare { 201 | actor_type: actor_type, 202 | action: erc_action(action), 203 | affirmed: erc_action(affirmed), 204 | denied: erc_action(denied), 205 | }, 206 | } 207 | } 208 | 209 | fn erc_action(act: ast::Action) -> ast::Action { 210 | use dna::ast::Action::Subcondition; 211 | match act { 212 | Subcondition(box cond) => { 213 | Subcondition(Box::new(eval_redundant_conditions(cond))) 214 | } 215 | otherwise => otherwise, 216 | } 217 | } 218 | 219 | #[derive(Debug, Clone)] 220 | pub struct ThoughtCycle { 221 | thoughts: Vec>, 222 | cycle_offset: usize, 223 | } 224 | 225 | impl ThoughtCycle { 226 | pub fn next(&mut self) -> Rc { 227 | let t = self.thoughts[self.cycle_offset].clone(); 228 | self.cycle_offset = (self.cycle_offset + 1) % self.thoughts.len(); 229 | t 230 | } 231 | } 232 | 233 | pub fn cycle_detect(dna: &DNA) -> Result { 234 | if !dna.valid() { 235 | return Err(parsing::Failure::DNAEmpty); 236 | } 237 | let f = |offset: usize| -> usize { 238 | parsing::Parser::new(dna, offset).next().unwrap().offset() 239 | }; 240 | 241 | let mut tortoise = f(0); 242 | let mut hare = f(tortoise); 243 | while tortoise != hare { 244 | tortoise = f(tortoise); 245 | hare = f(f(hare)); 246 | } 247 | let mut mu = 0; 248 | // reset tortoise 249 | tortoise = 0; 250 | while tortoise != hare { 251 | tortoise = f(tortoise); 252 | hare = f(hare); 253 | mu += 1; 254 | } 255 | let mut lam = 1; 256 | hare = f(tortoise); 257 | while tortoise != hare { 258 | hare = f(hare); 259 | lam += 1; 260 | } 261 | let mut new_iter = parsing::Parser::new(dna, 0); 262 | let mut thought: parsing::Decision; 263 | let mut thoughts = Vec::new(); 264 | for _ in 0..(mu + lam) { 265 | thought = new_iter.next().unwrap().into_result()?; 266 | thoughts.push(Rc::new(simplify(thought))); 267 | } 268 | Ok(ThoughtCycle { 269 | thoughts: thoughts, 270 | cycle_offset: mu, 271 | }) 272 | } 273 | -------------------------------------------------------------------------------- /EvoFighters/Utils.py: -------------------------------------------------------------------------------- 1 | '''Various odds and ends that are used throughout the other EvoFighters 2 | modules''' 3 | 4 | from __future__ import print_function 5 | 6 | import sys 7 | from collections import Counter 8 | 9 | from blessings import Terminal 10 | 11 | from Parsing import ITEM, ATTR, COND, VAL, ACT, DMG, SIG 12 | 13 | 14 | term = Terminal() 15 | 16 | _verbosity = 0 17 | 18 | 19 | def print_helper(fmt, *args, **kwargs): 20 | if 'prefix' in kwargs: 21 | prefix = kwargs['prefix'] 22 | del kwargs['prefix'] 23 | else: 24 | prefix = '' 25 | if 'thought' in kwargs: 26 | kwargs['thought'] = show_thought(kwargs['thought']) 27 | if 'sig_repr' in kwargs: 28 | kwargs['sig_repr'] = sig_repr(kwargs['sig_repr']) 29 | if 'item_repr' in kwargs: 30 | kwargs['item_repr'] = item_repr(kwargs['item_repr']) 31 | if 'val_repr' in kwargs: 32 | kwargs['val_repr'] = val_repr(kwargs['val_repr']) 33 | if 'val_repr2' in kwargs: 34 | kwargs['val_repr2'] = val_repr(kwargs['val_repr2']) 35 | if 'act_repr' in kwargs: 36 | kwargs['act_repr'] = act_repr(kwargs['act_repr']) 37 | 38 | formatted = fmt.format(*args, **kwargs) 39 | lines = ["{} {}".format(prefix, line) for line in formatted.splitlines()] 40 | print('\n'.join(lines)) 41 | 42 | 43 | def progress_bar(fmt_str, *args, **kwargs): 44 | r'''Generator to create a pipish progress bar. `progress` is a float from 45 | 0.0 to 1.0 representing the progress intended to be represented. It uses \r 46 | to overwrite the line it is printed on, so always print a newline before 47 | calling this function''' 48 | move_up_1 = '\033[1A' 49 | width = term.width 50 | def _prog_gen(): 51 | print() 52 | while True: 53 | progress = yield 54 | total_bars = (width - 5) 55 | num_bars = int(round(total_bars * progress)) 56 | _kwargs = {k : v() for k,v in kwargs.iteritems()} 57 | _args = [x() for x in args] 58 | msg = fmt_str.format(*_args, **_kwargs) 59 | num_lines = len(msg.split('\n')) 60 | print('{moveup}\r{prog:3.0f}% {bars: <80}\n{msg: ^80.80}'.format( 61 | moveup=move_up_1 * num_lines, 62 | prog=progress * 100, 63 | bars='|' * num_bars, 64 | msg=msg), 65 | end = '') 66 | sys.stdout.flush() 67 | # this is to get the generator initialized 68 | val = _prog_gen() 69 | next(val) 70 | return val 71 | 72 | 73 | def indent(corpus): 74 | '''Indent a string by 4 spaces per line''' 75 | return '\n'.join([' {}'.format(line) for line in corpus.splitlines()]) 76 | 77 | def branch_repr(condition, then_str, else_str): 78 | """Prints an if then else branch""" 79 | return '''\ 80 | if {condition}: 81 | {then_clause} 82 | else: 83 | {else_clause}\ 84 | '''.format(condition = condition, 85 | then_clause = indent(then_str), 86 | else_clause = indent(else_str)) 87 | 88 | def show_thought(tree): 89 | '''Show a thought as a pretty printed string''' 90 | return cond_repr(tree) 91 | 92 | def cond_repr(tree): 93 | '''Creates a string from a condition tree node''' 94 | cond_typ = tree[0] 95 | 96 | if cond_typ == COND['always']: 97 | act = act_repr(tree[1]) 98 | return "Always:\n{}".format(indent(act)) 99 | elif cond_typ == COND['in_range']: 100 | rng_min = val_repr(tree[1]) 101 | rng_max = val_repr(tree[2]) 102 | match = val_repr(tree[3]) 103 | cond_str = '{} is in the range {} to {}'.format(match, rng_min, rng_max) 104 | then_str = act_repr(tree[4]) 105 | else_str = act_repr(tree[5]) 106 | return branch_repr(cond_str, then_str, else_str) 107 | elif COND['less_than'] <= cond_typ <= COND['not_equal_to']: 108 | val1 = val_repr(tree[1]) 109 | val2 = val_repr(tree[2]) 110 | if cond_typ == COND['less_than']: 111 | relation = 'is less than' 112 | elif cond_typ == COND['greater_than']: 113 | relation = 'is greater than' 114 | elif cond_typ == COND['equal_to']: 115 | relation = 'is equal to' 116 | elif cond_typ == COND['not_equal_to']: 117 | relation = 'is not equal to' 118 | else: 119 | raise NotImplementedError('This cant happen! {}'.format(cond_typ)) 120 | cond_str = '{} {} {}'.format(val1, relation, val2) 121 | then_str = act_repr(tree[3]) 122 | else_str = act_repr(tree[4]) 123 | return branch_repr(cond_str, then_str, else_str) 124 | elif cond_typ in [COND['me_last_act'], COND['target_last_act']]: 125 | matchact = act_repr(tree[1]) 126 | if cond_typ == COND['me_last_act']: 127 | what = 'my last action was' 128 | else: 129 | what = "my target's last action was" 130 | cond_str = '{} "{}"'.format(what, matchact) 131 | then_str = act_repr(tree[2]) 132 | else_str = act_repr(tree[3]) 133 | return branch_repr(cond_str, then_str, else_str) 134 | else: 135 | return 'Unknown Condition({})'.format(cond_typ) 136 | 137 | 138 | def val_repr(tree): 139 | '''Creates a string from a value tree node''' 140 | val_typ = tree[0] 141 | 142 | if val_typ == VAL['literal']: 143 | return str(tree[1]) 144 | elif val_typ == VAL['random']: 145 | return 'a random number' 146 | elif val_typ in [VAL['me'], VAL['target']]: 147 | who = 'my' if val_typ == VAL['me'] else "the target's" 148 | attr = attr_repr(tree[1]) 149 | return '{} {}'.format(who, attr) 150 | else: 151 | return 'Unknown Value({})'.format(val_typ) 152 | 153 | def act_repr(tree): 154 | '''Creates a string from an action tree node''' 155 | act_typ = tree[0] 156 | 157 | if act_typ == ACT['subcondition']: 158 | return cond_repr(tree[1]) 159 | elif act_typ in [ACT['attack'], ACT['defend']]: 160 | dmg_str = dmg_repr(tree[1]) 161 | if act_typ == ACT['attack']: 162 | return 'attack with {}'.format(dmg_str) 163 | else: 164 | return 'defend with {}'.format(dmg_str) 165 | elif act_typ == ACT['signal']: 166 | sig = sig_repr(tree[1]) 167 | return 'signal {}'.format(sig) 168 | elif act_typ == ACT['use']: 169 | return 'use the top inventory item' 170 | elif act_typ == ACT['take']: 171 | return "take the target's top inventory item" 172 | elif act_typ == ACT['wait']: 173 | return 'wait' 174 | elif act_typ == ACT['flee']: 175 | return 'flee from the encounter' 176 | elif act_typ == ACT['mate']: 177 | return 'attempt to mate with the target' 178 | else: 179 | return 'Unknown Action({})'.format(act_typ) 180 | 181 | 182 | def attr_repr(attr): 183 | '''Creates a string from an attribute code''' 184 | if attr == ATTR['energy']: 185 | return 'energy level' 186 | elif attr == ATTR['signal']: 187 | return 'signal' 188 | elif attr == ATTR['generation']: 189 | return 'generation' 190 | elif attr == ATTR['kills']: 191 | return 'kills' 192 | elif attr == ATTR['survived']: 193 | return 'number of encounters survived' 194 | elif attr == ATTR['num_children']: 195 | return 'number of children' 196 | elif attr == ATTR['top_item']: 197 | return 'top inventory item' 198 | else: 199 | return 'Unknown attribute({})'.format(attr) 200 | 201 | 202 | def item_repr(item): 203 | '''Creates a string from an item code''' 204 | if item == ITEM['food']: 205 | return "a bread" 206 | elif item == ITEM['good_food']: 207 | return "a cheese" 208 | elif item == ITEM['better_food']: 209 | return "a fruit" 210 | elif item == ITEM['excellent_food']: 211 | return "a chocolate" 212 | else: 213 | return "Unknown Item({})".format(item) 214 | 215 | def inv_repr(inv): 216 | 'A string that represents an inventory succinctly' 217 | c = Counter(inv) 218 | fixup = lambda x: ' '.join(x.split()[1:]) 219 | return ', '.join('{} {}'.format(c, fixup(item_repr(i))) for i,c in c.iteritems()) 220 | 221 | 222 | def dmg_repr(damage): 223 | '''Creates a string from a damage code''' 224 | if damage == DMG['fire']: 225 | return 'Fire' 226 | elif damage == DMG['ice']: 227 | return 'Ice' 228 | elif damage == DMG['electricity']: 229 | return 'Electricity' 230 | else: 231 | return 'Unknown Damage Type({})'.format(damage) 232 | 233 | 234 | def sig_repr(signal): 235 | '''Creates a string from a signal code''' 236 | if signal == SIG['red']: 237 | return 'Red' 238 | elif signal == SIG['yellow']: 239 | return 'Yellow' 240 | elif signal == SIG['blue']: 241 | return 'Blue' 242 | elif signal == SIG['purple']: 243 | return 'Purple' 244 | elif signal == SIG['orange']: 245 | return 'Orange' 246 | elif signal == SIG['green']: 247 | return 'Green' 248 | else: 249 | return 'Unknown Signal({})'.format(signal) 250 | 251 | def dna_repr(dna): 252 | 'Represents DNA as blocks of color' 253 | output = [] 254 | for base in dna: 255 | output.append('\033[38;5;0m\033[48;5;{color}m{base}\033[0m'\ 256 | .format(color = base + 1, base = base \ 257 | if base != -1 else '|')) 258 | return ''.join(output) 259 | 260 | 261 | if __name__ == '__main__': 262 | import doctest 263 | doctest.testmod() 264 | -------------------------------------------------------------------------------- /EvoFighters/Creatures.py: -------------------------------------------------------------------------------- 1 | '''Contains Creature class and all genetic related functionality''' 2 | 3 | from random import randint 4 | 5 | import random as rand 6 | import cPickle as pickle 7 | from array import array 8 | 9 | import Parsing as P 10 | from EvoFighters.Parsing import ACT, ITEM, SIG, COND 11 | from EvoFighters.Eval import PerformableAction, evaluate 12 | from EvoFighters.Utils import inv_repr, dna_repr 13 | 14 | 15 | sd = None # Set by Arena once the savedata is created 16 | 17 | class Creature(object): 18 | '''Represents a creature''' 19 | # There will be a lot of these creatures, so we'll use slots for memory 20 | # efficiency 21 | __slots__ = ('dna', '_inv', 'energy', 'target', 'generation', 'num_children', 22 | 'signal', 'survived', 'kills', 'instr_used', 'instr_skipped', 23 | 'last_action', 'name', 'is_feeder', 'eaten', 'parents') 24 | 25 | wait_action = PerformableAction(ACT['wait'], None) 26 | count = 0 27 | 28 | def __init__(self, dna = None, parents = None): 29 | if dna is None: 30 | self.dna = array('b', [COND['always'], ACT['mate'], 31 | COND['always'], ACT['flee']]) 32 | else: 33 | self.dna = dna 34 | self._inv = [] 35 | self.energy = 40 36 | self.target = None 37 | self.generation = 0 38 | self.num_children = 0 39 | self.signal = -1 40 | self.survived = 0 41 | self.kills = 0 42 | self.instr_used = 0 43 | self.instr_skipped = 0 44 | self.last_action = Creature.wait_action 45 | self.is_feeder = False 46 | self.eaten = 0 47 | self.parents = parents 48 | self.name = Creature.count 49 | Creature.count += 1 50 | 51 | def __str__(self): 52 | return "<]Creature {0.name}[>".format(self) 53 | 54 | def __repr__(self): 55 | return '''\ 56 | []{0.name:=^76}[] 57 | DNA: {0.fullname} 58 | Inventory: {inv} 59 | Energy: {0.energy} 60 | Generation: {0.generation} 61 | Children: {0.num_children} 62 | Survived: {0.survived} 63 | Kills: {0.kills} 64 | Eaten: {0.eaten} 65 | Parents: {0.parents} 66 | Instructions used/skipped: {0.instr_used}/{0.instr_skipped} 67 | []{bar}[]'''.format(self, 68 | inv = inv_repr(self._inv), 69 | bar = ''.center(76, '=')) 70 | 71 | @property 72 | def copy(self): 73 | '''Performs a value copy of this creature''' 74 | return pickle.loads(pickle.dumps(self, 2)) 75 | 76 | @property 77 | def fullname(self): 78 | '''A compact view of a creature's dna.''' 79 | return dna_repr(self.dna) 80 | 81 | def add_item(self, item): 82 | if item is not None and len(self._inv) + 1 <= sd.settings.max_inv_size: 83 | self._inv.append(item) 84 | 85 | def pop_item(self): 86 | '' 87 | if self._inv: 88 | return self._inv.pop() 89 | else: 90 | return None 91 | 92 | @property 93 | def has_items(self): 94 | 'Whether creature has any items' 95 | return bool(self._inv) 96 | 97 | @property 98 | def top_item(self): 99 | 'What the top item is. Will throw an exception if no items' 100 | return self._inv[-1] 101 | 102 | @property 103 | def dead(self): 104 | '''Whether the creature is dead''' 105 | return self.energy <= 0 or not self.dna 106 | 107 | @property 108 | def alive(self): 109 | '''Whether the creature is alive, defined as "not self.dead"''' 110 | return not self.dead 111 | 112 | def decision_generator(self): 113 | '''Reads dna to decide next course of action. Outputs verbiage''' 114 | parser = P.Parser(self.dna) 115 | while self.alive: 116 | try: 117 | thought = next(parser) 118 | sd.print3("{0.name}'s thought process: \n{thought}", self, 119 | thought = thought.tree) 120 | sd.print3('which required {0.icount} instructions and {0.skipped} ' 121 | 'instructions skipped over', thought) 122 | self.instr_used += thought.icount 123 | self.instr_skipped += thought.skipped 124 | except P.TooMuchThinkingError as tmt: 125 | sd.print1('{.name} was paralyzed by analysis and died', self) 126 | self.energy = 0 127 | yield Creature.wait_action, tmt.icount + tmt.skipped 128 | continue 129 | decision = evaluate(self, thought.tree) 130 | sd.print2('{.name} decided to {}', self, decision) 131 | yield decision, thought.icount + thought.skipped 132 | raise StopIteration() 133 | 134 | def carryout(self, act): 135 | '''Carries out any actions that unlike mating and fighting, don't depend 136 | on what the target's current action is. Nothing will be done if the 137 | creature is dead. Return value is whether the fight should end.''' 138 | if self.dead: 139 | return 140 | #signalling 141 | elif act.typ == ACT['signal']: 142 | sd.print1('{.name} signals with color {sig_repr}', self, sig_repr = act.arg) 143 | self.signal = act.arg 144 | #using an item 145 | elif act.typ == ACT['use']: 146 | if self.has_items: 147 | sd.print1('{.name} uses {item_repr}', self, item_repr = self.top_item) 148 | self.use() 149 | else: 150 | sd.print2("{.name} tries to use an item, but doesn't have one", self) 151 | 152 | # take an item from the other's inventory 153 | elif act.typ == ACT['take']: 154 | if self.target.has_items: 155 | item = self.target.pop_item() 156 | sd.print1("{0.name} takes {item_repr} from {1.name}", self, self.target, 157 | item_repr = item) 158 | self.add_item(item) 159 | else: 160 | sd.print2("{0.name} tries to take an item from {1.name}, "\ 161 | "but there's nothing to take.", self, self.target) 162 | # waiting 163 | elif act.typ == ACT['wait']: 164 | sd.print2('{.name} waits', self) 165 | # defending with no corresponding attack 166 | elif act.typ == ACT['defend']: 167 | sd.print2('{.name} defends', self) 168 | elif act.typ == ACT['flee']: 169 | enemy_roll = randint(0, 100) * (self.target.energy / 40.0) 170 | my_roll = randint(0, 100) * (self.energy / 40.0) 171 | dmg = randint(0,3) 172 | if enemy_roll < my_roll: 173 | sd.print1('{.name} flees the encounter and takes {} damage', self, dmg) 174 | self.energy -= dmg 175 | raise StopIteration() 176 | else: 177 | sd.print1('{.name} tries to flee, but {.name} prevents it', self, self.target) 178 | else: 179 | raise RuntimeError("{0.name} did {1.typ} with magnitude {1.arg}"\ 180 | .format(self, act)) 181 | 182 | def use(self): 183 | 'Uses the top inventory item' 184 | item = self.pop_item() 185 | if item: 186 | if 0 <= item <= len(ITEM): 187 | mult = item + 1 188 | else: 189 | mult = 0 190 | energy_gain = 3 * mult 191 | sd.print2('{.name} gains {} life from {item_repr}', self, energy_gain, 192 | item_repr = item) 193 | self.energy += energy_gain 194 | 195 | 196 | class Feeder(Creature): 197 | '''A pitiful subclass of creature, used only for eating by creatures.''' 198 | 199 | _instance = None #singleton instance 200 | 201 | def __new__(cls, *args, **kwargs): 202 | 'Feeder is a singleton' 203 | if not cls._instance: 204 | cls._instance = super(Feeder, cls).__new__(cls, *args, **kwargs) 205 | cls._instance.dna = None 206 | cls._instance.target = None 207 | cls._instance.generation = 0 208 | cls._instance.num_children = 0 209 | cls._instance.survived = 0 210 | cls._instance.kills = 0 211 | cls._instance.instr_used = 0 212 | cls._instance.instr_skipped = 0 213 | cls._instance.last_action = Creature.wait_action 214 | cls._instance.is_feeder = True 215 | cls._instance.signal = SIG['green'] 216 | cls._instance.name = 'Feeder' 217 | cls._instance.parents = None 218 | cls._instance.eaten = 0 219 | cls._instance.energy = 1 220 | cls._instance._inv = Feeder._getinv() 221 | return cls._instance 222 | 223 | 224 | def __init__(self): 225 | pass 226 | 227 | @staticmethod 228 | def _getinv(): 229 | choices = [i for i in xrange(len(ITEM)) for _ in xrange(len(ITEM) - i)] 230 | return [rand.choice(choices)] 231 | 232 | def __str__(self): 233 | return '[|Feeder|]' 234 | 235 | def decision_generator(self): 236 | '''Dummy decision generator''' 237 | while self.alive: 238 | # always 'wait', and always think about it for more than the max 239 | # number of steps 240 | self.instr_used += 0 241 | self.instr_skipped += sd.settings.max_thinking_steps + 1 242 | yield (PerformableAction(ACT['wait'], None), 243 | sd.settings.max_thinking_steps + 1) 244 | yield StopIteration() 245 | 246 | @property 247 | def dead(self): 248 | '''Also dies if inventory is raided''' 249 | return self.energy <= 0 or not self.has_items 250 | 251 | def carryout(self, act): 252 | '''Never do anything''' 253 | sd.print2('Feeder does nothing') 254 | pass 255 | 256 | def gene_primer(dna): 257 | '''Breaks a dna list into chunks by the terminator -1.''' 258 | chunk = [] 259 | for base in dna: 260 | chunk.append(base) 261 | if base == -1: 262 | yield chunk 263 | chunk = [] 264 | if chunk: 265 | yield chunk 266 | 267 | 268 | def try_to_mate(sd, mating_chance, first_mate, fm_share, second_mate, sm_share): 269 | '''Takes a chance of mating, two creatures to mate, and the relative 270 | proportion of costs each creature must pay, mates two creatures to create a 271 | third.''' 272 | if randint(1,100) > mating_chance or first_mate.dead or second_mate.dead: 273 | return None 274 | if first_mate.is_feeder or second_mate.is_feeder: 275 | sd.print1('{.name} tried to mate with {.name}!', first_mate, second_mate) 276 | if first_mate.is_feeder: 277 | first_mate.energy = 0 278 | if second_mate.is_feeder: 279 | second_mate.energy = 0 280 | return None 281 | sd.print2('Attempting to mate') 282 | 283 | def pay_cost(p, share): 284 | cost = int(round(sd.settings.mating_cost * (share / 100.0))) 285 | while cost > 0: 286 | if p.has_items: 287 | item = p.pop_item() 288 | cost -= (item + 1) * 2 289 | else: 290 | sd.print1('{.name} ran out of items and failed to mate', p) 291 | return False 292 | return True 293 | 294 | if pay_cost(first_mate, fm_share) and pay_cost(second_mate, sm_share): 295 | return mate(first_mate, second_mate) 296 | else: 297 | return None 298 | 299 | 300 | def mate(p1, p2): 301 | '''Takes in two creatures, splices their dna together randomly by chunks, 302 | possibly mutates it, then spits out a new creature. Mutation rate is the 303 | chance that a mutation will occur''' 304 | # chunkify the dna 305 | dna1_primer = gene_primer(p1.dna) 306 | dna2_primer = gene_primer(p2.dna) 307 | child_genes = [] 308 | while True: 309 | gene1 = next(dna1_primer, []) 310 | gene2 = next(dna2_primer, []) 311 | if gene1 == [] and gene2 == []: 312 | break 313 | gene3 = rand.choice([gene1, gene2]) 314 | child_genes.append(gene3) 315 | if rand.uniform(0, 1) < sd.settings.mutation_rate: 316 | mutate(child_genes) 317 | child = Creature(array('b', [base for gene in child_genes for base in gene]), 318 | parents = (p1.name, p2.name)) 319 | child.generation = min(p1.generation, p2.generation) + 1 320 | p1.num_children += 1 321 | p2.num_children += 1 322 | return child 323 | 324 | 325 | def mutate(dna): 326 | '''Mutates the dna on either the genome or gene level''' 327 | if randint(0, int(10000/sd.settings.mutation_rate)) == 0: 328 | genome_level_mutation(dna) 329 | else: 330 | index = randint(0, len(dna) - 1) 331 | sd.print2('mutating gene {}', index) 332 | dna[index] = gene_level_mutation(dna[index]) 333 | 334 | def genome_level_mutation(dna): 335 | '''Mutate the dna on a meta-gene level''' 336 | def _swap(genome): 337 | 'Swap two genes' 338 | length = len(genome) 339 | i1 = randint(0, length - 1) 340 | i2 = randint(0, length - 1) 341 | sd.print2('swapped gene {} and {}', i1, i2) 342 | genome[i1], genome[i2] = genome[i2], genome[i1] 343 | def _double(genome): 344 | 'Doubles a gene' 345 | i = randint(0, len(genome) - 1) 346 | gene = genome[i] 347 | genome.insert(i, gene) 348 | def _delete(genome): 349 | 'Delete a gene' 350 | index = randint(0, len(genome) - 1) 351 | sd.print2('Deleted gene {}', index) 352 | del dna[index] 353 | 354 | rand.choice([_swap, _delete, _double])(dna) 355 | 356 | def gene_level_mutation(gene): 357 | '''Does a mutation on a gene in various different ways''' 358 | def _invert(x): 359 | 'Reverse the order of the bases in a gene' 360 | x.reverse() 361 | sd.print2('reversed gene') 362 | return x 363 | def _delete(_): 364 | 'Delete a gene' 365 | sd.print2('deleted gene') 366 | return [] 367 | def _insert(x): 368 | 'Insert an extra base in the gene' 369 | val = randint(-1, 9) 370 | index = randint(0, len(x) - 1) 371 | x.insert(index, val) 372 | sd.print2('inserted {} at {}', val, index) 373 | return x 374 | def _point(x): 375 | "Increment or decrement a base's value" 376 | val = int(round(rand.gauss(0, 1))) 377 | index = randint(0, len(x) - 1) 378 | new_base = (x[index] + 1 + val) % 11 - 1 379 | sd.print2('changed {} from {} to {}', index, x[index], new_base) 380 | x[index] = new_base 381 | return x 382 | def _swap(x): 383 | 'Swap two bases' 384 | i1 = randint(0, len(x) - 1) 385 | i2 = randint(0, len(x) - 1) 386 | x[i1], x[i2] = x[i2], x[i1] 387 | sd.print2('swapped bases {} and {}', i1, i2) 388 | return x 389 | if not gene: 390 | sd.print3('Mutated an empty gene!') 391 | return gene 392 | return rand.choice([_invert, 393 | _delete, 394 | _insert, 395 | _point, 396 | _swap, 397 | ])(list(gene)) 398 | -------------------------------------------------------------------------------- /EvoFighters/Arena.py: -------------------------------------------------------------------------------- 1 | """The Arena and how the fighters are to mess with each other""" 2 | from __future__ import print_function 3 | 4 | import random as rand 5 | import shlex 6 | from random import randint 7 | from itertools import izip 8 | from contextlib import contextmanager 9 | import operator as op 10 | import sys, os.path, time, cmd 11 | from collections import Counter 12 | from functools import wraps 13 | 14 | from pkg_resources import resource_string 15 | 16 | from EvoFighters.SaveData import SaveData, Settings 17 | from EvoFighters.Parsing import ACT 18 | from EvoFighters.Utils import (progress_bar, term) 19 | from EvoFighters import Creatures, Parsing, Eval 20 | from EvoFighters.Creatures import Creature, Feeder, try_to_mate 21 | 22 | 23 | def encounter(sd, p1, p2): 24 | '''Carries out an encounter between two creatures''' 25 | # these numbers were very carefully tuned to pretty much never go less than 26 | # 10 rounds 27 | max_rounds = abs(int(rand.gauss(200, 30))) 28 | children = [] 29 | sd.print1('Max rounds: {}', max_rounds) 30 | for rounds, (p1act, c1), (p2act, c2) in izip( 31 | xrange(max_rounds), 32 | p1.decision_generator(), 33 | p2.decision_generator()): 34 | sd.print2('Round {}', rounds) 35 | try: 36 | if c1 > c2: 37 | sd.print3('{0.name} is going first', p2) 38 | child = do_round(sd, p1, p1act, p2, p2act) 39 | else: 40 | sd.print3('{0.name} is going first', p1) 41 | child = do_round(sd, p2, p2act, p1, p1act) 42 | except FightOver as fo: 43 | sd.print3('The fight ended before it timed out') 44 | if fo.child is not None: 45 | children.append(fo.child) 46 | break 47 | if child is not None: 48 | children.append(child) 49 | if p1.dead or p2.dead: 50 | break 51 | p1.last_action = p1act 52 | p2.last_action = p2act 53 | else: 54 | # if the rounds timed out, penalty 55 | penalty = randint(1,5) 56 | sd.print1('Time is up!, both combatants take {} damage', penalty) 57 | p1.energy -= penalty 58 | p2.energy -= penalty 59 | def _victory(winner, loser): 60 | sd.print1('{.name} has killed {.name}', winner, loser) 61 | winner.add_item(loser.pop_item()) 62 | if loser.is_feeder: 63 | winner.eaten += 1 64 | winner.energy += randint(0,1) 65 | else: 66 | winner.energy += randint(0, sd.settings.winner_life_bonus) 67 | winner.survived += 1 68 | winner.kills += 1 69 | winner.last_action = Creature.wait_action 70 | if p2.dead and p1.alive: 71 | _victory(p1, p2) 72 | elif p1.dead and p2.alive: 73 | _victory(p2, p1) 74 | elif p1.dead and p2.dead: 75 | sd.print1('Both {0.name} and {1.name} have died.', p1, p2) 76 | else: 77 | if not p2.is_feeder: 78 | p1.survived += 1 79 | if not p1.is_feeder: 80 | p2.survived += 1 81 | p1.last_action = Creature.wait_action 82 | p2.last_action = Creature.wait_action 83 | p1.energy = min(40, p1.energy) 84 | p2.energy = min(40, p2.energy) 85 | return children 86 | 87 | def do_round(sd, p1, p1_act, p2, p2_act): 88 | '''Handles carrying out the decided actions for a single round''' 89 | # convenient short-hands to make code more readable 90 | ATTACKING = 1 91 | DEFENDING = 2 92 | MATING = 3 93 | OTHER = 4 94 | # defaults to OTHER if the key is not present 95 | act_kind = {ACT['attack'] : ATTACKING, 96 | ACT['defend'] : DEFENDING, 97 | ACT['mate'] : MATING, 98 | ACT['signal'] : OTHER, 99 | ACT['use'] : OTHER, 100 | ACT['take'] : OTHER, 101 | ACT['wait'] : OTHER, 102 | ACT['flee'] : OTHER } 103 | 104 | # c2h = chance to hit 105 | # Below indexes: 0=mate_chance, 1=p1_c2h, 2=p2_c2h, 3=dmg1_mult, 106 | # 4=dmg2_mult, 5=p1_share, 6=p2_share 107 | 108 | damage_matrix = { # 0 1 2 3 4 5 6 109 | (ATTACKING, ATTACKING) : ( 0, 75, 75, 50, 50, 0, 0), 110 | (ATTACKING, DEFENDING) : ( 0, 25, 25, 25, 25, 0, 0), 111 | (ATTACKING, MATING) : ( 50, 50, 0, 75, 0, 70, 30), 112 | (ATTACKING, OTHER) : ( 0,100, 0,100, 0, 0, 0), 113 | (DEFENDING, DEFENDING) : ( 0, 0, 0, 0, 0, 0, 0), 114 | (DEFENDING, MATING) : ( 25, 0, 0, 0, 0, 70, 30), 115 | (DEFENDING, OTHER) : ( 0, 0, 0, 0, 0, 0, 0), 116 | (MATING, MATING) : (100, 0, 0, 0, 0, 50, 50), 117 | (MATING, OTHER) : ( 75, 0, 0, 0, 0, 0,100), 118 | (OTHER, OTHER) : ( 0, 0, 0, 0, 0, 0, 0), 119 | # the rest of these are duplicates of the above with swapped order 120 | (DEFENDING, ATTACKING) : ( 0, 25, 25, 25, 25, 0, 0), 121 | (MATING, ATTACKING) : ( 50, 0, 50, 0, 75, 30, 70), 122 | (MATING, DEFENDING) : ( 25, 0, 0, 0, 0, 30, 70), 123 | (OTHER, ATTACKING) : ( 0, 0,100, 0,100, 0, 0), 124 | (OTHER, DEFENDING) : ( 0, 0, 0, 0, 0, 0, 0), 125 | (OTHER, MATING) : ( 75, 0, 0, 0, 0,100, 0), 126 | } 127 | mults = damage_matrix[(act_kind[p1_act.typ], act_kind[p2_act.typ])] 128 | def damage_fun(chance, mult): 129 | '''Takes a "chance to hit" and a "damage multiplier" and returns 130 | damage''' 131 | if randint(1,100) <= chance: 132 | return randint(1, int(round(((mult/100.0) * 6)))) 133 | else: 134 | return 0 135 | p1_dmg = damage_fun(mults[1], mults[3]) 136 | p2_dmg = damage_fun(mults[2], mults[4]) 137 | # TODO: take into account damage type! 138 | if p1_dmg > 0: 139 | sd.print1('{.name} takes {} damage', p2, p1_dmg) 140 | p2.energy -= p1_dmg 141 | if p2_dmg > 0: 142 | sd.print1('{.name} takes {} damage', p1, p2_dmg) 143 | p1.energy -= p2_dmg 144 | # we reverse the order of p1, p2 when calling try_to_mate because paying 145 | # costs first in mating is worse, and in this function p1 is preferred in 146 | # actions that happen to both creatures in order. Conceivably, p2 could die 147 | # without p1 paying any cost at all, even if p2 initiated mating against 148 | # p1's will 149 | child = try_to_mate(sd, mults[0], p2, mults[6], p1, mults[5]) 150 | if child: 151 | sd.print1('{.name} and {.name} have a child named {.name}', p1, p2, child) 152 | if not child.dna: 153 | sd.print1('But it was stillborn as it has no dna.') 154 | child = None 155 | try: 156 | if act_kind[p1_act.typ] == OTHER: 157 | p1.carryout(p1_act) 158 | if act_kind[p2_act.typ] == OTHER: 159 | p2.carryout(p2_act) 160 | except StopIteration: 161 | raise FightOver(child) 162 | sd.print3('{0.name} has {0.energy} life left', p1) 163 | sd.print3('{0.name} has {0.energy} life left', p2) 164 | return child 165 | 166 | class FightOver(StopIteration): 167 | '''Thrown when a fight is over''' 168 | def __init__(self, child): 169 | self.child = child 170 | 171 | def maxencounters(sd): 172 | '''Number of encounters required for a given population based on size''' 173 | return round((len(sd.creatures) ** 3) / (sd.settings.max_pop_size * 1000.0)) 174 | 175 | @contextmanager 176 | def random_encounter(creatures, feeder_count, dead, copy = False): 177 | '''A context manager that handles selecting two random creatures from the 178 | creature list, setting them as targets of each other, and then yielding to 179 | the actual encounter code.''' 180 | c_len = len(creatures) 181 | if c_len < 2: 182 | raise RuntimeError('Not enough creatures.') 183 | 184 | p1_i = rand.randint(0, c_len - 1) 185 | p2_i = rand.randint(0, c_len + feeder_count - 1) 186 | while p1_i == p2_i: 187 | p2_i = rand.randint(0, c_len + feeder_count - 1) 188 | p1 = creatures[p1_i] 189 | if p2_i < c_len: 190 | p2 = creatures[p2_i] 191 | else: 192 | p2 = Feeder() 193 | if copy: 194 | p1 = p1.copy 195 | p2 = p2.copy 196 | p1.energy = 40 197 | p2.energy = 40 198 | p1.target = p2 199 | p2.target = p1 200 | try: 201 | yield p1, p2 202 | finally: 203 | p1.target = None 204 | p2.target = None 205 | if p1.dead and not copy: 206 | creatures.remove(p1) 207 | #dead.append((p1.name, p1.generation, p1.parents)) 208 | if p2.dead and not copy and not p2.is_feeder: 209 | creatures.remove(p2) 210 | #dead.append((p2.name, p2.generation, p2.parents)) 211 | 212 | def simulate(sd): 213 | time_till_save = progress_bar( 214 | '{:4} creatures, {:4} feeders, {:,} encounters total', 215 | lambda: len(sd.creatures), 216 | lambda: sd.feeder_count, 217 | lambda: sd.num_encounters, 218 | ) 219 | timestamp = updatetime = time.time() 220 | try: 221 | while True: 222 | new_time = time.time() 223 | if len(sd.creatures) < 2: 224 | raise RuntimeError('Not enough creatures') 225 | if new_time - timestamp > sd.settings.save_interval: 226 | print('\nCurrently', len(sd.creatures), 'creatures alive.') 227 | sd.save() 228 | timestamp = time.time() 229 | print() 230 | if new_time - updatetime > (1.0 / sd.settings.fps): 231 | time_till_save.send( 232 | (time.time() - timestamp) / sd.settings.save_interval) 233 | updatetime = time.time() 234 | total_beings = len(sd.creatures) + sd.feeder_count 235 | if total_beings < sd.settings.max_pop_size : 236 | sd.feeder_count += 1 237 | 238 | with random_encounter(sd.creatures, sd.feeder_count, sd.dead) as (p1, p2): 239 | sd.print1('{.name} encounters {.name} in the wild', p1, p2) 240 | sd.creatures.extend(encounter(sd, p1, p2)) 241 | if not (p2.is_feeder or p1.is_feeder): 242 | sd.num_encounters += 1 243 | elif p2.dead: 244 | sd.feeder_count -= 1 245 | 246 | 247 | except KeyboardInterrupt: 248 | print('\nOk, let me just save real quick...') 249 | finally: 250 | sd.save() 251 | if len(sd.creatures) < 2: 252 | print('You need at least two creatures in your population to have '\ 253 | 'an encounter. Unfortunately, this means the end for your ' \ 254 | 'population.') 255 | if len(sd.creatures) == 1: 256 | print('Here is the last of its kind:') 257 | print(repr(sd.creatures.pop())) 258 | 259 | 260 | 261 | def do_random_encounter(sd, creatures): 262 | '''Runs a fight between two random creatures at the current verbosity''' 263 | with random_encounter(creatures, 0, [], copy = True) as (p1, p2): 264 | print(repr(p1)) 265 | print(repr(p2)) 266 | sd.print1('{0.name} is fighting {1.name}', p1, p2) 267 | encounter(sd, p1, p2) 268 | 269 | def preparse(*argspec): 270 | '''Parses input to cmd methods''' 271 | def _decorator(func): 272 | @wraps(func) 273 | def _wrapped(self, arg): 274 | try: 275 | args = [f(a) for f, a in zip(argspec, shlex.split(arg))] 276 | return func(*args) 277 | except ValueError: 278 | print('Bad args!') 279 | return _wrapped 280 | return _decorator 281 | 282 | class EvoCmd(cmd.Cmd): 283 | '''Command line processor for EvoFighters''' 284 | 285 | prompt = 'EvoFighters >>>> ' 286 | 287 | def __init__(self, sd): 288 | cmd.Cmd.__init__(self) 289 | self.sd = sd 290 | 291 | @property 292 | def intro(self): 293 | if term.width >= 90: 294 | width = 90 295 | elif term.width >= 79: 296 | width = 79 297 | elif term.width >= 51: 298 | width = 51 299 | else: 300 | return 'EvoFighters (You may want to widen your terminal)' 301 | 302 | return resource_string(__name__, 'banner_{}.ascii'.format(width)) 303 | 304 | def default(self, line): 305 | print("Sorry, that isn't a recognized command") 306 | 307 | def doc_header(self): 308 | return 'Available commands:' 309 | 310 | def do_simulate(self, arg): 311 | simulate(self.sd) 312 | 313 | def do_show(self, arg): 314 | '''Shows various things''' 315 | args = arg.split() 316 | if args[0] in (c.name for c in self.sd.creatures): 317 | print(repr(next((c for c in self.sd.creatures 318 | if c.name == args[0]), None))) 319 | elif args[0] == 'verbosity': 320 | print('verbosity = {}'.format(self.sd.settings.verbosity)) 321 | elif args[0] == 'random': 322 | print(repr(rand.choice(self.sd.creatures))) 323 | elif args[0] == 'max': 324 | try: 325 | print(repr(max(self.sd.creatures, 326 | key = op.attrgetter(args[1])))) 327 | except: 328 | print("Couldn't get the maximum of that") 329 | elif args[0] == 'min': 330 | try: 331 | print(repr(min(self.sd.creatures, 332 | key = op.attrgetter(args[1])))) 333 | except: 334 | print("Couldn't get the minimum of that.") 335 | elif arg == 'most skillful': 336 | def _skill(c): 337 | 'Determine skill number' 338 | if c.survived > 0: 339 | return (float(c.kills ** 2) / c.survived) 340 | else: 341 | return 0 342 | print(repr(max(self.sd.creatures, key = _skill))) 343 | else: 344 | print("Not sure what you want me to show you :(") 345 | 346 | def do_count(self, arg): 347 | '''Count either creatures or feeders''' 348 | if arg == 'creatures': 349 | num = len(self.sd.creatures) 350 | print('There are {} creatures'.format(num)) 351 | elif arg == 'feeders': 352 | num = self.sd.feeder_count 353 | print('There are {} feeders.'.format(num)) 354 | else: 355 | try: 356 | counter = Counter(getattr(c,arg) for c in self.sd.creatures) 357 | for val, count in counter.most_common(): 358 | print('{val} : {count}'.format(val = val, count = count)) 359 | except: 360 | print 361 | print("Not sure what we're counting here") 362 | 363 | def do_set(self, arg): 364 | '''Set a variable''' 365 | try: 366 | key, value = arg.split() 367 | self.sd.settings.set_from_strings([(key, value)]) 368 | except Exception as ex: 369 | print("Didn't work out:", str(ex)) 370 | 371 | def do_fight(self, arg): 372 | '''Watch a fight between two creatures''' 373 | args = arg.split() 374 | if len(args) >= 1: 375 | fighter1 = next((c for c in self.sd.creatures 376 | if c.name == int(args[0])), None) 377 | else: 378 | fighter1 = rand.choice(self.sd.creatures) 379 | if len(args) >= 2: 380 | fighter2 = next((c for c in self.sd.creatures 381 | if c.name == int(args[1])), None) 382 | else: 383 | fighter2 = rand.choice(self.sd.creatures) 384 | 385 | if fighter1 is None or fighter2 is None: 386 | print("Invalid fighter name") 387 | return 388 | 389 | do_random_encounter(self.sd, [fighter1, fighter2]) 390 | 391 | def do_EOF(self, arg): 392 | raise KeyboardInterrupt 393 | 394 | def do_exit(self, arg): 395 | 'Exit evofighters' 396 | raise KeyboardInterrupt 397 | 398 | def do_load(self, arg): 399 | args = shlex.split(arg) 400 | print(repr(args)) 401 | 402 | 403 | def main(): 404 | settings = Settings.from_config() 405 | if os.path.isfile(settings.save_file): 406 | with open(settings.save_file, 'r') as savefile: 407 | try: 408 | sd = SaveData.loadfrom(savefile) 409 | except Exception as e: 410 | print('Invalid save file!', e, file = sys.stderr) 411 | raise 412 | 413 | print('Loaded an existing save file with {gen_size} creatures with '\ 414 | '{num_encounters} encounters under their belt'\ 415 | .format(gen_size=len(sd.creatures), 416 | num_encounters=sd.num_encounters)) 417 | else: 418 | print('No save file found, creating a new generation!') 419 | sd = SaveData( 420 | creatures=[Creature() for i in xrange( 421 | 0, int(settings.max_pop_size * 1.0))], 422 | feeder_count=0, 423 | num_encounters=0, 424 | dead=[], 425 | count=settings.max_pop_size, 426 | settings=settings, 427 | ) 428 | sd.save() 429 | Creatures.sd = Parsing.sd = Eval.sd = sd 430 | try: 431 | EvoCmd(sd).cmdloop() 432 | except KeyboardInterrupt: 433 | print('Bye') 434 | 435 | if __name__ == '__main__': 436 | main() 437 | -------------------------------------------------------------------------------- /evofighters_rust/src/dna.rs: -------------------------------------------------------------------------------- 1 | use std::convert::From; 2 | use std::ops::{Index, IndexMut}; 3 | use std::mem; 4 | use std::hash::Hasher; 5 | use std::slice::Iter; 6 | 7 | use twox_hash::XxHash32; 8 | 9 | use stats::GlobalStatistics; 10 | use rng::RngState; 11 | 12 | #[derive(Debug, Copy, Clone, Serialize, Deserialize)] 13 | pub struct Gene([i8; 5]); 14 | 15 | impl Gene { 16 | pub const STOP_CODON: i8 = -1; 17 | pub const LENGTH: usize = 5; 18 | pub const MAX_MEANINGFUL_VALUE: i8 = 8; 19 | 20 | /// Produces a gene full of all stop codons. Useful for allocation 21 | /// then overwriting 22 | pub fn new() -> Gene { 23 | Gene([ 24 | Gene::STOP_CODON, 25 | Gene::STOP_CODON, 26 | Gene::STOP_CODON, 27 | Gene::STOP_CODON, 28 | Gene::STOP_CODON, 29 | ]) 30 | } 31 | 32 | /// Creates a gene for mating then fleeing. This is the initial gene. 33 | pub fn mate_then_flee() -> Gene { 34 | Gene([ 35 | lex::Condition::Always as i8, 36 | lex::Action::Mate as i8, 37 | Gene::STOP_CODON, 38 | lex::Condition::Always as i8, 39 | lex::Action::Flee as i8, 40 | ]) 41 | } 42 | 43 | pub fn always_wait() -> Gene { 44 | let mut gene = Gene::new(); 45 | gene.0[0] = lex::Condition::Always as i8; 46 | gene.0[1] = lex::Action::Wait as i8; 47 | gene 48 | } 49 | 50 | /// Whether the current Gene codes anything useful 51 | pub fn valid(&self) -> bool { 52 | self.0.iter().any(|&codon| codon != Gene::STOP_CODON) 53 | } 54 | 55 | /// Inversion of valid, to make code read better 56 | pub fn invalid(&self) -> bool { 57 | self.0.iter().all(|&codon| codon == Gene::STOP_CODON) 58 | } 59 | 60 | /// Sets all bases in the gene to the stop codon (making it 61 | /// invalid) 62 | fn clear(&mut self) { 63 | for elem in &mut self.0 { 64 | *elem = Gene::STOP_CODON 65 | } 66 | } 67 | 68 | pub fn iter(&self) -> Iter { 69 | self.0.iter() 70 | } 71 | 72 | pub(super) fn mutate(&mut self, rng: &mut RngState) -> Option { 73 | match rng.rand_range(1, 6) { 74 | 1 => { 75 | // reverse the order of bases in a gene 76 | self.0.reverse(); 77 | debug!("reversed gene"); 78 | None 79 | } 80 | 2 => { 81 | // deleting a gene 82 | self.clear(); 83 | debug!("deleted gene"); 84 | None 85 | } 86 | 3 => { 87 | // Create a new gene, and set one base in it to a random value 88 | let index = rng.rand_range(0, Gene::LENGTH); 89 | let val = rng.rand_range( 90 | Gene::STOP_CODON, 91 | Gene::MAX_MEANINGFUL_VALUE, 92 | ); 93 | debug!( 94 | "created a new gene with base {} at index {}", 95 | val, index 96 | ); 97 | let mut new_gene = Gene::new(); 98 | new_gene.0[index] = val; 99 | Some(new_gene) 100 | } 101 | 4 => { 102 | // increment a base in a gene, modulo the 103 | // max gene value 104 | let inc = rng.rand_range(1, 3); 105 | let index = rng.rand_range(0, Gene::LENGTH); 106 | let new_base = (self.0[index] + 1 + inc) 107 | % (Gene::MAX_MEANINGFUL_VALUE + 2) 108 | - 1; 109 | debug!( 110 | "added {} to base at {} with val {} to get {}", 111 | inc, index, self.0[index], new_base 112 | ); 113 | self.0[index] = new_base; 114 | None 115 | } 116 | 5 => { 117 | // swap two bases in the gene 118 | let i1 = rng.rand_range(0, Gene::LENGTH); 119 | let i2 = rng.rand_range(0, Gene::LENGTH); 120 | self.0.swap(i1, i2); 121 | debug!("swapped bases {} and {}", i1, i2); 122 | None 123 | } 124 | _ => unreachable!(), 125 | } 126 | } 127 | } 128 | 129 | impl Default for Gene { 130 | fn default() -> Self { 131 | Gene::new() 132 | } 133 | } 134 | 135 | impl Index for Gene { 136 | type Output = i8; 137 | 138 | fn index(&self, index: usize) -> &i8 { 139 | &self.0[index] 140 | } 141 | } 142 | 143 | impl IndexMut for Gene { 144 | fn index_mut(&mut self, index: usize) -> &mut i8 { 145 | &mut self.0[index] 146 | } 147 | } 148 | 149 | /// Core DNA data structure 150 | #[derive(Debug, Clone, Serialize, Deserialize)] 151 | pub struct DNA(Vec); 152 | 153 | impl DNA { 154 | /// Produce feeder DNA, which is just stop codons 155 | pub fn feeder() -> DNA { 156 | DNA(vec![Gene::always_wait()]) 157 | } 158 | 159 | /// Produce the default seed DNA, which evaluates to "always mate, 160 | /// always flee" 161 | pub fn seed() -> DNA { 162 | DNA(vec![Gene::mate_then_flee()]) 163 | } 164 | 165 | pub fn len(&self) -> usize { 166 | self.0.len() * Gene::LENGTH 167 | } 168 | 169 | pub fn base_stream(&self, offset: usize) -> DNAIter { 170 | DNAIter::new(self.clone(), offset) 171 | } 172 | 173 | pub fn valid(&self) -> bool { 174 | !self.0.is_empty() && self.0.iter().all(|&gene| gene.valid()) 175 | } 176 | 177 | pub fn combine( 178 | mother: &DNA, 179 | father: &DNA, 180 | rng: &mut RngState, 181 | mutation_rate: f64, 182 | ) -> (DNA, GlobalStatistics) { 183 | let mut m_iter = mother.0.clone().into_iter(); 184 | let mut f_iter = father.0.clone().into_iter(); 185 | let mut child_genes = Vec::new(); 186 | let mut stats = GlobalStatistics::new(); 187 | // TODO: This code is lousy with unnecessary allocations, 188 | // clean this up a bit, use more copies / references if possible 189 | loop { 190 | let gene1 = m_iter.next().unwrap_or_else(Gene::new); 191 | let gene2 = f_iter.next().unwrap_or_else(Gene::new); 192 | if gene1.invalid() && gene2.invalid() { 193 | break; 194 | } 195 | child_genes.push(if rng.rand() { gene1 } else { gene2 }); 196 | } 197 | if rng.rand_range(0.0, 1.0) < mutation_rate { 198 | DNA::mutate(&mut child_genes, rng, mutation_rate); 199 | stats.mutations += 1; 200 | } 201 | (DNA(child_genes), stats) 202 | } 203 | 204 | fn mutate(genes: &mut Vec, rng: &mut RngState, mutation_rate: f64) { 205 | if rng.rand_weighted_bool((10000.0 / mutation_rate) as u32) { 206 | DNA::genome_level_mutation(genes, rng) 207 | } else { 208 | let index = rng.rand_range(0, genes.len()); 209 | let gene_to_mutate = &mut genes[index]; 210 | debug!("Mutating gene {}", index); 211 | if let Some(new_gene) = gene_to_mutate.mutate(rng) { 212 | // Gene mutation produced a new gene, so push it in 213 | // after the current one 214 | genes.insert(index, new_gene) 215 | } 216 | } 217 | } 218 | 219 | fn genome_level_mutation(genome: &mut Vec, rng: &mut RngState) { 220 | match rng.rand_range(1, 4) { 221 | 1 => { 222 | // swap two genes 223 | let i1 = rng.rand_range(0, genome.len()); 224 | let i2 = rng.rand_range(0, genome.len()); 225 | debug!("swapped genes {} and {}", i1, i2); 226 | genome.as_mut_slice().swap(i1, i2); 227 | } 228 | 2 => { 229 | // double a gene 230 | let i = rng.rand_range(0, genome.len()); 231 | let gene = genome[i]; 232 | debug!("doubled gene {}", i); 233 | genome.insert(i, gene); 234 | } 235 | 3 => { 236 | // deletes a gene 237 | let i = rng.rand_range(0, genome.len()); 238 | debug!("Deleted gene {}", i); 239 | // Avoid shifting items if we can 240 | genome.remove(i); 241 | } 242 | _ => panic!("Generated in range 1 - 3! Should not reach."), 243 | } 244 | } 245 | 246 | pub fn hash(&self) -> u32 { 247 | self.seeded_hash(17) 248 | } 249 | 250 | pub fn seeded_hash(&self, seed: u32) -> u32 { 251 | let mut hasher = XxHash32::with_seed(seed); 252 | for gene in &self.0 { 253 | for base in gene.iter() { 254 | hasher.write_i8(*base) 255 | } 256 | } 257 | hasher.finish() as u32 258 | } 259 | } 260 | 261 | impl From> for DNA { 262 | fn from(other: Vec) -> DNA { 263 | let capacity_needed = other.len() 264 | + if other.len() / Gene::LENGTH == 0 { 265 | 0 266 | } else { 267 | 1 268 | }; 269 | let mut newvec: Vec = Vec::with_capacity(capacity_needed); 270 | let mut current_gene = Gene::new(); 271 | let mut current_index = 0; 272 | for item in other { 273 | if current_index >= Gene::LENGTH { 274 | newvec.push(mem::replace(&mut current_gene, Gene::new())); 275 | current_index = 0; 276 | } 277 | current_gene[current_index] = item; 278 | current_index += 1; 279 | } 280 | newvec.push(current_gene); 281 | DNA(newvec) 282 | } 283 | } 284 | 285 | #[derive(Debug, Clone)] 286 | pub struct DNAIter { 287 | dna: Vec, 288 | offset: usize, 289 | dna_len: usize, 290 | } 291 | 292 | impl DNAIter { 293 | fn new(dna: DNA, offset: usize) -> DNAIter { 294 | let len = dna.len(); 295 | DNAIter { 296 | dna: dna.0, 297 | offset: offset % len, 298 | dna_len: len, 299 | } 300 | } 301 | 302 | pub fn offset(&self) -> usize { 303 | self.offset 304 | } 305 | } 306 | 307 | impl Iterator for DNAIter { 308 | type Item = i8; 309 | fn next(&mut self) -> Option { 310 | let gene_offset = self.offset / Gene::LENGTH; 311 | let codon_offset = self.offset % Gene::LENGTH; 312 | let ret = Some(self.dna[gene_offset].0[codon_offset]); 313 | self.offset = (self.offset + 1) % self.dna_len; 314 | ret 315 | } 316 | 317 | fn size_hint(&self) -> (usize, Option) { 318 | (self.dna_len, None) 319 | } 320 | } 321 | 322 | /// The lexical module is for raw enums that are used as tokens from 323 | /// the `DNA`, and are fed to the parser. 324 | pub mod lex { 325 | use std::fmt; 326 | 327 | enum_from_primitive! { 328 | #[derive(Ord, PartialOrd, Eq, PartialEq, Debug, Copy, Clone)] 329 | /// Conditions are parsed from `DNA` and specify a test to do 330 | /// at fight time 331 | pub enum Condition { 332 | /// Always do the specified action 333 | Always, 334 | /// Do the specified action if the target value is in a 335 | /// particular range 336 | InRange, 337 | /// Do the specified action if the target value is less 338 | /// than another value 339 | LessThan, 340 | /// Do the specified action if the target value is greater 341 | /// than another value 342 | GreaterThan, 343 | /// Do the specified action if the target value is equal 344 | /// to another value 345 | EqualTo, 346 | /// Do the specified action if the target value is not 347 | /// equal to another value 348 | NotEqualTo, 349 | /// Do the specified action if my last action is the specified value 350 | MyLastAction, 351 | /// Do the specified action if the other fighter's last action is 352 | /// the specified value 353 | OtherLastAction, 354 | // pay attention to Gene::MAX_MEANINGFUL_VALUE if adding items 355 | } 356 | } 357 | 358 | enum_from_primitive! { 359 | #[derive(Ord, PartialOrd, Eq, PartialEq, Debug, Copy, Clone)] 360 | /// Values are parsed from `DNA` and specify how to get a value at fight time 361 | pub enum Value { 362 | /// A literal value is hardcoded in the `DNA` itself 363 | Literal, 364 | /// A random value will be generated each time 365 | Random, 366 | /// An attribute from the current fighter will be used as the value 367 | Me, 368 | /// An attribute from the opponent will be used as the value 369 | Other, 370 | // pay attention to Gene::MAX_MEANINGFUL_VALUE if adding items 371 | } 372 | } 373 | 374 | enum_from_primitive! { 375 | #[derive(Ord, PartialOrd, Eq, PartialEq, Debug, Copy, Clone)] 376 | /// Actions are parsed from `DNA` and specify an action to take 377 | pub enum Action { 378 | /// Subconditions check something and fork into two possible actions to take 379 | Subcondition, 380 | /// Attack the opponent 381 | Attack, 382 | /// Mate with the opponent 383 | Mate, 384 | /// Defend against the opponent (who may or may not be attacking) 385 | Defend, 386 | /// Attempt to eat an item from your inventory 387 | Eat, 388 | /// Signal a color to the opponent 389 | Signal, 390 | /// Attempt to take something from the opponent 391 | Take, 392 | /// Don't do anything 393 | Wait, 394 | /// Attempt to flee the encounter 395 | Flee, 396 | // If adding an action, update Gene::MAX_MEANINGFUL_VALUE to match 397 | } 398 | } 399 | 400 | enum_from_primitive! { 401 | #[derive(Ord, PartialOrd, Eq, PartialEq)] 402 | #[derive(Debug, Copy, Clone, Serialize, Deserialize)] 403 | /// Attributes are parsed from `DNA`. When a `Value` requires looking 404 | /// at a fighter's attributes, this decides which one is selected 405 | pub enum Attribute { 406 | /// the value of the fighter's energy 407 | Energy, 408 | /// The value of the signal the fighter is signalling 409 | Signal, 410 | /// The value of the generation the fighter belongs to 411 | Generation, 412 | /// The number of kills the fighter has 413 | Kills, 414 | /// The number of fights the fighter has survived 415 | Survived, 416 | /// The number of children the fighter has sired 417 | NumChildren, 418 | /// The value of the top item in the fighter's inventory 419 | TopItem, 420 | // pay attention to Gene::MAX_MEANINGFUL_VALUE if adding items 421 | } 422 | } 423 | 424 | impl fmt::Display for Attribute { 425 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 426 | match *self { 427 | Attribute::Energy => write!(f, "energy"), 428 | Attribute::Signal => write!(f, "signal"), 429 | Attribute::Generation => write!(f, "generation"), 430 | Attribute::Kills => write!(f, "kills"), 431 | Attribute::Survived => write!(f, "encounters survived"), 432 | Attribute::NumChildren => write!(f, "number of children"), 433 | Attribute::TopItem => write!(f, "top inventory item"), 434 | } 435 | } 436 | } 437 | 438 | enum_from_primitive! { 439 | #[derive(Ord, PartialOrd, Eq, PartialEq)] 440 | #[derive(Debug, Copy, Clone, Serialize, Deserialize)] 441 | /// Parsed from `DNA`, this represents the value of an item in the inventory 442 | pub enum Item { 443 | Food = 1, 444 | GoodFood, 445 | BetterFood, 446 | ExcellentFood, 447 | // pay attention to Gene::MAX_MEANINGFUL_VALUE if adding items 448 | } 449 | } 450 | 451 | enum_from_primitive! { 452 | #[derive(Ord, PartialOrd, Eq, PartialEq)] 453 | #[derive(Debug, Copy, Clone, Serialize, Deserialize)] 454 | /// Parsed from `DNA`, this represents the color of a signal 455 | pub enum Signal { 456 | Red = 1, 457 | Yellow, 458 | Blue, 459 | Purple, 460 | Orange, 461 | Green, 462 | // pay attention to Gene::MAX_MEANINGFUL_VALUE if adding items 463 | } 464 | } 465 | 466 | enum_from_primitive! { 467 | #[derive(Ord, PartialOrd, Eq, PartialEq)] 468 | #[derive(Debug, Copy, Clone, Serialize, Deserialize)] 469 | /// Parsed from `DNA`, this represents a damage type 470 | pub enum DamageType { 471 | /// Fire damage 472 | Fire, 473 | /// Ice damage 474 | Ice, 475 | /// Electricity damage 476 | Electricity, 477 | // pay attention to Gene::MAX_MEANINGFUL_VALUE if adding items 478 | } 479 | } 480 | } 481 | 482 | /// The `ast` module is structured trees of conditions and actions 483 | /// that need to be evaluated at fight time in order to determine 484 | /// which action the fighter should take. Unlike the `lex` module, 485 | /// these are not simply tokens. 486 | pub mod ast { 487 | use std::fmt; 488 | use dna::lex; 489 | 490 | #[derive(PartialEq, Eq, Debug, Copy, Clone, Serialize, Deserialize)] 491 | pub enum BinOp { 492 | LT, 493 | GT, 494 | EQ, 495 | NE, 496 | } 497 | 498 | impl fmt::Display for BinOp { 499 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 500 | match *self { 501 | BinOp::LT => write!(f, "less than"), 502 | BinOp::GT => write!(f, "greater than"), 503 | BinOp::EQ => write!(f, "equal to"), 504 | BinOp::NE => write!(f, "not equal to"), 505 | } 506 | } 507 | } 508 | 509 | #[derive(PartialEq, Eq, Debug, Copy, Clone, Serialize, Deserialize)] 510 | pub enum ActorType { 511 | Me, 512 | Other, 513 | } 514 | 515 | #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] 516 | pub enum Condition { 517 | Always(Action), 518 | RangeCompare { 519 | value: Value, 520 | bound_a: Value, 521 | bound_b: Value, 522 | affirmed: Action, 523 | denied: Action, 524 | }, 525 | BinCompare { 526 | operation: BinOp, 527 | lhs: Value, 528 | rhs: Value, 529 | affirmed: Action, 530 | denied: Action, 531 | }, 532 | ActionCompare { 533 | actor_type: ActorType, 534 | action: Action, 535 | affirmed: Action, 536 | denied: Action, 537 | }, 538 | } 539 | 540 | #[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)] 541 | pub enum Value { 542 | Literal(u8), 543 | Random, 544 | Me(lex::Attribute), 545 | Other(lex::Attribute), 546 | } 547 | 548 | impl fmt::Display for Value { 549 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 550 | match *self { 551 | Value::Literal(lit) => write!(f, "{}", lit), 552 | Value::Random => write!(f, "a random number"), 553 | Value::Me(ref attr) => write!(f, "my {}", attr), 554 | Value::Other(ref attr) => write!(f, "my target's {}", attr), 555 | } 556 | } 557 | } 558 | 559 | #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] 560 | pub enum Action { 561 | Subcondition(Box), 562 | Attack(lex::DamageType), 563 | Defend(lex::DamageType), 564 | Signal(lex::Signal), 565 | Eat, 566 | Take, 567 | Mate, 568 | Wait, 569 | Flee, 570 | } 571 | } 572 | -------------------------------------------------------------------------------- /evofighters_rust/Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "ansi_term" 3 | version = "0.10.2" 4 | source = "registry+https://github.com/rust-lang/crates.io-index" 5 | 6 | [[package]] 7 | name = "atty" 8 | version = "0.2.6" 9 | source = "registry+https://github.com/rust-lang/crates.io-index" 10 | dependencies = [ 11 | "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", 12 | "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 13 | "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 14 | ] 15 | 16 | [[package]] 17 | name = "bitflags" 18 | version = "1.0.1" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | 21 | [[package]] 22 | name = "cc" 23 | version = "1.0.4" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | 26 | [[package]] 27 | name = "cfg-if" 28 | version = "0.1.2" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | 31 | [[package]] 32 | name = "clap" 33 | version = "2.30.0" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | dependencies = [ 36 | "ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", 37 | "atty 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", 38 | "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 39 | "strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 40 | "textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", 41 | "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 42 | "vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 43 | ] 44 | 45 | [[package]] 46 | name = "derive_builder" 47 | version = "0.5.1" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | dependencies = [ 50 | "derive_builder_core 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 51 | "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", 52 | "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", 53 | ] 54 | 55 | [[package]] 56 | name = "derive_builder_core" 57 | version = "0.2.0" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | dependencies = [ 60 | "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", 61 | "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", 62 | ] 63 | 64 | [[package]] 65 | name = "dtoa" 66 | version = "0.4.2" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | 69 | [[package]] 70 | name = "enum_primitive" 71 | version = "0.1.1" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | dependencies = [ 74 | "num-traits 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 75 | ] 76 | 77 | [[package]] 78 | name = "evofighters_rust" 79 | version = "0.0.1" 80 | dependencies = [ 81 | "clap 2.30.0 (registry+https://github.com/rust-lang/crates.io-index)", 82 | "derive_builder 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 83 | "enum_primitive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 84 | "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 85 | "num 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", 86 | "num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 87 | "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 88 | "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", 89 | "serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", 90 | "serde_json 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", 91 | "time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", 92 | "twox-hash 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 93 | "xz2 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 94 | ] 95 | 96 | [[package]] 97 | name = "filetime" 98 | version = "0.1.15" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | dependencies = [ 101 | "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 102 | "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", 103 | "redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", 104 | ] 105 | 106 | [[package]] 107 | name = "fuchsia-zircon" 108 | version = "0.3.3" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | dependencies = [ 111 | "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 112 | "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 113 | ] 114 | 115 | [[package]] 116 | name = "fuchsia-zircon-sys" 117 | version = "0.3.3" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | 120 | [[package]] 121 | name = "itoa" 122 | version = "0.3.4" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | 125 | [[package]] 126 | name = "lazy_static" 127 | version = "1.0.0" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | 130 | [[package]] 131 | name = "libc" 132 | version = "0.2.36" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | 135 | [[package]] 136 | name = "lzma-sys" 137 | version = "0.1.9" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | dependencies = [ 140 | "cc 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 141 | "filetime 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", 142 | "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", 143 | "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 144 | ] 145 | 146 | [[package]] 147 | name = "num" 148 | version = "0.1.41" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | dependencies = [ 151 | "num-bigint 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", 152 | "num-complex 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", 153 | "num-integer 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", 154 | "num-iter 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)", 155 | "num-rational 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", 156 | "num-traits 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 157 | ] 158 | 159 | [[package]] 160 | name = "num-bigint" 161 | version = "0.1.41" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | dependencies = [ 164 | "num-integer 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", 165 | "num-traits 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 166 | "rand 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)", 167 | "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", 168 | ] 169 | 170 | [[package]] 171 | name = "num-complex" 172 | version = "0.1.41" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | dependencies = [ 175 | "num-traits 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 176 | "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", 177 | ] 178 | 179 | [[package]] 180 | name = "num-integer" 181 | version = "0.1.35" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | dependencies = [ 184 | "num-traits 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 185 | ] 186 | 187 | [[package]] 188 | name = "num-iter" 189 | version = "0.1.34" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | dependencies = [ 192 | "num-integer 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", 193 | "num-traits 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 194 | ] 195 | 196 | [[package]] 197 | name = "num-rational" 198 | version = "0.1.40" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | dependencies = [ 201 | "num-bigint 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", 202 | "num-integer 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", 203 | "num-traits 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 204 | "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", 205 | ] 206 | 207 | [[package]] 208 | name = "num-traits" 209 | version = "0.1.42" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | 212 | [[package]] 213 | name = "num_cpus" 214 | version = "1.8.0" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | dependencies = [ 217 | "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", 218 | ] 219 | 220 | [[package]] 221 | name = "pkg-config" 222 | version = "0.3.9" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | 225 | [[package]] 226 | name = "quote" 227 | version = "0.3.15" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | 230 | [[package]] 231 | name = "rand" 232 | version = "0.3.20" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | dependencies = [ 235 | "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 236 | "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", 237 | ] 238 | 239 | [[package]] 240 | name = "rand" 241 | version = "0.4.2" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | dependencies = [ 244 | "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 245 | "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", 246 | "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 247 | ] 248 | 249 | [[package]] 250 | name = "redox_syscall" 251 | version = "0.1.37" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | 254 | [[package]] 255 | name = "redox_termios" 256 | version = "0.1.1" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | dependencies = [ 259 | "redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", 260 | ] 261 | 262 | [[package]] 263 | name = "rustc-serialize" 264 | version = "0.3.24" 265 | source = "registry+https://github.com/rust-lang/crates.io-index" 266 | 267 | [[package]] 268 | name = "serde" 269 | version = "1.0.27" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | 272 | [[package]] 273 | name = "serde_derive" 274 | version = "1.0.27" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | dependencies = [ 277 | "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", 278 | "serde_derive_internals 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)", 279 | "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", 280 | ] 281 | 282 | [[package]] 283 | name = "serde_derive_internals" 284 | version = "0.19.0" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | dependencies = [ 287 | "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", 288 | "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", 289 | ] 290 | 291 | [[package]] 292 | name = "serde_json" 293 | version = "1.0.9" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | dependencies = [ 296 | "dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 297 | "itoa 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 298 | "num-traits 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 299 | "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", 300 | ] 301 | 302 | [[package]] 303 | name = "strsim" 304 | version = "0.7.0" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | 307 | [[package]] 308 | name = "syn" 309 | version = "0.11.11" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | dependencies = [ 312 | "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", 313 | "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", 314 | "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 315 | ] 316 | 317 | [[package]] 318 | name = "synom" 319 | version = "0.11.3" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | dependencies = [ 322 | "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 323 | ] 324 | 325 | [[package]] 326 | name = "termion" 327 | version = "1.5.1" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | dependencies = [ 330 | "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", 331 | "redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", 332 | "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 333 | ] 334 | 335 | [[package]] 336 | name = "textwrap" 337 | version = "0.9.0" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | dependencies = [ 340 | "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 341 | ] 342 | 343 | [[package]] 344 | name = "time" 345 | version = "0.1.39" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | dependencies = [ 348 | "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", 349 | "redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", 350 | "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 351 | ] 352 | 353 | [[package]] 354 | name = "twox-hash" 355 | version = "1.1.0" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | dependencies = [ 358 | "rand 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)", 359 | ] 360 | 361 | [[package]] 362 | name = "unicode-width" 363 | version = "0.1.4" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | 366 | [[package]] 367 | name = "unicode-xid" 368 | version = "0.0.4" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | 371 | [[package]] 372 | name = "vec_map" 373 | version = "0.8.0" 374 | source = "registry+https://github.com/rust-lang/crates.io-index" 375 | 376 | [[package]] 377 | name = "winapi" 378 | version = "0.3.4" 379 | source = "registry+https://github.com/rust-lang/crates.io-index" 380 | dependencies = [ 381 | "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 382 | "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 383 | ] 384 | 385 | [[package]] 386 | name = "winapi-i686-pc-windows-gnu" 387 | version = "0.4.0" 388 | source = "registry+https://github.com/rust-lang/crates.io-index" 389 | 390 | [[package]] 391 | name = "winapi-x86_64-pc-windows-gnu" 392 | version = "0.4.0" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | 395 | [[package]] 396 | name = "xz2" 397 | version = "0.1.4" 398 | source = "registry+https://github.com/rust-lang/crates.io-index" 399 | dependencies = [ 400 | "lzma-sys 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 401 | ] 402 | 403 | [metadata] 404 | "checksum ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6b3568b48b7cefa6b8ce125f9bb4989e52fbcc29ebea88df04cc7c5f12f70455" 405 | "checksum atty 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "8352656fd42c30a0c3c89d26dea01e3b77c0ab2af18230835c15e2e13cd51859" 406 | "checksum bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3c30d3802dfb7281680d6285f2ccdaa8c2d8fee41f93805dba5c4cf50dc23cf" 407 | "checksum cc 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "deaf9ec656256bb25b404c51ef50097207b9cbb29c933d31f92cae5a8a0ffee0" 408 | "checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de" 409 | "checksum clap 2.30.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1c07b9257a00f3fc93b7f3c417fc15607ec7a56823bc2c37ec744e266387de5b" 410 | "checksum derive_builder 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c998e6ab02a828dd9735c18f154e14100e674ed08cb4e1938f0e4177543f439" 411 | "checksum derive_builder_core 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "735e24ee9e5fa8e16b86da5007856e97d592e11867e45d76e0c0d0a164a0b757" 412 | "checksum dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "09c3753c3db574d215cba4ea76018483895d7bff25a31b49ba45db21c48e50ab" 413 | "checksum enum_primitive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "be4551092f4d519593039259a9ed8daedf0da12e5109c5280338073eaeb81180" 414 | "checksum filetime 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "714653f3e34871534de23771ac7b26e999651a0a228f47beb324dfdf1dd4b10f" 415 | "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" 416 | "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" 417 | "checksum itoa 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8324a32baf01e2ae060e9de58ed0bc2320c9a2833491ee36cd3b4c414de4db8c" 418 | "checksum lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d" 419 | "checksum libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)" = "1e5d97d6708edaa407429faa671b942dc0f2727222fb6b6539bf1db936e4b121" 420 | "checksum lzma-sys 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c1b93b78f89e8737dac81837fc8f5521ac162abcba902e1a3db949d55346d1da" 421 | "checksum num 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "cc4083e14b542ea3eb9b5f33ff48bd373a92d78687e74f4cc0a30caeb754f0ca" 422 | "checksum num-bigint 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "bdc1494b5912f088f260b775799468d9b9209ac60885d8186a547a0476289e23" 423 | "checksum num-complex 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "58de7b4bf7cf5dbecb635a5797d489864eadd03b107930cbccf9e0fd7428b47c" 424 | "checksum num-integer 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "d1452e8b06e448a07f0e6ebb0bb1d92b8890eea63288c0b627331d53514d0fba" 425 | "checksum num-iter 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)" = "7485fcc84f85b4ecd0ea527b14189281cf27d60e583ae65ebc9c088b13dffe01" 426 | "checksum num-rational 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "0c7cb72a95250d8a370105c828f388932373e0e94414919891a0f945222310fe" 427 | "checksum num-traits 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "9936036cc70fe4a8b2d338ab665900323290efb03983c86cbe235ae800ad8017" 428 | "checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30" 429 | "checksum pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3a8b4c6b8165cd1a1cd4b9b120978131389f64bdaf456435caa41e630edba903" 430 | "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" 431 | "checksum rand 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)" = "512870020642bb8c221bf68baa1b2573da814f6ccfe5c9699b1c303047abe9b1" 432 | "checksum rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eba5f8cb59cc50ed56be8880a5c7b496bfd9bd26394e176bc67884094145c2c5" 433 | "checksum redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "0d92eecebad22b767915e4d529f89f28ee96dbbf5a4810d2b844373f136417fd" 434 | "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" 435 | "checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" 436 | "checksum serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)" = "db99f3919e20faa51bb2996057f5031d8685019b5a06139b1ce761da671b8526" 437 | "checksum serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)" = "f4ba7591cfe93755e89eeecdbcc668885624829b020050e6aec99c2a03bd3fd0" 438 | "checksum serde_derive_internals 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6e03f1c9530c3fb0a0a5c9b826bdd9246a5921ae995d75f512ac917fc4dd55b5" 439 | "checksum serde_json 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c9db7266c7d63a4c4b7fe8719656ccdd51acf1bed6124b174f933b009fb10bcb" 440 | "checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" 441 | "checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" 442 | "checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" 443 | "checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" 444 | "checksum textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0b59b6b4b44d867f1370ef1bd91bfb262bf07bf0ae65c202ea2fbc16153b693" 445 | "checksum time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "a15375f1df02096fb3317256ce2cee6a1f42fc84ea5ad5fc8c421cfe40c73098" 446 | "checksum twox-hash 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "475352206e7a290c5fccc27624a163e8d0d115f7bb60ca18a64fc9ce056d7435" 447 | "checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f" 448 | "checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" 449 | "checksum vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887b5b631c2ad01628bbbaa7dd4c869f80d3186688f8d0b6f58774fbe324988c" 450 | "checksum winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "04e3bd221fcbe8a271359c04f21a76db7d0c6028862d1bb5512d85e1e2eb5bb3" 451 | "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 452 | "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 453 | "checksum xz2 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "98df591c3504d014dd791d998123ed00a476c7e26dc6b2e873cb55c6ac9e59fa" 454 | -------------------------------------------------------------------------------- /evofighters_rust/src/creatures.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::cmp::{max, min}; 3 | use std::rc::Rc; 4 | 5 | use dna; 6 | use dna::lex; 7 | use eval; 8 | use parsing; 9 | use parsing::Decision; 10 | use arena; 11 | use stats::{CreatureStats, GlobalStatistics}; 12 | use rng::RngState; 13 | use simplify::{cycle_detect, ThoughtCycle}; 14 | 15 | #[derive(Eq, PartialEq, Serialize, Deserialize, Debug, Clone, Copy)] 16 | pub struct CreatureID(u64); 17 | 18 | impl CreatureID { 19 | pub fn feeder() -> CreatureID { 20 | CreatureID(0) 21 | } 22 | 23 | pub fn is_feeder(&self) -> bool { 24 | self.0 == 0 25 | } 26 | 27 | pub(crate) fn parents_to_u32( 28 | (CreatureID(p1), CreatureID(p2)): (CreatureID, CreatureID), 29 | ) -> u32 { 30 | let p1_prime: u32 = (p1 ^ (p1 >> 16)) as u32; 31 | let p2_prime: u32 = (p2 ^ (p2 << 16)) as u32; 32 | p1_prime ^ p2_prime 33 | } 34 | } 35 | 36 | #[derive(Copy, Clone, Serialize, Deserialize, Debug)] 37 | pub struct IDGiver { 38 | next_id_to_give_out: u64, 39 | modulus: u64, 40 | } 41 | 42 | impl IDGiver { 43 | fn new(start: u64, modulus: u64) -> IDGiver { 44 | IDGiver { 45 | next_id_to_give_out: start, 46 | modulus, 47 | } 48 | } 49 | 50 | pub fn unthreaded() -> IDGiver { 51 | IDGiver::new(1, 1) 52 | } 53 | 54 | pub fn per_thread(num_threads: usize) -> Vec { 55 | assert!( 56 | num_threads > 0, 57 | "IDGiver::create must be called with size > 0" 58 | ); 59 | let nt = num_threads as u64; // avoid a ton of casts 60 | // next_id_to_give_out can never be zero, since that's the 61 | // feeder id. 62 | (1..(nt + 1)).map(|i| IDGiver::new(i, nt)).collect() 63 | } 64 | 65 | pub fn into_threads(self, num_threads: usize) -> Vec { 66 | assert!(self.modulus == 1); 67 | let IDGiver { 68 | next_id_to_give_out, 69 | .. 70 | } = self; 71 | let nt = num_threads as u64; 72 | 73 | (0..nt) 74 | .map(|i| IDGiver::new(i + next_id_to_give_out, nt)) 75 | .collect() 76 | } 77 | 78 | pub fn next_creature_id(&mut self) -> CreatureID { 79 | let id = self.next_id_to_give_out; 80 | self.next_id_to_give_out += self.modulus; 81 | CreatureID(id) 82 | } 83 | } 84 | 85 | #[derive(Debug, Clone, Serialize)] 86 | pub struct Creature { 87 | pub id: CreatureID, 88 | pub generation: usize, 89 | pub signal: Option, 90 | pub last_action: eval::PerformableAction, 91 | pub parents: (CreatureID, CreatureID), 92 | pub stats: CreatureStats, 93 | dna: dna::DNA, 94 | inv: Vec, 95 | energy: usize, 96 | #[serde(skip)] 97 | thought_cycle: ThoughtCycle, 98 | } 99 | 100 | impl fmt::Display for Creature { 101 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 102 | if self.is_feeder() { 103 | write!(f, "[Feeder]") 104 | } else { 105 | write!(f, "[Creature {}]", self.id.0) 106 | } 107 | } 108 | } 109 | 110 | impl Creature { 111 | const MAX_ENERGY: usize = 40; 112 | const MAX_INV_SIZE: usize = 3; 113 | const MATING_COST: usize = 3; 114 | const WINNER_LIFE_BONUS: usize = 5; 115 | 116 | fn new( 117 | id: CreatureID, 118 | dna: dna::DNA, 119 | generation: usize, 120 | parents: (CreatureID, CreatureID), 121 | ) -> Result { 122 | let thought_cycle = cycle_detect(&dna)?; 123 | Ok(Creature { 124 | dna: dna, 125 | inv: Vec::with_capacity(Creature::MAX_INV_SIZE), 126 | energy: Creature::MAX_ENERGY, 127 | thought_cycle, 128 | generation: generation, 129 | signal: None, 130 | last_action: eval::PerformableAction::NoAction, 131 | id: id, 132 | parents: parents, 133 | stats: CreatureStats::default(), 134 | }) 135 | } 136 | 137 | pub fn seed_creature(id: CreatureID) -> Creature { 138 | let dna = dna::DNA::seed(); 139 | // We know the seed dna is valid, so unwrapping 140 | let thought_cycle = cycle_detect(&dna).unwrap(); 141 | Creature { 142 | inv: Vec::with_capacity(Creature::MAX_INV_SIZE), 143 | energy: Creature::MAX_ENERGY, 144 | thought_cycle, 145 | dna: dna, 146 | generation: 0, 147 | signal: None, 148 | last_action: eval::PerformableAction::NoAction, 149 | id, 150 | parents: (CreatureID(0), CreatureID(0)), 151 | stats: CreatureStats::default(), 152 | } 153 | } 154 | 155 | pub fn hash(&self) -> u32 { 156 | self.dna 157 | .seeded_hash(CreatureID::parents_to_u32(self.parents)) 158 | } 159 | 160 | pub fn next_decision(&mut self) -> Rc { 161 | self.thought_cycle.next() 162 | } 163 | 164 | pub fn feeder() -> Creature { 165 | let dna = dna::DNA::feeder(); 166 | // We know the feeder dna is fine, so unwrapping 167 | let thought_cycle = cycle_detect(&dna).unwrap(); 168 | Creature { 169 | id: CreatureID::feeder(), 170 | dna, 171 | inv: vec![dna::lex::Item::Food], 172 | energy: 1, 173 | thought_cycle, 174 | generation: 0, 175 | signal: Some(dna::lex::Signal::Green), 176 | last_action: eval::PerformableAction::NoAction, 177 | parents: (CreatureID(0), CreatureID(0)), 178 | stats: CreatureStats::default(), 179 | } 180 | } 181 | 182 | pub fn is_feeder(&self) -> bool { 183 | self.id.is_feeder() 184 | } 185 | 186 | pub fn attr(&self, attr: lex::Attribute) -> usize { 187 | match attr { 188 | lex::Attribute::Energy => self.energy(), 189 | lex::Attribute::Signal => match self.signal { 190 | Some(sig) => sig as usize, 191 | None => 0, 192 | }, 193 | lex::Attribute::Generation => self.generation, 194 | lex::Attribute::Kills => self.stats.kills, 195 | lex::Attribute::Survived => self.stats.survived, 196 | lex::Attribute::NumChildren => self.stats.num_children, 197 | lex::Attribute::TopItem => match self.top_item() { 198 | Some(item) => item as usize, 199 | None => 0, 200 | }, 201 | } 202 | } 203 | 204 | fn has_items(&self) -> bool { 205 | !self.inv.is_empty() 206 | } 207 | 208 | pub fn add_item(&mut self, item: dna::lex::Item) { 209 | if self.inv.len() < Creature::MAX_INV_SIZE { 210 | self.inv.push(item) 211 | } else { 212 | debug!("{} tries to add {:?} but has no more space", self, item); 213 | } 214 | } 215 | 216 | pub fn survived_encounter(&mut self) { 217 | self.stats.survived += 1; 218 | // Want to reset the action at the end of every encounter 219 | self.last_action = eval::PerformableAction::NoAction; 220 | } 221 | 222 | fn set_signal(&mut self, signal: dna::lex::Signal) { 223 | self.signal = Some(signal) 224 | } 225 | 226 | fn pop_item(&mut self) -> Option { 227 | self.inv.pop() 228 | } 229 | 230 | pub fn top_item(&self) -> Option { 231 | if !self.inv.is_empty() { 232 | Some(self.inv[self.inv.len() - 1]) 233 | } else { 234 | None 235 | } 236 | } 237 | 238 | fn eat(&mut self, item: dna::lex::Item) { 239 | let energy_gain = 3 * item as usize; 240 | debug!("{} gains {} life from {:?}", self, energy_gain, item); 241 | self.gain_energy(energy_gain) 242 | } 243 | 244 | pub fn dead(&self) -> bool { 245 | !self.alive() 246 | } 247 | 248 | pub fn alive(&self) -> bool { 249 | self.energy > 0 && (!self.is_feeder() || self.has_items()) 250 | } 251 | 252 | pub fn steal_from(&mut self, other: &mut Creature) { 253 | if let Some(item) = other.pop_item() { 254 | self.add_item(item) 255 | } 256 | } 257 | 258 | pub fn energy(&self) -> usize { 259 | self.energy 260 | } 261 | 262 | pub fn lose_energy(&mut self, amount: usize) { 263 | self.energy = self.energy.saturating_sub(amount) 264 | } 265 | 266 | pub fn gain_energy(&mut self, amount: usize) { 267 | self.energy += amount; 268 | self.energy = min(Creature::MAX_ENERGY, self.energy); 269 | } 270 | 271 | pub fn gain_winner_energy(&mut self, rng: &mut RngState) { 272 | self.gain_energy(rng.rand_range(0, Creature::WINNER_LIFE_BONUS)) 273 | } 274 | 275 | pub fn kill(&mut self) { 276 | self.energy = 0; 277 | } 278 | 279 | pub fn has_eaten(&mut self) { 280 | self.stats.eaten += 1; 281 | } 282 | 283 | pub fn has_killed(&mut self) { 284 | self.stats.kills += 1; 285 | } 286 | 287 | pub fn mate_with( 288 | &mut self, 289 | other: &mut Creature, 290 | id_giver: &mut IDGiver, 291 | rng: &mut RngState, 292 | mutation_rate: f64, 293 | ) -> (Result, GlobalStatistics) { 294 | let (child_dna, stats) = 295 | dna::DNA::combine(&self.dna, &other.dna, rng, mutation_rate); 296 | let maybe_child = Creature::new( 297 | id_giver.next_creature_id(), // id 298 | child_dna, // dna 299 | max(self.generation, other.generation) + 1, // generation 300 | (self.id, other.id), // parents 301 | ); 302 | if maybe_child.is_ok() { 303 | self.stats.num_children += 1; 304 | other.stats.num_children += 1; 305 | } 306 | (maybe_child, stats) 307 | } 308 | 309 | pub fn pay_for_mating(&mut self, share: usize) -> bool { 310 | let mut cost = (Creature::MATING_COST as f64 * (share as f64 / 100.0)) 311 | .round() as isize; 312 | while cost > 0 { 313 | match self.pop_item() { 314 | Some(item) => { 315 | cost -= (item as isize) * 2; 316 | } 317 | None => { 318 | info!("{} ran out of items and failed to mate", self); 319 | return false; 320 | } 321 | } 322 | } 323 | true 324 | } 325 | 326 | pub fn carryout( 327 | &mut self, 328 | other: &mut Creature, 329 | action: eval::PerformableAction, 330 | ) -> arena::FightStatus { 331 | if self.is_feeder() { 332 | debug!("Feeder does nothing"); 333 | return arena::FightStatus::Continue; 334 | } 335 | if self.dead() { 336 | return arena::FightStatus::End; 337 | } 338 | match action { 339 | eval::PerformableAction::Signal(sig) => self.set_signal(sig), 340 | eval::PerformableAction::Eat => match self.pop_item() { 341 | Some(item) => { 342 | info!("{} eats {:?}", self, self.top_item()); 343 | self.eat(item); 344 | } 345 | None => debug!( 346 | "{} tries to eat an item, but \ 347 | doesn't have one", 348 | self 349 | ), 350 | }, 351 | eval::PerformableAction::Take => match other.pop_item() { 352 | Some(item) => { 353 | info!("{} takes {:?} from {}", self, item, other); 354 | self.add_item(item); 355 | } 356 | None => { 357 | debug!( 358 | "{} tries to take an item from {}, \ 359 | but there's nothing to take.", 360 | self, other 361 | ); 362 | } 363 | }, 364 | eval::PerformableAction::Wait => debug!("{} waits", self), 365 | // This is only defending with no corresponding attack 366 | eval::PerformableAction::Defend(dmg) => { 367 | debug!("{} defends with {:?} fruitlessly", self, dmg) 368 | } 369 | eval::PerformableAction::Flee => { 370 | let mut rng = RngState::from_creatures(self, other); 371 | let my_roll = rng.rand_range(0, self.energy); 372 | let other_roll = rng.rand_range(0, other.energy); 373 | let dmg = rng.rand_range(0, 4); 374 | if other_roll < my_roll { 375 | info!( 376 | "{} flees the encounter and takes \ 377 | {} damage", 378 | self, dmg 379 | ); 380 | self.lose_energy(dmg); 381 | return arena::FightStatus::End; 382 | } else { 383 | debug!("{} tries to flee, but {} prevents it", self, other); 384 | } 385 | } 386 | invalid_action => { 387 | panic!("Shouldn't have gotten {:?} here", invalid_action) 388 | } 389 | } 390 | arena::FightStatus::Continue 391 | } 392 | } 393 | 394 | /// Needed because some parts aren't serialized because they can be 395 | /// inferred from other fields 396 | #[derive(Deserialize)] 397 | pub struct DeserializableCreature { 398 | dna: dna::DNA, 399 | inv: Vec, 400 | energy: usize, 401 | generation: usize, 402 | signal: Option, 403 | last_action: eval::PerformableAction, 404 | id: CreatureID, 405 | parents: (CreatureID, CreatureID), 406 | stats: CreatureStats, 407 | } 408 | 409 | impl DeserializableCreature { 410 | pub fn into_creature(self) -> Creature { 411 | let DeserializableCreature { 412 | dna, 413 | inv, 414 | energy, 415 | generation, 416 | signal, 417 | last_action, 418 | id, 419 | parents, 420 | stats, 421 | } = self; 422 | // Invalid creatures are never serialized, so unwrapping 423 | let thought_cycle = cycle_detect(&dna).unwrap(); 424 | Creature { 425 | dna, 426 | inv, 427 | thought_cycle, 428 | energy, 429 | generation, 430 | signal, 431 | last_action, 432 | id, 433 | parents, 434 | stats, 435 | } 436 | } 437 | } 438 | 439 | #[derive(Serialize, Debug)] 440 | pub struct Creatures { 441 | creatures: Vec, 442 | max_pop_size: usize, 443 | feeder_count: usize, 444 | #[serde(skip)] 445 | rng: RngState, 446 | #[serde(skip)] 447 | id_giver: IDGiver, 448 | } 449 | 450 | impl Creatures { 451 | fn from_pieces( 452 | id_giver: IDGiver, 453 | max_pop_size: usize, 454 | rng: RngState, 455 | ) -> Creatures { 456 | let mut idgv = id_giver; 457 | let creatures = (0..max_pop_size) 458 | .map(|_idx| Creature::seed_creature(idgv.next_creature_id())) 459 | .collect(); 460 | Creatures { 461 | creatures, 462 | max_pop_size, 463 | feeder_count: 0, 464 | rng, 465 | id_giver, 466 | } 467 | } 468 | 469 | pub fn new(max_pop_size: usize) -> Creatures { 470 | Creatures::from_pieces( 471 | IDGiver::unthreaded(), 472 | max_pop_size, 473 | RngState::default(), 474 | ) 475 | } 476 | 477 | /// Create a new population, one for each thread 478 | pub fn per_thread( 479 | num_threads: usize, 480 | max_pop_size: usize, 481 | ) -> Vec { 482 | assert!( 483 | max_pop_size % num_threads == 0, 484 | "Max population size must be a multiple of the number of threads" 485 | ); 486 | let pop_per_thread = max_pop_size / num_threads; 487 | let mut rng = RngState::default(); 488 | 489 | IDGiver::per_thread(num_threads) 490 | .into_iter() 491 | .map(|id_giver| { 492 | Creatures::from_pieces(id_giver, pop_per_thread, rng.spawn()) 493 | }) 494 | .collect() 495 | } 496 | 497 | /// Split an existing population that's been loaded from disk into 498 | /// the specified number of threads 499 | pub fn split_by_thread(self, num_threads: usize) -> Vec { 500 | let Creatures { 501 | mut creatures, 502 | max_pop_size, 503 | feeder_count, 504 | mut rng, 505 | id_giver, 506 | } = self; 507 | let pop_rem = max_pop_size % num_threads; 508 | let pop_div = max_pop_size / num_threads; 509 | let creat_rem = creatures.len() % num_threads; 510 | let creat_div = creatures.len() / num_threads; 511 | let feed_rem = feeder_count % num_threads; 512 | let feed_div = feeder_count / num_threads; 513 | id_giver 514 | .into_threads(num_threads) 515 | .into_iter() 516 | .enumerate() 517 | .map(|(i, idg)| Creatures { 518 | max_pop_size: if i >= pop_rem { pop_div } else { pop_div + 1 }, 519 | feeder_count: if i >= feed_rem { 520 | feed_div 521 | } else { 522 | feed_div + 1 523 | }, 524 | rng: rng.spawn(), 525 | id_giver: idg, 526 | creatures: if i >= creat_rem { 527 | creatures.drain(0..creat_div) 528 | } else { 529 | creatures.drain(0..(creat_div + 1)) 530 | }.collect(), 531 | }) 532 | .collect() 533 | } 534 | 535 | pub fn id_giver(&mut self) -> &mut IDGiver { 536 | &mut self.id_giver 537 | } 538 | 539 | pub fn len(&self) -> usize { 540 | self.creatures.len() 541 | } 542 | 543 | pub fn refill_feeders(&mut self) { 544 | if self.len() + self.feeder_count < self.max_pop_size { 545 | self.feeder_count = 546 | self.max_pop_size - (self.feeder_count + self.len()); 547 | } 548 | } 549 | 550 | pub fn feeder_count(&self) -> usize { 551 | self.feeder_count 552 | } 553 | 554 | pub fn random_creature(&mut self) -> Creature { 555 | let index = self.rng.rand_range(0, self.creatures.len()); 556 | self.creatures.swap_remove(index) 557 | } 558 | 559 | pub fn random_creature_or_feeder(&mut self) -> Creature { 560 | let index = self.rng 561 | .rand_range(0, self.creatures.len() + self.feeder_count); 562 | if index < self.creatures.len() { 563 | self.creatures.swap_remove(index) 564 | } else { 565 | self.feeder_count -= 1; 566 | Creature::feeder() 567 | } 568 | } 569 | 570 | pub fn absorb(&mut self, creature: Creature) { 571 | if creature.dead() { 572 | () 573 | } else if creature.is_feeder() { 574 | self.feeder_count += 1; 575 | } else { 576 | self.creatures.push(creature); 577 | } 578 | } 579 | 580 | pub fn absorb_all(&mut self, creats: Vec) { 581 | for creature in creats { 582 | self.absorb(creature) 583 | } 584 | } 585 | 586 | pub fn shuffle(&mut self) { 587 | self.rng.shuffle(self.creatures.as_mut_slice()) 588 | } 589 | } 590 | 591 | #[derive(Deserialize)] 592 | pub struct DeserializableCreatures { 593 | creatures: Vec, 594 | max_pop_size: usize, 595 | feeder_count: usize, 596 | } 597 | 598 | impl DeserializableCreatures { 599 | pub fn into_creatures(self) -> Creatures { 600 | let DeserializableCreatures { 601 | creatures: deserialized_creatures, 602 | max_pop_size, 603 | feeder_count, 604 | } = self; 605 | let max_id = deserialized_creatures 606 | .iter() 607 | .fold(0, |max_id, creature| max(max_id, creature.id.0)); 608 | let creatures = deserialized_creatures 609 | .into_iter() 610 | .map(|x| x.into_creature()) 611 | .collect(); 612 | Creatures { 613 | creatures, 614 | max_pop_size, 615 | feeder_count, 616 | rng: RngState::default(), 617 | id_giver: IDGiver::new(max_id + 1, 1), 618 | } 619 | } 620 | } 621 | 622 | #[cfg(test)] 623 | mod tests { 624 | use super::*; 625 | 626 | #[test] 627 | fn split_by_thread_divides_evenly() { 628 | let id_giver = IDGiver::new(14, 1); 629 | let creats = Creatures { 630 | id_giver, 631 | rng: RngState::default(), 632 | feeder_count: 3, 633 | max_pop_size: 10, 634 | creatures: vec![ 635 | Creature::seed_creature(CreatureID(1)), 636 | Creature::seed_creature(CreatureID(3)), 637 | Creature::seed_creature(CreatureID(5)), 638 | Creature::seed_creature(CreatureID(7)), 639 | Creature::seed_creature(CreatureID(9)), 640 | Creature::seed_creature(CreatureID(11)), 641 | Creature::seed_creature(CreatureID(13)), 642 | ], 643 | }; 644 | let mut res = creats.split_by_thread(3); 645 | assert_eq!(res.len(), 3); 646 | let one = res.remove(0); 647 | let two = res.remove(0); 648 | let three = res.remove(0); 649 | assert_eq!(one.creatures.len(), 3); 650 | assert_eq!(one.creatures[0].id, CreatureID(1)); 651 | assert_eq!(one.creatures[1].id, CreatureID(3)); 652 | assert_eq!(one.creatures[2].id, CreatureID(5)); 653 | assert_eq!(one.max_pop_size, 4); 654 | assert_eq!(one.feeder_count, 1); 655 | assert_eq!(one.id_giver.next_id_to_give_out, 14); 656 | assert_eq!(one.id_giver.modulus, 3); 657 | 658 | assert_eq!(two.creatures.len(), 2); 659 | assert_eq!(two.creatures[0].id, CreatureID(7)); 660 | assert_eq!(two.creatures[1].id, CreatureID(9)); 661 | assert_eq!(two.max_pop_size, 3); 662 | assert_eq!(two.feeder_count, 1); 663 | assert_eq!(two.id_giver.next_id_to_give_out, 15); 664 | assert_eq!(two.id_giver.modulus, 3); 665 | 666 | assert_eq!(three.creatures.len(), 2); 667 | assert_eq!(three.creatures[0].id, CreatureID(11)); 668 | assert_eq!(three.creatures[1].id, CreatureID(13)); 669 | assert_eq!(three.max_pop_size, 3); 670 | assert_eq!(three.feeder_count, 1); 671 | assert_eq!(three.id_giver.next_id_to_give_out, 16); 672 | assert_eq!(three.id_giver.modulus, 3); 673 | } 674 | } 675 | -------------------------------------------------------------------------------- /evofighters_rust/src/arena.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::max; 2 | use std::mem; 3 | use std::time::{Duration, Instant}; 4 | use std::io; 5 | use std::io::Write; 6 | 7 | use creatures::{Creature, Creatures, IDGiver}; 8 | use eval; 9 | use parsing::Decision; 10 | 11 | use saver::{OwnedCheckpoint, Saver, Settings}; 12 | use stats::GlobalStatistics; 13 | use rng::RngState; 14 | 15 | #[derive(Debug, PartialEq, Eq, Copy, Clone)] 16 | pub enum FightStatus { 17 | End, 18 | Continue, 19 | } 20 | 21 | struct CreatureChance { 22 | chance_to_hit: usize, 23 | dmg_multiplier: usize, 24 | mating_share: usize, 25 | } 26 | 27 | impl CreatureChance { 28 | fn damage(&self, rng: &mut RngState) -> usize { 29 | if rng.rand_range(1, 101) <= self.chance_to_hit { 30 | let max_dmg = (self.dmg_multiplier * 6) / 100; 31 | rng.rand_range(1, max(max_dmg, 1)) 32 | } else { 33 | 0 34 | } 35 | } 36 | } 37 | 38 | struct Chances { 39 | chance_to_mate: usize, 40 | p1: CreatureChance, 41 | p2: CreatureChance, 42 | } 43 | 44 | fn damage_matrix( 45 | p1_act: eval::PerformableAction, 46 | p2_act: eval::PerformableAction, 47 | ) -> Chances { 48 | use eval::PerformableAction::{Attack, Defend, Mate}; 49 | // TODO: take into account damage type 50 | match (p1_act, p2_act) { 51 | (Attack(..), Attack(..)) => Chances { 52 | chance_to_mate: 0, 53 | p1: CreatureChance { 54 | chance_to_hit: 75, 55 | dmg_multiplier: 50, 56 | mating_share: 0, 57 | }, 58 | p2: CreatureChance { 59 | chance_to_hit: 75, 60 | dmg_multiplier: 50, 61 | mating_share: 0, 62 | }, 63 | }, 64 | (Attack(..), Defend(..)) | (Defend(..), Attack(..)) => Chances { 65 | chance_to_mate: 0, 66 | p1: CreatureChance { 67 | chance_to_hit: 25, 68 | dmg_multiplier: 25, 69 | mating_share: 0, 70 | }, 71 | p2: CreatureChance { 72 | chance_to_hit: 25, 73 | dmg_multiplier: 25, 74 | mating_share: 0, 75 | }, 76 | }, 77 | (Attack(..), Mate) => Chances { 78 | chance_to_mate: 50, 79 | p1: CreatureChance { 80 | chance_to_hit: 50, 81 | dmg_multiplier: 75, 82 | mating_share: 70, 83 | }, 84 | p2: CreatureChance { 85 | chance_to_hit: 0, 86 | dmg_multiplier: 0, 87 | mating_share: 30, 88 | }, 89 | }, 90 | (Attack(..), _) => Chances { 91 | chance_to_mate: 0, 92 | p1: CreatureChance { 93 | chance_to_hit: 100, 94 | dmg_multiplier: 100, 95 | mating_share: 0, 96 | }, 97 | p2: CreatureChance { 98 | chance_to_hit: 0, 99 | dmg_multiplier: 0, 100 | mating_share: 0, 101 | }, 102 | }, 103 | (Defend(..), Mate) => Chances { 104 | chance_to_mate: 25, 105 | p1: CreatureChance { 106 | chance_to_hit: 0, 107 | dmg_multiplier: 0, 108 | mating_share: 70, 109 | }, 110 | p2: CreatureChance { 111 | chance_to_hit: 0, 112 | dmg_multiplier: 0, 113 | mating_share: 30, 114 | }, 115 | }, 116 | (Mate, Mate) => Chances { 117 | chance_to_mate: 100, 118 | p1: CreatureChance { 119 | chance_to_hit: 0, 120 | dmg_multiplier: 0, 121 | mating_share: 50, 122 | }, 123 | p2: CreatureChance { 124 | chance_to_hit: 0, 125 | dmg_multiplier: 0, 126 | mating_share: 50, 127 | }, 128 | }, 129 | (Mate, Attack(..)) => Chances { 130 | chance_to_mate: 50, 131 | p1: CreatureChance { 132 | chance_to_hit: 0, 133 | dmg_multiplier: 0, 134 | mating_share: 30, 135 | }, 136 | p2: CreatureChance { 137 | chance_to_hit: 50, 138 | dmg_multiplier: 75, 139 | mating_share: 70, 140 | }, 141 | }, 142 | (Mate, Defend(..)) => Chances { 143 | chance_to_mate: 25, 144 | p1: CreatureChance { 145 | chance_to_hit: 0, 146 | dmg_multiplier: 0, 147 | mating_share: 30, 148 | }, 149 | p2: CreatureChance { 150 | chance_to_hit: 0, 151 | dmg_multiplier: 0, 152 | mating_share: 70, 153 | }, 154 | }, 155 | (Mate, _) => Chances { 156 | chance_to_mate: 75, 157 | p1: CreatureChance { 158 | chance_to_hit: 0, 159 | dmg_multiplier: 0, 160 | mating_share: 0, 161 | }, 162 | p2: CreatureChance { 163 | chance_to_hit: 0, 164 | dmg_multiplier: 0, 165 | mating_share: 100, 166 | }, 167 | }, 168 | (_, Attack(..)) => Chances { 169 | chance_to_mate: 0, 170 | p1: CreatureChance { 171 | chance_to_hit: 0, 172 | dmg_multiplier: 0, 173 | mating_share: 0, 174 | }, 175 | p2: CreatureChance { 176 | chance_to_hit: 100, 177 | dmg_multiplier: 100, 178 | mating_share: 0, 179 | }, 180 | }, 181 | (_, Mate) => Chances { 182 | chance_to_mate: 75, 183 | p1: CreatureChance { 184 | chance_to_hit: 0, 185 | dmg_multiplier: 0, 186 | mating_share: 100, 187 | }, 188 | p2: CreatureChance { 189 | chance_to_hit: 0, 190 | dmg_multiplier: 0, 191 | mating_share: 0, 192 | }, 193 | }, 194 | (_, _) => Chances { 195 | chance_to_mate: 0, 196 | p1: CreatureChance { 197 | chance_to_hit: 0, 198 | dmg_multiplier: 0, 199 | mating_share: 0, 200 | }, 201 | p2: CreatureChance { 202 | chance_to_hit: 0, 203 | dmg_multiplier: 0, 204 | mating_share: 0, 205 | }, 206 | }, 207 | } 208 | } 209 | 210 | fn not_attack_mate_defend(act: eval::PerformableAction) -> bool { 211 | use eval::PerformableAction::{Eat, Flee, Signal, Take, Wait}; 212 | match act { 213 | Signal(..) | Eat | Take | Wait | Flee => true, 214 | _ => false, 215 | } 216 | } 217 | 218 | enum SimStatus { 219 | NotStarted, 220 | EverythingRunningFine, 221 | NotEnoughCreatures, 222 | } 223 | 224 | /// Given an instant and how many events the thread slept for, will 225 | /// return how many events the thread should sleep for next time, and 226 | /// a percentage error in the last prediction 227 | struct RateData { 228 | pub events_per_second: u64, 229 | pub events_to_sleep: u64, 230 | pub prediction_error: f64, 231 | pub fps: f64, 232 | } 233 | 234 | impl RateData { 235 | const INITIAL_EVENTS_PER_SECOND_GUESS: u64 = 10_000; 236 | pub fn new( 237 | start_time: Instant, 238 | events_slept: u64, 239 | metric_fps: f64, 240 | ) -> RateData { 241 | let wanted_duration = metric_fps.recip(); 242 | let actual_duration = RateData::duration_to_f64(start_time.elapsed()); 243 | let events_per_second = (events_slept as f64) / actual_duration; 244 | RateData { 245 | events_to_sleep: (wanted_duration * events_per_second) as u64, 246 | events_per_second: events_per_second as u64, 247 | prediction_error: 1.0 - (actual_duration / wanted_duration), 248 | fps: actual_duration.recip(), 249 | } 250 | } 251 | 252 | /// Just some garbage so we have an initial value for these things 253 | pub fn initial() -> RateData { 254 | RateData { 255 | events_to_sleep: RateData::INITIAL_EVENTS_PER_SECOND_GUESS, 256 | events_per_second: 0, 257 | prediction_error: 0.0, 258 | fps: 30.0, 259 | } 260 | } 261 | 262 | /// Takes a standard duration and returns an f64 representing 263 | /// seconds 264 | fn duration_to_f64(dur: Duration) -> f64 { 265 | let seconds = dur.as_secs() as f64; 266 | let subseconds = f64::from(dur.subsec_nanos()) / 1_000_000_000.0; 267 | seconds + subseconds 268 | } 269 | } 270 | 271 | pub struct Arena { 272 | rng: RngState, 273 | population: Creatures, 274 | settings: Settings, 275 | stats: GlobalStatistics, 276 | total_events: u64, 277 | encounters: u64, 278 | events_since_last_print: u64, 279 | events_since_last_save: u64, 280 | rates: RateData, 281 | saver: Saver, 282 | sim_status: SimStatus, 283 | } 284 | 285 | impl Arena { 286 | pub fn new( 287 | population: Creatures, 288 | filename: &str, 289 | settings: Settings, 290 | ) -> Arena { 291 | Arena { 292 | rng: RngState::default(), 293 | population, 294 | settings, 295 | stats: GlobalStatistics::new(), 296 | total_events: 0, 297 | encounters: 0, 298 | events_since_last_print: 0, 299 | events_since_last_save: 0, 300 | rates: RateData::initial(), 301 | saver: Saver::new(filename), 302 | sim_status: SimStatus::NotStarted, 303 | } 304 | } 305 | 306 | pub fn from_checkpoint( 307 | checkpoint: OwnedCheckpoint, 308 | filename: &str, 309 | ) -> Arena { 310 | let OwnedCheckpoint { 311 | creatures, 312 | stats, 313 | settings, 314 | } = checkpoint; 315 | let mut arena = Arena::new(creatures, filename, settings); 316 | arena.stats = stats; 317 | arena 318 | } 319 | 320 | fn maybe_print_status(&mut self, timestamp: Instant) -> Instant { 321 | if self.events_since_last_print == self.rates.events_to_sleep { 322 | self.rates = RateData::new( 323 | timestamp, 324 | self.events_since_last_print, 325 | self.settings.metric_fps, 326 | ); 327 | print!( 328 | "\rCreatures: {creatures}, \ 329 | Feeders: {feeders}, \ 330 | F/C: {feeder_creature:.3}, \ 331 | Mutations: {mutations}, Events: {events}, \ 332 | Born: {born}, Eaten: {eaten}, kills: {kills}, \ 333 | eps: {eps}, err: {err:.1}%, \ 334 | FPS: {fps:.1} ", 335 | creatures = self.population.len(), 336 | feeders = self.population.feeder_count(), 337 | feeder_creature = self.population.feeder_count() as f64 338 | / self.population.len() as f64, 339 | mutations = self.stats.mutations, 340 | events = self.total_events, 341 | born = self.stats.children_born, 342 | eaten = self.stats.feeders_eaten, 343 | kills = self.stats.kills, 344 | eps = self.rates.events_per_second, 345 | err = self.rates.prediction_error * 100.0, 346 | fps = self.rates.fps, 347 | ); 348 | io::stdout().flush(); 349 | self.events_since_last_print = 0; 350 | Instant::now() 351 | } else { 352 | timestamp 353 | } 354 | } 355 | 356 | fn maybe_save(&mut self) { 357 | if self.rates.events_per_second > 0 358 | && self.rates.events_per_second * 30 <= self.events_since_last_save 359 | { 360 | println!( 361 | "\nHit {} out of estimated {} events, one moment...", 362 | self.rates.events_per_second * 30, 363 | self.events_since_last_save, 364 | ); 365 | // TODO: handle failed saves gracefully? 366 | self.saver.save(&self.population, &self.stats).unwrap(); 367 | println!("Saved to file"); 368 | self.events_since_last_save = 0; 369 | } 370 | } 371 | 372 | pub fn simulate(&mut self) { 373 | let mut timestamp = Instant::now(); 374 | self.sim_status = SimStatus::EverythingRunningFine; 375 | while self.population.len() >= 2 { 376 | timestamp = self.maybe_print_status(timestamp); 377 | self.maybe_save(); 378 | self.population.refill_feeders(); 379 | let p1 = self.population.random_creature(); 380 | let p2 = self.population.random_creature_or_feeder(); 381 | 382 | info!("{} encounters {} in the wild", p1, p2); 383 | if !p1.is_feeder() && !p2.is_feeder() { 384 | self.encounters += 1; 385 | } 386 | let mut enc = Encounter::new( 387 | p1, 388 | p2, 389 | self.settings.mutation_rate, 390 | &mut self.rng, 391 | self.population.id_giver(), 392 | ); 393 | enc.encounter(); 394 | let Encounter { 395 | children, 396 | p1, 397 | p2, 398 | stats: enc_stats, 399 | .. 400 | } = enc; 401 | self.stats.absorb(enc_stats); 402 | self.population.absorb_all(children); 403 | self.population.absorb(p1); 404 | self.population.absorb(p2); 405 | 406 | self.total_events += 1; 407 | self.events_since_last_save += 1; 408 | self.events_since_last_print += 1; 409 | } 410 | self.sim_status = SimStatus::NotEnoughCreatures; 411 | match self.sim_status { 412 | SimStatus::NotEnoughCreatures => { 413 | println!( 414 | "You need at least two creatures in your population \ 415 | to have an encounter. Unfortunately, this means the \ 416 | end for your population." 417 | ); 418 | if self.population.len() == 1 { 419 | println!( 420 | "Here is the last of its kind:\n{:?}", 421 | self.population.random_creature() 422 | ) 423 | } 424 | } 425 | _ => unreachable!(), 426 | } 427 | } 428 | } 429 | 430 | pub struct Encounter<'a> { 431 | pub p1: Creature, 432 | pub p2: Creature, 433 | pub stats: GlobalStatistics, 434 | pub children: Vec, 435 | rng: &'a mut RngState, 436 | id_giver: &'a mut IDGiver, 437 | 438 | max_rounds: usize, 439 | mutation_rate: f64, 440 | p1_action: eval::PerformableAction, 441 | p2_action: eval::PerformableAction, 442 | } 443 | 444 | impl<'a> Encounter<'a> { 445 | pub fn new( 446 | p1: Creature, 447 | p2: Creature, 448 | mutation_rate: f64, 449 | rng: &'a mut RngState, 450 | id_giver: &'a mut IDGiver, 451 | ) -> Encounter<'a> { 452 | let max_rounds = rng.normal_sample(200.0, 30.0) as usize; 453 | Encounter { 454 | p1, 455 | p2, 456 | stats: GlobalStatistics::new(), 457 | children: Vec::new(), 458 | rng, 459 | id_giver, 460 | max_rounds, 461 | mutation_rate, 462 | p1_action: eval::PerformableAction::NoAction, 463 | p2_action: eval::PerformableAction::NoAction, 464 | } 465 | } 466 | 467 | fn both_decided( 468 | &mut self, 469 | &Decision { 470 | tree: ref tree1, 471 | icount: i1, 472 | skipped: s1, 473 | .. 474 | }: &Decision, 475 | &Decision { 476 | tree: ref tree2, 477 | icount: i2, 478 | skipped: s2, 479 | .. 480 | }: &Decision, 481 | ) -> FightStatus { 482 | debug!("{} thinks {:?}", self.p1, tree1); 483 | debug!("{} thinks {:?}", self.p2, tree2); 484 | self.p1_action = eval::evaluate(&self.p1, &self.p2, tree1); 485 | self.p2_action = eval::evaluate(&self.p2, &self.p1, tree2); 486 | let (p1_cost, p2_cost) = (i1 + s1, i2 + s2); 487 | if p1_cost < p2_cost { 488 | trace!("{} is going first", self.p1); 489 | trace!("{} intends to {}", self.p1, self.p1_action); 490 | self.do_round() 491 | } else if p2_cost > p1_cost { 492 | trace!("{} is going first", self.p2); 493 | trace!("{} intends to {}", self.p2, self.p2_action); 494 | self.do_swapped_round() 495 | } else if self.rng.rand() { 496 | trace!("{} is going first", self.p1); 497 | self.do_round() 498 | } else { 499 | trace!("{} is going first", self.p2); 500 | self.do_swapped_round() 501 | } 502 | } 503 | 504 | pub fn encounter(&mut self) { 505 | info!("Max rounds: {}", self.max_rounds); 506 | // combine thought tree iterators, limit rounds 507 | let mut fight_timed_out = true; 508 | for round in 0..self.max_rounds { 509 | debug!("Round {}", round); 510 | let p1_decision = self.p1.next_decision(); 511 | let p2_decision = self.p2.next_decision(); 512 | self.stats.rounds += 1; 513 | let fight_status = self.both_decided(&p1_decision, &p2_decision); 514 | if let FightStatus::End = fight_status { 515 | fight_timed_out = false; 516 | break; 517 | } 518 | self.p1.last_action = self.p1_action; 519 | self.p2.last_action = self.p2_action; 520 | } 521 | if fight_timed_out { 522 | let penalty = self.rng.rand_range(1, 7); 523 | info!("Time is up! both combatants take {} damage", penalty); 524 | self.p1.lose_energy(penalty); 525 | self.p2.lose_energy(penalty); 526 | } 527 | if self.p1.alive() && self.p2.dead() { 528 | self.victory(); 529 | } else if self.p1.dead() && self.p2.alive() { 530 | self.swap_players(); 531 | self.victory(); 532 | } else if self.p1.dead() && self.p2.dead() { 533 | info!("Both {} and {} have died.", self.p1, self.p2) 534 | } else { 535 | self.p1.survived_encounter(); 536 | self.p2.survived_encounter(); 537 | } 538 | } 539 | 540 | fn try_mating( 541 | &mut self, 542 | mating_chance: usize, 543 | first_share: usize, 544 | second_share: usize, 545 | ) -> Option { 546 | if self.rng.rand_range(1, 101) > mating_chance || self.p2.dead() 547 | || self.p1.dead() 548 | { 549 | return None; 550 | } 551 | info!("{} tried to mate with {}!", self.p2, self.p1); 552 | if self.p2.is_feeder() || self.p1.is_feeder() { 553 | info!("{} tried to mate with {}", self.p2, self.p1); 554 | // Mating kills the feeder 555 | if self.p2.is_feeder() { 556 | self.p2.kill(); 557 | } 558 | if self.p1.is_feeder() { 559 | self.p1.kill(); 560 | } 561 | return None; 562 | } 563 | debug!("Attempting to mate"); 564 | if self.p2.pay_for_mating(first_share) 565 | && self.p1.pay_for_mating(second_share) 566 | { 567 | debug!("Both paid their debts, so they get to mate"); 568 | self.mate() 569 | } else { 570 | None 571 | } 572 | } 573 | 574 | fn mate(&mut self) -> Option { 575 | let (maybe_child, stats) = self.p1.mate_with( 576 | &mut self.p2, 577 | &mut self.id_giver, 578 | &mut self.rng, 579 | self.mutation_rate, 580 | ); 581 | self.stats.absorb(stats); 582 | match maybe_child { 583 | Err(_) => { 584 | info!("Child didn't live since it had invalid dna."); 585 | None 586 | } 587 | Ok(child) => { 588 | info!( 589 | "{} and {} have a child named {}", 590 | self.p1, self.p2, child 591 | ); 592 | Some(child) 593 | } 594 | } 595 | } 596 | 597 | fn victory(&mut self) { 598 | info!("{} has killed {}", self.p1, self.p2); 599 | self.p1.steal_from(&mut self.p2); 600 | if self.p2.is_feeder() { 601 | self.stats.feeders_eaten += 1; 602 | self.p1.has_eaten(); 603 | self.p1.gain_energy(self.rng.rand_range(0, 1)); 604 | self.p1.last_action = eval::PerformableAction::Wait; 605 | } else { 606 | self.p1.gain_winner_energy(&mut self.rng); 607 | self.p1.has_killed(); 608 | self.stats.kills += 1; 609 | self.p1.survived_encounter(); 610 | } 611 | } 612 | 613 | //swap the players in this encounter, some things are dependent on order 614 | fn swap_players(&mut self) { 615 | mem::swap(&mut self.p1, &mut self.p2); 616 | mem::swap(&mut self.p1_action, &mut self.p2_action); 617 | } 618 | 619 | fn do_swapped_round(&mut self) -> FightStatus { 620 | self.swap_players(); 621 | let result = self.do_round(); 622 | self.swap_players(); 623 | result 624 | } 625 | 626 | fn do_round(&mut self) -> FightStatus { 627 | let chances = damage_matrix(self.p1_action, self.p2_action); 628 | let p1_dmg = chances.p1.damage(&mut self.rng); 629 | let p2_dmg = chances.p2.damage(&mut self.rng); 630 | if p1_dmg > 0 { 631 | info!("{} takes {} damage", self.p2, p1_dmg); 632 | self.p2.lose_energy(p1_dmg) 633 | } 634 | if p2_dmg > 0 { 635 | info!("{} takes {} damage", self.p1, p2_dmg); 636 | self.p2.lose_energy(p2_dmg) 637 | } 638 | 639 | // we reverse the order of p1, p2 when calling try_to_mate because 640 | // paying costs first in mating is worse, and in this function p1 641 | // is preferred in actions that happen to both creatures in 642 | // order. Conceivably, p2 could die without p1 paying any cost at 643 | // all, even if p2 initiated mating against p1's will 644 | let maybe_child = self.try_mating( 645 | chances.chance_to_mate, 646 | chances.p2.mating_share, 647 | chances.p1.mating_share, 648 | ); 649 | if let Some(child) = maybe_child { 650 | self.children.push(child); 651 | self.stats.children_born += 1; 652 | }; 653 | 654 | if not_attack_mate_defend(self.p1_action) { 655 | if let FightStatus::End = 656 | self.p1.carryout(&mut self.p2, self.p1_action) 657 | { 658 | return FightStatus::End; 659 | } 660 | } 661 | if not_attack_mate_defend(self.p2_action) { 662 | if let FightStatus::End = 663 | self.p2.carryout(&mut self.p1, self.p2_action) 664 | { 665 | return FightStatus::End; 666 | } 667 | } 668 | trace!("{} has {} life left", self.p1, self.p1.energy()); 669 | trace!("{} has {} life left", self.p2, self.p2.energy()); 670 | FightStatus::Continue 671 | } 672 | } 673 | --------------------------------------------------------------------------------