Merge pull request #26 from mod-cpp/owaage_test_refactor
Testing and Code refactor.
This commit is contained in:
commit
434875e344
25 changed files with 362 additions and 204 deletions
|
@ -54,7 +54,7 @@ constexpr std::array<std::array<int, COLUMNS>, ROWS> board = {{
|
|||
// clang-format on
|
||||
|
||||
static Cell cellAtPosition(GridPosition point) {
|
||||
if (point.x >= COLUMNS || point.y >= ROWS)
|
||||
if (point.x < 0 || point.x >= COLUMNS || point.y < 0 || point.y >= ROWS)
|
||||
return Cell::wall;
|
||||
return Cell(board[point.y][point.x]);
|
||||
}
|
||||
|
@ -64,10 +64,10 @@ bool isWalkableForPacMan(GridPosition point) {
|
|||
}
|
||||
|
||||
bool isWalkableForGhost(GridPosition point, GridPosition origin, bool isEyes) {
|
||||
Cell cell = cellAtPosition(point);
|
||||
const Cell cell = cellAtPosition(point);
|
||||
if (cell == Cell::wall)
|
||||
return false;
|
||||
return isEyes || (isInPen(origin) || !isInPen(point));
|
||||
return isEyes || isInPen(origin) || !isInPen(point);
|
||||
}
|
||||
|
||||
bool isInPen(GridPosition point) {
|
||||
|
|
|
@ -5,7 +5,7 @@ if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
|||
find_package(OpenGL REQUIRED COMPONENTS OpenGL GLX)
|
||||
endif ()
|
||||
|
||||
file(GLOB_RECURSE sources CONFIGURE_DEPENDS "*.cpp")
|
||||
file(GLOB_RECURSE sources CONFIGURE_DEPENDS "*.cpp" "*.hpp")
|
||||
|
||||
add_library(libpacman ${sources})
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ Canvas::Canvas()
|
|||
game_font = loadFont("retro_font.ttf");
|
||||
}
|
||||
|
||||
void Canvas::update(const GameState & gameState, const Score & score) {
|
||||
void Canvas::update(const GameState & gameState) {
|
||||
clear();
|
||||
|
||||
renderMaze();
|
||||
|
@ -41,8 +41,8 @@ void Canvas::update(const GameState & gameState, const Score & score) {
|
|||
renderGhost(gameState.inky);
|
||||
renderGhost(gameState.clyde);
|
||||
|
||||
renderScore(score.points);
|
||||
renderLives(score.lives);
|
||||
renderScore(gameState.score.points);
|
||||
renderLives(gameState.score.lives);
|
||||
|
||||
renderPacMan(gameState.pacMan);
|
||||
|
||||
|
|
112
lib/Game.cpp
112
lib/Game.cpp
|
@ -5,125 +5,33 @@
|
|||
|
||||
namespace pacman {
|
||||
|
||||
constexpr int DEFAULT_LIVES = 3;
|
||||
constexpr int NORMAL_PELLET_POINTS = 10;
|
||||
constexpr int POWER_PELLET_POINTS = 50;
|
||||
constexpr int GHOST_POINTS = 200;
|
||||
|
||||
Game::Game() {
|
||||
score.lives = DEFAULT_LIVES;
|
||||
}
|
||||
|
||||
void Game::run() {
|
||||
|
||||
const std::chrono::milliseconds delta_time(1000 / 60);
|
||||
std::chrono::milliseconds accumulator(0);
|
||||
auto current_time = std::chrono::system_clock::now();
|
||||
|
||||
InputState inputState;
|
||||
|
||||
while (true) {
|
||||
auto newTime = std::chrono::system_clock::now();
|
||||
auto frameTime = std::chrono::duration_cast<std::chrono::milliseconds>(newTime - current_time);
|
||||
const auto newTime = std::chrono::system_clock::now();
|
||||
const auto frameTime = std::chrono::duration_cast<std::chrono::milliseconds>(newTime - current_time);
|
||||
|
||||
current_time = newTime;
|
||||
accumulator += frameTime;
|
||||
processEvents(inputState);
|
||||
if (inputState.close)
|
||||
|
||||
processEvents(gameState.inputState);
|
||||
if (gameState.inputState.close)
|
||||
return;
|
||||
|
||||
while (accumulator >= delta_time) {
|
||||
step(delta_time, inputState);
|
||||
gameState.step(delta_time);
|
||||
accumulator -= delta_time;
|
||||
}
|
||||
canvas.update(gameState, score);
|
||||
}
|
||||
}
|
||||
|
||||
void Game::killPacMan() {
|
||||
gameState.pacMan.die();
|
||||
score.lives--;
|
||||
timeSinceDeath = std::chrono::milliseconds(1);
|
||||
}
|
||||
|
||||
bool Game::pacManDying() const {
|
||||
return timeSinceDeath.count() != 0;
|
||||
}
|
||||
|
||||
void Game::handleDeathAnimation(std::chrono::milliseconds delta) {
|
||||
timeSinceDeath += delta;
|
||||
|
||||
if (timeSinceDeath.count() > 1000) {
|
||||
gameState.blinky.reset();
|
||||
gameState.pinky.reset();
|
||||
gameState.inky.reset();
|
||||
gameState.clyde.reset();
|
||||
gameState.pacMan.reset();
|
||||
timeSinceDeath = std::chrono::milliseconds(0);
|
||||
}
|
||||
}
|
||||
|
||||
void Game::step(std::chrono::milliseconds delta, InputState inputState) {
|
||||
|
||||
gameState.pacMan.update(delta, inputState.direction());
|
||||
|
||||
if (pacManDying()) {
|
||||
handleDeathAnimation(delta);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!gameState.pacMan.hasDirection())
|
||||
return;
|
||||
|
||||
gameState.blinky.update(delta, gameState);
|
||||
gameState.pinky.update(delta, gameState);
|
||||
gameState.inky.update(delta, gameState);
|
||||
gameState.clyde.update(delta, gameState);
|
||||
|
||||
checkCollision(gameState.blinky);
|
||||
checkCollision(gameState.pinky);
|
||||
checkCollision(gameState.inky);
|
||||
checkCollision(gameState.clyde);
|
||||
|
||||
|
||||
eatPellets();
|
||||
}
|
||||
|
||||
void Game::checkCollision(Ghost & ghost) {
|
||||
if (pacManDying() || ghost.isEyes())
|
||||
return;
|
||||
|
||||
if (ghost.positionInGrid() != gameState.pacMan.positionInGrid())
|
||||
return;
|
||||
|
||||
if (ghost.isFrightened()) {
|
||||
ghost.die();
|
||||
score.points += GHOST_POINTS;
|
||||
} else {
|
||||
killPacMan();
|
||||
}
|
||||
}
|
||||
|
||||
void Game::eatPellets() {
|
||||
const auto pos = gameState.pacMan.positionInGrid();
|
||||
if (gameState.pellets.eatPelletAtPosition(pos)) {
|
||||
score.eatenPellets++;
|
||||
score.points += NORMAL_PELLET_POINTS;
|
||||
}
|
||||
|
||||
if (gameState.superPellets.eatPelletAtPosition(pos)) {
|
||||
score.eatenPellets++;
|
||||
score.points += POWER_PELLET_POINTS;
|
||||
|
||||
gameState.blinky.frighten();
|
||||
gameState.pinky.frighten();
|
||||
gameState.inky.frighten();
|
||||
gameState.clyde.frighten();
|
||||
|
||||
canvas.update(gameState);
|
||||
}
|
||||
}
|
||||
|
||||
void Game::processEvents(InputState & inputState) {
|
||||
|
||||
auto event = canvas.pollEvent();
|
||||
const auto event = canvas.pollEvent();
|
||||
if (event && event.value().type == sf::Event::Closed) {
|
||||
inputState.close = true;
|
||||
return;
|
||||
|
|
89
lib/GameState.cpp
Normal file
89
lib/GameState.cpp
Normal file
|
@ -0,0 +1,89 @@
|
|||
#include "GameState.hpp"
|
||||
|
||||
namespace pacman {
|
||||
|
||||
constexpr int GHOST_POINTS = 200;
|
||||
constexpr int NORMAL_PELLET_POINTS = 10;
|
||||
constexpr int POWER_PELLET_POINTS = 50;
|
||||
|
||||
void GameState::step(std::chrono::milliseconds delta) {
|
||||
pacMan.update(delta, inputState.direction());
|
||||
|
||||
if (isPacManDying()) {
|
||||
handleDeathAnimation(delta);
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
checkCollision(blinky);
|
||||
checkCollision(pinky);
|
||||
checkCollision(inky);
|
||||
checkCollision(clyde);
|
||||
|
||||
eatPellets();
|
||||
}
|
||||
|
||||
void GameState::checkCollision(Ghost & ghost) {
|
||||
if (isPacManDying() || ghost.isEyes())
|
||||
return;
|
||||
|
||||
if (ghost.positionInGrid() != pacMan.positionInGrid())
|
||||
return;
|
||||
|
||||
if (ghost.isFrightened()) {
|
||||
ghost.die();
|
||||
score.points += GHOST_POINTS;
|
||||
} else {
|
||||
killPacMan();
|
||||
}
|
||||
}
|
||||
|
||||
void GameState::handleDeathAnimation(std::chrono::milliseconds delta) {
|
||||
timeSinceDeath += delta;
|
||||
|
||||
if (timeSinceDeath.count() > 1000) {
|
||||
blinky.reset();
|
||||
pinky.reset();
|
||||
inky.reset();
|
||||
clyde.reset();
|
||||
pacMan.reset();
|
||||
timeSinceDeath = std::chrono::milliseconds(0);
|
||||
}
|
||||
}
|
||||
|
||||
void GameState::eatPellets() {
|
||||
const auto pos = pacMan.positionInGrid();
|
||||
if (pellets.eatPelletAtPosition(pos)) {
|
||||
score.eatenPellets++;
|
||||
score.points += NORMAL_PELLET_POINTS;
|
||||
}
|
||||
|
||||
if (superPellets.eatPelletAtPosition(pos)) {
|
||||
score.eatenPellets++;
|
||||
score.points += POWER_PELLET_POINTS;
|
||||
|
||||
blinky.frighten();
|
||||
pinky.frighten();
|
||||
inky.frighten();
|
||||
clyde.frighten();
|
||||
}
|
||||
}
|
||||
|
||||
void GameState::killPacMan() {
|
||||
pacMan.die();
|
||||
score.lives--;
|
||||
timeSinceDeath = std::chrono::milliseconds(1);
|
||||
}
|
||||
|
||||
bool GameState::isPacManDying() const {
|
||||
return timeSinceDeath.count() != 0;
|
||||
}
|
||||
|
||||
}
|
|
@ -75,7 +75,7 @@ void Ghost::update(std::chrono::milliseconds time_delta, const GameState & gameS
|
|||
|
||||
if (state == State::Scatter || state == State::Chase) {
|
||||
timeChase += time_delta;
|
||||
auto newState = defaultStateAtDuration(std::chrono::duration_cast<std::chrono::seconds>(timeChase));
|
||||
const auto newState = defaultStateAtDuration(std::chrono::duration_cast<std::chrono::seconds>(timeChase));
|
||||
if (newState != state) {
|
||||
direction = oppositeDirection(direction);
|
||||
state = newState;
|
||||
|
@ -95,6 +95,9 @@ void Ghost::updatePosition(std::chrono::milliseconds time_delta, const GameState
|
|||
|
||||
double position_delta = (0.004 * time_delta.count()) * speed(gameState);
|
||||
|
||||
const auto old_position = pos;
|
||||
const GridPosition old_grid_position = positionToGridPosition(old_position);
|
||||
|
||||
switch (direction) {
|
||||
case Direction::NONE:
|
||||
break;
|
||||
|
@ -119,6 +122,10 @@ void Ghost::updatePosition(std::chrono::milliseconds time_delta, const GameState
|
|||
if (isPortal(positionInGrid(), direction)) {
|
||||
pos = gridPositionToPosition(teleport(positionInGrid()));
|
||||
}
|
||||
else if (!isWalkableForGhost(positionInGrid(), old_grid_position, isEyes())) {
|
||||
pos = old_position;
|
||||
direction = oppositeDirection(direction);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -160,7 +167,6 @@ void Ghost::updateDirection(const GameState & gameState) {
|
|||
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));
|
||||
|
||||
|
@ -172,7 +178,7 @@ void Ghost::updateDirection(const GameState & gameState) {
|
|||
if (opposite_direction)
|
||||
continue;
|
||||
|
||||
const GridPosition grid_position = { size_t(move.position.x), size_t(move.position.y) };
|
||||
const GridPosition grid_position = { int64_t(move.position.x), int64_t(move.position.y) };
|
||||
const bool can_walk = isWalkableForGhost(grid_position, current_grid_position, isEyes());
|
||||
if (!can_walk)
|
||||
continue;
|
||||
|
@ -184,7 +190,7 @@ void Ghost::updateDirection(const GameState & gameState) {
|
|||
return a.distance_to_target < b.distance_to_target;
|
||||
});
|
||||
|
||||
auto move = *optimal_move;
|
||||
const auto & move = *optimal_move;
|
||||
direction = move.direction;
|
||||
last_grid_position = current_grid_position;
|
||||
}
|
||||
|
@ -204,13 +210,17 @@ void Ghost::updateAnimation(std::chrono::milliseconds time_delta) {
|
|||
Ghost::State Ghost::defaultStateAtDuration(std::chrono::seconds seconds) {
|
||||
// This array denotes the duration of each state, alternating between scatter and chase
|
||||
std::array changes = { /*scatter*/ 7, 20, 7, 20, 5, 20, 5 };
|
||||
|
||||
// To know the current state we first compute the cumulative time using std::partial_sum
|
||||
// This gives us {7, 27, 34, 54, 59, 79, 84}
|
||||
std::partial_sum(std::begin(changes), std::end(changes), std::begin(changes));
|
||||
|
||||
// Then we look for the first value in the array greater than the time spent in chase/scatter states
|
||||
auto it = std::upper_bound(std::begin(changes), std::end(changes), seconds.count());
|
||||
|
||||
// We get the position of that iterator in the array
|
||||
auto count = std::distance(std::begin(changes), it);
|
||||
|
||||
// Because the first positition is scatter, all the even positions will be scatter
|
||||
// all the odd positions will be chase
|
||||
return count % 2 == 0 ? State::Scatter : State::Chase;
|
||||
|
|
|
@ -53,8 +53,8 @@ Position Inky::target(const GameState & gameState) const {
|
|||
|
||||
// And selects a point on the line crossing blinky and this position that is at twice that distance
|
||||
// away from blinky
|
||||
targetPosition.x += size_t((targetPosition.x - blinkyPosition.x) / distanceBetweenBlinkyAndTarget) * 2;
|
||||
targetPosition.y += size_t((targetPosition.y - blinkyPosition.y) / distanceBetweenBlinkyAndTarget) * 2;
|
||||
targetPosition.x += int64_t((targetPosition.x - blinkyPosition.x) / distanceBetweenBlinkyAndTarget) * 2;
|
||||
targetPosition.y += int64_t((targetPosition.y - blinkyPosition.y) / distanceBetweenBlinkyAndTarget) * 2;
|
||||
|
||||
return gridPositionToPosition(targetPosition);
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ GridPosition PacMan::positionInGrid() const {
|
|||
void PacMan::die() {
|
||||
if (dead)
|
||||
return;
|
||||
|
||||
dead = true;
|
||||
}
|
||||
|
||||
|
@ -33,8 +34,10 @@ void PacMan::update(std::chrono::milliseconds time_delta, Direction input_direct
|
|||
updateAnimationPosition(time_delta, false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (input_direction != Direction::NONE)
|
||||
desired_direction = input_direction;
|
||||
|
||||
const auto old = pos;
|
||||
updateMazePosition(time_delta);
|
||||
const bool paused = pos == old;
|
||||
|
@ -50,7 +53,6 @@ void PacMan::updateAnimationPosition(std::chrono::milliseconds time_delta, bool
|
|||
}
|
||||
|
||||
void PacMan::updateMazePosition(std::chrono::milliseconds time_delta) {
|
||||
|
||||
if (isPortal(positionInGrid(), direction)) {
|
||||
pos = gridPositionToPosition(teleport(positionInGrid()));
|
||||
return;
|
||||
|
@ -62,13 +64,13 @@ void PacMan::updateMazePosition(std::chrono::milliseconds time_delta) {
|
|||
auto moveToPosition = [position_delta](Position point, Direction move_direction) {
|
||||
switch (move_direction) {
|
||||
case Direction::LEFT:
|
||||
return GridPosition{ std::size_t(point.x - position_delta), std::size_t(point.y) };
|
||||
return GridPosition{ int64_t(point.x - position_delta), int64_t(point.y) };
|
||||
case Direction::RIGHT:
|
||||
return GridPosition{ std::size_t(point.x + pacman_size), std::size_t(point.y) };
|
||||
return GridPosition{ int64_t(point.x + pacman_size), int64_t(point.y) };
|
||||
case Direction::UP:
|
||||
return GridPosition{ std::size_t(point.x), std::size_t(point.y - position_delta) };
|
||||
return GridPosition{ int64_t(point.x), int64_t(point.y - position_delta) };
|
||||
case Direction::DOWN:
|
||||
return GridPosition{ std::size_t(point.x), std::size_t(point.y + pacman_size) };
|
||||
return GridPosition{ int64_t(point.x), int64_t(point.y + pacman_size) };
|
||||
case Direction::NONE:
|
||||
default:
|
||||
return positionToGridPosition(point);
|
||||
|
@ -79,14 +81,15 @@ void PacMan::updateMazePosition(std::chrono::milliseconds time_delta) {
|
|||
return isWalkableForPacMan(moveToPosition(pos, move_direction));
|
||||
};
|
||||
|
||||
if (canGo(desired_direction)) {
|
||||
if (desired_direction != direction && canGo(desired_direction)) {
|
||||
direction = desired_direction;
|
||||
}
|
||||
|
||||
if (canGo(direction)) {
|
||||
if (!canGo(direction)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (direction) {
|
||||
case Direction::NONE:
|
||||
break;
|
||||
case Direction::LEFT:
|
||||
pos.x -= position_delta;
|
||||
pos.y = std::floor(pos.y);
|
||||
|
@ -103,7 +106,9 @@ void PacMan::updateMazePosition(std::chrono::milliseconds time_delta) {
|
|||
pos.x = std::floor(pos.x);
|
||||
pos.y += position_delta;
|
||||
break;
|
||||
}
|
||||
case Direction::NONE:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -32,15 +32,13 @@ void PacManAnimation::updateAnimationPosition(std::chrono::milliseconds time_del
|
|||
return;
|
||||
|
||||
animation_position_delta += (0.02) * double(time_delta.count());
|
||||
|
||||
animation_position = int(animation_position + animation_position_delta);
|
||||
animation_position = int64_t(animation_position + animation_position_delta);
|
||||
|
||||
if (!dead)
|
||||
animation_position = animation_position % 4;
|
||||
|
||||
if(animation_position_delta > 1)
|
||||
animation_position_delta = animation_position_delta - 1;
|
||||
|
||||
}
|
||||
|
||||
void PacManAnimation::pause() {
|
||||
|
|
|
@ -47,8 +47,8 @@ constexpr GridPosition eyeSprite(Direction direction) {
|
|||
|
||||
constexpr GridPosition ghostSprite(Ghost ghost, Direction direction, bool alternative) {
|
||||
assert(ghost >= Ghost::blinky && ghost <= Ghost::clyde && "Invalid Ghost");
|
||||
auto y = static_cast<size_t>(ghost);
|
||||
size_t x = 0;
|
||||
auto y = static_cast<int64_t>(ghost);
|
||||
int64_t x = 0;
|
||||
switch (direction) {
|
||||
case Direction::RIGHT:
|
||||
x = 0;
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
#include "Direction.hpp"
|
||||
#include "Position.hpp"
|
||||
#include <array>
|
||||
|
@ -20,8 +18,7 @@ std::vector<GridPosition> initialPelletPositions();
|
|||
|
||||
std::vector<GridPosition> initialSuperPelletPositions();
|
||||
|
||||
inline Position penDoorPosition() {
|
||||
return { 13, 11 }; }
|
||||
inline Position penDoorPosition() { return { 13, 11 }; }
|
||||
inline Position initialPacManPosition() { return { 13.5, 23 }; }
|
||||
|
||||
} // namespace pacman
|
||||
|
|
|
@ -1,16 +1,20 @@
|
|||
#pragma once
|
||||
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
#include "GameState.hpp"
|
||||
#include "Position.hpp"
|
||||
#include "Score.hpp"
|
||||
#include <optional>
|
||||
|
||||
namespace pacman {
|
||||
|
||||
using Rect = sf::Rect<int>;
|
||||
using Sprite = sf::Sprite;
|
||||
|
||||
class Canvas {
|
||||
public:
|
||||
Canvas();
|
||||
void update(const GameState & gameState, const Score & score);
|
||||
void update(const GameState & gameState);
|
||||
std::optional<sf::Event> pollEvent();
|
||||
|
||||
private:
|
||||
|
|
|
@ -1,29 +1,20 @@
|
|||
#pragma once
|
||||
|
||||
#include "Canvas.hpp"
|
||||
#include "GameState.hpp"
|
||||
#include "InputState.hpp"
|
||||
#include "Canvas.hpp"
|
||||
|
||||
namespace pacman {
|
||||
|
||||
class Game {
|
||||
public:
|
||||
Game();
|
||||
void run();
|
||||
|
||||
private:
|
||||
Canvas canvas;
|
||||
GameState gameState;
|
||||
Score score;
|
||||
std::chrono::milliseconds timeSinceDeath{};
|
||||
|
||||
void step(std::chrono::milliseconds delta, InputState inputState);
|
||||
void eatPellets();
|
||||
void processEvents(InputState & inputState);
|
||||
void checkCollision(Ghost & ghost);
|
||||
void killPacMan();
|
||||
bool pacManDying() const;
|
||||
void handleDeathAnimation(std::chrono::milliseconds delta);
|
||||
};
|
||||
|
||||
} // namespace pacman
|
||||
|
|
|
@ -9,17 +9,31 @@
|
|||
#include "Pinky.hpp"
|
||||
#include "Score.hpp"
|
||||
#include "SuperPellets.hpp"
|
||||
#include "InputState.hpp"
|
||||
|
||||
namespace pacman {
|
||||
|
||||
struct GameState {
|
||||
void step(std::chrono::milliseconds delta);
|
||||
|
||||
Blinky blinky;
|
||||
Pinky pinky;
|
||||
Inky inky;
|
||||
Clyde clyde;
|
||||
|
||||
PacMan pacMan;
|
||||
InputState inputState;
|
||||
Pellets pellets;
|
||||
SuperPellets superPellets;
|
||||
|
||||
Score score;
|
||||
std::chrono::milliseconds timeSinceDeath{};
|
||||
|
||||
void checkCollision(Ghost & ghost);
|
||||
void handleDeathAnimation(std::chrono::milliseconds delta);
|
||||
void eatPellets();
|
||||
void killPacMan();
|
||||
bool isPacManDying() const;
|
||||
};
|
||||
|
||||
} // namespace pacman
|
||||
|
|
|
@ -23,9 +23,7 @@ public:
|
|||
virtual ~Ghost() = default;
|
||||
|
||||
GridPosition currentSprite() const;
|
||||
|
||||
Position position() const;
|
||||
|
||||
GridPosition positionInGrid() const;
|
||||
|
||||
void update(std::chrono::milliseconds time_delta, const GameState & gameState);
|
||||
|
@ -41,12 +39,6 @@ private:
|
|||
void updateDirection(const GameState & gameState);
|
||||
|
||||
protected:
|
||||
State defaultStateAtDuration(std::chrono::seconds seconds);
|
||||
|
||||
virtual double speed(const GameState & gameState) const = 0;
|
||||
virtual Position target(const GameState & gameState) const = 0;
|
||||
virtual Position initialPosition() const = 0;
|
||||
|
||||
Atlas::Ghost spriteSet;
|
||||
Direction direction = Direction::NONE;
|
||||
double timeForAnimation = 0;
|
||||
|
@ -56,6 +48,13 @@ protected:
|
|||
std::chrono::milliseconds timeChase = {};
|
||||
Position pos;
|
||||
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 Position initialPosition() const = 0;
|
||||
|
||||
bool isInPen() const;
|
||||
};
|
||||
|
||||
|
|
|
@ -8,15 +8,10 @@
|
|||
|
||||
namespace pacman {
|
||||
|
||||
class Board;
|
||||
class InputState;
|
||||
|
||||
class PacMan {
|
||||
public:
|
||||
GridPosition currentSprite() const;
|
||||
|
||||
Position position() const;
|
||||
|
||||
GridPosition positionInGrid() const;
|
||||
|
||||
void update(std::chrono::milliseconds time_delta, Direction input_direction);
|
||||
|
|
|
@ -18,7 +18,7 @@ public:
|
|||
void pause();
|
||||
|
||||
private:
|
||||
size_t animation_position = 0;
|
||||
int64_t animation_position = 0;
|
||||
double animation_position_delta = 0.0;
|
||||
};
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
#include <SFML/Graphics.hpp>
|
||||
#include <cmath>
|
||||
|
||||
namespace pacman {
|
||||
|
@ -11,17 +10,13 @@ struct Position {
|
|||
};
|
||||
|
||||
struct GridPosition {
|
||||
size_t x;
|
||||
size_t y;
|
||||
constexpr GridPosition(size_t x, size_t y) : x(x), y(y) {}
|
||||
int64_t x;
|
||||
int64_t y;
|
||||
constexpr GridPosition(int64_t x, int64_t y) : x(x), y(y) {}
|
||||
};
|
||||
|
||||
using Rect = sf::Rect<int>;
|
||||
|
||||
using Sprite = sf::Sprite;
|
||||
|
||||
inline GridPosition positionToGridPosition(Position pos) {
|
||||
return { size_t(std::round(pos.x)), size_t(std::round(pos.y)) };
|
||||
return { int64_t(std::round(pos.x)), int64_t(std::round(pos.y)) };
|
||||
}
|
||||
|
||||
inline Position gridPositionToPosition(GridPosition pos) {
|
||||
|
@ -36,14 +31,14 @@ constexpr bool operator!=(const GridPosition & a, const GridPosition & b) {
|
|||
return !(a == b);
|
||||
}
|
||||
|
||||
constexpr bool operator==(const Position & a, const Position & b) {
|
||||
return a.x == b.x && a.y == b.y;
|
||||
inline bool operator==(const Position & a, const Position & b) {
|
||||
// This is ok as a test unless x and y become very large.
|
||||
constexpr double epsilon = std::numeric_limits<double>::epsilon();
|
||||
return std::abs(a.x - b.x) <= epsilon && std::abs(a.y - b.y) <= epsilon;
|
||||
}
|
||||
|
||||
constexpr bool operator!=(const Position & a, const Position & b) {
|
||||
inline bool operator!=(const Position & a, const Position & b) {
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace pacman
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
#pragma once
|
||||
namespace pacman {
|
||||
|
||||
constexpr int DEFAULT_LIVES = 3;
|
||||
|
||||
struct Score {
|
||||
int lives = 0;
|
||||
int lives = DEFAULT_LIVES;
|
||||
int points = 0;
|
||||
int eatenPellets = 0;
|
||||
};
|
||||
|
|
|
@ -3,8 +3,9 @@ find_package(GTest REQUIRED)
|
|||
|
||||
include(GoogleTest)
|
||||
|
||||
add_executable(pacman_tests tests.cpp)
|
||||
file(GLOB_RECURSE sources CONFIGURE_DEPENDS "*.cpp")
|
||||
|
||||
add_executable(pacman_tests ${sources})
|
||||
target_link_libraries(pacman_tests GTest::GTest libpacman)
|
||||
|
||||
gtest_discover_tests(pacman_tests TEST_PREFIX pacman:)
|
||||
add_test(NAME monolithic COMMAND pacman_tests)
|
||||
gtest_discover_tests(pacman_tests)
|
26
test/testGameState.cpp
Normal file
26
test/testGameState.cpp
Normal file
|
@ -0,0 +1,26 @@
|
|||
#include "GameState.hpp"
|
||||
#include <gtest/gtest.h>
|
||||
#include <fmt/format.h>
|
||||
|
||||
TEST(GameStateTest, Fuzz) {
|
||||
pacman::GameState gameState;
|
||||
//fmt::print("{}\n", gameState.pellets.currentPositions().size());
|
||||
|
||||
int pacManDeathCount = 0;
|
||||
bool canCountDeath = false;
|
||||
for (std::size_t i = 0; i < 50000; ++i) {
|
||||
gameState.inputState.up = i % 7 ? true : false;
|
||||
gameState.inputState.down = i % 11 ? true : false;
|
||||
gameState.inputState.left = i % 13 ? true : false;
|
||||
gameState.inputState.right = i % 17 ? true : false;
|
||||
|
||||
canCountDeath = !gameState.isPacManDying();
|
||||
gameState.step(std::chrono::milliseconds(1000 / 30));
|
||||
if (canCountDeath && gameState.isPacManDying()) {
|
||||
pacManDeathCount++;
|
||||
}
|
||||
}
|
||||
|
||||
//fmt::print("{}\n", pacManDeathCount);
|
||||
//fmt::print("{}\n", gameState.pellets.currentPositions().size());
|
||||
}
|
77
test/testGhost.cpp
Normal file
77
test/testGhost.cpp
Normal file
|
@ -0,0 +1,77 @@
|
|||
#include "Blinky.hpp"
|
||||
#include "Clyde.hpp"
|
||||
#include "Inky.hpp"
|
||||
#include "Pinky.hpp"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
template<typename T>
|
||||
static void ghostInitHelper(const T& ghost, const double x, const double y) {
|
||||
const pacman::Position pos{ x, y };
|
||||
EXPECT_EQ(ghost.position(), pos);
|
||||
|
||||
const pacman::GridPosition gridPos = pacman::positionToGridPosition(pos);
|
||||
EXPECT_EQ(ghost.positionInGrid(), gridPos);
|
||||
|
||||
EXPECT_FALSE(ghost.isEyes());
|
||||
EXPECT_FALSE(ghost.isFrightened());
|
||||
}
|
||||
|
||||
TEST(GhostTest, Init) {
|
||||
pacman::Blinky blinky;
|
||||
ghostInitHelper(blinky, 13.5, 11);
|
||||
|
||||
pacman::Clyde clyde;
|
||||
ghostInitHelper(clyde, 15.5, 14);
|
||||
|
||||
pacman::Inky inky;
|
||||
ghostInitHelper(inky, 13.5, 14);
|
||||
|
||||
pacman::Pinky pinky;
|
||||
ghostInitHelper(pinky, 11.5, 14);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static void ghostFrightenHelper(T& ghost) {
|
||||
EXPECT_FALSE(ghost.isFrightened());
|
||||
ghost.frighten();
|
||||
EXPECT_TRUE(ghost.isFrightened());
|
||||
ghost.reset();
|
||||
EXPECT_FALSE(ghost.isFrightened());
|
||||
}
|
||||
|
||||
TEST(GhostTest, Frighten) {
|
||||
pacman::Blinky blinky;
|
||||
ghostFrightenHelper(blinky);
|
||||
|
||||
pacman::Clyde clyde;
|
||||
ghostFrightenHelper(clyde);
|
||||
|
||||
pacman::Inky inky;
|
||||
ghostFrightenHelper(inky);
|
||||
|
||||
pacman::Pinky pinky;
|
||||
ghostFrightenHelper(pinky);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static void ghostDeadHelper(T & ghost) {
|
||||
EXPECT_FALSE(ghost.isEyes());
|
||||
ghost.die();
|
||||
EXPECT_TRUE(ghost.isEyes());
|
||||
ghost.reset();
|
||||
EXPECT_FALSE(ghost.isEyes());
|
||||
}
|
||||
|
||||
TEST(GhostTest, Dead) {
|
||||
pacman::Blinky blinky;
|
||||
ghostDeadHelper(blinky);
|
||||
|
||||
pacman::Clyde clyde;
|
||||
ghostDeadHelper(clyde);
|
||||
|
||||
pacman::Inky inky;
|
||||
ghostDeadHelper(inky);
|
||||
|
||||
pacman::Pinky pinky;
|
||||
ghostDeadHelper(pinky);
|
||||
}
|
8
test/testPacMan.cpp
Normal file
8
test/testPacMan.cpp
Normal file
|
@ -0,0 +1,8 @@
|
|||
#include "PacMan.hpp"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
TEST(PacManTest, InitialPosition) {
|
||||
pacman::PacMan pacMan;
|
||||
EXPECT_EQ(pacMan.position().x, 13.5);
|
||||
EXPECT_EQ(pacMan.position().y, 23);
|
||||
}
|
46
test/testPosition.cpp
Normal file
46
test/testPosition.cpp
Normal file
|
@ -0,0 +1,46 @@
|
|||
#include "Position.hpp"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
TEST(PositionTest, PositionInit) {
|
||||
pacman::Position pos;
|
||||
EXPECT_DOUBLE_EQ(pos.x, 0.0);
|
||||
EXPECT_DOUBLE_EQ(pos.y, 0.0);
|
||||
|
||||
pacman::Position pos2{ 10.0, 20.0 };
|
||||
EXPECT_DOUBLE_EQ(pos2.x, 10.0);
|
||||
EXPECT_DOUBLE_EQ(pos2.y, 20.0);
|
||||
}
|
||||
|
||||
TEST(PositionTest, GridPositionInit) {
|
||||
pacman::GridPosition gridPos{ 10, 20 };
|
||||
EXPECT_EQ(gridPos.x, 10);
|
||||
EXPECT_EQ(gridPos.y, 20);
|
||||
}
|
||||
|
||||
TEST(PositionTest, ConvertPositionToGridPosition) {
|
||||
pacman::Position pos{ 10.0, 20.0 };
|
||||
const auto gridPos = pacman::positionToGridPosition(pos);
|
||||
EXPECT_EQ(gridPos.x, 10);
|
||||
EXPECT_EQ(gridPos.y, 20);
|
||||
}
|
||||
|
||||
TEST(PositionTest, ConvertGridPositionToPosition) {
|
||||
pacman::GridPosition gridPos{ 10, 20 };
|
||||
const auto pos = pacman::gridPositionToPosition(gridPos);
|
||||
EXPECT_DOUBLE_EQ(pos.x, 10.0);
|
||||
EXPECT_DOUBLE_EQ(pos.y, 20.0);
|
||||
}
|
||||
|
||||
TEST(PositionTest, PositionEquality) {
|
||||
pacman::Position pos1{ 10.0, 20.0 };
|
||||
pacman::Position pos2{ 10.0, 20.0 };
|
||||
EXPECT_TRUE(pos1 == pos2);
|
||||
|
||||
pacman::Position pos3{ 9.9, 19.9 };
|
||||
EXPECT_FALSE(pos1 == pos3);
|
||||
|
||||
pos3.x += 0.1;
|
||||
pos3.y += 0.1;
|
||||
|
||||
EXPECT_TRUE(pos1 == pos3);
|
||||
}
|
|
@ -1,12 +1,5 @@
|
|||
#include "PacMan.hpp"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
TEST(PacManTest, InitialPosition) {
|
||||
pacman::PacMan pacMan;
|
||||
EXPECT_EQ(pacMan.position().x, 13.5);
|
||||
EXPECT_EQ(pacMan.position().y, 23);
|
||||
}
|
||||
|
||||
int main(int argc, char * argv[]) {
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
|
|
Loading…
Reference in a new issue