Final move of files to a better place. Only thing in the root is very common stuff.
This commit is contained in:
parent
ebe84c4d78
commit
5179f1c781
95 changed files with 190 additions and 191 deletions
248
src/algos/dinkyecs.hpp
Normal file
248
src/algos/dinkyecs.hpp
Normal file
|
|
@ -0,0 +1,248 @@
|
|||
#pragma once
|
||||
|
||||
#include "dbc.hpp"
|
||||
#include <any>
|
||||
#include <functional>
|
||||
#include <queue>
|
||||
#include <tuple>
|
||||
#include <typeindex>
|
||||
#include <typeinfo>
|
||||
#include <unordered_map>
|
||||
#include <optional>
|
||||
#include <memory>
|
||||
|
||||
namespace DinkyECS
|
||||
{
|
||||
using Entity = unsigned long;
|
||||
|
||||
const Entity NONE = 0;
|
||||
|
||||
template <typename T>
|
||||
struct ComponentStorage {
|
||||
std::vector<T> data;
|
||||
};
|
||||
|
||||
struct Event {
|
||||
int event = 0;
|
||||
Entity entity = 0;
|
||||
std::any data;
|
||||
};
|
||||
|
||||
using EntityMap = std::unordered_map<Entity, size_t>;
|
||||
using EventQueue = std::queue<Event>;
|
||||
using TypeMap = std::unordered_map<std::type_index, std::any>;
|
||||
|
||||
struct World {
|
||||
unsigned long entity_count = NONE+1;
|
||||
std::unordered_map<std::type_index, EntityMap> $components;
|
||||
std::shared_ptr<TypeMap> $facts = nullptr;
|
||||
std::unordered_map<std::type_index, EventQueue> $events;
|
||||
std::unordered_map<std::type_index, std::any> $component_storages;
|
||||
std::unordered_map<std::type_index, std::queue<size_t>> $free_indices;
|
||||
std::unordered_map<Entity, bool> $constants;
|
||||
|
||||
World() : $facts(std::make_shared<TypeMap>())
|
||||
{}
|
||||
|
||||
Entity entity() { return ++entity_count; }
|
||||
|
||||
void destroy(DinkyECS::Entity entity) {
|
||||
dbc::check(!$constants.contains(entity), "trying to destroy an entity in constants");
|
||||
|
||||
for(auto& [tid, map] : $components) {
|
||||
|
||||
if(map.contains(entity)) {
|
||||
size_t index = map.at(entity);
|
||||
auto& free_queue = $free_indices.at(tid);
|
||||
free_queue.push(index);
|
||||
map.erase(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void clone_into(DinkyECS::World &to_world) {
|
||||
to_world.$constants = $constants;
|
||||
to_world.$facts = $facts;
|
||||
// BUG*10: entity IDs should be a global counter, not per world
|
||||
to_world.entity_count = entity_count;
|
||||
to_world.$component_storages = $component_storages;
|
||||
|
||||
for(auto [eid, is_set] : $constants) {
|
||||
dbc::check(is_set == true, "is_set was not true? WHAT?!");
|
||||
dbc::check(eid <= entity_count, fmt::format(
|
||||
"eid {} is not less than entity_count {}", eid, entity_count));
|
||||
|
||||
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.try_emplace(entity, true);
|
||||
}
|
||||
|
||||
void not_constant(DinkyECS::Entity entity) {
|
||||
$constants.erase(entity);
|
||||
}
|
||||
|
||||
template <typename Comp>
|
||||
size_t make_component() {
|
||||
auto &storage = component_storage_for<Comp>();
|
||||
auto &free_queue = $free_indices.at(std::type_index(typeid(Comp)));
|
||||
size_t index;
|
||||
|
||||
if(!free_queue.empty()) {
|
||||
index = free_queue.front();
|
||||
free_queue.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>{});
|
||||
$free_indices.try_emplace(type_index, std::queue<size_t>{});
|
||||
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);
|
||||
auto& free_queue = $free_indices.at(std::type_index(typeid(Comp)));
|
||||
free_queue.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>
|
||||
Comp* get_if(DinkyECS::Entity entity) {
|
||||
EntityMap &map = entity_map_for<Comp>();
|
||||
auto &storage = component_storage_for<Comp>();
|
||||
if(map.contains(entity)) {
|
||||
auto index = map.at(entity);
|
||||
return &storage.data[index];
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
};
|
||||
} // namespace DinkyECS
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
#pragma once
|
||||
#include "shiterator.hpp"
|
||||
#include "algos/shiterator.hpp"
|
||||
|
||||
namespace matrix {
|
||||
using Row = shiterator::BaseRow<int>;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
#pragma once
|
||||
#include "algos/matrix.hpp"
|
||||
#include "map.hpp"
|
||||
#include "game/map.hpp"
|
||||
|
||||
namespace maze {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#pragma once
|
||||
#include "point.hpp"
|
||||
#include "algos/point.hpp"
|
||||
#include "algos/matrix.hpp"
|
||||
#include <functional>
|
||||
|
||||
|
|
|
|||
20
src/algos/point.hpp
Normal file
20
src/algos/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);
|
||||
}
|
||||
};
|
||||
634
src/algos/shiterator.hpp
Normal file
634
src/algos/shiterator.hpp
Normal file
|
|
@ -0,0 +1,634 @@
|
|||
#pragma once
|
||||
#include <vector>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <array>
|
||||
#include <numeric>
|
||||
#include <algorithm>
|
||||
#include <fmt/core.h>
|
||||
#include "algos/point.hpp"
|
||||
#include "algos/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 `algos/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);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Same as rando_rect_t but it uses a centered box.
|
||||
*/
|
||||
template<typename MAT>
|
||||
struct rando_box_t {
|
||||
size_t x;
|
||||
size_t y;
|
||||
size_t x_offset;
|
||||
size_t y_offset;
|
||||
box_t<MAT> it;
|
||||
|
||||
rando_box_t(MAT &mat, size_t start_x, size_t start_y, size_t size) :
|
||||
it{mat, start_x, start_y, size}
|
||||
{
|
||||
x_offset = Random::uniform(size_t(0), it.right);
|
||||
y_offset = Random::uniform(size_t(0), it.bottom);
|
||||
}
|
||||
|
||||
bool next() {
|
||||
bool done = it.next();
|
||||
x = it.left + ((it.x + x_offset) % it.right);
|
||||
y = it.top + ((it.y + y_offset) % it.bottom);
|
||||
return done;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
32
src/algos/simplefsm.hpp
Normal file
32
src/algos/simplefsm.hpp
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
#pragma once
|
||||
|
||||
#include <fmt/core.h>
|
||||
|
||||
#ifndef FSM_DEBUG
|
||||
#define FSM_STATE(C, S, E, ...) case C::S: S(E, ##__VA_ARGS__); break
|
||||
#else
|
||||
static int last_event=-1;
|
||||
#define FSM_STATE(C, S, E, ...) case C::S: if(last_event != int(E)) { last_event = int(E); fmt::println(">> " #C " " #S " event={}, state={}", int(E), int($state));}; S(E, ##__VA_ARGS__); break
|
||||
#endif
|
||||
|
||||
template<typename S, typename E>
|
||||
class DeadSimpleFSM {
|
||||
protected:
|
||||
// BUG: don't put this in your class because state() won't work
|
||||
S $state = S::START;
|
||||
|
||||
public:
|
||||
template<typename... Types>
|
||||
void event(E event, Types... args);
|
||||
|
||||
void state(S next_state) {
|
||||
#ifdef FSM_DEBUG
|
||||
fmt::println("<< STATE: {} -> {}", int($state), int(next_state));
|
||||
#endif
|
||||
$state = next_state;
|
||||
}
|
||||
|
||||
bool in_state(S state) {
|
||||
return $state == state;
|
||||
}
|
||||
};
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
#pragma once
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include "map.hpp"
|
||||
#include "dinkyecs.hpp"
|
||||
#include "point.hpp"
|
||||
#include "game/map.hpp"
|
||||
#include "algos/dinkyecs.hpp"
|
||||
#include "algos/point.hpp"
|
||||
|
||||
struct CollisionData {
|
||||
DinkyECS::Entity entity = DinkyECS::NONE;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue