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