check for initial food placement
All checks were successful
Build / build (push) Successful in 1m59s

This commit is contained in:
2025-07-06 19:49:34 +02:00
parent f0156ccec4
commit 7b28a4c4f6

View File

@ -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 { fn prepare_initial_food(request: &Request) -> Box<[(u8, u8)]> {
corners, debug!("Preparing initial food spawn");
cardinals, let center = Coord {
corner_first, x: (request.board.width - 1) / 2,
food_spawns, y: (request.board.height) / 2,
food_chance, };
} let initial_food = request
} .board
.snakes
fn validate(&self, input_rand: &mut SeedRand, seed: i64, log: bool) -> bool { .iter()
if log { .enumerate()
debug!("Checking Start positions"); .map(|(i, snake)| {
} let head = snake.head;
let mut corners = [None; 4]; let possible_food_locations = [
for (a, b) in self.corners.iter().zip(corners.iter_mut()) { Coord {
*b = a.map(usize::from); x: head.x - 1,
} y: head.y - 1,
let mut cardinals = [None; 4]; },
for (a, b) in self.cardinals.iter().zip(cardinals.iter_mut()) { Coord {
*b = a.map(usize::from); x: head.x - 1,
} y: head.y + 1,
let rand = get_rand(input_rand, seed, 0); },
if !rand.shuffle_check(&mut corners) { Coord {
if log { x: head.x + 1,
error!("Corners are shuffled wrong"); y: head.y - 1,
} },
return false; Coord {
} x: head.x + 1,
if !rand.shuffle_check(&mut cardinals) { y: head.y + 1,
if log { },
error!("Cardinals are shuffled wrong"); ];
} let available_food_locations: Vec<_> = possible_food_locations
return false; .into_iter()
} .filter(|food| {
if rand.int_n(2) == usize::from(self.corner_first) { // Don't place in the center
if log { if *food == center {
error!("Corners and cardinals are flipped: {}", self.corner_first);
}
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]