use direction::Direction;
use offset_coordinate::OffsetCoordinate;
use bitflags::bitflags;
pub mod direction;
pub mod hex_grid;
pub mod offset_coordinate;
pub mod square_grid;
pub trait Grid {
type GridCoordinateType;
type DirectionArrayType;
fn edge_direction_array(&self) -> Self::DirectionArrayType;
fn corner_direction_array(&self) -> Self::DirectionArrayType;
fn size(&self) -> Size;
fn width(&self) -> u32 {
self.size().width
}
fn height(&self) -> u32 {
self.size().height
}
fn wrap_flags(&self) -> WrapFlags;
fn wrap_x(&self) -> bool {
self.wrap_flags().contains(WrapFlags::WrapX)
}
fn wrap_y(&self) -> bool {
self.wrap_flags().contains(WrapFlags::WrapY)
}
fn center(&self) -> [f32; 2];
fn left_bottom(&self) -> [f32; 2];
fn right_top(&self) -> [f32; 2];
fn offset_to_pixel(&self, offset: OffsetCoordinate) -> [f32; 2];
fn pixel_to_offset(&self, pixel: [f32; 2]) -> OffsetCoordinate;
fn cell_to_offset(&self, cell: Cell) -> OffsetCoordinate {
let width = self.size().width;
let height = self.size().height;
debug_assert!(
cell.0 < (width * height) as usize,
"Cell is out of bounds! Cell index: {}, Map size: {}x{}",
cell.0,
width,
height
);
let x = cell.0 as u32 % width;
let y = cell.0 as u32 / width;
OffsetCoordinate::from([x, y])
}
#[must_use = "iterators are lazy and do nothing unless consumed"]
fn offset_to_cell(&self, offset_coordinate: OffsetCoordinate) -> Result<Cell, String> {
self.normalize_offset(offset_coordinate)
.map(|normalized_coordinate| {
let [x, y] = normalized_coordinate.to_array();
Cell((x + y * self.size().width as i32) as usize)
})
}
#[must_use = "this `Result` may be an `Err` variant, which should be handled"]
fn normalize_offset(
&self,
offset_coordinate: OffsetCoordinate,
) -> Result<OffsetCoordinate, String> {
let mut x = offset_coordinate.0.x;
let mut y = offset_coordinate.0.y;
if self.wrap_x() {
x = x.rem_euclid(self.width() as i32);
}
if self.wrap_y() {
y = y.rem_euclid(self.height() as i32);
}
let offset_coordinate = OffsetCoordinate::new(x, y);
if self.within_grid_bounds(offset_coordinate) {
Ok(offset_coordinate)
} else {
Err(format!(
"Offset coordinate out of bounds: {:?}",
offset_coordinate
))
}
}
fn within_grid_bounds(&self, offset_coordinate: OffsetCoordinate) -> bool {
offset_coordinate.0.x >= 0
&& offset_coordinate.0.x < self.width() as i32
&& offset_coordinate.0.y >= 0
&& offset_coordinate.0.y < self.height() as i32
}
fn grid_coordinate_to_cell(&self, grid_coordinate: Self::GridCoordinateType) -> Option<Cell>;
fn distance_to(&self, start: Cell, dest: Cell) -> i32;
fn neighbor(self, center: Cell, direction: Direction) -> Option<Cell>;
#[must_use]
fn cells_at_distance(self, center: Cell, distance: u32) -> impl Iterator<Item = Cell>;
#[must_use]
fn cells_within_distance(self, center: Cell, distance: u32) -> impl Iterator<Item = Cell>;
fn estimate_direction(&self, start: Cell, dest: Cell) -> Option<Direction>;
fn rectangle_region(&self, origin: OffsetCoordinate, width: u32, height: u32) -> Rectangle
where
Self: Sized,
{
Rectangle::new(origin, width, height, self)
}
fn rectangle_region_from_corners(
&self,
origin: OffsetCoordinate,
top_right_corner: OffsetCoordinate,
) -> Rectangle
where
Self: Sized,
{
Rectangle::from_corners(origin, top_right_corner, self)
}
}
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub struct Size {
pub width: u32,
pub height: u32,
}
impl Size {
pub fn new(width: u32, height: u32) -> Self {
Self { width, height }
}
pub fn area(&self) -> u32 {
self.width * self.height
}
}
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct WrapFlags: u8 {
const WrapX = 0b0000_0001;
const WrapY = 0b0000_0010;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Cell(usize);
impl Cell {
pub fn new(index: usize) -> Self {
Self(index)
}
pub fn index(&self) -> usize {
self.0
}
}
pub trait GridSize: Grid {
fn world_size_type(&self) -> WorldSizeType {
let width = self.width();
let height = self.height();
let area = width * height;
let duel_area = Self::default_size(WorldSizeType::Duel).area();
let tiny_area = Self::default_size(WorldSizeType::Tiny).area();
let small_area = Self::default_size(WorldSizeType::Small).area();
let standard_area = Self::default_size(WorldSizeType::Standard).area();
let large_area = Self::default_size(WorldSizeType::Large).area();
let huge_area = Self::default_size(WorldSizeType::Huge).area();
match area {
area if area < duel_area => {
eprintln!(
"The map size is too small. The provided dimensions are {}x{}, which gives an area of {}. The minimum area is {} in the original CIV5 game.",
width, height, area, duel_area
);
WorldSizeType::Duel
}
area if area < tiny_area => WorldSizeType::Duel,
area if area < small_area => WorldSizeType::Tiny,
area if area < standard_area => WorldSizeType::Small,
area if area < large_area => WorldSizeType::Standard,
area if area < huge_area => WorldSizeType::Large,
_ => WorldSizeType::Huge,
}
}
fn default_size(world_size_type: WorldSizeType) -> Size;
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum WorldSizeType {
Duel,
Tiny,
Small,
Standard,
Large,
Huge,
}
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub struct Rectangle {
origin: OffsetCoordinate,
width: u32,
height: u32,
}
impl Rectangle {
pub fn new(origin: OffsetCoordinate, width: u32, height: u32, grid: &impl Grid) -> Self {
debug_assert!(
width > 0 && height > 0,
"Rectangle dimensions must be positive (got {}x{})",
width,
height
);
debug_assert!(
width <= grid.width() && height <= grid.height(),
"Rectangle dimensions {}x{} exceed grid size {}x{}",
width,
height,
grid.width(),
grid.height()
);
let normalize_origin = grid
.normalize_offset(origin)
.unwrap_or_else(|_| panic!("Offset coordinate out of bounds: {:?}", origin));
Self {
origin: normalize_origin,
width,
height,
}
}
pub fn from_corners(
origin: OffsetCoordinate,
top_right_corner: OffsetCoordinate,
grid: &impl Grid,
) -> Self {
let normalize_origin = grid
.normalize_offset(origin)
.unwrap_or_else(|_| panic!("Offset coordinate out of bounds: {:?}", origin));
let [mut width, mut height] = (top_right_corner.0 - normalize_origin.0 + 1).to_array();
if grid.wrap_x() {
width = width.rem_euclid(grid.width() as i32);
}
if grid.wrap_y() {
height = height.rem_euclid(grid.height() as i32);
}
debug_assert!(
width > 0
&& width <= grid.width() as i32
&& height > 0
&& height <= grid.height() as i32,
"The rectangle does not exist"
);
Self {
origin,
width: width as u32,
height: height as u32,
}
}
#[inline]
pub fn origin(&self) -> OffsetCoordinate {
self.origin
}
#[inline]
pub fn west_x(&self) -> i32 {
self.origin.0.x
}
#[inline]
pub fn south_y(&self) -> i32 {
self.origin.0.y
}
#[inline]
pub fn width(&self) -> u32 {
self.width
}
#[inline]
pub fn height(&self) -> u32 {
self.height
}
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub fn all_cells<'a>(self, grid: &'a impl Grid) -> impl Iterator<Item = Cell> + 'a {
(self.south_y()..self.south_y() + self.height as i32).flat_map(move |y| {
(self.west_x()..self.west_x() + self.width as i32).map({
move |x| {
let offset_coordinate = OffsetCoordinate::new(x, y);
grid.offset_to_cell(offset_coordinate).unwrap() }
})
})
}
pub fn contains(&self, cell: Cell, grid: &impl Grid) -> bool {
let [mut x, mut y] = grid.cell_to_offset(cell).to_array();
if x < self.west_x() {
x += grid.width() as i32;
}
if y < self.south_y() {
y += grid.height() as i32;
}
x >= self.west_x()
&& x < self.west_x() + self.width as i32
&& y >= self.south_y()
&& y < self.south_y() + self.height as i32
}
}