amazeing 0.8.1

Amazeing is a maze generator/solver application with simulation/visualization.
use crate::maze::{Maze, MazeData, NodeFactory, UnitShape};
use std::fs;
use std::fs::OpenOptions;
use std::io::Write;
use std::path::Path;
use std::str::FromStr;

pub(crate) fn dump_maze_to_file(path: &Path, maze: &Maze) {
    if fs::exists(path).unwrap_or(false) {
        fs::remove_file(path).unwrap();
    }

    let mut file = OpenOptions::new()
        .write(true)
        .create(true)
        .truncate(true)
        .open(path)
        .expect("Could not open file");

    file.write_all(format!("{}\n", maze.unit_shape).as_bytes())
        .expect("Could not write to file");

    let node_factory = NodeFactory::new(maze.rows(), maze.cols());

    for r in 0..maze.rows() {
        for c in 0..maze.cols() {
            if let Some(node) = node_factory.at(r, c) {
                file.write_all(format!("{: >2}", maze[node]).as_bytes())
                    .expect("Could not write to file");
                if c < maze.cols() - 1 {
                    file.write_all(" ".as_bytes()).expect("Could not write to file");
                }
            }
        }
        if r < maze.rows() - 1 {
            file.write_all("\n".as_bytes()).expect("Could not write to file");
        }
    }
}

pub(crate) fn load_maze_from_file(path: &Path) -> Maze {
    if !fs::exists(path).unwrap() {
        panic!("Maze file {} does not exists", path.display());
    }

    let file_data = fs::read_to_string(path).expect("Could not read maze file");
    let mut lines = file_data.split("\n").collect::<Vec<&str>>();

    Maze::from(
        UnitShape::from_str(lines.remove(0)).unwrap_or_else(|e| panic!("{}", e)),
        lines
            .iter()
            .map(|line| {
                line.split(" ")
                    .filter(|&x| !x.is_empty())
                    .map(|unit| unit.trim().parse::<i8>().unwrap())
                    .collect::<Vec<i8>>()
            })
            .collect::<MazeData>(),
    )
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::maze::{Maze, NodeFactory, OPEN, UnitShape, VOID};

    fn temp_path(name: &str) -> std::path::PathBuf {
        let ts = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap()
            .as_nanos();
        std::env::temp_dir().join(format!("{}_{}.maze", name, ts))
    }

    #[test]
    fn dump_and_load_roundtrip_preserves_maze() {
        let mut maze = Maze::new(UnitShape::Square, 2, 3, VOID);
        let f = NodeFactory::new(2, 3);
        maze[f.at(0, 0).unwrap()] = OPEN;
        maze[f.at(1, 2).unwrap()] = OPEN;

        let path = temp_path("amazeing_roundtrip");
        dump_maze_to_file(&path, &maze);
        let loaded = load_maze_from_file(&path);

        assert_eq!(loaded.unit_shape, maze.unit_shape);
        assert_eq!(loaded.data, maze.data);

        let _ = fs::remove_file(path);
    }

    #[test]
    #[should_panic]
    fn load_panics_for_missing_file() {
        let path = temp_path("amazeing_missing");
        load_maze_from_file(&path);
    }
}