/
trilirium
/
Pacman
Обзор
Документация
Войти
/
trilirium
/
Pacman
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
Game.cpp
862 строки
21 KB
Trilirium
first commit
19 июн 2026, 11:07
19 июн 2026, 11:07
d44591e
Код
Авторство
О чём код?
// Game.cpp // ///////////////////////////////////////////////////////////////////////////// #include "Game.h" #include "InitState.h" #include "DemoState.h" #include "CharacterSubstate.h" #include "CoinInsertedState.h" #include "GhostDeadSubstates.h" #include "PlayingState.h" #include "RedGhost.h" #include "PinkGhost.h" #include "BlueGhost.h" #include "OrangeGhost.h" #include "../Operations.h" #include "Pacman.h" #include "Video.h" #include "Writer.h" using namespace Puckman; GameState *Game::states[4]; ///////////////////////////////////////////////////////////////////////////// // initialization and cleanup ///////////////////////////////////////////////////////////////////////////// Game::Game() { resetState(); // inits scheduled tasks list Task::initScheduledTasksList(); state = 0; // creates the states states[0] = new InitState(); states[1] = new DemoState(); states[2] = new CoinInsertedState(); states[3] = new PlayingState(); // creates the characters pacman = new Pacman(); ghost[RED] = new RedGhost(); ghost[PINK] = new PinkGhost(); ghost[BLUE] = new BlueGhost(); ghost[ORANGE] = new OrangeGhost(); ghostDeadSubstates[0x00] = new GhostInitKillSubstate(); ghostDeadSubstates[0x01] = new GhostDeadNopSubstate(); ghostDeadSubstates[0x02] = new GhostEndKillSubstate(); } Game::~Game() { // deletes dead substates for (int i = 0; i < 3; i++){ delete ghostDeadSubstates[i]; } // deletes the characters delete pacman; for (int i = 0; i < 4; i++){ delete ghost[i]; } // delete game states for (int i = 0; i < 4; i++){ delete states[i]; } // deletes scheduled tasks list Task::endScheduledTasksList(); } void Game::resetState() { for (int i = 0; i < 7; i++){ ghostsOrientationChanges[i] = 0; } indexOrientationChanges = cntOrientationChanges = 0; // counters for movement cntGhostsMovHome = 0; cntGhostsAnimations = ghostsAnimationFrame = 0; cntGhostsPanicAnim = 0; cntSuperPillsAnimation = 0; // difficulty firstDiffFlag = secondDiffFlag = false; pillsFirstDiffFlag = pillsSecondDiffFlag = 0; pinkGhostExitHomeCounterLimit = blueGhostExitHomeCounterLimit = orangeGhostExitHomeCounterLimit = 0; inactivityCounterLimit = inactivityCounter = 0; eatenPillsAfterDead = 0; timeInPanicState = remainingTimeInPanicState = 0; currentKills = 0; fruitPosition = fruitEntry = fruitSprite = fruitColor = 0; cntShowHide1P2P = 0; collisionObject = 0; killingGhostState = ghostDeadAnimState = 0; for (int i = 0; i < 3; i++){ cutsceneState[i] = 0; } currentPlayer = 0; numberOfPlayers = 0; score1P = score2P = highScore = 0; gotBonusLife1P = gotBonusLife2P = false; } ///////////////////////////////////////////////////////////////////////////// // game's initialization methods ///////////////////////////////////////////////////////////////////////////// void Game::initGameState() { pacman->resetState(); for (int i = 0; i < 4; i++){ ghost[i]->resetState(); } for (int i = 0; i < 7; i++){ ghostsOrientationChanges[i] = 0; } indexOrientationChanges = cntOrientationChanges = 0; cntGhostsMovHome = 0; cntGhostsAnimations = ghostsAnimationFrame = 0; cntGhostsPanicAnim = 0; cntSuperPillsAnimation = 0; firstDiffFlag = secondDiffFlag = false; pillsFirstDiffFlag = pillsSecondDiffFlag = 0; pinkGhostExitHomeCounterLimit = blueGhostExitHomeCounterLimit = orangeGhostExitHomeCounterLimit = 0; inactivityCounter = inactivityCounterLimit = 0; eatenPillsAfterDead = 0; timeInPanicState = remainingTimeInPanicState = 0; currentKills = 0; fruitPosition = fruitEntry = fruitSprite = fruitColor = 0; cntShowHide1P2P = 0; collisionObject = 0; killingGhostState = ghostDeadAnimState = 0; } void Game::initCharacterState(bool special) { pacman->initState(special); for (int i = 0; i < 4; i++){ ghost[i]->initState(special); } fruitSprite = 0x3f; fruitColor = 0x00; fruitPosition = 0; } void Game::resetCharacterPositions() { fruitPosition = 0; pacman->posX = pacman->posY = 0; ghost[RED]->posX = ghost[RED]->posY = 0; ghost[PINK]->posX = ghost[PINK]->posY = 0; ghost[BLUE]->posX = ghost[BLUE]->posY = 0; ghost[ORANGE]->posX = ghost[ORANGE]->posY = 0; } void Game::initPlayersLevelData() { currentPlayer = 0; // init player state PlayerLevelState *p = &levelState[0]; p->diffEntry = 0; p->firstFruit = false; p->secondFruit = false; p->eatenPills = 0; p->pinkGhostHomeCounter = 0; p->blueGhostHomeCounter = 0; p->orangeGhostHomeCounter = 0; p->easierFlag = false; p->currentLevel = 0; // init player pills p->initPills(); // copy settings to the other player levelState[1].clone(p); } ///////////////////////////////////////////////////////////////////////////// // main loop ///////////////////////////////////////////////////////////////////////////// // resumes execution void Game::run() { // fill sprite list sprites.clear(); drawFruit(); pacman->draw(); for (int i = 0; i < 4; i++){ ghost[i]->draw(); } // updates the internal timers scheduler.updateInternalTimers(); // executes scheduled tasks scheduler.executeScheduledTasks(); // resumes execution states[state]->run(); // if we're not initializing, adjust and animate sprites if (state != INIT){ // animate sprites if (pacman->deadAnimState != 0){ for (int i = 0; i < 4; i++){ ghost[i]->selectProperAnimation(); } } else { if (collisionObject == 0){ pacman->selectProperAnimation(); for (int i = 0; i < 4; i++){ ghost[i]->selectProperAnimation(); } } } // update credits if a new coin is inserted settings.checkForCoinInserted(); settings.checkForNewCredit(); // show/hide 1UP or 2UP Writer::showOrHide1UP2UP(); if (state == DEMO){ // TODOSOUND: if we're in demo mode, make sure that we aren't playing any sound } } } ///////////////////////////////////////////////////////////////////////////// // game counters ///////////////////////////////////////////////////////////////////////////// // check if we have to set the difficulty flags void Game::updateDifficultyFlags() { // if orange ghost has exited home if (theGame->ghost[PINK]->substate != 0){ int remainingPills = 244 - theGame->levelState[0].eatenPills; // check if we have to set the first difficulty flag if (!theGame->firstDiffFlag){ if (remainingPills <= theGame->pillsFirstDiffFlag){ theGame->firstDiffFlag = true; } } // check if we have to set the second difficulty flag if (!theGame->secondDiffFlag){ if (remainingPills <= theGame->pillsSecondDiffFlag){ theGame->secondDiffFlag = true; } } } } // check if a ghost has to exit home void Game::checkGhostsExitHomeCounters() { // if pacman has died in this level, wait a bit to let the ghosts go out if (levelState[0].easierFlag){ eatenPillsAfterDead++; } else { // increment ghosts counters checking which ones are out if (ghost[ORANGE]->substate == 0){ if (ghost[BLUE]->substate == 0){ if (ghost[PINK]->substate == 0){ levelState[0].pinkGhostHomeCounter++; } else { levelState[0].blueGhostHomeCounter++; } } else { levelState[0].orangeGhostHomeCounter++; } } } } void Game::checkGhostsExitHomeInactivity() { // if pacman has just eaten a pill, reset inactivity counter if (pacman->pillsInLastMove != levelState[0].eatenPills){ inactivityCounter = 0; } else { inactivityCounter++; // if there's too much inactivity, get ghost out of home if (inactivityCounter == inactivityCounterLimit){ inactivityCounter = 0; // get a ghost out of home if (ghost[PINK]->substate == 0) { ghost[PINK]->substate = 2; } else if (ghost[BLUE]->substate == 0){ ghost[BLUE]->substate = 3; } else if (ghost[ORANGE]->substate == 0){ ghost[ORANGE]->substate = 3; } } } } ///////////////////////////////////////////////////////////////////////////// // movement and collision detection ///////////////////////////////////////////////////////////////////////////// void Game::moveAndHandleCollisions() { // if pacman is dying, keep animating pacman->animateDead(); // if pacman is alive if (pacman->deadAnimState == 0){ // if the game has to kill a ghost, does it killGhosts(); // move killed ghosts for (int i = 0; i < 4; i++){ ghost[i]->moveWhenKilled(); } // if there's a ghost dying, animate it if (collisionObject > 0){ animateGhostDead(); } else { moveAndCheckCollisions(); } } } void Game::moveAndCheckCollisions() { // calculate pacman collision with each ghost at tile level checkTileColision(); // calculate pacman collision with each ghost using absolute positions checkAbsolutePosColision(); // if there wasn't a collision if (collisionObject == 0){ // move pacman pacman->run(); // move the ghosts for (int i = 0; i < 4; i++){ ghost[i]->run(); } // if we're playing if (states[PLAYING]->substate == 0x03){ // check if the big pill effect has ended, and update ghosts accordingly checkEndOfSuperPillEffect(); // check if the ghosts have to go out checkIfPinkGhostGoesOut(); checkIfBlueGhostGoesOut(); checkIfOrangeGhostGoesOut(); } } } // check collisions at the tile level void Game::checkTileColision() { bool collision = false; for (int i = 3; i >= 0; i--){ // if the ghost is alive if (ghost[i]->state == 0){ // if it's in the same tile as pacman, we've detected a collision if ((pacman->tilePosX == ghost[i]->actualTilePosX) && (pacman->tilePosY == ghost[i]->actualTilePosY)){ collisionObject = i + 1; pacman->deadAnimState = i + 1; collision = true; break; } } } if (collision){ // if the ghost was in panic state, pacman continues alive if (ghost[collisionObject - 1]->panicMode){ pacman->deadAnimState = 0; // increment number of kills currentKills++; // updates the score updateScore(currentKills + 1); // TODOSOUND: set eaten sound in channel 2 } } } // check collisions using absolute positions void Game::checkAbsolutePosColision() { // if we haven't detected a collision yet and pacman has eaten a big pill if ((collisionObject == 0) && (pacman->superPillEffect)) { bool collision = false; for (int i = 3; i >= 0; i--){ // if the ghost is alive if (ghost[i]->state == 0){ // if the distance in both coordinates is < 4, we've detected a collision if (((abs(ghost[i]->posX - pacman->posX)) < 4) && ((abs(ghost[i]->posY - pacman->posY)) < 4)){ collisionObject = i + 1; pacman->deadAnimState = i + 1; collision = true; break; } } } if (collision){ // if the ghost was in panic state, pacman continues alive if (ghost[collisionObject - 1]->panicMode){ pacman->deadAnimState = 0; // increment number of kills currentKills++; // updates the score updateScore(currentKills + 1); // TODOSOUND: set eaten sound in channel 2 } } } } ///////////////////////////////////////////////////////////////////////////// // game's animation methods ///////////////////////////////////////////////////////////////////////////// void Game::animateGhostDead() { ghostDeadSubstates[ghostDeadAnimState]->run(); } void Game::updateGhostAnimCounters() { cntGhostsAnimations++; // change animation frame each 8 ticks if (cntGhostsAnimations == 8){ cntGhostsAnimations = 0; ghostsAnimationFrame = ghostsAnimationFrame ^ 1; } } void Game::updateSuperPillState() { cntSuperPillsAnimation++; // change pill state each 10 ticks if (cntSuperPillsAnimation == 10){ cntSuperPillsAnimation = 0; // if we're playing in the level if (states[PLAYING]->substate == 3){ int color = (CRAM[0x064] == 0x010) ? 0x00 : 0x10; // change super pills visibility CRAM[0x064] = CRAM[0x078] = CRAM[0x384] = CRAM[0x398] = color; } else { // we are in the demo int color = (CRAM[0x332] == 0x010) ? 0x00 : 0x10; // change super pills visibility CRAM[0x278] = CRAM[0x332] = color; } } } void Game::moveGhostAtHome() { // if there wasn't any unhandled collision if (collisionObject == 0){ // update movement bit pattern cntGhostsMovHome = Operations::rotateLeft<UINT8, 8>((UINT8)cntGhostsMovHome); // if we have to move, update ghosts move at home if (cntGhostsMovHome & 0x01){ for (int i = 0; i < 4; i++){ ghost[i]->moveAtHome(); } } } } void Game::setDeadGhostsColor() { // in demo, dead ghost are not visible, otherwise we see their eyes only int color = (states[DEMO]->substate == 0x22) ? 0x00 : 0x19; // change dead ghost colors for (int i = 0; i < 4; i++){ if (ghost[i]->state != 0){ ghost[i]->color = color; } } } void Game::setGhostsColors() { // if there wasn't any unhandled collision if (collisionObject == 0){ // when the counter is 0, update ghost colors if (cntGhostsPanicAnim == 0){ cntGhostsPanicAnim = 0x0e; if (pacman->superPillEffect){ if (remainingTimeInPanicState < 0x100){ // TODOSOUND: something sound related here if (pacman->color != 0x09){ pacman->color = 0x09; } else { // TODOSOUND: something sound related here } } } // set ghost colors for (int i = 0; i < 4; i++){ if (ghost[i]->panicMode){ // if super pill effect is ending, alternate colors if (remainingTimeInPanicState < 0x100){ ghost[i]->color = (ghost[i]->color == 0x11) ? 0x12 : 0x11; } } else { // set normal color ghost[i]->color = i*2 + 1; } } } cntGhostsPanicAnim--; } } ///////////////////////////////////////////////////////////////////////////// // misc methods ///////////////////////////////////////////////////////////////////////////// // update score (and maybe hi-score) and check for bonus life after picking an item void Game::updateScore(int action) { static int scorePoints[14] = { 10, 50, 200, 400, 800, 1600, 100, 300, 500, 700, 1000, 2000, 3000, 5000 }; // if we're not in demo mode if (state != DEMO){ // get a pointer to the score and add the points for the action int *score = (currentPlayer == 0) ? &score1P : &score2P; bool *gotBonusLife = (currentPlayer == 0) ? &gotBonusLife1P : &gotBonusLife2P; *score += scorePoints[action]; // check if the player deserves a bonus live if ((!(*gotBonusLife)) && (*score > settings.bonusLife)){ // increments number of lives and set bonus flag *gotBonusLife = true; levelState[0].lives++; levelState[0].displayedLives++; // TODOSOUND: play a sound // update lives Video::drawLives(levelState[0].displayedLives); } // print current player score Writer::printScore(*score, (currentPlayer == 0) ? 0x3fc : 0x3e9, 6, 4); // check if the player has more points than the high score if (*score > highScore){ highScore = *score; // print high score Writer::printScore(highScore, 0x3f2, 6, 4); } } } void Game::initSuperPillVars() { // init panic state time remainingTimeInPanicState = timeInPanicState; pacman->superPillEffect = true; pacman->hasToChangeOrientation = true; // used in demo mode // notify the ghosts of the super pill effect for (int i = 0; i < 4; i++){ ghost[i]->panicMode = true; ghost[i]->hasToChangeOrientation = true; } // init ghost animation counter cntGhostsPanicAnim = 0; currentKills = 0; // change ghosts sprite numbers and colors for (int i = 0; i < 4; i++){ ghost[i]->sprite = 0x1c; ghost[i]->color = 0x11; } // TODOSOUND: change channel 1 sound } void Game::savePillsConfiguration() { int offset = 0; // iterate through normal pills for (int i = 0; i < 240; i++){ offset += Video::pillOffsets[i]; // save pill state levelState[0].normalPills[i] = theGame->VRAM[offset] == 0x10; } // save big pills state theGame->levelState[0].superPills[0] = theGame->VRAM[0x064]; theGame->levelState[0].superPills[1] = theGame->VRAM[0x078]; theGame->levelState[0].superPills[2] = theGame->VRAM[0x384]; theGame->levelState[0].superPills[3] = theGame->VRAM[0x398]; } void Game::checkForOrienationChange() { if (pacman->superPillEffect || (indexOrientationChanges == 7)) return; cntOrientationChanges++; // get next change counter value int nextChange = ghostsOrientationChanges[indexOrientationChanges]; // if it's time to change orientation if (nextChange == cntOrientationChanges){ indexOrientationChanges++; for (int i = 0; i < 4; i++){ ghost[i]->hasToChangeOrientation = true; } } } void Game::checkDisplayFruit() { static int fruitData[21][3] = { { 0x00, 0x14, 0x06 }, { 0x01, 0x0f, 0x07 }, { 0x02, 0x15, 0x08 }, { 0x02, 0x15, 0x08 }, { 0x04, 0x14, 0x09 }, { 0x04, 0x14, 0x09 }, { 0x05, 0x17, 0x0a }, { 0x05, 0x17, 0x0a }, { 0x06, 0x09, 0x0b }, { 0x06, 0x09, 0x0b }, { 0x03, 0x16, 0x0c }, { 0x03, 0x16, 0x0c }, { 0x07, 0x16, 0x0d }, { 0x07, 0x16, 0x0d }, { 0x07, 0x16, 0x0d }, { 0x07, 0x16, 0x0d }, { 0x07, 0x16, 0x0d }, { 0x07, 0x16, 0x0d }, { 0x07, 0x16, 0x0d }, { 0x07, 0x16, 0x0d }, { 0x07, 0x16, 0x0d } }; if (pacman->deadAnimState == 0){ // if there's a fruit out, exits if (fruitEntry != 0) return; bool showFruit = false; // check if it's time to show a fruit (if it hasn't showed up yet) if (levelState[0].eatenPills == 0x46){ if (!levelState[0].firstFruit){ levelState[0].firstFruit = true; showFruit = true; } } else if (levelState[0].eatenPills == 0xaa){ if (!levelState[0].secondFruit){ levelState[0].secondFruit = true; showFruit = true; } } if (showFruit){ int index = levelState[0].currentLevel; if (index > 20) index = 20; // set fruit data fruitPosition = 0x8094; fruitSprite = fruitData[index][0]; fruitColor = fruitData[index][1]; fruitEntry = fruitData[index][2]; // clear fruit in 10 seconds theScheduler->addScheduledTask(new Task(4, Task::SECONDS, 10)); } } } ///////////////////////////////////////////////////////////////////////////// // helper methods ///////////////////////////////////////////////////////////////////////////// void Game::killGhosts() { // if there's a ghost to kill if (killingGhostState != 0){ // kill ghost and reset state ghost[killingGhostState - 1]->state = 1; killingGhostState = 0; } } void Game::checkEndOfSuperPillEffect() { if (pacman->superPillEffect){ bool anyGhostInPanicState = false; // check if any ghost is in panic mode for (int i = 0; i < 4; i++){ anyGhostInPanicState |= ghost[i]->panicMode; } if (anyGhostInPanicState){ remainingTimeInPanicState--; // if the super pill effect hasn't expired yet, exits if (remainingTimeInPanicState != 0) return; } // finish super pill effect pacman->color = 0x09; // if a ghost was alive, disables its panic mode for (int i = 0; i < 4; i++){ if (ghost[i]->state == 0){ ghost[i]->panicMode = false; } } remainingTimeInPanicState = 0; pacman->superPillEffect = false; cntGhostsPanicAnim = 0; currentKills = 0; } } void Game::checkIfPinkGhostGoesOut() { // if the ghost is at home if (ghost[PINK]->substate == 0){ if (levelState[0].easierFlag){ // set pink ghost to cross the door after 7 eaten pills if (eatenPillsAfterDead == 7){ ghost[PINK]->substate = 2; } } else { // set pink ghost to cross the door if its home counter has exceeded the limit if (levelState[0].pinkGhostHomeCounter >= pinkGhostExitHomeCounterLimit){ ghost[PINK]->substate = 2; } } } } void Game::checkIfBlueGhostGoesOut() { // if the ghost is at home if (ghost[BLUE]->substate == 0){ if (levelState[0].easierFlag){ // set blue ghost to cross the door after 17 eaten pills if (eatenPillsAfterDead == 17){ ghost[BLUE]->substate = 3; } } else { // set blue ghost to cross the door if its home counter has exceeded the limit if (levelState[0].blueGhostHomeCounter >= blueGhostExitHomeCounterLimit){ ghost[BLUE]->substate = 3; } } } } void Game::checkIfOrangeGhostGoesOut() { // if the ghost is at home if (ghost[ORANGE]->substate == 0){ if (levelState[0].easierFlag){ // reset easier flag after 32 eaten pills if (eatenPillsAfterDead == 32){ levelState[0].easierFlag = false; eatenPillsAfterDead = 0; } } else { // set orangeghost to cross the door if its home counter has exceeded the limit if (levelState[0].orangeGhostHomeCounter >= orangeGhostExitHomeCounterLimit){ ghost[ORANGE]->substate = 3; } } } } void Game::drawFruit() { // if there's a fruit displayed, add fruit sprite if (fruitEntry != 0){ Sprite spr((fruitPosition >> 8) & 0xff, fruitPosition & 0xff, fruitSprite, fruitColor); theGame->sprites.push_back(spr); } }