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