diff --git a/CMakeLists.txt b/CMakeLists.txt index 94f4f56fbccc8091b8093e3b2107c56e97d01cfe..593dc285ecf55564704b46a637189892eeaf8171 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,6 +47,12 @@ file(GLOB_RECURSE ADVANCED_WARS_SOURCES set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_BUILD_TYPE Debug) + +set(CMAKE_CXX_FLAGS_DEBUG "-g -O0 -Wall -Wextra -Wpedantic") +set(CMAKE_C_FLAGS_DEBUG "-g -O0 -Wall -Wextra -Wpedantic") + +add_definitions(-DDEBUG) # Compiler-Warnungen aktivieren if(MSVC) @@ -59,7 +65,8 @@ endif() set(ASSETS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src/assets) set(OUTPUT_ASSETS_DIR ${CMAKE_CURRENT_BINARY_DIR}/assets) file(MAKE_DIRECTORY ${OUTPUT_ASSETS_DIR}) -file(GLOB FONT_FILES ${ASSETS_DIR}/*.ttf) +file(GLOB FONT_FILES ${ASSETS_DIR}/*.TTF) +file(GLOB IMAGE_FILES ${ASSETS_DIR}/*.png) # Executable erstellen add_executable(advanced_wars ${ADVANCED_WARS_SOURCES}) @@ -78,6 +85,14 @@ foreach(FONT ${FONT_FILES}) ) endforeach() +foreach(IMAGE ${IMAGE_FILES}) + add_custom_command( + TARGET advanced_wars PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${IMAGE} ${OUTPUT_ASSETS_DIR} + COMMENT "Kopiere Image: ${IMAGE} nach ${OUTPUT_ASSETS_DIR}" + ) +endforeach() + set(CMAKE_MODULE_PATH ${ADVANCED_WARS_SOURCE_DIR}/cmake/ ${CMAKE_MODULE_PATH}) # Plattform-spezifische Konfiguration @@ -127,4 +142,4 @@ else() box2d m ) -endif() \ No newline at end of file +endif() diff --git a/README.md b/README.md index 8e5bcd0f74eb02bafe338d75ed3e513d8e39bcff..e464b3acdb696833c43b9cfd88279d0063da4246 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,31 @@ 4. Visual Studio erkennt automatisch das CMake-Projekt 5. Build über "Build All" ausführen +#### Falls Syntax errors + +1. Erstelle .vscode/c_cpp_properties.json Datei +2. Füge die folgende JSON so oder so ähnlich ein: + +```json +{ + "configurations": [ + { + "name": "Fedora", + "includePath": [ + "/usr/include", + "/usr/include/SDL2" + ], + "defines": [], + "intelliSenseMode": "linux-gcc-x64", + "compilerPath": "/usr/bin/gcc", + "cStandard": "c17", + "cppStandard": "c++17" + } + ], + "version": 4 +} +``` + ## Build-Optionen CMake kann mit verschiedenen Optionen konfiguriert werden: diff --git a/src/assets/ARCADECLASSIC.TTF b/src/assets/ARCADECLASSIC.TTF new file mode 100644 index 0000000000000000000000000000000000000000..394a9f781cedaa283a11b3b8b43c9006f4e5bac7 Binary files /dev/null and b/src/assets/ARCADECLASSIC.TTF differ diff --git a/src/assets/main_background.png b/src/assets/main_background.png new file mode 100644 index 0000000000000000000000000000000000000000..5958dc0d0a7144697dece04db5dc2a1646088207 Binary files /dev/null and b/src/assets/main_background.png differ diff --git a/src/engine.cpp b/src/engine.cpp index 21d679a519ec5749ada5fa457eaaa97bd0605330..1eb0c0f4f4a67e2363b503b5910bb557943d4563 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -1,12 +1,15 @@ #include "engine.hpp" +#include "SDL_events.h" #include "scene.hpp" #include "spritesheet.hpp" #include "window.hpp" #include <SDL.h> #include <SDL_image.h> #include <SDL_render.h> +#include <deque> +#include <memory> +#include <optional> #include <stdexcept> -#include <vector> namespace advanced_wars { @@ -17,15 +20,86 @@ Engine::Engine(Window& window) : window(window), quit(false) this->sdl_renderer = SDL_CreateRenderer( this->window.sdl_window(), -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); - if (sdl_renderer == nullptr) - { - throw std::runtime_error("SDL could not generate renderer: " + std::string(SDL_GetError())); + int imgFlags = IMG_INIT_PNG; + if (!(IMG_Init(imgFlags) & imgFlags)) { + throw std::runtime_error( + "SDL_image could not initialize! SDL_image Error: " + + std::string(IMG_GetError())); + } + + this->sdl_renderer = + SDL_CreateRenderer(this->window.sdl_window(), -1, + SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); + + if (sdl_renderer == nullptr) { + throw std::runtime_error("SDL could not generate renderer: " + + std::string(SDL_GetError())); + } +} + +std::deque<SDL_Event> &Engine::events() { return this->_events; } + +void Engine::push_scene(std::shared_ptr<Scene> scene) { + this->scenes.push_back(scene); +} + +void Engine::return_to_menu() { + // TODO: discuss if we outsource this to a separate function + // clear everything except the first scene + while (this->scenes.size() > 1) { + this->scenes.pop_back(); + } +} + + +std::optional<std::shared_ptr<Scene>> Engine::pop_scene() { + if (this->scenes.empty()) { + return std::nullopt; + } + std::shared_ptr<Scene> tmp = scenes.back(); + this->scenes.pop_back(); + + return tmp; +} + +void Engine::set_spritesheet(Spritesheet spritesheet) { + this->spritesheet = spritesheet; +} + +void Engine::pump() { + SDL_Event e; + while (SDL_PollEvent(&e)) { + if (e.type == SDL_QUIT) { + this->quit = true; + } else { + this->_events.push_back(e); } } -void Engine::set_scene(Scene& scene) -{ - this->scene = &scene; +void Engine::exit() { this->quit = true; } + +bool Engine::exited() { return this->quit; } + +void Engine::render() { + if (SDL_RenderClear(this->sdl_renderer) != 0) { + throw std::runtime_error("Could not clear renderer: " + + std::string(SDL_GetError())); + } + + if (scenes.empty()) { + SDL_RenderPresent(this->sdl_renderer); + return; + } + + std::shared_ptr<Scene> currentScene = scenes.back(); + if (!currentScene) { + SDL_RenderPresent(this->sdl_renderer); + return; + } + + currentScene->render(this); + + SDL_RenderPresent(this->sdl_renderer); } void Engine::set_spritesheet(Spritesheet& spritesheet) diff --git a/src/engine.hpp b/src/engine.hpp index 3bc80efd1e77db45f922bdf3a60594425fd99a24..4906923df6c0a9356ee78849e580d444ed9a5a67 100644 --- a/src/engine.hpp +++ b/src/engine.hpp @@ -1,16 +1,21 @@ #pragma once +#include "SDL_events.h" #include "scene.hpp" #include "spritesheet.hpp" #include "window.hpp" #include <SDL.h> #include <SDL_render.h> +#include <deque> +#include <memory> #include <optional> -#include <vector> namespace advanced_wars { +// Forward declaration +class Scene; + /** * @brief The main window of the game */ @@ -24,9 +29,17 @@ class Engine bool exited(); - void pump(); + void exit(); + + void pump(); + + void push_scene(std::shared_ptr<Scene> scene); + + std::optional<std::shared_ptr<Scene>> pop_scene(); + + void return_to_menu(); - void set_scene(Scene& scene); + std::deque<SDL_Event> &events(); void set_spritesheet(Spritesheet& spritesheet); diff --git a/src/level.cpp b/src/level.cpp index 9313b6063a2c0382f8ac25d513a076b493aaa15f..9597806ebc181666b86658e3da0e83daf061447d 100644 --- a/src/level.cpp +++ b/src/level.cpp @@ -8,16 +8,19 @@ #include <SDL.h> #include <iostream> #include <string> +#include "ui/pausemenu.hpp" +#include "ui/contextmenu.hpp" -namespace advanced_wars -{ +namespace advanced_wars { Level::Level( std::string name, int width, int height, std::vector<Tile> tiles, std::vector<Building> buildings, std::vector<Unit> units, std::vector<Effect> effects) - : name(name), width(width), height(height), tiles(tiles), id(0) + : name(name), width(width), height(height), tiles(tiles), id(0), context_menu(ContextMenu()), context_menu_active(false) { + context_menu.setOptions({"Move", "Info", "Wait"}); + for (Building building : buildings) { this->add_building(building); @@ -46,7 +49,8 @@ void Level::render(Engine& engine, std::vector<SDL_Event>& events) // Iterate over all events while (!events.empty()) { - events.erase(events.begin()); + handleEvent(engine, engine->events().at(0)); + engine->events().pop_front(); } // Tiles @@ -92,8 +96,48 @@ void Level::render(Engine& engine, std::vector<SDL_Event>& events) { std::cout << "Could not set render draw color: " << SDL_GetError() << std::endl; } + + SDL_RenderClear(engine->renderer()); + + if(context_menu_active) { + context_menu.render(engine); + } } +void Level::handleEvent(Engine *engine, SDL_Event &event) { + // Handle events for the level + if (event.type == SDL_KEYDOWN) { + if (event.key.keysym.sym == SDLK_ESCAPE) { + // Pause the game + std::cout << "Pausing game..." << std::endl; + SDL_Texture *currentTexture = SDL_CreateTexture(engine->renderer(), SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, 800, 600); + + PauseMenu pauseMenu(0, currentTexture); + engine->push_scene(std::make_shared<PauseMenu>(pauseMenu)); + } + if(context_menu_active){ + if(event.key.keysym.sym == SDLK_DOWN) { + context_menu.handleEvent(event); + } + if(event.key.keysym.sym == SDLK_UP) { + context_menu.handleEvent(event); + } + if(event.key.keysym.sym == SDLK_RETURN) { + if(context_menu.getSelectedOption() == "Wait"){ + context_menu_active = false; + } + } + + } + + } + if(event.type == SDL_MOUSEBUTTONDOWN) { + context_menu.update(event.button.x, event.button.y); + context_menu_active = true; + } +} + + int Level::add_building(Building building) { buildings.insert({id, building}); @@ -142,4 +186,5 @@ Effect Level::remove_effect(int id) return value; } -} // namespace advanced_wars + +} // namespace advanced_wars \ No newline at end of file diff --git a/src/level.hpp b/src/level.hpp index bb2db20d92180ca1eba34f44beb6826143b04cbd..33122616451998a5a2c069ca7601667f0aefb4c5 100644 --- a/src/level.hpp +++ b/src/level.hpp @@ -10,6 +10,7 @@ #include <string> #include <unordered_map> #include <vector> +#include "ui/contextmenu.hpp" namespace advanced_wars { @@ -26,6 +27,8 @@ class Level : public Scene void render(Engine& engine, std::vector<SDL_Event>& events); + void handleEvent(Engine *engine, SDL_Event &event); + int add_building(Building building); Building remove_building(int id); @@ -48,6 +51,9 @@ class Level : public Scene std::unordered_map<int, Unit> units; std::unordered_map<int, Effect> effects; + ContextMenu context_menu; + bool context_menu_active; + int id; }; diff --git a/src/main.cpp b/src/main.cpp index 2ff1123fc4a23ed7bd73fa6350fb8f8be1691d9d..82e36fce730948141faabd74002d104952244bc7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,8 +1,10 @@ #include "building.hpp" #include "effect.hpp" #include "engine.hpp" -#include "level.hpp" #include "spritesheet.hpp" +#include "ui/menu.hpp" +#include "ui/contextmenu.hpp" +#include <memory> #include "tile.hpp" #include "unit.hpp" #include "window.hpp" @@ -32,6 +34,19 @@ int main() Window window("Advanced Wars", 960, 960); Engine engine(window); + // render main menu + + std::shared_ptr<Menu> menu = std::make_shared<Menu>(0); + std::shared_ptr<ContextMenu> context_menu = std::make_shared<ContextMenu>(); + context_menu->setOptions({"Move", "Info", "Wait"}); + + + std::string basePath = SDL_GetBasePath(); + std::string relativePath = "assets/main_background.png"; + std::string fullPath = basePath + relativePath; + menu->loadBackground(engine.renderer(), fullPath.c_str()); + + engine.push_scene(menu); // Construct a level std::vector<Tile> tiles; @@ -42,6 +57,8 @@ int main() tiles.push_back(Tile(TileId::PLAIN, x, y)); } } + + engine.set_spritesheet(spritesheet); // Fill the edges with water for (size_t n = 0; n < 20; n++) diff --git a/src/scene.hpp b/src/scene.hpp index d61edefc09a188b0b3094a66fad83a8ea493f123..59067c232872416d1dd2b2a93f116064b831a84b 100644 --- a/src/scene.hpp +++ b/src/scene.hpp @@ -1,7 +1,7 @@ #pragma once +#include "engine.hpp" #include <SDL.h> -#include <vector> namespace advanced_wars { @@ -14,4 +14,5 @@ class Scene public: virtual void render(Engine& engine, std::vector<SDL_Event>& events) = 0; }; + } // namespace advanced_wars diff --git a/src/spritesheet.cpp b/src/spritesheet.cpp index c5ff86870e20e5fed39d7049ca5db401d63255cd..1a93c96fb55fbeb553b2d4acbb8d023f9204199f 100644 --- a/src/spritesheet.cpp +++ b/src/spritesheet.cpp @@ -7,7 +7,8 @@ */ #include "spritesheet.hpp" -#include "SDL_pixels.h" +#include <SDL_render.h> +#include "SDL_surface.h" #include "engine.hpp" #include "highfive/H5File.hpp" #include <SDL_image.h> diff --git a/src/spritesheet.hpp b/src/spritesheet.hpp index 7d82c39cbffdea9cd511cb1ea76fd8de94d5abcd..c6ae316725e2d6f088be7052cd904277efde047f 100644 --- a/src/spritesheet.hpp +++ b/src/spritesheet.hpp @@ -8,6 +8,7 @@ #pragma once +#include <SDL_render.h> #include <SDL.h> #include <SDL_render.h> #include <string> diff --git a/src/ui/contextmenu.cpp b/src/ui/contextmenu.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8bae5ff334cb15200d3cac4f296e266f871960c9 --- /dev/null +++ b/src/ui/contextmenu.cpp @@ -0,0 +1,83 @@ +#include "contextmenu.hpp" +#include <iostream> +#include <SDL_ttf.h> + +namespace advanced_wars { + + ContextMenu::ContextMenu() + : selectedOption(0) {} + + ContextMenu::~ContextMenu() {} + + void ContextMenu::setOptions(const std::vector<std::string>& newOptions) { + options = newOptions; + selectedOption = 0; // Reset auf die erste Option + } + + void ContextMenu::render(Engine* engine) { + if (!options.empty()) { + if (TTF_Init() == -1) { + std::cerr << "Failed to initialize TTF: " << TTF_GetError() << std::endl; + return; + } + + std::string basePath = SDL_GetBasePath(); + std::string relativePath = "assets/ARCADECLASSIC.TTF"; + std::string fullPath = basePath + relativePath; + TTF_Font *font = TTF_OpenFont(fullPath.c_str(), 16); + if (!font) { + std::cerr << "Failed to load font: " << TTF_GetError() << std::endl; + return; + } + + SDL_Color white = {255, 255, 255, 255}; + SDL_Color yellow = {192, 255, 0, 255}; + + int spacing = 20; // Abstand zwischen den Optionen + + //box around options + SDL_SetRenderDrawColor(engine->renderer(), 0, 0, 255, 255); + SDL_Rect box = {x, y - 3, 50, static_cast<int>(options.size() * spacing)}; + SDL_RenderFillRect(engine->renderer(), &box); + + SDL_SetRenderDrawColor(engine->renderer(), 0, 0, 0, 255); + + for (size_t i = 0; i < options.size(); ++i) { + SDL_Surface* textSurface = TTF_RenderText_Solid(font, options[i].c_str(), (i == selectedOption) ? yellow : white); + if (!textSurface) { + continue; + } + + SDL_Texture* textTexture = SDL_CreateTextureFromSurface(engine->renderer(), textSurface); + SDL_Rect textRect = {x+10, y + static_cast<int>(i * spacing), textSurface->w, textSurface->h}; + SDL_RenderCopy(engine->renderer(), textTexture, nullptr, &textRect); + + SDL_DestroyTexture(textTexture); + SDL_FreeSurface(textSurface); + } + + TTF_CloseFont(font); + TTF_Quit(); + } + } + + void ContextMenu::handleEvent(SDL_Event& event) { + if (event.type == SDL_KEYDOWN) { + if (event.key.keysym.sym == SDLK_DOWN) { + selectedOption = (selectedOption + 1) % options.size(); + } else if (event.key.keysym.sym == SDLK_UP) { + selectedOption = (selectedOption - 1 + options.size()) % options.size(); + } + } + } + + std::string ContextMenu::getSelectedOption() { + return options[selectedOption]; + } + + void ContextMenu::update(int x, int y) { + this->x = x; + this->y = y; + } + +} diff --git a/src/ui/contextmenu.hpp b/src/ui/contextmenu.hpp new file mode 100644 index 0000000000000000000000000000000000000000..94b095f928fa6a4a67f691e22b3799b8ac767599 --- /dev/null +++ b/src/ui/contextmenu.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include <SDL.h> +#include <vector> +#include <string> +#include "../scene.hpp" +#include "../engine.hpp" + +namespace advanced_wars { + +class ContextMenu : public Scene { +private: + size_t selectedOption; + std::vector<std::string> options; + int x; + int y; + +public: + ContextMenu(); + + void setOptions(const std::vector<std::string>& newOptions); + + void render(Engine* engine) override; + + void handleEvent(SDL_Event& event); + + void update(int x, int y); + + std::string getSelectedOption(); + + ~ContextMenu(); +}; + +} \ No newline at end of file diff --git a/src/ui/menu.cpp b/src/ui/menu.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9dafaffd1dfc4c2e92ef8dc2a833d8a8bcdcc013 --- /dev/null +++ b/src/ui/menu.cpp @@ -0,0 +1,168 @@ +#include "menu.hpp" +#include <SDL.h> +#include <SDL_image.h> +#include <SDL_ttf.h> +#include <iostream> +#include <string> +#include "../level.hpp" +#include "../building.hpp" +#include "../unit.hpp" +#include "../tile.hpp" +#include "../spritesheet.hpp" + +namespace advanced_wars { + +Menu::Menu(int selectedOption) + : selectedOption(selectedOption), + options({"Start Game", "Options", "Exit"}), backgroundTexture(nullptr) { + if (!(IMG_Init(IMG_INIT_PNG) & IMG_INIT_PNG)) { + std::cerr << "Failed to initialize SDL_image: " << IMG_GetError() + << std::endl; + } +} + +Menu::~Menu() { + if (backgroundTexture) { + SDL_DestroyTexture(backgroundTexture); + } + IMG_Quit(); +}; + +void Menu::render(Engine *engine) { + + // Iterate over all events + while (!engine->events().empty()) { + SDL_Event event = engine->events().at(0); + engine->events().pop_front(); + handleEvent(engine, event); + } + + if (backgroundTexture) { + SDL_RenderCopy(engine->renderer(), backgroundTexture, nullptr, nullptr); + } else { + SDL_SetRenderDrawColor(engine->renderer(), 0, 0, 0, 255); + SDL_RenderClear(engine->renderer()); + } + + if (TTF_Init() == -1) { + std::cerr << "Failed to initialize TTF: " << TTF_GetError() << std::endl; + return; + } + + std::string basePath = SDL_GetBasePath(); + std::string relativePath = "assets/ARCADECLASSIC.TTF"; + std::string fullPath = basePath + relativePath; + TTF_Font *titleFont = TTF_OpenFont(fullPath.c_str(), 48); + if (!titleFont) { + std::cerr << "Failed to load title font: " << fullPath << TTF_GetError() + << std::endl; + return; + } + + TTF_Font *menuFont = TTF_OpenFont(fullPath.c_str(), 24); + if (!menuFont) { + TTF_CloseFont(titleFont); + std::cerr << "Failed to load menu font: " << fullPath << TTF_GetError() + << std::endl; + return; + } + + SDL_Color white = {255, 255, 255, 255}; + SDL_Color yellow = {255, 255, 0, 255}; + + SDL_Surface *titleSurface = + TTF_RenderText_Solid(titleFont, "Advanced Wars", white); + if (titleSurface) { + SDL_Texture *titleTexture = + SDL_CreateTextureFromSurface(engine->renderer(), titleSurface); + SDL_Rect titleRect = {static_cast<int>((800 - titleSurface->w) / 2), 50, + titleSurface->w, titleSurface->h}; + SDL_RenderCopy(engine->renderer(), titleTexture, nullptr, &titleRect); + SDL_DestroyTexture(titleTexture); + SDL_FreeSurface(titleSurface); + } + + for (size_t i = 0; i < options.size(); ++i) { + SDL_Surface *textSurface = TTF_RenderText_Solid( + menuFont, options[i].c_str(), (i == selectedOption) ? yellow : white); + if (!textSurface) { + continue; + } + + SDL_Texture *textTexture = + SDL_CreateTextureFromSurface(engine->renderer(), textSurface); + SDL_Rect textRect = {static_cast<int>((800 - textSurface->w) / 2), + static_cast<int>(150 + i * 50), textSurface->w, + textSurface->h}; + SDL_RenderCopy(engine->renderer(), textTexture, nullptr, &textRect); + + SDL_DestroyTexture(textTexture); + SDL_FreeSurface(textSurface); + } + + TTF_CloseFont(titleFont); + TTF_CloseFont(menuFont); + TTF_Quit(); + + SDL_RenderPresent(engine->renderer()); +} + +void Menu::handleEvent(Engine *engine, SDL_Event &event) { + if (event.type == SDL_KEYDOWN) { + if (event.key.keysym.sym == SDLK_DOWN) { + selectedOption = (selectedOption + 1) % options.size(); + } else if (event.key.keysym.sym == SDLK_UP) { + selectedOption = (selectedOption - 1 + options.size()) % options.size(); + } else if (event.key.keysym.sym == SDLK_RETURN) { + if (options[selectedOption] == "Exit") { + std::cout << "Exiting game..." << std::endl; + engine->exit(); + } else if (options[selectedOption] == "Start Game") { + std::cout << "Starting game..." << std::endl; + + /* TODO REMOVE THIS BOILERPLATE CODE BEFORE MERGE */ + + Level level("Osnabrück", 20, 20, std::vector<Tile>(), + std::vector<Building>(), std::vector<Unit>()); + + engine->push_scene(std::make_shared<advanced_wars::Level>(level)); + + std::string basePath = SDL_GetBasePath(); + std::string relativePath = "assets/main_background.png"; + std::string fullPath = basePath + relativePath; + Spritesheet spritesheet(fullPath, *engine); + + engine->set_spritesheet(spritesheet); + + /* END OF BOILERPLATE CODE */ + + } else if (options[selectedOption] == "Options") { + std::cout << "Opening options..." << std::endl; + } + } + } +} + +void Menu::loadBackground(SDL_Renderer *renderer, + const std::string &imagePath) { + // Lade das Hintergrundbild + SDL_Surface *backgroundSurface = IMG_Load(imagePath.c_str()); + if (!backgroundSurface) { + std::cerr << "Failed to load background image: " << IMG_GetError() + << std::endl; + return; + } + + // Erstelle eine Textur aus der Oberfläche und speichere sie als + // Klassenmitglied + backgroundTexture = SDL_CreateTextureFromSurface(renderer, backgroundSurface); + SDL_FreeSurface(backgroundSurface); // Oberfläche freigeben, da sie nicht mehr + // benötigt wird + + if (!backgroundTexture) { + std::cerr << "Failed to create background texture: " << SDL_GetError() + << std::endl; + } +} + +} // namespace advanced_wars diff --git a/src/ui/menu.hpp b/src/ui/menu.hpp new file mode 100644 index 0000000000000000000000000000000000000000..43f84d081e9611a251a5a2430d35d9dde9771ffa --- /dev/null +++ b/src/ui/menu.hpp @@ -0,0 +1,86 @@ +#pragma once + +#include "../scene.hpp" +#include <SDL.h> +#include <array> +#include <iostream> +#include <string> +#include <vector> + +namespace advanced_wars { + +/** + * @class Menu + * @brief Represents the main menu of the game, allowing navigation between different options. + * + * This menu provides three selectable options: + * - "Start Game": Begins a new game session. + * - "Options": Opens the game settings. + * - "Exit": Closes the application. + */ +class Menu : public Scene { +private: + size_t selectedOption; ///< Index of the currently selected menu option. + std::array<std::string, 3> options; ///< The available menu options. + SDL_Texture *backgroundTexture; ///< Pointer to the background texture (if any). + +public: + /** + * @brief Constructs the Menu with an initial selected option. + * + * Initializes the menu with the available options and sets the currently + * selected option based on the given index. + * + * @param selectedOption The index of the initially selected menu option. + */ + Menu(int selectedOption); + + /** + * @brief Renders the menu on the screen. + * + * This method clears the screen, draws the background (if available), + * renders the menu title, and displays the selectable options. The currently + * selected option is highlighted in a different color. + * + * @param engine Pointer to the game engine, used for rendering. + */ + void render(Engine *engine) override; + + /** + * @brief Handles user input events for menu navigation. + * + * This method processes keyboard input to navigate through the menu options. + * - **Arrow Down (`SDLK_DOWN`)**: Moves the selection to the next option. + * - **Arrow Up (`SDLK_UP`)**: Moves the selection to the previous option. + * - **Enter (`SDLK_RETURN`)**: Confirms the selection: + * - **"Start Game"**: Loads the game scene. + * - **"Options"**: Opens the settings menu. + * - **"Exit"**: Closes the application. + * + * @param engine Pointer to the game engine, used to manage scenes. + * @param event The SDL event containing user input data. + */ + void handleEvent(Engine *engine, SDL_Event &event); + + /** + * @brief Loads a background image as a texture. + * + * This method loads an image file, converts it into an SDL texture, and + * assigns it as the menu's background. If the loading fails, an error is + * logged, and the menu will display a plain black background instead. + * + * @param renderer The SDL renderer used to create the texture. + * @param imagePath The file path to the background image. + */ + void loadBackground(SDL_Renderer *renderer, const std::string &imagePath); + + /** + * @brief Destroys the menu and releases resources. + * + * Cleans up allocated resources, including the background texture (if loaded), + * and ensures that SDL_Image is properly shut down. + */ + ~Menu(); +}; + +} // namespace advanced_wars \ No newline at end of file diff --git a/src/ui/pausemenu.cpp b/src/ui/pausemenu.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0dd592841e6e0fa352fca4b483d3e6e839dd5923 --- /dev/null +++ b/src/ui/pausemenu.cpp @@ -0,0 +1,118 @@ +#include "pausemenu.hpp" +#include "../engine.hpp" +#include <SDL_ttf.h> + +namespace advanced_wars { + +PauseMenu::PauseMenu(int selectedOption, SDL_Texture *backgroundTexture) + : selectedOption(selectedOption), + options({"Resume", "Options", "Exit"}), backgroundTexture(backgroundTexture) { + // Initialize SDL_ttf + if (TTF_Init() == -1) { + std::cerr << "Failed to initialize SDL_ttf: " << TTF_GetError() << std::endl; + } + + if (!backgroundTexture) { + this->backgroundTexture = nullptr; + } +} + +PauseMenu::~PauseMenu() { + if (backgroundTexture) { + SDL_DestroyTexture(backgroundTexture); + backgroundTexture = nullptr; + } + TTF_Quit(); +} + +void PauseMenu::render(Engine *engine) { + + while (!engine->events().empty()) { + SDL_Event event = engine->events().at(0); + engine->events().pop_front(); + handleEvent(engine, event); + } + + SDL_Renderer *renderer = engine->renderer(); + + // Render the existing level + //engine->render(); + + // Render the dialog background + if (backgroundTexture) { + SDL_RenderCopy(renderer, backgroundTexture, nullptr, nullptr); + } + + if (TTF_Init() == -1) { + std::cerr << "Failed to initialize TTF: " << TTF_GetError() << std::endl; + return; + } + + // Render the dialog options on top of the background + std::string basePath = SDL_GetBasePath(); + std::string relativePath = "assets/ARCADECLASSIC.TTF"; + std::string fullPath = basePath + relativePath; + + TTF_Font *font = TTF_OpenFont(fullPath.c_str(), 24); + if (!font) { + std::cerr << "Failed to load menu font: " << fullPath << " " << TTF_GetError() + << std::endl; + return; + } + + SDL_Color white = {255, 255, 255, 255}; + SDL_Color yellow = {255, 255, 0, 255}; + + for (size_t i = 0; i < options.size(); ++i) { + SDL_Surface *textSurface = TTF_RenderText_Solid( + font, options[i].c_str(), (i == selectedOption) ? yellow : white); + SDL_Texture *textTexture = SDL_CreateTextureFromSurface(renderer, textSurface); + + SDL_Rect destRect = {100, static_cast<int>(100 + i * 50), textSurface->w, textSurface->h}; + SDL_RenderCopy(renderer, textTexture, nullptr, &destRect); + + SDL_FreeSurface(textSurface); + SDL_DestroyTexture(textTexture); + } + TTF_CloseFont(font); + SDL_RenderPresent(renderer); +} + +void PauseMenu::handleEvent(Engine *engine, SDL_Event &event) { + if (event.type == SDL_KEYDOWN) { + if (event.key.keysym.sym == SDLK_DOWN) { + selectedOption = (selectedOption + 1) % options.size(); + } else if (event.key.keysym.sym == SDLK_UP) { + selectedOption = (selectedOption - 1 + options.size()) % options.size(); + } else if (event.key.keysym.sym == SDLK_ESCAPE) { + std::cout << "Resuming game..." << std::endl; + engine->pop_scene(); + } else if (event.key.keysym.sym == SDLK_RETURN) { + if (options[selectedOption] == "Exit") { + // exit into main menu + std::cout << "Exiting game..." << std::endl; + engine->return_to_menu(); + } else if (options[selectedOption] == "Resume") { + // resume game + std::cout << "Resuming game..." << std::endl; + engine->pop_scene(); + } + } + + } + // Handle events for the pause menu +} + + + +void PauseMenu::loadBackground(SDL_Renderer *renderer, const std::string &imagePath) { + SDL_Surface *surface = IMG_Load(imagePath.c_str()); + if (!surface) { + std::cerr << "Failed to load image: " << IMG_GetError() << std::endl; + return; + } + backgroundTexture = SDL_CreateTextureFromSurface(renderer, surface); + SDL_FreeSurface(surface); +} + +} // namespace advanced_wars \ No newline at end of file diff --git a/src/ui/pausemenu.hpp b/src/ui/pausemenu.hpp new file mode 100644 index 0000000000000000000000000000000000000000..6dc2d2cec236d7d614e8d012ce2ce935675c233d --- /dev/null +++ b/src/ui/pausemenu.hpp @@ -0,0 +1,92 @@ +#pragma once + +#include "../scene.hpp" +#include <SDL.h> +#include <array> +#include <iostream> +#include <string> +#include <vector> +#include <SDL_image.h> +#include <SDL_ttf.h> + +namespace advanced_wars { + +/** + * @class PauseMenu + * @brief A scene that represents the in-game pause menu. + * + * The pause menu allows the player to: + * - **Resume**: Return to the current game scene. + * - **Options**: (Currently not implemented). + * - **Exit**: Return to the main menu. + * + * The menu supports keyboard navigation and responds to user input. + */ +class PauseMenu : public Scene { +private: + size_t selectedOption; ///< Index of the currently selected menu option. + std::array<std::string, 3> options; ///< The available pause menu options. + SDL_Texture *backgroundTexture; ///< Pointer to the background texture (if available). + +public: + /** + * @brief Constructs the pause menu with a background texture. + * + * The pause menu initializes the menu options and stores the provided + * background texture. If no texture is provided, a default black background is used. + * + * @param selectedOption The index of the initially selected menu option. + * @param backgroundTexture A pointer to the background texture (can be nullptr). + */ + PauseMenu(int selectedOption, SDL_Texture *backgroundTexture); + + /** + * @brief Renders the pause menu on the screen. + * + * This method: + * - Draws the background (if available). + * - Displays the menu options with the currently selected option highlighted. + * - Presents the rendered frame to the screen. + * + * @param engine Pointer to the game engine, used for rendering. + */ + void render(Engine *engine) override; + + /** + * @brief Handles user input events for menu navigation. + * + * This method processes keyboard input to navigate and interact with the pause menu. + * - **Arrow Down (`SDLK_DOWN`)**: Moves the selection to the next option. + * - **Arrow Up (`SDLK_UP`)**: Moves the selection to the previous option. + * - **Escape (`SDLK_ESCAPE`)**: Resumes the game by removing the pause menu. + * - **Enter (`SDLK_RETURN`)**: Executes the selected option: + * - **"Resume"**: Closes the pause menu and resumes the game. + * - **"Exit"**: Returns to the main menu. + * + * @param engine Pointer to the game engine, used to manage scenes. + * @param event The SDL event containing user input data. + */ + void handleEvent(Engine *engine, SDL_Event &event); + + /** + * @brief Loads a background image as a texture. + * + * This method loads an image file, converts it into an SDL texture, and assigns it + * as the menu’s background. If the loading fails, an error is logged, and the menu + * will display a plain black background instead. + * + * @param renderer The SDL renderer used to create the texture. + * @param imagePath The file path to the background image. + */ + void loadBackground(SDL_Renderer *renderer, const std::string &imagePath); + + /** + * @brief Destroys the pause menu and releases resources. + * + * Cleans up allocated resources, including the background texture (if loaded), + * and ensures that SDL_ttf is properly shut down. + */ + ~PauseMenu(); +}; + +} // namespace advanced_wars \ No newline at end of file