termaze

maze generation and pathfinding visualizer
git clone git://git.yotsev.xyz/termaze.git
Log | Files | Refs | README | LICENSE

commit fe9a71d7f5564df0e610fa7054469e53b7309f75
Author: Petar Yotsev <petar@yotsev.xyz>
Date:   Tue, 16 Jun 2020 10:57:27 +0100

Initial commit

Diffstat:
ALICENSE | 339+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
AMakefile | 41+++++++++++++++++++++++++++++++++++++++++
AREADME.md | 44++++++++++++++++++++++++++++++++++++++++++++
Acolors.hpp | 17+++++++++++++++++
Amain.cpp | 309+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Amaze_gen.cpp | 829+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Amaze_gen.hpp | 36++++++++++++++++++++++++++++++++++++
Apathfinding.cpp | 415+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apathfinding.hpp | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Astate.hpp | 37+++++++++++++++++++++++++++++++++++++
Atimer.cpp | 33+++++++++++++++++++++++++++++++++
Atimer.hpp | 21+++++++++++++++++++++
Aui.cpp | 342+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aui.hpp | 18++++++++++++++++++
Avec2.hpp | 143+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
15 files changed, 2675 insertions(+), 0 deletions(-)

diff --git a/LICENSE b/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/Makefile b/Makefile @@ -0,0 +1,41 @@ +# WITH FLAGS +CC = g++ +CC_FLAGS = -lpanel -lncurses -std=c++11 -pthread -O2 +EXEC = termaze + + +$(EXEC): main.o ui.o maze_gen.o pathfinding.o + $(CC) $(CC_FLAGS) main.o maze_gen.o pathfinding.o ui.o timer.o -o $(EXEC) + + +main.o: main.cpp maze_gen.o pathfinding.o ui.o timer.o state.hpp vec2.hpp colors.hpp CLI11.hpp + $(CC) -c $(CC_FLAGS) maze_gen.o pathfinding.o ui.o main.cpp + +ui.o: ui.cpp maze_gen.o pathfinding.o state.hpp colors.hpp + $(CC) -c $(CC_FLAGS) maze_gen.o pathfinding.o ui.cpp + +maze_gen.o: maze_gen.cpp vec2.hpp timer.o state.hpp colors.hpp + $(CC) -c $(CC_FLAGS) maze_gen.cpp + +pathfinding.o: pathfinding.cpp timer.o vec2.hpp state.hpp colors.hpp + $(CC) -c $(CC_FLAGS) pathfinding.cpp + +timer.o: timer.cpp state.hpp + $(CC) -c timer.cpp + +# to do: figure out hot to use precompiled heeaders + +# state.hpp: state.hpp +# $(CC) -c state.hpp + +# vec2.hpp: vec2.hpp +# $(CC) -c vec2.hpp + +# colors.hpp: colors.hpp +# $(CC) -c colors.hpp + +# CLI11.hpp: CLI11.hpp +# $(CC) -c CLI11.hpp + +clean: + rm -f $(EXEC) *.o *.gch diff --git a/README.md b/README.md @@ -0,0 +1,44 @@ +# termaze + +--- + +A program that visualizes maze generation and pathfinding algorithms written in +C++. It's designed with minimal dependencies and performance in mind. It runs in +the terminal. + +## Features + +* Maze generation algorithms + * [X] Recursive backtracker + * [ ] Recursive divider + * [X] Recursive divider - flood fill version + * [X] Modified Kruskal's algorithm + * [X] Modified Prim's algorithm + * [ ] Modified Prim's algorithm - weighted graph + * [ ] Growing tree + * [X] Wilson's random walk algorithm +* Pathfinding algorithms + * [X] A* + * [X] Dijkstra's + * [X] Breadth first search + * [X] Depth first search + +## Usage + + ./termaze + +## Dependencies + +* ncurses + +You probably already have it if you use linux. + +## Installation + + git clone https://git.sr.ht/~petar-yotsev/termaze + cd termaze + make + +## License + +GPLv2 diff --git a/colors.hpp b/colors.hpp @@ -0,0 +1,17 @@ +#pragma once + +#ifndef COLORS_HPP +#define COLORS_HPP + +enum color { + black = 1, + red, + green, + yellow, + blue, + purple, + cyan, + white +}; + +#endif //COLORS_HPP diff --git a/main.cpp b/main.cpp @@ -0,0 +1,309 @@ +#include <chrono> +#include <iostream> +#include <map> +#include <ncurses.h> +#include <string> +#include <thread> +#include <time.h> +#include <unistd.h> + +/* #include "CLI11.hpp" */ +#include "colors.hpp" +#include "maze_gen.hpp" +#include "pathfinding.hpp" +#include "state.hpp" +#include "timer.hpp" +#include "ui.hpp" +#include "vec2.hpp" + +unsigned long mix(unsigned long a, unsigned long b, unsigned long c) +{ + a = a - b; + a = a - c; + a = a ^ (c >> 13); + b = b - c; + b = b - a; + b = b ^ (a << 8); + c = c - a; + c = c - b; + c = c ^ (b >> 13); + a = a - b; + a = a - c; + a = a ^ (c >> 12); + b = b - c; + b = b - a; + b = b ^ (a << 16); + c = c - a; + c = c - b; + c = c ^ (b >> 5); + a = a - b; + a = a - c; + a = a ^ (c >> 3); + b = b - c; + b = b - a; + b = b ^ (a << 10); + c = c - a; + c = c - b; + c = c ^ (b >> 15); + return c; +} + +struct node { + vec2<int> pos; + node* parent = nullptr; + std::vector<node*> neighbors; + bool isVisited = false; + int g = 0; + // adds valid neighbors + void AddNeighbors( + const bool* field, + node* grid, + const int& width, + const int& height) + { + neighbors.clear(); + // up + if (pos.y > 2 && field[(pos.y - 1) * width + pos.x]) + neighbors.push_back(&grid[(pos.y - 2) * width + pos.x]); + // down + if (pos.y < height - 2 && field[(pos.y + 1) * width + pos.x]) + neighbors.push_back(&grid[(pos.y + 2) * width + pos.x]); + // left + if (pos.x > 2 && field[(pos.y * width) + pos.x - 1]) + neighbors.push_back(&grid[(pos.y * width) + pos.x - 2]); + // right + if (pos.x < width - 2 && field[(pos.y * width) + pos.x + 1]) + neighbors.push_back(&grid[(pos.y * width) + pos.x + 2]); + } +}; + +void GetGridMap(const bool* field, node* grid, const int& width, const int& height, const vec2<int>& start) +{ + // initializing the grid of nodes used for pointer locations + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + grid[(y * width) + x].pos.x = x; + grid[(y * width) + x].pos.y = y; + grid[(y * width) + x].isVisited = false; + grid[(y * width) + x].parent = nullptr; + grid[(y * width) + x].g = 0; + } + } + // initializes the open set with the start pos + std::queue<node*> q; + q.push(&grid[start.y * width + start.x]); + node* current = q.front(); + while (!q.empty()) { + current = q.front(); + current->isVisited = true; + q.pop(); + current->AddNeighbors(field, grid, width, height); + for (node* n : current->neighbors) { + if (!n->isVisited) { + n->parent = current; + n->g = current->g + 1; + q.push(n); + } + } + } +} + +// initializes the state class that holds the configuration of the program +state& s = state::GetInstance(); + +int main(int argc, char** argv) +{ + // handling of command line arguments + /* CLI::App app; */ + /* app.add_option("-p,--pathfinder", s.pathfinder, "Select a pathfinder"); */ + /* app.add_option("-g,--generator", s.generator, "Select a generator"); */ + /* CLI11_PARSE(app, argc, argv); */ + + // setting a suitable seed for the random number generator + srand(mix(clock(), time(NULL), getpid())); + + // ncurses setup + initscr(); + cbreak(); + noecho(); + curs_set(0); + if (!has_colors()) { + endwin(); + printf("Your terminal does not support color!\n"); + return -1; + } + if (!can_change_color()) { + endwin(); + printf("Your terminal does not support redefining of colors!\n"); + return -1; + } + start_color(); + // COLOR_PAIR(n) + // 0 - COLOR_BLACK + // 1 - COLOR_RED + // 2 - COLOR_GREEN + // 3 - COLOR_YELLOW + // 4 - COLOR_BLUE + // 5 - COLOR_MAGENTA + // 6 - COLOR_CYAN + // 7 - COLOR_WHITE + init_color(COLOR_BLACK, 250, 250, 250); + init_color(COLOR_RED, 999, 300, 300); + init_color(COLOR_GREEN, 400, 999, 400); + init_color(COLOR_YELLOW, 999, 999, 400); + init_color(COLOR_BLUE, 15, 60, 870); + init_color(COLOR_MAGENTA, 650, 350, 650); + init_color(COLOR_CYAN, 200, 800, 800); + init_color(COLOR_WHITE, 999, 999, 999); + init_pair(black, COLOR_WHITE, COLOR_BLACK); + init_pair(red, COLOR_BLACK, COLOR_RED); + init_pair(green, COLOR_BLACK, COLOR_GREEN); + init_pair(yellow, COLOR_BLACK, COLOR_YELLOW); + init_pair(blue, COLOR_BLACK, COLOR_BLUE); + init_pair(purple, COLOR_BLACK, COLOR_MAGENTA); // not magenta, fix it | to do + init_pair(cyan, COLOR_BLACK, COLOR_CYAN); + init_pair(white, COLOR_BLACK, COLOR_WHITE); + + // declares variables used in the main loop to save memory allocations + vec2<int> start; + std::vector<vec2<int>> exits; + std::vector<vec2<int>> viableExits; + std::vector<vec2<int>> path; + + // starts a daemon that handles user input + std::thread KeyUpdate(ui::ExecuteKeys); + + while (true) { + // setting suitable width and height (odd) + int height = LINES; + int width = (COLS / 2); + if (height % 2 == 0) + --height; + if (width % 2 == 0) + --width; + + // initializing the binary field + bool field[width * height]; + for (int i = 0; i < width * height; ++i) + field[i] = false; + + // setting the background to white + s.draw.lock(); + attron(COLOR_PAIR(white)); + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + mvprintw(y, x * 2, " "); + } + } + attroff(COLOR_PAIR(white)); + refresh(); + s.draw.unlock(); + + // generates the maze and renders it + generators[s.generator](field, width, height); + + // gets the length of the longest path in the maze + start = vec2<int>((std::rand() % ((width - 1) / 2)) * 2 + 1, + (std::rand() % ((height - 1) / 2)) * 2 + 1); + node grid[width * height]; + GetGridMap(field, grid, width, height, start); + { + node* current = &grid[0]; + for (int y = 1; y < height; y += 2) { + for (int x = 1; x < width; x += 2) { + if (grid[(y * width) + x].g > current->g) + current = &grid[(y * width) + x]; + } + } + start = current->pos; + } + GetGridMap(field, grid, width, height, start); + int longestPathLength; + { + node* current = &grid[0]; + for (int y = 1; y < height; y += 2) { + for (int x = 1; x < width; x += 2) { + if (grid[(y * width) + x].g > current->g) + current = &grid[(y * width) + x]; + } + } + std::vector<vec2<int>> path; + node* previous = current; + do { + path.push_back(previous->pos); + previous = previous->parent; + } while (previous != nullptr); + longestPathLength = path.size(); + } + + // sets random start + start = vec2<int>((std::rand() % ((width - 1) / 2)) * 2 + 1, + (std::rand() % ((height - 1) / 2)) * 2 + 1); + + // draws random start + s.draw.lock(); + attron(COLOR_PAIR(green)); + mvprintw(start.y, start.x * 2, " "); + attroff(COLOR_PAIR(green)); + refresh(); + s.draw.unlock(); + + // sets random exit(s) + exits.clear(); + viableExits.clear(); + GetGridMap(field, grid, width, height, start); + s.draw.lock(); + attron(COLOR_PAIR(yellow)); + for (int y = 1; y < height; y += 2) { + for (int x = 1; x < width; x += 2) { + if (grid[(y * width) + x].g > (longestPathLength / 3)) { + viableExits.push_back(grid[y * width + x].pos); + /* mvprintw(grid[y * width + x].pos.y, grid[y * width + x].pos.x * 2, " "); */ + } + } + } + attroff(COLOR_PAIR(yellow)); + refresh(); + s.draw.unlock(); + for (int i = 0; i < 2; ++i) { + exits.push_back(viableExits[std::rand() % viableExits.size()]); + } + + // draws random exit(s) + s.draw.lock(); + attron(COLOR_PAIR(red)); + for (vec2<int> e : exits) { + mvprintw(e.y, e.x * 2, " "); + } + attroff(COLOR_PAIR(red)); + refresh(); + s.draw.unlock(); + + // gets the final path and renders the pathfinding + path = pathfinders[s.pathfinder](field, width, height, start, exits); + + s.draw.lock(); + refresh(); + s.draw.unlock(); + + // draws the final path + timer cycle; + vec2<int>* prev = &path.front(); + for (int i = 1; i < path.size(); ++i) { + s.draw.lock(); + attron(COLOR_PAIR(yellow)); + if (i != path.size() - 1) + mvprintw(path[i].y, path[i].x * 2, " "); + mvprintw((prev->y + path[i].y) / 2, (prev->x + path[i].x), " "); + attroff(COLOR_PAIR(yellow)); + refresh(); + s.draw.unlock(); + prev = &path[i]; + cycle.WaitInterval(s.time_path); + } + + cycle.WaitInterval(s.time_main); + } + endwin(); + return 0; +} diff --git a/maze_gen.cpp b/maze_gen.cpp @@ -0,0 +1,829 @@ +#include <cstdlib> +#include <exception> +#include <ncurses.h> + +#include <algorithm> +#include <functional> +#include <map> +#include <random> +#include <set> +#include <stack> +#include <thread> +#include <vector> + +#include "colors.hpp" +#include "maze_gen.hpp" +#include "state.hpp" +#include "timer.hpp" +#include "vec2.hpp" + +void maze_gen::Backtracker(bool* field, const int& width, const int& height) +{ + enum dir { + up, + down, + left, + right + }; + + // setting the coordinates of each cell according to their placement in the + // array because the contents of the cells will map to their locations in + // memory making addressing easier + vec2<int> grid[width * height]; + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + grid[(y * width) + x].y = y; + grid[(y * width) + x].x = x; + } + } + std::stack<vec2<int>*> cells; + std::vector<dir> dirs; + timer cycle; + + auto offset = [&](int x, int y) { + return ((cells.top()->y + y) * width) + cells.top()->x + x; + }; + auto drawoffset = [&](int x, int y) { + s.draw.lock(); + mvprintw(cells.top()->y + y, (cells.top()->x + x) * 2, " "); + s.draw.unlock(); + }; + + // setting a random root position + // because the mazes look more interesting when having irregular roots + cells.push(&grid[(((std::rand() % (height / 2)) * 2 + 1) * width) + + ((std::rand() % (width / 2)) * 2) + 1]); + field[offset(0, 0)] = true; + drawoffset(0, 0); + + while (!cells.empty()) { + // checking for viable directions + dirs.clear(); + if (cells.top()->y > 1 && !field[offset(0, -2)]) + dirs.emplace_back(up); + if (cells.top()->y < height - 2 && !field[offset(0, 2)]) + dirs.emplace_back(down); + if (cells.top()->x > 1 && !field[offset(-2, 0)]) + dirs.emplace_back(left); + if (cells.top()->x < width - 2 && !field[offset(2, 0)]) + dirs.emplace_back(right); + + // picks a random viable direction and carves a path two cells in that + // direction + if (!dirs.empty()) { + shuffle(dirs.begin(), dirs.end(), + std::default_random_engine(std::rand())); + switch (dirs[std::rand() % dirs.size()]) { + case up: + field[offset(0, -1)] = true; + field[offset(0, -2)] = true; + drawoffset(0, -1); + drawoffset(0, -2); + cells.push(&grid[offset(0, -2)]); + break; + case down: + field[offset(0, 1)] = true; + field[offset(0, 2)] = true; + drawoffset(0, 1); + drawoffset(0, 2); + cells.push(&grid[offset(0, 2)]); + break; + case left: + field[offset(-1, 0)] = true; + field[offset(-2, 0)] = true; + drawoffset(-1, 0); + drawoffset(-2, 0); + cells.push(&grid[offset(-2, 0)]); + break; + case right: + field[offset(1, 0)] = true; + field[offset(2, 0)] = true; + drawoffset(1, 0); + drawoffset(2, 0); + cells.push(&grid[offset(2, 0)]); + break; + } + } else { + // if there are no viable directions it backtracks + cells.pop(); + } + if (s.render_maze_gen) { + s.draw.lock(); + refresh(); + s.draw.unlock(); + cycle.WaitInterval(s.time_hard_maze_gen); + } + } +} + +void maze_gen::Kruskal(bool* field, const int& width, const int& height) +{ + struct cell { + vec2<int> pos; + cell* parent = nullptr; + }; + struct edge { + cell* primary = nullptr; + cell* secondary = nullptr; + } current; + + // setting the coordinates of each cell according to their placement in the + // array because the contents of the cells will map to their locations in + // memory making addressing easier + cell grid[width * height]; + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + grid[(y * width) + x].pos.x = x; + grid[(y * width) + x].pos.y = y; + } + } + + // initializing the list of edges + std::vector<edge> edges; + for (int y = 1; y < height - 1; y += 2) { + for (int x = 1; x < width - 1; x += 2) { + // initializing horizontal edges + if (x < width - 3) { + current.primary = &grid[(y * width) + x]; + current.secondary = &grid[(y * width) + x + 2]; + edges.push_back(current); + } + // initializing vertical edges + if (y < height - 3) { + current.primary = &grid[(y * width) + x]; + current.secondary = &grid[((y + 2) * width) + x]; + edges.push_back(current); + } + // setting the squares where it's guarantied to always be a path + // here instead of later when the path is being carved to prevent + // multiple unnecessary changes to the path when edges of the same + // cell are being evaluated + field[(y * width) + x] = true; + } + } + + shuffle(edges.begin(), edges.end(), + std::default_random_engine(std::rand())); + + // initializing the roots for set identification of cells + cell* pRoot; // primary cell root + cell* sRoot; // secondary cell root + cell* prev; + vec2<int> edgePos; + timer cycle; + + attron(COLOR_PAIR(black)); + for (edge e : edges) { + // finds the root of the primary cell + prev = e.primary; + while (prev->parent != nullptr) { + prev = prev->parent; + } + // if the cell has no root it sets the root to itself + if (prev == nullptr) { + pRoot = e.primary; + } else { + pRoot = prev; + } + + // finds the root of the secondary cell + prev = e.secondary; + while (prev->parent != nullptr) { + prev = prev->parent; + } + // if the cell has no root it sets the root to itself + if (prev == nullptr) { + sRoot = e.secondary; + } else { + sRoot = prev; + } + + // checks if the primary cell and the secondary cell are part of the + // same tree if not it joins the two trees until there is only one tree + // left (the maze) + if (pRoot != sRoot) { + edgePos = (e.primary + (e.secondary - e.primary) / 2)->pos; + field[(edgePos.y * width) + edgePos.x] = true; + + s.draw.lock(); + mvprintw(e.primary->pos.y, e.primary->pos.x * 2, " "); + mvprintw(e.secondary->pos.y, e.secondary->pos.x * 2, " "); + mvprintw(edgePos.y, edgePos.x * 2, " "); + + // need to add meta data option to state class + mvprintw(sRoot->pos.y, sRoot->pos.x * 2, " "); + sRoot->parent = pRoot; + attron(COLOR_PAIR(red)); + mvprintw(pRoot->pos.y, pRoot->pos.x * 2, " "); + attroff(COLOR_PAIR(red)); + s.draw.unlock(); + + if (s.render_maze_gen) { + s.draw.lock(); + refresh(); + s.draw.unlock(); + cycle.WaitInterval(s.time_hard_maze_gen); + } + } + } + s.draw.lock(); + mvprintw(sRoot->pos.y, sRoot->pos.x * 2, " "); + s.draw.unlock(); + attroff(COLOR_PAIR(black)); +} + +void maze_gen::Prim(bool* field, const int& width, const int& height) +{ + struct cell { + vec2<int> pos; + const cell* parent; + std::vector<cell*> neighbors; + // adds valid neighbors + void AddNeighbors(const bool* field, cell* grid, const int& width, + const int& height) + { + neighbors.clear(); + // up + if (pos.y > 1 && !field[(pos.y - 2) * width + pos.x]) + neighbors.emplace_back(&grid[(pos.y - 2) * width + pos.x]); + // down + if (pos.y < height - 2 && !field[(pos.y + 2) * width + pos.x]) + neighbors.emplace_back(&grid[(pos.y + 2) * width + pos.x]); + // left + if (pos.x > 1 && !field[(pos.y * width) + pos.x - 2]) + neighbors.emplace_back(&grid[(pos.y * width) + pos.x - 2]); + // right + if (pos.x < width - 2 && !field[(pos.y * width) + pos.x + 2]) + neighbors.emplace_back(&grid[(pos.y * width) + pos.x + 2]); + } + }; + + // setting the coordinates of each cell according to their placement in the + // array because the contents of the cells will map to their locations in + // memory making addressing easier + cell grid[width * height]; + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + grid[(y * width) + x].pos.x = x; + grid[(y * width) + x].pos.y = y; + } + } + + // gives the index of a cell in the field + auto Index = [&](vec2<int> v) { return v.y * width + v.x; }; + + // initializing the open set with a viable random position, aka the root + // because the mazes look more interesting when having irregular roots + std::vector<cell*> openSet; + openSet.push_back(&grid[(((std::rand() % (height / 2)) * 2 + 1) * width) + + ((std::rand() % (width / 2)) * 2) + 1]); + /* field[(openSet[0]->pos.y * width) + openSet[0]->pos.x] = true; */ + /* mvprintw(openSet[0]->pos.y, openSet[0]->pos.x * 2, " "); */ + openSet[0]->parent = openSet[0]; + + int index; + cell* current; + vec2<int> midpoint; + timer cycle; + + // carving a path to a random unexplored cell + // until there are no more explorable cells + while (!openSet.empty()) { + index = std::rand() % openSet.size(); + current = openSet[index]; + openSet.erase(openSet.begin() + index); + + // if the current position is not part of the maze, make it one + if (!field[Index(current->pos)]) { + current->AddNeighbors(field, grid, width, height); + s.draw.lock(); + attron(COLOR_PAIR(red)); + for (cell* n : current->neighbors) { + n->parent = current; + openSet.push_back(n); + mvprintw(n->pos.y, n->pos.x * 2, " "); + } + attroff(COLOR_PAIR(red)); + s.draw.unlock(); + + midpoint = (current + (current->parent - current) / 2)->pos; + field[Index(current->pos)] = true; + field[Index(midpoint)] = true; + s.draw.lock(); + mvprintw(current->pos.y, current->pos.x * 2, " "); + mvprintw(midpoint.y, midpoint.x * 2, " "); + if (s.render_maze_gen) { + refresh(); + cycle.WaitInterval(s.time_hard_maze_gen); + } + s.draw.unlock(); + } + } +} + +void maze_gen::Wilson(bool* field, const int& width, const int& height) +{ + enum direction { up, + down, + left, + right }; + + // setting the coordinates of each cell according to their placement in the + // array because the contents of the cells will map to their locations in + // memory making addressing easier + vec2<int> grid[width * height]; + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + grid[(y * width) + x].y = y; + grid[(y * width) + x].x = x; + } + } + std::vector<vec2<int>*> cells; + std::vector<direction> dirs; + vec2<int>* nextCell; + vec2<int>* prevCell; + vec2<int>* wall; + timer cycle; + + // gives the index of a cell in the grid + auto Index = [&](vec2<int>* v) { return v->y * width + v->x; }; + // gives the index of a cell with a coordinate offset in the grid + auto IndexOffset = [&](int x, int y) { + return ((cells.back()->y + y) * width) + cells.back()->x + x; + }; + // draws a cell in the grid + auto Draw = [&](vec2<int>* v) { + s.draw.lock(); + mvprintw(v->y, v->x * 2, " "); + s.draw.unlock(); + }; + // draws a cell with a coordinate offset + auto DrawOffset = [&](int x, int y) { + s.draw.lock(); + mvprintw(cells.back()->y + y, (cells.back()->x + x) * 2, " "); + s.draw.unlock(); + }; + + // looping through all path cells in the grid that are not part of the maze + field[width + 1] = true; + s.draw.lock(); + mvprintw(1, 2, " "); + s.draw.unlock(); + for (int y = 1; y < height; y += 2) { + for (int x = 1; x < width; x += 2) { + if (!field[y * width + x]) { + cells.clear(); + cells.emplace_back(&grid[y * width + x]); + Draw(cells.back()); + + // going on a loop erased random walk until the maze is reached + while (!field[IndexOffset(0, 0)]) { + dirs.clear(); + if (cells.back()->y > 1) + dirs.emplace_back(up); + if (cells.back()->y < height - 2) + dirs.emplace_back(down); + if (cells.back()->x > 1) + dirs.emplace_back(left); + if (cells.back()->x < width - 2) + dirs.emplace_back(right); + switch (dirs[std::rand() % dirs.size()]) { + case up: + nextCell = &grid[IndexOffset(0, -2)]; + break; + case down: + nextCell = &grid[IndexOffset(0, 2)]; + break; + case left: + nextCell = &grid[IndexOffset(-2, 0)]; + break; + case right: + nextCell = &grid[IndexOffset(2, 0)]; + break; + } + + // looping through the cells in the random walk + // to test if a loop has been created and erase it + attron(COLOR_PAIR(white)); + for (int i = cells.size() - 1; i >= 0; i--) { + if (cells[i] == nextCell) { + while (cells.back() != nextCell) { + Draw(cells.back()); + prevCell = cells.back(); + cells.pop_back(); + wall = cells.back() + (prevCell - cells.back()) / 2; + Draw(wall); + } + goto self_collision; + } + } + attroff(COLOR_PAIR(white)); + + // if no loop was detected committing the new cell to the + // walk + wall = cells.back() + (nextCell - cells.back()) / 2; + Draw(nextCell); + Draw(wall); + cells.emplace_back(nextCell); + self_collision: + if (s.render_maze_gen) { + s.draw.lock(); + refresh(); + s.draw.unlock(); + cycle.WaitInterval(s.time_soft_maze_gen); + } + } + + // committing the random walk to the maze + prevCell = cells[0]; + field[Index(prevCell)] = true; + for (int i = 1; i < cells.size(); ++i) { + wall = prevCell + (cells[i] - prevCell) / 2; + field[Index(wall)] = true; + field[Index(cells[i])] = true; + prevCell = cells[i]; + } + + if (s.render_maze_gen) { + s.draw.lock(); + refresh(); + s.draw.unlock(); + cycle.WaitInterval(s.time_hard_maze_gen); + } + } + } + } +} + +void maze_gen::RecursiveDivider(bool* field, const int& width, const int& height) +{ + struct cell { + vec2<int> pos; + bool isVisited = false; + char setName; + std::vector<cell*> neighbors; + // adds valid neighbors + void AddNeighbors( + cell* grid, + const int& width, + const int& height) + { + neighbors.clear(); + // up + if (pos.y > 2 && !grid[(pos.y - 2) * width + pos.x].isVisited) + neighbors.push_back(&grid[(pos.y - 2) * width + pos.x]); + // down + if (pos.y < height - 2 && !grid[(pos.y + 2) * width + pos.x].isVisited) + neighbors.push_back(&grid[(pos.y + 2) * width + pos.x]); + // left + if (pos.x > 2 && !grid[(pos.y * width) + pos.x - 2].isVisited) + neighbors.push_back(&grid[(pos.y * width) + pos.x - 2]); + // right + if (pos.x < width - 2 && !grid[(pos.y * width) + pos.x + 2].isVisited) + neighbors.push_back(&grid[(pos.y * width) + pos.x + 2]); + } + std::vector<cell*> GetNeighbors( + cell* grid, + const int& width, + const int& height) + { + std::vector<cell*> ns; + if (pos.x > 2) + ns.push_back(&grid[(pos.y * width) + pos.x - 2]); + if (pos.x < width - 2) + ns.push_back(&grid[(pos.y * width) + pos.x + 2]); + if (pos.y > 2) + ns.push_back(&grid[(pos.y - 2) * width + pos.x]); + if (pos.y < height - 2) + ns.push_back(&grid[(pos.y + 2) * width + pos.x]); + return ns; + } + }; + + // gets rid of the walls inside the maze + s.draw.lock(); + attron(COLOR_PAIR(black)); + for (int y = 1; y < height - 1; ++y) { + for (int x = 1; x < width - 1; ++x) { + field[y * width + x] = true; + mvprintw(y, x * 2, " "); + } + } + // sets the corner squares to walls for consistent representation of maze + for (int y = 2; y < height - 2; y += 2) { + for (int x = 2; x < width - 2; x += 2) { + field[y * width + x] = false; + } + } + if (s.render_maze_gen) + refresh(); + attroff(COLOR_PAIR(black)); + s.draw.unlock(); + // initializes the grid + cell grid[width * height]; + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + grid[(y * width) + x].pos.x = x; + grid[(y * width) + x].pos.y = y; + } + } + // populates the globals set + std::vector<cell*> l; // l for local set + for (int y = 1; y < height; y += 2) { + for (int x = 1; x < width; x += 2) { + l.push_back(&grid[y * width + x]); + } + } + + timer cycle; + + std::function<void(std::vector<cell*>&)> + Divide = [&](std::vector<cell*>& l) { + // base case + if (l.size() < 4) + return; + + // cleans the set from previous data + s.draw.lock(); + attron(COLOR_PAIR(purple)); + for (cell* c : l) { + c->setName = 'l'; + c->neighbors.clear(); + c->isVisited = false; + mvprintw(c->pos.y, c->pos.x * 2, " "); + } + attroff(COLOR_PAIR(purple)); + s.draw.unlock(); + + // gets two random seeds for set a and b respectively + cell* seedA = l[std::rand() % l.size()]; + seedA->setName = 'a'; + cell* seedB; + do { + seedB = l[std::rand() % l.size()]; + } while (seedB == seedA); + seedB->setName = 'b'; + std::vector<cell*> o; // o stands for the open set + o.push_back(seedA); + o.push_back(seedB); + seedA->isVisited = true; + seedB->isVisited = true; + s.draw.lock(); + attron(COLOR_PAIR(red)); + mvprintw(seedA->pos.y, seedA->pos.x * 2, " "); + attroff(COLOR_PAIR(red)); + attron(COLOR_PAIR(blue)); + mvprintw(seedB->pos.y, seedB->pos.x * 2, " "); + attroff(COLOR_PAIR(blue)); + if (s.render_maze_gen) + refresh(); + s.draw.unlock(); + + // expands a and b until they cover the whole of the local set + int index; + cell* current; + while (!o.empty()) { + index = std::rand() % o.size(); + current = o[index]; + o.erase(o.begin() + index); + current->AddNeighbors(grid, width, height); + if (s.render_maze_gen) { + s.draw.lock(); + attron(COLOR_PAIR(red)); + if (current->setName == 'b') + attron(COLOR_PAIR(blue)); + mvprintw(current->pos.y, current->pos.x * 2, " "); + attroff(COLOR_PAIR(blue)); + attroff(COLOR_PAIR(red)); + refresh(); + s.draw.unlock(); + } + for (cell* c : current->neighbors) { + c->setName = current->setName; + o.push_back(c); + } + if (!current->isVisited && s.render_maze_gen) { + cycle.WaitInterval(s.time_soft_maze_gen); + } + current->isVisited = true; + } + + // separates set a and b from the local set for further division + // and gets the boundary between set a and b + std::vector<cell*> a; + std::vector<cell*> b; + vec2<int>* midpoint; + std::vector<vec2<int>*> w; // wall + for (cell* c : l) { + if (c->setName == 'a') { + a.push_back(c); + } else { + b.push_back(c); + } + for (cell* n : c->GetNeighbors(grid, width, height)) { + // to do: see if you can avoid executing this twice + // i.e. c -> n wall and n -> c wall when n becomes c + if (c->setName != n->setName && c->setName != 's' && n->setName != 's') { + midpoint = &(c + (n - c) / 2)->pos; + w.push_back(midpoint); + // build a wall on the boundary + field[midpoint->y * width + midpoint->x] = false; + } + } + } + // draws the corners of the wall + s.draw.lock(); + attron(COLOR_PAIR(white)); + for (vec2<int>* mid : w) { + std::vector<cell*> wallCorners; + if (mid->y % 2 == 0) { // vertical wall | positions start from 0 + if (mid->x > 1) + wallCorners.push_back(&grid[(mid->y * width) + mid->x - 1]); + if (mid->x < width - 1) + wallCorners.push_back(&grid[(mid->y * width) + mid->x + 1]); + } else { // horizontal wall + if (mid->y > 1) + wallCorners.push_back(&grid[(mid->y - 1) * width + mid->x]); + if (mid->y < height - 1) + wallCorners.push_back(&grid[(mid->y + 1) * width + mid->x]); + } + for (cell* c : wallCorners) { + int nNeighborWalls = 0; + if (!field[(c->pos.y - 1) * width + c->pos.x]) + ++nNeighborWalls; + if (!field[(c->pos.y + 1) * width + c->pos.x]) + ++nNeighborWalls; + if (!field[(c->pos.y * width) + c->pos.x - 1]) + ++nNeighborWalls; + if (!field[(c->pos.y * width) + c->pos.x + 1]) + ++nNeighborWalls; + if (nNeighborWalls > 1) { + mvprintw(c->pos.y, c->pos.x * 2, " "); + } + } + } + // draws the middle of the wall + for (vec2<int>* mid : w) { + mvprintw(mid->y, mid->x * 2, " "); + } + attroff(COLOR_PAIR(white)); + if (s.render_maze_gen) + refresh(); + s.draw.unlock(); + if (s.render_maze_gen) + cycle.WaitInterval(s.time_soft_maze_gen); + // punches a hole in the wall + midpoint = w[std::rand() % w.size()]; + field[midpoint->y * width + midpoint->x] = true; + + s.draw.lock(); + attron(COLOR_PAIR(black)); + mvprintw(midpoint->y, midpoint->x * 2, " "); + attroff(COLOR_PAIR(black)); + if (s.render_maze_gen) + refresh(); + s.draw.unlock(); + if (s.render_maze_gen) + cycle.WaitInterval(s.time_hard_maze_gen); + + // cleans up after a division is made + if (s.render_maze_gen) { + s.draw.lock(); + attron(COLOR_PAIR(black)); + for (cell* c : l) { + c->setName = 's'; + mvprintw(c->pos.y, c->pos.x * 2, " "); + } + attroff(COLOR_PAIR(black)); + refresh(); + s.draw.unlock(); + } else { + for (cell* c : l) { + c->setName = 's'; + } + } + + Divide(a); + Divide(b); + }; + + Divide(l); + + if (!s.render_maze_gen) { + s.draw.lock(); + refresh(); + s.draw.unlock(); + } +} + +/* void maze_gen::RecursiveDivider(bool* field, const int& width, const int& height) */ +/* { */ +/* for (int y = 1; y < height - 1; ++y) { */ +/* for (int x = 1; x < width - 1; ++x) { */ +/* field[y * width + x] = true; */ +/* mvprintw(y, x * 2, " "); */ +/* } */ +/* } */ + +/* auto RandOdd = [&](int a, int b) { */ +/* return (a >= b) ? (std::rand() % ((a - b) / 2)) * 2 + 1 */ +/* : (std::rand() % ((b - a) / 2)) * 2; */ +/* }; */ + +/* auto RandEven = [&](int a, int b) { */ +/* return (a >= b) ? (std::rand() % ((a - b - 1) / 2)) * 2 + 1 */ +/* : (std::rand() % ((b - a - 1) / 2)) * 2 + 1; */ +/* }; */ + +/* refresh(); */ +/* timer cycle; */ + +/* std::function<void(vec2<int>&, vec2<int>&)> Divide = */ +/* [&](vec2<int>& top_left, vec2<int>& bottom_right) { */ +/* if (top_left.x >= bottom_right.x || top_left.y >= bottom_right.y) */ +/* return; */ + +/* attron(COLOR_PAIR(white)); */ +/* int vwallx = top_left.x + RandOdd(top_left.x, bottom_right.x); */ +/* for (int y = top_left.y; y <= bottom_right.y; ++y) { */ +/* field[y * width + vwallx] = false; */ +/* mvprintw(y, vwallx * 2, " "); */ +/* } */ +/* attroff(COLOR_PAIR(white)); */ +/* getch(); */ + +/* attron(COLOR_PAIR(black)); */ +/* int vwallholey = top_left.y + RandEven(top_left.y, bottom_right.y); */ +/* field[vwallholey * width + top_left.x + vwallx] = true; */ +/* mvprintw(vwallholey, (top_left.x + vwallx) * 2, " "); */ +/* attroff(COLOR_PAIR(black)); */ +/* getch(); */ + +/* attron(COLOR_PAIR(white)); */ +/* int hwally = top_left.y + RandOdd(top_left.y, bottom_right.y); */ +/* for (int x = top_left.x; x <= bottom_right.x; ++x) { */ +/* field[hwally * width + x] = false; */ +/* mvprintw(hwally, x * 2, " "); */ +/* } */ +/* attroff(COLOR_PAIR(white)); */ +/* getch(); */ + +/* attron(COLOR_PAIR(black)); */ +/* int vwallholex1 = top_left.x + RandEven(top_left.x, bottom_right.x); */ +/* int vwallholex2 = top_left.x + RandEven(top_left.x, bottom_right.x); */ +/* field[hwally * width + top_left.x + vwallholex1] = true; */ +/* field[hwally * width + top_left.x + vwallholex2] = true; */ +/* mvprintw(hwally, (top_left.x + vwallholex1) * 2, " "); */ +/* mvprintw(hwally, (top_left.x + vwallholex2) * 2, " "); */ +/* attroff(COLOR_PAIR(black)); */ +/* getch(); */ + +/* refresh(); */ + +/* vec2<int> tl(top_left); */ +/* vec2<int> br(top_left.x + vwallx, top_left.y + hwally); */ +/* /1* std::thread worker_tl(Divide, tl, br); *1/ */ +/* cycle.WaitInterval(s.time_hard_maze_gen); */ +/* Divide(tl, br); */ + +/* tl = vec2<int>(top_left.x + vwallx, top_left.y); */ +/* br = vec2<int>(bottom_right.x, top_left.y + hwally); */ +/* /1* std::thread worker_tr(Divide, tl, br); *1/ */ +/* cycle.WaitInterval(s.time_hard_maze_gen); */ +/* Divide(tl, br); */ + +/* tl = vec2<int>(top_left.x, top_left.y + hwally); */ +/* br = vec2<int>(top_left.x + vwallx, bottom_right.y); */ +/* /1* std::thread worker_bl(Divide, tl, br); *1/ */ +/* cycle.WaitInterval(s.time_hard_maze_gen); */ +/* Divide(tl, br); */ + +/* tl = vec2<int>(top_left.x + vwallx, top_left.y + hwally); */ +/* br = vec2<int>(bottom_right); */ +/* /1* std::thread worker_br(Divide, tl, br); *1/ */ +/* cycle.WaitInterval(s.time_hard_maze_gen); */ +/* Divide(tl, br); */ + +/* /1* worker_tl.join(); *1/ */ +/* /1* worker_tr.join(); *1/ */ +/* /1* worker_bl.join(); *1/ */ +/* /1* worker_br.join(); *1/ */ +/* }; */ +/* vec2<int> tl(1, 1); */ +/* vec2<int> br(width - 1, height - 1); */ +/* Divide(tl, br); */ +/* } */ + +std::map<std::string, + void (*)( + bool* field, + const int& width, + const int& height)> + generators = { + { "Backtracker", maze_gen::Backtracker }, + { "Kruskal", maze_gen::Kruskal }, + { "Prim", maze_gen::Prim }, + { "Wilson", maze_gen::Wilson }, + { "RecursiveDivider", maze_gen::RecursiveDivider } + }; + diff --git a/maze_gen.hpp b/maze_gen.hpp @@ -0,0 +1,36 @@ +#pragma once + +#ifndef MAZE_GEN_HPP +#define MAZE_GEN_HPP + +#include <ncurses.h> + +#include <algorithm> +#include <functional> +#include <map> +#include <random> +#include <stack> +#include <thread> +#include <vector> + +#include "colors.hpp" +#include "state.hpp" +#include "timer.hpp" +#include "vec2.hpp" + +namespace maze_gen { +void Backtracker(bool* field, const int& width, const int& height); +void Kruskal(bool* field, const int& width, const int& height); +void Prim(bool* field, const int& width, const int& height); +void Wilson(bool* field, const int& width, const int& height); +void RecursiveDivider(bool* field, const int& width, const int& height); +}; // namespace maze_gen + +#endif // MAZE_GEN_HPP + +extern std::map<std::string, + void (*)( + bool* field, + const int& width, + const int& height)> + generators; diff --git a/pathfinding.cpp b/pathfinding.cpp @@ -0,0 +1,415 @@ +#include <ncurses.h> + +#include <algorithm> +#include <map> +#include <queue> +#include <stack> +#include <string> +#include <vector> + +#include "colors.hpp" +#include "pathfinding.hpp" +#include "state.hpp" +#include "timer.hpp" +#include "vec2.hpp" + +std::vector<vec2<int>> pathfinding::Breadth( + const bool* field, + const int& width, + const int& height, + const vec2<int>& start, + const std::vector<vec2<int>>& exits) +{ + struct node { + vec2<int> pos; + node* parent = nullptr; + std::vector<node*> neighbors; + bool isVisited = false; + // adds valid neighbors + void AddNeighbors( + const bool* field, + node* grid, + const int& width, + const int& height) + { + neighbors.clear(); + // up + if (pos.y > 2 && field[(pos.y - 1) * width + pos.x]) + neighbors.push_back(&grid[(pos.y - 2) * width + pos.x]); + // down + if (pos.y < height - 2 && field[(pos.y + 1) * width + pos.x]) + neighbors.push_back(&grid[(pos.y + 2) * width + pos.x]); + // left + if (pos.x > 2 && field[(pos.y * width) + pos.x - 1]) + neighbors.push_back(&grid[(pos.y * width) + pos.x - 2]); + // right + if (pos.x < width - 2 && field[(pos.y * width) + pos.x + 1]) + neighbors.push_back(&grid[(pos.y * width) + pos.x + 2]); + } + }; + + // initializing the grid of nodes used for pointer locations + node grid[width * height]; + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + grid[(y * width) + x].pos.x = x; + grid[(y * width) + x].pos.y = y; + } + } + + // initializes the open set with the start pos + std::queue<node*> q; + q.push(&grid[start.y * width + start.x]); + + node* current = q.front(); + node* parent = current; + node* middle = nullptr; + timer cycle; + + while (!q.empty()) { + attron(COLOR_PAIR(green)); + s.draw.lock(); + mvprintw(start.y, start.x * 2, " "); + s.draw.unlock(); + attron(COLOR_PAIR(green)); + + current = q.front(); + current->isVisited = true; + if (current->parent != nullptr) + parent = current->parent; + for (vec2<int> e : exits) { + if (current->pos == e) { + std::vector<vec2<int>> path; + node* previous = current; + do { + path.push_back(previous->pos); + previous = previous->parent; + } while (previous != nullptr); + return path; + } + } + s.draw.lock(); + attron(COLOR_PAIR(blue)); + mvprintw(current->pos.y, current->pos.x * 2, " "); + mvprintw(((current->pos + parent->pos) / 2).y, + ((current->pos + parent->pos) / 2).x * 2, " "); + attroff(COLOR_PAIR(blue)); + s.draw.unlock(); + q.pop(); + + current->AddNeighbors(field, grid, width, height); + + s.draw.lock(); + attron(COLOR_PAIR(cyan)); + for (node* n : current->neighbors) { + if (!n->isVisited) { + n->parent = current; + q.push(n); + middle = (n + (current - n) / 2); + mvprintw(middle->pos.y, middle->pos.x * 2, " "); + } + } + attroff(COLOR_PAIR(cyan)); + s.draw.unlock(); + if (s.render_pathfinding) { + s.draw.lock(); + refresh(); + s.draw.unlock(); + cycle.WaitInterval(s.time_hard_pathfinding); + } + } + return std::vector<vec2<int>>(); +} + +std::vector<vec2<int>> pathfinding::Depth( + const bool* field, + const int& width, + const int& height, + const vec2<int>& start, + const std::vector<vec2<int>>& exits) +{ + struct node { + vec2<int> pos; + node* parent = nullptr; + std::vector<node*> neighbors; + bool isVisited = false; + // adds valid neighbors + void AddNeighbors( + const bool* field, + node* grid, + const int& width, + const int& height) + { + neighbors.clear(); + // up + if (pos.y > 2 && field[(pos.y - 1) * width + pos.x]) + neighbors.push_back(&grid[(pos.y - 2) * width + pos.x]); + // down + if (pos.y < height - 2 && field[(pos.y + 1) * width + pos.x]) + neighbors.push_back(&grid[(pos.y + 2) * width + pos.x]); + // left + if (pos.x > 2 && field[(pos.y * width) + pos.x - 1]) + neighbors.push_back(&grid[(pos.y * width) + pos.x - 2]); + // right + if (pos.x < width - 2 && field[(pos.y * width) + pos.x + 1]) + neighbors.push_back(&grid[(pos.y * width) + pos.x + 2]); + } + }; + + // initializing the grid of nodes used for pointer locations + node grid[width * height]; + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + grid[(y * width) + x].pos.x = x; + grid[(y * width) + x].pos.y = y; + } + } + + // initializes the open set with the start pos + std::stack<node*> q; + q.push(&grid[start.y * width + start.x]); + + node* current = q.top(); + node* parent; + node* wall; + node* middle = nullptr; + timer cycle; + + while (!q.empty()) { + attron(COLOR_PAIR(green)); + s.draw.lock(); + mvprintw(start.y, start.x * 2, " "); + s.draw.unlock(); + attron(COLOR_PAIR(green)); + current = q.top(); + current->isVisited = true; + if (current->parent != nullptr) { + parent = current->parent; + } else { + parent = current; + } + // finding the node pointer containing the + // wall between the current and previous nodes + // so the path doesn't have gaps when drawn + wall = parent + (current - parent) / 2; + for (vec2<int> e : exits) { + if (current->pos == e) { + std::vector<vec2<int>> path; + node* previous = current; + do { + path.push_back(previous->pos); + previous = previous->parent; + } while (previous != nullptr); + return path; + } + } + attron(COLOR_PAIR(blue)); + s.draw.lock(); + mvprintw(current->pos.y, current->pos.x * 2, " "); + mvprintw(wall->pos.y, wall->pos.x * 2, " "); + s.draw.unlock(); + attroff(COLOR_PAIR(blue)); + q.pop(); + current->AddNeighbors(field, grid, width, height); + + attron(COLOR_PAIR(cyan)); + s.draw.lock(); + for (node* n : current->neighbors) { + if (!n->isVisited) { + n->parent = current; + q.push(n); + middle = (n + (current - n) / 2); + mvprintw(middle->pos.y, middle->pos.x * 2, " "); + } + } + s.draw.unlock(); + attroff(COLOR_PAIR(cyan)); + if (s.render_pathfinding) { + s.draw.lock(); + refresh(); + s.draw.unlock(); + cycle.WaitInterval(s.time_hard_pathfinding); + } + } + return std::vector<vec2<int>>(); +} + +std::vector<vec2<int>> pathfinding::AStar( + const bool* field, + const int& width, + const int& height, + const vec2<int>& start, + const std::vector<vec2<int>>& exits) +{ + struct node { + vec2<int> pos; + node* parent = nullptr; + int g; + float h, f; + std::vector<node*> neighbors; + // adds valid neighbors + void AddNeighbors( + const bool* field, + node* grid, + const int& width, + const int& height) + { + neighbors.clear(); + // up + if (pos.y > 2 && field[(pos.y - 1) * width + pos.x]) + neighbors.push_back(&grid[(pos.y - 2) * width + pos.x]); + // down + if (pos.y < height - 2 && field[(pos.y + 1) * width + pos.x]) + neighbors.push_back(&grid[(pos.y + 2) * width + pos.x]); + // left + if (pos.x > 2 && field[(pos.y * width) + pos.x - 1]) + neighbors.push_back(&grid[(pos.y * width) + pos.x - 2]); + // right + if (pos.x < width - 2 && field[(pos.y * width) + pos.x + 1]) + neighbors.push_back(&grid[(pos.y * width) + pos.x + 2]); + } + }; + + // initializing the grid of nodes used for pointer locations + node grid[width * height]; + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + grid[(y * width) + x].pos.x = x; + grid[(y * width) + x].pos.y = y; + } + } + + // adding the neighbors of all the cells + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + grid[(y * width) + x].AddNeighbors(field, grid, width, height); + } + } + + int index; + node* current = nullptr; + std::vector<vec2<int>> path; + std::vector<node*> neighbors; + int g_temp; + node* neighbor = nullptr; + node* middle = nullptr; + vec2<int> finish; + timer cycle; + + std::vector<node*> openSet; + std::vector<node*> closedSet; + openSet.push_back(&grid[start.y * width + start.x]); + current = openSet.front(); + current->parent = nullptr; + current->g = 0.0f; + finish = exits[0]; + for (int i = 1; i < exits.size(); ++i) { + if ((exits[i] - current->pos).GetLength() + < (finish - current->pos).GetLength()) { + finish = exits[i]; + } + } + current->h = (current->pos - finish).GetLength(); + current->f = current->g + current->h; + + while (!openSet.empty()) { + index = 0; + for (int i = 1; i < openSet.size(); ++i) { + if (openSet[i]->f < openSet[index]->f) + index = i; + } + current = openSet[index]; + + for (vec2<int> e : exits) { + if (current->pos == e) { + node* previous = current; + do { + path.push_back(previous->pos); + previous = previous->parent; + } while (previous != nullptr); + return path; + } + } + + closedSet.push_back(current); + openSet.erase(openSet.begin() + index); + neighbors = current->neighbors; + if (current->parent != nullptr) { + middle = current + (current->parent - current) / 2; + } else { + middle = current; + } + s.draw.lock(); + attron(COLOR_PAIR(blue)); + mvprintw(current->pos.y, current->pos.x * 2, " "); + mvprintw(middle->pos.y, middle->pos.x * 2, " "); + attroff(COLOR_PAIR(blue)); + s.draw.unlock(); + if (current->pos == start) { + s.draw.lock(); + attron(COLOR_PAIR(green)); + mvprintw(start.y, start.x * 2, " "); + attroff(COLOR_PAIR(green)); + s.draw.unlock(); + } + + for (int i = 0; i < neighbors.size(); ++i) { + neighbor = neighbors[i]; + // checks if neighbor is in the closed set + // if not, it processes it + if ((std::find(closedSet.begin(), closedSet.end(), neighbor) + == closedSet.end())) { + g_temp = current->g + 1; + + // checks if the neighbor is already in the open set + // if not, it adds it + if ((std::find(openSet.begin(), openSet.end(), neighbor) + == openSet.end())) { + openSet.push_back(neighbor); + s.draw.lock(); + middle = (neighbor + (current - neighbor) / 2); + attron(COLOR_PAIR(cyan)); + mvprintw(middle->pos.y, middle->pos.x * 2, " "); + attroff(COLOR_PAIR(cyan)); + s.draw.unlock(); + } else if (g_temp >= neighbor->g) { + continue; + } + + finish = exits[0]; + for (int i = 1; i < exits.size(); ++i) { + if ((exits[i] - neighbor->pos).GetLength() + < (finish - neighbor->pos).GetLength()) { + finish = exits[i]; + } + } + + neighbor->g = g_temp; + neighbor->h = (neighbor->pos - finish).GetLength(); + neighbor->f = neighbor->h + neighbor->g; + neighbor->parent = current; + } + if (s.render_pathfinding) { + s.draw.lock(); + refresh(); + s.draw.unlock(); + cycle.WaitInterval(s.time_hard_pathfinding); + } + } + } + return path; +} + +std::map<std::string, + std::vector<vec2<int>> (*)( + const bool* field, + const int& width, + const int& height, + const vec2<int>& start, + const std::vector<vec2<int>>& exits)> + pathfinders = { + { "Breadth", pathfinding::Breadth }, + { "Dijkstra", pathfinding::Breadth }, + { "Depth", pathfinding::Depth }, + { "A*", pathfinding::AStar } + }; diff --git a/pathfinding.hpp b/pathfinding.hpp @@ -0,0 +1,51 @@ +#pragma once + +#ifndef PATHFINDING_HPP +#define PATHFINDING_HPP + +#include <ncurses.h> + +#include <algorithm> +#include <map> +#include <queue> +#include <stack> +#include <vector> + +#include "colors.hpp" +#include "state.hpp" +#include "timer.hpp" +#include "vec2.hpp" + +namespace pathfinding { +std::vector<vec2<int>> Breadth( + const bool* field, + const int& width, + const int& height, + const vec2<int>& start, + const std::vector<vec2<int>>& exits); + +std::vector<vec2<int>> Depth( + const bool* field, + const int& width, + const int& height, + const vec2<int>& start, + const std::vector<vec2<int>>& exits); + +std::vector<vec2<int>> AStar( + const bool* field, + const int& width, + const int& height, + const vec2<int>& start, + const std::vector<vec2<int>>& exits); +}; // pathfinding + +#endif // PATHFINDING_HPP + +extern std::map<std::string, + std::vector<vec2<int>> (*)( + const bool* field, + const int& width, + const int& height, + const vec2<int>& start, + const std::vector<vec2<int>>& exits)> + pathfinders; diff --git a/state.hpp b/state.hpp @@ -0,0 +1,37 @@ +#pragma once + +#ifndef STATE_HPP +#define STATE_HPP + +#include <mutex> +#include <string> + +class state { +public: + static state& GetInstance() + { + static state instance; + return instance; + } + state(state const&) = delete; + void operator=(state const&) = delete; + std::mutex draw; + // add meta data variable + state() {}; + int time_hard_maze_gen = 10; // time between algorithm cycles + int time_soft_maze_gen = 1; // time between cycles within algorithms cycles + int time_hard_pathfinding = 15; // time between algorithm cycles + int time_soft_pathfinding = 2; // time between cycles within algorithms cycles + int time_main = 1500; // time between main loops + int time_path = 15; // time between drawing steps in final path + bool render_maze_gen = true; // should you render maze generating proccess? + bool render_pathfinding = true; // should you render pathfinding proccess? + bool ui_is_open = false; // is ui open or waiting to be rendered? + bool ui_render_is_safe = false; // is it safe to render ui? + std::string pathfinder = "A*"; + std::string generator = "RecursiveDivider"; +}; + +#endif // STATE_HPP + +extern state& s; diff --git a/timer.cpp b/timer.cpp @@ -0,0 +1,33 @@ +#include "timer.hpp" +#include "state.hpp" +#include <chrono> +#include <thread> + +timer::timer() +{ + initTime = std::chrono::steady_clock::now(); +} + +timer::~timer() +{ +} + +void timer::WaitInterval(const float& time_ms) +{ + using namespace std::chrono; + if (s.ui_is_open) { + s.ui_render_is_safe = true; + while (s.ui_is_open) { + std::this_thread::sleep_for(milliseconds(3)); + } + s.ui_render_is_safe = false; + initTime = std::chrono::steady_clock::now(); + elapsedTime = milliseconds(0); + } else { + elapsedTime += duration<float>(time_ms / 1000); + steady_clock::time_point now = steady_clock::now(); + if (initTime + elapsedTime - now > milliseconds(0)) { + std::this_thread::sleep_for(initTime + elapsedTime - now); + } + } +} diff --git a/timer.hpp b/timer.hpp @@ -0,0 +1,21 @@ +#pragma once + +#ifndef TIMER_HPP +#define TIMER_HPP + +#include "state.hpp" +#include <chrono> +#include <thread> + +class timer { +public: + timer(); + ~timer(); + void WaitInterval(const float& time); + +private: + std::chrono::steady_clock::time_point initTime; + std::chrono::duration<float> elapsedTime = std::chrono::milliseconds(0); +}; + +#endif // TIMER_HPP diff --git a/ui.cpp b/ui.cpp @@ -0,0 +1,342 @@ +#include <chrono> +#include <map> +#include <mutex> +#include <ncurses.h> +#include <panel.h> +#include <string> +#include <thread> +#include <vector> + +#include "colors.hpp" +#include "maze_gen.hpp" +#include "pathfinding.hpp" +#include "state.hpp" +#include "ui.hpp" + +static std::string Option( + const std::vector<std::string>& options, + const std::string& current) +{ + // getting the right size for the sub window + int win_height = options.size() + 2; + int win_width = options[0].size(); + for (std::string opt : options) + if (opt.size() > win_width) + win_width = opt.size(); + win_width += 2 + 10; + int y_max, x_mas; + // creating and rendering the sub window + s.ui_is_open = true; + while (!s.ui_render_is_safe) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + s.draw.lock(); + attron(COLOR_PAIR(black)); + getmaxyx(stdscr, y_max, x_mas); + WINDOW* win = newwin( + win_height, + win_width, + (y_max / 2) - (win_height / 2), + (x_mas / 2) - (win_width / 2)); + PANEL* pan = new_panel(win); + box(win, 0, 0); + int highlight = 0; + // putting the highlight on the currently selected option + for (int i = 0; i < options.size(); ++i) + if (options[i] == current) + highlight = i; + int key; + while (true) { + for (int y = 0; y < options.size(); ++y) { + if (y == highlight) + wattron(win, A_REVERSE); + // prints the option + mvwprintw(win, y + 1, 2, options[y].c_str()); + // prints white space up to the selection box for proper + // highlighting + for (int x = options[y].size() + 2; x < win_width - 2; ++x) + mvwprintw(win, y + 1, x, " "); + // prints the selection box + mvwprintw(win, y + 1, win_width - 5, "[ ]"); + if (options[y] == current) + mvwprintw(win, y + 1, win_width - 4, "*"); + wattroff(win, A_REVERSE); + } + getkey: + key = wgetch(win); + switch (key) { + case 'j': + case KEY_DOWN: + ++highlight; + if (highlight >= options.size()) + highlight = 0; + break; + case 'k': + case KEY_UP: + --highlight; + if (highlight < 0) + highlight = options.size() - 1; + break; + case 10: // enter + case 32: // space + case 'l': + hide_panel(pan); + del_panel(pan); + update_panels(); + attroff(COLOR_PAIR(black)); + s.draw.unlock(); + s.ui_is_open = false; + return options[highlight]; + break; + case 27: // escape + case 'h': + case 'q': + hide_panel(pan); + del_panel(pan); + update_panels(); + attroff(COLOR_PAIR(black)); + s.draw.unlock(); + s.ui_is_open = false; + return current; + break; + default: + goto getkey; + break; + } + } +} + +static std::string Option(const std::vector<std::string>& options) +{ + // getting the right size for the sub window + int win_height = options.size() + 2; + int win_width = options[0].size(); + for (std::string opt : options) + if (opt.size() > win_width) + win_width = opt.size(); + win_width += 2 + 10; + int y_max, x_mas; + // creating and rendering the sub window + s.ui_is_open = true; + while (!s.ui_render_is_safe) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + s.draw.lock(); + attron(COLOR_PAIR(black)); + getmaxyx(stdscr, y_max, x_mas); + WINDOW* win = newwin( + win_height, + win_width, + (y_max / 2) - (win_height / 2), + (x_mas / 2) - (win_width / 2)); + PANEL* pan = new_panel(win); + box(win, 0, 0); + int highlight = 0; + int key; + while (true) { + for (int y = 0; y < options.size(); ++y) { + if (y == highlight) + wattron(win, A_REVERSE); + // prints the option + mvwprintw(win, y + 1, 2, options[y].c_str()); + // prints white space up to the selection box for proper + // highlighting + for (int x = options[y].size() + 2; x < win_width - 2; ++x) + mvwprintw(win, y + 1, x, " "); + wattroff(win, A_REVERSE); + } + getkey: + key = wgetch(win); + switch (key) { + case 'j': + case KEY_DOWN: + ++highlight; + if (highlight >= options.size()) + highlight = 0; + break; + case 'k': + case KEY_UP: + --highlight; + if (highlight < 0) + highlight = options.size() - 1; + break; + case 10: // enter + case 32: // space + case 'l': + hide_panel(pan); + del_panel(pan); + update_panels(); + attroff(COLOR_PAIR(black)); + s.draw.unlock(); + s.ui_is_open = false; + return options[highlight]; + break; + case 27: // escape + case 'h': + case 'q': + hide_panel(pan); + del_panel(pan); + update_panels(); + attroff(COLOR_PAIR(black)); + s.draw.unlock(); + s.ui_is_open = false; + return ""; + break; + default: + goto getkey; + break; + } + } +} + +void ui::RenderUI() +{ + std::vector<std::string> choices = { + "Maze generator =>", + "Pathfinder =>", + "Render maze generation", + "Render path-finding", + "Soft time of maze generation", + "Hard time of maze generation", + "Soft time of path-finding", + "Hard time of path-finding", + "Hard time for final path drawing", + "Time between maze generation" + }; + + std::string mmenu; + /* std::string mmenu = choices[0]; */ + while (true) { + + mmenu = Option(choices); + /* mmenu = Option(choices, mmenu); */ + + if (mmenu == "") + break; + + if (mmenu == "Maze generator =>") { + std::vector<std::string> names; + // extracts the keys from the generators map and puts them in names + for (std::map<std::string, + void (*)( + bool*field, + const int&width, + const int&height)>::iterator it + = generators.begin(); + it != generators.end(); + ++it) { + names.push_back(it->first); + } + s.generator = Option(names, s.generator); + } + + if (mmenu == "Pathfinder =>") { + std::vector<std::string> names; + // extracts the keys from the pathfinders map and puts them in names + for (std::map<std::string, + std::vector<vec2<int>> (*)( + const bool*field, + const int&width, + const int&height, + const vec2<int>&start, + const std::vector<vec2<int>>&exits)>::iterator it + = pathfinders.begin(); + it != pathfinders.end(); ++it) { + names.push_back(it->first); + } + s.pathfinder = Option(names, s.pathfinder); + } + } +} + +void ui::ExecuteKeys() +{ + int width, height; + int c; + while (true) { + getmaxyx(stdscr, height, width); + c = getch(); + s.draw.lock(); + attron(COLOR_PAIR(white)); + // clears the top portion of the screen of previous text + for (int i = 0; i <= width; ++i) { + mvprintw(0, i, " "); + } + // handles key presses + switch (c) { + case 'k': + s.time_hard_maze_gen += 1; + mvprintw(0, 0, "Maze generation hard time: %d", s.time_hard_maze_gen); + attroff(COLOR_PAIR(white)); + refresh(); + s.draw.unlock(); + break; + case 'K': + s.time_soft_maze_gen += 1; + mvprintw(0, 0, "Maze generation soft time: %d", s.time_soft_maze_gen); + attroff(COLOR_PAIR(white)); + refresh(); + s.draw.unlock(); + break; + case 'j': + s.time_hard_maze_gen -= 1; + mvprintw(0, 0, "Maze generation hard time: %d", s.time_hard_maze_gen); + attroff(COLOR_PAIR(white)); + refresh(); + s.draw.unlock(); + break; + case 'J': + s.time_soft_maze_gen -= 1; + mvprintw(0, 0, "Maze generation soft time: %d", s.time_soft_maze_gen); + attroff(COLOR_PAIR(white)); + refresh(); + s.draw.unlock(); + break; + case 'i': + s.time_hard_pathfinding += 1; + mvprintw(0, 0, "Pathfinding hard time: %d", s.time_hard_pathfinding); + attroff(COLOR_PAIR(white)); + refresh(); + s.draw.unlock(); + break; + case 'I': + s.time_soft_pathfinding += 1; + mvprintw(0, 0, "Pathfinding soft time: %d", s.time_soft_pathfinding); + attroff(COLOR_PAIR(white)); + refresh(); + s.draw.unlock(); + break; + case 'u': + s.time_hard_pathfinding -= 1; + mvprintw(0, 0, "Pathfinding hard time: %d", s.time_hard_pathfinding); + attroff(COLOR_PAIR(white)); + refresh(); + s.draw.unlock(); + break; + case 'U': + s.time_soft_pathfinding -= 1; + mvprintw(0, 0, "Pathfinding soft time: %d", s.time_soft_pathfinding); + attroff(COLOR_PAIR(white)); + refresh(); + s.draw.unlock(); + break; + case 27: + attroff(COLOR_PAIR(white)); + s.draw.unlock(); + RenderUI(); + break; + case 'q': + attroff(COLOR_PAIR(white)); + s.draw.unlock(); + endwin(); + exit(0); + default: + mvprintw(0, 0, "Unknown mapping: <%d>", c); + attroff(COLOR_PAIR(white)); + refresh(); + s.draw.unlock(); + break; + } + std::this_thread::sleep_for(std::chrono::milliseconds(3)); + } +} diff --git a/ui.hpp b/ui.hpp @@ -0,0 +1,18 @@ +#pragma once + +#ifndef UI_HPP +#define UI_HPP + +#include <ncurses.h> + +#include "colors.hpp" +#include "maze_gen.hpp" +#include "pathfinding.hpp" +#include "state.hpp" + +namespace ui { +extern void RenderUI(); +extern void ExecuteKeys(); +} + +#endif // UI_HPP diff --git a/vec2.hpp b/vec2.hpp @@ -0,0 +1,143 @@ +#pragma once + +#ifndef VEC2_HPP +#define VEC2_HPP + +#include <cmath> +#include <iostream> + +template <class T = float> +struct vec2 { + vec2() = default; + vec2(const T x, const T y) + : x(x) + , y(y) {}; + template <class O> + vec2(const vec2<O>& other) + : x(other.x) + , y(other.y) {}; + bool operator==(const vec2& rhs) const; + bool operator!=(const vec2& rhs) const; + vec2& operator=(const vec2& rhs); + vec2 operator+(const vec2& rhs) const; + vec2& operator+=(const vec2& rhs); + vec2 operator-(const vec2& rhs) const; + vec2& operator-=(const vec2& rhs); + vec2 operator*(float rhs) const; + vec2& operator*=(float rhs); + vec2 operator/(float rhs) const; + vec2& operator/=(float rhs); + T GetLengthSq() const; + float GetLength() const; + vec2<T> GetNormalized() const; + vec2<T>& Normalize(); + template <typename U> + friend std::ostream& operator<<(std::ostream& os, const vec2<U>& v); + + T x; + T y; +}; + +template <typename T> +bool vec2<T>::operator==(const vec2& rhs) const +{ + return x == rhs.x && y == rhs.y; +} + +template <typename T> +bool vec2<T>::operator!=(const vec2& rhs) const +{ + return x != rhs.x || y != rhs.y; +} + +template <typename T> +vec2<T>& vec2<T>::operator=(const vec2& rhs) +{ + x = rhs.x; + y = rhs.y; + return *this; +} + +template <typename T> +vec2<T> vec2<T>::operator+(const vec2& rhs) const +{ + return vec2<T>(x + rhs.x, y + rhs.y); // Can I get away with no T here? +} + +template <typename T> +vec2<T>& vec2<T>::operator+=(const vec2& rhs) +{ + return *this = *this + rhs; +} + +template <typename T> +vec2<T> vec2<T>::operator-(const vec2& rhs) const +{ + return vec2<T>(x - rhs.x, y - rhs.y); +} + +template <typename T> +vec2<T>& vec2<T>::operator-=(const vec2& rhs) +{ + return *this = *this - rhs; +} + +template <typename T> +vec2<T> vec2<T>::operator*(float rhs) const +{ + return vec2<T>(x * rhs, y * rhs); +} + +template <typename T> +vec2<T>& vec2<T>::operator*=(float rhs) +{ + return *this = *this * rhs; +} + +template <typename T> +vec2<T> vec2<T>::operator/(float rhs) const +{ + return vec2<T>(x / rhs, y / rhs); +} + +template <typename T> +vec2<T>& vec2<T>::operator/=(float rhs) +{ + return *this = *this / rhs; +} + +template <typename T> +T vec2<T>::GetLengthSq() const +{ + return (x * x) + (y * y); +} + +template <typename T> +float vec2<T>::GetLength() const +{ + return std::sqrt(GetLengthSq()); +} + +template <typename T> +vec2<T> vec2<T>::GetNormalized() const +{ + const float len = GetLength(); + if (len != 0.0f) { + return *this / len; + } + return *this; +} + +template <typename T> +vec2<T>& vec2<T>::Normalize() +{ + return *this = this->GetNormalized(); +} + +template <typename T> +std::ostream& operator<<(std::ostream& os, const vec2<T>& v) +{ + return os << "(" << v.x << ", " << v.y << ")"; +} + +#endif // VEC2_HPP