From 34445c754034503659248fc1df9bbdc1347fbb1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dag-Erling=20Sm=C3=B8rgrav?= Date: Mon, 18 Oct 2021 13:25:25 +0200 Subject: [PATCH 01/10] Add Q for quit. --- lib/Game.cpp | 3 +++ 1 file changed, 3 insertions(+) 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); -- 2.45.0 From b9354e3dd1ef332c75849574371a45013732b985 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dag-Erling=20Sm=C3=B8rgrav?= Date: Mon, 18 Oct 2021 13:27:30 +0200 Subject: [PATCH 02/10] Ex 14: add isWall(). --- lib/Board.cpp | 9 ++++++--- lib/include/Board.hpp | 1 + 2 files changed, 7 insertions(+), 3 deletions(-) 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/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); -- 2.45.0 From b42c02d0a6ba60b60a1f35619ba1f68d3b746347 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dag-Erling=20Sm=C3=B8rgrav?= Date: Mon, 18 Oct 2021 13:31:02 +0200 Subject: [PATCH 03/10] Ex 15: Add blank files for the fourth ghost, Dave. --- lib/Dave.cpp | 1 + lib/include/Dave.hpp | 1 + lib/include/GameState.hpp | 1 + 3 files changed, 3 insertions(+) create mode 100644 lib/Dave.cpp create mode 100644 lib/include/Dave.hpp diff --git a/lib/Dave.cpp b/lib/Dave.cpp new file mode 100644 index 0000000..e1afcf8 --- /dev/null +++ b/lib/Dave.cpp @@ -0,0 +1 @@ +#include "Dave.hpp" diff --git a/lib/include/Dave.hpp b/lib/include/Dave.hpp new file mode 100644 index 0000000..6f70f09 --- /dev/null +++ b/lib/include/Dave.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/lib/include/GameState.hpp b/lib/include/GameState.hpp index 65f1183..cf97ca3 100644 --- a/lib/include/GameState.hpp +++ b/lib/include/GameState.hpp @@ -1,6 +1,7 @@ #pragma once #include "Blinky.hpp" +#include "Dave.hpp" #include "Fruits.hpp" #include "Ghost.hpp" #include "Inky.hpp" -- 2.45.0 From dde8996131c4f7cd9167b42f80f25930151f872a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dag-Erling=20Sm=C3=B8rgrav?= Date: Mon, 18 Oct 2021 13:41:49 +0200 Subject: [PATCH 04/10] Ex 15: Add unit tests for isWall(). --- test/testBoard.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) 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 })); +} -- 2.45.0 From 31412de8eaeac1b7a80cbb377ebaa3c5c3b8140b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dag-Erling=20Sm=C3=B8rgrav?= Date: Mon, 18 Oct 2021 14:46:12 +0200 Subject: [PATCH 05/10] Ex 161: Implement Dave. --- lib/Canvas.cpp | 1 + lib/Dave.cpp | 37 +++++++++++++++++++++++++++++++++++++ lib/GameState.cpp | 5 +++++ lib/include/Atlas.hpp | 4 ++-- lib/include/Dave.hpp | 19 +++++++++++++++++++ lib/include/GameState.hpp | 1 + 6 files changed, 65 insertions(+), 2 deletions(-) diff --git a/lib/Canvas.cpp b/lib/Canvas.cpp index cac63ee..a4f01f6 100644 --- a/lib/Canvas.cpp +++ b/lib/Canvas.cpp @@ -39,6 +39,7 @@ void Canvas::render(const GameState & gameState) { renderGhost(gameState.blinky); renderGhost(gameState.pinky); renderGhost(gameState.inky); + renderGhost(gameState.dave); renderScore(gameState.score.points); renderLives(gameState.score.lives); diff --git a/lib/Dave.cpp b/lib/Dave.cpp index e1afcf8..1520855 100644 --- a/lib/Dave.cpp +++ b/lib/Dave.cpp @@ -1 +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/GameState.cpp b/lib/GameState.cpp index b47523c..d575937 100644 --- a/lib/GameState.cpp +++ b/lib/GameState.cpp @@ -24,12 +24,15 @@ void GameState::step(std::chrono::milliseconds delta) { pinky.update(delta); inky.setTarget(pacMan.positionInGrid(), pacMan.currentDirection(), blinky.positionInGrid()); inky.update(delta); + dave.setTarget(pacMan.position()); + dave.update(delta); fruit.update(delta, score.eatenPellets); checkCollision(blinky); checkCollision(pinky); checkCollision(inky); + checkCollision(dave); eatPellets(); eatFruit(); @@ -58,6 +61,7 @@ void GameState::handleDeathAnimation(std::chrono::milliseconds delta) { blinky.reset(); pinky.reset(); inky.reset(); + dave.reset(); pacMan.reset(); pacManAI.reset(); timeSinceDeath = std::chrono::milliseconds(0); @@ -78,6 +82,7 @@ void GameState::eatPellets() { blinky.frighten(); pinky.frighten(); inky.frighten(); + dave.frighten(); } } 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/Dave.hpp b/lib/include/Dave.hpp index 6f70f09..935fb96 100644 --- a/lib/include/Dave.hpp +++ b/lib/include/Dave.hpp @@ -1 +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 cf97ca3..976dc97 100644 --- a/lib/include/GameState.hpp +++ b/lib/include/GameState.hpp @@ -21,6 +21,7 @@ struct GameState { Blinky blinky; Pinky pinky; Inky inky; + Dave dave; PacMan pacMan; PacManAI pacManAI; -- 2.45.0 From c4d95e904c7a77fbcfa4f702068abbb6b03f1058 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dag-Erling=20Sm=C3=B8rgrav?= Date: Mon, 18 Oct 2021 15:22:18 +0200 Subject: [PATCH 06/10] Ex 162: Refactor ghost state out of game state. --- lib/Canvas.cpp | 8 ++++---- lib/GameState.cpp | 27 +++++-------------------- lib/GhostState.cpp | 41 ++++++++++++++++++++++++++++++++++++++ lib/include/GameState.hpp | 6 ++---- lib/include/GhostState.hpp | 26 ++++++++++++++++++++++++ 5 files changed, 78 insertions(+), 30 deletions(-) create mode 100644 lib/GhostState.cpp create mode 100644 lib/include/GhostState.hpp diff --git a/lib/Canvas.cpp b/lib/Canvas.cpp index a4f01f6..0c50c83 100644 --- a/lib/Canvas.cpp +++ b/lib/Canvas.cpp @@ -36,10 +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.dave); + 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/GameState.cpp b/lib/GameState.cpp index d575937..c25cc85 100644 --- a/lib/GameState.cpp +++ b/lib/GameState.cpp @@ -18,21 +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); - dave.setTarget(pacMan.position()); - dave.update(delta); + ghosts.setTarget(pacMan.positionInGrid(), pacMan.currentDirection()); + ghosts.update(delta); fruit.update(delta, score.eatenPellets); - - checkCollision(blinky); - checkCollision(pinky); - checkCollision(inky); - checkCollision(dave); + ghosts.checkCollision(*this); eatPellets(); eatFruit(); @@ -58,10 +48,7 @@ void GameState::handleDeathAnimation(std::chrono::milliseconds delta) { timeSinceDeath += delta; if (timeSinceDeath.count() > 1000) { - blinky.reset(); - pinky.reset(); - inky.reset(); - dave.reset(); + ghosts.reset(); pacMan.reset(); pacManAI.reset(); timeSinceDeath = std::chrono::milliseconds(0); @@ -78,11 +65,7 @@ void GameState::eatPellets() { if (superPellets.eatPelletAtPosition(pos)) { score.eatenPellets++; score.points += POWER_PELLET_POINTS; - - blinky.frighten(); - pinky.frighten(); - inky.frighten(); - dave.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/include/GameState.hpp b/lib/include/GameState.hpp index 976dc97..2837e54 100644 --- a/lib/include/GameState.hpp +++ b/lib/include/GameState.hpp @@ -4,6 +4,7 @@ #include "Dave.hpp" #include "Fruits.hpp" #include "Ghost.hpp" +#include "GhostState.hpp" #include "Inky.hpp" #include "InputState.hpp" #include "PacMan.hpp" @@ -18,10 +19,7 @@ namespace pacman { struct GameState { void step(std::chrono::milliseconds delta); - Blinky blinky; - Pinky pinky; - Inky inky; - Dave dave; + 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 -- 2.45.0 From 980a26587d6d0b748961eca7d2b1746a8cf3a727 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dag-Erling=20Sm=C3=B8rgrav?= Date: Tue, 19 Oct 2021 09:47:02 +0200 Subject: [PATCH 07/10] Ex 25: Enable AI tests. --- test/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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) -- 2.45.0 From 4b58b4115cdecd40069254d067a47990a0a5ed4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dag-Erling=20Sm=C3=B8rgrav?= Date: Tue, 19 Oct 2021 10:07:57 +0200 Subject: [PATCH 08/10] Ex 25: Implement pelletClosestToPacman(). --- lib/PacManAI.cpp | 11 +++++++---- test/testPacmanAI.cpp | 1 + 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/PacManAI.cpp b/lib/PacManAI.cpp index 4688b15..9168ab7 100644 --- a/lib/PacManAI.cpp +++ b/lib/PacManAI.cpp @@ -16,10 +16,13 @@ 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. 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}}, -- 2.45.0 From b97e1c6f2cfa090510e4215f83cf7ad3ef91ec8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dag-Erling=20Sm=C3=B8rgrav?= Date: Tue, 19 Oct 2021 09:48:30 +0200 Subject: [PATCH 09/10] Ex 25: Implement isValidMove(). --- lib/PacManAI.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/PacManAI.cpp b/lib/PacManAI.cpp index 9168ab7..523bdff 100644 --- a/lib/PacManAI.cpp +++ b/lib/PacManAI.cpp @@ -27,8 +27,8 @@ GridPosition PacManAI::pelletClosestToPacman(GridPosition 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. -- 2.45.0 From 30ae28cfc780441b02d73b27d4f61bbf5c3e32f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dag-Erling=20Sm=C3=B8rgrav?= Date: Tue, 19 Oct 2021 10:48:26 +0200 Subject: [PATCH 10/10] Ex 25: Implement PacManAI::optimalDirection(). --- lib/PacManAI.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/PacManAI.cpp b/lib/PacManAI.cpp index 523bdff..c605b6b 100644 --- a/lib/PacManAI.cpp +++ b/lib/PacManAI.cpp @@ -33,8 +33,11 @@ bool PacManAI::isValidMove(const Move & move) { // 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) { -- 2.45.0