des/ndctt2021 #1
15 changed files with 177 additions and 38 deletions
|
@ -59,13 +59,16 @@ static Cell cellAtPosition(GridPosition point) {
|
||||||
return Cell(board[point.y][point.x]);
|
return Cell(board[point.y][point.x]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isWall(GridPosition point) {
|
||||||
|
return cellAtPosition(point) == Cell::wall;
|
||||||
|
}
|
||||||
|
|
||||||
bool isWalkableForPacMan(GridPosition point) {
|
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) {
|
bool isWalkableForGhost(GridPosition target_position, GridPosition current_position, bool isEyes) {
|
||||||
const Cell cell = cellAtPosition(target_position);
|
if (isWall(target_position))
|
||||||
if (cell == Cell::wall)
|
|
||||||
return false;
|
return false;
|
||||||
return isEyes || isInPen(current_position) || !isInPen(target_position);
|
return isEyes || isInPen(current_position) || !isInPen(target_position);
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,9 +36,10 @@ void Canvas::render(const GameState & gameState) {
|
||||||
renderPellets(gameState.pellets);
|
renderPellets(gameState.pellets);
|
||||||
renderSuperPellets(gameState.superPellets);
|
renderSuperPellets(gameState.superPellets);
|
||||||
|
|
||||||
renderGhost(gameState.blinky);
|
renderGhost(gameState.ghosts.blinky);
|
||||||
renderGhost(gameState.pinky);
|
renderGhost(gameState.ghosts.pinky);
|
||||||
renderGhost(gameState.inky);
|
renderGhost(gameState.ghosts.inky);
|
||||||
|
renderGhost(gameState.ghosts.dave);
|
||||||
|
|
||||||
renderScore(gameState.score.points);
|
renderScore(gameState.score.points);
|
||||||
renderLives(gameState.score.lives);
|
renderLives(gameState.score.lives);
|
||||||
|
|
38
lib/Dave.cpp
Normal file
38
lib/Dave.cpp
Normal file
|
@ -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
|
|
@ -40,6 +40,9 @@ void Game::processEvents(InputState & inputState) {
|
||||||
if (sf::Keyboard::isKeyPressed(sf::Keyboard::A)) {
|
if (sf::Keyboard::isKeyPressed(sf::Keyboard::A)) {
|
||||||
inputState.enableAI = !inputState.enableAI;
|
inputState.enableAI = !inputState.enableAI;
|
||||||
}
|
}
|
||||||
|
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Q)) {
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
inputState.down = sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Down);
|
inputState.down = sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Down);
|
||||||
inputState.up = sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Up);
|
inputState.up = sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Up);
|
||||||
|
|
|
@ -18,18 +18,11 @@ void GameState::step(std::chrono::milliseconds delta) {
|
||||||
if (!pacMan.hasDirection())
|
if (!pacMan.hasDirection())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
blinky.setTarget(pacMan.position());
|
ghosts.setTarget(pacMan.positionInGrid(), pacMan.currentDirection());
|
||||||
blinky.update(delta);
|
ghosts.update(delta);
|
||||||
pinky.setTarget(pacMan.positionInGrid(), pacMan.currentDirection());
|
|
||||||
pinky.update(delta);
|
|
||||||
inky.setTarget(pacMan.positionInGrid(), pacMan.currentDirection(), blinky.positionInGrid());
|
|
||||||
inky.update(delta);
|
|
||||||
|
|
||||||
fruit.update(delta, score.eatenPellets);
|
fruit.update(delta, score.eatenPellets);
|
||||||
|
ghosts.checkCollision(*this);
|
||||||
checkCollision(blinky);
|
|
||||||
checkCollision(pinky);
|
|
||||||
checkCollision(inky);
|
|
||||||
|
|
||||||
eatPellets();
|
eatPellets();
|
||||||
eatFruit();
|
eatFruit();
|
||||||
|
@ -55,9 +48,7 @@ void GameState::handleDeathAnimation(std::chrono::milliseconds delta) {
|
||||||
timeSinceDeath += delta;
|
timeSinceDeath += delta;
|
||||||
|
|
||||||
if (timeSinceDeath.count() > 1000) {
|
if (timeSinceDeath.count() > 1000) {
|
||||||
blinky.reset();
|
ghosts.reset();
|
||||||
pinky.reset();
|
|
||||||
inky.reset();
|
|
||||||
pacMan.reset();
|
pacMan.reset();
|
||||||
pacManAI.reset();
|
pacManAI.reset();
|
||||||
timeSinceDeath = std::chrono::milliseconds(0);
|
timeSinceDeath = std::chrono::milliseconds(0);
|
||||||
|
@ -74,10 +65,7 @@ void GameState::eatPellets() {
|
||||||
if (superPellets.eatPelletAtPosition(pos)) {
|
if (superPellets.eatPelletAtPosition(pos)) {
|
||||||
score.eatenPellets++;
|
score.eatenPellets++;
|
||||||
score.points += POWER_PELLET_POINTS;
|
score.points += POWER_PELLET_POINTS;
|
||||||
|
ghosts.frighten();
|
||||||
blinky.frighten();
|
|
||||||
pinky.frighten();
|
|
||||||
inky.frighten();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
41
lib/GhostState.cpp
Normal file
41
lib/GhostState.cpp
Normal file
|
@ -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
|
|
@ -16,22 +16,28 @@ Direction PacManAI::suggestedDirection() const {
|
||||||
|
|
||||||
// This function is not yet implemented.
|
// This function is not yet implemented.
|
||||||
// You will implement it as part of module 25.
|
// You will implement it as part of module 25.
|
||||||
GridPosition PacManAI::pelletClosestToPacman(GridPosition,
|
GridPosition PacManAI::pelletClosestToPacman(GridPosition position,
|
||||||
std::vector<GridPosition> &) {
|
std::vector<GridPosition> & pellets) {
|
||||||
|
if (pellets.empty())
|
||||||
return { 0, 0 };
|
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.
|
// This function is not yet implemented.
|
||||||
// You will implement it as part of module 25.
|
// You will implement it as part of module 25.
|
||||||
bool PacManAI::isValidMove(const Move &) {
|
bool PacManAI::isValidMove(const Move & move) {
|
||||||
return false;
|
return isWalkableForPacMan(move.position) && move.direction != oppositeDirection(direction);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function is not yet implemented.
|
// This function is not yet implemented.
|
||||||
// You will implement it as part of module 25.
|
// You will implement it as part of module 25.
|
||||||
Direction PacManAI::optimalDirection(const std::array<Move, 4> &) {
|
Direction PacManAI::optimalDirection(const std::array<Move, 4> & moves) {
|
||||||
return Direction::NONE;
|
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) {
|
void PacManAI::update(const PacMan & pacMan, const Pellets & pellets) {
|
||||||
|
|
|
@ -12,7 +12,7 @@ enum class Ghost : unsigned int {
|
||||||
blinky = 2,
|
blinky = 2,
|
||||||
pinky = 3,
|
pinky = 3,
|
||||||
inky = 4,
|
inky = 4,
|
||||||
clyde = 5,
|
dave = 5,
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr GridPosition pacman_right_wide = { 0, 0 };
|
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) {
|
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<size_t>(ghost);
|
auto y = static_cast<size_t>(ghost);
|
||||||
size_t x = 0;
|
size_t x = 0;
|
||||||
switch (direction) {
|
switch (direction) {
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
namespace pacman {
|
namespace pacman {
|
||||||
|
|
||||||
|
bool isWall(GridPosition point);
|
||||||
bool isWalkableForPacMan(GridPosition point);
|
bool isWalkableForPacMan(GridPosition point);
|
||||||
bool isWalkableForGhost(GridPosition target_position, GridPosition current_position, bool isEyes);
|
bool isWalkableForGhost(GridPosition target_position, GridPosition current_position, bool isEyes);
|
||||||
bool isInPen(GridPosition point);
|
bool isInPen(GridPosition point);
|
||||||
|
|
20
lib/include/Dave.hpp
Normal file
20
lib/include/Dave.hpp
Normal file
|
@ -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
|
|
@ -1,8 +1,10 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Blinky.hpp"
|
#include "Blinky.hpp"
|
||||||
|
#include "Dave.hpp"
|
||||||
#include "Fruits.hpp"
|
#include "Fruits.hpp"
|
||||||
#include "Ghost.hpp"
|
#include "Ghost.hpp"
|
||||||
|
#include "GhostState.hpp"
|
||||||
#include "Inky.hpp"
|
#include "Inky.hpp"
|
||||||
#include "InputState.hpp"
|
#include "InputState.hpp"
|
||||||
#include "PacMan.hpp"
|
#include "PacMan.hpp"
|
||||||
|
@ -17,9 +19,7 @@ namespace pacman {
|
||||||
struct GameState {
|
struct GameState {
|
||||||
void step(std::chrono::milliseconds delta);
|
void step(std::chrono::milliseconds delta);
|
||||||
|
|
||||||
Blinky blinky;
|
GhostState ghosts;
|
||||||
Pinky pinky;
|
|
||||||
Inky inky;
|
|
||||||
|
|
||||||
PacMan pacMan;
|
PacMan pacMan;
|
||||||
PacManAI pacManAI;
|
PacManAI pacManAI;
|
||||||
|
|
26
lib/include/GhostState.hpp
Normal file
26
lib/include/GhostState.hpp
Normal file
|
@ -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
|
|
@ -8,4 +8,4 @@ target_link_libraries(pacman_tests Catch2::Catch2 libpacman)
|
||||||
|
|
||||||
# This setup the tests on CI. We disable the AI tests
|
# This setup the tests on CI. We disable the AI tests
|
||||||
# because you will have to implement them.
|
# because you will have to implement them.
|
||||||
add_test(NAME pacman_tests COMMAND pacman_tests "~[AI]")
|
add_test(NAME pacman_tests COMMAND pacman_tests)
|
||||||
|
|
|
@ -66,3 +66,14 @@ TEST_CASE("Teleport", "[board]") {
|
||||||
REQUIRE(result.x == portalRight.x);
|
REQUIRE(result.x == portalRight.x);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 }));
|
||||||
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ TEST_CASE("Find pellet closest to PacMan", "[AI]") {
|
||||||
PacManAI AI;
|
PacManAI AI;
|
||||||
using TestData = std::tuple<GridPosition, std::vector<GridPosition>, GridPosition>;
|
using TestData = std::tuple<GridPosition, std::vector<GridPosition>, GridPosition>;
|
||||||
auto data = GENERATE(
|
auto data = GENERATE(
|
||||||
|
TestData{{5, 5}, {}, {0, 0}},
|
||||||
TestData{{5, 5}, {{5, 6}}, {5, 6}},
|
TestData{{5, 5}, {{5, 6}}, {5, 6}},
|
||||||
TestData{{5, 5}, {{5, 5}}, {5, 5}},
|
TestData{{5, 5}, {{5, 5}}, {5, 5}},
|
||||||
TestData{{5, 5}, {{0, 0}, {5, 6}}, {5, 6}},
|
TestData{{5, 5}, {{0, 0}, {5, 6}}, {5, 6}},
|
||||||
|
|
Loading…
Reference in a new issue