From f8ac60f6a652f0a7cdfc40fc756ded2b1232df28 Mon Sep 17 00:00:00 2001 From: Christoph Urlacher Date: Fri, 20 Feb 2026 00:58:01 +0100 Subject: [PATCH] implement state editing --- include/config.hpp | 2 +- include/klotski.hpp | 10 ++++- include/renderer.hpp | 6 +-- src/klotski.cpp | 42 ++++++++++++++++- src/main.cpp | 105 ++++++++++++++++++++++++++++++++++++------- src/renderer.cpp | 30 +++++++++---- 6 files changed, 166 insertions(+), 29 deletions(-) diff --git a/include/config.hpp b/include/config.hpp index 8425538..bfbaf79 100644 --- a/include/config.hpp +++ b/include/config.hpp @@ -20,7 +20,7 @@ constexpr int MENU_COLS = 3; // Camera Controls constexpr float SIM_SPEED = 4.0; constexpr float CAMERA_FOV = 120.0; -constexpr float CAMERA_DISTANCE = 100.0; +constexpr float CAMERA_DISTANCE = 20.0; constexpr float MIN_CAMERA_DISTANCE = 2.0; constexpr float MAX_CAMERA_DISTANCE = 2000.0; constexpr float ZOOM_SPEED = 2.5; diff --git a/include/klotski.hpp b/include/klotski.hpp index 5c8bcba..07c4938 100644 --- a/include/klotski.hpp +++ b/include/klotski.hpp @@ -252,7 +252,15 @@ public: public: auto Hash() const -> int; - auto AddBlock(Block block) -> bool; + auto AddColumn() const -> State; + + auto RemoveColumn() const -> State; + + auto AddRow() const -> State; + + auto RemoveRow() const -> State; + + auto AddBlock(const Block &block) -> bool; auto GetBlock(int x, int y) const -> Block; diff --git a/include/renderer.hpp b/include/renderer.hpp index 6124376..5531652 100644 --- a/include/renderer.hpp +++ b/include/renderer.hpp @@ -92,10 +92,10 @@ public: const State ¤t) -> void; auto DrawKlotski(const State &state, int hov_x, int hov_y, int sel_x, - int sel_y) -> void; + int sel_y, int block_add_x, int block_add_y) -> void; - auto DrawMenu(const MassSpringSystem &masssprings, int current_preset) - -> void; + auto DrawMenu(const MassSpringSystem &masssprings, int current_preset, + const State ¤t_state) -> void; auto DrawTextures() -> void; }; diff --git a/src/klotski.cpp b/src/klotski.cpp index 0bbd7c8..dd1d605 100644 --- a/src/klotski.cpp +++ b/src/klotski.cpp @@ -45,7 +45,47 @@ auto Block::Collides(const Block &other) const -> bool { auto State::Hash() const -> int { return std::hash{}(state); } -auto State::AddBlock(Block block) -> bool { +auto State::AddColumn() const -> State { + State newstate = State(width + 1, height, restricted); + + for (const auto &block : *this) { + newstate.AddBlock(block); + } + + return newstate; +} + +auto State::RemoveColumn() const -> State { + State newstate = State(width - 1, height, restricted); + + for (const auto &block : *this) { + newstate.AddBlock(block); + } + + return newstate; +} + +auto State::AddRow() const -> State { + State newstate = State(width, height + 1, restricted); + + for (const auto &block : *this) { + newstate.AddBlock(block); + } + + return newstate; +} + +auto State::RemoveRow() const -> State { + State newstate = State(width, height - 1, restricted); + + for (const auto &block : *this) { + newstate.AddBlock(block); + } + + return newstate; +} + +auto State::AddBlock(const Block &block) -> bool { if (block.x + block.width > width || block.y + block.height > height) { return false; } diff --git a/src/main.cpp b/src/main.cpp index 1241b2e..53ba7b2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -25,10 +25,11 @@ auto apply_state(MassSpringSystem &mass_springs, StateGenerator generator) return s; }; -auto solve_closure(MassSpringSystem &mass_springs, const State board) -> void { +auto populate_masssprings(MassSpringSystem &mass_springs, + const State ¤t_state) -> void { std::pair, std::vector>> - closure = board.Closure(); + closure = current_state.Closure(); for (const auto &state : closure.first) { Vector3 pos = Vector3(static_cast(GetRandomValue(-10000, 10000)) / 1000.0, @@ -53,6 +54,14 @@ auto solve_closure(MassSpringSystem &mass_springs, const State board) -> void { << " Bytes for springs." << std::endl; } +auto clear_masssprings(MassSpringSystem &masssprings, + const State ¤t_state) -> std::string { + masssprings.masses.clear(); + masssprings.springs.clear(); + masssprings.AddMass(MASS, Vector3Zero(), false, current_state.state); + return current_state.state; +} + auto main(int argc, char *argv[]) -> int { // if (argc < 2) { // std::cout << "Missing .klotski file." << std::endl; @@ -83,6 +92,9 @@ auto main(int argc, char *argv[]) -> int { // Game loop float frametime; + bool has_block_add_xy = false; + int block_add_x = -1; + int block_add_y = -1; int hov_x = 0; int hov_y = 0; int sel_x = 0; @@ -95,6 +107,7 @@ auto main(int argc, char *argv[]) -> int { int time_measure_count = 0; while (!WindowShouldClose()) { frametime = GetFrameTime(); + std::string previous_state = current_state.state; // Mouse handling const int board_width = GetScreenWidth() / 2.0 - 2 * BOARD_PADDING; @@ -121,12 +134,58 @@ auto main(int argc, char *argv[]) -> int { hov_y = (m.y - MENU_HEIGHT - y_offset) / (block_size + 2 * BLOCK_PADDING); } if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { - sel_x = hov_x; - sel_y = hov_y; + // If we clicked a block... + if (current_state.GetBlock(hov_x, hov_y).IsValid()) { + sel_x = hov_x; + sel_y = hov_y; + } + // If we clicked empty space... + else { + // Select a position + if (!has_block_add_xy) { + if (hov_x >= 0 && hov_x < current_state.width && hov_y >= 0 && + hov_y < current_state.height) { + block_add_x = hov_x; + block_add_y = hov_y; + has_block_add_xy = true; + } + } + // If we have already selected a position + else { + int block_add_width = hov_x - block_add_x + 1; + int block_add_height = hov_y - block_add_y + 1; + if (block_add_width <= 0 || block_add_height <= 0) { + block_add_x = -1; + block_add_y = -1; + has_block_add_xy = false; + } else if (block_add_x >= 0 && + block_add_x + block_add_width <= current_state.width && + block_add_y >= 0 && + block_add_y + block_add_height <= current_state.height) { + bool success = current_state.AddBlock( + Block(block_add_x, block_add_y, block_add_width, + block_add_height, false)); + + if (success) { + block_add_x = -1; + block_add_y = -1; + has_block_add_xy = false; + previous_state = clear_masssprings(masssprings, current_state); + } + } + } + } + } else if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT)) { + if (current_state.RemoveBlock(hov_x, hov_y)) { + previous_state = clear_masssprings(masssprings, current_state); + } else if (has_block_add_xy) { + block_add_x = -1; + block_add_y = -1; + has_block_add_xy = false; + } } // Key handling - std::string previous_state = current_state.state; if (IsKeyPressed(KEY_W)) { if (current_state.MoveBlockAt(sel_x, sel_y, Direction::NOR)) { sel_y--; @@ -156,19 +215,34 @@ auto main(int argc, char *argv[]) -> int { previous_state = current_state.state; } else if (IsKeyPressed(KEY_R)) { current_state = generators[current_preset](); - previous_state = current_state.state; - } else if (IsKeyPressed(KEY_C)) { - solve_closure(masssprings, current_state); - renderer.UpdateWinningStates(masssprings, win_conditions[current_preset]); + previous_state = clear_masssprings(masssprings, current_state); } else if (IsKeyPressed(KEY_G)) { - masssprings.masses.clear(); - masssprings.springs.clear(); - masssprings.AddMass(MASS, Vector3Zero(), false, current_state.state); - previous_state = current_state.state; + previous_state = clear_masssprings(masssprings, current_state); + populate_masssprings(masssprings, current_state); + renderer.UpdateWinningStates(masssprings, win_conditions[current_preset]); + } else if (IsKeyPressed(KEY_C)) { + // We also need to clear the graph, in case the state has been edited. + // Then the graph would contain states that are impossible. + previous_state = clear_masssprings(masssprings, current_state); } else if (IsKeyPressed(KEY_I)) { renderer.mark_solutions = !renderer.mark_solutions; } else if (IsKeyPressed(KEY_O)) { renderer.connect_solutions = !renderer.connect_solutions; + } else if (IsKeyPressed(KEY_T)) { + current_state.restricted = !current_state.restricted; + } else if (IsKeyPressed(KEY_LEFT) && current_state.width > 1) { + current_state = current_state.RemoveColumn(); + previous_state = clear_masssprings(masssprings, current_state); + } else if (IsKeyPressed(KEY_RIGHT) && current_state.width < 9) { + current_state = current_state.AddColumn(); + previous_state = clear_masssprings(masssprings, current_state); + + } else if (IsKeyPressed(KEY_UP) && current_state.height > 1) { + current_state = current_state.RemoveRow(); + previous_state = clear_masssprings(masssprings, current_state); + } else if (IsKeyPressed(KEY_DOWN) && current_state.height < 9) { + current_state = current_state.AddRow(); + previous_state = clear_masssprings(masssprings, current_state); } if (previous_state != current_state.state) { @@ -206,8 +280,9 @@ auto main(int argc, char *argv[]) -> int { renderer.UpdateCamera(masssprings, current_state); renderer.UpdateTextureSizes(); renderer.DrawMassSprings(masssprings, current_state); - renderer.DrawKlotski(current_state, hov_x, hov_y, sel_x, sel_y); - renderer.DrawMenu(masssprings, current_preset); + renderer.DrawKlotski(current_state, hov_x, hov_y, sel_x, sel_y, block_add_x, + block_add_y); + renderer.DrawMenu(masssprings, current_preset, current_state); renderer.DrawTextures(); std::chrono::high_resolution_clock::time_point re = std::chrono::high_resolution_clock::now(); diff --git a/src/renderer.cpp b/src/renderer.cpp index 40aabaa..371f08a 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -176,7 +176,8 @@ auto Renderer::DrawMassSprings(const MassSpringSystem &masssprings, } auto Renderer::DrawKlotski(const State &state, int hov_x, int hov_y, int sel_x, - int sel_y) -> void { + int sel_y, int block_add_x, int block_add_y) + -> void { BeginTextureMode(klotski_target); ClearBackground(RAYWHITE); @@ -244,13 +245,22 @@ auto Renderer::DrawKlotski(const State &state, int hov_x, int hov_y, int sel_x, } } + // Draw editing starting position + if (block_add_x >= 0 && block_add_y >= 0) { + DrawCircle(x_offset + BOARD_PADDING + block_add_x * BLOCK_PADDING * 2 + + BLOCK_PADDING + block_add_x * block_size + block_size / 2, + y_offset + BOARD_PADDING + block_add_y * BLOCK_PADDING * 2 + + BLOCK_PADDING + block_add_y * block_size + block_size / 2, + block_size / 10.0, Fade(BLACK, 0.5)); + } + DrawLine(GetScreenWidth() / 2 - 1, 0, GetScreenWidth() / 2 - 1, GetScreenHeight() - MENU_HEIGHT, BLACK); EndTextureMode(); } -auto Renderer::DrawMenu(const MassSpringSystem &masssprings, int current_preset) - -> void { +auto Renderer::DrawMenu(const MassSpringSystem &masssprings, int current_preset, + const State ¤t_state) -> void { BeginTextureMode(menu_target); ClearBackground(RAYWHITE); @@ -264,7 +274,7 @@ auto Renderer::DrawMenu(const MassSpringSystem &masssprings, int current_preset) auto draw_btn = [&](int x, int y, std::string text, Color color) { int posx = MENU_PAD + x * (MENU_PAD + btn_width); int posy = MENU_PAD + y * (MENU_PAD + btn_height); - DrawRectangle(posx, posy, btn_width, btn_height, Fade(color, 0.5)); + DrawRectangle(posx, posy, btn_width, btn_height, Fade(color, 0.6)); DrawRectangleLines(posx, posy, btn_width, btn_height, color); DrawText(text.data(), posx + BUTTON_PAD, posy + BUTTON_PAD, btn_height - 2 * BUTTON_PAD, WHITE); @@ -284,16 +294,20 @@ auto Renderer::DrawMenu(const MassSpringSystem &masssprings, int current_preset) std::format("Lock Camera to Current State (L): {}", camera.target_lock), DARKGREEN); - draw_btn(1, 0, std::format("Reset Board State (R)"), DARKBLUE); - draw_btn(1, 1, std::format("Preset (M/N): {}", current_preset), DARKBLUE); + draw_btn(1, 0, std::format("Reset State + Graph (R)"), DARKBLUE); + draw_btn(1, 1, std::format("Add/Remove Col/Row (Arrow Keys)"), DARKBLUE); draw_btn(1, 2, std::format("Print Board State to Console (P)"), DARKBLUE); draw_btn(2, 0, + std::format("Preset (M/N): {}, {} (T)", current_preset, + current_state.restricted ? "Restricted" : "Free"), + DARKPURPLE); + draw_btn(2, 1, std::format("Populate Graph (G), Clear Graph (C)"), + DARKPURPLE); + draw_btn(2, 2, std::format("Mark (I): {} / Connect (O): {}", mark_solutions, connect_solutions), DARKPURPLE); - draw_btn(2, 1, std::format("Solve Board Closure (C)"), DARKPURPLE); - draw_btn(2, 2, std::format("Clear Graph (G)"), DARKPURPLE); DrawLine(0, MENU_HEIGHT - 1, GetScreenWidth(), MENU_HEIGHT - 1, BLACK); EndTextureMode();