Skip to content

Commit

Permalink
add selectable wrap around rule
Browse files Browse the repository at this point in the history
  • Loading branch information
dedztbh committed Apr 28, 2024
1 parent 6dabf1b commit d2a4537
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 15 deletions.
3 changes: 2 additions & 1 deletion game/board.gd
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ signal speed_changed
var init_matrix : Variant = null
var cells = []
var life_driver = LifeDriver.new()
var ruleset = Dictionary()

# Called when the node enters the scene tree for the first time.
func _ready():
Expand All @@ -25,7 +26,7 @@ func _ready():
life_driver.update_done.connect(_update_done)
next_iteration.connect(life_driver.next_iteration)

life_driver.setup(rows, columns, init_matrix, LifeDriver.BASIC)
life_driver.setup(rows, columns, init_matrix, LifeDriver.BASIC, ruleset)

emit_signal("speed_changed", 1 / $Timer.wait_time)

Expand Down
2 changes: 2 additions & 0 deletions game/main.gd
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ func _ready():
board.rows = 100
board.columns = 100
board.speed_changed.connect(board_speed_changed)
board.ruleset["wrap_around"] = $WrapAroundButton.button_pressed
add_child(board)


Expand Down Expand Up @@ -58,6 +59,7 @@ func _on_load_board_dialog_file_selected(path):
board.columns = width
board.rows = height
board.speed_changed.connect(board_speed_changed)
board.ruleset["wrap_around"] = $WrapAroundButton.button_pressed
add_child(board)


Expand Down
15 changes: 15 additions & 0 deletions game/main.tscn
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,21 @@ offset_top = -46.0
offset_right = 216.0
offset_bottom = -23.0

[node name="WrapAroundButton" type="CheckButton" parent="."]
offset_left = -175.0
offset_top = 77.0
offset_right = -11.0
offset_bottom = 108.0
button_pressed = true
text = "Wrap Around"

[node name="RulesLabel" type="Label" parent="."]
offset_left = -232.0
offset_top = 49.0
offset_right = -14.0
offset_bottom = 72.0
text = "Rules (take effect after load)"

[connection signal="file_selected" from="CanvasLayer/LoadBoardDialog" to="." method="_on_load_board_dialog_file_selected"]
[connection signal="button_down" from="LoadBoardButton" to="CanvasLayer/LoadBoardDialog" method="_on_load_board_button_button_down"]
[connection signal="pressed" from="SpeedUpButton" to="." method="_on_speed_up_button_pressed"]
Expand Down
18 changes: 16 additions & 2 deletions src/basic_engine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ concept BasicEngineContainer =
{ cboard[i] } -> std::convertible_to<EngineBase::state_t const &>;
};

template <BasicEngineContainer T = std::vector<EngineBase::state_t>>
struct BasicEngineRuleset {
bool wrap_around = false;
};

template <BasicEngineRuleset Ruleset, BasicEngineContainer T = std::vector<EngineBase::state_t>>
class BasicEngine : public EngineBase {
public:
using board_t = std::remove_reference_t<T>;
Expand Down Expand Up @@ -63,7 +67,17 @@ class BasicEngine : public EngineBase {
};
size_t count = 0;
for (const auto [x, y] : neighbor_offsets) {
if (at(curr_board, (x + W + i) % W, (y + H + j) % H)) {
const bool is_neighbor_alive = [&]() {
if constexpr (Ruleset.wrap_around) {
return at(curr_board, (x + W + i) % W, (y + H + j) % H);
} else {
const int64_t nx = x + i;
const int64_t ny = y + j;
return 0 <= nx && nx < W && 0 <= ny && ny < H && at(curr_board, nx, ny);
}
}();

if (is_neighbor_alive) {
++count;
}
}
Expand Down
48 changes: 37 additions & 11 deletions src/life_driver.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "life_driver.hpp"

#include <tuple>
#include <vector>

#include <godot_cpp/core/class_db.hpp>
Expand All @@ -12,24 +13,49 @@ using namespace godot;

using namespace lifepvp::engine;

void LifeDriver::setup(const size_t w, const size_t h, const Variant &init_board, const EngineType engine) {
// setup_engine_with_ruleset(tuple{bools...}, f) is same as f([]() { return Ruleset{bools...}; })
// Except bools need not be constexpr for Ruleset{bools...} to be constexpr.
template <class Ruleset, size_t I, bool... bools>
auto setup_engine_with_ruleset(const auto &tup, const auto f) requires(I == 0) {
return f([]() { return Ruleset{ bools... }; });
}

template <class Ruleset, size_t I, bool... bools>
auto setup_engine_with_ruleset(const auto &tup, const auto f) requires(0 < I && I <= std::tuple_size_v<std::remove_cvref_t<decltype(tup)>>) {
if (std::get<I - 1>(tup)) {
return setup_engine_with_ruleset<Ruleset, I - 1, bools..., true>(tup, f);
} else {
return setup_engine_with_ruleset<Ruleset, I - 1, bools..., false>(tup, f);
}
}

void LifeDriver::setup(const size_t w, const size_t h, const Variant &init_board, const EngineType engine, const Variant &ruleset) {
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:
if (engine == BASIC) {
// workaround for not being able to pass constexpr Ruleset directly
const auto make_engine = [&](const auto get_ruleset) {
if (init_board.get_type() == Variant::NIL) {
m_engine = std::make_unique<BasicEngine<>>(std::vector<EngineBase::state_t>(w * h), w, h, update_cell_cb, update_done_cb);
m_engine = std::make_unique<BasicEngine<get_ruleset()>>(std::vector<EngineBase::state_t>(w * h), w, h, update_cell_cb, update_done_cb);
} else if (init_board.get_type() == Variant::PACKED_BYTE_ARRAY) {
m_engine = std::make_unique<BasicEngine<PackedByteArray>>(init_board, w, h, update_cell_cb, update_done_cb);
m_engine = std::make_unique<BasicEngine<get_ruleset(), PackedByteArray>>(init_board, w, h, update_cell_cb, update_done_cb);
} else {
ERR_PRINT("Unknown init_board type");
ERR_PRINT("Unknown init_board type, should be either null or PackedByteArray");
}
break;

default:
ERR_PRINT("Unknown engine type");
return;
};
if (ruleset.get_type() == Variant::NIL) {
make_engine([](){ return BasicEngineRuleset{}; });
} else if (ruleset.get_type() == Variant::DICTIONARY) {
const Dictionary& dict = ruleset;
const auto tup = std::make_tuple<bool>(dict.get(String("wrap_around"), false));
setup_engine_with_ruleset<BasicEngineRuleset, std::tuple_size_v<decltype(tup)>>(tup, make_engine);
} else {
ERR_PRINT("Unknown init_board type, should be either null or PackedByteArray");
}
} else {
ERR_PRINT("Unknown engine type");
return;
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/life_driver.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class LifeDriver : public RefCounted {
BASIC
};

void setup(const size_t w, const size_t h, const Variant &init_board, const EngineType engine);
void setup(const size_t w, const size_t h, const Variant &init_board, const EngineType engine, const Variant& ruleset);

void next_iteration();

Expand Down

0 comments on commit d2a4537

Please sign in to comment.