diff --git a/SConstruct b/SConstruct index 5afc79a..2eb7f9f 100644 --- a/SConstruct +++ b/SConstruct @@ -50,6 +50,8 @@ env.Alias("compiledb", compilation_db) env = SConscript("godot-cpp/SConstruct", {"env": env, "customs": customs}) +env.Append(CXXFLAGS=["-std=c++20"]) + env.Append(CPPPATH=["src/"]) sources = Glob("src/*.cpp") diff --git a/game/Camera2D.gd b/game/Camera2D.gd new file mode 100644 index 0000000..660d9de --- /dev/null +++ b/game/Camera2D.gd @@ -0,0 +1,40 @@ +extends Camera2D + +# Speed of camera movement +@export var speed = 200 +# Zoom speed +@export var zoom_speed = 0.1 +# Minimum and maximum zoom levels +@export var min_zoom = 0.5 +@export var max_zoom = 2.0 + +func _process(delta): + var motion = Vector2.ZERO + + # Check for movement keys + if Input.is_action_pressed("ui_up"): + motion.y -= 1 + if Input.is_action_pressed("ui_down"): + motion.y += 1 + if Input.is_action_pressed("ui_left"): + motion.x -= 1 + if Input.is_action_pressed("ui_right"): + motion.x += 1 + + # Normalize to prevent faster diagonal movement + motion = motion.normalized() * speed + + # Apply the motion to the camera's position + position += motion * delta + + # Zoom in + if Input.is_action_pressed("ui_zoom_in"): + zoom = Vector2(zoom.x - zoom_speed * delta, zoom.y - zoom_speed * delta) + zoom.x = max(min_zoom, zoom.x) + zoom.y = max(min_zoom, zoom.y) + + # Zoom out + if Input.is_action_pressed("ui_zoom_out"): + zoom = Vector2(zoom.x + zoom_speed * delta, zoom.y + zoom_speed * delta) + zoom.x = min(max_zoom, zoom.x) + zoom.y = min(max_zoom, zoom.y) diff --git a/game/main.tscn b/game/main.tscn index c589172..88a38ae 100644 --- a/game/main.tscn +++ b/game/main.tscn @@ -1,15 +1,17 @@ -[gd_scene load_steps=3 format=3 uid="uid://dss4vqph6ixgk"] +[gd_scene load_steps=4 format=3 uid="uid://dss4vqph6ixgk"] [ext_resource type="PackedScene" uid="uid://djahqxlcbinjp" path="res://board.tscn" id="1_i8xh8"] [ext_resource type="Script" path="res://FPSCounter.gd" id="2_avnxl"] +[ext_resource type="Script" path="res://Camera2D.gd" id="2_wwmnb"] [node name="Main" type="Node"] -[node name="Board" parent="." instance=ExtResource("1_i8xh8")] -columns = 100 - [node name="Camera2D" type="Camera2D" parent="."] offset = Vector2(500, 300) +script = ExtResource("2_wwmnb") +speed = 500 +zoom_speed = 0.5 +min_zoom = 1.0 [node name="FPSCounter" type="Label" parent="Camera2D"] offset_left = -41.0 @@ -17,3 +19,6 @@ offset_top = -24.0 offset_right = -1.0 offset_bottom = -1.0 script = ExtResource("2_avnxl") + +[node name="Board" parent="." instance=ExtResource("1_i8xh8")] +columns = 100 diff --git a/game/project.godot b/game/project.godot index ec14a13..80d39d2 100644 --- a/game/project.godot +++ b/game/project.godot @@ -14,7 +14,19 @@ config/name="GameOfLifePVP" config/features=PackedStringArray("4.2", "GL Compatibility") config/icon="res://icon.svg" +[input] + +ui_zoom_in={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194323,"key_label":0,"unicode":0,"echo":false,"script":null) +] +} +ui_zoom_out={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194324,"key_label":0,"unicode":0,"echo":false,"script":null) +] +} + [rendering] -renderer/rendering_method.mobile="gl_compatibility" environment/defaults/default_clear_color=Color(0.301961, 0.301961, 0.301961, 1) diff --git a/src/basic_engine.cpp b/src/basic_engine.cpp deleted file mode 100644 index 3c0d9e6..0000000 --- a/src/basic_engine.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#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 index e0853cf..7e1cc30 100644 --- a/src/basic_engine.h +++ b/src/basic_engine.h @@ -1,32 +1,111 @@ #ifndef LIFEPVP_BASIC_ENGINE_H #define LIFEPVP_BASIC_ENGINE_H +#include #include #include "engine_base.h" +namespace lifepvp::engine { + +template +concept BoardConstructibleBySize = requires (T&& board) { + T(board.size()); +}; + +template +concept BoardResizable = std::is_default_constructible_v && + requires (T board, size_t x) { + board.resize(x); + }; + +template +concept BasicEngineContainer = + std::move_constructible && + (BoardResizable || BoardConstructibleBySize) && + requires (T& board, const T& cboard, size_t i) { + { board[i] } -> std::convertible_to; + { cboard[i] } -> std::convertible_to; + }; + +template> class BasicEngine : public EngineBase { public: - using board_t = std::vector; + using board_t = std::remove_reference_t; 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())} {} + : EngineBase(w, h, update_cb, done_cb) { + if constexpr (BoardConstructibleBySize) { + m_board_pair = {board, board_t(board.size())}; + } else { + static_assert(BoardResizable); + m_board_pair = {board, board_t()}; + m_board_pair[1].resize(w * h); + } + } virtual ~BasicEngine() = default; - void next_iteration() override; - state_t get_state(size_t i, size_t j) override; + + void next_iteration() override { + 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) { + static 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(); + } + + state_t get_state(size_t i, size_t j) override { + return at(m_board_pair[m_board], i, j); + }; 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; + state_t& at(board_t& v, size_t i, size_t j) { + return v[i * W + j]; + }; + + const state_t& at(const board_t& v, size_t i, size_t j) const { + return v[i * W + j]; + }; }; +} + #endif \ No newline at end of file diff --git a/src/engine_base.h b/src/engine_base.h index 5a5c650..2e83ede 100644 --- a/src/engine_base.h +++ b/src/engine_base.h @@ -4,6 +4,8 @@ #include #include +namespace lifepvp::engine { + class EngineBase { public: using state_t = uint8_t; @@ -26,4 +28,6 @@ class EngineBase { 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 index f20cfb4..eeb2b82 100644 --- a/src/life_driver.cpp +++ b/src/life_driver.cpp @@ -12,41 +12,23 @@ 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; - }; +using namespace lifepvp::engine; - const auto get_board_vec = [&]() { - switch (init_board.get_type()) { - case Variant::NIL: - return std::vector(w * h); +void LifeDriver::setup(const size_t w, const size_t h, const Variant& init_board, const EngineType engine) { - case Variant::PACKED_BYTE_ARRAY: - return init_board_to_vec(); - break; - - default: - ERR_PRINT("Unknown init_board type"); - return std::vector{}; - } - }; + const auto update_cell_cb = [&](size_t i, size_t j, uint8_t state) { emit_signal("update_cell", i, j, state); }; + const auto update_done_cb = [&]() { emit_signal("update_done"); }; 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"); - } - ); + if (init_board.get_type() == Variant::NIL) { + std::vector board(w * h); + m_engine = std::make_unique>(std::move(board), w, h, update_cell_cb, update_done_cb); + } else if (init_board.get_type() == Variant::PACKED_BYTE_ARRAY) { + m_engine = std::make_unique>(init_board, w, h, update_cell_cb, update_done_cb); + } else { + ERR_PRINT("Unknown init_board type"); + } break; default: @@ -56,7 +38,9 @@ void LifeDriver::setup(const size_t w, const size_t h, const Variant& init_board } void LifeDriver::next_iteration() { - m_engine->next_iteration(); + if (m_engine) { + m_engine->next_iteration(); + } } void LifeDriver::_bind_methods() { diff --git a/src/life_driver.h b/src/life_driver.h index ecf4fde..2b51fc9 100644 --- a/src/life_driver.h +++ b/src/life_driver.h @@ -26,7 +26,7 @@ class LifeDriver : public Object { static void _bind_methods(); private: - std::unique_ptr m_engine; + std::unique_ptr m_engine; }; }