diff --git a/lib/Atlas.hpp b/lib/Atlas.hpp new file mode 100644 index 0000000..610459d --- /dev/null +++ b/lib/Atlas.hpp @@ -0,0 +1,50 @@ +#pragma once +#include "Direction.hpp" +#include "Position.hpp" +#include "assert.h" + +namespace Atlas { + + enum class Ghost { + blinky = 2, + speedy = 3, + inky = 4, + clyde = 5, + }; + + constexpr PositionInt pacman_right_wide = { 0, 0 }; + constexpr PositionInt pacman_right_narrow = { 1, 0 }; + constexpr PositionInt pacman_closed = { 2, 0 }; + constexpr PositionInt pacman_left_narrow = { 3, 0 }; + constexpr PositionInt pacman_left_wide = { 4, 0 }; + constexpr PositionInt pacman_up_wide = { 5, 0 }; + constexpr PositionInt pacman_up_narrow = { 6, 0 }; + constexpr PositionInt pacman_down_wide = { 7, 0 }; + constexpr PositionInt pacman_down_narrow = { 8, 0 }; + + constexpr PositionInt ghostSprite(Ghost ghost, Direction direction, bool alternative) { + assert(ghost >= Ghost::blinky && ghost <= Ghost::clyde && "Invalid Ghost"); + int y = static_cast(ghost); + 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; + } + if (alternative) + x++; + return { x, y }; + } +} diff --git a/lib/Board.cpp b/lib/Board.cpp index f9429ed..0d94ea8 100644 --- a/lib/Board.cpp +++ b/lib/Board.cpp @@ -9,7 +9,7 @@ // 5 - pen doors static const uint8_t board[ROWS][COLUMNS] = { - // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 0 { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 }, // 1 { 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0 }, // 2 @@ -50,11 +50,11 @@ Board::Board() { } bool Board::isWalkableForPacMan(Position point, float d, Direction direction) const { - return isWalkable(point, d, direction, true); + return isWalkable(point, d, direction, true); } bool Board::isWalkableForGhost(Position point, float d, Direction direction) const { - return isWalkable(point, d, direction, false); + return isWalkable(point, d, direction, false); } bool Board::isWalkable(Position point, float position_delta, Direction direction, bool pacman) const { @@ -62,29 +62,29 @@ bool Board::isWalkable(Position point, float position_delta, Direction direction return true; auto cellAtPosition = [&](Position point, float position_delta, Direction direction) { - switch (direction) { - case Direction::LEFT: - return board_state[int(point.y)][int(point.x - position_delta)]; - case Direction::RIGHT: - return board_state[int(point.y)][int(point.x) + 1]; - case Direction::UP: - return board_state[int(point.y - position_delta)][int(point.x)]; - case Direction::DOWN: - return board_state[int(point.y) + 1][int(point.x)]; - case Direction::NONE: - default: - return uint8_t(0); - } + switch (direction) { + case Direction::LEFT: + return board_state[int(point.y)][int(point.x - position_delta)]; + case Direction::RIGHT: + return board_state[int(point.y)][int(point.x) + 1]; + case Direction::UP: + return board_state[int(point.y - position_delta)][int(point.x)]; + case Direction::DOWN: + return board_state[int(point.y) + 1][int(point.x)]; + case Direction::NONE: + default: + return uint8_t(Cell::wall); + } }; - auto cell = cellAtPosition(point, position_delta, direction); - return pacman ? cell != 0 : cell != 0 && cell != 5; + Cell cell = Cell(cellAtPosition(point, position_delta, direction)); + return pacman ? cell != Cell::wall : cell != Cell::wall && cell != Cell::pen_door; } std::vector Board::initialPelletPositions() const { std::vector positions; for (uint8_t row = 0; row < ROWS; row++) { for (uint8_t column = 0; column < COLUMNS; column++) { - if (board_state[row][column] == 1) + if (board_state[row][column] == uint8_t(Cell::pellet)) positions.push_back({ column, row }); } } @@ -95,7 +95,7 @@ std::vector Board::initialSuperPelletPositions() const { std::vector positions; for (uint8_t row = 0; row < ROWS; row++) { for (uint8_t column = 0; column < COLUMNS; column++) { - if (board_state[row][column] == 4) + if (board_state[row][column] == uint8_t(Cell::power_pellet)) positions.push_back({ column, row }); } } diff --git a/lib/Board.hpp b/lib/Board.hpp index 90fd82f..572d89f 100644 --- a/lib/Board.hpp +++ b/lib/Board.hpp @@ -12,6 +12,15 @@ const uint8_t COLUMNS = 28; class Board { public: + enum class Cell { + wall = 0, + pellet = 1, + nothing = 2, + door = 3, + power_pellet = 4, + pen_door = 5, + }; + Board(); [[nodiscard]] bool isWalkableForPacMan(Position point, float d, Direction direction) const; @@ -21,7 +30,19 @@ public: [[nodiscard]] std::vector initialSuperPelletPositions() const; - static Position initialPacManPosition() { return { 14, 23 }; } + static Position initialPacManPosition() { return { 13.5, 23 }; } + + static Position initialBlinkyPosition() { return { 13.5, 11 }; } + static Position blinkyScatterTarget() { return { 25, -2 }; } + + static Position initialSpeedyPosition() { return { 11.5, 14 }; } + static Position speedyScatterTarget() { return { 3, -2 }; } + + static Position initialInkyPosition() { return { 13.5, 14 }; } + static Position inkyScatterTarget() { return { 27, 30 }; } + + static Position initialClydePosition() { return { 15.5, 14 }; } + static Position clydeScatterTarget() { return { 0, 30 }; } private: [[nodiscard]] bool isWalkable(Position point, float d, Direction direction, bool pacman) const; diff --git a/lib/Canvas.cpp b/lib/Canvas.cpp index 525705a..48acf3d 100644 --- a/lib/Canvas.cpp +++ b/lib/Canvas.cpp @@ -1,5 +1,6 @@ #include "Canvas.hpp" - +#include "Game.hpp" +#include "Ghost.hpp" #include "PacMan.hpp" #include "Pellets.hpp" #include "SuperPellets.hpp" @@ -17,15 +18,21 @@ Canvas::Canvas() game_font = loadFont("joystix.ttf"); } -void Canvas::update(const PacMan & pacMan, const Pellets & pellets, const SuperPellets & superPellets, const Score & score) { +void Canvas::update(const Game & game) { clear(); renderMaze(); - renderPellets(pellets); - renderSuperPellets(superPellets); - renderPacMan(pacMan); - renderScore(score.points); - renderLives(score.lives); + renderPellets(game.pellets); + renderSuperPellets(game.superPellets); + renderPacMan(game.pacMan); + + renderGhost(game.blinky); + renderGhost(game.speedy); + renderGhost(game.inky); + renderGhost(game.clyde); + + renderScore(game.score.points); + renderLives(game.score.lives); render(); } @@ -73,6 +80,12 @@ void Canvas::renderPacMan(const PacMan & pac_man) { renderSprite(pacmanSprite, pos); } +void Canvas::renderGhost(const Ghost & ghost) { + Sprite sprite = getSprite(ghost.currentSprite()); + const auto & pos = ghost.position(); + renderSprite(sprite, pos); +} + void Canvas::renderScore(int score) { const int x = LEFT_MARGIN + MAZE_WIDTH + LEFT_MARGIN; const int y = TOP_MARGIN * 2; @@ -87,16 +100,16 @@ void Canvas::renderScore(int score) { } void Canvas::renderLives(int lives) { - constexpr PositionInt liveSprite{3, 0}; - const int x = LEFT_MARGIN + MAZE_WIDTH + LEFT_MARGIN; - const int y = maze_texture.getSize().y; + constexpr PositionInt liveSprite = Atlas::pacman_left_narrow; + const int x = LEFT_MARGIN + MAZE_WIDTH + LEFT_MARGIN; + const int y = maze_texture.getSize().y; - Sprite pacmanSprite = getSprite(liveSprite); - for(int i = 0; i < lives - 1; i++) { - PositionInt pos{x + i * pacmanSprite.getTextureRect().width, y}; - pacmanSprite.setPosition(pos.x, pos.y); - window.draw(pacmanSprite); - } + Sprite pacmanSprite = getSprite(liveSprite); + for (int i = 0; i < lives - 1; i++) { + PositionInt pos{ x + i * pacmanSprite.getTextureRect().width, y }; + pacmanSprite.setPosition(pos.x, pos.y); + window.draw(pacmanSprite); + } } Rect Canvas::windowDimensions() { diff --git a/lib/Canvas.hpp b/lib/Canvas.hpp index 002c09f..dfe11ec 100644 --- a/lib/Canvas.hpp +++ b/lib/Canvas.hpp @@ -4,6 +4,8 @@ #include "Score.hpp" #include +class Game; +class Ghost; class PacMan; class Pellets; class SuperPellets; @@ -11,7 +13,7 @@ class SuperPellets; class Canvas { public: Canvas(); - void update(const PacMan & pacMan, const Pellets & pellets, const SuperPellets & superPellets, const Score &); + void update(const Game & game); std::optional pollEvent(); private: @@ -28,6 +30,7 @@ private: void render(); void renderMaze(); void renderPacMan(const PacMan & pac_man); + void renderGhost(const Ghost & ghost); void renderPellets(const Pellets & pellets); void renderSuperPellets(const SuperPellets & superPellets); void renderSprite(Sprite sprite, Position pos); diff --git a/lib/Game.cpp b/lib/Game.cpp index f3fb914..a44d521 100644 --- a/lib/Game.cpp +++ b/lib/Game.cpp @@ -4,18 +4,18 @@ constexpr int DEFAULT_LIVES = 3; constexpr int NORMAL_PELLET_POINTS = 10; -constexpr int POWER_PELLET_POINTS = 50; +constexpr int POWER_PELLET_POINTS = 50; //constexpr int GHOST_POINTS[] = {200, 400, 800, 1600}; - - - Game::Game() : pacMan(board), pellets(board), - superPellets(board) -{ - score.lives = DEFAULT_LIVES; + superPellets(board), + blinky(board), + speedy(board), + inky(board), + clyde(board) { + score.lives = DEFAULT_LIVES; } auto Game::now() { @@ -24,44 +24,53 @@ auto Game::now() { void Game::run() { - const std::chrono::milliseconds delta_time (1000/60); + const std::chrono::milliseconds delta_time(1000 / 60); - std::chrono::milliseconds t(0); - std::chrono::milliseconds accumulator(0); - auto current_time = now(); + std::chrono::milliseconds t(0); + std::chrono::milliseconds accumulator(0); + auto current_time = now(); - InputState inputState; + InputState inputState; - while (true) { - auto newTime = now(); - auto frameTime = std::chrono::duration_cast(newTime - current_time); - current_time = newTime; - accumulator += frameTime; - processEvents(inputState); - if(inputState.close) - return; - while ( accumulator >= delta_time ) { - pacMan.update(delta_time, inputState, board); - eatPellets(); - accumulator -= delta_time; - t += delta_time; - } - canvas.update(pacMan, pellets, superPellets, score); + while (true) { + auto newTime = now(); + auto frameTime = std::chrono::duration_cast(newTime - current_time); + current_time = newTime; + accumulator += frameTime; + processEvents(inputState); + if (inputState.close) + return; + while (accumulator >= delta_time) { + step(delta_time, inputState); + accumulator -= delta_time; + t += delta_time; } + canvas.update(*this); + } +} + +void Game::step(std::chrono::milliseconds delta, InputState inputState) { + pacMan.update(delta, inputState, board); + + blinky.update(delta, board); + speedy.update(delta, board); + inky.update(delta, board); + clyde.update(delta, board); + + eatPellets(); } void Game::eatPellets() { const auto pos = pacMan.positionInGrid(); - if(pellets.eatPelletAtPosition(pos)) { - score.eatenPellets++; - score.points += NORMAL_PELLET_POINTS; + if (pellets.eatPelletAtPosition(pos)) { + score.eatenPellets++; + score.points += NORMAL_PELLET_POINTS; } - if(superPellets.eatPelletAtPosition(pos)) { - score.eatenPellets++; - score.points += POWER_PELLET_POINTS; + if (superPellets.eatPelletAtPosition(pos)) { + score.eatenPellets++; + score.points += POWER_PELLET_POINTS; } - } void Game::processEvents(InputState & inputState) { diff --git a/lib/Game.hpp b/lib/Game.hpp index abac7a7..88d8adc 100644 --- a/lib/Game.hpp +++ b/lib/Game.hpp @@ -1,10 +1,11 @@ #pragma once -#include "Score.hpp" #include "Board.hpp" #include "Canvas.hpp" +#include "Ghost.hpp" #include "PacMan.hpp" #include "Pellets.hpp" +#include "Score.hpp" #include "SuperPellets.hpp" class InputState; @@ -12,19 +13,24 @@ class InputState; class Game { public: Game(); - void run(); private: + friend class Canvas; + Canvas canvas; Board board; PacMan pacMan; Pellets pellets; SuperPellets superPellets; + Blinky blinky; + Speedy speedy; + Inky inky; + Clyde clyde; Score score; + void step(std::chrono::milliseconds delta, InputState inputState); void eatPellets(); - void processEvents(InputState & inputState); [[nodiscard]] static auto now(); diff --git a/lib/Ghost.cpp b/lib/Ghost.cpp new file mode 100644 index 0000000..48a3612 --- /dev/null +++ b/lib/Ghost.cpp @@ -0,0 +1,45 @@ +#include "Ghost.hpp" +#include + +Ghost::Ghost(Atlas::Ghost spritesSet, Position startingPosition, Position scatterTarget) + : spritesSet(spritesSet), + pos(startingPosition), + startingPosition(startingPosition), + scatterTarget(scatterTarget) { +} + +[[nodiscard]] PositionInt Ghost::currentSprite() const { + return Atlas::ghostSprite(spritesSet, direction, alternate_animation); +} + +Position Ghost::position() const { + return pos; +} + +Position Ghost::positionInGrid() const { + return { std::round(pos.x), std::round(pos.y) }; +} + +void Ghost::update(std::chrono::milliseconds time_delta, const Board & board) { + time += time_delta.count(); + if (time >= 250) { + time = 0; + alternate_animation = !alternate_animation; + } +} + +Blinky::Blinky(const Board & board) + : Ghost(Atlas::Ghost::blinky, board.initialBlinkyPosition(), board.blinkyScatterTarget()) { +} + +Speedy::Speedy(const Board & board) + : Ghost(Atlas::Ghost::speedy, board.initialSpeedyPosition(), board.speedyScatterTarget()) { +} + +Inky::Inky(const Board & board) + : Ghost(Atlas::Ghost::inky, board.initialInkyPosition(), board.inkyScatterTarget()) { +} + +Clyde::Clyde(const Board & board) + : Ghost(Atlas::Ghost::clyde, board.initialClydePosition(), board.clydeScatterTarget()) { +} diff --git a/lib/Ghost.hpp b/lib/Ghost.hpp new file mode 100644 index 0000000..02a52ed --- /dev/null +++ b/lib/Ghost.hpp @@ -0,0 +1,57 @@ +#pragma once + +#include + +#include "Atlas.hpp" +#include "Board.hpp" +#include "Position.hpp" + +class Ghost { +public: + enum class State { + Chase, + Scatter, + Freightened, + Eyes, + }; + + explicit Ghost(Atlas::Ghost spritesSet, Position startingPosition, Position scatterTarget); + + [[nodiscard]] PositionInt currentSprite() const; + + [[nodiscard]] Position position() const; + + [[nodiscard]] Position positionInGrid() const; + + void update(std::chrono::milliseconds time_delta, const Board & board); + +protected: + Atlas::Ghost spritesSet; + Direction direction = Direction::NONE; + double time = 0; + bool alternate_animation = false; + State state = State::Chase; + Position pos; + Position startingPosition; + Position scatterTarget; +}; + +class Blinky : public Ghost { +public: + explicit Blinky(const Board & board); +}; + +class Speedy : public Ghost { +public: + explicit Speedy(const Board & board); +}; + +class Inky : public Ghost { +public: + explicit Inky(const Board & board); +}; + +class Clyde : public Ghost { +public: + explicit Clyde(const Board & board); +}; diff --git a/lib/PacMan.cpp b/lib/PacMan.cpp index 8158640..aa22ea4 100644 --- a/lib/PacMan.cpp +++ b/lib/PacMan.cpp @@ -37,12 +37,11 @@ void PacMan::setDirection(const InputState & state) { } void PacMan::updateAnimationPosition(std::chrono::milliseconds time_delta, bool paused) { - if(paused) { - pacManAnimation.pause(); - } - else { - pacManAnimation.updateAnimationPosition(time_delta); - } + if (paused) { + pacManAnimation.pause(); + } else { + pacManAnimation.updateAnimationPosition(time_delta); + } } void PacMan::updateMazePosition(std::chrono::milliseconds time_delta, const Board & board) { diff --git a/lib/PacManAnimation.cpp b/lib/PacManAnimation.cpp index 4d2d57a..538fc6a 100644 --- a/lib/PacManAnimation.cpp +++ b/lib/PacManAnimation.cpp @@ -12,7 +12,7 @@ PositionInt PacManAnimation::animationFrame(Direction direction) const { return down_animation[animation_position]; case Direction::NONE: default: - return closed; + return Atlas::pacman_closed; } } @@ -23,6 +23,6 @@ void PacManAnimation::updateAnimationPosition(std::chrono::milliseconds time_del } void PacManAnimation::pause() { - animation_position = 0; - animation_position_delta = 0; + animation_position = 0; + animation_position_delta = 0; } diff --git a/lib/PacManAnimation.hpp b/lib/PacManAnimation.hpp index 9ce5327..5c2de23 100644 --- a/lib/PacManAnimation.hpp +++ b/lib/PacManAnimation.hpp @@ -1,5 +1,6 @@ #pragma once +#include "Atlas.hpp" #include "Board.hpp" #include "Direction.hpp" #include "InputState.hpp" @@ -14,21 +15,11 @@ public: void updateAnimationPosition(std::chrono::milliseconds time_delta); void pause(); - private: uint8_t animation_position = 0; float animation_position_delta = 0.0; - const PositionInt right_wide = { 0, 0 }; - const PositionInt right_narrow = { 1, 0 }; - const PositionInt closed = { 2, 0 }; - const PositionInt left_narrow = { 3, 0 }; - const PositionInt left_wide = { 4, 0 }; - const PositionInt up_wide = { 5, 0 }; - const PositionInt up_narrow = { 6, 0 }; - const PositionInt down_wide = { 7, 0 }; - const PositionInt down_narrow = { 8, 0 }; - const PositionInt down_animation[4]{ down_wide, down_narrow, closed, down_narrow }; - const PositionInt left_animation[4]{ left_wide, left_narrow, closed, left_narrow }; - const PositionInt right_animation[4]{ right_wide, right_narrow, closed, right_narrow }; - const PositionInt up_animation[4]{ up_wide, up_narrow, closed, up_narrow }; + const PositionInt down_animation[4]{ Atlas::pacman_down_wide, Atlas::pacman_down_narrow, Atlas::pacman_closed, Atlas::pacman_down_narrow }; + const PositionInt left_animation[4]{ Atlas::pacman_left_wide, Atlas::pacman_left_narrow, Atlas::pacman_closed, Atlas::pacman_left_narrow }; + const PositionInt right_animation[4]{ Atlas::pacman_right_wide, Atlas::pacman_right_narrow, Atlas::pacman_closed, Atlas::pacman_right_narrow }; + const PositionInt up_animation[4]{ Atlas::pacman_up_wide, Atlas::pacman_up_narrow, Atlas::pacman_closed, Atlas::pacman_up_narrow }; }; diff --git a/lib/Score.hpp b/lib/Score.hpp index b713cd7..fe780a6 100644 --- a/lib/Score.hpp +++ b/lib/Score.hpp @@ -1,7 +1,7 @@ #pragma once struct Score { - int lives = 0; - int points = 0; - int eatenPellets = 0; + int lives = 0; + int points = 0; + int eatenPellets = 0; };