Pac-Man Evolution
Loading...
Searching...
No Matches
ObjectsPacMan.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
24PacMan class
25
26------------------------------------------------------------------------ */
27
28#include "ObjectsPacMan.h"
29#include "GameField.h"
30#include "ResourceManager.h"
31#include "BrainsFactory.h"
32
33// PacMan constructor.
34PacMan::PacMan(Sint32 iMX, Sint32 iMY, GameField* GF) : Actor(PME_OBJECT_PACMAN, iMX, iMY, GF)
35{
36 // Init vars
37 iLifes = PME_PACMAN_START_LIFES;
38 iSpeed = PME_PACMAN_START_SPEED;
39 iPointsForEatingGhostsPerPelletPower = 0;
40
41 // Create our state objects
42 pStateInit = new(std::nothrow) PacManStateInit("Init");
43 pStateEvading = new(std::nothrow) PacManStateEvading("Evading");
44 pStateChasing = new(std::nothrow) PacManStateChasing("Chasing");
45 pStateDeath = new(std::nothrow) PacManStateDeath("Death");
46
47 #ifdef DEBUG_INTERNAL
48 Main::Instance().ILogMgr().get()->msg(LML_LOW, " [PacMan] PacMan ID '%d' created.\n", iID);
49 #endif
50}
51
52// PacMan destructor.
53PacMan::~PacMan()
54{
55 iLifes = 0;
56 delete pStateInit;
57 pStateInit = nullptr;
58 delete pStateEvading;
59 pStateEvading = nullptr;
60 delete pStateChasing;
61 pStateChasing = nullptr;
62 delete pStateDeath;
63 pStateDeath = nullptr;
64 #ifdef DEBUG_INTERNAL
65 Main::Instance().ILogMgr().get()->msg(LML_LOW, " [PacMan] PacMan ID '%d' deleted.\n", iID);
66 #endif
67}
68
69// PacMan logic execution.
70// Return PME_ACTOR_ALIVE, PME_ACTOR_SPECIAL or PME_GAME_OVER
71Sint32 PacMan::execute()
72{
73 Sint32 iRet = PME_ACTOR_ALIVE;
74
75 // With no more lifes left...
76 if(iLifes <= 0) return PME_GAME_OVER;
77
78 // Execute current state.
79 // Note the states have full access to all our methods/attributes
80 if(pCurrentState) pCurrentState->execute(this);
81
82 // Call base method (set the object sprite position)
83 Actor::execute();
84
85 // Check for init, chase and death states -> put us in special mode: no wavetick counter!
86 if(stateCurrentCheck(*pStateInit) || stateCurrentCheck(*pStateChasing) || stateCurrentCheck(*pStateDeath)) iRet = PME_ACTOR_SPECIAL;
87
88 // Return
89 return iRet;
90}
91
92// Debug PacMan object.
93Sint32 PacMan::debug(Sint32 iMode)
94{
95 Main &mC64 = Main::Instance();
96 string sDebug, sTmp;
97 Font* pFont = Main::Instance().IFontMgr().get(ResourceManager::Instance().get(RM_FONT_CONSOLE));
98
99 // Call our base class (render target sprite)
100 Actor::debug(iMode);
101
102 // PacMan information
103 pFont->setPosition(PME_DEBUG_PANEL_PACMAN_X, PME_DEBUG_PANEL_PACMAN_Y + iMazeRenderingY);
104 pFont->render("PacMan info");
105
106 // Maze and pixel position
107 mC64.ITool().intToStrDec(iMazeX, sTmp);
108 sDebug = "Position: [";
109 sDebug += sTmp;
110 sDebug += ",";
111 mC64.ITool().intToStrDec(iMazeY, sTmp);
112 sDebug += sTmp;
113 sDebug += "] (";
114 mC64.ITool().intToStrDec(iPositionX, sTmp);
115 sDebug += sTmp;
116 sDebug += ",";
117 mC64.ITool().intToStrDec(iPositionY, sTmp);
118 sDebug += sTmp;
119 sDebug += ")";
120 pFont->setPosition(PME_DEBUG_PANEL_PACMAN_X, PME_DEBUG_PANEL_PACMAN_Y + iMazeRenderingY + 16);
121 pFont->render(sDebug);
122
123 // Speed
124 sDebug = "Speed: ";
125 mC64.ITool().intToStrDec(iSpeed, sTmp);
126 sDebug += sTmp;
127 pFont->setPosition(PME_DEBUG_PANEL_PACMAN_X, PME_DEBUG_PANEL_PACMAN_Y + iMazeRenderingY + 32);
128 pFont->render(sDebug);
129
130 // Target
131 mC64.ITool().intToStrDec(pointTarget.iX, sTmp);
132 sDebug = "Target: [";
133 sDebug += sTmp;
134 sDebug += ",";
135 mC64.ITool().intToStrDec(pointTarget.iY, sTmp);
136 sDebug += sTmp;
137 sDebug += "]";
138 pFont->setPosition(PME_DEBUG_PANEL_PACMAN_X, PME_DEBUG_PANEL_PACMAN_Y + iMazeRenderingY + 48);
139 pFont->render(sDebug);
140
141 // State
142 pCurrentState->getName(sDebug);
143 sDebug = "State: " + sDebug;
144 pFont->setPosition(PME_DEBUG_PANEL_PACMAN_X, PME_DEBUG_PANEL_PACMAN_Y + iMazeRenderingY + 64);
145 pFont->render(sDebug);
146
147 // Brain name
148 if(BrainsFactory::Instance().getName(idBrain, sDebug) == -1) sDebug = "Empty";
149 sDebug = "Brain: " + sDebug;
150 pFont->setPosition(PME_DEBUG_PANEL_PACMAN_X, PME_DEBUG_PANEL_PACMAN_Y + iMazeRenderingY + 80);
151 pFont->render(sDebug);
152
153 return 0;
154}
155
156// Return number of lifes.
157Sint32 PacMan::getLifes()
158{
159 return iLifes;
160}
161
162// Message for going to Init state.
163Sint32 PacMan::msgGoInit()
164{
165 // Call base method
166 Actor::msgGoInit();
167 stateChange(pStateInit);
168 return 0;
169}
170
171// PacMan reacts changing to chasing state.
172Sint32 PacMan::msgPelletPowerEaten(Sint32 iX, Sint32 iY)
173{
174 stateChange(pStateChasing);
175 return 0;
176}
177
178// PacMan reaction to a collision event.
179// return 0: eaten by a ghost. Going death state.
180// return >0: points for eating a ghost.
181Sint32 PacMan::msgGhostCollision()
182{
183 // If I am on chasing state... then a ghost was eaten :)
184 if(stateCurrentCheck(*pStateChasing))
185 {
186 if(iPointsForEatingGhostsPerPelletPower == 0) iPointsForEatingGhostsPerPelletPower = PME_POINTS_EAT_GHOST;
187 else iPointsForEatingGhostsPerPelletPower *= 2;
188 return iPointsForEatingGhostsPerPelletPower;
189 }
190 // Otherwise... we were eating by a ghost
191 else stateChange(pStateDeath);
192 return 0;
193}
194
195// Given a desired target (could be valid or not), we try to reach it applying our rules:
196// - try to reach target using euclidean distance
197// - increase the cost of going back, it is allowed but with a penalty for avoiding loops
198// - keep moving
199// - result is stored on vPath
200Sint32 PacMan::applyMovementRules(Sint32 iTX, Sint32 iTY)
201{
202 Sint32 i, iSelected = 0;
203 double dLowest = 1000.0f;
204 vector<MazePoint> vMovementOptions;
205
206 // 1.Get possible movement options. ToDO: tunnels?
207 pGameField->getMovementOptions(iMazeX, iMazeY, vMovementOptions);
208
209 // 2.1.If there is only a possible way, avoid more calcs
210 if(vMovementOptions.size() == 1) iSelected = 0;
211
212 // 2.2.Assign distances to target and select the closest one.
213 // In case of match, we select the first one: it is already prioritized in getMovementOptions()
214 else
215 {
216 for(i = 0; i < vMovementOptions.size(); ++i) euclideanDistance(iTX, iTY, vMovementOptions[i]);
217
218 // Increase the cost(distance) of going back
219 for(i = 0; i < vMovementOptions.size(); ++i)
220 {
221 if((vMovementOptions[i].iX == iMazePrevX) && (vMovementOptions[i].iY == iMazePrevY))
222 {
223 // 1.75 is the a very good value: avoid to fall in loops and turn when in front of a ghost
224 // Smaller than that and it will loop on corners more frequently and greater than that and will not evade ghost
225 vMovementOptions[i].dDistance += 1.75;
226 }
227 }
228
229 // Get the lowest distance
230 for(i = 0; i < vMovementOptions.size(); ++i)
231 {
232 if(dLowest > vMovementOptions[i].dDistance)
233 {
234 dLowest = vMovementOptions[i].dDistance;
235 iSelected = i;
236 }
237 }
238 }
239
240 // 4.Add the selected point to the path
241 vPath.push_back(vMovementOptions[iSelected]);
242 return 0;
243}
244
245// Init State
246// Show the Get Ready message waiting PME_GETREADY_TIME before going to Evading state
247PacManStateInit::PacManStateInit(const string& sN) : State(sN) { iTime = 0; }
248void PacManStateInit::enter(Actor* pAct)
249{
250 PacMan* pPM = reinterpret_cast<PacMan*>(pAct);
251
252 // Call our base class method
253 State::enter(pAct);
254
255 // Reset our movemements vars
256 pPM->eAM = Actor::eActorMoving::AM_NEXT_STEP;
257 pPM->vPath.clear();
258
259 iTime = Main::Instance().ITimer().getTicksNow();
260}
261void PacManStateInit::execute(Actor* pAct)
262{
263 PacMan* pPM = reinterpret_cast<PacMan*>(pAct);
264 if(Main::Instance().ITimer().getTicksNow() > (iTime + pPM->pGameField->messageGetReady(-1))) pPM->stateChange(pPM->pStateEvading);
265 else pPM->pGameField->messageGetReady(1);
266}
267void PacManStateInit::exit(Actor* pAct)
268{
269 // Call our base class method
270 State::exit(pAct);
271
272 reinterpret_cast<PacMan*>(pAct)->pGameField->messageGetReady(0);
273}
274
275// Evading State
276// Only use execute() method, the rest of methods, we use our base class
277PacManStateEvading::PacManStateEvading(const string& sN) : State(sN) { iTX = iTY = iITX = iITY = 0; bGetNewTarget = true; }
278void PacManStateEvading::execute(Actor* pAct)
279{
280 Sint32 iTargetType;
281 MazePoint pointImmediateTarget;
282 PacMan* pPM = reinterpret_cast<PacMan*>(pAct);
283
284 // 1.When we reach our target, request to get a new target
285 if((pPM->iMazeX == iTX) && (pPM->iMazeY == iTY)) bGetNewTarget = true;
286
287 // 2.At each new maze point, request once to get a new target
288 if((pPM->iMazeX != iITX) || (pPM->iMazeY != iITY))
289 {
290 iITX = pPM->iMazeX;
291 iITY = pPM->iMazeY;
292 bGetNewTarget = true;
293 }
294
295 // 3.If our brain has an "inmmediate target" type, request new target
296 iTargetType = BrainsFactory::Instance().getTargetType(pPM->idBrain);
297 if(iTargetType == PME_BRAIN_IMMEDIATE_TARGET) bGetNewTarget = true;
298
299 // 4.Call to the ghost brain for getting a new target in (iTX,iTY)
300 if(bGetNewTarget)
301 {
302 pPM->think(iTX, iTY);
303 pPM->pointTarget.iX = iTX;
304 pPM->pointTarget.iY = iTY;
305 bGetNewTarget = false;
306 }
307
308 // 5.1.With "high-level target", apply PacMan movement rules
309 if(iTargetType == PME_BRAIN_HIGHLEVEL_TARGET)
310 {
311 if(pPM->vPath.size() == 0)
312 {
313 pPM->applyMovementRules(iTX, iTY);
314 }
315 }
316 // 5.2.With an "immediate target" type just go to the new requested target
317 else
318 {
319 pPM->vPath.clear();
320 pointImmediateTarget.iX = iTX;
321 pointImmediateTarget.iY = iTY;
322 pPM->vPath.push_back(pointImmediateTarget);
323 }
324}
325
326// Chasing State
327// execute() method is the same of evading state.
328PacManStateChasing::PacManStateChasing(const string& sN) : State(sN) { iTicks = 0; }
329void PacManStateChasing::enter(Actor* pAct)
330{
331 PacMan* pPM = reinterpret_cast<PacMan*>(pAct);
332
333 // Call our base class method
334 State::enter(pAct);
335
336 // Reset the points for eating successive ghosts during the same empowered time
337 pPM->iPointsForEatingGhostsPerPelletPower = 0;
338
339 // Init our ticks based on the baseline LFR rate(40).
340 iTicks = (PME_PACMAN_ENPOWERED_TIME * 40) / 1000;
341
342 // Change the GlobalWaveMode to evading
343 pPM->pGameField->setWaveMode(PME_GLOBAL_WAVE_EVADING);
344}
345void PacManStateChasing::execute(Actor* pAct)
346{
347 PacMan* pPM = reinterpret_cast<PacMan*>(pAct);
348
349 // Control the time spent on this mode and update the GlobalWaveMode
350 if(iTicks < 0) pPM->stateChange(pPM->pStateEvading);
351 else
352 {
353 // When 1/4 of ticks left, change GlobalWaveMode
354 if(iTicks < ((PME_PACMAN_ENPOWERED_TIME * 40) / 4) / 1000) pPM->pGameField->setWaveMode(PME_GLOBAL_WAVE_EVADING_END);
355 pPM->pStateEvading->execute(pAct);
356 }
357 --iTicks;
358}
359void PacManStateChasing::exit(Actor* pAct)
360{
361 // Call our base class
362 State::exit(pAct);
363
364 // Restore previous GlobalWaveMode
365 reinterpret_cast<PacMan*>(pAct)->pGameField->restoreWaveMode();
366}
367
368// Death State
369// - play death anim and once finished, substract a life
370// - call GameField initObjects()
371PacManStateDeath::PacManStateDeath(const string& sN) : State(sN) {}
372void PacManStateDeath::enter(Actor* pAct)
373{
374 // Call our base class method
375 State::enter(pAct);
376
377 PacMan* pPM = reinterpret_cast<PacMan*>(pAct);
378 Sprite* sprObj = Main::Instance().ISpriteMgr().get(pPM->sprID);
379
380 // Set death sprite
381 pPM->eSS = Actor::eSpriteState::SS_DEATH;
382 sprObj->selectAnim(SPR_STATE_NORMAL);
383}
384void PacManStateDeath::execute(Actor* pAct)
385{
386 PacMan* pPM = reinterpret_cast<PacMan*>(pAct);
387 Sprite* sprObj = Main::Instance().ISpriteMgr().get(pPM->sprID);
388
389 // Wait till the animation is over
390 if(sprObj->status() == C64_STATUS_END)
391 {
392 --pPM->iLifes;
393 pPM->pGameField->initObjects(); // Re-init all objects changing to Init state
394 }
395}