From ef20432d9ddf28161febcbbfcf57ef967ccf27c8 Mon Sep 17 00:00:00 2001 From: Patricia Aas Date: Fri, 27 Nov 2020 14:10:09 +0100 Subject: [PATCH] Copy over pacman game --- 01_foundation/CMakeLists.txt | 1 - 01_foundation/main.cpp | 4 - 02_foundation/CMakeLists.txt | 1 - 02_foundation/main.cpp | 4 - 11_intermediate/CMakeLists.txt | 1 - 11_intermediate/main.cpp | 4 - CMakeLists.txt | 22 +++-- README.md | 45 +++++++++- assets/maze.png | Bin 0 -> 890880 bytes assets/sprites32.png | Bin 0 -> 4984 bytes conanfile.py | 19 +++++ src/Board.cpp | 103 +++++++++++++++++++++++ src/Board.h | 35 ++++++++ src/CMakeLists.txt | 12 +++ src/Direction.h | 12 +++ src/Game.cpp | 60 +++++++++++++ src/Game.h | 24 ++++++ src/GameWindow.cpp | 149 +++++++++++++++++++++++++++++++++ src/GameWindow.h | 65 ++++++++++++++ src/InputState.cpp | 1 + src/InputState.h | 13 +++ src/PacMan.cpp | 73 ++++++++++++++++ src/PacMan.h | 45 ++++++++++ src/Position.h | 11 +++ src/main.cpp | 7 ++ 25 files changed, 682 insertions(+), 29 deletions(-) delete mode 100644 01_foundation/CMakeLists.txt delete mode 100644 01_foundation/main.cpp delete mode 100644 02_foundation/CMakeLists.txt delete mode 100644 02_foundation/main.cpp delete mode 100644 11_intermediate/CMakeLists.txt delete mode 100644 11_intermediate/main.cpp create mode 100644 assets/maze.png create mode 100644 assets/sprites32.png create mode 100644 conanfile.py create mode 100644 src/Board.cpp create mode 100644 src/Board.h create mode 100644 src/CMakeLists.txt create mode 100644 src/Direction.h create mode 100644 src/Game.cpp create mode 100644 src/Game.h create mode 100644 src/GameWindow.cpp create mode 100644 src/GameWindow.h create mode 100644 src/InputState.cpp create mode 100644 src/InputState.h create mode 100644 src/PacMan.cpp create mode 100644 src/PacMan.h create mode 100644 src/Position.h create mode 100644 src/main.cpp diff --git a/01_foundation/CMakeLists.txt b/01_foundation/CMakeLists.txt deleted file mode 100644 index e90e180..0000000 --- a/01_foundation/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -add_executable(01_main main.cpp) \ No newline at end of file diff --git a/01_foundation/main.cpp b/01_foundation/main.cpp deleted file mode 100644 index 20a8e10..0000000 --- a/01_foundation/main.cpp +++ /dev/null @@ -1,4 +0,0 @@ -#include -int main() { - std::cout << "Hello World\n"; -} diff --git a/02_foundation/CMakeLists.txt b/02_foundation/CMakeLists.txt deleted file mode 100644 index 6894533..0000000 --- a/02_foundation/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -add_executable(02_main main.cpp) \ No newline at end of file diff --git a/02_foundation/main.cpp b/02_foundation/main.cpp deleted file mode 100644 index 20a8e10..0000000 --- a/02_foundation/main.cpp +++ /dev/null @@ -1,4 +0,0 @@ -#include -int main() { - std::cout << "Hello World\n"; -} diff --git a/11_intermediate/CMakeLists.txt b/11_intermediate/CMakeLists.txt deleted file mode 100644 index 5f5712a..0000000 --- a/11_intermediate/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -add_executable(11_main main.cpp) \ No newline at end of file diff --git a/11_intermediate/main.cpp b/11_intermediate/main.cpp deleted file mode 100644 index 20a8e10..0000000 --- a/11_intermediate/main.cpp +++ /dev/null @@ -1,4 +0,0 @@ -#include -int main() { - std::cout << "Hello World\n"; -} diff --git a/CMakeLists.txt b/CMakeLists.txt index aa5a58d..e40cffe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,16 +1,14 @@ -if (NOT APPLE AND NOT WIN32) - set(CMAKE_TOOLCHAIN_FILE toolchains/linux_clang_11.cmake) -endif () - -cmake_minimum_required(VERSION 3.10) -project(modern_cpp C CXX) +cmake_minimum_required(VERSION 3.17) +project(pacman) set(CMAKE_CXX_STANDARD 20) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS NO) -include_directories(include) +# Download automatically, you can also just copy the conan.cmake file +if (NOT EXISTS "${CMAKE_BINARY_DIR}/conan.cmake") + message(STATUS "Downloading conan.cmake from https://github.com/conan-io/cmake-conan") + file(DOWNLOAD "https://github.com/conan-io/cmake-conan/raw/v0.15/conan.cmake" + "${CMAKE_BINARY_DIR}/conan.cmake") +endif () -add_subdirectory(01_foundation) -add_subdirectory(02_foundation) -add_subdirectory(11_intermediate) \ No newline at end of file +set(CMAKE_MODULE_PATH ${CMAKE_BINARY_DIR}/src) +add_subdirectory(src) diff --git a/README.md b/README.md index 644a7c5..900e58c 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,43 @@ -# modern_cpp_exercises -Exercises for the TurtleSec training "Mod(C++)" - Foundation and Intermediate +# Mod(C++) - Pac-Man Exercise + +## Ghosts +This will probably become relevant https://en.wikipedia.org/wiki/Ghosts_(Pac-Man) +This as well https://youtu.be/ataGotQ7ir8 +https://gameinternals.com/understanding-pac-man-ghost-behavior +https://www.gamasutra.com/view/feature/3938/the_pacman_dossier.php?print=1 + +## Windows Toolchain + +Visual Studio Community 2019 - latest (MSVC 19.28.29334.0) + +## Get Clion + +https://www.jetbrains.com/clion/download/download-thanks.html + +## Get Python 3 + +https://docs.python.org/3/using/windows.html#the-full-installer + +## Get Conan + +pip3 install conan --upgrade + +## Conan on Linux + +Set this environment variable and Conan will tell you which packages you need to +intall on Linux. +CONAN_SYSREQUIRES_MODE=verify + +In case of link errors around sndio, try to uninstall this package and rebuild the conan cache: +~~~ +sudo apt remove libsndio-dev* +~~~ +https://bugzilla.libsdl.org/show_bug.cgi?id=5105 + +## Currently does not work on windows - Get Clang + +Clang 10 works seamlessly with Conan (version 1.31.4) - with 11 you have to update your +/.conan/settings.yml file, add "11" to the list at "clang: version:" + +https://releases.llvm.org/download.html +https://www.jetbrains.com/help/clion/quick-tutorial-on-configuring-clion-on-windows.html#clang-cl diff --git a/assets/maze.png b/assets/maze.png new file mode 100644 index 0000000000000000000000000000000000000000..3fb7ac685305f7054a8b0b4d3237907b285fb5f4 GIT binary patch literal 890880 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 Mi1XpfL#WID2S4bm5dZ)H literal 0 HcmV?d00001 diff --git a/conanfile.py b/conanfile.py new file mode 100644 index 0000000..8b53e0f --- /dev/null +++ b/conanfile.py @@ -0,0 +1,19 @@ +from conans import ConanFile, CMake + +class ConanDependencies(ConanFile): + + settings = "os", "compiler", "build_type", "arch" + generators = "cmake", "cmake_find_package" + default_options = { + "sdl2_image:jpg": "libjpeg" + } + + def requirements(self): + self.requires("sdl2/2.0.9@bincrafters/stable") + self.requires("sdl2_image/2.0.4@bincrafters/stable") + + def imports(self): + self.copy("*.dll", dst="bin", src="bin") + self.copy("*.dylib*", dst="bin", src="lib") + self.copy('*.so*', dst='lib', src='lib') + self.copy("license*", dst="licenses", folder=True, ignore_case=True) diff --git a/src/Board.cpp b/src/Board.cpp new file mode 100644 index 0000000..37b0b55 --- /dev/null +++ b/src/Board.cpp @@ -0,0 +1,103 @@ +#include "Board.h" + +// Legend +// 0 - wall +// 1 - pellet +// 2 - nothing +// 3 - door +// 4 - superpower + +// 16 pixels per square +// Maze in pixels - width: 448 - height - 496 + +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,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 + {0,4,0,0,0,0,1,0,0,0,0,0,1,0,0,1,0,0,0,0,0,1,0,0,0,0,4,0}, // 3 + {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}, // 4 + {0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0}, // 5 + {0,1,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,1,0}, // 6 + {0,1,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,1,0}, // 7 + {0,1,1,1,1,1,1,0,0,1,1,1,1,0,0,1,1,1,1,0,0,1,1,1,1,1,1,0}, // 8 + {0,0,0,0,0,0,1,0,0,0,0,0,2,0,0,2,0,0,0,0,0,1,0,0,0,0,0,0}, // 9 + {0,0,0,0,0,0,1,0,0,0,0,0,2,0,0,2,0,0,0,0,0,1,0,0,0,0,0,0}, // 10 + {0,0,0,0,0,0,1,0,0,2,2,2,2,2,2,2,2,2,2,0,0,1,0,0,0,0,0,0}, // 11 + {0,0,0,0,0,0,1,0,0,2,0,0,0,3,3,0,0,0,2,0,0,1,0,0,0,0,0,0}, // 12 + {0,0,0,0,0,0,1,0,0,2,0,0,0,0,0,0,0,0,2,0,0,1,0,0,0,0,0,0}, // 13 + {3,2,2,2,2,2,1,2,2,2,0,0,0,0,0,0,0,0,2,2,2,1,2,2,2,2,2,3}, // 14 + {0,0,0,0,0,0,1,0,0,2,0,0,0,0,0,0,0,0,2,0,0,1,0,0,0,0,0,0}, // 15 + {0,0,0,0,0,0,1,0,0,2,0,0,0,0,0,0,0,0,2,0,0,1,0,0,0,0,0,0}, // 16 + {0,0,0,0,0,0,1,0,0,2,2,2,2,2,2,2,2,2,2,0,0,1,0,0,0,0,0,0}, // 17 + {0,0,0,0,0,0,1,0,0,2,0,0,0,0,0,0,0,0,2,0,0,1,0,0,0,0,0,0}, // 18 + {0,0,0,0,0,0,1,0,0,2,0,0,0,0,0,0,0,0,2,0,0,1,0,0,0,0,0,0}, // 19 + {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}, // 20 + {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}, // 21 + {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}, // 22 + {0,4,1,1,0,0,1,1,1,1,1,1,1,2,2,1,1,1,1,1,1,1,0,0,1,1,4,0}, // 23 + {0,0,0,1,0,0,1,0,0,1,0,0,0,0,0,0,0,0,1,0,0,1,0,0,1,0,0,0}, // 24 + {0,0,0,1,0,0,1,0,0,1,0,0,0,0,0,0,0,0,1,0,0,1,0,0,1,0,0,0}, // 25 + {0,1,1,1,1,1,1,0,0,1,1,1,1,0,0,1,1,1,1,0,0,1,1,1,1,1,1,0}, // 26 + {0,1,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0}, // 27 + {0,1,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0}, // 28 + {0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0}, // 29 + {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}, // 30 +}; + +Board::Board() { + resetBoardState(); +} + +void Board::resetBoardState() { + for (uint8_t row = 0; row < ROWS; row++) + for (uint8_t column = 0; column < COLUMNS; column++) + board_state[row][column] = board[row][column]; +} + +bool Board::isWalkable(Position point, float_t position_delta, Direction direction) const { + + switch (direction) { + case Direction::LEFT: + return board_state[int(point.y)][int(point.x - position_delta)] != 0; + case Direction::RIGHT: + return board_state[int(point.y)][int(point.x) + 1] != 0; + case Direction::UP: + return board_state[int(point.y - position_delta)][int(point.x)] != 0; + case Direction::DOWN: + return board_state[int(point.y) + 1][int(point.x)] != 0; + case Direction::NONE: + default: return true; + } +} + +SDL_Rect Board::pelletSprite() { + return pellet; +} + +SDL_Rect Board::superPelletSprite() { + return super_pellet; +} + +std::vector Board::pelletPositions() { + std::vector positions; + for (uint8_t row = 0; row < ROWS; row++) { + for (uint8_t column = 0; column < COLUMNS; column++) { + if (board_state[row][column] == 1) + positions.push_back({column, row}); + } + } + return positions; +} + +std::vector Board::superPelletPositions() { + // Hard coded is probably better than this + std::vector positions; + for (uint8_t row = 0; row < ROWS; row++) { + for (uint8_t column = 0; column < COLUMNS; column++) { + if (board_state[row][column] == 4) + positions.push_back({column, row}); + } + } + return positions; +} diff --git a/src/Board.h b/src/Board.h new file mode 100644 index 0000000..a0dc037 --- /dev/null +++ b/src/Board.h @@ -0,0 +1,35 @@ +#ifndef PACMAN_BOARD_H +#define PACMAN_BOARD_H + +#include "Direction.h" +#include "Position.h" +#include +#include +#include + +const uint8_t ROWS = 31; +const uint8_t COLUMNS = 28; + +class Board { +public: + Board(); + + [[nodiscard]] bool isWalkable(Position point, float_t d, Direction direction) const; + + SDL_Rect pelletSprite(); + + SDL_Rect superPelletSprite(); + + std::vector pelletPositions(); + + std::vector superPelletPositions(); + +private: + uint8_t board_state[ROWS][COLUMNS]; + const SDL_Rect super_pellet = {0*32, 9*32, 32, 32}; + const SDL_Rect pellet = {1*32, 9*32, 32, 32}; + + void resetBoardState(); +}; + +#endif //PACMAN_BOARD_H diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..e8949bd --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,12 @@ +include(${CMAKE_BINARY_DIR}/conan.cmake) +conan_add_remote(NAME bincrafters INDEX 1 URL https://api.bintray.com/conan/bincrafters/public-conan) + +conan_cmake_run(CONANFILE ../conanfile.py BASIC_SETUP CMAKE_TARGETS BUILD missing) + +find_package(sdl2 REQUIRED) +find_package(sdl2_image REQUIRED) +include_directories(${sdl2_INCLUDE_DIRS} ${sdl2_image_INCLUDE_DIRS}) + +file(GLOB_RECURSE sources CONFIGURE_DEPENDS "*.cpp") +add_executable(${PROJECT_NAME} ${sources}) +target_link_libraries(${PROJECT_NAME} sdl2::sdl2 sdl2_image::sdl2_image) \ No newline at end of file diff --git a/src/Direction.h b/src/Direction.h new file mode 100644 index 0000000..461767d --- /dev/null +++ b/src/Direction.h @@ -0,0 +1,12 @@ +#ifndef PACMAN_DIRECTION_H +#define PACMAN_DIRECTION_H + +enum class Direction { + NONE, + LEFT, + RIGHT, + UP, + DOWN +}; + +#endif //PACMAN_DIRECTION_H diff --git a/src/Game.cpp b/src/Game.cpp new file mode 100644 index 0000000..eb01f99 --- /dev/null +++ b/src/Game.cpp @@ -0,0 +1,60 @@ +#include "Game.h" + +#include + +#include + +Game::Game() + : window(448*2, 496*2) { +} + +auto Game::now() { + return std::chrono::system_clock::now(); +} + +void Game::run() { + InputState inputState; + auto current_time = now(); + while (!inputState.close) { + processEvents(inputState); + auto time_delta = now() - current_time; + auto milli_delta = std::chrono::duration_cast(time_delta); + pacMan.update(milli_delta, inputState, board); + current_time += time_delta; + window.update(pacMan, board); + } +} + +void Game::processEvents(InputState & inputState) { + SDL_Event event; + while (SDL_PollEvent(&event)) { + switch (event.type) { + case SDL_QUIT: + inputState.close = true; + break; + case SDL_KEYDOWN: + keyToggle(event, inputState, true); + break; + case SDL_KEYUP: + keyToggle(event, inputState, false); + break; + } + } +} + +void Game::keyToggle(const SDL_Event & event, InputState & inputState, bool on) { + switch (event.key.keysym.sym) { + case SDLK_UP: + inputState.up = on; + break; + case SDLK_DOWN: + inputState.down = on; + break; + case SDLK_LEFT: + inputState.left = on; + break; + case SDLK_RIGHT: + inputState.right = on; + break; + } +} diff --git a/src/Game.h b/src/Game.h new file mode 100644 index 0000000..ac8798c --- /dev/null +++ b/src/Game.h @@ -0,0 +1,24 @@ +#ifndef PACMAN_GAME_H +#define PACMAN_GAME_H + +#include "Board.h" +#include "GameWindow.h" +#include "InputState.h" +#include "PacMan.h" + +class Game { +public: + Game(); + void run(); + +private: + GameWindow window; + PacMan pacMan; + Board board; + + static void processEvents(InputState & inputState) ; + static void keyToggle(const SDL_Event & event, InputState & inputState, bool on); + [[nodiscard]] static auto now() ; +}; + +#endif //PACMAN_GAME_H diff --git a/src/GameWindow.cpp b/src/GameWindow.cpp new file mode 100644 index 0000000..e551f15 --- /dev/null +++ b/src/GameWindow.cpp @@ -0,0 +1,149 @@ +#include "GameWindow.h" +#include "PacMan.h" + +#include +#include +#include + +GameWindow::GameWindow(int width, int height) { + initSDL(); + initSDLImage(); + auto sdl_window = createWindow(width, height); + auto sdl_renderer = createRenderer(sdl_window); + createWindowSurface(sdl_window); + setDrawColor(sdl_renderer); + maze_texture = loadTexture(sdl_renderer, "../../../assets/maze.png"); + sprite_texture = loadTexture(sdl_renderer, "../../../assets/sprites32.png"); +} + +void GameWindow::update(const PacMan & pacMan, Board board) { + SDL_RenderClear(renderer.get()); + + renderMaze(); + renderBoard(board); + renderPacMan(pacMan); + + SDL_RenderPresent(renderer.get()); +} + +void GameWindow::renderMaze() const { + renderTexture(maze_texture.get(), nullptr, nullptr); +} + +void GameWindow::renderBoard(Board board) { + renderPellets(board); + renderSuperPellets(board); +} + +void GameWindow::renderSuperPellets(Board & board) const { + SDL_Rect sprite_rect = board.superPelletSprite(); + std::vector superPelletPositions = board.superPelletPositions(); + for (const auto & pos : superPelletPositions) { + SDL_Rect maze_rect = targetRect({ float_t(pos.x), float_t(pos.y) }, 16); + renderTexture(sprite_texture.get(), &sprite_rect, &maze_rect); + } +} + +void GameWindow::renderPellets(Board & board) const { + SDL_Rect sprite_rect = board.pelletSprite(); + std::vector pelletPositions = board.pelletPositions(); + for (const auto & pos : pelletPositions) { + SDL_Rect maze_rect = targetRect({ float_t(pos.x), float_t(pos.y) }, 16); + renderTexture(sprite_texture.get(), &sprite_rect, &maze_rect); + } +} + +void GameWindow::renderPacMan(const PacMan & pac_man) const { + Position maze_position = pac_man.currentPosition(); + SDL_Rect maze_rect = targetRect(maze_position, 16); + SDL_Rect sprite_rect = pac_man.currentSprite(); + renderTexture(sprite_texture.get(), &sprite_rect, &maze_rect); +} + +SDL_Rect GameWindow::targetRect(const Position & position, int pixel_increase) { + int pixels = 32; + int displacement = pixel_increase / 2; + return { + int(pixels * position.x) - displacement, + int(pixels * position.y) - displacement, + (pixels + pixel_increase), + (pixels + pixel_increase) + }; +} + +void GameWindow::renderTexture(SDL_Texture * texture, SDL_Rect * texture_rect, SDL_Rect * target_rect) const { + if (SDL_RenderCopy(renderer.get(), texture, texture_rect, target_rect) < 0) + exitFailure("Failed to copy texture to renderer"); +} + +void GameWindow::initSDL() { + if (SDL_Init(SDL_INIT_EVERYTHING) < 0) + exitFailure("Failed to initialize the SDL2 library"); +} + +void GameWindow::initSDLImage() { + int img_flags = IMG_INIT_PNG; + if (IMG_Init(img_flags) != img_flags) + exitImgFailure("Failed to init SDL_Image with png"); +} + +SDL_Window * GameWindow::createWindow(int width, int height) { + window = std::unique_ptr(SDL_CreateWindow( + "Pacman", + SDL_WINDOWPOS_CENTERED, + SDL_WINDOWPOS_CENTERED, + width, + height, + SDL_WINDOW_OPENGL)); + + if (!window) + exitFailure("Failed to create window"); + + return window.get(); +} + +SDL_Renderer * GameWindow::createRenderer(SDL_Window * sdl_window) { + renderer = std::unique_ptr(SDL_CreateRenderer( + sdl_window, + -1, + SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC)); + + if (!renderer) + exitFailure("Failed to create renderer"); + + return renderer.get(); +} + +void GameWindow::createWindowSurface(SDL_Window * sdl_window) { + window_surface = std::unique_ptr(SDL_GetWindowSurface(sdl_window)); + if (!window_surface) + exitFailure("Failed to get the surface from the window"); +} + +void GameWindow::setDrawColor(SDL_Renderer * sdl_renderer) { + if (SDL_SetRenderDrawColor(sdl_renderer, 0, 0, 0, SDL_ALPHA_OPAQUE) < 0) + exitFailure("Failed to set renderer color"); +} + +std::unique_ptr GameWindow::loadTexture(SDL_Renderer * sdl_renderer, const std::string& path) { + auto surface = std::unique_ptr(IMG_Load(path.c_str())); + if (!surface) + exitImgFailure("Failed to load image"); + + auto texture = std::unique_ptr(SDL_CreateTextureFromSurface(sdl_renderer, surface.get())); + if (!texture) + exitFailure("Failed to create texture from surface"); + return texture; +} + +void GameWindow::exitFailure(const std::string& message) { + std::cerr << message << "\n"; + std::cerr << "SDL2 Error: " << SDL_GetError() << "\n"; + exit(1); +} + +void GameWindow::exitImgFailure(const std::string& message) { + std::cerr << message << "\n"; + std::cerr << "SDL2_Image Error: " << IMG_GetError() << "\n"; + exit(1); +} diff --git a/src/GameWindow.h b/src/GameWindow.h new file mode 100644 index 0000000..65bc730 --- /dev/null +++ b/src/GameWindow.h @@ -0,0 +1,65 @@ +#ifndef PACMAN_GAMEWINDOW_H +#define PACMAN_GAMEWINDOW_H + +#include +#include + +#include "PacMan.h" +#include + +struct SDL_Window_Deleter { + void operator()(SDL_Window * window) { + SDL_DestroyWindow(window); + } +}; + +struct SDL_Renderer_Deleter { + void operator()(SDL_Renderer * renderer) { + SDL_DestroyRenderer(renderer); + } +}; + +struct SDL_Surface_Deleter { + void operator()(SDL_Surface * surface) { + SDL_FreeSurface(surface); + } +}; + +struct SDL_Texture_Deleter { + void operator()(SDL_Texture * texture) { + SDL_DestroyTexture(texture); + } +}; + +class PacMan; + +class GameWindow { +public: + explicit GameWindow(int width, int height); + void update(const PacMan & pacMan, Board board); + +private: + std::unique_ptr window; + std::unique_ptr renderer; + std::unique_ptr window_surface; + std::unique_ptr maze_texture; + std::unique_ptr sprite_texture; + SDL_Window * createWindow(int width, int height); + SDL_Renderer * createRenderer(SDL_Window * window); + void createWindowSurface(SDL_Window * sdl_window); + static void initSDL(); + static void initSDLImage(); + static void setDrawColor(SDL_Renderer * sdl_renderer); + static void exitFailure(const std::string& message); + static void exitImgFailure(const std::string& message); + static std::unique_ptr loadTexture(SDL_Renderer * sdl_renderer, const std::string& path); + void renderMaze() const; + void renderPacMan(const PacMan & pac_man) const; + void renderBoard(Board board); + void renderPellets(Board & board) const; + void renderSuperPellets(Board & board) const; + static SDL_Rect targetRect(const Position & position, int pixel_increase); + void renderTexture(SDL_Texture * texture, SDL_Rect * texture_rect, SDL_Rect * target_rect) const; +}; + +#endif //PACMAN_GAMEWINDOW_H diff --git a/src/InputState.cpp b/src/InputState.cpp new file mode 100644 index 0000000..10ecd04 --- /dev/null +++ b/src/InputState.cpp @@ -0,0 +1 @@ +#include "InputState.h" diff --git a/src/InputState.h b/src/InputState.h new file mode 100644 index 0000000..c375894 --- /dev/null +++ b/src/InputState.h @@ -0,0 +1,13 @@ +#ifndef PACMAN_INPUTSTATE_H +#define PACMAN_INPUTSTATE_H + +class InputState { +public: + bool close = false; + bool up = false; + bool down = false; + bool left = false; + bool right = false; +}; + +#endif //PACMAN_INPUTSTATE_H diff --git a/src/PacMan.cpp b/src/PacMan.cpp new file mode 100644 index 0000000..398cf1e --- /dev/null +++ b/src/PacMan.cpp @@ -0,0 +1,73 @@ +#include "PacMan.h" + +PacMan::PacMan() : + right_animation{right_wide, right_narrow, closed, right_narrow}, + left_animation{left_wide, left_narrow, closed, left_narrow}, + up_animation{up_wide, up_narrow, closed, up_narrow}, + down_animation{down_wide, down_narrow, closed, down_narrow} { +} + +SDL_Rect PacMan::currentSprite() const { + switch (direction) { + case Direction::NONE: return closed; + case Direction::LEFT: return left_animation[animation_position]; + case Direction::RIGHT: return right_animation[animation_position]; + case Direction::UP: return up_animation[animation_position]; + case Direction::DOWN: return down_animation[animation_position]; + } +} + +Position PacMan::currentPosition() const { + return pos; +} + +void PacMan::update(std::chrono::milliseconds time_delta, InputState state, const Board & board) { + setDirection(state); + updateAnimationPosition(time_delta); + updateMazePosition(time_delta, board); +} + +void PacMan::setDirection(const InputState & state) { + if (state.left) + direction = Direction::LEFT; + else if (state.right) + direction = Direction::RIGHT; + else if (state.up) + direction = Direction::UP; + else if (state.down) + direction = Direction::DOWN; +} + +void PacMan::updateAnimationPosition(std::chrono::milliseconds time_delta) { + animation_position_delta += (time_delta.count() / 100.0); + animation_position = int(animation_position + animation_position_delta) % 4; + animation_position_delta = (animation_position_delta < 1) ? animation_position_delta : (animation_position_delta - 1); +} + +void PacMan::updateMazePosition(std::chrono::milliseconds time_delta, const Board & board) { + float_t position_delta = (time_delta.count() / 128.0); + + if (board.isWalkable(pos, position_delta, direction)) { + switch (direction) { + case Direction::NONE: + break; + case Direction::LEFT: + pos.x -= position_delta; + pos.y = floor(pos.y); + break; + case Direction::RIGHT: + pos.x += position_delta; + pos.y = floor(pos.y); + break; + case Direction::UP: + pos.x = floor(pos.x); + pos.y -= position_delta; + break; + case Direction::DOWN: + pos.x = floor(pos.x); + pos.y += position_delta; + break; + } + } + +} diff --git a/src/PacMan.h b/src/PacMan.h new file mode 100644 index 0000000..e9a2a1c --- /dev/null +++ b/src/PacMan.h @@ -0,0 +1,45 @@ +#ifndef PACMAN_PACMAN_H +#define PACMAN_PACMAN_H + +#include "Board.h" +#include "Direction.h" +#include "InputState.h" +#include "Position.h" + +#include +#include + +class PacMan { +public: + PacMan(); + [[nodiscard]] SDL_Rect currentSprite() const; + [[nodiscard]] Position currentPosition() const; + + void update(std::chrono::milliseconds time_delta, InputState state, const Board & board); + +private: + + Direction direction = Direction::NONE; + Position pos = {14, 23}; + const SDL_Rect right_wide = {0*32, 0, 32, 32}; + const SDL_Rect right_narrow = {1*32, 0, 32, 32}; + const SDL_Rect closed = {2*32, 0, 32, 32}; + const SDL_Rect left_narrow = {3*32, 0, 32, 32}; + const SDL_Rect left_wide = {4*32, 0, 32, 32}; + const SDL_Rect up_wide = {5*32, 0, 32, 32}; + const SDL_Rect up_narrow = {6*32, 0, 32, 32}; + const SDL_Rect down_wide = {7*32, 0, 32, 32}; + const SDL_Rect down_narrow = {8*32, 0, 32, 32}; + const SDL_Rect right_animation[4]; + const SDL_Rect left_animation[4]; + const SDL_Rect up_animation[4]; + const SDL_Rect down_animation[4]; + uint8_t animation_position = 0; + float_t animation_position_delta = 0.0; + + void setDirection(const InputState & state); + void updateAnimationPosition(std::chrono::milliseconds time_delta); + void updateMazePosition(std::chrono::milliseconds time_delta, const Board & board); +}; + +#endif //PACMAN_PACMAN_H diff --git a/src/Position.h b/src/Position.h new file mode 100644 index 0000000..6a08fe7 --- /dev/null +++ b/src/Position.h @@ -0,0 +1,11 @@ +#ifndef PACMAN_POSITION_H +#define PACMAN_POSITION_H + +#include + +struct Position { + float_t x; + float_t y; +}; + +#endif //PACMAN_POSITION_H diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..6bb4a08 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,7 @@ +#include "Game.h" + +extern "C" int main([[maybe_unused]] int argc, [[maybe_unused]] char * argv[]) { + Game game; + game.run(); + return 0; +}