Pac-Man Evolution
Loading...
Searching...
No Matches
MazeDynamic.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
24MazeGenerator class
25
26 Based on Shaun LeBron code (https://github.com/shaunlebron/pacman-mazegen) using "tetris" variant.
27
28------------------------------------------------------------------------ */
29
30#include "MazeDynamic.h"
31#include <algorithm>
32#include <map>
33#include <math.h>
34
35#define UP 0
36#define RIGHT 1
37#define DOWN 2
38#define LEFT 3
39
40#ifdef DEBUG_GENERATOR
41static double aRandsSequence[] = {0.5129719235575136, 0.34348663304017557, 0.6661675989013662, 0.8596401856537104, 0.7144370682013301, 0.6571361789295214, 0.34061865256273527, 0.26202101108360587, 0.8688143512246322, 0.28898641980463524, 0.5304491684794037, 0.5554456481914671, 0.885355280505715, 0.8190719526946917, 0.6974568050485852, 0.8828828341655219, 0.276211699397634, 0.5462919073561716, 0.4671018356657537, 0.9350492706174056, 0.4779938422203398, 0.14721994418484585, 0.07612282547423832, 0.08780624670671422, 0.5943574517779637, 0.5221223829803883, 0.950987812197468, 0.2515330680939367, 0.14530868975510303, 0.2141918743985214, 0.74386825375617, 0.04525929141234375, 0.5753222964802585, 0.09554464411922603, 0.3460382031154865, 0.5435667417622243, 0.5786996323071909, 0.7541216428297157, 0.815797431679153, 0.6148391444621712, 0.013622973716154396, 0.26998664508467574, 0.3755701627468324, 0.7366815337453705, 0.8647608188233733, 0.13491547393494874, 0.2651724843447003, 0.6839982200395627, 0.670120029478374, 0.4513346589990226, 0.148727134762499, 0.47898349686439845, 0.352502337478225, 0.013382332037334077, 0.4780248626694803, 0.11471872347846479, 0.772679046209471, 0.08110571353164886, 0.9184054717229515, 0.15836324769915255, 0.2670807865518203, 0.43575980318532426, 0.19995378915659767, 0.8578353258248839, 0.023989304783126553, 0.39804897722056665, 0.4524796211277611, 0.18039796942653386, 0.04254758763279742, 0.2051481413629721, 0.5348289162359152, 0.3487847843118419, 0.7850501537804806, 0.9191684131753015, 0.8660164189740158, 0.9956579431789447, 0.6957477848968698, 0.03147128686622458, 0.9029694124616181, 0.4411391854837914, 0.52393126757956, 0.6292688283103802, 0.5824683335701215, 0.0279643412305417, 0.7908692161361748, 0.7254442799010183, 0.8205915580620644 };
42static Sint32 iRandsSequenceSize = 87;
43#endif
44
45Maze::Maze(Sint32 iR, Sint32 iC)
46{
47 rows = iR;
48 cols = iC;
49 memset(tallRows, 0, sizeof(tallRows));
50 memset(narrowCols, 0, sizeof(narrowCols));
51 iNumRands = 0;
52
53 // Always the same seed -> deterministic mazes
54 randState = 0;
55 Main::Instance().ITool().randSeedWELL(29031978, randSeed);
56};
57
58Maze::~Maze()
59{
60 vCells.clear();
61}
62
63// Create a new maze (internal format)
64Sint32 Maze::createMaze(vector<Uint8>& vTiles)
65{
66 // try to generate a valid map, and keep count of tries.
67 Sint32 genCount = 0;
68 while(true)
69 {
70 reset();
71 generate();
72 genCount++;
73 if(!isDesirable()) continue;
74 setUpScaleCoords();
75 joinWalls();
76
77 if(!createTunnels()) continue;
78 break;
79 }
80 generateTiles(vTiles);
81#ifdef DEBUG_GENERATOR
82 for(Sint32 i = 0; i < tileCells.size(); ++i) tileCells[i].info(); // Debug information
83#endif
84 return genCount;
85}
86
87Maze::stCell::stCell()
88{
89 x = -1;
90 y = -1;
91 filled = false;
92 connect[0] = false;
93 connect[1] = false;
94 connect[2] = false;
95 connect[3] = false;
96 next[0] = nullptr;
97 next[1] = nullptr;
98 next[2] = nullptr;
99 next[3] = nullptr;
100 no = -1;
101 group = -1;
102 final_x = final_w = final_y = final_h = 0;
103 isRaiseHeightCandidate = false;
104 isShrinkWidthCandidate = false;
105 shrinkWidth = false;
106 raiseHeight = false;
107 isJoinCandidate = false;
108 topTunnel = false;
109 singleDeadEndDir = false;
110 isEdgeTunnelCandidate = false;
111 isVoidTunnelCandidate = false;
112 isSingleDeadEndCandidate = false;
113 isDoubleDeadEndCandidate = false;
114}
115
116#ifdef DEBUG_GENERATOR
117void Maze::stCell::info()
118{
119 if(x == -1) return;
120 Main& mC64 = Main::Instance();
121 Log& mLog = *mC64.ILogMgr().get();
122 mLog.msg(LML_NORMAL, "(%d,%d)-%s-", x, y, (filled == true) ? "true" : "false");
123 mLog.msg(LML_NORMAL, "%s.%s.%s.%s-",
124 (connect[0] == true) ? "true" : "false", (connect[1] == true) ? "true" : "false", (connect[2] == true) ? "true" : "false", (connect[3] == true) ? "true" : "false");
125 mLog.msg(LML_NORMAL, "%s.%s.%s.%s-",
126 (next[0] == nullptr) ? "null" : "defined", (next[1] == nullptr) ? "null" : "defined", (next[2] == nullptr) ? "null" : "defined", (next[3] == nullptr) ? "null" : "defined");
127 mLog.msg(LML_NORMAL, "%d-%d-", no, group);
128 mLog.msg(LML_NORMAL, "%d.%d.%d.%d-", final_x, final_w, final_y, final_h);
129 mLog.msg(LML_NORMAL, "%s.%s.%s.%s.%s.%s\n",
130 (isRaiseHeightCandidate) ? "isRaiseHeightCandidate" : "",
131 (isShrinkWidthCandidate) ? "isShrinkWidthCandidate" : "",
132 (shrinkWidth) ? "shrinkWidth" : "",
133 (raiseHeight) ? "raiseHeight" : "",
134 (isJoinCandidate) ? "isJoinCandidate" : "",
135 (topTunnel) ? "topTunnel" : ""
136 );
137}
138#endif
139
140Sint32 Maze::reset()
141{
142 Sint32 i;
143 stCell sCell, *pCell;
144
145 // Clean all cells
146 vCells.clear();
147
148 // initialize cells
149 for(i = 0; i < rows * cols; i++)
150 {
151 sCell.x = i % cols;
152 sCell.y = (Sint32)floor(i / cols);
153 vCells.push_back(sCell);
154 }
155
156 // allow each cell to refer to surround cells by direction
157 for(i = 0; i < rows * cols; i++)
158 {
159 pCell = &vCells[i];
160 if(pCell->x > 0) pCell->next[LEFT] = &vCells[i - 1];
161 if(pCell->x < cols - 1) pCell->next[RIGHT] = &vCells[i + 1];
162 if(pCell->y > 0) pCell->next[UP] = &vCells[i - cols];
163 if(pCell->y < rows - 1) pCell->next[DOWN] = &vCells[i + cols];
164 }
165
166 // define the ghost home square
167 i = 3 * cols;
168 pCell = &vCells[i];
169 pCell->filled = true;
170 pCell->connect[LEFT] = pCell->connect[RIGHT] = pCell->connect[DOWN] = true;
171
172 i++;
173 pCell = &vCells[i];
174 pCell->filled = true;
175 pCell->connect[LEFT] = pCell->connect[DOWN] = true;
176
177 i += cols - 1;
178 pCell = &vCells[i];
179 pCell->filled = true;
180 pCell->connect[LEFT] = pCell->connect[UP] = pCell->connect[RIGHT] = true;
181
182 i++;
183 pCell = &vCells[i];
184 pCell->filled = true;
185 pCell->connect[UP] = pCell->connect[LEFT] = true;
186
187 numFilled = numGroups = iNumRands = 0;
188 return 0;
189}
190
191#ifdef DEBUG_GENERATOR
192// Fixed sequence for debugging
193Sint32 Maze::getRandomInt(Sint32 min, Sint32 max)
194{
195 Sint32 iRet;
196 double iRnd;
197 iRnd = aRandsSequence[iNumRands % iRandsSequenceSize];
198 iRet = (Sint32)floor(iRnd * (max - min + 1)) + min;
199 iNumRands++;
200 return iRet;
201}
202
203// Fixed sequence for debugging
204double Maze::getRandomReal()
205{
206 double iRnd = aRandsSequence[iNumRands % iRandsSequenceSize];
207 iNumRands++;
208 return iRnd;
209}
210#else
211
212// Random generator
213Sint32 Maze::getRandomInt(Sint32 min, Sint32 max)
214{
215 ++iNumRands;
216 return (Sint32)floor(Main::Instance().ITool().randRealWELL(&randState, randSeed) * (max - min + 1)) + min;
217};
218
219// Random generator
220double Maze::getRandomReal()
221{
222 ++iNumRands;
223 return Main::Instance().ITool().randRealWELL(&randState, randSeed);
224}
225#endif
226
227Maze::stCell* Maze::randomElement(vector<stCell*>& vList)
228{
229 if(vList.size() > 0)
230 {
231 return vList[getRandomInt(0, (Sint32)vList.size() - 1)];
232 }
233 return nullptr;
234}
235
236Sint32 Maze::suffle(vector<stCell*>& vSuffle)
237{
238 Sint32 iLen = (Sint32)vSuffle.size();
239 Sint32 i, j;
240 stCell* pCell;
241
242 for(i = 0; i < iLen; i++)
243 {
244 j = getRandomInt(0, iLen - 1);
245 pCell = vSuffle[i];
246 vSuffle[i] = vSuffle[j];
247 vSuffle[j] = pCell;
248 }
249 return 0;
250}
251
252void Maze::getOpenCells(stCell& cell, Sint32 prevDir, Sint32 size, vector<Sint32>& openCells)
253{
254 Sint32 i;
255 openCells.clear();
256
257 for(i = 0; i < 4; i++)
258 {
259 if(isOpenCell(cell, i, prevDir, size))
260 {
261 openCells.push_back(i);
262 }
263 }
264}
265
266Sint32 Maze::getLeftMostEmptyCells(vector<stCell*>& leftCells)
267{
268 Sint32 x, y;
269 stCell* pCell;
270 leftCells.clear();
271
272 for(x = 0; x < cols; x++)
273 {
274 for(y = 0; y < rows; y++)
275 {
276 pCell = &vCells[x + y * cols];
277 if(!pCell->filled) leftCells.push_back(pCell);
278 }
279
280 if(leftCells.size() > 0) break;
281 }
282 return 0;
283}
284
285bool Maze::isOpenCell(stCell& cell, Sint32 i, Sint32 prevDir, Sint32 size)
286{
287 // prevent wall from going through starting position
288 if(cell.y == 6 && cell.x == 0 && i == DOWN || cell.y == 7 && cell.x == 0 && i == UP) return false;
289
290 // prevent long straight pieces of length 3
291 if(size == 2 && (i == prevDir || (i + 2) % 4 == prevDir)) return false;
292
293 // examine an adjacent empty cell
294 if(cell.next[i] && !cell.next[i]->filled)
295 {
296 // only open if the cell to the left of it is filled
297 if(cell.next[i]->next[LEFT] && !cell.next[i]->next[LEFT]->filled) {} // RPP out of range pointer?
298 else return true;
299 }
300 return false;
301}
302
303void Maze::connectCell(stCell& cell, Sint32 dir)
304{
305 cell.connect[dir] = true;
306 cell.next[dir]->connect[(dir + 2) % 4] = true;
307 if(cell.x == 0 && dir == RIGHT) cell.connect[LEFT] = true;
308}
309
310void Maze::setResizeCandidates()
311{
312 Sint32 i;
313 Sint32 x, y;
314 bool* q;
315 bool* q2;
316
317 for(i = 0; i < rows * cols; i++)
318 {
319 stCell& c = vCells[i];
320 x = i % cols;
321 y = (Sint32)floor(i / cols);
322
323 // determine if it has flexible height
324 // |_|
325 //
326 // or
327 // _
328 // | |
329 q = c.connect;
330 if((c.x == 0 || !q[LEFT]) && (c.x == cols - 1 || !q[RIGHT]) && q[UP] != q[DOWN]) c.isRaiseHeightCandidate = true;
331
332 // _ _
333 // |_ _|
334 //
335 stCell* c2 = c.next[RIGHT];
336 if(c2 != nullptr)
337 {
338 q2 = c2->connect;
339 if(((c.x == 0 || !q[LEFT]) && !q[UP] && !q[DOWN]) && ((c2->x == cols - 1 || !q2[RIGHT]) && !q2[UP] && !q2[DOWN])) c.isRaiseHeightCandidate = c2->isRaiseHeightCandidate = true;
340 }
341
342 // determine if it has flexible width
343 // if cell is on the right edge with an opening to the right
344 if(c.x == cols - 1 && q[RIGHT]) c.isShrinkWidthCandidate = true;
345
346 // _
347 // |_
348 //
349 // or
350 // _
351 // _|
352 //
353 if((c.y == 0 || !q[UP]) && (c.y == rows - 1 || !q[DOWN]) && q[LEFT] != q[RIGHT]) c.isShrinkWidthCandidate = true;
354 }
355}
356
357// Identify if a cell is the center of a cross.
358bool Maze::cellIsCrossCenter(stCell& c)
359{
360 return c.connect[UP] && c.connect[RIGHT] && c.connect[DOWN] && c.connect[LEFT];
361}
362
363bool Maze::canShrinkWidth(Sint32 x, Sint32 y)
364{
365 Sint32 i;
366 Sint32 numCandidates = 0;
367 stCell* c2;
368 vector<stCell*> pCandidates;
369
370 // Can cause no more tight turns.
371 if(y == rows - 1) return true;
372
373 // get the right-hand-side bound
374 for(i = x; i < cols; i++)
375 {
376 stCell& c = vCells[i + y * cols];
377 c2 = c.next[DOWN]; // RPP lacks of ;
378 if((!c.connect[RIGHT] || cellIsCrossCenter(c)) && (!c2->connect[RIGHT] || cellIsCrossCenter(*c2))) break;
379 }
380
381 // build candidate list
382 for(; c2; c2 = c2->next[LEFT])
383 {
384 if(c2->isShrinkWidthCandidate)
385 {
386 pCandidates.push_back(c2);
387 numCandidates++;
388 }
389
390 // cannot proceed further without causing irreconcilable tight turns
391 if((!c2->connect[LEFT] || cellIsCrossCenter(*c2)) && (!c2->next[UP]->connect[LEFT] || cellIsCrossCenter(*c2->next[UP]))) break;
392 }
393
394 // Shuffle the candidates
395 suffle(pCandidates);
396
397 for(i = 0; i < numCandidates; i++)
398 {
399 c2 = pCandidates[i];
400 if(canShrinkWidth(c2->x, c2->y))
401 {
402 c2->shrinkWidth = true;
403 narrowCols[c2->y] = c2->x;
404 return true;
405 }
406 }
407 return false;
408}
409
410bool Maze::chooseNarrowCols()
411{
412 Sint32 x;
413
414 for(x = cols - 1; x >= 0; x--)
415 {
416 stCell& c = vCells[x];
417 if(c.isShrinkWidthCandidate && canShrinkWidth(x, 0))
418 {
419 c.shrinkWidth = true;
420 narrowCols[c.y] = c.x;
421 return true;
422 }
423 }
424 return false;
425}
426
427bool Maze::canRaiseHeight(Sint32 x, Sint32 y)
428{
429 Sint32 i;
430 Sint32 numCandidates = 0;
431 stCell* c2;
432 vector<stCell*> pCandidates;
433
434 // Can cause no more tight turns.
435 if(x == cols - 1) return true;
436
437 // find the first cell below that will create too tight a turn on the right
438 for(i = y; i >= 0; i--)
439 {
440 stCell& c = vCells[x + i * cols];
441 c2 = c.next[RIGHT]; // RPP lacks of ;
442 if((!c.connect[UP] || cellIsCrossCenter(c)) && (!c2->connect[UP] || cellIsCrossCenter(*c2))) break;
443 }
444
445 // Proceed from the right cell upwards, looking for a cell that can be raised.
446 for(; c2; c2 = c2->next[DOWN])
447 {
448 if(c2->isRaiseHeightCandidate)
449 {
450 pCandidates.push_back(c2);
451 numCandidates++;
452 }
453
454 // cannot proceed further without causing irreconcilable tight turns
455 if((!c2->connect[DOWN] || cellIsCrossCenter(*c2)) && (!c2->next[LEFT]->connect[DOWN] || cellIsCrossCenter(*c2->next[LEFT]))) break;
456 }
457
458 // Shuffle the candidates
459 suffle(pCandidates);
460
461 for(i = 0; i < numCandidates; i++)
462 {
463 c2 = pCandidates[i];
464 if(canRaiseHeight(c2->x, c2->y))
465 {
466 c2->raiseHeight = true;
467 tallRows[c2->x] = c2->y;
468 return true;
469 }
470 }
471
472 return false;
473}
474
475bool Maze::chooseTallRows()
476{
477 // From the top left, examine cells below until hitting top of ghost house.
478 // A raisable cell must be found before the ghost house.
479 Sint32 y;
480 for(y = 0; y < 3; y++)
481 {
482 stCell& c = vCells[y*cols];
483 if(c.isRaiseHeightCandidate && canRaiseHeight(0, y))
484 {
485 c.raiseHeight = true;
486 tallRows[c.x] = c.y;
487 return true;
488 }
489 }
490 return false;
491}
492
493// ensure there are no two stacked/side-by-side 2-cell pieces.
494bool Maze::isHori(Sint32 x, Sint32 y)
495{
496 bool* q1 = vCells[x + y * cols].connect;
497 bool* q2 = vCells[x + 1 + y * cols].connect;
498 return !q1[UP] && !q1[DOWN] && (x == 0 || !q1[LEFT]) && q1[RIGHT] && !q2[UP] && !q2[DOWN] && q2[LEFT] && !q2[RIGHT];
499}
500
501bool Maze::isVert(Sint32 x, Sint32 y)
502{
503 bool* q1 = vCells[x + y * cols].connect;
504 bool* q2 = vCells[x + (y + 1)*cols].connect;
505 if(x == cols - 1)
506 {
507 // special case (we can consider two single cells as vertical at the right edge)
508 return !q1[LEFT] && !q1[UP] && !q1[DOWN] && !q2[LEFT] && !q2[UP] && !q2[DOWN];
509 }
510 return !q1[LEFT] && !q1[RIGHT] && !q1[UP] && q1[DOWN] && !q2[LEFT] && !q2[RIGHT] && q2[UP] && !q2[DOWN];
511}
512
513// This is a function to detect impurities in the map that have no heuristic implemented to avoid it yet in gen().
514bool Maze::isDesirable()
515{
516 Sint32 x, y, g;
517
518 // ensure a solid top right corner
519 stCell& rCTR = vCells[4];
520 if(rCTR.connect[UP] || rCTR.connect[RIGHT]) return false;
521
522 // ensure a solid bottom right corner
523 stCell& rCBR = vCells[rows*cols - 1];
524 if(rCBR.connect[DOWN] || rCBR.connect[RIGHT]) return false;
525
526 for(y = 0; y < rows - 1; y++)
527 {
528 for(x = 0; x < cols - 1; x++)
529 {
530 if(isHori(x, y) && isHori(x, y + 1) || isVert(x, y) && isVert(x + 1, y))
531 {
532 // don't allow them in the middle because they'll be two large when reflected.
533 if(x == 0) return false;
534
535 // Join the four cells to create a square.
536 vCells[x + y * cols].connect[DOWN] = true;
537 vCells[x + y * cols].connect[RIGHT] = true;
538 g = vCells[x + y * cols].group;
539
540 vCells[x + 1 + y * cols].connect[DOWN] = true;
541 vCells[x + 1 + y * cols].connect[LEFT] = true;
542 vCells[x + 1 + y * cols].group = g;
543
544 vCells[x + (y + 1)*cols].connect[UP] = true;
545 vCells[x + (y + 1)*cols].connect[RIGHT] = true;
546 vCells[x + (y + 1)*cols].group = g;
547
548 vCells[x + 1 + (y + 1)*cols].connect[UP] = true;
549 vCells[x + 1 + (y + 1)*cols].connect[LEFT] = true;
550 vCells[x + 1 + (y + 1)*cols].group = g;
551 }
552 }
553 }
554
555 if(!chooseTallRows()) return false;
556
557 if(!chooseNarrowCols()) return false;
558
559 return true;
560}
561
562// set the final position and size of each cell when upscaling the simple model to actual size
563void Maze::setUpScaleCoords()
564{
565 Sint32 i;
566 for(i = 0; i < rows * cols; i++)
567 {
568 stCell& c = vCells[i];
569 c.final_x = c.x * 3;
570 if(narrowCols[c.y] < c.x) c.final_x--;
571 c.final_y = c.y * 3;
572 if(tallRows[c.x] < c.y) c.final_y++;
573 c.final_w = c.shrinkWidth ? 2 : 3;
574 c.final_h = c.raiseHeight ? 4 : 3;
575 }
576}
577
578// randomly join wall pieces to the boundary to increase difficulty
579void Maze::joinWalls()
580{
581 Sint32 x, y;
582
583 // join cells to the top boundary
584 for(x = 0; x < cols; x++)
585 {
586 stCell& c = vCells[x];
587 if(!c.connect[LEFT] && !c.connect[RIGHT] && !c.connect[UP] && (!c.connect[DOWN] || !c.next[DOWN]->connect[DOWN])) // RPP out of range pointer?
588 {
589 // ensure it will not create a dead-end
590 if((!c.next[LEFT] || !c.next[LEFT]->connect[UP]) && (c.next[RIGHT] && !c.next[RIGHT]->connect[UP])) // RPP out of range pointer?
591 {
592 // prevent connecting very large piece
593 if(!(c.next[DOWN] && c.next[DOWN]->connect[RIGHT] && c.next[DOWN]->next[RIGHT]->connect[RIGHT]))
594 {
595 c.isJoinCandidate = true;
596 if(getRandomReal() <= 0.25) c.connect[UP] = true;
597 }
598 }
599 }
600 }
601
602 // join cells to the bottom boundary
603 for(x = 0; x < cols; x++)
604 {
605 stCell& c = vCells[x + (rows - 1)*cols];
606 if(!c.connect[LEFT] && !c.connect[RIGHT] && !c.connect[DOWN] && (!c.connect[UP] || !c.next[UP]->connect[UP]))
607 {
608 // ensure it will not creat a dead-end
609 if((!c.next[LEFT] || !c.next[LEFT]->connect[DOWN]) && (c.next[RIGHT] && !c.next[RIGHT]->connect[DOWN]))
610 {
611 // prevent connecting very large piece
612 if(!(c.next[UP] && c.next[UP]->connect[RIGHT] && c.next[UP]->next[RIGHT]->connect[RIGHT]))
613 {
614 c.isJoinCandidate = true;
615 if(getRandomReal() <= 0.25) c.connect[DOWN] = true;
616 }
617 }
618 }
619 }
620
621 // join cells to the right boundary
622 for(y = 1; y < rows - 1; y++)
623 {
624 stCell& c = vCells[cols - 1 + y * cols];
625 if(c.raiseHeight) continue;
626 if(!c.connect[RIGHT] && !c.connect[UP] && !c.connect[DOWN] && !c.next[UP]->connect[RIGHT] && !c.next[DOWN]->connect[RIGHT])
627 {
628 if(c.connect[LEFT])
629 {
630 stCell* c2 = c.next[LEFT];
631 if(!c2->connect[UP] && !c2->connect[DOWN] && !c2->connect[LEFT])
632 {
633 c.isJoinCandidate = true;
634 if(getRandomReal() <= 0.5) c.connect[RIGHT] = true;
635 }
636 }
637 }
638 }
639}
640
641void Maze::fillCell(stCell& cell)
642{
643 cell.filled = true;
644 cell.no = numFilled++;
645 cell.group = numGroups;
646}
647
648void Maze::selectSingleDeadEnd(stCell* c)
649{
650 c->connect[RIGHT] = true;
651 if(c->singleDeadEndDir == UP)
652 {
653 c->topTunnel = true;
654 }
655 else
656 {
657 c->next[DOWN]->topTunnel = true;
658 }
659};
660
661void Maze::replaceGroup(Sint32 oldg, Sint32 newg)
662{
663 Sint32 i;
664
665 for(i = 0; i < rows*cols; i++)
666 {
667 stCell& c = vCells[i];
668 if(c.group == oldg) c.group = newg;
669 }
670}
671
672bool Maze::createTunnels()
673{
674 // declare candidates
675 vector<stCell*> edgeTunnelCells;
676 vector<stCell*> topEdgeTunnelCells;
677 vector<stCell*> botEdgeTunnelCells;
678 vector<stCell*> voidTunnelCells;
679 vector<stCell*> topVoidTunnelCells;
680 vector<stCell*> botVoidTunnelCells;
681 vector<stCell*> singleDeadEndCells;
682 vector<stCell*> topSingleDeadEndCells;
683 vector<stCell*> botSingleDeadEndCells;
684 vector<stCell*> doubleDeadEndCells;
685
686 Sint32 numTunnelsCreated = 0;
687 Sint32 y;
688 bool upDead, downDead;
689 stCell* pCell;
690
691 // prepare candidates
692 for(y = 0; y < rows; y++)
693 {
694 stCell& c = vCells[cols - 1 + y * cols];
695 if(c.connect[UP])
696 {
697 continue;
698 }
699 if(c.y > 1 && c.y < rows - 2)
700 {
701 c.isEdgeTunnelCandidate = true;
702 edgeTunnelCells.push_back(&c);
703 if(c.y <= 2)
704 {
705 topEdgeTunnelCells.push_back(&c);
706 }
707 else if(c.y >= 5)
708 {
709 botEdgeTunnelCells.push_back(&c);
710 }
711 }
712 upDead = (!c.next[UP] || c.next[UP]->connect[RIGHT]);
713 downDead = (!c.next[DOWN] || c.next[DOWN]->connect[RIGHT]);
714 if(c.connect[RIGHT])
715 {
716 if(upDead)
717 {
718 c.isVoidTunnelCandidate = true;
719 voidTunnelCells.push_back(&c);
720 if(c.y <= 2)
721 {
722 topVoidTunnelCells.push_back(&c);
723 }
724 else if(c.y >= 6)
725 {
726 botVoidTunnelCells.push_back(&c);
727 }
728 }
729 }
730 else
731 {
732 if(c.connect[DOWN])
733 {
734 continue;
735 }
736 if(upDead != downDead)
737 {
738 if(!c.raiseHeight && y < rows - 1 && !c.next[LEFT]->connect[LEFT])
739 {
740 singleDeadEndCells.push_back(&c);
741 c.isSingleDeadEndCandidate = true;
742 c.singleDeadEndDir = upDead ? UP : DOWN;
743 Sint32 offset = upDead ? 1 : 0;
744 if(c.y <= 1 + offset)
745 {
746 topSingleDeadEndCells.push_back(&c);
747 }
748 else if(c.y >= 5 + offset)
749 {
750 botSingleDeadEndCells.push_back(&c);
751 }
752 }
753 }
754 else if(upDead && downDead)
755 {
756 if(y > 0 && y < rows - 1)
757 {
758 if(c.next[LEFT]->connect[UP] && c.next[LEFT]->connect[DOWN])
759 {
760 c.isDoubleDeadEndCandidate = true;
761 if(c.y >= 2 && c.y <= 5)
762 {
763 doubleDeadEndCells.push_back(&c);
764 }
765 }
766 }
767 }
768 }
769 }
770
771 // choose tunnels from candidates
772 Sint32 numTunnelsDesired = getRandomReal() <= 0.45 ? 2 : 1;
773 if(numTunnelsDesired == 1)
774 {
775 if(pCell = randomElement(voidTunnelCells))
776 {
777 pCell->topTunnel = true;
778 }
779 else if(pCell = randomElement(singleDeadEndCells))
780 {
781 selectSingleDeadEnd(pCell);
782 }
783 else if(pCell = randomElement(edgeTunnelCells))
784 {
785 pCell->topTunnel = true;
786 }
787 else
788 {
789 return false;
790 }
791 }
792 else if(numTunnelsDesired == 2)
793 {
794 if(pCell = randomElement(doubleDeadEndCells))
795 {
796 pCell->connect[RIGHT] = true;
797 pCell->topTunnel = true;
798 pCell->next[DOWN]->topTunnel = true;
799 }
800 else
801 {
802 numTunnelsCreated = 1;
803 if(pCell = randomElement(topVoidTunnelCells))
804 {
805 pCell->topTunnel = true;
806 }
807 else if(pCell = randomElement(topSingleDeadEndCells))
808 {
809 selectSingleDeadEnd(pCell);
810 }
811 else if(pCell = randomElement(topEdgeTunnelCells))
812 {
813 pCell->topTunnel = true;
814 }
815 else
816 {
817 // could not find a top tunnel opening
818 numTunnelsCreated = 0;
819 }
820
821 if(pCell = randomElement(botVoidTunnelCells))
822 {
823 pCell->topTunnel = true;
824 }
825 else if(pCell = randomElement(botSingleDeadEndCells))
826 {
827 selectSingleDeadEnd(pCell);
828 }
829 else if(pCell = randomElement(botEdgeTunnelCells))
830 {
831 pCell->topTunnel = true;
832 }
833 else
834 {
835 // could not find a bottom tunnel opening
836 if(numTunnelsCreated == 0)
837 {
838 return false;
839 }
840 }
841 }
842 }
843
844 // don't allow a horizontal path to cut straight through a map (through tunnels)
845 bool exit;
846 Sint32 topy;
847 for(y = 0; y < rows; y++)
848 {
849 pCell = &vCells[cols - 1 + y * cols];
850 if(pCell->topTunnel)
851 {
852 exit = true;
853 topy = pCell->final_y;
854 while(pCell->next[LEFT])
855 {
856 pCell = pCell->next[LEFT];
857 if(!pCell->connect[UP] && pCell->final_y == topy)
858 {
859 continue;
860 }
861 else
862 {
863 exit = false;
864 break;
865 }
866 }
867 if(exit)
868 {
869 return false;
870 }
871 }
872 }
873
874 // clear unused void tunnels (dead ends)
875 Sint32 len = (Sint32)voidTunnelCells.size();
876 Sint32 i;
877 for(i = 0; i < len; i++)
878 {
879 pCell = voidTunnelCells[i];
880 if(!pCell->topTunnel)
881 {
882 replaceGroup(pCell->group, pCell->next[UP]->group);
883 pCell->connect[UP] = true;
884 pCell->next[UP]->connect[DOWN] = true;
885 }
886 }
887
888 return true;
889}
890
891// generate a maze (internal format)
892void Maze::generate()
893{
894 vector<stCell*> openCells; // list of open cells around the center cell
895 Sint32 numOpenCells; // size of openCells // RPP remove this and use .size()
896 vector<Sint32> nearCells;
897 stCell* newCell = nullptr; // most recent cell filled
898
899 Sint32 dir = -1; // the most recent direction of growth relative to the center cell
900 Sint32 i; // loop control variable used for iterating directions
901
902 Sint32 size; // current number of cells in the current group
903 double probStopGrowingAtSize[] = { // probability of stopping growth at sizes...
904 0, // size 0
905 0, // size 1
906 0.10, // size 2
907 0.5, // size 3
908 0.75, // size 4
909 1 }; // size 5
910
911 // A single cell group of size 1 is allowed at each row at y=0 and y=rows-1,
912 // so keep count of those created.
913 std::map<Sint32, Sint32> singleCount;
914 singleCount.insert(std::pair<Sint32, Sint32>(0, 0));
915 singleCount.insert(std::pair<Sint32, Sint32>(rows - 1, 0));
916 double probTopAndBotSingleCellJoin = 0.35;
917
918 // A count and limit of the number long pieces (i.e. an "L" of size 4 or "T" of size 5)
919 Sint32 longPieces = 0;
920 Sint32 maxLongPieces = 1;
921 Sint32 probExtendAtSize2 = 1;
922 double probExtendAtSize3or4 = 0.5;
923
924 for(numGroups = 0; ; numGroups++)
925 {
926 // find all the leftmost empty cells
927 getLeftMostEmptyCells(openCells);
928
929 // stop add pieces if there are no more empty cells.
930 numOpenCells = (Sint32)openCells.size();
931 if(numOpenCells == 0) break;
932
933 // choose the center cell to be a random open cell, and fill it.
934 stCell* firstCell = openCells[getRandomInt(0, numOpenCells - 1)]; // cell at the center of growth (open cells are chosen around this cell)
935 stCell* pCell = firstCell; // the starting cell of the current group
936
937 fillCell(*pCell);
938
939 // randomly allow one single-cell piece on the top or bottom of the map.
940 if(pCell->x < cols - 1 && (singleCount.count(pCell->y)) && getRandomReal() <= probTopAndBotSingleCellJoin)
941 {
942 if(singleCount[pCell->y] == 0)
943 {
944 pCell->connect[pCell->y == 0 ? UP : DOWN] = true;
945 singleCount[pCell->y]++;
946 continue;
947 }
948 }
949
950 // number of cells in this contiguous group
951 size = 1;
952
953 if(pCell->x == cols - 1)
954 {
955 // if the first cell is at the right edge, then don't grow it.
956 pCell->connect[RIGHT] = true;
957 pCell->isRaiseHeightCandidate = true;
958 }
959 else
960 {
961 // only allow the piece to grow to 5 cells at most.
962 while(size < 5)
963 {
964 bool stop = false;
965 if(size == 2)
966 {
967 // With a horizontal 2-cell group, try to turn it into a 4-cell "L" group.
968 // This is done here because this case cannot be reached when a piece has already grown to size 3.
969 stCell& c = *firstCell;
970 if(c.x > 0 && c.connect[RIGHT] && c.next[RIGHT] && c.next[RIGHT]->next[RIGHT])
971 {
972 if(longPieces < maxLongPieces && getRandomReal() <= probExtendAtSize2)
973 {
974 stCell* pC = c.next[RIGHT]->next[RIGHT];
975 std::map<Sint32, bool> dirs;
976 if(isOpenCell(*pC, UP)) dirs[UP] = true;
977 if(isOpenCell(*pC, DOWN)) dirs[DOWN] = true;
978 if(dirs[UP] && dirs[DOWN]) i = (getRandomInt(0, 1) == 0 ? UP : DOWN);
979 else if(dirs[UP]) i = UP;
980 else if(dirs[DOWN]) i = DOWN;
981 else i = -1;
982
983 if(i != -1)
984 {
985 connectCell(*pC, LEFT);
986 fillCell(*pC);
987 connectCell(*pC, i);
988 fillCell(*pC->next[i]);
989 longPieces++;
990 size += 2;
991 stop = true;
992 }
993 }
994 }
995 }
996
997 if(!stop)
998 {
999 // find available open adjacent cells.
1000 getOpenCells(*pCell, dir, size, nearCells);
1001 numOpenCells = (Sint32)nearCells.size();
1002
1003 // if no open cells found from center point, then use the last cell as the new center
1004 // but only do this if we are of length 2 to prevent numerous short pieces.
1005 // then recalculate the open adjacent cells.
1006 if(nearCells.size() == 0 && size == 2)
1007 {
1008 pCell = newCell;
1009 getOpenCells(*pCell, dir, size, nearCells);
1010 numOpenCells = (Sint32)nearCells.size();
1011 }
1012
1013 // no more adjacent cells, so stop growing this piece.
1014 if(numOpenCells == 0) stop = true;
1015 else
1016 {
1017 // choose a random valid direction to grow.
1018 dir = nearCells[getRandomInt(0, numOpenCells - 1)];
1019 newCell = pCell->next[dir];
1020
1021 // connect the cell to the new cell.
1022 connectCell(*pCell, dir);
1023
1024 // fill the cell
1025 fillCell(*newCell);
1026
1027 // increase the size count of this piece.
1028 size++;
1029
1030 // don't let center pieces grow past 3 cells
1031 if(firstCell->x == 0 && size == 3) stop = true;
1032
1033 // Use a probability to determine when to stop growing the piece.
1034 if(getRandomReal() <= probStopGrowingAtSize[size]) stop = true;
1035 }
1036 }
1037
1038 // Close the piece.
1039 if(stop)
1040 {
1041 if(size == 1)
1042 {
1043 // This is probably impossible because this loop is never entered with size==1.
1044 }
1045 else if(size == 2)
1046 {
1047 // With a vertical 2-cell group, attach to the right wall if adjacent.
1048 stCell& c = *firstCell;
1049 if(c.x == cols - 1)
1050 {
1051 // select the top cell
1052 if(c.connect[UP]) c = *c.next[UP];
1053 c.connect[RIGHT] = c.next[DOWN]->connect[RIGHT] = true;
1054 }
1055 }
1056 else if(size == 3 || size == 4)
1057 {
1058 // Try to extend group to have a long leg
1059 if(longPieces < maxLongPieces && firstCell->x > 0 && getRandomReal() <= probExtendAtSize3or4)
1060 {
1061 vector<Sint32> dirs;
1062 dirs.clear();
1063 Sint32 dirsLength = 0;
1064 for(i = 0; i < 4; i++)
1065 {
1066 if(pCell->connect[i] && isOpenCell(*pCell->next[i], i))
1067 {
1068 dirs.push_back(i);
1069 dirsLength++;
1070 }
1071 }
1072 if(dirsLength > 0)
1073 {
1074 i = dirs[getRandomInt(0, dirsLength - 1)];
1075 stCell& c = *pCell->next[i];
1076 connectCell(c, i);
1077 fillCell(*c.next[i]);
1078 longPieces++;
1079 }
1080 }
1081 }
1082 break;
1083 }
1084 }
1085 }
1086 }
1087 setResizeCandidates();
1088}
1089
1090// getter and setter for tiles (ensures vertical symmetry axis)
1091void Maze::setTile(Sint32 x, Sint32 y, Uint8 v, vector<Uint8>& tiles)
1092{
1093 if(x<0 || x>subcols - 1 || y<0 || y>subrows - 1) return;
1094 x -= 2;
1095 tiles[midcols + x + y * fullcols] = v;
1096 tiles[midcols - 1 - x + y * fullcols] = v;
1097}
1098
1099Sint32 Maze::getTile(Sint32 x, Sint32 y, vector<Uint8>& tiles)
1100{
1101 if(x<0 || x>subcols - 1 || y<0 || y>subrows - 1) return -1;
1102 x -= 2;
1103 return tiles[midcols + x + y * fullcols];
1104}
1105
1106// getter and setter for tile cells
1107void Maze::setTileCell(Sint32 x, Sint32 y, stCell& cell)
1108{
1109 if(x<0 || x>subcols - 1 || y<0 || y>subrows - 1) return;
1110 x -= 2;
1111 tileCells[x + y * subcols] = cell;
1112}
1113
1114Maze::stCell* Maze::getTileCell(Sint32 x, Sint32 y)
1115{
1116 Sint32 i;
1117 if(x<0 || x>subcols - 1 || y<0 || y>subrows - 1) return nullptr;
1118 x -= 2;
1119 i = x + y * subcols;
1120 if(i < 0) return nullptr; // Out of bounds
1121 if(tileCells[i].x == -1) return nullptr; // Undefined elements
1122 return &tileCells[i];
1123}
1124
1125bool Maze::getTopEnergizerRange(Sint32& miny, Sint32& maxy, vector<Uint8>& tiles)
1126{
1127 Sint32 x = subcols - 2;
1128 Sint32 y;
1129 maxy = subrows / 2;
1130
1131 for(y = 2; y < maxy; y++)
1132 {
1133 if(getTile(x, y, tiles) == '.' && getTile(x, y + 1, tiles) == '.')
1134 {
1135 miny = y + 1;
1136 break;
1137 }
1138 }
1139 maxy = (std::min)(maxy, miny + 7); // VS: bypass min macro definition
1140 for(y = miny + 1; y < maxy; y++)
1141 {
1142 if(getTile(x - 1, y, tiles) == '.')
1143 {
1144 maxy = y - 1;
1145 break;
1146 }
1147 }
1148 return true;
1149}
1150
1151bool Maze::getBotEnergizerRange(Sint32& miny, Sint32& maxy, vector<Uint8>& tiles)
1152{
1153 Sint32 x = subcols - 2;
1154 Sint32 y;
1155 miny = subrows / 2;
1156 for(y = subrows - 3; y >= miny; y--)
1157 {
1158 if(getTile(x, y, tiles) == '.' && getTile(x, y + 1, tiles) == '.')
1159 {
1160 maxy = y;
1161 break;
1162 }
1163 }
1164 miny = (std::max)(miny, maxy - 7); // VS: bypass max macro definition
1165 for(y = maxy - 1; y > miny; y--)
1166 {
1167 if(getTile(x - 1, y, tiles) == '.')
1168 {
1169 miny = y + 1;
1170 break;
1171 }
1172 }
1173 return true;
1174}
1175
1176// erase pellets in the tunnels
1177void Maze::eraseUntilIntersection(Sint32 x, Sint32 y, vector<Uint8>& tiles)
1178{
1179 struct sAdj
1180 {
1181 Sint32 x, y;
1182 } sAdjust;
1183 vector<sAdj> vAdj;
1184
1185 //var adj;
1186 while(true)
1187 {
1188 if(getTile(x - 1, y, tiles) == '.')
1189 {
1190 sAdjust.x = x - 1;
1191 sAdjust.y = y;
1192 vAdj.push_back(sAdjust);
1193 }
1194 if(getTile(x + 1, y, tiles) == '.')
1195 {
1196 sAdjust.x = x + 1;
1197 sAdjust.y = y;
1198 vAdj.push_back(sAdjust);
1199 }
1200 if(getTile(x, y - 1, tiles) == '.')
1201 {
1202 sAdjust.x = x;
1203 sAdjust.y = y - 1;
1204 vAdj.push_back(sAdjust);
1205 }
1206 if(getTile(x, y + 1, tiles) == '.')
1207 {
1208 sAdjust.x = x;
1209 sAdjust.y = y + 1;
1210 vAdj.push_back(sAdjust);
1211 }
1212 if(vAdj.size() == 1)
1213 {
1214 setTile(x, y, ' ', tiles);
1215 x = vAdj[0].x;
1216 y = vAdj[0].y;
1217 }
1218 else
1219 {
1220 break;
1221 }
1222 }
1223}
1224
1225#ifdef DEBUG_GENERATOR
1226// Debugging maze tiles...
1227#include <fstream>
1228#include <sstream>
1229#endif
1230
1231Sint32 Maze::generateTiles(vector<Uint8>& vTiles)
1232{
1233 Sint32 i, j, x, y, x0, y0;
1234 stCell* c;
1235 vTiles.clear(); // each is a character indicating a wall(|), path(.), or blank(_).
1236 tileCells.clear(); // maps each tile to a specific cell of our simple map
1237 subrows = rows * 3 + 1 + 3;
1238 subcols = cols * 3 - 1 + 2;
1239 midcols = subcols - 2;
1240 fullcols = (subcols - 2) * 2;
1241
1242 // initialize tiles
1243 stCell cellContainer; // Todo: with a constructor it could be initiated
1244 for(i = 0; i < subrows*fullcols; i++) vTiles.push_back('_');
1245 for(i = 0; i < subrows*subcols; i++) tileCells.push_back(cellContainer);
1246
1247 // set tile cells
1248 for(i = 0; i < rows*cols; i++)
1249 {
1250 c = &vCells[i];
1251 for(x0 = 0; x0 < c->final_w; x0++)
1252 {
1253 for(y0 = 0; y0 < c->final_h; y0++)
1254 {
1255 setTileCell(c->final_x + x0, c->final_y + 1 + y0, *c);
1256 }
1257 }
1258 }
1259
1260 // set path tiles
1261 stCell* cl; stCell* cu;
1262 for(y = 0; y < subrows; y++)
1263 {
1264 for(x = 0; x < subcols; x++)
1265 {
1266 c = getTileCell(x, y); // cell
1267 cl = getTileCell(x - 1, y); // left cell
1268 cu = getTileCell(x, y - 1); // up cell
1269
1270 if(c)
1271 {
1272 // inside map
1273 if(cl && c->group != cl->group || // at vertical boundary
1274 cu && c->group != cu->group || // at horizontal boundary
1275 !cu && !c->connect[UP]) // at top boundary
1276 {
1277 setTile(x, y, '.', vTiles);
1278 }
1279 }
1280 else
1281 {
1282 // outside map
1283 if(cl && (!cl->connect[RIGHT] || getTile(x - 1, y, vTiles) == '.') || // at right boundary
1284 cu && (!cu->connect[DOWN] || getTile(x, y - 1, vTiles) == '.')) // at bottom boundary
1285 {
1286 setTile(x, y, '.', vTiles);
1287 }
1288 }
1289
1290 // at corner connecting two paths
1291 if(getTile(x - 1, y, vTiles) == '.' && getTile(x, y - 1, vTiles) == '.' && getTile(x - 1, y - 1, vTiles) == '_') setTile(x, y, '.', vTiles);
1292 }
1293 }
1294
1295 #ifdef DEBUG_GENERATOR
1296 // Debugging maze tiles...
1297 std::ofstream vFile("DumpC-Tiles.txt");
1298 if(!vFile) return -1;
1299 for(i = 0; i < vTiles.size(); ++i)
1300 {
1301 vFile << vTiles[i] << std::endl;
1302 }
1303 vFile.close();
1304 #endif
1305
1306 // extend tunnels
1307 for(c = &vCells[cols - 1]; c; c = c->next[DOWN])
1308 {
1309 if(c->topTunnel)
1310 {
1311 y = c->final_y + 1;
1312 setTile(subcols - 1, y, '.', vTiles);
1313 setTile(subcols - 2, y, '.', vTiles);
1314 }
1315 }
1316
1317 // fill in walls
1318 for(y = 0; y < subrows; y++)
1319 {
1320 for(x = 0; x < subcols; x++)
1321 {
1322 // any blank tile that shares a vertex with a path tile should be a wall tile
1323 if(getTile(x, y, vTiles) != '.' && (getTile(x - 1, y, vTiles) == '.' || getTile(x, y - 1, vTiles) == '.' || getTile(x + 1, y, vTiles) == '.' || getTile(x, y + 1, vTiles) == '.' ||
1324 getTile(x - 1, y - 1, vTiles) == '.' || getTile(x + 1, y - 1, vTiles) == '.' || getTile(x + 1, y + 1, vTiles) == '.' || getTile(x - 1, y + 1, vTiles) == '.'))
1325 {
1326 setTile(x, y, '|', vTiles);
1327 }
1328 }
1329 }
1330
1331 // create the ghost door
1332 setTile(2, 12, '-', vTiles);
1333
1334 // set energizers
1335 x = subcols - 2;
1336 Sint32 miny, maxy;
1337
1338 if(getTopEnergizerRange(miny, maxy, vTiles))
1339 {
1340 y = getRandomInt(miny, maxy);
1341 setTile(x, y, 'o', vTiles);
1342 }
1343 if(getBotEnergizerRange(miny, maxy, vTiles))
1344 {
1345 y = getRandomInt(miny, maxy);
1346 setTile(x, y, 'o', vTiles);
1347 }
1348
1349 x = subcols - 1;
1350 for(y = 0; y < subrows; y++)
1351 {
1352 if(getTile(x, y, vTiles) == '.')
1353 {
1354 eraseUntilIntersection(x, y, vTiles);
1355 }
1356 }
1357
1358 // erase pellets on starting position
1359 setTile(1, subrows - 8, ' ', vTiles);
1360
1361 // erase pellets around the ghost house
1362 for(i = 0; i < 7; i++)
1363 {
1364 // erase pellets from bottom of the ghost house proceeding down until
1365 // reaching a pellet tile that isn't surround by walls
1366 // on the left and right
1367 y = subrows - 14;
1368 setTile(i, y, ' ', vTiles);
1369 j = 1;
1370 while(getTile(i, y + j, vTiles) == '.' &&
1371 getTile(i - 1, y + j, vTiles) == '|' &&
1372 getTile(i + 1, y + j, vTiles) == '|')
1373 {
1374 setTile(i, y + j, ' ', vTiles);
1375 j++;
1376 }
1377
1378 // erase pellets from top of the ghost house proceeding up until
1379 // reaching a pellet tile that isn't surround by walls
1380 // on the left and right
1381 y = subrows - 20;
1382 setTile(i, y, ' ', vTiles);
1383 j = 1;
1384 while(getTile(i, y - j, vTiles) == '.' &&
1385 getTile(i - 1, y - j, vTiles) == '|' &&
1386 getTile(i + 1, y - j, vTiles) == '|')
1387 {
1388 setTile(i, y - j, ' ', vTiles);
1389 j++;
1390 }
1391 }
1392 // erase pellets on the side of the ghost house
1393 for(i = 0; i < 7; i++)
1394 {
1395 // erase pellets from side of the ghost house proceeding right until
1396 // reaching a pellet tile that isn't surround by walls
1397 // on the top and bottom.
1398 x = 6;
1399 y = subrows - 14 - i;
1400 setTile(x, y, ' ', vTiles);
1401 j = 1;
1402 while(getTile(x + j, y, vTiles) == '.' &&
1403 getTile(x + j, y - 1, vTiles) == '|' &&
1404 getTile(x + j, y + 1, vTiles) == '|')
1405 {
1406 setTile(x + j, y, ' ', vTiles);
1407 j++;
1408 }
1409 }
1410 return 0;
1411}