use shortestpath::Gradient;
use crate::{Cursor, Team, World};
pub fn repr_world_mesh(world: &World) -> String {
let mesh = world.mesh();
let (width, height, _depth) = mesh.dimensions();
let mut output = String::new();
for y in 0..height {
for x in 0..width {
let c = if mesh.cell_at(x, y, 0).is_some() { '.' } else { '#' };
output.push(c);
}
output.push('\n');
}
output
}
pub fn repr_world_with_team_gradient(world: &World, team: &Team) -> String {
repr_world_with_gradient(world, team.gradient())
}
pub fn repr_world_with_gradient(world: &World, gradient: &Gradient) -> String {
let mesh = world.mesh();
let (width, height, _depth) = mesh.dimensions();
let mut output = String::new();
for y in 0..height {
for x in 0..width {
let c = match mesh.cell_at(x, y, 0) {
Some(idx) => {
let dist = gradient.get_distance(idx);
if dist.is_infinite() || dist > 1e10 {
'?'
} else {
let digit = (dist as usize) % 10;
char::from_digit(digit as u32, 10).unwrap_or('?')
}
}
None => '#',
};
output.push(c);
}
output.push('\n');
}
output
}
pub fn repr_world_armies(world: &World) -> String {
let (width, height, _depth) = world.dimensions();
let mut output = String::new();
for y in 0..height {
for x in 0..width {
let c = fighter_char_at(world, x, y, 0);
output.push(c);
}
output.push('\n');
}
output
}
pub fn repr_world_armies_and_cursors(world: &World) -> String {
let (width, height, _depth) = world.dimensions();
let mut output = String::new();
for y in 0..height {
for x in 0..width {
let c = if let Some(cursor_char) = cursor_char_at(world, x, y) {
cursor_char
} else {
fighter_char_at(world, x, y, 0)
};
output.push(c);
}
output.push('\n');
}
output
}
fn fighter_char_at(world: &World, x: usize, y: usize, z: usize) -> char {
if !world.is_walkable(x, y, z) {
return '#'; }
let Some(fighter_id) = world.grid().fighter_at(x, y, z) else {
return '.'; };
let Some(fighter) = world.armies().get_fighter(&fighter_id) else {
return '.'; };
let Some(team) = world.get_team(&fighter.team_id) else {
return '?'; };
let first_char = team.name().chars().next().unwrap_or('?');
if fighter.health > 0.5 {
first_char.to_ascii_uppercase()
} else {
first_char.to_ascii_lowercase()
}
}
fn latin_to_greek(c: char) -> char {
const GREEK_MAP: [char; 26] = [
'α', 'β', 'γ', 'δ', 'ε', 'φ', 'γ', 'η', 'ι', '?', 'κ', 'λ', 'μ', 'ν', 'ο', 'π', '?', 'ρ', 'σ', 'τ', 'υ', '?', 'ω', 'ξ', 'ψ', 'ζ', ];
let upper = c.to_ascii_uppercase();
if upper.is_ascii_uppercase() {
let index = (upper as u8 - b'A') as usize;
GREEK_MAP[index]
} else {
'?'
}
}
fn cursor_char_at(world: &World, cell_x: usize, cell_y: usize) -> Option<char> {
for (_, cursor) in world.cursors() {
let cursor_cell_x = cursor.x().floor() as usize;
let cursor_cell_y = cursor.y().floor() as usize;
if cursor_cell_x == cell_x && cursor_cell_y == cell_y {
if let Some(team) = world.get_team(&cursor.team_id()) {
let first_char = team.name().chars().next().unwrap_or('?');
return Some(latin_to_greek(first_char));
}
return Some('?');
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
use crate::quad_mesh::QuadMesh;
use crate::{BLUE, RED};
fn create_test_world(width: usize, height: usize) -> World {
let gradient_mesh = QuadMesh::new(width, height, 1, |_, _, _| true).unwrap();
World::new(gradient_mesh)
}
fn create_test_world_with_walls<F: Fn(usize, usize, usize) -> bool>(
width: usize,
height: usize,
is_passable: F,
) -> World {
let gradient_mesh = QuadMesh::new(width, height, 1, &is_passable).unwrap();
World::new(gradient_mesh)
}
#[test]
fn test_repr_world_mesh_full() {
let world = create_test_world(5, 3);
let repr = repr_world_mesh(&world);
assert_eq!(repr, ".....\n.....\n.....\n");
}
#[test]
fn test_repr_world_mesh_with_walls() {
let world = create_test_world_with_walls(3, 3, |x, y, _| !(x == 1 && (y == 0 || y == 2)));
let repr = repr_world_mesh(&world);
assert_eq!(repr, ".#.\n...\n.#.\n");
}
#[test]
fn test_repr_world_with_team_gradient_uncomputed() {
let mut world = create_test_world(3, 3);
let team_id = world.add_team(RED, "Red".to_string());
let team = world.get_team(&team_id).unwrap();
let repr = repr_world_with_team_gradient(&world, team);
assert_eq!(repr, "???\n???\n???\n");
}
#[test]
fn test_repr_world_armies_empty() {
let world = create_test_world(3, 3);
let repr = repr_world_armies(&world);
assert_eq!(repr, "...\n...\n...\n");
}
#[test]
fn test_repr_world_armies_with_fighter() {
let mut world = create_test_world(3, 3);
let team_id = world.add_team(RED, "Red".to_string());
world.spawn_fighter(team_id, 0, 0, 0).expect("Failed to spawn fighter");
let repr = repr_world_armies(&world);
assert!(repr.starts_with('R'), "Expected 'R' at start, got: {}", repr);
}
#[test]
fn test_repr_world_armies_multiple_teams() {
let mut world = create_test_world(5, 1);
let red_id = world.add_team(RED, "Red".to_string());
let blue_id = world.add_team(BLUE, "Blue".to_string());
world.spawn_fighter(red_id, 0, 0, 0).expect("Failed to spawn red");
world.spawn_fighter(blue_id, 3, 0, 0).expect("Failed to spawn blue");
let repr = repr_world_armies(&world);
assert_eq!(repr, "R..B.\n");
}
#[test]
fn test_repr_world_armies_health_affects_case() {
let mut world = create_test_world(3, 1);
let team_id = world.add_team(RED, "Red".to_string());
let healthy_id = world.spawn_fighter(team_id, 0, 0, 0).expect("Failed to spawn");
let weak_id = world.spawn_fighter(team_id, 2, 0, 0).expect("Failed to spawn");
world.armies_mut().get_fighter_mut(&healthy_id).unwrap().health = 0.8;
world.armies_mut().get_fighter_mut(&weak_id).unwrap().health = 0.3;
let repr = repr_world_armies(&world);
assert_eq!(repr, "R.r\n");
}
#[test]
fn test_latin_to_greek_mapping() {
assert_eq!(latin_to_greek('A'), 'α');
assert_eq!(latin_to_greek('B'), 'β');
assert_eq!(latin_to_greek('G'), 'γ');
assert_eq!(latin_to_greek('R'), 'ρ');
assert_eq!(latin_to_greek('S'), 'σ');
assert_eq!(latin_to_greek('X'), 'ξ');
assert_eq!(latin_to_greek('Y'), 'ψ');
assert_eq!(latin_to_greek('Z'), 'ζ');
assert_eq!(latin_to_greek('J'), '?');
assert_eq!(latin_to_greek('Q'), '?');
assert_eq!(latin_to_greek('V'), '?');
assert_eq!(latin_to_greek('a'), 'α');
assert_eq!(latin_to_greek('b'), 'β');
assert_eq!(latin_to_greek('1'), '?');
}
#[test]
fn test_repr_world_armies_and_cursors_with_cursor() {
let mut world = create_test_world(5, 5);
let team_id = world.add_team(RED, "Red".to_string());
world.add_cursor(team_id);
let repr = repr_world_armies_and_cursors(&world);
assert!(repr.contains('ρ'), "Expected ρ in repr: {}", repr);
}
#[test]
fn test_repr_world_armies_and_cursors_multiple_teams() {
let mut world = create_test_world(10, 10);
let red_id = world.add_team(RED, "Red".to_string());
let blue_id = world.add_team(BLUE, "Blue".to_string());
world.add_cursor(red_id);
world.add_cursor(blue_id);
let repr = repr_world_armies_and_cursors(&world);
assert!(repr.contains('ρ'), "Expected ρ (Red) in repr: {}", repr);
assert!(repr.contains('β'), "Expected β (Blue) in repr: {}", repr);
}
#[test]
fn test_repr_world_armies_and_cursors_no_cursors() {
let mut world = create_test_world(3, 3);
let team_id = world.add_team(RED, "Red".to_string());
world.spawn_fighter(team_id, 1, 1, 0).expect("Failed to spawn");
let repr = repr_world_armies_and_cursors(&world);
assert!(repr.contains('R'), "Expected R in repr: {}", repr);
assert!(!repr.contains('ρ'), "Should not contain cursor");
}
}