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