From 25e62e3bdd2e26f4c3e717e4c9231cf08d37af0e Mon Sep 17 00:00:00 2001 From: Tomas Vybiral <tomas@vybiral.me> Date: Tue, 15 Dec 2020 20:02:43 +0100 Subject: [PATCH] levels and game victory --- resources/levels/01.txt | 10 ++++ src/game/CMakeLists.txt | 1 - src/game/game.cpp | 27 ++++++++- src/game/game.h | 2 + src/game/input/abstract_input.h | 6 +- src/game/input/events.hpp | 5 ++ src/game/input/sdl_input.h | 4 +- src/game/renderer/abstract_renderer.h | 4 +- src/game/renderer/sdl_renderer.cpp | 30 ++++++++-- src/game/renderer/sdl_renderer.h | 2 + src/game/state/CMakeLists.txt | 3 +- src/game/state/level.cpp | 85 +++++++++++++++++++++++++++ src/game/state/level.h | 67 +++++++++++++++++++++ src/game/state/state.cpp | 2 + src/game/state/state.h | 3 + 15 files changed, 237 insertions(+), 14 deletions(-) create mode 100644 resources/levels/01.txt create mode 100644 src/game/state/level.cpp create mode 100644 src/game/state/level.h diff --git a/resources/levels/01.txt b/resources/levels/01.txt new file mode 100644 index 0000000..a53f3f6 --- /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 a23c6db..bd1b739 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 e557ca8..4d7fcbc 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 ac0cf98..11d0a96 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 433b6a0..d86a83a 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 5cd573e..85946cd 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 2d290f0..ed5be6e 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 222771a..800eba2 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 973d1f7..b903e5c 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 69396b5..21b4474 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 42f8be0..debce46 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 0000000..40912b7 --- /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 0000000..4247def --- /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 fc09964..9676aa9 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 2e863bf..5ca28e4 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: -- GitLab