monorepo/coordinates/src/hex_map.rs

196 lines
5.9 KiB
Rust

/*
Copyright 2022-2023, Savanni D'Gerinel <savanni@luminescent-dreams.com>
This file is part of the Luminescent Dreams Tools.
Luminescent Dreams Tools is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
Luminescent Dreams Tools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with Luminescent Dreams Tools. If not, see <https://www.gnu.org/licenses/>.
*/
use crate::{hex::AxialAddr, Error};
use nom::{
bytes::complete::tag,
character::complete::alphanumeric1,
error::ParseError,
multi::many1,
sequence::{delimited, separated_pair},
Finish, IResult, Parser,
};
use std::{
collections::{hash_map::Entry, HashMap},
path::PathBuf,
};
#[derive(Clone, Debug, PartialEq)]
pub struct Map<A> {
cells: HashMap<AxialAddr, A>,
}
impl<A: Default + Clone> Map<A> {
pub fn new_hexagonal(radius: usize) -> Map<A> {
let cells = vec![(AxialAddr::origin(), Default::default())]
.into_iter()
.chain(
AxialAddr::origin()
.addresses(radius)
.map(|addr| (addr, Default::default())),
)
.collect::<HashMap<AxialAddr, A>>();
Map { cells }
}
pub fn contains(&self, coordinate: AxialAddr) -> bool {
self.cells.contains_key(&coordinate)
}
pub fn cell(&mut self, addr: AxialAddr) -> Entry<AxialAddr, A> {
self.cells.entry(addr)
}
pub fn get(&self, addr: &AxialAddr) -> Option<&A> {
self.cells.get(addr)
}
}
pub fn read_file<A: Default + From<String>>(path: PathBuf) -> Result<Map<A>, Error> {
let data = std::fs::read_to_string(path)?;
Ok(parse_data(data.lines()))
}
pub fn write_file<A: Clone>(path: PathBuf, map: Map<A>) -> Result<(), Error>
where
String: From<A>,
{
std::fs::write(path, write_data(map).join("\n")).map_err(Error::from)
}
pub fn parse_data<'a, A: Default + From<String>>(
data: impl Iterator<Item = &'a str> + 'a,
) -> Map<A> {
fn parse_line<A: From<String>>(input: &str) -> Result<(AxialAddr, A), nom::error::Error<&str>> {
fn parse<A: From<String>>(input: &str) -> IResult<&str, (AxialAddr, A)> {
let (input, addr) = parse_address(input)?;
let (input, value) = cell_value::<A>(input)?;
Ok((input, (addr, value)))
}
parse(input).finish().map(|(_, pair)| pair)
}
let cells = data
.map(|line| parse_line::<A>(&line).unwrap())
.collect::<Vec<(AxialAddr, A)>>();
let cells = cells.into_iter().collect::<HashMap<AxialAddr, A>>();
Map { cells }
}
fn write_data<A: Clone>(map: Map<A>) -> Vec<String>
where
String: From<A>,
{
map.cells
.iter()
.map(|(addr, val)| format!("[{}, {}] {}", addr.q(), addr.r(), String::from(val.clone())))
.collect()
}
fn parse_address(input: &str) -> IResult<&str, AxialAddr> {
let (input, (q, r)) = delimited(
tag("["),
separated_pair(
nom::character::complete::i32,
tag(", "),
nom::character::complete::i32,
),
tag("] "),
)(input)?;
Ok((input, (AxialAddr::new(q, r))))
}
fn cell_value<A: From<String>>(input: &str) -> IResult<&str, A> {
many1(alphanumeric1)
.or(tag(".").map(|v| vec![v]))
.map(|v| v.join(""))
.map(|v| A::from(v))
.parse(input)
}
#[cfg(test)]
mod test {
use std::collections::HashSet;
use super::*;
#[derive(Clone, Debug, PartialEq)]
struct MapVal(char);
impl Default for MapVal {
fn default() -> MapVal {
MapVal('.')
}
}
impl From<MapVal> for String {
fn from(v: MapVal) -> String {
format!("{}", v.0)
}
}
impl From<String> for MapVal {
fn from(s: String) -> MapVal {
MapVal(s.chars().next().unwrap())
}
}
#[test]
fn it_can_modify_a_map() {
let mut map: Map<MapVal> = Map::new_hexagonal(1);
map.cell(AxialAddr::new(0, 0))
.and_modify(|cell| *cell = MapVal('L'));
assert_eq!(map.get(&AxialAddr::new(0, 0)), Some(&MapVal('L')));
assert_eq!(map.get(&AxialAddr::new(0, 1)), Some(&MapVal('.')));
}
#[test]
fn it_can_serialize_a_map() {
let mut map: Map<MapVal> = Map::new_hexagonal(1);
map.cell(AxialAddr::new(0, 0))
.and_modify(|cell| *cell = MapVal('L'));
map.cell(AxialAddr::new(1, 0))
.and_modify(|cell| *cell = MapVal('A'));
let mut expected: HashSet<String> = HashSet::new();
expected.insert("[0, 0] L".to_owned());
expected.insert("[1, 0] A".to_owned());
expected.insert("[0, 1] .".to_owned());
expected.insert("[-1, 1] .".to_owned());
expected.insert("[-1, 0] .".to_owned());
expected.insert("[0, -1] .".to_owned());
expected.insert("[1, -1] .".to_owned());
let map_rows = write_data(map);
assert_eq!(map_rows.len(), expected.len());
map_rows
.iter()
.for_each(|row| assert!(expected.contains(row)));
expected
.iter()
.for_each(|expected| assert!(map_rows.contains(expected)));
}
#[test]
fn it_can_deserialize_a_map() {
let map_data = "[0, 0] L
[1, 0] A
[0, 1] .
[-1, 1] .
[-1, 0] .
[0, -1] .
[1, -1] .";
let map: Map<MapVal> = parse_data(map_data.lines());
assert_eq!(map.get(&AxialAddr::new(0, 0)), Some(&MapVal('L')));
assert_eq!(map.get(&AxialAddr::new(1, 0)), Some(&MapVal('A')));
assert_eq!(map.get(&AxialAddr::new(0, 1)), Some(&MapVal('.')));
}
}