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],
cardinals: [Option<u8>; 4],
corner_first: bool,
food_spawns: Vec<FoodSpawn>,
initial_food: Box<[(u8, u8)]>,
food_spawns: Box<[FoodSpawn]>,
food_chance: u8,
}
@ -258,42 +259,116 @@ 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);
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");
let food_chance = request.game.ruleset.settings.food_spawn_chance;
let food_min = request.game.ruleset.settings.minimum_food;
@ -354,94 +429,116 @@ impl PreparedGame {
}
})
.collect();
Self {
corners,
cardinals,
corner_first,
food_spawns,
food_chance,
}
(food_chance, food_spawns)
}
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);
}
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;
}
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)
// 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))
{
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:?}");
// 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;
}
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
}
}
})
.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]