From b3e15c8466f2e81f515447ab5430534dab102f14 Mon Sep 17 00:00:00 2001 From: Allan Legemaate Date: Sat, 16 Dec 2023 20:27:57 -0500 Subject: [PATCH] feat: add state engine (#1) --- .vscode/settings.json | 69 ++++++++++++++++++- README.md | 4 ++ src/core/asset.cpp | 4 +- src/core/asset.h | 4 +- src/core/asset_manager.cpp | 4 +- src/core/asset_manager.h | 4 +- src/core/core.cpp | 4 +- src/core/core.h | 6 +- src/core/state.cpp | 100 ++++++++++++++++++++++++++++ src/core/state.h | 132 +++++++++++++++++++++++++++++++++++++ src/game/state_game.h | 58 ++++++++++++++++ src/game/state_init.cpp | 36 ++++++++++ src/game/state_init.h | 62 +++++++++++++++++ src/game/state_intro.h | 63 ++++++++++++++++++ src/game/state_menu.cpp | 14 ++++ src/game/state_menu.h | 58 ++++++++++++++++ src/main.cpp | 41 ++++++++---- 17 files changed, 639 insertions(+), 24 deletions(-) create mode 100644 src/core/state.cpp create mode 100644 src/core/state.h create mode 100644 src/game/state_game.h create mode 100644 src/game/state_init.cpp create mode 100644 src/game/state_init.h create mode 100644 src/game/state_intro.h create mode 100644 src/game/state_menu.cpp create mode 100644 src/game/state_menu.h diff --git a/.vscode/settings.json b/.vscode/settings.json index 6be89b5..23d5ab5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,7 +8,74 @@ "ostream": "cpp", "cstddef": "cpp", "fstream": "cpp", - "*.tcc": "cpp" + "*.tcc": "cpp", + "memory": "cpp", + "cctype": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "cstdarg": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "any": "cpp", + "array": "cpp", + "atomic": "cpp", + "bit": "cpp", + "bitset": "cpp", + "charconv": "cpp", + "chrono": "cpp", + "codecvt": "cpp", + "compare": "cpp", + "concepts": "cpp", + "condition_variable": "cpp", + "cstdint": "cpp", + "deque": "cpp", + "forward_list": "cpp", + "list": "cpp", + "map": "cpp", + "set": "cpp", + "unordered_map": "cpp", + "unordered_set": "cpp", + "vector": "cpp", + "exception": "cpp", + "algorithm": "cpp", + "iterator": "cpp", + "memory_resource": "cpp", + "numeric": "cpp", + "optional": "cpp", + "random": "cpp", + "ratio": "cpp", + "source_location": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "utility": "cpp", + "format": "cpp", + "future": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "istream": "cpp", + "limits": "cpp", + "mutex": "cpp", + "new": "cpp", + "numbers": "cpp", + "ranges": "cpp", + "semaphore": "cpp", + "span": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "stdfloat": "cpp", + "stop_token": "cpp", + "streambuf": "cpp", + "thread": "cpp", + "cinttypes": "cpp", + "typeinfo": "cpp", + "valarray": "cpp", + "variant": "cpp" }, "sonarlint.pathToCompileCommands": "${workspaceFolder}/compile_commands.json" } diff --git a/README.md b/README.md index d2d82ee..b3f6ad0 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,10 @@ WIP Open Source Stornghold Crusader clone ## Setup +### Assets + +Copy your entire Stronghold Crusader folder to `assets/proprietary/` + ### Windows (MSYS2) ```bash diff --git a/src/core/asset.cpp b/src/core/asset.cpp index 22b3a4e..5ac85c8 100644 --- a/src/core/asset.cpp +++ b/src/core/asset.cpp @@ -9,7 +9,7 @@ #include "../lib/parsers/tgx_parser.h" #include "core.h" -namespace oshc::asset +namespace oshc::core::asset { // Texture @@ -173,4 +173,4 @@ bool Music::is_playing() const return Mix_PlayingMusic() == 1; } -} // namespace oshc::asset \ No newline at end of file +} // namespace oshc::core::asset \ No newline at end of file diff --git a/src/core/asset.h b/src/core/asset.h index 1eee072..961c01a 100644 --- a/src/core/asset.h +++ b/src/core/asset.h @@ -17,7 +17,7 @@ #include #include -namespace oshc::asset +namespace oshc::core::asset { /** @@ -205,4 +205,4 @@ class Music : public Asset /// @brief Shared pointer to underlying music std::shared_ptr m_data; }; -} // namespace oshc::asset \ No newline at end of file +} // namespace oshc::core::asset \ No newline at end of file diff --git a/src/core/asset_manager.cpp b/src/core/asset_manager.cpp index 925473d..9142e41 100644 --- a/src/core/asset_manager.cpp +++ b/src/core/asset_manager.cpp @@ -4,7 +4,7 @@ #include #include -namespace oshc::asset +namespace oshc::core::asset { // Load assets from json @@ -68,4 +68,4 @@ Music AssetManager::get_music(const std::string &id) return music[id]; } -} // namespace oshc::asset \ No newline at end of file +} // namespace oshc::core::asset \ No newline at end of file diff --git a/src/core/asset_manager.h b/src/core/asset_manager.h index 0ef53dc..ebb81c5 100644 --- a/src/core/asset_manager.h +++ b/src/core/asset_manager.h @@ -19,7 +19,7 @@ #include "asset.h" -namespace oshc::asset +namespace oshc::core::asset { class AssetManager @@ -67,4 +67,4 @@ class AssetManager std::map> music; }; -} // namespace oshc::asset \ No newline at end of file +} // namespace oshc::core::asset \ No newline at end of file diff --git a/src/core/core.cpp b/src/core/core.cpp index f464744..88dd237 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -8,7 +8,9 @@ SDL_Renderer *renderer = nullptr; SDL_Window *window = nullptr; -oshc::asset::AssetManager asset_manager = oshc::asset::AssetManager(); +oshc::core::asset::AssetManager asset_manager = oshc::core::asset::AssetManager(); + +oshc::core::state::StateEngine state_engine = oshc::core::state::StateEngine(); void init() { diff --git a/src/core/core.h b/src/core/core.h index fbad414..10ce266 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -12,6 +12,7 @@ #pragma once #include "asset_manager.h" +#include "state.h" #include #include @@ -29,7 +30,10 @@ extern SDL_Renderer *renderer; extern SDL_Window *window; /// @brief Global asset manager -extern oshc::asset::AssetManager asset_manager; +extern oshc::core::asset::AssetManager asset_manager; + +// @breif Global state engine +extern oshc::core::state::StateEngine state_engine; /** * @brief Initialize SDL2 and create window diff --git a/src/core/state.cpp b/src/core/state.cpp new file mode 100644 index 0000000..4d80f3a --- /dev/null +++ b/src/core/state.cpp @@ -0,0 +1,100 @@ +#include "state.h" + +#include +#include + +#include "core.h" + +#include "../game/state_game.h" +#include "../game/state_init.h" +#include "../game/state_intro.h" +#include "../game/state_menu.h" + +namespace oshc::core::state +{ + +void StateEngine::render() const +{ + if (!m_state) + { + return; + } + + SDL_RenderClear(oshc::core::renderer); + m_state->render(); + SDL_RenderPresent(oshc::core::renderer); +} + +void StateEngine::update() +{ + if (m_state) + { + m_state->update(); + } + + change_state(); +} + +void StateEngine::set_next_state(const ProgramState state) +{ + m_next_state = state; +} + +ProgramState StateEngine::get_active_state_id() const +{ + return m_active_state; +} + +void StateEngine::change_state() +{ + // If the state needs to be changed + if (m_next_state == ProgramState::STATE_NULL) + { + return; + } + + // Delete the current state + if (m_state) + { + m_state->destroy(); + m_state = nullptr; + } + + // Change the state + switch (m_next_state) + { + case ProgramState::STATE_GAME: + m_state = std::make_unique(*this); + std::cout << "Switched state to game." << std::endl; + break; + + case ProgramState::STATE_MENU: + m_state = std::make_unique(*this); + std::cout << "Switched state to main menu." << std::endl; + break; + + case ProgramState::STATE_INIT: + m_state = std::make_unique(*this); + std::cout << "Switched state to init." << std::endl; + break; + + case ProgramState::STATE_INTRO: + m_state = std::make_unique(*this); + std::cout << "Switched state to intro." << std::endl; + break; + + default: + std::cout << "Exiting program." << std::endl; + break; + } + + m_state->init(); + + // Change the current state ID + m_active_state = m_next_state; + + // NULL the next state ID + m_next_state = ProgramState::STATE_NULL; +} + +} // namespace oshc::core::state \ No newline at end of file diff --git a/src/core/state.h b/src/core/state.h new file mode 100644 index 0000000..9d9c7e6 --- /dev/null +++ b/src/core/state.h @@ -0,0 +1,132 @@ +/** + * @file state.h + * @author Allan Legemaate (alegemaate@gmail.com) + * @brief Central game state management + * @version 0.1 + * @date 2023-12-16 + * + * @copyright Copyright (c) 2023 + * + */ + +#pragma once + +#include + +namespace oshc::core::state +{ + +// Class +class State; + +/** + * @brief Enum of possible game states + * + */ +enum class ProgramState +{ + STATE_INIT, + STATE_INTRO, + STATE_GAME, + STATE_MENU, + STATE_EXIT, + STATE_NULL +}; + +/** + * @brief State engine + * + */ +class StateEngine +{ + public: + /** + * @brief Update active state + * + */ + void update(); + + /** + * @brief Render active state + * + */ + void render() const; + + /** + * @brief Set the Next State + * + * @param state Next state id + */ + void set_next_state(const ProgramState state); + + /** + * @brief Get the active state id + * + * @return ProgramState + */ + ProgramState get_active_state_id() const; + + private: + /** + * @brief Internal state change routine + * + */ + void change_state(); + + /// @breif Next state in queue + ProgramState m_next_state{ProgramState::STATE_NULL}; + + /// @breif Active state + ProgramState m_active_state{ProgramState::STATE_NULL}; + + /// @brief Pointer to active state + std::unique_ptr m_state{nullptr}; +}; + +/** + * @brief State base class + * + */ +class State +{ + public: + /** + * @brief Create a new state + * + * @param engine State engine reference + */ + explicit State(StateEngine &engine) : engine(engine){}; + + /// @brief Default destructor + virtual ~State() = default; + + /** + * @brief On state init + * + */ + virtual void init() = 0; + + /** + * @brief On state render + * + */ + virtual void render() = 0; + + /** + * @brief On state destroy + * + */ + virtual void destroy() = 0; + + /** + * @brief On state update + * + */ + virtual void update() = 0; + + private: + /// @brief State engine reference + StateEngine &engine; +}; + +} // namespace oshc::core::state diff --git a/src/game/state_game.h b/src/game/state_game.h new file mode 100644 index 0000000..4d8af95 --- /dev/null +++ b/src/game/state_game.h @@ -0,0 +1,58 @@ +/** + * @file state_game.h + * @author Allan Legemaate (alegemaate@gmail.com) + * @brief Game state + * @version 0.1 + * @date 2023-12-16 + * + * @copyright Copyright (c) 2023 + * + */ + +#pragma once + +#include "../core/state.h" + +namespace oshc::state +{ +class StateGame : public oshc::core::state::State +{ + public: + /** + * @brief Construct a new State Game object + * + */ + explicit StateGame(oshc::core::state::StateEngine &engine) : oshc::core::state::State(engine){}; + + /** + * @brief Destroy the State Game object + * + */ + ~StateGame() override = default; + + /** + * @brief Init state + * + */ + void init() override{}; + + /** + * @brief Update state + * + */ + void update() override{}; + + /** + * @brief Render state + * + */ + void render() override{}; + + /** + * @brief Destroy state + * + */ + void destroy() override{}; +}; + +} // namespace oshc::state diff --git a/src/game/state_init.cpp b/src/game/state_init.cpp new file mode 100644 index 0000000..c981ef8 --- /dev/null +++ b/src/game/state_init.cpp @@ -0,0 +1,36 @@ +#include "state_init.h" + +#include "../core/core.h" + +namespace oshc::state +{ + +void StateInit::init() +{ + oshc::core::init(); + oshc::core::asset_manager.init("assets/config/assets.json"); + oshc::core::asset_manager.get_music("a_pane_in_the_glass").play(); + + m_fake_loader = SDL_GetTicks(); +} + +void StateInit::update() +{ + if (SDL_GetTicks() - m_fake_loader > 3000) + { + oshc::core::state_engine.set_next_state(oshc::core::state::ProgramState::STATE_MENU); + } +} + +void StateInit::render() +{ + SDL_RenderCopy(oshc::core::renderer, oshc::core::asset_manager.get_texture("frontend_loading").get().get(), nullptr, + nullptr); +} + +void StateInit::destroy() +{ + oshc::core::asset_manager.get_music("a_pane_in_the_glass").stop(); +} + +} // namespace oshc::state \ No newline at end of file diff --git a/src/game/state_init.h b/src/game/state_init.h new file mode 100644 index 0000000..33efd5b --- /dev/null +++ b/src/game/state_init.h @@ -0,0 +1,62 @@ +/** + * @file state_init.h + * @author Allan Legemaate (alegemaate@gmail.com) + * @brief Initial state + * @version 0.1 + * @date 2023-12-16 + * + * @copyright Copyright (c) 2023 + * + */ + +#pragma once + +#include "../core/state.h" + +namespace oshc::state +{ +class StateInit : public oshc::core::state::State +{ + public: + /** + * @brief Construct a new State Init object + * + */ + explicit StateInit(oshc::core::state::StateEngine &engine) : oshc::core::state::State(engine){}; + + /** + * @brief Destroy the State Init object + * + */ + ~StateInit() override = default; + + /** + * @brief Init state + * + */ + void init() override; + + /** + * @brief Update state + * + */ + void update() override; + + /** + * @brief Render state + * + */ + void render() override; + + /** + * @brief Destroy state + * + */ + void destroy() override; + + private: + /// @brief Fake loader + int m_fake_loader = 0; +}; + +} // namespace oshc::state diff --git a/src/game/state_intro.h b/src/game/state_intro.h new file mode 100644 index 0000000..41d74e6 --- /dev/null +++ b/src/game/state_intro.h @@ -0,0 +1,63 @@ +/** + * @file state_intro.h + * @author Allan Legemaate (alegemaate@gmail.com) + * @brief Intro state + * @version 0.1 + * @date 2023-12-16 + * + * @copyright Copyright (c) 2023 + * + */ + +#pragma once + +#include "../core/state.h" + +namespace oshc::state +{ + +/** + * @brief Intro state + * + */ +class StateIntro : public oshc::core::state::State +{ + public: + /** + * @brief Construct a new State Intro object + * + */ + explicit StateIntro(oshc::core::state::StateEngine &engine) : oshc::core::state::State(engine){}; + + /** + * @brief Destroy the State Intro object + * + */ + ~StateIntro() override = default; + + /** + * @brief Init state + * + */ + void init() override{}; + + /** + * @brief Update state + * + */ + void update() override{}; + + /** + * @brief Render state + * + */ + void render() override{}; + + /** + * @brief Destroy state + * + */ + void destroy() override{}; +}; + +} // namespace oshc::state diff --git a/src/game/state_menu.cpp b/src/game/state_menu.cpp new file mode 100644 index 0000000..d5909bd --- /dev/null +++ b/src/game/state_menu.cpp @@ -0,0 +1,14 @@ +#include "state_menu.h" + +#include "../core/core.h" +#include + +namespace oshc::state +{ +void StateMenu::render() +{ + SDL_RenderCopy(oshc::core::renderer, oshc::core::asset_manager.get_texture("frontend_main").get().get(), nullptr, + nullptr); +} + +} // namespace oshc::state \ No newline at end of file diff --git a/src/game/state_menu.h b/src/game/state_menu.h new file mode 100644 index 0000000..1adf5e6 --- /dev/null +++ b/src/game/state_menu.h @@ -0,0 +1,58 @@ +/** + * @file state_menu.h + * @author Allan Legemaate (alegemaate@gmail.com) + * @brief Menu state + * @version 0.1 + * @date 2023-12-16 + * + * @copyright Copyright (c) 2023 + * + */ + +#pragma once + +#include "../core/state.h" + +namespace oshc::state +{ +class StateMenu : public oshc::core::state::State +{ + public: + /** + * @brief Construct a new State Menu object + * + */ + explicit StateMenu(oshc::core::state::StateEngine &engine) : oshc::core::state::State(engine){}; + + /** + * @brief Destroy the State Menu object + * + */ + ~StateMenu() override = default; + + /** + * @brief Init state + * + */ + void init() override{}; + + /** + * @brief Update state + * + */ + void update() override{}; + + /** + * @brief Render state + * + */ + void render() override; + + /** + * @brief Destroy state + * + */ + void destroy() override{}; +}; + +} // namespace oshc::state diff --git a/src/main.cpp b/src/main.cpp index 92777df..185dec1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,25 +3,40 @@ #include #include -#include "core/asset_manager.h" #include "core/core.h" +/** + * @brief Entry point for the program + * + * @param argc Number of arguments + * @param argv Array of arguments + * @return int Exit code + */ int main(int argc, char **argv) { - oshc::core::init(); - oshc::core::asset_manager.init("assets/config/assets.json"); + oshc::core::state_engine.set_next_state(oshc::core::state::ProgramState::STATE_INIT); - std::cout << "Hello World!" << std::endl; - - oshc::core::asset_manager.get_music("a_pane_in_the_glass").play(); - - for (int i = 0; i < 100; i++) + while (oshc::core::state_engine.get_active_state_id() != oshc::core::state::ProgramState::STATE_EXIT) { - SDL_RenderClear(oshc::core::renderer); - SDL_RenderCopy(oshc::core::renderer, oshc::core::asset_manager.get_texture("frontend_loading").get().get(), - nullptr, nullptr); - SDL_RenderPresent(oshc::core::renderer); - SDL_Delay(100); + oshc::core::state_engine.update(); + oshc::core::state_engine.render(); + + // Check if esc pressed + SDL_Event event; + while (SDL_PollEvent(&event)) + { + if (event.type == SDL_QUIT) + { + oshc::core::state_engine.set_next_state(oshc::core::state::ProgramState::STATE_EXIT); + } + if (event.type == SDL_KEYDOWN) + { + if (event.key.keysym.sym == SDLK_ESCAPE) + { + oshc::core::state_engine.set_next_state(oshc::core::state::ProgramState::STATE_EXIT); + } + } + } } return 0;