diff --git a/lib/Atlas.hpp b/lib/Atlas.hpp index 610459d..9f7cd44 100644 --- a/lib/Atlas.hpp +++ b/lib/Atlas.hpp @@ -22,6 +22,33 @@ namespace Atlas { constexpr PositionInt pacman_down_wide = { 7, 0 }; constexpr PositionInt pacman_down_narrow = { 8, 0 }; + constexpr PositionInt ghost_frightened1 = { 0, 7 }; + constexpr PositionInt ghost_frightened2 = { 1, 7 }; + constexpr PositionInt ghost_frightened3 = { 2, 7 }; + constexpr PositionInt ghost_frightened4 = { 3, 7 }; + + constexpr PositionInt eyeSprite(Direction direction) { + int x = 0; + switch (direction) { + case Direction::RIGHT: + x = 0; + break; + case Direction::DOWN: + x = 2; + break; + case Direction::LEFT: + x = 4; + break; + case Direction::UP: + x = 6; + break; + default: + x = 0; + break; + } + return { x, 6 }; + } + constexpr PositionInt ghostSprite(Ghost ghost, Direction direction, bool alternative) { assert(ghost >= Ghost::blinky && ghost <= Ghost::clyde && "Invalid Ghost"); int y = static_cast(ghost); diff --git a/lib/Board.cpp b/lib/Board.cpp index bb392b4..f69f342 100644 --- a/lib/Board.cpp +++ b/lib/Board.cpp @@ -58,16 +58,15 @@ bool Board::isWalkableForGhost(Position point, float d, Direction direction) con } bool Board::isWalkable(Position point) const { - return board_state[int(point.y)][int(point.x)] != uint8_t(Cell::wall); + return board_state[int(point.y)][int(point.x)] != uint8_t(Cell::wall); } -bool Board::isWalkableForGost(Position point, Position origin) const -{ - return isWalkable(point) && (isInPen(origin) || !isInPen(point)); +bool Board::isWalkableForGost(Position point, Position origin, bool isEyes) const { + return isWalkable(point) && (isEyes || (isInPen(origin) || !isInPen(point))); } bool Board::isInPen(Position point) const { - return board_state[int(point.y)][int(point.x)] == uint8_t(Cell::pen); + return board_state[int(point.y)][int(point.x)] == uint8_t(Cell::pen); } bool Board::isWalkable(Position point, float position_delta, Direction direction, bool pacman) const { diff --git a/lib/Board.hpp b/lib/Board.hpp index c917d64..caeb0b6 100644 --- a/lib/Board.hpp +++ b/lib/Board.hpp @@ -26,7 +26,7 @@ public: [[nodiscard]] bool isWalkableForPacMan(Position point, float d, Direction direction) const; [[nodiscard]] bool isWalkableForGhost(Position point, float d, Direction direction) const; - [[nodiscard]] bool isWalkableForGost(Position point, Position origin) const; + [[nodiscard]] bool isWalkableForGost(Position point, Position origin, bool isEyes) const; [[nodiscard]] bool isWalkable(Position point) const; [[nodiscard]] bool isInPen(Position point) const; diff --git a/lib/Canvas.cpp b/lib/Canvas.cpp index fe03204..ce89e72 100644 --- a/lib/Canvas.cpp +++ b/lib/Canvas.cpp @@ -24,15 +24,17 @@ void Canvas::update(const Game & game) { renderMaze(); renderPellets(game.pellets); renderSuperPellets(game.superPellets); - renderPacMan(game.pacMan); - std::apply([&](const auto&... ghost) { - (renderGhost(ghost),...); - }, game.ghosts); + std::apply([&](const auto &... ghost) { + (renderGhost(ghost), ...); + }, + game.ghosts); renderScore(game.score.points); renderLives(game.score.lives); + renderPacMan(game.pacMan); + render(); } diff --git a/lib/Direction.hpp b/lib/Direction.hpp index 7884fc0..a1b5371 100644 --- a/lib/Direction.hpp +++ b/lib/Direction.hpp @@ -8,14 +8,18 @@ enum class Direction { DOWN }; -inline Direction oppositeDirection(Direction d) -{ - switch (d) { - case Direction::LEFT: return Direction::RIGHT; - case Direction::RIGHT: return Direction::LEFT; - case Direction::UP: return Direction::DOWN; - case Direction::DOWN: return Direction::UP; - case Direction::NONE: return d; - } - return d; +inline Direction oppositeDirection(Direction d) { + switch (d) { + case Direction::LEFT: + return Direction::RIGHT; + case Direction::RIGHT: + return Direction::LEFT; + case Direction::UP: + return Direction::DOWN; + case Direction::DOWN: + return Direction::UP; + case Direction::NONE: + return d; + } + return d; } diff --git a/lib/Game.cpp b/lib/Game.cpp index b8222d3..2ab6831 100644 --- a/lib/Game.cpp +++ b/lib/Game.cpp @@ -5,7 +5,7 @@ constexpr int DEFAULT_LIVES = 3; constexpr int NORMAL_PELLET_POINTS = 10; constexpr int POWER_PELLET_POINTS = 50; -//constexpr int GHOST_POINTS[] = {200, 400, 800, 1600}; +constexpr int GHOST_POINTS = 200; Game::Game() : pacMan(board), @@ -15,7 +15,6 @@ Game::Game() score.lives = DEFAULT_LIVES; } - auto Game::now() { return std::chrono::system_clock::now(); } @@ -48,15 +47,54 @@ void Game::run() { } void Game::step(std::chrono::milliseconds delta, InputState inputState) { + pacMan.update(delta, inputState, board); + if (timeSinceDeath.count() != 0) { + timeSinceDeath += delta; + } + + if (timeSinceDeath.count() > 1000) { + std::apply([&](auto &... ghost) { + (ghost.reset(), ...); + }, + ghosts); + pacMan.reset(board); + timeSinceDeath = std::chrono::milliseconds(0); + } + + if (timeSinceDeath.count()) + return; + + if (!pacMan.onTheMove()) + return; + std::apply([&](auto &... ghost) { - (ghost.update(delta, board),...); - }, ghosts); + (ghost.update(delta, board), ...); + (checkCollision(ghost), ...); + }, + ghosts); eatPellets(); } +void Game::checkCollision(Ghost & g) { + if (timeSinceDeath.count() || g.isEyes()) + return; + + if (g.positionInGrid() != pacMan.positionInGrid()) + return; + + if (g.isFrightened()) { + g.eat(); + score.points += GHOST_POINTS; + } else { + pacMan.eat(); + score.lives--; + timeSinceDeath = std::chrono::milliseconds(1); + } +} + void Game::eatPellets() { const auto pos = pacMan.positionInGrid(); if (pellets.eatPelletAtPosition(pos)) { @@ -67,6 +105,10 @@ void Game::eatPellets() { if (superPellets.eatPelletAtPosition(pos)) { score.eatenPellets++; score.points += POWER_PELLET_POINTS; + std::apply([&](auto &... ghost) { + (ghost.frighten(), ...); + }, + ghosts); } } diff --git a/lib/Game.hpp b/lib/Game.hpp index d486736..f54725a 100644 --- a/lib/Game.hpp +++ b/lib/Game.hpp @@ -25,10 +25,12 @@ private: SuperPellets superPellets; std::tuple ghosts; Score score; + std::chrono::milliseconds timeSinceDeath; void step(std::chrono::milliseconds delta, InputState inputState); void eatPellets(); void processEvents(InputState & inputState); + void checkCollision(Ghost & g); [[nodiscard]] static auto now(); }; diff --git a/lib/Ghost.cpp b/lib/Ghost.cpp index 032ebdf..1d3aa91 100644 --- a/lib/Ghost.cpp +++ b/lib/Ghost.cpp @@ -8,8 +8,50 @@ Ghost::Ghost(Atlas::Ghost spritesSet, Position startingPosition, Position scatte scatterTarget(scatterTarget) { } +void Ghost::frighten() { + if (state > State::Scatter) + return; + direction = oppositeDirection(direction); + state = State::Frightened; + timeFrighten = 0; +} + +bool Ghost::isFrightened() const { + return state == State::Frightened; +} + +bool Ghost::isEyes() const { + return state == State::Eyes; +} + +void Ghost::eat() { + if (state == State::Eyes) + return; + direction = oppositeDirection(direction); + state = State::Eyes; + timeFrighten = 0; + timeChase = 0; +} + +void Ghost::reset() { + pos = startingPosition; +} + [[nodiscard]] PositionInt Ghost::currentSprite() const { - return Atlas::ghostSprite(spritesSet, direction, alternate_animation); + switch (state) { + default: + return Atlas::ghostSprite(spritesSet, direction, (animationIndex % 2) == 0); + case State::Frightened: + if (timeFrighten < 3500) + return (animationIndex % 2) == 0 ? Atlas::ghost_frightened2 : Atlas::ghost_frightened1; + return std::array{ Atlas::ghost_frightened1, + Atlas::ghost_frightened2, + Atlas::ghost_frightened3, + Atlas::ghost_frightened4 }[animationIndex]; + case State::Eyes: + return Atlas::eyeSprite(direction); + } + return {}; } Position Ghost::position() const { @@ -17,89 +59,107 @@ Position Ghost::position() const { } Position Ghost::positionInGrid() const { - return { std::round(pos.x), std::round(pos.y) }; + return { std::round(pos.x), std::round(pos.y) }; } void Ghost::update(std::chrono::milliseconds time_delta, const Board & board) { - updateAnimation(time_delta); - updatePosition(time_delta, board); + if (state == State::Eyes && board.isInPen(positionInGrid())) + state = State::Scatter; + + if (state == State::Frightened) { + timeFrighten += time_delta.count(); + if (timeFrighten > 6000) + state = State::Scatter; + } + + updateAnimation(time_delta); + updatePosition(time_delta, board); } void Ghost::updatePosition(std::chrono::milliseconds time_delta, const Board & board) { - updateDirection(board); + updateDirection(board); - float position_delta = (0.004 * time_delta.count()) * 0.75; + float position_delta = (0.004 * time_delta.count()) * speed(); - switch (direction) { - case Direction::NONE: - break; - case Direction::LEFT: - pos.x -= position_delta; - pos.y = round(pos.y); - break; - case Direction::RIGHT: - pos.x += position_delta; - pos.y = round(pos.y); - break; - case Direction::UP: - pos.x = round(pos.x); - pos.y -= position_delta; - break; - case Direction::DOWN: - pos.x = round(pos.x); - pos.y += position_delta; - break; - } + switch (direction) { + case Direction::NONE: + break; + case Direction::LEFT: + pos.x -= position_delta; + pos.y = round(pos.y); + break; + case Direction::RIGHT: + pos.x += position_delta; + pos.y = round(pos.y); + break; + case Direction::UP: + pos.x = round(pos.x); + pos.y -= position_delta; + break; + case Direction::DOWN: + pos.x = round(pos.x); + pos.y += position_delta; + break; + } +} + +double Ghost::speed() const { + if (state == State::Eyes) + return 2; + if (state == State::Frightened) + return 0.5; + return 0.75; } void Ghost::updateDirection(const Board & board) { - auto cell = positionInGrid(); - if(cell == lastIntersection) - return; + auto cell = positionInGrid(); + if (cell == lastIntersection) + return; - struct NewDirection { - Direction direction; - Position position; - double distance; - }; + struct NewDirection { + Direction direction; + Position position; + double distance; + }; - auto [x , y] = cell; - std::array directions = { - NewDirection{Direction::UP, {x, y-1}, 0}, - NewDirection{Direction::LEFT, {x-1, y}, 0}, - NewDirection{Direction::DOWN, {x, y+1}, 0}, - NewDirection{Direction::RIGHT, {x+1, y}, 0} - }; - const Position target = this->target(board); + auto [x, y] = cell; + std::array directions = { + NewDirection{ Direction::UP, { x, y - 1 }, 0 }, + NewDirection{ Direction::LEFT, { x - 1, y }, 0 }, + NewDirection{ Direction::DOWN, { x, y + 1 }, 0 }, + NewDirection{ Direction::RIGHT, { x + 1, y }, 0 } + }; + const Position target = this->target(board); - for(auto && d : directions) { - d.distance = (d.direction != oppositeDirection(direction) && board.isWalkableForGost(d.position, cell)) ? - std::hypot(d.position.x - target.x, d.position.y - target.y) - : std::numeric_limits::infinity(); - } + for (auto && d : directions) { + d.distance = (d.direction != oppositeDirection(direction) && board.isWalkableForGost(d.position, cell, state == State::Eyes)) ? std::hypot(d.position.x - target.x, d.position.y - target.y) + : std::numeric_limits::infinity(); + } - auto it = std::min_element(directions.begin(), directions.end(), [](const auto & a, const auto &b) { - return a.distance < b.distance; - }); + auto it = std::min_element(directions.begin(), directions.end(), [](const auto & a, const auto & b) { + return a.distance < b.distance; + }); - lastIntersection = cell; - direction = it->direction; + lastIntersection = cell; + direction = it->direction; } Position Ghost::target(const Board & board) const { - if(board.isInPen(positionInGrid())) - return board.penDoorPosition(); + if (state == State::Eyes) + return startingPosition; - return scatterTarget; + if (board.isInPen(positionInGrid())) + return board.penDoorPosition(); + + return scatterTarget; } -void Ghost::updateAnimation(std::chrono::milliseconds time_delta) -{ - time += time_delta.count(); - if (time >= 250) { - time = 0; - alternate_animation = !alternate_animation; - } +void Ghost::updateAnimation(std::chrono::milliseconds time_delta) { + timeForAnimation += time_delta.count(); + if (timeForAnimation >= 250) { + timeForAnimation = 0; + animationIndex = (animationIndex + 1) % 4; + } } Blinky::Blinky(const Board & board) diff --git a/lib/Ghost.hpp b/lib/Ghost.hpp index 1f3deb2..d36e1e5 100644 --- a/lib/Ghost.hpp +++ b/lib/Ghost.hpp @@ -11,7 +11,7 @@ public: enum class State { Chase, Scatter, - Freightened, + Frightened, Eyes, }; @@ -24,8 +24,14 @@ public: [[nodiscard]] Position positionInGrid() const; void update(std::chrono::milliseconds time_delta, const Board & board); + void frighten(); + void eat(); + bool isFrightened() const; + bool isEyes() const; + void reset(); private: + double speed() const; void updateAnimation(std::chrono::milliseconds time_delta); void updatePosition(std::chrono::milliseconds time_delta, const Board & board); void updateDirection(const Board & board); @@ -34,13 +40,15 @@ private: protected: Atlas::Ghost spritesSet; Direction direction = Direction::NONE; - double time = 0; - bool alternate_animation = false; + double timeForAnimation = 0; + int animationIndex = 0; State state = State::Chase; + int timeFrighten = 0; + int timeChase = 0; Position pos; Position startingPosition; Position scatterTarget; - Position lastIntersection = {-1, -1}; + Position lastIntersection = { -1, -1 }; }; class Blinky : public Ghost { diff --git a/lib/PacMan.cpp b/lib/PacMan.cpp index aa22ea4..c3e62d1 100644 --- a/lib/PacMan.cpp +++ b/lib/PacMan.cpp @@ -5,7 +5,7 @@ PacMan::PacMan(const Board & board) : pos(Board::initialPacManPosition()) {} PositionInt PacMan::currentSprite() const { - return pacManAnimation.animationFrame(direction); + return eaten ? pacManAnimation.deathAnimationFrame(direction) : pacManAnimation.animationFrame(direction); } Position PacMan::position() const { @@ -16,11 +16,27 @@ Position PacMan::positionInGrid() const { return { std::round(pos.x), std::round(pos.y) }; } -void PacMan::update(std::chrono::milliseconds time_delta, InputState state, const Board & board) { - setDirection(state); - const auto old = pos; - updateMazePosition(time_delta, board); +void PacMan::eat() { + if (eaten) + return; + eaten = true; + direction = Direction::NONE; +} +void PacMan::reset(const Board & b) { + eaten = false; + direction = Direction::NONE; + pos = b.initialPacManPosition(); +} + +void PacMan::update(std::chrono::milliseconds time_delta, InputState state, const Board & board) { + if (eaten) { + updateAnimationPosition(time_delta, false); + return; + } + const auto old = pos; + setDirection(state); + updateMazePosition(time_delta, board); const bool paused = pos == old; updateAnimationPosition(time_delta, paused); } @@ -40,7 +56,7 @@ void PacMan::updateAnimationPosition(std::chrono::milliseconds time_delta, bool if (paused) { pacManAnimation.pause(); } else { - pacManAnimation.updateAnimationPosition(time_delta); + pacManAnimation.updateAnimationPosition(time_delta, eaten); } } diff --git a/lib/PacMan.hpp b/lib/PacMan.hpp index 77bd2e1..a4c7f97 100644 --- a/lib/PacMan.hpp +++ b/lib/PacMan.hpp @@ -21,11 +21,18 @@ public: void update(std::chrono::milliseconds time_delta, InputState state, const Board & board); + void eat(); + void reset(const Board & b); + bool onTheMove() const { + return direction != Direction::NONE; + } + private: Direction direction = Direction::NONE; Direction desired_direction = Direction::NONE; Position pos; PacManAnimation pacManAnimation; + bool eaten = false; void setDirection(const InputState & state); diff --git a/lib/PacManAnimation.cpp b/lib/PacManAnimation.cpp index 538fc6a..cabe5b0 100644 --- a/lib/PacManAnimation.cpp +++ b/lib/PacManAnimation.cpp @@ -16,9 +16,20 @@ PositionInt PacManAnimation::animationFrame(Direction direction) const { } } -void PacManAnimation::updateAnimationPosition(std::chrono::milliseconds time_delta) { +[[nodiscard]] PositionInt PacManAnimation::deathAnimationFrame(Direction direction) const { + return PositionInt{ animation_position, 1 }; +} + +void PacManAnimation::updateAnimationPosition(std::chrono::milliseconds time_delta, bool dead) { + if (dead && animation_position >= 11) + return; + animation_position_delta += (0.02) * float(time_delta.count()); - animation_position = int(animation_position + animation_position_delta) % 4; + animation_position = int(animation_position + animation_position_delta); + + if (!dead) + animation_position = animation_position % 4; + animation_position_delta = (animation_position_delta < 1) ? animation_position_delta : (animation_position_delta - 1); } diff --git a/lib/PacManAnimation.hpp b/lib/PacManAnimation.hpp index 5c2de23..a1f683a 100644 --- a/lib/PacManAnimation.hpp +++ b/lib/PacManAnimation.hpp @@ -11,8 +11,9 @@ class PacManAnimation { public: [[nodiscard]] PositionInt animationFrame(Direction direction) const; + [[nodiscard]] PositionInt deathAnimationFrame(Direction direction) const; - void updateAnimationPosition(std::chrono::milliseconds time_delta); + void updateAnimationPosition(std::chrono::milliseconds time_delta, bool dead); void pause(); private: