use super::types::ShapeType;
#[derive(Debug, Clone)]
pub struct ShapeAsciiArt {
lines: Vec<&'static str>,
width: u16,
height: u16,
}
impl ShapeAsciiArt {
fn new(lines: Vec<&'static str>) -> Self {
let height = lines.len() as u16;
let width = lines.iter().map(|l| l.chars().count()).max().unwrap_or(0) as u16;
Self {
lines,
width,
height,
}
}
pub fn lines(&self) -> &[&'static str] {
&self.lines
}
pub fn width(&self) -> u16 {
self.width
}
pub fn height(&self) -> u16 {
self.height
}
pub fn char_at(&self, x: u16, y: u16) -> Option<char> {
self.lines
.get(y as usize)
.and_then(|line| line.chars().nth(x as usize))
.filter(|&c| c != ' ')
}
pub fn chars_with_positions(&self) -> impl Iterator<Item = (u16, u16, char)> + '_ {
self.lines.iter().enumerate().flat_map(|(y, line)| {
line.chars()
.enumerate()
.filter(|(_, c)| *c != ' ')
.map(move |(x, c)| (x as u16, y as u16, c))
})
}
pub fn collision_vertices(&self, shape_type: ShapeType) -> Vec<(f32, f32)> {
match shape_type {
ShapeType::Circle => {
let radius = 3.5;
(0..12)
.map(|i| {
let angle = (i as f32) * std::f32::consts::PI * 2.0 / 12.0;
(radius * angle.cos(), radius * angle.sin())
})
.collect()
}
ShapeType::Triangle => {
vec![
(0.0, 2.5), (-3.5, -2.5), (3.5, -2.5), ]
}
ShapeType::Square => {
vec![
(-3.5, 2.5), (-3.5, -2.5), (3.5, -2.5), (3.5, 2.5), ]
}
ShapeType::Star => {
let outer_radius = 3.5;
let inner_radius = 1.5;
(0..10)
.map(|i| {
let r = if i % 2 == 0 {
outer_radius
} else {
inner_radius
};
let angle =
std::f32::consts::PI / 2.0 - (i as f32) * std::f32::consts::PI / 5.0;
(r * angle.cos(), r * angle.sin())
})
.collect()
}
ShapeType::LineStraight => {
vec![
(-2.5, 1.0), (-2.5, -1.0), (2.5, -1.0), (2.5, 1.0), ]
}
ShapeType::LineVertical => {
vec![
(-1.0, 2.5), (-1.0, -2.5), (1.0, -2.5), (1.0, 2.5), ]
}
}
}
}
pub fn get_ascii_art(shape_type: ShapeType) -> ShapeAsciiArt {
match shape_type {
ShapeType::Circle => ShapeAsciiArt::new(vec![
" ### ", " ##### ", "#######", "#######", "#######", " ##### ", " ### ",
]),
ShapeType::Triangle => ShapeAsciiArt::new(vec![
" ^ ", " /X\\ ", " /XXX\\ ", "/XXXXX\\", "+-----+",
]),
ShapeType::Square => {
ShapeAsciiArt::new(vec!["+-----+", "|XXXXX|", "|XXXXX|", "|XXXXX|", "+-----+"])
}
ShapeType::Star => ShapeAsciiArt::new(vec![
" X ", " XXX ", "XXXXXXX", " XXXXX ", " XX XX ", "XX XX",
]),
ShapeType::LineStraight => ShapeAsciiArt::new(vec!["#####", "#####"]),
ShapeType::LineVertical => ShapeAsciiArt::new(vec!["##", "##", "##", "##", "##"]),
}
}
pub fn get_rotated_ascii_art(shape_type: ShapeType, rotation_degrees: i32) -> ShapeAsciiArt {
let rotation = rotation_degrees.rem_euclid(360);
match shape_type {
ShapeType::Circle => get_ascii_art(shape_type),
ShapeType::Square => get_ascii_art(shape_type),
ShapeType::Triangle => match rotation {
90 => ShapeAsciiArt::new(vec!["+--\\ ", "|XXX> ", "|XXXX>", "|XXX> ", "+--/ "]),
180 => ShapeAsciiArt::new(vec![
"+-----+", "\\XXXXX/", " \\XXX/ ", " \\X/ ", " v ",
]),
270 => ShapeAsciiArt::new(vec![" /--+", " <XXX|", "<XXXX|", " <XXX|", " \\--+"]),
_ => get_ascii_art(shape_type),
},
ShapeType::Star => match rotation {
90 => ShapeAsciiArt::new(vec![
"XX ", " XX X ", " XXXXX", " XXXXX ", " XX X ", "XX ",
]),
180 => ShapeAsciiArt::new(vec![
"XX XX", " XX XX ", " XXXXX ", "XXXXXXX", " XXX ", " X ",
]),
270 => ShapeAsciiArt::new(vec![
" XX", " X XX ", "XXXXX ", " XXXXX ", " X XX ", " XX",
]),
_ => get_ascii_art(shape_type),
},
ShapeType::LineStraight => match rotation {
90 | 270 => get_ascii_art(ShapeType::LineVertical),
_ => get_ascii_art(shape_type),
},
ShapeType::LineVertical => match rotation {
90 | 270 => get_ascii_art(ShapeType::LineStraight),
_ => get_ascii_art(shape_type),
},
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_circle_dimensions() {
let art = get_ascii_art(ShapeType::Circle);
assert_eq!(art.height(), 7, "Height: {}", art.height());
assert_eq!(art.width(), 7, "Width: {}", art.width());
}
#[test]
fn test_all_shapes_have_content() {
for shape_type in ShapeType::all() {
let art = get_ascii_art(shape_type);
assert!(
art.height() >= 2,
"{:?} height {} too small",
shape_type,
art.height()
);
assert!(
art.width() >= 2,
"{:?} width {} too small",
shape_type,
art.width()
);
let chars: Vec<_> = art.chars_with_positions().collect();
assert!(
!chars.is_empty(),
"{:?} has no visible characters",
shape_type
);
}
}
#[test]
fn test_chars_with_positions() {
let art = get_ascii_art(ShapeType::Square);
let chars: Vec<_> = art.chars_with_positions().collect();
assert!(!chars.is_empty());
assert!(chars.contains(&(0, 0, '+')));
}
#[test]
fn test_collision_vertices() {
let art = get_ascii_art(ShapeType::Square);
let vertices = art.collision_vertices(ShapeType::Square);
assert_eq!(vertices.len(), 4);
let triangle_vertices = art.collision_vertices(ShapeType::Triangle);
assert_eq!(triangle_vertices.len(), 3); }
}