Pac-Man Evolution
Loading...
Searching...
No Matches
MapSearchAStar.cpp
1/*----------------------------------------------------------------------
2Pac-Man Evolution - Roberto Prieto
3Copyright (C) 2018-2024 MegaStorm Systems
4contact@megastormsystems.com - http://www.megastormsystems.com
5
6This software is provided 'as-is', without any express or implied
7warranty. In no event will the authors be held liable for any damages
8arising from the use of this software.
9
10Permission is granted to anyone to use this software for any purpose,
11including commercial applications, and to alter it and redistribute it
12freely, subject to the following restrictions:
13
141. The origin of this software must not be misrepresented; you must not
15claim that you wrote the original software. If you use this software
16in a product, an acknowledgment in the product documentation would be
17appreciated but is not required.
182. Altered source versions must be plainly marked as such, and must not be
19misrepresented as being the original software.
203. This notice may not be removed or altered from any source distribution.
21
22------------------------------------------------------------------------
23
24MapSearch A* class
25
26------------------------------------------------------------------------ */
27
28#include "MapSearchAStar.h"
29#include "GameField.h"
30#include <algorithm>
31#include "Pac-Man_Evolution.h"
32
33// Class MazeNode (hidden for users)
34class MazeNode : public CMemPME
35{
36public:
37 MazeNode();
38 MazeNode(Sint32, Sint32);
39 bool isGoal(MazeNode*);
40 bool isSameState(MazeNode*);
41 void info();
42
43public:
44 Uint32 iX;
45 Uint32 iY;
46 MazeNode *pParent;
47 MazeNode *pChild;
48
49 float g; // Cost of this node plus all ancestors
50 float h; // Heuristic estimation to the target
51 float f; // Sum of g + h
52};
53
54class HeapCompare_f
55{
56public:
57 bool operator() (const MazeNode* n1, const MazeNode* n2) const { return n1->f > n2->f; }
58};
59
60// Internal defines
61#define COST_ORTHOGONAL 1.0f
62//#define DEBUG_MAPSEARCH
63
64// --- MazeNode class implementation ---
65MazeNode::MazeNode()
66{
67 pParent = pChild = nullptr;
68 g = h = f = 0.0f;
69 iX = iY = 0;
70}
71
72MazeNode::MazeNode(Sint32 iNX, Sint32 iNY)
73{
74 pParent = pChild = nullptr;
75 h = f = g = 0.0f;
76 iX = iNX; iY = iNY;
77}
78
79bool MazeNode::isSameState(MazeNode* pNode)
80{
81 if((iX == pNode->iX) && (iY == pNode->iY)) return true;
82 else return false;
83}
84
85bool MazeNode::isGoal(MazeNode* pNode)
86{
87 if((iX == pNode->iX) && (iY == pNode->iY)) return true;
88 return false;
89}
90
91void MazeNode::info()
92{
93 Main::Instance().ILogMgr().get()->msg(LML_NORMAL, " [MazeNode] Info: (%d,%d) f(%.3f)=g(%.3f)+h(%.3f)\n", iX, iY, f, g, h);
94}
95
96// --- MapSearchAStar class implementation ---
97MapSearchAStar::MapSearchAStar(GameField* GF)
98{
99 vOpenList.clear();
100 vClosedList.clear();
101 vSuccessors.clear();
102 iAStarSteps = 0;
103 eSS = SS_NOT_INITIALISED;
104 pNodeStart = nullptr;
105 pNodeGoal = nullptr;
106 pNodeCurrentSolution = nullptr;
107 iMinimumAcceptableValue = 0;
108 bCancelRequest = false;
109
110 // Debug
111 iAllocateNodeCount = 0;
112 iFreeNodeCount = 0;
113
114 // Pointer to needed components
115 pGameField = GF;
116}
117
118MapSearchAStar::~MapSearchAStar()
119{
120 pGameField = nullptr;
121}
122
123// Return a path using A* algorithm
124MapSearchAStar::eSearchState MapSearchAStar::findPath(Sint32 iXStart, Sint32 iYStart, Sint32 iXGoal, Sint32 iYGoal, vector<MazePoint>& pPath, Sint32 iValid)
125{
126 // Check that current position and destination are not the same
127 if(iXStart == iXGoal && iYStart == iYGoal) return SS_INVALID;
128
129 // Init some vars
130 iMinimumAcceptableValue = iValid;
131 iAStarSteps = 0;
132 eSS = SS_SEARCHING;
133
134 // Allocate start and goal nodes
135 iAllocateNodeCount = 0;
136 iFreeNodeCount = 0;
137 bCancelRequest = false;
138 pNodeStart = allocateNode();
139 pNodeGoal = allocateNode();
140
141 // Set start and goal nodes
142 pNodeStart->iX = iXStart; pNodeStart->iY = iYStart;
143 pNodeGoal->iX = iXGoal; pNodeGoal->iY = iYGoal;
144 pNodeStart->g = 0;
145 pNodeStart->h = heuristic(pNodeStart);
146 pNodeStart->f = pNodeStart->g + pNodeStart->h;
147 pNodeStart->pParent = 0;
148
149 // Push start node to the open list
150 vOpenList.push_back(pNodeStart);
151
152 // Main A* loop
153 do
154 {
155 AStarDoStep();
156 ++iAStarSteps;
157 } while(eSS == SS_SEARCHING);
158
159 // If the search succeeded, return the path
160 if(eSS == SS_SUCCEEDED)
161 {
162 MazeNode* node = getSolutionStart();
163 MazePoint point;
164 for(;; )
165 {
166 node = getSolutionNext();
167 if(!node) break;
168 point.iX = node->iX;
169 point.iY = node->iY;
170 pPath.push_back(point);
171 //node->info();
172 };
173 // First element of the vector is the target so we are "poping" elements from the vector
174 std::reverse(pPath.begin(), pPath.end());
175 freeSolutionNodes();
176 }
177
178 // Show info
179 #ifdef DEBUG_MAPSEARCH
180 Main::Instance().ILogMgr().get()->msg(LML_NORMAL, " [MapSearchA*] Info: iterations %d - Allocated/free nodes %d/%d - ", iAStarSteps, iAllocateNodeCount, iFreeNodeCount);
181 if(eSS == SS_FAILED) Main::Instance().ILogMgr().get()->msg(LML_NORMAL, "Goal node not found\n");
182 else Main::Instance().ILogMgr().get()->msg(LML_NORMAL, "Goal node found with %d steps\n", pPath.size());
183 #endif
184 return eSS;
185}
186
187// Heuristic method: estimation to the goal node using Manhattan distance as we allow to move in 4 directions
188float MapSearchAStar::heuristic(MazeNode* pNode)
189{
190 float fHeuristic;
191 float D, cross;
192
193 // Better to set it to close to the lowest cost between two tiles
194 D = 1.2f;
195
196 // Manhattan distance
197 float dx = abs((float)pNode->iX - (float)pNodeGoal->iX);
198 float dy = abs((float)pNode->iY - (float)pNodeGoal->iY);
199 fHeuristic = D * (dx + dy);
200
201 // Breaking ties: prefer paths that are along the straight line from start to goal nodes
202 float dx1 = (float)pNode->iX - (float)pNodeGoal->iX;
203 float dy1 = (float)pNode->iY - (float)pNodeGoal->iY;
204 float dx2 = (float)pNodeStart->iX - (float)pNodeGoal->iX;
205 float dy2 = (float)pNodeStart->iY - (float)pNodeGoal->iY;
206 cross = abs(dx1 * dy2 - dx2 * dy1);
207
208 fHeuristic = fHeuristic + cross * 0.001f;
209 return fHeuristic;
210}
211
212// Get the cost of moving to a node
213float MapSearchAStar::cost(MazeNode *successor, Sint32 x, Sint32 y)
214{
215 float fCost = COST_ORTHOGONAL;
216
217 // ToDO: with pellets we could decrease the cost
218 return fCost;
219}
220
221// A* algorithm running step by step
222void MapSearchAStar::AStarDoStep()
223{
224 // If we succeeded or failed, return
225 if((eSS == SS_SUCCEEDED) || (eSS == SS_FAILED)) return;
226
227 // If no more opened nodes or a cancel request, return
228 if(vOpenList.empty() || bCancelRequest)
229 {
230 freeAllNodes();
231 eSS = SS_FAILED;
232 return;
233 }
234
235 // Get the best node till now (lowest f)
236 MazeNode* n = vOpenList.front();
237 pop_heap(vOpenList.begin(), vOpenList.end(), HeapCompare_f());
238 vOpenList.pop_back();
239
240 // Is it the goal?
241 if(n->isGoal(pNodeGoal))
242 {
243 // The user is going to use the Goal Node he passed in so copy the parent pointer of n
244 pNodeGoal->pParent = n->pParent;
245
246 // A special case is that the goal was passed in as the start state so handle that here
247 if(n != pNodeStart)
248 {
249 freeNode(n);
250
251 // set the child pointers in each node (except Goal which has no child)
252 MazeNode* nodeChild = pNodeGoal;
253 MazeNode* nodeParent = pNodeGoal->pParent;
254
255 do
256 {
257 nodeParent->pChild = nodeChild;
258 nodeChild = nodeParent;
259 nodeParent = nodeParent->pParent;
260 } while(nodeChild != pNodeStart); // Start is always the first node by definition
261 }
262
263 // Free non-used nodes on current solution
264 freeUnusedNodes();
265 eSS = SS_SUCCEEDED;
266 return;
267 }
268
269 // No, keep searching
270 else
271 {
272 // Get new successors
273 vSuccessors.clear();
274 bool ret = getSuccessors(n, n->pParent);
275 if(!ret)
276 {
277 // free the nodes that may previously have been added
278 for(vector< MazeNode * >::iterator successor = vSuccessors.begin(); successor != vSuccessors.end(); ++successor)
279 {
280 freeNode((*successor));
281 }
282
283 vSuccessors.clear(); // empty vector of successor nodes to n
284
285 // free up everything else we allocated
286 freeAllNodes();
287
288 eSS = SS_OUT_OF_MEMORY;
289 return;
290 }
291
292 // Look for the best successor node
293 for(vector< MazeNode * >::iterator successor = vSuccessors.begin(); successor != vSuccessors.end(); ++successor)
294 {
295 float newg = n->g + cost((*successor), n->iX, n->iY);
296
297 // Now we need to find whether the node is on the open or closed lists
298 // If it is but the node that is already on them is better (lower g)
299 // then we can forget about this successor
300
301 // First linear search of open list to find node
302 vector< MazeNode * >::iterator openlist_result;
303 for(openlist_result = vOpenList.begin(); openlist_result != vOpenList.end(); ++openlist_result)
304 {
305 if((*openlist_result)->isSameState((*successor))) break;
306 }
307 if(openlist_result != vOpenList.end())
308 {
309 // we found this state on open
310 if((*openlist_result)->g <= newg)
311 {
312 freeNode((*successor));
313 // the one on Open is cheaper than this one
314 continue;
315 }
316 }
317
318 vector< MazeNode * >::iterator closedlist_result;
319 for(closedlist_result = vClosedList.begin(); closedlist_result != vClosedList.end(); ++closedlist_result)
320 {
321 if((*closedlist_result)->isSameState((*successor))) break;
322 }
323
324 if(closedlist_result != vClosedList.end())
325 {
326 // we found this state on closed
327 if((*closedlist_result)->g <= newg)
328 {
329 // the one on Closed is cheaper than this one
330 freeNode((*successor));
331 continue;
332 }
333 }
334
335 // This node is the best node so far with this particular state so lets keep it and set up its AStar specific data ...
336 (*successor)->pParent = n;
337 (*successor)->g = newg;
338 (*successor)->h = heuristic((*successor));
339 (*successor)->f = (*successor)->g + (*successor)->h;
340
341 // Remove successor from closed if it was on it
342 if(closedlist_result != vClosedList.end())
343 {
344 // remove it from Closed
345 freeNode((*closedlist_result));
346 vClosedList.erase(closedlist_result);
347 }
348
349 // Update old version of this node
350 if(openlist_result != vOpenList.end())
351 {
352 freeNode((*openlist_result));
353 vOpenList.erase(openlist_result);
354
355 // re-make the heap
356 make_heap(vOpenList.begin(), vOpenList.end(), HeapCompare_f());
357 }
358
359 // heap now unsorted
360 vOpenList.push_back((*successor));
361
362 // sort back element into heap
363 push_heap(vOpenList.begin(), vOpenList.end(), HeapCompare_f());
364 }
365
366 // push n onto Closed, as we have expanded it now
367 vClosedList.push_back(n);
368 }
369 return;
370}
371
372// Add a successor to the list of succesors
373bool MapSearchAStar::addSuccessor(MazeNode* State)
374{
375 MazeNode* node = allocateNode();
376 if(node)
377 {
378 *node = *State;
379 vSuccessors.push_back(node);
380 return true;
381 }
382 return false;
383}
384
385// Add all posible successors to the current node
386// Check the Gamefield state looking for wakable cells
387bool MapSearchAStar::getSuccessors(MazeNode *current, MazeNode *parent_node)
388{
389 MazeNode NewNode;
390 Sint32 parent_x = -1;
391 Sint32 parent_y = -1;
392
393 if(parent_node)
394 {
395 parent_x = parent_node->iX;
396 parent_y = parent_node->iY;
397 }
398
399 // Left node
400 if((pGameField->getState(current->iX - 1, current->iY) >= iMinimumAcceptableValue) && !((parent_x == current->iX - 1) && (parent_y == current->iY)))
401 {
402 NewNode = MazeNode(current->iX - 1, current->iY);
403 addSuccessor(&NewNode);
404 }
405 // Right node
406 if((pGameField->getState(current->iX + 1, current->iY) >= iMinimumAcceptableValue) && !((parent_x == current->iX + 1) && (parent_y == current->iY)))
407 {
408 NewNode = MazeNode(current->iX + 1, current->iY);
409 addSuccessor(&NewNode);
410 }
411 // Up node
412 if((pGameField->getState(current->iX, current->iY - 1) >= iMinimumAcceptableValue) && !((parent_x == current->iX) && (parent_y == current->iY - 1)))
413 {
414 NewNode = MazeNode(current->iX, current->iY - 1);
415 addSuccessor(&NewNode);
416 }
417 // Down node
418 if((pGameField->getState(current->iX, current->iY + 1) >= iMinimumAcceptableValue) && !((parent_x == current->iX) && (parent_y == current->iY + 1)))
419 {
420 NewNode = MazeNode(current->iX, current->iY + 1);
421 addSuccessor(&NewNode);
422 }
423 return true;
424}
425
426// Cancel the search
427void MapSearchAStar::cancelSearch()
428{
429 bCancelRequest = true;
430}
431
432// Create a node
433MazeNode* MapSearchAStar::allocateNode()
434{
435 iAllocateNodeCount++;
436 MazeNode* p = new(std::nothrow) MazeNode;
437 return p;
438}
439
440// Destroy a node
441void MapSearchAStar::freeNode(MazeNode* node)
442{
443 iFreeNodeCount++;
444 delete node;
445}
446
447// Free all used nodes
448void MapSearchAStar::freeAllNodes()
449{
450 // Free nodes on the opened list
451 vector<MazeNode*>::iterator iterOpen = vOpenList.begin();
452 while(iterOpen != vOpenList.end())
453 {
454 MazeNode* n = (*iterOpen);
455 freeNode(n);
456 ++iterOpen;
457 }
458 vOpenList.clear();
459
460 // Free nodes on the closed list
461 vector<MazeNode*>::iterator iterClosed;
462 for(iterClosed = vClosedList.begin(); iterClosed != vClosedList.end(); ++iterClosed)
463 {
464 MazeNode* n = (*iterClosed);
465 freeNode(n);
466 }
467 vClosedList.clear();
468}
469
470// Free all non-used nodes
471void MapSearchAStar::freeUnusedNodes()
472{
473 // Remove all non-used nodes on the opened list
474 vector<MazeNode*>::iterator iterOpen = vOpenList.begin();
475 while(iterOpen != vOpenList.end())
476 {
477 MazeNode* n = (*iterOpen);
478 if(!n->pChild) { freeNode(n); n = nullptr; }
479 ++iterOpen;
480 }
481 vOpenList.clear();
482
483 // Remove all non-used nodes on the closed list
484 vector<MazeNode*>::iterator iterClosed;
485 for(iterClosed = vClosedList.begin(); iterClosed != vClosedList.end(); ++iterClosed)
486 {
487 MazeNode* n = (*iterClosed);
488 if(!n->pChild) { freeNode(n); n = nullptr; }
489 }
490 vClosedList.clear();
491}
492
493// Free nodes used on the solution
494void MapSearchAStar::freeSolutionNodes()
495{
496 MazeNode* n = pNodeStart;
497 if(pNodeStart->pChild)
498 {
499 do
500 {
501 MazeNode* del = n;
502 n = n->pChild;
503 freeNode(del);
504 del = nullptr;
505 } while(n != pNodeGoal);
506
507 freeNode(n);
508 }
509 else
510 {
511 // Start and goal nodes are the same, only remove both
512 freeNode(pNodeStart);
513 freeNode(pNodeGoal);
514 }
515
516}
517
518// Get solution start node
519MazeNode* MapSearchAStar::getSolutionStart()
520{
521 pNodeCurrentSolution = pNodeStart;
522 if(pNodeStart) return pNodeStart;
523 else return nullptr;
524}
525
526// Get solution next node
527MazeNode* MapSearchAStar::getSolutionNext()
528{
529 if(pNodeCurrentSolution)
530 {
531 if(pNodeCurrentSolution->pChild)
532 {
533 MazeNode* child = pNodeCurrentSolution->pChild;
534 pNodeCurrentSolution = pNodeCurrentSolution->pChild;
535 return child;
536 }
537 }
538 return nullptr;
539}
540
541// Get solution last node
542MazeNode* MapSearchAStar::getSolutionEnd()
543{
544 pNodeCurrentSolution = pNodeGoal;
545 if(pNodeGoal) return pNodeGoal;
546 else return nullptr;
547}
548
549// Get solution previous node
550MazeNode* MapSearchAStar::getSolutionPrev()
551{
552 if(pNodeCurrentSolution)
553 {
554 if(pNodeCurrentSolution->pParent)
555 {
556 MazeNode* parent = pNodeCurrentSolution->pParent;
557 pNodeCurrentSolution = pNodeCurrentSolution->pParent;
558 return parent;
559 }
560 }
561 return nullptr;
562}
563
564// Debugging methods
565
566// Get opened list start node
567MazeNode* MapSearchAStar::getOpenListStart()
568{
569 float f, g, h;
570 return getOpenListStart(f, g, h);
571}
572MazeNode* MapSearchAStar::getOpenListStart(float &f, float &g, float &h)
573{
574 iterDbgOpen = vOpenList.begin();
575 if(iterDbgOpen != vOpenList.end())
576 {
577 f = (*iterDbgOpen)->f;
578 g = (*iterDbgOpen)->g;
579 h = (*iterDbgOpen)->h;
580 return (*iterDbgOpen);
581 }
582 return nullptr;
583}
584
585// Get opened list next node
586MazeNode* MapSearchAStar::getOpenListNext()
587{
588 float f, g, h;
589 return getOpenListNext(f, g, h);
590}
591MazeNode* MapSearchAStar::getOpenListNext(float &f, float &g, float &h)
592{
593 ++iterDbgOpen;
594 if(iterDbgOpen != vOpenList.end())
595 {
596 f = (*iterDbgOpen)->f;
597 g = (*iterDbgOpen)->g;
598 h = (*iterDbgOpen)->h;
599 return (*iterDbgOpen);
600 }
601 return nullptr;
602}
603
604// Get closed list start node
605MazeNode* MapSearchAStar::getClosedListStart()
606{
607 float f, g, h;
608 return getClosedListStart(f, g, h);
609}
610MazeNode* MapSearchAStar::getClosedListStart(float &f, float &g, float &h)
611{
612 iterDbgClosed = vClosedList.begin();
613 if(iterDbgClosed != vClosedList.end())
614 {
615 f = (*iterDbgClosed)->f;
616 g = (*iterDbgClosed)->g;
617 h = (*iterDbgClosed)->h;
618 return (*iterDbgClosed);
619 }
620 return nullptr;
621}
622
623// Get closed list next node
624MazeNode* MapSearchAStar::getClosedListNext()
625{
626 float f, g, h;
627 return getClosedListNext(f, g, h);
628}
629MazeNode* MapSearchAStar::getClosedListNext(float &f, float &g, float &h)
630{
631 ++iterDbgClosed;
632 if(iterDbgClosed != vClosedList.end())
633 {
634 f = (*iterDbgClosed)->f;
635 g = (*iterDbgClosed)->g;
636 h = (*iterDbgClosed)->h;
637 return (*iterDbgClosed);
638 }
639 return nullptr;
640}