pacman/lib/Ghost.cpp

199 lines
5.2 KiB
C++
Raw Normal View History

2021-06-24 11:32:52 +00:00
#include "Ghost.hpp"
2021-06-28 12:07:28 +00:00
#include <array>
2021-07-01 12:53:52 +00:00
#include <cmath>
2021-06-24 11:32:52 +00:00
2021-07-05 12:10:01 +00:00
namespace pacman {
2021-06-24 11:32:52 +00:00
Ghost::Ghost(Atlas::Ghost spritesSet, Position startingPosition, Position scatterTarget)
: spritesSet(spritesSet),
pos(startingPosition),
startingPosition(startingPosition),
scatterTarget(scatterTarget) {
}
2021-06-28 10:42:21 +00:00
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;
}
2021-07-05 09:46:49 +00:00
[[nodiscard]] GridPosition Ghost::currentSprite() const {
2021-06-28 10:42:21 +00:00
switch (state) {
default:
return Atlas::ghostSprite(spritesSet, direction, (animationIndex % 2) == 0);
case State::Eyes:
return Atlas::eyeSprite(direction);
2021-07-01 12:53:52 +00:00
case State::Frightened:
if (timeFrighten < 3500)
return Atlas::initialFrightened(animationIndex);
else
return Atlas::endingFrightened(animationIndex);
2021-06-28 10:42:21 +00:00
}
2021-06-24 11:32:52 +00:00
}
Position Ghost::position() const {
return pos;
}
2021-07-05 11:54:54 +00:00
GridPosition Ghost::positionInGrid() const {
2021-07-06 10:35:23 +00:00
return positionToGridPosition(pos);
2021-06-24 11:32:52 +00:00
}
void Ghost::update(std::chrono::milliseconds time_delta, const Board & board) {
2021-07-01 12:53:52 +00:00
if (state == State::Eyes && isInPen(board))
2021-06-28 10:42:21 +00:00
state = State::Scatter;
if (state == State::Frightened) {
timeFrighten += time_delta.count();
if (timeFrighten > 6000)
state = State::Scatter;
}
updateAnimation(time_delta);
updatePosition(time_delta, board);
}
2021-07-01 12:53:52 +00:00
bool Ghost::isInPen(const Board & board) const {
return board.isInPen(positionInGrid());
}
void Ghost::updatePosition(std::chrono::milliseconds time_delta, const Board & board) {
2021-06-28 10:42:21 +00:00
updateDirection(board);
2021-07-05 09:40:10 +00:00
double position_delta = (0.004 * time_delta.count()) * speed();
2021-06-28 10:42:21 +00:00
switch (direction) {
case Direction::NONE:
break;
case Direction::LEFT:
pos.x -= position_delta;
pos.y = round(pos.y);
break;
case Direction::RIGHT:
pos.x += position_delta;
pos.y = round(pos.y);
break;
case Direction::UP:
pos.x = round(pos.x);
pos.y -= position_delta;
break;
case Direction::DOWN:
pos.x = round(pos.x);
pos.y += position_delta;
break;
}
}
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) {
2021-07-06 10:35:23 +00:00
const auto current_grid_position = positionInGrid();
if (current_grid_position == last_grid_position)
2021-06-28 10:42:21 +00:00
return;
2021-07-06 10:35:23 +00:00
struct Move {
2021-07-05 12:10:01 +00:00
Direction direction;
2021-07-06 10:35:23 +00:00
Position position;
double distance = std::numeric_limits<double>::infinity();
2021-06-28 10:42:21 +00:00
};
2021-07-06 10:35:23 +00:00
const Position current_position = { double(current_grid_position.x), double(current_grid_position.y) };
const auto [x, y] = current_position;
std::array<Move, 4> possible_moves = { { Move{ Direction::UP, { x, y - 1 } },
Move{ Direction::LEFT, { x - 1, y } },
Move{ Direction::DOWN, { x, y + 1 } },
Move{ Direction::RIGHT, { x + 1, y } } } };
2021-06-28 10:42:21 +00:00
2021-07-06 10:35:23 +00:00
const Position target_position = target(board);
for (auto & move : possible_moves) {
const bool invalid_position = (move.position.x < 0 || move.position.y < 0);
if (invalid_position)
continue;
const bool opposite_direction = (move.direction == oppositeDirection(direction));
if (opposite_direction)
continue;
const GridPosition grid_position = {size_t(move.position.x), size_t(move.position.y)};
const bool can_walk = board.isWalkableForGost(grid_position, current_grid_position, isEyes());
if (!can_walk)
continue;
move.distance = std::hypot(move.position.x - target_position.x, move.position.y - target_position.y);
2021-06-28 10:42:21 +00:00
}
2021-07-06 10:35:23 +00:00
const auto optimal_move = std::min_element(possible_moves.begin(), possible_moves.end(), [](const auto & a, const auto & b) {
2021-06-28 10:42:21 +00:00
return a.distance < b.distance;
});
2021-07-06 15:09:42 +00:00
auto move = *optimal_move;
direction = move.direction;
2021-07-06 10:35:23 +00:00
last_grid_position = current_grid_position;
}
Position Ghost::target(const Board & board) const {
2021-06-28 10:42:21 +00:00
if (state == State::Eyes)
return startingPosition;
if (board.isInPen(positionInGrid()))
return board.penDoorPosition();
2021-06-28 10:42:21 +00:00
return scatterTarget;
}
2021-06-28 10:42:21 +00:00
void Ghost::updateAnimation(std::chrono::milliseconds time_delta) {
timeForAnimation += time_delta.count();
if (timeForAnimation >= 250) {
timeForAnimation = 0;
animationIndex = (animationIndex + 1) % 4;
}
2021-06-24 11:32:52 +00:00
}
Blinky::Blinky(const Board & board)
: Ghost(Atlas::Ghost::blinky, board.initialBlinkyPosition(), board.blinkyScatterTarget()) {
}
Speedy::Speedy(const Board & board)
: Ghost(Atlas::Ghost::speedy, board.initialSpeedyPosition(), board.speedyScatterTarget()) {
}
Inky::Inky(const Board & board)
: Ghost(Atlas::Ghost::inky, board.initialInkyPosition(), board.inkyScatterTarget()) {
}
Clyde::Clyde(const Board & board)
: Ghost(Atlas::Ghost::clyde, board.initialClydePosition(), board.clydeScatterTarget()) {
}
2021-07-05 12:10:01 +00:00
} // namespace pacman