├── .gitignore ├── rustfmt.toml ├── docs └── images │ ├── demo.gif │ ├── demo2.gif │ └── demo5.gif ├── .github └── workflows │ └── rust.yml ├── Cargo.toml ├── README.md ├── LICENSE ├── src ├── constants.rs ├── render.rs ├── qlearn.rs ├── main.rs ├── gen_alg.rs └── game.rs └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 120 2 | edition = "2018" 3 | -------------------------------------------------------------------------------- /docs/images/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/const-i/snake_01/HEAD/docs/images/demo.gif -------------------------------------------------------------------------------- /docs/images/demo2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/const-i/snake_01/HEAD/docs/images/demo2.gif -------------------------------------------------------------------------------- /docs/images/demo5.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/const-i/snake_01/HEAD/docs/images/demo5.gif -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Build 17 | run: cargo build --verbose 18 | - name: Run tests 19 | run: cargo test --verbose 20 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "snake_01" 3 | version = "0.1.0" 4 | authors = ["Sea6 "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | rand = "0.7.3" 11 | rand_distr = "0.2.2" 12 | piston = "0.49.0" 13 | pistoncore-glutin_window = "0.63.0" 14 | piston2d-graphics = "0.36.0" 15 | piston2d-opengl_graphics = "0.72.0" 16 | rayon = "1.3.0" 17 | itertools = "0.9.0" 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Snake v01 2 | 3 | ![Rust](https://github.com/const-i/snake_01/workflows/Rust/badge.svg) 4 | 5 | Implementation of Genetic Algorithm and Q-Learning for creating a Snake AI in Rust. 6 | 7 | This has been an attempt at learning the Rust language, and using it as a way to learn some concepts of Machine Learning, Data Parallelism, and Game Design. 8 | 9 | ## Demo 10 | 11 | ![Demo GIF](./docs/images/demo5.gif) 12 | 13 | ## How to play 14 | 15 | ``` 16 | $ git clone https://github.com/const-i/snake_01.git 17 | $ cd snake_01 18 | $ cargo run 19 | ``` 20 | 21 | 22 | ## License 23 | 24 | See the [LICENSE](./LICENSE) file for license rights and limitations. 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Srideep Chatterjee 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/constants.rs: -------------------------------------------------------------------------------- 1 | pub static NAME: &str = "Snake v01"; 2 | 3 | // Board Dimensions 4 | pub const BOARD_WIDTH: u8 = 10; 5 | pub const BOARD_HEIGHT: u8 = 10; 6 | 7 | // Colours 8 | pub const RED: [f32; 4] = [1.0, 0.0, 0.0, 1.0]; 9 | pub const GREEN: [f32; 4] = [0.0, 1.0, 0.0, 1.0]; 10 | pub const YELLOW: [f32; 4] = [1.0, 1.0, 0.0, 1.0]; 11 | pub const BLACK: [f32; 4] = [0.0, 0.0, 0.0, 1.0]; 12 | 13 | // Neural Network Game 14 | pub const NUM_INDIVIDUALS: u32 = 1000; 15 | pub const NUM_GAMES_NN: u32 = 20; 16 | pub const NUM_GENERATIONS: u32 = 20; 17 | pub const NN_MAX_GAME_TIME: u32 = 100; 18 | 19 | // Q-Learing Game 20 | pub const NUM_GAMES_QL: u32 = 2000; // Plateau after 2000 games 21 | pub const NUM_QLS: u32 = 4; // Should be a multiple of number of cores 22 | 23 | // Genetic Algorithm Properties 24 | pub const MUTATION_PROBABILITY: f64 = 0.005; 25 | pub const CROSSOVER_PROBABILITY: f64 = 0.01; 26 | 27 | // Game Render Properties 28 | pub const BLOCK_SIZE: u32 = 30; 29 | pub const RENDER_UPS: u64 = 20; 30 | pub const RENDER_FPS_MAX: u64 = 20; 31 | 32 | // Q-Learning Properties 33 | pub const EPSILON_GREEDY: f64 = 0.0; // Looks like best results are with 0. Probably SARSA would de better here 34 | pub const LEARNING_RATE: f64 = 0.01; // Lower seems to be better, but too low gets worse 35 | pub const DISCOUNT_FACTOR: f64 = 0.9; // Seems to make not much difference 36 | -------------------------------------------------------------------------------- /src/render.rs: -------------------------------------------------------------------------------- 1 | extern crate glutin_window; 2 | extern crate graphics; 3 | extern crate opengl_graphics; 4 | extern crate piston; 5 | 6 | use crate::constants::*; 7 | use crate::game::{Block, Brain, Direction, Game}; 8 | 9 | use glutin_window::GlutinWindow; 10 | use opengl_graphics::{GlGraphics, OpenGL}; 11 | use piston::event_loop::{EventLoop, EventSettings, Events}; 12 | use piston::input::keyboard::Key; 13 | use piston::input::{Button, PressEvent, RenderArgs, RenderEvent, UpdateEvent}; //UpdateArgs 14 | use piston::window::WindowSettings; 15 | 16 | pub struct Render { 17 | window: GlutinWindow, 18 | events: Events, 19 | gl: GlGraphics, 20 | } 21 | 22 | impl Render { 23 | pub fn new() -> Render { 24 | Render { 25 | window: WindowSettings::new( 26 | NAME, 27 | [BOARD_WIDTH as u32 * BLOCK_SIZE, BOARD_HEIGHT as u32 * BLOCK_SIZE], 28 | ) 29 | .graphics_api(OpenGL::V3_2) 30 | .vsync(true) 31 | .exit_on_esc(true) 32 | .build() 33 | .unwrap(), 34 | events: Events::new(EventSettings::new().ups(RENDER_UPS).max_fps(RENDER_FPS_MAX)), 35 | gl: GlGraphics::new(OpenGL::V3_2), 36 | } 37 | } 38 | 39 | pub fn run(&mut self) { 40 | let mut game = Game::new(); 41 | game.init(); 42 | 43 | while let Some(e) = self.events.next(&mut self.window) { 44 | if let Some(args) = e.render_args() { 45 | self.render_game(&args, &game); 46 | } 47 | 48 | if let Some(args) = e.update_args() { 49 | game.next_tick(args.dt); 50 | } 51 | 52 | if let Some(button) = e.press_args() { 53 | self.handle_events(button, &mut game); 54 | } 55 | } 56 | } 57 | 58 | pub fn run_brain(&mut self, brain: &mut T) { 59 | let mut game = Game::new(); 60 | game.init(); 61 | 62 | while let Some(e) = self.events.next(&mut self.window) { 63 | if let Some(args) = e.render_args() { 64 | self.render_game(&args, &game); 65 | } 66 | 67 | if let Some(args) = e.update_args() { 68 | let dir = game.get_dir_from_brain(brain); 69 | game.update(dir); 70 | game.next_tick(args.dt); 71 | } 72 | 73 | if let Some(button) = e.press_args() { 74 | self.handle_events(button, &mut game); 75 | } 76 | } 77 | } 78 | 79 | fn handle_events(&mut self, button: Button, game: &mut Game) { 80 | match button { 81 | Button::Keyboard(key) => match key { 82 | Key::Up => game.update(Direction::UP), 83 | Key::Down => game.update(Direction::DOWN), 84 | Key::Left => game.update(Direction::LEFT), 85 | Key::Right => game.update(Direction::RIGHT), 86 | Key::Space => game.init(), 87 | _ => {} 88 | }, 89 | _ => {} 90 | } 91 | } 92 | 93 | fn render_game(&mut self, args: &RenderArgs, game: &Game) { 94 | self.gl.draw(args.viewport(), |_c, g| { 95 | graphics::clear(BLACK, g); 96 | }); 97 | for b in game.snake.body.iter() { 98 | self.render_block(&b); 99 | } 100 | self.render_block(&game.food); 101 | } 102 | 103 | fn render_block(&mut self, block: &Block) { 104 | //args: &RenderArgs 105 | 106 | use graphics::math::Matrix2d; 107 | use graphics::Transformed; 108 | 109 | let square_ = graphics::rectangle::Rectangle::new(block.colour).border(graphics::rectangle::Border { 110 | color: BLACK, 111 | radius: 0.01, 112 | }); 113 | let dims_ = 114 | graphics::rectangle::rectangle_by_corners(0.0, 0.0, 2.0 / BOARD_WIDTH as f64, 2.0 / BOARD_HEIGHT as f64); 115 | let transform_: Matrix2d = graphics::math::identity() 116 | .trans( 117 | -((BOARD_WIDTH / 2) as f64) * 2.0 / BOARD_WIDTH as f64, 118 | (BOARD_HEIGHT / 2 - 1) as f64 * 2.0 / BOARD_HEIGHT as f64, 119 | ) 120 | .trans( 121 | (block.position.x as f64) * 2.0 / BOARD_WIDTH as f64, 122 | -(block.position.y as f64) * 2.0 / BOARD_HEIGHT as f64, 123 | ); 124 | let draw_state_ = graphics::draw_state::DrawState::default(); 125 | square_.draw(dims_, &draw_state_, transform_, &mut self.gl); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/qlearn.rs: -------------------------------------------------------------------------------- 1 | extern crate rand; 2 | 3 | use itertools::Itertools; 4 | use rand::Rng; 5 | 6 | use crate::constants::*; 7 | use crate::game::Brain; 8 | 9 | pub struct StateAction { 10 | pub state: Vec, 11 | pub quality: Vec, 12 | } 13 | 14 | impl StateAction { 15 | pub fn new(state: Vec, num_actions: usize) -> StateAction { 16 | StateAction { 17 | state, 18 | //quality: (0..num_actions).map(|_| get_normal()).collect(), 19 | quality: vec![0.0_f64; num_actions], // Initializing at 0 gives objectively better results than using a normal distribution 20 | } 21 | } 22 | } 23 | 24 | pub struct QLearner { 25 | pub q: Vec, 26 | pub epsilon: f64, 27 | pub learning_rate: f64, 28 | pub discount_factor: f64, 29 | pub num_states: usize, 30 | pub len_states: usize, 31 | pub num_actions: usize, 32 | } 33 | 34 | impl QLearner { 35 | pub fn new(len_states: usize, num_actions: usize) -> QLearner { 36 | let states: Vec> = (0..len_states).map(|_| (0..2)).multi_cartesian_product().collect(); 37 | let mut sa: Vec = Vec::new(); 38 | for state in states { 39 | let a: Vec = state.iter().map(|x| *x as f64).collect(); 40 | sa.push(StateAction::new(a, num_actions)); 41 | } 42 | QLearner { 43 | num_states: sa.len(), 44 | q: sa, 45 | epsilon: EPSILON_GREEDY, 46 | learning_rate: LEARNING_RATE, 47 | discount_factor: DISCOUNT_FACTOR, 48 | len_states, 49 | num_actions, 50 | } 51 | } 52 | } 53 | 54 | impl Brain for QLearner { 55 | fn get_action(&mut self, state: &Vec) -> Option { 56 | // Following the epsilon-greedy policy 57 | let mut rng = rand::thread_rng(); 58 | if rng.gen::() > 1.0_f64 - self.epsilon { 59 | Some(rng.gen_range(0, self.num_actions)) 60 | } else { 61 | let found = self.q.iter().find(|sa| sa.state == *state); 62 | match found { 63 | Some(sa) => get_index_max_float(&sa.quality), 64 | None => { 65 | let state_copy: Vec = state.iter().copied().map(|x| x).collect(); 66 | let sa = StateAction::new(state_copy, self.num_actions); 67 | let action = get_index_max_float(&sa.quality); 68 | self.q.push(sa); 69 | action 70 | } 71 | } 72 | } 73 | } 74 | 75 | fn train(&mut self, state_initial: &Vec, action: usize, reward: f64, state_final: &Vec) -> Option { 76 | let index_initial = self.q.iter().position(|sa| sa.state == *state_initial); 77 | let index_final = self.q.iter().position(|sa| sa.state == *state_final); 78 | if index_initial == None || index_final == None { 79 | None 80 | } else { 81 | let ii = index_initial.unwrap(); 82 | let fi = index_final.unwrap(); 83 | self.q[ii].quality[action] = self.q[ii].quality[action] 84 | + self.learning_rate 85 | * (reward + self.discount_factor * get_max_float(&self.q[fi].quality).unwrap() 86 | - self.q[ii].quality[action]); 87 | Some(true) 88 | } 89 | } 90 | } 91 | 92 | fn get_index_max_float(input: &Vec) -> Option { 93 | input 94 | .iter() 95 | .enumerate() 96 | .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)) 97 | .map(|(index, _)| index) 98 | } 99 | 100 | fn get_max_float(input: &Vec) -> Option<&f64> { 101 | input 102 | .iter() 103 | .max_by(|&a, &b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)) 104 | } 105 | 106 | #[cfg(test)] 107 | mod tests { 108 | use super::*; 109 | 110 | #[test] 111 | fn get_permutations() { 112 | let perms: Vec> = (0..3).map(|_| (0..2)).multi_cartesian_product().collect(); 113 | println!("{:?}", perms); 114 | assert_eq!(perms.len(), 2_usize.pow(3)); 115 | } 116 | 117 | #[test] 118 | fn test_get_index_max_float() { 119 | let mut rng = rand::thread_rng(); 120 | let q: Vec = (0..4).map(|_| rng.gen()).collect(); 121 | println!("{:?}", q); 122 | let i = get_index_max_float(&q); 123 | println!("{:?}", i); 124 | assert!(i.is_some()); 125 | } 126 | 127 | #[test] 128 | fn test_get_max_float() { 129 | let mut rng = rand::thread_rng(); 130 | let q: Vec = (0..4).map(|_| rng.gen()).collect(); 131 | println!("{:?}", q); 132 | let q_max = get_max_float(&q); 133 | println!("{:?}", q_max); 134 | assert!(q_max.is_some()); 135 | } 136 | 137 | #[test] 138 | fn test_qlearner_new() { 139 | let ql = QLearner::new(8, 4); 140 | assert_eq!(ql.q.len(), 2_usize.pow(8)); 141 | assert_eq!(ql.q[0].state.len(), 8); 142 | assert_eq!(ql.q[0].quality.len(), 4); 143 | assert_eq!(ql.q[0].state, vec![0.0_f64; 8]); 144 | } 145 | 146 | #[test] 147 | fn test_qlearner_get_action() { 148 | let mut ql = QLearner::new(8, 4); 149 | let state = vec![0.0_f64; 8]; 150 | let action = ql.get_action(&state); 151 | println!("{:?}", get_index_max_float(&ql.q[0].quality)); 152 | assert_eq!(action, get_index_max_float(&ql.q[0].quality)); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod constants; 2 | mod game; 3 | mod gen_alg; 4 | mod qlearn; 5 | mod render; 6 | 7 | extern crate rayon; 8 | 9 | use rayon::prelude::*; 10 | 11 | use crate::constants::*; 12 | use crate::game::{Brain, Game}; 13 | use crate::gen_alg::{Population, NN}; 14 | use crate::qlearn::QLearner; 15 | use crate::render::Render; 16 | 17 | enum GameType { 18 | Human, 19 | GeneticAlgorithm, 20 | QLearning, 21 | } 22 | 23 | fn main() { 24 | let game_type = GameType::QLearning; 25 | 26 | match game_type { 27 | GameType::Human => render_game(), 28 | GameType::GeneticAlgorithm => { 29 | iterate_population(NUM_INDIVIDUALS, NUM_GAMES_NN, NUM_GENERATIONS, fitness_function_nn) 30 | } 31 | GameType::QLearning => iterate_qls(NUM_QLS, NUM_GAMES_QL, fitness_function_ql), 32 | } 33 | } 34 | 35 | fn fitness_function_nn(_delta_t: i64, dist_before: i64, dist_after: i64, snake_eat: i64, snake_dead: i64) -> f64 { 36 | let mut fitness: f64 = 0.0_f64; 37 | // Distance 38 | if dist_after < dist_before { 39 | fitness += 0.3_f64; 40 | } else { 41 | fitness -= 0.5_f64; 42 | } 43 | if dist_before >= 2 && dist_after < 2 { 44 | fitness += 0.5_f64; 45 | } 46 | // Time 47 | fitness += 0.1_f64; 48 | // Food 49 | if snake_eat > 0 { 50 | fitness += 5.0_f64; 51 | } 52 | // Dead 53 | if snake_dead > 0 { 54 | fitness -= 1.0_f64; 55 | } 56 | fitness 57 | } 58 | 59 | fn fitness_function_ql(_delta_t: i64, dist_before: i64, dist_after: i64, snake_eat: i64, snake_dead: i64) -> f64 { 60 | let mut fitness: f64 = 0.0_f64; 61 | // Distance 62 | if dist_after < dist_before { 63 | fitness += 0.3_f64; 64 | } else { 65 | fitness -= 0.7_f64; 66 | } 67 | if dist_before >= 2 && dist_after < 2 { 68 | fitness += 0.5_f64; 69 | } 70 | // Time 71 | fitness += 0.1_f64; 72 | // Food 73 | if snake_eat > 0 { 74 | fitness += 5.0_f64; 75 | } 76 | // Dead 77 | if snake_dead > 0 { 78 | fitness -= 1.0_f64; 79 | } 80 | fitness 81 | } 82 | 83 | // -------------------------------------------------------------------------------------- 84 | // ----------------------------------Neural Network-------------------------------------- 85 | // -------------------------------------------------------------------------------------- 86 | 87 | fn iterate_population( 88 | num_nn: u32, 89 | num_games: u32, 90 | num_generations: u32, 91 | fitness_function: fn(i64, i64, i64, i64, i64) -> f64, 92 | ) { 93 | let mut pop = Population::new_defined(num_nn, &[[8, 8], [8, 4]]); 94 | for i in 0..num_generations - 1 { 95 | pop.fitness = population_play_parallel(&mut pop.nn, num_games, fitness_function); 96 | let sorted_index = pop.get_sorted_index(); 97 | println!("Gen: {}; Fitness: {}", i, pop.fitness[sorted_index[0]]); 98 | pop = pop.create_next_generation(); 99 | } 100 | 101 | pop.fitness = population_play_parallel(&mut pop.nn, num_games, fitness_function); 102 | let sorted_index = pop.get_sorted_index(); 103 | println!("Final Fitness: {}", pop.fitness[sorted_index[0]]); 104 | let mut render = Render::new(); 105 | render.run_brain(&mut pop.nn[sorted_index[0]]); 106 | } 107 | 108 | fn population_play_parallel( 109 | nns: &mut Vec, 110 | num_games: u32, 111 | fitness_function: fn(i64, i64, i64, i64, i64) -> f64, 112 | ) -> Vec { 113 | let fitness: Vec = nns 114 | .par_iter_mut() 115 | .map(|n| play_brain(n, num_games, fitness_function)) 116 | .collect(); 117 | fitness 118 | } 119 | 120 | // -------------------------------------------------------------------------------------- 121 | // ----------------------------------Q Learning------------------------------------------ 122 | // -------------------------------------------------------------------------------------- 123 | 124 | fn iterate_qls(num_qls: u32, num_games: u32, fitness_function: fn(i64, i64, i64, i64, i64) -> f64) { 125 | let mut qls: Vec = (0..num_qls).map(|_| QLearner::new(8, 4)).collect(); 126 | let max_i = ql_play_parallel(&mut qls, num_games, fitness_function); 127 | let mut render = Render::new(); 128 | render.run_brain(&mut qls[max_i]); 129 | } 130 | 131 | fn ql_play_parallel( 132 | qls: &mut Vec, 133 | num_games: u32, 134 | fitness_function: fn(i64, i64, i64, i64, i64) -> f64, 135 | ) -> usize { 136 | let fitness: Vec = qls 137 | .par_iter_mut() 138 | .map(|ql| play_brain(ql, num_games, fitness_function)) 139 | .collect(); 140 | fitness 141 | .iter() 142 | .enumerate() 143 | .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)) 144 | .map(|(index, _)| index) 145 | .unwrap_or(0) 146 | } 147 | 148 | // -------------------------------------------------------------------------------------- 149 | // ----------------------------------Generic Brain--------------------------------------- 150 | // -------------------------------------------------------------------------------------- 151 | 152 | 153 | fn play_brain(brain: &mut T, num_games: u32, fitness_function: fn(i64, i64, i64, i64, i64) -> f64) -> f64 { 154 | let mut game = Game::new(); 155 | let mut fitness: f64 = 0f64; 156 | for _ in 0..num_games { 157 | game.init(); 158 | fitness += game.run_brain(brain, fitness_function); 159 | } 160 | fitness / num_games as f64 161 | } 162 | 163 | // -------------------------------------------------------------------------------------- 164 | // ----------------------------------Human Game------------------------------------------ 165 | // -------------------------------------------------------------------------------------- 166 | 167 | fn render_game() { 168 | let mut render = Render::new(); 169 | render.run(); 170 | } 171 | -------------------------------------------------------------------------------- /src/gen_alg.rs: -------------------------------------------------------------------------------- 1 | extern crate rand; 2 | 3 | use rand::Rng; 4 | use rand_distr::{Distribution, Normal}; 5 | 6 | use crate::constants::*; 7 | use crate::game::Brain; 8 | 9 | fn sigmoid(z: f64) -> f64 { 10 | let e = std::f64::consts::E; 11 | 1.0 / (1.0 + e.powf(-z)) 12 | } 13 | 14 | fn get_normal() -> f64 { 15 | let mut rng = rand::thread_rng(); 16 | let normal = Normal::new(0.0, 1.0).unwrap(); 17 | normal.sample(&mut rng) 18 | } 19 | 20 | pub struct Layer { 21 | pub num_inputs: u32, 22 | pub num_neurons: u32, 23 | pub weights: Vec>, 24 | pub biases: Vec, 25 | } 26 | 27 | impl Layer { 28 | pub fn new(num_inputs: u32, num_neurons: u32) -> Option { 29 | if num_inputs == 0 || num_neurons == 0 { 30 | None 31 | } else { 32 | let biases: Vec = (0..num_neurons).map(|_| get_normal()).collect(); 33 | let weights: Vec> = (0..num_neurons) 34 | .map(|_| (0..num_inputs).map(|_| get_normal()).collect()) 35 | .collect(); 36 | Some(Layer { 37 | num_inputs, 38 | num_neurons, 39 | weights, 40 | biases, 41 | }) 42 | } 43 | } 44 | 45 | fn feed_forward(&self, inputs: &Vec) -> Option> { 46 | if self.num_inputs != inputs.len() as u32 { 47 | None 48 | } else { 49 | let wx: Vec = self 50 | .weights 51 | .iter() 52 | .map(|ws| ws.iter().zip(inputs.iter()).map(|(w, x)| w * x).sum()) 53 | .collect(); 54 | let wx_b: Vec = wx.iter().zip(self.biases.iter()).map(|(wx, b)| wx + b).collect(); 55 | Some(wx_b.iter().map(|z| sigmoid(*z)).collect()) 56 | } 57 | } 58 | 59 | fn should_mutate() -> bool { 60 | let mut rng = rand::thread_rng(); 61 | rng.gen::() > 1f64 - MUTATION_PROBABILITY 62 | } 63 | 64 | fn mutate(&mut self) { 65 | self.biases = self 66 | .biases 67 | .iter() 68 | .map(|b| if Layer::should_mutate() { get_normal() } else { *b }) 69 | .collect(); 70 | self.weights = self 71 | .weights 72 | .iter() 73 | .map(|ws| { 74 | ws.iter() 75 | .map(|w| if Layer::should_mutate() { get_normal() } else { *w }) 76 | .collect() 77 | }) 78 | .collect(); 79 | } 80 | 81 | fn should_crossover() -> bool { 82 | let mut rng = rand::thread_rng(); 83 | rng.gen::() > 1f64 - CROSSOVER_PROBABILITY 84 | } 85 | 86 | fn crossover(&self, other: &Layer) -> (Layer, Layer) { 87 | let mut child1 = Layer::new(self.num_inputs, self.num_neurons).unwrap(); 88 | let mut child2 = Layer::new(self.num_inputs, self.num_neurons).unwrap(); 89 | 90 | // Cannot iter() over here since destructuring assignments are not allowed 91 | // https://github.com/rust-lang/rfcs/issues/372 92 | 93 | for i in 0..self.num_neurons as usize { 94 | if Layer::should_crossover() { 95 | child1.biases[i] = other.biases[i]; 96 | child2.biases[i] = self.biases[i]; 97 | } else { 98 | child1.biases[i] = self.biases[i]; 99 | child2.biases[i] = other.biases[i]; 100 | } 101 | for j in 0..self.num_inputs as usize { 102 | if Layer::should_crossover() { 103 | child1.weights[i][j] = other.weights[i][j]; 104 | child2.weights[i][j] = self.weights[i][j]; 105 | } else { 106 | child1.weights[i][j] = self.weights[i][j]; 107 | child2.weights[i][j] = other.weights[i][j]; 108 | } 109 | } 110 | } 111 | 112 | (child1, child2) 113 | } 114 | } 115 | 116 | pub struct NN { 117 | pub layers: Vec, 118 | } 119 | 120 | impl NN { 121 | pub fn new() -> NN { 122 | NN { layers: Vec::new() } 123 | } 124 | 125 | pub fn new_defined(layer_def: &[[usize; 2]]) -> NN { 126 | let mut nn = NN::new(); 127 | for layer in layer_def { 128 | nn.add(Layer::new(layer[0] as u32, layer[1] as u32).unwrap()); 129 | } 130 | 131 | nn 132 | } 133 | 134 | pub fn add(&mut self, layer: Layer) -> bool { 135 | if self.layers.is_empty() || self.layers.last().unwrap().num_neurons == layer.num_inputs { 136 | self.layers.push(layer); 137 | true 138 | } else { 139 | false 140 | } 141 | } 142 | 143 | pub fn propagate(&self, inputs: &Vec) -> Option> { 144 | if self.layers.is_empty() || self.layers[0].num_inputs != inputs.len() as u32 { 145 | None 146 | } else { 147 | let mut this_in = inputs; 148 | let mut this_out: Vec = Vec::new(); 149 | for layer in &self.layers { 150 | let temp = layer.feed_forward(&this_in); 151 | match temp { 152 | Some(vals) => this_out = vals, 153 | None => return None, 154 | } 155 | //this_out = layer.feed_forward(&this_in).unwrap(); 156 | this_in = &this_out; 157 | } 158 | Some(this_out) 159 | } 160 | } 161 | 162 | fn mutate(&mut self) { 163 | for layer in &mut self.layers { 164 | layer.mutate(); 165 | } 166 | } 167 | 168 | fn crossover(&self, other: &NN) -> (NN, NN) { 169 | let mut child1 = NN::new(); 170 | let mut child2 = NN::new(); 171 | for (p1, p2) in self.layers.iter().zip(other.layers.iter()) { 172 | let (c1, c2) = p1.crossover(p2); 173 | child1.add(c1); 174 | child2.add(c2); 175 | } 176 | (child1, child2) 177 | } 178 | } 179 | 180 | impl Brain for NN { 181 | fn get_action(&mut self, inputs: &Vec) -> Option { 182 | let output = self.propagate(inputs); 183 | match output { 184 | Some(vals) => get_index_max_float(&vals), 185 | None => None, 186 | } 187 | } 188 | 189 | fn train( 190 | &mut self, 191 | _state_initial: &Vec, 192 | _action: usize, 193 | _reward: f64, 194 | _state_final: &Vec, 195 | ) -> Option { 196 | Some(true) 197 | } 198 | } 199 | 200 | pub struct Population { 201 | pub length: usize, 202 | pub nn: Vec, 203 | pub fitness: Vec, 204 | } 205 | 206 | impl Population { 207 | pub fn new() -> Population { 208 | Population { 209 | length: 0, 210 | nn: Vec::new(), 211 | fitness: Vec::new(), 212 | } 213 | } 214 | 215 | pub fn new_defined(num_nn: u32, layer_def: &[[usize; 2]]) -> Population { 216 | let mut pop = Population::new(); 217 | for _ in 0..num_nn { 218 | pop.add(NN::new_defined(layer_def)); 219 | } 220 | pop 221 | } 222 | 223 | pub fn add(&mut self, nn: NN) { 224 | self.nn.push(nn); 225 | self.fitness.push(0f64); 226 | self.length += 1; 227 | } 228 | 229 | pub fn create_next_generation(&self) -> Population { 230 | let mut pop = Population::new(); 231 | let sorted_index = self.get_sorted_index(); 232 | let mid = self.length % 2 + self.length / 2; 233 | for i in 0..mid { 234 | let p1 = &self.nn[sorted_index[i]]; 235 | let p2 = &self.nn[sorted_index[i + 1]]; 236 | let (mut c1, mut c2) = p1.crossover(p2); 237 | c1.mutate(); 238 | c2.mutate(); 239 | pop.add(c1); 240 | if i == mid - 1 && self.length % 2 == 1 { 241 | // Do nothing 242 | } else { 243 | pop.add(c2); 244 | } 245 | } 246 | pop 247 | } 248 | 249 | pub fn get_sorted_index(&self) -> Vec { 250 | let mut index: Vec<(usize, f64)> = self.fitness.clone().into_iter().enumerate().collect(); 251 | index.sort_by(|(_, af), (_, bf)| bf.partial_cmp(af).unwrap_or(std::cmp::Ordering::Equal)); 252 | index.iter().map(|(i, _)| *i).collect() 253 | } 254 | } 255 | 256 | fn get_index_max_float(input: &Vec) -> Option { 257 | input 258 | .iter() 259 | .enumerate() 260 | .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)) 261 | .map(|(index, _)| index) 262 | } 263 | 264 | #[cfg(test)] 265 | mod tests { 266 | use super::*; 267 | 268 | #[test] 269 | fn test_sigmoid() { 270 | assert!(sigmoid(-1000f64) >= 0f64); 271 | assert!(sigmoid(-1000f64) <= 0.5f64); 272 | assert!(sigmoid(1000f64) >= 0.5f64); 273 | assert!(sigmoid(1000f64) <= 1f64); 274 | assert_eq!(sigmoid(0f64), 0.5f64); 275 | } 276 | 277 | #[test] 278 | fn test_layer_new() { 279 | let layer = Layer::new(3, 2).unwrap(); 280 | assert_eq!(layer.num_inputs, 3); 281 | assert_eq!(layer.num_neurons, 2); 282 | assert_eq!(layer.weights.len(), 2); 283 | assert_eq!(layer.weights.first().unwrap().len(), 3); 284 | assert_eq!(layer.biases.len(), 2); 285 | let res = Layer::new(0, 0); 286 | match res { 287 | Some(_) => assert!(false), 288 | None => assert!(true), 289 | } 290 | } 291 | 292 | #[test] 293 | fn test_layer_feed_forward() { 294 | let layer = Layer::new(3, 2).unwrap(); 295 | let mut inputs = vec![1f64, 2f64]; 296 | let mut outputs = layer.feed_forward(&inputs); 297 | assert_eq!(outputs, None); 298 | inputs = vec![0f64, 1f64, 0f64]; 299 | outputs = layer.feed_forward(&inputs); 300 | println!("{:?}", outputs); 301 | assert_eq!(outputs.unwrap().len(), 2); 302 | //assert!(false); 303 | } 304 | 305 | #[test] 306 | fn test_nn_new() { 307 | let nn = NN::new(); 308 | assert_eq!(nn.layers.len(), 0); 309 | } 310 | 311 | #[test] 312 | fn test_nn_new_defined() { 313 | let nn = NN::new_defined(&[[4, 3], [3, 2], [2, 1]]); 314 | assert_eq!(nn.layers.len(), 3); 315 | assert_eq!(nn.layers[0].num_inputs, 4); 316 | assert_eq!(nn.layers[0].num_neurons, 3); 317 | assert_eq!(nn.layers[2].num_inputs, 2); 318 | assert_eq!(nn.layers[2].num_neurons, 1); 319 | } 320 | 321 | #[test] 322 | fn test_nn_add() { 323 | let mut nn = NN::new(); 324 | let layer1 = Layer::new(3, 2).unwrap(); 325 | let layer2 = Layer::new(2, 1).unwrap(); 326 | let layer3 = Layer::new(3, 1).unwrap(); 327 | assert!(nn.add(layer1)); 328 | assert!(nn.add(layer2)); 329 | assert!(!nn.add(layer3)); 330 | } 331 | 332 | #[test] 333 | fn test_nn_propagate() { 334 | let nn = NN::new_defined(&[[3, 2], [2, 1]]); 335 | let inputs = vec![0.0_f64, 1.0_f64]; 336 | let outputs = nn.propagate(&inputs); 337 | assert_eq!(outputs, None); 338 | let inputs = vec![0.0_f64, 1.0_f64, 0.0_f64]; 339 | let outputs = nn.propagate(&inputs); 340 | assert_ne!(outputs, None); 341 | let vals = outputs.unwrap(); 342 | assert_eq!(vals.len(), 1); 343 | assert!(vals[0] >= 0f64); 344 | assert!(vals[0] <= 1f64); 345 | } 346 | 347 | #[test] 348 | fn test_population_new() { 349 | let pop = Population::new(); 350 | assert_eq!(pop.nn.len(), 0); 351 | } 352 | 353 | #[test] 354 | fn test_population_new_defined() { 355 | let pop = Population::new_defined(10, &[[4, 3], [3, 2], [2, 1]]); 356 | assert_eq!(pop.length, 10); 357 | assert_eq!(pop.nn[0].layers.len(), 3); 358 | assert_eq!(pop.nn[0].layers[0].num_inputs, 4); 359 | assert_eq!(pop.nn[0].layers[0].num_neurons, 3); 360 | assert_eq!(pop.nn[9].layers[2].num_inputs, 2); 361 | assert_eq!(pop.nn[9].layers[2].num_neurons, 1); 362 | } 363 | 364 | #[test] 365 | fn test_population_add() { 366 | let mut pop = Population::new(); 367 | let mut nn1 = NN::new(); 368 | let layer1 = Layer::new(3, 2).unwrap(); 369 | let layer2 = Layer::new(2, 1).unwrap(); 370 | nn1.add(layer1); 371 | nn1.add(layer2); 372 | pop.add(nn1); 373 | assert_eq!(pop.length, 1); 374 | let mut nn2 = NN::new(); 375 | let layer1 = Layer::new(3, 2).unwrap(); 376 | let layer2 = Layer::new(2, 1).unwrap(); 377 | nn2.add(layer1); 378 | nn2.add(layer2); 379 | pop.add(nn2); 380 | assert_eq!(pop.length, 2); 381 | assert_eq!(pop.nn.len(), 2); 382 | assert_eq!(pop.fitness.len(), 2); 383 | } 384 | 385 | #[test] 386 | fn test_population_sort() { 387 | let mut pop = Population::new(); 388 | let mut nn1 = NN::new(); 389 | let layer1 = Layer::new(3, 2).unwrap(); 390 | let layer2 = Layer::new(2, 1).unwrap(); 391 | nn1.add(layer1); 392 | nn1.add(layer2); 393 | let mut nn2 = NN::new(); 394 | let layer1 = Layer::new(3, 2).unwrap(); 395 | let layer2 = Layer::new(2, 1).unwrap(); 396 | nn2.add(layer1); 397 | nn2.add(layer2); 398 | pop.nn.push(nn1); 399 | pop.nn.push(nn2); 400 | pop.fitness = vec![5.0, 10.0]; 401 | let si = pop.get_sorted_index(); 402 | assert_eq!(si, vec![1, 0]); 403 | let mut nn3 = NN::new(); 404 | let layer1 = Layer::new(3, 2).unwrap(); 405 | let layer2 = Layer::new(2, 1).unwrap(); 406 | nn3.add(layer1); 407 | nn3.add(layer2); 408 | pop.nn.push(nn3); 409 | pop.fitness = vec![5.0, 10.0, 7.0]; 410 | let si = pop.get_sorted_index(); 411 | assert_eq!(si, vec![1, 2, 0]); 412 | } 413 | 414 | #[test] 415 | fn test_population_create_next_generation() { 416 | let mut pop = Population::new(); 417 | let mut nn1 = NN::new(); 418 | let layer1 = Layer::new(3, 2).unwrap(); 419 | let layer2 = Layer::new(2, 1).unwrap(); 420 | nn1.add(layer1); 421 | nn1.add(layer2); 422 | let mut nn2 = NN::new(); 423 | let layer1 = Layer::new(3, 2).unwrap(); 424 | let layer2 = Layer::new(2, 1).unwrap(); 425 | nn2.add(layer1); 426 | nn2.add(layer2); 427 | pop.add(nn1); 428 | pop.add(nn2); 429 | pop.fitness = vec![5.0, 10.0]; 430 | let next_gen = pop.create_next_generation(); 431 | assert_eq!(pop.length, next_gen.length); 432 | let mut nn3 = NN::new(); 433 | let layer1 = Layer::new(3, 2).unwrap(); 434 | let layer2 = Layer::new(2, 1).unwrap(); 435 | nn3.add(layer1); 436 | nn3.add(layer2); 437 | pop.add(nn3); 438 | pop.fitness = vec![5.0, 10.0, 7.0]; 439 | let next_gen = pop.create_next_generation(); 440 | assert_eq!(pop.length, next_gen.length); 441 | } 442 | } 443 | -------------------------------------------------------------------------------- /src/game.rs: -------------------------------------------------------------------------------- 1 | extern crate rand; 2 | 3 | use rand::Rng; 4 | use std::collections::VecDeque; 5 | use std::fmt; 6 | 7 | use crate::constants::*; 8 | use crate::gen_alg::NN; 9 | use crate::qlearn::QLearner; 10 | 11 | pub trait Brain { 12 | fn get_action(&mut self, inputs: &Vec) -> Option; 13 | fn train(&mut self, state_initial: &Vec, action: usize, reward: f64, state_final: &Vec) -> Option; 14 | } 15 | 16 | #[derive(Copy, Clone)] 17 | pub struct Position { 18 | pub x: u8, 19 | pub y: u8, 20 | } 21 | 22 | impl Position { 23 | fn new() -> Position { 24 | Position { 25 | x: BOARD_WIDTH / 2, 26 | y: BOARD_HEIGHT / 2, 27 | } 28 | } 29 | 30 | fn new_offset(x: i8, y: i8) -> Position { 31 | let mut pos = Position::new(); 32 | pos.offset(x, y); 33 | pos 34 | } 35 | 36 | fn offset(&mut self, x: i8, y: i8) { 37 | self.x = Position::calc_offset(self.x, x, BOARD_WIDTH); 38 | self.y = Position::calc_offset(self.y, y, BOARD_HEIGHT); 39 | } 40 | 41 | fn calc_offset(val: u8, offset: i8, max_val: u8) -> u8 { 42 | if (val == 0 && offset < 0) || (val >= max_val - 1 && offset > 0) { 43 | val 44 | } else { 45 | let off_max = offset as i16 % max_val as i16; 46 | if off_max < 0 { 47 | let x1 = off_max as u8; 48 | let x2 = x1 - std::u8::MAX / 2 - 1 + max_val; 49 | let x3 = x2 - std::u8::MAX / 2 - 1; 50 | (val + x3) % max_val 51 | } else { 52 | (val + off_max as u8) % max_val 53 | } 54 | } 55 | } 56 | } 57 | 58 | impl PartialEq for Position { 59 | fn eq(&self, other: &Self) -> bool { 60 | self.x == other.x && self.y == other.y 61 | } 62 | } 63 | 64 | impl fmt::Debug for Position { 65 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 66 | f.debug_struct("Point").field("x", &self.x).field("y", &self.y).finish() 67 | } 68 | } 69 | 70 | pub struct Block { 71 | pub position: Position, 72 | pub colour: [f32; 4], 73 | } 74 | 75 | #[derive(Debug, PartialEq, Copy, Clone)] 76 | pub enum Direction { 77 | UP, 78 | DOWN, 79 | LEFT, 80 | RIGHT, 81 | } 82 | 83 | impl Direction { 84 | fn opposite(&mut self) -> Direction { 85 | match self { 86 | Direction::UP => Direction::DOWN, 87 | Direction::DOWN => Direction::UP, 88 | Direction::LEFT => Direction::RIGHT, 89 | Direction::RIGHT => Direction::LEFT, 90 | } 91 | } 92 | } 93 | 94 | pub struct Snake { 95 | pub body: VecDeque, 96 | pub direction: Direction, 97 | pub alive: bool, 98 | pub eat: bool, 99 | } 100 | 101 | impl Snake { 102 | fn new() -> Snake { 103 | Snake { 104 | body: VecDeque::from(vec![ 105 | Block { 106 | position: Position::new(), 107 | colour: YELLOW, 108 | }, 109 | Block { 110 | position: Position::new_offset(-1, 0), 111 | colour: GREEN, 112 | }, 113 | Block { 114 | position: Position::new_offset(-2, 0), 115 | colour: GREEN, 116 | }, 117 | ]), 118 | direction: Direction::RIGHT, 119 | alive: true, 120 | eat: false, 121 | } 122 | } 123 | 124 | fn update(&mut self, mut dir: Direction) { 125 | if self.direction == dir.opposite() { 126 | // Do nothing 127 | } else { 128 | self.direction = dir; 129 | } 130 | } 131 | 132 | fn perform_next(&mut self, food_pos: &mut Position) { 133 | if self.alive { 134 | let next_pos = self.next_head_pos(); 135 | if self.check_collide_wall(next_pos) || self.check_collide_body(next_pos) { 136 | self.alive = false; 137 | } else if self.check_eat_food(next_pos, *food_pos) { 138 | self.eat_next(food_pos); 139 | self.eat = true; 140 | } else { 141 | self.move_next(); 142 | } 143 | } 144 | } 145 | 146 | fn next_head_pos(&mut self) -> Position { 147 | let mut current_head = self.body[0].position; 148 | match self.direction { 149 | Direction::RIGHT => current_head.offset(1, 0), 150 | Direction::UP => current_head.offset(0, -1), 151 | Direction::LEFT => current_head.offset(-1, 0), 152 | Direction::DOWN => current_head.offset(0, 1), 153 | } 154 | current_head 155 | } 156 | 157 | fn check_collide_wall(&self, next_pos: Position) -> bool { 158 | self.body[0].position == next_pos 159 | } 160 | 161 | fn check_collide_body(&self, pos: Position) -> bool { 162 | self.body.iter().any(|block| block.position == pos) 163 | } 164 | 165 | fn check_eat_food(&self, next_pos: Position, food_pos: Position) -> bool { 166 | next_pos == food_pos 167 | } 168 | 169 | fn move_next(&mut self) { 170 | for i in (1..self.body.len()).rev() { 171 | self.body[i].position = self.body[i - 1].position; 172 | } 173 | self.body[0].position = self.next_head_pos(); 174 | } 175 | 176 | fn eat_next(&mut self, pos: &mut Position) { 177 | let head = Block { 178 | position: *pos, 179 | colour: YELLOW, 180 | }; 181 | self.body.push_front(head); 182 | self.body[1].colour = GREEN; 183 | } 184 | } 185 | 186 | pub struct Game { 187 | pub snake: Snake, 188 | pub food: Block, 189 | pub time: u32, 190 | pub score: u32, 191 | } 192 | 193 | impl Game { 194 | pub fn new() -> Game { 195 | Game { 196 | snake: Snake::new(), 197 | food: Block { 198 | position: Position::new(), 199 | colour: RED, 200 | }, 201 | time: 0, 202 | score: 0, 203 | } 204 | } 205 | 206 | pub fn init(&mut self) { 207 | self.snake = Snake::new(); 208 | self.food.position = self.get_food_pos(); 209 | self.time = 0; 210 | self.score = 0; 211 | } 212 | 213 | pub fn update(&mut self, dir: Direction) { 214 | self.snake.update(dir); 215 | } 216 | 217 | pub fn next_tick(&mut self, _dt: f64) { 218 | if self.snake.alive { 219 | self.snake.perform_next(&mut self.food.position); 220 | self.time += 1; 221 | if self.snake.eat { 222 | self.score += 1; 223 | self.food.position = self.get_food_pos(); 224 | self.snake.eat = false; 225 | } 226 | } 227 | } 228 | 229 | pub fn run_brain(&mut self, brain: &mut T, fitness_function: fn(i64, i64, i64, i64, i64) -> f64) -> f64 { 230 | self.init(); 231 | let mut fitness: f64 = 0f64; 232 | while self.snake.alive { 233 | let state_initial = self.get_nn_inputs(); 234 | let action = brain.get_action(&state_initial).unwrap(); 235 | let dir = self.get_direction_from_index(action); 236 | self.update(dir); 237 | 238 | // Before moving store some results 239 | let dist_before = self.get_food_dist(); 240 | let time_before = self.time; 241 | 242 | // Make the move 243 | self.next_tick(1f64); 244 | 245 | // After the move store some results 246 | let dist_after = self.get_food_dist(); 247 | let time_after = self.time; 248 | let snake_eat = if self.snake.eat { 1i64 } else { 0i64 }; 249 | let snake_dead = if self.snake.alive { 0i64 } else { 1i64 }; 250 | 251 | // Get fitness & State 252 | let fit = fitness_function( 253 | time_after as i64 - time_before as i64, 254 | dist_before, 255 | dist_after, 256 | snake_eat, 257 | snake_dead, 258 | ); 259 | let state_final = self.get_nn_inputs(); 260 | fitness += fit; 261 | 262 | // Update the Q Matrix 263 | brain.train(&state_initial, action, fit, &state_final); 264 | 265 | // End if we are out of time 266 | if self.time >= NN_MAX_GAME_TIME { 267 | self.snake.alive = false; 268 | } 269 | } 270 | fitness 271 | } 272 | 273 | pub fn get_dir_from_brain(&self, brain: &mut T) -> Direction { 274 | self.get_direction_from_index(brain.get_action(&self.get_nn_inputs()).unwrap()) 275 | } 276 | 277 | pub fn get_direction_from_index(&self, index: usize) -> Direction { 278 | match index { 279 | 0 => Direction::RIGHT, 280 | 1 => Direction::UP, 281 | 2 => Direction::LEFT, 282 | 3 => Direction::DOWN, 283 | _ => self.snake.direction, 284 | } 285 | } 286 | 287 | fn get_food_pos(&mut self) -> Position { 288 | let mut rng = rand::thread_rng(); 289 | loop { 290 | let pos = Position { 291 | x: rng.gen_range(0, BOARD_WIDTH), 292 | y: rng.gen_range(0, BOARD_HEIGHT), 293 | }; 294 | if !self.snake.check_collide_body(pos) { 295 | return pos; 296 | } 297 | } 298 | } 299 | 300 | pub fn get_food_dist(&self) -> i64 { 301 | let dist_x = (self.snake.body[0].position.x as i64 - self.food.position.x as i64).abs(); 302 | let dist_y = (self.snake.body[0].position.y as i64 - self.food.position.y as i64).abs(); 303 | dist_x + dist_y 304 | } 305 | 306 | pub fn get_nn_inputs(&self) -> Vec { 307 | let head_pos = self.snake.body[0].position; 308 | let food_pos = self.food.position; 309 | 310 | let mut pos_right = head_pos; 311 | pos_right.offset(1, 0); 312 | let right_dead = self.get_pos_dead(pos_right); 313 | let right_food = if food_pos.y == head_pos.y && food_pos.x > head_pos.x { 314 | 1f64 315 | } else { 316 | 0f64 317 | }; 318 | 319 | let mut pos_up = head_pos; 320 | pos_up.offset(0, -1); 321 | let up_dead = self.get_pos_dead(pos_up); 322 | let up_food = if food_pos.x == head_pos.x && food_pos.y > head_pos.y { 323 | 1f64 324 | } else { 325 | 0f64 326 | }; 327 | 328 | let mut pos_left = head_pos; 329 | pos_left.offset(-1, 0); 330 | let left_dead = self.get_pos_dead(pos_left); 331 | let left_food = if food_pos.y == head_pos.y && food_pos.x < head_pos.x { 332 | 1f64 333 | } else { 334 | 0f64 335 | }; 336 | 337 | let mut pos_down = head_pos; 338 | pos_down.offset(0, 1); 339 | let down_dead = self.get_pos_dead(pos_down); 340 | let down_food = if food_pos.x == head_pos.x && food_pos.y < head_pos.y { 341 | 1f64 342 | } else { 343 | 0f64 344 | }; 345 | 346 | vec![ 347 | right_dead, right_food, up_dead, up_food, left_dead, left_food, down_dead, down_food, 348 | ] 349 | } 350 | 351 | fn get_pos_dead(&self, pos: Position) -> f64 { 352 | if self.snake.check_collide_wall(pos) || self.snake.check_collide_body(pos) { 353 | 1f64 354 | } else { 355 | 0f64 356 | } 357 | } 358 | } 359 | 360 | #[cfg(test)] 361 | mod tests { 362 | use super::*; 363 | 364 | #[test] 365 | fn test_position_new() { 366 | let pos = Position::new(); 367 | assert_eq!(pos.x, BOARD_WIDTH / 2); 368 | assert_eq!(pos.y, BOARD_HEIGHT / 2); 369 | } 370 | 371 | #[test] 372 | fn test_position_offset() { 373 | let pos1 = Position::new(); 374 | let mut pos2 = Position::new(); 375 | pos2.offset(0, 0); 376 | assert_eq!(pos2.x, pos1.x); 377 | pos2 = Position::new(); 378 | pos2.offset(1, 0); 379 | assert_eq!(pos2.x, pos1.x + 1); 380 | pos2 = Position::new(); 381 | pos2.offset(-1, 0); 382 | assert_eq!(pos2.x + 1, pos1.x); 383 | pos2 = Position::new(); 384 | pos2.offset(BOARD_WIDTH as i8, 0); 385 | assert_eq!(pos2.x, pos1.x); 386 | pos2 = Position::new(); 387 | pos2.offset((BOARD_WIDTH as i8) + 1, 0); 388 | assert_eq!(pos2.x, pos1.x + 1); 389 | pos2 = Position::new(); 390 | pos2.offset(-(BOARD_WIDTH as i8), 0); 391 | assert_eq!(pos2.x, pos1.x); 392 | pos2 = Position::new(); 393 | pos2.offset(-(BOARD_WIDTH as i8) - 1, 0); 394 | assert_eq!(pos2.x + 1, pos1.x); 395 | } 396 | 397 | #[test] 398 | fn test_position_new_offset() { 399 | let mut pos1 = Position::new(); 400 | let mut pos2 = Position::new_offset(0, 0); 401 | assert_eq!(pos1, pos2); 402 | pos1 = Position::new(); 403 | pos1.offset(1, 0); 404 | pos2 = Position::new_offset(1, 0); 405 | assert_eq!(pos1, pos2); 406 | pos1 = Position::new(); 407 | pos1.offset(-1, 0); 408 | pos2 = Position::new_offset(-1, 0); 409 | assert_eq!(pos1, pos2); 410 | pos1 = Position::new(); 411 | pos1.offset(BOARD_WIDTH as i8, 0); 412 | pos2 = Position::new_offset(BOARD_WIDTH as i8, 0); 413 | assert_eq!(pos1, pos2); 414 | pos1 = Position::new(); 415 | pos1.offset(BOARD_WIDTH as i8 + 1, 0); 416 | pos2 = Position::new_offset(BOARD_WIDTH as i8 + 1, 0); 417 | assert_eq!(pos1, pos2); 418 | pos1 = Position::new(); 419 | pos1.offset(-(BOARD_WIDTH as i8), 0); 420 | pos2 = Position::new_offset(-(BOARD_WIDTH as i8), 0); 421 | assert_eq!(pos1, pos2); 422 | pos1 = Position::new(); 423 | pos1.offset(-(BOARD_WIDTH as i8) - 1, 0); 424 | pos2 = Position::new_offset(-(BOARD_WIDTH as i8) - 1, 0); 425 | assert_eq!(pos1, pos2); 426 | } 427 | 428 | #[test] 429 | fn test_snake_new() { 430 | let snake = Snake::new(); 431 | assert_eq!(snake.body.len(), 3); 432 | assert_eq!(snake.direction, Direction::RIGHT); 433 | let pos1 = Position::new(); 434 | let pos2 = Position::new_offset(-1, 0); 435 | let pos3 = Position::new_offset(-2, 0); 436 | assert_eq!(snake.body[0].position, pos1); 437 | assert_eq!(snake.body[1].position, pos2); 438 | assert_eq!(snake.body[2].position, pos3); 439 | } 440 | 441 | #[test] 442 | fn test_snake_next_head_pos() { 443 | let mut snake = Snake::new(); 444 | let next_pos = snake.next_head_pos(); 445 | let pos = Position::new_offset(1, 0); 446 | assert_eq!(next_pos, pos); 447 | } 448 | 449 | #[test] 450 | fn test_snake_check_collide_wall() { 451 | let snake = Snake::new(); 452 | let mut pos = Position::new(); 453 | assert!(snake.check_collide_wall(pos)); 454 | pos = Position::new_offset(1, 0); 455 | assert!(!snake.check_collide_wall(pos)); 456 | } 457 | 458 | #[test] 459 | fn test_snake_check_collide_body() { 460 | let snake = Snake::new(); 461 | let mut pos = Position::new(); 462 | assert!(snake.check_collide_body(pos)); 463 | pos.offset(1, 0); 464 | assert!(!snake.check_collide_body(pos)); 465 | pos.offset(-3, 0); 466 | assert!(snake.check_collide_body(pos)); 467 | pos.offset(-4, 0); 468 | assert!(!snake.check_collide_body(pos)); 469 | } 470 | 471 | #[test] 472 | fn test_snake_move_next() { 473 | let mut snake = Snake::new(); 474 | snake.move_next(); 475 | let mut pos = Position::new_offset(1, 0); 476 | assert_eq!(snake.body[0].position, pos); 477 | pos = Position::new_offset(-1, 0); 478 | assert_eq!(snake.body[2].position, pos); 479 | pos = Position::new_offset(-2, 0); 480 | assert!(!snake.check_collide_body(pos)); 481 | } 482 | 483 | #[test] 484 | fn test_snake_eat_next() { 485 | let mut snake = Snake::new(); 486 | let mut next_pos = snake.next_head_pos(); 487 | snake.eat_next(&mut next_pos); 488 | assert_eq!(snake.body.len(), 4); 489 | let mut pos = Position::new_offset(1, 0); 490 | assert_eq!(snake.body[0].position, pos); 491 | pos = Position::new_offset(-2, 0); 492 | assert_eq!(snake.body[3].position, pos); 493 | pos = Position::new_offset(-3, 0); 494 | assert!(!snake.check_collide_body(pos)); 495 | } 496 | 497 | #[test] 498 | fn test_snake_update() { 499 | let mut snake = Snake::new(); 500 | snake.update(Direction::LEFT); 501 | assert_eq!(snake.direction, Direction::RIGHT); 502 | snake = Snake::new(); 503 | snake.update(Direction::UP); 504 | assert_eq!(snake.direction, Direction::UP); 505 | snake = Snake::new(); 506 | snake.update(Direction::DOWN); 507 | assert_eq!(snake.direction, Direction::DOWN); 508 | snake = Snake::new(); 509 | snake.update(Direction::RIGHT); 510 | assert_eq!(snake.direction, Direction::RIGHT); 511 | } 512 | 513 | #[test] 514 | fn test_snake_perform_next() { 515 | let mut snake = Snake::new(); 516 | let mut food = Position::new_offset(1, 0); 517 | snake.perform_next(&mut food); 518 | assert_eq!(snake.body.len(), 4); 519 | food = Position::new_offset(0, 1); 520 | snake.update(Direction::UP); 521 | snake.perform_next(&mut food); 522 | let mut pos = Position::new_offset(1, -1); 523 | assert_eq!(snake.body[0].position, pos); 524 | pos = Position::new_offset(-1, 0); 525 | assert_eq!(snake.body[3].position, pos); 526 | pos = Position::new_offset(-2, 0); 527 | assert!(!snake.check_collide_body(pos)); 528 | // Check whether we collide with the walls and die 529 | while snake.body[0].position.y >= 1 { 530 | snake.perform_next(&mut food); 531 | } 532 | let next_pos = snake.next_head_pos(); 533 | assert!(snake.check_collide_wall(next_pos)); 534 | assert!(snake.alive); 535 | snake.perform_next(&mut food); 536 | assert!(!snake.alive); 537 | // Check whether we collide with ourself and die 538 | snake = Snake::new(); 539 | food = Position::new_offset(1, 0); 540 | snake.perform_next(&mut food); 541 | food = Position::new_offset(2, 0); 542 | snake.perform_next(&mut food); 543 | assert_eq!(snake.body.len(), 5); 544 | snake.update(Direction::UP); 545 | snake.perform_next(&mut food); 546 | snake.update(Direction::LEFT); 547 | snake.perform_next(&mut food); 548 | snake.update(Direction::DOWN); 549 | snake.perform_next(&mut food); 550 | assert!(!snake.alive); 551 | // Check whether we collide with the walls and die 552 | snake = Snake::new(); 553 | snake.update(Direction::DOWN); 554 | snake.perform_next(&mut food); 555 | pos = Position::new_offset(0, 1); 556 | assert_eq!(snake.body[0].position, pos); 557 | while snake.body[0].position.y <= BOARD_HEIGHT - 2 { 558 | snake.perform_next(&mut food); 559 | } 560 | let next_pos = snake.next_head_pos(); 561 | println!( 562 | "Head: {:?}; Next: {:?}; Alive: {}", 563 | snake.body[0].position, next_pos, snake.alive 564 | ); 565 | snake.perform_next(&mut food); 566 | println!( 567 | "Head: {:?}; Next: {:?}; Alive: {}", 568 | snake.body[0].position, next_pos, snake.alive 569 | ); 570 | assert!(!snake.alive); 571 | } 572 | 573 | #[test] 574 | fn test_game_new() { 575 | let game = Game::new(); 576 | assert_eq!(game.snake.body[0].position, game.food.position); 577 | assert_eq!(game.snake.body.len(), 3); 578 | assert_eq!(game.time, 0); 579 | assert_eq!(game.score, 0); 580 | } 581 | 582 | #[test] 583 | fn test_game_get_food_pos() { 584 | let mut game = Game::new(); 585 | for _ in 0..10 { 586 | let pos = game.get_food_pos(); 587 | assert!(!game.snake.check_collide_body(pos)); 588 | } 589 | } 590 | 591 | #[test] 592 | fn test_game_init() { 593 | let mut game = Game::new(); 594 | game.init(); 595 | assert!(!game.snake.check_collide_body(game.food.position)); 596 | assert_eq!(game.time, 0); 597 | assert_eq!(game.score, 0); 598 | } 599 | 600 | #[test] 601 | fn test_game_update() { 602 | let mut game = Game::new(); 603 | game.init(); 604 | assert_eq!(game.snake.direction, Direction::RIGHT); 605 | game.update(Direction::UP); 606 | assert_eq!(game.snake.direction, Direction::UP); 607 | game.update(Direction::DOWN); 608 | assert_eq!(game.snake.direction, Direction::UP); 609 | } 610 | 611 | #[test] 612 | fn test_game_next_tick() { 613 | let mut game = Game::new(); 614 | game.init(); 615 | let mut pos = Position::new(); 616 | assert_eq!(game.snake.body[0].position, pos); 617 | game.next_tick(0.1); 618 | pos.offset(1, 0); 619 | assert_eq!(game.snake.body[0].position, pos); 620 | } 621 | 622 | #[test] 623 | fn test_game_get_dir_from_brain() { 624 | let mut game = Game::new(); 625 | game.init(); 626 | let mut nn = NN::new_defined(&[[8, 8], [8, 6], [6, 4]]); 627 | let dir = game.get_dir_from_brain(&mut nn); 628 | println!("{:?}", dir); 629 | let mut ql = QLearner::new(8, 4); 630 | let dir = game.get_dir_from_brain(&mut ql); 631 | println!("{:?}", dir); 632 | assert!(true); 633 | } 634 | 635 | #[test] 636 | fn test_game_get_dir_nn() { 637 | let mut game = Game::new(); 638 | game.init(); 639 | let mut nn = NN::new_defined(&[[8, 8], [8, 6], [6, 4]]); 640 | let board = game.get_nn_inputs(); 641 | let out = nn.propagate(&board).unwrap(); 642 | let dir = game.get_dir_from_brain(&mut nn); 643 | 644 | fn get_index_max_float(input: &Vec) -> Option { 645 | input 646 | .iter() 647 | .enumerate() 648 | .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)) 649 | .map(|(index, _)| index) 650 | } 651 | 652 | let max_i = get_index_max_float(&out).unwrap(); 653 | let max_dir = game.get_direction_from_index(max_i); 654 | 655 | assert_eq!(dir, max_dir); 656 | } 657 | 658 | #[test] 659 | fn test_game_run_nn() { 660 | use std::cmp; 661 | 662 | fn fitness_function(_delta_t: i64, dist_before: i64, dist_after: i64, snake_eat: i64, _snake_dead: i64) -> f64 { 663 | let mut fitness: f64 = 0.0_f64; 664 | if dist_after < dist_before { 665 | fitness += 1.0_f64; 666 | } else { 667 | fitness -= 2.0_f64; 668 | } 669 | fitness += 1.0_f64; // Time 670 | fitness += 100.0_f64 * snake_eat as f64; 671 | fitness 672 | //500 * score + time - 2 * food_distance 673 | //100 * score + score * 1000 / (time + 1) + time - food_distance 674 | } 675 | 676 | let mut game = Game::new(); 677 | game.init(); 678 | let mut nn = NN::new_defined(&[[8, 8], [8, 4]]); 679 | game.run_brain(&mut nn, fitness_function); 680 | println!("{}", game.time); 681 | assert!(game.time >= cmp::min(BOARD_WIDTH as u32, BOARD_HEIGHT as u32) / 2); 682 | } 683 | 684 | #[test] 685 | fn test_game_get_nn_inputs() { 686 | let mut game = Game::new(); 687 | game.init(); 688 | println!("****Start****"); 689 | let mut inputs = game.get_nn_inputs(); 690 | assert_eq!(inputs.len(), 8); 691 | println!("****Right Food****"); 692 | game.food.position.x = game.snake.body[0].position.x + 1; 693 | game.food.position.y = game.snake.body[0].position.y; 694 | inputs = game.get_nn_inputs(); 695 | assert_eq!(inputs[1], 1f64); 696 | println!("****Right Dead****"); 697 | for _ in 0..BOARD_WIDTH / 2 - 1 { 698 | game.next_tick(0.1); 699 | } 700 | let inputs = game.get_nn_inputs(); 701 | assert_eq!(inputs[0], 1f64); 702 | println!("****Up Right Dead****"); 703 | game.update(Direction::UP); 704 | for _ in 0..BOARD_HEIGHT / 2 { 705 | game.next_tick(0.1); 706 | } 707 | let inputs = game.get_nn_inputs(); 708 | assert_eq!(inputs[0], 1f64); 709 | assert_eq!(inputs[2], 1f64); 710 | println!("****Up Right Dead****"); 711 | game.update(Direction::LEFT); 712 | for _ in 0..BOARD_WIDTH - 1 { 713 | game.next_tick(0.1); 714 | } 715 | let inputs = game.get_nn_inputs(); 716 | assert_eq!(inputs[0], 1f64); 717 | assert_eq!(inputs[2], 1f64); 718 | assert_eq!(inputs[4], 1f64); 719 | println!("****Down Left Dead****"); 720 | game.update(Direction::DOWN); 721 | for _ in 0..BOARD_HEIGHT - 1 { 722 | game.next_tick(0.1); 723 | } 724 | let inputs = game.get_nn_inputs(); 725 | assert_eq!(inputs[2], 1f64); 726 | assert_eq!(inputs[4], 1f64); 727 | assert_eq!(inputs[6], 1f64); 728 | println!("****Down Right Dead****"); 729 | game.update(Direction::RIGHT); 730 | for _ in 0..BOARD_WIDTH - 1 { 731 | game.next_tick(0.1); 732 | } 733 | let inputs = game.get_nn_inputs(); 734 | assert_eq!(inputs[0], 1f64); 735 | assert_eq!(inputs[4], 1f64); 736 | assert_eq!(inputs[6], 1f64); 737 | } 738 | } 739 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "adler32" 5 | version = "1.0.4" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2" 8 | 9 | [[package]] 10 | name = "andrew" 11 | version = "0.2.1" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | checksum = "9b7f09f89872c2b6b29e319377b1fbe91c6f5947df19a25596e121cf19a7b35e" 14 | dependencies = [ 15 | "bitflags", 16 | "line_drawing", 17 | "rusttype 0.7.9", 18 | "walkdir", 19 | "xdg", 20 | "xml-rs", 21 | ] 22 | 23 | [[package]] 24 | name = "android_glue" 25 | version = "0.2.3" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "000444226fcff248f2bc4c7625be32c63caccfecc2723a2b9f78a7487a49c407" 28 | 29 | [[package]] 30 | name = "approx" 31 | version = "0.3.2" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "f0e60b75072ecd4168020818c0107f2857bb6c4e64252d8d3983f6263b40a5c3" 34 | dependencies = [ 35 | "num-traits", 36 | ] 37 | 38 | [[package]] 39 | name = "autocfg" 40 | version = "1.0.0" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" 43 | 44 | [[package]] 45 | name = "backtrace" 46 | version = "0.3.46" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "b1e692897359247cc6bb902933361652380af0f1b7651ae5c5013407f30e109e" 49 | dependencies = [ 50 | "backtrace-sys", 51 | "cfg-if", 52 | "libc", 53 | "rustc-demangle", 54 | ] 55 | 56 | [[package]] 57 | name = "backtrace-sys" 58 | version = "0.1.36" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "78848718ee1255a2485d1309ad9cdecfc2e7d0362dd11c6829364c6b35ae1bc7" 61 | dependencies = [ 62 | "cc", 63 | "libc", 64 | ] 65 | 66 | [[package]] 67 | name = "bitflags" 68 | version = "1.2.1" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 71 | 72 | [[package]] 73 | name = "block" 74 | version = "0.1.6" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" 77 | 78 | [[package]] 79 | name = "bytemuck" 80 | version = "1.2.0" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "37fa13df2292ecb479ec23aa06f4507928bef07839be9ef15281411076629431" 83 | 84 | [[package]] 85 | name = "byteorder" 86 | version = "1.3.4" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" 89 | 90 | [[package]] 91 | name = "cc" 92 | version = "1.0.52" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "c3d87b23d6a92cd03af510a5ade527033f6aa6fa92161e2d5863a907d4c5e31d" 95 | 96 | [[package]] 97 | name = "cfg-if" 98 | version = "0.1.10" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 101 | 102 | [[package]] 103 | name = "cgl" 104 | version = "0.2.3" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "55e7ec0b74fe5897894cbc207092c577e87c52f8a59e8ca8d97ef37551f60a49" 107 | dependencies = [ 108 | "gleam", 109 | "libc", 110 | ] 111 | 112 | [[package]] 113 | name = "cloudabi" 114 | version = "0.0.3" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" 117 | dependencies = [ 118 | "bitflags", 119 | ] 120 | 121 | [[package]] 122 | name = "cocoa" 123 | version = "0.18.5" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "1706996401131526e36b3b49f0c4d912639ce110996f3ca144d78946727bce54" 126 | dependencies = [ 127 | "bitflags", 128 | "block", 129 | "core-foundation", 130 | "core-graphics", 131 | "foreign-types", 132 | "libc", 133 | "objc", 134 | ] 135 | 136 | [[package]] 137 | name = "color_quant" 138 | version = "1.0.1" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "0dbbb57365263e881e805dc77d94697c9118fd94d8da011240555aa7b23445bd" 141 | 142 | [[package]] 143 | name = "core-foundation" 144 | version = "0.6.4" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "25b9e03f145fd4f2bf705e07b900cd41fc636598fe5dc452fd0db1441c3f496d" 147 | dependencies = [ 148 | "core-foundation-sys", 149 | "libc", 150 | ] 151 | 152 | [[package]] 153 | name = "core-foundation-sys" 154 | version = "0.6.2" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b" 157 | 158 | [[package]] 159 | name = "core-graphics" 160 | version = "0.17.3" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "56790968ab1c8a1202a102e6de05fc6e1ec87da99e4e93e9a7d13efbfc1e95a9" 163 | dependencies = [ 164 | "bitflags", 165 | "core-foundation", 166 | "foreign-types", 167 | "libc", 168 | ] 169 | 170 | [[package]] 171 | name = "crc32fast" 172 | version = "1.2.0" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" 175 | dependencies = [ 176 | "cfg-if", 177 | ] 178 | 179 | [[package]] 180 | name = "crossbeam-deque" 181 | version = "0.7.3" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285" 184 | dependencies = [ 185 | "crossbeam-epoch", 186 | "crossbeam-utils", 187 | "maybe-uninit", 188 | ] 189 | 190 | [[package]] 191 | name = "crossbeam-epoch" 192 | version = "0.8.2" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" 195 | dependencies = [ 196 | "autocfg", 197 | "cfg-if", 198 | "crossbeam-utils", 199 | "lazy_static", 200 | "maybe-uninit", 201 | "memoffset", 202 | "scopeguard", 203 | ] 204 | 205 | [[package]] 206 | name = "crossbeam-queue" 207 | version = "0.2.1" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "c695eeca1e7173472a32221542ae469b3e9aac3a4fc81f7696bcad82029493db" 210 | dependencies = [ 211 | "cfg-if", 212 | "crossbeam-utils", 213 | ] 214 | 215 | [[package]] 216 | name = "crossbeam-utils" 217 | version = "0.7.2" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" 220 | dependencies = [ 221 | "autocfg", 222 | "cfg-if", 223 | "lazy_static", 224 | ] 225 | 226 | [[package]] 227 | name = "deflate" 228 | version = "0.8.4" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "e7e5d2a2273fed52a7f947ee55b092c4057025d7a3e04e5ecdbd25d6c3fb1bd7" 231 | dependencies = [ 232 | "adler32", 233 | "byteorder", 234 | ] 235 | 236 | [[package]] 237 | name = "dlib" 238 | version = "0.4.1" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "77e51249a9d823a4cb79e3eca6dcd756153e8ed0157b6c04775d04bf1b13b76a" 241 | dependencies = [ 242 | "libloading", 243 | ] 244 | 245 | [[package]] 246 | name = "downcast-rs" 247 | version = "1.1.1" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "52ba6eb47c2131e784a38b726eb54c1e1484904f013e576a25354d0124161af6" 250 | 251 | [[package]] 252 | name = "either" 253 | version = "1.5.3" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" 256 | 257 | [[package]] 258 | name = "fnv" 259 | version = "1.0.6" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | checksum = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" 262 | 263 | [[package]] 264 | name = "foreign-types" 265 | version = "0.3.2" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 268 | dependencies = [ 269 | "foreign-types-shared", 270 | ] 271 | 272 | [[package]] 273 | name = "foreign-types-shared" 274 | version = "0.1.1" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 277 | 278 | [[package]] 279 | name = "getrandom" 280 | version = "0.1.14" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" 283 | dependencies = [ 284 | "cfg-if", 285 | "libc", 286 | "wasi", 287 | ] 288 | 289 | [[package]] 290 | name = "gif" 291 | version = "0.10.3" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "471d90201b3b223f3451cd4ad53e34295f16a1df17b1edf3736d47761c3981af" 294 | dependencies = [ 295 | "color_quant", 296 | "lzw", 297 | ] 298 | 299 | [[package]] 300 | name = "gl" 301 | version = "0.11.0" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "d7d8c8e25e8ed44d4813809205090162723a866fb4be3a9d8bb983c9a0bf98f1" 304 | dependencies = [ 305 | "gl_generator 0.10.0", 306 | ] 307 | 308 | [[package]] 309 | name = "gl_generator" 310 | version = "0.10.0" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "a0ffaf173cf76c73a73e080366bf556b4776ece104b06961766ff11449f38604" 313 | dependencies = [ 314 | "khronos_api 3.1.0", 315 | "log", 316 | "xml-rs", 317 | ] 318 | 319 | [[package]] 320 | name = "gl_generator" 321 | version = "0.13.1" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "ca98bbde17256e02d17336a6bdb5a50f7d0ccacee502e191d3e3d0ec2f96f84a" 324 | dependencies = [ 325 | "khronos_api 3.1.0", 326 | "log", 327 | "xml-rs", 328 | ] 329 | 330 | [[package]] 331 | name = "gleam" 332 | version = "0.6.19" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "cae10d7c99d0e77b4766e850a60898a17c1abaf01075531f1066f03dc7dc5fc5" 335 | dependencies = [ 336 | "gl_generator 0.13.1", 337 | ] 338 | 339 | [[package]] 340 | name = "glutin" 341 | version = "0.21.2" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "5371b35b309dace06be1b81b5f6adb1c9de578b7dbe1e74bf7e4ef762cf6febd" 344 | dependencies = [ 345 | "android_glue", 346 | "cgl", 347 | "cocoa", 348 | "core-foundation", 349 | "core-graphics", 350 | "glutin_egl_sys", 351 | "glutin_emscripten_sys", 352 | "glutin_gles2_sys", 353 | "glutin_glx_sys", 354 | "glutin_wgl_sys", 355 | "lazy_static", 356 | "libloading", 357 | "objc", 358 | "osmesa-sys", 359 | "parking_lot", 360 | "wayland-client", 361 | "winapi", 362 | "winit", 363 | ] 364 | 365 | [[package]] 366 | name = "glutin_egl_sys" 367 | version = "0.1.4" 368 | source = "registry+https://github.com/rust-lang/crates.io-index" 369 | checksum = "772edef3b28b8ad41e4ea202748e65eefe8e5ffd1f4535f1219793dbb20b3d4c" 370 | dependencies = [ 371 | "gl_generator 0.13.1", 372 | "winapi", 373 | ] 374 | 375 | [[package]] 376 | name = "glutin_emscripten_sys" 377 | version = "0.1.1" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "80de4146df76e8a6c32b03007bc764ff3249dcaeb4f675d68a06caf1bac363f1" 380 | 381 | [[package]] 382 | name = "glutin_gles2_sys" 383 | version = "0.1.4" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | checksum = "07e853d96bebcb8e53e445225c3009758c6f5960d44f2543245f6f07b567dae0" 386 | dependencies = [ 387 | "gl_generator 0.13.1", 388 | "objc", 389 | ] 390 | 391 | [[package]] 392 | name = "glutin_glx_sys" 393 | version = "0.1.6" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "08c243de74d6cf5ea100c788826d2fb9319de315485dd4b310811a663b3809c3" 396 | dependencies = [ 397 | "gl_generator 0.13.1", 398 | "x11-dl", 399 | ] 400 | 401 | [[package]] 402 | name = "glutin_wgl_sys" 403 | version = "0.1.4" 404 | source = "registry+https://github.com/rust-lang/crates.io-index" 405 | checksum = "a93dba7ee3a0feeac0f437141ff25e71ce2066bcf1a706acab1559ffff94eb6a" 406 | dependencies = [ 407 | "gl_generator 0.13.1", 408 | ] 409 | 410 | [[package]] 411 | name = "hermit-abi" 412 | version = "0.1.12" 413 | source = "registry+https://github.com/rust-lang/crates.io-index" 414 | checksum = "61565ff7aaace3525556587bd2dc31d4a07071957be715e63ce7b1eccf51a8f4" 415 | dependencies = [ 416 | "libc", 417 | ] 418 | 419 | [[package]] 420 | name = "image" 421 | version = "0.23.4" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | checksum = "9117f4167a8f21fa2bb3f17a652a760acd7572645281c98e3b612a26242c96ee" 424 | dependencies = [ 425 | "bytemuck", 426 | "byteorder", 427 | "gif", 428 | "jpeg-decoder", 429 | "num-iter", 430 | "num-rational", 431 | "num-traits", 432 | "png", 433 | "scoped_threadpool", 434 | "tiff", 435 | ] 436 | 437 | [[package]] 438 | name = "inflate" 439 | version = "0.4.5" 440 | source = "registry+https://github.com/rust-lang/crates.io-index" 441 | checksum = "1cdb29978cc5797bd8dcc8e5bf7de604891df2a8dc576973d71a281e916db2ff" 442 | dependencies = [ 443 | "adler32", 444 | ] 445 | 446 | [[package]] 447 | name = "interpolation" 448 | version = "0.2.0" 449 | source = "registry+https://github.com/rust-lang/crates.io-index" 450 | checksum = "d3b7357d2bbc5ee92f8e899ab645233e43d21407573cceb37fed8bc3dede2c02" 451 | 452 | [[package]] 453 | name = "itertools" 454 | version = "0.9.0" 455 | source = "registry+https://github.com/rust-lang/crates.io-index" 456 | checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" 457 | dependencies = [ 458 | "either", 459 | ] 460 | 461 | [[package]] 462 | name = "jpeg-decoder" 463 | version = "0.1.19" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | checksum = "5b47b4c4e017b01abdc5bcc126d2d1002e5a75bbe3ce73f9f4f311a916363704" 466 | dependencies = [ 467 | "byteorder", 468 | "rayon", 469 | ] 470 | 471 | [[package]] 472 | name = "khronos_api" 473 | version = "2.2.0" 474 | source = "registry+https://github.com/rust-lang/crates.io-index" 475 | checksum = "037ab472c33f67b5fbd3e9163a2645319e5356fcd355efa6d4eb7fff4bbcb554" 476 | 477 | [[package]] 478 | name = "khronos_api" 479 | version = "3.1.0" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" 482 | 483 | [[package]] 484 | name = "lazy_static" 485 | version = "1.4.0" 486 | source = "registry+https://github.com/rust-lang/crates.io-index" 487 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 488 | 489 | [[package]] 490 | name = "libc" 491 | version = "0.2.69" 492 | source = "registry+https://github.com/rust-lang/crates.io-index" 493 | checksum = "99e85c08494b21a9054e7fe1374a732aeadaff3980b6990b94bfd3a70f690005" 494 | 495 | [[package]] 496 | name = "libloading" 497 | version = "0.5.2" 498 | source = "registry+https://github.com/rust-lang/crates.io-index" 499 | checksum = "f2b111a074963af1d37a139918ac6d49ad1d0d5e47f72fd55388619691a7d753" 500 | dependencies = [ 501 | "cc", 502 | "winapi", 503 | ] 504 | 505 | [[package]] 506 | name = "line_drawing" 507 | version = "0.7.0" 508 | source = "registry+https://github.com/rust-lang/crates.io-index" 509 | checksum = "5cc7ad3d82c845bdb5dde34ffdcc7a5fb4d2996e1e1ee0f19c33bc80e15196b9" 510 | dependencies = [ 511 | "num-traits", 512 | ] 513 | 514 | [[package]] 515 | name = "lock_api" 516 | version = "0.3.4" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75" 519 | dependencies = [ 520 | "scopeguard", 521 | ] 522 | 523 | [[package]] 524 | name = "log" 525 | version = "0.4.8" 526 | source = "registry+https://github.com/rust-lang/crates.io-index" 527 | checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" 528 | dependencies = [ 529 | "cfg-if", 530 | ] 531 | 532 | [[package]] 533 | name = "lzw" 534 | version = "0.10.0" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | checksum = "7d947cbb889ed21c2a84be6ffbaebf5b4e0f4340638cba0444907e38b56be084" 537 | 538 | [[package]] 539 | name = "malloc_buf" 540 | version = "0.0.6" 541 | source = "registry+https://github.com/rust-lang/crates.io-index" 542 | checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" 543 | dependencies = [ 544 | "libc", 545 | ] 546 | 547 | [[package]] 548 | name = "maybe-uninit" 549 | version = "2.0.0" 550 | source = "registry+https://github.com/rust-lang/crates.io-index" 551 | checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" 552 | 553 | [[package]] 554 | name = "memmap" 555 | version = "0.7.0" 556 | source = "registry+https://github.com/rust-lang/crates.io-index" 557 | checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" 558 | dependencies = [ 559 | "libc", 560 | "winapi", 561 | ] 562 | 563 | [[package]] 564 | name = "memoffset" 565 | version = "0.5.4" 566 | source = "registry+https://github.com/rust-lang/crates.io-index" 567 | checksum = "b4fc2c02a7e374099d4ee95a193111f72d2110197fe200272371758f6c3643d8" 568 | dependencies = [ 569 | "autocfg", 570 | ] 571 | 572 | [[package]] 573 | name = "miniz_oxide" 574 | version = "0.3.6" 575 | source = "registry+https://github.com/rust-lang/crates.io-index" 576 | checksum = "aa679ff6578b1cddee93d7e82e263b94a575e0bfced07284eb0c037c1d2416a5" 577 | dependencies = [ 578 | "adler32", 579 | ] 580 | 581 | [[package]] 582 | name = "nix" 583 | version = "0.14.1" 584 | source = "registry+https://github.com/rust-lang/crates.io-index" 585 | checksum = "6c722bee1037d430d0f8e687bbdbf222f27cc6e4e68d5caf630857bb2b6dbdce" 586 | dependencies = [ 587 | "bitflags", 588 | "cc", 589 | "cfg-if", 590 | "libc", 591 | "void", 592 | ] 593 | 594 | [[package]] 595 | name = "num-integer" 596 | version = "0.1.42" 597 | source = "registry+https://github.com/rust-lang/crates.io-index" 598 | checksum = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" 599 | dependencies = [ 600 | "autocfg", 601 | "num-traits", 602 | ] 603 | 604 | [[package]] 605 | name = "num-iter" 606 | version = "0.1.40" 607 | source = "registry+https://github.com/rust-lang/crates.io-index" 608 | checksum = "dfb0800a0291891dd9f4fe7bd9c19384f98f7fbe0cd0f39a2c6b88b9868bbc00" 609 | dependencies = [ 610 | "autocfg", 611 | "num-integer", 612 | "num-traits", 613 | ] 614 | 615 | [[package]] 616 | name = "num-rational" 617 | version = "0.2.4" 618 | source = "registry+https://github.com/rust-lang/crates.io-index" 619 | checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" 620 | dependencies = [ 621 | "autocfg", 622 | "num-integer", 623 | "num-traits", 624 | ] 625 | 626 | [[package]] 627 | name = "num-traits" 628 | version = "0.2.11" 629 | source = "registry+https://github.com/rust-lang/crates.io-index" 630 | checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" 631 | dependencies = [ 632 | "autocfg", 633 | ] 634 | 635 | [[package]] 636 | name = "num_cpus" 637 | version = "1.13.0" 638 | source = "registry+https://github.com/rust-lang/crates.io-index" 639 | checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" 640 | dependencies = [ 641 | "hermit-abi", 642 | "libc", 643 | ] 644 | 645 | [[package]] 646 | name = "objc" 647 | version = "0.2.7" 648 | source = "registry+https://github.com/rust-lang/crates.io-index" 649 | checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" 650 | dependencies = [ 651 | "malloc_buf", 652 | ] 653 | 654 | [[package]] 655 | name = "ordered-float" 656 | version = "1.0.2" 657 | source = "registry+https://github.com/rust-lang/crates.io-index" 658 | checksum = "18869315e81473c951eb56ad5558bbc56978562d3ecfb87abb7a1e944cea4518" 659 | dependencies = [ 660 | "num-traits", 661 | ] 662 | 663 | [[package]] 664 | name = "osmesa-sys" 665 | version = "0.1.2" 666 | source = "registry+https://github.com/rust-lang/crates.io-index" 667 | checksum = "88cfece6e95d2e717e0872a7f53a8684712ad13822a7979bc760b9c77ec0013b" 668 | dependencies = [ 669 | "shared_library", 670 | ] 671 | 672 | [[package]] 673 | name = "parking_lot" 674 | version = "0.9.0" 675 | source = "registry+https://github.com/rust-lang/crates.io-index" 676 | checksum = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252" 677 | dependencies = [ 678 | "lock_api", 679 | "parking_lot_core", 680 | "rustc_version", 681 | ] 682 | 683 | [[package]] 684 | name = "parking_lot_core" 685 | version = "0.6.2" 686 | source = "registry+https://github.com/rust-lang/crates.io-index" 687 | checksum = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" 688 | dependencies = [ 689 | "cfg-if", 690 | "cloudabi", 691 | "libc", 692 | "redox_syscall", 693 | "rustc_version", 694 | "smallvec", 695 | "winapi", 696 | ] 697 | 698 | [[package]] 699 | name = "percent-encoding" 700 | version = "2.1.0" 701 | source = "registry+https://github.com/rust-lang/crates.io-index" 702 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 703 | 704 | [[package]] 705 | name = "piston" 706 | version = "0.49.0" 707 | source = "registry+https://github.com/rust-lang/crates.io-index" 708 | checksum = "b20fda60cf7c0cf8fdbdac34a0418bea5480bb4a9ba73379c54ffd2a125f19f8" 709 | dependencies = [ 710 | "pistoncore-event_loop", 711 | "pistoncore-input", 712 | "pistoncore-window", 713 | ] 714 | 715 | [[package]] 716 | name = "piston-float" 717 | version = "1.0.0" 718 | source = "registry+https://github.com/rust-lang/crates.io-index" 719 | checksum = "f900be47e312e126cc71d35548e8e31edd3901b92ab82d1c4c4757e6b5526564" 720 | 721 | [[package]] 722 | name = "piston-graphics_api_version" 723 | version = "0.2.0" 724 | source = "registry+https://github.com/rust-lang/crates.io-index" 725 | checksum = "c54f48a0072e8b2490935bc46a3ddb50ca96fdc071f675e9296063598836b7ac" 726 | 727 | [[package]] 728 | name = "piston-shaders_graphics2d" 729 | version = "0.3.1" 730 | source = "registry+https://github.com/rust-lang/crates.io-index" 731 | checksum = "97bc17dac1dfff3e5cb84116062c7b46ff9d3dc0d88696a46d2f054cf64a10b6" 732 | 733 | [[package]] 734 | name = "piston-texture" 735 | version = "0.8.0" 736 | source = "registry+https://github.com/rust-lang/crates.io-index" 737 | checksum = "62c93564eef40a9920d026697f63d224efd7ac80981fb418fe1dad447c2d9bdd" 738 | 739 | [[package]] 740 | name = "piston-viewport" 741 | version = "1.0.0" 742 | source = "registry+https://github.com/rust-lang/crates.io-index" 743 | checksum = "01abb19b781051290d0837b9294c26d419cc4156907c21ffe86705e219446798" 744 | dependencies = [ 745 | "piston-float", 746 | ] 747 | 748 | [[package]] 749 | name = "piston2d-graphics" 750 | version = "0.36.0" 751 | source = "registry+https://github.com/rust-lang/crates.io-index" 752 | checksum = "1b7c7c36f6df5e0473d3ca3dd56a27acd75372903fcad1c647594f8c91ce1ff3" 753 | dependencies = [ 754 | "fnv", 755 | "interpolation", 756 | "piston-texture", 757 | "piston-viewport", 758 | "read_color", 759 | "rusttype 0.8.3", 760 | "vecmath", 761 | ] 762 | 763 | [[package]] 764 | name = "piston2d-opengl_graphics" 765 | version = "0.72.0" 766 | source = "registry+https://github.com/rust-lang/crates.io-index" 767 | checksum = "a4f140a01b3fb2b28216288a52111de6ce46772964d0447463b55ede4da00607" 768 | dependencies = [ 769 | "fnv", 770 | "gl", 771 | "image", 772 | "khronos_api 2.2.0", 773 | "piston-shaders_graphics2d", 774 | "piston-texture", 775 | "piston-viewport", 776 | "piston2d-graphics", 777 | "shader_version", 778 | ] 779 | 780 | [[package]] 781 | name = "pistoncore-event_loop" 782 | version = "0.49.0" 783 | source = "registry+https://github.com/rust-lang/crates.io-index" 784 | checksum = "557307a26418afb123d77c8a8a6589f40a82e6a5ad7b94421aaf0bd2779231c1" 785 | dependencies = [ 786 | "pistoncore-input", 787 | "pistoncore-window", 788 | ] 789 | 790 | [[package]] 791 | name = "pistoncore-glutin_window" 792 | version = "0.63.0" 793 | source = "registry+https://github.com/rust-lang/crates.io-index" 794 | checksum = "acc73c42620d0596b7eae0b1c766110cb98171c37501409a2027969d55f70b36" 795 | dependencies = [ 796 | "gl", 797 | "glutin", 798 | "pistoncore-input", 799 | "pistoncore-window", 800 | "shader_version", 801 | ] 802 | 803 | [[package]] 804 | name = "pistoncore-input" 805 | version = "0.28.0" 806 | source = "registry+https://github.com/rust-lang/crates.io-index" 807 | checksum = "05730818d7db7272339b8e2e36c0dbe33697e3df793f94ba1d1525d06ffa2ce7" 808 | dependencies = [ 809 | "bitflags", 810 | "piston-viewport", 811 | "serde", 812 | "serde_derive", 813 | ] 814 | 815 | [[package]] 816 | name = "pistoncore-window" 817 | version = "0.44.0" 818 | source = "registry+https://github.com/rust-lang/crates.io-index" 819 | checksum = "18041bc73291d9466796e300137d7cff82c9aab2ff7f1fe7fe4ca675ad1d1ea3" 820 | dependencies = [ 821 | "piston-graphics_api_version", 822 | "pistoncore-input", 823 | ] 824 | 825 | [[package]] 826 | name = "pkg-config" 827 | version = "0.3.17" 828 | source = "registry+https://github.com/rust-lang/crates.io-index" 829 | checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" 830 | 831 | [[package]] 832 | name = "png" 833 | version = "0.16.3" 834 | source = "registry+https://github.com/rust-lang/crates.io-index" 835 | checksum = "2c68a431ed29933a4eb5709aca9800989758c97759345860fa5db3cfced0b65d" 836 | dependencies = [ 837 | "bitflags", 838 | "crc32fast", 839 | "deflate", 840 | "inflate", 841 | ] 842 | 843 | [[package]] 844 | name = "ppv-lite86" 845 | version = "0.2.6" 846 | source = "registry+https://github.com/rust-lang/crates.io-index" 847 | checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" 848 | 849 | [[package]] 850 | name = "proc-macro2" 851 | version = "0.4.30" 852 | source = "registry+https://github.com/rust-lang/crates.io-index" 853 | checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" 854 | dependencies = [ 855 | "unicode-xid 0.1.0", 856 | ] 857 | 858 | [[package]] 859 | name = "proc-macro2" 860 | version = "1.0.10" 861 | source = "registry+https://github.com/rust-lang/crates.io-index" 862 | checksum = "df246d292ff63439fea9bc8c0a270bed0e390d5ebd4db4ba15aba81111b5abe3" 863 | dependencies = [ 864 | "unicode-xid 0.2.0", 865 | ] 866 | 867 | [[package]] 868 | name = "quote" 869 | version = "0.6.13" 870 | source = "registry+https://github.com/rust-lang/crates.io-index" 871 | checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" 872 | dependencies = [ 873 | "proc-macro2 0.4.30", 874 | ] 875 | 876 | [[package]] 877 | name = "quote" 878 | version = "1.0.3" 879 | source = "registry+https://github.com/rust-lang/crates.io-index" 880 | checksum = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f" 881 | dependencies = [ 882 | "proc-macro2 1.0.10", 883 | ] 884 | 885 | [[package]] 886 | name = "rand" 887 | version = "0.7.3" 888 | source = "registry+https://github.com/rust-lang/crates.io-index" 889 | checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" 890 | dependencies = [ 891 | "getrandom", 892 | "libc", 893 | "rand_chacha", 894 | "rand_core", 895 | "rand_hc", 896 | ] 897 | 898 | [[package]] 899 | name = "rand_chacha" 900 | version = "0.2.2" 901 | source = "registry+https://github.com/rust-lang/crates.io-index" 902 | checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" 903 | dependencies = [ 904 | "ppv-lite86", 905 | "rand_core", 906 | ] 907 | 908 | [[package]] 909 | name = "rand_core" 910 | version = "0.5.1" 911 | source = "registry+https://github.com/rust-lang/crates.io-index" 912 | checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" 913 | dependencies = [ 914 | "getrandom", 915 | ] 916 | 917 | [[package]] 918 | name = "rand_distr" 919 | version = "0.2.2" 920 | source = "registry+https://github.com/rust-lang/crates.io-index" 921 | checksum = "96977acbdd3a6576fb1d27391900035bf3863d4a16422973a409b488cf29ffb2" 922 | dependencies = [ 923 | "rand", 924 | ] 925 | 926 | [[package]] 927 | name = "rand_hc" 928 | version = "0.2.0" 929 | source = "registry+https://github.com/rust-lang/crates.io-index" 930 | checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" 931 | dependencies = [ 932 | "rand_core", 933 | ] 934 | 935 | [[package]] 936 | name = "raw-window-handle" 937 | version = "0.3.3" 938 | source = "registry+https://github.com/rust-lang/crates.io-index" 939 | checksum = "0a441a7a6c80ad6473bd4b74ec1c9a4c951794285bf941c2126f607c72e48211" 940 | dependencies = [ 941 | "libc", 942 | ] 943 | 944 | [[package]] 945 | name = "rayon" 946 | version = "1.3.0" 947 | source = "registry+https://github.com/rust-lang/crates.io-index" 948 | checksum = "db6ce3297f9c85e16621bb8cca38a06779ffc31bb8184e1be4bed2be4678a098" 949 | dependencies = [ 950 | "crossbeam-deque", 951 | "either", 952 | "rayon-core", 953 | ] 954 | 955 | [[package]] 956 | name = "rayon-core" 957 | version = "1.7.0" 958 | source = "registry+https://github.com/rust-lang/crates.io-index" 959 | checksum = "08a89b46efaf957e52b18062fb2f4660f8b8a4dde1807ca002690868ef2c85a9" 960 | dependencies = [ 961 | "crossbeam-deque", 962 | "crossbeam-queue", 963 | "crossbeam-utils", 964 | "lazy_static", 965 | "num_cpus", 966 | ] 967 | 968 | [[package]] 969 | name = "read_color" 970 | version = "1.0.0" 971 | source = "registry+https://github.com/rust-lang/crates.io-index" 972 | checksum = "9f4c8858baa4ad3c8bcc156ae91a0ffe22b76a3975c40c49b4f04c15c6bce0da" 973 | 974 | [[package]] 975 | name = "redox_syscall" 976 | version = "0.1.56" 977 | source = "registry+https://github.com/rust-lang/crates.io-index" 978 | checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" 979 | 980 | [[package]] 981 | name = "rustc-demangle" 982 | version = "0.1.16" 983 | source = "registry+https://github.com/rust-lang/crates.io-index" 984 | checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" 985 | 986 | [[package]] 987 | name = "rustc_version" 988 | version = "0.2.3" 989 | source = "registry+https://github.com/rust-lang/crates.io-index" 990 | checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" 991 | dependencies = [ 992 | "semver", 993 | ] 994 | 995 | [[package]] 996 | name = "rusttype" 997 | version = "0.7.9" 998 | source = "registry+https://github.com/rust-lang/crates.io-index" 999 | checksum = "310942406a39981bed7e12b09182a221a29e0990f3e7e0c971f131922ed135d5" 1000 | dependencies = [ 1001 | "rusttype 0.8.3", 1002 | ] 1003 | 1004 | [[package]] 1005 | name = "rusttype" 1006 | version = "0.8.3" 1007 | source = "registry+https://github.com/rust-lang/crates.io-index" 1008 | checksum = "9f61411055101f7b60ecf1041d87fb74205fb20b0c7a723f07ef39174cf6b4c0" 1009 | dependencies = [ 1010 | "approx", 1011 | "ordered-float", 1012 | "stb_truetype", 1013 | ] 1014 | 1015 | [[package]] 1016 | name = "same-file" 1017 | version = "1.0.6" 1018 | source = "registry+https://github.com/rust-lang/crates.io-index" 1019 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 1020 | dependencies = [ 1021 | "winapi-util", 1022 | ] 1023 | 1024 | [[package]] 1025 | name = "scoped_threadpool" 1026 | version = "0.1.9" 1027 | source = "registry+https://github.com/rust-lang/crates.io-index" 1028 | checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" 1029 | 1030 | [[package]] 1031 | name = "scopeguard" 1032 | version = "1.1.0" 1033 | source = "registry+https://github.com/rust-lang/crates.io-index" 1034 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 1035 | 1036 | [[package]] 1037 | name = "semver" 1038 | version = "0.9.0" 1039 | source = "registry+https://github.com/rust-lang/crates.io-index" 1040 | checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 1041 | dependencies = [ 1042 | "semver-parser", 1043 | ] 1044 | 1045 | [[package]] 1046 | name = "semver-parser" 1047 | version = "0.7.0" 1048 | source = "registry+https://github.com/rust-lang/crates.io-index" 1049 | checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 1050 | 1051 | [[package]] 1052 | name = "serde" 1053 | version = "1.0.106" 1054 | source = "registry+https://github.com/rust-lang/crates.io-index" 1055 | checksum = "36df6ac6412072f67cf767ebbde4133a5b2e88e76dc6187fa7104cd16f783399" 1056 | 1057 | [[package]] 1058 | name = "serde_derive" 1059 | version = "1.0.106" 1060 | source = "registry+https://github.com/rust-lang/crates.io-index" 1061 | checksum = "9e549e3abf4fb8621bd1609f11dfc9f5e50320802273b12f3811a67e6716ea6c" 1062 | dependencies = [ 1063 | "proc-macro2 1.0.10", 1064 | "quote 1.0.3", 1065 | "syn", 1066 | ] 1067 | 1068 | [[package]] 1069 | name = "shader_version" 1070 | version = "0.6.0" 1071 | source = "registry+https://github.com/rust-lang/crates.io-index" 1072 | checksum = "478e783fb7533629e19120ca01246884f8effff5f4d20ce81d34c7c47a2866fa" 1073 | dependencies = [ 1074 | "piston-graphics_api_version", 1075 | ] 1076 | 1077 | [[package]] 1078 | name = "shared_library" 1079 | version = "0.1.9" 1080 | source = "registry+https://github.com/rust-lang/crates.io-index" 1081 | checksum = "5a9e7e0f2bfae24d8a5b5a66c5b257a83c7412304311512a0c054cd5e619da11" 1082 | dependencies = [ 1083 | "lazy_static", 1084 | "libc", 1085 | ] 1086 | 1087 | [[package]] 1088 | name = "smallvec" 1089 | version = "0.6.13" 1090 | source = "registry+https://github.com/rust-lang/crates.io-index" 1091 | checksum = "f7b0758c52e15a8b5e3691eae6cc559f08eee9406e548a4477ba4e67770a82b6" 1092 | dependencies = [ 1093 | "maybe-uninit", 1094 | ] 1095 | 1096 | [[package]] 1097 | name = "smithay-client-toolkit" 1098 | version = "0.4.6" 1099 | source = "registry+https://github.com/rust-lang/crates.io-index" 1100 | checksum = "2ccb8c57049b2a34d2cc2b203fa785020ba0129d31920ef0d317430adaf748fa" 1101 | dependencies = [ 1102 | "andrew", 1103 | "bitflags", 1104 | "dlib", 1105 | "lazy_static", 1106 | "memmap", 1107 | "nix", 1108 | "wayland-client", 1109 | "wayland-commons", 1110 | "wayland-protocols", 1111 | ] 1112 | 1113 | [[package]] 1114 | name = "snake_01" 1115 | version = "0.1.0" 1116 | dependencies = [ 1117 | "itertools", 1118 | "piston", 1119 | "piston2d-graphics", 1120 | "piston2d-opengl_graphics", 1121 | "pistoncore-glutin_window", 1122 | "rand", 1123 | "rand_distr", 1124 | "rayon", 1125 | ] 1126 | 1127 | [[package]] 1128 | name = "stb_truetype" 1129 | version = "0.3.1" 1130 | source = "registry+https://github.com/rust-lang/crates.io-index" 1131 | checksum = "f77b6b07e862c66a9f3e62a07588fee67cd90a9135a2b942409f195507b4fb51" 1132 | dependencies = [ 1133 | "byteorder", 1134 | ] 1135 | 1136 | [[package]] 1137 | name = "syn" 1138 | version = "1.0.18" 1139 | source = "registry+https://github.com/rust-lang/crates.io-index" 1140 | checksum = "410a7488c0a728c7ceb4ad59b9567eb4053d02e8cc7f5c0e0eeeb39518369213" 1141 | dependencies = [ 1142 | "proc-macro2 1.0.10", 1143 | "quote 1.0.3", 1144 | "unicode-xid 0.2.0", 1145 | ] 1146 | 1147 | [[package]] 1148 | name = "tiff" 1149 | version = "0.4.0" 1150 | source = "registry+https://github.com/rust-lang/crates.io-index" 1151 | checksum = "002351e428db1eb1d8656d4ca61947c3519ac3191e1c804d4600cd32093b77ad" 1152 | dependencies = [ 1153 | "byteorder", 1154 | "lzw", 1155 | "miniz_oxide", 1156 | ] 1157 | 1158 | [[package]] 1159 | name = "unicode-xid" 1160 | version = "0.1.0" 1161 | source = "registry+https://github.com/rust-lang/crates.io-index" 1162 | checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" 1163 | 1164 | [[package]] 1165 | name = "unicode-xid" 1166 | version = "0.2.0" 1167 | source = "registry+https://github.com/rust-lang/crates.io-index" 1168 | checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" 1169 | 1170 | [[package]] 1171 | name = "vecmath" 1172 | version = "1.0.0" 1173 | source = "registry+https://github.com/rust-lang/crates.io-index" 1174 | checksum = "956ae1e0d85bca567dee1dcf87fb1ca2e792792f66f87dced8381f99cd91156a" 1175 | dependencies = [ 1176 | "piston-float", 1177 | ] 1178 | 1179 | [[package]] 1180 | name = "void" 1181 | version = "1.0.2" 1182 | source = "registry+https://github.com/rust-lang/crates.io-index" 1183 | checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" 1184 | 1185 | [[package]] 1186 | name = "walkdir" 1187 | version = "2.3.1" 1188 | source = "registry+https://github.com/rust-lang/crates.io-index" 1189 | checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" 1190 | dependencies = [ 1191 | "same-file", 1192 | "winapi", 1193 | "winapi-util", 1194 | ] 1195 | 1196 | [[package]] 1197 | name = "wasi" 1198 | version = "0.9.0+wasi-snapshot-preview1" 1199 | source = "registry+https://github.com/rust-lang/crates.io-index" 1200 | checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 1201 | 1202 | [[package]] 1203 | name = "wayland-client" 1204 | version = "0.21.13" 1205 | source = "registry+https://github.com/rust-lang/crates.io-index" 1206 | checksum = "49963e5f9eeaf637bfcd1b9f0701c99fd5cd05225eb51035550d4272806f2713" 1207 | dependencies = [ 1208 | "bitflags", 1209 | "downcast-rs", 1210 | "libc", 1211 | "nix", 1212 | "wayland-commons", 1213 | "wayland-scanner", 1214 | "wayland-sys", 1215 | ] 1216 | 1217 | [[package]] 1218 | name = "wayland-commons" 1219 | version = "0.21.13" 1220 | source = "registry+https://github.com/rust-lang/crates.io-index" 1221 | checksum = "40c08896768b667e1df195d88a62a53a2d1351a1ed96188be79c196b35bb32ec" 1222 | dependencies = [ 1223 | "nix", 1224 | "wayland-sys", 1225 | ] 1226 | 1227 | [[package]] 1228 | name = "wayland-protocols" 1229 | version = "0.21.13" 1230 | source = "registry+https://github.com/rust-lang/crates.io-index" 1231 | checksum = "4afde2ea2a428eee6d7d2c8584fdbe8b82eee8b6c353e129a434cd6e07f42145" 1232 | dependencies = [ 1233 | "bitflags", 1234 | "wayland-client", 1235 | "wayland-commons", 1236 | "wayland-scanner", 1237 | "wayland-sys", 1238 | ] 1239 | 1240 | [[package]] 1241 | name = "wayland-scanner" 1242 | version = "0.21.13" 1243 | source = "registry+https://github.com/rust-lang/crates.io-index" 1244 | checksum = "bf3828c568714507315ee425a9529edc4a4aa9901409e373e9e0027e7622b79e" 1245 | dependencies = [ 1246 | "proc-macro2 0.4.30", 1247 | "quote 0.6.13", 1248 | "xml-rs", 1249 | ] 1250 | 1251 | [[package]] 1252 | name = "wayland-sys" 1253 | version = "0.21.13" 1254 | source = "registry+https://github.com/rust-lang/crates.io-index" 1255 | checksum = "520ab0fd578017a0ee2206623ba9ef4afe5e8f23ca7b42f6acfba2f4e66b1628" 1256 | dependencies = [ 1257 | "dlib", 1258 | "lazy_static", 1259 | ] 1260 | 1261 | [[package]] 1262 | name = "winapi" 1263 | version = "0.3.8" 1264 | source = "registry+https://github.com/rust-lang/crates.io-index" 1265 | checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" 1266 | dependencies = [ 1267 | "winapi-i686-pc-windows-gnu", 1268 | "winapi-x86_64-pc-windows-gnu", 1269 | ] 1270 | 1271 | [[package]] 1272 | name = "winapi-i686-pc-windows-gnu" 1273 | version = "0.4.0" 1274 | source = "registry+https://github.com/rust-lang/crates.io-index" 1275 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1276 | 1277 | [[package]] 1278 | name = "winapi-util" 1279 | version = "0.1.5" 1280 | source = "registry+https://github.com/rust-lang/crates.io-index" 1281 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1282 | dependencies = [ 1283 | "winapi", 1284 | ] 1285 | 1286 | [[package]] 1287 | name = "winapi-x86_64-pc-windows-gnu" 1288 | version = "0.4.0" 1289 | source = "registry+https://github.com/rust-lang/crates.io-index" 1290 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1291 | 1292 | [[package]] 1293 | name = "winit" 1294 | version = "0.19.5" 1295 | source = "registry+https://github.com/rust-lang/crates.io-index" 1296 | checksum = "1e96eb4bb472fa43e718e8fa4aef82f86cd9deac9483a1e1529230babdb394a8" 1297 | dependencies = [ 1298 | "android_glue", 1299 | "backtrace", 1300 | "bitflags", 1301 | "cocoa", 1302 | "core-foundation", 1303 | "core-graphics", 1304 | "lazy_static", 1305 | "libc", 1306 | "log", 1307 | "objc", 1308 | "parking_lot", 1309 | "percent-encoding", 1310 | "raw-window-handle", 1311 | "smithay-client-toolkit", 1312 | "wayland-client", 1313 | "winapi", 1314 | "x11-dl", 1315 | ] 1316 | 1317 | [[package]] 1318 | name = "x11-dl" 1319 | version = "2.18.5" 1320 | source = "registry+https://github.com/rust-lang/crates.io-index" 1321 | checksum = "2bf981e3a5b3301209754218f962052d4d9ee97e478f4d26d4a6eced34c1fef8" 1322 | dependencies = [ 1323 | "lazy_static", 1324 | "libc", 1325 | "maybe-uninit", 1326 | "pkg-config", 1327 | ] 1328 | 1329 | [[package]] 1330 | name = "xdg" 1331 | version = "2.2.0" 1332 | source = "registry+https://github.com/rust-lang/crates.io-index" 1333 | checksum = "d089681aa106a86fade1b0128fb5daf07d5867a509ab036d99988dec80429a57" 1334 | 1335 | [[package]] 1336 | name = "xml-rs" 1337 | version = "0.8.3" 1338 | source = "registry+https://github.com/rust-lang/crates.io-index" 1339 | checksum = "b07db065a5cf61a7e4ba64f29e67db906fb1787316516c4e6e5ff0fea1efcd8a" 1340 | --------------------------------------------------------------------------------