1use super::*;
5
6impl Board {
7 pub fn spawn_gull(&mut self, x: u8, y: u8, dir: Direction) {
10 assert!(
11 self.in_bounds(i32::from(x), i32::from(y)),
12 "gull off the board"
13 );
14 let tile = self.index(i32::from(x), i32::from(y));
15 assert!(
16 !matches!(self.tiles[tile as usize], TileKind::Rock | TileKind::Kelp),
17 "gull on a rock or in kelp"
18 );
19 let id = self.next_gull_id;
20 self.next_gull_id += 1;
21 let handed = self.roll_handedness();
22 let takeoff_in = self.roll_takeoff();
23 let mut gull = Gull {
24 id,
25 tile,
26 dir,
27 progress: 0,
28 prev_tile: tile,
29 prev_progress: 0,
30 prev_dir: dir,
31 handed,
32 state: GullState::Walking,
33 takeoff_in,
34 };
35 self.resolve_walls_for(tile, &mut gull.dir, handed, Walker::Gull);
36 self.gulls.push(gull);
37 }
38
39 pub(super) fn run_gull_spawner(&mut self) {
43 if self.gull_period == 0 {
44 return;
45 }
46 let period = if self.in_surge() {
47 (self.gull_period / 2).max(1)
48 } else {
49 self.gull_period
50 };
51 if !self.tick.is_multiple_of(u64::from(period)) {
52 return;
53 }
54 if self.gulls.len() >= GULL_CAP {
58 return;
59 }
60 let (w, h) = (u32::from(self.width), u32::from(self.height));
61 let perimeter = if w > 1 && h > 1 {
62 2 * w + 2 * h - 4
63 } else {
64 w * h
65 };
66 let k = self.rng.next_u32() % perimeter;
67 let (x, y, dir) = if k < w {
68 (k, 0, Direction::Down) } else if k < 2 * w {
70 (k - w, h - 1, Direction::Up) } else if k < 2 * w + (h - 2) {
72 (0, k - 2 * w + 1, Direction::Right) } else {
74 (w - 1, k - 2 * w - (h - 2) + 1, Direction::Left) };
76 let tile = self.index(x as i32, y as i32);
77 if matches!(self.tiles[tile as usize], TileKind::Rock | TileKind::Kelp) {
78 return; }
80 self.spawn_gull(x as u8, y as u8, dir);
81 }
82
83 fn roll_takeoff(&mut self) -> u32 {
84 TAKEOFF_MIN + self.rng.next_u32() % (TAKEOFF_MAX - TAKEOFF_MIN + 1)
85 }
86
87 pub(super) fn move_gulls(&mut self) {
88 let mut departed: Vec<usize> = Vec::new();
89 for i in 0..self.gulls.len() {
90 let mut gull = self.gulls[i];
91 gull.prev_tile = gull.tile;
92 gull.prev_progress = gull.progress;
93 gull.prev_dir = gull.dir;
94 let raided = match gull.state {
95 GullState::Walking => self.walk_gull(&mut gull),
96 GullState::Flying { .. } => self.fly_gull(&mut gull),
97 };
98 if raided {
99 departed.push(i);
103 } else {
104 self.gulls[i] = gull;
105 }
106 }
107 for &i in departed.iter().rev() {
108 self.gulls.remove(i);
109 }
110 }
111
112 fn walk_gull(&mut self, gull: &mut Gull) -> bool {
114 if gull.takeoff_in == 0 {
116 let distance =
117 FLIGHT_MIN + (self.rng.next_u32() % u32::from(FLIGHT_MAX - FLIGHT_MIN + 1)) as u8;
118 gull.state = GullState::Flying {
119 remaining: distance,
120 };
121 return self.fly_gull(gull);
122 }
123 gull.takeoff_in -= 1;
124 if !self.passable_for(gull.tile, gull.dir, Walker::Gull) {
125 return false; }
127 gull.progress += self.walk_step(gull.tile, GULL_WALK_SPEED);
128 while gull.progress >= SUBUNITS_PER_TILE {
129 gull.progress -= SUBUNITS_PER_TILE;
130 gull.tile = self.neighbor(gull.tile, gull.dir);
131 if self.gull_arrival(gull) {
132 return true;
133 }
134 }
135 false
136 }
137
138 fn gull_arrival(&mut self, gull: &mut Gull) -> bool {
142 let t = gull.tile as usize;
143 if let TileKind::Castle(owner) = self.tiles[t] {
144 self.damage_castle(owner, gull.tile);
145 return true;
146 }
147 if self.turnstile_deflect(gull.tile, &mut gull.dir, gull.handed, Walker::Gull) {
148 return false;
149 }
150 if let Some(mut sp) = self.signposts[t] {
151 gull.dir = sp.dir;
152 self.signposts[t] = match sp.health {
153 SignpostHealth::Full => {
154 sp.health = SignpostHealth::Worn;
155 Some(sp)
156 }
157 SignpostHealth::Worn => None,
158 };
159 }
160 self.resolve_walls_for(gull.tile, &mut gull.dir, gull.handed, Walker::Gull);
161 false
162 }
163
164 fn fly_gull(&mut self, gull: &mut Gull) -> bool {
168 let GullState::Flying { mut remaining } = gull.state else {
169 return false;
170 };
171 let mut landed = false;
172 gull.progress += self.tempo_speed(GULL_FLY_SPEED);
173 while gull.progress >= SUBUNITS_PER_TILE {
174 gull.progress -= SUBUNITS_PER_TILE;
175 let (x, y) = self.coords(gull.tile);
176 let (dx, dy) = gull.dir.offset();
177 if !self.wrap && !self.in_bounds(x + dx, y + dy) {
178 gull.dir = gull.dir.reverse();
179 let (rx, ry) = gull.dir.offset();
180 if !self.in_bounds(x + rx, y + ry) {
181 gull.state = GullState::Walking;
183 gull.takeoff_in = self.roll_takeoff();
184 return false;
185 }
186 }
187 gull.tile = self.neighbor(gull.tile, gull.dir);
188 remaining = remaining.saturating_sub(1);
189 if remaining == 0 {
190 if matches!(
191 self.tiles[gull.tile as usize],
192 TileKind::Rock | TileKind::Kelp
193 ) {
194 remaining = 1; } else {
196 landed = true;
197 gull.state = GullState::Walking;
198 gull.takeoff_in = self.roll_takeoff();
199 self.resolve_walls_for(gull.tile, &mut gull.dir, gull.handed, Walker::Gull);
202 if let TileKind::Castle(owner) = self.tiles[gull.tile as usize] {
205 self.damage_castle(owner, gull.tile);
206 return true;
207 }
208 break;
209 }
210 }
211 }
212 if !landed && let GullState::Flying { remaining: r } = &mut gull.state {
213 *r = remaining;
214 }
215 false
216 }
217
218 pub(super) fn damage_castle(&mut self, owner: PlayerId, castle_tile: u16) {
225 let score = self.scores[owner as usize];
226 let target = score / 2;
227 let spill = (score - target).min(SPILL_CAP);
228 self.scores[owner as usize] = target;
229
230 let (cx, cy) = self.coords(castle_tile);
231 for (nx, ny, ox, oy) in self
232 .ring_openings(cx, cy, &CASTLE_RING)
233 .into_iter()
234 .take(spill as usize)
235 {
236 let dir = Direction::toward(ox, oy);
238 let handed = self.roll_handedness();
239 self.spawn_crab(nx as u8, ny as u8, dir, handed, CrabKind::Common);
240 }
241 }
242
243 fn sub_position(&self, tile: u16, dir: Direction, progress: u16) -> (i32, i32) {
252 let (x, y) = self.coords(tile);
253 let (dx, dy) = sub_offset(dir, progress);
254 (
255 x * i32::from(SUBUNITS_PER_TILE) + dx,
256 y * i32::from(SUBUNITS_PER_TILE) + dy,
257 )
258 }
259
260 pub(super) fn gulls_eat(&mut self) {
264 for g in 0..self.gulls.len() {
265 let gull = self.gulls[g];
266 if gull.state != GullState::Walking {
267 continue;
268 }
269 let (gx, gy) = self.sub_position(gull.tile, gull.dir, gull.progress);
270 let mut c = 0;
271 while c < self.crabs.len() {
272 let crab = self.crabs[c];
273 let (cx, cy) = self.sub_position(crab.tile, crab.dir, crab.progress);
274 if (gx - cx).unsigned_abs() + (gy - cy).unsigned_abs() <= u32::from(EAT_RANGE) {
275 self.crabs.remove(c);
276 continue;
277 }
278 c += 1;
279 }
280 }
281 }
282}