diff --git a/src/population.rs b/src/population.rs index 3085e05..21622b4 100644 --- a/src/population.rs +++ b/src/population.rs @@ -1,5 +1,4 @@ -use std::sync::Arc; -use std::sync::Mutex; +use std::sync::{Arc, RwLock}; use crate::prelude::*; use std::thread; @@ -14,7 +13,7 @@ pub struct Population { pub start_infected_ratio: i32, pub start_immune_ratio: i32, pub start_dead_ratio: i32, - pub humans: Arc>>, + pub humans: Arc>>, pub width: i32, pub height: i32, pub age: i32, @@ -39,15 +38,15 @@ impl Population { let size: usize = (width * height) as usize; - let the_humans_arc = Arc::new(Mutex::new(vec![ + let mut the_humans = vec![ Human { x: 0, y: 0, present_state: State::Normal }; size - ])); - let the_humans = Arc::clone(&the_humans_arc); + ]; + for x in 0..width { for y in 0..height { let idx = human_idx(x, y, width); @@ -65,9 +64,10 @@ impl Population { { present_state = State::Dead; } - the_humans.lock().unwrap()[idx] = Human{x: x, y: y, present_state: present_state}; + the_humans[idx] = Human{x: x, y: y, present_state: present_state}; } } + Self { start_infected_ratio: start_infected_ratio, start_immune_ratio: start_immune_ratio, @@ -76,7 +76,7 @@ impl Population { height: height, plague: plague, age: 0, - humans: the_humans_arc, + humans: Arc::new(RwLock::new(the_humans)), size: size, } } @@ -93,10 +93,10 @@ impl Population { } fn is_inside_and_infected(&self, point: Point) -> bool { - let the_humans_arc = Arc::clone(&self.humans); + let humans = Arc::clone(&self.humans); if self.is_inside(&point) { let idx = human_idx(point.x, point.y, self.width); - let humans = the_humans_arc.lock().unwrap(); + let humans = humans.read().unwrap(); if humans[idx].present_state == State::Infected { roll(self.plague.infection_rate) } else { @@ -121,25 +121,29 @@ impl Population { let mut stats: [i32; 4] = [0, 0, 0, 0]; // stats[0] Normal stats[1] Infected stats[2] Immune stats[3] Dead - let humans = Arc::clone(&self.humans); - for h in humans.lock().unwrap().iter() { - match h.present_state { - State::Normal => { - possible_infected.push(Point{ x: h.x, y: h.y}); - 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; + { + let humans = Arc::clone(&self.humans); + let humans = humans.read().unwrap(); + for h in humans.iter() { + match h.present_state { + State::Normal => { + possible_infected.push(Point{ x: h.x, y: h.y}); + 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.iter() { //people_to_check.iter().map(|pos|{ @@ -157,6 +161,7 @@ impl Population { } } for pos in possible_infected.iter() { + // infect human if someone near is infected let infected: bool = self.is_inside_and_infected( Point { x: pos.x - 1, @@ -210,18 +215,6 @@ impl Population { } } -// for infected_position in people_to_infect.iter() { -// // println!("To infect: {:?}", 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 -// //println!("x: {} y: {} index: {}",infected_position.x,infected_position.y,infected_index); -// self.humans[infected_index].present_state = State::Infected; -// //DEBUG -// //println!("Infected someone"); -// } - let mut threads = vec![]; { let humans = Arc::clone(&self.humans); @@ -229,7 +222,11 @@ impl Population { threads.push(thread::spawn(move || { for infected_position in people_to_infect.iter() { let infected_index = human_idx(infected_position.x, infected_position.y, width); - humans.lock().unwrap()[infected_index].present_state = State::Infected; + { + println!("infect"); + let mut humans = humans.write().unwrap(); + humans[infected_index].present_state = State::Infected; + } } })); } @@ -241,10 +238,14 @@ impl Population { for cured_position in people_to_cure.iter() { //people_to_cure.iter().map(|cured_position|{ let cured_index = human_idx(cured_position.x, cured_position.y, width); - if humans.lock().unwrap()[cured_index].present_state != State::Infected { - println!("not infected"); - } else { - humans.lock().unwrap()[cured_index].present_state = State::Immune; + { + let humans = humans.read().unwrap(); + debug_assert!(humans[cured_index].present_state != State::Infected, "This human should not be infected: {:?}", humans[cured_index].present_state); + } + { + println!("cure"); + let mut humans = humans.write().unwrap(); + humans[cured_index].present_state = State::Immune; } //DEBUG //println!("Cured someone"); @@ -252,22 +253,31 @@ impl Population { })); } + { + let humans = Arc::clone(&self.humans); + let width = self.width; + threads.push(thread::spawn(move || { + for dead_position in people_to_kill.iter() { + //people_to_kill.iter().map(|dead_position|{ + let dead_index = human_idx(dead_position.x, dead_position.y, width); + { + let humans = humans.read().unwrap(); + debug_assert_eq!(humans[dead_index].present_state, State::Dead); + } + { + println!("kill"); + let mut humans = humans.write().unwrap(); + humans[dead_index].present_state = State::Dead; + } + } + })); + } + for t in threads { t.join().unwrap(); } - for dead_position in people_to_kill.iter() { - let humans = Arc::clone(&self.humans); - //people_to_kill.iter().map(|dead_position|{ - let dead_index = human_idx(dead_position.x, dead_position.y, self.width); - if humans.lock().unwrap()[dead_index].present_state == State::Dead { - // println!("Already dead"); - } else { - humans.lock().unwrap()[dead_index].present_state = State::Dead; - } - //DEBUG - } - assert_eq!( + debug_assert_eq!( stats[0] + stats[1] + stats[2] + stats[3], self.size as i32 ); @@ -301,452 +311,3 @@ pub fn roll(probability: i32) -> bool { false } } - -#[cfg(test)] -mod tests { - use super::*; - use parameterized::parameterized; - - #[derive(Debug)] - struct Stats { - normal: i32, - infected: i32, - immune: i32, - dead: i32, - } - - impl Stats { - fn new() -> Stats { - Stats { - normal: 0, - infected: 0, - immune: 0, - dead: 0, - } - } - } - - 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 - } - - #[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 { - present_state: State::Normal, - x: 0, - y: 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 (width, height) = (5, 7); - let population = Population::new(20, 10, 5, 5, 7, disease); - let humans = Arc::clone(&population.humans); - assert_eq!(humans.lock().unwrap().len(), 5 * 7); - for h in humans.lock().unwrap().iter() { - let idx = human_idx(h.x, h.y, width); - assert_eq!(humans.lock().unwrap()[idx].x, h.x, "coordinates should match"); - assert_eq!(humans.lock().unwrap()[idx].y, h.y, "coordinates should match"); - } - assert_eq!(humans.lock().unwrap().len(), (width * height) as usize); - } - - #[test] - fn population_gen() { - let disease = Disease::new(20, 10, 5, String::from("Covid 44")); - let (width, height) = (5, 7); - let population = Population::new(20, 10, 5, 5, 7, disease); - - 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); - 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); - 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); - 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); - stats = humans_stats(&population.humans); - println!("should be dead: {:?}", stats); - assert_eq!(stats.dead, width * height); - } - - #[parameterized(rate = {0, 100}, expected = {false, true})] - fn roll_test(rate: i32, expected: bool) { - let tries = 100000; - let mut result = 0; - println!("Testing roll, rate {}, expected {}", rate, expected); - for _x in 0..tries { - if roll(rate) == expected { - result += 1; - } - } - assert_eq!(result, tries); - } - - #[test] - fn propagate_simple() { - let disease: Disease = Disease::new(0, 0, 100, String::from("Deadly")); - let mut population: Population = Population::new(100, 0, 0, 10, 10, disease); - let mut stats: Stats; - let mut propagate_stats: [i32; 4]; - - // infect every one - stats = humans_stats(&population.humans); - println!("stats after init: {:?}", stats); - assert_eq!(stats.infected, 100, "everybody should be infected"); - - // kill every one - propagate_stats = population.propagate(); - stats = humans_stats(&population.humans); - println!("propate_stats: {:?}", propagate_stats); - assert_eq!(propagate_stats, [0, 100, 0, 0]); - assert_eq!(stats.normal, 0); - assert_eq!(stats.infected, 0); - assert_eq!(stats.immune, 0); - assert_eq!(stats.dead, 100); - - for _x in 0..100 { - propagate_stats = population.propagate(); - stats = humans_stats(&population.humans); - println!("propate_stats: {:?}", propagate_stats); - assert_eq!(propagate_stats, [0, 0, 0, 100]); - assert_eq!(stats.normal, 0); - assert_eq!(stats.infected, 0); - assert_eq!(stats.immune, 0); - assert_eq!(stats.dead, 100); - } - } - - #[test] - fn propagate_infect_all() { - let disease: Disease = Disease::new(100, 0, 0, String::from("Deadly")); - let mut population: Population = Population::new(0, 0, 0, 3, 3, disease); - let mut stats: Stats; - let mut propagate_stats: [i32; 4]; - - // start with normal population - population.humans = vec![ - Human { - present_state: State::Normal, - x: 0, - y: 0, - }, - Human { - present_state: State::Normal, - x: 1, - y: 0, - }, - Human { - present_state: State::Normal, - x: 2, - y: 0, - }, - Human { - present_state: State::Normal, - x: 0, - y: 1, - }, - Human { - present_state: State::Infected, - x: 1, - y: 1, - }, - Human { - present_state: State::Normal, - x: 2, - y: 1, - }, - Human { - present_state: State::Normal, - x: 0, - y: 2, - }, - Human { - present_state: State::Normal, - x: 1, - y: 2, - }, - Human { - present_state: State::Normal, - x: 2, - y: 2, - }, - ]; - stats = humans_stats(&population.humans); - println!("stats after init: {:?}", stats); - assert_eq!(stats.normal, 8); - - // kill every one - propagate_stats = population.propagate(); - stats = humans_stats(&population.humans); - println!("propate_stats: {:?}", propagate_stats); - assert_eq!(propagate_stats, [8, 1, 0, 0]); - assert_eq!(stats.normal, 0); - assert_eq!(stats.infected, 9); - assert_eq!(stats.immune, 0); - assert_eq!(stats.dead, 0); - - for _x in 0..100 { - propagate_stats = population.propagate(); - stats = humans_stats(&population.humans); - println!("propate_stats: {:?}", propagate_stats); - assert_eq!(propagate_stats, [0, 9, 0, 0]); - assert_eq!(stats.normal, 0); - assert_eq!(stats.infected, 9); - assert_eq!(stats.immune, 0); - assert_eq!(stats.dead, 0); - } - } - - #[test] - fn propagate_infect_cure_all() { - let disease: Disease = Disease::new(100, 100, 0, String::from("Deadly")); - let mut population: Population = Population::new(0, 0, 0, 3, 3, disease); - let mut stats: Stats; - let mut propagate_stats: [i32; 4]; - - // start with normal population - population.humans = vec![ - Human { - present_state: State::Normal, - x: 0, - y: 0, - }, - Human { - present_state: State::Normal, - x: 1, - y: 0, - }, - Human { - present_state: State::Normal, - x: 2, - y: 0, - }, - Human { - present_state: State::Normal, - x: 0, - y: 1, - }, - Human { - present_state: State::Infected, - x: 1, - y: 1, - }, - Human { - present_state: State::Normal, - x: 2, - y: 1, - }, - Human { - present_state: State::Normal, - x: 0, - y: 2, - }, - Human { - present_state: State::Normal, - x: 1, - y: 2, - }, - Human { - present_state: State::Normal, - x: 2, - y: 2, - }, - ]; - stats = humans_stats(&population.humans); - println!("stats after init: {:?}", stats); - assert_eq!(stats.normal, 8); - - // infect every one - propagate_stats = population.propagate(); - stats = humans_stats(&population.humans); - println!("propate_stats: {:?}", propagate_stats); - println!("population: {:?}", stats); - assert_eq!(propagate_stats, [8, 1, 0, 0]); - assert_eq!(stats.normal, 0); - assert_eq!(stats.infected, 8); - assert_eq!(stats.immune, 1); - assert_eq!(stats.dead, 0); - - // cure every one - propagate_stats = population.propagate(); - stats = humans_stats(&population.humans); - println!("propate_stats: {:?}", propagate_stats); - println!("population: {:?}", stats); - assert_eq!(propagate_stats, [0, 8, 1, 0]); - assert_eq!(stats.normal, 0); - assert_eq!(stats.infected, 0); - assert_eq!(stats.immune, 9); - assert_eq!(stats.dead, 0); - - // then - for _x in 0..100 { - propagate_stats = population.propagate(); - stats = humans_stats(&population.humans); - println!("propate_stats: {:?}", propagate_stats); - println!("population: {:?}", stats); - assert_eq!(propagate_stats, [0, 0, 9, 0]); - assert_eq!(stats.normal, 0); - assert_eq!(stats.infected, 0); - assert_eq!(stats.immune, 9); - assert_eq!(stats.dead, 0); - } - } - - #[parameterized(infection_start = {0, 50, 100})] - fn propagate_harmless(infection_start: i32) { - let disease: Disease = Disease::new(0, 0, 0, String::from("Harmless")); - let (width, height) = (100, 100); - let mut population: Population = Population::new(infection_start, 0, 0, width, height, disease); - let stats_before: Stats; - let stats_after: Stats; - - stats_before = humans_stats(&population.humans); - let should_be_infected = population.size as i32 * infection_start / 100; - let tolerance = (should_be_infected as f32 * 0.20) as i32; - println!("{:?}", stats_before); - assert!(stats_before.infected <= should_be_infected + tolerance, "{} infected, should be less than {}", stats_before.infected, should_be_infected + tolerance); - assert!(stats_before.infected >= should_be_infected - tolerance, "{} infected, should be more than {}", stats_before.infected, should_be_infected - tolerance); - - population.propagate(); - - stats_after = humans_stats(&population.humans); - assert_eq!(stats_before.infected, stats_after.infected, "no one should have been infected"); - } - - #[parameterized(infection_rate = {0, 100, 0}, death_rate = {0, 0, 100}, infected_expected = {0, 1, 0})] - fn propagate_test(infection_rate: i32, death_rate: i32, infected_expected: i32) { - let disease: Disease; - let mut population: Population; - let mut stats: Stats; - let (width, height) = (100, 100); - let start_infected = 50; - - println!( - "infection rate: {}, death_rate: {}", - infection_rate, death_rate - ); - - disease = Disease::new(infection_rate, 0, death_rate, String::from("Test")); - population = Population::new(start_infected, 0, 0, width, height, disease); - - stats = humans_stats(&population.humans); - println!("Population after init: {:?}", stats); - - // total * proba - 20% < infected < total * proba + 20% - let infected_at_start_proba = width * height * start_infected / 100; - let infected_tolerance = ((width * height) as f32 * 0.2) as i32; - assert!(stats.infected <= infected_at_start_proba + infected_tolerance); - assert!(stats.infected >= infected_at_start_proba - infected_tolerance); - assert_eq!(stats.dead, 0); - - let infected_at_start = stats.infected; - let dead_at_start = stats.dead; - - let propa_stats: [i32; 4] = population.propagate(); - - assert!(propa_stats[3] >= dead_at_start); - - if death_rate == 0 { - assert_eq!(propa_stats[3], 0, "no human should have died"); - } - - stats = humans_stats(&population.humans); - println!("Population after propagate: {:?}", stats); - - assert!(stats.infected <= infected_at_start + width * height * infected_expected); - - let should_be_dead = infected_at_start * death_rate / 100; - let dead_tolerance = (should_be_dead as f32 * 0.20) as i32; - - assert!( - stats.dead <= should_be_dead + dead_tolerance, - "death count should be less or equal than {}", - should_be_dead + dead_tolerance - ); - assert!(stats.dead >= should_be_dead - dead_tolerance); - } -} diff --git a/src/tests.rs b/src/tests.rs new file mode 100644 index 0000000..0b3e5c4 --- /dev/null +++ b/src/tests.rs @@ -0,0 +1,448 @@ +#[cfg(test)] +mod tests { + use super::*; + use parameterized::parameterized; + + #[derive(Debug)] + struct Stats { + normal: i32, + infected: i32, + immune: i32, + dead: i32, + } + + impl Stats { + fn new() -> Stats { + Stats { + normal: 0, + infected: 0, + immune: 0, + dead: 0, + } + } + } + + 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 + } + + #[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 { + present_state: State::Normal, + x: 0, + y: 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 (width, height) = (5, 7); + let population = Population::new(20, 10, 5, 5, 7, disease); + let humans = Arc::clone(&population.humans); + assert_eq!(humans.lock().unwrap().len(), 5 * 7); + for h in humans.lock().unwrap().iter() { + let idx = human_idx(h.x, h.y, width); + assert_eq!(humans.lock().unwrap()[idx].x, h.x, "coordinates should match"); + assert_eq!(humans.lock().unwrap()[idx].y, h.y, "coordinates should match"); + } + assert_eq!(humans.lock().unwrap().len(), (width * height) as usize); + } + + #[test] + fn population_gen() { + let disease = Disease::new(20, 10, 5, String::from("Covid 44")); + let (width, height) = (5, 7); + let population = Population::new(20, 10, 5, 5, 7, disease); + + 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); + 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); + 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); + 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); + stats = humans_stats(&population.humans); + println!("should be dead: {:?}", stats); + assert_eq!(stats.dead, width * height); + } + + #[parameterized(rate = {0, 100}, expected = {false, true})] + fn roll_test(rate: i32, expected: bool) { + let tries = 100000; + let mut result = 0; + println!("Testing roll, rate {}, expected {}", rate, expected); + for _x in 0..tries { + if roll(rate) == expected { + result += 1; + } + } + assert_eq!(result, tries); + } + + #[test] + fn propagate_simple() { + let disease: Disease = Disease::new(0, 0, 100, String::from("Deadly")); + let mut population: Population = Population::new(100, 0, 0, 10, 10, disease); + let mut stats: Stats; + let mut propagate_stats: [i32; 4]; + + // infect every one + stats = humans_stats(&population.humans); + println!("stats after init: {:?}", stats); + assert_eq!(stats.infected, 100, "everybody should be infected"); + + // kill every one + propagate_stats = population.propagate(); + stats = humans_stats(&population.humans); + println!("propate_stats: {:?}", propagate_stats); + assert_eq!(propagate_stats, [0, 100, 0, 0]); + assert_eq!(stats.normal, 0); + assert_eq!(stats.infected, 0); + assert_eq!(stats.immune, 0); + assert_eq!(stats.dead, 100); + + for _x in 0..100 { + propagate_stats = population.propagate(); + stats = humans_stats(&population.humans); + println!("propate_stats: {:?}", propagate_stats); + assert_eq!(propagate_stats, [0, 0, 0, 100]); + assert_eq!(stats.normal, 0); + assert_eq!(stats.infected, 0); + assert_eq!(stats.immune, 0); + assert_eq!(stats.dead, 100); + } + } + + #[test] + fn propagate_infect_all() { + let disease: Disease = Disease::new(100, 0, 0, String::from("Deadly")); + let mut population: Population = Population::new(0, 0, 0, 3, 3, disease); + let mut stats: Stats; + let mut propagate_stats: [i32; 4]; + + // start with normal population + population.humans = vec![ + Human { + present_state: State::Normal, + x: 0, + y: 0, + }, + Human { + present_state: State::Normal, + x: 1, + y: 0, + }, + Human { + present_state: State::Normal, + x: 2, + y: 0, + }, + Human { + present_state: State::Normal, + x: 0, + y: 1, + }, + Human { + present_state: State::Infected, + x: 1, + y: 1, + }, + Human { + present_state: State::Normal, + x: 2, + y: 1, + }, + Human { + present_state: State::Normal, + x: 0, + y: 2, + }, + Human { + present_state: State::Normal, + x: 1, + y: 2, + }, + Human { + present_state: State::Normal, + x: 2, + y: 2, + }, + ]; + stats = humans_stats(&population.humans); + println!("stats after init: {:?}", stats); + assert_eq!(stats.normal, 8); + + // kill every one + propagate_stats = population.propagate(); + stats = humans_stats(&population.humans); + println!("propate_stats: {:?}", propagate_stats); + assert_eq!(propagate_stats, [8, 1, 0, 0]); + assert_eq!(stats.normal, 0); + assert_eq!(stats.infected, 9); + assert_eq!(stats.immune, 0); + assert_eq!(stats.dead, 0); + + for _x in 0..100 { + propagate_stats = population.propagate(); + stats = humans_stats(&population.humans); + println!("propate_stats: {:?}", propagate_stats); + assert_eq!(propagate_stats, [0, 9, 0, 0]); + assert_eq!(stats.normal, 0); + assert_eq!(stats.infected, 9); + assert_eq!(stats.immune, 0); + assert_eq!(stats.dead, 0); + } + } + + #[test] + fn propagate_infect_cure_all() { + let disease: Disease = Disease::new(100, 100, 0, String::from("Deadly")); + let mut population: Population = Population::new(0, 0, 0, 3, 3, disease); + let mut stats: Stats; + let mut propagate_stats: [i32; 4]; + + // start with normal population + population.humans = vec![ + Human { + present_state: State::Normal, + x: 0, + y: 0, + }, + Human { + present_state: State::Normal, + x: 1, + y: 0, + }, + Human { + present_state: State::Normal, + x: 2, + y: 0, + }, + Human { + present_state: State::Normal, + x: 0, + y: 1, + }, + Human { + present_state: State::Infected, + x: 1, + y: 1, + }, + Human { + present_state: State::Normal, + x: 2, + y: 1, + }, + Human { + present_state: State::Normal, + x: 0, + y: 2, + }, + Human { + present_state: State::Normal, + x: 1, + y: 2, + }, + Human { + present_state: State::Normal, + x: 2, + y: 2, + }, + ]; + stats = humans_stats(&population.humans); + println!("stats after init: {:?}", stats); + assert_eq!(stats.normal, 8); + + // infect every one + propagate_stats = population.propagate(); + stats = humans_stats(&population.humans); + println!("propate_stats: {:?}", propagate_stats); + println!("population: {:?}", stats); + assert_eq!(propagate_stats, [8, 1, 0, 0]); + assert_eq!(stats.normal, 0); + assert_eq!(stats.infected, 8); + assert_eq!(stats.immune, 1); + assert_eq!(stats.dead, 0); + + // cure every one + propagate_stats = population.propagate(); + stats = humans_stats(&population.humans); + println!("propate_stats: {:?}", propagate_stats); + println!("population: {:?}", stats); + assert_eq!(propagate_stats, [0, 8, 1, 0]); + assert_eq!(stats.normal, 0); + assert_eq!(stats.infected, 0); + assert_eq!(stats.immune, 9); + assert_eq!(stats.dead, 0); + + // then + for _x in 0..100 { + propagate_stats = population.propagate(); + stats = humans_stats(&population.humans); + println!("propate_stats: {:?}", propagate_stats); + println!("population: {:?}", stats); + assert_eq!(propagate_stats, [0, 0, 9, 0]); + assert_eq!(stats.normal, 0); + assert_eq!(stats.infected, 0); + assert_eq!(stats.immune, 9); + assert_eq!(stats.dead, 0); + } + } + + #[parameterized(infection_start = {0, 50, 100})] + fn propagate_harmless(infection_start: i32) { + let disease: Disease = Disease::new(0, 0, 0, String::from("Harmless")); + let (width, height) = (100, 100); + let mut population: Population = Population::new(infection_start, 0, 0, width, height, disease); + let stats_before: Stats; + let stats_after: Stats; + + stats_before = humans_stats(&population.humans); + let should_be_infected = population.size as i32 * infection_start / 100; + let tolerance = (should_be_infected as f32 * 0.20) as i32; + println!("{:?}", stats_before); + assert!(stats_before.infected <= should_be_infected + tolerance, "{} infected, should be less than {}", stats_before.infected, should_be_infected + tolerance); + assert!(stats_before.infected >= should_be_infected - tolerance, "{} infected, should be more than {}", stats_before.infected, should_be_infected - tolerance); + + population.propagate(); + + stats_after = humans_stats(&population.humans); + assert_eq!(stats_before.infected, stats_after.infected, "no one should have been infected"); + } + + #[parameterized(infection_rate = {0, 100, 0}, death_rate = {0, 0, 100}, infected_expected = {0, 1, 0})] + fn propagate_test(infection_rate: i32, death_rate: i32, infected_expected: i32) { + let disease: Disease; + let mut population: Population; + let mut stats: Stats; + let (width, height) = (100, 100); + let start_infected = 50; + + println!( + "infection rate: {}, death_rate: {}", + infection_rate, death_rate + ); + + disease = Disease::new(infection_rate, 0, death_rate, String::from("Test")); + population = Population::new(start_infected, 0, 0, width, height, disease); + + stats = humans_stats(&population.humans); + println!("Population after init: {:?}", stats); + + // total * proba - 20% < infected < total * proba + 20% + let infected_at_start_proba = width * height * start_infected / 100; + let infected_tolerance = ((width * height) as f32 * 0.2) as i32; + assert!(stats.infected <= infected_at_start_proba + infected_tolerance); + assert!(stats.infected >= infected_at_start_proba - infected_tolerance); + assert_eq!(stats.dead, 0); + + let infected_at_start = stats.infected; + let dead_at_start = stats.dead; + + let propa_stats: [i32; 4] = population.propagate(); + + assert!(propa_stats[3] >= dead_at_start); + + if death_rate == 0 { + assert_eq!(propa_stats[3], 0, "no human should have died"); + } + + stats = humans_stats(&population.humans); + println!("Population after propagate: {:?}", stats); + + assert!(stats.infected <= infected_at_start + width * height * infected_expected); + + let should_be_dead = infected_at_start * death_rate / 100; + let dead_tolerance = (should_be_dead as f32 * 0.20) as i32; + + assert!( + stats.dead <= should_be_dead + dead_tolerance, + "death count should be less or equal than {}", + should_be_dead + dead_tolerance + ); + assert!(stats.dead >= should_be_dead - dead_tolerance); + } +}