196 lines
5.9 KiB
Rust
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('.')));
|
|
}
|
|
}
|