src/world.rs 4.2 K · 155 lines · raw · history

1 use bevy::prelude::*;
2
3 pub const TILE_SIZE: f32 = 16.0;
4
5 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
6 pub enum Terrain {
7 Grass,
8 Soil,
9 Sand,
10 ShallowWater,
11 DeepWater,
12 Stone,
13 }
14
15 impl Terrain {
16 pub const ALL: [Terrain; 6] = [
17 Terrain::Grass,
18 Terrain::Soil,
19 Terrain::Sand,
20 Terrain::ShallowWater,
21 Terrain::DeepWater,
22 Terrain::Stone,
23 ];
24
25 pub fn index(self) -> u16 {
26 self as u16
27 }
28
29 pub fn move_cost(self) -> Option<u32> {
30 match self {
31 Terrain::Grass | Terrain::Soil => Some(100),
32 Terrain::Sand => Some(130),
33 Terrain::ShallowWater => Some(300),
34 Terrain::DeepWater | Terrain::Stone => None,
35 }
36 }
37
38 pub fn color(self) -> Srgba {
39 match self {
40 Terrain::Grass => Srgba::rgb_u8(92, 140, 64),
41 Terrain::Soil => Srgba::rgb_u8(122, 92, 60),
42 Terrain::Sand => Srgba::rgb_u8(214, 196, 140),
43 Terrain::ShallowWater => Srgba::rgb_u8(72, 132, 184),
44 Terrain::DeepWater => Srgba::rgb_u8(36, 76, 140),
45 Terrain::Stone => Srgba::rgb_u8(118, 118, 124),
46 }
47 }
48 }
49
50 #[derive(Resource, Clone, Debug)]
51 pub struct WorldMap {
52 pub size: UVec2,
53 tiles: Vec<Terrain>,
54 }
55
56 impl WorldMap {
57 pub fn filled(size: UVec2, terrain: Terrain) -> Self {
58 Self {
59 size,
60 tiles: vec![terrain; size.element_product() as usize],
61 }
62 }
63
64 pub fn in_bounds(&self, pos: IVec2) -> bool {
65 pos.x >= 0 && pos.y >= 0 && (pos.x as u32) < self.size.x && (pos.y as u32) < self.size.y
66 }
67
68 fn index(&self, pos: UVec2) -> usize {
69 (pos.y * self.size.x + pos.x) as usize
70 }
71
72 pub fn get(&self, pos: IVec2) -> Option<Terrain> {
73 self.in_bounds(pos)
74 .then(|| self.tiles[self.index(pos.as_uvec2())])
75 }
76
77 pub fn set(&mut self, pos: UVec2, terrain: Terrain) {
78 let i = self.index(pos);
79 self.tiles[i] = terrain;
80 }
81
82 pub fn move_cost(&self, pos: IVec2) -> Option<u32> {
83 self.get(pos).and_then(Terrain::move_cost)
84 }
85
86 pub fn positions(&self) -> impl Iterator<Item = UVec2> + use<> {
87 let size = self.size;
88 (0..size.y).flat_map(move |y| (0..size.x).map(move |x| UVec2::new(x, y)))
89 }
90
91 pub fn world_size(&self) -> Vec2 {
92 self.size.as_vec2() * TILE_SIZE
93 }
94
95 pub fn tile_center(pos: UVec2) -> Vec2 {
96 (pos.as_vec2() + 0.5) * TILE_SIZE
97 }
98
99 pub fn tile_at(&self, world: Vec2) -> Option<UVec2> {
100 let pos = (world / TILE_SIZE).floor().as_ivec2();
101 self.in_bounds(pos).then(|| pos.as_uvec2())
102 }
103 }
104
105 pub fn hash2(x: u32, y: u32) -> u32 {
106 let mut h = x.wrapping_mul(0x9E37_79B1) ^ y.wrapping_mul(0x85EB_CA77);
107 h ^= h >> 15;
108 h = h.wrapping_mul(0x2C1B_3C6D);
109 h ^= h >> 12;
110 h = h.wrapping_mul(0x297A_2D39);
111 h ^ (h >> 15)
112 }
113
114 #[cfg(test)]
115 mod tests {
116 use super::*;
117
118 #[test]
119 fn get_out_of_bounds_is_none() {
120 let map = WorldMap::filled(UVec2::new(4, 3), Terrain::Grass);
121 assert_eq!(map.get(IVec2::new(-1, 0)), None);
122 assert_eq!(map.get(IVec2::new(4, 0)), None);
123 assert_eq!(map.get(IVec2::new(0, 3)), None);
124 assert_eq!(map.get(IVec2::new(3, 2)), Some(Terrain::Grass));
125 }
126
127 #[test]
128 fn tiles_are_stored_row_major_from_bottom_left() {
129 let mut map = WorldMap::filled(UVec2::new(4, 3), Terrain::Grass);
130 map.set(UVec2::new(1, 2), Terrain::Stone);
131 assert_eq!(map.tiles[2 * 4 + 1], Terrain::Stone);
132 }
133
134 #[test]
135 fn tile_at_inverts_tile_center() {
136 let map = WorldMap::filled(UVec2::new(32, 128), Terrain::Grass);
137 for pos in [UVec2::ZERO, UVec2::new(17, 99), map.size - 1] {
138 assert_eq!(map.tile_at(WorldMap::tile_center(pos)), Some(pos));
139 }
140 }
141
142 #[test]
143 fn tile_at_outside_world_is_none() {
144 let map = WorldMap::filled(UVec2::new(32, 128), Terrain::Grass);
145 assert_eq!(map.tile_at(Vec2::new(-0.1, 0.0)), None);
146 assert_eq!(map.tile_at(map.world_size()), None);
147 }
148
149 #[test]
150 fn terrain_index_matches_position_in_all() {
151 for (i, t) in Terrain::ALL.iter().enumerate() {
152 assert_eq!(t.index() as usize, i);
153 }
154 }
155 }