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,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 {