diff --git a/fast.png b/fast.png new file mode 100644 index 0000000..9dd62cc Binary files /dev/null and b/fast.png differ diff --git a/pause.png b/pause.png new file mode 100644 index 0000000..1b83a46 Binary files /dev/null and b/pause.png differ diff --git a/play.png b/play.png new file mode 100644 index 0000000..4812096 Binary files /dev/null and b/play.png differ diff --git a/restart.png b/restart.png new file mode 100644 index 0000000..3897af4 Binary files /dev/null and b/restart.png differ diff --git a/src/main.rs b/src/main.rs index 70dd8c9..e57fd66 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,12 @@ mod player; mod population; mod world; -use macroquad::prelude::*; +use std::borrow::BorrowMut; + +use macroquad::{ + prelude::*, + ui::{hash, root_ui, widgets, Skin}, +}; use population::Population; pub const WIDTH: f32 = 800.; @@ -22,6 +27,10 @@ fn window_conf() -> Conf { #[macroquad::main(window_conf)] async fn main() { rand::srand(macroquad::miniquad::date::now() as _); + let pause = load_texture("pause.png").await.unwrap(); + let play = load_texture("play.png").await.unwrap(); + let fast = load_texture("fast.png").await.unwrap(); + let restart = load_texture("restart.png").await.unwrap(); let th = (screen_height() - HEIGHT) * 0.5; let gamecam = Camera2D { @@ -51,29 +60,151 @@ async fn main() { // }; let mut pop = Population::new(100); let mut speedup = false; + let mut paused = false; + let mut checkbox = false; + let mut combobox = 0; + let mut text = String::new(); + let mut number = 0.0; + + let skin = { + let boxed = root_ui() + .style_builder() + .background(Image { + width: 3, + height: 3, + bytes: vec![ + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, + ], + }) + .background_margin(RectOffset::new(1., 1., 1., 1.)); + + let window_style = root_ui() + .style_builder() + .background(Image { + width: 1, + height: 1, + bytes: vec![0; 4], + }) + .background_margin(RectOffset::new(0., 0., 0., 0.)) + .build(); + let button_style = boxed + .color_hovered(RED) + .color_clicked(BLUE) + .text_color(WHITE) + .text_color_hovered(WHITE) + .text_color_clicked(WHITE) + .margin(RectOffset::new(10., 10., 8., 8.)) + .build(); + let label_style = root_ui() + .style_builder() + .text_color(WHITE) + .font_size(24) + .margin(RectOffset::new(5., 5., 4., 4.)) + .build(); + + Skin { + window_style, + button_style, + label_style, + margin: 0., + ..root_ui().default_skin() + } + }; + + root_ui().push_skin(&skin); loop { - // set_camera(&cam); clear_background(BLACK); if is_key_pressed(KeyCode::S) { speedup = !speedup; } if speedup { - for _ in 0..1000 { - pop.update(); + if !paused { + for _ in 0..1000 { + pop.update(); + } } } else { - pop.update(); + if !paused { + pop.update(); + } pop.draw(); } draw_rectangle_lines(-WIDTH * 0.5, -HEIGHT * 0.5, WIDTH, HEIGHT, 2., WHITE); + draw_rectangle_lines( + WIDTH * 0.5 + th, + -HEIGHT * 0.5, + screen_width() - WIDTH - 3. * th, + 34., + 2., + WHITE, + ); + draw_rectangle_lines( + WIDTH * 0.5 + th, + -HEIGHT * 0.5 + (screen_height() - 3. * th) * 0.5 - 34., + screen_width() - WIDTH - 3. * th, + 34., + 2., + WHITE, + ); set_camera(&maincam); // draw_circle(0., 0., 20., RED); - pop.worlds[0].see_brain().draw( + pop.worlds[0].player.draw_brain( screen_width() - WIDTH - 3. * th, (screen_height() - 3. * th) * 0.5, ); + set_camera(&gamecam); + root_ui().window( + hash!(), + vec2(WIDTH + 2. * th, th), + vec2(screen_width() - WIDTH - 3. * th + 1., 34.), + |ui| { + ui.label(None, &format!("Generation: {}", pop.gen)); + ui.same_line(278.); + widgets::Button::new("Load Model").ui(ui); + ui.same_line(0.); + widgets::Button::new("Save Model").ui(ui); + ui.same_line(0.); + if widgets::Button::new(fast).ui(ui) { + speedup = !speedup; + }; + ui.same_line(0.); + if widgets::Button::new(restart).ui(ui) { + pop = Population::new(100); + }; + ui.same_line(0.); + if widgets::Button::new(if paused { play } else { pause }).ui(ui) { + paused = !paused; + }; + }, + ); + root_ui().window( + hash!(), + vec2(WIDTH + 2. * th, (screen_height() - th) * 0.5 - 34.), + vec2(screen_width() - WIDTH - 3. * th + 1., 34.), + |ui| { + ui.label(None, &format!("Generation: {}", pop.gen)); + ui.same_line(278.); + widgets::Button::new("Load Model").ui(ui); + ui.same_line(0.); + widgets::Button::new("Save Model").ui(ui); + ui.same_line(0.); + if widgets::Button::new(fast).ui(ui) { + speedup = !speedup; + }; + ui.same_line(0.); + if widgets::Button::new(restart).ui(ui) { + pop = Population::new(100); + }; + ui.same_line(0.); + if widgets::Button::new(if paused { play } else { pause }).ui(ui) { + paused = !paused; + }; + }, + ); // set_camera(&maincam); // draw_texture_ex( diff --git a/src/nn.rs b/src/nn.rs index 58fec5d..d9a6c5e 100644 --- a/src/nn.rs +++ b/src/nn.rs @@ -78,10 +78,10 @@ impl NN { } } - pub fn feed_forward(&self, inputs: Vec) -> Vec { + pub fn feed_forward(&self, inputs: &Vec) -> Vec { // println!("inputs: {:?}", inputs); - let mut y = DMatrix::from_vec(inputs.len(), 1, inputs); - for i in 0..self.config.len() - 1 { + let mut y = DMatrix::from_vec(inputs.len(), 1, inputs.to_vec()); + for i in 0..self.config.len() - 2 { y = (&self.weights[i] * y.insert_row(self.config[i] - 1, 1.)).map(|x| { match self.activ_func { ActivationFunc::ReLU => x.max(0.), @@ -89,13 +89,14 @@ impl NN { ActivationFunc::Tanh => x.tanh(), } }); - // println!("w{}: {}", i, self.weights[i]); - // println!("y: {}", y); } + let i = self.config.len() - 2; + y = (&self.weights[i] * y.insert_row(self.config[i] - 1, 1.)) + .map(|x| 1. / (1. + (-x).exp())); y.column(0).data.into_slice().to_vec() } - pub fn draw(&self, width: f32, height: f32) { + pub fn draw(&self, width: f32, height: f32, inputs: &Vec) { draw_rectangle_lines(-width * 0.5, -height * 0.5, width, height, 2., WHITE); let width = width * 0.8; @@ -106,31 +107,34 @@ impl NN { for (i, layer) in self .config .iter() - .take(self.config.len() - 1) - .map(|x| x - 1) - .chain(self.config.last().map(|&x| x)) + // .take(self.config.len() - 1) + // .map(|x| x - 1) + // .chain(self.config.last().map(|&x| x)) .enumerate() { p1s = p2s; p2s = Vec::new(); - for neuron in 0..layer { + for neuron in 0..*layer { p2s.push(( i as f32 * width / (self.config.len() - 1) as f32 - width * 0.5, neuron as f32 * vspace - (vspace * (layer - 1) as f32) * 0.5, )); } - for (k, j, p1, p2) in p1s - .iter() - .enumerate() - .flat_map(|(k, x)| p2s.iter().enumerate().map(move |(j, y)| (k, j, *x, *y))) - { + for (k, j, p1, p2) in p1s.iter().enumerate().flat_map(|(k, x)| { + p2s.iter() + .take(p2s.len() - if i == self.config.len() - 1 { 0 } else { 1 }) + .enumerate() + .map(move |(j, y)| (k, j, *x, *y)) + }) { + let weight = *self.weights[i - 1].index((j, k)); + let c = if weight < 0. { 0. } else { 1. }; draw_line( p1.0, p1.1, p2.0, p2.1, 1., - Color::new(1., 1., 1., (self.weights[i - 1].index((j, k))).abs()), + Color::new(1., c, c, weight.abs()), ); } for p in &p1s { @@ -142,6 +146,10 @@ impl NN { draw_circle(p.0, p.1, 10., WHITE); draw_circle(p.0, p.1, 9., BLACK); } + draw_rectangle(width * 0.45, height * 0.45, 10., 10., RED); + draw_text("-ve", width * 0.45 + 20., height * 0.45 + 10., 20., WHITE); + draw_rectangle(width * 0.45, height * 0.45 + 20., 10., 10., WHITE); + draw_text("+ve", width * 0.45 + 20., height * 0.45 + 30., 20., WHITE); } pub fn export(&self) -> String { diff --git a/src/player.rs b/src/player.rs index c8b4cd3..abf357a 100644 --- a/src/player.rs +++ b/src/player.rs @@ -6,14 +6,16 @@ use crate::{asteroids::Asteroid, nn::NN, HEIGHT, WIDTH}; #[derive(Default)] pub struct Player { pub pos: Vec2, - pub vel: Vec2, + vel: Vec2, acc: f32, pub dir: Vec2, rot: f32, drag: f32, bullets: Vec, asteroid: Option, - asteroid_data: Vec<(f32, f32, f32)>, + inputs: Vec, + outputs: Vec, + // asteroid_data: Vec<(f32, f32, f32)>, last_shot: u8, shot_interval: u8, pub brain: Option, @@ -36,6 +38,7 @@ impl Player { alive: true, debug: false, shots: 4, + outputs: vec![false; 4], ..Default::default() } @@ -51,7 +54,7 @@ impl Player { // ); p.brain = Some(brain); } else { - p.brain = Some(NN::new(vec![5, 8, 8, 4])); + p.brain = Some(NN::new(vec![5, 6, 6, 4])); } p } @@ -117,9 +120,9 @@ impl Player { self.lifespan += 1; self.last_shot += 1; self.acc = 0.; - let mut keys = vec![false, false, false, false]; + self.outputs = vec![false; 4]; if let Some(ast) = self.asteroid.as_ref() { - let inputs = vec![ + self.inputs = vec![ (ast.pos - self.pos).length() / WIDTH, self.dir.angle_between(ast.pos - self.pos), (ast.vel - self.vel).x / 11., @@ -140,21 +143,25 @@ impl Player { // ); if let Some(brain) = &self.brain { - keys = brain.feed_forward(inputs).iter().map(|&x| x > 0.).collect(); + self.outputs = brain + .feed_forward(&self.inputs) + .iter() + .map(|&x| x > 0.85) + .collect(); } } - if is_key_down(KeyCode::Right) && self.debug || keys[0] { + if is_key_down(KeyCode::Right) && self.debug || self.outputs[0] { self.rot = (self.rot + 0.1 + TAU as f32) % TAU as f32; self.dir = vec2(self.rot.cos(), self.rot.sin()); } - if is_key_down(KeyCode::Left) && self.debug || keys[1] { + if is_key_down(KeyCode::Left) && self.debug || self.outputs[1] { self.rot = (self.rot - 0.1 + TAU as f32) % TAU as f32; self.dir = vec2(self.rot.cos(), self.rot.sin()); } - if is_key_down(KeyCode::Up) && self.debug || keys[2] { + if is_key_down(KeyCode::Up) && self.debug || self.outputs[2] { self.acc = 0.14; } - if is_key_down(KeyCode::Space) && self.debug || keys[3] { + if is_key_down(KeyCode::Space) && self.debug || self.outputs[3] { if self.last_shot > self.shot_interval { self.last_shot = 0; self.shots += 1; @@ -243,6 +250,12 @@ impl Player { bullet.draw(); } } + + pub fn draw_brain(&self, width: f32, height: f32) { + if let Some(brain) = &self.brain { + brain.draw(width, height, &self.inputs); + } + } } struct Bullet { diff --git a/src/population.rs b/src/population.rs index 4ba6fab..66f6fab 100644 --- a/src/population.rs +++ b/src/population.rs @@ -5,7 +5,7 @@ use crate::{nn::NN, world::World, HEIGHT, WIDTH}; #[derive(Default)] pub struct Population { size: usize, - gen: i32, + pub gen: i32, best: bool, pub worlds: Vec, } @@ -49,13 +49,13 @@ impl Population { world.draw(); } } - draw_text( - &format!("Gen: {}", self.gen), - -150. + WIDTH * 0.5, - 30. - HEIGHT * 0.5, - 32., - WHITE, - ); + // draw_text( + // &format!("Gen: {}", self.gen), + // -150. + WIDTH * 0.5, + // 30. - HEIGHT * 0.5, + // 32., + // WHITE, + // ); // draw black background outside the screen let th = (screen_height() - HEIGHT) * 0.5;