1#![cfg_attr(all(test, feature = "unstable"), feature(test))]
2
3#[cfg(all(test, feature = "unstable"))]
4mod benchmarks;
5
6#[cfg(feature = "gui")]
7mod gui;
8
9mod args;
10mod board;
11
12use args::{Alignment, Args, parse_args};
13use board::Board;
14use std::time::{Duration, Instant};
15
16#[cfg(all(feature = "test_mainthread", feature = "gui"))]
17pub use gui::test_helper::EXAMPLES;
18
19pub const CLEAR: &str = "\x1b[H\x1b[2J";
20
21pub fn run() {
22 let args = parse_args();
23 let cli_run_gens = args.generation_limit.or(args.generations.and(Some(0)));
24 let brd = make_board(&args);
25
26 #[cfg(feature = "gui")]
27 if args.no_gui {
28 cli(brd, args.ups, cli_run_gens);
29 } else {
30 gui::run(
31 brd,
32 args.scale,
33 args.ups,
34 args.generations.is_none() || args.generation_limit.is_some(),
35 args.generation_limit,
36 args.exit_on_finish,
37 );
38 }
39 #[cfg(not(feature = "gui"))]
40 cli(brd, args.ups, cli_run_gens);
41}
42
43fn make_board(args: &Args) -> Board {
44 let mut brd = if let Some(template) = &args.template {
45 let (top, right, bottom, left) = if let Some(padding) = &args.padding {
46 parse_padding(padding)
47 } else {
48 let vertical_padding = (args.rows - template.rows()) as isize;
49 let horizontal_padding = (args.cols - template.cols()) as isize;
50
51 alignment_padding(args.align, horizontal_padding, vertical_padding)
52 };
53
54 template.pad(top, right, bottom, left)
55 } else {
56 Board::new(args.rows, args.cols).random()
57 };
58
59 for _ in 0..args.generations.unwrap_or(0) {
60 brd = brd.next_generation();
61 }
62
63 brd
64}
65
66fn parse_padding(padding: &[isize]) -> (isize, isize, isize, isize) {
67 match *padding {
68 [x] => (x, x, x, x),
69 [vert, horiz] => (vert, horiz, vert, horiz),
70 [t, horiz, b] => (t, horiz, b, horiz),
71 [t, r, b, l] => (t, r, b, l),
72 ref err => unreachable!("bad value for padding: '{err:?}'"),
73 }
74}
75
76fn alignment_padding(
77 align: Alignment,
78 horizontal_padding: isize,
79 vertical_padding: isize,
80) -> (isize, isize, isize, isize) {
81 let (top, bottom) = match align {
82 Alignment::TopLeft | Alignment::Top | Alignment::TopRight => (0, vertical_padding),
83 Alignment::Left | Alignment::Center | Alignment::Right => (
84 vertical_padding / 2,
85 vertical_padding / 2 + vertical_padding % 2,
86 ),
87 Alignment::BottomLeft | Alignment::Bottom | Alignment::BottomRight => (vertical_padding, 0),
88 };
89 let (left, right) = match align {
90 Alignment::TopLeft | Alignment::Left | Alignment::BottomLeft => (0, horizontal_padding),
91 Alignment::Top | Alignment::Center | Alignment::Bottom => (
92 horizontal_padding / 2,
93 horizontal_padding / 2 + horizontal_padding % 2,
94 ),
95 Alignment::TopRight | Alignment::Right | Alignment::BottomRight => (horizontal_padding, 0),
96 };
97
98 (top, right, bottom, left)
99}
100
101fn cli(mut brd: Board, ups: u64, run_gens: Option<usize>) {
102 if run_gens == Some(0) {
103 println!("{brd}");
104 } else {
105 let frame_time: Duration = Duration::from_secs_f64(1.0 / ups as f64);
106 let mut frame_start;
107
108 while run_gens.is_none() || Some(brd.generation()) <= run_gens {
109 frame_start = Instant::now();
110 println!("{CLEAR}{brd}");
111 brd = brd.next_generation();
112 std::thread::sleep(
113 frame_time.saturating_sub(Instant::now().duration_since(frame_start)),
114 );
115 }
116 }
117}
118
119#[test]
120fn verify_cli() {
121 use clap::CommandFactory;
122 Args::command().debug_assert();
123}
124
125#[test]
126fn test_parse_padding() {
127 assert_eq!(parse_padding(&[1]), (1, 1, 1, 1));
128 assert_eq!(parse_padding(&[1, 2]), (1, 2, 1, 2));
129 assert_eq!(parse_padding(&[1, 2, 3]), (1, 2, 3, 2));
130 assert_eq!(parse_padding(&[1, 2, 3, 4]), (1, 2, 3, 4));
131}
132
133#[test]
134#[should_panic = "bad value for padding: '[]'"]
135fn test_parse_padding_invalid() {
136 parse_padding(&[]);
137}
138
139#[test]
140#[should_panic = "bad value for padding: '[1, 2, 3, 4, 5]'"]
141fn test_parse_padding_invalid_2() {
142 parse_padding(&[1, 2, 3, 4, 5]);
143}
144
145#[test]
146fn test_alignment_padding() {
147 assert_eq!(alignment_padding(Alignment::Top, 2, 2), (0, 1, 2, 1));
148 assert_eq!(alignment_padding(Alignment::TopLeft, 2, 2), (0, 2, 2, 0));
149 assert_eq!(alignment_padding(Alignment::TopRight, 2, 2), (0, 0, 2, 2));
150 assert_eq!(alignment_padding(Alignment::Center, 2, 2), (1, 1, 1, 1));
151 assert_eq!(alignment_padding(Alignment::Left, 2, 2), (1, 2, 1, 0));
152 assert_eq!(alignment_padding(Alignment::Right, 2, 2), (1, 0, 1, 2));
153 assert_eq!(alignment_padding(Alignment::Bottom, 2, 2), (2, 1, 0, 1));
154 assert_eq!(alignment_padding(Alignment::BottomLeft, 2, 2), (2, 2, 0, 0));
155 assert_eq!(
156 alignment_padding(Alignment::BottomRight, 2, 2),
157 (2, 0, 0, 2)
158 );
159}