1 #include <cstdio>
2 #include <cstdint>
3 #include <cstddef>
4 #include <csignal>
5 #include <cstdlib>
6 #include <cstring>
7 #include <cassert>
8 #include <cmath>
9 #include <cctype>
10
11 #include <unistd.h>
12 #include <sys/ioctl.h>
13 #include <termios.h>
14 #include <poll.h>
15
16 #include <vector>
17 #include <algorithm>
18 #include <iterator>
19 #include <unordered_map>
20 #include <chrono>
21 #include <thread>
22 #include <functional>
23
24 #include "cxxmatrix.hpp"
25 #include "mandel.hpp"
26 #include "conway.hpp"
27
28 namespace cxxmatrix::config {
29 constexpr std::chrono::milliseconds default_frame_interval {40};
30 constexpr int default_decay = 100; // 既定の寿命
31 }
32
33 namespace cxxmatrix {
34
35 struct frame_scheduler {
36 using clock_type = std::chrono::high_resolution_clock;
37 clock_type::time_point prev;
38 std::chrono::milliseconds frame_interval;
frame_schedulercxxmatrix::frame_scheduler39 frame_scheduler() {
40 frame_interval = config::default_frame_interval;
41 prev = clock_type::now();
42 }
next_framecxxmatrix::frame_scheduler43 void next_frame() {
44 clock_type::time_point until = prev + frame_interval;
45 clock_type::time_point now = clock_type::now();
46 if (until > now)
47 std::this_thread::sleep_for(until - now);
48 prev = now;
49 }
50 };
51
52 struct tcell_t {
53 char32_t c = U' ';
54 byte fg = 16;
55 byte bg = 16;
56 bool bold = false;
57 double diffuse = 0;
58 };
59
60 enum cell_flags {
61 cflag_disable_bold = 0x1,
62 };
63
64 struct cell_t {
65 char32_t c = U' ';
66 int birth = 0; // 設置時刻
67 double power = 0; // 初期の明るさ
68 double decay = config::default_decay; // 寿命
69 std::uint32_t flags = 0;
70
71 double stage = 0; // 現在の消滅段階 (0..1.0)
72 double current_power = 0; // 現在の明るさ(瞬き処理の前) (0..1.0)
73 };
74
75 struct thread_t {
76 int x, y;
77 int age, speed;
78 double power;
79 int decay;
80 };
81
82 struct layer_t {
83 int cols, rows;
84 int scrollx, scrolly;
85 std::vector<cell_t> content;
86 std::vector<thread_t> threads;
87
88 private:
89 int error_rate_modulo = 20;
90 public:
set_error_ratecxxmatrix::layer_t91 void set_error_rate(double value) {
92 error_rate_modulo = value > 0.0 ? std::ceil(20 / value) : 0;
93 }
94
95 public:
resizecxxmatrix::layer_t96 void resize(int cols, int rows) {
97 content.clear();
98 content.resize(cols * rows);
99 this->cols = cols;
100 this->rows = rows;
101 scrollx = 0;
102 scrolly = 0;
103 }
cellcxxmatrix::layer_t104 cell_t& cell(int x, int y) {
105 return content[y * cols + x];
106 }
rcellcxxmatrix::layer_t107 cell_t& rcell(int x, int y) {
108 x = util::mod(x + scrollx, cols);
109 y = util::mod(y + scrolly, rows);
110 return cell(x, y);
111 }
cellcxxmatrix::layer_t112 cell_t const& cell(int x, int y) const {
113 return const_cast<layer_t*>(this)->cell(x, y);
114 }
rcellcxxmatrix::layer_t115 cell_t const& rcell(int x, int y) const {
116 return const_cast<layer_t*>(this)->rcell(x, y);
117 }
118
119 public:
add_threadcxxmatrix::layer_t120 void add_thread(thread_t const& thread) {
121 threads.emplace_back(thread);
122 threads.back().x += scrollx;
123 threads.back().y += scrolly;
124 }
step_threadscxxmatrix::layer_t125 void step_threads(int now) {
126 // remove out of range threads
127 threads.erase(
128 std::remove_if(threads.begin(), threads.end(),
129 [this] (auto const& pos) -> bool {
130 int const y = pos.y - scrolly;
131 return y < 0 || rows <= y;
132 }), threads.end());
133
134 // grow threads
135 for (thread_t& pos : threads) {
136 if (pos.age++ % pos.speed == 0) {
137 cell_t& cell = this->cell(util::mod(pos.x, cols), util::mod(pos.y, rows));
138 cell.birth = now;
139 cell.power = pos.power;
140 cell.decay = pos.decay;
141 cell.flags = 0;
142 cell.c = util::rand_char();
143 pos.y++;
144 }
145 }
146 }
147
148 public:
resolve_levelcxxmatrix::layer_t149 void resolve_level(int now) {
150 for (int y = 0; y < rows; y++) {
151 for (int x = 0; x < cols; x++) {
152 cell_t& cell = this->rcell(x, y);
153 if (cell.c == ' ') continue;
154
155 int const age = now - cell.birth;
156 cell.stage = 1.0 - age / cell.decay;
157 if (cell.stage < 0.0) {
158 cell.c = ' ';
159 continue;
160 }
161
162 cell.current_power = cell.power * cell.stage;
163 if (error_rate_modulo && util::rand() % error_rate_modulo == 0)
164 cell.c = util::rand_char();
165 }
166 }
167 }
168 };
169
170 typedef std::uint32_t key_t;
171
172 enum key_flags {
173 key_up = 0x110000,
174 key_down = 0x110001,
175 key_right = 0x110002,
176 key_left = 0x110003,
177 };
key_ctrl(key_t k)178 inline constexpr key_t key_ctrl(key_t k) { return k & 0x1F; }
179
180 struct key_reader {
181 bool term_internal = false;
182 struct termios term_termios_save;
183 bool term_nonblock_save = false;
184
185 std::function<void(key_t)> proc;
186
187 public:
leavecxxmatrix::key_reader188 void leave() {
189 if (!term_internal) return;
190 term_internal = false;
191 tcsetattr(STDIN_FILENO, TCSAFLUSH, &this->term_termios_save);
192 }
entercxxmatrix::key_reader193 void enter() {
194 if (term_internal) return;
195 term_internal = true;
196
197 tcgetattr(STDIN_FILENO, &this->term_termios_save);
198 struct termios termios = this->term_termios_save;
199 termios.c_lflag &= ~(ECHO | ICANON | IEXTEN); // シグナルは使うので ISIG は消さない
200 termios.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
201 termios.c_cflag &= ~(CSIZE | PARENB);
202 termios.c_cflag |= CS8;
203 termios.c_oflag &= ~(OPOST);
204 termios.c_cc[VMIN] = 1;
205 termios.c_cc[VTIME] = 0;
206 tcsetattr(STDIN_FILENO, TCSAFLUSH, &termios);
207 }
208
209 private:
process_keycxxmatrix::key_reader210 void process_key(key_t k) {
211 if (proc) proc(k);
212 }
213
214 bool esc = false;
process_bytecxxmatrix::key_reader215 void process_byte(byte b) {
216 if (b == 0x1b) {
217 esc = true;
218 return;
219 }
220 if (esc) {
221 if (0x40 <= b && b < 0x80) {
222 switch (b) {
223 case 'A': esc = false; process_key(key_up ); break;
224 case 'B': esc = false; process_key(key_down ); break;
225 case 'C': esc = false; process_key(key_right); break;
226 case 'D': esc = false; process_key(key_left ); break;
227 case '[': break;
228 case 'O': break;
229 default: esc = false; break;
230 }
231 } else if (0x80 <= b) {
232 process_key(0x1b);
233 process_key(b);
234 esc = false;
235 }
236 } else {
237 process_key(b);
238 }
239 }
nonblock_readcxxmatrix::key_reader240 static ssize_t nonblock_read(int fd, byte* buffer, ssize_t size) {
241 struct pollfd pollfd;
242 pollfd.fd = fd;
243 pollfd.events = POLLIN | POLLERR;
244 poll(&pollfd, 1, 0);
245 if (pollfd.revents & POLLIN)
246 return read(fd, buffer, size);
247 return 0;
248 }
249 public:
processcxxmatrix::key_reader250 void process() {
251 byte buffer[1024];
252 ssize_t nread;
253 while ((nread = nonblock_read(STDIN_FILENO, buffer, 1024)) > 0) {
254 for (ssize_t i = 0; i < nread; i++)
255 process_byte(buffer[i]);
256 }
257 }
258 };
259
260
261 enum scene_t {
262 scene_none = 0,
263 scene_number = 1,
264 scene_banner = 2,
265 scene_rain = 3,
266 scene_conway = 4,
267 scene_mandelbrot = 5,
268 scene_rain_forever = 6,
269 scene_exit = 7, // Exit (menu)
270 scene_loop = 99,
271
272 scene_count = 7,
273 };
274
275 struct buffer {
276 private:
277 bool setting_diffuse_enabled = true;
278 bool setting_twinkle_enabled = true;
279 bool setting_preserve_background = false;
280 double setting_rain_interval = 150;
281 public:
set_diffuse_enabledcxxmatrix::buffer282 void set_diffuse_enabled(bool value) {
283 this->setting_diffuse_enabled = value;
284 }
set_twinkle_enabledcxxmatrix::buffer285 void set_twinkle_enabled(bool value) {
286 this->setting_twinkle_enabled = value;
287 this->update_twinkle_rendering();
288 }
set_preserve_backgroundcxxmatrix::buffer289 void set_preserve_background(bool value) {
290 this->setting_preserve_background = value;
291 }
set_rain_densitycxxmatrix::buffer292 void set_rain_density(double value) {
293 setting_rain_interval = 150 / value;
294 }
295
296 private:
297 layer_t layers[3];
298 public:
set_error_ratecxxmatrix::buffer299 void set_error_rate(double value) {
300 for (auto& layer : layers)
301 layer.set_error_rate(value);
302 }
303
304 private:
305 int cols, rows;
306 std::vector<tcell_t> old_content;
307 std::vector<tcell_t> new_content;
308 std::FILE* file;
309
310 private:
311 bool flag_resize = false;
312 public:
notify_resizecxxmatrix::buffer313 void notify_resize() {
314 flag_resize = true;
315 }
process_signalscxxmatrix::buffer316 void process_signals() {
317 if (flag_resize) {
318 flag_resize = false;
319 initialize();
320 redraw();
321 }
322 }
323
324 private:
325 frame_scheduler scheduler;
next_framecxxmatrix::buffer326 void next_frame() {
327 process_signals();
328 scheduler.next_frame();
329 }
330 public:
set_frame_ratecxxmatrix::buffer331 void set_frame_rate(double frame_rate) {
332 using msec_rep = std::chrono::milliseconds::rep;
333 constexpr msec_rep max_frame_interval = 1000 * 3600;
334
335 double const frame_interval = 1000 / frame_rate;
336 scheduler.frame_interval = std::chrono::milliseconds((msec_rep) std::clamp(frame_interval, 1.0, (double) max_frame_interval));
337 }
338
339 public:
340 key_reader kreader;
341
342 public:
buffercxxmatrix::buffer343 buffer() {
344 initialize_color_table(47);
345 }
346
347 private:
put_utf8cxxmatrix::buffer348 void put_utf8(char32_t uc) {
349 std::uint32_t u = uc;
350 if (u < 0x80) {
351 std::putc(u, file);
352 } else if (u < 0x800) {
353 std::putc(0xC0 | (u >> 6), file);
354 std::putc(0x80 | (u & 0x3F), file);
355 } else if (u < 0x10000) {
356 std::putc(0xE0 | (u >> 12), file);
357 std::putc(0x80 | (0x3F & u >> 6), file);
358 std::putc(0x80 | (0x3F & u), file);
359 } else if (u < 0x200000) {
360 std::putc(0xF0 | (u >> 18), file);
361 std::putc(0x80 | (0x3F & u >> 12), file);
362 std::putc(0x80 | (0x3F & u >> 6), file);
363 std::putc(0x80 | (0x3F & u), file);
364 }
365 }
366
367 byte fg;
368 byte bg;
369 bool bold;
sgr0cxxmatrix::buffer370 void sgr0() {
371 std::fprintf(file, "\x1b[H\x1b[m");
372 px = py = 0;
373 fg = 0;
374 bg = 0;
375 bold = false;
376 }
set_colorcxxmatrix::buffer377 void set_color(tcell_t const& tcell) {
378 if (tcell.bg != this->bg) {
379 this->bg = tcell.bg;
380 if (setting_preserve_background && this->bg == color_table[0])
381 std::fprintf(file, "\x1b[49m");
382 else
383 std::fprintf(file, "\x1b[48;5;%dm", this->bg);
384 }
385 if (tcell.c != ' ') {
386 if (tcell.fg != fg) {
387 this->fg = tcell.fg;
388 std::fprintf(file, "\x1b[38;5;%dm", this->fg);
389 }
390 if (tcell.bold != bold) {
391 this->bold = tcell.bold;
392 std::fprintf(file, "\x1b[%dm", this->bold ? 1 : 22);
393 }
394 }
395 }
396
397 private:
398 int px = -1, py = -1;
goto_xycxxmatrix::buffer399 void goto_xy(int x, int y) {
400 if (y == py) {
401 if (x != px) {
402 if (x == 0) {
403 std::putc('\r', file);
404 } else if (px - 3 <= x && x < px) {
405 while (x < px--)
406 std::putc('\b', file);
407 } else {
408 std::fprintf(file, "\x1b[%dG", x + 1);
409 }
410 px = x;
411 }
412 return;
413 }
414
415 // \v が思うように動いていない?
416 // if (x <= px && py < y && (x == 0 ? 1 : px - x) + (y - py) <= (x == 0 || x == px ? 3 : 5)) {
417 // if (x != px) {
418 // if (x == 0) {
419 // std::putc('\r', file);
420 // px = 0;
421 // } else {
422 // while (x < px--)
423 // std::putc('\b', file);
424 // }
425 // }
426 // while (py++ < y)
427 // std::putc('\v', file);
428 // return;
429 // }
430
431 if (x == 0) {
432 std::fprintf(file, "\x1b[%dH", y + 1);
433 px = x;
434 py = y;
435 return;
436 } else if (x == px) {
437 if (y < py) {
438 std::fprintf(file, "\x1b[%dA", py - y);
439 } else {
440 std::fprintf(file, "\x1b[%dB", y - py);
441 }
442 py = y;
443 return;
444 }
445
446 std::fprintf(file, "\x1b[%d;%dH", y + 1, x + 1);
447 px = x;
448 py = y;
449 }
450
451 private:
is_changedcxxmatrix::buffer452 static bool is_changed(tcell_t const& ncell, tcell_t const& ocell) {
453 if (ncell.c != ocell.c || ncell.bg != ocell.bg) return true;
454 if (ncell.c == ' ') return false;
455 if (ncell.fg != ocell.fg || ncell.bold != ocell.bold) return true;
456 return false;
457 }
term_draw_cellcxxmatrix::buffer458 bool term_draw_cell(int x, int y, std::size_t index, bool force_write) {
459 tcell_t& ncell = new_content[index];
460 tcell_t& ocell = old_content[index];
461 if (ncell.fg == ocell.bg) ncell.c = ' ';
462 if (force_write || is_changed(ncell, ocell)) {
463 goto_xy(x, y);
464 set_color(ncell);
465 put_utf8(ncell.c);
466 px++;
467 ocell = ncell;
468 return true;
469 }
470 return false;
471 }
472
473 public:
redrawcxxmatrix::buffer474 void redraw() {
475 goto_xy(0, 0);
476 for (int y = 0; y < rows; y++) {
477 for (int x = 0; x < cols; x++) {
478 // 行末 xenl 対策
479 if (y == rows - 1) {
480 if (x == cols - 2) {
481 tcell_t const& cell = new_content[y * cols + x + 1];
482 set_color(cell);
483 put_utf8(cell.c);
484 std::fprintf(file, "\b\x1b[@");
485 } else if (x == cols -1) {
486 continue;
487 }
488 }
489
490 tcell_t const& tcell = new_content[y * cols + x];
491 set_color(tcell);
492 put_utf8(tcell.c);
493 }
494 }
495 std::fprintf(file, "\x1b[H");
496 std::fflush(file);
497
498 old_content.resize(new_content.size());
499 for (std::size_t i = 0; i < new_content.size(); i++)
500 old_content[i] = new_content[i];
501 }
502
draw_contentcxxmatrix::buffer503 void draw_content() {
504 for (int y = 0; y < rows; y++) {
505 for (int x = 0; x < cols - 1; x++) {
506 std::size_t const index = y * cols + x;
507
508 bool dirty = true;
509
510 // 行末 xenl 対策
511 if (x == cols - 2 && term_draw_cell(x, y, index + 1, false)) {
512 std::fprintf(file, "\b\x1b[@");
513 px--;
514 dirty = true;
515 }
516
517 term_draw_cell(x, y, index, dirty);
518 }
519 }
520 std::fflush(file);
521 process_signals();
522 }
523
524 private:
clear_diffusecxxmatrix::buffer525 void clear_diffuse() {
526 for (int y = 0; y < rows; y++) {
527 for (int x = 0; x < cols; x++) {
528 std::size_t const index = y * cols + x;
529 tcell_t& tcell = new_content[index];
530 tcell.diffuse = 0;
531 tcell.bg = color_table[0];
532 }
533 }
534 }
add_diffusecxxmatrix::buffer535 void add_diffuse(int x, int y, double value) {
536 if (0 <= y && y < rows && 0 <= x && x < cols && value > 0) {
537 std::size_t const index = y * cols + x;
538 tcell_t& tcell = new_content[index];
539 tcell.diffuse += value;
540 }
541 }
resolve_diffusecxxmatrix::buffer542 void resolve_diffuse() {
543 for (int y = 0; y < rows; y++) {
544 for (int x = 0; x < cols; x++) {
545 std::size_t const index = y * cols + x;
546 tcell_t& tcell = new_content[index];
547 double const diffuse = std::min(0.04 * tcell.diffuse, 0.3);
548 tcell.bg = color_table[(int) (diffuse * (color_table.size() - 1))];
549 }
550 }
551 }
552
553 public:
554 int now = 100;
555
556 private:
557 static constexpr double default_twinkle = 0.2;
558 double m_twinkle = default_twinkle;
559 double m_twinkle_rendering = m_twinkle;
update_twinkle_renderingcxxmatrix::buffer560 void update_twinkle_rendering() {
561 if (setting_twinkle_enabled)
562 m_twinkle_rendering = m_twinkle;
563 else
564 m_twinkle_rendering = 0.0;
565 }
set_twinklecxxmatrix::buffer566 void set_twinkle(double value) {
567 this->m_twinkle = value;
568 }
569
570 private:
571 std::vector<byte> color_table;
572
get_color_codecxxmatrix::buffer573 static int get_color_code(double R, double G, double B) {
574 int r = std::round(R * 5);
575 int g = std::round(G * 5);
576 int b = std::round(B * 5);
577 return 16 + r * 36 + g * 6 + b;
578 }
initialize_color_table_rgbcxxmatrix::buffer579 bool initialize_color_table_rgb(byte r, byte g, byte b) {
580 int const mx = std::max(r, std::max(g, b));
581 int const mn = std::min(r, std::min(g, b));
582 if (mx == 0) return false;
583 double const R = (double) r / mx;
584 double const G = (double) g / mx;
585 double const B = (double) b / mx;
586
587 color_table.clear();
588 color_table.reserve(6 + 5 - mn);
589 for (int i = 0; i <= 5; i++)
590 color_table.push_back(get_color_code(R * i / 5, G * i / 5, B * i / 5));
591 for (int i = mn + 1; i <= 5; i++) {
592 double const R1 = 1.0 - (1.0 - R) * (5 - i) / (5 - mn);
593 double const G1 = 1.0 - (1.0 - G) * (5 - i) / (5 - mn);
594 double const B1 = 1.0 - (1.0 - B) * (5 - i) / (5 - mn);
595 color_table.push_back(get_color_code(R1, G1, B1));
596 }
597 return true;
598 }
initialize_color_table_graycxxmatrix::buffer599 void initialize_color_table_gray() {
600 color_table.clear();
601 color_table.reserve(25);
602 for (int i = 232; i < 256; i++)
603 color_table.push_back(i);
604 color_table.push_back(231);
605 }
606 public:
initialize_color_tablecxxmatrix::buffer607 void initialize_color_table(byte color) {
608 if (color < 16) {
609 int const r = color & 1;
610 int const g = color / 2 & 1;
611 int const b = color / 4 & 1;
612 if (initialize_color_table_rgb(r, g, b)) return;
613 } else if (color < 232) {
614 color -= 16;
615 int const r = color / 36;
616 int const g = color / 6 % 6;
617 int const b = color % 6;
618 if (initialize_color_table_rgb(r, g, b)) return;
619 }
620 initialize_color_table_gray();
621 }
622
623 private:
clear_contentcxxmatrix::buffer624 void clear_content() {
625 for (auto& tcell: new_content) {
626 tcell.c = ' ';
627 tcell.fg = color_table[0];
628 tcell.bg = color_table[0];
629 tcell.bold = false;
630 }
631 }
632
rend_cellcxxmatrix::buffer633 cell_t const* rend_cell(int x, int y, double& power) {
634 cell_t const* ret = nullptr;
635 for (auto& layer: layers) {
636 auto const& cell = layer.rcell(x, y);
637 if (cell.c != ' ') {
638 if (!ret) ret = &cell;
639 if (cell.current_power > power) power = cell.current_power;
640 }
641 }
642 return ret;
643 }
644
construct_render_contentcxxmatrix::buffer645 void construct_render_content() {
646 clear_diffuse();
647 for (int y = 0; y < rows; y++) {
648 for (int x = 0; x < cols; x++) {
649 std::size_t const index = y * cols + x;
650 tcell_t& tcell = new_content[index];
651
652 double current_power = 0.0;
653 cell_t const* lcell = this->rend_cell(x, y, current_power);
654 if (!lcell) {
655 tcell.c = ' ';
656 continue;
657 }
658
659 tcell.c = lcell->c;
660
661 // current_power = 現在の輝度 (瞬き)
662 if (m_twinkle_rendering != 0.0) {
663 current_power -= std::hypot(current_power * m_twinkle_rendering, 0.1) * util::randf();
664 if (current_power < 0.0) current_power = 0.0;
665 }
666
667 // level = 色番号
668 double const fractional_level = util::interpolate(current_power, 0.6, color_table.size());
669 int level = fractional_level;
670 if (m_twinkle_rendering != 0.0 && util::randf() > fractional_level - level) level++;
671 level = std::min<int>(level, color_table.size() - 1);
672
673 tcell.fg = color_table[level];
674 tcell.bold = !(lcell->flags & cflag_disable_bold) && lcell->stage > 0.5;
675
676 if (!setting_diffuse_enabled) continue;
677
678 double const twinkle_power = (double) level / (color_table.size() - 1);
679 double const p0 = ((1.0 / 0.3) * (twinkle_power - 0.0));
680 double const p1 = ((1.0 / 0.3) * (twinkle_power - 0.3));
681 double const p2 = ((1.0 / 0.5) * (twinkle_power - 0.7));
682
683 tcell.diffuse += p0;
684 add_diffuse(x - 1, y, p1);
685 add_diffuse(x + 1, y, p1);
686 add_diffuse(x, y - 1, p1);
687 add_diffuse(x, y + 1, p1);
688 add_diffuse(x - 1, y - 1, p2);
689 add_diffuse(x + 1, y - 1, p2);
690 add_diffuse(x - 1, y + 1, p2);
691 add_diffuse(x + 1, y + 1, p2);
692 }
693 }
694
695 if (setting_diffuse_enabled)
696 resolve_diffuse();
697 }
698
699 public:
render_directcxxmatrix::buffer700 void render_direct() {
701 now++;
702 this->draw_content();
703 }
render_layerscxxmatrix::buffer704 void render_layers() {
705 now++;
706 for (auto& layer: layers) {
707 layer.step_threads(now);
708 layer.resolve_level(now);
709 }
710 this->construct_render_content();
711 this->draw_content();
712 }
713
714 bool term_internal = false;
term_leavecxxmatrix::buffer715 void term_leave() {
716 if (!term_internal) return;
717 term_internal = false;
718 std::fprintf(file, "\x18"); // CAN
719 std::fprintf(file, "\x1b[m\x1b[%dH\n", rows);
720 std::fprintf(file, "\x1b[?1049l\x1b[?25h");
721 std::fflush(file);
722 kreader.leave();
723 }
term_entercxxmatrix::buffer724 void term_enter() {
725 if (term_internal) return;
726 term_internal = true;
727 kreader.enter();
728 std::fprintf(file, "\x1b[?1049h\x1b[?25l");
729 sgr0();
730 redraw();
731 std::fflush(file);
732 }
733
734 bool is_menu = false;
process_keycxxmatrix::buffer735 void process_key(key_t k) {
736 if (is_menu) {
737 menu_process_key(k);
738 } else {
739 switch (k) {
740 case key_ctrl('m'):
741 case key_ctrl('j'):
742 menu_initialize();
743 break;
744 }
745 }
746 }
747
initializecxxmatrix::buffer748 void initialize() {
749 kreader.proc = [this] (key_t k) { this->process_key(k); };
750 struct winsize ws;
751 ioctl(STDIN_FILENO, TIOCGWINSZ, (char*) &ws);
752 cols = ws.ws_col;
753 rows = ws.ws_row;
754 file = stdout;
755 new_content.clear();
756 new_content.resize(cols * rows);
757
758 for (auto& layer : layers)
759 layer.resize(cols, rows);
760 }
761
finalizecxxmatrix::buffer762 void finalize() {
763 term_leave();
764 }
765
766
767 public:
s3rain_scroll_func_tanhcxxmatrix::buffer768 static double s3rain_scroll_func_tanh(double value) {
769 value = value / 200.0 - 10.0;
770 constexpr double tanh_range = 2.0;
771 static double th1 = std::tanh(tanh_range);
772 value = std::max(value, -tanh_range * 2.0);
773
774 if (value < -tanh_range) {
775 return -th1 + (1.0 - th1 * th1) * (value + tanh_range);
776 } else if (value < tanh_range) {
777 return std::tanh(value);
778 } else {
779 return th1 + (1.0 - th1 * th1) * (value - tanh_range);
780 }
781 }
s3rain_scroll_func_constcxxmatrix::buffer782 static double s3rain_scroll_func_const(double) {
783 return 0.0;
784 }
785
786 public:
s3raincxxmatrix::buffer787 void s3rain(std::uint32_t nloop, double (*scroll_func)(double)) {
788 static byte speed_table[] = {2, 2, 2, 2, 3, 3, 6, 6, 6, 7, 7, 8, 8, 8};
789
790 double const scr0 = scroll_func(0);
791 int initial_scrollx[3];
792 int initial_scrolly[3];
793 for (int i = 0; i < 3; i++) {
794 initial_scrollx[i] = layers[i].scrollx;
795 initial_scrolly[i] = layers[i].scrolly;
796 }
797
798 for (std::uint32_t loop = 0; nloop == 0 || loop < nloop; loop++) {
799 // add new threads
800 if (now % (int) std::ceil(setting_rain_interval / cols) == 0) {
801 thread_t thread;
802 thread.x = util::rand() % cols;
803 thread.y = 0;
804 thread.age = 0;
805 thread.speed = speed_table[util::rand() % std::size(speed_table)];
806 thread.power = 2.0 / thread.speed;
807 thread.decay = config::default_decay;
808
809 int const layer = thread.speed < 3 ? 0 : thread.speed < 5 ? 1 : 2;
810 layers[layer].add_thread(thread);
811 }
812
813 double const scr = scroll_func(loop) - scr0;
814 layers[0].scrollx = initial_scrollx[0] - std::round(500 * scr);
815 layers[1].scrollx = initial_scrollx[1] - std::round(50 * scr);
816 layers[2].scrollx = initial_scrollx[2] + std::round(200 * scr);
817
818 layers[0].scrolly = initial_scrolly[0] - std::round(25 * scr);
819 layers[1].scrolly = initial_scrolly[1] + std::round(20 * scr);
820 layers[2].scrolly = initial_scrolly[2] + std::round(45 * scr);
821
822 render_layers();
823 next_frame();
824 kreader.process();
825 if (is_menu) return;
826 }
827 std::uint32_t const wait = 8 * rows + config::default_decay;
828 for (std::uint32_t loop = 0; loop < wait; loop++) {
829 render_layers();
830 next_frame();
831 kreader.process();
832 if (is_menu) return;
833 }
834 }
835
836 private:
s1number_fill_numberscxxmatrix::buffer837 void s1number_fill_numbers(int stripe) {
838 for (int y = 0; y < rows; y++) {
839 for (int x = 0; x < cols; x++) {
840 tcell_t& tcell = new_content[y * cols + x];
841 cell_t& cell = layers[1].rcell(x, y);
842 if (stripe && x % stripe == 0) {
843 cell.c = ' ';
844 tcell.c = ' ';
845 } else {
846 cell.c = U'0' + util::rand() % 10;
847 cell.birth = now - std::round((0.5 + 0.1 * util::randf()) * config::default_decay);
848 cell.power = 1.0;
849 cell.decay = config::default_decay;
850 cell.flags = cflag_disable_bold;
851 tcell.c = cell.c;
852 tcell.fg = color_table[color_table.size() / 2 + util::rand_char() % 3];
853 }
854 }
855 }
856 }
857
858 public:
s1numbercxxmatrix::buffer859 void s1number() {
860 clear_content();
861 int stripe_periods[] = {0, 32, 16, 8, 4, 2, 2, 2};
862 for (int stripe: stripe_periods) {
863 for (int i = 0; i < 20; i++) {
864 s1number_fill_numbers(stripe);
865 render_direct();
866 next_frame();
867 kreader.process();
868 if (is_menu) return;
869 }
870 }
871 }
872
873 private:
s2banner_decodecxxmatrix::buffer874 static void s2banner_decode(std::vector<char32_t>& msg, const char* msg_u8) {
875 while (msg.size() < s2banner_max_message_size && *msg_u8) {
876 std::uint32_t code = (byte) *msg_u8++;
877 int remain;
878 std::uint32_t min_code;
879 if (code < 0xC0) {
880 if (code >= 0x80) goto error_char;
881 remain = 0;
882 min_code = 0;
883 } else if (code < 0xE0) {
884 remain = 1;
885 min_code = 1 << 7;
886 } else if (code < 0xF0) {
887 remain = 2;
888 min_code = 1 << 11;
889 } else if (code < 0xF8) {
890 remain = 3;
891 min_code = 1 << 16;
892 } else if (code < 0xFC) {
893 remain = 4;
894 min_code = 1 << 21;
895 } else if (code < 0xFE) {
896 remain = 5;
897 min_code = 1 << 26;
898 } else {
899 goto error_char;
900 }
901
902 if (remain) code &= (1 << (6 - remain)) - 1;
903 while (remain-- && 0x80 <= (byte) *msg_u8 && (byte) *msg_u8 < 0xC0)
904 code = code << 6 | (*msg_u8++ & 0x3F);
905 if (code < min_code) goto error_char;
906 msg.push_back(code);
907 continue;
908 error_char:
909 msg.push_back(0xFFFD);
910 }
911 }
912
913 struct glyph_definition_t {
914 char32_t c;
915 int w;
916 int lines[7];
917
operator ()cxxmatrix::buffer::glyph_definition_t918 bool operator()(int x, int y) const {
919 return lines[y] & (1 << x);
920 }
921 };
922 struct glyph_t {
923 int h, w;
924 int render_width;
925 glyph_definition_t const* def;
926 public:
operator ()cxxmatrix::buffer::glyph_t927 bool operator()(int x, int y) const {
928 return def && (*def)(x, y);
929 }
930 };
931
932 static constexpr int s2banner_initial_input = 40;
933 static constexpr int s2banner_cell_width = 10;
934 static constexpr int s2banner_cell_height = 7;
935 static constexpr std::size_t s2banner_max_message_size = 0x1000;
936
937 struct banner_message_t {
938 std::vector<char32_t> text;
939 std::vector<glyph_t> glyphs;
940 int min_width = 0; // 表示に必要な最低幅
941
942 int render_width = 0; // 全体の表示幅
943 int min_progress = 0; // 最小の文字表示幅
944
945 private:
glyph_datacxxmatrix::buffer::banner_message_t946 static glyph_definition_t const* glyph_data(char32_t c) {
947 static glyph_definition_t glyph_defs[] = {
948 #include "glyph.inl"
949 };
950 static std::unordered_map<char32_t, glyph_definition_t const*> map;
951 if (map.empty()) {
952 for (auto const& def : glyph_defs)
953 map[def.c] = &def;
954 }
955 auto it = map.find(c);
956 if (it == map.end() && c != ' ')
957 it = map.find(U'\uFFFD');
958 return it != map.end() ? it->second : nullptr;
959 }
960 public:
set_textcxxmatrix::buffer::banner_message_t961 void set_text(const char* msg) {
962 text.clear();
963 s2banner_decode(text, msg);
964 }
resolve_glyphcxxmatrix::buffer::banner_message_t965 void resolve_glyph() {
966 glyphs.clear();
967 this->min_width = 0;
968 for (char32_t c: text) {
969 if (U'a' <= c && c <= U'z')
970 c = c - U'a' + U'A';
971
972 glyph_t g;
973 g.def = glyph_data(c);
974 g.h = 7;
975 g.w = g.def ? g.def->w : 5;
976 g.render_width = g.w + 1;
977
978 if (glyphs.size()) this->min_width++;
979 this->min_width += g.w;
980 glyphs.push_back(g);
981 }
982 }
983
adjust_widthcxxmatrix::buffer::banner_message_t984 void adjust_width(int cols) {
985 // Adjust rendering width
986 int rest = cols - this->min_width - 4;
987 this->render_width = this->min_width;
988 for (glyph_t& g: glyphs)
989 g.render_width = g.w + 1;
990 while (rest > 0) {
991 int min_progress = glyphs[0].render_width;
992 int min_progress_count = 0;
993 for (glyph_t const& g: glyphs) {
994 if (g.render_width < min_progress) {
995 min_progress = g.render_width;
996 min_progress_count = 1;
997 } else if (g.render_width == min_progress) {
998 min_progress_count++;
999 }
1000 }
1001
1002 if (min_progress >= s2banner_cell_width * 3 / 2) break;
1003 rest -= min_progress_count;
1004 if (rest < 0) break;
1005
1006 for (glyph_t& g: glyphs)
1007 if (g.render_width == min_progress) g.render_width++;
1008 this->render_width += min_progress_count;
1009 this->min_progress = min_progress;
1010 }
1011 }
1012 };
1013
1014 class banner_t {
1015 std::vector<banner_message_t> data;
1016
1017 public:
add_message(std::string const & msg)1018 void add_message(std::string const& msg) {
1019 banner_message_t message;
1020 message.set_text(msg.c_str());
1021 message.resolve_glyph();
1022 data.emplace_back(std::move(message));
1023 }
begin()1024 std::vector<banner_message_t>::iterator begin() { return data.begin(); }
end()1025 std::vector<banner_message_t>::iterator end() { return data.end(); }
1026
1027 public:
max_min_width() const1028 int max_min_width() const {
1029 int width = 0;
1030 for (auto const& message: data)
1031 width = std::max(width, message.min_width);
1032 return width;
1033 }
max_number_of_characters() const1034 int max_number_of_characters() const {
1035 std::size_t nchar = 0;
1036 for (auto const& message: data)
1037 nchar = std::max(nchar, message.text.size());
1038 return (int) nchar;
1039 }
1040 };
1041
1042 banner_t banner;
1043
s2banner_write_lettercxxmatrix::buffer1044 void s2banner_write_letter(int x0, int y0, glyph_t const& glyph, int type) {
1045 x0 += (glyph.render_width - 1 - glyph.w) / 2;
1046 for (int y = 0; y < glyph.h; y++) {
1047 if (y0 + y >= rows) continue;
1048 for (int x = 0; x < glyph.w; x++) {
1049 if (x0 + x >= cols) continue;
1050 if (glyph(x, y)) s2banner_set_char(x0, y0, x, y, type);
1051 }
1052 }
1053 }
1054
s2banner_write_caretcxxmatrix::buffer1055 void s2banner_write_caret(banner_message_t const& message, int x0, int y0, bool set, int type) {
1056 x0 += std::max(0, (message.min_progress - 1 - s2banner_cell_width) / 2);
1057 for (int y = 0; y < s2banner_cell_height; y++) {
1058 if (y0 + y >= rows) continue;
1059 for (int x = 0; x < s2banner_cell_width - 1; x++) {
1060 if (x0 + x >= cols) continue;
1061 s2banner_set_char(x0, y0, x, y, set ? type : 0);
1062 }
1063 }
1064 }
1065
s2banner_put_charcxxmatrix::buffer1066 void s2banner_put_char(int x0, int y0, int x, int y, int type, char32_t uchar) {
1067 if (type == 0) {
1068 cell_t& cell = layers[0].rcell(x0 + x, y0 + y);
1069 cell.c = ' ';
1070 } else if (type == 1) {
1071 cell_t& cell = layers[0].rcell(x0 + x, y0 + y);
1072 cell.c = uchar;
1073 cell.birth = now;
1074 cell.power = 1.0;
1075 cell.decay = 20;
1076 cell.flags = 0;
1077 } else if (type == 2) {
1078 s2banner_put_char(x0, y0, x, y, uchar, 1);
1079
1080 thread_t thread;
1081 thread.x = x0 + x;
1082 thread.y = y0 + y;
1083 thread.age = 0;
1084 thread.speed = s2banner_cell_height - y;
1085 if (thread.speed > 2) thread.speed += util::rand() % 3 - 1;
1086 thread.power = 2.0 / 3.0;
1087 thread.decay = 30;
1088 layers[1].add_thread(thread);
1089 }
1090 }
s2banner_set_charcxxmatrix::buffer1091 void s2banner_set_char(int x0, int y0, int x, int y, int type) {
1092 if (type == 0)
1093 s2banner_put_char(x0, y0, x, y, type, ' ');
1094 else
1095 s2banner_put_char(x0, y0, x, y, type, util::rand_char());
1096 }
1097
s2banner_add_threadcxxmatrix::buffer1098 void s2banner_add_thread(int ilayer, int interval) {
1099 if (now % (1 + interval / cols) == 0) {
1100 thread_t thread;
1101 thread.x = util::rand() % cols;
1102 thread.y = 0;
1103 thread.age = 0;
1104 thread.speed = 8;
1105 thread.power = 0.5;
1106 thread.decay = config::default_decay;
1107 layers[ilayer].add_thread(thread);
1108 }
1109 }
1110
s2banner_show_messagecxxmatrix::buffer1111 void s2banner_show_message(banner_message_t& message, int mode) {
1112 int nchar, display_width, display_height;
1113 switch (mode) {
1114 default:
1115 case 0:
1116 message.adjust_width(cols);
1117 nchar = message.glyphs.size();
1118 display_width = message.render_width + message.min_progress;
1119 display_height = message.glyphs[0].h;
1120 break;
1121 case 1:
1122 case 2:
1123 nchar = (int) message.text.size();
1124 display_width = mode * nchar;
1125 display_height = 1;
1126 break;
1127 }
1128
1129 // 最後に文字入力が起こった位置と時刻
1130 int input_index = -1;
1131 int input_time = 0;
1132
1133 int loop_max = s2banner_initial_input + nchar * 5 + 130;
1134 for (int loop = 0; loop <= loop_max; loop++) {
1135 int type = 1;
1136 if (loop == loop_max) type = 2;
1137
1138 int x0 = (cols - display_width) / 2, y0 = (rows - display_height) / 2;
1139 if (mode != 0 && util::rand() % 20 == 0)
1140 y0 += util::rand() % 7 - 3;
1141 for (int i = 0; i < nchar; i++) {
1142 if ((loop - s2banner_initial_input) / 5 <= i) break;
1143
1144 bool caret_moved = false;
1145 if (input_index < i) {
1146 input_index = i;
1147 input_time = loop;
1148 caret_moved = true;
1149 }
1150
1151 switch (mode) {
1152 case 0:
1153 {
1154 glyph_t const& g = message.glyphs[i];
1155 if (caret_moved)
1156 s2banner_write_caret(message, x0, y0, false, type);
1157 s2banner_write_letter(x0, y0, g, type);
1158 x0 += g.render_width;
1159 }
1160 break;
1161 default:
1162 {
1163 char32_t c = message.text[i];
1164 if (U'a' <= c && c <= U'z') c = c - U'a' + U'A';
1165 s2banner_put_char(x0, y0, 0, 0, type, c);
1166 }
1167 x0 += mode;
1168 break;
1169 }
1170 }
1171
1172 switch (mode) {
1173 case 0:
1174 // if (!((loop - input_time) / 25 & 1))
1175 // s2banner_write_caret(message, x0, y0, true);
1176 s2banner_write_caret(message, x0, y0, !((loop - input_time) / 25 & 1), type);
1177 //s2banner_write_caret(message, x0, y0, true);
1178 break;
1179 default:
1180 s2banner_put_char(x0, y0, 0, 0, type, U'\u2589');
1181 break;
1182 }
1183
1184 s2banner_add_thread(1, 2000);
1185 render_layers();
1186 next_frame();
1187 kreader.process();
1188 if (is_menu) return;
1189 }
1190 }
1191 public:
1192 public:
s2banner_add_messagecxxmatrix::buffer1193 void s2banner_add_message(std::string const& message) {
1194 banner.add_message(message);
1195 }
s2bannercxxmatrix::buffer1196 void s2banner() {
1197 // mode = 0: glyph を使って表示
1198 // mode = 1: 単純に文字を並べる
1199 // mode = 2: 1文字ずつ空白を空けて文字を並べる
1200 int mode = 1;
1201 if (banner.max_min_width() < cols) {
1202 mode = 0;
1203 } else if (banner.max_number_of_characters() * 2 < cols) {
1204 mode = 2;
1205 }
1206
1207 for (banner_message_t& message: banner)
1208 s2banner_show_message(message, mode);
1209 }
1210
1211 private:
1212 conway_t s4conway_board;
s4conway_framecxxmatrix::buffer1213 void s4conway_frame(double theta, double scal, double power) {
1214 s4conway_board.set_size(cols, rows);
1215 s4conway_board.set_transform(scal, theta);
1216 for (int y = 0; y < rows; y++) {
1217 for (int x = 0; x < cols; x++) {
1218 cell_t& cell = layers[2].rcell(x, y);
1219 switch (s4conway_board.get_pixel(x, y, power)) {
1220 case 1:
1221 cell.c = util::rand_char();
1222 cell.birth = now;
1223 cell.power = power;
1224 cell.decay = 100;
1225 cell.flags = cflag_disable_bold;
1226 break;
1227 case 2:
1228 cell.c = util::rand_char();
1229 cell.birth = now;
1230 cell.power = power * 0.2;
1231 cell.decay = 100;
1232 cell.flags = cflag_disable_bold;
1233 break;
1234 default:
1235 cell.c = ' ';
1236 break;
1237 }
1238 }
1239 }
1240 }
1241 public:
s4conwaycxxmatrix::buffer1242 void s4conway() {
1243 s4conway_board.initialize();
1244 double time = 0.0;
1245 double distance = 0.48;
1246
1247 std::uint32_t loop;
1248 for (loop = 0; loop < 2000; loop++) {
1249 distance += 1.0 * (loop > 1500 ? distance * 0.01 : 0.04);
1250 time += 0.005 * distance;
1251 s4conway_board.step(time);
1252 s4conway_frame(0.5 + loop * 0.01, 0.01 * distance, std::min(0.8, 3.0 / std::sqrt(distance)));
1253 render_layers();
1254 next_frame();
1255 kreader.process();
1256 if (is_menu) return;
1257 }
1258 }
1259
1260 private:
1261 mandelbrot_t s5mandel_data;
s5mandel_framecxxmatrix::buffer1262 void s5mandel_frame(double theta, double scale, double power_scale) {
1263 s5mandel_data.resize(cols, rows);
1264 s5mandel_data.update_frame(theta, scale);
1265 for (int y = 0; y < rows; y++) {
1266 for (int x = 0; x < cols; x++) {
1267 cell_t& cell = layers[1].rcell(x, y);
1268 double const power = s5mandel_data(x, y);
1269 if (power < 0.05) {
1270 cell.c = ' ';
1271 } else {
1272 cell.c = util::rand_char();
1273 cell.birth = now;
1274 cell.power = power * power_scale;
1275 cell.decay = 100;
1276 cell.flags = cflag_disable_bold;
1277 }
1278 }
1279 }
1280 }
1281
1282 public:
s5mandelcxxmatrix::buffer1283 void s5mandel() {
1284 set_twinkle(0.1);
1285
1286 double const scale0 = 1e-17, scaleN = 30.0 / std::min(cols, rows);
1287 std::uint32_t const nloop = 3000;
1288 double const mag1 = std::pow(scaleN / scale0, 1.0 / nloop);
1289
1290 double scale = scale0;
1291 double theta = 0.5;
1292 std::uint32_t loop;
1293 for (loop = 0; loop < nloop; loop++) {
1294 scale *= mag1;
1295 theta -= 0.01;
1296 s5mandel_frame(theta, scale, std::min(0.01 * loop, 1.0));
1297 render_layers();
1298 next_frame();
1299 kreader.process();
1300 if (is_menu) return;
1301 }
1302 for (loop = 0; loop < 100; loop++) {
1303 render_layers();
1304 next_frame();
1305 kreader.process();
1306 if (is_menu) return;
1307 }
1308
1309 set_twinkle(default_twinkle);
1310 }
1311
1312 private:
1313 static constexpr int menu_index_min = scene_number;
1314 static constexpr int menu_index_max = scene_exit;
1315 int menu_index = menu_index_min;
1316
menu_initializecxxmatrix::buffer1317 void menu_initialize() {
1318 is_menu = true;
1319 menu_index = menu_index_min;
1320 }
1321
menu_process_keycxxmatrix::buffer1322 void menu_process_key(key_t k) {
1323 switch (k) {
1324 case key_ctrl('p'):
1325 case 'k':
1326 case key_up:
1327 if (menu_index > menu_index_min)
1328 menu_index--;
1329 break;
1330 case key_ctrl('n'):
1331 case 'j':
1332 case key_down:
1333 if (menu_index < menu_index_max)
1334 menu_index++;
1335 break;
1336 case key_ctrl('m'):
1337 case key_ctrl('j'):
1338 is_menu = false;
1339 break;
1340 }
1341 }
menu_frame_draw_stringcxxmatrix::buffer1342 void menu_frame_draw_string(int y0, scene_t scene, const char* name) {
1343 std::size_t const len = std::strlen(name);
1344 int const progress = 2;
1345 int const x0 = (cols - len * progress) / 2;
1346 double const power = scene == menu_index ? 1.0 : 0.5;
1347 double const flags = scene == menu_index ? 0 : cflag_disable_bold;
1348 for (std::size_t i = 0; i < len; i++) {
1349 cell_t& cell = layers[0].rcell(x0 + i * progress, y0);
1350 cell.c = std::toupper(name[i]);
1351 cell.birth = now;
1352 cell.power = power;
1353 cell.decay = 20;
1354 cell.flags = flags;
1355 }
1356 }
1357 public:
show_menucxxmatrix::buffer1358 int show_menu() {
1359 while (is_menu) {
1360 int const line_height = std::clamp(rows / scene_count, 1, 3);
1361 int const y0 = (rows - scene_count * line_height) / 2;
1362 int i = 0;
1363 menu_frame_draw_string(y0 + i++ * line_height, scene_number , "Number falls");
1364 menu_frame_draw_string(y0 + i++ * line_height, scene_banner , "Banner");
1365 menu_frame_draw_string(y0 + i++ * line_height, scene_rain , "Matrix rain");
1366 menu_frame_draw_string(y0 + i++ * line_height, scene_conway , "Conway's Game of Life");
1367 menu_frame_draw_string(y0 + i++ * line_height, scene_mandelbrot , "Mandelbrot set");
1368 menu_frame_draw_string(y0 + i++ * line_height, scene_rain_forever, "Rain forever");
1369 menu_frame_draw_string(y0 + i++ * line_height, scene_exit , "Exit");
1370
1371 s2banner_add_thread(1, 5000);
1372 render_layers();
1373 next_frame();
1374 kreader.process();
1375 if (!is_menu) break;
1376 }
1377
1378 return menu_index;
1379 }
1380
1381 public:
scenecxxmatrix::buffer1382 void scene(scene_t s) {
1383 switch (s) {
1384 case scene_none:
1385 break;
1386 case scene_number:
1387 this->s1number();
1388 break;
1389 case scene_banner:
1390 this->s2banner();
1391 break;
1392 case scene_rain:
1393 this->s3rain(2800, buffer::s3rain_scroll_func_tanh);
1394 break;
1395 case scene_conway:
1396 this->s4conway();
1397 break;
1398 case scene_mandelbrot:
1399 this->s5mandel();
1400 break;
1401 case scene_rain_forever:
1402 this->s3rain(0, buffer::s3rain_scroll_func_const);
1403 break;
1404 case scene_exit:
1405 this->finalize();
1406 std::exit(0);
1407 case scene_loop:
1408 break;
1409 }
1410 }
1411 };
1412
1413 buffer buff;
1414
trapint(int sig)1415 void trapint(int sig) {
1416 buff.finalize();
1417 std::signal(sig, SIG_DFL);
1418 std::raise(sig);
1419 std::exit(128 + sig);
1420 }
trapwinch(int)1421 void trapwinch(int) {
1422 buff.notify_resize();
1423 }
traptstp(int sig)1424 void traptstp(int sig) {
1425 buff.term_leave();
1426 std::signal(sig, SIG_DFL);
1427 std::raise(sig);
1428 }
trapcont(int)1429 void trapcont(int) {
1430 buff.term_enter();
1431 buff.notify_resize();
1432 std::signal(SIGTSTP, traptstp);
1433 }
1434
1435 } /* end of namespace cxxmatrix */
1436
1437 using namespace cxxmatrix;
1438
1439 struct arguments {
1440 bool flag_error = false;
1441 bool flag_help = false;
1442
1443 public:
print_helparguments1444 void print_help(std::FILE* file) {
1445 std::fprintf(file,
1446 "cxxmatrix (C++ Matrix)\n"
1447 "usage: cxxmatrix [OPTIONS...] [[--] MESSAGE...]\n"
1448 "\n"
1449 "MESSAGE\n"
1450 " Add a message for 'banner' scene. When no messages are specified, a\n"
1451 " message \"C++ MATRIX\" will be used.\n"
1452 "\n"
1453 //------------------------------------------------------------------------------
1454 "OPTIONS\n"
1455 " --help Show help.\n"
1456 " -- The rest arguments are processed as MESSAGE.\n"
1457 " -m, --message=MESSAGE\n"
1458 " Add a message for 'banner' scene.\n"
1459 " -s, --scene=SCENE\n"
1460 " Add scenes. Comma separated list of 'number', 'banner', 'rain',\n"
1461 " 'conway', 'mandelbrot', 'rain-forever' and 'loop'.\n"
1462 " -c, --color=COLOR\n"
1463 " Set color. One of 'default', 'black', 'red', 'green', 'yellow',\n"
1464 " 'blue', 'magenta', 'cyan', 'white', and integer 0-255 (256 index\n"
1465 " color).\n"
1466 " --frame-rate=NUM\n"
1467 " Set the frame rate per second. A positive number less than or\n"
1468 " equal to 1000. The default is 25.\n"
1469 " --error-rate=NUM\n"
1470 " Set the factor for the rate of character changes. A\n"
1471 " non-negative number. The default is 1.0.\n"
1472 " --diffuse\n"
1473 " --no-diffuse\n"
1474 " Turn on/off the background-color effect. Turned on by default.\n"
1475 " --twinkle\n"
1476 " --no-twinkle\n"
1477 " Turn on/off the twinkling effect. Turned on by default.\n"
1478 " --preserve-background\n"
1479 " --no-preserve-background\n"
1480 " Preserve terminal background or not. Not preserve by default.\n"
1481 " --rain-density=NUM\n"
1482 " Set the factor for the density of rain drops. A positive\n"
1483 " number. The default is 1.0.\n"
1484 "\n"
1485 "Keyboard\n"
1486 " C-c (SIGINT) Quit\n"
1487 " C-z (SIGTSTP) Suspend\n"
1488 " C-m, RET Show menu\n"
1489 "\n"
1490 );
1491 }
1492 private:
1493 int argc;
1494 char** argv;
1495 int iarg;
1496 char const* arg;
get_optargarguments1497 char const* get_optarg(char c) {
1498 if (*arg) {
1499 char const* ret = arg;
1500 arg = "";
1501 return ret;
1502 } else if (iarg < argc) {
1503 return argv[iarg++];
1504 } else {
1505 std::fprintf(stderr, "cxxmatrix: missing option argument for '-%c'.", c);
1506 flag_error = true;
1507 return nullptr;
1508 }
1509 }
1510
1511 char const* longopt_arg;
is_longoptarguments1512 bool is_longopt(const char* name) {
1513 const char* p = arg;
1514 while (*name && *name == *p) p++, name++;
1515 if (*name) return false;
1516 if (*p == '=') {
1517 longopt_arg = p + 1;
1518 return true;
1519 } else if (*p == '\0') {
1520 longopt_arg = nullptr;
1521 return true;
1522 } else {
1523 return false;
1524 }
1525 }
get_longoptargarguments1526 char const* get_longoptarg() {
1527 if (longopt_arg) {
1528 return longopt_arg;
1529 } else if (iarg < argc) {
1530 return argv[iarg++];
1531 } else {
1532 std::fprintf(stderr, "cxxmatrix: missing option argument for \"--%s\"\n", arg);
1533 flag_error = true;
1534 return nullptr;
1535 }
1536 }
1537
1538 public:
1539 std::vector<std::string> messages;
1540 private:
push_messagearguments1541 void push_message(const char* message) {
1542 messages.push_back(message);
1543 }
1544
1545 public:
1546 std::vector<scene_t> scenes;
1547 private:
push_scenearguments1548 void push_scene(const char* scene) {
1549 std::vector<std::string_view> names = util::split(scene, ',');
1550 for (auto const& name: names) {
1551 if (name == "number") {
1552 scenes.push_back(scene_number);
1553 } else if (name == "banner") {
1554 scenes.push_back(scene_banner);
1555 } else if (name == "conway") {
1556 scenes.push_back(scene_conway);
1557 } else if (name == "rain") {
1558 scenes.push_back(scene_rain);
1559 } else if (name == "mandelbrot") {
1560 scenes.push_back(scene_mandelbrot);
1561 } else if (name == "loop") {
1562 if (scenes.empty()) {
1563 std::fprintf(stderr, "cxxmatrix: nothing to loop (-s loop)\n");
1564 flag_error = true;
1565 return;
1566 }
1567 scenes.push_back(scene_loop);
1568 } else if (name == "rain-forever") {
1569 scenes.push_back(scene_rain_forever);
1570 } else {
1571 std::fprintf(stderr, "cxxxmatrix: unknown value for scene (%.*s)\n", (int) name.size(), name.data());
1572 flag_error = true;
1573 }
1574 }
1575 }
1576
1577 public:
1578 byte color = 47;
1579 private:
set_colorarguments1580 void set_color(const char* color_name) {
1581 std::string_view view = color_name;
1582 if (view == "black") {
1583 this->color = 0;
1584 return;
1585 } else if (view == "red") {
1586 this->color = 1;
1587 return;
1588 } else if (view == "green") {
1589 this->color = 2;
1590 return;
1591 } else if (view == "yellow") {
1592 this->color = 3;
1593 return;
1594 } else if (view == "blue") {
1595 this->color = 4;
1596 return;
1597 } else if (view == "magenta") {
1598 this->color = 5;
1599 return;
1600 } else if (view == "cyan") {
1601 this->color = 6;
1602 return;
1603 } else if (view == "white") {
1604 this->color = 7;
1605 return;
1606 } else if (view == "default") {
1607 this->color = 47;
1608 return;
1609 } else if (std::isdigit(view[0])) {
1610 int const value = std::atoi(view.data());
1611 if (value < 256) {
1612 this->color = value;
1613 return;
1614 }
1615 }
1616
1617 std::fprintf(stderr, "cxxmatrix: invalid value for color (%s)\n", view.data());
1618 flag_error = true;
1619 }
1620
1621 public:
1622 bool flag_diffuse_enabled = true;
1623 bool flag_twinkle_enabled = true;
1624 bool flag_preserve_background = false;
1625 double frame_rate = 25;
1626 double error_rate = 1.0;
1627 double rain_density = 1.0;
1628 private:
set_frame_ratearguments1629 void set_frame_rate(const char* frame_rate_text) {
1630 if (std::isdigit(frame_rate_text[0])) {
1631 double const value = std::atof(frame_rate_text);
1632 if (0.0 < value && value <= 1000.0) {
1633 this->frame_rate = value;
1634 return;
1635 }
1636 }
1637
1638 std::fprintf(stderr, "cxxmatrix: the frame rate (%s) needs to be a positive number <= 1000.0.\n", frame_rate_text);
1639 flag_error = true;
1640 }
set_error_ratearguments1641 void set_error_rate(const char* error_rate_text) {
1642 if (std::isdigit(error_rate_text[0])) {
1643 double const value = std::atof(error_rate_text);
1644 if (0.0 <= value) {
1645 this->error_rate = value;
1646 return;
1647 }
1648 }
1649
1650 std::fprintf(stderr, "cxxmatrix: the error rate (%s) needs to be a non-negative number.\n", error_rate_text);
1651 flag_error = true;
1652 }
set_rain_densityarguments1653 void set_rain_density(const char* rain_density_text) {
1654 if (std::isdigit(rain_density_text[0])) {
1655 double const value = std::atof(rain_density_text);
1656 if (0.0 < value) {
1657 this->rain_density = value;
1658 return;
1659 }
1660 }
1661
1662 std::fprintf(stderr, "cxxmatrix: the rain density (%s) needs to be a positive number.\n", rain_density_text);
1663 flag_error = true;
1664 }
1665
1666 public:
processarguments1667 bool process(int argc, char** argv) {
1668 bool flag_literal = false;
1669 this->argc = argc;
1670 this->argv = argv;
1671 this->iarg = 1;
1672 while (iarg < argc) {
1673 arg = argv[iarg++];
1674 if (!flag_literal && arg[0] == '-') {
1675 if (arg[1] == '-') {
1676 arg += 2;
1677 if (!*arg) {
1678 flag_literal = true;
1679 } else if (is_longopt("help")) {
1680 flag_help = true;
1681 } else if (is_longopt("diffuse")) {
1682 flag_diffuse_enabled = true;
1683 } else if (is_longopt("no-diffuse")) {
1684 flag_diffuse_enabled = false;
1685 } else if (is_longopt("twinkle")) {
1686 flag_twinkle_enabled = true;
1687 } else if (is_longopt("no-twinkle")) {
1688 flag_twinkle_enabled = false;
1689 } else if (is_longopt("preserve-background")) {
1690 flag_preserve_background = true;
1691 } else if (is_longopt("no-preserve-background")) {
1692 flag_preserve_background = false;
1693 } else if (is_longopt("message")) {
1694 push_message(get_longoptarg());
1695 } else if (is_longopt("scene")) {
1696 push_scene(get_longoptarg());
1697 } else if (is_longopt("color")) {
1698 set_color(get_longoptarg());
1699 } else if (is_longopt("frame-rate")) {
1700 set_frame_rate(get_longoptarg());
1701 } else if (is_longopt("error-rate")) {
1702 set_error_rate(get_longoptarg());
1703 } else if (is_longopt("rain-density")) {
1704 set_rain_density(get_longoptarg());
1705 } else {
1706 std::fprintf(stderr, "cxxmatrix: unknown long option (--%s)\n", arg);
1707 flag_error = true;
1708 }
1709 } else {
1710 arg++;
1711 while (char const c = *arg++) {
1712 switch (c) {
1713 case 'm':
1714 if (char const* opt = get_optarg(c))
1715 push_message(opt);
1716 break;
1717 case 's':
1718 if (char const* opt = get_optarg(c))
1719 push_scene(opt);
1720 break;
1721 case 'c':
1722 if (char const* opt = get_optarg(c))
1723 set_color(opt);
1724 break;
1725 default:
1726 std::fprintf(stderr, "cxxmatrix: unknown option (-%c)\n", c);
1727 flag_error = true;
1728 break;
1729 }
1730 }
1731 }
1732 continue;
1733 }
1734 push_message(arg);
1735 }
1736 return !flag_error;
1737 }
argumentsarguments1738 arguments(int argc, char** argv) {
1739 this->process(argc, argv);
1740 }
1741 };
1742
main(int argc,char ** argv)1743 int main(int argc, char** argv) {
1744 arguments args(argc, argv);
1745 if (args.flag_error) return 2;
1746 if (args.flag_help) {
1747 args.print_help(stdout);
1748 return 0;
1749 }
1750
1751 if (args.scenes.empty()) {
1752 args.scenes.push_back(scene_number);
1753 args.scenes.push_back(scene_banner);
1754 args.scenes.push_back(scene_rain);
1755 args.scenes.push_back(scene_conway);
1756 args.scenes.push_back(scene_mandelbrot);
1757 args.scenes.push_back(scene_rain_forever);
1758 }
1759 if (args.messages.size()) {
1760 for (std::string const& msg: args.messages)
1761 buff.s2banner_add_message(msg);
1762 } else {
1763 buff.s2banner_add_message("C++ Matrix");
1764 }
1765 buff.initialize_color_table(args.color);
1766 buff.set_frame_rate(args.frame_rate);
1767 buff.set_error_rate(args.error_rate);
1768 buff.set_diffuse_enabled(args.flag_diffuse_enabled);
1769 buff.set_twinkle_enabled(args.flag_twinkle_enabled);
1770 buff.set_preserve_background(args.flag_preserve_background);
1771 buff.set_rain_density(args.rain_density);
1772
1773 std::signal(SIGINT, trapint);
1774 std::signal(SIGWINCH, trapwinch);
1775 std::signal(SIGTSTP, traptstp);
1776 std::signal(SIGCONT, trapcont);
1777
1778 buff.initialize();
1779 buff.term_enter();
1780 std::size_t index = 0;
1781 while (index < args.scenes.size()) {
1782 scene_t const scene = args.scenes[index++];
1783 switch (scene) {
1784 case scene_none: break;
1785 case scene_loop: index = 0; break;
1786 default:
1787 buff.scene(scene);
1788 break;
1789 }
1790
1791 if (buff.is_menu) break;
1792 }
1793
1794 if (buff.is_menu) {
1795 for (;;) {
1796 buff.is_menu = true;
1797 scene_t const scene = (scene_t) buff.show_menu();
1798 buff.scene(scene);
1799 }
1800 }
1801
1802 buff.finalize();
1803 return 0;
1804 }
1805