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
// std library
use std::cmp;
use std::collections::HashSet;
use std::collections::VecDeque;

// Extern crates
use rand::prelude::*;
use rand::{thread_rng, Rng};

use itertools::Itertools;

// Local modules
mod gridcell;
pub use gridcell::{AreaType, GridCell};

mod gridroom;
use gridroom::GridRoom;

// Need RouteMethod from rpgmap::route
use super::route::RouteMethod;

#[derive(Clone, Debug)]
pub struct GridMap {
    xmax  : usize,
    ymax  : usize,
    cells : Vec<Vec<GridCell>>,
}

impl GridMap {
    /// Make a new GridMap
    pub fn new(xmax: usize, ymax: usize) -> GridMap {
        GridMap {
            xmax,
            ymax,
            cells: vec![vec![GridCell::new(); ymax]; xmax],
        }
    }

    /// Returns size of map in (x, y) format
    pub fn get_limits(&self) -> (usize, usize) {
        (self.xmax, self.ymax)
    }

    pub fn get_cell_ref(&self, x: usize, y: usize) -> &GridCell {
        &self.cells[x][y]
    }

    /// Set the entrance at a particular location. The cell at this location
    /// will be marked as having the entrance area type. Typically this will
    /// be coloured differently on a map.
    pub fn place_entrance(&mut self, (x, y): (usize, usize)) {
        if x >= self.xmax || y >= self.ymax {
            panic!("Tried to place an entrance outside the bounds of the map");
        }

        self.cells[x][y].area = AreaType::Entrance;
    }

    /// Similar to place entrance, however it starts with the coordinates and
    /// finds the nearest spot that is already a "room". This allows entrances
    /// to be placed in non-deterministic generators, such as caves.
    pub fn place_entrance_near(&mut self, (x, y): (usize, usize)) -> Result<(), &'static str> {
        if x >= self.xmax || y >= self.ymax {
            return Err("asked for an entrance outside map boundaries");
        }

        let (x, y) = self
            .find_by((x, y), &|cell: &GridCell| -> bool { cell.is_room() })
            .unwrap();

        self.place_entrance((x, y));
        Ok(())
    }

    /// Set a room
    pub fn place_room(&mut self, (x0, y0): (usize, usize), (x1, y1): (usize, usize)) {
        // For lower bounds, note that 0 is implicit lower bound in usize
        let x_lower = cmp::min(x0, x1); // Calculate the lower bound of x
        let y_lower = cmp::min(y0, y1); // Calculate the lower bound of y
        let x_upper = cmp::min(self.xmax, cmp::max(x0, x1)); // Calculate the upper bound of x
        let y_upper = cmp::min(self.ymax, cmp::max(y0, y1)); // Calculate the upper bound of y

        for i in x_lower..x_upper + 1 {
            for j in y_lower..y_upper + 1 {
                if self.cells[i][j].area != AreaType::Entrance {
                    self.cells[i][j].area = AreaType::Room;
                }
            }
        }
    }

    /// Place a room by setting the origin and width/height
    ///
    /// Arguments:
    ///  *   (x, y) -> The origin of the room. This will be a corner.
    ///  *   (w, h) -> Width/Height of the room. Note, they're isize because
    ///               you can specify the w/h to be left OR right of the origin
    ///               and up OR down, respectively.
    pub fn place_room_dimensions(&mut self, (x, y): (usize, usize), (w, h): (isize, isize)) {
        let x_lower = if w >= 0 { x } else { x - w as usize };
        let y_lower = if h >= 0 { y } else { y - h as usize };
        let x_upper = if w >= 0 { x + w as usize } else { x };
        let y_upper = if h >= 0 { y + h as usize } else { y };

        self.place_room((x_lower, y_lower), (x_upper, y_upper));
    }

    /// Place a hallway between two points
    pub fn place_hallway(
        &mut self,
        (x0, y0): (usize, usize),
        (x1, y1): (usize, usize),
        route: RouteMethod,
    ) {
        let route_selected = match route {
            RouteMethod::HorizontalFirst => RouteMethod::HorizontalFirst,
            RouteMethod::VerticalFirst => RouteMethod::VerticalFirst,
            RouteMethod::Manhattan => {
                if rand::random::<bool>() {
                    RouteMethod::HorizontalFirst
                } else {
                    RouteMethod::VerticalFirst
                }
            }
        };

        match route_selected {
            RouteMethod::HorizontalFirst => {
                self.place_room((x0, y0), (x1, y0));
                self.place_room((x1, y0), (x1, y1));
            }
            RouteMethod::VerticalFirst => {
                self.place_room((x0, y0), (x0, y1));
                self.place_room((x0, y1), (x1, y1));
            }
            _ => panic!("Found unsupported route!"),
        };
    }

    /// Find the nearest connected cell to the cell specified
    fn find_nearest_connected(&self, (x, y): (usize, usize)) -> Option<(usize, usize)> {
        self.find_by((x, y), &|cell: &GridCell| -> bool { cell.is_room() })
    }

    /// Find a cell with an arbitrary condition. This function takes a starting
    /// point and searches for nearby cells that satisfy condition 'cond'. The
    /// condition is passed in in the form of a function that takes a gridcell
    /// and outputs a result containing a boolean stating whether the match has
    /// been made or not.
    fn find_by<F>(&self, (x, y): (usize, usize), cond: &F) -> Option<(usize, usize)>
    where
        F: Fn(&GridCell) -> bool,
    {
        let mut rooms = Vec::<(usize, usize)>::new();
        let mut found = false;
        let mut radius: usize = 0;

        while !found {
            radius += 1; // Increase search radius every loop
            let xmin = x.saturating_sub(radius); // Bounds xmin at 0
            let xmax = cmp::min(self.xmax - 1, x + radius); // Bounds xmax at self.xmax
            let ymin = y.saturating_sub(radius);
            let ymax = cmp::min(self.ymax - 1, y + radius);

            if xmin == 0 && ymin == 0 && xmax == self.xmax - 1 && ymax == self.ymax - 1 {
                // If this condition is true then we've searched the whole grid
                // and didn't find what we were looking for. Return None, which
                // indicates that no rooms were found.
                return None;
            }

            // Scan horizontal neighbours
            for i in xmin..xmax + 1 {
                if cond(&self.cells[i][ymin]) {
                    rooms.push((i, ymin));
                    found = true;
                }
                if cond(&self.cells[i][ymax]) {
                    rooms.push((i, ymax));
                    found = true;
                }
            }

            // Scan virtical neighbours
            for j in ymin..ymax + 1 {
                if cond(&self.cells[xmin][j]) {
                    rooms.push((xmin, j));
                    found = true;
                }
                if cond(&self.cells[xmax][j]) {
                    rooms.push((xmax, j));
                    found = true;
                }
            }
        }

        // Now pick a random room
        let mut rng = thread_rng();
        // If we found a room then we need to make a copy of the value
        // that's found there. x.choose() returns a reference and not
        // the value itself.
        if let Some(idx) = rooms.choose(&mut rng) {
            Some(*idx)
        } else {
            None
        }
    }

    /// Generate random cells with a biasing towards more/less rooms. Limit is a value
    /// between 1 and 100. This limit sets the chance that the cells are a room.
    /// Higher limit means fewer rooms.
    pub fn generate_random_cells(&mut self, limit: i64) {
        let mut rng = thread_rng();
        for i in 0..self.xmax {
            for j in 0..self.ymax {
                let val = rng.gen_range(1..100);
                if val > limit {
                    self.cells[i][j].area = AreaType::Room;
                } else {
                    self.cells[i][j].area = AreaType::Nothing;
                }
            }
        }
    }

    pub fn generate_annealed_random_cells(&mut self) {
        // Start by generating a random grid
        self.generate_random_cells(80);

        // Anneal by removing stragglers
        for i in 1..self.xmax {
            for j in 1..self.ymax {
                let alone = self.cells[i - 1][j].is_empty()
                    && self.cells[i][j - 1].is_empty()
                    && self.cells[i + 1][j].is_empty()
                    && self.cells[i][j + 1].is_empty();
                if alone {
                    self.cells[i][j].area = AreaType::Nothing;
                }
            }
        }
    }

    /// Place a randomly sized room of up to scale length or width.
    pub fn place_random_room(&mut self, scale: usize, connect: bool) {
        // Initialize a random number generator
        let mut rng = rand::thread_rng();

        // Generate size of the room
        let width = rng.gen_range(2..scale);
        let height = rng.gen_range(2..scale);

        // Generate the origin (location) of the room
        let x0 = rng.gen_range(1..self.xmax);
        let y0 = rng.gen_range(1..self.ymax);

        // See if we need to connect the room to an existing one.
        if connect {
            // Find the nearest connected location and return
            // the coordinates.
            let (x, y) = self
                .find_nearest_connected((x0, y0))
                .expect("no existing rooms to connect");
            // Drow the hallway; some of this will be overwritten by
            // the room placement below.
            self.place_hallway((x0, y0), (x, y), RouteMethod::Manhattan);
        }

        // Set x/y min/max while checking for overflows on either
        // the lower or upper bounds.
        let xmin = x0.saturating_sub(width / 2);
        let ymin = y0.saturating_sub(height / 2);
        let xmax = cmp::min(self.xmax - 1, x0 + width / 2);
        let ymax = cmp::min(self.ymax - 1, y0 + width / 2);

        self.place_room((xmin, ymin), (xmax, ymax));
    }

    pub fn generate_dungeon(&mut self, num_rooms: i64, room_size: i64) {
        for _ in 0..num_rooms {
            self.place_random_room(room_size as usize, false);
        }

        let mut rooms = self.partition_rooms();
        let mut distance: isize = 36;

        while rooms.len() > 1 {
            for rooms_combo in rooms.iter().combinations(2) {
                let room0 = rooms_combo[0];
                let room1 = rooms_combo[1];

                if room0 == room1 {
                    continue;
                }

                let (cell0, cell1) = room0.nearest_cells(room1).expect("finding nearest cells failed");

                if (cell0.0 as isize - cell1.0 as isize).pow(2) + (cell0.1 as isize - cell1.1 as isize).pow(2) < distance {
                    self.place_hallway(cell0, cell1, RouteMethod::Manhattan);
                }
            }

            rooms = self.partition_rooms();
            distance += 150;
        }

    }

    /// Determine if i, or j lies on an edge.
    fn on_edge(&self, i: usize, j: usize) -> bool {
        // Logic here is pretty simple: if either i or j is the max
        // value or 0 then they're on the edge of the map. All maps
        // are rectangular.
        i == 0 || i == self.xmax - 1 || j == 0 || j == self.ymax - 1
    }

    fn cave_anneal_cell(&self, i: usize, j: usize) -> bool {
        if !self.on_edge(i, j) {
            let mut neighbours = 0;

            // 3x3 grid
            for x in i - 1..i + 2 {
                for y in j - 1..j + 2 {
                    if self.cells[x][y].area == AreaType::Room {
                        neighbours += 1;
                    }
                }
            }

            // Whether we can anneal the cell
            neighbours >= 5
        } else {
            false
        }
    }

    fn generate_cave_iteration(&mut self) {
        let mut tmp_map = self.cells.to_vec();

        for i in 0..self.xmax {
            for j in 0..self.ymax {
                if self.cave_anneal_cell(i, j) {
                    tmp_map[i][j].area = AreaType::Room;
                } else {
                    tmp_map[i][j].area = AreaType::Nothing;
                }
            }
        }

        self.cells = tmp_map;
    }

    /// Clear away small, unattached rooms.
    fn remove_orphans(&mut self) {
        let mut size;
        for i in 0..self.xmax {
            for j in 0..self.ymax {
                size = self.get_room_size(i, j);
                if size < 15 {
                    self.clear_room(i, j);
                }
            }
        }
    }

    fn get_room_size(&self, i: usize, j: usize) -> u64 {
        // Output value
        let mut size = 0;

        // Process all of the points in a list. Doing this in an iterative fashion because
        // of max recursion limits.
        let mut proc_queue = VecDeque::new();
        proc_queue.push_back((i, j));

        // Mask to make sure we don't revisit rooms
        let mut visited = vec![false; (self.xmax) * (self.ymax)];

        while !proc_queue.is_empty() {
            // Remove a room from the queue and unpack the values
            let (x, y) = proc_queue.pop_front().unwrap();

            // If we have visited this cell before or it is not a "room" then
            // we should stop processing and move on to the next
            if visited[x * self.xmax + y] {
                continue;
            } else if self.cells[x][y].area != AreaType::Room {
                continue;
            }

            // We got here so it's a room that we haven't visited. Mark it as visited now
            visited[x * self.xmax + y] = true;

            // This is a room and we haven't visited, so add one to size.
            size += 1;

            // Add all neighbours to the queue
            proc_queue.push_back((x - 1, y));
            proc_queue.push_back((x + 1, y));
            proc_queue.push_back((x, y - 1));
            proc_queue.push_back((x, y + 1));
        }
        // Returning the full size of the room
        size
    }

    fn clear_room(&mut self, x: usize, y: usize) {
        if self.cells[x][y].area == AreaType::Nothing {
            return;
        }

        let mut proc_queue = VecDeque::new();
        proc_queue.push_back((x, y));

        while !proc_queue.is_empty() {
            let (i, j) = proc_queue.pop_front().unwrap();

            if self.cells[i][j].area == AreaType::Nothing {
                continue;
            }

            self.cells[i][j].area = AreaType::Nothing;
            proc_queue.push_back((i + 1, j));
            proc_queue.push_back((i - 1, j));
            proc_queue.push_back((i, j + 1));
            proc_queue.push_back((i, j - 1));
        }
    }

    pub fn generate_cave(&mut self, iter: i64, seed_limit: i64) {
        // Makes a random selection of cells
        self.generate_random_cells(seed_limit);

        // Anneal the cells into blobs
        for _ in 0..iter {
            self.generate_cave_iteration();
        }

        // Get rid of small caves; reduces visual noise
        self.remove_orphans();

        // Connect caves together
        let caves = self.partition_rooms();

        // So many n^2 algos :-(
        for caves_combo in caves.iter().combinations(2) {
            let cave = caves_combo[0];
            let cave2 = caves_combo[1];

            let (cell1, cell2) = cave.nearest_cells(cave2).expect("finding nearest cells failed");

            if (cell1.0 as isize - cell2.0 as isize).pow(2) + (cell1.1 as isize - cell2.1 as isize).pow(2) < 36 {
                self.place_hallway(cell1, cell2, RouteMethod::Manhattan);
            }
        }
    }

    fn partition_rooms(&self) -> Vec<GridRoom> {
        self.partition_spaces(false)
    }

    /// Partition the map into groups of cells, called Rooms.
    ///
    /// The 'rooms' are just collections of cells of the same type, so there is
    /// at least one room that contains the walls/Nothing cells. These rooms
    /// can then be used for path processing or connectivity testing.
    fn partition_spaces(&self, include_nothing: bool) -> Vec<GridRoom> {
        let mut out = Vec::new();

        // Make an set of the unvisited cells. Use this for finding new
        // locations
        let mut unvisited = HashSet::<(usize, usize)>::with_capacity(self.xmax * self.ymax);
        for i in 0..self.xmax {
            for j in 0..self.ymax {
                unvisited.insert((i, j));
            }
        }

        // Now keep looping until we've covered ever cell in the map and found
        // all of the rooms
        while !unvisited.is_empty() {
            // Each time, we start with an index; we don't really care what
            // it is so the unordered hashset works well here.
            let first_index = unvisited.iter().next().unwrap();
            let mut x = first_index.0;
            let mut y = first_index.1;
            let this_area_type = &self.cells[x][y].area;

            // This is going to be a 'room' (which includes contiguous AreaType::Nothing
            // spaces). Make a new one here that we're going to build up.
            let mut room = GridRoom::new();

            // Need a queue for the flood algorithm. We're going to keep adding
            // to this queue until we run out of spaces to add to it.
            let mut proc_queue = VecDeque::new();
            // Our initial seed is the starting index that we got from 'unvisited'
            proc_queue.push_back((x, y));

            // The queue works on the concept that we can keep adding to it
            // whenever we find a new cell of our room but that we don't when
            // the new cell is not a part of our room. We flood from the initial
            // cell, meaning that we start at the starting index and then add
            // the index at the above/below/left/right of that cell. In the beginning
            // this fans out a lot, with massive expansion of the queue, but as
            // we begin to run into walls then we stop adding to the queue and
            // it begins to drain.
            while !proc_queue.is_empty() {
                // pop_front makes this a FIFO. Doesn't really matter, we could
                // have done it another way.
                let index = proc_queue.pop_front().unwrap();
                x = index.0;
                y = index.1;

                if x >= self.xmax || y >= self.ymax {
                    // If the value is too big then just continue. The value
                    // has already been removed from the queue and it doesn't
                    // need to be removed from unvisited since it's outside
                    // the map area.
                    continue;
                }

                if self.cells[x][y].area != *this_area_type {
                    // Check that the cell is the correct type. If it is, then continue
                    // with processing it, otherwise don't remove it from the unvisited
                    // list (since we still might need to visit it).
                    continue;
                }

                if !unvisited.remove(&index) {
                    // HashSet.remove() returns a bool that's true if the value
                    // was in the set. In this case that tells us if we've been
                    // here before. If we have, then don't do any further processing.
                    continue;
                }

                // If we've got here then the cell is a part of our room because
                // it has the correct type and we haven't processed it yet. Add
                // it too our room and then add the immediate nearest neighbours
                // to our processing queue.
                room.add_cell(&index).expect("failed to add cell");
                proc_queue.push_back((x + 1, y));
                proc_queue.push_back((x.saturating_sub(1), y));
                proc_queue.push_back((x, y + 1));
                proc_queue.push_back((x, y.saturating_sub(1)));
            }
            // The room is now complete; add it to our output vector and forget
            // about this particular room.
            if *this_area_type != AreaType::Nothing || include_nothing {
                out.push(room);
            }
        }

        // Room processing is done. Return.
        out
    }
}