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