use std::{collections::HashMap, fmt::Display};
use glam::{ivec2, IVec2};
mod test;
const LEFT_BRACKET: char = '⟨';
const RIGHT_BRACKET: char = '⟩';
fn hexagonal_to_cartesian(hexagonal_coords: IVec2) -> IVec2 {
let b1 = ivec2(-5, -1);
let b2 = ivec2(5, -1);
b1 * hexagonal_coords.x + b2 * hexagonal_coords.y
}
const STATIC_TILE_ELEMENTS: [(IVec2, char); 8] = [
(ivec2(-1, 1), '-'),
(ivec2(0, 1), '-'),
(ivec2(1, 1), '-'),
(ivec2(-3, 0), LEFT_BRACKET),
(ivec2(3, 0), RIGHT_BRACKET),
(ivec2(-1, -1), '-'),
(ivec2(0, -1), '-'),
(ivec2(1, -1), '-'),
];
const DYNAMIC_TILE_ELEMENTS: [(IVec2, char, char); 4] = [
(ivec2(-2, 1), '\\', RIGHT_BRACKET),
(ivec2(2, 1), '/', LEFT_BRACKET),
(ivec2(-2, -1), '/', RIGHT_BRACKET),
(ivec2(2, -1), '\\', LEFT_BRACKET),
];
#[derive(Debug, Clone, Default)]
pub struct HexagonalBoard<T> {
values: HashMap<IVec2, T>,
}
impl<T> HexagonalBoard<T> {
pub fn char_map(&self, into_char: impl Fn(&T) -> char) -> HashMap<IVec2, char> {
let min_x = self
.values
.keys()
.map(|pos| pos.x - pos.y)
.max()
.unwrap_or(0);
let max_y = self
.values
.keys()
.map(|pos| pos.x + pos.y)
.max()
.unwrap_or(0);
let origin_offset = ivec2(5 * min_x + 3, max_y + 1);
let mut output = HashMap::new();
for (hexagonal_coords, value) in &self.values {
let cartesian_coords = hexagonal_to_cartesian(*hexagonal_coords) + origin_offset;
output.insert(cartesian_coords, into_char(value));
for (offset, char) in STATIC_TILE_ELEMENTS {
output.insert(cartesian_coords + offset, char);
}
for (offset, single, multiple) in DYNAMIC_TILE_ELEMENTS {
match output.get(&(cartesian_coords + offset)) {
None => output.insert(cartesian_coords + offset, single),
Some(&RIGHT_BRACKET | &LEFT_BRACKET) => None,
Some(_) => output.insert(cartesian_coords + offset, multiple),
};
}
}
output
}
pub fn render_with(&self, into_char: impl Fn(&T) -> char) -> String {
render_char_map(self.char_map(into_char))
}
pub fn render(&self) -> String
where
for<'a> char: From<T>,
T: Copy,
{
render_char_map(self.char_map(|t| char::from(*t)))
}
}
fn render_char_map(char_map: HashMap<IVec2, char>) -> String {
let max_x = char_map.keys().map(|pos| pos.x).max().unwrap_or(0);
let max_y = char_map.keys().map(|pos| pos.y).max().unwrap_or(0);
let mut output = String::with_capacity((max_x * max_y) as usize);
for y in 0..=max_y {
for x in 0..=max_x {
output.push(*char_map.get(&ivec2(x, y)).unwrap_or(&' '));
}
if y != max_y {
output.push('\n');
}
}
output
}
impl<P: Into<IVec2>, T, V> From<V> for HexagonalBoard<T>
where
V: IntoIterator<Item = (P, T)>,
{
fn from(value: V) -> Self {
value.into_iter().collect()
}
}
impl<P: Into<IVec2>, T> FromIterator<(P, T)> for HexagonalBoard<T> {
fn from_iter<I: IntoIterator<Item = (P, T)>>(iter: I) -> Self {
Self {
values: iter
.into_iter()
.map(|(position, char)| (position.into(), char))
.collect(),
}
}
}
impl<T> Display for HexagonalBoard<T>
where
for<'a> char: From<T>,
T: Copy,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.render())
}
}