use crate::{
buffer::{CellBuffer, Contacts, Span},
fragment,
fragment::{Arc, Circle},
Cell, Point, Settings,
};
use indexmap::IndexMap;
use once_cell::sync::Lazy;
use std::{
collections::{BTreeMap, HashMap},
iter::FromIterator,
};
pub const CIRCLES_TO_SKIP_FOR_ARC: usize = 3;
static CIRCLE_ART_MAP: Lazy<Vec<(&'static str, Horizontal, f32, f32, Cell)>> =
Lazy::new(|| {
vec![
(
r#"
()
"#,
Horizontal::Half,
1.0,
0.5,
Cell::new(0, 0),
),
(
r#"
(_)
"#,
Horizontal::Half,
1.5,
0.5,
Cell::new(1, 0),
),
(
r#"
__
(__)
"#,
Horizontal::Half,
2.0,
1.5,
Cell::new(1, 1),
),
(
r#"
,-.
( )
`-'
"#,
Horizontal::Half,
2.5,
1.5,
Cell::new(2, 1),
),
(
r#"
.--.
( )
`--'
"#,
Horizontal::Half,
3.0,
1.5,
Cell::new(2, 1),
),
(
r#"
_
.' '.
( )
`._.'
"#,
Horizontal::Half,
3.5,
2.5,
Cell::new(3, 2),
),
(
r#"
__
,' '.
( )
`.__.'
"#,
Horizontal::Half,
4.0,
2.5,
Cell::new(3, 2),
),
(
r#"
___
,' '.
( )
`. .'
`-'
"#,
Horizontal::Half,
4.5,
2.5,
Cell::new(4, 2),
),
(
r#"
___
,' `.
/ \
\ /
`.___.'
"#,
Horizontal::LeftEdge,
4.5,
3.0,
Cell::new(4, 2),
),
(
r#"
____
,' `.
/ \
\ /
`.____.'
"#,
Horizontal::LeftEdge,
5.0,
3.0,
Cell::new(4, 2),
),
(
r#"
____
.' `.
/ \
( )
\ /
`.____.'
"#,
Horizontal::Half,
6.0,
3.5,
Cell::new(5, 3),
),
(
r#"
_____
,' `.
/ \
( )
\ /
`._____.'
"#,
Horizontal::Half,
6.5,
3.5,
Cell::new(6, 3),
),
(
r#"
______
,' `.
/ \
| |
| |
\ /
`.______.'
"#,
Horizontal::Half,
7.0,
4.0,
Cell::new(6, 3),
),
(
r#"
_______
,' `.
/ \
| |
| |
\ /
`._______.'
"#,
Horizontal::Half,
7.5,
4.0,
Cell::new(7, 3),
),
(
r#"
________
,' `.
/ \
| |
| |
| |
\ /
`.________.'
"#,
Horizontal::Half,
8.0,
4.5,
Cell::new(7, 4),
),
(
r#"
__-----__
,' `.
/ \
| |
| |
| |
\ /
`. .'
`-------'
"#,
Horizontal::Half,
8.5,
4.5,
Cell::new(8, 4),
),
(
r#"
.--------.
,' `.
/ \
| |
| |
| |
\ /
`. .'
`--------'
"#,
Horizontal::Half,
9.0,
4.5,
Cell::new(8, 4),
),
(
r#"
_.-'''''-._
,' `.
/ \
. .
| |
| |
| |
\ /
`._ _.'
'-.....-'
"#,
Horizontal::Half,
9.5,
5.5,
Cell::new(9, 5),
),
(
r#"
_.-''''''-._
,' `.
/ \
. .
| |
| |
| |
\ /
`._ _.'
'-......-'
"#,
Horizontal::Half,
10.0,
5.5,
Cell::new(9, 5),
),
(
r#"
_.-'''''''-._
,' `.
/ \
. .
| |
| |
| |
\ /
`._ _.'
'-.......-'
"#,
Horizontal::Half,
10.5,
5.5,
Cell::new(10, 5),
),
(
r#"
_.-''''''''-._
,' `.
/ \
. .
| |
| |
| |
| |
\ /
`._ _.'
'-........-'
"#,
Horizontal::Half,
11.0,
5.5,
Cell::new(10, 5),
),
(
r#"
_.-'''''''''-._
,' `.
/ \
. .
| |
| |
| |
| |
\ /
`._ _.'
'-.........-'
"#,
Horizontal::Half,
11.5,
5.5,
Cell::new(11, 5),
),
]
});
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
pub enum Horizontal {
LeftEdge,
Half,
}
pub struct CircleArt {
ascii_art: &'static str,
start_edge: Horizontal,
offset_center_x: f32,
offset_center_y: f32,
}
impl CircleArt {
fn center_cell(&self) -> Cell {
let mut center_cell_x = self.offset_center_x - self.edge_increment_x();
if !self.is_shared_x() {
center_cell_x -= 0.5;
}
let mut center_cell_y = self.offset_center_y;
if !self.is_shared_y() {
center_cell_y -= 0.5;
}
Cell::new(center_cell_x.floor() as i32, center_cell_y.floor() as i32)
}
fn width(&self) -> f32 {
let cb = CellBuffer::from(self.ascii_art);
let (lo, hi) = cb.bounds().expect("circle must have bounds");
match self.start_edge {
Horizontal::LeftEdge => (hi.x - lo.x) as f32 + 1.0,
Horizontal::Half => (hi.x - lo.x) as f32,
}
}
fn center(&self) -> Point {
let center_x = self.radius() + self.edge_increment_x();
let center_y = self.offset_center_y * 2.0;
Point::new(center_x, center_y)
}
fn edge_increment_x(&self) -> f32 {
match self.start_edge {
Horizontal::LeftEdge => 0.0,
Horizontal::Half => 0.5,
}
}
fn radius(&self) -> f32 {
self.width() / 2.0
}
fn diameter(&self) -> i32 {
(self.radius() * 2.0).floor() as i32
}
fn is_shared_x(&self) -> bool {
self.offset_center_x * 2.0 % 2.0 == 1.0
}
fn is_shared_y(&self) -> bool {
self.offset_center_y * 2.0 % 2.0 == 1.0
}
}
pub struct ArcSpans {
diameter: i32,
arc_spans: Vec<(Arc, Span)>,
}
static CIRCLE_MAP: Lazy<Vec<CircleArt>> = Lazy::new(|| {
Vec::from_iter(CIRCLE_ART_MAP.iter().enumerate().map(
|(
ndx,
(art, edge_case, offset_center_x, offset_center_y, arc2_center),
)| {
CircleArt {
ascii_art: *art,
start_edge: *edge_case,
offset_center_x: *offset_center_x,
offset_center_y: *offset_center_y,
}
},
))
});
static FRAGMENTS_CIRCLE: Lazy<Vec<(Vec<Contacts>, Circle)>> = Lazy::new(|| {
Vec::from_iter(CIRCLE_MAP.iter().map(|circle_art| {
(
circle_art_to_group(circle_art.ascii_art),
Circle::new(circle_art.center(), circle_art.radius(), false),
)
}))
});
pub static DIAMETER_CIRCLE: Lazy<HashMap<i32, (Point, Span)>> =
Lazy::new(|| {
HashMap::from_iter(CIRCLE_MAP.iter().map(|circle_art| {
let cb = CellBuffer::from(circle_art.ascii_art);
let mut spans= Vec::<Span>::from(&cb);
assert_eq!(spans.len(), 1);
let span = spans.remove(0).localize();
(circle_art.diameter(), (circle_art.center(), span))
}))
});
pub static CIRCLES_SPAN: Lazy<IndexMap<Circle, Span>> = Lazy::new(|| {
IndexMap::from_iter(CIRCLE_MAP.iter().map(|circle_art| {
let cb = CellBuffer::from(circle_art.ascii_art);
let mut spans = Vec::<Span>::from(&cb);
assert_eq!(spans.len(), 1);
let span = spans.remove(0).localize();
(
Circle::new(circle_art.center(), circle_art.radius(), false),
span,
)
}))
});
pub static QUARTER_ARC_SPAN: Lazy<BTreeMap<i32, ArcSpans>> = Lazy::new(|| {
BTreeMap::from_iter(CIRCLE_MAP.iter().skip(CIRCLES_TO_SKIP_FOR_ARC).map(
|circle_art| {
let span = circle_art_to_span(circle_art.ascii_art);
let bounds = span.cell_bounds().expect("must have bounds");
let top_left = bounds.top_left();
let bottom_right = bounds.bottom_right();
let top_right = bounds.top_right();
let bottom_left = bounds.bottom_left();
let center = circle_art.center();
let radius = circle_art.radius();
let p1 = Point::new(center.x + radius, center.y);
let p2 = Point::new(center.x, center.y - radius);
let p3 = Point::new(center.x - radius, center.y);
let p4 = Point::new(center.x, center.y + radius);
let arc2_center = circle_art.center_cell();
let span1_center = Cell::new(
(center.x.floor() / Cell::width()) as i32,
arc2_center.y,
);
let span2_center = arc2_center;
let span3_center = Cell::new(
arc2_center.x,
(center.y.floor() / Cell::height()) as i32,
);
let span4_center = Cell::new(
(center.x.floor() / Cell::width()) as i32,
(center.y.floor() / Cell::height()) as i32,
);
let bounds1 = Cell::rearrange_bound(span1_center, top_right);
let bounds2 = Cell::rearrange_bound(top_left, span2_center);
let bounds3 = Cell::rearrange_bound(bottom_left, span3_center);
let bounds4 = Cell::rearrange_bound(span4_center, bottom_right);
let span1 = span.extract(bounds1.0, bounds1.1).localize();
let span2 = span.extract(bounds2.0, bounds2.1).localize();
let span3 = span.extract(bounds3.0, bounds3.1).localize();
let span4 = span.extract(bounds4.0, bounds4.1).localize();
let arc1_start = bounds1.0.localize_point(p1);
let arc1_end = bounds1.0.localize_point(p2);
let arc2_start = bounds2.0.localize_point(p2);
let arc2_end = bounds2.0.localize_point(p3);
let arc3_start = bounds3.0.localize_point(p3);
let arc3_end = bounds3.0.localize_point(p4);
let arc4_start = bounds4.0.localize_point(p4);
let arc4_end = bounds4.0.localize_point(p1);
let arc1 = Arc::new(arc1_start, arc1_end, radius);
let arc2 = Arc::new(arc2_start, arc2_end, radius);
let arc3 = Arc::new(arc3_start, arc3_end, radius);
let arc4 = Arc::new(arc4_start, arc4_end, radius);
let diameter = circle_art.diameter();
(
diameter,
ArcSpans {
diameter,
arc_spans: vec![
(arc1, span1),
(arc2, span2),
(arc3, span3),
(arc4, span4),
],
},
)
},
))
});
pub static HALF_ARC_SPAN: Lazy<BTreeMap<i32, ArcSpans>> = Lazy::new(|| {
BTreeMap::from_iter(CIRCLE_MAP.iter().skip(CIRCLES_TO_SKIP_FOR_ARC).map(
|circle_art| {
let span = circle_art_to_span(circle_art.ascii_art);
let bounds = span.cell_bounds().expect("must have bounds");
let top_left = bounds.top_left();
let bottom_right = bounds.bottom_right();
let top_right = bounds.top_right();
let bottom_left = bounds.bottom_left();
assert_eq!(top_left.y, top_right.y);
assert_eq!(top_left.x, bottom_left.x);
assert_eq!(top_right.x, bottom_right.x);
assert_eq!(bottom_left.y, bottom_right.y);
let center_cell = circle_art.center_cell();
let center = circle_art.center();
let radius = circle_art.radius();
let p1 = Point::new(center.x + radius, center.y);
let p2 = Point::new(center.x, center.y - radius);
let p3 = Point::new(center.x - radius, center.y);
let p4 = Point::new(center.x, center.y + radius);
let arc2_center = center_cell;
let span1_center = Cell::new(
(center.x.floor() / Cell::width()) as i32,
arc2_center.y,
);
let span2_center = arc2_center;
let span3_center = Cell::new(
arc2_center.x,
(center.y.floor() / Cell::height()) as i32,
);
let span4_center = Cell::new(
(center.x.floor() / Cell::width()) as i32,
(center.y.floor() / Cell::height()) as i32,
);
let top_tangent = Cell::new(top_right.x, span1_center.y);
let bottom_tangent = Cell::new(bottom_left.x, span3_center.y);
let left_tangent = Cell::new(span2_center.x, top_left.y);
let right_tangent = Cell::new(span1_center.x, top_right.y);
let bounds_top_half = Cell::rearrange_bound(top_left, top_tangent);
let bounds_bottom_half =
Cell::rearrange_bound(bottom_tangent, bottom_right);
let bounds_left_half =
Cell::rearrange_bound(left_tangent, bottom_left);
let bounds_right_half =
Cell::rearrange_bound(right_tangent, bottom_right);
let span_top_half = span
.extract(bounds_top_half.0, bounds_top_half.1)
.localize();
let span_bottom_half = span
.extract(bounds_bottom_half.0, bounds_bottom_half.1)
.localize();
let span_left_half = span
.extract(bounds_left_half.0, bounds_left_half.1)
.localize();
let span_right_half = span
.extract(bounds_right_half.0, bounds_right_half.1)
.localize();
let bottom_half_start = bounds_bottom_half.0.localize_point(p3);
let bottom_half_end = bounds_bottom_half.0.localize_point(p1);
let right_half_start = bounds_right_half.0.localize_point(p4);
let right_half_end = bounds_right_half.0.localize_point(p2);
let arc_top_half = Arc::new(p1, p3, radius);
let arc_bottom_half =
Arc::new(bottom_half_start, bottom_half_end, radius);
let arc_left_half = Arc::new(p2, p4, radius);
let arc_right_half =
Arc::new(right_half_start, right_half_end, radius);
let diameter = circle_art.diameter();
(
diameter,
ArcSpans {
diameter,
arc_spans: vec![
(arc_top_half, span_top_half),
(arc_bottom_half, span_bottom_half),
(arc_left_half, span_left_half),
(arc_right_half, span_right_half),
],
},
)
},
))
});
pub static THREE_QUARTERS_ARC_SPAN: Lazy<BTreeMap<i32, ArcSpans>> =
Lazy::new(|| {
BTreeMap::from_iter(
CIRCLE_MAP
.iter()
.skip(CIRCLES_TO_SKIP_FOR_ARC)
.map(|circle_art| {
let span = circle_art_to_span(circle_art.ascii_art);
let bounds = span.cell_bounds().expect("must have bounds");
let top_left = bounds.top_left();
let bottom_right = bounds.bottom_right();
let top_right = bounds.top_right();
let bottom_left = bounds.bottom_left();
let center = circle_art.center();
let radius = circle_art.radius();
let p1 = Point::new(center.x + radius, center.y);
let p2 = Point::new(center.x, center.y - radius);
let p3 = Point::new(center.x - radius, center.y);
let p4 = Point::new(center.x, center.y + radius);
let arc2_center = circle_art.center_cell();
let span1_center = Cell::new(
(center.x.floor() / Cell::width()) as i32,
arc2_center.y,
);
let span2_center = arc2_center;
let span3_center = Cell::new(
arc2_center.x,
(center.y.floor() / Cell::height()) as i32,
);
let span4_center = Cell::new(
(center.x.floor() / Cell::width()) as i32,
(center.y.floor() / Cell::height()) as i32,
);
let top_tangent = Cell::new(top_right.x, span1_center.y);
let bottom_tangent =
Cell::new(bottom_left.x, span3_center.y);
let left_tangent = Cell::new(span2_center.x, top_left.y);
let right_tangent = Cell::new(span1_center.x, top_right.y);
let bounds1 =
Cell::rearrange_bound(span1_center, top_right);
let bounds2 = Cell::rearrange_bound(top_left, span2_center);
let bounds3 =
Cell::rearrange_bound(bottom_left, span3_center);
let bounds4 =
Cell::rearrange_bound(span4_center, bottom_right);
let span1 = span.extract(bounds1.0, bounds1.1);
let span2 = span.extract(bounds2.0, bounds2.1);
let span3 = span.extract(bounds3.0, bounds3.1);
let span4 = span.extract(bounds4.0, bounds4.1);
let span_123 = span1
.merge_no_check(&span2)
.merge_no_check(&span3)
.localize();
let span_234 = span2
.merge_no_check(&span3)
.merge_no_check(&span4)
.localize();
let span_341 = span3
.merge_no_check(&span4)
.merge_no_check(&span1)
.localize();
let span_412 = span4
.merge_no_check(&span1)
.merge_no_check(&span2)
.localize();
let arc_123 = Arc::major(p1, p4, radius);
let arc_234 = Arc::major(p2, p1, radius);
let arc_341 = Arc::major(p3, p2, radius);
let arc_412 = Arc::major(p4, p3, radius);
let diameter = circle_art.diameter();
(
diameter,
ArcSpans {
diameter,
arc_spans: vec![
(arc_123, span_123),
(arc_234, span_234),
(arc_341, span_341),
(arc_412, span_412),
],
},
)
}),
)
});
pub static FLATTENED_QUARTER_ARC_SPAN: Lazy<
BTreeMap<DiameterArc, (Arc, Span)>,
> = Lazy::new(|| {
BTreeMap::from_iter(QUARTER_ARC_SPAN.iter().flat_map(
|(diameter, arc_spans)| {
arc_spans.arc_spans.iter().enumerate().map(
move |(arc_index, arc_span)| {
(
DiameterArc {
diameter: *diameter,
arc: arc_index,
},
arc_span.clone(),
)
},
)
},
))
});
pub static FLATTENED_HALF_ARC_SPAN: Lazy<BTreeMap<DiameterArc, (Arc, Span)>> =
Lazy::new(|| {
BTreeMap::from_iter(HALF_ARC_SPAN.iter().flat_map(
|(diameter, arc_spans)| {
arc_spans.arc_spans.iter().enumerate().map(
move |(arc_index, arc_span)| {
(
DiameterArc {
diameter: *diameter,
arc: arc_index,
},
arc_span.clone(),
)
},
)
},
))
});
pub static FLATTENED_THREE_QUARTERS_ARC_SPAN: Lazy<
BTreeMap<DiameterArc, (Arc, Span)>,
> = Lazy::new(|| {
BTreeMap::from_iter(THREE_QUARTERS_ARC_SPAN.iter().flat_map(
|(diameter, arc_spans)| {
arc_spans.arc_spans.iter().enumerate().map(
move |(arc_index, arc_span)| {
(
DiameterArc {
diameter: *diameter,
arc: arc_index,
},
arc_span.clone(),
)
},
)
},
))
});
#[derive(Default, Hash, PartialEq, Eq)]
pub struct DiameterArc {
diameter: i32,
arc: usize,
}
impl Ord for DiameterArc {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.diameter
.cmp(&other.diameter)
.then(self.arc.cmp(&other.arc))
}
}
impl PartialOrd for DiameterArc {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
fn circle_art_to_group(art: &str) -> Vec<Contacts> {
circle_art_to_span(art).into()
}
fn circle_art_to_span(art: &str) -> Span {
let cell_buffer = CellBuffer::from(art);
let mut spans = Vec::<Span>::from(&cell_buffer);
assert_eq!(spans.len(), 1);
spans.remove(0).localize()
}
pub fn endorse_circle_span(search: &Span) -> Option<(&Circle, Span)> {
CIRCLES_SPAN.iter().rev().find_map(|(circle, span)| {
let search_localized = search.clone().localize();
let (matched, unmatched) = is_subset_of(span, &search_localized);
if matched {
let unmatched_cell_chars = search
.iter()
.enumerate()
.filter_map(|(i, cell_char)| {
if unmatched.contains(&i) {
Some(*cell_char)
} else {
None
}
})
.collect::<Vec<_>>();
Some((circle, Span::from(unmatched_cell_chars)))
} else {
None
}
})
}
pub fn endorse_quarter_arc_span(search: &Span) -> Option<(&Arc, Span)> {
FLATTENED_QUARTER_ARC_SPAN.iter().rev().find_map(
|(_diameter, (arc, span))| {
let search_localized = search.clone().localize();
let (matched, unmatched) = is_subset_of(span, &search_localized);
if matched {
let unmatched_cell_chars = search
.iter()
.enumerate()
.filter_map(|(i, cell_char)| {
if unmatched.contains(&i) {
Some(*cell_char)
} else {
None
}
})
.collect::<Vec<_>>();
Some((arc, Span::from(unmatched_cell_chars)))
} else {
None
}
},
)
}
pub fn endorse_half_arc_span(search: &Span) -> Option<(&Arc, Span)> {
FLATTENED_HALF_ARC_SPAN
.iter()
.rev()
.find_map(|(_diameter, (arc, span))| {
let search_localized = search.clone().localize();
let (matched, unmatched) = is_subset_of(span, &search_localized);
if matched {
let unmatched_cell_chars = search
.iter()
.enumerate()
.filter_map(|(i, cell_char)| {
if unmatched.contains(&i) {
Some(*cell_char)
} else {
None
}
})
.collect::<Vec<_>>();
Some((arc, Span::from(unmatched_cell_chars)))
} else {
None
}
})
}
pub fn endorse_three_quarters_arc_span(search: &Span) -> Option<(&Arc, Span)> {
FLATTENED_THREE_QUARTERS_ARC_SPAN.iter().rev().find_map(
|(_diameter, (arc, span))| {
let search_localized = search.clone().localize();
let (matched, unmatched) = is_subset_of(span, &search_localized);
if matched {
let unmatched_cell_chars = search
.iter()
.enumerate()
.filter_map(|(i, cell_char)| {
if unmatched.contains(&i) {
Some(*cell_char)
} else {
None
}
})
.collect::<Vec<_>>();
Some((arc, Span::from(unmatched_cell_chars)))
} else {
None
}
},
)
}
fn is_subset_of<T: PartialEq>(
subset: &[T],
big_set: &[T],
) -> (bool, Vec<usize>) {
let mut unmatched = vec![];
let mut matched = 0;
for (_i, set) in subset.iter().enumerate() {
if big_set.contains(set) {
matched += 1;
}
}
for (bi, bset) in big_set.iter().enumerate() {
if !subset.contains(bset) {
unmatched.push(bi);
}
}
(matched == subset.len(), unmatched)
}
#[cfg(test)]
mod test_circle_map;