Add ghosts

No movement for now!
This commit is contained in:
Corentin Jabot 2021-06-24 13:32:52 +02:00 committed by Patricia Aas
parent 79506d218e
commit 93186dc8b9
13 changed files with 295 additions and 101 deletions

50
lib/Atlas.hpp Normal file
View File

@ -0,0 +1,50 @@
#pragma once
#include "Direction.hpp"
#include "Position.hpp"
#include "assert.h"
namespace Atlas {
enum class Ghost {
blinky = 2,
speedy = 3,
inky = 4,
clyde = 5,
};
constexpr PositionInt pacman_right_wide = { 0, 0 };
constexpr PositionInt pacman_right_narrow = { 1, 0 };
constexpr PositionInt pacman_closed = { 2, 0 };
constexpr PositionInt pacman_left_narrow = { 3, 0 };
constexpr PositionInt pacman_left_wide = { 4, 0 };
constexpr PositionInt pacman_up_wide = { 5, 0 };
constexpr PositionInt pacman_up_narrow = { 6, 0 };
constexpr PositionInt pacman_down_wide = { 7, 0 };
constexpr PositionInt pacman_down_narrow = { 8, 0 };
constexpr PositionInt ghostSprite(Ghost ghost, Direction direction, bool alternative) {
assert(ghost >= Ghost::blinky && ghost <= Ghost::clyde && "Invalid Ghost");
int y = static_cast<int>(ghost);
int x = 0;
switch (direction) {
case Direction::RIGHT:
x = 0;
break;
case Direction::DOWN:
x = 2;
break;
case Direction::LEFT:
x = 4;
break;
case Direction::UP:
x = 6;
break;
default:
x = 0;
break;
}
if (alternative)
x++;
return { x, y };
}
}

View File

@ -9,7 +9,7 @@
// 5 - pen doors
static const uint8_t board[ROWS][COLUMNS] = {
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 0
{ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 }, // 1
{ 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0 }, // 2
@ -50,11 +50,11 @@ Board::Board() {
}
bool Board::isWalkableForPacMan(Position point, float d, Direction direction) const {
return isWalkable(point, d, direction, true);
return isWalkable(point, d, direction, true);
}
bool Board::isWalkableForGhost(Position point, float d, Direction direction) const {
return isWalkable(point, d, direction, false);
return isWalkable(point, d, direction, false);
}
bool Board::isWalkable(Position point, float position_delta, Direction direction, bool pacman) const {
@ -62,29 +62,29 @@ bool Board::isWalkable(Position point, float position_delta, Direction direction
return true;
auto cellAtPosition = [&](Position point, float position_delta, Direction direction) {
switch (direction) {
case Direction::LEFT:
return board_state[int(point.y)][int(point.x - position_delta)];
case Direction::RIGHT:
return board_state[int(point.y)][int(point.x) + 1];
case Direction::UP:
return board_state[int(point.y - position_delta)][int(point.x)];
case Direction::DOWN:
return board_state[int(point.y) + 1][int(point.x)];
case Direction::NONE:
default:
return uint8_t(0);
}
switch (direction) {
case Direction::LEFT:
return board_state[int(point.y)][int(point.x - position_delta)];
case Direction::RIGHT:
return board_state[int(point.y)][int(point.x) + 1];
case Direction::UP:
return board_state[int(point.y - position_delta)][int(point.x)];
case Direction::DOWN:
return board_state[int(point.y) + 1][int(point.x)];
case Direction::NONE:
default:
return uint8_t(Cell::wall);
}
};
auto cell = cellAtPosition(point, position_delta, direction);
return pacman ? cell != 0 : cell != 0 && cell != 5;
Cell cell = Cell(cellAtPosition(point, position_delta, direction));
return pacman ? cell != Cell::wall : cell != Cell::wall && cell != Cell::pen_door;
}
std::vector<PositionInt> Board::initialPelletPositions() const {
std::vector<PositionInt> positions;
for (uint8_t row = 0; row < ROWS; row++) {
for (uint8_t column = 0; column < COLUMNS; column++) {
if (board_state[row][column] == 1)
if (board_state[row][column] == uint8_t(Cell::pellet))
positions.push_back({ column, row });
}
}
@ -95,7 +95,7 @@ std::vector<PositionInt> Board::initialSuperPelletPositions() const {
std::vector<PositionInt> positions;
for (uint8_t row = 0; row < ROWS; row++) {
for (uint8_t column = 0; column < COLUMNS; column++) {
if (board_state[row][column] == 4)
if (board_state[row][column] == uint8_t(Cell::power_pellet))
positions.push_back({ column, row });
}
}

View File

@ -12,6 +12,15 @@ const uint8_t COLUMNS = 28;
class Board {
public:
enum class Cell {
wall = 0,
pellet = 1,
nothing = 2,
door = 3,
power_pellet = 4,
pen_door = 5,
};
Board();
[[nodiscard]] bool isWalkableForPacMan(Position point, float d, Direction direction) const;
@ -21,7 +30,19 @@ public:
[[nodiscard]] std::vector<PositionInt> initialSuperPelletPositions() const;
static Position initialPacManPosition() { return { 14, 23 }; }
static Position initialPacManPosition() { return { 13.5, 23 }; }
static Position initialBlinkyPosition() { return { 13.5, 11 }; }
static Position blinkyScatterTarget() { return { 25, -2 }; }
static Position initialSpeedyPosition() { return { 11.5, 14 }; }
static Position speedyScatterTarget() { return { 3, -2 }; }
static Position initialInkyPosition() { return { 13.5, 14 }; }
static Position inkyScatterTarget() { return { 27, 30 }; }
static Position initialClydePosition() { return { 15.5, 14 }; }
static Position clydeScatterTarget() { return { 0, 30 }; }
private:
[[nodiscard]] bool isWalkable(Position point, float d, Direction direction, bool pacman) const;

View File

@ -1,5 +1,6 @@
#include "Canvas.hpp"
#include "Game.hpp"
#include "Ghost.hpp"
#include "PacMan.hpp"
#include "Pellets.hpp"
#include "SuperPellets.hpp"
@ -17,15 +18,21 @@ Canvas::Canvas()
game_font = loadFont("joystix.ttf");
}
void Canvas::update(const PacMan & pacMan, const Pellets & pellets, const SuperPellets & superPellets, const Score & score) {
void Canvas::update(const Game & game) {
clear();
renderMaze();
renderPellets(pellets);
renderSuperPellets(superPellets);
renderPacMan(pacMan);
renderScore(score.points);
renderLives(score.lives);
renderPellets(game.pellets);
renderSuperPellets(game.superPellets);
renderPacMan(game.pacMan);
renderGhost(game.blinky);
renderGhost(game.speedy);
renderGhost(game.inky);
renderGhost(game.clyde);
renderScore(game.score.points);
renderLives(game.score.lives);
render();
}
@ -73,6 +80,12 @@ void Canvas::renderPacMan(const PacMan & pac_man) {
renderSprite(pacmanSprite, pos);
}
void Canvas::renderGhost(const Ghost & ghost) {
Sprite sprite = getSprite(ghost.currentSprite());
const auto & pos = ghost.position();
renderSprite(sprite, pos);
}
void Canvas::renderScore(int score) {
const int x = LEFT_MARGIN + MAZE_WIDTH + LEFT_MARGIN;
const int y = TOP_MARGIN * 2;
@ -87,16 +100,16 @@ void Canvas::renderScore(int score) {
}
void Canvas::renderLives(int lives) {
constexpr PositionInt liveSprite{3, 0};
const int x = LEFT_MARGIN + MAZE_WIDTH + LEFT_MARGIN;
const int y = maze_texture.getSize().y;
constexpr PositionInt liveSprite = Atlas::pacman_left_narrow;
const int x = LEFT_MARGIN + MAZE_WIDTH + LEFT_MARGIN;
const int y = maze_texture.getSize().y;
Sprite pacmanSprite = getSprite(liveSprite);
for(int i = 0; i < lives - 1; i++) {
PositionInt pos{x + i * pacmanSprite.getTextureRect().width, y};
pacmanSprite.setPosition(pos.x, pos.y);
window.draw(pacmanSprite);
}
Sprite pacmanSprite = getSprite(liveSprite);
for (int i = 0; i < lives - 1; i++) {
PositionInt pos{ x + i * pacmanSprite.getTextureRect().width, y };
pacmanSprite.setPosition(pos.x, pos.y);
window.draw(pacmanSprite);
}
}
Rect Canvas::windowDimensions() {

View File

@ -4,6 +4,8 @@
#include "Score.hpp"
#include <optional>
class Game;
class Ghost;
class PacMan;
class Pellets;
class SuperPellets;
@ -11,7 +13,7 @@ class SuperPellets;
class Canvas {
public:
Canvas();
void update(const PacMan & pacMan, const Pellets & pellets, const SuperPellets & superPellets, const Score &);
void update(const Game & game);
std::optional<sf::Event> pollEvent();
private:
@ -28,6 +30,7 @@ private:
void render();
void renderMaze();
void renderPacMan(const PacMan & pac_man);
void renderGhost(const Ghost & ghost);
void renderPellets(const Pellets & pellets);
void renderSuperPellets(const SuperPellets & superPellets);
void renderSprite(Sprite sprite, Position pos);

View File

@ -4,18 +4,18 @@
constexpr int DEFAULT_LIVES = 3;
constexpr int NORMAL_PELLET_POINTS = 10;
constexpr int POWER_PELLET_POINTS = 50;
constexpr int POWER_PELLET_POINTS = 50;
//constexpr int GHOST_POINTS[] = {200, 400, 800, 1600};
Game::Game()
: pacMan(board),
pellets(board),
superPellets(board)
{
score.lives = DEFAULT_LIVES;
superPellets(board),
blinky(board),
speedy(board),
inky(board),
clyde(board) {
score.lives = DEFAULT_LIVES;
}
auto Game::now() {
@ -24,44 +24,53 @@ auto Game::now() {
void Game::run() {
const std::chrono::milliseconds delta_time (1000/60);
const std::chrono::milliseconds delta_time(1000 / 60);
std::chrono::milliseconds t(0);
std::chrono::milliseconds accumulator(0);
auto current_time = now();
std::chrono::milliseconds t(0);
std::chrono::milliseconds accumulator(0);
auto current_time = now();
InputState inputState;
InputState inputState;
while (true) {
auto newTime = now();
auto frameTime = std::chrono::duration_cast<std::chrono::milliseconds>(newTime - current_time);
current_time = newTime;
accumulator += frameTime;
processEvents(inputState);
if(inputState.close)
return;
while ( accumulator >= delta_time ) {
pacMan.update(delta_time, inputState, board);
eatPellets();
accumulator -= delta_time;
t += delta_time;
}
canvas.update(pacMan, pellets, superPellets, score);
while (true) {
auto newTime = now();
auto frameTime = std::chrono::duration_cast<std::chrono::milliseconds>(newTime - current_time);
current_time = newTime;
accumulator += frameTime;
processEvents(inputState);
if (inputState.close)
return;
while (accumulator >= delta_time) {
step(delta_time, inputState);
accumulator -= delta_time;
t += delta_time;
}
canvas.update(*this);
}
}
void Game::step(std::chrono::milliseconds delta, InputState inputState) {
pacMan.update(delta, inputState, board);
blinky.update(delta, board);
speedy.update(delta, board);
inky.update(delta, board);
clyde.update(delta, board);
eatPellets();
}
void Game::eatPellets() {
const auto pos = pacMan.positionInGrid();
if(pellets.eatPelletAtPosition(pos)) {
score.eatenPellets++;
score.points += NORMAL_PELLET_POINTS;
if (pellets.eatPelletAtPosition(pos)) {
score.eatenPellets++;
score.points += NORMAL_PELLET_POINTS;
}
if(superPellets.eatPelletAtPosition(pos)) {
score.eatenPellets++;
score.points += POWER_PELLET_POINTS;
if (superPellets.eatPelletAtPosition(pos)) {
score.eatenPellets++;
score.points += POWER_PELLET_POINTS;
}
}
void Game::processEvents(InputState & inputState) {

View File

@ -1,10 +1,11 @@
#pragma once
#include "Score.hpp"
#include "Board.hpp"
#include "Canvas.hpp"
#include "Ghost.hpp"
#include "PacMan.hpp"
#include "Pellets.hpp"
#include "Score.hpp"
#include "SuperPellets.hpp"
class InputState;
@ -12,19 +13,24 @@ class InputState;
class Game {
public:
Game();
void run();
private:
friend class Canvas;
Canvas canvas;
Board board;
PacMan pacMan;
Pellets pellets;
SuperPellets superPellets;
Blinky blinky;
Speedy speedy;
Inky inky;
Clyde clyde;
Score score;
void step(std::chrono::milliseconds delta, InputState inputState);
void eatPellets();
void processEvents(InputState & inputState);
[[nodiscard]] static auto now();

45
lib/Ghost.cpp Normal file
View File

@ -0,0 +1,45 @@
#include "Ghost.hpp"
#include <cmath>
Ghost::Ghost(Atlas::Ghost spritesSet, Position startingPosition, Position scatterTarget)
: spritesSet(spritesSet),
pos(startingPosition),
startingPosition(startingPosition),
scatterTarget(scatterTarget) {
}
[[nodiscard]] PositionInt Ghost::currentSprite() const {
return Atlas::ghostSprite(spritesSet, direction, alternate_animation);
}
Position Ghost::position() const {
return pos;
}
Position Ghost::positionInGrid() const {
return { std::round(pos.x), std::round(pos.y) };
}
void Ghost::update(std::chrono::milliseconds time_delta, const Board & board) {
time += time_delta.count();
if (time >= 250) {
time = 0;
alternate_animation = !alternate_animation;
}
}
Blinky::Blinky(const Board & board)
: Ghost(Atlas::Ghost::blinky, board.initialBlinkyPosition(), board.blinkyScatterTarget()) {
}
Speedy::Speedy(const Board & board)
: Ghost(Atlas::Ghost::speedy, board.initialSpeedyPosition(), board.speedyScatterTarget()) {
}
Inky::Inky(const Board & board)
: Ghost(Atlas::Ghost::inky, board.initialInkyPosition(), board.inkyScatterTarget()) {
}
Clyde::Clyde(const Board & board)
: Ghost(Atlas::Ghost::clyde, board.initialClydePosition(), board.clydeScatterTarget()) {
}

57
lib/Ghost.hpp Normal file
View File

@ -0,0 +1,57 @@
#pragma once
#include <chrono>
#include "Atlas.hpp"
#include "Board.hpp"
#include "Position.hpp"
class Ghost {
public:
enum class State {
Chase,
Scatter,
Freightened,
Eyes,
};
explicit Ghost(Atlas::Ghost spritesSet, Position startingPosition, Position scatterTarget);
[[nodiscard]] PositionInt currentSprite() const;
[[nodiscard]] Position position() const;
[[nodiscard]] Position positionInGrid() const;
void update(std::chrono::milliseconds time_delta, const Board & board);
protected:
Atlas::Ghost spritesSet;
Direction direction = Direction::NONE;
double time = 0;
bool alternate_animation = false;
State state = State::Chase;
Position pos;
Position startingPosition;
Position scatterTarget;
};
class Blinky : public Ghost {
public:
explicit Blinky(const Board & board);
};
class Speedy : public Ghost {
public:
explicit Speedy(const Board & board);
};
class Inky : public Ghost {
public:
explicit Inky(const Board & board);
};
class Clyde : public Ghost {
public:
explicit Clyde(const Board & board);
};

View File

@ -37,12 +37,11 @@ void PacMan::setDirection(const InputState & state) {
}
void PacMan::updateAnimationPosition(std::chrono::milliseconds time_delta, bool paused) {
if(paused) {
pacManAnimation.pause();
}
else {
pacManAnimation.updateAnimationPosition(time_delta);
}
if (paused) {
pacManAnimation.pause();
} else {
pacManAnimation.updateAnimationPosition(time_delta);
}
}
void PacMan::updateMazePosition(std::chrono::milliseconds time_delta, const Board & board) {

View File

@ -12,7 +12,7 @@ PositionInt PacManAnimation::animationFrame(Direction direction) const {
return down_animation[animation_position];
case Direction::NONE:
default:
return closed;
return Atlas::pacman_closed;
}
}
@ -23,6 +23,6 @@ void PacManAnimation::updateAnimationPosition(std::chrono::milliseconds time_del
}
void PacManAnimation::pause() {
animation_position = 0;
animation_position_delta = 0;
animation_position = 0;
animation_position_delta = 0;
}

View File

@ -1,5 +1,6 @@
#pragma once
#include "Atlas.hpp"
#include "Board.hpp"
#include "Direction.hpp"
#include "InputState.hpp"
@ -14,21 +15,11 @@ public:
void updateAnimationPosition(std::chrono::milliseconds time_delta);
void pause();
private:
uint8_t animation_position = 0;
float animation_position_delta = 0.0;
const PositionInt right_wide = { 0, 0 };
const PositionInt right_narrow = { 1, 0 };
const PositionInt closed = { 2, 0 };
const PositionInt left_narrow = { 3, 0 };
const PositionInt left_wide = { 4, 0 };
const PositionInt up_wide = { 5, 0 };
const PositionInt up_narrow = { 6, 0 };
const PositionInt down_wide = { 7, 0 };
const PositionInt down_narrow = { 8, 0 };
const PositionInt down_animation[4]{ down_wide, down_narrow, closed, down_narrow };
const PositionInt left_animation[4]{ left_wide, left_narrow, closed, left_narrow };
const PositionInt right_animation[4]{ right_wide, right_narrow, closed, right_narrow };
const PositionInt up_animation[4]{ up_wide, up_narrow, closed, up_narrow };
const PositionInt down_animation[4]{ Atlas::pacman_down_wide, Atlas::pacman_down_narrow, Atlas::pacman_closed, Atlas::pacman_down_narrow };
const PositionInt left_animation[4]{ Atlas::pacman_left_wide, Atlas::pacman_left_narrow, Atlas::pacman_closed, Atlas::pacman_left_narrow };
const PositionInt right_animation[4]{ Atlas::pacman_right_wide, Atlas::pacman_right_narrow, Atlas::pacman_closed, Atlas::pacman_right_narrow };
const PositionInt up_animation[4]{ Atlas::pacman_up_wide, Atlas::pacman_up_narrow, Atlas::pacman_closed, Atlas::pacman_up_narrow };
};

View File

@ -1,7 +1,7 @@
#pragma once
struct Score {
int lives = 0;
int points = 0;
int eatenPellets = 0;
int lives = 0;
int points = 0;
int eatenPellets = 0;
};