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:
parent
b9a5169eab
commit
1c05f500b0
14 changed files with 115 additions and 94 deletions
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
39
lib/Inky.cpp
39
lib/Inky.cpp
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue