2020-12-26 15:53:06 +00:00
|
|
|
use super::*;
|
2021-02-10 01:18:22 +00:00
|
|
|
use common::spiral::Spiral2d;
|
2021-02-12 20:30:30 +00:00
|
|
|
use std::ops::Range;
|
2020-12-26 15:53:06 +00:00
|
|
|
|
2021-02-04 12:47:46 +00:00
|
|
|
pub const TILE_SIZE: u32 = 7;
|
|
|
|
pub const ZONE_SIZE: u32 = 16;
|
|
|
|
pub const ZONE_RADIUS: u32 = 16;
|
|
|
|
pub const TILE_RADIUS: u32 = ZONE_SIZE * ZONE_RADIUS;
|
|
|
|
pub const MAX_BLOCK_RADIUS: u32 = TILE_SIZE * TILE_RADIUS;
|
2020-12-26 15:53:06 +00:00
|
|
|
|
|
|
|
pub struct TileGrid {
|
2021-02-23 12:42:45 +00:00
|
|
|
pub(crate) bounds: Aabr<i32>, // Inclusive
|
2020-12-26 15:53:06 +00:00
|
|
|
zones: Grid<Option<Grid<Option<Tile>>>>,
|
|
|
|
}
|
|
|
|
|
2021-02-04 12:47:46 +00:00
|
|
|
impl Default for TileGrid {
|
|
|
|
fn default() -> Self {
|
2020-12-26 15:53:06 +00:00
|
|
|
Self {
|
2021-02-23 12:42:45 +00:00
|
|
|
bounds: Aabr::new_empty(Vec2::zero()),
|
2020-12-26 15:53:06 +00:00
|
|
|
zones: Grid::populate_from(Vec2::broadcast(ZONE_RADIUS as i32 * 2 + 1), |_| None),
|
|
|
|
}
|
|
|
|
}
|
2021-02-04 12:47:46 +00:00
|
|
|
}
|
2020-12-26 15:53:06 +00:00
|
|
|
|
2021-02-04 12:47:46 +00:00
|
|
|
impl TileGrid {
|
2021-02-10 01:18:22 +00:00
|
|
|
pub fn get(&self, tpos: Vec2<i32>) -> &Tile {
|
|
|
|
static EMPTY: Tile = Tile::empty();
|
|
|
|
|
2020-12-26 15:53:06 +00:00
|
|
|
let tpos = tpos + TILE_RADIUS as i32;
|
|
|
|
self.zones
|
2021-02-12 20:30:30 +00:00
|
|
|
.get(tpos.map(|e| e.div_euclid(ZONE_SIZE as i32)))
|
2021-02-06 23:53:25 +00:00
|
|
|
.and_then(|zone| {
|
|
|
|
zone.as_ref()?
|
|
|
|
.get(tpos.map(|e| e.rem_euclid(ZONE_SIZE as i32)))
|
|
|
|
})
|
2020-12-26 15:53:06 +00:00
|
|
|
.and_then(|tile| tile.as_ref())
|
2021-02-10 01:18:22 +00:00
|
|
|
.unwrap_or(&EMPTY)
|
2020-12-26 15:53:06 +00:00
|
|
|
}
|
|
|
|
|
2021-02-23 12:42:45 +00:00
|
|
|
// WILL NOT EXPAND BOUNDS!
|
2020-12-26 15:53:06 +00:00
|
|
|
pub fn get_mut(&mut self, tpos: Vec2<i32>) -> Option<&mut Tile> {
|
|
|
|
let tpos = tpos + TILE_RADIUS as i32;
|
2021-02-12 20:30:30 +00:00
|
|
|
self.zones.get_mut(tpos.map(|e| e.div_euclid(ZONE_SIZE as i32))).and_then(|zone| {
|
2021-02-06 23:53:25 +00:00
|
|
|
zone.get_or_insert_with(|| {
|
2021-02-12 20:30:30 +00:00
|
|
|
Grid::populate_from(Vec2::broadcast(ZONE_SIZE as i32), |_| None)
|
2021-02-06 23:53:25 +00:00
|
|
|
})
|
|
|
|
.get_mut(tpos.map(|e| e.rem_euclid(ZONE_SIZE as i32)))
|
|
|
|
.map(|tile| tile.get_or_insert_with(|| Tile::empty()))
|
|
|
|
})
|
2020-12-26 15:53:06 +00:00
|
|
|
}
|
2021-02-04 12:47:46 +00:00
|
|
|
|
2021-02-10 01:18:22 +00:00
|
|
|
pub fn set(&mut self, tpos: Vec2<i32>, tile: Tile) -> Option<Tile> {
|
2021-02-23 12:42:45 +00:00
|
|
|
self.bounds.expand_to_contain_point(tpos);
|
2021-02-10 01:18:22 +00:00
|
|
|
self.get_mut(tpos).map(|t| std::mem::replace(t, tile))
|
|
|
|
}
|
|
|
|
|
2021-02-17 01:47:16 +00:00
|
|
|
pub fn find_near<R>(&self, tpos: Vec2<i32>, f: impl Fn(Vec2<i32>, &Tile) -> Option<R>) -> Option<(R, Vec2<i32>)> {
|
|
|
|
const MAX_SEARCH_RADIUS_BLOCKS: u32 = 70;
|
2021-02-12 20:30:30 +00:00
|
|
|
const MAX_SEARCH_CELLS: u32 = ((MAX_SEARCH_RADIUS_BLOCKS / TILE_SIZE) * 2 + 1).pow(2);
|
2021-02-17 01:47:16 +00:00
|
|
|
Spiral2d::new()
|
|
|
|
.take(MAX_SEARCH_CELLS as usize)
|
|
|
|
.map(|r| tpos + r)
|
|
|
|
.find_map(|tpos| (&f)(tpos, self.get(tpos)).zip(Some(tpos)))
|
2021-02-12 20:30:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn grow_aabr(&self, center: Vec2<i32>, area_range: Range<u32>, min_dims: Extent2<u32>) -> Result<Aabr<i32>, Aabr<i32>> {
|
2021-02-17 01:47:16 +00:00
|
|
|
let mut aabr = Aabr { min: center, max: center + 1 };
|
|
|
|
|
|
|
|
if !self.get(center).is_empty() {
|
|
|
|
return Err(aabr);
|
|
|
|
};
|
2021-02-12 20:30:30 +00:00
|
|
|
|
|
|
|
let mut last_growth = 0;
|
|
|
|
for i in 0.. {
|
|
|
|
if i - last_growth >= 4 {
|
|
|
|
break;
|
|
|
|
} else if aabr.size().product() + if i % 2 == 0 { aabr.size().h } else { aabr.size().w } > area_range.end as i32 {
|
|
|
|
break;
|
|
|
|
} else {
|
2021-02-17 02:17:19 +00:00
|
|
|
// `center.sum()` to avoid biasing certain directions
|
|
|
|
match (i + center.sum().abs()) % 4 {
|
2021-02-17 01:47:16 +00:00
|
|
|
0 if (aabr.min.y..aabr.max.y + 1).all(|y| self.get(Vec2::new(aabr.max.x, y)).is_empty()) => {
|
2021-02-12 20:30:30 +00:00
|
|
|
aabr.max.x += 1;
|
|
|
|
last_growth = i;
|
|
|
|
},
|
2021-02-17 01:47:16 +00:00
|
|
|
1 if (aabr.min.x..aabr.max.x + 1).all(|x| self.get(Vec2::new(x, aabr.max.y)).is_empty()) => {
|
2021-02-12 20:30:30 +00:00
|
|
|
aabr.max.y += 1;
|
|
|
|
last_growth = i;
|
|
|
|
},
|
2021-02-17 01:47:16 +00:00
|
|
|
2 if (aabr.min.y..aabr.max.y + 1).all(|y| self.get(Vec2::new(aabr.min.x - 1, y)).is_empty()) => {
|
2021-02-12 20:30:30 +00:00
|
|
|
aabr.min.x -= 1;
|
|
|
|
last_growth = i;
|
|
|
|
},
|
2021-02-17 01:47:16 +00:00
|
|
|
3 if (aabr.min.x..aabr.max.x + 1).all(|x| self.get(Vec2::new(x, aabr.min.y - 1)).is_empty()) => {
|
2021-02-12 20:30:30 +00:00
|
|
|
aabr.min.y -= 1;
|
|
|
|
last_growth = i;
|
|
|
|
},
|
|
|
|
_ => {},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if aabr.size().product() as u32 >= area_range.start
|
|
|
|
&& aabr.size().w as u32 >= min_dims.w
|
|
|
|
&& aabr.size().h as u32 >= min_dims.h
|
|
|
|
{
|
|
|
|
Ok(aabr)
|
|
|
|
} else {
|
|
|
|
Err(aabr)
|
|
|
|
}
|
2021-02-04 12:47:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-12 20:30:30 +00:00
|
|
|
#[derive(Clone, PartialEq)]
|
2021-02-04 12:47:46 +00:00
|
|
|
pub enum TileKind {
|
|
|
|
Empty,
|
2021-02-23 12:42:45 +00:00
|
|
|
Hazard(HazardKind),
|
2021-02-17 01:47:16 +00:00
|
|
|
Field,
|
|
|
|
Road,
|
2021-02-04 12:47:46 +00:00
|
|
|
Building { levels: u32 },
|
2021-02-17 01:47:16 +00:00
|
|
|
Castle,
|
|
|
|
Wall,
|
2020-12-26 15:53:06 +00:00
|
|
|
}
|
|
|
|
|
2021-02-23 12:42:45 +00:00
|
|
|
#[derive(Clone, PartialEq)]
|
2020-12-26 15:53:06 +00:00
|
|
|
pub struct Tile {
|
2021-02-12 20:30:30 +00:00
|
|
|
pub(crate) kind: TileKind,
|
|
|
|
pub(crate) plot: Option<Id<Plot>>,
|
2020-12-26 15:53:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Tile {
|
2021-02-10 01:18:22 +00:00
|
|
|
pub const fn empty() -> Self {
|
2020-12-26 15:53:06 +00:00
|
|
|
Self {
|
2021-02-04 12:47:46 +00:00
|
|
|
kind: TileKind::Empty,
|
2020-12-26 15:53:06 +00:00
|
|
|
plot: None,
|
|
|
|
}
|
|
|
|
}
|
2021-02-04 12:47:46 +00:00
|
|
|
|
2021-02-12 20:30:30 +00:00
|
|
|
/// Create a tile that is not associated with any plot.
|
|
|
|
pub const fn free(kind: TileKind) -> Self {
|
|
|
|
Self {
|
|
|
|
kind,
|
|
|
|
plot: None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-06 23:53:25 +00:00
|
|
|
pub fn is_empty(&self) -> bool { self.kind == TileKind::Empty }
|
2021-02-17 01:47:16 +00:00
|
|
|
|
|
|
|
pub fn is_obstacle(&self) -> bool {
|
|
|
|
matches!(
|
|
|
|
self.kind,
|
2021-02-23 12:42:45 +00:00
|
|
|
TileKind::Hazard(_)
|
|
|
|
| TileKind::Building { .. }
|
2021-02-17 01:47:16 +00:00
|
|
|
| TileKind::Castle
|
|
|
|
| TileKind::Wall
|
|
|
|
)
|
|
|
|
}
|
2020-12-26 15:53:06 +00:00
|
|
|
}
|
2021-02-23 12:42:45 +00:00
|
|
|
|
|
|
|
#[derive(Copy, Clone, PartialEq)]
|
|
|
|
pub enum HazardKind {
|
|
|
|
Water,
|
|
|
|
Hill { gradient: f32 },
|
|
|
|
}
|