civ_map_generator 0.1.5

A civilization map generator
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
//! This module defines the [`TileMap`] struct and its associated methods.
//! It provides functionality to manage and manipulate a map of tiles, including
//! querying tile properties, placing resources, and managing layers of data.
//!
//! # Architecture
//!
//! The `TileMap` is the core data structure representing a game map. It consists of:
//!
//! - **Parallel Arrays**: Each tile property (terrain, base terrain, features, etc.) is stored in a separate vector indexed by tile position
//! - **Layer System**: Uses [`Layer`] enum to track placement constraints and prevent overlapping elements
//! - **Spatial Indexing**: Area and landmass IDs for efficient region-based queries
//!
//! # Map Generation Pipeline
//!
//! Map generation follows a three-phase process defined in [`Generator`](crate::map_generator::Generator):
//!
//! 1. **Terrain Generation**: Create terrain types, base terrains, features, rivers, and lakes
//! 2. **Element Placement**: Place civilizations, city-states, natural wonders, and resources
//! 3. **Finalization**: Fix graphical issues and recalculate spatial data
//!
//! # Layer System
//!
//! The layer system prevents invalid element placement through "impact and ripple" values:
//!
//! - **Impact** (value = 99): Marks tiles where elements are placed or forbidden
//! - **Ripple** (values 1-98): Indicates proximity to placed elements, with higher values meaning closer distance
//!
//! Different layers have different ripple behaviors.
//! See [`TileMap::layer_data`] and [`TileMap::place_impact_and_ripples`] for detailed implementation.

use crate::{
    grid::{direction::Direction, hex_grid::*},
    map_parameters::{MapParameters, ResourceSetting, WorldGrid},
    nation::Nation,
    tile::Tile,
    tile_component::*,
};
use arrayvec::ArrayVec;
use enum_map::{Enum, EnumMap, enum_map};
use rand::{Rng, SeedableRng, rngs::StdRng};
use std::{
    cmp::{max, min},
    collections::{BTreeMap, HashMap},
};

mod impls;

pub(crate) use impls::*;

#[derive(PartialEq, Debug)]
pub struct TileMap {
    /// Random number generator seeded for reproducible map generation.
    pub random_number_generator: StdRng,

    /// World grid configuration including size, orientation, and wrap settings.
    pub world_grid: WorldGrid,

    /// List of all rivers in the map. Each river is a sequence of [`RiverEdge`] segments.
    pub river_list: Vec<River>,

    /// Terrain type (Water/Flatland/Hill/Mountain) for each tile.
    /// Indexed by [`Tile::index()`].
    pub terrain_type_list: Vec<TerrainType>,

    /// Base terrain (Ocean/Coast/Grassland/etc.) for each tile.
    /// Indexed by [`Tile::index()`].
    pub base_terrain_list: Vec<BaseTerrain>,

    /// Optional feature (Forest/Jungle/Marsh/etc.) for each tile.
    /// Indexed by [`Tile::index()`].
    pub feature_list: Vec<Option<Feature>>,

    /// Optional natural wonder for each tile.
    /// Indexed by [`Tile::index()`].
    pub natural_wonder_list: Vec<Option<NaturalWonder>>,

    /// Optional resource with quantity for each tile.
    /// Indexed by [`Tile::index()`].
    pub resource_list: Vec<Option<(Resource, u32)>>,

    /// Area ID for connected regions.
    /// Indexed by [`Tile::index()`].
    pub area_id_list: Vec<usize>,

    /// Landmass ID for connected land areas separated by water.
    /// Indexed by [`Tile::index()`].
    pub landmass_id_list: Vec<usize>,

    /// List of all areas (connected regions). Index matches area IDs.
    pub area_list: Vec<Area>,

    /// List of all landmasses. Index matches landmass IDs.
    pub landmass_list: Vec<Landmass>,

    /// Mapping of civilization starting tiles to their assigned nations.
    pub starting_tile_and_civilization: BTreeMap<Tile, Nation>,

    /// Mapping of city-state starting tiles to their assigned nations.
    pub starting_tile_and_city_state: BTreeMap<Tile, Nation>,

    /// List of regions for dividing the map among civilizations.
    /// Capacity is limited to [`MapParameters::MAX_CIVILIZATION_NUM`].
    region_list: ArrayVec<Region, { MapParameters::MAX_CIVILIZATION_NUM as usize }>,

    /// Layer data tracking placement constraints for different element types.
    ///
    /// Each layer uses one of two modes:
    ///
    /// **Mode 1: Binary Placement Control** (CityState, Marble)
    /// - `0`: No constraint
    /// - `1`: Within influence range (placement forbidden)
    /// - `99`: Element placed or explicitly forbidden
    ///
    /// **Mode 2: Distance-Based Gradient** (Resources, Civilization, NaturalWonder)
    /// - `0`: No constraint
    /// - `1-98`: Within influence range (higher = closer to source)
    /// - `99`: Element placed or explicitly forbidden
    ///
    /// See [`Layer`] documentation for details on adding new layers.
    pub layer_data: EnumMap<Layer, Vec<u32>>,

    /// City-state starting tiles with their assigned region indices.
    /// `None` region index means the city-state is in an uninhabited area.
    city_state_starting_tile_and_region_index: Vec<(Tile, Option<usize>)>,

    /// Tracks luxury resource role assignments (region, city-state, special, random, unused).
    luxury_resource_role: LuxuryResourceRole,

    /// Count of how many regions each luxury resource type has been assigned to.
    ///
    /// Used to adjust assignment probability - the more regions a luxury is in,
    /// the less likely it is to be assigned to additional regions in the future.
    ///
    /// Keys are from [`LuxuryResourceRole::luxury_assigned_to_regions`].
    /// Values should not exceed [`MapParameters::MAX_REGIONS_PER_EXCLUSIVE_LUXURY_TYPE`].
    luxury_assign_to_region_count: HashMap<Resource, u32>,
}

impl TileMap {
    /// Creates a new empty tile map with the given parameters.
    ///
    /// All tile property lists are initialized with default values:
    /// - Terrain types default to [`TerrainType::Water`]
    /// - Base terrains default to [`BaseTerrain::Ocean`]
    /// - Features, natural wonders, and resources default to `None`
    /// - Layer data initialized to zeros (no constraints)
    ///
    /// # Arguments
    ///
    /// * `map_parameters` - Configuration including seed, world grid, and generation settings
    ///
    /// # Performance
    ///
    /// Allocates vectors with capacity equal to total tile count (width × height).
    pub fn new(map_parameters: &MapParameters) -> Self {
        let random_number_generator = StdRng::seed_from_u64(map_parameters.seed);

        let world_grid = map_parameters.world_grid;
        let height = world_grid.grid.size.height;
        let width = world_grid.grid.size.width;

        let size = (height * width) as usize;

        let layer_data = enum_map! {
            _ => vec![0; size],
        };

        let region_list = ArrayVec::new();

        Self {
            random_number_generator,
            world_grid,
            river_list: Vec::new(),
            terrain_type_list: vec![TerrainType::Water; size],
            base_terrain_list: vec![BaseTerrain::Ocean; size],
            feature_list: vec![None; size],
            natural_wonder_list: vec![None; size],
            resource_list: vec![None; size],
            area_id_list: Vec::with_capacity(size),
            landmass_id_list: Vec::with_capacity(size),
            area_list: Vec::new(),
            landmass_list: Vec::new(),
            region_list,
            layer_data,
            starting_tile_and_civilization: BTreeMap::new(),
            starting_tile_and_city_state: BTreeMap::new(),
            city_state_starting_tile_and_region_index: Vec::new(),
            luxury_resource_role: LuxuryResourceRole::default(),
            luxury_assign_to_region_count: HashMap::new(),
        }
    }

    /// Returns an iterator over all tiles in the map.
    ///
    /// Tiles are yielded in row-major order (left-to-right, bottom-to-top).
    ///
    /// # Examples
    ///
    /// ```
    /// # use civ_map_generator::tile_map::TileMap;
    /// # use civ_map_generator::map_parameters::{MapParametersBuilder, WorldGrid};
    /// # let params = MapParametersBuilder::new(WorldGrid::default()).build();
    /// # let map = TileMap::new(&params);
    /// for tile in map.all_tiles() {
    ///     println!("Tile index: {}", tile.index());
    /// }
    /// ```
    #[must_use = "iterators are lazy and do nothing unless consumed"]
    pub fn all_tiles(&self) -> impl Iterator<Item = Tile> + use<> {
        let size = &self.world_grid.size();
        (0..((size.width * size.height) as usize)).map(Tile::new)
    }

    /// Place impact and ripples for a given tile and layer.
    ///
    /// When you add an element (such as a starting tile of civilization, a city state, a natural wonder, a marble, or a resource...) to the map,
    /// if you want to ensure no other elements appear around the element being added, you can use this function.
    ///
    /// # Arguments
    ///
    /// - `tile`: the tile to place the impact and ripples on.
    /// - `layer`: the layer to place the impact and ripples on. It should be a variant of the [`Layer`] enum.
    /// - `radius`: the radius of the ripple. The ripple will be placed on all tiles within this radius. When it is `0`, only the impact will be placed on the `tile`.
    ///     - When layer is [`Layer::Strategic`], [`Layer::Luxury`] or [`Layer::Bonus`], [`Layer::Fish`], this argument is used to determine the ripple radius.
    ///     - When layer is other variants, this argument is ignored (recommended to use [`u32::MAX`] as placeholder).
    ///
    /// # Notice
    ///
    /// You can place impact and ripples to forbid other elements to appear around a specific tile, even if you are not adding an element to this tile.
    /// See [`TileMap::normalize_civilization_starting_tile`] for an example.
    ///
    pub fn place_impact_and_ripples(&mut self, tile: Tile, layer: Layer, radius: u32) {
        match layer {
            Layer::Strategic | Layer::Luxury | Layer::Bonus | Layer::Fish => {
                self.place_impact_and_ripples_for_resource(tile, layer, radius)
            }
            Layer::CityState => {
                self.place_impact_and_ripples_for_resource(tile, Layer::CityState, 4);

                self.place_impact_and_ripples_for_resource(tile, Layer::Luxury, 3);
                // Strategic layer, should be at start point only. That means if we are placing a city state at current tile, forbid to place strategic resources on it.
                self.place_impact_and_ripples_for_resource(tile, Layer::Strategic, 0);
                self.place_impact_and_ripples_for_resource(tile, Layer::Bonus, 3);
                self.place_impact_and_ripples_for_resource(tile, Layer::Fish, 3);
                self.place_impact_and_ripples_for_resource(tile, Layer::Marble, 3);
            }
            Layer::NaturalWonder => {
                self.place_impact_and_ripples_for_resource(
                    tile,
                    Layer::NaturalWonder,
                    self.world_grid.size().height / 5,
                );
                let natural_wonder = tile.natural_wonder(self);
                if let Some(natural_wonder) = natural_wonder {
                    match natural_wonder {
                        NaturalWonder::MountFuji => {
                            self.place_impact_and_ripples_for_resource(tile, Layer::Strategic, 0);
                            self.place_impact_and_ripples_for_resource(tile, Layer::Luxury, 0);
                            self.place_impact_and_ripples_for_resource(tile, Layer::Bonus, 0);
                            self.place_impact_and_ripples_for_resource(tile, Layer::CityState, 0);
                            self.place_impact_and_ripples_for_resource(tile, Layer::Marble, 1);
                        }
                        NaturalWonder::Krakatoa | NaturalWonder::GreatBarrierReef => {
                            self.place_impact_and_ripples_for_resource(tile, Layer::Strategic, 1);
                            self.place_impact_and_ripples_for_resource(tile, Layer::Luxury, 1);
                            self.place_impact_and_ripples_for_resource(tile, Layer::Bonus, 1);
                            self.place_impact_and_ripples_for_resource(tile, Layer::CityState, 1);
                            self.place_impact_and_ripples_for_resource(tile, Layer::Marble, 1);
                            // The tile beneath natural wonders on water should block fish resources.
                            self.place_impact_and_ripples_for_resource(tile, Layer::Fish, 1);
                        }
                        _ => {
                            self.place_impact_and_ripples_for_resource(tile, Layer::Strategic, 1);
                            self.place_impact_and_ripples_for_resource(tile, Layer::Luxury, 1);
                            self.place_impact_and_ripples_for_resource(tile, Layer::Bonus, 1);
                            self.place_impact_and_ripples_for_resource(tile, Layer::CityState, 1);
                            self.place_impact_and_ripples_for_resource(tile, Layer::Marble, 1);
                        }
                    }
                }
            }
            Layer::Marble => {
                self.place_impact_and_ripples_for_resource(tile, Layer::Luxury, 1);
                self.place_impact_and_ripples_for_resource(tile, Layer::Marble, 6);
            }
            Layer::Civilization => self.place_impact_and_ripples_for_civilization(tile),
        }
    }

    // function AssignStartingPlots:PlaceImpactAndRipples
    /// Places the impact and ripple values for a starting tile of civilization.
    ///
    /// We will place the impact on the tile and then ripple outwards to the surrounding tiles.
    fn place_impact_and_ripples_for_civilization(&mut self, tile: Tile) {
        let grid = self.world_grid.grid;

        let impact_value = 99;
        let ripple_values = [97, 95, 92, 89, 69, 57, 24, 15];

        // Start points need to impact the resource layers.
        self.place_impact_and_ripples_for_resource(tile, Layer::Luxury, 3);
        // Strategic layer, should be at start point only. That means if we are placing a civilization at current tile, forbid to place strategic resources on it.
        self.place_impact_and_ripples_for_resource(tile, Layer::Strategic, 0);
        self.place_impact_and_ripples_for_resource(tile, Layer::Bonus, 3);
        self.place_impact_and_ripples_for_resource(tile, Layer::Fish, 3);
        // Natural Wonders layer, set a minimum distance of 5 tiles (4 ripples) away.
        self.place_impact_and_ripples_for_resource(tile, Layer::NaturalWonder, 4);

        self.layer_data[Layer::Civilization][tile.index()] = impact_value;

        self.layer_data[Layer::CityState][tile.index()] = 1;

        for (index, ripple_value) in ripple_values.into_iter().enumerate() {
            let distance = index as u32 + 1;

            tile.tiles_at_distance(distance, grid)
                .for_each(|tile_at_distance| {
                    let mut current_value =
                        self.layer_data[Layer::Civilization][tile_at_distance.index()];
                    if current_value != 0 {
                        // First choose the greater of the two, existing value or current ripple.
                        let stronger_value = max(current_value, ripple_value);
                        // Now increase it by 1.2x to reflect that multiple civs are in range of this plot.
                        let overlap_value = min(97, (stronger_value as f64 * 1.2) as u32);
                        current_value = overlap_value;
                    } else {
                        current_value = ripple_value;
                    }
                    // Update the layer data with the new value.
                    self.layer_data[Layer::Civilization][tile_at_distance.index()] = current_value;

                    if distance <= 6 {
                        self.layer_data[Layer::CityState][tile_at_distance.index()] = 1;
                    }
                })
        }
    }

    // AssignStartingPlots:PlaceResourceImpact
    /// Place impact and ripple for resource on the map.
    ///
    /// We will place the resource impact on the tile and then place a ripple on all tiles within the radius.
    ///
    /// # Arguments
    ///
    /// - `tile`: the tile to place the resource impact on.
    /// - `layer`: the layer to place the resource impact and ripple on. `layer` should not be [`Layer::Civilization`].
    /// - `radius`: the radius of the ripple. The ripple will be placed on all tiles within this radius.
    ///
    /// # Panics
    ///
    /// Panics in debug mode if `layer` is [`Layer::Civilization`]. If you want to place impact and ripples on the civilization layer, use [`TileMap::place_impact_and_ripples_for_civilization`].
    ///
    /// # Notice
    ///
    /// Different with the original code, we use the same impact value `(99)` for all layers, but the behavior is the same.
    ///
    /// Different from the original code, [Layer::Fish]'s implementation varies, but the behavior is the same.
    fn place_impact_and_ripples_for_resource(&mut self, tile: Tile, layer: Layer, radius: u32) {
        debug_assert_ne!(
            layer,
            Layer::Civilization,
            "`place_impact_and_ripples_for_resource` should not be used for `Layer::Civilization`, use `place_impact_and_ripples_for_civilization` instead."
        );

        let grid = self.world_grid.grid;

        // Different with the original code, we use the same impact value for all layers, but the behavior is the same.
        // In the original code, when layer is Fish or Marble, the impact value is 1. When layer is other, the impact value is 99.
        let impact_value = 99;

        self.layer_data[layer][tile.index()] = impact_value;

        if radius == 0 {
            return;
        }

        if radius > 0 && radius < (grid.size.height / 2) {
            for distance in 1..=radius {
                // `distance` is the distance from the center tile to the current tile.
                // The larger the distance, the smaller the ripple value.
                let ripple_value = radius - distance + 1;
                // Iterate over all tiles at this distance.
                tile.tiles_at_distance(distance, grid)
                    .for_each(|tile_at_distance| {
                        // The current tile's ripple value.
                        let mut current_value = self.layer_data[layer][tile_at_distance.index()];
                        match layer {
                            // Different from the original code, Layer::Fish's implementation is the same as other resource layers, but the behavior is the same.
                            Layer::Strategic | Layer::Luxury | Layer::Bonus | Layer::NaturalWonder | Layer::Fish => {
                                if current_value != 0 {
                                    // First choose the greater of the two, existing value or current ripple.
                                    let stronger_value = max(current_value, ripple_value);
                                    // Now increase it by 2 to reflect that multiple civs are in range of this plot.
                                    let overlap_value = min(50, stronger_value + 2);
                                    current_value = overlap_value;
                                } else {
                                    current_value = ripple_value;
                                }
                            }
                            Layer::CityState | Layer::Marble => {
                                current_value = 1;
                            }
                            Layer::Civilization => {
                                unreachable!("Civilization layer should not be used in place_resource_impact function.");
                            }
                        }
                        // Update the layer data with the new value.
                        self.layer_data[layer][tile_at_distance.index()] = current_value;
                    })
            }
        }
    }

    // function AssignStartingPlots:AttemptToPlaceHillsAtPlot
    /// Attempts to place a Hill at the currently chosen tile.
    /// If successful, it returns `true`, otherwise it returns `false`.
    pub fn attempt_to_place_hill_at_tile(&mut self, tile: Tile) -> bool {
        if tile.resource(self).is_none()
            && tile.terrain_type(self) != TerrainType::Water
            && tile.feature(self) != Some(Feature::Forest)
            && !tile.has_river(self)
        {
            tile.set_terrain_type(self, TerrainType::Hill);
            tile.clear_feature(self);
            tile.clear_natural_wonder(self);
            true
        } else {
            false
        }
    }

    // function AssignStartingPlots:AttemptToPlaceBonusResourceAtPlot
    /// Attempts to place a Bonus Resource at the currently chosen tile.
    ///
    /// # Returns
    ///
    /// Returns a tuple of two booleans:
    ///
    /// - The first boolean is `true` if something was placed.
    /// - The second boolean is `true` as well if [`Feature::Oasis`] was placed.
    pub fn attempt_to_place_bonus_resource_at_tile(
        &mut self,
        tile: Tile,
        allow_oasis: bool,
    ) -> (bool, bool) {
        let terrain_type = tile.terrain_type(self);
        let base_terrain = tile.base_terrain(self);
        let feature = tile.feature(self);

        if tile.resource(self).is_none()
            && base_terrain != BaseTerrain::Snow
            && feature != Some(Feature::Oasis)
        {
            match terrain_type {
                TerrainType::Water => {
                    if base_terrain == BaseTerrain::Coast && feature.is_none() {
                        tile.set_resource(self, Resource::Fish, 1);
                        return (true, false);
                    }
                }
                TerrainType::Flatland => {
                    if feature.is_none() {
                        match base_terrain {
                            BaseTerrain::Grassland => {
                                tile.set_resource(self, Resource::Cattle, 1);
                                return (true, false);
                            }
                            BaseTerrain::Desert => {
                                if tile.is_freshwater(self) {
                                    tile.set_resource(self, Resource::Wheat, 1);
                                    return (true, false);
                                } else if allow_oasis {
                                    tile.set_feature(self, Feature::Oasis);
                                    return (true, true);
                                }
                            }
                            BaseTerrain::Plain => {
                                tile.set_resource(self, Resource::Wheat, 1);
                                return (true, false);
                            }
                            BaseTerrain::Tundra => {
                                tile.set_resource(self, Resource::Deer, 1);
                                return (true, false);
                            }
                            _ => {
                                unreachable!()
                            }
                        }
                    } else if feature == Some(Feature::Forest) {
                        tile.set_resource(self, Resource::Deer, 1);
                        return (true, false);
                    } else if feature == Some(Feature::Jungle) {
                        tile.set_resource(self, Resource::Bananas, 1);
                        return (true, false);
                    }
                }
                TerrainType::Mountain => (),
                TerrainType::Hill => {
                    if feature.is_none() {
                        tile.set_resource(self, Resource::Sheep, 1);
                        return (true, false);
                    } else if feature == Some(Feature::Forest) {
                        tile.set_resource(self, Resource::Deer, 1);
                        return (true, false);
                    } else if feature == Some(Feature::Jungle) {
                        tile.set_resource(self, Resource::Bananas, 1);
                        return (true, false);
                    }
                }
            }
        }
        // Nothing placed.
        (false, false)
    }

    // function AssignStartingPlots:PlaceSpecificNumberOfResources
    /// Places a specific number of resources on the map.
    ///
    /// Before calling this function, make sure `tile_list` has been shuffled.
    ///
    /// # Arguments
    ///
    /// - `quantity`: The number of every type resource that can be placed on the tile.\
    ///   For example, when placing `Horses`, `quantity` is 2, which means that the tile has 2 `Horses`.\
    ///   In CIV5, when resource is bonus or luxury, `quantity` is always 1;
    ///   When resource is strategic, `quantity` is usually determined by [`ResourceSetting`].
    /// - `amount`: The number of tiles intended to receive an assignment of this resource.
    /// - `ratio`: Determines when secondary and tertiary lists come in to play, should be in (0, 1].\
    ///   The num of tiles we will assign this resource is the minimum of `amount` and `(ratio * tile_list.len() as f64).ceil() as u32`.\
    ///   For example, if we are assigning Sugar resources to Marsh, then if we are to assign 8 Sugar
    ///   resources (`amount = 8`), but there are only 4 Marsh plots in the list (`tile_list.len() = 4`):
    ///     - `ratio = 1`, the num of tiles we will assign is 4, we would assign a Sugar to every single marsh plot, and then the function return an unplaced value of 4.
    ///     - `ratio = 0.5`, the num of tiles we will assign is 2, we would assign only 2 Sugars to the 4 marsh plots, and the function return a value of 6.
    ///     - `ratio <= 0.25`, the num of tiles we will assign is 1, we would assign 1 Sugar and return 7, as the ratio results will be rounded up not down, to the nearest integer.
    /// - `layer`: The layer we should tackle resource impact or ripple. If None, the resource can be placed on any tiles of `tile_list` that are not already assigned to a resource.
    /// - `min_radius` and `max_radius`: Related to `resource_impact` when we place resources on tiles.
    ///     - If `layer` is None, then `min_radius` and `max_radius` are ignored.
    ///     - If `layer` is not [`Layer::Strategic`], [`Layer::Luxury`], [`Layer::Bonus`], or [`Layer::Fish`], then `min_radius` and `max_radius` are ignored as well.
    /// - `tile_list`: The list of tiles that are candidates to place the resource on.
    ///
    /// # Returns
    ///
    /// - The number of resources that were not placed.
    ///   It is equal to `amount` minus the number of tiles that were assigned a resource.
    ///
    /// # Panics
    ///
    /// - `max_radius` must be greater than or equal to `min_radius`. Otherwise, the function will panic.
    #[allow(clippy::too_many_arguments)]
    pub fn place_specific_number_of_resources(
        &mut self,
        resource: Resource,
        quantity: u32,
        amount: u32,
        ratio: f64,
        layer: Option<Layer>,
        min_radius: u32,
        max_radius: u32,
        tile_list: &[Tile],
    ) -> u32 {
        debug_assert!(
            max_radius >= min_radius,
            "'max_radius' must be greater than or equal to 'min_radius'!"
        );

        if tile_list.is_empty() {
            return amount;
        }

        let has_impact = matches!(
            layer,
            Some(Layer::Strategic | Layer::Luxury | Layer::Bonus | Layer::Fish)
        );

        // Store how many resources are left to place
        let mut num_left_to_place = amount;

        // Calculate how many tiles is the candidates to place the resource on based on the ratio.
        // That means only a certain number of tiles in the `tile_list` will be assigned
        // If `ratio` is 1, then all tiles will be the candidates for assignment.
        // If `ratio` is less than 1, then the number of tiles to be the candidates is calculated
        let num_candidate_tiles = (ratio * tile_list.len() as f64).ceil() as u32;

        // `amount` is the number of tiles intended to receive an assignment of this resource.
        // `num_resources` is the maximum number of tiles that can receive an assignment of this resource.
        // `num_resources` is the minimum of `amount` and `num_candidate_tiles`.
        let num_resources = min(amount, num_candidate_tiles);

        for _ in 1..=num_resources {
            for &tile in tile_list.iter() {
                if !has_impact || self.layer_data[layer.unwrap()][tile.index()] == 0 {
                    // Place resource on tile if it doesn't have a resource already
                    if tile.resource(self).is_none() {
                        tile.set_resource(self, resource, quantity);
                        num_left_to_place -= 1;
                    }
                    // Place impact and ripples if `has_impact` is true
                    if has_impact {
                        let radius = self
                            .random_number_generator
                            .random_range(min_radius..=max_radius);
                        self.place_impact_and_ripples(tile, layer.unwrap(), radius)
                    }
                    break;
                }
            }
        }

        num_left_to_place
    }

    // AssignStartingPlots:GenerateLuxuryPlotListsAtCitySite
    /// Clear [`Feature::Ice`] from the map within a given radius of the city site.
    ///
    /// # Notice
    ///
    /// In the original code, `clear ice near city site` and `generate luxury plot lists at city site` are combined in one method.
    /// We have extracted the `generate luxury plot lists at city site` into a separate method.
    /// If you want to generate luxury plot lists at city site, you need to call [`TileMap::generate_luxury_tile_lists_at_city_site`].
    pub fn clear_ice_near_city_site(&mut self, city_site: Tile, radius: u32) {
        let grid = self.world_grid.grid;

        for ripple_radius in 1..=radius {
            city_site
                .tiles_at_distance(ripple_radius, grid)
                .for_each(|tile_at_distance| {
                    let feature = tile_at_distance.feature(self);
                    if feature == Some(Feature::Ice) {
                        tile_at_distance.clear_feature(self);
                    }
                })
        }
    }
}

// function AssignStartingPlots:GetMajorStrategicResourceQuantityValues
// TODO: This function should be implemented in future.
/// Determines the quantity per tile for each strategic resource's major deposit size.
///
/// # Notice
///
/// In some maps, If we cannot place oil in the sea, we should increase the resource amounts on land to compensate.
pub fn get_major_strategic_resource_quantity_values(
    resource_setting: ResourceSetting,
) -> (u32, u32, u32, u32, u32, u32) {
    let (uran_amt, horse_amt, oil_amt, iron_amt, coal_amt, alum_amt) = match resource_setting {
        ResourceSetting::Sparse => (2, 4, 5, 4, 5, 5),
        ResourceSetting::Abundant => (4, 6, 9, 9, 10, 10),
        _ => (4, 4, 7, 6, 7, 8), // Default
    };

    (uran_amt, horse_amt, oil_amt, iron_amt, coal_amt, alum_amt)
}

/// The `Layer` enum represents a layer associated with an element added to the map.
/// Each element is linked to a specific variant of the `Layer`.
///
/// The element can be a starting tile for a civilization, a city-state, a natural wonder, a marble, a resource, and more.
///
/// The `Layer` enum is used in [`TileMap::layer_data`]. For more information, see [`TileMap::layer_data`].
///
/// # How to add a new layer
/// For example, when you add an element `Stone` to the map, you want to ensure that no other elements appear around the element being added.
/// To do this, you need to add a new layer to the `Layer` enum. you need to:
/// 1. Add a new variant to the `Layer` enum. for example:
/// ```rust
/// # #[cfg(never)]
/// pub enum Layer {
///    Strategic,
///    Luxury,
///    // ... other existing variants
///    Stone,  // New variant added
/// }
/// ```
///
/// 2. Add a new case to [`TileMap::place_impact_and_ripples`] in the `TileMap` struct. This function is responsible for placing the impact of the element on the map and creating ripples if necessary.
/// ```rust
/// # #[cfg(never)]
/// pub fn place_impact_and_ripples(
///     &mut self,
///     map_parameters: &MapParameters,
///     tile: Tile,
///     layer: Layer,
///     radius: Option<u32>,
/// ) {
///     match layer {
///         // ... other existing cases
///         Layer::Stone => {
///             // ... implementation for the new layer
///         }
///     }
/// }
/// ```
///
/// 3. When you add a `Stone` to the map, you need to call [`TileMap::place_impact_and_ripples`] with the new layer.
///
#[derive(Enum, Clone, Copy, PartialEq, Eq, Debug)]
pub enum Layer {
    Strategic,
    Luxury,
    Bonus,
    Fish,
    CityState,
    NaturalWonder,
    Marble,
    Civilization,
}

/// Represents a river in the tile map.
pub type River = Vec<RiverEdge>;

/// Represents a river edge in the tile map.
/// Multiple consecutive `RiverEdge` can be used to represent a river.
///
/// Usually, we use [`River`] to represent a river.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RiverEdge {
    /// The position of the river edge in the tile map.
    pub tile: Tile,
    /// The flow direction of the river edge.
    pub flow_direction: Direction,
}

impl RiverEdge {
    /// Creates a new `RiverEdge` with the given tile and flow direction.
    pub fn new(tile: Tile, flow_direction: Direction) -> Self {
        Self {
            tile,
            flow_direction,
        }
    }

    /// Get the start and end corner directions of the river edge.
    ///
    /// According to the flow direction, we can determine which corners of the tile the river edge starts and ends at.
    ///
    /// # Returns
    ///
    /// Returns an array containing the start and end corner directions of the current tile.
    /// According to the start and end corners, we can draw the river edge on the current tile.
    pub fn start_and_end_corner_directions(&self, grid: HexGrid) -> [Direction; 2] {
        use {Direction::*, HexOrientation::*};

        // Match on both orientation and flow direction simultaneously
        match (grid.layout.orientation, self.flow_direction) {
            // Pointy-top orientation cases
            (Pointy, North) => [SouthEast, NorthEast], // North flow connects SE and NE corners
            (Pointy, NorthEast) => [South, SouthEast], // NE flow connects S and SE corners
            (Pointy, SouthEast) => [SouthWest, South], // SE flow connects SW and S corners
            (Pointy, South) => [NorthEast, SouthEast], // South flow connects NE and SE corners
            (Pointy, SouthWest) => [SouthEast, South], // SW flow connects SE and S corners
            (Pointy, NorthWest) => [South, SouthWest], // NW flow connects S and SW corners

            // Flat-top orientation cases
            (Flat, NorthEast) => [SouthEast, East], // NE flow connects SE and E corners
            (Flat, East) => [SouthWest, SouthEast], // E flow connects SW and SE corners
            (Flat, SouthEast) => [NorthEast, East], // SE flow connects NE and E corners
            (Flat, SouthWest) => [East, SouthEast], // SW flow connects E and SE corners
            (Flat, West) => [SouthEast, SouthWest], // W flow connects SE and SW corners
            (Flat, NorthWest) => [East, NorthEast], // NW flow connects E and NE corners

            // Invalid combinations - directions that don't exist in certain orientations
            (Pointy, East | West) | (Flat, North | South) => {
                panic!("Invalid flow direction for this hex orientation")
            }
        }
    }

    /// Gets the edge direction corresponding to the given flow direction in the current tile.
    ///
    /// According to the flow direction, we can determine which edge of the tile the river edge belongs to.
    ///
    /// # Returns
    ///
    /// Returns the edge direction corresponding to the given flow direction in the current tile.
    pub fn edge_direction(&self, grid: HexGrid) -> Direction {
        use {Direction::*, HexOrientation::*};

        match (grid.layout.orientation, self.flow_direction) {
            // Pointy orientation cases
            (Pointy, North | South) => East,
            (Pointy, NorthEast | SouthWest) => SouthEast,
            (Pointy, NorthWest | SouthEast) => SouthWest,

            // Flat orientation cases
            (Flat, NorthWest | SouthEast) => NorthEast,
            (Flat, NorthEast | SouthWest) => SouthEast,
            (Flat, East | West) => South,

            // Invalid combinations
            _ => panic!("Invalid flow direction for hex orientation"),
        }
    }
}