diff --git a/lib/Blinky.cpp b/lib/Blinky.cpp index 3016830..34b363f 100644 --- a/lib/Blinky.cpp +++ b/lib/Blinky.cpp @@ -1,4 +1,5 @@ #include "Blinky.hpp" +#include "GameState.hpp" namespace pacman { @@ -22,7 +23,7 @@ Position Blinky::target(const GameState & gameState) const { if (isInPen()) return penDoorPosition(); - return scatterTarget(); + return state == State::Chase ? gridPositionToPosition(gameState.pacMan.positionInGrid()) : scatterTarget(); } Position Blinky::initialPosition() const { diff --git a/lib/Clyde.cpp b/lib/Clyde.cpp index ebcd1fc..b11e380 100644 --- a/lib/Clyde.cpp +++ b/lib/Clyde.cpp @@ -1,4 +1,5 @@ #include "Clyde.hpp" +#include "GameState.hpp" namespace pacman { @@ -22,7 +23,15 @@ Position Clyde::target(const GameState & gameState) const { if (isInPen()) return penDoorPosition(); - return scatterTarget(); + // Clyde always target its scatter target, unless pacman is further than 8 tiles away + auto targetPosition = scatterTarget(); + + const auto & pacmanPosition = gameState.pacMan.positionInGrid(); + auto distanceFomPacMan = std::hypot(pos.x - pacmanPosition.x, pos.y - pacmanPosition.y); + if (state == State::Chase && distanceFomPacMan > 8) + targetPosition = gridPositionToPosition(pacmanPosition); + + return targetPosition; } Position Clyde::initialPosition() const { diff --git a/lib/Ghost.cpp b/lib/Ghost.cpp index 353a06f..90576ac 100644 --- a/lib/Ghost.cpp +++ b/lib/Ghost.cpp @@ -1,6 +1,7 @@ #include "Ghost.hpp" #include #include +#include namespace pacman { @@ -13,7 +14,7 @@ void Ghost::frighten() { return; direction = oppositeDirection(direction); state = State::Frightened; - timeFrighten = 0; + timeFrighten = {}; } bool Ghost::isFrightened() const { @@ -29,15 +30,15 @@ void Ghost::die() { return; direction = oppositeDirection(direction); state = State::Eyes; - timeFrighten = 0; - timeChase = 0; + timeFrighten = {}; + timeChase = {}; } void Ghost::reset() { pos = initialPosition(); state = State::Scatter; - timeFrighten = 0; - timeChase = 0; + timeFrighten = {}; + timeChase = {}; } [[nodiscard]] GridPosition Ghost::currentSprite() const { @@ -47,7 +48,7 @@ void Ghost::reset() { case State::Eyes: return Atlas::eyeSprite(direction); case State::Frightened: - if (timeFrighten < 3500) + if (timeFrighten.count() < 3500) return Atlas::initialFrightened(animationIndex); else return Atlas::endingFrightened(animationIndex); @@ -67,11 +68,20 @@ void Ghost::update(std::chrono::milliseconds time_delta, const GameState & gameS state = State::Scatter; if (state == State::Frightened) { - timeFrighten += time_delta.count(); - if (timeFrighten > 6000) + timeFrighten += time_delta; + if (timeFrighten.count() > 6000) state = State::Scatter; } + if (state == State::Scatter || state == State::Chase) { + timeChase += time_delta; + auto newState = defaultStateAtDuration(std::chrono::duration_cast(timeChase)); + if (newState != state) { + direction = oppositeDirection(direction); + state = newState; + } + } + updateAnimation(time_delta); updatePosition(time_delta, gameState); } @@ -105,6 +115,10 @@ void Ghost::updatePosition(std::chrono::milliseconds time_delta, const GameState pos.y += position_delta; break; } + + if (isPortal(positionInGrid(), direction)) { + pos = gridPositionToPosition(teleport(positionInGrid())); + } } /* @@ -144,6 +158,10 @@ void Ghost::updateDirection(const GameState & gameState) { const Position target_position = target(gameState); for (auto & move : possible_moves) { + + if (isPortal(current_grid_position, move.direction)) + move.position = gridPositionToPosition(teleport(current_grid_position)); + const bool invalid_position = (move.position.x < 0 || move.position.y < 0); if (invalid_position) continue; @@ -177,4 +195,23 @@ void Ghost::updateAnimation(std::chrono::milliseconds time_delta) { } } +/* + * Ghosts alternate between the scatter and chase states at + * specific intervals + */ +Ghost::State Ghost::defaultStateAtDuration(std::chrono::seconds s) { + // This array denotes the duration of each state, alternating between scatter and chase + std::array changes = { /*scatter*/ 7, 20, 7, 20, 5, 20, 5 }; + // To know the current state we first compute the cumulative time using std::partial_sum + // This gives us {7, 27, 34, 54, 59, 79, 84} + std::partial_sum(std::begin(changes), std::end(changes), std::begin(changes)); + // Then we look for the first value in the array greater than the time spend in chase/scatter states + auto it = std::upper_bound(std::begin(changes), std::end(changes), s.count()); + // We get the position of that iterator in the array + auto count = std::distance(std::begin(changes), it); + // Because the first positition is scatter, all the even positions will be scatter + // all the odd positions will be chase + return count % 2 == 0 ? State::Scatter : State::Chase; +} + } // namespace pacman diff --git a/lib/Inky.cpp b/lib/Inky.cpp index 78b2e07..075eefe 100644 --- a/lib/Inky.cpp +++ b/lib/Inky.cpp @@ -1,4 +1,5 @@ #include "Inky.hpp" +#include "GameState.hpp" namespace pacman { @@ -22,7 +23,40 @@ Position Inky::target(const GameState & gameState) const { if (isInPen()) return penDoorPosition(); - return scatterTarget(); + if (state == State::Scatter) + return scatterTarget(); + + // Inky first selects a position 2 cell away from pacman in his direction. + GridPosition targetPosition = gameState.pacMan.positionInGrid(); + switch (gameState.pacMan.currentDirection()) { + case Direction::LEFT: + targetPosition.x -= 2; + break; + case Direction::RIGHT: + targetPosition.x += 2; + break; + case Direction::UP: + targetPosition.y -= 2; + targetPosition.x -= 2; + break; + case Direction::DOWN: + targetPosition.y += 2; + break; + case Direction::NONE: + assert("Pacman should be moving!"); + break; + } + + // Then it calculates the distance between Blinky and this position + const auto & blinkyPosition = gameState.blinky.positionInGrid(); + auto distanceBetweenBlinkyAndTarget = std::hypot(blinkyPosition.x - targetPosition.x, blinkyPosition.y - targetPosition.y); + + // And selects a point on the line crossing blinky and this position that is at twice that distance + // away from blinky + targetPosition.x += ((targetPosition.x - blinkyPosition.x) / distanceBetweenBlinkyAndTarget) * 2; + targetPosition.y += ((targetPosition.y - blinkyPosition.y) / distanceBetweenBlinkyAndTarget) * 2; + + return gridPositionToPosition(targetPosition); } Position Inky::initialPosition() const { diff --git a/lib/Pinky.cpp b/lib/Pinky.cpp index 60afb09..e372359 100644 --- a/lib/Pinky.cpp +++ b/lib/Pinky.cpp @@ -1,4 +1,5 @@ #include "Pinky.hpp" +#include "GameState.hpp" namespace pacman { @@ -22,7 +23,30 @@ Position Pinky::target(const GameState & gameState) const { if (isInPen()) return penDoorPosition(); - return scatterTarget(); + if (state == State::Scatter) + return scatterTarget(); + + // Inky first selects a position 2 cell away from pacman in his direction. + GridPosition targetPosition = gameState.pacMan.positionInGrid(); + switch (gameState.pacMan.currentDirection()) { + case Direction::LEFT: + targetPosition.x -= 4; + break; + case Direction::RIGHT: + targetPosition.x += 4; + break; + case Direction::UP: + targetPosition.y -= 4; + targetPosition.x -= 4; + break; + case Direction::DOWN: + targetPosition.y += 4; + break; + case Direction::NONE: + assert("Pacman should be moving!"); + break; + } + return gridPositionToPosition(targetPosition); } Position Pinky::initialPosition() const { diff --git a/lib/include/Ghost.hpp b/lib/include/Ghost.hpp index d1ce68c..d61b06d 100644 --- a/lib/include/Ghost.hpp +++ b/lib/include/Ghost.hpp @@ -41,6 +41,7 @@ private: void updateDirection(const GameState & gameState); protected: + State defaultStateAtDuration(std::chrono::seconds s); virtual double speed(const GameState & gameState) const = 0; virtual Position target(const GameState & gameState) const = 0; @@ -51,8 +52,8 @@ protected: double timeForAnimation = 0; int animationIndex = 0; State state = State::Chase; - int timeFrighten = 0; - int timeChase = 0; + std::chrono::milliseconds timeFrighten = {}; + std::chrono::milliseconds timeChase = {}; Position pos; GridPosition last_grid_position = { 0, 0 }; [[nodiscard]] bool isInPen() const; diff --git a/lib/include/PacMan.hpp b/lib/include/PacMan.hpp index e998d51..39ea40e 100644 --- a/lib/include/PacMan.hpp +++ b/lib/include/PacMan.hpp @@ -27,6 +27,10 @@ public: return direction != Direction::NONE; } + Direction currentDirection() const { + return direction; + } + private: Direction direction = Direction::NONE; Direction desired_direction = Direction::NONE;