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
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}