commit 8193df09e09d6e711ec2ceb7b7de49414f44a51f Author: shylie Date: Sat Apr 18 11:00:58 2026 -0400 Initial commit diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..f1b5bbc --- /dev/null +++ b/.clang-format @@ -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 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..3652580 --- /dev/null +++ b/CMakeLists.txt @@ -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 +) diff --git a/include/sand.h b/include/sand.h new file mode 100644 index 0000000..890b9e6 --- /dev/null +++ b/include/sand.h @@ -0,0 +1,114 @@ +#ifndef SAND_H +#define SAND_H + +#include +#include +#include +#include + +namespace sand +{ + +struct type +{ + uint16_t id; + uint16_t default_result; +}; +using type_map = std::unordered_map; + +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>> + 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 default_conversions; + std::unordered_map>> + rules; + std::vector elements; + bool iter; +}; // class Sand + +} // namespace sand + +#endif // SAND_H diff --git a/src/lualib.cpp b/src/lualib.cpp new file mode 100644 index 0000000..71193b7 --- /dev/null +++ b/src/lualib.cpp @@ -0,0 +1,12 @@ +#ifdef __cplusplus +extern "C" +{ +#endif // __cplusplus + +#include + +#ifdef __cplusplus +} +#endif // __cplusplus + +extern "C" int luaopen_sand(lua_State* state) { return 1; } diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..1e300b7 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,83 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +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(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; +} diff --git a/src/sand.cpp b/src/sand.cpp new file mode 100644 index 0000000..81f8ead --- /dev/null +++ b/src/sand.cpp @@ -0,0 +1,344 @@ +#include "sand.h" + +#include +#include + +using namespace sand; + +namespace +{ + +template +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(element, elements, index, end, fn); + } + else + { + return binary_search(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 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; +}