movingai 2.2.0

MovingAI Benchmark Map/Scen File Parser
Documentation
#![allow(clippy::tabs_in_doc_comments)]
use crate::map2d::MovingAiMap;
use crate::map2d::SceneRecord;

/// Contains all the parser functions.
use std::fs::File;
use std::io;
use std::io::prelude::*;
use std::path;

/// Parse a MovingAI `.map` file.
///
/// # Arguments
///  * `path` represents the path to the file location.
///
/// # Returns
///  It returns the parsed map as a `MovingAiMap` or an `Err`.
///
/// # Panics
///  For the time, it panics if the map format it is not correct.
///  TODO: Catch all these errors and encode them into `Result`.
///
/// # Errors
///  Return errors if it is not possible to open the specified file.
///
/// # Examples
///
/// ```
/// use std::path::Path;
/// use movingai::parser::parse_map_file;
///
/// let map = parse_map_file(Path::new("./tests/arena.map")).unwrap();
/// ```
pub fn parse_map_file(path: &path::Path) -> io::Result<MovingAiMap> {
    let mut file = File::open(path)?;
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;

    parse_map(&contents)
}

/// Parse a string representing a MovingAI `.map`.
///
/// # Arguments
///  * `contents` a string in the `.map` format.
///
/// # Returns
///  It returns the parsed map as a `MovingAiMap` or an `Err`.
///
/// # Panics
///  For the time, it panics if the map format it is not correct.
///  TODO: Catch all these errors and encode them into `Result`.
///
/// # Errors
///  Return errors if it is not possible to open the specified file.
///
/// # Examples
///
/// ```
/// use std::path::Path;
/// use movingai::parser::parse_map;
///
/// let map = parse_map("type octile\nheight 1\nwidth 1\nmap\nT").unwrap();
/// ```
pub fn parse_map(contents: &str) -> io::Result<MovingAiMap> {
    let mut height: usize = 0;
    let mut width: usize = 0;
    let mut map_type = String::from("empty");
    let mut map: Vec<char> = Vec::new();

    let mut parse_map = false;
    for line in contents.lines() {
        if parse_map {
            map.extend(line.chars());
            continue;
        }
        if line.trim() == "map" {
            parse_map = true;
        } else {
            let mut parts = line.split_whitespace();
            if let (Some(key), Some(value)) = (parts.next(), parts.next()) {
                if parts.next().is_none() {
                    match key {
                        "type" => map_type = value.to_string(),
                        "height" => {
                            height = value.parse::<usize>().map_err(|_| {
                                io::Error::new(
                                    io::ErrorKind::InvalidData,
                                    "Error parsing map height.",
                                )
                            })?
                        }
                        "width" => {
                            width = value.parse::<usize>().map_err(|_| {
                                io::Error::new(
                                    io::ErrorKind::InvalidData,
                                    "Error parsing map width.",
                                )
                            })?
                        }
                        _ => {}
                    }
                }
            }
        }
    }

    if height == 0 {
        return Err(io::Error::new(
            io::ErrorKind::InvalidData,
            "Map height is missing or zero.",
        ));
    }
    if width == 0 {
        return Err(io::Error::new(
            io::ErrorKind::InvalidData,
            "Map width is missing or zero.",
        ));
    }

    MovingAiMap::new(map_type, height, width, map)
        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string()))
}

/// Parse a MovingAI `.scen` file.
///
/// # Arguments
///  * `path` represents the path to the file location.
///
/// # Returns
///  It returns the parsed map as a `Vec<SceneRecord>` or an `Err`.
///
/// # Panics
///  For the time, it panics if the map format it is not correct.
///
/// # Errors
///  Return errors if it is not possible to open the specified file.
///
/// # Examples
///
/// ```
/// use std::path::Path;
/// use movingai::parser::parse_scen_file;
///
/// let scen = parse_scen_file(Path::new("./tests/arena2.map.scen")).unwrap();
/// ```
pub fn parse_scen_file(path: &path::Path) -> io::Result<Vec<SceneRecord>> {
    let mut file = File::open(path)?;
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;

    parse_scen(&contents)
}

/// Parse a string representing a MovingAI `.scen`.
///
/// # Arguments
///  * `contents` the string representing the `.scen` file.
///
/// # Returns
///  It returns the parsed map as a `Vec<SceneRecord>` or an `Err`.
///
/// # Panics
///  For the time, it panics if the map format it is not correct.
///
/// # Errors
///  Return errors if it is not possible to open the specified file.
///
/// # Examples
///
/// ```
/// use std::path::Path;
/// use movingai::parser::parse_scen;
///
/// let scen = parse_scen("version 1\n0	maps/dao/arena.map	49	49	1	11	1	12	1").unwrap();
/// ```
pub fn parse_scen(contents: &str) -> io::Result<Vec<SceneRecord>> {
    let mut table: Vec<SceneRecord> = Vec::new();

    for line in contents.lines() {
        if line.starts_with("version") {
            continue;
        }
        if line.is_empty() {
            continue;
        }
        let record: Vec<&str> = line.split('\t').collect();
        if record.len() < 9 {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                format!("Expected 9 fields in scene record, found {}", record.len()),
            ));
        }
        table.push(SceneRecord {
            bucket: record[0].parse::<u32>().map_err(|_| {
                io::Error::new(io::ErrorKind::InvalidData, "Error parsing bucket size.")
            })?,
            map_file: String::from(record[1]),
            map_width: record[2].parse::<usize>().map_err(|_| {
                io::Error::new(io::ErrorKind::InvalidData, "Error parsing map width.")
            })?,
            map_height: record[3].parse::<usize>().map_err(|_| {
                io::Error::new(io::ErrorKind::InvalidData, "Error parsing map height.")
            })?,
            start_pos: (
                record[4].parse::<usize>().map_err(|_| {
                    io::Error::new(io::ErrorKind::InvalidData, "Error parsing start x.")
                })?,
                record[5].parse::<usize>().map_err(|_| {
                    io::Error::new(io::ErrorKind::InvalidData, "Error parsing start y.")
                })?,
            ),
            goal_pos: (
                record[6].parse::<usize>().map_err(|_| {
                    io::Error::new(io::ErrorKind::InvalidData, "Error parsing goal x")
                })?,
                record[7].parse::<usize>().map_err(|_| {
                    io::Error::new(io::ErrorKind::InvalidData, "Error parsing goal y")
                })?,
            ),
            optimal_length: record[8].parse::<f64>().map_err(|_| {
                io::Error::new(io::ErrorKind::InvalidData, "Error parsing optimal length.")
            })?,
        })
    }

    Ok(table)
}