-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgrid.rs
238 lines (212 loc) · 7.72 KB
/
grid.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
use std::cmp::Reverse;
use strum::IntoEnumIterator;
use strum_macros::EnumIter;
#[derive(EnumIter, Copy, Clone, Debug, PartialEq)]
pub enum Move {
Up,
Down,
Left,
Right,
}
impl Move {
pub fn opposite(&self) -> Move {
match self {
Move::Up => Move::Down,
Move::Down => Move::Up,
Move::Left => Move::Right,
Move::Right => Move::Left,
}
}
}
#[derive(Copy, Clone, PartialEq, Debug, Eq, Hash)]
pub struct Pos {
pub x: i16,
pub y: i16,
}
impl Pos {
pub fn moved(&self, m: Move) -> Pos {
Pos {
x: match m {
Move::Left => self.x - 1,
Move::Right => self.x + 1,
_ => self.x,
},
y: match m {
Move::Up => self.y - 1,
Move::Down => self.y + 1,
_ => self.y,
}
}
}
pub fn manhattan_dist(&self, other: &Pos) -> usize {
((self.x - other.x).abs() + (self.y - other.y).abs()) as usize
}
pub fn dist_squared(&self, other: &Pos) -> usize {
let dx = self.x - other.x;
let dy = self.y - other.y;
(dx * dx + dy * dy) as usize
}
}
pub type EmptyTile = usize;
#[derive(Clone, PartialEq)]
pub struct Grid {
pub width: u8,
pub height: u8,
/// Dims: [x][y], true for walls
pub tiles: Vec<Vec<bool>>,
// 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<EmptyTile>>,
/// At an 'empty_tiles' index, neighbor indices.
neighbors: Vec<Vec<EmptyTile>>,
/// At an 'empty_tiles' index, available moves (Move analogous to neighbors).
moves: Vec<Vec<Move>>,
/// Used by hawk (sheriff.js).
pub best_intersections: Vec<Pos>,
}
impl Grid {
pub fn new(width: u8, height: u8, tiles: Vec<Vec<bool>>) -> Self {
let mut empty_tiles = Vec::new();
let mut empty_tiles_lookup = vec![
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 the above in unit test
for x in 0..(width as usize) {
for y in 0..(height as usize) {
if !tiles[x][y] {
let idx = empty_tiles.len();
empty_tiles.push(Pos { x: x as i16, y: y as i16 });
empty_tiles_lookup[x][y] = idx;
}
}
}
let mut grid = Self {
width, height, tiles, empty_tiles, empty_tiles_lookup,
// The following are easier to compute with 'grid' helper functions.
neighbors: Vec::new(),
moves: Vec::new(),
best_intersections: Vec::new(),
};
grid.neighbors = grid._compute_neighbors();
grid.moves = grid._compute_moves();
// Note: intersections computation assumes that neighbors are computed.
grid.best_intersections = grid._compute_best_intersections();
grid
}
pub fn dims(&self) -> (u8, u8) {
(self.width, self.height)
}
/// Get possible moves at a position, following getPossibleDirections order.
pub fn available_moves(&self, from: &Pos) -> &Vec<Move> {
&self.moves[self.empty_tile_idx(from) as usize]
}
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]
}
pub fn on_grid(&self, pos: &Pos) -> bool {
pos.x >= 0 && pos.x < self.width.into() && pos.y >= 0 && pos.y < self.height.into()
}
pub fn empty_tile_idx(&self, pos: &Pos) -> usize {
assert!(self.is_empty(pos), "{:?} is not empty", pos);
self.empty_tiles_lookup[pos.x as usize][pos.y as usize]
}
pub fn line_of_sight(&self, a: &Pos, b: &Pos) -> bool {
if a.x == b.x {
let start = a.y.min(b.y) + 1;
let end = a.y.max(b.y) - 1;
(start..=end).all(|y| !self.tiles[a.x as usize][y as usize])
} else if a.y == b.y {
let start = a.x.min(b.x) + 1;
let end = a.x.max(b.x) - 1;
(start..=end).all(|x| !self.tiles[x as usize][a.y as usize])
} else {
false
}
}
fn get_intersections(&self) -> Vec<Pos> {
// Note: order here assumes outer loop on xs, inner loop on ys.
// 'empty_tiles' fits that profile.
self.empty_tiles.iter().enumerate()
.filter(|&(i, _)| self.get_neighbors(i).count() >= 3)
.map(|(_, pos)| pos).cloned()
.collect()
}
fn get_row_length(&self, pos: &Pos) -> usize {
let mut row_length = 0;
row_length += (0..pos.y).rev()
.take_while(|&y| !self.tiles[pos.x as usize][y as usize]).count();
row_length += ((pos.y+1)..self.height as i16)
.take_while(|&y| !self.tiles[pos.x as usize][y as usize]).count();
row_length += (0..pos.x).rev()
.take_while(|&x| !self.tiles[x as usize][pos.y as usize]).count();
row_length += ((pos.x+1)..self.width as i16)
.take_while(|&x| !self.tiles[x as usize][pos.y as usize]).count();
row_length
}
fn _compute_neighbors(&self) -> Vec<Vec<EmptyTile>> {
self.empty_tiles.iter()
.map(|p| {
Move::iter()
.map(|m| p.moved(m))
.filter(|n| self.is_empty(n))
.map(|n| self.empty_tile_idx(&n))
.collect()
}).collect()
}
fn _compute_moves(&self) -> Vec<Vec<Move>> {
// Same order as getPossibleDirections: ["left", "right", "up", "down"]
// https://github.com/JesseEmond/blitz-2025-registration/blob/e2472c198b9ebea2e88ca07d6df8759f11fcaf4b/disassembled_js/490a918d96484178d4b23d814405ac87/challenge/threats/threat.decomp.js#L129
self.empty_tiles.iter()
.map(|p| {
[Move::Left, Move::Right, Move::Up, Move::Down].into_iter()
.filter(|&m| self.is_empty(&p.moved(m)))
.collect()
}).collect()
}
fn _compute_best_intersections(&self) -> Vec<Pos> {
let mut intersections = self.get_intersections();
intersections.sort_by_key(|p| Reverse(self.get_row_length(p)));
intersections.into_iter().take(10).collect()
}
}
/// Helper to make 'tiles' from a [y][x] structure (matches visually) of
/// '#'s and ' 's.
pub fn make_grid(rows: Vec<&str>) -> Grid {
let width = if rows.is_empty() { 0 } else { rows[0].len() };
let height = rows.len();
let mut tiles = vec![vec![false; height]; width];
for x in 0..width {
for y in 0..height {
let c = rows[y].as_bytes()[x];
assert!(c == b' ' || c == b'#',
"make_grid expects rows with spaces and #s: '{}'", c);
tiles[x][y] = c == b'#';
}
}
Grid::new(width as u8, height as u8, tiles)
}
#[allow(dead_code)]
pub fn debug_print(grid: &Grid, highlights: Vec<(&Pos, char)>) {
for y in 0..(grid.height as i16) {
for x in 0..(grid.width as i16) {
let pos = Pos { x, y };
let highlight = highlights.iter()
.filter(|(&p, _)| p == pos)
.map(|(_, c)| c)
.next();
if let Some(c) = highlight {
print!("{}", c);
} else if grid.is_empty(&pos) {
print!(" ");
} else {
print!("#");
}
}
println!();
}
}