Skip to content

Commit

Permalink
???: more pathfinding 🙃
Browse files Browse the repository at this point in the history
  • Loading branch information
eerii committed Dec 9, 2023
1 parent 909598a commit 096482f
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 105 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ a game for the bevy jam 4

- [ ] mvp gameplay loop (sab mañ) 0.3
- [x] game score
- [ ] multiple start/end points
- [x] multiple start/end points
- [ ] spawn end points
- [ ] lose timer and visual feedback

Expand Down
146 changes: 74 additions & 72 deletions src/spirits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,11 @@ use crate::{
GameState,
};

const SPIRIT_SPEED: f32 = 150.;
const SPIRIT_SIZE: f32 = 40.;
const MAX_SPIRITS_IN_TILE: u32 = 2;

const FUN_A: f32 = 0.75;
const FUN_B: f32 = 0.01;
const SPIRIT_SPEED: f32 = 200.;
const SPIRIT_SIZE: f32 = 32.;
const MAX_SPIRITS_IN_TILE: u32 = 3;
const MAX_DISTANCE: f32 = std::f32::MAX;
const FUN_A: f32 = 10.;

// ······
// Plugin
Expand Down Expand Up @@ -48,6 +47,7 @@ pub struct Spirit {
curr_tile: TilePos,
next_tile: Option<TilePos>,
next_pos: Vec2,
selected_end: Option<TilePos>,
vel: Vec2,
}

Expand All @@ -58,6 +58,7 @@ impl Spirit {
curr_tile,
next_tile: None,
next_pos: curr_pos,
selected_end: None,
vel: Vec2::ZERO,
}
}
Expand All @@ -72,7 +73,7 @@ fn spawn_spirit(
time: Res<Time>,
assets: Res<GameAssets>,
mut start: Query<(&TilePos, &mut StartTile, &mut PathTile)>,
spirits: Query<&Transform, With<Spirit>>,
mut spirits: Query<(&Transform, &mut Spirit)>,
tilemap: Query<(&TilemapGridSize, &TilemapType, &Transform)>,
) {
for (start_pos, mut start_tile, mut start_path) in start.iter_mut() {
Expand All @@ -89,8 +90,9 @@ fn spawn_spirit(
// If there is already another entity there, don't spawn
if start_path.count >= 1 {
// Check all spirits
for spirit in spirits.iter() {
if (spirit.translation.xy() - pos).length() < SPIRIT_SIZE {
for (trans, mut spirit) in spirits.iter_mut() {
if (trans.translation.xy() - pos).length() < SPIRIT_SIZE {
spirit.prev_tile = None;
return;
}
}
Expand All @@ -102,7 +104,7 @@ fn spawn_spirit(
SpriteBundle {
texture: assets.bevy_icon.clone(),
transform: Transform::from_translation(pos.extend(1.))
.with_scale(Vec3::splat(0.16)),
.with_scale(Vec3::splat(0.15)),
..default()
},
Spirit::new(*start_pos, pos),
Expand All @@ -117,6 +119,7 @@ fn next_tile_spirit(
mut score: ResMut<GameScore>,
mut spirit: Query<(Entity, &Transform, &mut Spirit)>,
mut paths: Query<(&TilePos, &mut PathTile)>,
start: Query<Entity, With<StartTile>>,
end: Query<Entity, With<EndTile>>,
tilemap: Query<
(
Expand All @@ -138,15 +141,19 @@ fn next_tile_spirit(
map_type,
map_trans,
) {
// If it is on the current tile, continue
if spirit.next_tile.is_some() && spirit.curr_tile == tile_pos {
// Check if it is full
if let Some(entity) = storage.get(&tile_pos) {
if let Ok((_, path)) = paths.get(entity) {
if path.count < MAX_SPIRITS_IN_TILE {
continue;
}
}
continue;
}

// Update counts
if let Some(entity) = storage.get(&tile_pos) {
if let Ok((_, mut path)) = paths.get_mut(entity) {
path.count += 1;
}
}
if let Some(entity) = storage.get(&spirit.curr_tile) {
if let Ok((_, mut path)) = paths.get_mut(entity) {
path.count = path.count.saturating_sub(1);
}
}
spirit.curr_tile = tile_pos;
Expand All @@ -155,18 +162,22 @@ fn next_tile_spirit(
// Get the next tile
if spirit.next_tile.is_none() || spirit.next_tile.unwrap() == tile_pos {
spirit.next_tile = None;
let mut tile_distance = std::f32::INFINITY;
let mut tile_distance = &MAX_DISTANCE;

// If the spirit is on the end tile, despawn
if let Some(entity) = storage.get(&tile_pos) {
if end.get(entity).is_ok() {
cmd.get_entity(spirit_entity).unwrap().despawn_recursive();
score.score += 1;
if let Ok((_, mut path)) = paths.get_mut(entity) {
path.count = 0;
}
continue;
}
if let Ok((_, path)) = paths.get(entity) {
tile_distance = *path.distance.values().next().unwrap_or(&0.);
// FIX:
if let Some(end) = spirit.selected_end {
tile_distance = path.distance.get(&end).unwrap_or(&MAX_DISTANCE);
}
}
}

Expand Down Expand Up @@ -195,52 +206,53 @@ fn next_tile_spirit(
}
}

/*let worst = if n > 1 {
Some(
neighbours
.clone()
.max_by(|(_, a), (_, b)| {
a.distance
.partial_cmp(&b.distance)
.unwrap_or(std::cmp::Ordering::Equal)
})
.unwrap(),
)
} else {
None
};*/

// Choose the next tile to move to
// For this, it must have a path score less than the current one, or else it will stay put
// Also, we must check that there are not too many entities in this path
// From the possible next tiles, it chooses the one with the lowest score
let mut rng = rand::thread_rng();
let next = neighbours
.map(|(pos, path)| {
let mut dist = *path.distance.values().next().unwrap_or(&0.); // FIX: // + path.count as f32 * 0.1;
if let Some(prev) = spirit.prev_tile {
if prev == *pos {
dist += 100.;
}
}
/*if let Some((_, worst)) = worst {
if path.distance == worst.distance && spirit.prev_tile.is_none() {
dist += 90.;
}
}*/
if n > 1 {
let r = rng.gen_range(
0.0..(MAX_SPIRITS_IN_TILE - path.count) as f32 * FUN_A + FUN_B,
);
dist -= r * r * r * r;
}
let min_dist = |a: &f32, b: &f32| {
let r = rand::thread_rng().gen_range(-FUN_A / 2.0..FUN_A / 2.0);
(a + r).partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)
};

// Get the end position
// If there is no selected end, calculate the closest one
let (end, dist) = if let Some(end) = spirit.selected_end {
(
spirit.selected_end.as_ref().unwrap(),
path.distance.get(&end).unwrap_or(&MAX_DISTANCE),
)
} else {
path.distance
.iter()
.min_by(|(_, a), (_, b)| min_dist(a, b))
.unwrap_or((&tile_pos, &MAX_DISTANCE))
};

// Add a random offset to the distance
let r = rand::thread_rng().gen_range(0.0..0.1);

(*pos, dist, path.count)
(*pos, dist.clone() + r, Some(end.clone()), path.count)
})
.filter(|(_, dist, count)| {
*count < MAX_SPIRITS_IN_TILE && *dist < tile_distance
.filter(|(pos, dist, _, count)| {
let is_start = if let Some(entity) = storage.get(pos) {
start.get(entity).is_ok()
} else {
false
};
let is_prev = if let Some(prev) = spirit.prev_tile {
prev == *pos
} else {
false
};
*count < MAX_SPIRITS_IN_TILE
&& *dist < *tile_distance
&& !is_start
&& !is_prev
})
.min_by(|(_, a, _), (_, b, _)| {
.min_by(|(_, a, _, _), (_, b, _, _)| {
a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)
});

Expand All @@ -253,20 +265,7 @@ fn next_tile_spirit(
spirit.next_tile = Some(next.unwrap().0);
spirit.next_pos =
tile_to_pos(&spirit.next_tile.unwrap(), grid_size, map_type, map_trans);

// Update counts
if let Some(entity) = storage.get(&spirit.curr_tile) {
if let Ok((_, mut path)) = paths.get_mut(entity) {
path.count = path.count.saturating_sub(1);
}
}
if let Some(entity) = storage.get(&spirit.next_tile.unwrap()) {
if end.get(entity).is_err() {
if let Ok((_, mut path)) = paths.get_mut(entity) {
path.count += 1;
}
}
}
spirit.selected_end = next.unwrap().2;
}
}
}
Expand All @@ -291,6 +290,9 @@ fn spirit_collision(mut spirits: Query<(&mut Spirit, &Transform)>) {
let dist = delta.length();
if dist < SPIRIT_SIZE {
let dir = delta.normalize_or_zero();
// Add random offset
let r = rand::thread_rng().gen_range(-1.0..1.0);
let dir = (dir + Vec2::new(r, r)).normalize_or_zero();
sa.vel = sa.vel.lerp(dir * SPIRIT_SPEED, 3. / dist.max(3.));
sb.vel = sb.vel.lerp(-dir * SPIRIT_SPEED, 3. / dist.max(3.));
}
Expand Down
72 changes: 40 additions & 32 deletions src/tilemap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ impl Default for StartTile {
fn default() -> Self {
Self {
completed_once: false,
spawn_timer: Timer::from_seconds(1., TimerMode::Repeating),
spawn_timer: Timer::from_seconds(0.5, TimerMode::Repeating),
}
}
}
Expand Down Expand Up @@ -137,21 +137,9 @@ fn init_tilemap(mut cmd: Commands, tile_assets: Res<TilemapAssets>) {
cmd.entity(storage.get(&TilePos { x: 0, y: 7 }).unwrap())
.insert((StartTile::default(), PathTile::default()));
cmd.entity(storage.get(&TilePos { x: 14, y: 7 }).unwrap())
.insert((
EndTile,
PathTile {
distance: HashMap::from([(TilePos { x: 14, y: 7 }, 0.)]),
..default()
},
));
/*cmd.entity(storage.get(&TilePos { x: 14, y: 3 }).unwrap())
.insert((
EndTile,
PathTile {
distance: HashMap::from([(TilePos { x: 14, y: 3 }, 0.)]),
..default()
},
));*/
.insert((EndTile, PathTile::default()));
cmd.entity(storage.get(&TilePos { x: 14, y: 3 }).unwrap())
.insert((EndTile, PathTile::default()));

// Create tilemap
let map_type = TilemapType::default();
Expand Down Expand Up @@ -275,7 +263,13 @@ fn highlight_tile(
Option<&StartTile>,
Option<&EndTile>,
)>,
end_tiles: Query<(&TilePos, With<EndTile>)>,
) {
let mut ends = Vec::new();
for (pos, _) in end_tiles.iter() {
ends.push(*pos);
}

for (mut tex, mut color, mut flip, selected, path, start, end) in tiles.iter_mut() {
*color = TileColor::default();
if selected.is_some() {
Expand All @@ -296,18 +290,22 @@ fn highlight_tile(
*flip = flip_from_rotation(path.unwrap().rot);

let dist = &path.unwrap().distance;
if dist.is_empty() {
continue;
}
let min_dist = dist
.values()
.min_by(|a, b| a.partial_cmp(b).unwrap())
.unwrap();
*color = if *min_dist < std::f32::INFINITY {
let dist_a = if let Some(d) = dist.get(&ends[0]) {
*d
} else {
std::f32::INFINITY
};
let dist_b = if let Some(d) = dist.get(&ends[1]) {
*d
} else {
std::f32::INFINITY
};

*color = if dist_a < std::f32::INFINITY || dist_b < std::f32::INFINITY {
TileColor(Color::rgb(
min_dist / 25.,
1. - min_dist / 25.,
(min_dist - 20.) / 50.,
dist_a / 25.,
1. - (dist_a + dist_b) / 50.,
dist_b / 25.,
))
} else {
TileColor::default()
Expand All @@ -324,19 +322,29 @@ fn pathfinding(
end: Query<&TilePos, With<EndTile>>,
mut paths: Query<&mut PathTile>,
) {
// Clear all path distances
for mut path in paths.iter_mut() {
path.distance.clear();
}

if let Ok((size, storage)) = tilemap.get_single() {
for end_pos in end.iter() {
let mut open = BinaryHeap::new();
let mut distances = HashMap::new();

// Add the end position to the queue
distances.insert(*end_pos, 0.);
open.push(PathfindingNode {
pos: *end_pos,
distance: 0.,
});
distances.insert(*end_pos, 0.);

// TODO: INITIALIZE ALL DISTANCES TO MAX
if let Some(entity) = storage.get(end_pos) {
if let Ok(mut path) = paths.get_mut(entity) {
path.distance.insert(*end_pos, 0.);
}
}

// Start iterating through the queue
while let Some(PathfindingNode { pos, distance }) = open.pop() {
// Get the neighbouring tiles
let neighbours = get_neighbours(&pos, size);
Expand All @@ -346,13 +354,13 @@ fn pathfinding(
if let Ok(mut path) = paths.get_mut(entity) {
// Djikstra's algorithm to find the shortest path from each tile
let dist = distance + 1.;
if dist < *distances.get(&neighbour).unwrap_or(&f32::INFINITY) {
if dist < *distances.get(&neighbour).unwrap_or(&std::f32::INFINITY) {
distances.insert(neighbour, dist);
open.push(PathfindingNode {
pos: neighbour,
distance: dist,
});
path.distance.insert(*end_pos, dist * 2.);
path.distance.insert(*end_pos, dist);
}
}
}
Expand Down

0 comments on commit 096482f

Please sign in to comment.