gol 0.1.13

Conway's game of life in OpenGL.
Documentation
extern crate clap;
extern crate gol;

use clap::{App, Arg, ArgMatches};
use gol::data::Grid;
use gol::rendering;
use std::error::Error;
use std::fmt::Display;
use std::process::exit;
use std::str::FromStr;

fn main() {
    exit(match inner_main() {
        Ok(()) => 0,
        Err(err) => {
            println!("{err}");
            1
        }
    })
}

fn inner_main() -> Result<(), Box<dyn Error>> {
    let matches = App::new("Game of Life")
        .version(version().as_ref())
        .about("Conway's Game of Life in OpenGL!")
        .arg(
            Arg::with_name("grid-width")
                .short("w")
                .long("grid-width")
                .default_value("100")
                .help("Width of the grid"),
        )
        .arg(
            Arg::with_name("grid-height")
                .short("h")
                .long("grid-height")
                .default_value("80")
                .help("Height of the grid"),
        )
        .arg(
            Arg::with_name("window-width")
                .long("window-width")
                .default_value("1024")
                .help("Width of the window"),
        )
        .arg(
            Arg::with_name("window-height")
                .long("window-height")
                .default_value("768")
                .help("Height of the window"),
        )
        .arg(
            Arg::with_name("update-rate")
                .short("u")
                .long("update-rate")
                .default_value("30")
                .help("Number of updates to the game board per second"),
        )
        .get_matches();

    let grid_width = get_number("grid-width", Some(0), &matches);
    let grid_height = get_number("grid-height", Some(0), &matches);
    let window_width = get_number("window-width", Some(0), &matches);
    let window_height = get_number("window-height", Some(0), &matches);
    let updates_per_second = get_number("update-rate", None, &matches);

    let grid = Grid::new(grid_width, grid_height);
    let app = rendering::App::new(grid, window_width, window_height, updates_per_second);
    app?.run()
}

fn version() -> String {
    let (maj, min, pat) = (
        option_env!("CARGO_PKG_VERSION_MAJOR"),
        option_env!("CARGO_PKG_VERSION_MINOR"),
        option_env!("CARGO_PKG_VERSION_PATCH"),
    );
    match (maj, min, pat) {
        (Some(maj), Some(min), Some(pat)) => format!("{maj}.{min}.{pat}"),
        _ => String::new(),
    }
}

fn get_number<A>(name: &str, maybe_min: Option<A>, matches: &ArgMatches<'_>) -> A
where
    A: FromStr + PartialOrd + Display + Copy,
    <A as FromStr>::Err: std::fmt::Debug,
{
    matches
        .value_of(name)
        .and_then(|s| s.parse::<A>().ok())
        .and_then(|u| match maybe_min {
            Some(min) => {
                if u > min {
                    Some(u)
                } else {
                    None
                }
            }
            _ => Some(u),
        })
        .expect(
            &{
                if let Some(min) = maybe_min {
                    format!("{name} should be a positive number greater than {min}.")
                } else {
                    format!("{name} should be a positive number.")
                }
            }[..],
        )
}