Refactoring ghost's target and update to no longer require *this. They each have their own target function which sets a target variable within Ghost.

This commit is contained in:
Ólafur Waage 2021-09-23 14:57:53 +02:00
parent b9a5169eab
commit 1c05f500b0
14 changed files with 115 additions and 94 deletions

View File

@ -8,7 +8,7 @@ Blinky::Blinky()
pos = initialPosition();
}
double Blinky::speed(const GameState &) const {
double Blinky::speed() const {
if (state == State::Eyes)
return 2;
if (state == State::Frightened)
@ -16,15 +16,18 @@ double Blinky::speed(const GameState &) const {
return 0.75;
}
Position Blinky::target(const GameState & gameState) const {
if (state == State::Eyes)
return initialPosition();
void Blinky::setTarget(Position pacManPos) {
if (state == State::Eyes) {
target = initialPosition();
return;
}
if (isInPen())
return penDoorPosition();
if (isInPen()) {
target = penDoorPosition();
return;
}
const auto pacManPosition = gridPositionToPosition(gameState.pacMan.positionInGrid());
return state == State::Chase ? pacManPosition : scatterTarget();
target = state == State::Chase ? pacManPos : scatterTarget();
}
Position Blinky::initialPosition() const {
@ -35,4 +38,4 @@ Position Blinky::scatterTarget() const {
return { 25, -3 };
}
}
} // namespace pacman

View File

@ -8,7 +8,7 @@ Clyde::Clyde()
pos = initialPosition();
}
double Clyde::speed(const GameState &) const {
double Clyde::speed() const {
if (state == State::Eyes)
return 2;
if (state == State::Frightened)
@ -16,24 +16,27 @@ double Clyde::speed(const GameState &) const {
return 0.75;
}
Position Clyde::target(const GameState & gameState) const {
if (state == State::Eyes)
return initialPosition();
void Clyde::setTarget(Position pacManPos) {
if (state == State::Eyes) {
target = initialPosition();
return;
}
if (isInPen())
return penDoorPosition();
if (isInPen()) {
target = penDoorPosition();
return;
}
// Clyde always target its scatter target, unless pacman is further than 8 tiles away
auto targetPosition = scatterTarget();
if (state == State::Scatter)
return targetPosition;
target = scatterTarget();
if (state == State::Scatter) {
return;
}
const auto pacManPosition = gameState.pacMan.position();
const auto distanceFomPacMan = std::hypot(pos.x - double(pacManPosition.x), pos.y - double(pacManPosition.y));
if (distanceFomPacMan > 8)
targetPosition = pacManPosition;
return targetPosition;
const auto distanceFomPacMan = std::hypot(pos.x - pacManPos.x, pos.y - pacManPos.y);
if (distanceFomPacMan > 8) {
target = pacManPos;
}
}
Position Clyde::initialPosition() const {

View File

@ -3,14 +3,14 @@
namespace pacman {
void Fruits::update(std::chrono::milliseconds time_delta, const GameState & gameState) {
void Fruits::update(std::chrono::milliseconds time_delta, int eatenPellets) {
if (visible) {
time_visible += time_delta;
}
if (time_visible > std::chrono::seconds(9)) {
hide();
} else if ((index == 0 && gameState.score.eatenPellets >= 70) || (index == 1 && gameState.score.eatenPellets >= 170)) {
} else if ((index == 0 && eatenPellets >= 70) || (index == 1 && eatenPellets >= 170)) {
// We show the fruit twice, once at 70 pellets and once at 170
visible = true;
}

View File

@ -18,11 +18,16 @@ void GameState::step(std::chrono::milliseconds delta) {
if (!pacMan.hasDirection())
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
inky.update(delta, *this);
clyde.update(delta, *this);
fruit.update(delta, *this);
blinky.setTarget(pacMan.position());
blinky.update(delta);
pinky.setTarget(pacMan.positionInGrid(), pacMan.currentDirection());
pinky.update(delta);
inky.setTarget(pacMan.positionInGrid(), pacMan.currentDirection(), blinky.positionInGrid());
inky.update(delta);
clyde.setTarget(pacMan.position());
clyde.update(delta);
fruit.update(delta, score.eatenPellets);
checkCollision(blinky);
checkCollision(pinky);

View File

@ -70,7 +70,7 @@ Direction Ghost::currentDirection() const {
return direction;
}
void Ghost::update(std::chrono::milliseconds time_delta, const GameState & gameState) {
void Ghost::update(std::chrono::milliseconds time_delta) {
if (state == State::Eyes && isInPen())
state = State::Scatter;
@ -90,17 +90,17 @@ void Ghost::update(std::chrono::milliseconds time_delta, const GameState & gameS
}
updateAnimation(time_delta);
updatePosition(time_delta, gameState);
updatePosition(time_delta);
}
bool Ghost::isInPen() const {
return pacman::isInPen(positionInGrid());
}
void Ghost::updatePosition(std::chrono::milliseconds time_delta, const GameState & gameState) {
updateDirection(gameState);
void Ghost::updatePosition(std::chrono::milliseconds time_delta) {
updateDirection();
double position_delta = (0.004 * double(time_delta.count())) * speed(gameState);
double position_delta = (0.004 * double(time_delta.count())) * speed();
const auto old_position = pos;
const GridPosition old_grid_position = positionToGridPosition(old_position);
@ -150,7 +150,7 @@ void Ghost::updatePosition(std::chrono::milliseconds time_delta, const GameState
* In the scatter state, each ghost tries to reach an unreachable position outside of the map.
* This makes ghosts run in circle around the island at each of the 4 map corner.
*/
void Ghost::updateDirection(const GameState & gameState) {
void Ghost::updateDirection() {
const auto current_grid_position = positionInGrid();
if (current_grid_position == last_grid_position)
return;
@ -170,8 +170,6 @@ void Ghost::updateDirection(const GameState & gameState) {
Move{ Direction::RIGHT, { x + 1, y } }
};
const Position target_position = target(gameState);
for (auto & move : possible_moves) {
if (isPortal(current_grid_position, move.direction))
move.position = gridPositionToPosition(teleport(current_grid_position));
@ -189,7 +187,7 @@ void Ghost::updateDirection(const GameState & gameState) {
if (!can_walk)
continue;
move.distance_to_target = std::hypot(move.position.x - target_position.x, move.position.y - target_position.y);
move.distance_to_target = std::hypot(move.position.x - target.x, move.position.y - target.y);
}
const auto optimal_move = std::min_element(possible_moves.begin(), possible_moves.end(), [](const auto & a, const auto & b) {

View File

@ -8,7 +8,7 @@ Inky::Inky()
pos = initialPosition();
}
double Inky::speed(const GameState &) const {
double Inky::speed() const {
if (state == State::Eyes)
return 2;
if (state == State::Frightened)
@ -16,19 +16,25 @@ double Inky::speed(const GameState &) const {
return 0.75;
}
Position Inky::target(const GameState & gameState) const {
if (state == State::Eyes)
return initialPosition();
void Inky::setTarget(GridPosition pacManPos, Direction pacManDir, GridPosition blinkyPos) {
if (state == State::Eyes) {
target = initialPosition();
return;
}
if (isInPen())
return penDoorPosition();
if (isInPen()) {
target = penDoorPosition();
return;
}
if (state == State::Scatter)
return scatterTarget();
if (state == State::Scatter) {
target = scatterTarget();
return;
}
// Inky first selects a position 2 cell away from pacman in his direction.
GridPosition targetPosition = gameState.pacMan.positionInGrid();
switch (gameState.pacMan.currentDirection()) {
GridPosition targetPosition = pacManPos;
switch (pacManDir) {
case Direction::LEFT:
targetPosition.x -= 2;
break;
@ -48,15 +54,14 @@ Position Inky::target(const GameState & gameState) const {
}
// Then it calculates the distance between Blinky and this position
const auto & blinkyPosition = gameState.blinky.positionInGrid();
const double distanceBetweenBlinkyAndTarget = std::hypot(blinkyPosition.x - targetPosition.x, blinkyPosition.y - targetPosition.y);
const double distanceBetweenBlinkyAndTarget = std::hypot(blinkyPos.x - targetPosition.x, blinkyPos.y - targetPosition.y);
// And selects a point on the line crossing blinky and this position that is at twice that distance
// away from blinky
targetPosition.x += std::size_t((double(targetPosition.x) - double(blinkyPosition.x)) / distanceBetweenBlinkyAndTarget) * 2;
targetPosition.y += std::size_t((double(targetPosition.y) - double(blinkyPosition.y)) / distanceBetweenBlinkyAndTarget) * 2;
// And selects a point on the line crossing blinky and
// this position that is at twice that distance away from blinky
targetPosition.x += std::size_t((double(targetPosition.x) - double(blinkyPos.x)) / distanceBetweenBlinkyAndTarget) * 2;
targetPosition.y += std::size_t((double(targetPosition.y) - double(blinkyPos.y)) / distanceBetweenBlinkyAndTarget) * 2;
return gridPositionToPosition(targetPosition);
target = gridPositionToPosition(targetPosition);
}
Position Inky::initialPosition() const {

View File

@ -8,7 +8,7 @@ Pinky::Pinky()
pos = initialPosition();
}
double Pinky::speed(const GameState &) const {
double Pinky::speed() const {
if (state == State::Eyes)
return 2;
if (state == State::Frightened)
@ -16,19 +16,25 @@ double Pinky::speed(const GameState &) const {
return 0.75;
}
Position Pinky::target(const GameState & gameState) const {
if (state == State::Eyes)
return initialPosition();
void Pinky::setTarget(GridPosition pacManPos, Direction pacManDir) {
if (state == State::Eyes) {
target = initialPosition();
return;
}
if (isInPen())
return penDoorPosition();
if (isInPen()) {
target = penDoorPosition();
return;
}
if (state == State::Scatter)
return scatterTarget();
if (state == State::Scatter) {
target = scatterTarget();
return;
}
// Inky first selects a position 2 cell away from pacman in his direction.
GridPosition targetPosition = gameState.pacMan.positionInGrid();
switch (gameState.pacMan.currentDirection()) {
GridPosition targetPosition = pacManPos;
switch (pacManDir) {
case Direction::LEFT:
targetPosition.x -= 4;
break;
@ -46,7 +52,8 @@ Position Pinky::target(const GameState & gameState) const {
assert(false && "Pacman should be moving");
break;
}
return gridPositionToPosition(targetPosition);
target = gridPositionToPosition(targetPosition);
}
Position Pinky::initialPosition() const {
@ -57,4 +64,4 @@ Position Pinky::scatterTarget() const {
return { 3, -2 };
}
}
} // namespace pacman

View File

@ -7,10 +7,10 @@ namespace pacman {
class Blinky final : public Ghost {
public:
Blinky();
void setTarget(Position pacManPos);
protected:
double speed(const GameState & gameState) const override;
Position target(const GameState & gameState) const override;
double speed() const override;
Position initialPosition() const override;
private:

View File

@ -7,10 +7,10 @@ namespace pacman {
class Clyde final : public Ghost {
public:
explicit Clyde();
void setTarget(Position pacManPos);
protected:
double speed(const GameState & gameState) const override;
Position target(const GameState & gameState) const override;
double speed() const override;
Position initialPosition() const override;
private:

View File

@ -8,7 +8,7 @@ struct GameState;
class Fruits {
public:
void update(std::chrono::milliseconds time_delta, const GameState & gameState);
void update(std::chrono::milliseconds time_delta, int eatenPellets);
GridPosition currentSprite() const;
Position position() const;

View File

@ -27,7 +27,7 @@ public:
GridPosition positionInGrid() const;
Direction currentDirection() const;
void update(std::chrono::milliseconds time_delta, const GameState & gameState);
void update(std::chrono::milliseconds time_delta);
void frighten();
void die();
bool isFrightened() const;
@ -36,8 +36,8 @@ public:
private:
void updateAnimation(std::chrono::milliseconds time_delta);
void updatePosition(std::chrono::milliseconds time_delta, const GameState & gameState);
void updateDirection(const GameState & gameState);
void updatePosition(std::chrono::milliseconds time_delta);
void updateDirection();
protected:
Atlas::Ghost spriteSet;
@ -48,12 +48,12 @@ protected:
std::chrono::milliseconds timeFrighten = {};
std::chrono::milliseconds timeChase = {};
Position pos;
Position target;
GridPosition last_grid_position = { 0, 0 };
State defaultStateAtDuration(std::chrono::seconds seconds);
virtual double speed(const GameState & gameState) const = 0;
virtual Position target(const GameState & gameState) const = 0;
virtual double speed() const = 0;
virtual Position initialPosition() const = 0;
bool isInPen() const;

View File

@ -7,10 +7,10 @@ namespace pacman {
class Inky final : public Ghost {
public:
explicit Inky();
void setTarget(GridPosition pacManPos, Direction pacManDir, GridPosition blinkyPos);
protected:
double speed(const GameState & gameState) const override;
Position target(const GameState & gameState) const override;
double speed() const override;
Position initialPosition() const override;
private:

View File

@ -7,10 +7,10 @@ namespace pacman {
class Pinky final : public Ghost {
public:
explicit Pinky();
void setTarget(GridPosition pacManPos, Direction pacManDir);
protected:
double speed(const GameState & gameState) const override;
Position target(const GameState & gameState) const override;
double speed() const override;
Position initialPosition() const override;
private:

View File

@ -17,47 +17,47 @@ TEST_CASE("Fruit Visibility", "[fruits]") {
REQUIRE_FALSE(gameState.fruit.isVisible());
SECTION("9 seconds but no pellets eaten") {
gameState.fruit.update(std::chrono::milliseconds(9001), gameState);
gameState.fruit.update(std::chrono::milliseconds(9001), gameState.score.eatenPellets);
REQUIRE_FALSE(gameState.fruit.isVisible());
}
SECTION("9 seconds and 70 pellets eaten") {
// "Eat 70 pellets", do an update and check the state
gameState.score.eatenPellets = 70;
gameState.fruit.update(std::chrono::milliseconds(1), gameState);
gameState.fruit.update(std::chrono::milliseconds(1), gameState.score.eatenPellets);
REQUIRE(gameState.fruit.isVisible());
// Wait more than 9 seconds and then check the state again
gameState.fruit.update(std::chrono::milliseconds(9001), gameState);
gameState.fruit.update(std::chrono::milliseconds(9001), gameState.score.eatenPellets);
REQUIRE_FALSE(gameState.fruit.isVisible());
}
SECTION("70 and 170 pellets eaten") {
// "Eat 70 pellets", do an update and check the state
gameState.score.eatenPellets = 70;
gameState.fruit.update(std::chrono::milliseconds(1), gameState);
gameState.fruit.update(std::chrono::milliseconds(1), gameState.score.eatenPellets);
REQUIRE(gameState.fruit.isVisible());
// Wait more than 9 seconds and then check the state again
gameState.fruit.update(std::chrono::milliseconds(9001), gameState);
gameState.fruit.update(std::chrono::milliseconds(9001), gameState.score.eatenPellets);
REQUIRE_FALSE(gameState.fruit.isVisible());
// "Eat 170 pellets", do an update and check the state
gameState.score.eatenPellets = 170;
gameState.fruit.update(std::chrono::milliseconds(1), gameState);
gameState.fruit.update(std::chrono::milliseconds(1), gameState.score.eatenPellets);
REQUIRE(gameState.fruit.isVisible());
// Wait more than 9 seconds and then check the state again
gameState.fruit.update(std::chrono::milliseconds(9001), gameState);
gameState.fruit.update(std::chrono::milliseconds(9001), gameState.score.eatenPellets);
REQUIRE_FALSE(gameState.fruit.isVisible());
// We should never get a visible state again, since we only show 2 fruits
gameState.score.eatenPellets = 1000;
gameState.fruit.update(std::chrono::milliseconds(1), gameState);
gameState.fruit.update(std::chrono::milliseconds(1), gameState.score.eatenPellets);
REQUIRE_FALSE(gameState.fruit.isVisible());
// Wait more than 9 seconds and then check the state again
gameState.fruit.update(std::chrono::milliseconds(9001), gameState);
gameState.fruit.update(std::chrono::milliseconds(9001), gameState.score.eatenPellets);
REQUIRE_FALSE(gameState.fruit.isVisible());
}
@ -65,12 +65,12 @@ TEST_CASE("Fruit Visibility", "[fruits]") {
REQUIRE(gameState.fruit.eat() == 0);
gameState.score.eatenPellets = 70;
gameState.fruit.update(std::chrono::milliseconds(1), gameState);
gameState.fruit.update(std::chrono::milliseconds(1), gameState.score.eatenPellets);
REQUIRE(gameState.fruit.isVisible());
REQUIRE(gameState.fruit.eat() == gameState.fruit.value());
// Wait more than 9 seconds and then check the state again
gameState.fruit.update(std::chrono::milliseconds(9001), gameState);
gameState.fruit.update(std::chrono::milliseconds(9001), gameState.score.eatenPellets);
REQUIRE_FALSE(gameState.fruit.isVisible());
REQUIRE(gameState.fruit.eat() == 0);
}