Skip to content

Commit 25e98f9

Browse files
committed
Adds moving ships, sharks, and whales
Introduces ships sailing across the water's surface, sharks swimming underwater, and whales with spout animations to the aquarium. These additions enhance the visual appeal of the aquarium by adding dynamic elements that move across the screen. They start off-screen and re-enter from the opposite side, creating a continuous animation loop.
1 parent 4bf4d63 commit 25e98f9

File tree

1 file changed

+298
-0
lines changed

1 file changed

+298
-0
lines changed

src/widgets/asciiquarium.rs

Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,30 @@ pub struct Seaweed {
4747
pub sway_phase: u8,
4848
}
4949

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+
5074
/// Environment effects and static props.
5175
#[derive(Debug, Clone)]
5276
pub struct AquariumEnvironment {
@@ -56,6 +80,12 @@ pub struct AquariumEnvironment {
5680
pub seaweed: Vec<Seaweed>,
5781
/// Whether to render the castle at bottom-right.
5882
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>,
5989
}
6090

6191
impl Default for AquariumEnvironment {
@@ -64,6 +94,9 @@ impl Default for AquariumEnvironment {
6494
water_phase: 0,
6595
seaweed: Vec::new(),
6696
castle: true,
97+
ships: Vec::new(),
98+
sharks: Vec::new(),
99+
whales: Vec::new(),
67100
}
68101
}
69102
}
@@ -128,6 +161,102 @@ const CASTLE: &str = r#"
128161
|_______|__|_|_|_|__|_______|
129162
"#;
130163

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+
131260
fn measure_block(art: &str) -> (usize, usize) {
132261
let mut w = 0usize;
133262
let mut h = 0usize;
@@ -169,6 +298,37 @@ pub fn update_aquarium(state: &mut AquariumState, assets: &[FishArt]) {
169298
ensure_environment_initialized(state);
170299

171300
// 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+
}
172332
for fish in &mut state.fishes {
173333
fish.position.0 += fish.velocity.0;
174334
fish.position.1 += fish.velocity.1;
@@ -228,6 +388,53 @@ pub fn update_aquarium(state: &mut AquariumState, assets: &[FishArt]) {
228388
}
229389
state.bubbles = kept;
230390

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+
231438
// Advance environment phases.
232439
state.env.water_phase = state.env.water_phase.wrapping_add(1);
233440
state.tick = state.tick.wrapping_add(1);
@@ -264,6 +471,29 @@ pub fn render_aquarium_to_string(state: &AquariumState, assets: &[FishArt]) -> S
264471
}
265472
}
266473

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+
267497
// 2) Castle at bottom-right if enabled.
268498
if state.env.castle {
269499
let (cw, ch) = measure_block(CASTLE);
@@ -332,6 +562,74 @@ pub fn render_aquarium_to_string(state: &AquariumState, assets: &[FishArt]) -> S
332562
}
333563
}
334564

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+
335633
// 4) Fish (overdraw seaweed/castle/water where they overlap).
336634
for fish in &state.fishes {
337635
let art = match assets.get(fish.fish_art_index) {

0 commit comments

Comments
 (0)