Skip to content

Commit

Permalink
Optim: precompute tile neighbors
Browse files Browse the repository at this point in the history
  • Loading branch information
JesseEmond committed Oct 5, 2024
1 parent 66b089f commit 239c202
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 17 deletions.
29 changes: 26 additions & 3 deletions bot/src/grid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ impl Pos {
}
}

pub type EmptyTile = usize;

#[derive(Clone)]
pub struct Grid {
pub width: u8,
Expand All @@ -63,7 +65,9 @@ pub struct Grid {
// Precomputed features of the grid on init.
pub empty_tiles: Vec<Pos>,
/// At [x][y], index in 'empty_tiles' (only valid for empty tiles).
pub empty_tiles_lookup: Vec<Vec<usize>>,
pub empty_tiles_lookup: Vec<Vec<EmptyTile>>,
/// At an 'empty_tiles' index, neighbor indices.
neighbors: Vec<Vec<EmptyTile>>,
}

impl Grid {
Expand All @@ -73,7 +77,7 @@ impl Grid {
vec![usize::MAX; height as usize]; width as usize];
// Note: the order of the loops here matters (outer xs, inner ys), to
// match the JS code for get_aggressive_path.
// TODO: verify in unit test
// TODO: verify the above in unit test
for x in 0..(width as usize) {
for y in 0..(height as usize) {
if !tiles[x][y] {
Expand All @@ -83,7 +87,22 @@ impl Grid {
}
}
}
Self { width, height, tiles, empty_tiles, empty_tiles_lookup }
let mut neighbors = Vec::new();
for p in &empty_tiles {
let mut pos_neighbors = Vec::new();
for m in Move::iter() {
let n = p.moved(m);
if n.x < 0 || n.x >= width as i16 || n.y < 0 || n.y >= height as i16 {
continue;
}
if !tiles[n.x as usize][n.y as usize] {
let idx = empty_tiles_lookup[n.x as usize][n.y as usize];
pos_neighbors.push(idx);
}
}
neighbors.push(pos_neighbors);
}
Self { width, height, tiles, empty_tiles, empty_tiles_lookup, neighbors }
}

pub fn available_moves(&self, from: &Pos) -> Vec<Move> {
Expand All @@ -92,6 +111,10 @@ impl Grid {
.collect()
}

pub fn get_neighbors(&self, from: EmptyTile) -> impl Iterator<Item = EmptyTile> + '_ {
self.neighbors[from].iter().cloned()
}

pub fn is_empty(&self, pos: &Pos) -> bool {
self.on_grid(pos) && !self.tiles[pos.x as usize][pos.y as usize]
}
Expand Down
18 changes: 4 additions & 14 deletions bot/src/pathfinding.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
use std::collections::VecDeque;

use strum::IntoEnumIterator;

use crate::grid::{Grid, Move, Pos};
use crate::grid::{EmptyTile, Grid, Pos};

pub type Cost = usize;
/// Impossible cost in our game.
Expand All @@ -11,7 +9,7 @@ const COST_INFINITY: Cost = Cost::MAX;
pub type Path = Vec<Pos>;

/// Index into the 'empty_tiles' of a grid.
type Node = usize;
type Node = EmptyTile;

/// State of pathfinding from a given start point.
struct PathfinderState {
Expand Down Expand Up @@ -79,12 +77,7 @@ trait Pathfinder {
let pos = grid.empty_tiles[pos_idx];
// Note: order is irrelevant, since we enforce order in the
// pathfinder implementations to match the JS behavior anyway.
for d in Move::iter() {
let next_pos = pos.moved(d);
if !grid.is_empty(&next_pos) {
continue;
}
let next_pos_idx = grid.empty_tile_idx(&next_pos);
for next_pos_idx in grid.get_neighbors(pos_idx) {
let prev_cost = state.cost[next_pos_idx];
let new_cost = current_cost + 1;
if prev_cost == COST_INFINITY {
Expand Down Expand Up @@ -283,10 +276,7 @@ mod tests {
assert_eq!(Some(pos_idx), slow.next_node(&state));
let current_cost = state.cost[pos_idx];
let pos = grid.empty_tiles[pos_idx];
for d in Move::iter() {
let next_pos = pos.moved(d);
if !grid.is_empty(&next_pos) { continue; }
let next_pos_idx = grid.empty_tile_idx(&next_pos);
for next_pos_idx in grid.get_neighbors(pos_idx) {
let prev_cost = state.cost[next_pos_idx];
let new_cost = current_cost + 1;
if prev_cost == COST_INFINITY {
Expand Down

0 comments on commit 239c202

Please sign in to comment.