Pacman and ghosts can eat each others

This commit is contained in:
Corentin Jabot 2021-06-28 12:42:21 +02:00 committed by Patricia Aas
parent ced6cd829c
commit 5b0e561afc
13 changed files with 278 additions and 99 deletions

View file

@ -22,6 +22,33 @@ namespace Atlas {
constexpr PositionInt pacman_down_wide = { 7, 0 };
constexpr PositionInt pacman_down_narrow = { 8, 0 };
constexpr PositionInt ghost_frightened1 = { 0, 7 };
constexpr PositionInt ghost_frightened2 = { 1, 7 };
constexpr PositionInt ghost_frightened3 = { 2, 7 };
constexpr PositionInt ghost_frightened4 = { 3, 7 };
constexpr PositionInt eyeSprite(Direction direction) {
int x = 0;
switch (direction) {
case Direction::RIGHT:
x = 0;
break;
case Direction::DOWN:
x = 2;
break;
case Direction::LEFT:
x = 4;
break;
case Direction::UP:
x = 6;
break;
default:
x = 0;
break;
}
return { x, 6 };
}
constexpr PositionInt ghostSprite(Ghost ghost, Direction direction, bool alternative) {
assert(ghost >= Ghost::blinky && ghost <= Ghost::clyde && "Invalid Ghost");
int y = static_cast<int>(ghost);

View file

@ -61,9 +61,8 @@ bool Board::isWalkable(Position point) const {
return board_state[int(point.y)][int(point.x)] != uint8_t(Cell::wall);
}
bool Board::isWalkableForGost(Position point, Position origin) const
{
return isWalkable(point) && (isInPen(origin) || !isInPen(point));
bool Board::isWalkableForGost(Position point, Position origin, bool isEyes) const {
return isWalkable(point) && (isEyes || (isInPen(origin) || !isInPen(point)));
}
bool Board::isInPen(Position point) const {

View file

@ -26,7 +26,7 @@ public:
[[nodiscard]] bool isWalkableForPacMan(Position point, float d, Direction direction) const;
[[nodiscard]] bool isWalkableForGhost(Position point, float d, Direction direction) const;
[[nodiscard]] bool isWalkableForGost(Position point, Position origin) const;
[[nodiscard]] bool isWalkableForGost(Position point, Position origin, bool isEyes) const;
[[nodiscard]] bool isWalkable(Position point) const;
[[nodiscard]] bool isInPen(Position point) const;

View file

@ -24,15 +24,17 @@ void Canvas::update(const Game & game) {
renderMaze();
renderPellets(game.pellets);
renderSuperPellets(game.superPellets);
renderPacMan(game.pacMan);
std::apply([&](const auto&... ghost) {
(renderGhost(ghost),...);
}, game.ghosts);
std::apply([&](const auto &... ghost) {
(renderGhost(ghost), ...);
},
game.ghosts);
renderScore(game.score.points);
renderLives(game.score.lives);
renderPacMan(game.pacMan);
render();
}

View file

@ -8,14 +8,18 @@ enum class Direction {
DOWN
};
inline Direction oppositeDirection(Direction d)
{
inline Direction oppositeDirection(Direction d) {
switch (d) {
case Direction::LEFT: return Direction::RIGHT;
case Direction::RIGHT: return Direction::LEFT;
case Direction::UP: return Direction::DOWN;
case Direction::DOWN: return Direction::UP;
case Direction::NONE: return d;
case Direction::LEFT:
return Direction::RIGHT;
case Direction::RIGHT:
return Direction::LEFT;
case Direction::UP:
return Direction::DOWN;
case Direction::DOWN:
return Direction::UP;
case Direction::NONE:
return d;
}
return d;
}

View file

@ -5,7 +5,7 @@
constexpr int DEFAULT_LIVES = 3;
constexpr int NORMAL_PELLET_POINTS = 10;
constexpr int POWER_PELLET_POINTS = 50;
//constexpr int GHOST_POINTS[] = {200, 400, 800, 1600};
constexpr int GHOST_POINTS = 200;
Game::Game()
: pacMan(board),
@ -15,7 +15,6 @@ Game::Game()
score.lives = DEFAULT_LIVES;
}
auto Game::now() {
return std::chrono::system_clock::now();
}
@ -48,15 +47,54 @@ void Game::run() {
}
void Game::step(std::chrono::milliseconds delta, InputState inputState) {
pacMan.update(delta, inputState, board);
if (timeSinceDeath.count() != 0) {
timeSinceDeath += delta;
}
if (timeSinceDeath.count() > 1000) {
std::apply([&](auto &... ghost) {
(ghost.update(delta, board),...);
}, ghosts);
(ghost.reset(), ...);
},
ghosts);
pacMan.reset(board);
timeSinceDeath = std::chrono::milliseconds(0);
}
if (timeSinceDeath.count())
return;
if (!pacMan.onTheMove())
return;
std::apply([&](auto &... ghost) {
(ghost.update(delta, board), ...);
(checkCollision(ghost), ...);
},
ghosts);
eatPellets();
}
void Game::checkCollision(Ghost & g) {
if (timeSinceDeath.count() || g.isEyes())
return;
if (g.positionInGrid() != pacMan.positionInGrid())
return;
if (g.isFrightened()) {
g.eat();
score.points += GHOST_POINTS;
} else {
pacMan.eat();
score.lives--;
timeSinceDeath = std::chrono::milliseconds(1);
}
}
void Game::eatPellets() {
const auto pos = pacMan.positionInGrid();
if (pellets.eatPelletAtPosition(pos)) {
@ -67,6 +105,10 @@ void Game::eatPellets() {
if (superPellets.eatPelletAtPosition(pos)) {
score.eatenPellets++;
score.points += POWER_PELLET_POINTS;
std::apply([&](auto &... ghost) {
(ghost.frighten(), ...);
},
ghosts);
}
}

View file

@ -25,10 +25,12 @@ private:
SuperPellets superPellets;
std::tuple<Blinky, Speedy, Inky, Clyde> ghosts;
Score score;
std::chrono::milliseconds timeSinceDeath;
void step(std::chrono::milliseconds delta, InputState inputState);
void eatPellets();
void processEvents(InputState & inputState);
void checkCollision(Ghost & g);
[[nodiscard]] static auto now();
};

View file

@ -8,8 +8,50 @@ Ghost::Ghost(Atlas::Ghost spritesSet, Position startingPosition, Position scatte
scatterTarget(scatterTarget) {
}
void Ghost::frighten() {
if (state > State::Scatter)
return;
direction = oppositeDirection(direction);
state = State::Frightened;
timeFrighten = 0;
}
bool Ghost::isFrightened() const {
return state == State::Frightened;
}
bool Ghost::isEyes() const {
return state == State::Eyes;
}
void Ghost::eat() {
if (state == State::Eyes)
return;
direction = oppositeDirection(direction);
state = State::Eyes;
timeFrighten = 0;
timeChase = 0;
}
void Ghost::reset() {
pos = startingPosition;
}
[[nodiscard]] PositionInt Ghost::currentSprite() const {
return Atlas::ghostSprite(spritesSet, direction, alternate_animation);
switch (state) {
default:
return Atlas::ghostSprite(spritesSet, direction, (animationIndex % 2) == 0);
case State::Frightened:
if (timeFrighten < 3500)
return (animationIndex % 2) == 0 ? Atlas::ghost_frightened2 : Atlas::ghost_frightened1;
return std::array{ Atlas::ghost_frightened1,
Atlas::ghost_frightened2,
Atlas::ghost_frightened3,
Atlas::ghost_frightened4 }[animationIndex];
case State::Eyes:
return Atlas::eyeSprite(direction);
}
return {};
}
Position Ghost::position() const {
@ -21,6 +63,15 @@ Position Ghost::positionInGrid() const {
}
void Ghost::update(std::chrono::milliseconds time_delta, const Board & board) {
if (state == State::Eyes && board.isInPen(positionInGrid()))
state = State::Scatter;
if (state == State::Frightened) {
timeFrighten += time_delta.count();
if (timeFrighten > 6000)
state = State::Scatter;
}
updateAnimation(time_delta);
updatePosition(time_delta, board);
}
@ -28,7 +79,7 @@ void Ghost::update(std::chrono::milliseconds time_delta, const Board & board) {
void Ghost::updatePosition(std::chrono::milliseconds time_delta, const Board & board) {
updateDirection(board);
float position_delta = (0.004 * time_delta.count()) * 0.75;
float position_delta = (0.004 * time_delta.count()) * speed();
switch (direction) {
case Direction::NONE:
@ -52,9 +103,17 @@ void Ghost::updatePosition(std::chrono::milliseconds time_delta, const Board & b
}
}
double Ghost::speed() const {
if (state == State::Eyes)
return 2;
if (state == State::Frightened)
return 0.5;
return 0.75;
}
void Ghost::updateDirection(const Board & board) {
auto cell = positionInGrid();
if(cell == lastIntersection)
if (cell == lastIntersection)
return;
struct NewDirection {
@ -63,22 +122,21 @@ void Ghost::updateDirection(const Board & board) {
double distance;
};
auto [x , y] = cell;
auto [x, y] = cell;
std::array directions = {
NewDirection{Direction::UP, {x, y-1}, 0},
NewDirection{Direction::LEFT, {x-1, y}, 0},
NewDirection{Direction::DOWN, {x, y+1}, 0},
NewDirection{Direction::RIGHT, {x+1, y}, 0}
NewDirection{ Direction::UP, { x, y - 1 }, 0 },
NewDirection{ Direction::LEFT, { x - 1, y }, 0 },
NewDirection{ Direction::DOWN, { x, y + 1 }, 0 },
NewDirection{ Direction::RIGHT, { x + 1, y }, 0 }
};
const Position target = this->target(board);
for(auto && d : directions) {
d.distance = (d.direction != oppositeDirection(direction) && board.isWalkableForGost(d.position, cell)) ?
std::hypot(d.position.x - target.x, d.position.y - target.y)
for (auto && d : directions) {
d.distance = (d.direction != oppositeDirection(direction) && board.isWalkableForGost(d.position, cell, state == State::Eyes)) ? std::hypot(d.position.x - target.x, d.position.y - target.y)
: std::numeric_limits<double>::infinity();
}
auto it = std::min_element(directions.begin(), directions.end(), [](const auto & a, const auto &b) {
auto it = std::min_element(directions.begin(), directions.end(), [](const auto & a, const auto & b) {
return a.distance < b.distance;
});
@ -87,18 +145,20 @@ void Ghost::updateDirection(const Board & board) {
}
Position Ghost::target(const Board & board) const {
if(board.isInPen(positionInGrid()))
if (state == State::Eyes)
return startingPosition;
if (board.isInPen(positionInGrid()))
return board.penDoorPosition();
return scatterTarget;
}
void Ghost::updateAnimation(std::chrono::milliseconds time_delta)
{
time += time_delta.count();
if (time >= 250) {
time = 0;
alternate_animation = !alternate_animation;
void Ghost::updateAnimation(std::chrono::milliseconds time_delta) {
timeForAnimation += time_delta.count();
if (timeForAnimation >= 250) {
timeForAnimation = 0;
animationIndex = (animationIndex + 1) % 4;
}
}

View file

@ -11,7 +11,7 @@ public:
enum class State {
Chase,
Scatter,
Freightened,
Frightened,
Eyes,
};
@ -24,8 +24,14 @@ public:
[[nodiscard]] Position positionInGrid() const;
void update(std::chrono::milliseconds time_delta, const Board & board);
void frighten();
void eat();
bool isFrightened() const;
bool isEyes() const;
void reset();
private:
double speed() const;
void updateAnimation(std::chrono::milliseconds time_delta);
void updatePosition(std::chrono::milliseconds time_delta, const Board & board);
void updateDirection(const Board & board);
@ -34,13 +40,15 @@ private:
protected:
Atlas::Ghost spritesSet;
Direction direction = Direction::NONE;
double time = 0;
bool alternate_animation = false;
double timeForAnimation = 0;
int animationIndex = 0;
State state = State::Chase;
int timeFrighten = 0;
int timeChase = 0;
Position pos;
Position startingPosition;
Position scatterTarget;
Position lastIntersection = {-1, -1};
Position lastIntersection = { -1, -1 };
};
class Blinky : public Ghost {

View file

@ -5,7 +5,7 @@ PacMan::PacMan(const Board & board)
: pos(Board::initialPacManPosition()) {}
PositionInt PacMan::currentSprite() const {
return pacManAnimation.animationFrame(direction);
return eaten ? pacManAnimation.deathAnimationFrame(direction) : pacManAnimation.animationFrame(direction);
}
Position PacMan::position() const {
@ -16,11 +16,27 @@ Position PacMan::positionInGrid() const {
return { std::round(pos.x), std::round(pos.y) };
}
void PacMan::update(std::chrono::milliseconds time_delta, InputState state, const Board & board) {
setDirection(state);
const auto old = pos;
updateMazePosition(time_delta, board);
void PacMan::eat() {
if (eaten)
return;
eaten = true;
direction = Direction::NONE;
}
void PacMan::reset(const Board & b) {
eaten = false;
direction = Direction::NONE;
pos = b.initialPacManPosition();
}
void PacMan::update(std::chrono::milliseconds time_delta, InputState state, const Board & board) {
if (eaten) {
updateAnimationPosition(time_delta, false);
return;
}
const auto old = pos;
setDirection(state);
updateMazePosition(time_delta, board);
const bool paused = pos == old;
updateAnimationPosition(time_delta, paused);
}
@ -40,7 +56,7 @@ void PacMan::updateAnimationPosition(std::chrono::milliseconds time_delta, bool
if (paused) {
pacManAnimation.pause();
} else {
pacManAnimation.updateAnimationPosition(time_delta);
pacManAnimation.updateAnimationPosition(time_delta, eaten);
}
}

View file

@ -21,11 +21,18 @@ public:
void update(std::chrono::milliseconds time_delta, InputState state, const Board & board);
void eat();
void reset(const Board & b);
bool onTheMove() const {
return direction != Direction::NONE;
}
private:
Direction direction = Direction::NONE;
Direction desired_direction = Direction::NONE;
Position pos;
PacManAnimation pacManAnimation;
bool eaten = false;
void setDirection(const InputState & state);

View file

@ -16,9 +16,20 @@ PositionInt PacManAnimation::animationFrame(Direction direction) const {
}
}
void PacManAnimation::updateAnimationPosition(std::chrono::milliseconds time_delta) {
[[nodiscard]] PositionInt PacManAnimation::deathAnimationFrame(Direction direction) const {
return PositionInt{ animation_position, 1 };
}
void PacManAnimation::updateAnimationPosition(std::chrono::milliseconds time_delta, bool dead) {
if (dead && animation_position >= 11)
return;
animation_position_delta += (0.02) * float(time_delta.count());
animation_position = int(animation_position + animation_position_delta) % 4;
animation_position = int(animation_position + animation_position_delta);
if (!dead)
animation_position = animation_position % 4;
animation_position_delta = (animation_position_delta < 1) ? animation_position_delta : (animation_position_delta - 1);
}

View file

@ -11,8 +11,9 @@
class PacManAnimation {
public:
[[nodiscard]] PositionInt animationFrame(Direction direction) const;
[[nodiscard]] PositionInt deathAnimationFrame(Direction direction) const;
void updateAnimationPosition(std::chrono::milliseconds time_delta);
void updateAnimationPosition(std::chrono::milliseconds time_delta, bool dead);
void pause();
private: