1use crate::config::Config;
2use crate::smoke::{add_smoke, get_smoke_particles, set_generation_gate, update_smoke};
3use crate::terminal::Terminal;
4use crate::train::ascii::*;
5use std::io;
6
7pub fn render_frame(
8 terminal: &Terminal,
9 x: i32,
10 pattern: usize,
11 config: &Config,
12) -> io::Result<()> {
13 let frame = build_frame(terminal, x, pattern, config);
14 terminal.render_frame(frame)?;
15 Ok(())
16}
17
18fn build_frame(terminal: &Terminal, x: i32, pattern: usize, config: &Config) -> String {
19 let mut frame = String::new();
20
21 frame.push_str("\x1B[2J\x1B[H");
23
24 set_generation_gate(x % 4 == 0);
26
27 update_smoke();
29
30 build_smoke(&mut frame, terminal);
32
33 if config.logo {
34 build_logo(&mut frame, terminal, x, pattern, config);
35 } else if config.c51 {
36 build_c51(&mut frame, terminal, x, pattern, config);
37 } else {
38 build_d51(&mut frame, terminal, x, pattern, config);
39 }
40
41 if config.accident {
42 build_man(&mut frame, terminal, x, config);
43 }
44
45 frame
46}
47
48fn build_d51(frame: &mut String, terminal: &Terminal, x: i32, pattern: usize, config: &Config) {
49 let train_height = 10i32;
50 let y_center = (terminal.height() as i32 - train_height) / 2;
51 let y_base = if config.flying {
52 y_center - (x / 4)
53 } else {
54 y_center
55 };
56
57 let pattern = pattern % D51_PATTERNS;
58
59 for (i, line) in D51_STR.iter().enumerate() {
61 let y = y_base + i as i32;
62 if y >= 0 && y < terminal.height() as i32 {
63 add_line_to_frame(frame, x, y, line, terminal);
64 }
65 }
66
67 let funnel_y = y_base - 1;
69 add_smoke(x + D51_FUNNEL as i32, funnel_y);
70
71 for (i, line) in D51_WHL[pattern].iter().enumerate() {
73 let y = y_base + (D51_STR.len() as i32) + i as i32;
74 if y >= 0 && y < terminal.height() as i32 {
75 add_line_to_frame(frame, x, y, line, terminal);
76 }
77 }
78
79 for (i, line) in D51_COAL.iter().enumerate() {
81 let y = y_base + i as i32;
82 if y >= 0 && y < terminal.height() as i32 {
83 add_line_to_frame(frame, x + 53, y, line, terminal);
84 }
85 }
86}
87
88fn build_c51(frame: &mut String, terminal: &Terminal, x: i32, pattern: usize, config: &Config) {
89 let train_height = 10i32;
90 let y_center = (terminal.height() as i32 - train_height) / 2;
91 let y_base = if config.flying {
92 y_center - (x / 4)
93 } else {
94 y_center
95 };
96
97 let pattern = pattern % C51_PATTERNS;
98
99 for (i, line) in C51_STR.iter().enumerate() {
101 let y = y_base + i as i32;
102 if y >= 0 && y < terminal.height() as i32 {
103 add_line_to_frame(frame, x, y, line, terminal);
104 }
105 }
106
107 let funnel_y = y_base - 1;
109 add_smoke(x + C51_FUNNEL as i32, funnel_y);
110
111 for (i, line) in C51_WHL[pattern].iter().enumerate() {
113 let y = y_base + (C51_STR.len() as i32) + i as i32;
114 if y >= 0 && y < terminal.height() as i32 {
115 add_line_to_frame(frame, x, y, line, terminal);
116 }
117 }
118
119 for (i, line) in C51_COAL.iter().enumerate() {
121 let y = y_base + i as i32;
122 if y >= 0 && y < terminal.height() as i32 {
123 add_line_to_frame(frame, x + 55, y, line, terminal);
124 }
125 }
126}
127
128fn build_logo(frame: &mut String, terminal: &Terminal, x: i32, pattern: usize, config: &Config) {
129 let train_height = 10i32;
130 let y_center = (terminal.height() as i32 - train_height) / 2;
131 let y_base = if config.flying {
132 y_center - (x / 4)
133 } else {
134 y_center
135 };
136
137 let pattern = pattern % 6;
138
139 for (i, line) in LOGO_STR.iter().enumerate() {
141 let y = y_base + i as i32;
142 if y >= 0 && y < terminal.height() as i32 {
143 add_line_to_frame(frame, x, y, line, terminal);
144 }
145 }
146
147 let funnel_y = y_base - 1;
149 add_smoke(x + LOGO_FUNNEL as i32, funnel_y);
150
151 for (i, line) in LOGO_WHL[pattern].iter().enumerate() {
153 let y = y_base + LOGO_STR.len() as i32 + i as i32;
154 if y >= 0 && y < terminal.height() as i32 {
155 add_line_to_frame(frame, x, y, line, terminal);
156 }
157 }
158
159 for (i, line) in LOGO_COAL.iter().enumerate() {
161 let y = y_base + LOGO_STR.len() as i32 + 2 + i as i32;
162 if y >= 0 && y < terminal.height() as i32 {
163 add_line_to_frame(frame, x, y, line, terminal);
164 }
165 }
166
167 for (i, line) in LOGO_CAR.iter().enumerate() {
169 let y = y_base + LOGO_STR.len() as i32 + 2 + i as i32;
170 if y >= 0 && y < terminal.height() as i32 {
171 add_line_to_frame(frame, x, y, line, terminal);
172 }
173 }
174}
175
176fn build_man(frame: &mut String, terminal: &Terminal, x: i32, _config: &Config) {
177 let y = terminal.height() as i32 - 3;
178 for (i, line) in MAN.iter().enumerate() {
179 let y = y + i as i32;
180 if y >= 0 && y < terminal.height() as i32 {
181 add_line_to_frame(frame, x + 15, y, line, terminal);
182 }
183 }
184}
185
186fn build_smoke(frame: &mut String, terminal: &Terminal) {
187 let particles = get_smoke_particles();
188
189 for particle in particles {
190 if particle.x >= 0
193 && particle.x < terminal.width() as i32
194 && particle.y >= 0
195 && particle.y < terminal.height() as i32
196 {
197 let pattern_idx = particle.pattern.min(15) as usize;
198 let kind = particle.kind % 2;
199
200 if let Some(smoke_str) = SMOKE_PATTERN.get(kind) {
201 if let Some(ch_str) = smoke_str.get(pattern_idx) {
202 let mut x_offset = 0;
204 for ch in ch_str.chars() {
205 let draw_x = (particle.x as i32 + x_offset) as u16;
206 if (particle.x as i32 + x_offset) >= 0
207 && (particle.x as i32 + x_offset) < terminal.width() as i32
208 && ch != ' '
209 {
210 add_char_to_frame(frame, draw_x, particle.y as u16, ch);
211 }
212 x_offset += 1;
213 }
214 }
215 }
216 }
217 }
218}
219
220fn add_line_to_frame(frame: &mut String, start_x: i32, y: i32, line: &str, terminal: &Terminal) {
221 if y < 0 || y >= terminal.height() as i32 {
222 return;
223 }
224
225 for (i, ch) in line.chars().enumerate() {
226 let x = start_x + i as i32;
227 if x >= 0 && x < terminal.width() as i32 {
228 add_char_to_frame(frame, x as u16, y as u16, ch);
229 }
230 }
231}
232
233fn add_char_to_frame(frame: &mut String, x: u16, y: u16, ch: char) {
234 frame.push_str(&format!("\x1B[{};{}H{}", y + 1, x + 1, ch));
236}