diff --git a/modules/generative_grammar/generator.cpp b/modules/generative_grammar/generator.cpp index caed0fc05f..f1a81b38ef 100644 --- a/modules/generative_grammar/generator.cpp +++ b/modules/generative_grammar/generator.cpp @@ -4,7 +4,7 @@ #include "macros.h" void Generator::_bind_methods() { - BIND_HPROPERTY(Variant::OBJECT, state, PROPERTY_HINT_RESOURCE_TYPE, "Symbol"); + BIND_HPROPERTY(Variant::OBJECT, state, PROPERTY_HINT_RESOURCE_TYPE, "Sentence"); } void Generator::ready() { @@ -19,6 +19,13 @@ void Generator::_notification(int what) { return; case NOTIFICATION_READY: ready(); + case NOTIFICATION_CHILD_ORDER_CHANGED: + for (Variant child : get_children()) { + if (Rule * rule{ cast_to(child) }) { + this->rule = rule; + return; + } + } return; } } diff --git a/modules/generative_grammar/generator.h b/modules/generative_grammar/generator.h index e0bca38510..513fc469e1 100644 --- a/modules/generative_grammar/generator.h +++ b/modules/generative_grammar/generator.h @@ -1,6 +1,6 @@ #pragma once -#include "generative_grammar/symbol.h" +#include "generative_grammar/grammar.h" #include "scene/3d/node_3d.h" class Generator : public Node3D { @@ -12,8 +12,9 @@ protected: void _notification(int what); public: - Ref state{}; + Ref state{}; + Rule *rule{ nullptr }; private: - GET_SET_FNS(Ref, state); + GET_SET_FNS(Ref, state); }; diff --git a/modules/generative_grammar/grammar.cpp b/modules/generative_grammar/grammar.cpp index d00c925258..9b7fbcedb0 100644 --- a/modules/generative_grammar/grammar.cpp +++ b/modules/generative_grammar/grammar.cpp @@ -1,25 +1,156 @@ #include "generative_grammar/grammar.h" #include "core/object/class_db.h" +#include "core/variant/typed_array.h" #include "macros.h" void Sentence::_bind_methods() { - BIND_PROPERTY(Variant::STRING, symbols_string); - BIND_PROPERTY(Variant::INT, width); + BIND_HPROPERTY(Variant::STRING, symbols_string, PROPERTY_HINT_MULTILINE_TEXT); + BIND_PROPERTY(Variant::VECTOR2I, size); } bool Sentence::is_terminal() const { - for (char32_t c : this->symbols) { - if (String::char_uppercase(c)) { + for (Symbol c : this->symbols) { + if (!symbol_is_terminal(c)) { return false; } } return true; // no non-terminals in sentence } -void Rule::_bind_methods() { +Symbol Sentence::get_at(Vector2i coord) const { + int idx{ coord.x + (coord.y * this->size.x) }; + ERR_FAIL_COND_V_EDMSG(idx < this->symbols.size(), NonTerminals::Invalid, "Sentence::get_at: invalid coordinate"); + return this->symbols.get(idx); +} + +void Sentence::set_at(Vector2i coord, Symbol symbol) { + int idx{ coord.x + (coord.y * this->size.x) }; + ERR_FAIL_COND_EDMSG(idx < this->symbols.size(), "Sentence::set_at: invalid coordinate"); + this->symbols.set(idx, symbol); +} + +bool Sentence::check_match_at(Vector2i at, Ref pattern) { + Vector2i const pattern_size{ pattern->get_size() }; + if (at.x + pattern_size.x > this->size.x || at.y + pattern_size.y > this->size.y) { + return false; // can't match, out of bounds + } + for (Vector2i itr{ 0, 0 }; itr.y < pattern_size.x; ++itr.y) { + for (; itr.x + at.x < pattern_size.x; ++itr.x) { + if (get_at(at + itr) != pattern->get_at(itr)) { + return false; // difference found + } + } + itr.x = at.x; + } + return true; +} + +void Sentence::write_subsentence(Vector2i at, Ref sentence) { + for (Vector2i pos{ 0, 0 }; pos.y < sentence->get_size().y; ++pos.y) { + for (; pos.x < sentence->get_size().x; ++pos.x) { + set_at(at + pos, sentence->get_at(pos)); + } + pos.x = 0; + } +} + +void Sentence::set_symbols_string(String value) { + int size{ this->size.x * this->size.y }; + int writer{ 0 }; + for (int reader{ 0 }; writer < size; ++reader) { + char32_t c{ value.length() >= reader ? value[reader] : (char32_t)'B' }; + if (c != '\n') { + this->symbols.set(writer, c); + ++writer; + } + } +} + +String Sentence::get_symbols_string() const { + String value{}; + for (int i{ 0 }; i < this->symbols.size(); ++i) { + if (i != 0 && i % this->size.x == 0) { + value += L'\n'; + } + value += this->symbols.get(i); + } + return value; +} + +void Sentence::set_size(Vector2i value) { + this->size = value; + this->symbols.clear(); + this->symbols.resize_initialized(this->size.x * this->size.y); + for (int i{ 0 }; i < this->symbols.size(); ++i) { + this->symbols.set(i, 'B'); + } +} + +Vector2i Sentence::get_size() const { + return this->size; +} + +void CompositeRule::_notification(int what) { + switch (what) { + case NOTIFICATION_READY: + case NOTIFICATION_CHILD_ORDER_CHANGED: + this->rules.clear(); + for (Variant child : get_children()) { + if (Rule * rule{ cast_to(child) }) { + this->rules.push_back(rule); + } + } + return; + } +} + +void CompositeRule::try_apply(Ref sentence) { + for (Rule *rule : this->rules) { + rule->try_apply(sentence); + } +} + +void ReplaceRule::_bind_methods() { BIND_HPROPERTY(Variant::OBJECT, pattern, PROPERTY_HINT_RESOURCE_TYPE, Sentence::get_class_static()); BIND_HPROPERTY(Variant::OBJECT, result, PROPERTY_HINT_RESOURCE_TYPE, Sentence::get_class_static()); } -void Rule::try_apply(Ref sentence) { +void ReplaceRule::try_apply(Ref sentence) { + Vector2i max{ sentence->get_size() - this->pattern->get_size() }; + if (max.x < 0 || max.y < 0) { + return; // cannot continue, no area in sentence big enough + } + for (Vector2i pos{ 0, 0 }; pos.y < max.y; ++pos.y) { + for (; pos.x < max.x; ++pos.x) { + if (sentence->check_match_at(pos, this->pattern)) { + sentence->write_subsentence(pos, this->result); + } + } + pos.x = 0; + } +} + +void ResizeRule::_bind_methods() { + BIND_PROPERTY(Variant::INT, factor); +} + +void ResizeRule::fill_area(Ref data, Vector2i coord, Symbol tile) { + for (Vector2i at{ 0, 0 }; at.y < this->factor; ++at.y) { + for (; at.x < this->factor; ++at.x) { + data->set_at(coord * this->factor + at, tile); + } + at.x = 0; + } +} + +void ResizeRule::try_apply(Ref sentence) { + Vector2i size_before{ sentence->size }; + Vector before{ sentence->symbols }; + sentence->set_size(size_before * this->factor); + for (Vector2i at{ 0, 0 }; at.y < size_before.y; ++at.y) { + for (; at.x < size_before.x; ++at.x) { + fill_area(sentence, at, before.get(at.x + at.y * size_before.x)); + } + at.x = 0; + } } diff --git a/modules/generative_grammar/grammar.h b/modules/generative_grammar/grammar.h index a69c7d6bb5..b84e473d2b 100644 --- a/modules/generative_grammar/grammar.h +++ b/modules/generative_grammar/grammar.h @@ -1,7 +1,23 @@ #pragma once -#include "core/io/resource.h" #include "macros.h" +#include "scene/main/node.h" + +typedef char32_t Symbol; + +namespace NonTerminals { +enum : Symbol { + Start = 'S', + Blocked = 'B', + Invalid = '!', +}; +}; + +namespace Terminals { +enum : Symbol { + Invalid = '!', +}; +}; class Sentence : public Resource { GDCLASS(Sentence, Resource); @@ -9,24 +25,54 @@ class Sentence : public Resource { public: bool is_terminal() const; + Symbol get_at(Vector2i coord) const; + void set_at(Vector2i coord, Symbol symbol); + bool check_match_at(Vector2i at, Ref pattern); + void write_subsentence(Vector2i at, Ref sentence); + static bool symbol_is_terminal(Symbol symbol) { + return String::char_lowercase(symbol) == symbol; + } -private: - Vector symbols{}; - int width{ 0 }; +public: + Vector symbols{}; + Vector2i size{ 0, 0 }; public: void set_symbols_string(String value); String get_symbols_string() const; - void set_width(int width); - int get_width() const; + void set_size(Vector2i width); + Vector2i get_size() const; }; -struct Rule : public Resource { - GDCLASS(Rule, Resource); +class Rule : public Node { + GDCLASS(Rule, Node); + static void _bind_methods() {} + +public: + virtual void try_apply(Ref graph) {} +}; + +class CompositeRule : public Rule { + GDCLASS(CompositeRule, Rule); + static void _bind_methods() {} + void order_changed(); + +protected: + void _notification(int what); + +public: + void try_apply(Ref graph) override; + +protected: + Vector rules{}; +}; + +class ReplaceRule : public Rule { + GDCLASS(ReplaceRule, Rule); static void _bind_methods(); public: - void try_apply(Ref graph); + void try_apply(Ref graph) override; private: Ref pattern{}; @@ -36,3 +82,23 @@ public: GET_SET_FNS(Ref, pattern); GET_SET_FNS(Ref, result); }; + +class ResizeRule : public CompositeRule { + GDCLASS(ResizeRule, Rule); + static void _bind_methods(); + void fill_area(Ref data, Vector2i coord, Symbol tile); + +public: + void try_apply(Ref sentence) override; + +private: + int factor{ 10 }; + +public: + GET_SET_FNS(int, factor); +}; + +class PickRandomRule : public CompositeRule { + GDCLASS(PickRandomRule, CompositeRule); + static void _bind_methods() {} +}; diff --git a/modules/generative_grammar/register_types.cpp b/modules/generative_grammar/register_types.cpp index 62f15d8de6..69f63c02a9 100644 --- a/modules/generative_grammar/register_types.cpp +++ b/modules/generative_grammar/register_types.cpp @@ -1,14 +1,16 @@ #include "generative_grammar/register_types.h" #include "core/object/class_db.h" #include "generative_grammar/generator.h" -#include "generative_grammar/symbol.h" +#include "generative_grammar/grammar.h" void initialize_generative_grammar_module(ModuleInitializationLevel p_level) { if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { return; } - ClassDB::register_class(); - ClassDB::register_class(); + ClassDB::register_abstract_class(); + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); ClassDB::register_class(); } diff --git a/project/data/rules/test_rule.tres b/project/data/rules/test_rule.tres deleted file mode 100644 index 8f5fcf78b9..0000000000 --- a/project/data/rules/test_rule.tres +++ /dev/null @@ -1,13 +0,0 @@ -[gd_resource type="Rule" format=3 uid="uid://cb5x75r5umlh5"] - -[sub_resource type="Symbol" id="Symbol_26ujg"] - -[sub_resource type="Symbol" id="Symbol_titn7"] -type = 1 - -[sub_resource type="Symbol" id="Symbol_kwj57"] -children_array = Array[Object]([SubResource("Symbol_titn7")]) - -[resource] -match = SubResource("Symbol_26ujg") -result = SubResource("Symbol_kwj57") diff --git a/project/scenes/levels/dungeon.tscn b/project/scenes/levels/dungeon.tscn index 269690c8c0..ec3dcbd7dc 100644 --- a/project/scenes/levels/dungeon.tscn +++ b/project/scenes/levels/dungeon.tscn @@ -1,8 +1,16 @@ [gd_scene format=3 uid="uid://cak2tf2adjv8j"] -[sub_resource type="Symbol" id="Symbol_bb2w7"] +[sub_resource type="Sentence" id="Sentence_bb2w7"] +symbols_string = "BBBBB +BBBBB +BBBBB +BBBBB +BBBBB" +size = Vector2i(5, 5) [node name="Dungeon" type="Node3D" unique_id=719313039] [node name="Generator" type="Generator" parent="." unique_id=1532743122] -state = SubResource("Symbol_bb2w7") +state = SubResource("Sentence_bb2w7") + +[node name="ReplaceRule" type="ReplaceRule" parent="Generator" unique_id=1787373751]