check for initial food placement
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				Build / build (push) Successful in 1m59s
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	Build / build (push) Successful in 1m59s
				
			This commit is contained in:
		| @@ -240,7 +240,8 @@ struct PreparedGame { | |||||||
|     corners: [Option<u8>; 4], |     corners: [Option<u8>; 4], | ||||||
|     cardinals: [Option<u8>; 4], |     cardinals: [Option<u8>; 4], | ||||||
|     corner_first: bool, |     corner_first: bool, | ||||||
|     food_spawns: Vec<FoodSpawn>, |     initial_food: Box<[(u8, u8)]>, | ||||||
|  |     food_spawns: Box<[FoodSpawn]>, | ||||||
|     food_chance: u8, |     food_chance: u8, | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -258,42 +259,116 @@ enum FoodSpawn { | |||||||
|  |  | ||||||
| impl PreparedGame { | impl PreparedGame { | ||||||
|     fn prepare(requests: &[Request]) -> Self { |     fn prepare(requests: &[Request]) -> Self { | ||||||
|         debug!("Preparing initial board state"); |  | ||||||
|         let request = &requests[0]; |         let request = &requests[0]; | ||||||
|         assert_eq!(request.turn, 0); |         let (corner_first, corners, cardinals) = prepare_starts(request); | ||||||
|         let mn = 1; |  | ||||||
|         let md = (request.board.width - 1) / 2; |  | ||||||
|         let mx = request.board.width - 2; |  | ||||||
|         let corners = [ |  | ||||||
|             Coord { x: mn, y: mn }, |  | ||||||
|             Coord { x: mn, y: mx }, |  | ||||||
|             Coord { x: mx, y: mn }, |  | ||||||
|             Coord { x: mx, y: mx }, |  | ||||||
|         ]; |  | ||||||
|         let cardinals = [ |  | ||||||
|             Coord { x: mn, y: md }, |  | ||||||
|             Coord { x: md, y: mn }, |  | ||||||
|             Coord { x: md, y: mx }, |  | ||||||
|             Coord { x: mx, y: md }, |  | ||||||
|         ]; |  | ||||||
|         let corner_first = corners.contains(&request.board.snakes[0].head); |  | ||||||
|         let corners = corners.map(|start| { |  | ||||||
|             request |  | ||||||
|                 .board |  | ||||||
|                 .snakes |  | ||||||
|                 .iter() |  | ||||||
|                 .position(|snake| snake.head == start) |  | ||||||
|                 .map(|pos| (pos % 4).az()) |  | ||||||
|         }); |  | ||||||
|         let cardinals = cardinals.map(|start| { |  | ||||||
|             request |  | ||||||
|                 .board |  | ||||||
|                 .snakes |  | ||||||
|                 .iter() |  | ||||||
|                 .position(|snake| snake.head == start) |  | ||||||
|                 .map(|pos| (pos % 4).az()) |  | ||||||
|         }); |  | ||||||
|  |  | ||||||
|  |         let initial_food = prepare_initial_food(request); | ||||||
|  |  | ||||||
|  |         let (food_chance, food_spawns) = prepare_food_placement(requests, request); | ||||||
|  |  | ||||||
|  |         Self { | ||||||
|  |             corners, | ||||||
|  |             cardinals, | ||||||
|  |             corner_first, | ||||||
|  |             initial_food, | ||||||
|  |             food_spawns, | ||||||
|  |             food_chance, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn validate(&self, input_rand: &mut SeedRand, seed: i64, log: bool) -> bool { | ||||||
|  |         if log { | ||||||
|  |             debug!("Checking Start positions"); | ||||||
|  |         } | ||||||
|  |         let mut corners = [None; 4]; | ||||||
|  |         for (a, b) in self.corners.iter().zip(corners.iter_mut()) { | ||||||
|  |             *b = a.map(usize::from); | ||||||
|  |         } | ||||||
|  |         let mut cardinals = [None; 4]; | ||||||
|  |         for (a, b) in self.cardinals.iter().zip(cardinals.iter_mut()) { | ||||||
|  |             *b = a.map(usize::from); | ||||||
|  |         } | ||||||
|  |         let rand = get_rand(input_rand, seed, 0); | ||||||
|  |         if !rand.shuffle_check(&mut corners) { | ||||||
|  |             if log { | ||||||
|  |                 error!("Corners are shuffled wrong"); | ||||||
|  |             } | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |         if !rand.shuffle_check(&mut cardinals) { | ||||||
|  |             if log { | ||||||
|  |                 error!("Cardinals are shuffled wrong"); | ||||||
|  |             } | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |         if rand.int_n(2) == usize::from(self.corner_first) { | ||||||
|  |             if log { | ||||||
|  |                 error!("Corners and cardinals are flipped: {}", self.corner_first); | ||||||
|  |             } | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if log { | ||||||
|  |             debug!("Checking Starting food"); | ||||||
|  |         } | ||||||
|  |         for &(selected, possibilities) in &self.initial_food { | ||||||
|  |             if rand.int_n(usize::from(possibilities)).az::<u8>() != selected { | ||||||
|  |                 if log { | ||||||
|  |                     error!("Initial food spawn is wrong: expected {selected}"); | ||||||
|  |                 } | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         self.food_spawns.iter().enumerate().all(|(turn, spawn)| { | ||||||
|  |             if log { | ||||||
|  |                 debug!("Checking turn {turn}"); | ||||||
|  |             } | ||||||
|  |             let rand = get_rand(input_rand, seed, (turn).az()); | ||||||
|  |  | ||||||
|  |             match spawn { | ||||||
|  |                 spawn @ (FoodSpawn::Forced { | ||||||
|  |                     selected, | ||||||
|  |                     free_spots, | ||||||
|  |                 } | ||||||
|  |                 | FoodSpawn::Random { | ||||||
|  |                     selected, | ||||||
|  |                     free_spots, | ||||||
|  |                 }) => { | ||||||
|  |                     if matches!(spawn, FoodSpawn::Random { .. }) | ||||||
|  |                         && 100 - rand.int_n(100) >= usize::from(self.food_chance) | ||||||
|  |                     { | ||||||
|  |                         if log { | ||||||
|  |                             error!("Food not spawned, when some should have been spawned"); | ||||||
|  |                         } | ||||||
|  |                         return false; | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     let mut foods = vec![None; usize::from(*free_spots)]; | ||||||
|  |                     foods[usize::from(*selected)] = Some(0); | ||||||
|  |                     let correct = rand.shuffle_check(&mut foods); | ||||||
|  |                     if log && !correct { | ||||||
|  |                         error!("Wrong food selected: {foods:?}"); | ||||||
|  |                     } | ||||||
|  |                     correct | ||||||
|  |                 } | ||||||
|  |                 FoodSpawn::None => { | ||||||
|  |                     let correct = 100 - rand.int_n(100) >= usize::from(self.food_chance); | ||||||
|  |                     if log && !correct { | ||||||
|  |                         error!("Food spawned, when none should have been spawned"); | ||||||
|  |                     } | ||||||
|  |                     correct | ||||||
|  |                 } | ||||||
|  |                 FoodSpawn::Blocked => { | ||||||
|  |                     // We can't get any info if there isn't even a chance for food | ||||||
|  |                     true | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fn prepare_food_placement(requests: &[Request], request: &Request) -> (u8, Box<[FoodSpawn]>) { | ||||||
|     debug!("Preparing food placement"); |     debug!("Preparing food placement"); | ||||||
|     let food_chance = request.game.ruleset.settings.food_spawn_chance; |     let food_chance = request.game.ruleset.settings.food_spawn_chance; | ||||||
|     let food_min = request.game.ruleset.settings.minimum_food; |     let food_min = request.game.ruleset.settings.minimum_food; | ||||||
| @@ -354,94 +429,116 @@ impl PreparedGame { | |||||||
|             } |             } | ||||||
|         }) |         }) | ||||||
|         .collect(); |         .collect(); | ||||||
|  |     (food_chance, food_spawns) | ||||||
|         Self { |  | ||||||
|             corners, |  | ||||||
|             cardinals, |  | ||||||
|             corner_first, |  | ||||||
|             food_spawns, |  | ||||||
|             food_chance, |  | ||||||
|         } |  | ||||||
| } | } | ||||||
|  |  | ||||||
|     fn validate(&self, input_rand: &mut SeedRand, seed: i64, log: bool) -> bool { | fn prepare_initial_food(request: &Request) -> Box<[(u8, u8)]> { | ||||||
|         if log { |     debug!("Preparing initial food spawn"); | ||||||
|             debug!("Checking Start positions"); |     let center = Coord { | ||||||
|         } |         x: (request.board.width - 1) / 2, | ||||||
|         let mut corners = [None; 4]; |         y: (request.board.height) / 2, | ||||||
|         for (a, b) in self.corners.iter().zip(corners.iter_mut()) { |     }; | ||||||
|             *b = a.map(usize::from); |     let initial_food = request | ||||||
|         } |         .board | ||||||
|         let mut cardinals = [None; 4]; |         .snakes | ||||||
|         for (a, b) in self.cardinals.iter().zip(cardinals.iter_mut()) { |         .iter() | ||||||
|             *b = a.map(usize::from); |         .enumerate() | ||||||
|         } |         .map(|(i, snake)| { | ||||||
|         let rand = get_rand(input_rand, seed, 0); |             let head = snake.head; | ||||||
|         if !rand.shuffle_check(&mut corners) { |             let possible_food_locations = [ | ||||||
|             if log { |                 Coord { | ||||||
|                 error!("Corners are shuffled wrong"); |                     x: head.x - 1, | ||||||
|             } |                     y: head.y - 1, | ||||||
|             return false; |                 }, | ||||||
|         } |                 Coord { | ||||||
|         if !rand.shuffle_check(&mut cardinals) { |                     x: head.x - 1, | ||||||
|             if log { |                     y: head.y + 1, | ||||||
|                 error!("Cardinals are shuffled wrong"); |                 }, | ||||||
|             } |                 Coord { | ||||||
|             return false; |                     x: head.x + 1, | ||||||
|         } |                     y: head.y - 1, | ||||||
|         if rand.int_n(2) == usize::from(self.corner_first) { |                 }, | ||||||
|             if log { |                 Coord { | ||||||
|                 error!("Corners and cardinals are flipped: {}", self.corner_first); |                     x: head.x + 1, | ||||||
|             } |                     y: head.y + 1, | ||||||
|  |                 }, | ||||||
|  |             ]; | ||||||
|  |             let available_food_locations: Vec<_> = possible_food_locations | ||||||
|  |                 .into_iter() | ||||||
|  |                 .filter(|food| { | ||||||
|  |                     // Don't place in the center | ||||||
|  |                     if *food == center { | ||||||
|                         return false; |                         return false; | ||||||
|                     } |                     } | ||||||
|  |  | ||||||
|         self.food_spawns.iter().enumerate().all(|(turn, spawn)| { |                     // Food must be further than snake from center on at least one axis | ||||||
|             if log { |                     if !((food.x < head.x && head.x < center.x) | ||||||
|                 debug!("Checking turn {turn}"); |                         || (food.x > head.x && head.x > center.x) | ||||||
|             } |                         || (food.y < head.y && head.y < center.y) | ||||||
|             let rand = get_rand(input_rand, seed, (turn).az()); |                         || (food.y > head.y && head.y > center.y)) | ||||||
|  |  | ||||||
|             match spawn { |  | ||||||
|                 spawn @ (FoodSpawn::Forced { |  | ||||||
|                     selected, |  | ||||||
|                     free_spots, |  | ||||||
|                 } |  | ||||||
|                 | FoodSpawn::Random { |  | ||||||
|                     selected, |  | ||||||
|                     free_spots, |  | ||||||
|                 }) => { |  | ||||||
|                     if matches!(spawn, FoodSpawn::Random { .. }) |  | ||||||
|                         && 100 - rand.int_n(100) >= usize::from(self.food_chance) |  | ||||||
|                     { |                     { | ||||||
|                         if log { |  | ||||||
|                             error!("Food not spawned, when some should have been spawned"); |  | ||||||
|                         } |  | ||||||
|                         return false; |                         return false; | ||||||
|                     } |                     } | ||||||
|  |  | ||||||
|                     let mut foods = vec![None; usize::from(*free_spots)]; |                     // Don't spawn food in corners | ||||||
|                     foods[usize::from(*selected)] = Some(0); |                     if (food.x == 0 || food.x == (request.board.width - 1)) | ||||||
|                     let correct = rand.shuffle_check(&mut foods); |                         && (food.y == 0 || food.y == (request.board.height - 1)) | ||||||
|                     if log && !correct { |                     { | ||||||
|                         error!("Wrong food selected: {foods:?}"); |                         return false; | ||||||
|                     } |                     } | ||||||
|                     correct |  | ||||||
|                 } |  | ||||||
|                 FoodSpawn::None => { |  | ||||||
|                     let correct = 100 - rand.int_n(100) >= usize::from(self.food_chance); |  | ||||||
|                     if log && !correct { |  | ||||||
|                         error!("Food spawned, when none should have been spawned"); |  | ||||||
|                     } |  | ||||||
|                     correct |  | ||||||
|                 } |  | ||||||
|                 FoodSpawn::Blocked => { |  | ||||||
|                     // We can't get any info if there isn't even a chance for food |  | ||||||
|                     true |                     true | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|                 }) |                 }) | ||||||
|  |                 .collect(); | ||||||
|  |  | ||||||
|  |             let possibilities = available_food_locations.len().az(); | ||||||
|  |             let selected = available_food_locations | ||||||
|  |                 .iter() | ||||||
|  |                 .position(|food| *food == request.board.food[i]) | ||||||
|  |                 .unwrap() | ||||||
|  |                 .az(); | ||||||
|  |             (selected, possibilities) | ||||||
|  |         }) | ||||||
|  |         .collect(); | ||||||
|  |     initial_food | ||||||
| } | } | ||||||
|  |  | ||||||
|  | fn prepare_starts(request: &Request) -> (bool, [Option<u8>; 4], [Option<u8>; 4]) { | ||||||
|  |     debug!("Preparing initial board state"); | ||||||
|  |     assert_eq!(request.turn, 0); | ||||||
|  |     let mn = 1; | ||||||
|  |     let md = (request.board.width - 1) / 2; | ||||||
|  |     let mx = request.board.width - 2; | ||||||
|  |     let corners = [ | ||||||
|  |         Coord { x: mn, y: mn }, | ||||||
|  |         Coord { x: mn, y: mx }, | ||||||
|  |         Coord { x: mx, y: mn }, | ||||||
|  |         Coord { x: mx, y: mx }, | ||||||
|  |     ]; | ||||||
|  |     let cardinals = [ | ||||||
|  |         Coord { x: mn, y: md }, | ||||||
|  |         Coord { x: md, y: mn }, | ||||||
|  |         Coord { x: md, y: mx }, | ||||||
|  |         Coord { x: mx, y: md }, | ||||||
|  |     ]; | ||||||
|  |     let corner_first = corners.contains(&request.board.snakes[0].head); | ||||||
|  |     let corners = corners.map(|start| { | ||||||
|  |         request | ||||||
|  |             .board | ||||||
|  |             .snakes | ||||||
|  |             .iter() | ||||||
|  |             .position(|snake| snake.head == start) | ||||||
|  |             .map(|pos| (pos % 4).az()) | ||||||
|  |     }); | ||||||
|  |     let cardinals = cardinals.map(|start| { | ||||||
|  |         request | ||||||
|  |             .board | ||||||
|  |             .snakes | ||||||
|  |             .iter() | ||||||
|  |             .position(|snake| snake.head == start) | ||||||
|  |             .map(|pos| (pos % 4).az()) | ||||||
|  |     }); | ||||||
|  |     (corner_first, corners, cardinals) | ||||||
| } | } | ||||||
|  |  | ||||||
| #[must_use] | #[must_use] | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user