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 0000000..3fb7ac6 Binary files /dev/null and b/assets/maze.png differ diff --git a/assets/sprites32.png b/assets/sprites32.png new file mode 100644 index 0000000..246a123 Binary files /dev/null and b/assets/sprites32.png differ 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; +}