diff --git a/src/main.rs b/src/main.rs index 018535c..4150f9f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,7 +11,7 @@ use macroquad::{ prelude::*, ui::{hash, root_ui, widgets, Skin}, }; -use population::Population; +use population::{AutoSwitch, Population}; use world::World; pub const WIDTH: f32 = 800.; @@ -76,8 +76,15 @@ async fn main() { ActivationFunc::Sigmoid, ActivationFunc::Tanh, ]; + let mut auto_switch = Some(AutoSwitch::Best); - let mut pop = Population::new(size as usize, hlayers.clone(), mut_rate, activs[activ]); + let mut pop = Population::new( + size as usize, + auto_switch, + hlayers.clone(), + mut_rate, + activs[activ], + ); let ui_thick = 34.; let nums = &[ @@ -192,15 +199,36 @@ async fn main() { ], }) .background_margin(RectOffset::new(1., 1., 1., 1.)) - .color_hovered(RED) - .color_clicked(BLUE) + .color_hovered(GREEN) + .color_clicked(GREEN) .text_color(WHITE) .text_color_hovered(WHITE) - .text_color_clicked(WHITE) + .text_color_clicked(GREEN) .margin(RectOffset::new(4., 4., 2., 2.)) .color_inactive(WHITE) .build(); + let mut skin3 = skin2.clone(); + skin3.button_style = root_ui() + .style_builder() + .background(Image { + width: 3, + height: 3, + bytes: vec![ + 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 0, 0, 255, 0, + 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, + ], + }) + .background_margin(RectOffset::new(1., 1., 1., 1.)) + .color_hovered(GREEN) + .color_clicked(GREEN) + .text_color(GREEN) + .text_color_hovered(GREEN) + .text_color_clicked(GREEN) + .margin(RectOffset::new(4., 4., 2., 2.)) + .color_inactive(GREEN) + .build(); + root_ui().push_skin(&skin); loop { clear_background(BLACK); @@ -297,6 +325,7 @@ async fn main() { pop = Population::new( size as usize, + auto_switch, hlayers.clone(), mut_rate, activs[activ], @@ -360,6 +389,7 @@ async fn main() { if widgets::Button::new(restart).ui(ui) { pop = Population::new( size as usize, + auto_switch, hlayers.clone(), mut_rate, activs[activ], @@ -371,9 +401,60 @@ async fn main() { hash!(), vec2(ui_width * 0.2, ui_height * 0.8 - 2. * th - 2. * ui_thick), ) - .position(vec2(ui_width * 0.8 - th, ui_height * 0.2 + ui_thick + th)) + .position(vec2(ui_width * 0.6, ui_height * 0.23 + ui_thick + th)) + .ui(ui, |ui| { + ui.label(None, "Track Ship:"); + + if ui.button(None, "Best Alive") { + pop.track_best(false); + } + if ui.button(None, "Current #1") { + pop.track_best(true); + } + if ui.button(None, "LastGen #1") { + pop.track_prev_best(); + } + ui.label(None, " "); + ui.label(None, "Auto Switch"); + ui.label(None, "When Dead to:"); + + if auto_switch == Some(AutoSwitch::Best) { + ui.push_skin(&skin3); + ui.button(None, "Current #1"); + ui.pop_skin(); + } else { + if ui.button(None, "Current #1") { + auto_switch = Some(AutoSwitch::Best); + pop.auto_switch = auto_switch; + } + } + if auto_switch == Some(AutoSwitch::BestAlive) { + ui.push_skin(&skin3); + ui.button(None, "Best Alive"); + ui.pop_skin(); + } else { + if ui.button(None, "Best Alive") { + auto_switch = Some(AutoSwitch::BestAlive); + pop.auto_switch = auto_switch; + } + } + if auto_switch.is_none() { + ui.push_skin(&skin3); + ui.button(None, "Do Nothing"); + ui.pop_skin(); + } else { + if ui.button(None, "Do Nothing") { + auto_switch = None; + pop.auto_switch = auto_switch; + } + } + }); + widgets::Group::new( + hash!(), + vec2(ui_width * 0.2, ui_height * 0.8 - 2. * th - 2. * ui_thick), + ) + .position(vec2(ui_width * 0.8 - th, ui_height * 0.22 + ui_thick + th)) .ui(ui, |ui| { - // ui.input_text(hash!(), "vec2(100., 100.)", &mut xy); ui.label(None, "Hidden Layers"); ui.label(None, "Neurons Config"); @@ -383,6 +464,7 @@ async fn main() { if prev_hlayers != hlayers { pop = Population::new( size as usize, + auto_switch, hlayers.clone(), mut_rate, activs[activ], diff --git a/src/population.rs b/src/population.rs index db78373..ce5ae2a 100644 --- a/src/population.rs +++ b/src/population.rs @@ -6,6 +6,12 @@ use crate::{ HEIGHT, WIDTH, }; +#[derive(Clone, Copy, PartialEq)] +pub enum AutoSwitch { + Best, + BestAlive, +} + #[derive(Default)] pub struct Population { size: usize, @@ -15,16 +21,24 @@ pub struct Population { pub worlds: Vec, pub track: usize, pub hlayers: Vec, + pub auto_switch: Option, } impl Population { - pub fn new(size: usize, hlayers: Vec, mut_rate: f32, activ: ActivationFunc) -> Self { + pub fn new( + size: usize, + auto_switch: Option, + hlayers: Vec, + mut_rate: f32, + activ: ActivationFunc, + ) -> Self { Self { size, hlayers: hlayers.clone(), worlds: (0..size) .map(|_| World::new(Some(hlayers.clone()), Some(mut_rate), Some(activ))) .collect(), + auto_switch, ..Default::default() } } @@ -37,6 +51,14 @@ impl Population { world.update(); } } + if self.worlds[self.track].over { + if let Some(auto_switch) = self.auto_switch { + match auto_switch { + AutoSwitch::Best => self.track_best(true), + AutoSwitch::BestAlive => self.track_best(false), + } + } + } if !alive { self.gen += 1; self.next_gen(); @@ -62,6 +84,25 @@ impl Population { } } } + pub fn track_best(&mut self, can_be_dead: bool) { + self.worlds[self.track].track(false); + let i = self + .worlds + .iter() + .enumerate() + .filter(|(_, w)| !w.over || can_be_dead) + .max_by(|(_, a), (_, b)| a.fitness.total_cmp(&b.fitness)) + .map(|(i, _)| i); + if let Some(i) = i { + self.worlds[i].track(true); + self.track = i; + } + } + pub fn track_prev_best(&mut self) { + self.worlds[self.track].track(false); + self.worlds[0].track(true); + self.track = 0; + } pub fn change_mut(&mut self, mut_rate: f32) { for world in &mut self.worlds { diff --git a/src/world.rs b/src/world.rs index 2908d7c..9105271 100644 --- a/src/world.rs +++ b/src/world.rs @@ -181,7 +181,7 @@ impl World { draw_text_ex( if self.over { "DEAD" } else { "ALIVE" }, -width * 0.5 + 20., - 70., + 55., { let mut p = params.clone(); p.color = if self.over { RED } else { GREEN }; @@ -191,19 +191,25 @@ impl World { draw_text_ex( &format!("Hits: {}", self.score), -width * 0.5 + 20., - 90., + 75., params, ); draw_text_ex( &format!("Fired: {}", self.player.shots), -width * 0.5 + 20., - 110., + 95., params, ); draw_text_ex( &format!("Fitness: {:.2}", self.fitness), -width * 0.5 + 20., - 130., + 115., + params, + ); + draw_text_ex( + &format!("Lifetime: {:.2}", self.player.lifespan as f32 / 60.), + -width * 0.5 + 20., + 135., params, ); let str = &format!("RANK #{}", rank);