diff --git a/src/disease.rs b/src/disease.rs index ee6eeaa..fe49ddf 100644 --- a/src/disease.rs +++ b/src/disease.rs @@ -1,25 +1,24 @@ // use crate::prelude::*; - // pub const MUTATION_TRAIT_INCREASE_PROBABILITY:i32 = 50; // pub const MUTATION_TRAIT_CHANGE_AMOUNT:i32 = 20; -#[derive(Debug)] +// #[derive(Debug)] pub struct Disease { - pub infection_rate:u32, - pub curing_rate:u32, - pub death_rate:u32, + pub infection_rate: u32, + pub curing_rate: u32, + pub death_rate: u32, pub traits: Vec, pub name: String, } -impl Disease{ - pub fn new(infection_r:u32,curing_r:u32,death_r:u32,the_name:String) -> Self{ - Self{ - infection_rate : infection_r, - curing_rate : curing_r, - death_rate : death_r, - name : the_name, - traits : vec![infection_r,curing_r,death_r], +impl Disease { + pub fn new(infection_r: u32, curing_r: u32, death_r: u32, the_name: String) -> Self { + Self { + infection_rate: infection_r, + curing_rate: curing_r, + death_rate: death_r, + name: the_name, + traits: vec![infection_r, curing_r, death_r], } } // pub fn mutate(&mut self){ @@ -39,4 +38,4 @@ impl Disease{ // self.traits[i] = new_ratio as u32; // } // } -} \ No newline at end of file +} diff --git a/src/human.rs b/src/human.rs index d4dab2f..741069a 100644 --- a/src/human.rs +++ b/src/human.rs @@ -1,7 +1,7 @@ // use crate::prelude::*; // #[derive(Copy, Clone, PartialEq)] -#[derive(PartialEq, Debug)] +#[derive(PartialEq)] pub enum State { Normal, Infected, @@ -10,17 +10,17 @@ pub enum State { } // #[derive(Clone)] pub struct Human { - pub present_state : State, - pub x : i32, - pub y : i32, + pub present_state: State, + pub x: i32, + pub y: i32, } -impl Human{ +impl Human { // humans with state "Normal" - pub fn new(pos_x :i32, pos_y :i32) -> Self{ - Self{ - present_state : State::Normal, - x : pos_x, - y : pos_y + pub fn new(pos_x: i32, pos_y: i32) -> Self { + Self { + present_state: State::Normal, + x: pos_x, + y: pos_y, } } // pub fn new_empty() -> Self{ @@ -30,4 +30,4 @@ impl Human{ // y : 0, // } // } -} \ No newline at end of file +} diff --git a/src/main.rs b/src/main.rs index c263a5e..473dea8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,26 +1,27 @@ -mod human; mod disease; +mod human; mod population; mod prelude { - pub use crate::human::*; pub use crate::disease::*; + pub use crate::human::*; pub use crate::population::*; - pub use rand::Rng; - pub use console::Term; pub use console::style; - pub const CORRECTED_PERCENTAGE:i32 = 101; + pub use console::Term; + pub use rand::Rng; + pub const CORRECTED_PERCENTAGE: i32 = 101; } use prelude::*; fn main() { let term = Term::stdout(); - term.write_line("********** Rusty Propagation (Console) 2022 **********").expect("Oops Looks like we have a problem here..."); - term.write_line("Press any key to start the propagation").expect("Oops Looks like we have a problem here..."); + term.write_line("********** Rusty Propagation (Console) 2022 **********") + .expect("Oops Looks like we have a problem here..."); + term.write_line("Press any key to start the propagation") + .expect("Oops Looks like we have a problem here..."); - - let disease = Disease::new(20,10,5,String::from("Covid 44")); - let mut population = Population::new(20,10,5,300,300,disease); + let disease = Disease::new(20, 10, 5, String::from("Covid 44")); + let mut population = Population::new(20, 10, 5, 300, 300, disease); //population.change_disease(disease); println!("Before Filling"); //population.display(); @@ -28,17 +29,21 @@ fn main() { println!("After Filling"); //population.display(); println!("After Propagation"); - let mut stats: [i32;4]; - // = [0,0,0,0]; - let mut counter:u32 = 0; - loop{ + let mut stats: [i32; 4]; + // = [0,0,0,0]; + let mut counter: u32 = 0; + loop { counter += 1; - stats=population.propagate(); + stats = population.propagate(); //population.display(); - println!("Infecteds: {} Immunes: {} Deads: {}",stats[1],stats[2],stats[3]); - if stats[1] == 0 {break;} - } - println!("Propagation finished in {} steps",counter); + println!( + "Infecteds: {} Immunes: {} Deads: {}", + stats[1], stats[2], stats[3] + ); + if stats[1] == 0 { + break; + } + } + println!("Propagation finished in {} steps", counter); //population.display(); - } diff --git a/src/population.rs b/src/population.rs index 70a3394..096cab1 100644 --- a/src/population.rs +++ b/src/population.rs @@ -1,148 +1,220 @@ use crate::prelude::*; +// #[derive(Debug)] +pub struct Point { + x: i32, + y: i32, +} + #[derive(Debug)] -pub struct Point{ - x:i32, - y:i32, +struct Stats { + normal: i32, + infected: i32, + immune: i32, + dead: i32, } -pub struct Population{ - pub start_infected_ratio:u32, - pub start_immune_ratio:u32, - pub start_dead_ratio:u32, - pub humans:Vec, - pub width:i32, - pub height:i32, - pub age:i32, - pub plague:Disease, -} - -pub fn human_idx(x: i32, y: i32, width: i32) -> usize { - ((y * width) + x)as usize -} - -#[test] -fn popuplation_gen() { - let disease = Disease::new(20,10,5,String::from("Covid 44")); - let population = Population::new(20,10,5,5,7,disease); - assert_eq!(population.humans.len(), 5 * 7); - for human in population.humans.iter() { - assert_eq!(human.present_state, State::Normal); +impl Stats { + fn new() -> Stats { + Stats { + normal: 0, + infected: 0, + immune: 0, + dead: 0, + } } } -impl Population{ - pub fn new(start_infected_ratio:u32,start_immune_ratio:u32,start_dead_ratio:u32,width:i32,height:i32,plague:Disease)->Self{ - let mut the_humans: Vec = Vec::with_capacity((width*height) as usize); - for x in 0..width{ - for y in 0..height{ +pub struct Population { + pub start_infected_ratio: u32, + pub start_immune_ratio: u32, + pub start_dead_ratio: u32, + pub humans: Vec, + pub width: i32, + pub height: i32, + pub age: i32, + pub plague: Disease, +} + +pub fn human_idx(x: i32, y: i32, width: i32) -> usize { + ((y * width) + x) as usize +} + +fn humans_stats(humans: &Vec) -> Stats { + let mut stats: Stats = Stats::new(); + for human in humans.iter() { + match human.present_state { + State::Normal => { + stats.normal += 1; + } + State::Infected => { + stats.infected += 1; + } + State::Immune => { + stats.immune += 1; + } + State::Dead => { + stats.dead += 1; + } + } + } + stats +} + + +impl Population { + pub fn new( + start_infected_ratio: u32, + start_immune_ratio: u32, + start_dead_ratio: u32, + width: i32, + height: i32, + plague: Disease, + ) -> Self { + let mut the_humans: Vec = Vec::with_capacity((width * height) as usize); + for x in 0..width { + for y in 0..height { the_humans.push(Human::new(x, y)); } } - Self{ - start_infected_ratio:start_infected_ratio, - start_immune_ratio:start_immune_ratio, - start_dead_ratio:start_dead_ratio, - width:width, - height:height, - plague:plague, - age:0, - humans:the_humans, + Self { + start_infected_ratio: start_infected_ratio, + start_immune_ratio: start_immune_ratio, + start_dead_ratio: start_dead_ratio, + width: width, + height: height, + plague: plague, + age: 0, + humans: the_humans, } } // pub fn change_disease(&mut self, plague:Disease){ // self.plague = plague; // } - pub fn generate(&mut self){ + pub fn generate(&mut self) { //The ratios will not be exact, for example someone who wants 100% infected 100% immune and 100% dead, he will have 100% dead because they are overwriting each others //Maybe consider limiting the total to not exceed 100 in the view //Other thing, there will always be more of the last one because someone who is already infected for example could be then put to immune or dead //One solution to this issue would be to have if else else statements but in this case for example 20% would be lower with the last because its 20% on the remaining population and not of the all //In other words I did it that way but it can be changed just its not the right method to have perfect ratios let mut rng = rand::thread_rng(); - - let mut i: i32 = 0; + for x in self.humans.iter_mut() { - if rng.gen_range(0..CORRECTED_PERCENTAGE) < self.start_infected_ratio as i32 - { + if (self.start_infected_ratio) > 0 && (rng.gen_range(0..CORRECTED_PERCENTAGE) <= self.start_infected_ratio as i32) { x.present_state = State::Infected; - } - if rng.gen_range(0..CORRECTED_PERCENTAGE) < self.start_immune_ratio as i32 - { + } else if self.start_immune_ratio > 0 && rng.gen_range(0..CORRECTED_PERCENTAGE) <= self.start_immune_ratio as i32 { x.present_state = State::Immune; - } - if rng.gen_range(0..CORRECTED_PERCENTAGE) < self.start_dead_ratio as i32 - { + } else if self.start_dead_ratio > 0 && rng.gen_range(0..CORRECTED_PERCENTAGE) <= self.start_dead_ratio as i32 { x.present_state = State::Dead; } - i += 1; } - println!("generate for {} humans", i); } - pub fn propagate(&mut self)->[i32;4]{ - let mut people_to_check:Vec = Vec::with_capacity((self.width * self.height) as usize); - let mut people_to_infect:Vec = Vec::with_capacity((self.width * self.height) as usize); - let mut people_to_cure:Vec = Vec::with_capacity((self.width * self.height) as usize); - let mut people_to_kill:Vec = Vec::with_capacity((self.width * self.height) as usize); - let mut stats: [i32;4] = [0,0,0,0]; + + pub fn propagate(&mut self) -> [i32; 4] { + let mut people_to_check: Vec = + Vec::with_capacity((self.width * self.height) as usize); + let mut people_to_infect: Vec = + Vec::with_capacity((self.width * self.height) as usize); + let mut people_to_cure: Vec = + Vec::with_capacity((self.width * self.height) as usize); + let mut people_to_kill: Vec = + Vec::with_capacity((self.width * self.height) as usize); + let mut stats: [i32; 4] = [0, 0, 0, 0]; // stats[0] Normal stats[1] Infected stats[2] Immune stats[3] Dead for h in self.humans.iter() { - match h.present_state{ - State::Normal => {stats[0] += 1;} - State::Infected => {people_to_check.push(Point{x:h.x,y:h.y});stats[1]+=1;} - State::Immune => {stats[2] += 1;} - State::Dead => {stats[3] += 1;} + match h.present_state { + State::Normal => { + stats[0] += 1; + } + State::Infected => { + people_to_check.push(Point { x: h.x, y: h.y }); + stats[1] += 1; + } + State::Immune => { + stats[2] += 1; + } + State::Dead => { + stats[3] += 1; + } } } -// for pos in &people_to_check { + // for pos in &people_to_check { for pos in people_to_check.iter() { - //people_to_check.iter().map(|pos|{ + //people_to_check.iter().map(|pos|{ //get all the other people next to me and check if i die cure or infect //now we can start to check if people would be infected or not //let idx = human_idx(pos.x as i32, pos.y as i32, self.width as i32); - if pos.x > 0 && pos.x < self.width - 1 && pos.y > 0 && pos.y < self.height -1 { - if self.roll(self.plague.curing_rate){ - //checks if the man dies - people_to_cure.push(Point{x:pos.x,y:pos.y}); - }else{ - if self.roll(self.plague.death_rate){ + if pos.x > 0 && pos.x < self.width - 1 && pos.y > 0 && pos.y < self.height - 1 { + if self.roll(self.plague.curing_rate) { + //checks if the man dies + people_to_cure.push(Point { x: pos.x, y: pos.y }); + } else { + if self.roll(self.plague.death_rate) { //cheks if the man dies - people_to_kill.push(Point{x:pos.x,y:pos.y}); - }else{ - let mut possible_infections:Vec = Vec::with_capacity(8); + people_to_kill.push(Point { x: pos.x, y: pos.y }); + } else { + let mut possible_infections: Vec = Vec::with_capacity(8); // Vec::new(); //possible_infections.push(Point{x:pos.x,y:pos.y}); - possible_infections.push(Point{x:pos.x -1,y:pos.y -1}); //Top Left - possible_infections.push(Point{x:pos.x,y:pos.y-1}); //Top - possible_infections.push(Point{x:pos.x +1,y:pos.y -1}); //Top Right + possible_infections.push(Point { + x: pos.x - 1, + y: pos.y - 1, + }); //Top Left + possible_infections.push(Point { + x: pos.x, + y: pos.y - 1, + }); //Top + possible_infections.push(Point { + x: pos.x + 1, + y: pos.y - 1, + }); //Top Right - possible_infections.push(Point{x:pos.x -1,y:pos.y}); //Left - possible_infections.push(Point{x:pos.x +1,y:pos.y}); //Right + possible_infections.push(Point { + x: pos.x - 1, + y: pos.y, + }); //Left + possible_infections.push(Point { + x: pos.x + 1, + y: pos.y, + }); //Right - possible_infections.push(Point{x:pos.x -1,y:pos.y + 1}); //Bottom Left - possible_infections.push(Point{x:pos.x,y:pos.y + 1}); //Bottom - possible_infections.push(Point{x:pos.x + 1,y:pos.y + 1}); //Bottom Right + possible_infections.push(Point { + x: pos.x - 1, + y: pos.y + 1, + }); //Bottom Left + possible_infections.push(Point { + x: pos.x, + y: pos.y + 1, + }); //Bottom + possible_infections.push(Point { + x: pos.x + 1, + y: pos.y + 1, + }); //Bottom Right for poss_infected_pos in possible_infections.iter() { - //possible_infections.iter().map(|poss_infected_pos|{ - let inf_idx = human_idx(poss_infected_pos.x, poss_infected_pos.y, self.width); - if self.humans[inf_idx].present_state == State::Normal{ - if self.roll(self.plague.infection_rate){ - people_to_infect.push(Point{x:poss_infected_pos.x,y:poss_infected_pos.y}); + //possible_infections.iter().map(|poss_infected_pos|{ + let inf_idx = + human_idx(poss_infected_pos.x, poss_infected_pos.y, self.width); + if self.humans[inf_idx].present_state == State::Normal { + if self.roll(self.plague.infection_rate) { + people_to_infect.push(Point { + x: poss_infected_pos.x, + y: poss_infected_pos.y, + }); } } - }; + } } } - }else{ - //TODO + } else { + //TODO //Check every special cases (corners sides etc..) //REMOVE WHEN IMPLEMENTED //It is here to prevent an infected in the borders to keep the programm running as he thinks there are still people to simulate - people_to_kill.push(Point{x:pos.x,y:pos.y}); + people_to_kill.push(Point { x: pos.x, y: pos.y }); // @## // ### @@ -176,11 +248,16 @@ impl Population{ // ### // ##@ } - }; - println!("{} to infect, {} to cure, {} to kill", people_to_infect.len(), people_to_cure.len(), people_to_kill.len()); + } + println!( + "{} to infect, {} to cure, {} to kill", + people_to_infect.len(), + people_to_cure.len(), + people_to_kill.len() + ); for infected_position in &people_to_infect { // println!("To infect: {:?}", infected_position); - //people_to_infect.iter().map(|infected_position|{ + //people_to_infect.iter().map(|infected_position|{ let infected_index = human_idx(infected_position.x, infected_position.y, self.width); // let _ = infected_position.x; //DEBUG @@ -188,18 +265,18 @@ impl Population{ self.humans[infected_index].present_state = State::Infected; //DEBUG //println!("Infected someone"); - }; + } for cured_position in &people_to_cure { - //people_to_cure.iter().map(|cured_position|{ + //people_to_cure.iter().map(|cured_position|{ let cured_index = human_idx(cured_position.x, cured_position.y, self.width); self.humans[cured_index].present_state = State::Immune; //DEBUG //println!("Cured someone"); - }; + } for dead_position in &people_to_kill { - //people_to_kill.iter().map(|dead_position|{ + //people_to_kill.iter().map(|dead_position|{ let dead_index = human_idx(dead_position.x, dead_position.y, self.width); if self.humans[dead_index].present_state == State::Dead { println!("Already dead"); @@ -207,13 +284,13 @@ impl Population{ self.humans[dead_index].present_state = State::Dead; } //DEBUG - }; + } stats } - pub fn roll(&self,probability:u32)->bool{ + pub fn roll(&self, probability: u32) -> bool { let mut rng = rand::thread_rng(); rng.gen_range(0..CORRECTED_PERCENTAGE) <= probability as i32 - } + } // pub fn display(&mut self){ // let sprite = "#"; // print!("\n"); @@ -229,6 +306,132 @@ impl Population{ // } // } // print!("\n"); - // } + // } // } -} \ No newline at end of file +} + +#[cfg(test)] +mod tests { + use super::*; + use parameterized::parameterized; + + #[parameterized(x = { + 2, 3, 5 + }, y = { + 1, 4, 0 + }, width = { + 3, 5, 7 + }, res = { + 5, 23, 5 + })] + fn test_human_idx(x: i32, y: i32, width: i32, res: usize) { + assert_eq!(human_idx(x, y, width), res); + } + + #[test] + fn test_human_stats() { + let mut humans: Vec = Vec::with_capacity(10); + let mut stats: Stats; + + for _ in 0..10 { + humans.push(Human::new(0, 0)); + } + stats = humans_stats(&humans); + assert_eq!(stats.normal, 10); + + for x in 0..2 { + humans[x].present_state = State::Infected; + } + for x in 2..5 { + humans[x].present_state = State::Immune; + } + for x in 5..9 { + humans[x].present_state = State::Dead; + } + stats = humans_stats(&humans); + assert_eq!(stats.normal, 1); + assert_eq!(stats.infected, 2); + assert_eq!(stats.immune, 3); + assert_eq!(stats.dead, 4); + } + + #[test] + fn population_new() { + let disease = Disease::new(20, 10, 5, String::from("Covid 44")); + let population = Population::new(20, 10, 5, 5, 7, disease); + assert_eq!(population.humans.len(), 5 * 7); + for human in population.humans.iter() { + assert!(human.present_state == State::Normal, "all humans should be normal"); + } + } + + #[test] + fn population_gen() { + let disease = Disease::new(20, 10, 5, String::from("Covid 44")); + let (width, height) = (5, 7); + let mut population = Population::new(20, 10, 5, 5, 7, disease); + + population.generate(); + let stats: Stats = humans_stats(&population.humans); + println!("Stats: {:?}", stats); + + assert_eq!( + stats.normal + stats.infected + stats.immune + stats.dead, + width * height + ); + } + + #[test] + fn plague_init_stats() { + let mut disease: Disease; + let mut population: Population; + let mut stats: Stats; + let (width, height) = (5, 7); + + disease = Disease::new(0, 0, 0, String::from("Test")); + population = Population::new(0, 0, 0, width, height, disease); + population.generate(); + stats = humans_stats(&population.humans); + println!("should be normal: {:?}", stats); + assert_eq!(stats.normal, width * height); + + disease = Disease::new(0, 0, 0, String::from("Test")); + population = Population::new(100, 0, 0, width, height, disease); + population.generate(); + stats = humans_stats(&population.humans); + println!("should be infected: {:?}", stats); + assert_eq!(stats.infected, width * height); + + disease = Disease::new(0, 0, 0, String::from("Test")); + population = Population::new(0, 100, 0, width, height, disease); + population.generate(); + stats = humans_stats(&population.humans); + println!("should be immune: {:?}", stats); + assert_eq!(stats.immune, width * height); + + disease = Disease::new(0, 0, 0, String::from("Test")); + population = Population::new(0, 0, 100, width, height, disease); + population.generate(); + stats = humans_stats(&population.humans); + println!("should be dead: {:?}", stats); + assert_eq!(stats.dead, width * height); + + } + + #[parameterized(infection_rate = {0, 100}, infected_expected = {0, 1})] + fn propage_test(infection_rate: u32, infected_expected: i32) { + let disease: Disease; + let mut population: Population; + let mut stats: Stats; + let (width, height) = (100, 100); + + disease = Disease::new(infection_rate, 0, 0, String::from("Test")); + population = Population::new(50, 0, 0, width, height, disease); + stats = humans_stats(&population.humans); + println!("Population: {:?}", stats); + population.generate(); + population.propagate(); + stats = humans_stats(&population.humans); + assert!(stats.normal <= width * height * infected_expected); + } +}