shortestpath 0.10.0

Shortest Path is an experimental library finding the shortest path from A to B.
Documentation
// Copyright (C) 2025 Christian Mauduit <ufoot@ufoot.org>

//! String representation utilities for sources.
//!
//! This module provides functions to convert `Source2D` and `Source3D` objects
//! into human-readable ASCII art strings, useful for debugging, testing, and
//! visualization.

use super::{CellType, Source2D, Source3D};

/// Represents a 2D source as a string.
///
/// Converts a `Source2D` into ASCII art where:
/// - `.` represents free/walkable cells
/// - `#` represents wall/blocked cells
/// - `?` represents cells that returned an error
///
/// Each row ends with a newline character.
///
/// # Example
///
/// ```
/// use shortestpath::mesh_source::{Source2DFromText, repr_source_2d};
///
/// let source = Source2DFromText::from_text("...\n.#.\n...");
/// let repr = repr_source_2d(&source);
/// assert_eq!(repr, "...\n.#.\n...\n");
/// ```
pub fn repr_source_2d(source: &impl Source2D) -> String {
    let mut output = String::new();
    for y in 0..source.height() {
        for x in 0..source.width() {
            let c = match source.get(x, y) {
                Ok(CellType::FLOOR) => '.',
                Ok(CellType::WALL) => '#',
                Err(_) => '?',
            };
            output.push(c);
        }
        output.push('\n');
    }
    output
}

/// Represents a 3D source as a string.
///
/// Converts a `Source3D` into ASCII art where:
/// - `.` represents free/walkable cells
/// - `#` represents wall/blocked cells
/// - `?` represents cells that returned an error
/// - `----` separates layers (z-levels)
///
/// Each row ends with a newline, and each layer is followed by "----\n".
///
/// # Example
///
/// ```
/// use shortestpath::mesh_source::{Source3DFromText, repr_source_3d};
///
/// let source = Source3DFromText::from_text("...\n.#.\n---\n...\n...");
/// let repr = repr_source_3d(&source);
/// // repr contains both layers separated by "----\n"
/// ```
pub fn repr_source_3d(source: &impl Source3D) -> String {
    let mut output = String::new();
    for z in 0..source.depth() {
        for y in 0..source.height() {
            for x in 0..source.width() {
                let c = match source.get(x, y, z) {
                    Ok(CellType::FLOOR) => '.',
                    Ok(CellType::WALL) => '#',
                    Err(_) => '?',
                };
                output.push(c);
            }
            output.push('\n');
        }
        output.push_str("----\n");
    }
    output
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::mesh_source::{Source2DFromText, Source3DFromText};

    #[test]
    fn test_repr_source_2d() {
        let source = Source2DFromText::from_text("...\n.#.\n...");
        let repr = repr_source_2d(&source);
        assert_eq!(repr, "...\n.#.\n...\n");
    }

    #[test]
    fn test_repr_source_3d() {
        let source = Source3DFromText::from_text("...\n.#.\n---\n...\n...");
        let repr = repr_source_3d(&source);
        assert_eq!(repr, "...\n.#.\n----\n...\n...\n----\n");
    }

    #[test]
    fn test_repr_source_2d_all_walls() {
        let source = Source2DFromText::from_text("###\n###");
        let repr = repr_source_2d(&source);
        assert_eq!(repr, "###\n###\n");
    }

    #[test]
    fn test_repr_source_2d_empty() {
        let source = Source2DFromText::from_text("");
        let repr = repr_source_2d(&source);
        assert_eq!(repr, "");
    }
}