GUECS: Minimal components from zedcaster that will let me make a GUI for a game.
This commit is contained in:
		
							parent
							
								
									10ecf50bc0
								
							
						
					
					
						commit
						1be770d62d
					
				
					 28 changed files with 2528 additions and 30 deletions
				
			
		
							
								
								
									
										9
									
								
								Makefile
									
										
									
									
									
								
							
							
						
						
									
										9
									
								
								Makefile
									
										
									
									
									
								
							|  | @ -6,7 +6,10 @@ reset: | |||
| patch: | ||||
| 	powershell "cp ./patches/process.h ./subprojects/libgit2-1.9.0/src/util/process.h" | ||||
| 
 | ||||
| build: | ||||
| %.cpp : %.rl | ||||
| 	ragel -o $@ $< | ||||
| 
 | ||||
| build: lel_parser.cpp | ||||
| 	meson compile -C builddir | ||||
| 
 | ||||
| config: | ||||
|  | @ -19,10 +22,10 @@ test: build | |||
| install: build test | ||||
| 	powershell "cp ./builddir/subprojects/libgit2-1.9.0/liblibgit2package.dll ." | ||||
| 	powershell "cp ./builddir/subprojects/efsw/libefsw.dll ." | ||||
| 	powershell "cp builddir/escape_turings_tarpit.exe ." | ||||
| 	powershell "cp builddir/ttpit.exe ." | ||||
| 
 | ||||
| run: install | ||||
| 	./escape_turings_tarpit.exe | ||||
| 	./ttpit.exe | ||||
| 
 | ||||
| clean: | ||||
| 	meson compile --clean -C builddir | ||||
|  |  | |||
							
								
								
									
										15
									
								
								color.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								color.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,15 @@ | |||
| #pragma once | ||||
| #include <SFML/Graphics/Color.hpp> | ||||
| 
 | ||||
| namespace ColorValue { | ||||
|   const sf::Color BLACK{0, 0, 0}; | ||||
|   const sf::Color DARK_DARK{10, 10, 10}; | ||||
|   const sf::Color DARK_MID{30, 30, 30}; | ||||
|   const sf::Color DARK_LIGHT{60, 60, 60}; | ||||
|   const sf::Color MID{100, 100, 100}; | ||||
|   const sf::Color LIGHT_DARK{150, 150, 150}; | ||||
|   const sf::Color LIGHT_MID{200, 200, 200}; | ||||
|   const sf::Color LIGHT_LIGHT{230, 230, 230}; | ||||
|   const sf::Color WHITE{255, 255, 255}; | ||||
|   const sf::Color TRANSPARENT = sf::Color::Transparent; | ||||
| } | ||||
							
								
								
									
										35
									
								
								config.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								config.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,35 @@ | |||
| #include "config.hpp" | ||||
| #include "dbc.hpp" | ||||
| #include <fmt/core.h> | ||||
| 
 | ||||
| using nlohmann::json; | ||||
| using fmt::format; | ||||
| 
 | ||||
| Config::Config(const std::string src_path) : $src_path(src_path) { | ||||
|   std::ifstream infile($src_path); | ||||
|   $config = json::parse(infile); | ||||
| } | ||||
| 
 | ||||
| json &Config::operator[](const std::string &key) { | ||||
|   dbc::check($config.contains(key), fmt::format("ERROR in config, key {} doesn't exist.", key)); | ||||
|   return $config[key]; | ||||
| } | ||||
| 
 | ||||
| std::wstring Config::wstring(const std::string main_key, const std::string sub_key) { | ||||
|   dbc::check($config.contains(main_key), fmt::format("ERROR wstring main/key in config, main_key {} doesn't exist.", main_key)); | ||||
|   dbc::check($config[main_key].contains(sub_key), fmt::format("ERROR wstring in config, main_key/key {}/{} doesn't exist.", main_key, sub_key)); | ||||
| 
 | ||||
|   const std::string& str_val = $config[main_key][sub_key]; | ||||
|   std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> $converter; | ||||
|   return $converter.from_bytes(str_val); | ||||
| } | ||||
| 
 | ||||
| std::vector<std::string> Config::keys() { | ||||
|   std::vector<std::string> the_fucking_keys; | ||||
| 
 | ||||
|   for(auto& [key, value] : $config.items()) { | ||||
|     the_fucking_keys.push_back(key); | ||||
|   } | ||||
| 
 | ||||
|   return the_fucking_keys; | ||||
| } | ||||
							
								
								
									
										19
									
								
								config.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								config.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,19 @@ | |||
| #pragma once | ||||
| #include <nlohmann/json.hpp> | ||||
| #include <fstream> | ||||
| #include <codecvt> | ||||
| 
 | ||||
| struct Config { | ||||
|   nlohmann::json $config; | ||||
|   std::string $src_path; | ||||
| 
 | ||||
|   Config(const std::string src_path); | ||||
| 
 | ||||
|   Config(nlohmann::json config, std::string src_path) | ||||
|     : $config(config), $src_path(src_path) {} | ||||
| 
 | ||||
|   nlohmann::json &operator[](const std::string &key); | ||||
|   nlohmann::json &json() { return $config; }; | ||||
|   std::wstring wstring(const std::string main_key, const std::string sub_key); | ||||
|   std::vector<std::string> keys(); | ||||
| }; | ||||
							
								
								
									
										28
									
								
								constants.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								constants.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,28 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <string> | ||||
| #include "color.hpp" | ||||
| #include <array> | ||||
| 
 | ||||
| constexpr const int TEXTURE_WIDTH=256; | ||||
| constexpr const int TEXTURE_HEIGHT=256; | ||||
| constexpr const int SCREEN_WIDTH=1280; | ||||
| constexpr const int SCREEN_HEIGHT=720; | ||||
| 
 | ||||
| constexpr const bool VSYNC=false; | ||||
| constexpr const int FRAME_LIMIT=60; | ||||
| 
 | ||||
| constexpr const int GUECS_PADDING = 3; | ||||
| constexpr const int GUECS_BORDER_PX = 1; | ||||
| constexpr const int GUECS_FONT_SIZE = 30; | ||||
| const sf::Color GUECS_FILL_COLOR = ColorValue::DARK_MID; | ||||
| const sf::Color GUECS_TEXT_COLOR = ColorValue::LIGHT_LIGHT; | ||||
| const sf::Color GUECS_BG_COLOR = ColorValue::MID; | ||||
| const sf::Color GUECS_BORDER_COLOR = ColorValue::MID; | ||||
| constexpr const char *FONT_FILE_NAME="assets/text.otf"; | ||||
| 
 | ||||
| #ifdef NDEBUG | ||||
| constexpr const bool DEBUG_BUILD=false; | ||||
| #else | ||||
| constexpr const bool DEBUG_BUILD=true; | ||||
| #endif | ||||
							
								
								
									
										35
									
								
								dbc.cpp
									
										
									
									
									
								
							
							
						
						
									
										35
									
								
								dbc.cpp
									
										
									
									
									
								
							|  | @ -1,40 +1,47 @@ | |||
| #include "dbc.hpp" | ||||
| #include <iostream> | ||||
| 
 | ||||
| void dbc::log(const string &message) { | ||||
|   fmt::print("{}\n", message); | ||||
| void dbc::log(const string &message, const std::source_location location) { | ||||
|   std::cout << '[' << location.file_name() << ':' | ||||
|      << location.line() << "|" | ||||
|      << location.function_name() << "] " | ||||
|      << message << std::endl; | ||||
| } | ||||
| 
 | ||||
| void dbc::sentinel(const string &message) { | ||||
|   string err = fmt::format("[SENTINEL!] {}\n", message); | ||||
| void dbc::sentinel(const string &message, const std::source_location location) { | ||||
|   string err = fmt::format("[SENTINEL!] {}", message); | ||||
|   dbc::log(err, location); | ||||
|   throw dbc::SentinelError{err}; | ||||
| } | ||||
| 
 | ||||
| void dbc::pre(const string &message, bool test) { | ||||
| void dbc::pre(const string &message, bool test, const std::source_location location) { | ||||
|   if(!test) { | ||||
|     string err = fmt::format("[PRE!] {}\n", message); | ||||
|     string err = fmt::format("[PRE!] {}", message); | ||||
|     dbc::log(err, location); | ||||
|     throw dbc::PreCondError{err}; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void dbc::pre(const string &message, std::function<bool()> tester) { | ||||
|   dbc::pre(message, tester()); | ||||
| void dbc::pre(const string &message, std::function<bool()> tester, const std::source_location location) { | ||||
|   dbc::pre(message, tester(), location); | ||||
| } | ||||
| 
 | ||||
| void dbc::post(const string &message, bool test) { | ||||
| void dbc::post(const string &message, bool test, const std::source_location location) { | ||||
|   if(!test) { | ||||
|     string err = fmt::format("[POST!] {}\n", message); | ||||
|     string err = fmt::format("[POST!] {}", message); | ||||
|     dbc::log(err, location); | ||||
|     throw dbc::PostCondError{err}; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void dbc::post(const string &message, std::function<bool()> tester) { | ||||
|   dbc::post(message, tester()); | ||||
| void dbc::post(const string &message, std::function<bool()> tester, const std::source_location location) { | ||||
|   dbc::post(message, tester(), location); | ||||
| } | ||||
| 
 | ||||
| void dbc::check(bool test, const string &message) { | ||||
| void dbc::check(bool test, const string &message, const std::source_location location) { | ||||
|   if(!test) { | ||||
|     string err = fmt::format("[CHECK!] {}\n", message); | ||||
|     fmt::println("{}", err); | ||||
|     dbc::log(err, location); | ||||
|     throw dbc::CheckError{err}; | ||||
|   } | ||||
| } | ||||
|  |  | |||
							
								
								
									
										35
									
								
								dbc.hpp
									
										
									
									
									
								
							
							
						
						
									
										35
									
								
								dbc.hpp
									
										
									
									
									
								
							|  | @ -3,6 +3,7 @@ | |||
| #include <string> | ||||
| #include <fmt/core.h> | ||||
| #include <functional> | ||||
| #include <source_location> | ||||
| 
 | ||||
| using std::string; | ||||
| 
 | ||||
|  | @ -19,11 +20,31 @@ namespace dbc { | |||
|   class PreCondError : public Error {}; | ||||
|   class PostCondError : public Error {}; | ||||
| 
 | ||||
|   void log(const string &message); | ||||
|   void sentinel(const string &message); | ||||
|   void pre(const string &message, bool test); | ||||
|   void pre(const string &message, std::function<bool()> tester); | ||||
|   void post(const string &message, bool test); | ||||
|   void post(const string &message, std::function<bool()> tester); | ||||
|   void check(bool test, const string &message); | ||||
|   void log(const string &message, | ||||
|       const std::source_location location = | ||||
|       std::source_location::current()); | ||||
| 
 | ||||
|   [[noreturn]] void sentinel(const string &message, | ||||
|       const std::source_location location = | ||||
|       std::source_location::current()); | ||||
| 
 | ||||
|   void pre(const string &message, bool test, | ||||
|       const std::source_location location = | ||||
|       std::source_location::current()); | ||||
| 
 | ||||
|   void pre(const string &message, std::function<bool()> tester, | ||||
|       const std::source_location location = | ||||
|       std::source_location::current()); | ||||
| 
 | ||||
|   void post(const string &message, bool test, | ||||
|       const std::source_location location = | ||||
|       std::source_location::current()); | ||||
| 
 | ||||
|   void post(const string &message, std::function<bool()> tester, | ||||
|       const std::source_location location = | ||||
|       std::source_location::current()); | ||||
| 
 | ||||
|   void check(bool test, const string &message, | ||||
|       const std::source_location location = | ||||
|       std::source_location::current()); | ||||
| } | ||||
|  |  | |||
							
								
								
									
										214
									
								
								dinkyecs.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										214
									
								
								dinkyecs.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,214 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include "dbc.hpp" | ||||
| #include <any> | ||||
| #include <functional> | ||||
| #include <queue> | ||||
| #include <tuple> | ||||
| #include <typeindex> | ||||
| #include <typeinfo> | ||||
| #include <unordered_map> | ||||
| #include <optional> | ||||
| 
 | ||||
| namespace DinkyECS | ||||
| { | ||||
|   typedef unsigned long Entity; | ||||
| 
 | ||||
|   using EntityMap = std::unordered_map<Entity, size_t>; | ||||
| 
 | ||||
|   template <typename T> | ||||
|     struct ComponentStorage { | ||||
|       std::vector<T> data; | ||||
|       std::queue<size_t> free_indices; | ||||
|     }; | ||||
| 
 | ||||
|   struct Event { | ||||
|     int event = 0; | ||||
|     Entity entity = 0; | ||||
|     std::any data; | ||||
|   }; | ||||
| 
 | ||||
|   typedef std::queue<Event> EventQueue; | ||||
| 
 | ||||
|   struct World { | ||||
|     unsigned long entity_count = 0; | ||||
|     std::unordered_map<std::type_index, EntityMap> $components; | ||||
|     std::unordered_map<std::type_index, std::any> $facts; | ||||
|     std::unordered_map<std::type_index, EventQueue> $events; | ||||
|     std::unordered_map<std::type_index, std::any> $component_storages; | ||||
|     std::vector<Entity> $constants; | ||||
| 
 | ||||
|     Entity entity() { return ++entity_count; } | ||||
| 
 | ||||
|     void clone_into(DinkyECS::World &to_world) { | ||||
|       to_world.$constants = $constants; | ||||
|       to_world.$facts = $facts; | ||||
|       to_world.entity_count = entity_count; | ||||
|       to_world.$component_storages = $component_storages; | ||||
| 
 | ||||
|       for(auto eid : $constants) { | ||||
|         for(const auto &[tid, eid_map] : $components) { | ||||
|           auto &their_map = to_world.$components[tid]; | ||||
|           if(eid_map.contains(eid)) { | ||||
|             their_map.insert_or_assign(eid, eid_map.at(eid)); | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     void make_constant(DinkyECS::Entity entity) { | ||||
|       $constants.push_back(entity); | ||||
|     } | ||||
| 
 | ||||
|     template <typename Comp> | ||||
|       size_t make_component() { | ||||
|         auto &storage = component_storage_for<Comp>(); | ||||
|         size_t index; | ||||
| 
 | ||||
|         if(!storage.free_indices.empty()) { | ||||
|           index = storage.free_indices.front(); | ||||
|           storage.free_indices.pop(); | ||||
|         } else { | ||||
|           storage.data.emplace_back(); | ||||
|           index = storage.data.size() - 1; | ||||
|         } | ||||
| 
 | ||||
|         return index; | ||||
|       } | ||||
| 
 | ||||
|     template <typename Comp> | ||||
|       ComponentStorage<Comp> &component_storage_for() { | ||||
|         auto type_index = std::type_index(typeid(Comp)); | ||||
|         $component_storages.try_emplace(type_index, ComponentStorage<Comp>{}); | ||||
|         return std::any_cast<ComponentStorage<Comp> &>( | ||||
|             $component_storages.at(type_index)); | ||||
|       } | ||||
| 
 | ||||
|     template <typename Comp> | ||||
|       EntityMap &entity_map_for() { | ||||
|         return $components[std::type_index(typeid(Comp))]; | ||||
|       } | ||||
| 
 | ||||
|     template <typename Comp> | ||||
|       EventQueue &queue_map_for() { | ||||
|         return $events[std::type_index(typeid(Comp))]; | ||||
|       } | ||||
| 
 | ||||
|     template <typename Comp> | ||||
|       void remove(Entity ent) { | ||||
|         EntityMap &map = entity_map_for<Comp>(); | ||||
| 
 | ||||
|         if(map.contains(ent)) { | ||||
|           size_t index = map.at(ent); | ||||
|           component_storage_for<Comp>().free_indices.push(index); | ||||
|         } | ||||
| 
 | ||||
|         map.erase(ent); | ||||
|       } | ||||
| 
 | ||||
|     template <typename Comp> | ||||
|       void set_the(Comp val) { | ||||
|         $facts.insert_or_assign(std::type_index(typeid(Comp)), val); | ||||
|       } | ||||
| 
 | ||||
|     template <typename Comp> | ||||
|       Comp &get_the() { | ||||
|         auto comp_id = std::type_index(typeid(Comp)); | ||||
|         dbc::check($facts.contains(comp_id), | ||||
|             fmt::format("!!!! ATTEMPT to access world fact that hasn't " | ||||
|               "been set yet: {}", | ||||
|               typeid(Comp).name())); | ||||
| 
 | ||||
|         // use .at to get std::out_of_range if fact not set
 | ||||
|         std::any &res = $facts.at(comp_id); | ||||
|         return std::any_cast<Comp &>(res); | ||||
|       } | ||||
| 
 | ||||
|     template <typename Comp> | ||||
|       bool has_the() { | ||||
|         auto comp_id = std::type_index(typeid(Comp)); | ||||
|         return $facts.contains(comp_id); | ||||
|       } | ||||
| 
 | ||||
|     template <typename Comp> | ||||
|       void set(Entity ent, Comp val) { | ||||
|         EntityMap &map = entity_map_for<Comp>(); | ||||
| 
 | ||||
|         if(has<Comp>(ent)) { | ||||
|           get<Comp>(ent) = val; | ||||
|           return; | ||||
|         } | ||||
| 
 | ||||
|         map.insert_or_assign(ent, make_component<Comp>()); | ||||
|         get<Comp>(ent) = val; | ||||
|       } | ||||
| 
 | ||||
|     template <typename Comp> | ||||
|       Comp &get(Entity ent) { | ||||
|         EntityMap &map = entity_map_for<Comp>(); | ||||
|         auto &storage = component_storage_for<Comp>(); | ||||
|         auto index = map.at(ent); | ||||
|         return storage.data[index]; | ||||
|       } | ||||
| 
 | ||||
|     template <typename Comp> | ||||
|       bool has(Entity ent) { | ||||
|         EntityMap &map = entity_map_for<Comp>(); | ||||
|         return map.contains(ent); | ||||
|       } | ||||
| 
 | ||||
|     template <typename Comp> | ||||
|       void query(std::function<void(Entity, Comp &)> cb) { | ||||
|         EntityMap &map = entity_map_for<Comp>(); | ||||
| 
 | ||||
|         for(auto &[entity, index] : map) { | ||||
|           cb(entity, get<Comp>(entity)); | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|     template <typename CompA, typename CompB> | ||||
|       void query(std::function<void(Entity, CompA &, CompB &)> cb) { | ||||
|         EntityMap &map_a = entity_map_for<CompA>(); | ||||
|         EntityMap &map_b = entity_map_for<CompB>(); | ||||
| 
 | ||||
|         for(auto &[entity, index_a] : map_a) { | ||||
|           if(map_b.contains(entity)) { | ||||
|             cb(entity, get<CompA>(entity), get<CompB>(entity)); | ||||
|           } | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|     template <typename Comp> | ||||
|       void send(Comp event, Entity entity, std::any data) { | ||||
|         EventQueue &queue = queue_map_for<Comp>(); | ||||
|         queue.push({event, entity, data}); | ||||
|       } | ||||
| 
 | ||||
|     template <typename Comp> | ||||
|       Event recv() { | ||||
|         EventQueue &queue = queue_map_for<Comp>(); | ||||
|         Event evt = queue.front(); | ||||
|         queue.pop(); | ||||
|         return evt; | ||||
|       } | ||||
| 
 | ||||
|     template <typename Comp> | ||||
|       bool has_event() { | ||||
|         EventQueue &queue = queue_map_for<Comp>(); | ||||
|         return !queue.empty(); | ||||
|       } | ||||
| 
 | ||||
|     /* std::optional can't do references. Don't try it!
 | ||||
|      * Actually, this sucks, either delete it or have it | ||||
|      * return pointers (assuming optional can handle pointers) | ||||
|      */ | ||||
|     template <typename Comp> | ||||
|       std::optional<Comp> get_if(DinkyECS::Entity entity) { | ||||
|         if(has<Comp>(entity)) { | ||||
|           return std::make_optional<Comp>(get<Comp>(entity)); | ||||
|         } else { | ||||
|           return std::nullopt; | ||||
|         } | ||||
|       } | ||||
|   }; | ||||
| } // namespace DinkyECS
 | ||||
							
								
								
									
										7
									
								
								events.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								events.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,7 @@ | |||
| #pragma once | ||||
| 
 | ||||
| namespace Events { | ||||
|   enum GUI { | ||||
|     START, NOOP | ||||
|   }; | ||||
| } | ||||
							
								
								
									
										310
									
								
								guecs.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										310
									
								
								guecs.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,310 @@ | |||
| #include "guecs.hpp" | ||||
| #include "shaders.hpp" | ||||
| #include "sound.hpp" | ||||
| 
 | ||||
| namespace guecs { | ||||
| 
 | ||||
|   void Textual::init(lel::Cell &cell, shared_ptr<sf::Font> font_ptr) { | ||||
|     dbc::check(font_ptr != nullptr, "you failed to initialize this WideText"); | ||||
|     if(font == nullptr) font = font_ptr; | ||||
|     if(text == nullptr) text = make_shared<sf::Text>(*font, content, size); | ||||
|     text->setFillColor(color); | ||||
| 
 | ||||
|     if(centered) { | ||||
|       auto bounds = text->getLocalBounds(); | ||||
|       auto text_cell = lel::center(bounds.size.x, bounds.size.y, cell); | ||||
|       // this stupid / 2 is because SFML renders from baseline rather than from the claimed bounding box
 | ||||
|       text->setPosition({float(text_cell.x), float(text_cell.y) - text_cell.h / 2}); | ||||
|     } else { | ||||
|       text->setPosition({float(cell.x + padding * 2), float(cell.y + padding * 2)}); | ||||
|     } | ||||
| 
 | ||||
|     text->setCharacterSize(size); | ||||
|   } | ||||
| 
 | ||||
|   void Textual::update(std::wstring& new_content) { | ||||
|     content = new_content; | ||||
|     text->setString(content); | ||||
|   } | ||||
| 
 | ||||
|   void Sprite::init(lel::Cell &cell) { | ||||
|     auto sprite_texture = textures::get(name); | ||||
| 
 | ||||
|     sprite = make_shared<sf::Sprite>( | ||||
|         *sprite_texture.texture, | ||||
|         sprite_texture.sprite->getTextureRect()); | ||||
| 
 | ||||
|     sprite->setPosition({ | ||||
|         float(cell.x + padding), | ||||
|         float(cell.y + padding)}); | ||||
| 
 | ||||
|     auto bounds = sprite->getLocalBounds(); | ||||
| 
 | ||||
|     sprite->setScale({ | ||||
|         float(cell.w - padding * 2) / bounds.size.x, | ||||
|         float(cell.h - padding * 2) / bounds.size.y}); | ||||
|   } | ||||
| 
 | ||||
|   void Rectangle::init(lel::Cell& cell) { | ||||
|     sf::Vector2f size{float(cell.w) - padding * 2, float(cell.h) - padding * 2}; | ||||
|     if(shape == nullptr) shape = make_shared<sf::RectangleShape>(size); | ||||
|     shape->setPosition({float(cell.x + padding), float(cell.y + padding)}); | ||||
|     shape->setFillColor(color); | ||||
|     shape->setOutlineColor(border_color); | ||||
|     shape->setOutlineThickness(border_px); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   void Meter::init(lel::Cell& cell) { | ||||
|     bar.init(cell); | ||||
|   } | ||||
| 
 | ||||
|   void Sound::play(bool hover) { | ||||
|     if(!hover) { | ||||
|       sound::play(on_click); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   void Background::init() { | ||||
|     sf::Vector2f size{float(w), float(h)}; | ||||
|     if(shape == nullptr) shape = make_shared<sf::RectangleShape>(size); | ||||
|     shape->setPosition({float(x), float(y)}); | ||||
|     shape->setFillColor(color); | ||||
|   } | ||||
| 
 | ||||
|   void Effect::init(lel::Cell &cell) { | ||||
|     $shader_version = shaders::version(); | ||||
|     $shader = shaders::get(name); | ||||
|     $shader->setUniform("u_resolution", sf::Vector2f({float(cell.w), float(cell.h)})); | ||||
|     $clock = std::make_shared<sf::Clock>(); | ||||
|   } | ||||
| 
 | ||||
|   void Effect::step() { | ||||
|     sf::Time cur_time = $clock->getElapsedTime(); | ||||
|     float u_time = cur_time.asSeconds(); | ||||
| 
 | ||||
|     if(u_time < $u_time_end) { | ||||
|       $shader->setUniform("u_duration", duration); | ||||
|       $shader->setUniform("u_time_end", $u_time_end); | ||||
|       $shader->setUniform("u_time", u_time); | ||||
|     } else { | ||||
|       $active = false; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   void Effect::run() { | ||||
|     $active = true; | ||||
|     sf::Time u_time = $clock->getElapsedTime(); | ||||
|     $u_time_end = u_time.asSeconds() + duration; | ||||
|   } | ||||
| 
 | ||||
|   shared_ptr<sf::Shader> Effect::checkout_ptr() { | ||||
|     if(shaders::updated($shader_version)) { | ||||
|       $shader = shaders::get(name); | ||||
|       $shader_version = shaders::version(); | ||||
|     } | ||||
| 
 | ||||
|     return $shader; | ||||
|   } | ||||
| 
 | ||||
|   UI::UI() { | ||||
|     $font = make_shared<sf::Font>(FONT_FILE_NAME); | ||||
|   } | ||||
| 
 | ||||
|   void UI::position(int x, int y, int width, int height) { | ||||
|     $parser.position(x, y, width, height); | ||||
|   } | ||||
| 
 | ||||
|   void UI::layout(std::string grid) { | ||||
|     $grid = grid; | ||||
|     bool good = $parser.parse($grid); | ||||
|     dbc::check(good, "LEL parsing failed."); | ||||
| 
 | ||||
|     for(auto& [name, cell] : $parser.cells) { | ||||
|       auto ent = init_entity(name); | ||||
|       $world.set<lel::Cell>(ent, cell); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   DinkyECS::Entity UI::init_entity(std::string name) { | ||||
|     auto entity = $world.entity(); | ||||
|     // this lets you look up an entity by name
 | ||||
|     $name_ents.insert_or_assign(name, entity); | ||||
|     // this makes it easier to get the name during querying
 | ||||
|     $world.set<CellName>(entity, {name}); | ||||
|     return entity; | ||||
|   } | ||||
| 
 | ||||
|   DinkyECS::Entity UI::entity(std::string name) { | ||||
|     dbc::check($name_ents.contains(name), | ||||
|         fmt::format("GUECS entity {} does not exist. Forgot to init_entity?", name)); | ||||
|     return $name_ents.at(name); | ||||
|   } | ||||
| 
 | ||||
|   void UI::init() { | ||||
|     if($world.has_the<Background>()) { | ||||
|       auto& bg = $world.get_the<Background>(); | ||||
|       bg.init(); | ||||
|     } | ||||
| 
 | ||||
|     $world.query<Background>([](auto, auto& bg) { | ||||
|         bg.init(); | ||||
|     }); | ||||
| 
 | ||||
|     $world.query<lel::Cell, Rectangle>([](auto, auto& cell, auto& rect) { | ||||
|       rect.init(cell); | ||||
|     }); | ||||
| 
 | ||||
|     $world.query<lel::Cell, Effect>([](auto, auto& cell, auto& shader) { | ||||
|         shader.init(cell); | ||||
|     }); | ||||
| 
 | ||||
|     $world.query<Rectangle, Meter>([](auto, auto& bg, auto &) { | ||||
|         bg.shape->setFillColor(ColorValue::BLACK); | ||||
|     }); | ||||
| 
 | ||||
|     $world.query<lel::Cell, Meter>([](auto, auto &cell, auto& meter) { | ||||
|         meter.init(cell); | ||||
|     }); | ||||
| 
 | ||||
|     $world.query<lel::Cell, Textual>([this](auto, auto& cell, auto& text) { | ||||
|       text.init(cell, $font); | ||||
|     }); | ||||
| 
 | ||||
|     $world.query<lel::Cell, Label>([this](auto, auto& cell, auto& text) { | ||||
|       text.init(cell, $font); | ||||
|     }); | ||||
| 
 | ||||
|     $world.query<lel::Cell, Sprite>([&](auto, auto &cell, auto &sprite) { | ||||
|         sprite.init(cell); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   void UI::debug_layout(sf::RenderWindow& window) { | ||||
|     $world.query<lel::Cell>([&](const auto, auto &cell) { | ||||
|         sf::RectangleShape rect{{float(cell.w), float(cell.h)}}; | ||||
|         rect.setPosition({float(cell.x), float(cell.y)}); | ||||
|         rect.setFillColor(sf::Color::Transparent); | ||||
|         rect.setOutlineColor(sf::Color::Red); | ||||
|         rect.setOutlineThickness(2.0f); | ||||
|         window.draw(rect); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   void UI::render(sf::RenderWindow& window) { | ||||
|     if($world.has_the<Background>()) { | ||||
|       auto& bg = $world.get_the<Background>(); | ||||
|       window.draw(*bg.shape); | ||||
|     } | ||||
| 
 | ||||
|     $world.query<Effect>([&](auto, auto& shader) { | ||||
|       if(shader.$active) shader.step(); | ||||
|     }); | ||||
| 
 | ||||
|     $world.query<Rectangle>([&](auto ent, auto& rect) { | ||||
|         render_helper(window, ent, true, rect.shape); | ||||
|     }); | ||||
| 
 | ||||
|     $world.query<lel::Cell, Meter>([&](auto ent, auto& cell, const auto &meter) { | ||||
|         float level = std::clamp(meter.percent, 0.0f, 1.0f) * float(cell.w); | ||||
|         // ZED: this 6 is a border width, make it a thing
 | ||||
|         meter.bar.shape->setSize({std::max(level, 0.0f), float(cell.h - 6)}); | ||||
|         render_helper(window, ent, true, meter.bar.shape); | ||||
|     }); | ||||
| 
 | ||||
|     $world.query<Sprite>([&](auto ent, auto& sprite) { | ||||
|         render_helper(window, ent, false, sprite.sprite); | ||||
|     }); | ||||
| 
 | ||||
|     $world.query<Label>([&](auto ent, auto& text) { | ||||
|         render_helper(window, ent, false, text.text); | ||||
|     }); | ||||
| 
 | ||||
|     $world.query<Textual>([&](auto ent, auto& text) { | ||||
|         render_helper(window, ent, true, text.text); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   bool UI::mouse(float x, float y, bool hover) { | ||||
|     int action_count = 0; | ||||
| 
 | ||||
|     $world.query<lel::Cell, Clickable>([&](auto ent, auto& cell, auto &clicked) { | ||||
|       if((x >= cell.x && x <= cell.x + cell.w) && | ||||
|           (y >= cell.y && y <= cell.y + cell.h)) | ||||
|       { | ||||
|         do_if<Effect>(ent, [hover](auto& effect) { | ||||
|           effect.$shader->setUniform("hover", hover); | ||||
|           effect.run(); | ||||
|         }); | ||||
| 
 | ||||
|         do_if<Sound>(ent, [hover](auto& sound) { | ||||
|             // here set that it played then only play once
 | ||||
|             sound.play(hover); | ||||
|         }); | ||||
| 
 | ||||
|         if(hover) return; // kinda gross
 | ||||
| 
 | ||||
|         if(auto action_data = get_if<ActionData>(ent)) { | ||||
|           clicked.action(ent, action_data->data); | ||||
|         } else { | ||||
|           clicked.action(ent, {}); | ||||
|         } | ||||
| 
 | ||||
|         action_count++; | ||||
|       } else { | ||||
|         // via ORBLISHJ
 | ||||
|         // just reset the hover trigger for all that aren't hit
 | ||||
|         // then in the ^^ positive branch play it and set it played
 | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
|     return action_count > 0; | ||||
|   } | ||||
| 
 | ||||
|   void UI::show_sprite(string region, string sprite_name) { | ||||
|     auto ent = entity(region); | ||||
| 
 | ||||
|     if(!has<Sprite>(ent)) { | ||||
|       Sprite to_show{sprite_name}; | ||||
|       auto& cell = cell_for(ent); | ||||
|       to_show.init(cell); | ||||
|       set<guecs::Sprite>(ent, to_show); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   void UI::show_text(string region, wstring content) { | ||||
|     auto ent = entity(region); | ||||
| 
 | ||||
|     if(auto text = get_if<Textual>(ent)) { | ||||
|       text->text->setString(content); | ||||
|     } else { | ||||
|       auto &cell = cell_for(ent); | ||||
|       Textual to_set{content, 20}; | ||||
|       to_set.init(cell, $font); | ||||
|       to_set.text->setFillColor(ColorValue::LIGHT_MID); | ||||
|       set<Textual>(ent, to_set); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   void UI::show_label(string region, wstring content) { | ||||
|     auto ent = entity(region); | ||||
| 
 | ||||
|     if(auto text = get_if<Label>(ent)) { | ||||
|       text->text->setString(content); | ||||
|     } else { | ||||
|       auto &cell = cell_for(ent); | ||||
|       Label to_set{content, 20}; | ||||
|       to_set.init(cell, $font); | ||||
|       to_set.text->setFillColor(ColorValue::LIGHT_MID); | ||||
|       set<Label>(ent, to_set); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   Clickable make_action(DinkyECS::World& target, Events::GUI event) { | ||||
|     return {[&, event](auto ent, auto data){ | ||||
|       // remember that ent is passed in from the UI::mouse handler
 | ||||
|       target.send<Events::GUI>(event, ent, data); | ||||
|     }}; | ||||
|   } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										235
									
								
								guecs.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										235
									
								
								guecs.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,235 @@ | |||
| #pragma once | ||||
| #include "color.hpp" | ||||
| #include "dinkyecs.hpp" | ||||
| #include "lel.hpp" | ||||
| #include <string> | ||||
| #include <memory> | ||||
| #include <SFML/Graphics.hpp> | ||||
| #include "textures.hpp" | ||||
| #include <functional> | ||||
| #include "events.hpp" | ||||
| #include "constants.hpp" | ||||
| #include <any> | ||||
| #include "shaders.hpp" | ||||
| 
 | ||||
| namespace guecs { | ||||
|   using std::shared_ptr, std::make_shared, std::wstring, std::string; | ||||
| 
 | ||||
|   struct Textual { | ||||
|     std::wstring content; | ||||
|     unsigned int size = GUECS_FONT_SIZE; | ||||
|     sf::Color color = GUECS_TEXT_COLOR; | ||||
|     int padding = GUECS_PADDING; | ||||
|     bool centered = false; | ||||
|     shared_ptr<sf::Font> font = nullptr; | ||||
|     shared_ptr<sf::Text> text = nullptr; | ||||
| 
 | ||||
|     void init(lel::Cell &cell, shared_ptr<sf::Font> font_ptr); | ||||
|     void update(std::wstring& new_content); | ||||
|   }; | ||||
| 
 | ||||
|   struct Label : public Textual { | ||||
|     template<typename... Args> | ||||
|     Label(Args... args) : Textual(args...) | ||||
|     { | ||||
|       centered = true; | ||||
|     } | ||||
| 
 | ||||
|     Label() { | ||||
|       centered = true; | ||||
|     }; | ||||
|   }; | ||||
| 
 | ||||
|   struct Clickable { | ||||
|     /* This is actually called by UI::mouse and passed the entity ID of the
 | ||||
|      * button pressed so you can interact with it in the event handler. | ||||
|      */ | ||||
|     std::function<void(DinkyECS::Entity ent, std::any data)> action; | ||||
|   }; | ||||
| 
 | ||||
|   struct Sprite { | ||||
|     std::string name; | ||||
|     int padding = GUECS_PADDING; | ||||
|     std::shared_ptr<sf::Sprite> sprite = nullptr; | ||||
|     std::shared_ptr<sf::Texture> texture = nullptr; | ||||
| 
 | ||||
|     void init(lel::Cell &cell); | ||||
|   }; | ||||
| 
 | ||||
|   struct Rectangle { | ||||
|     int padding = GUECS_PADDING; | ||||
|     sf::Color color = GUECS_FILL_COLOR; | ||||
|     sf::Color border_color = GUECS_BORDER_COLOR; | ||||
|     int border_px = GUECS_BORDER_PX; | ||||
|     shared_ptr<sf::RectangleShape> shape = nullptr; | ||||
| 
 | ||||
|     void init(lel::Cell& cell); | ||||
|   }; | ||||
| 
 | ||||
|   struct Meter { | ||||
|     float percent = 1.0f; | ||||
|     Rectangle bar; | ||||
| 
 | ||||
|     void init(lel::Cell& cell); | ||||
|   }; | ||||
| 
 | ||||
|   struct ActionData { | ||||
|     std::any data; | ||||
|   }; | ||||
| 
 | ||||
|   struct CellName { | ||||
|     std::string name; | ||||
|   }; | ||||
| 
 | ||||
|   struct Effect { | ||||
|     float duration = 0.1f; | ||||
|     std::string name{"ui_shader"}; | ||||
|     float $u_time_end = 0.0; | ||||
|     bool $active = false; | ||||
|     std::shared_ptr<sf::Clock> $clock = nullptr; | ||||
|     std::shared_ptr<sf::Shader> $shader = nullptr; | ||||
|     int $shader_version = 0; | ||||
| 
 | ||||
|     void init(lel::Cell &cell); | ||||
|     void run(); | ||||
|     void step(); | ||||
|     shared_ptr<sf::Shader> checkout_ptr(); | ||||
|   }; | ||||
| 
 | ||||
|   struct Sound { | ||||
|     std::string on_click{"ui_click"}; | ||||
|     void play(bool hover); | ||||
|   }; | ||||
| 
 | ||||
|   struct Background { | ||||
|     float x = 0.0f; | ||||
|     float y = 0.0f; | ||||
|     float w = 0.0f; | ||||
|     float h = 0.0f; | ||||
|     sf::Color color = GUECS_BG_COLOR; | ||||
|     shared_ptr<sf::RectangleShape> shape = nullptr; | ||||
| 
 | ||||
|     Background(lel::Parser& parser, sf::Color bg_color=GUECS_BG_COLOR) : | ||||
|       x(parser.grid_x), | ||||
|       y(parser.grid_y), | ||||
|       w(parser.grid_w), | ||||
|       h(parser.grid_h), | ||||
|       color(bg_color) | ||||
|     {} | ||||
| 
 | ||||
|     Background() {} | ||||
| 
 | ||||
|     void init(); | ||||
|   }; | ||||
| 
 | ||||
|   class UI { | ||||
|     public: | ||||
|       DinkyECS::World $world; | ||||
|       std::unordered_map<std::string, DinkyECS::Entity> $name_ents; | ||||
|       shared_ptr<sf::Font> $font = nullptr; | ||||
|       lel::Parser $parser; | ||||
|       std::string $grid = ""; | ||||
| 
 | ||||
|       UI(); | ||||
| 
 | ||||
|       void position(int x, int y, int width, int height); | ||||
|       void layout(std::string grid); | ||||
|       DinkyECS::Entity init_entity(std::string name); | ||||
|       DinkyECS::Entity entity(std::string name); | ||||
| 
 | ||||
|       inline lel::CellMap& cells() { | ||||
|         return $parser.cells; | ||||
|       } | ||||
| 
 | ||||
|       inline DinkyECS::World& world() { | ||||
|         return $world; | ||||
|       } | ||||
| 
 | ||||
|       void init(); | ||||
|       void render(sf::RenderWindow& window); | ||||
|       bool mouse(float x, float y, bool hover); | ||||
|       void debug_layout(sf::RenderWindow& window); | ||||
| 
 | ||||
|       template <typename Comp> | ||||
|       void set(DinkyECS::Entity ent, Comp val) { | ||||
|         $world.set<Comp>(ent, val); | ||||
|       } | ||||
| 
 | ||||
|       template <typename Comp> | ||||
|       void set_init(DinkyECS::Entity ent, Comp val) { | ||||
|         dbc::check(has<lel::Cell>(ent),"WRONG! slot is missing its cell?!"); | ||||
|         auto& cell = get<lel::Cell>(ent); | ||||
|         val.init(cell); | ||||
|         $world.set<Comp>(ent, val); | ||||
|       } | ||||
| 
 | ||||
|       template <typename Comp> | ||||
|       void do_if(DinkyECS::Entity ent, std::function<void(Comp &)> cb) { | ||||
|         if($world.has<Comp>(ent)) { | ||||
|           cb($world.get<Comp>(ent)); | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       lel::Cell& cell_for(DinkyECS::Entity ent) { | ||||
|         return $world.get<lel::Cell>(ent); | ||||
|       } | ||||
| 
 | ||||
|       lel::Cell& cell_for(std::string name) { | ||||
|         DinkyECS::Entity ent = entity(name); | ||||
|         return $world.get<lel::Cell>(ent); | ||||
|       } | ||||
| 
 | ||||
|       template <typename Comp> | ||||
|         Comp& get(DinkyECS::Entity entity) { | ||||
|           return $world.get<Comp>(entity); | ||||
|         } | ||||
| 
 | ||||
|       template <typename Comp> | ||||
|         std::optional<Comp> get_if(DinkyECS::Entity entity) { | ||||
|           return $world.get_if<Comp>(entity); | ||||
|         } | ||||
| 
 | ||||
|       template <typename Comp> | ||||
|         bool has(DinkyECS::Entity entity) { | ||||
|           return $world.has<Comp>(entity); | ||||
|         } | ||||
| 
 | ||||
|       template <typename Comp> | ||||
|         void remove(DinkyECS::Entity ent) { | ||||
|           $world.remove<Comp>(ent); | ||||
|         } | ||||
| 
 | ||||
|       template <typename Comp> | ||||
|       void close(string region) { | ||||
|         auto ent = entity(region); | ||||
|         remove<Comp>(ent); | ||||
|       } | ||||
| 
 | ||||
|       template<typename T> | ||||
|       void render_helper(sf::RenderWindow& window, DinkyECS::Entity ent, bool is_shape, T& target) { | ||||
|         sf::Shader *shader_ptr = nullptr; | ||||
| 
 | ||||
|         if($world.has<Effect>(ent)) { | ||||
|           auto& shader = $world.get<Effect>(ent); | ||||
| 
 | ||||
|           if(shader.$active) { | ||||
|             auto ptr = shader.checkout_ptr(); | ||||
|             ptr->setUniform("is_shape", is_shape); | ||||
|             // NOTE: this is needed because SFML doesn't handle shared_ptr
 | ||||
|             shader_ptr = ptr.get(); | ||||
|           } | ||||
|         } | ||||
| 
 | ||||
|         window.draw(*target, shader_ptr); | ||||
|       } | ||||
| 
 | ||||
|       void show_sprite(string region, string sprite_name); | ||||
|       void show_text(string region, wstring content); | ||||
|       void update_text(string region, wstring content); | ||||
|       void update_label(string region, wstring content); | ||||
|       void show_label(string region, wstring content); | ||||
|   }; | ||||
| 
 | ||||
|   Clickable make_action(DinkyECS::World& target, Events::GUI event); | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										117
									
								
								lel.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								lel.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,117 @@ | |||
| #include "lel.hpp" | ||||
| #include <fmt/core.h> | ||||
| #include "dbc.hpp" | ||||
| #include <numeric> | ||||
| 
 | ||||
| #include "lel_parser.cpp" | ||||
| 
 | ||||
| namespace lel { | ||||
| 
 | ||||
|   Parser::Parser(int x, int y, int width, int height) : | ||||
|     grid_x(x), | ||||
|     grid_y(y), | ||||
|     grid_w(width), | ||||
|     grid_h(height), | ||||
|     cur(0, 0) | ||||
|   { | ||||
|   } | ||||
| 
 | ||||
|   Parser::Parser() : cur(0, 0) { } | ||||
| 
 | ||||
|   void Parser::position(int x, int y, int width, int height) { | ||||
|     grid_x = x; | ||||
|     grid_y = y; | ||||
|     grid_w = width; | ||||
|     grid_h = height; | ||||
|   } | ||||
| 
 | ||||
|   void Parser::id(std::string name) { | ||||
|     if(name != "_") { | ||||
|       dbc::check(!cells.contains(name), | ||||
|           fmt::format("duplicate cell name {}", name)); | ||||
|       cells.insert_or_assign(name, cur); | ||||
|     } | ||||
| 
 | ||||
|     cur = {cur.col + 1, cur.row}; | ||||
|     auto& row = grid.back(); | ||||
|     row.push_back(name); | ||||
|   } | ||||
| 
 | ||||
|   void Parser::finalize() { | ||||
|     size_t rows = grid.size(); | ||||
|     int cell_height = grid_h / rows; | ||||
| 
 | ||||
|     for(auto& row : grid) { | ||||
|       size_t columns = row.size(); | ||||
|       int cell_width = grid_w / columns; | ||||
|       dbc::check(cell_width > 0, "invalid cell width calc"); | ||||
|       dbc::check(cell_height > 0, "invalid cell height calc"); | ||||
| 
 | ||||
|       for(auto& name : row) { | ||||
|         if(name == "_") continue; | ||||
|         auto& cell = cells.at(name); | ||||
| 
 | ||||
|         // ZED: getting a bit hairy but this should work
 | ||||
|         if(cell.percent) { | ||||
|           // when percent mode we have to take unset to 100%
 | ||||
|           if(cell.max_w == 0) cell.max_w = 100; | ||||
|           if(cell.max_h == 0) cell.max_h = 100; | ||||
|           cell.max_w *= cell_width * 0.01; | ||||
|           cell.max_h *= cell_height * 0.01; | ||||
|         } else { | ||||
|           if(cell.max_w == 0) cell.max_w = cell_width; | ||||
|           if(cell.max_h == 0) cell.max_h = cell_height; | ||||
|         } | ||||
| 
 | ||||
|         cell.w = cell.expand ? std::min(cell.max_w, grid_w) : std::min(cell_width, cell.max_w); | ||||
|         cell.h = cell.expand ? std::min(cell.max_h, grid_h) : std::min(cell_height, cell.max_h); | ||||
| 
 | ||||
|         dbc::check(cell.h > 0, fmt::format("invalid height cell {}", name)); | ||||
|         dbc::check(cell.w > 0, fmt::format("invalid width cell {}", name)); | ||||
| 
 | ||||
|         cell.x = grid_x + (cell.col * cell_width); | ||||
|         cell.y = grid_y + (cell.row * cell_height); | ||||
| 
 | ||||
|         // keep the midpoint since it is used a lot
 | ||||
|         cell.mid_x = std::midpoint(cell.x, cell.x + cell.w); | ||||
|         cell.mid_y = std::midpoint(cell.y, cell.y + cell.h); | ||||
| 
 | ||||
|         // perform alignments
 | ||||
|         if(cell.right) cell.x += cell_width - cell.w; | ||||
|         if(cell.bottom) cell.y += cell_height - cell.h; | ||||
|         if(cell.center) { | ||||
|           cell.x = cell.mid_x - cell.w / 2; | ||||
|           cell.y = cell.mid_y - cell.h / 2; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   void Parser::reset() { | ||||
|     cur = {0, 0}; | ||||
|     grid.clear(); | ||||
|     cells.clear(); | ||||
|   } | ||||
| 
 | ||||
|   std::optional<std::string> Parser::hit(int x, int y) { | ||||
|     for(auto& [name, cell] : cells) { | ||||
|       if((x >= cell.x && x <= cell.x + cell.w) && | ||||
|           (y >= cell.y && y <= cell.y + cell.h)) { | ||||
|         return name; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     return std::nullopt; | ||||
|   } | ||||
| 
 | ||||
|   Cell center(int width, int height, Cell &parent) { | ||||
|     Cell copy = parent; | ||||
| 
 | ||||
|     copy.x = parent.mid_x - width / 2; | ||||
|     copy.y = parent.mid_y - height / 2; | ||||
|     copy.w = width; | ||||
|     copy.h = height; | ||||
| 
 | ||||
|     return copy; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										57
									
								
								lel.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								lel.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,57 @@ | |||
| #pragma once | ||||
| #include <string> | ||||
| #include <unordered_map> | ||||
| #include <functional> | ||||
| #include <optional> | ||||
| #include <vector> | ||||
| #include "dbc.hpp" | ||||
| 
 | ||||
| namespace lel { | ||||
| 
 | ||||
|   struct Cell { | ||||
|     int x = 0; | ||||
|     int y = 0; | ||||
|     int w = 0; | ||||
|     int h = 0; | ||||
|     int mid_x = 0; | ||||
|     int mid_y = 0; | ||||
|     int max_w = 0; | ||||
|     int max_h = 0; | ||||
|     int col = 0; | ||||
|     int row = 0; | ||||
|     bool right = false; | ||||
|     bool bottom = false; | ||||
|     bool expand = false; | ||||
|     bool center = false; | ||||
|     bool percent = false; | ||||
| 
 | ||||
|     Cell(int col, int row) : col(col), row(row) {} | ||||
|     Cell() {} | ||||
|   }; | ||||
| 
 | ||||
|   using Row = std::vector<std::string>; | ||||
|   using CellMap = std::unordered_map<std::string, Cell>; | ||||
| 
 | ||||
|   struct Parser { | ||||
|     int grid_x = 0; | ||||
|     int grid_y = 0; | ||||
|     int grid_w = 0; | ||||
|     int grid_h = 0; | ||||
|     Cell cur; | ||||
|     std::vector<Row> grid; | ||||
|     CellMap cells; | ||||
| 
 | ||||
|     Parser(int x, int y, int width, int height); | ||||
|     Parser(); | ||||
| 
 | ||||
|     void position(int x, int y, int width, int height); | ||||
|     void id(std::string name); | ||||
|     void reset(); | ||||
|     bool parse(std::string input); | ||||
|     void finalize(); | ||||
|     std::optional<std::string> hit(int x, int y); | ||||
|   }; | ||||
| 
 | ||||
| 
 | ||||
|   Cell center(int width, int height, Cell &parent); | ||||
| } | ||||
							
								
								
									
										263
									
								
								lel_parser.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										263
									
								
								lel_parser.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,263 @@ | |||
| 
 | ||||
| #line 1 "lel_parser.rl" | ||||
| /***** !!!!!! THIS IS INCLUDED BY lel.cpp DO NOT PUT IN BUILD!!!!!! ******/ | ||||
| 
 | ||||
| #include "lel.hpp" | ||||
| #include <fmt/core.h> | ||||
| #include <iostream> | ||||
| 
 | ||||
| namespace lel { | ||||
| 
 | ||||
| 
 | ||||
| #line 42 "lel_parser.rl" | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| #line 12 "lel_parser.cpp" | ||||
| static const char _Parser_actions[] = { | ||||
| 	0, 1, 1, 1, 2, 1, 3, 1,  | ||||
| 	4, 1, 5, 1, 6, 1, 9, 1,  | ||||
| 	10, 1, 11, 1, 12, 1, 13, 2,  | ||||
| 	0, 7, 2, 0, 8, 2, 4, 1,  | ||||
| 	2, 4, 5 | ||||
| }; | ||||
| 
 | ||||
| static const char _Parser_key_offsets[] = { | ||||
| 	0, 0, 4, 20, 33, 35, 39, 41,  | ||||
| 	44, 56, 61 | ||||
| }; | ||||
| 
 | ||||
| static const char _Parser_trans_keys[] = { | ||||
| 	32, 91, 9, 13, 32, 37, 40, 42,  | ||||
| 	46, 61, 94, 95, 9, 13, 60, 62,  | ||||
| 	65, 90, 97, 122, 37, 40, 42, 46,  | ||||
| 	61, 94, 95, 60, 62, 65, 90, 97,  | ||||
| 	122, 48, 57, 41, 44, 48, 57, 48,  | ||||
| 	57, 41, 48, 57, 32, 93, 95, 124,  | ||||
| 	9, 13, 48, 57, 65, 90, 97, 122,  | ||||
| 	32, 93, 124, 9, 13, 32, 91, 9,  | ||||
| 	13, 0 | ||||
| }; | ||||
| 
 | ||||
| static const char _Parser_single_lengths[] = { | ||||
| 	0, 2, 8, 7, 0, 2, 0, 1,  | ||||
| 	4, 3, 2 | ||||
| }; | ||||
| 
 | ||||
| static const char _Parser_range_lengths[] = { | ||||
| 	0, 1, 4, 3, 1, 1, 1, 1,  | ||||
| 	4, 1, 1 | ||||
| }; | ||||
| 
 | ||||
| static const char _Parser_index_offsets[] = { | ||||
| 	0, 0, 4, 17, 28, 30, 34, 36,  | ||||
| 	39, 48, 53 | ||||
| }; | ||||
| 
 | ||||
| static const char _Parser_indicies[] = { | ||||
| 	0, 2, 0, 1, 3, 4, 5, 6,  | ||||
| 	7, 9, 7, 10, 3, 8, 10, 10,  | ||||
| 	1, 4, 5, 6, 7, 9, 7, 10,  | ||||
| 	8, 10, 10, 1, 11, 1, 12, 13,  | ||||
| 	14, 1, 15, 1, 16, 17, 1, 18,  | ||||
| 	20, 19, 21, 18, 19, 19, 19, 1,  | ||||
| 	22, 23, 24, 22, 1, 25, 2, 25,  | ||||
| 	1, 0 | ||||
| }; | ||||
| 
 | ||||
| static const char _Parser_trans_targs[] = { | ||||
| 	1, 0, 2, 2, 3, 4, 3, 3,  | ||||
| 	3, 3, 8, 5, 3, 6, 5, 7,  | ||||
| 	3, 7, 9, 8, 10, 2, 9, 10,  | ||||
| 	2, 10 | ||||
| }; | ||||
| 
 | ||||
| static const char _Parser_trans_actions[] = { | ||||
| 	0, 0, 3, 0, 17, 0, 13, 5,  | ||||
| 	11, 15, 21, 19, 23, 23, 0, 19,  | ||||
| 	26, 0, 7, 0, 32, 29, 0, 9,  | ||||
| 	1, 0 | ||||
| }; | ||||
| 
 | ||||
| static const int Parser_start = 1; | ||||
| static const int Parser_first_final = 10; | ||||
| static const int Parser_error = 0; | ||||
| 
 | ||||
| static const int Parser_en_main = 1; | ||||
| 
 | ||||
| 
 | ||||
| #line 45 "lel_parser.rl" | ||||
| 
 | ||||
| bool Parser::parse(std::string input) { | ||||
|   reset(); | ||||
|   int cs = 0; | ||||
|   const char *start = nullptr; | ||||
|   const char *begin = input.data(); | ||||
|   const char *p = input.data(); | ||||
|   const char *pe = p + input.size(); | ||||
|   std::string tk; | ||||
| 
 | ||||
|    | ||||
| #line 93 "lel_parser.cpp" | ||||
| 	{ | ||||
| 	cs = Parser_start; | ||||
| 	} | ||||
| 
 | ||||
| #line 56 "lel_parser.rl" | ||||
|    | ||||
| #line 96 "lel_parser.cpp" | ||||
| 	{ | ||||
| 	int _klen; | ||||
| 	unsigned int _trans; | ||||
| 	const char *_acts; | ||||
| 	unsigned int _nacts; | ||||
| 	const char *_keys; | ||||
| 
 | ||||
| 	if ( p == pe ) | ||||
| 		goto _test_eof; | ||||
| 	if ( cs == 0 ) | ||||
| 		goto _out; | ||||
| _resume: | ||||
| 	_keys = _Parser_trans_keys + _Parser_key_offsets[cs]; | ||||
| 	_trans = _Parser_index_offsets[cs]; | ||||
| 
 | ||||
| 	_klen = _Parser_single_lengths[cs]; | ||||
| 	if ( _klen > 0 ) { | ||||
| 		const char *_lower = _keys; | ||||
| 		const char *_mid; | ||||
| 		const char *_upper = _keys + _klen - 1; | ||||
| 		while (1) { | ||||
| 			if ( _upper < _lower ) | ||||
| 				break; | ||||
| 
 | ||||
| 			_mid = _lower + ((_upper-_lower) >> 1); | ||||
| 			if ( (*p) < *_mid ) | ||||
| 				_upper = _mid - 1; | ||||
| 			else if ( (*p) > *_mid ) | ||||
| 				_lower = _mid + 1; | ||||
| 			else { | ||||
| 				_trans += (unsigned int)(_mid - _keys); | ||||
| 				goto _match; | ||||
| 			} | ||||
| 		} | ||||
| 		_keys += _klen; | ||||
| 		_trans += _klen; | ||||
| 	} | ||||
| 
 | ||||
| 	_klen = _Parser_range_lengths[cs]; | ||||
| 	if ( _klen > 0 ) { | ||||
| 		const char *_lower = _keys; | ||||
| 		const char *_mid; | ||||
| 		const char *_upper = _keys + (_klen<<1) - 2; | ||||
| 		while (1) { | ||||
| 			if ( _upper < _lower ) | ||||
| 				break; | ||||
| 
 | ||||
| 			_mid = _lower + (((_upper-_lower) >> 1) & ~1); | ||||
| 			if ( (*p) < _mid[0] ) | ||||
| 				_upper = _mid - 2; | ||||
| 			else if ( (*p) > _mid[1] ) | ||||
| 				_lower = _mid + 2; | ||||
| 			else { | ||||
| 				_trans += (unsigned int)((_mid - _keys)>>1); | ||||
| 				goto _match; | ||||
| 			} | ||||
| 		} | ||||
| 		_trans += _klen; | ||||
| 	} | ||||
| 
 | ||||
| _match: | ||||
| 	_trans = _Parser_indicies[_trans]; | ||||
| 	cs = _Parser_trans_targs[_trans]; | ||||
| 
 | ||||
| 	if ( _Parser_trans_actions[_trans] == 0 ) | ||||
| 		goto _again; | ||||
| 
 | ||||
| 	_acts = _Parser_actions + _Parser_trans_actions[_trans]; | ||||
| 	_nacts = (unsigned int) *_acts++; | ||||
| 	while ( _nacts-- > 0 ) | ||||
| 	{ | ||||
| 		switch ( *_acts++ ) | ||||
| 		{ | ||||
| 	case 0: | ||||
| #line 13 "lel_parser.rl" | ||||
| 	{tk = input.substr(start - begin, p - start); } | ||||
| 	break; | ||||
| 	case 1: | ||||
| #line 15 "lel_parser.rl" | ||||
| 	{} | ||||
| 	break; | ||||
| 	case 2: | ||||
| #line 16 "lel_parser.rl" | ||||
| 	{ grid.push_back(Row()); } | ||||
| 	break; | ||||
| 	case 3: | ||||
| #line 17 "lel_parser.rl" | ||||
| 	{ cur.bottom = (*p) == '.'; } | ||||
| 	break; | ||||
| 	case 4: | ||||
| #line 18 "lel_parser.rl" | ||||
| 	{ id(input.substr(start - begin, p - start)); } | ||||
| 	break; | ||||
| 	case 5: | ||||
| #line 19 "lel_parser.rl" | ||||
| 	{ cur.col = 0; cur.row++; } | ||||
| 	break; | ||||
| 	case 6: | ||||
| #line 20 "lel_parser.rl" | ||||
| 	{ cur.right = (*p) == '>'; } | ||||
| 	break; | ||||
| 	case 7: | ||||
| #line 21 "lel_parser.rl" | ||||
| 	{cur.max_w = std::stoi(tk); } | ||||
| 	break; | ||||
| 	case 8: | ||||
| #line 22 "lel_parser.rl" | ||||
| 	{ cur.max_h = std::stoi(tk); } | ||||
| 	break; | ||||
| 	case 9: | ||||
| #line 23 "lel_parser.rl" | ||||
| 	{ cur.expand = true; } | ||||
| 	break; | ||||
| 	case 10: | ||||
| #line 24 "lel_parser.rl" | ||||
| 	{ cur.center = true; } | ||||
| 	break; | ||||
| 	case 11: | ||||
| #line 25 "lel_parser.rl" | ||||
| 	{ cur.percent = true; } | ||||
| 	break; | ||||
| 	case 12: | ||||
| #line 35 "lel_parser.rl" | ||||
| 	{ start = p; } | ||||
| 	break; | ||||
| 	case 13: | ||||
| #line 38 "lel_parser.rl" | ||||
| 	{start = p;} | ||||
| 	break; | ||||
| #line 211 "lel_parser.cpp" | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| _again: | ||||
| 	if ( cs == 0 ) | ||||
| 		goto _out; | ||||
| 	if ( ++p != pe ) | ||||
| 		goto _resume; | ||||
| 	_test_eof: {} | ||||
| 	_out: {} | ||||
| 	} | ||||
| 
 | ||||
| #line 57 "lel_parser.rl" | ||||
| 
 | ||||
|   bool good = pe - p == 0; | ||||
|   if(good) { | ||||
|     finalize(); | ||||
|   } else { | ||||
|     dbc::log("error at:"); | ||||
|     std::cout << p; | ||||
|   } | ||||
|   return good; | ||||
| } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										68
									
								
								lel_parser.rl
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								lel_parser.rl
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,68 @@ | |||
| /***** !!!!!! THIS IS INCLUDED BY lel.cpp DO NOT PUT IN BUILD!!!!!! ******/ | ||||
| 
 | ||||
| #include "lel.hpp" | ||||
| #include <fmt/core.h> | ||||
| #include <iostream> | ||||
| 
 | ||||
| namespace lel { | ||||
| 
 | ||||
| %%{ | ||||
|   machine Parser; | ||||
|   alphtype char; | ||||
| 
 | ||||
|   action token {tk = input.substr(start - begin, fpc - start); } | ||||
| 
 | ||||
|   action col {} | ||||
|   action ltab { grid.push_back(Row()); } | ||||
|   action valign { cur.bottom = fc == '.'; } | ||||
|   action id { id(input.substr(start - begin, fpc - start)); } | ||||
|   action row { cur.col = 0; cur.row++; } | ||||
|   action align { cur.right = fc == '>'; } | ||||
|   action setwidth {cur.max_w = std::stoi(tk); } | ||||
|   action setheight { cur.max_h = std::stoi(tk); } | ||||
|   action expand { cur.expand = true; } | ||||
|   action center { cur.center = true; } | ||||
|   action percent { cur.percent = true; } | ||||
| 
 | ||||
|   col = "|" $col; | ||||
|   ltab = "[" $ltab; | ||||
|   rtab = "]" $row; | ||||
|   valign = ("^" | ".") $valign; | ||||
|   expand = "*" $expand; | ||||
|   center = "=" $center; | ||||
|   percent = "%" $percent; | ||||
|   halign = ("<" | ">") $align; | ||||
|   number =  digit+ >{ start = fpc; } %token; | ||||
|   setw = ("(" number %setwidth ("," number %setheight)? ")") ; | ||||
|   modifiers = (percent | center | expand | valign | halign | setw); | ||||
|   id = modifiers* ((alpha | '_')+ :>> (alnum | '_')*) >{start = fpc;} %id; | ||||
|   row = space* ltab space* id space* (col space* id space*)* space* rtab space*; | ||||
| 
 | ||||
|   main := row+; | ||||
| }%% | ||||
| 
 | ||||
| %% write data; | ||||
| 
 | ||||
| bool Parser::parse(std::string input) { | ||||
|   reset(); | ||||
|   int cs = 0; | ||||
|   const char *start = nullptr; | ||||
|   const char *begin = input.data(); | ||||
|   const char *p = input.data(); | ||||
|   const char *pe = p + input.size(); | ||||
|   std::string tk; | ||||
| 
 | ||||
|   %% write init; | ||||
|   %% write exec; | ||||
| 
 | ||||
|   bool good = pe - p == 0; | ||||
|   if(good) { | ||||
|     finalize(); | ||||
|   } else { | ||||
|     dbc::log("error at:"); | ||||
|     std::cout << p; | ||||
|   } | ||||
|   return good; | ||||
| } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										31
									
								
								matrix.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								matrix.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,31 @@ | |||
| #include "matrix.hpp" | ||||
| #include "dbc.hpp" | ||||
| #include <fmt/core.h> | ||||
| #include <cmath> | ||||
| #include <cstdlib> | ||||
| #include "constants.hpp" | ||||
| 
 | ||||
| using namespace fmt; | ||||
| using std::min, std::max; | ||||
| 
 | ||||
| namespace matrix { | ||||
|   void dump(const std::string &msg, Matrix &map, int show_x, int show_y) { | ||||
|     println("----------------- {}", msg); | ||||
| 
 | ||||
|     for(each_row it{map}; it.next();) { | ||||
|       int cell = map[it.y][it.x]; | ||||
| 
 | ||||
|       if(int(it.x) == show_x && int(it.y) == show_y) { | ||||
|         print("{:x}<", cell); | ||||
|       } else if(cell > 15 && cell < 32) { | ||||
|         print("{:x}+", cell - 16); | ||||
|       } else if(cell > 31) { | ||||
|         print("* "); | ||||
|       } else { | ||||
|         print("{:x} ", cell); | ||||
|       } | ||||
| 
 | ||||
|       if(it.row) print("\n"); | ||||
|     } | ||||
|   } | ||||
| } | ||||
							
								
								
									
										49
									
								
								matrix.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								matrix.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,49 @@ | |||
| #pragma once | ||||
| #include <vector> | ||||
| #include <queue> | ||||
| #include <string> | ||||
| #include <array> | ||||
| #include <numeric> | ||||
| #include <algorithm> | ||||
| #include <fmt/core.h> | ||||
| #include "dbc.hpp" | ||||
| #include "shiterator.hpp" | ||||
| 
 | ||||
| namespace matrix { | ||||
|   using Row = shiterator::BaseRow<int>; | ||||
|   using Matrix = shiterator::Base<int>; | ||||
| 
 | ||||
|   using viewport = shiterator::viewport_t<Matrix>; | ||||
| 
 | ||||
|   using each_cell = shiterator::each_cell_t<Matrix>; | ||||
| 
 | ||||
|   using each_row = shiterator::each_row_t<Matrix>; | ||||
|   using box = shiterator::box_t<Matrix>; | ||||
|   using compass = shiterator::compass_t<Matrix>; | ||||
|   using circle = shiterator::circle_t<Matrix>; | ||||
|   using rectangle = shiterator::rectangle_t<Matrix>; | ||||
|   using rando_rect = shiterator::rando_rect_t<Matrix>; | ||||
|   using line = shiterator::line; | ||||
| 
 | ||||
|   void dump(const std::string &msg, Matrix &map, int show_x=-1, int show_y=-1); | ||||
| 
 | ||||
|   inline Matrix make(size_t width, size_t height) { | ||||
|     return shiterator::make<int>(width, height); | ||||
|   } | ||||
| 
 | ||||
|   inline bool inbounds(Matrix &mat, size_t x, size_t y) { | ||||
|     return shiterator::inbounds(mat, x, y); | ||||
|   } | ||||
| 
 | ||||
|   inline size_t width(Matrix &mat) { | ||||
|     return shiterator::width(mat); | ||||
|   } | ||||
| 
 | ||||
|   inline size_t height(Matrix &mat) { | ||||
|     return shiterator::height(mat); | ||||
|   } | ||||
| 
 | ||||
|   inline void assign(Matrix &out, int new_value) { | ||||
|     shiterator::assign(out, new_value); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										19
									
								
								meson.build
									
										
									
									
									
								
							
							
						
						
									
										19
									
								
								meson.build
									
										
									
									
									
								
							|  | @ -93,16 +93,23 @@ dependencies += [ | |||
| ] | ||||
| 
 | ||||
| sources = [ | ||||
|     'game_engine.cpp', | ||||
|     'dbc.cpp', | ||||
|     'gui.cpp', | ||||
|     'watcher.cpp', | ||||
|     'builder.cpp', | ||||
|     'config.cpp', | ||||
|     'dbc.cpp', | ||||
|     'game_engine.cpp', | ||||
|     'guecs.cpp', | ||||
|     'gui.cpp', | ||||
|     'lel.cpp', | ||||
|     'matrix.cpp', | ||||
|     'rand.cpp', | ||||
|     'sfmlbackend.cpp', | ||||
|     'shaders.cpp', | ||||
|     'sound.cpp', | ||||
|     'textures.cpp', | ||||
|     'watcher.cpp', | ||||
| ] | ||||
| 
 | ||||
| 
 | ||||
| executable('escape_turings_tarpit', sources + [ | ||||
| executable('ttpit', sources + [ | ||||
|   'main.cpp' | ||||
|   ], | ||||
|   cpp_args: cpp_args, | ||||
|  |  | |||
							
								
								
									
										20
									
								
								point.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								point.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,20 @@ | |||
| #pragma once | ||||
| #include <vector> | ||||
| 
 | ||||
| struct Point { | ||||
|   size_t x = 0; | ||||
|   size_t y = 0; | ||||
| 
 | ||||
|   bool operator==(const Point& other) const { | ||||
|     return other.x == x && other.y == y; | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| typedef std::vector<Point> PointList; | ||||
| 
 | ||||
| template<> struct std::hash<Point> { | ||||
|   size_t operator()(const Point& p) const { | ||||
|     auto hasher = std::hash<int>(); | ||||
|     return hasher(p.x) ^ hasher(p.y); | ||||
|   } | ||||
| }; | ||||
							
								
								
									
										6
									
								
								rand.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								rand.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,6 @@ | |||
| #include "rand.hpp" | ||||
| 
 | ||||
| namespace Random { | ||||
|   std::random_device RNG; | ||||
|   std::mt19937 GENERATOR(RNG()); | ||||
| } | ||||
							
								
								
									
										28
									
								
								rand.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								rand.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,28 @@ | |||
| #pragma once | ||||
| #include <random> | ||||
| 
 | ||||
| 
 | ||||
| namespace Random { | ||||
|   extern std::mt19937 GENERATOR; | ||||
| 
 | ||||
|   template<typename T> | ||||
|   T uniform(T from, T to) { | ||||
|     std::uniform_int_distribution<T> rand(from, to); | ||||
| 
 | ||||
|     return rand(GENERATOR); | ||||
|   } | ||||
| 
 | ||||
|   template<typename T> | ||||
|   T uniform_real(T from, T to) { | ||||
|     std::uniform_real_distribution<T> rand(from, to); | ||||
| 
 | ||||
|     return rand(GENERATOR); | ||||
|   } | ||||
| 
 | ||||
|   template<typename T> | ||||
|   T normal(T mean, T stddev) { | ||||
|     std::normal_distribution<T> rand(mean, stddev); | ||||
| 
 | ||||
|     return rand(GENERATOR); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										77
									
								
								shaders.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								shaders.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,77 @@ | |||
| #include "shaders.hpp" | ||||
| #include <SFML/Graphics/Image.hpp> | ||||
| #include "dbc.hpp" | ||||
| #include <fmt/core.h> | ||||
| #include "config.hpp" | ||||
| #include <memory> | ||||
| 
 | ||||
| namespace shaders { | ||||
|   using std::shared_ptr, std::make_shared; | ||||
| 
 | ||||
|   static ShaderManager SMGR; | ||||
|   static bool INITIALIZED = false; | ||||
|   static int VERSION = 0; | ||||
| 
 | ||||
|   inline void configure_shader_defaults(std::shared_ptr<sf::Shader> ptr) { | ||||
|     ptr->setUniform("source", sf::Shader::CurrentTexture); | ||||
|   } | ||||
| 
 | ||||
|   bool load_shader(std::string name, nlohmann::json& settings) { | ||||
|     std::string file_name = settings["file_name"]; | ||||
|     auto ptr = std::make_shared<sf::Shader>(); | ||||
|     bool good = ptr->loadFromFile(file_name, sf::Shader::Type::Fragment); | ||||
| 
 | ||||
|     if(good) { | ||||
|       configure_shader_defaults(ptr); | ||||
|       SMGR.shaders.try_emplace(name, name, file_name, ptr); | ||||
|     } | ||||
|     return good; | ||||
|   } | ||||
| 
 | ||||
|   void init() { | ||||
|     if(!INITIALIZED) { | ||||
|       dbc::check(sf::Shader::isAvailable(), "no shaders?!"); | ||||
|       INITIALIZED = true; | ||||
|       Config config("assets/shaders.json"); | ||||
|       bool good = load_shader("ERROR", config["ERROR"]); | ||||
|       dbc::check(good, "Failed to load ERROR shader. Look in assets/shaders.json"); | ||||
| 
 | ||||
|       for(auto& [name, settings] : config.json().items()) { | ||||
|         if(name == "ERROR") continue; | ||||
| 
 | ||||
|         dbc::check(!SMGR.shaders.contains(name), | ||||
|             fmt::format("shader name '{}' duplicated in assets/shaders.json", name)); | ||||
|         good = load_shader(name, settings); | ||||
| 
 | ||||
|         if(!good) { | ||||
|           dbc::log(fmt::format("failed to load shader {}", name)); | ||||
|           SMGR.shaders.insert_or_assign(name, SMGR.shaders.at("ERROR")); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   std::shared_ptr<sf::Shader> get(const std::string& name) { | ||||
|     dbc::check(INITIALIZED, "you forgot to shaders::init()"); | ||||
|     dbc::check(SMGR.shaders.contains(name), | ||||
|         fmt::format("shader name '{}' not in assets/shaders.json", name)); | ||||
|     auto& rec = SMGR.shaders.at(name); | ||||
|     return rec.ptr; | ||||
|   } | ||||
| 
 | ||||
|   int reload() { | ||||
|     VERSION++; | ||||
|     INITIALIZED = false; | ||||
|     SMGR.shaders.clear(); | ||||
|     init(); | ||||
|     return VERSION; | ||||
|   } | ||||
| 
 | ||||
|   bool updated(int my_version) { | ||||
|     return my_version != VERSION; | ||||
|   } | ||||
| 
 | ||||
|   int version() { | ||||
|     return VERSION; | ||||
|   } | ||||
| }; | ||||
							
								
								
									
										28
									
								
								shaders.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								shaders.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,28 @@ | |||
| #pragma once | ||||
| #include <cstdint> | ||||
| #include <vector> | ||||
| #include <string> | ||||
| #include <SFML/Graphics.hpp> | ||||
| #include <unordered_map> | ||||
| #include <memory> | ||||
| #include "matrix.hpp" | ||||
| #include <nlohmann/json.hpp> | ||||
| 
 | ||||
| namespace shaders { | ||||
|   struct Record { | ||||
|     std::string name; | ||||
|     std::string file_name; | ||||
|     std::shared_ptr<sf::Shader> ptr = nullptr; | ||||
|   }; | ||||
| 
 | ||||
|   struct ShaderManager { | ||||
|     std::unordered_map<std::string, Record> shaders; | ||||
|   }; | ||||
| 
 | ||||
|   std::shared_ptr<sf::Shader> get(const std::string& name); | ||||
|   void init(); | ||||
|   bool load_shader(std::string& name, nlohmann::json& settings); | ||||
|   bool updated(int my_version); | ||||
|   int reload(); | ||||
|   int version(); | ||||
| } | ||||
							
								
								
									
										607
									
								
								shiterator.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										607
									
								
								shiterator.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,607 @@ | |||
| #pragma once | ||||
| #include <vector> | ||||
| #include <queue> | ||||
| #include <string> | ||||
| #include <array> | ||||
| #include <numeric> | ||||
| #include <algorithm> | ||||
| #include <fmt/core.h> | ||||
| #include "point.hpp" | ||||
| #include "rand.hpp" | ||||
| #include "dbc.hpp" | ||||
| 
 | ||||
| /*
 | ||||
|  * # What is This Shit? | ||||
|  * | ||||
|  * Announcing the Shape Iterators, or "shiterators" for short.  The best shite | ||||
|  * for C++ for-loops since that [one youtube | ||||
|  * video](https://www.youtube.com/watch?v=rX0ItVEVjHc) told everyone to
 | ||||
|  * recreate SQL databases with structs. You could also say these are Shaw's | ||||
|  * Iterators, but either way they are the _shite_. Or are they shit? You decide. | ||||
|  * Maybe they're "shite"? | ||||
|  * | ||||
|  * A shiterator is a simple generator that converts 2D shapes into a 1D stream | ||||
|  * of x/y coordinates.  You give it a matrix, some parameters like start, end, | ||||
|  * etc. and each time you call `next()` you get the next viable x/y coordinate to | ||||
|  * complete the shape. This makes them far superior to _any_ existing for-loop | ||||
|  * technology because shiterators operate _intelligently_ in shapes.  Other | ||||
|  * [programming pundits](https://www.youtube.com/watch?v=tD5NrevFtbU) will say
 | ||||
|  * their 7000 line "easy to maintain" switch statements are better at drawing | ||||
|  * shapes, but they're wrong.  My way of making a for-loop do stuff is vastly | ||||
|  * superior because it doesn't use a switch _or_ a virtual function _or_ | ||||
|  * inheritance at all.  That means they have to be the _fastest_. Feel free to run | ||||
|  * them 1000 times and bask in the glory of 1 nanosecond difference performance. | ||||
|  * | ||||
|  * It's science and shite. | ||||
|  * | ||||
|  * More importantly, shiterators are simple and easy to use.  They're so easy to | ||||
|  * use you _don't even use the 3rd part of the for-loop_.  What? You read that right, | ||||
|  * not only have I managed to eliminate _both_ massive horrible to maintain switches, | ||||
|  * and also avoided virtual functions, but I've also _eliminated one entire part | ||||
|  * of the for-loop_. This obviously makes them way faster than other inferior | ||||
|  * three-clause-loop-trash.  Just look at this comparison: | ||||
|  * | ||||
|  * ```cpp | ||||
|  * for(it = trash.begin(); it != trash.end(); it++) { | ||||
|  *     std::cout << it << std::endl; | ||||
|  * } | ||||
|  * ``` | ||||
|  * | ||||
|  * ```cpp | ||||
|  * for(each_cell it{mat}; it.next();) { | ||||
|  *    std::cout << mat[it.y][it.x] << std::endl; | ||||
|  * } | ||||
|  * ``` | ||||
|  * | ||||
|  * Obviously this will outperform _any_ iterator invented in the last 30 years, but the best | ||||
|  * thing about shiterators is their composability and ability to work simultaneously across | ||||
|  * multiple matrices in one loop: | ||||
|  * | ||||
|  * ```cpp | ||||
|  * for(line it{start, end}; it.next();) { | ||||
|  *    for(compass neighbor{walls, it.x, it.y}; neighbor.next();) { | ||||
|  *        if(walls[neighbor.y][neighbor.x] == 1) { | ||||
|  *          wall_update[it.y][it.x] = walls[it.y][it.x] + 10; | ||||
|  *        } | ||||
|  *    } | ||||
|  * } | ||||
|  * ``` | ||||
|  * | ||||
|  * This code sample (maybe, because I didn't run it) draws a line from | ||||
|  * `start` to `end` then looks at each neighbor on a compass (north, south, east, west) | ||||
|  * at each point to see if it's set to 1.  If it is then it copies that cell over to | ||||
|  * another matrix with +10.  Why would you need this?  Your Wizard just shot a fireball | ||||
|  * down a corridor and you need to see if anything in the path is within 1 square of it. | ||||
|  * | ||||
|  * You _also_ don't even need to use a for-loop.  Yes, you can harken back to the old | ||||
|  * days when we did everything RAW inside a Duff's Device between a while-loop for | ||||
|  * that PERFORMANCE because who cares about maintenance? You're a game developer! Tests? | ||||
|  * Don't need a test if it runs fine on Sony Playstation only.  Maintenance? You're moving | ||||
|  * on to the next project in two weeks anyway right?!  Use that while-loop and a shiterator | ||||
|  * to really help that next guy: | ||||
|  * | ||||
|  * ```cpp | ||||
|  * box it{walls, center_x, center_y, 20}; | ||||
|  * while(it.next()) { | ||||
|  *     walls[it.y][it.x] = 1; | ||||
|  * } | ||||
|  * ``` | ||||
|  * | ||||
|  * ## Shiterator "Guarantees" | ||||
|  * | ||||
|  * Just like Rust [guarantees no memory leaks](https://github.com/pop-os/cosmic-comp/issues/1133),
 | ||||
|  * a shiterator tries to ensure a few things, if it can: | ||||
|  * | ||||
|  * 1. All x/y values will be within the Matrix you give it. The `line` shiterator doesn't though. | ||||
|  * 2. They try to not store anything and only calculate the math necessary to linearlize the shape. | ||||
|  * 3. You can store them and incrementally call next to get the next value. | ||||
|  * 4. You should be able to compose them together on the same Matrix or different matrices of the same dimensions. | ||||
|  * 5. Most of them will only require 1 for-loop, the few that require 2 only do this so you can draw the inside of a shape.  `circle` is like this. | ||||
|  * 6. They don't assume any particular classes or require subclassing.  As long as the type given enables `mat[y][x]` (row major) access then it'll work. | ||||
|  * 7. The matrix given to a shiterator isn't actually attached to it, so you can use one matrix to setup an iterator, then apply the x/y values to any other matrix of the same dimensions. Great for smart copying and transforming. | ||||
|  * 8. More importantly, shiterators _do not return any values from the matrix_. They only do the math for coordinates and leave it to you to work your matrix. | ||||
|  * | ||||
|  * These shiterators are used all over the game to do map rendering, randomization, drawing, nearly everything that involves a shape. | ||||
|  * | ||||
|  * ## Algorithms I Need | ||||
|  * | ||||
|  * I'm currently looking for a few algorithms, so if you know how to do these let me know: | ||||
|  * | ||||
|  * 1. _Flood fill_  This turns out to be really hard because most algorithms require keeping track of visited cells with a queue, recursion, etc. | ||||
|  * 2. _Random rectangle fill_  I have something that mostly works but it's really only random across each y-axis, then separate y-axes are randomized. | ||||
|  * 3. _Dijkstra Map_ I have a Dijkstra algorithm but it's not in this style yet.  Look in `worldbuilder.cpp` for my current implementation. | ||||
|  * 4. _Viewport_ Currently working on this but I need to have a rectangle I can move around as a viewport. | ||||
|  * | ||||
|  * | ||||
|  * ## Usage | ||||
|  * | ||||
|  * Check the `matrix.hpp` for an example if you want to make it more conventient for your own type. | ||||
|  * | ||||
|  * ## Thanks | ||||
|  * | ||||
|  * Special thanks to Amit and hirdrac for their help with the math and for | ||||
|  * giving me the initial idea. hirdrac doesn't want to be held responsible for | ||||
|  * this travesty but he showed me that you can do iteration and _not_ use the | ||||
|  * weird C++ iterators.  Amit did a lot to show me how to do these calculations | ||||
|  * without branching.  Thanks to you both--and to everyone else--for helping me while I | ||||
|  * stream my development. | ||||
|  * | ||||
|  * ### SERIOUS DISCLAIMER | ||||
|  * | ||||
|  * I am horribly bad at trigonometry and graphics algorithms, so if you've got an idea to improve them | ||||
|  * or find a bug shoot me an email at help@learncodethehardway.com. | ||||
|  */ | ||||
| namespace shiterator { using std::vector, std::queue, std::array; using | ||||
|   std::min, std::max, std::floor; | ||||
| 
 | ||||
|   template<typename T> | ||||
|   using BaseRow = vector<T>; | ||||
| 
 | ||||
|   template<typename T> | ||||
|   using Base = vector<BaseRow<T>>; | ||||
| 
 | ||||
|   template<typename T> | ||||
|   inline Base<T> make(size_t width, size_t height) { | ||||
|     Base<T> result(height, BaseRow<T>(width)); | ||||
|     return result; | ||||
|   } | ||||
| 
 | ||||
|   /*
 | ||||
|    * Just a quick thing to reset a matrix to a value. | ||||
|    */ | ||||
|   template<typename MAT, typename VAL> | ||||
|   inline void assign(MAT &out, VAL new_value) { | ||||
|     for(auto &row : out) { | ||||
|       row.assign(row.size(), new_value); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   /*
 | ||||
|    * Tells you if a coordinate is in bounds of the matrix | ||||
|    * and therefore safe to use. | ||||
|    */ | ||||
|   template<typename MAT> | ||||
|   inline bool inbounds(MAT &mat, size_t x, size_t y) { | ||||
|     // since Point.x and Point.y are size_t any negatives are massive
 | ||||
|     return (y < mat.size()) && (x < mat[0].size()); | ||||
|   } | ||||
| 
 | ||||
|   /*
 | ||||
|    * Gives the width of a matrix. Assumes row major (y/x) | ||||
|    * and vector API .size(). | ||||
|    */ | ||||
|   template<typename MAT> | ||||
|   inline size_t width(MAT &mat) { | ||||
|     return mat[0].size(); | ||||
|   } | ||||
| 
 | ||||
|   /*
 | ||||
|    * Same as shiterator::width but just the height. | ||||
|    */ | ||||
|   template<typename MAT> | ||||
|   inline size_t height(MAT &mat) { | ||||
|     return mat.size(); | ||||
|   } | ||||
| 
 | ||||
|   /*
 | ||||
|    * These are internal calculations that help | ||||
|    * with keeping track of the next x coordinate. | ||||
|    */ | ||||
|   inline size_t next_x(size_t x, size_t width) { | ||||
|     return (x + 1) * ((x + 1) < width); | ||||
|   } | ||||
| 
 | ||||
|   /*
 | ||||
|    * Same as next_x but updates the next y coordinate. | ||||
|    * It uses the fact that when x==0 you have a new | ||||
|    * line so increment y. | ||||
|    */ | ||||
|   inline size_t next_y(size_t x, size_t y) { | ||||
|     return y + (x == 0); | ||||
|   } | ||||
| 
 | ||||
|   /*
 | ||||
|    * Figures out if you're at the end of the shape, | ||||
|    * which is usually when y > height. | ||||
|    */ | ||||
|   inline bool at_end(size_t y, size_t height) { | ||||
|     return y < height; | ||||
|   } | ||||
| 
 | ||||
|   /*
 | ||||
|    * Determines if you're at the end of a row. | ||||
|    */ | ||||
|   inline bool end_row(size_t x, size_t width) { | ||||
|     return x == width - 1; | ||||
|   } | ||||
| 
 | ||||
|   /*
 | ||||
|    * Most basic shiterator. It just goes through | ||||
|    * every cell in the matrix in linear order | ||||
|    * with not tracking of anything else. | ||||
|    */ | ||||
|   template<typename MAT> | ||||
|   struct each_cell_t { | ||||
|     size_t x = ~0; | ||||
|     size_t y = ~0; | ||||
|     size_t width = 0; | ||||
|     size_t height = 0; | ||||
| 
 | ||||
|     each_cell_t(MAT &mat) | ||||
|     { | ||||
|       height = shiterator::height(mat); | ||||
|       width = shiterator::width(mat); | ||||
|     } | ||||
| 
 | ||||
|     bool next() { | ||||
|       x = next_x(x, width); | ||||
|       y = next_y(x, y); | ||||
|       return at_end(y, height); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   /*
 | ||||
|    * This is just each_cell_t but it sets | ||||
|    * a boolean value `bool row` so you can | ||||
|    * tell when you've reached the end of a | ||||
|    * row.  This is mostly used for printing | ||||
|    * out a matrix and similar just drawing the | ||||
|    * whole thing with its boundaries. | ||||
|    */ | ||||
|   template<typename MAT> | ||||
|   struct each_row_t { | ||||
|     size_t x = ~0; | ||||
|     size_t y = ~0; | ||||
|     size_t width = 0; | ||||
|     size_t height = 0; | ||||
|     bool row = false; | ||||
| 
 | ||||
|     each_row_t(MAT &mat) { | ||||
|       height = shiterator::height(mat); | ||||
|       width = shiterator::width(mat); | ||||
|     } | ||||
| 
 | ||||
|     bool next() { | ||||
|       x = next_x(x, width); | ||||
|       y = next_y(x, y); | ||||
|       row = end_row(x, width); | ||||
|       return at_end(y, height); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   /*
 | ||||
|    * This is a CENTERED box, that will create | ||||
|    * a centered rectangle around a point of a | ||||
|    * certain dimension. This kind of needs a | ||||
|    * rewrite but if you want a rectangle from | ||||
|    * a upper corner then use rectangle_t type. | ||||
|    * | ||||
|    * Passing 1 parameter for the size will make | ||||
|    * a square. | ||||
|    */ | ||||
|   template<typename MAT> | ||||
|   struct box_t { | ||||
|     size_t from_x; | ||||
|     size_t from_y; | ||||
|     size_t x = 0; // these are set in constructor
 | ||||
|     size_t y = 0; // again, no fancy ~ trick needed
 | ||||
|     size_t left = 0; | ||||
|     size_t top = 0; | ||||
|     size_t right = 0; | ||||
|     size_t bottom = 0; | ||||
| 
 | ||||
|     box_t(MAT &mat, size_t at_x, size_t at_y, size_t size) : | ||||
|       box_t(mat, at_x, at_y, size, size) { | ||||
|       } | ||||
| 
 | ||||
|     box_t(MAT &mat, size_t at_x, size_t at_y, size_t width, size_t height) : | ||||
|       from_x(at_x), from_y(at_y) | ||||
|     { | ||||
|       size_t h = shiterator::height(mat); | ||||
|       size_t w = shiterator::width(mat); | ||||
| 
 | ||||
|       // keeps it from going below zero
 | ||||
|       // need extra -1 to compensate for the first next()
 | ||||
|       left = max(from_x, width) - width; | ||||
|       x = left - 1;  // must be -1 for next()
 | ||||
|       // keeps it from going above width
 | ||||
|       right = min(from_x + width + 1, w); | ||||
| 
 | ||||
|       // same for these two
 | ||||
|       top = max(from_y, height) - height; | ||||
|       y = top - (left == 0); | ||||
|       bottom = min(from_y + height + 1, h); | ||||
|     } | ||||
| 
 | ||||
|     bool next() { | ||||
|       // calc next but allow to go to 0 for next
 | ||||
|       x = next_x(x, right); | ||||
|       // x will go to 0, which signals new line
 | ||||
|       y = next_y(x, y);  // this must go here
 | ||||
|       // if x==0 then this moves it to min_x
 | ||||
|       x = max(x, left); | ||||
|       // and done
 | ||||
| 
 | ||||
|       return at_end(y, bottom); | ||||
|     } | ||||
| 
 | ||||
|     /*
 | ||||
|      * This was useful for doing quick lighting | ||||
|      * calculations, and I might need to implement | ||||
|      * it in other shiterators. It gives the distance | ||||
|      * to the center from the current x/y. | ||||
|      */ | ||||
|     float distance() { | ||||
|       int dx = from_x - x; | ||||
|       int dy = from_y - y; | ||||
| 
 | ||||
|       return sqrt((dx * dx) + (dy * dy)); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   /*
 | ||||
|    * Stupid simple compass shape North/South/East/West. | ||||
|    * This comes up a _ton_ when doing searching, flood | ||||
|    * algorithms, collision, etc.  Probably not the | ||||
|    * fastest way to do it but good enough. | ||||
|    */ | ||||
|   template<typename MAT> | ||||
|   struct compass_t { | ||||
|     size_t x = 0; // these are set in constructor
 | ||||
|     size_t y = 0; // again, no fancy ~ trick needed
 | ||||
|     array<int, 4> x_dirs{0, 1, 0, -1}; | ||||
|     array<int, 4> y_dirs{-1, 0, 1, 0}; | ||||
|     size_t max_dirs=0; | ||||
|     size_t dir = ~0; | ||||
| 
 | ||||
|     compass_t(MAT &mat, size_t x, size_t y) : | ||||
|       x(x), y(y) | ||||
|     { | ||||
|       array<int, 4> x_in{0, 1, 0, -1}; | ||||
|       array<int, 4> y_in{-1, 0, 1, 0}; | ||||
| 
 | ||||
|       for(size_t i = 0; i < 4; i++) { | ||||
|         int nx = x + x_in[i]; | ||||
|         int ny = y + y_in[i]; | ||||
|         if(shiterator::inbounds(mat, nx, ny)) { | ||||
|           x_dirs[max_dirs] = nx; | ||||
|           y_dirs[max_dirs] = ny; | ||||
|           max_dirs++; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     bool next() { | ||||
|       dir++; | ||||
|       if(dir < max_dirs) { | ||||
|         x = x_dirs[dir]; | ||||
|         y = y_dirs[dir]; | ||||
|         return true; | ||||
|       } else { | ||||
|         return false; | ||||
|       } | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   /*
 | ||||
|    * Draws a line from start to end using a algorithm from | ||||
|    * https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm
 | ||||
|    * No idea if the one I picked is best but it's the one | ||||
|    * that works in the shiterator requirements and produced | ||||
|    * good results. | ||||
|    * | ||||
|    * _WARNING_: This one doesn't check if the start/end are | ||||
|    * within your Matrix, as it's assumed _you_ did that | ||||
|    * already. | ||||
|    */ | ||||
|   struct line { | ||||
|     int x; | ||||
|     int y; | ||||
|     int x1; | ||||
|     int y1; | ||||
|     int sx; | ||||
|     int sy; | ||||
|     int dx; | ||||
|     int dy; | ||||
|     int error; | ||||
| 
 | ||||
|     line(Point start, Point end) : | ||||
|         x(start.x), y(start.y), | ||||
|         x1(end.x), y1(end.y) | ||||
|     { | ||||
|       dx = std::abs(x1 - x); | ||||
|       sx = x < x1 ? 1 : -1; | ||||
|       dy = std::abs(y1 - y) * -1; | ||||
|       sy = y < y1 ? 1 : -1; | ||||
|       error = dx + dy; | ||||
|     } | ||||
| 
 | ||||
|     bool next() { | ||||
|       if(x != x1 || y != y1) { | ||||
|         int e2 = 2 * error; | ||||
| 
 | ||||
|         if(e2 >= dy) { | ||||
|           error = error + dy; | ||||
|           x = x + sx; | ||||
|         } | ||||
| 
 | ||||
|         if(e2 <= dx) { | ||||
|           error = error + dx; | ||||
|           y = y + sy; | ||||
|         } | ||||
|         return true; | ||||
|       } else { | ||||
|         return false; | ||||
|       } | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   /*
 | ||||
|    * Draws a simple circle using a fairly naive algorithm | ||||
|    * but one that actually worked.  So, so, so, so many | ||||
|    * circle drawing algorithms described online don't work | ||||
|    * or are flat wrong.  Even the very best I could find | ||||
|    * did overdrawing of multiple lines or simply got the | ||||
|    * math wrong.  Keep in mind, _I_ am bad at this trig math | ||||
|    * so if I'm finding errors in your circle drawing then | ||||
|    * you got problems. | ||||
|    * | ||||
|    * This one is real simple, and works. If you got better | ||||
|    * then take the challenge but be ready to get it wrong. | ||||
|    */ | ||||
|   template<typename MAT> | ||||
|   struct circle_t { | ||||
|     float center_x; | ||||
|     float center_y; | ||||
|     float radius = 0.0f; | ||||
|     int y = 0; | ||||
|     int dx = 0; | ||||
|     int dy = 0; | ||||
|     int left = 0; | ||||
|     int right = 0; | ||||
|     int top = 0; | ||||
|     int bottom = 0; | ||||
|     int width = 0; | ||||
|     int height = 0; | ||||
| 
 | ||||
|     circle_t(MAT &mat, Point center, float radius) : | ||||
|       center_x(center.x), center_y(center.y), radius(radius) | ||||
|     { | ||||
|       width = shiterator::width(mat); | ||||
|       height = shiterator::height(mat); | ||||
|       top = max(int(floor(center_y - radius)), 0); | ||||
|       bottom = min(int(floor(center_y + radius)), height - 1); | ||||
| 
 | ||||
|       y = top; | ||||
|     } | ||||
| 
 | ||||
|     bool next() { | ||||
|       y++; | ||||
|       if(y <= bottom) { | ||||
|         dy = y - center_y; | ||||
|         dx = floor(sqrt(radius * radius - dy * dy)); | ||||
|         left = max(0, int(center_x) - dx); | ||||
|         right = min(width, int(center_x) + dx + 1); | ||||
|         return true; | ||||
|       } else { | ||||
|         return false; | ||||
|       } | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   /*
 | ||||
|    * Basic rectangle shiterator, and like box and rando_rect_t you can | ||||
|    * pass only 1 parameter for size to do a square. | ||||
|    */ | ||||
|   template<typename MAT> | ||||
|   struct rectangle_t { | ||||
|     int x; | ||||
|     int y; | ||||
|     int top; | ||||
|     int left; | ||||
|     int width; | ||||
|     int height; | ||||
|     int right; | ||||
|     int bottom; | ||||
| 
 | ||||
|     rectangle_t(MAT &mat, size_t start_x, size_t start_y, size_t size) : | ||||
|       rectangle_t(mat, start_x, start_y, size, size) { | ||||
|       } | ||||
| 
 | ||||
|     rectangle_t(MAT &mat, size_t start_x, size_t start_y, size_t width, size_t height) : | ||||
|       top(start_y), | ||||
|       left(start_x), | ||||
|       width(width), | ||||
|       height(height) | ||||
|     { | ||||
|       size_t h = shiterator::height(mat); | ||||
|       size_t w = shiterator::width(mat); | ||||
|       y = start_y - 1; | ||||
|       x = left - 1;  // must be -1 for next()
 | ||||
|       right = min(start_x + width, w); | ||||
| 
 | ||||
|       y = start_y; | ||||
|       bottom = min(start_y + height, h); | ||||
|     } | ||||
| 
 | ||||
|     bool next() { | ||||
|       x = next_x(x, right); | ||||
|       y = next_y(x, y); | ||||
|       x = max(x, left); | ||||
|       return at_end(y, bottom); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   /*
 | ||||
|    * WIP:  This one is used to place entities randomly but | ||||
|    * could be used for effects like random destruction of floors. | ||||
|    * It simply "wraps" the rectangle_t but randomizes the x/y values | ||||
|    * using a random starting point.  This makes it random across the | ||||
|    * x-axis but only partially random across the y. | ||||
|    */ | ||||
|   template<typename MAT> | ||||
|   struct rando_rect_t { | ||||
|     int x; | ||||
|     int y; | ||||
|     int x_offset; | ||||
|     int y_offset; | ||||
|     rectangle_t<MAT> it; | ||||
| 
 | ||||
|     rando_rect_t(MAT &mat, size_t start_x, size_t start_y, size_t size) : | ||||
|       rando_rect_t(mat, start_x, start_y, size, size) { | ||||
|       } | ||||
| 
 | ||||
|     rando_rect_t(MAT &mat, size_t start_x, size_t start_y, size_t width, size_t height) : | ||||
|       it{mat, start_x, start_y, width, height} | ||||
|     { | ||||
|       x_offset = Random::uniform(0, int(width)); | ||||
|       y_offset = Random::uniform(0, int(height)); | ||||
|     } | ||||
| 
 | ||||
|     bool next() { | ||||
|       bool done = it.next(); | ||||
|       x = it.left + ((it.x + x_offset) % it.width); | ||||
|       y = it.top + ((it.y + y_offset) % it.height); | ||||
|       return done; | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   /*
 | ||||
|    * BROKEN: I'm actually not sure what I'm trying to | ||||
|    * do here yet. | ||||
|    */ | ||||
|   template<typename MAT> | ||||
|   struct viewport_t { | ||||
|     Point start; | ||||
|     // this is the point in the map
 | ||||
|     size_t x; | ||||
|     size_t y; | ||||
|     // this is the point inside the box, start at 0
 | ||||
|     size_t view_x = ~0; | ||||
|     size_t view_y = ~0; | ||||
|     // viewport width/height
 | ||||
|     size_t width; | ||||
|     size_t height; | ||||
| 
 | ||||
|     viewport_t(MAT &mat, Point start, int max_x, int max_y) : | ||||
|       start(start), | ||||
|       x(start.x-1), | ||||
|       y(start.y-1) | ||||
|     { | ||||
|       width = std::min(size_t(max_x), shiterator::width(mat) - start.x); | ||||
|       height = std::min(size_t(max_y), shiterator::height(mat) - start.y); | ||||
|       fmt::println("viewport_t max_x, max_y {},{} vs matrix {},{}, x={}, y={}", | ||||
|           max_x, max_y, shiterator::width(mat), shiterator::height(mat), x, y); | ||||
|     } | ||||
| 
 | ||||
|     bool next() { | ||||
|       y = next_y(x, y); | ||||
|       x = next_x(x, width); | ||||
|       view_x = next_x(view_x, width); | ||||
|       view_y = next_y(view_x, view_y); | ||||
|       return at_end(y, height); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										82
									
								
								sound.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								sound.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,82 @@ | |||
| #include "sound.hpp" | ||||
| #include "dbc.hpp" | ||||
| #include <fmt/core.h> | ||||
| #include "config.hpp" | ||||
| 
 | ||||
| namespace sound { | ||||
|   static SoundManager SMGR; | ||||
|   static bool initialized = false; | ||||
|   static bool muted = false; | ||||
| 
 | ||||
|   using namespace fmt; | ||||
|   using std::make_shared; | ||||
|   namespace fs = std::filesystem; | ||||
| 
 | ||||
|   SoundPair& get_sound_pair(const std::string& name) { | ||||
|     dbc::check(initialized, "You need to call sound::init() first"); | ||||
| 
 | ||||
|     if(SMGR.sounds.contains(name)) { | ||||
|       // get the sound from the sound map
 | ||||
|       return SMGR.sounds.at(name); | ||||
|     } else { | ||||
|       dbc::log(fmt::format("Attempted to stop {} sound but not available.", | ||||
|             name)); | ||||
|       return SMGR.sounds.at("blank"); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   void init() { | ||||
|     if(!initialized) { | ||||
|       Config assets("assets/config.json"); | ||||
| 
 | ||||
|       for(auto& el : assets["sounds"].items()) { | ||||
|         load(el.key(), el.value()); | ||||
|       } | ||||
|       initialized = true; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   void load(const std::string& name, const std::string& sound_path) { | ||||
|     dbc::check(fs::exists(sound_path), fmt::format("sound file {} does not exist", sound_path)); | ||||
| 
 | ||||
|     // create the buffer and keep in the buffer map
 | ||||
|     auto buffer = make_shared<sf::SoundBuffer>(sound_path); | ||||
| 
 | ||||
|     // set it on the sound and keep in the sound map
 | ||||
|     auto sound = make_shared<sf::Sound>(*buffer); | ||||
|     sound->setRelativeToListener(false); | ||||
|     sound->setPosition({0.0f, 0.0f, 1.0f}); | ||||
| 
 | ||||
|     SMGR.sounds.try_emplace(name, buffer, sound); | ||||
|   } | ||||
| 
 | ||||
|   void play(const std::string& name, bool loop) { | ||||
|     if(muted) return; | ||||
|     auto& pair = get_sound_pair(name); | ||||
|     pair.sound->setLooping(loop); | ||||
|     // play it
 | ||||
|     pair.sound->play(); | ||||
|   } | ||||
| 
 | ||||
|   void stop(const std::string& name) { | ||||
|     auto& pair = get_sound_pair(name); | ||||
|     pair.sound->stop(); | ||||
|   } | ||||
| 
 | ||||
|   bool playing(const std::string& name) { | ||||
|     auto& pair = get_sound_pair(name); | ||||
|     auto status = pair.sound->getStatus(); | ||||
|     return status == sf::SoundSource::Status::Playing; | ||||
|   } | ||||
| 
 | ||||
|   void play_at(const std::string& name, float x, float y, float z) { | ||||
|     auto& pair = get_sound_pair(name); | ||||
|     pair.sound->setPosition({x, y, z}); | ||||
|     pair.sound->play(); | ||||
|   } | ||||
| 
 | ||||
|   void mute(bool setting) { | ||||
|     muted = setting; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										26
									
								
								sound.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								sound.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,26 @@ | |||
| #pragma once | ||||
| #include <string> | ||||
| #include <filesystem> | ||||
| #include <memory> | ||||
| #include <unordered_map> | ||||
| #include <SFML/Audio.hpp> | ||||
| 
 | ||||
| namespace sound { | ||||
|   struct SoundPair { | ||||
|     std::shared_ptr<sf::SoundBuffer> buffer; | ||||
|     std::shared_ptr<sf::Sound> sound; | ||||
|   }; | ||||
| 
 | ||||
|   struct SoundManager { | ||||
|     std::unordered_map<std::string, SoundPair> sounds; | ||||
|   }; | ||||
| 
 | ||||
|   void init(); | ||||
|   void load(const std::string& name, const std::string& path); | ||||
|   void play(const std::string& name, bool loop=false); | ||||
|   void play_at(const std::string& name, float x, float y, float z); | ||||
|   void stop(const std::string& name); | ||||
|   void mute(bool setting); | ||||
|   bool playing(const std::string& name); | ||||
|   SoundPair& get_sound_pair(const std::string& name); | ||||
| } | ||||
							
								
								
									
										99
									
								
								textures.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								textures.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,99 @@ | |||
| #include "textures.hpp" | ||||
| #include <SFML/Graphics/Image.hpp> | ||||
| #include "dbc.hpp" | ||||
| #include <fmt/core.h> | ||||
| #include "config.hpp" | ||||
| #include <memory> | ||||
| 
 | ||||
| namespace textures { | ||||
|   using std::shared_ptr, std::make_shared; | ||||
| 
 | ||||
|   static TextureManager TMGR; | ||||
|   static bool initialized = false; | ||||
| 
 | ||||
|   void load_sprites() { | ||||
|     Config assets("assets/config.json"); | ||||
| 
 | ||||
|     for(auto& [name, settings] : assets["sprites"].items()) { | ||||
|       auto texture = make_shared<sf::Texture>(settings["path"]); | ||||
| 
 | ||||
|       texture->setSmooth(assets["graphics"]["smooth_textures"]); | ||||
|       auto sprite = make_shared<sf::Sprite>(*texture); | ||||
| 
 | ||||
|       int width = settings["frame_width"]; | ||||
|       int height = settings["frame_height"]; | ||||
|       sprite->setTextureRect({{0,0}, {width, height}}); | ||||
| 
 | ||||
|       TMGR.sprite_textures.try_emplace(name, name, sprite, texture); | ||||
|     } | ||||
| 
 | ||||
|     TMGR.floor = load_image(assets["sprites"]["floor"]["path"]); | ||||
|     TMGR.ceiling = load_image(assets["sprites"]["ceiling"]["path"]); | ||||
|   } | ||||
| 
 | ||||
|   void load_tiles() { | ||||
|     Config assets("assets/tiles.json"); | ||||
|     auto &tiles = assets.json(); | ||||
| 
 | ||||
|     for(auto &el : tiles.items()) { | ||||
|       auto &config = el.value(); | ||||
|       TMGR.surfaces.emplace_back(load_image(config["texture"])); | ||||
|       wchar_t tid = config["display"]; | ||||
|       int surface_i = TMGR.surfaces.size() - 1; | ||||
|       TMGR.char_to_texture[tid] = surface_i; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   void init() { | ||||
|     if(!initialized) { | ||||
|       load_tiles(); | ||||
|       load_sprites(); | ||||
|       initialized = true; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   SpriteTexture get(std::string name) { | ||||
|     dbc::check(initialized, "you forgot to call textures::init()"); | ||||
|     dbc::check(TMGR.sprite_textures.contains(name), | ||||
|         fmt::format("!!!!! texture pack does not contain {} sprite", name)); | ||||
| 
 | ||||
|     auto result = TMGR.sprite_textures.at(name); | ||||
| 
 | ||||
|     dbc::check(result.sprite != nullptr, | ||||
|         fmt::format("bad sprite from textures::get named {}", name)); | ||||
|     dbc::check(result.texture != nullptr, | ||||
|         fmt::format("bad texture from textures::get named {}", name)); | ||||
| 
 | ||||
|     return result; | ||||
|   } | ||||
| 
 | ||||
|   sf::Image load_image(std::string filename) { | ||||
|     sf::Image texture; | ||||
|     bool good = texture.loadFromFile(filename); | ||||
|     dbc::check(good, fmt::format("failed to load {}", filename)); | ||||
|     return texture; | ||||
|   } | ||||
| 
 | ||||
|   const uint32_t* get_surface(size_t num) { | ||||
|     return (const uint32_t *)TMGR.surfaces[num].getPixelsPtr(); | ||||
|   } | ||||
| 
 | ||||
|   matrix::Matrix convert_char_to_texture(matrix::Matrix &tile_ids) { | ||||
|     auto result = matrix::make(matrix::width(tile_ids), matrix::height(tile_ids)); | ||||
| 
 | ||||
|     for(matrix::each_cell it(tile_ids); it.next();) { | ||||
|       wchar_t tid = tile_ids[it.y][it.x]; | ||||
|       result[it.y][it.x] = TMGR.char_to_texture.at(tid); | ||||
|     } | ||||
| 
 | ||||
|     return result; | ||||
|   } | ||||
| 
 | ||||
|   const uint32_t* get_floor() { | ||||
|     return (const uint32_t *)TMGR.floor.getPixelsPtr(); | ||||
|   } | ||||
| 
 | ||||
|   const uint32_t* get_ceiling() { | ||||
|     return (const uint32_t *)TMGR.ceiling.getPixelsPtr(); | ||||
|   } | ||||
| }; | ||||
							
								
								
									
										39
									
								
								textures.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								textures.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,39 @@ | |||
| #pragma once | ||||
| #include <cstdint> | ||||
| #include <vector> | ||||
| #include <string> | ||||
| #include <SFML/Graphics.hpp> | ||||
| #include <unordered_map> | ||||
| #include <memory> | ||||
| #include "matrix.hpp" | ||||
| 
 | ||||
| namespace textures { | ||||
| 
 | ||||
|   struct SpriteTexture { | ||||
|     std::string name; | ||||
|     std::shared_ptr<sf::Sprite> sprite = nullptr; | ||||
|     std::shared_ptr<sf::Texture> texture = nullptr; | ||||
|   }; | ||||
| 
 | ||||
|   struct TextureManager { | ||||
|     std::vector<sf::Image> surfaces; | ||||
|     std::unordered_map<std::string, SpriteTexture> sprite_textures; | ||||
|     std::unordered_map<wchar_t, int> char_to_texture; | ||||
|     sf::Image floor; | ||||
|     sf::Image ceiling; | ||||
|   }; | ||||
| 
 | ||||
|   void init(); | ||||
| 
 | ||||
|   SpriteTexture get(std::string name); | ||||
| 
 | ||||
|   sf::Image load_image(std::string filename); | ||||
| 
 | ||||
|   const uint32_t* get_surface(size_t num); | ||||
| 
 | ||||
|   matrix::Matrix convert_char_to_texture(matrix::Matrix &from); | ||||
| 
 | ||||
|   const uint32_t* get_floor(); | ||||
| 
 | ||||
|   const uint32_t* get_ceiling(); | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Zed A. Shaw
						Zed A. Shaw