- Notifications
You must be signed in to change notification settings - Fork0
nizamiza/save-princesses
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
This is a university project at FIIT STU, Bratislava, Slovakia.
Resonance cascade at Black Mesa has caused massive damage to the society of the modernEarth. 7 Hour War brought Combine tyrany upon whole planet. But these events had effectnot only on the future of the humanity, but also on its past.
As multi-dimensional portals were opening around the whole globe, they were causingtearing between parallel universes. Present and the past were colliding. During this chaos,Ancient Dragon from foreign galaxy has entered skies of the medieval Europe.Reigning fear and death, people were certain that this was the end of the world.
Dragon breathed fire upon countries and burned thousands of men, women, and children.Noone could stop him. But he was not just an animal, self-aware and hehad plans for the Earth. He wanted to control all of the kingdoms and become the soleEmperor.
His first action towards his plan was to kidnap princesses of the Samhradh royal family.King Samhradh was devastated, but he wanted his daughters back! So he anounced an enormousaward to anyone who could manage to slay the dragon and return princesses back home. But toKing's disappointment, there was noone willing to risk his life... Except one person.
After few days since kidnapping, a mysterious knight arrived to Samhradh Castle. Hepromised to the King that he would slay the beast and bring back all of his daughters.Hope filled King's heart and he promised that he would double the award if the mysteriousknight would succeed in his mission. So he set horses towards the Dark Mountain, where theAncient Dragon had taken Samhradh princesses.
When he arrived to the foot of the mountain, he saw a massive labyrinth of passages throughsharp cliffs. This was the only way to proceed towards the dragon... Before he entered thelabyrinth, he heard a strainge voice behind his back. When he turned, he saw an elderlyman with a covered face. Man spoke:
"You cannot defeat him when he is awake. You must wait... thenyou shall strike. Make haste, for now his slumber begins..."
Save Princesses - is a small program to solve maze like maps with the goal to slay thedragon, before he wakes up, and save all of the princesses that he has kidnapped.
Program receives 2 dimensional array of characters that represents the maze, as wellas the wake parameter of the dragon. Set of allowed characters is defined as follows:
Character | Meaning |
---|---|
D | Dragon |
P | Princess |
C | Road |
H | Bush |
N | Wall |
Input can be provided in the form of a file (one map per file), where first line of thefile contains three numbers:rows
,cols
,dragon's wake parameter
. Rest of the filemust containrows
amount lines, where each line length equalscols
. Each charactermust be from the set of the allowed characters.
File path can be provided as an argument for the program. For example:
$ save_princesses data/map
wheredata/map
stands for the filepath. When program is called with an argument, itsolves the labyrinth provides an output to the console and exits. If file was not foundor could not be opened, program will fallback to the interaction mode.
This project comes with an interaction tool which launches when no filepath for thelabyrinth is specified as a command line argument, or if opening of the specified filehad failed.
After launching, interaction tool will greet you with the commands list:
Command | Function |
---|---|
h | output commands list |
f [filepath] | read labyrinth from the file |
g [rows] [cols] [drake_t] | generate labyrinth |
p | print current labyrinth |
s | save princesses |
q | exit program |
You can compile program with the providedMakefile
. It will compilesave_princesses
program intoout
folder relative to the project directory. Make sure you havemake
andgcc
installed on your system, then simply run:
$ make
If you wish to run provided test scenarios, you can compile them by providing additionalargumenttests
to the make:
$ make tests
Tests executable file will be put into the sameout
folder. In an old UNIX fashion, ifeverything is ok, running the executable won't produce any output to the console. However,if you wish to see what functions are being tested and what results are being produced,you can provide an additional--verbose
or-v
argument to the test program:
$ out/tests --verbose
This will run tests and output information about test functions to the console.
Project contains implementations of Dijkstra's shortest path algorithm (SPA), min-heap,and stack. Min-heap and Dijkstra's SPA are used to solve mazes: kill the dragonand save the princesses before the dragon wakes up. Stack is used for generating mapsfor testing purposes.
As mentioned above, Dijkstra's SPA and min-heap were used for maze solving.Dijkstra's algorithm works as BFS (breadth first search), but with a priority queue.
To solve the maze, it is converted to an undirected graph. All walls and intermediateroads are skipped.
When looking for the shortest path between two nodes of the graph, we start with an emptypriority queue (heap):
structPath*find_shortest_path(structGraph*graph,intstart[2],intdest[2]){structHeap*heap=new_heap(graph->nodes_count);...}
We create a boolean array that tells us which nodes we already have visited. Then We setour start node's priority to0
, because we are already on that node, and for the rest ofthe graph we set all of its nodes priorities toinfinity, or as in our case, toINT_MAX
, which represents that we haven't visited those nodes yet, and we don't knowhow far they are:
boolvisited[graph->rows*graph->cols];memset(visited,0,sizeof(bool)*graph->rows*graph->cols);reset_priorities(graph);graph->nodes[start[0]][start[1]].priority=0;
Then we insert start node into our queue:
insert(heap,&graph->nodes[start[0]][start[1]]);
Finally, we enter the main loop which runs until the queue is empty, or we've found ourdestination, in which case we justbreak
out of the loop.
Each iteration we extract (pop) the minimum value in our queue, which is always on thetop, and check if it is our destination. If yes, then we exit the loop. Otherwise we checkits neighbours, and if they were not visited, we compute their priority relative to thecurrent node, and if it is less then their current priority, we update them and insertthem into our queue:
while (!isempty(heap)) {node=extract_min(heap);if (node->row==dest[0]&&node->col==dest[1])break;for (inti=0;i<DIRECTIONS_COUNT;i++) {structNode*neigh=node->neighbours[i];if (!neigh||visited[(neigh->row*graph->cols)+neigh->col])continue;intpriority=node->priority+calc_distance(node,neigh);if (priority<neigh->priority) {set_priority(node,neigh);insert(heap,neigh);}}visited[node->row*graph->cols+node->col]= true;}
This algorithm is guaranteed to find the shortest path between two nodes of the graph.
After scanning the maze and discovering all of the key nodes (dragon and princesses), weneed to compute the most optimal path from the dragon to each princess. This can beachieved with a permutation of all possible paths:
staticvoidpermute(inttargets[][2],intstart,inttargetsc,intresults[][2],int*offset) {if (start==targetsc) {for (inti=0;i<targetsc;i++)copy_pos(results[*offset+i],targets[i]);*offset+=targetsc;return;}for (inti=start;i<targetsc;i++) {swap_pos(targets[start],targets[i]);permute(targets,start+1,targetsc,results,offset);swap_pos(targets[start],targets[i]); } }
Note: this algorithm is taken fromwww.geeksforgeeks.com.
This algorithm takesO(2 * N * N!)
time, whereN
is the amount of target coordinates.Then, for each of these permutations we find the shortest path with Dijkstra's algorithm.
Dijkstra's SPA has time complexity ofO(|V|+|E| * log|V|)
(where|V|
is the number ofnodes and|E|
is the number of edges). Combined with our permutations and initialiazationtime of the Dijkstra's algorithm, this gives usO(2 * P * P! + P! * P * ((|V| + |E| * log|V|) + L + M))
, whereP
is the amount ofprincesses.L + M
is the time it takes to concatenate two paths, whereL
is the amountof cells in the first path, andM
is the amount of cells in the second path.
Finally, we need to sort our paths and take the shortest one as the main path. For thispurposes we can use quick sort. Quick sort takesO(N * logN)
comparisons to sortN
items, In the worst case, it makesO(N^2)
comparisons. So if we take the worst casescenario, then our program will takeO(2 * P * P! + P! * P * ((|V| + 4 * log|V|) + L + M) + O(N!^2))
time to compute thebest path.4
is the maximum amount of edges that node may have in theManhattan likepaths, which is exactly the case with our mazes.
Each node of the graph has a structure:
structNode {charspan;introw;intcol;intpriority;structNode*previous;structNode*neighbours[DIRECTIONS_COUNT]; };
whereDIRECTIONS_COUNT
equals4
. This structure has size of56
bytes.
To reduce amount of generated nodes, intermediate roads are skipped. For example, map:
CCCCDCCCP
will produce only 3 nodes: starting node, dragon node, princesses node.
Map generation is realized through simple algorithm with stack. Simply put, we just walkentire map by pushing each coordinate onto stack. If we've encounter a dead end, we can easilybacktrack by popping coordinates from the stack. There isn't really much to stay aboutthe logic of this algorithm, more challenging part (at least for me) is to create wallsbetween paths. The most important bit is thiswhile
loop:
while (peek_top(stack)) {intdir=find_direction(map,visited,row,col);if (dir<0) {if (pop_pair(stack,&col,&row)<0)gotostack_failure;continue;}add_walls(map,dir,row,col);if (push_direction(stack,visited,dir,&row,&col)<0)gotostack_failure;}
Similar to searching paths, we keep track of the visited nodes. We keep traversing themap until we backtrack all the way back to the beginning of the map, i.e. the stack is empty.So in that regard, this loop is a bit similar to Dijkstra's SPA main loop.
Testing was realized through custom and random generated maps. Custom maps were takenfrom thissource.
Here are some of the testing results:
Map size | Princesses count | Distance to dragon | Time to slay dragon | Time to save princesses | Path time complexity |
---|---|---|---|---|---|
10 x 10 | 1 | 23 | ~20000 ns | ~8000 ns | 53 |
20 x 20 | 1 | 16 | ~12000 ns | ~35700 ns | 39 |
50 x 50 | 1 | 76 | ~232000 ns | ~250000 ns | 148 |
100 x 100 | 1 | 117 | ~360000 ns | ~315000 ns | 238 |
20 x 20 | 2 | 20 | ~17700 ns | ~67700 ns | 29 |
50 x 50 | 2 | 3 | ~47400 ns | ~551300 ns | 131 |
500 x 500 | 2 | 644 | ~17508500 ns | ~60907400 ns | 1763 |
500 x 500 | 3 | 580 | ~13010900 ns | ~230075800 ns | 1558 |
10 x 10 | 4 | 10 | ~13000 ns | ~360000 ns | 40 |
50 x 50 | 4 | 100 | ~300000 ns | ~8630000 ns | 319 |
100 x 100 | 4 | 154 | ~745000 ns | ~41000000 ns | 545 |
500 x 500 | 4 | 275 | ~5032400 ns | ~61251200 ns | 1852 |
10 x 10 | 5 | 1 | ~2000 ns | ~1660000 ns | 56 |
20 x 20 | 5 | 21 | ~13100 ns | ~7561700 ns | 142 |
100 x 100 | 5 | 13 | ~170000 ns | ~262000000 ns | 576 |
Nizomiddin Toshpulatov (tremul27@gmail.com)FIIT STU, Bratislava, 2020