diff --git a/lib/Board.cpp b/lib/Board.cpp index 9a169ba..db7c081 100644 --- a/lib/Board.cpp +++ b/lib/Board.cpp @@ -59,13 +59,16 @@ static Cell cellAtPosition(GridPosition point) { return Cell(board[point.y][point.x]); } +bool isWall(GridPosition point) { + return cellAtPosition(point) == Cell::wall; +} + bool isWalkableForPacMan(GridPosition point) { - return cellAtPosition(point) != Cell::wall && cellAtPosition(point) != Cell::pen; + return !isWall(point) && !isInPen(point); } bool isWalkableForGhost(GridPosition target_position, GridPosition current_position, bool isEyes) { - const Cell cell = cellAtPosition(target_position); - if (cell == Cell::wall) + if (isWall(target_position)) return false; return isEyes || isInPen(current_position) || !isInPen(target_position); } diff --git a/lib/Canvas.cpp b/lib/Canvas.cpp index cac63ee..0c50c83 100644 --- a/lib/Canvas.cpp +++ b/lib/Canvas.cpp @@ -36,9 +36,10 @@ void Canvas::render(const GameState & gameState) { renderPellets(gameState.pellets); renderSuperPellets(gameState.superPellets); - renderGhost(gameState.blinky); - renderGhost(gameState.pinky); - renderGhost(gameState.inky); + renderGhost(gameState.ghosts.blinky); + renderGhost(gameState.ghosts.pinky); + renderGhost(gameState.ghosts.inky); + renderGhost(gameState.ghosts.dave); renderScore(gameState.score.points); renderLives(gameState.score.lives); diff --git a/lib/Dave.cpp b/lib/Dave.cpp new file mode 100644 index 0000000..1520855 --- /dev/null +++ b/lib/Dave.cpp @@ -0,0 +1,38 @@ +#include "Dave.hpp" + +namespace pacman { + +Dave::Dave() + : Ghost(Atlas::Ghost::dave) { + pos = initialPosition(); +} + +double Dave::speed() const { + if (state == State::Eyes) + return 2; + if (state == State::Frightened) + return 0.5; + return 0.75; +} + +void Dave::setTarget(Position pacManPos) { + if (isInPen()) { + target = penDoorPosition(); + return; + } + if (positionDistance(pos, pacManPos) > 8) { + target = pacManPos; + } else { + target = scatterTarget(); + } +} + +Position Dave::initialPosition() const { + return { 15.5, 14 }; +} + +Position Dave::scatterTarget() const { + return { 0, 30 }; +} + +} // namespace pacman diff --git a/lib/Game.cpp b/lib/Game.cpp index 6d9f864..d3333ab 100644 --- a/lib/Game.cpp +++ b/lib/Game.cpp @@ -40,6 +40,9 @@ void Game::processEvents(InputState & inputState) { if (sf::Keyboard::isKeyPressed(sf::Keyboard::A)) { inputState.enableAI = !inputState.enableAI; } + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Q)) { + exit(0); + } inputState.down = sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Down); inputState.up = sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Up); diff --git a/lib/GameState.cpp b/lib/GameState.cpp index b47523c..c25cc85 100644 --- a/lib/GameState.cpp +++ b/lib/GameState.cpp @@ -18,18 +18,11 @@ void GameState::step(std::chrono::milliseconds delta) { if (!pacMan.hasDirection()) return; - blinky.setTarget(pacMan.position()); - blinky.update(delta); - pinky.setTarget(pacMan.positionInGrid(), pacMan.currentDirection()); - pinky.update(delta); - inky.setTarget(pacMan.positionInGrid(), pacMan.currentDirection(), blinky.positionInGrid()); - inky.update(delta); + ghosts.setTarget(pacMan.positionInGrid(), pacMan.currentDirection()); + ghosts.update(delta); fruit.update(delta, score.eatenPellets); - - checkCollision(blinky); - checkCollision(pinky); - checkCollision(inky); + ghosts.checkCollision(*this); eatPellets(); eatFruit(); @@ -55,9 +48,7 @@ void GameState::handleDeathAnimation(std::chrono::milliseconds delta) { timeSinceDeath += delta; if (timeSinceDeath.count() > 1000) { - blinky.reset(); - pinky.reset(); - inky.reset(); + ghosts.reset(); pacMan.reset(); pacManAI.reset(); timeSinceDeath = std::chrono::milliseconds(0); @@ -74,10 +65,7 @@ void GameState::eatPellets() { if (superPellets.eatPelletAtPosition(pos)) { score.eatenPellets++; score.points += POWER_PELLET_POINTS; - - blinky.frighten(); - pinky.frighten(); - inky.frighten(); + ghosts.frighten(); } } diff --git a/lib/GhostState.cpp b/lib/GhostState.cpp new file mode 100644 index 0000000..ffb61ae --- /dev/null +++ b/lib/GhostState.cpp @@ -0,0 +1,41 @@ +#include "GhostState.hpp" +#include "GameState.hpp" + +namespace pacman { + +void GhostState::setTarget(GridPosition pacManPosition, Direction pacManDirection) { + blinky.setTarget(gridPositionToPosition(pacManPosition)); + pinky.setTarget(pacManPosition, pacManDirection); + inky.setTarget(pacManPosition, pacManDirection, blinky.positionInGrid()); + dave.setTarget(gridPositionToPosition(pacManPosition)); +} + +void GhostState::update(std::chrono::milliseconds delta) { + blinky.update(delta); + pinky.update(delta); + inky.update(delta); + dave.update(delta); +} + +void GhostState::checkCollision(GameState & gameState) { + gameState.checkCollision(blinky); + gameState.checkCollision(pinky); + gameState.checkCollision(inky); + gameState.checkCollision(dave); +} + +void GhostState::reset(void) { + blinky.reset(); + pinky.reset(); + inky.reset(); + dave.reset(); +} + +void GhostState::frighten(void) { + blinky.frighten(); + pinky.frighten(); + inky.frighten(); + dave.frighten(); +} + +} // namespace pacman diff --git a/lib/PacManAI.cpp b/lib/PacManAI.cpp index 4688b15..c605b6b 100644 --- a/lib/PacManAI.cpp +++ b/lib/PacManAI.cpp @@ -16,22 +16,28 @@ Direction PacManAI::suggestedDirection() const { // This function is not yet implemented. // You will implement it as part of module 25. -GridPosition PacManAI::pelletClosestToPacman(GridPosition, - std::vector &) { - - return {0, 0}; +GridPosition PacManAI::pelletClosestToPacman(GridPosition position, + std::vector & pellets) { + if (pellets.empty()) + return { 0, 0 }; + return *std::min_element(pellets.begin(), pellets.end(), [position](GridPosition a, GridPosition b) { + return positionDistance(a, position) < positionDistance(b, position); + }); } // This function is not yet implemented. // You will implement it as part of module 25. -bool PacManAI::isValidMove(const Move &) { - return false; +bool PacManAI::isValidMove(const Move & move) { + return isWalkableForPacMan(move.position) && move.direction != oppositeDirection(direction); } // This function is not yet implemented. // You will implement it as part of module 25. -Direction PacManAI::optimalDirection(const std::array &) { - return Direction::NONE; +Direction PacManAI::optimalDirection(const std::array & moves) { + auto bestMove = std::min_element(moves.begin(), moves.end(), [](Move a, Move b) { + return a.distanceToTarget < b.distanceToTarget; + }); + return bestMove->direction; } void PacManAI::update(const PacMan & pacMan, const Pellets & pellets) { diff --git a/lib/include/Atlas.hpp b/lib/include/Atlas.hpp index 6a70cd8..747fd2a 100644 --- a/lib/include/Atlas.hpp +++ b/lib/include/Atlas.hpp @@ -12,7 +12,7 @@ enum class Ghost : unsigned int { blinky = 2, pinky = 3, inky = 4, - clyde = 5, + dave = 5, }; constexpr GridPosition pacman_right_wide = { 0, 0 }; @@ -46,7 +46,7 @@ constexpr GridPosition eyeSprite(Direction direction) { } constexpr GridPosition ghostSprite(Ghost ghost, Direction direction, bool alternative) { - assert(ghost >= Ghost::blinky && ghost <= Ghost::clyde && "Invalid Ghost"); + assert(ghost >= Ghost::blinky && ghost <= Ghost::dave && "Invalid Ghost"); auto y = static_cast(ghost); size_t x = 0; switch (direction) { diff --git a/lib/include/Board.hpp b/lib/include/Board.hpp index ae24e7b..d0ae875 100644 --- a/lib/include/Board.hpp +++ b/lib/include/Board.hpp @@ -8,6 +8,7 @@ namespace pacman { +bool isWall(GridPosition point); bool isWalkableForPacMan(GridPosition point); bool isWalkableForGhost(GridPosition target_position, GridPosition current_position, bool isEyes); bool isInPen(GridPosition point); diff --git a/lib/include/Dave.hpp b/lib/include/Dave.hpp new file mode 100644 index 0000000..935fb96 --- /dev/null +++ b/lib/include/Dave.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include "Ghost.hpp" + +namespace pacman { + +class Dave final : public Ghost { +public: + Dave(); + void setTarget(Position pacManPos); + +protected: + double speed() const override; + Position initialPosition() const override; + +private: + Position scatterTarget() const; +}; + +} // namespace pacman diff --git a/lib/include/GameState.hpp b/lib/include/GameState.hpp index 65f1183..2837e54 100644 --- a/lib/include/GameState.hpp +++ b/lib/include/GameState.hpp @@ -1,8 +1,10 @@ #pragma once #include "Blinky.hpp" +#include "Dave.hpp" #include "Fruits.hpp" #include "Ghost.hpp" +#include "GhostState.hpp" #include "Inky.hpp" #include "InputState.hpp" #include "PacMan.hpp" @@ -17,9 +19,7 @@ namespace pacman { struct GameState { void step(std::chrono::milliseconds delta); - Blinky blinky; - Pinky pinky; - Inky inky; + GhostState ghosts; PacMan pacMan; PacManAI pacManAI; diff --git a/lib/include/GhostState.hpp b/lib/include/GhostState.hpp new file mode 100644 index 0000000..7b862b4 --- /dev/null +++ b/lib/include/GhostState.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include "Blinky.hpp" +#include "Dave.hpp" +#include "Inky.hpp" +#include "Pinky.hpp" + +namespace pacman { + +class GameState; + +class GhostState { +public: + void setTarget(GridPosition pacManPosition, Direction pacManDirection); + void update(std::chrono::milliseconds delta); + void checkCollision(GameState & gameState); + void reset(); + void frighten(); + + Blinky blinky; + Pinky pinky; + Inky inky; + Dave dave; +}; + +} // namespace pacman diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index ad6067e..96f48ef 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -8,4 +8,4 @@ target_link_libraries(pacman_tests Catch2::Catch2 libpacman) # This setup the tests on CI. We disable the AI tests # because you will have to implement them. -add_test(NAME pacman_tests COMMAND pacman_tests "~[AI]") +add_test(NAME pacman_tests COMMAND pacman_tests) diff --git a/test/testBoard.cpp b/test/testBoard.cpp index c9c6fb5..8e296a1 100644 --- a/test/testBoard.cpp +++ b/test/testBoard.cpp @@ -65,4 +65,15 @@ TEST_CASE("Teleport", "[board]") { const pacman::GridPosition result = pacman::teleport(portalLeft); REQUIRE(result.x == portalRight.x); } -} \ No newline at end of file +} + +TEST_CASE("Is wall", "[board]") { + REQUIRE(pacman::isWall(pacman::GridPosition{ 0, 0 })); + REQUIRE(pacman::isWall(pacman::GridPosition{ 27, 30 })); + REQUIRE(!pacman::isWall(pacman::GridPosition{ 1, 1 })); + REQUIRE(!pacman::isWall(pacman::GridPosition{ 26, 29 })); + REQUIRE(!pacman::isWall(pacman::GridPosition{ 0, 14 })); + REQUIRE(!pacman::isWall(pacman::GridPosition{ 27, 14 })); + REQUIRE(!pacman::isWall(pacman::GridPosition{ 11, 13 })); + REQUIRE(!pacman::isWall(pacman::GridPosition{ 16, 15 })); +} diff --git a/test/testPacmanAI.cpp b/test/testPacmanAI.cpp index 4d429fb..4e9e609 100644 --- a/test/testPacmanAI.cpp +++ b/test/testPacmanAI.cpp @@ -7,6 +7,7 @@ TEST_CASE("Find pellet closest to PacMan", "[AI]") { PacManAI AI; using TestData = std::tuple, GridPosition>; auto data = GENERATE( + TestData{{5, 5}, {}, {0, 0}}, TestData{{5, 5}, {{5, 6}}, {5, 6}}, TestData{{5, 5}, {{5, 5}}, {5, 5}}, TestData{{5, 5}, {{0, 0}, {5, 6}}, {5, 6}},