2021-06-16 10:52:04 +00:00
|
|
|
#include "Canvas.hpp"
|
2021-07-08 08:17:05 +00:00
|
|
|
#include "Scaling.hpp"
|
2021-06-20 16:52:34 +00:00
|
|
|
#include <fmt/format.h>
|
|
|
|
#include <string>
|
|
|
|
#include <vector>
|
2021-06-16 10:52:04 +00:00
|
|
|
|
2021-07-05 12:10:01 +00:00
|
|
|
namespace pacman {
|
|
|
|
|
2021-06-16 10:52:04 +00:00
|
|
|
Canvas::Canvas()
|
2021-09-10 12:44:40 +00:00
|
|
|
: window(sf::VideoMode(std::uint32_t(viewDimensions().width / 2), std::uint32_t(viewDimensions().height / 2)),
|
2021-07-02 12:47:33 +00:00
|
|
|
"Pacman",
|
2021-07-08 14:57:18 +00:00
|
|
|
sf::Style::Titlebar | sf::Style::Close),
|
2021-08-02 13:28:07 +00:00
|
|
|
view(sf::FloatRect(0, 0, float(viewDimensions().width), float(viewDimensions().height))) {
|
2021-07-07 09:33:22 +00:00
|
|
|
|
|
|
|
window.setView(view);
|
2021-06-22 13:37:36 +00:00
|
|
|
window.setFramerateLimit(60);
|
2021-06-20 16:52:34 +00:00
|
|
|
window.setVerticalSyncEnabled(true);
|
2021-07-07 09:33:22 +00:00
|
|
|
|
|
|
|
// We render the game in view at twice the native resolution,
|
|
|
|
// Then project it on a scaled window - on some mac we get the
|
|
|
|
// scaling factor of the window to adjust the resolution
|
2021-07-08 14:57:18 +00:00
|
|
|
const auto scale = scaling_factor_for_window(window.getSystemHandle());
|
2021-09-11 07:42:53 +00:00
|
|
|
const auto width = (viewDimensions().width / 2.0) * scale;
|
|
|
|
const auto height = (viewDimensions().height / 2.2) * scale;
|
2021-08-02 13:28:07 +00:00
|
|
|
window.setSize(sf::Vector2u(unsigned(width), unsigned(height)));
|
2021-07-07 09:33:22 +00:00
|
|
|
|
2021-06-20 16:52:34 +00:00
|
|
|
maze_texture = loadTexture("maze.png");
|
|
|
|
sprites_texture = loadTexture("sprites32.png");
|
2021-07-06 18:48:29 +00:00
|
|
|
game_font = loadFont("retro_font.ttf");
|
2021-06-20 16:52:34 +00:00
|
|
|
}
|
2021-06-16 10:52:04 +00:00
|
|
|
|
2021-10-06 11:30:07 +00:00
|
|
|
void Canvas::render(const GameState & gameState) {
|
2021-06-20 16:52:34 +00:00
|
|
|
clear();
|
2021-06-16 10:52:04 +00:00
|
|
|
|
|
|
|
renderMaze();
|
2021-07-08 14:57:18 +00:00
|
|
|
renderPellets(gameState.pellets);
|
|
|
|
renderSuperPellets(gameState.superPellets);
|
2021-06-24 11:32:52 +00:00
|
|
|
|
2021-10-18 13:22:18 +00:00
|
|
|
renderGhost(gameState.ghosts.blinky);
|
|
|
|
renderGhost(gameState.ghosts.pinky);
|
|
|
|
renderGhost(gameState.ghosts.inky);
|
|
|
|
renderGhost(gameState.ghosts.dave);
|
2021-06-24 11:32:52 +00:00
|
|
|
|
2021-09-07 14:00:19 +00:00
|
|
|
renderScore(gameState.score.points);
|
|
|
|
renderLives(gameState.score.lives);
|
2021-06-16 10:52:04 +00:00
|
|
|
|
2021-09-13 12:47:09 +00:00
|
|
|
renderFruits(gameState.fruit, gameState.score.eatenFruits);
|
|
|
|
|
2021-07-08 14:57:18 +00:00
|
|
|
renderPacMan(gameState.pacMan);
|
2021-06-28 10:42:21 +00:00
|
|
|
|
2021-06-20 16:52:34 +00:00
|
|
|
render();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Canvas::clear() {
|
|
|
|
window.clear(sf::Color::Black);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Canvas::render() {
|
|
|
|
window.display();
|
2021-06-16 10:52:04 +00:00
|
|
|
}
|
|
|
|
|
2021-06-20 16:52:34 +00:00
|
|
|
std::optional<sf::Event> Canvas::pollEvent() {
|
2021-06-22 08:28:23 +00:00
|
|
|
sf::Event event{};
|
2021-06-20 16:52:34 +00:00
|
|
|
if (window.pollEvent(event))
|
|
|
|
return event;
|
|
|
|
return std::nullopt;
|
2021-06-16 10:52:04 +00:00
|
|
|
}
|
|
|
|
|
2021-06-20 16:52:34 +00:00
|
|
|
void Canvas::renderMaze() {
|
2021-07-06 20:16:11 +00:00
|
|
|
Sprite maze;
|
|
|
|
maze.setTexture(maze_texture);
|
|
|
|
maze.setTextureRect(sf::IntRect{
|
|
|
|
0,
|
|
|
|
0,
|
2021-07-07 09:33:22 +00:00
|
|
|
MAZE_WIDTH,
|
|
|
|
MAZE_HEIGHT });
|
|
|
|
maze.setScale(MAZE_SCALE_UP, MAZE_SCALE_UP);
|
|
|
|
maze.setPosition(LEFT_MARGIN, TOP_MARGIN);
|
2021-06-20 16:52:34 +00:00
|
|
|
window.draw(maze);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Canvas::renderPellets(const Pellets & pellets) {
|
2021-06-16 10:52:04 +00:00
|
|
|
Sprite pellet = getSprite(pellets.currentSprite());
|
2021-10-05 09:58:06 +00:00
|
|
|
std::vector<GridPosition> pelletPositions = pellets.allPellets();
|
2021-06-16 10:52:04 +00:00
|
|
|
for (const auto & pos : pelletPositions) {
|
2021-07-07 09:09:53 +00:00
|
|
|
renderSprite(pellet, gridPositionToPosition(pos));
|
2021-06-16 10:52:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-20 16:52:34 +00:00
|
|
|
void Canvas::renderSuperPellets(const SuperPellets & superPellets) {
|
2021-06-16 10:52:04 +00:00
|
|
|
Sprite pellet = getSprite(superPellets.currentSprite());
|
2021-10-05 09:58:06 +00:00
|
|
|
std::vector<GridPosition> superPelletPositions = superPellets.allPellets();
|
2021-06-16 10:52:04 +00:00
|
|
|
for (const auto & pos : superPelletPositions) {
|
2021-07-07 09:09:53 +00:00
|
|
|
renderSprite(pellet, gridPositionToPosition(pos));
|
2021-06-16 10:52:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-20 16:52:34 +00:00
|
|
|
void Canvas::renderPacMan(const PacMan & pac_man) {
|
2021-06-16 10:52:04 +00:00
|
|
|
Sprite pacmanSprite = getSprite(pac_man.currentSprite());
|
|
|
|
const auto & pos = pac_man.position();
|
2021-06-22 10:00:29 +00:00
|
|
|
renderSprite(pacmanSprite, pos);
|
2021-06-16 10:52:04 +00:00
|
|
|
}
|
|
|
|
|
2021-09-15 11:39:42 +00:00
|
|
|
void Canvas::renderFruits(const Fruits & fruit, int eatenFruits) {
|
|
|
|
Sprite sprite = getSprite(fruit.currentSprite());
|
|
|
|
if (fruit.isVisible()) {
|
|
|
|
const auto & pos = fruit.position();
|
|
|
|
renderSprite(sprite, pos);
|
|
|
|
}
|
2021-09-15 12:40:07 +00:00
|
|
|
|
2021-09-15 11:42:52 +00:00
|
|
|
const auto x = static_cast<size_t>(LEFT_MARGIN + TARGET_MAZE_WIDTH + LEFT_MARGIN);
|
|
|
|
const auto y = static_cast<size_t>((TARGET_MAZE_HEIGHT / 3.0) * 2);
|
2021-09-15 11:39:42 +00:00
|
|
|
|
|
|
|
for (auto i = 0; i < eatenFruits + 1; i++) {
|
2021-09-15 12:40:07 +00:00
|
|
|
const auto sprite_position = float(i) * SPRITE_WIDTH * 1.5f;
|
|
|
|
const sf::Vector2f pos{ x + sprite_position, y };
|
2021-09-15 11:39:42 +00:00
|
|
|
sprite.setPosition(pos.x, pos.y);
|
2021-09-15 12:40:07 +00:00
|
|
|
|
2021-09-15 11:39:42 +00:00
|
|
|
window.draw(sprite);
|
|
|
|
}
|
2021-09-13 12:47:09 +00:00
|
|
|
}
|
|
|
|
|
2021-06-24 11:32:52 +00:00
|
|
|
void Canvas::renderGhost(const Ghost & ghost) {
|
|
|
|
Sprite sprite = getSprite(ghost.currentSprite());
|
|
|
|
const auto & pos = ghost.position();
|
|
|
|
renderSprite(sprite, pos);
|
|
|
|
}
|
|
|
|
|
2021-06-16 13:18:00 +00:00
|
|
|
void Canvas::renderScore(int score) {
|
2021-07-07 09:33:22 +00:00
|
|
|
const int x = (LEFT_MARGIN + TARGET_MAZE_WIDTH + LEFT_MARGIN);
|
2021-07-08 14:57:18 +00:00
|
|
|
const int y = (TOP_MARGIN * 2);
|
2021-07-07 09:33:22 +00:00
|
|
|
|
2021-06-20 16:52:34 +00:00
|
|
|
sf::Text text;
|
2021-07-07 09:33:22 +00:00
|
|
|
text.setPosition(x, y);
|
2021-06-20 16:52:34 +00:00
|
|
|
text.setFont(game_font);
|
|
|
|
text.setString(fmt::format("SCORE\n{}", score));
|
2021-07-07 09:33:22 +00:00
|
|
|
text.setCharacterSize(40);
|
2021-06-20 16:52:34 +00:00
|
|
|
text.setFillColor(sf::Color::White);
|
|
|
|
window.draw(text);
|
2021-06-16 13:18:00 +00:00
|
|
|
}
|
|
|
|
|
2021-06-24 08:44:13 +00:00
|
|
|
void Canvas::renderLives(int lives) {
|
2021-07-05 09:46:49 +00:00
|
|
|
constexpr GridPosition liveSprite = Atlas::pacman_left_narrow;
|
2021-07-07 09:33:22 +00:00
|
|
|
const size_t x = (LEFT_MARGIN + TARGET_MAZE_WIDTH + LEFT_MARGIN);
|
|
|
|
const size_t y = TARGET_MAZE_HEIGHT;
|
2021-06-24 11:32:52 +00:00
|
|
|
|
|
|
|
Sprite pacmanSprite = getSprite(liveSprite);
|
2021-09-10 12:44:40 +00:00
|
|
|
for (auto i = 0; i < lives - 1; i++) {
|
|
|
|
auto life_position = float(i) * SPRITE_WIDTH * 1.5f;
|
2021-08-02 13:28:07 +00:00
|
|
|
sf::Vector2f pos{ x + life_position, y };
|
2021-07-07 09:33:22 +00:00
|
|
|
pacmanSprite.setPosition(pos.x, pos.y);
|
2021-06-24 11:32:52 +00:00
|
|
|
window.draw(pacmanSprite);
|
|
|
|
}
|
2021-06-24 08:44:13 +00:00
|
|
|
}
|
|
|
|
|
2021-07-07 09:33:22 +00:00
|
|
|
Rect Canvas::viewDimensions() {
|
2021-07-08 14:57:18 +00:00
|
|
|
const double width = (LEFT_MARGIN + TARGET_MAZE_WIDTH + SCORE_WIDTH);
|
|
|
|
const double height = (TOP_MARGIN + TARGET_MAZE_HEIGHT + BOTTOM_MARGIN);
|
2021-07-07 09:33:22 +00:00
|
|
|
return { 0, 0, int(width), int(height) };
|
2021-06-16 10:52:04 +00:00
|
|
|
}
|
|
|
|
|
2021-07-05 09:46:49 +00:00
|
|
|
Sprite Canvas::getSprite(GridPosition coordinate) const {
|
2021-06-20 16:52:34 +00:00
|
|
|
sf::Sprite sprite;
|
|
|
|
sprite.setTexture(sprites_texture);
|
2021-07-07 09:33:22 +00:00
|
|
|
sprite.setTextureRect(sf::IntRect{ int(coordinate.x * SPRITE_WIDTH),
|
|
|
|
int(coordinate.y * SPRITE_HEIGHT),
|
|
|
|
SPRITE_WIDTH,
|
|
|
|
SPRITE_HEIGHT });
|
2021-06-20 16:52:34 +00:00
|
|
|
return sprite;
|
2021-06-16 10:52:04 +00:00
|
|
|
}
|
|
|
|
|
2021-06-22 10:00:29 +00:00
|
|
|
void Canvas::renderSprite(Sprite sprite, Position pos) {
|
2021-07-07 09:33:22 +00:00
|
|
|
pos.x = LEFT_MARGIN + (pos.x * SPRITE_WIDTH);
|
2021-07-08 14:57:18 +00:00
|
|
|
pos.y = TOP_MARGIN + (pos.y * SPRITE_HEIGHT);
|
2021-08-02 13:28:07 +00:00
|
|
|
sprite.setPosition(float(pos.x), float(pos.y));
|
2021-06-20 16:52:34 +00:00
|
|
|
window.draw(sprite);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void exitFailure(const std::string & message) {
|
|
|
|
fmt::print("{}\n", message);
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
sf::Texture Canvas::loadTexture(std::string_view path) {
|
|
|
|
sf::Texture texture;
|
|
|
|
if (!texture.loadFromFile(std::string{ path })) {
|
|
|
|
exitFailure(fmt::format("Failed to load image {}", path));
|
|
|
|
}
|
|
|
|
return texture;
|
|
|
|
}
|
|
|
|
|
|
|
|
sf::Font Canvas::loadFont(std::string_view path) {
|
|
|
|
sf::Font font;
|
2021-10-17 19:33:54 +00:00
|
|
|
if (!font.loadFromFile(std::string{ path })) {
|
2021-06-20 16:52:34 +00:00
|
|
|
exitFailure(fmt::format("Failed to load font {}", path));
|
|
|
|
}
|
|
|
|
return font;
|
2021-06-16 10:52:04 +00:00
|
|
|
}
|
2021-07-05 12:10:01 +00:00
|
|
|
|
|
|
|
} // namespace pacman
|