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

View file

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

View file

@ -3,14 +3,14 @@
namespace pacman { 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) { if (visible) {
time_visible += time_delta; time_visible += time_delta;
} }
if (time_visible > std::chrono::seconds(9)) { if (time_visible > std::chrono::seconds(9)) {
hide(); 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 // We show the fruit twice, once at 70 pellets and once at 170
visible = true; visible = true;
} }

View file

@ -18,11 +18,16 @@ void GameState::step(std::chrono::milliseconds delta) {
if (!pacMan.hasDirection()) if (!pacMan.hasDirection())
return; return;
blinky.update(delta, *this); // waage: urgh, I wanna remove this blinky.setTarget(pacMan.position());
pinky.update(delta, *this); // ghosts know what they want, which is usually pacman's location blinky.update(delta);
inky.update(delta, *this); pinky.setTarget(pacMan.positionInGrid(), pacMan.currentDirection());
clyde.update(delta, *this); pinky.update(delta);
fruit.update(delta, *this); 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(blinky);
checkCollision(pinky); checkCollision(pinky);

View file

@ -70,7 +70,7 @@ Direction Ghost::currentDirection() const {
return direction; 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()) if (state == State::Eyes && isInPen())
state = State::Scatter; state = State::Scatter;
@ -90,17 +90,17 @@ void Ghost::update(std::chrono::milliseconds time_delta, const GameState & gameS
} }
updateAnimation(time_delta); updateAnimation(time_delta);
updatePosition(time_delta, gameState); updatePosition(time_delta);
} }
bool Ghost::isInPen() const { bool Ghost::isInPen() const {
return pacman::isInPen(positionInGrid()); return pacman::isInPen(positionInGrid());
} }
void Ghost::updatePosition(std::chrono::milliseconds time_delta, const GameState & gameState) { void Ghost::updatePosition(std::chrono::milliseconds time_delta) {
updateDirection(gameState); 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 auto old_position = pos;
const GridPosition old_grid_position = positionToGridPosition(old_position); 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. * 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. * 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(); const auto current_grid_position = positionInGrid();
if (current_grid_position == last_grid_position) if (current_grid_position == last_grid_position)
return; return;
@ -170,8 +170,6 @@ void Ghost::updateDirection(const GameState & gameState) {
Move{ Direction::RIGHT, { x + 1, y } } Move{ Direction::RIGHT, { x + 1, y } }
}; };
const Position target_position = target(gameState);
for (auto & move : possible_moves) { for (auto & move : possible_moves) {
if (isPortal(current_grid_position, move.direction)) if (isPortal(current_grid_position, move.direction))
move.position = gridPositionToPosition(teleport(current_grid_position)); move.position = gridPositionToPosition(teleport(current_grid_position));
@ -189,7 +187,7 @@ void Ghost::updateDirection(const GameState & gameState) {
if (!can_walk) if (!can_walk)
continue; 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) { 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(); pos = initialPosition();
} }
double Inky::speed(const GameState &) const { double Inky::speed() const {
if (state == State::Eyes) if (state == State::Eyes)
return 2; return 2;
if (state == State::Frightened) if (state == State::Frightened)
@ -16,19 +16,25 @@ double Inky::speed(const GameState &) const {
return 0.75; return 0.75;
} }
Position Inky::target(const GameState & gameState) const { void Inky::setTarget(GridPosition pacManPos, Direction pacManDir, GridPosition blinkyPos) {
if (state == State::Eyes) if (state == State::Eyes) {
return initialPosition(); target = initialPosition();
return;
}
if (isInPen()) if (isInPen()) {
return penDoorPosition(); target = penDoorPosition();
return;
}
if (state == State::Scatter) if (state == State::Scatter) {
return scatterTarget(); target = scatterTarget();
return;
}
// Inky first selects a position 2 cell away from pacman in his direction. // Inky first selects a position 2 cell away from pacman in his direction.
GridPosition targetPosition = gameState.pacMan.positionInGrid(); GridPosition targetPosition = pacManPos;
switch (gameState.pacMan.currentDirection()) { switch (pacManDir) {
case Direction::LEFT: case Direction::LEFT:
targetPosition.x -= 2; targetPosition.x -= 2;
break; break;
@ -48,15 +54,14 @@ Position Inky::target(const GameState & gameState) const {
} }
// Then it calculates the distance between Blinky and this position // Then it calculates the distance between Blinky and this position
const auto & blinkyPosition = gameState.blinky.positionInGrid(); const double distanceBetweenBlinkyAndTarget = std::hypot(blinkyPos.x - targetPosition.x, blinkyPos.y - targetPosition.y);
const double distanceBetweenBlinkyAndTarget = std::hypot(blinkyPosition.x - targetPosition.x, blinkyPosition.y - targetPosition.y);
// And selects a point on the line crossing blinky and this position that is at twice that distance // And selects a point on the line crossing blinky and
// away from blinky // 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.x += std::size_t((double(targetPosition.x) - double(blinkyPos.x)) / distanceBetweenBlinkyAndTarget) * 2;
targetPosition.y += std::size_t((double(targetPosition.y) - double(blinkyPosition.y)) / 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 { Position Inky::initialPosition() const {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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