From d5379872ce50c0cbd8a139ee92d50a988210c743 Mon Sep 17 00:00:00 2001 From: Corentin Jabot Date: Tue, 5 Oct 2021 11:58:06 +0200 Subject: [PATCH] Cleanup AI code --- lib/Canvas.cpp | 4 +- lib/GameState.cpp | 6 ++- lib/PacManAI.cpp | 94 ++++++++++++++++++------------------ lib/include/PacManAI.hpp | 11 +++++ lib/include/Pellets.hpp | 2 +- lib/include/SuperPellets.hpp | 2 +- test/testPellets.cpp | 6 +-- 7 files changed, 71 insertions(+), 54 deletions(-) diff --git a/lib/Canvas.cpp b/lib/Canvas.cpp index fc8c132..39eb12b 100644 --- a/lib/Canvas.cpp +++ b/lib/Canvas.cpp @@ -80,7 +80,7 @@ void Canvas::renderMaze() { void Canvas::renderPellets(const Pellets & pellets) { Sprite pellet = getSprite(pellets.currentSprite()); - std::vector pelletPositions = pellets.currentPositions(); + std::vector pelletPositions = pellets.allPellets(); for (const auto & pos : pelletPositions) { renderSprite(pellet, gridPositionToPosition(pos)); } @@ -88,7 +88,7 @@ void Canvas::renderPellets(const Pellets & pellets) { void Canvas::renderSuperPellets(const SuperPellets & superPellets) { Sprite pellet = getSprite(superPellets.currentSprite()); - std::vector superPelletPositions = superPellets.currentPositions(); + std::vector superPelletPositions = superPellets.allPellets(); for (const auto & pos : superPelletPositions) { renderSprite(pellet, gridPositionToPosition(pos)); } diff --git a/lib/GameState.cpp b/lib/GameState.cpp index aaf8eae..6ed89f9 100644 --- a/lib/GameState.cpp +++ b/lib/GameState.cpp @@ -8,7 +8,10 @@ constexpr int POWER_PELLET_POINTS = 50; void GameState::step(std::chrono::milliseconds delta) { pacManAI.update(pacMan, pellets); - pacMan.update(delta, inputState.direction()); + pacMan.update(delta, + pacManAI.suggestedDirection() + //inputState.direction() + ); if (isPacManDying()) { handleDeathAnimation(delta); @@ -59,6 +62,7 @@ void GameState::handleDeathAnimation(std::chrono::milliseconds delta) { pinky.reset(); inky.reset(); pacMan.reset(); + pacManAI.reset(); timeSinceDeath = std::chrono::milliseconds(0); } } diff --git a/lib/PacManAI.cpp b/lib/PacManAI.cpp index f4dfece..4844c08 100644 --- a/lib/PacManAI.cpp +++ b/lib/PacManAI.cpp @@ -4,44 +4,66 @@ #include namespace pacman { + +void PacManAI::reset() { + pos = {}; + direction = Direction::RIGHT; +} + Direction PacManAI::suggestedDirection() const { return direction; } +GridPosition PacManAI::pelletClosestToPacman(GridPosition pacmanGridPosition, + const Pellets & pellets) { + auto pelletPositions = pellets.allPellets(); + auto pelletSort = [&pacmanGridPosition](GridPosition pelletA, GridPosition pelletB) { + double distanceA = positionDistance(pacmanGridPosition, pelletA); + double distanceB = positionDistance(pacmanGridPosition, pelletB); + return distanceA < distanceB; + }; + std::sort(pelletPositions.begin(), pelletPositions.end(), pelletSort); + return pelletPositions[0]; +} + +bool PacManAI::isValidMove(const Move & move) { + const bool isOpposite = (move.direction == oppositeDirection(direction)); + if (isOpposite) { + return false; + } + + const bool canWalk = isWalkableForPacMan(move.position); + if (!canWalk) { + return false; + } + return true; +} + +Direction PacManAI::optimalDirection(const std::array & moves) { + const auto optimalMove = std::min_element(moves.begin(), moves.end(), [](const auto & a, const auto & b) { + return a.distanceToTarget < b.distanceToTarget; + }); + + const auto & move = *optimalMove; + return move.direction; +} + void PacManAI::update(const PacMan & pacMan, const Pellets & pellets) { const GridPosition pacManGridPos = pacMan.positionInGrid(); const GridPosition currentGridPos = positionToGridPosition(pos); - if (currentGridPos == pacManGridPos) { + + if (!isIntersection(pacManGridPos) || currentGridPos == pacManGridPos) { return; } - pos = gridPositionToPosition(pacManGridPos); - - if (!isIntersection(pacManGridPos)) { - return; - } - - auto pelletPositions = pellets.currentPositions(); + const auto & pelletPositions = pellets.allPellets(); if (pelletPositions.empty()) { return; } - auto pelletSort = [&pacManGridPos](GridPosition pelletA, GridPosition pelletB) { - double distanceA = positionDistance(pacManGridPos, pelletA); - double distanceB = positionDistance(pacManGridPos, pelletB); - return distanceA < distanceB; - }; - std::sort(pelletPositions.begin(), pelletPositions.end(), pelletSort); - const auto & target = pelletPositions[0]; - const Position targetPos{ double(target.x), double(target.y) }; + const GridPosition targetPos = pelletClosestToPacman(pacManGridPos, pellets); - struct Move { - Direction direction = Direction::NONE; - Position position; - double distanceToTarget = std::numeric_limits::infinity(); - }; - - const Position currentPosition = { double(pacManGridPos.x), double(pacManGridPos.y) }; + const GridPosition currentPosition = pacMan.positionInGrid(); const auto [x, y] = currentPosition; std::array possibleMoves = { Move{ Direction::UP, { x, y - 1 } }, @@ -51,30 +73,10 @@ void PacManAI::update(const PacMan & pacMan, const Pellets & pellets) { }; for (auto & move : possibleMoves) { - const bool invalidPosition = (move.position.x < 0 || move.position.y < 0); - if (invalidPosition) { + if (!isValidMove(move)) continue; - } - - const bool isOpposite = (move.direction == oppositeDirection(direction)); - if (isOpposite) { - continue; - } - - const GridPosition gridMove = { size_t(move.position.x), size_t(move.position.y) }; - const bool canWalk = isWalkableForPacMan(gridMove); - if (!canWalk) { - continue; - } - move.distanceToTarget = positionDistance(move.position, targetPos); } - - const auto optimalMove = std::min_element(possibleMoves.begin(), possibleMoves.end(), [](const auto & a, const auto & b) { - return a.distanceToTarget < b.distanceToTarget; - }); - - const auto & move = *optimalMove; - direction = move.direction; + direction = optimalDirection(possibleMoves); } -} // namespace pacman \ No newline at end of file +} // namespace pacman diff --git a/lib/include/PacManAI.hpp b/lib/include/PacManAI.hpp index 2073054..b42be8d 100644 --- a/lib/include/PacManAI.hpp +++ b/lib/include/PacManAI.hpp @@ -13,8 +13,19 @@ namespace pacman { class PacManAI { public: + struct Move { + Direction direction = Direction::NONE; + GridPosition position; + double distanceToTarget = std::numeric_limits::infinity(); + }; + void update(const PacMan & pacMan, const Pellets & pellets); Direction suggestedDirection() const; + GridPosition pelletClosestToPacman(GridPosition pacmanGridPosition, + const Pellets & pellets); + bool isValidMove(const Move & move); + Direction optimalDirection(const std::array & moves); + void reset(); private: Position pos; diff --git a/lib/include/Pellets.hpp b/lib/include/Pellets.hpp index f6f77f8..d89fbf0 100644 --- a/lib/include/Pellets.hpp +++ b/lib/include/Pellets.hpp @@ -13,7 +13,7 @@ public: return sprite; }; - std::vector currentPositions() const { + std::vector allPellets() const { return positions; } diff --git a/lib/include/SuperPellets.hpp b/lib/include/SuperPellets.hpp index 5638292..9edce7a 100644 --- a/lib/include/SuperPellets.hpp +++ b/lib/include/SuperPellets.hpp @@ -13,7 +13,7 @@ public: return sprite; } - std::vector currentPositions() const { + std::vector allPellets() const { return positions; } diff --git a/test/testPellets.cpp b/test/testPellets.cpp index b11a80a..b90ef7d 100644 --- a/test/testPellets.cpp +++ b/test/testPellets.cpp @@ -4,7 +4,7 @@ template static void pelletInitTest(const T & pellets, size_t size, size_t spriteX, size_t spriteY) { - REQUIRE(pellets.currentPositions().size() == size); + REQUIRE(pellets.allPellets().size() == size); REQUIRE(pellets.currentSprite().x == spriteX); REQUIRE(pellets.currentSprite().y == spriteY); } @@ -19,13 +19,13 @@ TEST_CASE("Pellet initialization", "[pellets]") { template static void pelletEatingTest(T & pellets, size_t countBefore, size_t countAfter, size_t x, size_t y) { - REQUIRE(pellets.currentPositions().size() == countBefore); + REQUIRE(pellets.allPellets().size() == countBefore); if (countBefore != countAfter) { REQUIRE(pellets.eatPelletAtPosition(pacman::GridPosition{ x, y })); } else { REQUIRE_FALSE(pellets.eatPelletAtPosition(pacman::GridPosition{ x, y })); } - REQUIRE(pellets.currentPositions().size() == countAfter); + REQUIRE(pellets.allPellets().size() == countAfter); } TEST_CASE("Eating pellets", "[pellets]") {