rust_life/
lib.rs

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}