返回 CodeWhale
native_poses.rs
根目录 / crates / tui / src / tui / ambient_life / native_poses.rs
1 //! Bounded native replacements for the ASCII fish and jelly silhouettes.
2 //! These are authored dot poses, not another simulation. The habitat owns
3 //! clock, travel, brightness, collision and lifetime. Two tiny immutable tables
4 //! retain their rows so a frame borrows glyphs without allocating strings.
5 //! Braille remains discrete: half-column / quarter-row motion is not pixels.
6
7 use super::pet_sim::BRAILLE_BITS;
8 use std::sync::LazyLock;
9
10 // Fish: 2 directions × 4 tail poses × 2 horizontal × 2 vertical dot offsets.
11 static FISH: LazyLock<Vec<String>> = LazyLock::new(|| {
12 (0..32)
13 .map(|index| {
14 let dy = index % 2;
15 let dx = index / 2 % 2;
16 let pose = index / 4 % 4;
17 let right = index / 16 != 0;
18 let mut dots = Vec::from([(2, 1), (3, 0), (4, 0), (5, 1), (6, 1), (3, 2), (4, 2)]);
19 // A small forked tail opens, folds and opens; no blinking eye or flash.
20 dots.extend(match pose {
21 0 => [(0, 0), (0, 2), (1, 1)],
22 1 => [(0, 0), (1, 1), (1, 2)],
23 2 => [(1, 0), (0, 1), (1, 2)],
24 _ => [(1, 0), (1, 1), (0, 2)],
25 });
26 if !right {
27 for point in &mut dots {
28 point.0 = 6 - point.0;
29 }
30 }
31 raster(&dots, 4, 1, dx, dy).remove(0)
32 })
33 .collect()
34 });
35
36 // Jelly: 16 bell/tentacle poses × 2 horizontal × 4 vertical dot offsets.
37 // Eight-by-eight source dots plus offsets fit the existing 5×3-cell habitat.
38 static JELLY: LazyLock<Vec<[String; 3]>> = LazyLock::new(|| {
39 (0..128)
40 .map(|index| {
41 let dy = index % 4;
42 let dx = index / 4 % 2;
43 let phase = (index / 8) as f64 / 16.0 * std::f64::consts::TAU;
44 let contracted = (1.0 - phase.cos()) * 0.5;
45 let inset = usize::from(contracted > 0.55);
46 let mut dots = Vec::with_capacity(24);
47 // An open contour, never a solid luminous block.
48 dots.extend([
49 (2, 0),
50 (3, 0),
51 (4, 0),
52 (5, 0),
53 (1 + inset, 1),
54 (6 - inset, 1),
55 ]);
56 for x in inset..8 - inset {
57 dots.push((x, 2));
58 }
59 for y in 3..8 {
60 // Wave travels down both arms after the bell; columns are offset.
61 let lag = (y - 2) as f64 * 0.45 + 0.75;
62 let left = (2.0 + (phase - lag).sin() * 0.9).round() as usize;
63 let right = (5.0 + (phase - lag - 0.8).sin() * 0.9).round() as usize;
64 dots.extend([(left, y), (right, y)]);
65 }
66 let mut rows = raster(&dots, 5, 3, dx, dy).into_iter();
67 std::array::from_fn(|_| rows.next().expect("three jelly rows"))
68 })
69 .collect()
70 });
71
72 fn raster(
73 dots: &[(usize, usize)],
74 width: usize,
75 height: usize,
76 dx: usize,
77 dy: usize,
78 ) -> Vec<String> {
79 let mut cells = vec![0u8; width * height];
80 for &(x, y) in dots {
81 let (x, y) = (x + dx, y + dy);
82 assert!(
83 x < width * 2 && y < height * 4,
84 "native pose escaped its habitat"
85 );
86 cells[y / 4 * width + x / 2] |= BRAILLE_BITS[y % 4][x % 2];
87 }
88 cells
89 .chunks_exact(width)
90 .map(|row| {
91 row.iter()
92 .map(|bits| {
93 if *bits == 0 {
94 ' '
95 } else {
96 char::from_u32(0x2800 + u32::from(*bits)).expect("braille dot mask")
97 }
98 })
99 .collect()
100 })
101 .collect()
102 }
103
104 pub(super) fn fish(right: bool, pose: usize, dx: usize, dy: usize) -> &'static str {
105 &FISH[usize::from(right) * 16 + pose % 4 * 4 + dx % 2 * 2 + dy % 2]
106 }
107
108 pub(super) fn jelly(pose: usize, dx: usize, dy: usize) -> &'static [String; 3] {
109 &JELLY[pose % 16 * 8 + dx % 2 * 4 + dy % 4]
110 }
111
111 lines RUST