1use super::*;
5
6impl Board {
7 pub fn gull_may_stand(&self, x: u8, y: u8) -> bool {
13 self.in_bounds(i32::from(x), i32::from(y))
14 && !matches!(self.tile_at(x, y), TileKind::Rock | TileKind::Kelp)
15 }
16
17 pub fn spawn_gull(&mut self, x: u8, y: u8, dir: Direction) {
20 assert!(
21 self.in_bounds(i32::from(x), i32::from(y)),
22 "gull off the board"
23 );
24 assert!(self.gull_may_stand(x, y), "gull on a rock or in kelp");
25 let tile = self.index(i32::from(x), i32::from(y));
26 let id = self.next_gull_id;
27 self.next_gull_id += 1;
28 let handed = self.roll_handedness();
29 let takeoff_in = self.roll_takeoff();
30 let mut gull = Gull {
31 id,
32 tile,
33 dir,
34 progress: 0,
35 prev: Pose {
36 tile,
37 dir,
38 progress: 0,
39 },
40 handed,
41 state: GullState::Walking,
42 takeoff_in,
43 };
44 self.resolve_walls_for(tile, &mut gull.dir, handed, Walker::Gull);
45 self.gulls.push(gull);
46 }
47
48 pub(super) fn run_gull_spawner(&mut self) {
52 if self.rules.gull_period == 0 {
53 return;
54 }
55 let period = if self.in_surge() {
56 (self.rules.gull_period / 2).max(1)
57 } else {
58 self.rules.gull_period
59 };
60 if !self.tick.is_multiple_of(u64::from(period)) {
61 return;
62 }
63 if self.gulls.len() >= GULL_CAP {
67 return;
68 }
69 let (w, h) = (u32::from(self.grid.width), u32::from(self.grid.height));
70 let perimeter = if w > 1 && h > 1 {
71 2 * w + 2 * h - 4
72 } else {
73 w * h
74 };
75 let k = self.rng.next_u32() % perimeter;
76 let (x, y, dir) = if k < w {
77 (k, 0, Direction::Down) } else if k < 2 * w {
79 (k - w, h - 1, Direction::Up) } else if k < 2 * w + (h - 2) {
81 (0, k - 2 * w + 1, Direction::Right) } else {
83 (w - 1, k - 2 * w - (h - 2) + 1, Direction::Left) };
85 let tile = self.index(x as i32, y as i32);
86 if matches!(
87 self.grid.tiles[tile as usize],
88 TileKind::Rock | TileKind::Kelp
89 ) {
90 return; }
92 self.spawn_gull(x as u8, y as u8, dir);
93 }
94
95 fn roll_takeoff(&mut self) -> u32 {
96 TAKEOFF_MIN + self.rng.next_u32() % (TAKEOFF_MAX - TAKEOFF_MIN + 1)
97 }
98
99 pub(super) fn move_gulls(&mut self) {
100 let mut departed: Vec<usize> = Vec::new();
101 for i in 0..self.gulls.len() {
102 let mut gull = self.gulls[i];
103 gull.prev = gull.pose();
104 let raided = match gull.state {
105 GullState::Walking => self.walk_gull(&mut gull),
106 GullState::Flying { .. } => self.fly_gull(&mut gull),
107 };
108 if raided {
109 departed.push(i);
113 } else {
114 self.gulls[i] = gull;
115 }
116 }
117 for &i in departed.iter().rev() {
118 self.gulls.remove(i);
119 }
120 }
121
122 fn walk_gull(&mut self, gull: &mut Gull) -> bool {
124 if gull.takeoff_in == 0 {
126 let distance =
127 FLIGHT_MIN + (self.rng.next_u32() % u32::from(FLIGHT_MAX - FLIGHT_MIN + 1)) as u8;
128 gull.state = GullState::Flying {
129 remaining: distance,
130 };
131 return self.fly_gull(gull);
132 }
133 gull.takeoff_in -= 1;
134 if !self.passable_for(gull.tile, gull.dir, Walker::Gull) {
135 return false; }
137 gull.progress += self.walk_step(gull.tile, GULL_WALK_SPEED);
138 while gull.progress >= SUBUNITS_PER_TILE {
139 gull.progress -= SUBUNITS_PER_TILE;
140 gull.tile = self.neighbor(gull.tile, gull.dir);
141 if self.gull_arrival(gull) {
142 return true;
143 }
144 }
145 false
146 }
147
148 fn gull_arrival(&mut self, gull: &mut Gull) -> bool {
152 let t = gull.tile as usize;
153 if let TileKind::Castle(owner) = self.grid.tiles[t] {
154 if self.rules.castle_raids {
155 self.damage_castle(owner, gull.tile);
156 }
157 return true;
158 }
159 if self.turnstile_deflect(gull.tile, &mut gull.dir, gull.handed, Walker::Gull) {
160 return false;
161 }
162 if let Some(mut sp) = self.signposts[t] {
163 gull.dir = sp.dir;
164 self.signposts[t] = match sp.health {
165 SignpostHealth::Full => {
166 sp.health = SignpostHealth::Worn;
167 Some(sp)
168 }
169 SignpostHealth::Worn => None,
170 };
171 }
172 self.resolve_walls_for(gull.tile, &mut gull.dir, gull.handed, Walker::Gull);
173 false
174 }
175
176 fn fly_gull(&mut self, gull: &mut Gull) -> bool {
180 let GullState::Flying { mut remaining } = gull.state else {
181 return false;
182 };
183 let mut landed = false;
184 gull.progress += self.tempo_speed(GULL_FLY_SPEED);
185 while gull.progress >= SUBUNITS_PER_TILE {
186 gull.progress -= SUBUNITS_PER_TILE;
187 let (x, y) = self.coords(gull.tile);
188 let (dx, dy) = gull.dir.offset();
189 if !self.wrap && !self.in_bounds(x + dx, y + dy) {
190 gull.dir = gull.dir.reverse();
191 let (rx, ry) = gull.dir.offset();
192 if !self.in_bounds(x + rx, y + ry) {
193 gull.state = GullState::Walking;
196 gull.takeoff_in = self.roll_takeoff();
197 return false;
198 }
199 }
200 gull.tile = self.neighbor(gull.tile, gull.dir);
201 remaining = remaining.saturating_sub(1);
202 if remaining == 0 {
203 if matches!(
204 self.grid.tiles[gull.tile as usize],
205 TileKind::Rock | TileKind::Kelp
206 ) {
207 remaining = 1; } else {
209 landed = true;
210 gull.state = GullState::Walking;
211 gull.takeoff_in = self.roll_takeoff();
212 self.resolve_walls_for(gull.tile, &mut gull.dir, gull.handed, Walker::Gull);
215 if let TileKind::Castle(owner) = self.grid.tiles[gull.tile as usize] {
218 if self.rules.castle_raids {
219 self.damage_castle(owner, gull.tile);
220 }
221 return true;
222 }
223 break;
224 }
225 }
226 }
227 if !landed && let GullState::Flying { remaining: r } = &mut gull.state {
228 *r = remaining;
229 }
230 false
231 }
232
233 pub(super) fn damage_castle(&mut self, owner: PlayerId, castle_tile: u16) {
240 let score = self.scores[owner as usize];
241 let target = score / 2;
242 let spill = (score - target).min(SPILL_CAP);
243 self.scores[owner as usize] = target;
244
245 let (cx, cy) = self.coords(castle_tile);
246 for (nx, ny, ox, oy) in self
247 .ring_openings(cx, cy, &CASTLE_RING)
248 .into_iter()
249 .take(spill as usize)
250 {
251 let dir = Direction::toward(ox, oy);
253 let handed = self.roll_handedness();
254 self.spawn_crab(nx as u8, ny as u8, dir, handed, CrabKind::Common);
255 }
256 }
257
258 fn sub_position(&self, tile: u16, dir: Direction, progress: u16) -> (i32, i32) {
267 let (x, y) = self.coords(tile);
268 let (dx, dy) = sub_offset(dir, progress);
269 (
270 x * i32::from(SUBUNITS_PER_TILE) + dx,
271 y * i32::from(SUBUNITS_PER_TILE) + dy,
272 )
273 }
274
275 fn sub_distance(&self, a: (i32, i32), b: (i32, i32)) -> u32 {
282 let (mut dx, mut dy) = ((a.0 - b.0).unsigned_abs(), (a.1 - b.1).unsigned_abs());
283 if self.wrap {
284 let sub = u32::from(SUBUNITS_PER_TILE);
285 let (w, h) = (
286 u32::from(self.width()) * sub,
287 u32::from(self.height()) * sub,
288 );
289 dx %= w;
290 dy %= h;
291 dx = dx.min(w - dx);
292 dy = dy.min(h - dy);
293 }
294 dx + dy
295 }
296
297 pub(super) fn gulls_eat(&mut self) {
301 for g in 0..self.gulls.len() {
302 let gull = self.gulls[g];
303 if gull.state != GullState::Walking {
304 continue;
305 }
306 let at = self.sub_position(gull.tile, gull.dir, gull.progress);
307 let mut c = 0;
308 while c < self.crabs.len() {
309 let crab = self.crabs[c];
310 let crab_at = self.sub_position(crab.tile, crab.dir, crab.progress);
311 if self.sub_distance(at, crab_at) <= u32::from(EAT_RANGE) {
312 self.crabs.remove(c);
313 continue;
314 }
315 c += 1;
316 }
317 }
318 }
319}