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,107 +259,18 @@ 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()) |  | ||||||
|         }); |  | ||||||
|  |  | ||||||
|         debug!("Preparing food placement"); |         let initial_food = prepare_initial_food(request); | ||||||
|         let food_chance = request.game.ruleset.settings.food_spawn_chance; |  | ||||||
|         let food_min = request.game.ruleset.settings.minimum_food; |  | ||||||
|  |  | ||||||
|         let food_spawns = requests |         let (food_chance, food_spawns) = prepare_food_placement(requests, request); | ||||||
|             .windows(2) |  | ||||||
|             .filter_map(|window| { |  | ||||||
|                 if let [previous, next] = window { |  | ||||||
|                     if previous.turn + 1 == next.turn { |  | ||||||
|                         Some((previous, next)) |  | ||||||
|                     } else { |  | ||||||
|                         None |  | ||||||
|                     } |  | ||||||
|                 } else { |  | ||||||
|                     error!("Windows didn't return 2 requests: {requests:#?}"); |  | ||||||
|                     None |  | ||||||
|                 } |  | ||||||
|             }) |  | ||||||
|             .map(|(previous, next)| { |  | ||||||
|                 let mut tmp = next.clone(); |  | ||||||
|                 tmp.board.food.clone_from(&previous.board.food); |  | ||||||
|                 let heads: Vec<_> = tmp.board.snakes.iter().map(|snake| snake.head).collect(); |  | ||||||
|                 tmp.board.food.retain(|coord| !heads.contains(coord)); |  | ||||||
|                 let free_spots = get_unoccupied_points(&tmp); |  | ||||||
|  |  | ||||||
|                 if free_spots.is_empty() { |  | ||||||
|                     return FoodSpawn::Blocked; |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 let diff: Vec<_> = next |  | ||||||
|                     .board |  | ||||||
|                     .food |  | ||||||
|                     .iter() |  | ||||||
|                     .filter(|next| !previous.board.food.contains(next)) |  | ||||||
|                     .collect(); |  | ||||||
|                 if diff.is_empty() { |  | ||||||
|                     // We didn't spawn any food. It was only eaten |  | ||||||
|                     return FoodSpawn::None; |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 let selected = free_spots |  | ||||||
|                     .iter() |  | ||||||
|                     .position(|spot| spot == diff[0]) |  | ||||||
|                     .unwrap() |  | ||||||
|                     .az(); |  | ||||||
|                 let free_spots = free_spots.len().az(); |  | ||||||
|  |  | ||||||
|                 if next.board.food.len() == usize::from(food_min) { |  | ||||||
|                     FoodSpawn::Forced { |  | ||||||
|                         selected, |  | ||||||
|                         free_spots, |  | ||||||
|                     } |  | ||||||
|                 } else { |  | ||||||
|                     FoodSpawn::Random { |  | ||||||
|                         selected, |  | ||||||
|                         free_spots, |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             }) |  | ||||||
|             .collect(); |  | ||||||
|  |  | ||||||
|         Self { |         Self { | ||||||
|             corners, |             corners, | ||||||
|             cardinals, |             cardinals, | ||||||
|             corner_first, |             corner_first, | ||||||
|  |             initial_food, | ||||||
|             food_spawns, |             food_spawns, | ||||||
|             food_chance, |             food_chance, | ||||||
|         } |         } | ||||||
| @@ -396,6 +308,18 @@ impl PreparedGame { | |||||||
|             return false; |             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)| { |         self.food_spawns.iter().enumerate().all(|(turn, spawn)| { | ||||||
|             if log { |             if log { | ||||||
|                 debug!("Checking turn {turn}"); |                 debug!("Checking turn {turn}"); | ||||||
| @@ -444,6 +368,179 @@ impl PreparedGame { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | fn prepare_food_placement(requests: &[Request], request: &Request) -> (u8, Box<[FoodSpawn]>) { | ||||||
|  |     debug!("Preparing food placement"); | ||||||
|  |     let food_chance = request.game.ruleset.settings.food_spawn_chance; | ||||||
|  |     let food_min = request.game.ruleset.settings.minimum_food; | ||||||
|  |  | ||||||
|  |     let food_spawns = requests | ||||||
|  |         .windows(2) | ||||||
|  |         .filter_map(|window| { | ||||||
|  |             if let [previous, next] = window { | ||||||
|  |                 if previous.turn + 1 == next.turn { | ||||||
|  |                     Some((previous, next)) | ||||||
|  |                 } else { | ||||||
|  |                     None | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 error!("Windows didn't return 2 requests: {requests:#?}"); | ||||||
|  |                 None | ||||||
|  |             } | ||||||
|  |         }) | ||||||
|  |         .map(|(previous, next)| { | ||||||
|  |             let mut tmp = next.clone(); | ||||||
|  |             tmp.board.food.clone_from(&previous.board.food); | ||||||
|  |             let heads: Vec<_> = tmp.board.snakes.iter().map(|snake| snake.head).collect(); | ||||||
|  |             tmp.board.food.retain(|coord| !heads.contains(coord)); | ||||||
|  |             let free_spots = get_unoccupied_points(&tmp); | ||||||
|  |  | ||||||
|  |             if free_spots.is_empty() { | ||||||
|  |                 return FoodSpawn::Blocked; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             let diff: Vec<_> = next | ||||||
|  |                 .board | ||||||
|  |                 .food | ||||||
|  |                 .iter() | ||||||
|  |                 .filter(|next| !previous.board.food.contains(next)) | ||||||
|  |                 .collect(); | ||||||
|  |             if diff.is_empty() { | ||||||
|  |                 // We didn't spawn any food. It was only eaten | ||||||
|  |                 return FoodSpawn::None; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             let selected = free_spots | ||||||
|  |                 .iter() | ||||||
|  |                 .position(|spot| spot == diff[0]) | ||||||
|  |                 .unwrap() | ||||||
|  |                 .az(); | ||||||
|  |             let free_spots = free_spots.len().az(); | ||||||
|  |  | ||||||
|  |             if next.board.food.len() == usize::from(food_min) { | ||||||
|  |                 FoodSpawn::Forced { | ||||||
|  |                     selected, | ||||||
|  |                     free_spots, | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 FoodSpawn::Random { | ||||||
|  |                     selected, | ||||||
|  |                     free_spots, | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }) | ||||||
|  |         .collect(); | ||||||
|  |     (food_chance, food_spawns) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fn prepare_initial_food(request: &Request) -> Box<[(u8, u8)]> { | ||||||
|  |     debug!("Preparing initial food spawn"); | ||||||
|  |     let center = Coord { | ||||||
|  |         x: (request.board.width - 1) / 2, | ||||||
|  |         y: (request.board.height) / 2, | ||||||
|  |     }; | ||||||
|  |     let initial_food = request | ||||||
|  |         .board | ||||||
|  |         .snakes | ||||||
|  |         .iter() | ||||||
|  |         .enumerate() | ||||||
|  |         .map(|(i, snake)| { | ||||||
|  |             let head = snake.head; | ||||||
|  |             let possible_food_locations = [ | ||||||
|  |                 Coord { | ||||||
|  |                     x: head.x - 1, | ||||||
|  |                     y: head.y - 1, | ||||||
|  |                 }, | ||||||
|  |                 Coord { | ||||||
|  |                     x: head.x - 1, | ||||||
|  |                     y: head.y + 1, | ||||||
|  |                 }, | ||||||
|  |                 Coord { | ||||||
|  |                     x: head.x + 1, | ||||||
|  |                     y: head.y - 1, | ||||||
|  |                 }, | ||||||
|  |                 Coord { | ||||||
|  |                     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; | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     // Food must be further than snake from center on at least one axis | ||||||
|  |                     if !((food.x < head.x && head.x < center.x) | ||||||
|  |                         || (food.x > head.x && head.x > center.x) | ||||||
|  |                         || (food.y < head.y && head.y < center.y) | ||||||
|  |                         || (food.y > head.y && head.y > center.y)) | ||||||
|  |                     { | ||||||
|  |                         return false; | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     // Don't spawn food in corners | ||||||
|  |                     if (food.x == 0 || food.x == (request.board.width - 1)) | ||||||
|  |                         && (food.y == 0 || food.y == (request.board.height - 1)) | ||||||
|  |                     { | ||||||
|  |                         return false; | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     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] | ||||||
| //#[flame] | //#[flame] | ||||||
| fn get_rand(rand: &mut SeedRand, seed: i64, turn: i64) -> &mut SeedRand { | fn get_rand(rand: &mut SeedRand, seed: i64, turn: i64) -> &mut SeedRand { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user