@@ -47,6 +47,30 @@ pub struct Seaweed {
47
47
pub sway_phase : u8 ,
48
48
}
49
49
50
+ /// A surface ship moving along the waterline.
51
+ #[ derive( Debug , Clone ) ]
52
+ pub struct Ship {
53
+ pub x : f32 ,
54
+ pub y : usize ,
55
+ pub vx : f32 ,
56
+ }
57
+
58
+ /// A shark swimming under water.
59
+ #[ derive( Debug , Clone ) ]
60
+ pub struct Shark {
61
+ pub x : f32 ,
62
+ pub y : usize ,
63
+ pub vx : f32 ,
64
+ }
65
+
66
+ /// A whale swimming under water (with a spout animation).
67
+ #[ derive( Debug , Clone ) ]
68
+ pub struct Whale {
69
+ pub x : f32 ,
70
+ pub y : usize ,
71
+ pub vx : f32 ,
72
+ }
73
+
50
74
/// Environment effects and static props.
51
75
#[ derive( Debug , Clone ) ]
52
76
pub struct AquariumEnvironment {
@@ -56,6 +80,12 @@ pub struct AquariumEnvironment {
56
80
pub seaweed : Vec < Seaweed > ,
57
81
/// Whether to render the castle at bottom-right.
58
82
pub castle : bool ,
83
+ /// Surface ships.
84
+ pub ships : Vec < Ship > ,
85
+ /// Underwater sharks.
86
+ pub sharks : Vec < Shark > ,
87
+ /// Underwater whales.
88
+ pub whales : Vec < Whale > ,
59
89
}
60
90
61
91
impl Default for AquariumEnvironment {
@@ -64,6 +94,9 @@ impl Default for AquariumEnvironment {
64
94
water_phase : 0 ,
65
95
seaweed : Vec :: new ( ) ,
66
96
castle : true ,
97
+ ships : Vec :: new ( ) ,
98
+ sharks : Vec :: new ( ) ,
99
+ whales : Vec :: new ( ) ,
67
100
}
68
101
}
69
102
}
@@ -128,6 +161,102 @@ const CASTLE: &str = r#"
128
161
|_______|__|_|_|_|__|_______|
129
162
"# ;
130
163
164
+ // Ships (left/right)
165
+ const SHIP_R : & str = r#"
166
+ | | |
167
+ )_) )_) )_)
168
+ )___))___))___)\
169
+ )____)____)_____)\\\
170
+ _____|____|____|____\\\\\__
171
+ \ /
172
+ "# ;
173
+
174
+ const SHIP_L : & str = r#"
175
+ | | |
176
+ (_( (_( (_(
177
+ /(___((___((___(
178
+ //(_____(____(____(
179
+ __///____|____|____|_____
180
+ \ /
181
+ "# ;
182
+
183
+ // Sharks (left/right) - simplified large ASCII
184
+ const SHARK_R : & str = r#"
185
+ __
186
+ ( `\
187
+ ,??????????????????????????) `\
188
+ ;' `.????????????????????????( `\__
189
+ ; `.?????????????__..---'' `~~~~-._
190
+ `. `.____...--'' (b `--._
191
+ > _.-' .(( ._ )
192
+ .`.-`--...__ .-' -.___.....-(|/|/|/|/'
193
+ ;.'?????????`. ...----`.___.',,,_______......---'
194
+ '???????????'-'
195
+ "# ;
196
+
197
+ const SHARK_L : & str = r#"
198
+ __
199
+ /' )
200
+ /' (??????????????????????????,
201
+ __/' )????????????????????????.' `;
202
+ _.-~~~~' ``---..__?????????????.' ;
203
+ _.--' b) ``--...____.' .'
204
+ ( _. )). `-._ <
205
+ `\|\|\|\|)-.....___.- `-. __...--'-.'.
206
+ `---......_______,,,`.___.'----... .'?????????`.;
207
+ `-`???????????`
208
+ "# ;
209
+
210
+ // Whales (left/right)
211
+ const WHALE_R : & str = r#"
212
+ .-----:
213
+ .' `.
214
+ ,????/ (o) \
215
+ \`._/ ,__)
216
+ "# ;
217
+
218
+ const WHALE_L : & str = r#"
219
+ :-----.
220
+ .' `.
221
+ / (o) \????,
222
+ (__, \_.'/
223
+ "# ;
224
+
225
+ // Water spout frames (small)
226
+ const SPOUT_FRAMES : [ & str ; 7 ] = [
227
+ r#"
228
+
229
+ :
230
+ "# ,
231
+ r#"
232
+ :
233
+ :
234
+ "# ,
235
+ r#"
236
+ . .
237
+ -:-
238
+ :
239
+ "# ,
240
+ r#"
241
+ . .
242
+ .-:-.
243
+ :
244
+ "# ,
245
+ r#"
246
+ . .
247
+ '.-:-.`
248
+ ' : '
249
+ "# ,
250
+ r#"
251
+ .- -.
252
+ ; : ;
253
+ "# ,
254
+ r#"
255
+
256
+ ; ;
257
+ "# ,
258
+ ] ;
259
+
131
260
fn measure_block ( art : & str ) -> ( usize , usize ) {
132
261
let mut w = 0usize ;
133
262
let mut h = 0usize ;
@@ -169,6 +298,37 @@ pub fn update_aquarium(state: &mut AquariumState, assets: &[FishArt]) {
169
298
ensure_environment_initialized ( state) ;
170
299
171
300
// Integrate fish and handle bounce.
301
+
302
+ // Spawn default moving entities if none exist.
303
+ if state. env . ships . is_empty ( ) {
304
+ // Start a ship just off the left edge moving right.
305
+ let ( sw, _) = measure_block ( SHIP_R ) ;
306
+ state. env . ships . push ( Ship {
307
+ x : -( sw as f32 ) ,
308
+ y : 0 ,
309
+ vx : 1.0 ,
310
+ } ) ;
311
+ }
312
+ if state. env . sharks . is_empty ( ) {
313
+ // Place shark below waterlines.
314
+ let ( _, sh) = measure_block ( SHARK_R ) ;
315
+ let base = 9 ;
316
+ let y = state. size . 1 . saturating_sub ( sh + 3 ) . max ( base) ;
317
+ state. env . sharks . push ( Shark {
318
+ x : -40.0 ,
319
+ y,
320
+ vx : 1.2 ,
321
+ } ) ;
322
+ }
323
+ if state. env . whales . is_empty ( ) {
324
+ // Place whale at mid-depth moving left.
325
+ let y = ( state. size . 1 / 3 ) . max ( 6 ) ;
326
+ state. env . whales . push ( Whale {
327
+ x : state. size . 0 as f32 + 10.0 ,
328
+ y,
329
+ vx : -0.6 ,
330
+ } ) ;
331
+ }
172
332
for fish in & mut state. fishes {
173
333
fish. position . 0 += fish. velocity . 0 ;
174
334
fish. position . 1 += fish. velocity . 1 ;
@@ -228,6 +388,53 @@ pub fn update_aquarium(state: &mut AquariumState, assets: &[FishArt]) {
228
388
}
229
389
state. bubbles = kept;
230
390
391
+ // Move ships.
392
+ for ship in & mut state. env . ships {
393
+ ship. x += ship. vx ;
394
+ let ( sw, _) = if ship. vx >= 0.0 {
395
+ measure_block ( SHIP_R )
396
+ } else {
397
+ measure_block ( SHIP_L )
398
+ } ;
399
+ if ship. vx >= 0.0 && ship. x > state. size . 0 as f32 {
400
+ ship. x = -( sw as f32 ) ;
401
+ } else if ship. vx < 0.0 && ship. x + sw as f32 <= 0.0 {
402
+ ship. x = state. size . 0 as f32 ;
403
+ }
404
+ }
405
+
406
+ // Move sharks.
407
+ for shark in & mut state. env . sharks {
408
+ shark. x += shark. vx ;
409
+ let ( sw, _) = if shark. vx >= 0.0 {
410
+ measure_block ( SHARK_R )
411
+ } else {
412
+ measure_block ( SHARK_L )
413
+ } ;
414
+ if shark. vx >= 0.0 && shark. x > state. size . 0 as f32 {
415
+ // Re-enter from left
416
+ shark. x = -( sw as f32 ) ;
417
+ } else if shark. vx < 0.0 && shark. x + sw as f32 <= 0.0 {
418
+ // Re-enter from right
419
+ shark. x = state. size . 0 as f32 ;
420
+ }
421
+ }
422
+
423
+ // Move whales.
424
+ for whale in & mut state. env . whales {
425
+ whale. x += whale. vx ;
426
+ let ( ww, _) = if whale. vx >= 0.0 {
427
+ measure_block ( WHALE_R )
428
+ } else {
429
+ measure_block ( WHALE_L )
430
+ } ;
431
+ if whale. vx >= 0.0 && whale. x > state. size . 0 as f32 {
432
+ whale. x = -( ww as f32 ) ;
433
+ } else if whale. vx < 0.0 && whale. x + ww as f32 <= 0.0 {
434
+ whale. x = state. size . 0 as f32 ;
435
+ }
436
+ }
437
+
231
438
// Advance environment phases.
232
439
state. env . water_phase = state. env . water_phase . wrapping_add ( 1 ) ;
233
440
state. tick = state. tick . wrapping_add ( 1 ) ;
@@ -264,6 +471,29 @@ pub fn render_aquarium_to_string(state: &AquariumState, assets: &[FishArt]) -> S
264
471
}
265
472
}
266
473
474
+ // Render ships over waterlines near the surface.
475
+ for ship in & state. env . ships {
476
+ let x0 = ship. x . floor ( ) as isize ;
477
+ let y0 = ship. y as isize ;
478
+ let art = if ship. vx >= 0.0 { SHIP_R } else { SHIP_L } ;
479
+ for ( dy, line) in art. lines ( ) . enumerate ( ) {
480
+ let y = y0 + dy as isize ;
481
+ if y < 0 || y >= h as isize {
482
+ continue ;
483
+ }
484
+ for ( dx, ch) in line. chars ( ) . enumerate ( ) {
485
+ if ch == ' ' {
486
+ continue ;
487
+ }
488
+ let x = x0 + dx as isize ;
489
+ if x < 0 || x >= w as isize {
490
+ continue ;
491
+ }
492
+ grid[ y as usize * w + x as usize ] = ch;
493
+ }
494
+ }
495
+ }
496
+
267
497
// 2) Castle at bottom-right if enabled.
268
498
if state. env . castle {
269
499
let ( cw, ch) = measure_block ( CASTLE ) ;
@@ -332,6 +562,74 @@ pub fn render_aquarium_to_string(state: &AquariumState, assets: &[FishArt]) -> S
332
562
}
333
563
}
334
564
565
+ // Render whales (with spout) and sharks under water.
566
+ for whale in & state. env . whales {
567
+ let x0 = whale. x . floor ( ) as isize ;
568
+ let y0 = whale. y as isize ;
569
+ let art = if whale. vx >= 0.0 { WHALE_R } else { WHALE_L } ;
570
+ // Whale body
571
+ for ( dy, line) in art. lines ( ) . enumerate ( ) {
572
+ let y = y0 + dy as isize ;
573
+ if y < 0 || y >= h as isize {
574
+ continue ;
575
+ }
576
+ for ( dx, ch) in line. chars ( ) . enumerate ( ) {
577
+ if ch == ' ' {
578
+ continue ;
579
+ }
580
+ let x = x0 + dx as isize ;
581
+ if x < 0 || x >= w as isize {
582
+ continue ;
583
+ }
584
+ grid[ y as usize * w + x as usize ] = ch;
585
+ }
586
+ }
587
+ // Water spout above head (simple animation)
588
+ let frame = ( state. tick as usize / 4 ) % SPOUT_FRAMES . len ( ) ;
589
+ let spout = SPOUT_FRAMES [ frame] ;
590
+ // Approximate blowhole position a bit right of whale x
591
+ let spx = x0 + if whale. vx >= 0.0 { 8 } else { 3 } ;
592
+ let spy = y0. saturating_sub ( 3 ) ;
593
+ for ( dy, line) in spout. lines ( ) . enumerate ( ) {
594
+ let y = spy + dy as isize ;
595
+ if y < 0 || y >= h as isize {
596
+ continue ;
597
+ }
598
+ for ( dx, ch) in line. chars ( ) . enumerate ( ) {
599
+ if ch == ' ' {
600
+ continue ;
601
+ }
602
+ let x = spx + dx as isize ;
603
+ if x < 0 || x >= w as isize {
604
+ continue ;
605
+ }
606
+ grid[ y as usize * w + x as usize ] = ch;
607
+ }
608
+ }
609
+ }
610
+
611
+ for shark in & state. env . sharks {
612
+ let x0 = shark. x . floor ( ) as isize ;
613
+ let y0 = shark. y as isize ;
614
+ let art = if shark. vx >= 0.0 { SHARK_R } else { SHARK_L } ;
615
+ for ( dy, line) in art. lines ( ) . enumerate ( ) {
616
+ let y = y0 + dy as isize ;
617
+ if y < 0 || y >= h as isize {
618
+ continue ;
619
+ }
620
+ for ( dx, ch) in line. chars ( ) . enumerate ( ) {
621
+ if ch == ' ' {
622
+ continue ;
623
+ }
624
+ let x = x0 + dx as isize ;
625
+ if x < 0 || x >= w as isize {
626
+ continue ;
627
+ }
628
+ grid[ y as usize * w + x as usize ] = ch;
629
+ }
630
+ }
631
+ }
632
+
335
633
// 4) Fish (overdraw seaweed/castle/water where they overlap).
336
634
for fish in & state. fishes {
337
635
let art = match assets. get ( fish. fish_art_index ) {
0 commit comments