From 9031609348b70d78c931b72fd25b2a7ddd06092d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=93lafur=20Waage?= Date: Fri, 17 Sep 2021 16:44:16 +0200 Subject: [PATCH] Work on the AI project. Not sure if this is good as an exercise. --- lib/Board.cpp | 30 +++++++++++ lib/GameState.cpp | 21 ++++---- lib/PacManAI.cpp | 96 +++++++++++++++++++++++++++++++++++- lib/Pellets.cpp | 8 +++ lib/SuperPellets.cpp | 8 +++ lib/include/Board.hpp | 12 ++++- lib/include/PacManAI.hpp | 16 ++++++ lib/include/Pellets.hpp | 1 + lib/include/SuperPellets.hpp | 1 + 9 files changed, 180 insertions(+), 13 deletions(-) diff --git a/lib/Board.cpp b/lib/Board.cpp index 583e4e9..abf561e 100644 --- a/lib/Board.cpp +++ b/lib/Board.cpp @@ -79,6 +79,36 @@ bool isPortal(GridPosition point, Direction direction) { (cellAtPosition(point) == Cell::right_portal && direction == Direction::RIGHT); } +GridPosition iterateGridPosition(GridPosition point, Direction direction) { + switch (direction) { + case Direction::LEFT: { + if (point.x != 0) { + point.x -= 1; + } + break; + } + case Direction::RIGHT: { + point.x += 1; + break; + } + case Direction::UP: { + if (point.y != 0) { + point.y -= 1; + } + break; + } + case Direction::DOWN: { + point.y += 1; + break; + } + case Direction::NONE: + default: + break; + } + + return point; +} + GridPosition teleport(GridPosition point) { size_t right = COLUMNS - 1; size_t left = 0; diff --git a/lib/GameState.cpp b/lib/GameState.cpp index 8610a44..157dd16 100644 --- a/lib/GameState.cpp +++ b/lib/GameState.cpp @@ -7,7 +7,8 @@ constexpr int NORMAL_PELLET_POINTS = 10; constexpr int POWER_PELLET_POINTS = 50; void GameState::step(std::chrono::milliseconds delta) { - pacMan.update(delta, inputState.direction()); + pacManAI.update(pacMan, pellets, superPellets, blinky, clyde, inky, pinky); + pacMan.update(delta, pacManAI.suggestedDirection()); if (isPacManDying()) { handleDeathAnimation(delta); @@ -18,7 +19,7 @@ void GameState::step(std::chrono::milliseconds delta) { return; blinky.update(delta, *this); // waage: urgh, I wanna remove this - pinky.update(delta, *this); // ghosts know what they want, which is usually pacman's location + pinky.update(delta, *this); // ghosts know what they want, which is usually pacman's location inky.update(delta, *this); clyde.update(delta, *this); fruit.update(delta, *this); @@ -80,14 +81,14 @@ void GameState::eatPellets() { } void GameState::eatFruit() { - const auto pos = pacMan.positionInGrid(); - const auto fruitpos = positionToGridPosition(fruit.position()); + const auto pos = pacMan.positionInGrid(); + const auto fruitpos = positionToGridPosition(fruit.position()); - // TODO: hitboxes based collision - if(fruit.isVisible() && pos == fruitpos) { - score.points += fruit.eat(); - score.eatenFruits++; - } + // TODO: hitboxes based collision + if (fruit.isVisible() && pos == fruitpos) { + score.points += fruit.eat(); + score.eatenFruits++; + } } void GameState::killPacMan() { @@ -100,4 +101,4 @@ bool GameState::isPacManDying() const { return timeSinceDeath.count() != 0; } -} +} // namespace pacman diff --git a/lib/PacManAI.cpp b/lib/PacManAI.cpp index 98d280e..d55399f 100644 --- a/lib/PacManAI.cpp +++ b/lib/PacManAI.cpp @@ -1,4 +1,98 @@ #include "PacManAI.hpp" +#include "Board.hpp" +#include + namespace pacman { -} \ No newline at end of file +Direction PacManAI::suggestedDirection() const { + return direction; +} + +void PacManAI::update(const PacMan pacMan, const Pellets & pellets, const SuperPellets & superPellets, const Blinky & blinky, const Clyde & clyde, const Inky & inky, const Pinky & pinky) { + const GridPosition pacManGridPos = pacMan.positionInGrid(); + const GridPosition currentGridPos = positionToGridPosition(pos); + if (currentGridPos == pacManGridPos) { + return; + } + + pos = gridPositionToPosition(pacManGridPos); + + if (!isIntersection(pacManGridPos)) { + return; + } + + struct GridMove { + Direction direction = Direction::NONE; + GridPosition position; + bool hasGhost = false; + size_t pelletCount = 0; + }; + + std::array possibleMoves = { + GridMove{ Direction::UP, { pacManGridPos.x, pacManGridPos.y - 1 } }, + GridMove{ Direction::LEFT, { pacManGridPos.x - 1, pacManGridPos.y } }, + GridMove{ Direction::DOWN, { pacManGridPos.x, pacManGridPos.y + 1 } }, + GridMove{ Direction::RIGHT, { pacManGridPos.x + 1, pacManGridPos.y } } + }; + + auto isGhostDangerous = [](const auto ghost, GridPosition pos) { + return !ghost.isFrightened() && ghost.positionInGrid() == pos; + }; + + for (auto & move : possibleMoves) { + if (!isWalkableForPacMan(move.position)) { + move.direction = Direction::NONE; + continue; + } + + GridPosition posTest = move.position; + while (isWalkableForPacMan(posTest)) { + if (pellets.isPellet(posTest) || superPellets.isPellet(posTest)) { + move.pelletCount++; + } + + if (isGhostDangerous(blinky, posTest) || isGhostDangerous(clyde, posTest) || isGhostDangerous(inky, posTest) || isGhostDangerous(pinky, posTest)) { + move.hasGhost = true; + } + + const GridPosition oldPosTest = posTest; + posTest = iterateGridPosition(posTest, move.direction); + if (posTest == oldPosTest) { + break; + } + } + } + + auto sortCondition = [&pacMan](const GridMove & moveA, const GridMove & moveB) { + if (moveA.pelletCount == moveB.pelletCount) { + return (moveA.direction == pacMan.currentDirection() ? true : false); + } + return moveA.pelletCount > moveB.pelletCount; + }; + + std::sort(possibleMoves.begin(), possibleMoves.end(), sortCondition); + + for (const auto & move : possibleMoves) { + fmt::print("{}, {}, {}\n", move.pelletCount, move.hasGhost, move.direction); + } + fmt::print("\n"); + + for (const auto & move : possibleMoves) { + if (move.direction != Direction::NONE && !move.hasGhost) { + direction = move.direction; + break; + } + } + + // Then out of all of the movable directions, pick one and exit the loop + /* for (const auto & move : possibleMoves) { + if (move.direction == Direction::NONE || move.hasGhost) { + continue; + } + if (pellets.isPellet(move.position) || superPellets.isPellet(move.position)) { + direction = move.direction; + break; + } + }*/ +} +} // namespace pacman \ No newline at end of file diff --git a/lib/Pellets.cpp b/lib/Pellets.cpp index 39de56d..0ffd443 100644 --- a/lib/Pellets.cpp +++ b/lib/Pellets.cpp @@ -6,6 +6,14 @@ namespace pacman { Pellets::Pellets() : positions(initialPelletPositions()) {} +bool Pellets::isPellet(GridPosition p) const { + auto match = [&p](GridPosition pellet) { + return p.x == pellet.x && p.y == pellet.y; + }; + + return std::any_of(positions.begin(), positions.end(), match); +} + bool Pellets::eatPelletAtPosition(GridPosition p) { auto it = std::find(positions.begin(), positions.end(), p); if (it == positions.end()) diff --git a/lib/SuperPellets.cpp b/lib/SuperPellets.cpp index 0e851e6..16449d7 100644 --- a/lib/SuperPellets.cpp +++ b/lib/SuperPellets.cpp @@ -6,6 +6,14 @@ namespace pacman { SuperPellets::SuperPellets() : positions(initialSuperPelletPositions()) {} +bool SuperPellets::isPellet(GridPosition p) const { + auto match = [&p](GridPosition pellet) { + return p.x == pellet.x && p.y == pellet.y; + }; + + return std::any_of(positions.begin(), positions.end(), match); +} + bool SuperPellets::eatPelletAtPosition(GridPosition p) { auto it = std::find(positions.begin(), positions.end(), p); if (it == positions.end()) diff --git a/lib/include/Board.hpp b/lib/include/Board.hpp index d3fc6ec..2684a49 100644 --- a/lib/include/Board.hpp +++ b/lib/include/Board.hpp @@ -12,13 +12,21 @@ bool isWalkableForPacMan(GridPosition point); bool isWalkableForGhost(GridPosition point, GridPosition origin, bool isEyes); bool isInPen(GridPosition point); bool isPortal(GridPosition point, Direction direction); +GridPosition iterateGridPosition(GridPosition point, Direction direction); GridPosition teleport(GridPosition point); std::vector initialPelletPositions(); std::vector initialSuperPelletPositions(); -inline Position penDoorPosition() { return { 13, 11 }; } -inline Position initialPacManPosition() { return { 13.5, 23 }; } +inline Position penDoorPosition() { + return { 13, 11 }; +} +inline Position initialPacManPosition() { + return { 13.5, 23 }; +} + +bool isWalkableStraightLine(GridPosition pointA, GridPosition pointB); +bool isIntersection(GridPosition point); } // namespace pacman diff --git a/lib/include/PacManAI.hpp b/lib/include/PacManAI.hpp index c426e53..c6b96ea 100644 --- a/lib/include/PacManAI.hpp +++ b/lib/include/PacManAI.hpp @@ -1,9 +1,25 @@ #pragma once +#include "Blinky.hpp" +#include "Clyde.hpp" +#include "Direction.hpp" +#include "Inky.hpp" +#include "PacMan.hpp" +#include "Pellets.hpp" +#include "Pinky.hpp" +#include "Position.hpp" +#include "SuperPellets.hpp" + namespace pacman { class PacManAI { public: + void update(const PacMan pacMan, const Pellets & pellets, const SuperPellets & superPellets, const Blinky & blinky, const Clyde & clyde, const Inky & inky, const Pinky & pinky); + Direction suggestedDirection() const; + +private: + Position pos; + Direction direction = Direction::RIGHT; }; } // namespace pacman diff --git a/lib/include/Pellets.hpp b/lib/include/Pellets.hpp index 90f0514..f6f77f8 100644 --- a/lib/include/Pellets.hpp +++ b/lib/include/Pellets.hpp @@ -17,6 +17,7 @@ public: return positions; } + bool isPellet(GridPosition p) const; bool eatPelletAtPosition(GridPosition p); private: diff --git a/lib/include/SuperPellets.hpp b/lib/include/SuperPellets.hpp index 245daae..5638292 100644 --- a/lib/include/SuperPellets.hpp +++ b/lib/include/SuperPellets.hpp @@ -17,6 +17,7 @@ public: return positions; } + bool isPellet(GridPosition p) const; bool eatPelletAtPosition(GridPosition p); private: