Refactoring Game and GameState. Moving game state update logic into that class. Game is now only event loop.

This commit is contained in:
Ólafur Waage 2021-09-07 16:00:19 +02:00
parent ee4b216056
commit 55fbb53591
8 changed files with 119 additions and 114 deletions

View file

@ -29,7 +29,7 @@ Canvas::Canvas()
game_font = loadFont("retro_font.ttf"); game_font = loadFont("retro_font.ttf");
} }
void Canvas::update(const GameState & gameState, const Score & score) { void Canvas::update(const GameState & gameState) {
clear(); clear();
renderMaze(); renderMaze();
@ -41,8 +41,8 @@ void Canvas::update(const GameState & gameState, const Score & score) {
renderGhost(gameState.inky); renderGhost(gameState.inky);
renderGhost(gameState.clyde); renderGhost(gameState.clyde);
renderScore(score.points); renderScore(gameState.score.points);
renderLives(score.lives); renderLives(gameState.score.lives);
renderPacMan(gameState.pacMan); renderPacMan(gameState.pacMan);

View file

@ -5,124 +5,32 @@
namespace pacman { 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() { void Game::run() {
const std::chrono::milliseconds delta_time(1000 / 60); const std::chrono::milliseconds delta_time(1000 / 60);
std::chrono::milliseconds accumulator(0); std::chrono::milliseconds accumulator(0);
auto current_time = std::chrono::system_clock::now(); auto current_time = std::chrono::system_clock::now();
InputState inputState;
while (true) { while (true) {
auto newTime = std::chrono::system_clock::now(); auto newTime = std::chrono::system_clock::now();
auto frameTime = std::chrono::duration_cast<std::chrono::milliseconds>(newTime - current_time); auto frameTime = std::chrono::duration_cast<std::chrono::milliseconds>(newTime - current_time);
current_time = newTime; current_time = newTime;
accumulator += frameTime; accumulator += frameTime;
processEvents(inputState);
if (inputState.close) processEvents(gameState.inputState);
if (gameState.inputState.close)
return; return;
while (accumulator >= delta_time) { while (accumulator >= delta_time) {
step(delta_time, inputState); gameState.step(delta_time);
accumulator -= 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) { void Game::processEvents(InputState & inputState) {
auto event = canvas.pollEvent(); auto event = canvas.pollEvent();
if (event && event.value().type == sf::Event::Closed) { if (event && event.value().type == sf::Event::Closed) {
inputState.close = true; inputState.close = true;

89
lib/GameState.cpp Normal file
View 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;
}
}

View file

@ -4,7 +4,6 @@
#include "GameState.hpp" #include "GameState.hpp"
#include "Position.hpp" #include "Position.hpp"
#include "Score.hpp"
#include <optional> #include <optional>
namespace pacman { namespace pacman {
@ -15,7 +14,7 @@ using Sprite = sf::Sprite;
class Canvas { class Canvas {
public: public:
Canvas(); Canvas();
void update(const GameState & gameState, const Score & score); void update(const GameState & gameState);
std::optional<sf::Event> pollEvent(); std::optional<sf::Event> pollEvent();
private: private:

View file

@ -8,22 +8,13 @@ namespace pacman {
class Game { class Game {
public: public:
Game();
void run(); void run();
private: private:
Canvas canvas; Canvas canvas;
GameState gameState; GameState gameState;
Score score;
std::chrono::milliseconds timeSinceDeath{};
void step(std::chrono::milliseconds delta, InputState inputState);
void eatPellets();
void processEvents(InputState & inputState); void processEvents(InputState & inputState);
void checkCollision(Ghost & ghost);
void killPacMan();
bool pacManDying() const;
void handleDeathAnimation(std::chrono::milliseconds delta);
}; };
} // namespace pacman } // namespace pacman

View file

@ -9,17 +9,31 @@
#include "Pinky.hpp" #include "Pinky.hpp"
#include "Score.hpp" #include "Score.hpp"
#include "SuperPellets.hpp" #include "SuperPellets.hpp"
#include "InputState.hpp"
namespace pacman { namespace pacman {
struct GameState { struct GameState {
void step(std::chrono::milliseconds delta);
Blinky blinky; Blinky blinky;
Pinky pinky; Pinky pinky;
Inky inky; Inky inky;
Clyde clyde; Clyde clyde;
PacMan pacMan; PacMan pacMan;
InputState inputState;
Pellets pellets; Pellets pellets;
SuperPellets superPellets; 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 } // namespace pacman

View file

@ -1,8 +1,10 @@
#pragma once #pragma once
namespace pacman { namespace pacman {
constexpr int DEFAULT_LIVES = 3;
struct Score { struct Score {
int lives = 0; int lives = DEFAULT_LIVES;
int points = 0; int points = 0;
int eatenPellets = 0; int eatenPellets = 0;
}; };

2
test/testGame.cpp Normal file
View file

@ -0,0 +1,2 @@
#include "PacMan.hpp"
#include <gtest/gtest.h>