#include "Maze.h" #include #include #include #include #include #include #include #include void ext::Maze::initialize(int columns, int rows, int floors, double horizontal_bias, double vertical_bias) { /* The Maze class contructor * Input: * int columns - Set the number of columns in the maze. Correspond to the X axis (length). * int rows - Set the number of rows in the maze. Correspond to the Y axis (width). * int floors - Set the number of floors in the maze. Correspond to the Z axis (height). * * All three must be larger than 1. * * double horizontal_bias - Sets the likelihood of a passage between rooms on the same row. * double vertical_bias - Sets the likelihood of a passage between rooms in the same column. * * Both must be between 0 and 1 exclusive. * Higher number gives higher likelihood of a passage. * * Output: * Maze object */ if (rows < 1 || columns < 1 || floors < 1){ std::cout << "A maze must have dimensions greater than zero.\n"; throw std::invalid_argument("A maze must have dimensions greater than zero.\n"); } LENGTH = columns; WIDTH = rows; HEIGHT = floors; if (horizontal_bias <= 0 || horizontal_bias >= 1 || vertical_bias <= 0 || vertical_bias >= 1){ std::cout << "Biases must be between 0 and 1 exclusive.\n"; throw std::invalid_argument("Biases must be between 0 and 1 exclusive.\n"); } EAST_WALL_THRESHOLD = horizontal_bias; SOUTH_WALL_THRESHOLD = vertical_bias; //Creating a vector of all rooms. cells.assign(LENGTH*WIDTH*HEIGHT, -1); // seed seed = time(NULL); // Give each room a different set number (ascending). // (Which is used during maze generation) std::iota(std::begin(cells), std::end(cells), 0); }; int ext::Maze::get(int i, int col, int floor){ // Gets the value stored at row i, column j, level k, in cells; return cells.at(col + LENGTH*i + LENGTH*WIDTH*floor); } void ext::Maze::set(int row, int col, int floor, int val){ // Set the value of cell at (row, col, floor) to val cells[col + LENGTH*row + LENGTH*WIDTH*floor] = val; return; } void ext::Maze::build(){ /* This function generates a maze. * * It uses sets to keep track of areas created by removing walls. * Initially each room has it's own set, known by it's room value. * The room sets grows as walls are removed and sets joined. * To keep track, each room value is mapped to itself, or the room value it has joined. * The mapping and sets are reset at each floor. * * Summary: * It goes through each cell, removing the walls randomly. * (First WEST to EAST, then NORTH to SOUTH, then DOWN to UP. * Just as one write in English, putting each new page on top of the other) * If a wall is between two rooms in the same set, it is not removed. * * Each room value denote which set it belongs to * * Each wall removed is stored in as a passage between the two rooms. * (The rooms position is stored in ext::Maze::passages) * * If the current room value is not mapped, then map it to itself. * Set current room value to it's mapped value. * * Add the room to the set given by room value * * If a EASTERN wall is removed, then: * - The set of the connected room is added to the set of this room. * - The set of the connected room is deleted. * - Map the connected room value to the current room value. * - Change the connected room value to the current room value. * - Add connected room to set. * * If a SOUTHERN wall is removed, then: * - Change the connected room value to the current room value. * - Add the connected room to set. * * After all the rooms of the current floor has been visited: * - Go through each set, pick a random room, and make a passage up. * - Clear the sets, and mapping. * * On the last floor, each set on each row must have a passage south. * * On the last row on the last floor, passages between unconnected sets are removed. * * Finally each room value is set according to which walls remain. (Including floor and ceiling) */ // The room sets std::map> room_set; // The mapping of connected rooms std::map merged_room_sets; int room_value; int east_room_value; int south_room_value; // To get different mazes each time srand(seed); for (int floor = 0; floor < HEIGHT - 1; floor++){ // Go through each floor but the last. for (int row = 0; row < WIDTH; row++){ // Go through each row for(int col = 0; col < LENGTH; col++){ // Go through each element in row (Each column) room_value = get(row, col, floor); // If this room value is mapped to another, get that value: else map value to itself if(merged_room_sets.find(room_value) != merged_room_sets.end()){ room_value = merged_room_sets[room_value]; } else { merged_room_sets[room_value] = room_value; } // If not the last cell in row, store eastern room value if (col next_room_position_set = room_set[get(row, col+1, floor)]; // Add set of eastern room to this set. room_set[room_value].insert(room_set[room_value].end(), next_room_position_set.begin(), next_room_position_set.end() ); // Remove eastern room set room_set.erase(get(row, col+1, floor)); // Map eastern room value to this room value. merged_room_sets[get(row,col+1,floor)] = room_value; // Set eastern room value to this set(row, col+1, floor, room_value); // Add the new horizontal passage created. passages.push_back(std::make_tuple(row, col, floor, row, col+1, floor)); } // Else the eastern wall remain // Don't remove southern boundary wall. if(row == WIDTH - 1){} // If current and southern room is not in same set, then maybe remove wall else if(rand() < SOUTH_WALL_THRESHOLD * RAND_MAX && room_value != south_room_value){ // Set southern room value to this one set(row+1, col, floor, room_value); // Add the newly created vertical passage. passages.push_back(std::make_tuple(row, col, floor, row+1, col, floor)); } //Else wall down remain } // All rooms in this row has been visited } // All rows on this floor has been visited // Clear room set mapping merged_room_sets.clear(); // Go through all sets this floor for(auto entry : room_set) { auto group = entry.second; int x, y, z; // Picking out a random room in each group // Room position saved in matrix notation (i, j, k = y, x, z) std::tie(y, x, z) = group.at(rand() % group.size()); // Only one passage up for each group. To avoid graph cycles passages.push_back(std::make_tuple(y, x, z, y, x, z+1)); } // Passages up have been set. Clearing room groups for next floor. room_set.clear(); }// Finished with all but last floor int floor = HEIGHT - 1; std::set can_go_south; //deal with last floor, but leave the last row for(int row = 0; row < WIDTH - 1; row++){ for(int col = 0; col