diff --git a/resources/levels/01.txt b/resources/levels/01.txt new file mode 100644 index 0000000000000000000000000000000000000000..a53f3f6c3645ee5cf43ddf60bb9ef0d46174ed71 --- /dev/null +++ b/resources/levels/01.txt @@ -0,0 +1,10 @@ +LEVEL: + start +NEXT: + level_01 +ENEMIES: + 120 240 + 240 240 + 360 240 + 480 240 + 600 240 \ No newline at end of file diff --git a/src/game/CMakeLists.txt b/src/game/CMakeLists.txt index a23c6dbcd0a0dfbac900e7db99dec8e7551aa323..bd1b739cb642f979209b3e27ca833c3d31a6e2f4 100644 --- a/src/game/CMakeLists.txt +++ b/src/game/CMakeLists.txt @@ -1,5 +1,4 @@ add_subdirectory(state) -add_subdirectory(levels) add_subdirectory(input) add_subdirectory(renderer) diff --git a/src/game/game.cpp b/src/game/game.cpp index e557ca81552b6477bbb46e0aef0a0b31e8211578..4d7fcbcbe1a7ef74cff40c30ef4f604ffb5dd711 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -5,6 +5,7 @@ #include "renderer/sdl_renderer.h" #include "single_shot.h" #include "multi_shot.h" +#include "state/level.h" #include <variant> #include <chrono> @@ -28,6 +29,14 @@ game::game(uint32_t fps):current_state(), shoot_control = new single_shot(); spdlog::get("game")->debug("Fps set to: {} and delta_time set to: {}", fps, delta_time); spdlog::get("game")->info("Finished game component initialization."); + + spdlog::get("game")->info("Loading levels"); + level::create().from_file("levels/01.txt"); + level::create().with_enemy_at({320, 120}).with_enemy_at({160, 240}).with_enemy_at({480, 360}).save_as("level_01"); + + level first_level = std::move(level::get("start")); + current_state.enemies = first_level.enemies; + current_state.next_level_id = first_level.next_level_id; } game::~game() { @@ -50,6 +59,8 @@ void game::run_loop() { process_events(); update_state(); render_current_state(); + if (try_to_switch_level()) + break; auto end = std::chrono::system_clock::now(); std::chrono::duration<double> diff = end - start; SDL_Delay(static_cast<uint32_t>((delta_time - diff.count())*1000)); @@ -57,6 +68,20 @@ void game::run_loop() { spdlog::get("game")->info("Game loop ended"); } +bool game::try_to_switch_level() { + if (current_state.enemies.size() == 0 && current_state.projectiles.size() == 0) { + if (current_state.next_level_id != "") { + level next_level = std::move(level::get(current_state.next_level_id)); + current_state.enemies = next_level.enemies; + current_state.next_level_id = next_level.next_level_id; + } else { + renderer->show_win_dialog(); + return true; + } + } + return false; +} + void game::process_events() { input_processor->poll_events(); while(input_processor->has_events()) { @@ -90,7 +115,7 @@ void game::process_events() { current_state = std::move(state::pop()); }); do_event<shoot_event>(e, [&](auto&) {shoot = true;}); - do_event<switch_controller>(e, [&](auto& sc) { + do_event<switch_controller>(e, [&](const auto& sc) { if (shoot_control->can_switch()) { delete shoot_control; switch(sc.mode) { diff --git a/src/game/game.h b/src/game/game.h index ac0cf98cf51ac02309facd3bf61773874658472b..11d0a96fd3e1990213c4b1a2d9a45618a6a06580 100644 --- a/src/game/game.h +++ b/src/game/game.h @@ -30,6 +30,8 @@ protected: /// Helper function that processes event of defined type using provided funtion (lambda) template<typename T> void do_event(const event& event, const std::function<void(const T&)>& trigger); + /// Tries to load next level. Returns true if can't load any new levels. + bool try_to_switch_level(); /// Current game state state current_state; diff --git a/src/game/input/abstract_input.h b/src/game/input/abstract_input.h index 433b6a000480cae3427c0a83f361fc1f86d3cb24..d86a83a4444b99dbd6327f1402ff4f345b3ef416 100644 --- a/src/game/input/abstract_input.h +++ b/src/game/input/abstract_input.h @@ -4,17 +4,17 @@ #include <vector> #include "events.hpp" -/** - * Abstract class that defines common interface for all input processors - */ +/// Abstract class that defines common interface for all input processors class abstract_input { public: + /// Virtual destructor virtual ~abstract_input() = default; /// Check if there are any events to be proccessed bool has_events() const; /// Tryies to get next evet. Can throw exception if there is no event to be pulled event& get_next_event(); + /// virtual function to poll events from underlying api virtual void poll_events() = 0; protected: size_t index; ///< Current index of polled event diff --git a/src/game/input/events.hpp b/src/game/input/events.hpp index 5cd573eda19deb460e03eeb40c9fff2186f10ddf..85946cdcfa1a89f32a8b39e3e5ec6c476fca2e41 100644 --- a/src/game/input/events.hpp +++ b/src/game/input/events.hpp @@ -4,6 +4,11 @@ #include <variant> #include <cstdint> +template<typename T> +struct change { + T data; +}; + struct change_move_vector { constexpr static int32_t DEFAULT = 5; int32_t delta; diff --git a/src/game/input/sdl_input.h b/src/game/input/sdl_input.h index 2d290f03cc205c38144d6d16330196bad18ec3a6..ed5be6e6e7bac6dfc5430ba7815a2c077aa220d3 100644 --- a/src/game/input/sdl_input.h +++ b/src/game/input/sdl_input.h @@ -4,9 +4,7 @@ #include "abstract_input.h" #include <SDL2/SDL.h> -/** - * Processes user inputs using SDL's own system. - */ +/// Processes user inputs using SDL's own system. class sdl_input: public abstract_input { public: /// Initializes SDL input system diff --git a/src/game/renderer/abstract_renderer.h b/src/game/renderer/abstract_renderer.h index 222771a9c4545cf9ec293203affc40b0a0100efd..800eba20669960a40d49bd125d95c161353111a5 100644 --- a/src/game/renderer/abstract_renderer.h +++ b/src/game/renderer/abstract_renderer.h @@ -7,9 +7,11 @@ class abstract_renderer{ public: /// Virtual destructor to prevent memory leaks - virtual ~abstract_renderer() {} + virtual ~abstract_renderer() = default; /// Virtual draw method that is called by the game controller. virtual void render_state(const state& current_state, bool debug_ui) = 0; + /// Shows game win dialog + virtual void show_win_dialog() = 0; }; #endif//ABSTRACT_RENDERER_H \ No newline at end of file diff --git a/src/game/renderer/sdl_renderer.cpp b/src/game/renderer/sdl_renderer.cpp index 973d1f7c9a45ae9d758e487fd7cd818f865a35e7..b903e5cab26554a3272db49d22c679745aa604a9 100644 --- a/src/game/renderer/sdl_renderer.cpp +++ b/src/game/renderer/sdl_renderer.cpp @@ -49,7 +49,7 @@ void sdl_renderer::render_state(const state& cs, bool debug_ui) { cs.player.position.get() - static_cast<int>(16*cs.player.force.get()*sin(cs.player.angle.get()))); SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); - // render_enemies(cs.enemies); + render_enemies(cs.enemies); render_projectiles(cs.projectiles); render_ui(cs, debug_ui); SDL_RenderPresent(renderer); @@ -82,9 +82,24 @@ void sdl_renderer::render_text(const std::string& text, const vector2& pos) { SDL_FreeSurface(text_surface); } -// void sdl_renderer::render_enemies(const std::vector<enemy>& enemies) { - -// } +void sdl_renderer::render_enemies(const std::vector<enemy>& enemies) { + static vector2 enemy_pic_size{32, 32}; + for (auto& en: enemies) { + vector2 pos{en.position.x - static_cast<int>(en.radius), + en.position.y - static_cast<int>(en.radius)}; + switch(en.variant) { + case 0: + render_texture(resources->enemy1, pos, enemy_pic_size); + break; + case 1: + render_texture(resources->enemy2, pos, enemy_pic_size); + break; + default: + render_texture(resources->enemy3, pos, enemy_pic_size); + break; + } + } +} void sdl_renderer::render_projectiles(const std::vector<projectile>& projectiles) { static vector2 projectile_size{30, 30}; @@ -108,4 +123,11 @@ void sdl_renderer::render_ui(const state& cs, bool debug) { } oss << "SCORE: " << cs.score; render_text(oss.str(), {2, 0}); +} + +void sdl_renderer::show_win_dialog() { + if (SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION, "Victory", + "Congratulation you have WON the game.", window) != 0) { + throw std::runtime_error(SDL_GetError()); + } } \ No newline at end of file diff --git a/src/game/renderer/sdl_renderer.h b/src/game/renderer/sdl_renderer.h index 69396b59b0ae047c4cc93705abbafd03e858c125..21b4474c918f83de4901fad07692a3077e4184dc 100644 --- a/src/game/renderer/sdl_renderer.h +++ b/src/game/renderer/sdl_renderer.h @@ -16,6 +16,8 @@ public: /// Renders passed state to created window void render_state(const state& current_state, bool debug_ui) override; + /// Renders win dialog uisng SDL api + void show_win_dialog() override; protected: /// Helper function that renders all enemies to screen. void render_enemies(const std::vector<enemy>& enemies); diff --git a/src/game/state/CMakeLists.txt b/src/game/state/CMakeLists.txt index 42f8be012cc2b1c65f5fd1b3333fc80cdb766614..debce46533faead015c58d62e870ebbf62700ee2 100644 --- a/src/game/state/CMakeLists.txt +++ b/src/game/state/CMakeLists.txt @@ -1,7 +1,8 @@ add_library(state state.h state.cpp shooter.h shooter.cpp - entities.h entities.cpp) + entities.h entities.cpp + level.h level.cpp) target_include_directories(state PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/src/game/state/level.cpp b/src/game/state/level.cpp new file mode 100644 index 0000000000000000000000000000000000000000..40912b701dda7aea9d78d8aed6ffe4325ff2ace6 --- /dev/null +++ b/src/game/state/level.cpp @@ -0,0 +1,85 @@ +#include "level.h" + +#include <stdexcept> +#include <memory> +#include <fstream> + +std::map<std::string, level> level::levels; + +level::level(): enemies(), next_level_id() { } + +level::level(const level& other): enemies(other.enemies), + next_level_id(other.next_level_id) { } + +level& level::operator=(const level& other) { + enemies = other.enemies; + next_level_id = other.next_level_id; + return *this; +} + +level_creator level::create() { + return level_creator(); +} + +level level::get(const std::string& level_id) { + return level::levels.at(level_id); +} + +level_creator::level_creator(): to_be_created() { } + +level_creator& level_creator::with_enemy_at(const vector2& position) { + to_be_created.enemies.emplace_back(position, 16); + return *this; +} + +level_creator& level_creator::set_next_level(const std::string& level_id) { + to_be_created.next_level_id = level_id; + return *this; +} + +void level_creator::save_as(const std::string& level_id) { + level::levels[level_id] = to_be_created; +} + +void level_creator::from_file(const std::string& path_to_file) { + std::ifstream ifs(path_to_file); + if (!ifs.good()) { + throw std::runtime_error("Can't find level file at: " + path_to_file); + } + std::string tmp, id; + ifs >> tmp; + if (tmp != "LEVEL:" || !ifs.good()) { + throw std::runtime_error("Can't load level info. LEVEL id must be first information"); + } + ifs >> id; + if (id.size() == 0 || !ifs.good()) { + throw std::runtime_error("Can't load level info. No id data"); + } + ifs >> tmp; + if (tmp != "NEXT:" || !ifs.good()) { + throw std::runtime_error("Can't load level info. No id data or second entry isn't next level id."); + } + ifs >> to_be_created.next_level_id; + if (to_be_created.next_level_id.size() == 0 || !ifs.good()) { + throw std::runtime_error("Can't load level info. No next level id data found."); + } + ifs >> tmp; + if (tmp != "ENEMIES:" || !ifs.good()) { + throw std::runtime_error("Can't load level info. No next level id data found or missing ENEMIES tag."); + } + while (true) { + vector2 position; + ifs >> position.x >> position.y; + if (!ifs.good()) + break; + to_be_created.enemies.emplace_back(position, 16); + } + if (to_be_created.enemies.size() == 0) { + throw std::runtime_error("Can't load level info. No enemies data were suplied."); + } + level::levels[id] = to_be_created; +} + +level level_creator::get() { + return to_be_created; +} \ No newline at end of file diff --git a/src/game/state/level.h b/src/game/state/level.h new file mode 100644 index 0000000000000000000000000000000000000000..4247deff323ab965832aedb83f94d58f76d0c3f8 --- /dev/null +++ b/src/game/state/level.h @@ -0,0 +1,67 @@ +#ifndef LEVEL_H +#define LEVEL_H + +#include <map> +#include <string> +#include <vector> + +#include "entities.h" + +class level_creator; + +/// Level data structure. Contains list of enemies to spawn and next level id. +class level { +public: + /// Default contructor - creates empty level with empty next level id. + level(); + /// Copy constructor - to be able to pass level from funtions + level(const level& other); + /// Copy operator + level& operator=(const level& other); + + /// List of all enemies that are going to be spawned in this level. + std::vector<enemy> enemies; + /// Next level id. If empty than games should end. + std::string next_level_id; + + /// Static map of all levels in level_id -> level relation. + static std::map<std::string, level> levels; + + /// Creates level_creator class that can create/load level from scratch + static level_creator create(); + /// Returns level by defined level id. Throws out of range error if level with defined id doesn't exist. + static level get(const std::string& level_id); +}; + +/// Class that is able to create/load level data. +class level_creator { +public: + /// Creates basic level_creator + level_creator(); + /// Copy contructor + level_creator(const level_creator& other) = default; + + /// Adds enemy at defined position + [[nodiscard]] level_creator& with_enemy_at(const vector2& position); + /// Set next level id to current level + [[nodiscard]] level_creator& set_next_level(const std::string& level_id); + /// Saves level with defined id. + void save_as(const std::string& level_id); + /// Loads level from file. + /// If file doesn't exists it throws an error. + /// File must be in this format: + /// LEVEL: + /// {level_id} + /// NEXT: + /// {next_level_id} + /// ENEMIES: + /// {list of positions as pairs of number seperated by white spaces} + void from_file(const std::string& path_to_file); + /// Returns currently created level + [[nodiscard]] level get(); +protected: + /// Level that is being created + level to_be_created; +}; + +#endif//LEVEL_H \ No newline at end of file diff --git a/src/game/state/state.cpp b/src/game/state/state.cpp index fc09964da961f7143d6e3abe3f9a3e7428221966..9676aa9bf6aee57618690a966174515a97686eb7 100644 --- a/src/game/state/state.cpp +++ b/src/game/state/state.cpp @@ -24,6 +24,8 @@ state& state::operator=(const state& other) { player = other.player; enemies = other.enemies; projectiles = other.projectiles; + score = other.score; + next_level_id = other.next_level_id; return *this; } diff --git a/src/game/state/state.h b/src/game/state/state.h index 2e863bfcadb8d36f9bbb39655965240c4ee94629..5ca28e4e103f69df313b9a947c62fb67a786d8ea 100644 --- a/src/game/state/state.h +++ b/src/game/state/state.h @@ -3,6 +3,7 @@ #include <list> #include <vector> +#include <string> #include "entities.h" #include "shooter.h" @@ -35,6 +36,8 @@ public: std::vector<projectile> projectiles; /// Current game score uint32_t score; + /// Id of next level to load to this state + std::string next_level_id; /// Check if there are enough stored states static bool can_pop(); protected: