From 82d618f692e67b58e415d810145f6a8941800030 Mon Sep 17 00:00:00 2001 From: Christoph Urlacher Date: Wed, 25 Feb 2026 12:43:32 +0100 Subject: [PATCH] update menu + block placing visualization + add move history --- include/state.hpp | 6 +++++ src/input.cpp | 4 ++++ src/main.cpp | 1 - src/renderer.cpp | 61 ++++++++++++++++++++++++++++++----------------- src/state.cpp | 40 +++++++++++++++++++++++++------ 5 files changed, 82 insertions(+), 30 deletions(-) diff --git a/include/state.hpp b/include/state.hpp index ba2bbfd..d4ef7ca 100644 --- a/include/state.hpp +++ b/include/state.hpp @@ -7,6 +7,7 @@ #include "puzzle.hpp" #include +#include #include #include @@ -20,6 +21,7 @@ public: std::unordered_map states; std::unordered_set winning_states; std::unordered_set visited_states; + std::stack history; // Other stuff maps from mass to state :/ std::unordered_map masses; @@ -86,6 +88,10 @@ public: auto GoToWorst() -> void; + auto GoToNearestTarget() -> void; + + auto PopHistory() -> void; + auto CurrentMassIndex() const -> std::size_t; }; diff --git a/src/input.cpp b/src/input.cpp index d933c45..a207dd5 100644 --- a/src/input.cpp +++ b/src/input.cpp @@ -155,6 +155,10 @@ auto InputHandler::HandleKeys() -> void { state.NextPath(); } else if (IsKeyPressed(KEY_V)) { state.GoToWorst(); + } else if (IsKeyPressed(KEY_B)) { + state.GoToNearestTarget(); + } else if (IsKeyPressed(KEY_BACKSPACE)) { + state.PopHistory(); } else if (IsKeyPressed(KEY_F)) { state.current_state.ToggleRestricted(); state.ClearGraph(); diff --git a/src/main.cpp b/src/main.cpp index f71b9ab..e7ca7da 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -50,7 +50,6 @@ auto main(int argc, char *argv[]) -> int { #endif // Input update - state.previous_state = state.current_state; input.HandleInput(); state.UpdateGraph(); // Add state added after user input diff --git a/src/renderer.cpp b/src/renderer.cpp index 93535a9..3eed467 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -267,13 +267,20 @@ auto Renderer::DrawKlotski() -> void { } // Draw editing starting position - if (input.block_add_x >= 0 && input.block_add_y >= 0) { - DrawCircle( + if (input.block_add_x >= 0 && input.block_add_y >= 0 && + input.hov_x >= input.block_add_x && input.hov_y >= input.block_add_y) { + int block_width = input.hov_x - input.block_add_x + 1; + int block_height = input.hov_y - input.block_add_y + 1; + DrawRectangle( x_offset + BOARD_PADDING + input.block_add_x * BLOCK_PADDING * 2 + - BLOCK_PADDING + input.block_add_x * block_size + block_size / 2, + BLOCK_PADDING + input.block_add_x * block_size, y_offset + BOARD_PADDING + input.block_add_y * BLOCK_PADDING * 2 + - BLOCK_PADDING + input.block_add_y * block_size + block_size / 2, - block_size / 10.0, Fade(BLACK, 0.5)); + BLOCK_PADDING + input.block_add_y * block_size, + block_width * block_size + block_width * 2 * BLOCK_PADDING - + 2 * BLOCK_PADDING, + block_height * block_size + block_height * 2 * BLOCK_PADDING - + 2 * BLOCK_PADDING, + Fade(BLOCK_COLOR, 0.5)); } // Draw board goal position @@ -316,42 +323,52 @@ auto Renderer::DrawMenu(const std::vector &masses) -> void { 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.6)); + DrawRectangle(posx, posy, btn_width, btn_height, Fade(color, 0.7)); DrawRectangleLines(posx, posy, btn_width, btn_height, color); DrawText(text.data(), posx + BUTTON_PAD, posy + BUTTON_PAD, btn_height - 2.0 * BUTTON_PAD, WHITE); }; + // Left column draw_btn(0, 0, - std::format("States: {}, Transitions: {}, Winning: {}", + std::format("States: {} / Transitions: {} / Winning: {}", masses.size(), state.springs.size(), state.winning_states.size()), + ORANGE); + draw_btn(0, 1, + std::format("Preset (M/N): {}, {} (F)", state.current_preset, + state.current_state.restricted ? "Restricted" : "Free"), + ORANGE); + draw_btn(0, 2, std::format("Pan (LMB) / Rotate (RMB) / Zoom (Wheel)"), DARKGREEN); draw_btn( - 0, 1, - std::format("Camera Distance (SHIFT for Fast Zoom): {}", camera.distance), - DARKGREEN); - draw_btn( - 0, 2, + 0, 3, std::format("Lock Camera to Current State (L): {}", camera.target_lock), DARKGREEN); - draw_btn(1, 0, std::format("Reset State (R)"), DARKBLUE); + // Center column + draw_btn(1, 0, std::format("Select (LMB) / Move (W, A, S, D) / Target (T)"), + 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(1, 2, std::format("Add/Remove Block (LMB/RMB), Set Goal (MMB)"), + DARKBLUE); + draw_btn(1, 3, std::format("Print State (P) / Reset State (R)"), DARKBLUE); - draw_btn(2, 0, - std::format("Preset (M/N): {}, {} (F)", state.current_preset, - state.current_state.restricted ? "Restricted" : "Free"), + // Right column + draw_btn(2, 0, std::format("Populate Graph (G), Clear Graph (C)"), DARKPURPLE); - draw_btn(2, 1, std::format("Populate Graph (G), Clear Graph (C)"), - DARKPURPLE); - draw_btn(2, 2, - std::format("Path (U): {} / Mark (I): {} / Connect (O): {}", + draw_btn(2, 1, + std::format("Path (U): {} / Goals (I): {} / Connect (O): {}", input.mark_path, input.mark_solutions, input.connect_solutions), DARKPURPLE); - draw_btn(2, 3, std::format("Path forward (Space) / To worst (V)"), + draw_btn(2, 2, std::format("Best move (Space) / Move back (Backspace)"), + DARKPURPLE); + draw_btn(2, 3, + std::format("Worst (V) / Nearest target (B) / Moves remaining: {}", + state.winning_path.size() > 0 + ? state.winning_path.size() - 1 + : 0), DARKPURPLE); DrawLine(0, MENU_HEIGHT - 1, GetScreenWidth(), MENU_HEIGHT - 1, BLACK); diff --git a/src/state.cpp b/src/state.cpp index 26e7c30..02b502e 100644 --- a/src/state.cpp +++ b/src/state.cpp @@ -133,7 +133,17 @@ auto StateManager::UpdateGraph() -> void { } visited_states.insert(current_state); + + if (history.size() > 0 && history.top() == current_state) { + // We don't pop the stack when moving backwards to indicate if we need to + // push or pop here + history.pop(); + } else { + history.push(previous_state); + } + FindTargetPath(); + previous_state = current_state; } auto StateManager::ClearGraph() -> void { @@ -143,6 +153,7 @@ auto StateManager::ClearGraph() -> void { masses.clear(); winning_path.clear(); springs.clear(); + history = std::stack(); target_distances.Clear(); physics.ClearCmd(); @@ -193,8 +204,8 @@ auto StateManager::FindTargetPath() -> void { } winning_path = GetPath(target_distances, CurrentMassIndex()); - std::cout << "Nearest target is " << winning_path.size() << " moves away." - << std::endl; + // std::cout << "Nearest target is " << winning_path.size() << " moves away." + // << std::endl; } auto StateManager::FindWorstState() -> State { @@ -205,8 +216,8 @@ auto StateManager::FindWorstState() -> State { int max = 0; int index = 0; for (std::size_t i = 0; i < target_distances.distances.size(); ++i) { - if (target_distances.distances[i] > max) { - max = target_distances.distances[i]; + if (target_distances.distances.at(i) > max) { + max = target_distances.distances.at(i); index = i; } } @@ -214,9 +225,24 @@ auto StateManager::FindWorstState() -> State { return masses.at(index); } -auto StateManager::GoToWorst() -> void { - current_state = FindWorstState(); - FindTargetPath(); +auto StateManager::GoToWorst() -> void { current_state = FindWorstState(); } + +auto StateManager::GoToNearestTarget() -> void { + if (target_distances.Empty()) { + return; + } + + current_state = + masses.at(target_distances.nearest_targets.at(CurrentMassIndex())); +} + +auto StateManager::PopHistory() -> void { + if (history.size() == 0) { + return; + } + + current_state = history.top(); + // history.pop(); // Done in UpdateGraph(); } auto StateManager::CurrentMassIndex() const -> std::size_t {