rusty-train 0.1.0

A graphical user interface for constructing 18xx maps and identifying optimal train routes.
//! Read collections of tiles from JSON files and draw them as PNG files.
//!
//! Run this as an example to draw arbitrary tile collections:
//!
//!     cargo run --example draw_tiles -- --help
//!
//! Run this as a test to draw the tile collections generated by the
//! save_tiles example:
//!
//!     cargo test --example draw_tiles
//!
use navig18xx::prelude::*;

mod output;

type Result = std::result::Result<(), Box<dyn std::error::Error>>;

#[test]
fn test_draw_tiles() -> Result {
    let json_dir = output::Dir::Examples;
    let output_dir = output::Dir::Examples;

    let try_files = vec![
        ("tile_catalogue.json", 8, 16, Orientation::FlatTop),
        ("tile_1830.json", 8, 13, Orientation::PointedTop),
        ("tile_1861.json", 8, 16, Orientation::FlatTop),
        ("tile_1867.json", 8, 16, Orientation::FlatTop),
    ];
    for (basename, rows, cols, orientation) in &try_files {
        let json_file = json_dir.join(basename);
        if !json_file.exists() {
            println!("{} not found", json_file.to_str().unwrap());
            continue;
        }

        let png_basename =
            std::path::Path::new(basename).with_extension("png");
        let png_file = output_dir.join(png_basename);
        draw_tiles(&json_file, png_file, *rows, *cols, *orientation)?;
    }

    Ok(())
}

fn main() -> Result {
    let output_dir = output::Dir::Root;

    let mut rows: usize = 6;
    let mut cols: usize = 14;
    let mut orientation = Orientation::FlatTop;
    let mut json_files: Vec<String> = vec![];

    // Skip the first argument, which is typically the path to this
    // executable, but could conceivably contain anything.
    let mut args = std::env::args();
    args.next();

    while let Some(arg) = args.next() {
        match arg.as_str() {
            "-h" | "--help" => {
                print_usage();
                return Ok(());
            }
            "-r" => {
                if let Some(row_str) = args.next() {
                    rows = row_str.parse::<usize>()?
                } else {
                    panic!("Missing argument for {}", arg)
                }
            }
            "-c" => {
                if let Some(row_str) = args.next() {
                    cols = row_str.parse::<usize>()?
                } else {
                    panic!("Missing argument for {}", arg)
                }
            }
            "-f" => {
                orientation = Orientation::FlatTop;
            }
            "-p" => {
                orientation = Orientation::PointedTop;
            }
            _ => json_files.push(arg),
        }
    }

    if json_files.is_empty() {
        println!("ERROR: No input files given");
        print_usage();
        return Ok(());
    }

    for json_file in &json_files {
        let png_basename =
            std::path::Path::new(json_file).with_extension("png");
        let png_file = output_dir.join(png_basename);

        draw_tiles(json_file, png_file, rows, cols, orientation)?;
    }

    Ok(())
}

fn draw_tiles<P: AsRef<std::path::Path>>(
    json_file: P,
    png_file: std::path::PathBuf,
    rows: usize,
    cols: usize,
    orientation: Orientation,
) -> Result {
    let hex_max_diameter = 125.0;
    let mut hex = Hex::new(hex_max_diameter);
    hex.set_orientation(orientation);
    let margin = 10;
    let bg_rgba = Some(Colour::WHITE);

    let json_str = json_file.as_ref().to_str().unwrap();
    println!("Reading {} ...", json_str);
    let tiles = read_tiles(json_file)?;

    let example = place_tiles(hex, &tiles, rows, cols);
    example.draw_map();

    println!("Writing {} ...", png_file.to_str().unwrap());
    example.write_png(margin, bg_rgba, png_file);
    Ok(())
}

fn print_usage() {
    println!();
    println!("draw_tiles [-c COLS] [-r ROWS] [-f|-p] JSON_FILES");
    println!();
    println!("    -c COLS       The number of tile columns");
    println!("    -r ROWS       The number of tile rows");
    println!("    -f            Orient tiles so the top is flat (default)");
    println!("    -p            Orient tiles so the top is pointed");
    println!("    JSON_FILES    The tile JSON file(s) to draw");
    println!();
}

fn place_tiles(
    hex: Hex,
    tiles: &[Tile],
    rows: usize,
    cols: usize,
) -> Example {
    // Build an iterator over tile names for the tile catalogue.
    let tile_names = tiles.iter().map(|t| &t.name).cycle();

    let coords = Coordinates {
        orientation: hex.orientation(),
        letters: Letters::AsColumns,
        first_row: FirstRow::OddColumns,
    };

    // Build an iterator over the map hexes.
    let mut tile_addrs: Vec<String> = vec![];
    let rows: Vec<isize> = (0..rows as isize).collect();
    let cols: Vec<isize> = (0..cols as isize).collect();
    for row in &rows {
        for col in &cols {
            let addr = HexAddress::new(*row, *col);
            let addr_string = coords.format(&addr).unwrap();
            tile_addrs.push(addr_string)
        }
    }

    // Combine the two iterators to place a tile at each map hex.
    let placed_tiles: Vec<_> = tile_addrs
        .iter()
        .zip(tile_names)
        .map(|(addr, name)| tile_at(name, addr))
        .collect();

    let tokens: Vec<(String, _)> = vec![];

    Example::new_catalogue(hex, tokens, placed_tiles, tiles.to_vec(), coords)
}