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");
}
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);

View File

@ -5,124 +5,32 @@
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);
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();
if (event && event.value().type == sf::Event::Closed) {
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 "Position.hpp"
#include "Score.hpp"
#include <optional>
namespace pacman {
@ -15,7 +14,7 @@ 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:

View File

@ -8,22 +8,13 @@ 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

View File

@ -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

View File

@ -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;
};

2
test/testGame.cpp Normal file
View File

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