From a66a111750262e1808087cf69d47fcf552859a21 Mon Sep 17 00:00:00 2001 From: Peiqi Liu Date: Sat, 20 Apr 2024 01:59:22 -0700 Subject: [PATCH] basic gol done --- SConstruct | 2 +- game/bin/example.gdextension | 23 ------------ game/bin/lifepvp.gdextension | 23 ++++++++++++ game/board.gd | 37 +++++++++++++++++++ game/board.tscn | 6 ++++ src/basic_engine.cpp | 52 +++++++++++++++++++++++++++ src/basic_engine.h | 32 +++++++++++++++++ src/engine_base.h | 29 +++++++++++++++ src/life_driver.cpp | 70 ++++++++++++++++++++++++++++++++++++ src/life_driver.h | 36 +++++++++++++++++++ src/register_types.cpp | 8 +++-- src/register_types.h | 6 ++-- 12 files changed, 295 insertions(+), 29 deletions(-) delete mode 100644 game/bin/example.gdextension create mode 100644 game/bin/lifepvp.gdextension create mode 100644 src/basic_engine.cpp create mode 100644 src/basic_engine.h create mode 100644 src/engine_base.h create mode 100644 src/life_driver.cpp create mode 100644 src/life_driver.h diff --git a/SConstruct b/SConstruct index 9e31601..5afc79a 100644 --- a/SConstruct +++ b/SConstruct @@ -11,7 +11,7 @@ def validate_parent_dir(key, val, env): raise UserError("'%s' is not a directory: %s" % (key, os.path.dirname(val))) -libname = "EXTENSION-NAME" +libname = "lifepvp" projectdir = "game" localEnv = Environment(tools=["default"], PLATFORM="") diff --git a/game/bin/example.gdextension b/game/bin/example.gdextension deleted file mode 100644 index d72a302..0000000 --- a/game/bin/example.gdextension +++ /dev/null @@ -1,23 +0,0 @@ -[configuration] - -entry_symbol = "example_library_init" -compatibility_minimum = "4.2" - -[libraries] - -macos.debug = "res://bin/libgdexample.macos.template_debug.framework" -macos.release = "res://bin/libgdexample.macos.template_release.framework" -windows.debug.x86_32 = "res://bin/libgdexample.windows.template_debug.x86_32.dll" -windows.release.x86_32 = "res://bin/libgdexample.windows.template_release.x86_32.dll" -windows.debug.x86_64 = "res://bin/libgdexample.windows.template_debug.x86_64.dll" -windows.release.x86_64 = "res://bin/libgdexample.windows.template_release.x86_64.dll" -linux.debug.x86_64 = "res://bin/libgdexample.linux.template_debug.x86_64.so" -linux.release.x86_64 = "res://bin/libgdexample.linux.template_release.x86_64.so" -linux.debug.arm64 = "res://bin/libgdexample.linux.template_debug.arm64.so" -linux.release.arm64 = "res://bin/libgdexample.linux.template_release.arm64.so" -linux.debug.rv64 = "res://bin/libgdexample.linux.template_debug.rv64.so" -linux.release.rv64 = "res://bin/libgdexample.linux.template_release.rv64.so" -android.debug.x86_64 = "res://bin/libgdexample.android.template_debug.x86_64.so" -android.release.x86_64 = "res://bin/libgdexample.android.template_release.x86_64.so" -android.debug.arm64 = "res://bin/libgdexample.android.template_debug.arm64.so" -android.release.arm64 = "res://bin/libgdexample.android.template_release.arm64.so" diff --git a/game/bin/lifepvp.gdextension b/game/bin/lifepvp.gdextension new file mode 100644 index 0000000..482cee9 --- /dev/null +++ b/game/bin/lifepvp.gdextension @@ -0,0 +1,23 @@ +[configuration] + +entry_symbol = "lifepvp_library_init" +compatibility_minimum = "4.2" + +[libraries] + +macos.debug = "res://bin/macos/liblifepvp.macos.template_debug.framework" +macos.release = "res://bin/macos/liblifepvp.macos.template_release.framework" +windows.debug.x86_32 = "res://bin/windows/liblifepvp.windows.template_debug.x86_32.dll" +windows.release.x86_32 = "res://bin/windows/liblifepvp.windows.template_release.x86_32.dll" +windows.debug.x86_64 = "res://bin/windows/liblifepvp.windows.template_debug.x86_64.dll" +windows.release.x86_64 = "res://bin/windows/liblifepvp.windows.template_release.x86_64.dll" +linux.debug.x86_64 = "res://bin/linux/liblifepvp.linux.template_debug.x86_64.so" +linux.release.x86_64 = "res://bin/linux/liblifepvp.linux.template_release.x86_64.so" +linux.debug.arm64 = "res://bin/linux/liblifepvp.linux.template_debug.arm64.so" +linux.release.arm64 = "res://bin/linux/liblifepvp.linux.template_release.arm64.so" +linux.debug.rv64 = "res://bin/linux/liblifepvp.linux.template_debug.rv64.so" +linux.release.rv64 = "res://bin/linux/liblifepvp.linux.template_release.rv64.so" +android.debug.x86_64 = "res://bin/android/liblifepvp.android.template_debug.x86_64.so" +android.release.x86_64 = "res://bin/android/liblifepvp.android.template_release.x86_64.so" +android.debug.arm64 = "res://bin/android/liblifepvp.android.template_debug.arm64.so" +android.release.arm64 = "res://bin/android/liblifepvp.android.template_release.arm64.so" diff --git a/game/board.gd b/game/board.gd index f391a68..8ed2b2c 100644 --- a/game/board.gd +++ b/game/board.gd @@ -1,13 +1,50 @@ extends GridContainer +signal next_iteration + +var life_driver : LifeDriver +var cells : Array + # Called when the node enters the scene tree for the first time. func _ready(): for i in range(columns): + cells.append([]) for j in range(columns): var new_cell = ColorRect.new() new_cell.custom_minimum_size = Vector2(10, 10) add_child(new_cell) + cells[-1].append(new_cell) + life_driver = LifeDriver.new() + life_driver.update_cell.connect(_update_cell) + life_driver.update_done.connect(_update_done) + next_iteration.connect(life_driver.next_iteration) + + var bytearray = PackedByteArray() + bytearray.resize(columns * columns) + bytearray[0 * columns + 1] = 1 + bytearray[1 * columns + 2] = 1 + bytearray[2 * columns + 0] = 1 + bytearray[2 * columns + 1] = 1 + bytearray[2 * columns + 2] = 1 + _update_cell(0, 1, 1) + _update_cell(1, 2, 1) + _update_cell(2, 0, 1) + _update_cell(2, 1, 1) + _update_cell(2, 2, 1) + + life_driver.setup(columns, columns, bytearray, LifeDriver.BASIC) + # Called every frame. 'delta' is the elapsed time since the previous frame. func _process(delta): pass + +func _update_cell(i: int, j: int, state: int): + cells[i][j].color = Color.BLACK if state else Color.WHITE + +func _update_done(): + pass + + +func _on_timer_timeout(): + emit_signal("next_iteration") diff --git a/game/board.tscn b/game/board.tscn index 4250b30..38a247b 100644 --- a/game/board.tscn +++ b/game/board.tscn @@ -7,3 +7,9 @@ theme_override_constants/h_separation = 1 theme_override_constants/v_separation = 1 columns = 42 script = ExtResource("1_o7lpq") + +[node name="Timer" type="Timer" parent="."] +wait_time = 0.1 +autostart = true + +[connection signal="timeout" from="Timer" to="." method="_on_timer_timeout"] diff --git a/src/basic_engine.cpp b/src/basic_engine.cpp new file mode 100644 index 0000000..3c0d9e6 --- /dev/null +++ b/src/basic_engine.cpp @@ -0,0 +1,52 @@ +#include + +#include "basic_engine.h" + +void BasicEngine::next_iteration() { + const auto& curr_board = m_board_pair[m_board]; + auto& next_board = m_board_pair[m_board ^ 1]; + + for (size_t i = 0; i < W; ++i) { + for (size_t j = 0; j < H; ++j) { + const std::pair neighbor_offsets[] + {{-1, -1}, {-1, 0}, {-1, 1}, + { 0, -1}, { 0, 1}, + { 1, -1}, { 1, 0}, { 1, 1}}; + size_t count = 0; + for (const auto [x, y] : neighbor_offsets) { + if (at(curr_board, (x+W+i)%W, (y+H+j)%H)) { + ++count; + } + } + + const state_t curr_cell = at(curr_board, i, j); + state_t& next_cell = at(next_board, i, j); + if (count < 2 || count > 3) { + next_cell = 0; // die + } else if (count == 3) { + next_cell = 1; // survive or create + } else { // count == 2 + next_cell = curr_cell; // survive + } + + if (next_cell != curr_cell) { + m_update_cb(i, j, next_cell); + } + } + } + + m_board ^= 1; + m_done_cb(); +} + +BasicEngine::state_t BasicEngine::get_state(size_t i, size_t j) { + return at(m_board_pair[m_board], i, j); +} + +BasicEngine::state_t& BasicEngine::at(board_t& v, size_t i, size_t j) { + return v[i * W + j]; +} + +const BasicEngine::state_t& BasicEngine::at(const board_t& v, size_t i, size_t j) const { + return v[i * W + j]; +} diff --git a/src/basic_engine.h b/src/basic_engine.h new file mode 100644 index 0000000..e0853cf --- /dev/null +++ b/src/basic_engine.h @@ -0,0 +1,32 @@ +#ifndef LIFEPVP_BASIC_ENGINE_H +#define LIFEPVP_BASIC_ENGINE_H + +#include + +#include "engine_base.h" + +class BasicEngine : public EngineBase { +public: + using board_t = std::vector; + + BasicEngine(board_t&& board, + const size_t w, + const size_t h, + const update_cb_t update_cb, + const done_cb_t done_cb) + : EngineBase(w, h, update_cb, done_cb), + m_board_pair{board, board_t(board.size())} {} + + virtual ~BasicEngine() = default; + void next_iteration() override; + state_t get_state(size_t i, size_t j) override; + +protected: + size_t m_board = 0; + std::array m_board_pair; + + state_t& at(board_t& v, size_t i, size_t j); + const state_t& at(const board_t& v, size_t i, size_t j) const; +}; + +#endif \ No newline at end of file diff --git a/src/engine_base.h b/src/engine_base.h new file mode 100644 index 0000000..5a5c650 --- /dev/null +++ b/src/engine_base.h @@ -0,0 +1,29 @@ +#ifndef LIFEPVP_ENGINE_BASE_H +#define LIFEPVP_ENGINE_BASE_H + +#include +#include + +class EngineBase { +public: + using state_t = uint8_t; + using update_cb_t = std::function; + using done_cb_t = std::function; + + EngineBase(const size_t w, + const size_t h, + const update_cb_t update_cb, + const done_cb_t done_cb) + : W(w), H(h), m_update_cb(update_cb), m_done_cb(done_cb) {} + + virtual ~EngineBase() = default; + virtual void next_iteration() = 0; + virtual state_t get_state(size_t, size_t) = 0; +protected: + const size_t W; + const size_t H; + const update_cb_t m_update_cb; + const done_cb_t m_done_cb; +}; + +#endif \ No newline at end of file diff --git a/src/life_driver.cpp b/src/life_driver.cpp new file mode 100644 index 0000000..f20cfb4 --- /dev/null +++ b/src/life_driver.cpp @@ -0,0 +1,70 @@ +#include "life_driver.h" + +#include +#include + +#include +#include + + +#include "engine_base.h" +#include "basic_engine.h" + +using namespace godot; + +void LifeDriver::setup(const size_t w, const size_t h, const Variant& init_board, const EngineType engine) { + + const auto init_board_to_vec = [&]() { + const PackedByteArray& array = std::forward(init_board); + static_assert(sizeof(*(array.ptr())) == sizeof(EngineBase::state_t)); + std::vector vec(w * h); + memcpy(vec.data(), array.ptr(), sizeof(EngineBase::state_t) * vec.size()); + return vec; + }; + + const auto get_board_vec = [&]() { + switch (init_board.get_type()) { + case Variant::NIL: + return std::vector(w * h); + + case Variant::PACKED_BYTE_ARRAY: + return init_board_to_vec(); + break; + + default: + ERR_PRINT("Unknown init_board type"); + return std::vector{}; + } + }; + + switch (engine) { + case BASIC: + m_engine = std::make_unique(get_board_vec(), w, h, + [&](size_t i, size_t j, uint8_t state) { + emit_signal("update_cell", i, j, state); + }, + [&]() { + emit_signal("update_done"); + } + ); + break; + + default: + ERR_PRINT("Unknown engine type"); + return; + } +} + +void LifeDriver::next_iteration() { + m_engine->next_iteration(); +} + +void LifeDriver::_bind_methods() { + ADD_SIGNAL(MethodInfo("update_cell", PropertyInfo(Variant::INT, "i"), PropertyInfo(Variant::INT, "j"), PropertyInfo(Variant::INT, "state"))); + ADD_SIGNAL(MethodInfo("update_done")); + + ClassDB::bind_method(D_METHOD("setup", "w", "h", "init_board", "engine_type"), &LifeDriver::setup); + ClassDB::bind_method(D_METHOD("next_iteration"), &LifeDriver::next_iteration); + + BIND_ENUM_CONSTANT(BASIC); +} diff --git a/src/life_driver.h b/src/life_driver.h new file mode 100644 index 0000000..ecf4fde --- /dev/null +++ b/src/life_driver.h @@ -0,0 +1,36 @@ +#ifndef LIFEPVP_LIFE_DRIVER_H +#define LIFEPVP_LIFE_DRIVER_H + +#include + +#include +#include + +#include "engine_base.h" + +namespace godot { + +class LifeDriver : public Object { + GDCLASS(LifeDriver, Object) + +public: + enum EngineType { + BASIC + }; + + void setup(const size_t w, const size_t h, const Variant& init_board, const EngineType engine); + + void next_iteration(); + +protected: + static void _bind_methods(); + +private: + std::unique_ptr m_engine; +}; + +} + +VARIANT_ENUM_CAST(LifeDriver::EngineType); + +#endif // LIFEPVP_LIFE_DRIVER_H \ No newline at end of file diff --git a/src/register_types.cpp b/src/register_types.cpp index 2c86044..ca59b4f 100644 --- a/src/register_types.cpp +++ b/src/register_types.cpp @@ -1,4 +1,7 @@ #include "register_types.h" + +#include "life_driver.h" + #include #include #include @@ -11,7 +14,8 @@ void initialize_gdextension_types(ModuleInitializationLevel p_level) if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { return; } - //ClassDB::register_class(); + + ClassDB::register_class(); } void uninitialize_gdextension_types(ModuleInitializationLevel p_level) { @@ -23,7 +27,7 @@ void uninitialize_gdextension_types(ModuleInitializationLevel p_level) { extern "C" { // Initialization - GDExtensionBool GDE_EXPORT example_library_init(GDExtensionInterfaceGetProcAddress p_get_proc_address, GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization) + GDExtensionBool GDE_EXPORT lifepvp_library_init(GDExtensionInterfaceGetProcAddress p_get_proc_address, GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization) { GDExtensionBinding::InitObject init_obj(p_get_proc_address, p_library, r_initialization); init_obj.register_initializer(initialize_gdextension_types); diff --git a/src/register_types.h b/src/register_types.h index e6fe2b0..2407970 100644 --- a/src/register_types.h +++ b/src/register_types.h @@ -1,7 +1,7 @@ -#ifndef EXAMPLE_REGISTER_TYPES_H -#define EXAMPLE_REGISTER_TYPES_H +#ifndef LIFEPVP_REGISTER_TYPES_H +#define LIFEPVP_REGISTER_TYPES_H void initialize_gdextension_types(); void uninitialize_gdextension_types(); -#endif // EXAMPLE_REGISTER_TYPES_H \ No newline at end of file +#endif // LIFEPVP_REGISTER_TYPES_H \ No newline at end of file