Compare commits

..

26 Commits

Author SHA1 Message Date
m
73b49dd403 Pushing whats there 2024-11-22 13:46:04 -05:00
m
70b762b2ce Some corrections to cellular automata generation 2023-07-23 15:41:01 -04:00
53e3bf7fa4 Merge pull request 'feature/rla-19_Zoom' (#20) from feature/rla-19_Zoom into master
Reviewed-on: #20
2023-07-23 18:39:48 +00:00
m
ddfa3c1dc9 Zoom fully working and code cleaned up, scaling x and y now done in game rather than rendering, closes #19 2023-07-23 14:39:16 -04:00
m
3d056481b2 Merge branch 'master' into feature/rla-19_Zoom 2023-07-23 14:34:34 -04:00
m
206868efc5 Working zoom 2023-07-23 14:33:59 -04:00
f2f875a976 Merge pull request 'Implements cellular automata, closes #13' (#16) from feature/rla-13_CellularAutomata into master
Reviewed-on: #16
2023-07-23 17:45:59 +00:00
m
a11e4fa5a1 Implements cellular automata, closes #13 2023-07-23 13:45:25 -04:00
f5bd7c949a Merge pull request 'Removes the test alog, cleans up some renderer stuff, starts work on cellular automata, closes #14' (#15) from feature/rla-14_AlgorithmsUI into master
Reviewed-on: #15
2023-07-23 14:58:44 +00:00
m
f6b6293532 Removes the test alog, cleans up some renderer stuff, starts work on cellular automata, closes #14 2023-07-23 10:58:01 -04:00
daef8463bf Merge pull request 'feature/rla-11_AlgorithmsP1' (#12) from feature/rla-11_AlgorithmsP1 into master
Reviewed-on: #12
2023-07-23 05:04:15 +00:00
m
abf3e3a719 Cleaned up drunken walk, closes #11 2023-07-23 01:03:10 -04:00
m
d7fa6069f8 Drunken walk working but needs some updates as it can go 'off map' and then walk back in, effectively 2023-07-23 00:57:19 -04:00
55e2186c86 Merge pull request 'feature/rla-7_Logging' (#10) from feature/rla-7_Logging into master
Reviewed-on: #10
2023-07-19 02:01:13 +00:00
m
dbf0458f82 Adds engine configurable minimum logging level, closes #7 2023-07-18 22:00:20 -04:00
m
f71ecc432a Adds basic logging setup using a static logger and a few logging levels 2023-07-18 21:56:06 -04:00
8f454f6cdc Merge pull request 'Switches to a rendering pipeline with separated out steps, closes #8' (#9) from feature/rla-8_RenderingPipeline into master
Reviewed-on: #9
2023-07-18 22:38:12 +00:00
m
ec07eeda57 Switches to a rendering pipeline with separated out steps, closes #8 2023-07-18 18:36:38 -04:00
m
16ca6de3d4 Switch to vcpkg from Conan after something broke our build and the Conan support in clion seems outdated 2023-07-17 23:35:05 -04:00
a01ff2ec0e Renderers no longer have an init function and are expected to be prepared in their constructor (whatever it is)
We can do something similar for input processor (probably a good idea?) or go with the plan for game and use a template, engine can know about the renderers as long as it treats them as generic pointers, but shouldn't know about the game
Game can either be a templated make_unique or we can just enforce a game class naming
Or pass game in at start loop
2022-01-20 08:49:32 -05:00
bc6fa836b0 Moved out renderer to being selected in engine constructor based on a parsed enum. Next is to have the renderer initialize in constructor, and probably make game into a templated parameter that does the same along with input processor 2022-01-20 08:20:17 -05:00
1a917af13c fixed several memory leaks caused by not including virtual destructors, added a config for the visualizer, next up is lua 2021-12-22 12:33:47 -05:00
00abbbebbd Fixed an issue with lua context manager, tested to make sure it worked, started a document for the algorithm lua structure, updated engine loop 2021-12-12 23:36:13 -05:00
8213dd8cb3 started pulling in lua stuff, fixed an issue with camera range 2021-12-12 17:17:09 -05:00
d5fd89e228 ranges done, cool stuff 2021-12-12 12:06:31 -05:00
f360be0c8d About to test out ranges of points 2021-12-12 10:42:53 -05:00
42 changed files with 993 additions and 146 deletions

1
.gitignore vendored
View File

@@ -1 +1,2 @@
/cmake-build-debug/ /cmake-build-debug/
.name

View File

@@ -1,12 +1,36 @@
set(CMAKE_CXX_STANDARD 20) # c++ 20 set(CMAKE_CXX_STANDARD 20) # c++ 20
cmake_minimum_required(VERSION 3.22) # Specifies the required CMake version. set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
cmake_minimum_required(VERSION 3.16) # Specifies the required CMake version.
project(rla_iipp) # Defines the project name. project(rla_iipp) # Defines the project name.
include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake) # Includes the contents of the conanbuildinfo.cmake file. #[[include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake) # Includes the contents of the conanbuildinfo.cmake file.
conan_basic_setup() # Prepares the CMakeList.txt for Conan. conan_basic_setup() # Prepares the CMakeList.txt for Conan.]]
# $source_files is a space-delimited list of filenames. # $source_files is a space-delimited list of filenames.
add_executable(rla_iipp src/main.cpp src/engine/engine.hpp src/engine/game/game.hpp src/engine/rendering/renderer.hpp src/game/visualizer.hpp src/game/visualizer.cpp src/engine/rendering/sdl/sdlrenderer.cpp src/engine/rendering/sdl/sdlrenderer.hpp src/engine/rendering/sdl/sdlrenderer.cpp src/engine/rendering/sdl/sdlrenderer.hpp src/engine/engine.cpp src/engine/engine.hpp src/engine/rendering/sdl/sdltexturemanager.cpp src/engine/rendering/sdl/sdltexturemanager.hpp src/engine/resources/resourcemanager.hpp src/engine/rendering/sdl/sdlfontmanager.cpp src/engine/rendering/sdl/sdlfontmanager.hpp src/engine/input/inputprocessor.hpp src/engine/input/sdlinputprocessor.cpp src/engine/input/sdlinputprocessor.hpp src/engine/utility/camera.cpp src/engine/utility/camera.hpp src/engine/utility/point.hpp src/engine/utility/rectangle.hpp src/engine/utility/grid2d.hpp src/game/dungeonalgorithm.cpp src/game/dungeonalgorithm.hpp) # Specifies the executable to build. add_executable(rla_iipp src/main.cpp src/engine/engine.hpp src/engine/game/game.hpp src/engine/rendering/renderer.hpp src/game/visualizer.hpp src/game/visualizer.cpp src/engine/rendering/sdl/sdlrenderer.cpp src/engine/rendering/sdl/sdlrenderer.hpp src/engine/rendering/sdl/sdlrenderer.cpp src/engine/rendering/sdl/sdlrenderer.hpp src/engine/engine.cpp src/engine/engine.hpp src/engine/rendering/sdl/sdltexturemanager.cpp src/engine/rendering/sdl/sdltexturemanager.hpp src/engine/resources/resourcemanager.hpp src/engine/rendering/sdl/sdlfontmanager.cpp src/engine/rendering/sdl/sdlfontmanager.hpp src/engine/input/inputprocessor.hpp src/engine/input/sdlinputprocessor.cpp src/engine/input/sdlinputprocessor.hpp src/engine/utility/camera.cpp src/engine/utility/camera.hpp src/engine/utility/point.hpp src/engine/utility/rectangle.hpp src/engine/utility/grid2d.hpp src/engine/scripting/luacontextmanager.cpp src/engine/scripting/luacontextmanager.hpp src/game/algorithmmanager.cpp src/game/algorithmmanager.hpp src/game/tilemap.cpp src/game/tilemap.hpp src/engine/rlengineexception.hpp src/engine/rendering/renderingpipelinestep.hpp src/engine/rendering/pipeline_steps/gamerenderingstep.cpp src/engine/rendering/pipeline_steps/gamerenderingstep.hpp src/engine/rendering/pipeline_steps/fpscounterrenderingstep.cpp src/engine/rendering/pipeline_steps/fpscounterrenderingstep.hpp src/engine/modules/logger.cpp src/engine/modules/logger.hpp) # Specifies the executable to build.
target_link_libraries(rla_iipp ${CONAN_LIBS}) # Specifies what libraries to link, using Conan.
# vcpkg packages
find_package(SDL2 CONFIG REQUIRED)
target_link_libraries(rla_iipp
PRIVATE
$<TARGET_NAME_IF_EXISTS:SDL2::SDL2main>
$<IF:$<TARGET_EXISTS:SDL2::SDL2>,SDL2::SDL2,SDL2::SDL2-static>
)
find_package(SDL2_ttf CONFIG REQUIRED)
target_link_libraries(rla_iipp PRIVATE $<IF:$<TARGET_EXISTS:SDL2_ttf::SDL2_ttf>,SDL2_ttf::SDL2_ttf,SDL2_ttf::SDL2_ttf-static>)
find_package(SDL2_image CONFIG REQUIRED)
target_link_libraries(rla_iipp PRIVATE $<IF:$<TARGET_EXISTS:SDL2_image::SDL2_image>,SDL2_image::SDL2_image,SDL2_image::SDL2_image-static>)
find_package(fmt CONFIG REQUIRED)
target_link_libraries(rla_iipp PRIVATE fmt::fmt)
find_package(tomlplusplus CONFIG REQUIRED)
target_link_libraries(rla_iipp PRIVATE tomlplusplus::tomlplusplus)
find_package(sol2 CONFIG REQUIRED)
target_link_libraries(rla_iipp PRIVATE lua)
target_link_libraries(rla_iipp PRIVATE sol2)
find_package(range-v3 CONFIG REQUIRED)
target_link_libraries(rla_iipp PRIVATE range-v3::meta range-v3::concepts range-v3::range-v3)
#[[target_link_libraries(rla_iipp ${CONAN_LIBS}) # Specifies what libraries to link, using Conan.]]
file(COPY assets DESTINATION ${CMAKE_BINARY_DIR}/bin) file(COPY assets DESTINATION ${CMAKE_BINARY_DIR}/bin)
file(COPY config DESTINATION ${CMAKE_BINARY_DIR}/bin) file(COPY config DESTINATION ${CMAKE_BINARY_DIR}/bin)
file(COPY scripts DESTINATION ${CMAKE_BINARY_DIR}/bin)

View File

@@ -1,10 +0,0 @@
[requires]
sdl/2.0.16
sdl_ttf/2.0.15
sdl_image/2.0.5
fmt/8.0.1
tomlplusplus/2.5.0
sol2/3.2.3
range-v3/0.11.0
[generators]
cmake

View File

@@ -3,10 +3,14 @@ target_fps = 61
window_width = 800 window_width = 800
window_height = 600 window_height = 600
window_title = "Roguelike Algorithm Visualizer" window_title = "Roguelike Algorithm Visualizer"
rendering_engine = 0
[overlay] [overlay]
show_fps = true show_fps = true
[logging]
minimum_level = 1
[font] [font]
default_font_name = "Consolas-Regular" default_font_name = "Consolas-Regular"
default_font_size = 12 default_font_size = 12

9
config/visualizer.toml Normal file
View File

@@ -0,0 +1,9 @@
[rendering]
tile_size_w = 48
tile_size_h = 48
[map]
map_size_w = 80
map_size_h = 50
floor_char = '.'
wall_char = '#'

28
documents/algorithms.txt Normal file
View File

@@ -0,0 +1,28 @@
Dungeon generation algorithms are written in lua and dropped into scripting->algorithms. They are automatically parsed by the visualizer and assigned keys in the program to run them.
Algorithms are made up of a single lua file (possibly more with requires) with the following functionality:
Provided:
global algorithm_manager.register_algorithm(name, init_function, update_function, updates_per_second)
Registers an algorithm with the program with a given name, init function, update function, and updates per second
map.draw_floor(x, y)
Draws a floor on the map at a given x, y position
map.draw_wall(x, y)
draws a wall on the map at a given x, y position
map.fill(bool wall)
Fills the map with either floors (false) or walls (true)
map.is_wall(x, y)
returns true if map[x,y] is a wall, false otherwise
Necessary:
init_function(map_w, map_h)
Should initialize the function fresh with a given width and height.
update()
Called updates_per_second times per second. Should calculate new map state and draw on the map as needed. The map will have the previous state retained.
Returns true if finished, else false

View File

@@ -0,0 +1,84 @@
CellularAutomata = {
map_w = 0,
map_h = 0,
iterations = 0,
}
function CellularAutomata.get_neighbors(map, x, y)
local min_x = math.max(0, x-1)
local min_y = math.max(0, y-1)
local max_x = math.min(x+1, map:get_width()-1)
local max_y = math.min(y+1, map:get_height()-1)
local result = {}
for mx = min_x, max_x do
for my = min_y, max_y do
table.insert(result, {x = mx, y = my})
end
end
return result
end
function CellularAutomata:initialize(w, h)
self.map_w = w
self.map_h = h
self.iterations = 3
math.randomseed()
for mx = 0, w-1 do
for my = 0, h-1 do
local is_wall = math.random(0, 1) == 1
if is_wall then
visualizer.map:draw_wall(mx, my)
else
visualizer.map:draw_floor(mx, my)
end
end
end
end
function CellularAutomata:update()
if self.iterations <= 0 then
return true
end
local old_map = visualizer.map
local new_map = old_map:clone()
for mx = 0, self.map_w - 1 do
for my = 0, self.map_h - 1 do
local neighbors = self.get_neighbors(old_map, mx, my)
local expected_neighbors = 9
local walls = expected_neighbors - #neighbors
local required_walls = 5
--print(old_map:is_wall(mx, my))
--print(new_map:is_wall(mx, my))
for k,v in ipairs(neighbors) do
if old_map:is_wall(v.x, v.y) then
walls = walls + 1
end
end
if walls >= required_walls then
new_map:draw_wall(mx, my)
else
new_map:draw_floor(mx, my)
end
--print(old_map:is_wall(mx, my))
--print(new_map:is_wall(mx, my))
end
end
-- Now draw the new map onto the existing one
for mx = 0, self.map_w - 1 do
for my = 0, self.map_h - 1 do
if new_map:is_wall(mx, my) then
old_map:draw_wall(mx, my)
else
old_map:draw_floor(mx, my)
end
end
end
self.iterations = self.iterations - 1
return false
end
visualizer.algorithm_manager:register_algorithm("Cellular Automata", function (w, h) CellularAutomata:initialize(w, h) end, function () return CellularAutomata:update() end, 1)

View File

@@ -0,0 +1,36 @@
DrunkenWalk = {
curr_x = 0,
curr_y = 0,
map_w = 0,
map_h = 0,
iterations = 0
}
function DrunkenWalk:initialize(w, h)
math.randomseed()
self.map_w = w
self.map_h = h
self.iterations = 200
visualizer.map:fill(true)
self.curr_x = math.random(0, w-1)
self.curr_y = math.random(0, h-1);
end
function DrunkenWalk:update()
new_x = self.curr_x + math.random(-1, 1)
new_y = self.curr_y + math.random(-1, 1)
while new_x >= self.map_w or new_x < 0 or new_y >= self.map_h or new_y < 0 do
new_x = self.curr_x + math.random(-1, 1)
new_y = self.curr_y + math.random(-1, 1)
end
self.curr_x = new_x
self.curr_y = new_y
visualizer.map:draw_floor(self.curr_x, self.curr_y)
self.iterations = self.iterations - 1
if self.iterations == 0 then
return true
end
return false
end
visualizer.algorithm_manager:register_algorithm("Drunken Walk", function (w, h) DrunkenWalk:initialize(w, h) end, function () return DrunkenWalk:update() end, 60)

View File

@@ -3,7 +3,10 @@
// //
#include "engine.hpp" #include "engine.hpp"
#include <cstdio> #include "rendering/sdl/sdlrenderer.hpp"
#include "rlengineexception.hpp"
#include "rendering/pipeline_steps/gamerenderingstep.hpp"
#include "rendering/pipeline_steps/fpscounterrenderingstep.hpp"
#include <chrono> #include <chrono>
#include <thread> #include <thread>
#include <exception> #include <exception>
@@ -17,34 +20,51 @@ void Engine::start_loop() {
if(renderer == nullptr) { if(renderer == nullptr) {
throw MissingRendererException(); throw MissingRendererException();
} }
auto render_params = RendererParams{this->config.window_title, this->config.window_width, this->config.window_height, this->config.default_font};
renderer->initialize(render_params);
renderer->flush(); renderer->flush();
auto game_params = GameInitArgs{this->config.target_fps, this->config.window_width, this->config.window_height}; auto game_params = GameInitArgs{this->config.target_fps, this->config.window_width, this->config.window_height};
game->initialize(game_params); game->initialize(game_params);
Logger::log(LogLevel::DEBUG, "Engine init finished");
std::vector<std::unique_ptr<RenderingPipelineStep>> rendering_steps = build_render_pipline();
auto duration_per_frame = one_second_milli / this->config.target_fps; auto duration_per_frame = one_second_milli / this->config.target_fps;
auto last_frame = std::chrono::high_resolution_clock::now();
while(true) { while(true) {
auto starttime = std::chrono::high_resolution_clock::now(); auto starttime = std::chrono::high_resolution_clock::now();
// Input and update
auto input = this->input_processor->process_input(); auto input = this->input_processor->process_input();
bool end = game->update(input); bool end = game->update(input);
if(end) { if(end) {
break; break;
} }
game->render(renderer.get());
auto endtime = std::chrono::high_resolution_clock::now();
last_frame = render_fps(endtime, last_frame);
renderer->flush(); // not technically correct to do this here, but is done so we can put the fps over the display
std::this_thread::sleep_for(duration_per_frame); // Rendering
std::for_each(rendering_steps.begin(), rendering_steps.end(), [this](std::unique_ptr<RenderingPipelineStep>& step) {
step->render_step(this->renderer.get());
});
renderer->flush();
// timing and sleep
auto endtime = std::chrono::high_resolution_clock::now();
auto update_duration = std::chrono::duration_cast<std::chrono::milliseconds>(endtime - starttime);
std::this_thread::sleep_for(duration_per_frame - update_duration);
} }
} }
Engine::Engine(std::unique_ptr<Game> game, std::unique_ptr<Renderer> renderer, Engine::Engine(std::unique_ptr<Game> game,
std::unique_ptr<InputProcessor> input_processor) { std::unique_ptr<InputProcessor> input_processor) {
this->renderer = std::move(renderer);
this->game = std::move(game); this->game = std::move(game);
this->input_processor = std::move(input_processor); this->input_processor = std::move(input_processor);
this->parse_config(ENGINE_CONFIG_FILE);
Logger::set_minimum_loglevel(this->config.minimum_loglevel);
this->setup_renderer(this->config.selected_renderer);
}
void Engine::load_config(const std::string &path, const std::function<void (toml::table)>& f) {
std::string full_path = fmt::format("{}/{}", CONFIG_DIR, path);
auto table = toml::parse_file(full_path);
f(table);
}
void Engine::parse_config(const std::string& config_file_path) {
EngineConfig new_config = EngineConfig(); EngineConfig new_config = EngineConfig();
load_config("engine.toml", [&new_config](const toml::table& table) { load_config("engine.toml", [&new_config](const toml::table& table) {
new_config.show_fps = table["overlay"]["show_fps"].value_or<bool>(DEFAULT_SHOW_FPS); new_config.show_fps = table["overlay"]["show_fps"].value_or<bool>(DEFAULT_SHOW_FPS);
@@ -55,26 +75,29 @@ Engine::Engine(std::unique_ptr<Game> game, std::unique_ptr<Renderer> renderer,
auto default_font_path = fmt::format("fonts/{}.ttf", table["font"]["default_font_name"].value_or(DEFAULT_FONT_NAME)); auto default_font_path = fmt::format("fonts/{}.ttf", table["font"]["default_font_name"].value_or(DEFAULT_FONT_NAME));
auto default_font_size = table["font"]["default_font_size"].value_or(DEFAULT_FONT_SIZE); auto default_font_size = table["font"]["default_font_size"].value_or(DEFAULT_FONT_SIZE);
new_config.default_font = TextRenderDetails{default_font_path, default_font_size, COLOR_WHITE}; new_config.default_font = TextRenderDetails{default_font_path, default_font_size, COLOR_WHITE};
new_config.selected_renderer = static_cast<RendererTypes>(table["display"]["rendering_engine"].value_or((int)RendererTypes::SDL_HARDWARE));
new_config.minimum_loglevel = static_cast<LogLevel>(table["logging"]["minimum_level"].value_or((int)LogLevel::DEBUG));
}); });
this->config = new_config; this->config = new_config;
} }
std::chrono::time_point<std::chrono::system_clock> void Engine::setup_renderer(RendererTypes rendering_engine) {
Engine::render_fps(std::chrono::time_point<std::chrono::system_clock> end, std::unique_ptr<Renderer> rendering_engine_ptr;
std::chrono::time_point<std::chrono::system_clock> lastframe) { auto render_params = RendererParams{this->config.window_title, this->config.window_width, this->config.window_height, this->config.default_font};
double frames = 1; switch(rendering_engine) {
std::chrono::duration<double> dur = end - lastframe; case SDL_HARDWARE: rendering_engine_ptr = std::make_unique<SdlRenderer>(render_params); break;
if(this->config.show_fps) { default: throw RLEngineException("Invalid renderer specified");
//printf("fps: %f\n", frames/dur.count());
this->renderer->draw_text(std::nullopt, fmt::format("{:.2f}", frames/dur.count()), 0, 0);
} }
return end; this->renderer = std::move(rendering_engine_ptr);
} }
void Engine::load_config(const std::string &path, const std::function<void (toml::table)>& f) { std::vector<std::unique_ptr<RenderingPipelineStep>> Engine::build_render_pipline() {
std::string full_path = fmt::format("{}/{}", CONFIG_DIR, path); std::vector<std::unique_ptr<RenderingPipelineStep>> result = std::vector<std::unique_ptr<RenderingPipelineStep>>();
auto table = toml::parse_file(full_path); result.push_back(std::make_unique<GameRenderingStep>(this->game.get()));
f(table); if(this->config.show_fps) {
result.push_back(std::make_unique<FpsCounterRenderingStep>());
}
return result;
} }

View File

@@ -9,10 +9,13 @@
#include <chrono> #include <chrono>
#include "game/game.hpp" #include "game/game.hpp"
#include <fmt/format.h> #include <fmt/format.h>
#include <toml.hpp> #include <toml++/toml.h>
#include <functional> #include <functional>
#include "rendering/renderer.hpp" #include "rendering/renderer.hpp"
#include "input/inputprocessor.hpp" #include "input/inputprocessor.hpp"
#include "rendering/renderingpipelinestep.hpp"
#include "modules/logger.hpp"
#define MISSING_GAME_ERROR "Game pointer is null" #define MISSING_GAME_ERROR "Game pointer is null"
#define MISSING_RENDERER_ERROR "Renderer pointer is null" #define MISSING_RENDERER_ERROR "Renderer pointer is null"
#define RESOURCE_DIR "assets" #define RESOURCE_DIR "assets"
@@ -24,6 +27,11 @@
#define DEFAULT_SHOW_FPS false #define DEFAULT_SHOW_FPS false
#define DEFAULT_FONT_NAME "Consolas-Regular" #define DEFAULT_FONT_NAME "Consolas-Regular"
#define DEFAULT_FONT_SIZE 12 #define DEFAULT_FONT_SIZE 12
#define ENGINE_CONFIG_FILE "engine.toml"
enum RendererTypes : int {
SDL_HARDWARE = 0
};
struct EngineConfig { struct EngineConfig {
unsigned int target_fps; unsigned int target_fps;
@@ -32,11 +40,13 @@ struct EngineConfig {
bool show_fps; bool show_fps;
std::string window_title; std::string window_title;
TextRenderDetails default_font; TextRenderDetails default_font;
RendererTypes selected_renderer;
LogLevel minimum_loglevel;
}; };
class Engine { class Engine {
public: public:
Engine(std::unique_ptr<Game> game, std::unique_ptr<Renderer> renderer, Engine(std::unique_ptr<Game> game,
std::unique_ptr<InputProcessor> input_processor); std::unique_ptr<InputProcessor> input_processor);
void start_loop(); void start_loop();
static void load_config(const std::string& path, const std::function<void (toml::table)>& f); static void load_config(const std::string& path, const std::function<void (toml::table)>& f);
@@ -48,17 +58,19 @@ private:
std::unique_ptr<Game> game = nullptr; std::unique_ptr<Game> game = nullptr;
std::unique_ptr<Renderer> renderer = nullptr; std::unique_ptr<Renderer> renderer = nullptr;
std::unique_ptr<InputProcessor> input_processor = nullptr; std::unique_ptr<InputProcessor> input_processor = nullptr;
std::chrono::time_point<std::chrono::system_clock> render_fps(std::chrono::time_point<std::chrono::system_clock> end, std::chrono::time_point<std::chrono::system_clock> lastframe); void parse_config(const std::string& config_file_path);
void setup_renderer(RendererTypes rendering_engine);
std::vector<std::unique_ptr<RenderingPipelineStep>> build_render_pipline();
}; };
class MissingGameException : public std::exception { class MissingGameException : public std::exception {
public: public:
const char *what() const _GLIBCXX_TXN_SAFE_DYN _GLIBCXX_NOTHROW override; [[nodiscard]] const char *what() const _GLIBCXX_TXN_SAFE_DYN _GLIBCXX_NOTHROW override;
}; };
class MissingRendererException : public std::exception { class MissingRendererException : public std::exception {
public: public:
const char *what() const _GLIBCXX_TXN_SAFE_DYN _GLIBCXX_NOTHROW override; [[nodiscard]] const char *what() const _GLIBCXX_TXN_SAFE_DYN _GLIBCXX_NOTHROW override;
}; };

View File

@@ -18,6 +18,8 @@ public:
virtual bool update(InputResult input) = 0; virtual bool update(InputResult input) = 0;
virtual void render(Renderer* renderer) = 0; virtual void render(Renderer* renderer) = 0;
virtual void initialize(GameInitArgs args)=0; virtual void initialize(GameInitArgs args)=0;
virtual ~Game()=default;
}; };
#endif //RLA_IIPP_GAME_HPP #endif //RLA_IIPP_GAME_HPP

View File

@@ -269,6 +269,7 @@ struct InputResult {
class InputProcessor { class InputProcessor {
public: public:
virtual InputResult process_input()=0; virtual InputResult process_input()=0;
virtual ~InputProcessor()=default;
}; };
#endif //RLA_IIPP_INPUTPROCESSOR_HPP #endif //RLA_IIPP_INPUTPROCESSOR_HPP

View File

@@ -15,6 +15,8 @@ class SdlInputProcessor : public InputProcessor {
public: public:
SdlInputProcessor(); SdlInputProcessor();
~SdlInputProcessor() override = default;
private: private:
std::unordered_map<SDL_Keycode,InputProcessorKeycode> keycode_map; std::unordered_map<SDL_Keycode,InputProcessorKeycode> keycode_map;
InputProcessorKeycode get_keycode(SDL_Keycode keycode); InputProcessorKeycode get_keycode(SDL_Keycode keycode);

View File

@@ -0,0 +1,47 @@
//
// Created by m on 7/18/23.
//
#include "logger.hpp"
#include <fmt/format.h>
#include <fmt/chrono.h>
#include <iostream>
LogLevel Logger::minimum_loglevel = LogLevel::DEBUG;
void Logger::set_minimum_loglevel(LogLevel logLevel) {
Logger::minimum_loglevel = logLevel;
}
void Logger::log(LogLevel level, const std::string &msg) {
if(level < Logger::minimum_loglevel) {
return;
}
auto loglevel_name = "UNKNOWN";
switch (level) {
case LogLevel::TRACE:
loglevel_name = "TRA";
break;
case DEBUG:
loglevel_name = "DEB";
break;
case INFO:
loglevel_name = "INF";
break;
case WARNING:
loglevel_name = "WAR";
break;
case ERROR:
loglevel_name = "ERR";
break;
case NONE:
loglevel_name = "NON";
break;
}
auto output = fmt::format("[{}][{}] {}", std::chrono::system_clock::now(), loglevel_name, msg);
if(level == LogLevel::ERROR) {
std::cerr << output << "\n";
}
std::cout << output << "\n";
}

View File

@@ -0,0 +1,29 @@
//
// Created by m on 7/18/23.
//
#ifndef RLA_IIPP_LOGGER_HPP
#define RLA_IIPP_LOGGER_HPP
#include <string>
#include <unordered_map>
enum LogLevel: int {
TRACE = 0,
DEBUG = 1,
INFO = 2,
WARNING = 3,
ERROR = 4,
NONE = 5
};
class Logger {
public:
static void set_minimum_loglevel(LogLevel logLevel);
static void log(LogLevel level, const std::string& msg);
private:
static LogLevel minimum_loglevel;
};
#endif //RLA_IIPP_LOGGER_HPP

View File

@@ -0,0 +1,19 @@
//
// Created by m on 7/18/23.
//
#include <fmt/format.h>
#include "fpscounterrenderingstep.hpp"
void FpsCounterRenderingStep::render_step(Renderer *renderer) {
const double frames = 1;
const double smooth = 0.9;
std::chrono::time_point<std::chrono::system_clock> end = std::chrono::system_clock::now();
std::chrono::duration<double> dur = end - this->lastFrame;
renderer->draw_text(std::nullopt, fmt::format("{:.2f}", frames/dur.count()), 0, 0);
this->lastFrame = end;
}
FpsCounterRenderingStep::FpsCounterRenderingStep() {
this->lastFrame = std::chrono::high_resolution_clock::now();
}

View File

@@ -0,0 +1,22 @@
//
// Created by m on 7/18/23.
//
#ifndef RLA_IIPP_FPSCOUNTERRENDERINGSTEP_HPP
#define RLA_IIPP_FPSCOUNTERRENDERINGSTEP_HPP
#include <chrono>
#include "../renderingpipelinestep.hpp"
class FpsCounterRenderingStep : public RenderingPipelineStep {
void render_step(Renderer *renderer) override;
public:
FpsCounterRenderingStep();
private:
std::chrono::time_point<std::chrono::system_clock> lastFrame;
};
#endif //RLA_IIPP_FPSCOUNTERRENDERINGSTEP_HPP

View File

@@ -0,0 +1,11 @@
//
// Created by m on 7/18/23.
//
#include "gamerenderingstep.hpp"
void GameRenderingStep::render_step(Renderer *renderer) {
game->render(renderer);
}
GameRenderingStep::GameRenderingStep(Game *game) : game(game) {}

View File

@@ -0,0 +1,22 @@
//
// Created by m on 7/18/23.
//
#ifndef RLA_IIPP_GAMERENDERINGSTEP_HPP
#define RLA_IIPP_GAMERENDERINGSTEP_HPP
#include "../renderingpipelinestep.hpp"
#include "../../game/game.hpp"
class GameRenderingStep : public RenderingPipelineStep {
void render_step(Renderer *renderer) override;
private:
Game* game;
public:
explicit GameRenderingStep(Game *game);
};
#endif //RLA_IIPP_GAMERENDERINGSTEP_HPP

View File

@@ -22,6 +22,9 @@ public:
}; };
#define COLOR_BLACK Color(0, 0, 0, 255) #define COLOR_BLACK Color(0, 0, 0, 255)
#define COLOR_WHITE Color(255, 255, 255, 255) #define COLOR_WHITE Color(255, 255, 255, 255)
#define COLOR_RED Color(255, 0, 0, 255)
#define COLOR_GREEN Color(0, 255, 0, 255)
#define COLOR_BLUE Color(0, 0, 255, 255)
struct SpriteSheet { struct SpriteSheet {
std::string path; std::string path;
@@ -55,11 +58,14 @@ struct RendererParams {
class Renderer { class Renderer {
public: public:
virtual void initialize(RendererParams params)=0;
virtual void flush() = 0; virtual void flush() = 0;
virtual void draw_point(int x, int y, Color color) = 0; virtual void draw_point(int x, int y, Color color) = 0;
virtual void draw_text(std::optional<TextRenderDetails> details, std::string text,int x, int y)=0; virtual void draw_text(std::optional<TextRenderDetails> details, std::string text,int x, int y)=0;
virtual void draw_sprite(Sprite sprite, int x, int y)=0; virtual void draw_sprite(Sprite sprite, int x, int y)=0;
virtual void draw_sprite(Sprite sprite, int x, int y, float scale)=0;
virtual TextRenderDetails get_default_text_render_details()=0;
virtual ~Renderer() = default;
}; };
#endif //RLA_IIPP_RENDERER_HPP #endif //RLA_IIPP_RENDERER_HPP

View File

@@ -0,0 +1,17 @@
//
// Created by m on 7/18/23.
//
#ifndef RLA_IIPP_RENDERINGPIPELINESTEP_HPP
#define RLA_IIPP_RENDERINGPIPELINESTEP_HPP
#include "renderer.hpp"
class RenderingPipelineStep {
public:
virtual ~RenderingPipelineStep() = default;
virtual void render_step(Renderer* renderer) = 0;
};
#endif //RLA_IIPP_RENDERINGPIPELINESTEP_HPP

View File

@@ -2,40 +2,9 @@
// Created by m on 12/3/21. // Created by m on 12/3/21.
// //
#include <cstdio> #include <cstdio>
#include <utility>
#include "sdlrenderer.hpp" #include "sdlrenderer.hpp"
void SdlRenderer::initialize(RendererParams params) {
if(SDL_Init(SDL_INIT_VIDEO) < 0) {
printf("SDL vide oinit failed: %s\n", SDL_GetError());
exit(EXIT_FAILURE);
}
auto img_flags = IMG_INIT_PNG;
if(!(IMG_Init(img_flags) & img_flags)) {
printf("Failed to init image loading: %s\n", IMG_GetError());
exit(EXIT_FAILURE);
}
if(TTF_Init() < 0) {
printf("Failed to initialize font loading: %s\n", TTF_GetError());
exit(EXIT_FAILURE);
}
SDL_Window* sdl_window = SDL_CreateWindow(params.title.c_str(), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, params.width, params.height, SDL_WINDOW_SHOWN);
if(sdl_window == nullptr) {
printf("error making window: %s\n", SDL_GetError());
exit(EXIT_FAILURE);
}
else {
this->window = std::unique_ptr<SDL_Window, SDL_WindowDeleter>(sdl_window);
SDL_Renderer* sdl_renderer = SDL_CreateRenderer(this->window.get(), -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
this->renderer = std::shared_ptr<SDL_Renderer>(sdl_renderer, SDL_DestroyRenderer);
this->texture_manager = std::make_unique<SdlTextureManager>(this->renderer);
this->font_manager = std::make_unique<SdlFontManager>();
this->renderer_params = params;
SDL_RenderClear(this->renderer.get());
}
}
void SdlRenderer::flush() { void SdlRenderer::flush() {
SDL_RenderPresent(this->renderer.get()); SDL_RenderPresent(this->renderer.get());
SDL_SetRenderDrawColor(this->renderer.get(), COLOR_BLACK.r, COLOR_BLACK.g, COLOR_BLACK.b, COLOR_BLACK.a); SDL_SetRenderDrawColor(this->renderer.get(), COLOR_BLACK.r, COLOR_BLACK.g, COLOR_BLACK.b, COLOR_BLACK.a);
@@ -51,17 +20,21 @@ void SdlRenderer::draw_text(std::optional<TextRenderDetails> details, std::strin
TextRenderDetails unpacked_details = details.value_or(this->renderer_params.default_font); TextRenderDetails unpacked_details = details.value_or(this->renderer_params.default_font);
std::shared_ptr<TTF_Font> font = this->font_manager->fetch_resource(SdlFontArgs(unpacked_details.size, unpacked_details.font_path)); std::shared_ptr<TTF_Font> font = this->font_manager->fetch_resource(SdlFontArgs(unpacked_details.size, unpacked_details.font_path));
std::shared_ptr<SDL_Texture> texture = this->texture_manager->load_text_as_texture(text, font, unpacked_details.color); std::shared_ptr<SDL_Texture> texture = this->texture_manager->load_text_as_texture(text, font, unpacked_details.color);
render_texture(x, y, texture, nullptr); render_texture(x, y, texture, nullptr, nullptr);
} }
SdlRenderer::~SdlRenderer() { SdlRenderer::~SdlRenderer() {
// clear resource managers pointing to shared pointers
// This avoids an invalid read, since otherwise TTF_Quit will cleanup fonts and then clearing the shared pointers will try it again
font_manager->clear_all_resources();
texture_manager->clear_all_resources();
// exit subsystems // exit subsystems
IMG_Quit(); IMG_Quit();
TTF_Quit(); TTF_Quit();
SDL_Quit(); SDL_Quit();
} }
void SdlRenderer::render_texture(int x, int y, const std::shared_ptr<SDL_Texture>& texture, SDL_Rect *src) { void SdlRenderer::render_texture(int x, int y, const std::shared_ptr<SDL_Texture>& texture, SDL_Rect *src, SDL_Rect *dest) {
int w, h; int w, h;
if(src == nullptr) { if(src == nullptr) {
SDL_QueryTexture(texture.get(), nullptr, nullptr, &w, &h); SDL_QueryTexture(texture.get(), nullptr, nullptr, &w, &h);
@@ -70,11 +43,21 @@ void SdlRenderer::render_texture(int x, int y, const std::shared_ptr<SDL_Texture
w = src->w; w = src->w;
h = src->h; h = src->h;
} }
SDL_Rect rect = {x,y,w,h}; SDL_Rect destination_rect;
SDL_RenderCopy(this->renderer.get(), texture.get(), src, &rect); if(dest == nullptr) {
destination_rect = {x,y,w,h};
}
else {
destination_rect = *dest;
}
SDL_RenderCopy(this->renderer.get(), texture.get(), src, &destination_rect);
} }
void SdlRenderer::draw_sprite(Sprite sprite, int x, int y) { void SdlRenderer::draw_sprite(Sprite sprite, int x, int y) {
this->draw_sprite(sprite, x, y, 1);
}
void SdlRenderer::draw_sprite(Sprite sprite, int x, int y, float scale) {
SpriteSheet *sprite_sheet = sprite.sprite_sheet; SpriteSheet *sprite_sheet = sprite.sprite_sheet;
TextureManagerFetchArgs args = TextureManagerFetchArgs {sprite_sheet->path, sprite_sheet->color_key}; TextureManagerFetchArgs args = TextureManagerFetchArgs {sprite_sheet->path, sprite_sheet->color_key};
std::shared_ptr<SDL_Texture> texture = this->texture_manager->fetch_resource(args); std::shared_ptr<SDL_Texture> texture = this->texture_manager->fetch_resource(args);
@@ -84,5 +67,37 @@ void SdlRenderer::draw_sprite(Sprite sprite, int x, int y) {
int src_y = l*sprite_sheet->sprite_height; int src_y = l*sprite_sheet->sprite_height;
int src_x = (sprite_sheet->sprite_width * sprite.index) % w; int src_x = (sprite_sheet->sprite_width * sprite.index) % w;
auto source = SDL_Rect{src_x, src_y, sprite_sheet->sprite_width, sprite_sheet->sprite_height}; auto source = SDL_Rect{src_x, src_y, sprite_sheet->sprite_width, sprite_sheet->sprite_height};
render_texture(x, y, texture, &source); auto dest = SDL_Rect {x, y, (int)std::ceil(sprite_sheet->sprite_width * scale), (int)std::ceil(sprite_sheet->sprite_height * scale)};
render_texture(x, y, texture, &source, &dest);
}
SdlRenderer::SdlRenderer(RendererParams renderer_params) : renderer_params(std::move(renderer_params)) {
if(SDL_Init(SDL_INIT_VIDEO) < 0) {
printf("SDL vide oinit failed: %s\n", SDL_GetError());
exit(EXIT_FAILURE);
}
auto img_flags = IMG_INIT_PNG;
if(!(IMG_Init(img_flags) & img_flags)) {
printf("Failed to init image loading: %s\n", IMG_GetError());
exit(EXIT_FAILURE);
}
if(TTF_Init() < 0) {
printf("Failed to initialize font loading: %s\n", TTF_GetError());
exit(EXIT_FAILURE);
}
SDL_Window* sdl_window = SDL_CreateWindow(this->renderer_params.title.c_str(), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, this->renderer_params.width, this->renderer_params.height, SDL_WINDOW_SHOWN);
if(sdl_window == nullptr) {
printf("error making window: %s\n", SDL_GetError());
exit(EXIT_FAILURE);
}
else {
this->window = std::unique_ptr<SDL_Window, SDL_WindowDeleter>(sdl_window);
SDL_Renderer* sdl_renderer = SDL_CreateRenderer(this->window.get(), -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
this->renderer = std::shared_ptr<SDL_Renderer>(sdl_renderer, SDL_DestroyRenderer);
this->texture_manager = std::make_unique<SdlTextureManager>(this->renderer);
this->font_manager = std::make_unique<SdlFontManager>();
SDL_RenderClear(this->renderer.get());
}
} }

View File

@@ -17,21 +17,23 @@ struct SDL_WindowDeleter {
class SdlRenderer : public Renderer { class SdlRenderer : public Renderer {
public: public:
~SdlRenderer(); ~SdlRenderer() override;
explicit SdlRenderer(RendererParams renderer_params);
void draw_text(std::optional<TextRenderDetails> details, std::string text, int x, int y) override; void draw_text(std::optional<TextRenderDetails> details, std::string text, int x, int y) override;
void initialize(RendererParams params) override;
void flush() override; void flush() override;
void draw_point(int x, int y, Color color) override; void draw_point(int x, int y, Color color) override;
void draw_sprite(Sprite sprite, int x, int y) override; void draw_sprite(Sprite sprite, int x, int y) override;
void draw_sprite(Sprite sprite, int x, int y, float scale) override;
TextRenderDetails get_default_text_render_details() override {
return this->renderer_params.default_font;
}
private: private:
RendererParams renderer_params; RendererParams renderer_params;
std::unique_ptr<SDL_Window, SDL_WindowDeleter> window; std::unique_ptr<SDL_Window, SDL_WindowDeleter> window;
std::shared_ptr<SDL_Renderer> renderer = nullptr; std::shared_ptr<SDL_Renderer> renderer = nullptr;
std::unique_ptr<SdlTextureManager> texture_manager = nullptr; std::unique_ptr<SdlTextureManager> texture_manager = nullptr;
std::unique_ptr<SdlFontManager> font_manager = nullptr; std::unique_ptr<SdlFontManager> font_manager = nullptr;
void render_texture(int x, int y, const std::shared_ptr<SDL_Texture>& texture, SDL_Rect *src); void render_texture(int x, int y, const std::shared_ptr<SDL_Texture>& texture, SDL_Rect *src, SDL_Rect *dest);
}; };

View File

@@ -27,7 +27,7 @@ std::shared_ptr<SDL_Texture> SdlTextureManager::load_resource(TextureManagerFetc
if(texture == nullptr) { if(texture == nullptr) {
throw MissingTextureException(full_path, SDL_GetError()); throw MissingTextureException(full_path, SDL_GetError());
} }
return std::shared_ptr<SDL_Texture>(texture, SDL_DestroyTexture); return {texture, SDL_DestroyTexture};
} }
std::string SdlTextureManager::get_key(TextureManagerFetchArgs args) { std::string SdlTextureManager::get_key(TextureManagerFetchArgs args) {

View File

@@ -12,12 +12,21 @@ class ResourceManager {
public: public:
virtual T fetch_resource(A args) { virtual T fetch_resource(A args) {
K key = get_key(args); K key = get_key(args);
if(this->resource_map.find(key) == this->resource_map.end()) { if(!this->resource_map.contains(key)) {
T resource = this->load_resource(args); T resource = this->load_resource(args);
this->resource_map[key] = resource; this->resource_map[key] = resource;
} }
return this->resource_map[key]; return this->resource_map[key];
} }
virtual void clear_resource(A args) {
K key = get_key(args);
if(this->resource_map.contains(key)) {
this->resource_map.erase(key);
}
}
virtual void clear_all_resources() {
this->resource_map.clear();
}
protected: protected:
std::unordered_map<K, T> resource_map; std::unordered_map<K, T> resource_map;

View File

@@ -0,0 +1,27 @@
//
// Created by m on 12/20/21.
//
#ifndef RLA_IIPP_RLENGINEEXCEPTION_HPP
#define RLA_IIPP_RLENGINEEXCEPTION_HPP
#include <exception>
#include <string>
#include <utility>
class RLEngineException : public std::exception {
public:
[[nodiscard]] const char *what() const _GLIBCXX_TXN_SAFE_DYN _GLIBCXX_NOTHROW override {
return message.c_str();
}
explicit RLEngineException(std::string message) : message(std::move(message)) {}
private:
std::string message;
};
#endif //RLA_IIPP_RLENGINEEXCEPTION_HPP

View File

@@ -0,0 +1,16 @@
//
// Created by m on 12/12/21.
//
#include "luacontextmanager.hpp"
std::string LuaContextManager::get_key(std::string args) {
return args;
}
// TODO: Switch the arg to a struct and use that to init libraries
std::shared_ptr<sol::state> LuaContextManager::load_resource(std::string args) {
auto lua = std::make_shared<sol::state>();
lua->open_libraries(sol::lib::base, sol::lib::package, sol::lib::math, sol::lib::table);
return lua;
}

View File

@@ -0,0 +1,28 @@
//
// Created by m on 12/12/21.
//
#ifndef RLA_IIPP_LUACONTEXTMANAGER_HPP
#define RLA_IIPP_LUACONTEXTMANAGER_HPP
#include <sol/sol.hpp>
#include "../resources/resourcemanager.hpp"
class LuaContextManager : public ResourceManager<std::shared_ptr<sol::state>, std::string, std::string> {
public:
std::shared_ptr<sol::state> get_default_context() {
return fetch_resource(DEFAULT_CONTEXT_KEY);
}
void clear_default_context() {
clear_resource(DEFAULT_CONTEXT_KEY);
}
protected:
std::string get_key(std::string args) override;
std::shared_ptr<sol::state> load_resource(std::string args) override;
private:
const std::string DEFAULT_CONTEXT_KEY = "default";
};
#endif //RLA_IIPP_LUACONTEXTMANAGER_HPP

View File

@@ -9,8 +9,14 @@ Rectangle Camera::get_bounds() {
} }
void Camera::move_camera(Point new_center) { void Camera::move_camera(Point new_center) {
bool recalculate_bounds = false;
if(new_center != this->center) {
recalculate_bounds = true;
}
this->center = new_center; this->center = new_center;
if(recalculate_bounds) {
this->cached_bounds = calculate_bounds(); this->cached_bounds = calculate_bounds();
}
} }
void Camera::set_bounds(int max_x, int max_y) { void Camera::set_bounds(int max_x, int max_y) {
@@ -19,7 +25,9 @@ void Camera::set_bounds(int max_x, int max_y) {
this->cached_bounds = calculate_bounds(); this->cached_bounds = calculate_bounds();
} }
Rectangle Camera::calculate_bounds() { Rectangle Camera::calculate_bounds() const {
auto tiles_rendered_x = this->get_tiles_rendered_x();
auto tiles_rendered_y = this->get_tiles_rendered_y();
int minx = std::clamp(center.x-(tiles_rendered_x/2), 0, std::max(0,max_x - tiles_rendered_x)); int minx = std::clamp(center.x-(tiles_rendered_x/2), 0, std::max(0,max_x - tiles_rendered_x));
int miny = std::clamp(center.y-(tiles_rendered_y/2), 0, std::max(0,max_y - tiles_rendered_y)); int miny = std::clamp(center.y-(tiles_rendered_y/2), 0, std::max(0,max_y - tiles_rendered_y));
int width = std::min(tiles_rendered_x, max_x-minx); int width = std::min(tiles_rendered_x, max_x-minx);
@@ -31,3 +39,33 @@ Point Camera::camera_coords_to_screen_coords(Point camera_coord) {
auto bounds = get_bounds(); auto bounds = get_bounds();
return Point{camera_coord.x-bounds.x, camera_coord.y-bounds.y}; return Point{camera_coord.x-bounds.x, camera_coord.y-bounds.y};
} }
ranges::any_view<Point> Camera::get_range() {
auto bounds = get_bounds();
int height = bounds.height;
int width = bounds.width;
int miny = bounds.y;
int minx = bounds.x;
return ranges::views::iota(miny, miny+height) | ranges::views::transform([width, minx](int y) {
return ranges::views::iota(minx, minx+width) | ranges::views::transform([y](int x) {
return Point{x,y};
});
}) | ranges::views::join;
}
float Camera::get_scale() const {
return scale;
}
void Camera::set_scale(float scale) {
this->scale = scale;
this->cached_bounds = calculate_bounds();
}
int Camera::get_tiles_rendered_x() const {
return (int)std::ceil(tiles_rendered_x / this->scale);
}
int Camera::get_tiles_rendered_y() const {
return (int)std::ceil(tiles_rendered_y / this->scale);
}

View File

@@ -8,6 +8,7 @@
#include "point.hpp" #include "point.hpp"
#include "rectangle.hpp" #include "rectangle.hpp"
#include <range/v3/view.hpp>
#include <algorithm> #include <algorithm>
class Camera { class Camera {
@@ -19,12 +20,21 @@ private:
int max_x=0; int max_x=0;
int max_y=0; int max_y=0;
Rectangle cached_bounds=Rectangle(); Rectangle cached_bounds=Rectangle();
Rectangle calculate_bounds(); Rectangle calculate_bounds() const;
float scale = 1;
[[nodiscard]] int get_tiles_rendered_x() const;
[[nodiscard]] int get_tiles_rendered_y() const;
public:
[[nodiscard]] float get_scale() const;
void set_scale(float scale);
public: public:
Rectangle get_bounds(); Rectangle get_bounds();
void move_camera(Point new_center); void move_camera(Point new_center);
void set_bounds(int max_x, int max_y); void set_bounds(int max_x, int max_y);
Point camera_coords_to_screen_coords(Point camera_coord); Point camera_coords_to_screen_coords(Point camera_coord);
ranges::any_view<Point> get_range();
Camera()=default; Camera()=default;
Camera(const Point &center, int tilesRenderedX, int tilesRenderedY, int maxX, int maxY) : center(center), Camera(const Point &center, int tilesRenderedX, int tilesRenderedY, int maxX, int maxY) : center(center),

View File

@@ -7,7 +7,8 @@
#include <vector> #include <vector>
#include <range/v3/view.hpp>
#include "point.hpp"
template<typename T> template<typename T>
class Grid2D { class Grid2D {
private: private:
@@ -28,6 +29,15 @@ public:
T get(int x, int y) { T get(int x, int y) {
return grid[y][x]; return grid[y][x];
} }
ranges::any_view<Point> get_range() {
int height = get_height();
int width = get_width();
return ranges::views::iota(0, height) | ranges::views::transform([height, width](int y) {
return ranges::views::iota(0, width) | ranges::views::transform([y](int x) {
return Point{x,y};
});
}) | ranges::views::join;
}
}; };

View File

@@ -10,6 +10,14 @@ struct Point {
int x; int x;
int y; int y;
bool operator==(const Point &rhs) const {
return x == rhs.x &&
y == rhs.y;
}
bool operator!=(const Point &rhs) const {
return !(rhs == *this);
}
}; };

View File

@@ -0,0 +1,62 @@
//
// Created by m on 12/13/21.
//
#include "algorithmmanager.hpp"
#include "../engine/modules/logger.hpp"
#include <utility>
#include <fmt/format.h>
void AlgorithmManager::register_algorithm(std::string name, sol::function initialize, sol::function update,
unsigned int updates_per_second) {
unsigned int frames_per_update = target_fps / updates_per_second;
DungeonAlgorithm algorithm = {
frames_per_update,
std::move(update),
std::move(initialize),
std::move(name)
};
algorithms.push_back(algorithm);
}
ranges::any_view<DungeonAlgorithm*, ranges::category::random_access | ranges::category::sized> AlgorithmManager::get_algorithms() {
auto view = algorithms | ranges::view::transform([](auto& algo) {
return &algo;
});
return view;
}
void AlgorithmManager::load_algorithm(DungeonAlgorithm *algorithm, int w, int h) {
algorithm_done = false;
ticks = 0;
loaded_algorithm = algorithm;
auto init_result = loaded_algorithm->initialize(w, h);
if(!init_result.valid()) {
sol::error err = init_result;
Logger::log(LogLevel::ERROR, fmt::format("error while initializing lua: {}", err.what()));
}
}
void AlgorithmManager::tick_algorithm() {
if(this->loaded_algorithm == nullptr || algorithm_done) {
return;
}
if(ticks >= this->loaded_algorithm->frames_per_update) {
ticks = 0;
auto update_result = this->loaded_algorithm->update();
if(update_result.valid()) {
algorithm_done = update_result;
if(algorithm_done) {
Logger::log(LogLevel::DEBUG, "Finished running algorithm");
}
}
else {
algorithm_done = true;
sol::error err = update_result;
Logger::log(LogLevel::ERROR, fmt::format("An error occurred while running update: {}", err.what()));
}
}
ticks++;
}

View File

@@ -0,0 +1,36 @@
//
// Created by m on 12/13/21.
//
#ifndef RLA_IIPP_ALGORITHMMANAGER_HPP
#define RLA_IIPP_ALGORITHMMANAGER_HPP
#include <sol/sol.hpp>
#include <range/v3/view.hpp>
struct DungeonAlgorithm {
unsigned int frames_per_update=1;
sol::protected_function update;
sol::protected_function initialize;
std::string name;
};
class AlgorithmManager {
private:
std::vector<DungeonAlgorithm> algorithms;
unsigned int target_fps=1;
unsigned int ticks=0;
DungeonAlgorithm* loaded_algorithm = nullptr;
bool algorithm_done = false;
public:
void register_algorithm(std::string name, sol::function initialize, sol::function update, unsigned int updates_per_second);
ranges::any_view<DungeonAlgorithm*, ranges::category::random_access | ranges::category::sized> get_algorithms();
void load_algorithm(DungeonAlgorithm* algorithm, int w, int h);
void tick_algorithm();
DungeonAlgorithm* get_currently_running_algorithm() {
return algorithm_done ? nullptr : loaded_algorithm;
}
explicit AlgorithmManager(unsigned int targetFps) : target_fps(targetFps) {}
AlgorithmManager() = default;
};
#endif //RLA_IIPP_ALGORITHMMANAGER_HPP

View File

@@ -1,5 +0,0 @@
//
// Created by m on 12/12/21.
//
#include "dungeonalgorithm.hpp"

View File

@@ -1,14 +0,0 @@
//
// Created by m on 12/12/21.
//
#ifndef RLA_IIPP_DUNGEONALGORITHM_HPP
#define RLA_IIPP_DUNGEONALGORITHM_HPP
class DungeonAlgorithm {
};
#endif //RLA_IIPP_DUNGEONALGORITHM_HPP

50
src/game/tilemap.cpp Normal file
View File

@@ -0,0 +1,50 @@
//
// Created by m on 12/14/21.
//
#include "tilemap.hpp"
#include "../engine/utility/point.hpp"
#include "../engine/modules/logger.hpp"
#include <fmt/format.h>
void TileMap::draw_floor(int x, int y) {
Logger::log(LogLevel::DEBUG, fmt::format("Drawing floor at x: {} y: {}", x, y));
grid.insert(x, y, floor_tile);
}
void TileMap::draw_wall(int x, int y) {
Logger::log(LogLevel::DEBUG, fmt::format("Drawing wall at x: {} y: {}", x, y));
grid.insert(x, y, wall_tile);
}
char TileMap::get_tile(int x, int y) {
return grid.get(x, y);
}
void TileMap::fill(bool wall) {
Logger::log(LogLevel::DEBUG, fmt::format("Running fill with wall: {}", wall));
for(Point p : grid.get_range()) {
if(wall) {
draw_wall(p.x, p.y);
}
else {
draw_floor(p.x, p.y);
}
}
}
bool TileMap::is_wall(int x, int y) {
return get_tile(x, y) == wall_tile;
}
int TileMap::get_width() {
return grid.get_width();
}
int TileMap::get_height() {
return grid.get_height();
}
TileMap TileMap::clone() {
return {this->get_width(), this->get_height(), this->wall_tile, this->floor_tile};
}

35
src/game/tilemap.hpp Normal file
View File

@@ -0,0 +1,35 @@
//
// Created by m on 12/14/21.
//
#ifndef RLA_IIPP_TILEMAP_HPP
#define RLA_IIPP_TILEMAP_HPP
#include "../engine/utility/grid2d.hpp"
class TileMap {
private:
Grid2D<char> grid = Grid2D<char>(0, 0);
char wall_tile;
char floor_tile;
public:
void draw_floor(int x, int y);
void draw_wall(int x, int y);
void fill(bool wall);
char get_tile(int x, int y);
bool is_wall(int x, int y);
int get_width();
int get_height();
TileMap clone();
TileMap(int width, int height, char wall_tile, char floor_tile) {
this->grid = Grid2D<char>(width, height);
this->wall_tile = wall_tile;
this->floor_tile = floor_tile;
}
};
#endif //RLA_IIPP_TILEMAP_HPP

View File

@@ -2,69 +2,149 @@
// Created by m on 12/3/21. // Created by m on 12/3/21.
// //
#include <filesystem>
#include "visualizer.hpp" #include "visualizer.hpp"
#include "tilemap.hpp"
bool Visualizer::update(InputResult input) { bool Visualizer::update(InputResult input) {
if(input.keycode == KEY_ESCAPE) { if(input.keycode == KEY_ESCAPE) {
return true; return true;
} }
this->algorithm_manager.tick_algorithm();
switch(input.keycode) { switch(input.keycode) {
case KEY_RIGHT: x += 1; break; case KEY_RIGHT: x += 1; break;
case KEY_LEFT: x -= 1; break; case KEY_LEFT: x -= 1; break;
case KEY_UP: y -= 1; break; case KEY_UP: y -= 1; break;
case KEY_DOWN: y += 1; break; case KEY_DOWN: y += 1; break;
case KEY_KP_PLUS:
this->camera.set_scale(this->camera.get_scale() * 2); break;
case KEY_KP_MINUS:
this->camera.set_scale(this->camera.get_scale() / 2); break;
default: break; default: break;
} }
if(input.keycode >= InputProcessorKeycode::KEY_1 && input.keycode <= InputProcessorKeycode::KEY_9) {
if(this->keycode_to_algorithm.contains(input.keycode)) {
auto algorithm = this->keycode_to_algorithm[input.keycode];
this->algorithm_manager.load_algorithm(algorithm, this->map_size_w, this->map_size_h);
}
}
this->x = std::clamp(this->x, 0, (int)this->tile_map.get_width()-1); this->x = std::clamp(this->x, 0, (int)this->tile_map.get_width()-1);
this->y = std::clamp(this->y, 0, (int)this->tile_map.get_height()-1); this->y = std::clamp(this->y, 0, (int)this->tile_map.get_height()-1);
this->camera.move_camera(Point{x,y}); this->camera.move_camera(Point{x,y});
return false; return false;
} }
void Visualizer::render(Renderer *renderer) { void Visualizer::render(Renderer *renderer) {
SpriteSheet tiles = SpriteSheet{"sprites/map1.bmp", TILE_WIDTH, TILE_HEIGHT}; SpriteSheet tiles = SpriteSheet{"sprites/map1.bmp", tile_width, tile_height};
SpriteSheet characters = SpriteSheet{"sprites/character.bmp", TILE_WIDTH, TILE_HEIGHT, COLOR_BLACK}; SpriteSheet characters = SpriteSheet{"sprites/character.bmp", tile_width, tile_height, COLOR_BLACK};
float scale = this->camera.get_scale();
int scaled_tile_width = (int)std::ceil(tile_width * scale);
int scaled_tile_height = (int)std::ceil(tile_height * scale);
auto grass = Sprite{&tiles, 0}; auto grass = Sprite{&tiles, 0};
auto wall = Sprite{&tiles, 497}; auto wall = Sprite{&tiles, 497};
auto character = Sprite{&characters, this->sprite_index}; auto character = Sprite{&characters, this->sprite_index};
auto bounds = this->camera.get_bounds(); for(Point p : camera.get_range()) {
auto wp = camera.camera_coords_to_screen_coords(p);
for(int ty : ranges::views::iota(bounds.y, bounds.y+bounds.height)) {
for(int tx : ranges::views::iota(bounds.x, bounds.x+bounds.width)) {
auto wp = camera.camera_coords_to_screen_coords(Point{tx, ty});
int wx = wp.x, wy = wp.y; int wx = wp.x, wy = wp.y;
if(this->tile_map.get(tx, ty) == FLOOR_CHAR) { if(this->tile_map.get_tile(p.x, p.y) == floor_char) {
renderer->draw_sprite(grass, wx*TILE_WIDTH, wy*TILE_HEIGHT); renderer->draw_sprite(grass, wx*scaled_tile_width, wy*scaled_tile_height, scale);
} }
else { else {
renderer->draw_sprite(wall, wx*TILE_WIDTH, wy*TILE_HEIGHT); renderer->draw_sprite(wall, wx*scaled_tile_width, wy*scaled_tile_height, scale);
} }
if(tx == x && ty == y) { if(p.x == x && p.y == y) {
renderer->draw_sprite(character, wx*TILE_WIDTH, wy*TILE_HEIGHT); renderer->draw_sprite(character, wx*scaled_tile_width, wy*scaled_tile_height, scale);
} }
} }
// Draw keycode to algo mappings
auto default_font_details = renderer->get_default_text_render_details();
auto running_algorithm = this->algorithm_manager.get_currently_running_algorithm();
for(auto pairing : this->keycode_to_algorithm) {
auto index = pairing.first-InputProcessorKeycode::KEY_1;
auto draw_params = default_font_details;
if(pairing.second == running_algorithm) {
draw_params.color = COLOR_RED;
}
renderer->draw_text(draw_params, fmt::format("{}: {}", index+1, pairing.second->name), 0, this->window_height-((index+1)*default_font_details.size));
} }
return; // for breaking
} }
void Visualizer::initialize(GameInitArgs args) { void Visualizer::initialize(GameInitArgs args) {
this->window_width = args.window_width; this->window_width = args.window_width;
this->window_height = args.window_height; this->window_height = args.window_height;
int tilesx = (window_width / TILE_WIDTH)+1; Engine::load_config(CONFIG_FILE, [this](toml::table table) {
int tilesy = (window_height / TILE_HEIGHT)+1; this->tile_width = table["rendering"]["tile_size_w"].value_or(DEFAULT_TILE_WIDTH);
initialize_map(MAP_SIZE_W, MAP_SIZE_H); this->tile_height = table["rendering"]["tile_size_h"].value_or(DEFAULT_TILE_HEIGHT);
this->map_size_w = table["map"]["map_size_w"].value_or(DEFAULT_MAP_SIZE_W);
this->map_size_h = table["map"]["map_size_h"].value_or(DEFAULT_MAP_SIZE_H);
this->floor_char = table["map"]["floor_char"].value_or(DEFAULT_FLOOR_CHAR)[0];
this->wall_char = table["map"]["wall_char"].value_or(DEFAULT_WALL_CHAR)[0];
});
int tilesx = (window_width / tile_width)+1;
int tilesy = (window_height / tile_height)+1;
initialize_map(map_size_w, map_size_h);
this->camera = Camera(Point{x,y}, tilesx, tilesy, tile_map.get_width(), tile_map.get_height()); this->camera = Camera(Point{x,y}, tilesx, tilesy, tile_map.get_width(), tile_map.get_height());
this->algorithm_manager = AlgorithmManager(args.target_fps);
this->initialize_context();
this->load_algorithms();
Logger::log(LogLevel::DEBUG, "Game initialization finished");
} }
void Visualizer::initialize_map(int width, int height) { void Visualizer::initialize_map(int width, int height) {
this->tile_map = Grid2D<char>(width, height); this->tile_map = TileMap(width, height, wall_char, floor_char);
for(int ty=0;ty<this->tile_map.get_height();ty++) { for(int ty=0;ty<this->tile_map.get_height();ty++) {
for (int tx = 0; tx < tile_map.get_width(); tx++) { for (int tx = 0; tx < tile_map.get_width(); tx++) {
this->tile_map.insert(tx, ty, FLOOR_CHAR); this->tile_map.draw_floor(tx, ty);
} }
} }
} }
void Visualizer::initialize_context() {
auto lua = lua_context_manager.get_default_context();
auto tilemap_lua = lua->new_usertype<TileMap>("TileMap", sol::no_constructor);
tilemap_lua["draw_floor"] = &TileMap::draw_floor;
tilemap_lua["draw_wall"] = &TileMap::draw_wall;
tilemap_lua["fill"] = &TileMap::fill;
tilemap_lua["is_wall"] = &TileMap::is_wall;
tilemap_lua["clone"] = &TileMap::clone;
tilemap_lua["get_width"] = &TileMap::get_width;
tilemap_lua["get_height"] = &TileMap::get_height;
auto algo_manager_lua = lua->new_usertype<AlgorithmManager>("AlgorithmManager", sol::no_constructor);
algo_manager_lua["register_algorithm"] = &AlgorithmManager::register_algorithm;
auto game_state_lua = lua->new_usertype<Visualizer>("Visualizer", sol::no_constructor);
game_state_lua.set("algorithm_manager", sol::readonly(&Visualizer::algorithm_manager));
game_state_lua.set("map", &Visualizer::tile_map);
lua->set("visualizer", this);
}
void Visualizer::load_algorithms() {
auto lua = lua_context_manager.get_default_context();
const std::string algorithms_directory = "scripts/algorithms";
for(const auto& file : std::filesystem::directory_iterator(algorithms_directory)) {
lua->script_file(file.path());
}
Logger::log(LogLevel::DEBUG, "finished loading lua files");
auto algorithms = this->algorithm_manager.get_algorithms();
auto algorithm_count = algorithms.size();
Logger::log(LogLevel::DEBUG, fmt::format("algorithms: {}", algorithm_count));
for(int code = InputProcessorKeycode::KEY_1;code < InputProcessorKeycode::KEY_9;code++) {
auto index = code-InputProcessorKeycode::KEY_1;
if(algorithms.size() <= index) {
break;
}
auto algo = algorithms[index];
this->keycode_to_algorithm[(InputProcessorKeycode)code] = algo;
}
}
Visualizer::~Visualizer() = default; Visualizer::~Visualizer() = default;

View File

@@ -5,17 +5,21 @@
#include "../engine/rendering/renderer.hpp" #include "../engine/rendering/renderer.hpp"
#include "../engine/utility/camera.hpp" #include "../engine/utility/camera.hpp"
#include "../engine/utility/grid2d.hpp" #include "../engine/utility/grid2d.hpp"
#include "../engine/scripting/luacontextmanager.hpp"
#include "algorithmmanager.hpp"
#include "tilemap.hpp"
#include <vector> #include <vector>
#include <range/v3/range.hpp> #include <range/v3/range.hpp>
#include <range/v3/view.hpp> #include <range/v3/view.hpp>
#ifndef RLA_IIPP_VISUALIZER_HPP #ifndef RLA_IIPP_VISUALIZER_HPP
#define RLA_IIPP_VISUALIZER_HPP #define RLA_IIPP_VISUALIZER_HPP
#define MAP_SIZE_W 20 #define DEFAULT_MAP_SIZE_W 20
#define MAP_SIZE_H 20 #define DEFAULT_MAP_SIZE_H 20
#define TILE_WIDTH 48 #define DEFAULT_TILE_WIDTH 48
#define TILE_HEIGHT 48 #define DEFAULT_TILE_HEIGHT 48
#define WALL_CHAR '#' #define DEFAULT_WALL_CHAR "#"
#define FLOOR_CHAR '.' #define DEFAULT_FLOOR_CHAR "."
#define CONFIG_FILE "visualizer.toml"
class Visualizer : public Game { class Visualizer : public Game {
public: public:
@@ -32,14 +36,26 @@ public:
private: private:
int window_width = 0; int window_width = 0;
int window_height = 0; int window_height = 0;
Grid2D<char> tile_map = Grid2D<char>(0, 0); TileMap tile_map = TileMap(0, 0, DEFAULT_WALL_CHAR[0], DEFAULT_FLOOR_CHAR[0]);
Camera camera = Camera(); Camera camera = Camera();
LuaContextManager lua_context_manager;
AlgorithmManager algorithm_manager;
std::unordered_map<InputProcessorKeycode, DungeonAlgorithm*> keycode_to_algorithm;
int x = 0; int x = 0;
int y=0; int y=0;
int sprite_index = 1; int sprite_index = 1;
char floor_char;
char wall_char;
int map_size_w;
int map_size_h;
int tile_width;
int tile_height;
void initialize_map(int width, int height); void initialize_map(int width, int height);
void initialize_context();
void load_algorithms();
}; };

View File

@@ -7,11 +7,9 @@
#include "engine/input/sdlinputprocessor.hpp" #include "engine/input/sdlinputprocessor.hpp"
int main() { int main() {
std::unique_ptr<Renderer> renderer = std::make_unique<SdlRenderer>();
std::unique_ptr<Game> game = std::make_unique<Visualizer>(); std::unique_ptr<Game> game = std::make_unique<Visualizer>();
std::unique_ptr<InputProcessor> input_processor = std::make_unique<SdlInputProcessor>(); std::unique_ptr<InputProcessor> input_processor = std::make_unique<SdlInputProcessor>();
auto* engine = new Engine(std::move(game), std::move(renderer), std::move(input_processor)); auto engine = std::make_unique<Engine>(std::move(game), std::move(input_processor));
engine->start_loop(); engine->start_loop();
return 0; return 0;

37
vcpkg.json Normal file
View File

@@ -0,0 +1,37 @@
{
"name" : "rla-iipp",
"version-string" : "1.0.0",
"builtin-baseline" : "21bbb14c4113b89cd06402e852e075341722304f",
"dependencies" : [ {
"name" : "sdl2",
"version>=" : "2.26.5",
"$comment" : " find_package(SDL2 CONFIG REQUIRED)\n\n target_link_libraries(main\n\n PRIVATE\n\n $<TARGET_NAME_IF_EXISTS:SDL2::SDL2main>\n\n $<IF:$<TARGET_EXISTS:SDL2::SDL2>,SDL2::SDL2,SDL2::SDL2-static>\n\n )\n"
}, {
"name" : "sdl2-ttf",
"version>=" : "2.20.2",
"$comment" : " find_package(SDL2_ttf CONFIG REQUIRED)\n\n target_link_libraries(main PRIVATE $<IF:$<TARGET_EXISTS:SDL2_ttf::SDL2_ttf>,SDL2_ttf::SDL2_ttf,SDL2_ttf::SDL2_ttf-static>)\n"
}, {
"name" : "sdl2-image",
"version>=" : "2.6.3",
"$comment" : " find_package(SDL2_image CONFIG REQUIRED)\n\n target_link_libraries(main PRIVATE $<IF:$<TARGET_EXISTS:SDL2_image::SDL2_image>,SDL2_image::SDL2_image,SDL2_image::SDL2_image-static>)\n"
}, {
"name" : "fmt",
"version>=" : "10.0.0",
"$comment" : " find_package(fmt CONFIG REQUIRED)\n\n target_link_libraries(main PRIVATE fmt::fmt)\n"
}, {
"name" : "tomlplusplus",
"version>=" : "3.1.0",
"$comment" : " # this is heuristically generated, and may not be correct\n\n find_package(tomlplusplus CONFIG REQUIRED)\n\n target_link_libraries(main PRIVATE tomlplusplus::tomlplusplus)\n"
}, {
"name" : "sol2",
"version>=" : "3.3.0",
"$comment" : " # this is heuristically generated, and may not be correct\n\n find_package(sol2 CONFIG REQUIRED)\n\n target_link_libraries(main PRIVATE sol2)\n"
}, {
"name" : "range-v3",
"version>=" : "0.12.0#1",
"$comment" : " # this is heuristically generated, and may not be correct\n\n find_package(range-v3 CONFIG REQUIRED)\n\n target_link_libraries(main PRIVATE range-v3::meta range-v3::concepts range-v3::range-v3)\n"
}, {
"name" : "lua",
"version>=" : "5.4.6"
} ]
}