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;
|
||
|
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 + From<String> + Clone> Map<A>
|
||
|
where
|
||
|
String: From<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 from_file(path: PathBuf) -> Map<A> {}
|
||
|
|
||
|
pub fn to_file(&self, path: PathBuf) -> () {}
|
||
|
|
||
|
pub fn from_data(data: Vec<String>) -> 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
|
||
|
.into_iter()
|
||
|
.map(|line| parse_line::<A>(&line).unwrap())
|
||
|
.collect::<Vec<(AxialAddr, A)>>();
|
||
|
let cells = cells.into_iter().collect::<HashMap<AxialAddr, A>>();
|
||
|
Map { cells }
|
||
|
}
|
||
|
|
||
|
pub fn to_data(&self) -> Vec<String> {
|
||
|
self.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 = map.to_data();
|
||
|
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> = Map::from_data(
|
||
|
map_data
|
||
|
.lines()
|
||
|
.map(|l| l.to_owned())
|
||
|
.collect::<Vec<String>>(),
|
||
|
);
|
||
|
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('.')));
|
||
|
}
|
||
|
}
|