Pacman and ghosts can eat each others
This commit is contained in:
parent
ced6cd829c
commit
5b0e561afc
13 changed files with 278 additions and 99 deletions
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
50
lib/Game.cpp
50
lib/Game.cpp
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in a new issue