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], | ||||
|     cardinals: [Option<u8>; 4], | ||||
|     corner_first: bool, | ||||
|     food_spawns: Vec<FoodSpawn>, | ||||
|     initial_food: Box<[(u8, u8)]>, | ||||
|     food_spawns: Box<[FoodSpawn]>, | ||||
|     food_chance: u8, | ||||
| } | ||||
|  | ||||
| @@ -258,107 +259,18 @@ enum FoodSpawn { | ||||
|  | ||||
| impl PreparedGame { | ||||
|     fn prepare(requests: &[Request]) -> Self { | ||||
|         debug!("Preparing initial board state"); | ||||
|         let request = &requests[0]; | ||||
|         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()) | ||||
|         }); | ||||
|         let (corner_first, corners, cardinals) = prepare_starts(request); | ||||
|  | ||||
|         debug!("Preparing food placement"); | ||||
|         let food_chance = request.game.ruleset.settings.food_spawn_chance; | ||||
|         let food_min = request.game.ruleset.settings.minimum_food; | ||||
|         let initial_food = prepare_initial_food(request); | ||||
|  | ||||
|         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(); | ||||
|         let (food_chance, food_spawns) = prepare_food_placement(requests, request); | ||||
|  | ||||
|         Self { | ||||
|             corners, | ||||
|             cardinals, | ||||
|             corner_first, | ||||
|             initial_food, | ||||
|             food_spawns, | ||||
|             food_chance, | ||||
|         } | ||||
| @@ -396,6 +308,18 @@ impl PreparedGame { | ||||
|             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}"); | ||||
| @@ -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] | ||||
| //#[flame] | ||||
| fn get_rand(rand: &mut SeedRand, seed: i64, turn: i64) -> &mut SeedRand { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user