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