Pac-Man Evolution
Loading...
Searching...
No Matches
GameField.cpp
1/*----------------------------------------------------------------------
2Pac-Man Evolution - Roberto Prieto
3 Copyright (C) 2018-2025 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
24GameField class
25
26------------------------------------------------------------------------ */
27
28// Includes
29#include "GameField.h"
30#include "ResourceManager.h"
31#include "MazeDynamic.h"
32#include "MazeStatic.h"
33#include "BrainsFactory.h"
34#include "MapSearchAStar.h"
35#include "Objects.h"
36#include "ObjectsPacMan.h"
37#include "ObjectsGhost.h"
38#include <math.h>
39
40// Constructor
41GameField::GameField(GlobalStatus* GS)
42{
43 // Initialize variables
44 sGlobalWave.reset();
45 iMazeReady = iNumPellets = iNumEatenPellets = 0;
46 iTimeStart = 0;
47 iGetReadyTime = PME_GETREADY_TIME;
48 iMazePixelX = iMazePixelY = 0;
49 iFieldArray = nullptr;
50 vObjects.clear();
51 iRenderGraphicsStatus = -1;
52 iSpecialTextCounter = -1;
53 sSpecialMessageCounter.clear();
54 sSpecialMessage.clear();
55 bDebug = bDebugDisableObjectRender = -1;
56 bDebugShowTarget = 1;
57 iDebugMode = 0;
58
59 // Internal components
60 pMapSearch = new(std::nothrow) MapSearchAStar(this);
61 pMazeGen = nullptr;
62
63 // Pointer to needed components
64 pGlobalStatus = GS;
65
66 #ifdef DEBUG_INTERNAL
67 Main::Instance().ILogMgr().get()->msg(LML_LOW, " [Gamefield] Info: Object initialized.\n");
68 #endif
69}
70
71// Destructor
72GameField::~GameField()
73{
74 // Delete internal components
75 if(pMapSearch != nullptr) delete pMapSearch;
76 pMapSearch = nullptr;
77
78 // Close
79 close();
80
81 #ifdef DEBUG_INTERNAL
82 Main::Instance().ILogMgr().get()->msg(LML_LOW, " [Gamefield] Info: Object closed.\n");
83 #endif
84}
85
86// Load first maze of the given type and create the objects
87Sint32 GameField::init()
88{
89 Actor* pActor = nullptr;
90 Sint32 iPacManBrain, iGhostRedBrain, iGhostPinkBrain, iGhostBlueBrain, iGhostOrangeBrain;
91
92 if(iMazeReady != 0) return PME_BREAK;
93
94 // Workbench mode: brains assignment, disable: getReadyTime, scattering ghost mode and PacMan death animation
95 if(pGlobalStatus->iGameType == PME_GAME_WORKBENCH)
96 {
97 iPacManBrain = pGlobalStatus->workBench.iPacManBrain;
98 iGhostRedBrain = pGlobalStatus->workBench.iGhostRedBrain;
99 iGhostPinkBrain = pGlobalStatus->workBench.iGhostPinkBrain;
100 iGhostBlueBrain = pGlobalStatus->workBench.iGhostBlueBrain;
101 iGhostOrangeBrain = pGlobalStatus->workBench.iGhostOrangeBrain;
102 iGetReadyTime = 0;
103 ResourceManager::Instance().setPacManDeathAnim(0);
104 }
105 // Standard and Evolution modes: brains assignment, enable: getReadyTime, scattering ghost mode and PacMan death animation
106 else
107 {
108 // PacMan always with the human brain
109 iPacManBrain = PME_BRAIN_TYPE_HUMAN;
110 // Standard game with fixed logic brains
111 if(pGlobalStatus->iGameType == PME_GAME_STANDARD) iGhostRedBrain = iGhostPinkBrain = iGhostBlueBrain = iGhostOrangeBrain = PME_BRAIN_TYPE_FIXED;
112 // Evolution game with EVN logic brains
113 else iGhostRedBrain = iGhostPinkBrain = iGhostBlueBrain = iGhostOrangeBrain = PME_BRAIN_TYPE_EVOLVED;
114 iGetReadyTime = PME_GETREADY_TIME;
115 ResourceManager::Instance().setPacManDeathAnim(1);
116 }
117
118 // Create the field array
119 if(iFieldArray == nullptr) iFieldArray = create2DArray<sField>(MAZE_HEIGHT, MAZE_WIDTH);
120
121 // Create PacMan
122 pActor = new(std::nothrow) PacMan(14, 23, this);
123 pActor->setBrain(iPacManBrain | PME_OBJECT_PACMAN);
124 vObjects.push_back(pActor);
125
126 // Create Red Ghost
127 if(iGhostRedBrain != PME_OBJECT_NULL)
128 {
129 pActor = new(std::nothrow) Ghost(PME_OBJECT_GHOST_RED, 12, 14, this);
130 pActor->setBrain(iGhostRedBrain | PME_OBJECT_GHOST_RED);
131 vObjects.push_back(pActor);
132 }
133
134 // Create Pink Ghost
135 if(iGhostPinkBrain != PME_OBJECT_NULL)
136 {
137 pActor = new(std::nothrow) Ghost(PME_OBJECT_GHOST_PINK, 13, 14, this);
138 pActor->setBrain(iGhostPinkBrain | PME_OBJECT_GHOST_PINK);
139 vObjects.push_back(pActor);
140 }
141
142 // Create Blue Ghost
143 if(iGhostBlueBrain != PME_OBJECT_NULL)
144 {
145 pActor = new(std::nothrow) Ghost(PME_OBJECT_GHOST_BLUE, 14, 14, this);
146 pActor->setBrain(iGhostBlueBrain | PME_OBJECT_GHOST_BLUE);
147 vObjects.push_back(pActor);
148 }
149
150 // Create Orange Ghost
151 if(iGhostOrangeBrain != PME_OBJECT_NULL)
152 {
153 pActor = new(std::nothrow) Ghost(PME_OBJECT_GHOST_ORANGE, 15, 14, this);
154 pActor->setBrain(iGhostOrangeBrain | PME_OBJECT_GHOST_ORANGE);
155 vObjects.push_back(pActor);
156 }
157
158 #ifdef DEBUG_INTERNAL
159 Main::Instance().ILogMgr().get()->msg(LML_NORMAL, " [GameField] Info: Maze loaded and game initialized.\n");
160 #endif
161
162 // Initialize dynamic maze generator
163 pMazeGen = new(std::nothrow) Maze(9, 5);
164
165 // Load the next maze
166 return nextMaze();
167}
168
169// Load next maze
170Sint32 GameField::nextMaze()
171{
172 Sint32 x = 0, i, y = 0, iPowerPellet = PME_OBJECT_PELLET_POWER1;
173 char* pMaze = nullptr;
174 Object* pObj = nullptr;
175 vector<Uint8> vTiles;
176
177 if(iFieldArray == nullptr) return PME_BREAK;
178
179 // Destroy power pellets objects (if applies)
180 for(i = 0; i < vObjects.size();)
181 {
182 if(vObjects[i]->getID() >= PME_OBJECT_PELLET_POWER1)
183 {
184 delete vObjects[i];
185 vObjects.erase(vObjects.begin() + i);
186 }
187 else ++i;
188 }
189 iNumPellets = iNumEatenPellets = 0;
190
191 // Use a static maze
192 if(pGlobalStatus->iGameType == PME_GAME_STANDARD)
193 {
194 if(iMazeStaticLength < (MAZE_WIDTH * MAZE_HEIGHT)) return PME_BREAK;
195 i = iMazeReady % iMazeStaticNumber;
196 pMaze = (char*)mazeStatic[i];
197 iMazeDynamicGenerationAttemps = 1;
198 iMazeDynamicGenerationTime = 0;
199 }
200 // Load dynamic and deterministic maze
201 else
202 {
203 i = static_cast<Sint32>(Main::Instance().ITimer().getTicksNow());
204 iMazeDynamicGenerationAttemps = pMazeGen->createMaze(vTiles);
205 iMazeDynamicGenerationTime = static_cast<Sint32>(Main::Instance().ITimer().getTicksNow());
206 iMazeDynamicGenerationTime -= i;
207 pMaze = (char*)&vTiles[0];
208 }
209
210 // Parse the maze
211 for(y = 0; y < MAZE_HEIGHT; y++)
212 for(x = 0; x < MAZE_WIDTH; x++)
213 {
214 // Default values
215 iFieldArray[y][x].iState = PME_STATE_NULL;
216 iFieldArray[y][x].iItem = PME_ITEM_NULL;
217 iFieldArray[y][x].iObject = PME_OBJECT_NULL;
218
219 // Parse current maze tile
220 switch(*pMaze)
221 {
222 // External maze tile
223 case '_':
224 // Same as default values
225 break;
226
227 // Wall tile
228 case '|':
229 iFieldArray[y][x].iState = PME_STATE_WALL;
230 break;
231
232 // Walkable and empty tile
233 case ' ':
234 iFieldArray[y][x].iState = PME_STATE_WALKABLE;
235 break;
236
237 // Walkable and a pellet tile
238 case '.':
239 iFieldArray[y][x].iState = PME_STATE_WALKABLE;
240 iFieldArray[y][x].iItem = PME_ITEM_PELLET;
241 ++iNumPellets;
242 break;
243
244 // Walkable and a power pellet tile
245 case 'o':
246 iFieldArray[y][x].iState = PME_STATE_WALKABLE;
247 if(pGlobalStatus->workBench.iTraining != 1)
248 {
249 pObj = new(std::nothrow) Object(iPowerPellet, x, y, this);
250 vObjects.push_back(pObj);
251 iFieldArray[y][x].iObject = iPowerPellet;
252 iPowerPellet *= 2;
253 ++iNumPellets;
254 }
255 break;
256 }
257 ++pMaze; // Next maze tile
258 }
259
260 // Ghost home's door and home initialization
261 iFieldArray[12][13].iState = PME_STATE_WALKABLE_GHOST;
262 iFieldArray[12][14].iState = PME_STATE_NULL;
263 for(y = 13; y < 16; y++)
264 for(x = 11; x < 17; x++)
265 {
266 // Default values
267 iFieldArray[y][x].iState = PME_STATE_WALKABLE_GHOST;
268 }
269
270 // PacMan and ghosts initial position
271 initObjects();
272
273 // Music fade out and screen to black, then start playing new game music
274 Main::Instance().IAudioTrackMgr().fadeOutTag(ATT_MUSIC, 500);
275 Main::Instance().IConfigMgr().get()->fadeToColor(0, 0, 0, 500);
276 Main::Instance().IAudioTrackMgr().get(ResourceManager::Instance().get(RM_MUS_GAME))->play(-1);
277 pGlobalStatus->iRenderScreen = PME_SCREEN_GAME;
278
279 ++iMazeReady;
280 return PME_LOOP;
281}
282
283// Close current maze removing all resources and objects
284Sint32 GameField::close()
285{
286 Sint32 i;
287
288 // Remove all objects
289 for(i = 0; i < vObjects.size(); ++i) delete vObjects[i];
290
291 // Reset vars
292 iMazeReady = iNumPellets = iNumEatenPellets = 0;
293 iTimeStart = 0;
294 iMazePixelX = iMazePixelY = 0;
295 if(iFieldArray) delete2DArray(iFieldArray);
296 iFieldArray = nullptr;
297 vObjects.clear();
298 iRenderGraphicsStatus = -1;
299 iSpecialTextCounter = -1;
300 sSpecialMessageCounter.clear();
301 sSpecialMessage.clear();
302
303 // Delete our maze generator
304 if(pMazeGen != nullptr) delete pMazeGen;
305 pMazeGen = nullptr;
306
307 // Music fade out and screen to black
308 Main::Instance().IAudioTrackMgr().fadeOutTag(ATT_MUSIC, 500);
309 Main::Instance().IConfigMgr().get()->fadeToColor(0, 0, 0, 500);
310
311 #ifdef DEBUG_INTERNAL
312 Main::Instance().ILogMgr().get()->msg(LML_NORMAL, " [GameField] Info: Maze closed.\n\n");
313 #endif
314
315 return 0;
316}
317
318// Main execution of the maze
319// Return PME_MAZE_END when current maze is over and going to next one or PME_BREAK when game is aborted/finished (running out of lifes)
320Sint32 GameField::execute()
321{
322 Sint32 iDone = PME_LOOP, i;
323 SDL_Event eEvent;
324 Main& mC64 = Main::Instance();
325
326 // Is the maze ready?
327 if(iMazeReady == 0) return PME_BREAK;
328
329 // Display starting message
330 if(pGlobalStatus->iGameType != PME_GAME_WORKBENCH) messageStart();
331
332 // Main maze loop
333 // mC64.ITimer().init(TS_RESET); // ToDO: reset stats but keep LFR, RFR but does not work
334 iTimeStart = mC64.ITimer().getTicksNow();
335 iRenderGraphicsStatus = RENDERGRAPHICS_GAME;
336 while(iDone == PME_LOOP)
337 {
338 // Update GlobalWave
339 sGlobalWave.update();
340
341 // Execution of our objects
342 for(i = 0; (i < vObjects.size()) && (iDone == PME_LOOP) && (sSpecialMessage != "PAUSED"); ++i)
343 {
344 // Put more attention when PacMan is executed:
345 // 1.Running out of lifes and the game is over
346 // 2.Being on chasing state and the wave ticks is not modified
347 if(vObjects[i]->getID() == PME_OBJECT_PACMAN)
348 {
349 // PacMan can return PME_ACTOR_ALIVE, PME_ACTOR_SPECIAL or PME_GAME_OVER
350 iDone = vObjects[i]->execute();
351 // Reduce a wave tick while PacMan is not in special mode
352 if(iDone != PME_ACTOR_SPECIAL) --sGlobalWave.iTicks;
353 else iDone = PME_LOOP; // Keep the main loop running
354 }
355
356 // Rest of objects execution. Always return PME_LOOP so dont care about it
357 else vObjects[i]->execute();
358 }
359
360 // Detect end of maze when no pellets left
361 if(iNumPellets == iNumEatenPellets)
362 {
363 iDone = PME_MAZE_END;
364 }
365
366 // Detect end of assigned time
367 if(pGlobalStatus->iGameType == PME_GAME_WORKBENCH)
368 {
369 if(pGlobalStatus->workBench.iTime != 0)
370 {
371 if((mC64.ITimer().getTicksNow() - iTimeStart) > (pGlobalStatus->workBench.iTime * 1000))
372 {
373 iDone = PME_BREAK;
374 }
375 }
376 }
377
378 // Game event loop
379 while(mC64.update(&eEvent))
380 {
381 switch(eEvent.type)
382 {
383 case SDL_EVENT_KEY_UP:
384 switch(eEvent.key.key)
385 {
386 // Finish the game
387 case SDLK_ESCAPE:
388 iDone = PME_BREAK;
389 break;
390
391 // Pause the game
392 case SDLK_SPACE:
393 if(sSpecialMessage == "PAUSED") sSpecialMessage.clear();
394 else if(sSpecialMessage.size() == 0) sSpecialMessage = "PAUSED";
395 break;
396
397 // Enter debug mode
398 case SDLK_F1:
399 bDebug = bDebug * (-1);
400 break;
401 case SDLK_F2:
402 if(bDebug == 1)
403 {
404 if(iDebugMode > 0) --iDebugMode;
405 }
406 break;
407 case SDLK_F3:
408 if(bDebug == 1)
409 {
410 if(iDebugMode < 4) ++iDebugMode;
411 }
412 break;
413 case SDLK_F4:
414 // Enable or disable objects rendering
415 if(bDebug == 1)
416 {
417 bDebugDisableObjectRender = bDebugDisableObjectRender * (-1);
418 }
419 break;
420 case SDLK_F5:
421 // Take a snapshot
422 if(bDebug == 1)
423 {
424 mC64.IConfigMgr().get()->getSnapshot("screenshot.png");
425 }
426 break;
427 case SDLK_F6:
428 // Force to finish current maze and advance to the next one
429 if(bDebug == 1)
430 {
431 iDone = PME_MAZE_END;
432 }
433 break;
434 case SDLK_F7:
435 // Enable or disable showing target sprite
436 if(bDebug == 1)
437 {
438 bDebugShowTarget = bDebugShowTarget * (-1);
439 }
440 break;
441 case SDLK_F9:
442 vObjects[0]->msgGhostCollision(); // kill pacman TODO REMOVE
443 for(i = 0; i < vObjects.size(); ++i) vObjects[i]->msgPause();
444 break;
445 }
446 break;
447 }
448 }
449 }
450
451 // Detected game end
452 if(iDone != PME_MAZE_END)
453 {
454 if(pGlobalStatus->iGameType != PME_GAME_WORKBENCH) messageEnd(iDone);
455 }
456
457 // Print out game info
458 if(pGlobalStatus->workBench.iTraining != 1)
459 {
460 mC64.ILogMgr().get()->msg(LML_NORMAL, " [GameField] Info: Maze '%d' (%s %d %dms)- %2.2f FPS during %2.2f seconds - Points %d \n",
461 iMazeReady, (pGlobalStatus->iGameType == PME_GAME_STANDARD) ? "static" : "dynamic", iMazeDynamicGenerationAttemps, iMazeDynamicGenerationTime,
462 mC64.ITimer().getAverageRFR(), (float)(mC64.ITimer().getTicksNow() - iTimeStart) / 1000.0f,
463 pGlobalStatus->iPoints);
464 }
465
466 return iDone;
467}
468
469// Render graphics callback with different states
470Sint32 GameField::render(Sint32 iMode)
471{
472 Sint32 iScreenW, iScreenH;
473 Sint32 x, y, i, iLifes;
474 SDL_FRect rDst;
475 string sText, sTmp;
476 Main& mC64 = Main::Instance();
477 Font* pFont;
478 Screen* pScreen;
479
480 // Is the maze ready?
481 if(iMazeReady == 0) return PME_BREAK;
482
483 // Get screen size and auto-adapt to screen changes. Get the starting coordinates for the maze rendering used by all objects.
484 pScreen = mC64.IConfigMgr().get();
485 pScreen->getSize(&iScreenW, &iScreenH);
486 iMazePixelX = (iScreenW - (MAZE_TILE_SIZE * MAZE_WIDTH)) / 2;
487 iMazePixelY = MAZE_TILE_SIZE;
488
489 // Sprite size equal to tile size
490 rDst.w = rDst.h = (float)MAZE_TILE_SIZE;
491
492 // Render black background
493 pScreen->clear();
494
495 // Render game background
496 // ToDO: a starfield?
497
498 // Render the "static" parts of the maze: walls and pellets
499 for(y = 0; y < MAZE_HEIGHT; y++)
500 {
501 for(x = 0; x < MAZE_WIDTH; x++)
502 {
503 if(iFieldArray[y][x].iState == PME_STATE_WALL)
504 {
505 rDst.x = (x * rDst.w) + (float)iMazePixelX;
506 rDst.y = (y * rDst.h) + (float)iMazePixelY;
507 mC64.IGFX().rectFilled(rDst.x, rDst.y, rDst.x + rDst.w, rDst.y + rDst.h, 0x0000AAFF);
508 }
509 if(iFieldArray[y][x].iItem == PME_ITEM_PELLET)
510 {
511 rDst.x = (x * rDst.w) + (float)iMazePixelX;
512 rDst.y = (y * rDst.h) + (float)iMazePixelY;
513 i = ResourceManager::Instance().get(RM_SPR_PELLET);
514 mC64.ISpriteMgr().get(i)->setPosition(rDst.x, rDst.y);
515 mC64.ISpriteMgr().get(i)->render();
516 }
517 }
518 }
519
520 // Render objects: power pellets, ghosts and PacMan. Get lifes of PacMan
521 for(i = (Sint32)vObjects.size() - 1; vObjects.size() > i; --i) // Reverse mode for rendering PacMan sprite over the rest of objects
522 {
523 if(vObjects[i]->getID() == PME_OBJECT_PACMAN)
524 {
525 iLifes = reinterpret_cast<PacMan*>(vObjects[i])->getLifes();
526 }
527 if(bDebugDisableObjectRender == -1) vObjects[i]->render(iMazePixelX, iMazePixelY);
528 }
529
530 // Render the score, highest score and pacman lifes
531 pFont = mC64.IFontMgr().get(ResourceManager::Instance().get(RM_FONT_SCORE));
532 pFont->setPosition((float)iMazePixelX, -8.0f);
533 mC64.ITool().intToStrDec(pGlobalStatus->iPoints, sTmp);
534 sText = "Score "; sText += sTmp;
535 pFont->render(sText);
536 rDst.x = (float)((iScreenW / 2) - MAZE_TILE_SIZE - (MAZE_TILE_SIZE / 2));
537 rDst.y = 0.0f; rDst.w = (float)MAZE_TILE_SIZE; rDst.h = (float)MAZE_TILE_SIZE;
538 while(iLifes > 0)
539 {
540 mC64.IImageMgr().get(ResourceManager::Instance().get(RM_IMG_ICON))->render(0, nullptr, &rDst);
541 --iLifes;
542 rDst.x += (float)MAZE_TILE_SIZE;
543 }
544 mC64.ITool().intToStrDec(pGlobalStatus->iHighestScore, sTmp);
545 sText = "Highest "; sText += sTmp;
546 pFont->setPosition((float)(iMazePixelX + (MAZE_WIDTH * MAZE_TILE_SIZE) - 250), -8.0f);
547
548 pFont->render(sText);
549
550 // While we are in RENDERGRAPHICS_START status (at the starting of the maze)
551 if(iRenderGraphicsStatus == RENDERGRAPHICS_START)
552 {
553 pScreen = mC64.IConfigMgr().get();
554 pScreen->getSize(&i, nullptr);
555 pFont = mC64.IFontMgr().get(ResourceManager::Instance().get(RM_FONT_INFO));
556
557 specialText(pFont, (float)((i - pFont->getWidth(sSpecialMessage)) / 2), (float)((iScreenH / 2) - 150.0f), sSpecialMessage, iSpecialTextCounter);
558 pFont->setPosition((float)((i - pFont->getWidth(sSpecialMessageCounter)) / 2), (float)((iScreenH / 2) - 80));
559 pFont->render(sSpecialMessageCounter);
560 }
561 // Normal rendering game state
562 else
563 {
564 // Render special message if present: get ready!
565 if(sSpecialMessage.size() > 0)
566 {
567 pFont->setPosition((float)((iScreenW - pFont->getWidth(sSpecialMessage)) / 2), (float)((iScreenH / 2) + 20));
568 pFont->render(sSpecialMessage);
569 }
570
571 // Debug information
572 if(bDebug == 1)
573 {
574 for(i = 0; i < vObjects.size(); ++i) vObjects[i]->debug(bDebugShowTarget);
575 debug();
576 }
577 }
578
579 return 0;
580}
581
582// Return a reference to the A* component
583MapSearchAStar& GameField::mapSearch()
584{
585 return *pMapSearch;
586}
587
588// Get the state from the maze given x,y
589Sint32 GameField::getState(Sint32 iMX, Sint32 iMY)
590{
591 // Check boundaries
592 if(iMX < 0) return PME_STATE_NULL;
593 else if(iMX > MAZE_WIDTH - 1) return PME_STATE_NULL;
594 if(iMY < 0) return PME_STATE_NULL;
595 else if(iMY > MAZE_HEIGHT - 1) return PME_STATE_NULL;
596
597 // Get status
598 return iFieldArray[iMY][iMX].iState;
599}
600
601// Get the item from the maze given x,y
602Sint32 GameField::getItem(Sint32 iMX, Sint32 iMY)
603{
604 // Check boundaries
605 if(iMX < 0) return PME_STATE_NULL;
606 else if(iMX > MAZE_WIDTH - 1) return PME_STATE_NULL;
607 if(iMY < 0) return PME_STATE_NULL;
608 else if(iMY > MAZE_HEIGHT - 1) return PME_STATE_NULL;
609
610 // Get status
611 return iFieldArray[iMY][iMX].iItem;
612}
613
614// Get the percent of eaten pellets on this maze. Used for the ghosts going out of their home.
615Sint32 GameField::getEatenPelletsPercent()
616{
617 return (iNumEatenPellets * 100) / iNumPellets;
618}
619
620// Get an object maze position.
621Sint32 GameField::getObjectPosition(Sint32 iID, Sint32 &iX, Sint32 &iY)
622{
623 Sint32 i = getObjectIndex(iID);
624 if(i != -1)
625 {
626 vObjects[i]->getPositionMaze(iX, iY);
627 i = 0;
628 }
629 return i;
630}
631
632// Get an object direction
633Sint32 GameField::getObjectDirection(Sint32 iID, Sint32 &iX, Sint32 &iY)
634{
635 Sint32 i = getObjectIndex(iID);
636 if(i != -1)
637 {
638 vObjects[i]->getDirection(iX, iY);
639 i = 0;
640 }
641 return i;
642}
643
644// Get an object state name
645Sint32 GameField::getObjectStateName(Sint32 iID, string &sN)
646{
647 Sint32 i = getObjectIndex(iID);
648 if(i != -1)
649 {
650 reinterpret_cast<Actor*>(vObjects[i])->getStateName(sN);
651 i = 0;
652 }
653 return i;
654}
655
656// Return the closest pellet to the given object
657Sint32 GameField::getClosestPellet(Sint32 iID, Sint32 &iX, Sint32 &iY)
658{
659 Sint32 iRadius = 1, iOX, iOY;
660 Sint32 i = getObjectIndex(iID);
661 if(i != -1)
662 {
663 vObjects[i]->getPositionMaze(iOX, iOY);
664
665 // Check with iRadius[1,32]
666 while(iRadius < 32)
667 {
668 for(int y = -iRadius; y <= iRadius; y++)
669 {
670 for(int x = -iRadius; x <= iRadius; x++)
671 {
672 if(x*x + y * y <= iRadius * iRadius)
673 {
674 iX = iOX + x;
675 iY = iOY + y;
676 // ToDO: add power pellet too
677 if(getItem(iX, iY) == PME_ITEM_PELLET)
678 {
679 //printf("Radius %d (%d,%d)\n", iRadius, iX, iY);
680 return 0;
681 }
682 }
683 }
684 }
685 ++iRadius;
686 }
687
688 }
689 return i;
690}
691
692// Move an object to a new position. The position must be valid.
693// Here we check for pellets, power pellets and ghost collisions
694Sint32 GameField::moveTo(Sint32 iID, Sint32 iCurMX, Sint32 iCurMY, Sint32 iNewMX, Sint32 iNewMY)
695{
696 Sint32 i, iObjs, iTmp;
697 Sint32 iX = 0, iY = 0;
698
699 // Remove from old position
700 iFieldArray[iCurMY][iCurMX].iObject -= iID;
701
702 // Set to new position
703 iFieldArray[iNewMY][iNewMX].iObject += iID;
704
705 // Get objects on this new position
706 iObjs = iFieldArray[iNewMY][iNewMX].iObject;
707
708 // Only with PacMan, we check:
709 if(iID == PME_OBJECT_PACMAN)
710 {
711 // Pellet item
712 if(iFieldArray[iNewMY][iNewMX].iItem == PME_ITEM_PELLET)
713 {
714 iFieldArray[iNewMY][iNewMX].iItem = PME_ITEM_NULL;
715 Main::Instance().IAudioTrackMgr().get(ResourceManager::Instance().get(RM_SND_GAMEEATPELLET))->play();
716 addPoints(PME_POINTS_EAT_PELLET);
717 ++iNumEatenPellets;
718 }
719
720 // Power Pellets
721 if(iObjs >= PME_OBJECT_PELLET_POWER1)
722 {
723 iTmp = PME_GET_PELLET(iObjs); // Get only the power pellet id
724 iFieldArray[iNewMY][iNewMX].iObject -= iTmp;
725 Main::Instance().IAudioTrackMgr().get(ResourceManager::Instance().get(RM_SND_GAMEEATPELLETPOWER))->play();
726 addPoints(PME_POINTS_EAT_PELLET_POWER);
727 ++iNumEatenPellets;
728
729 // Get coordinates for informing ghosts and remove power pellet object
730 i = getObjectIndex(iTmp);
731 if(i != -1)
732 {
733 vObjects[i]->getPositionMaze(iX, iY);
734 delete vObjects[i];
735 vObjects.erase(vObjects.begin() + i);
736 }
737
738 // PacMan and Ghosts react to a power pellet
739 for(i = 0; i < vObjects.size(); ++i) vObjects[i]->msgPelletPowerEaten(iX, iY);
740 }
741 }
742
743 // Check for PacMan/Ghosts collisions
744 if(iObjs & PME_OBJECT_PACMAN) // PacMan is on this position
745 {
746 iTmp = 2; // Assume no ghost collision
747
748 // Check for each ghost: send the message only to the first one found (not in death state)
749 if(iObjs & PME_OBJECT_GHOST_RED) // Red Ghost
750 {
751 i = getObjectIndex(PME_OBJECT_GHOST_RED);
752 if(i != -1) iTmp = vObjects[i]->msgGhostCollision();
753 }
754 else if((iObjs & PME_OBJECT_GHOST_BLUE) && (iTmp == 2)) // Blue Ghost
755 {
756 i = getObjectIndex(PME_OBJECT_GHOST_BLUE);
757 if(i != -1) iTmp = vObjects[i]->msgGhostCollision();
758 }
759 else if((iObjs & PME_OBJECT_GHOST_PINK) && (iTmp == 2)) // Pink Ghost
760 {
761 i = getObjectIndex(PME_OBJECT_GHOST_PINK);
762 if(i != -1) iTmp = vObjects[i]->msgGhostCollision();
763 }
764 else if((iObjs & PME_OBJECT_GHOST_ORANGE) && (iTmp == 2)) // Orange Ghost
765 {
766 i = getObjectIndex(PME_OBJECT_GHOST_ORANGE);
767 if(i != -1) iTmp = vObjects[i]->msgGhostCollision();
768 }
769
770 // With iTmp = 0, PacMan has eaten a ghost
771 if(iTmp == 0)
772 {
773 i = getObjectIndex(PME_OBJECT_PACMAN);
774 if(i != -1)
775 {
776 Main::Instance().IAudioTrackMgr().get(ResourceManager::Instance().get(RM_SND_GAMEEATGHOST))->play();
777 addPoints(vObjects[i]->msgGhostCollision());
778 }
779 }
780 // With iTmp = 1, PacMan was eaten by a ghost. Sent the pause message to all objects (ghosts)
781 else if(iTmp == 1)
782 {
783 i = getObjectIndex(PME_OBJECT_PACMAN);
784 if(i != -1)
785 {
786 if(pGlobalStatus->iGameType != PME_GAME_WORKBENCH) Main::Instance().IAudioTrackMgr().get(ResourceManager::Instance().get(RM_SND_GAMEPLAYERDEATH))->play();
787 vObjects[i]->msgGhostCollision();
788 for(i = 0; i < vObjects.size(); ++i) vObjects[i]->msgPause();
789 }
790 }
791 // With iTmp = 2 nothing happens (collision between PacMan and a death Ghost)
792
793 }
794 return 0;
795}
796
797// Show or hide the get ready message.
798// Controlled by the PacMan while in Init state.
799Sint32 GameField::messageGetReady(Sint32 iShow)
800{
801 if(iShow == 0) sSpecialMessage.clear();
802 else if(iShow == 1) sSpecialMessage = "Get Ready!";
803 return iGetReadyTime;
804}
805
806// Initialize the objects (PacMan and Ghosts) setting the start position and init state
807Sint32 GameField::initObjects()
808{
809 Sint32 i, x, y;
810
811 // Clean previous status and take care of power pellet objects! if a ghost was over one of them it will create a shadow copy of the ghost
812 for(y = 0; y < MAZE_HEIGHT; y++)
813 for(x = 0; x < MAZE_WIDTH; x++)
814 {
815 if(iFieldArray[y][x].iObject < PME_OBJECT_PELLET_POWER1) iFieldArray[y][x].iObject = PME_OBJECT_NULL;
816 else
817 {
818 // Reset only the power pellet removing any possible ghost
819 if((iFieldArray[y][x].iObject & PME_OBJECT_PELLET_POWER1) == PME_OBJECT_PELLET_POWER1) iFieldArray[y][x].iObject = PME_OBJECT_PELLET_POWER1;
820 else if((iFieldArray[y][x].iObject & PME_OBJECT_PELLET_POWER2) == PME_OBJECT_PELLET_POWER2) iFieldArray[y][x].iObject = PME_OBJECT_PELLET_POWER2;
821 else if((iFieldArray[y][x].iObject & PME_OBJECT_PELLET_POWER3) == PME_OBJECT_PELLET_POWER3) iFieldArray[y][x].iObject = PME_OBJECT_PELLET_POWER3;
822 else if((iFieldArray[y][x].iObject & PME_OBJECT_PELLET_POWER4) == PME_OBJECT_PELLET_POWER4) iFieldArray[y][x].iObject = PME_OBJECT_PELLET_POWER4;
823 }
824 }
825
826 // Fixed start position
827 iFieldArray[23][14].iObject = PME_OBJECT_PACMAN;
828 iFieldArray[14][12].iObject = PME_OBJECT_GHOST_RED;
829 iFieldArray[14][13].iObject = PME_OBJECT_GHOST_PINK;
830 iFieldArray[14][14].iObject = PME_OBJECT_GHOST_BLUE;
831 iFieldArray[14][15].iObject = PME_OBJECT_GHOST_ORANGE;
832
833 // Init the objects
834 for(i = 0; i < vObjects.size(); ++i) vObjects[i]->msgGoInit();
835
836 // Init the wave mode
837 sGlobalWave.reset();
838
839 // Workbench mode disables scattering wave
840 if(pGlobalStatus->iGameType == PME_GAME_WORKBENCH) sGlobalWave.iChanges = PME_CRUISE_MODE;
841 return 0;
842}
843
844// Validate the maze position as walkable or try to find a valid position around the original request up to a radius of 4
845// It should cover all possible cases
846Sint32 GameField::validateMazePosition(Sint32& iX, Sint32& iY)
847{
848 Sint32 iRadius = 1, iOX, iOY;
849
850 // Quick "clipping"
851 if(iX < 0) iX = 0;
852 else if(iX > (MAZE_WIDTH - 1)) iX = MAZE_WIDTH - 1;
853 if(iY < 0) iY = 0;
854 else if(iY >(MAZE_HEIGHT - 1)) iY = MAZE_HEIGHT - 1;
855
856 // Check with iRadius[1,4]
857 iOX = iX;
858 iOY = iY;
859 while(iRadius < 4)
860 {
861 for(int y = -iRadius; y <= iRadius; y++)
862 {
863 for(int x = -iRadius; x <= iRadius; x++)
864 {
865 if(x*x + y * y <= iRadius * iRadius)
866 {
867 iX = iOX + x;
868 iY = iOY + y;
869 if(getState(iX, iY) == PME_STATE_WALKABLE)
870 {
871 //printf("Radius %d (%d,%d)\n", iRadius, iX, iY);
872 return 0;
873 }
874 }
875 }
876 }
877 ++iRadius;
878 }
879
880 return -1;
881}
882
883// Return in the provided vector the posible movement options.
884// At least always two points (maze has not dead-ends) and more than two, it is an intersection
885Sint32 GameField::getMovementOptions(Sint32 iX, Sint32 iY, vector<MazePoint>& vMP)
886{
887 MazePoint vPoint;
888
889 // Clear vector
890 vMP.clear();
891
892 // First priority: Up
893 if(getState(iX, iY - 1) == PME_STATE_WALKABLE)
894 {
895 vPoint.iX = iX;
896 vPoint.iY = iY - 1;
897 vMP.push_back(vPoint);
898 }
899
900 // Second priority: Left
901 if(getState(iX - 1, iY) == PME_STATE_WALKABLE)
902 {
903 vPoint.iX = iX - 1;
904 vPoint.iY = iY;
905 vMP.push_back(vPoint);
906 }
907 // Left tunnel detected, allowit!
908 else if((iX - 1) < 0)
909 {
910 vPoint.iX = iX - 1;
911 vPoint.iY = iY;
912 vMP.push_back(vPoint);
913 }
914
915 // Third priority: Down
916 if(getState(iX, iY + 1) == PME_STATE_WALKABLE)
917 {
918 vPoint.iX = iX;
919 vPoint.iY = iY + 1;
920 vMP.push_back(vPoint);
921 }
922
923 // Last priority: Right
924 if(getState(iX + 1, iY) == PME_STATE_WALKABLE)
925 {
926 vPoint.iX = iX + 1;
927 vPoint.iY = iY;
928 vMP.push_back(vPoint);
929 }
930 // Right tunnel detected, allowit!
931 else if((iX + 1) > (MAZE_WIDTH - 1))
932 {
933 vPoint.iX = iX + 1;
934 vPoint.iY = iY;
935 vMP.push_back(vPoint);
936 }
937
938 return 0;
939}
940
941// Return current wave mode
942Sint32 GameField::getWaveMode()
943{
944 return sGlobalWave.iMode;
945}
946
947// Set the wave mode. Only works for the two evading modes set by PacMan
948Sint32 GameField::setWaveMode(Sint32 iMode)
949{
950 if(iMode == PME_GLOBAL_WAVE_EVADING)
951 {
952 sGlobalWave.iPreviousMode = sGlobalWave.iMode;
953 sGlobalWave.iMode = PME_GLOBAL_WAVE_EVADING;
954 }
955 else if(iMode == PME_GLOBAL_WAVE_EVADING_END) sGlobalWave.iMode = PME_GLOBAL_WAVE_EVADING_END;
956 return 0;
957}
958
959// Restore previous wave mode
960Sint32 GameField::restoreWaveMode()
961{
962 sGlobalWave.iMode = sGlobalWave.iPreviousMode;
963 return 0;
964}
965
966// Update waves controlling the changes between them
967Sint32 GameField::sGlobalWave::update()
968{
969 // Entry point
970 if(iMode == 0)
971 {
972 iMode = PME_GLOBAL_WAVE_CHASING;
973 iTicks = static_cast<Sint32>((PME_GHOST_CHASING_TIME * Main::Instance().ITimer().getLFR()) / 1000);
974 return 0;
975 }
976
977 // Global evading for Ghosts
978 if((iMode == PME_GLOBAL_WAVE_EVADING) || (iMode == PME_GLOBAL_WAVE_EVADING_END)) return 0;
979
980 // After this number of changes, we fix chasing mode
981 if(iChanges >= PME_CRUISE_MODE)
982 {
983 iMode = PME_GLOBAL_WAVE_CHASING;
984 iTicks = static_cast<Sint32>((PME_GHOST_CHASING_TIME * Main::Instance().ITimer().getLFR()) / 1000);
985 }
986
987 // Keep changing states
988 else if(iTicks < 0)
989 {
990 if(iMode == PME_GLOBAL_WAVE_CHASING)
991 {
992 iMode = PME_GLOBAL_WAVE_SCATTERING;
993 ++iChanges;
994 iTicks = static_cast<Sint32>((PME_GHOST_SCATTERING_TIME * Main::Instance().ITimer().getLFR()) / 1000);
995 }
996 else
997 {
998 iMode = PME_GLOBAL_WAVE_CHASING;
999 ++iChanges;
1000 iTicks = static_cast<Sint32>((PME_GHOST_CHASING_TIME * Main::Instance().ITimer().getLFR()) / 1000);
1001 }
1002 }
1003 return 0;
1004}
1005
1006// Reset the wave ticks mode
1007Sint32 GameField::sGlobalWave::reset()
1008{
1009 iMode = iChanges = iPreviousMode = 0;
1010 return 0;
1011}
1012
1013// Show the message at the beginning of the maze
1014Sint32 GameField::messageStart()
1015{
1016 Uint64 iTime = 0, iTmp, iStart, iDelay;
1017 Uint8 iDone;
1018 SDL_Event eEvent;
1019 string sMazeNumber;
1020 Main& mC64 = Main::Instance();
1021
1022 iSpecialTextCounter = 0;
1023 sSpecialMessage = "Starting maze ";
1024 mC64.ITool().intToStrDec(iMazeReady, sMazeNumber);
1025 sSpecialMessage += sMazeNumber;
1026 iDone = 3; // Number of seconds
1027 iDelay = (iDone * 1000);
1028 iRenderGraphicsStatus = RENDERGRAPHICS_START;
1029 mC64.IAudioTrackMgr().get(ResourceManager::Instance().get(RM_SND_GAMESTARTING))->play();
1030 iStart = mC64.ITimer().getTicksNow();
1031
1032 while(iDone > 0)
1033 {
1034 // Logic update
1035 iTmp = mC64.ITimer().getTicksNow();
1036 if(iTime == 0)
1037 {
1038 iTime = iTmp;
1039 mC64.ITool().intToStrDec(iDone, sSpecialMessageCounter);
1040 }
1041 else if(iTmp > (iTime + 1000))
1042 {
1043 iTime = 0;
1044 iDone--;
1045 }
1046 iSpecialTextCounter += 2;
1047 // Try to avoid too much error propagation on the delay (it will never be equal to iDelayStep)
1048 // As soon as the total time spent here is greater than the delay, we return back
1049 if(iTmp > (iStart + iDelay)) iDone = 0;
1050
1051 // Event loop for allowing the rendering to kick-in
1052 while(mC64.update(&eEvent));
1053 }
1054 // Clear it as we will be using for get ready message
1055 sSpecialMessage.clear();
1056 return PME_LOOP;
1057}
1058
1059// Show a message at the end of game
1060// It receives as parameter: PME_BREAK(game aborted) or PME_GAME_OVER(game over)
1061Sint32 GameField::messageEnd(Sint32 bFlag)
1062{
1063 Sint32 iDone = PME_LOOP;
1064 SDL_Event eEvent;
1065 string sText;
1066 Main& mC64 = Main::Instance();
1067 Panel* myPanel = mC64.IGUIMgr().getPanel(ResourceManager::Instance().get(RM_PANEL_GAME));
1068
1069 // Fade out music and play special game end music
1070 mC64.IAudioTrackMgr().fadeOutTag(ATT_MUSIC, 500);
1071 // Select mode
1072 switch(bFlag)
1073 {
1074 case PME_BREAK:
1075 myPanel->getWidget(ID_GAME_LABEL)->setText("Game Aborted");
1076 mC64.IAudioTrackMgr().get(ResourceManager::Instance().get(RM_SND_GAMEABORT))->play();
1077 break;
1078 case PME_GAME_OVER:
1079 myPanel->getWidget(ID_GAME_LABEL)->setText(" Game Over! ");
1080 mC64.IAudioTrackMgr().get(ResourceManager::Instance().get(RM_SND_GAMEOVER))->play();
1081 break;
1082 default:
1083 return -1;
1084 }
1085
1086 // If not enought points for entering in HoF, disable input name widget
1087 if(pGlobalStatus->iLowestScore > pGlobalStatus->iPoints) myPanel->getWidget(ID_GAME_ENTERNAME)->hide();
1088 else myPanel->getWidget(ID_GAME_ENTERNAME)->show();
1089
1090 // End maze message loop
1091 mC64.ICursorMgr().show();
1092 mC64.IGUIMgr().getPanel(ResourceManager::Instance().get(RM_PANEL_GAME))->baseWidget().show();
1093 while(iDone == PME_LOOP)
1094 {
1095 // Logic update
1096
1097 // Event loop
1098 while(mC64.update(&eEvent))
1099 {
1100 switch(eEvent.type)
1101 {
1102 case C64_EVENT:
1103 // Check for widget activity
1104 if(eEvent.user.code == C64_EVENT_WIDGET)
1105 {
1106 if(*static_cast<Sint32*>(eEvent.user.data1) == ID_GAME_CLOSE)
1107 {
1108 mC64.IAudioTrackMgr().get(ResourceManager::Instance().get(RM_SND_CLICKOK))->play();
1109 iDone = PME_BREAK;
1110 break;
1111 }
1112 }
1113 break;
1114 }
1115 }
1116 }
1117
1118 // Get the name of the player. Default to PacMan
1119 myPanel->getWidget(ID_GAME_ENTERNAME)->getText(sText);
1120 if(sText.empty()) sText = "PacMan";
1121 mC64.ITool().szCopy(pGlobalStatus->szName, sText.c_str(), sizeof(pGlobalStatus->szName));
1122 #ifdef DEBUG_INTERNAL
1123 mC64.ILogMgr().get()->msg(LML_INFO, " Player '%s' - Points '%d' (%d<->%d)\n", sText.c_str(), pGlobalStatus->iPoints, pGlobalStatus->iLowestScore, pGlobalStatus->iHighestScore);
1124 #endif
1125
1126 // Return
1127 sSpecialMessage.clear();
1128 mC64.ICursorMgr().hide();
1129 mC64.IGUIMgr().getPanel(ResourceManager::Instance().get(RM_PANEL_GAME))->baseWidget().hide();
1130 return PME_LOOP;
1131}
1132
1133
1134// Render a special text effect
1135Sint32 GameField::specialText(Font* pFont, Sint32 iX, Sint32 iY, string &sText, Sint32 iCount)
1136{
1137 double dAngle = 0;
1138 Sint32 i, iXPos, iYPos;
1139 char sChar[2];
1140
1141 // Count modulates the effect
1142 dAngle = (double)iCount * 5.0;
1143
1144 // One iteration per each character on the string
1145 for(i = 0; i < (Sint32)sText.length(); i++)
1146 {
1147 sChar[0] = sText[i];
1148 sChar[1] = '\0';
1149 if(i == 0) iXPos = iX;
1150 iYPos = (Sint32)(iY + sin(SDL_PI_D / 180 * (dAngle + i * 12)) * pFont->getHeight());
1151 pFont->setPosition((float)iXPos, (float)iYPos);
1152 pFont->render(sChar);
1153 iXPos = iXPos + pFont->getWidth(sChar);
1154 }
1155 return 0;
1156}
1157
1158// Render debug information
1159Sint32 GameField::debug()
1160{
1161 Sint32 x, y, iFW, iFH;
1162 string sDebug, sTmp;
1163 Main& mC64 = Main::Instance();
1164 Font* pFont = mC64.IFontMgr().get(ResourceManager::Instance().get(RM_FONT_CONSOLE));
1165
1166
1167 if(iMazeReady == 0) return -1;
1168 iFH = pFont->getHeight();
1169
1170 // Render each tile attributes on screen
1171 for(y = 0; y < MAZE_HEIGHT; y++)
1172 {
1173 for(x = 0; x < MAZE_WIDTH; x++)
1174 {
1175 // Select debug mode. No room for showing all
1176 switch(iDebugMode)
1177 {
1178 // Tile coordinates
1179 case 1:
1180 mC64.ITool().intToStrDec(x, sTmp);
1181 sDebug = sTmp; sDebug += ",";
1182 mC64.ITool().intToStrDec(y, sTmp);
1183 sDebug += sTmp;
1184 break;
1185
1186 // Tile state
1187 case 2:
1188 mC64.ITool().intToStrDec(iFieldArray[y][x].iState, sTmp);
1189 sDebug = sTmp;
1190 break;
1191
1192 // Tile item
1193 case 3:
1194 mC64.ITool().intToStrDec(iFieldArray[y][x].iItem, sTmp);
1195 sDebug = sTmp;
1196 break;
1197
1198 // Tile object
1199 case 4:
1200 mC64.ITool().intToStrDec(iFieldArray[y][x].iObject, sTmp);
1201 sDebug = sTmp;
1202 break;
1203 }
1204
1205 // Center debug info in the tile
1206 iFW = pFont->getWidth(sDebug);
1207 pFont->setPosition((float)((x * MAZE_TILE_SIZE + iMazePixelX) - ((iFW - MAZE_TILE_SIZE) / 2)), (float)((y * MAZE_TILE_SIZE + iMazePixelY) - ((iFH - MAZE_TILE_SIZE) / 2)));
1208 pFont->render(sDebug);
1209 }
1210 }
1211
1212 // Display debug mode info
1213 sDebug = "Debug mode: ";
1214 switch(iDebugMode)
1215 {
1216 case 0:
1217 sDebug += "info";
1218 break;
1219 case 1:
1220 sDebug += "coordinates";
1221 break;
1222 case 2:
1223 sDebug += "states";
1224 break;
1225 case 3:
1226 sDebug += "items";
1227 break;
1228 case 4:
1229 sDebug += "objects";
1230 break;
1231 }
1232 pFont->setPosition((float)PME_DEBUG_PANEL_GAMEFIELD_X, (float)(PME_DEBUG_PANEL_GAMEFIELD_Y + iMazePixelY));
1233 pFont->render(sDebug);
1234 if(bDebugDisableObjectRender == 1) sDebug = "Objects rendering disabled";
1235 else sDebug = "Objects rendering enabled";
1236 pFont->setPosition((float)PME_DEBUG_PANEL_GAMEFIELD_X, (float)(PME_DEBUG_PANEL_GAMEFIELD_Y + iMazePixelY + 16));
1237 pFont->render(sDebug);
1238 sDebug = "Maze: ";
1239 mC64.ITool().intToStrDec(iMazeReady, sTmp);
1240 sDebug += sTmp; sDebug += " - Wave: ";
1241 mC64.ITool().intToStrDec(sGlobalWave.iTicks, sTmp);
1242 sDebug += sTmp;
1243 pFont->setPosition((float)PME_DEBUG_PANEL_GAMEFIELD_X, (float)(PME_DEBUG_PANEL_GAMEFIELD_Y + iMazePixelY + 48));
1244 pFont->render(sDebug);
1245 sDebug = "Pellets: ";
1246 mC64.ITool().intToStrDec(iNumEatenPellets, sTmp);
1247 sDebug += sTmp; sDebug += "/";
1248 mC64.ITool().intToStrDec(iNumPellets, sTmp);
1249 sDebug += sTmp; sDebug += " (";
1250 mC64.ITool().intToStrDec(getEatenPelletsPercent(), sTmp);
1251 sDebug += sTmp; sDebug += "%)";
1252 pFont->setPosition((float)PME_DEBUG_PANEL_GAMEFIELD_X, (float)(PME_DEBUG_PANEL_GAMEFIELD_Y + iMazePixelY + 64));
1253 pFont->render(sDebug);
1254
1255 sDebug = "LFR/RFR: ";
1256 mC64.ITool().intToStrDec(mC64.ITimer().getCurrentLFR(), sTmp);
1257 sDebug += sTmp; sDebug += "/";
1258 mC64.ITool().intToStrDec(mC64.ITimer().getCurrentRFR(), sTmp);
1259 sDebug += sTmp;
1260 pFont->setPosition((float)PME_DEBUG_PANEL_GAMEFIELD_X, (float)(PME_DEBUG_PANEL_GAMEFIELD_Y + iMazePixelY + 80));
1261 pFont->render(sDebug);
1262
1263 return 0;
1264}
1265
1266// Add the points to pacman score
1267Sint32 GameField::addPoints(Sint32 iP)
1268{
1269 pGlobalStatus->iPoints += iP;
1270 if(pGlobalStatus->iHighestScore < pGlobalStatus->iPoints) pGlobalStatus->iHighestScore = pGlobalStatus->iPoints;
1271 return 0;
1272}
1273
1274// Return the index position on vObjects of the requested object ID
1275// Althought the first position belongs to PacMan and the next 4 to the ghosts, instead of using this trick
1276// we prefer to be on the safe side and look for the position.
1277Sint32 GameField::getObjectIndex(Sint32 iID)
1278{
1279 Sint32 i;
1280
1281 for(i = 0; i < vObjects.size(); ++i)
1282 {
1283 if(vObjects[i]->getID() == iID) return i;
1284 }
1285 return -1;
1286}
1287
1288// Used by EVNTrainer for training brains
1289Actor* GameField::getActor(Sint32 iID)
1290{
1291 Sint32 i = getObjectIndex(iID);
1292 if(i != -1)
1293 {
1294 return reinterpret_cast<Actor*>(vObjects[i]);
1295 }
1296 return nullptr;
1297}
1298
1299// Used by EVNTrainer for retrieving the maze number
1300Sint32 GameField::getMazeNumber()
1301{
1302 return iMazeReady;
1303}
1304
1305// Template: dynamic 2D array of a given data type.
1306template <typename T> T** GameField::create2DArray(Sint32 height, Sint32 width)
1307{
1308 T **ppi;
1309 T *pool;
1310 T *curPtr;
1311
1312 // Allocate memory for array of elements of column
1313 ppi = new(std::nothrow) T*[height];
1314 // Allocate memory for array of elements of each row
1315 pool = new(std::nothrow) T[width * height];
1316
1317 // Now point the pointers in the right place
1318 curPtr = pool;
1319 for(int i = 0; i < height; i++)
1320 {
1321 *(ppi + i) = curPtr;
1322 curPtr += width;
1323 }
1324 // Return
1325 return ppi;
1326}
1327
1328// Template: delete a dynamic 2D array.
1329template <typename T> Sint32 GameField::delete2DArray(T** Array)
1330{
1331 delete[] * Array;
1332 delete[] Array;
1333
1334 return 0;
1335}