Initial commit
This commit is contained in:
commit
8193df09e0
12
.clang-format
Normal file
12
.clang-format
Normal file
@ -0,0 +1,12 @@
|
||||
BasedOnStyle: GNU
|
||||
SpaceBeforeParens: ControlStatementsExceptControlMacros
|
||||
PointerAlignment: Left
|
||||
UseTab: Never
|
||||
IndentWidth: 2
|
||||
ContinuationIndentWidth: 2
|
||||
ConstructorInitializerIndentWidth: 2
|
||||
BreakAfterReturnType: Automatic
|
||||
BreakConstructorInitializers: AfterColon
|
||||
PackConstructorInitializers: Never
|
||||
IncludeBlocks: Regroup
|
||||
BreakBeforeBraces: Allman
|
||||
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
build/
|
||||
32
CMakeLists.txt
Normal file
32
CMakeLists.txt
Normal file
@ -0,0 +1,32 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
project(sand)
|
||||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(LUAJIT REQUIRED luajit)
|
||||
|
||||
add_library(sand SHARED
|
||||
src/sand.cpp
|
||||
src/lualib.cpp
|
||||
)
|
||||
|
||||
target_include_directories(sand
|
||||
PUBLIC include
|
||||
PRIVATE ${LUAJIT_INCLUDE_DIRS}
|
||||
)
|
||||
target_link_libraries(sand PRIVATE ${LUAJIT_LIBRARIES})
|
||||
set_target_properties(sand PROPERTIES
|
||||
CXX_STANDARD 20
|
||||
CXX_STANDARD_REQUIRED ON
|
||||
)
|
||||
|
||||
find_package(SDL3 CONFIG REQUIRED)
|
||||
|
||||
add_executable(sandtest
|
||||
src/main.cpp
|
||||
)
|
||||
target_link_libraries(sandtest PRIVATE sand SDL3)
|
||||
set_target_properties(sandtest PROPERTIES
|
||||
CXX_STANDARD 20
|
||||
CXX_STANDARD_REQUIRED ON
|
||||
)
|
||||
114
include/sand.h
Normal file
114
include/sand.h
Normal file
@ -0,0 +1,114 @@
|
||||
#ifndef SAND_H
|
||||
#define SAND_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace sand
|
||||
{
|
||||
|
||||
struct type
|
||||
{
|
||||
uint16_t id;
|
||||
uint16_t default_result;
|
||||
};
|
||||
using type_map = std::unordered_map<std::string, type>;
|
||||
|
||||
class RulesBuilder;
|
||||
class TypeBuilder
|
||||
{
|
||||
friend RulesBuilder;
|
||||
|
||||
public:
|
||||
TypeBuilder& add_type(const std::string& name);
|
||||
TypeBuilder& add_type(const std::string& name,
|
||||
const std::string& default_result);
|
||||
|
||||
RulesBuilder finish();
|
||||
|
||||
private:
|
||||
uint16_t next_type_id = 0;
|
||||
type_map types;
|
||||
}; // class TypeBuilder
|
||||
|
||||
class Rule;
|
||||
class Sand;
|
||||
class RulesBuilder
|
||||
{
|
||||
friend TypeBuilder;
|
||||
friend Rule;
|
||||
friend Sand;
|
||||
|
||||
public:
|
||||
Rule add_rule(const std::string& source_name, const std::string& to);
|
||||
|
||||
Sand build(uint16_t width, uint16_t height, const std::string& initial);
|
||||
|
||||
private:
|
||||
RulesBuilder(const TypeBuilder& type_builder);
|
||||
|
||||
void finish_rule(const Rule& rule);
|
||||
|
||||
type_map types;
|
||||
std::unordered_map<uint16_t,
|
||||
std::vector<std::pair<unsigned __int128, uint16_t>>>
|
||||
rules;
|
||||
}; // class RulesBuilder
|
||||
|
||||
class Rule
|
||||
{
|
||||
friend RulesBuilder;
|
||||
|
||||
public:
|
||||
using match_fn = bool (*)(const std::string& name);
|
||||
|
||||
~Rule();
|
||||
|
||||
Rule& top_left(match_fn match);
|
||||
Rule& top_middle(match_fn match);
|
||||
Rule& top_right(match_fn match);
|
||||
Rule& middle_left(match_fn match);
|
||||
Rule& middle_right(match_fn match);
|
||||
Rule& bottom_left(match_fn match);
|
||||
Rule& bottom_middle(match_fn match);
|
||||
Rule& bottom_right(match_fn match);
|
||||
|
||||
private:
|
||||
Rule(RulesBuilder& builder, uint16_t source, uint16_t to);
|
||||
|
||||
RulesBuilder& builder;
|
||||
uint16_t source;
|
||||
uint16_t to;
|
||||
match_fn matches[8];
|
||||
}; // class RuleBuilder
|
||||
|
||||
class Sand
|
||||
{
|
||||
friend RulesBuilder;
|
||||
|
||||
public:
|
||||
void set(uint16_t x, uint16_t y, const std::string& type);
|
||||
uint16_t get(uint16_t x, uint16_t y);
|
||||
|
||||
void update();
|
||||
|
||||
private:
|
||||
Sand(const RulesBuilder& builder, uint16_t width, uint16_t height,
|
||||
const std::string& initial);
|
||||
|
||||
uint16_t width;
|
||||
uint16_t height;
|
||||
type_map types;
|
||||
std::unordered_map<uint16_t, uint16_t> default_conversions;
|
||||
std::unordered_map<uint16_t,
|
||||
std::vector<std::pair<unsigned __int128, uint16_t>>>
|
||||
rules;
|
||||
std::vector<uint16_t> elements;
|
||||
bool iter;
|
||||
}; // class Sand
|
||||
|
||||
} // namespace sand
|
||||
|
||||
#endif // SAND_H
|
||||
12
src/lualib.cpp
Normal file
12
src/lualib.cpp
Normal file
@ -0,0 +1,12 @@
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif // __cplusplus
|
||||
|
||||
#include <luajit.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif // __cplusplus
|
||||
|
||||
extern "C" int luaopen_sand(lua_State* state) { return 1; }
|
||||
83
src/main.cpp
Normal file
83
src/main.cpp
Normal file
@ -0,0 +1,83 @@
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3/SDL_events.h>
|
||||
#include <SDL3/SDL_mouse.h>
|
||||
#include <SDL3/SDL_oldnames.h>
|
||||
#include <SDL3/SDL_surface.h>
|
||||
#include <SDL3/SDL_timer.h>
|
||||
#include <SDL3/SDL_video.h>
|
||||
#include <sand.h>
|
||||
|
||||
uint8_t lookup[][3]
|
||||
= { { 0, 0, 0 }, { 50, 0, 0 }, { 100, 100, 100 }, { 200, 200, 200 } };
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
auto rb = sand::TypeBuilder()
|
||||
.add_type("offgrid")
|
||||
.add_type("air")
|
||||
.add_type("stone1")
|
||||
.add_type("stone2")
|
||||
.finish();
|
||||
|
||||
rb.add_rule("air", "stone1")
|
||||
.top_middle([](const auto& t) { return t == "stone1"; });
|
||||
|
||||
rb.add_rule("stone1", "air")
|
||||
.top_middle([](const auto& t) { return t == "air" || t == "offgrid"; })
|
||||
.bottom_middle([](const auto& t) { return t == "air"; });
|
||||
|
||||
auto s = rb.build(64, 64, "air");
|
||||
|
||||
SDL_Init(SDL_INIT_VIDEO);
|
||||
SDL_Window* window = SDL_CreateWindow("sand test", 512, 512, 0);
|
||||
SDL_Surface* surface = SDL_CreateSurface(64, 64, SDL_PIXELFORMAT_RGB24);
|
||||
|
||||
int prev_ms = SDL_GetTicks();
|
||||
float time = 0;
|
||||
bool cont = true;
|
||||
while (cont)
|
||||
{
|
||||
SDL_Event e;
|
||||
while (SDL_PollEvent(&e))
|
||||
{
|
||||
if (e.type == SDL_EVENT_QUIT)
|
||||
{
|
||||
cont = false;
|
||||
}
|
||||
}
|
||||
|
||||
int ms = SDL_GetTicks();
|
||||
float dt = (ms - prev_ms) / 1000.0f;
|
||||
prev_ms = ms;
|
||||
time += dt;
|
||||
|
||||
float x, y;
|
||||
int state;
|
||||
if (time > 0.008 && (state = SDL_GetMouseState(&x, &y)))
|
||||
{
|
||||
time = 0;
|
||||
s.set(x * 64 / 512, y * 64 / 512, state == 1 ? "stone1" : "stone2");
|
||||
}
|
||||
|
||||
SDL_LockSurface(surface);
|
||||
for (int i = 0; i < 64; i++)
|
||||
{
|
||||
for (int j = 0; j < 64; j++)
|
||||
{
|
||||
memcpy(&static_cast<uint8_t*>(surface->pixels)[3 * (i + j * 64)],
|
||||
lookup[s.get(i, j)], 3);
|
||||
}
|
||||
}
|
||||
SDL_UnlockSurface(surface);
|
||||
SDL_BlitSurfaceScaled(surface, nullptr, SDL_GetWindowSurface(window),
|
||||
nullptr, SDL_SCALEMODE_NEAREST);
|
||||
SDL_UpdateWindowSurface(window);
|
||||
|
||||
s.update();
|
||||
}
|
||||
|
||||
SDL_DestroyWindow(window);
|
||||
SDL_Quit();
|
||||
|
||||
return 0;
|
||||
}
|
||||
344
src/sand.cpp
Normal file
344
src/sand.cpp
Normal file
@ -0,0 +1,344 @@
|
||||
#include "sand.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
|
||||
using namespace sand;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
template <typename T>
|
||||
T& binary_search(T element, T* elements, size_t begin, size_t end,
|
||||
auto (*fn)(T, T)->bool)
|
||||
{
|
||||
if (end - begin == 1)
|
||||
{
|
||||
return elements[begin];
|
||||
}
|
||||
|
||||
size_t index = begin + (end - begin) / 2;
|
||||
|
||||
if (!fn(element, elements[index]) && !fn(elements[index], element))
|
||||
{
|
||||
return elements[index];
|
||||
}
|
||||
else if (fn(element, elements[index]))
|
||||
{
|
||||
return binary_search<T>(element, elements, index, end, fn);
|
||||
}
|
||||
else
|
||||
{
|
||||
return binary_search<T>(element, elements, begin, index, fn);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TypeBuilder& TypeBuilder::add_type(const std::string& name)
|
||||
{
|
||||
if (!types.contains(name))
|
||||
{
|
||||
types[name] = { .id = next_type_id, .default_result = next_type_id };
|
||||
next_type_id += 1;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
TypeBuilder& TypeBuilder::add_type(const std::string& name,
|
||||
const std::string& default_result)
|
||||
{
|
||||
add_type(name);
|
||||
add_type(default_result);
|
||||
|
||||
types[name].default_result = types[default_result].id;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
RulesBuilder TypeBuilder::finish() { return RulesBuilder(*this); }
|
||||
|
||||
Rule RulesBuilder::add_rule(const std::string& name, const std::string& to)
|
||||
{
|
||||
return Rule(*this, types[name].id, types[to].id);
|
||||
}
|
||||
|
||||
Sand RulesBuilder::build(uint16_t width, uint16_t height,
|
||||
const std::string& initial)
|
||||
{
|
||||
for (auto& [key, rule] : rules)
|
||||
{
|
||||
std::sort(rule.begin(), rule.end(),
|
||||
[](auto a, auto b) { return a.first < b.first; });
|
||||
}
|
||||
return Sand(*this, width, height, initial);
|
||||
}
|
||||
|
||||
RulesBuilder::RulesBuilder(const TypeBuilder& type_builder) :
|
||||
types(type_builder.types)
|
||||
{
|
||||
}
|
||||
|
||||
void RulesBuilder::finish_rule(const Rule& rule)
|
||||
{
|
||||
// 8 nested loops is kinda silly, maybe there's a better way to do this?
|
||||
// top left
|
||||
for (const auto& [tl_name, tl_type] : types)
|
||||
{
|
||||
if (!rule.matches[0] || rule.matches[0](tl_name))
|
||||
{
|
||||
// top middle
|
||||
for (const auto& [tm_name, tm_type] : types)
|
||||
{
|
||||
if (!rule.matches[1] || rule.matches[1](tm_name))
|
||||
{
|
||||
// top right
|
||||
for (const auto& [tr_name, tr_type] : types)
|
||||
{
|
||||
if (!rule.matches[2] || rule.matches[2](tr_name))
|
||||
{
|
||||
// middle left
|
||||
for (const auto& [ml_name, ml_type] : types)
|
||||
{
|
||||
if (!rule.matches[3] || rule.matches[3](ml_name))
|
||||
{
|
||||
// middle right
|
||||
for (const auto& [mr_name, mr_type] : types)
|
||||
{
|
||||
if (!rule.matches[4] || rule.matches[4](mr_name))
|
||||
{
|
||||
// bottom left
|
||||
for (const auto& [bl_name, bl_type] : types)
|
||||
{
|
||||
if (!rule.matches[5] || rule.matches[5](bl_name))
|
||||
{
|
||||
// bottom middle
|
||||
for (const auto& [bm_name, bm_type] : types)
|
||||
{
|
||||
if (!rule.matches[6] || rule.matches[6](bm_name))
|
||||
{
|
||||
// bottom right
|
||||
for (const auto& [br_name, br_type] : types)
|
||||
{
|
||||
if (!rule.matches[7]
|
||||
|| rule.matches[7](br_name))
|
||||
{
|
||||
unsigned __int128 neighbors = 0;
|
||||
|
||||
neighbors += tl_type.id;
|
||||
neighbors <<= 16;
|
||||
|
||||
neighbors += tm_type.id;
|
||||
neighbors <<= 16;
|
||||
|
||||
neighbors += tr_type.id;
|
||||
neighbors <<= 16;
|
||||
|
||||
neighbors += ml_type.id;
|
||||
neighbors <<= 16;
|
||||
|
||||
neighbors += mr_type.id;
|
||||
neighbors <<= 16;
|
||||
|
||||
neighbors += bl_type.id;
|
||||
neighbors <<= 16;
|
||||
|
||||
neighbors += bm_type.id;
|
||||
neighbors <<= 16;
|
||||
|
||||
neighbors += br_type.id;
|
||||
|
||||
if (!rules.contains(rule.source))
|
||||
{
|
||||
rules[rule.source] = {};
|
||||
}
|
||||
|
||||
rules[rule.source].push_back(
|
||||
{ neighbors, rule.to });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rule::~Rule() { builder.finish_rule(*this); }
|
||||
|
||||
Rule& Rule::top_left(match_fn match)
|
||||
{
|
||||
matches[0] = match;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Rule& Rule::top_middle(match_fn match)
|
||||
{
|
||||
matches[1] = match;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Rule& Rule::top_right(match_fn match)
|
||||
{
|
||||
matches[2] = match;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Rule& Rule::middle_left(match_fn match)
|
||||
{
|
||||
matches[3] = match;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Rule& Rule::middle_right(match_fn match)
|
||||
{
|
||||
matches[4] = match;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Rule& Rule::bottom_left(match_fn match)
|
||||
{
|
||||
matches[5] = match;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Rule& Rule::bottom_middle(match_fn match)
|
||||
{
|
||||
matches[6] = match;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Rule& Rule::bottom_right(match_fn match)
|
||||
{
|
||||
matches[7] = match;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Rule::Rule(RulesBuilder& builder, uint16_t source, uint16_t to) :
|
||||
builder(builder),
|
||||
source(source),
|
||||
to(to),
|
||||
matches{}
|
||||
{
|
||||
}
|
||||
|
||||
Sand::Sand(const RulesBuilder& builder, uint16_t width, uint16_t height,
|
||||
const std::string& initial) :
|
||||
width(width),
|
||||
height(height),
|
||||
types(builder.types),
|
||||
rules(builder.rules),
|
||||
elements(width * height * 2, types[initial].id),
|
||||
iter(false)
|
||||
{
|
||||
for (const auto& [name, type] : types)
|
||||
{
|
||||
default_conversions[type.id] = type.default_result;
|
||||
}
|
||||
}
|
||||
|
||||
void Sand::set(uint16_t x, uint16_t y, const std::string& type)
|
||||
{
|
||||
elements[x + y * width + iter * width * height] = types[type].id;
|
||||
}
|
||||
|
||||
uint16_t Sand::get(uint16_t x, uint16_t y)
|
||||
{
|
||||
return elements[x + y * width + iter * width * height];
|
||||
}
|
||||
|
||||
void Sand::update()
|
||||
{
|
||||
for (uint32_t i = 0; i < width; i++)
|
||||
{
|
||||
for (uint32_t j = 0; j < height; j++)
|
||||
{
|
||||
auto& current_rule = rules[get(i, j)];
|
||||
|
||||
unsigned __int128 neighbors = 0;
|
||||
if (i > 0 && j > 0)
|
||||
{
|
||||
neighbors += get(i - 1, j - 1);
|
||||
}
|
||||
neighbors <<= 16;
|
||||
|
||||
if (j > 0)
|
||||
{
|
||||
neighbors += get(i, j - 1);
|
||||
}
|
||||
neighbors <<= 16;
|
||||
|
||||
if (i < width - 1 && j > 0)
|
||||
{
|
||||
neighbors += get(i + 1, j);
|
||||
}
|
||||
neighbors <<= 16;
|
||||
|
||||
if (i > 0)
|
||||
{
|
||||
neighbors += get(i - 1, j);
|
||||
}
|
||||
neighbors <<= 16;
|
||||
|
||||
if (i < width - 1)
|
||||
{
|
||||
neighbors += get(i + 1, j);
|
||||
}
|
||||
neighbors <<= 16;
|
||||
|
||||
if (i > 0 && j < height - 1)
|
||||
{
|
||||
neighbors += get(i - 1, j + 1);
|
||||
}
|
||||
neighbors <<= 16;
|
||||
|
||||
if (j < height - 1)
|
||||
{
|
||||
neighbors += get(i, j + 1);
|
||||
}
|
||||
neighbors <<= 16;
|
||||
|
||||
if (i < width - 1 && j < height - 1)
|
||||
{
|
||||
neighbors += get(i + 1, j + 1);
|
||||
}
|
||||
|
||||
uint16_t result = default_conversions[get(i, j)];
|
||||
if (!current_rule.empty())
|
||||
{
|
||||
std::pair<unsigned __int128, uint16_t> element = { neighbors, 0 };
|
||||
bool (*fn)(decltype(element), decltype(element))
|
||||
= [](decltype(element) a, decltype(element) b) -> bool
|
||||
{ return a.first > b.first; };
|
||||
const auto& found = binary_search(element, current_rule.data(), 0,
|
||||
current_rule.size(), fn);
|
||||
if (found.first == neighbors)
|
||||
{
|
||||
result = found.second;
|
||||
}
|
||||
}
|
||||
|
||||
elements[i + j * width + !iter * width * height] = result;
|
||||
}
|
||||
}
|
||||
|
||||
iter = !iter;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user