update menu + block placing visualization + add move history
This commit is contained in:
@ -7,6 +7,7 @@
|
|||||||
#include "puzzle.hpp"
|
#include "puzzle.hpp"
|
||||||
|
|
||||||
#include <raymath.h>
|
#include <raymath.h>
|
||||||
|
#include <stack>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
|
||||||
@ -20,6 +21,7 @@ public:
|
|||||||
std::unordered_map<State, std::size_t> states;
|
std::unordered_map<State, std::size_t> states;
|
||||||
std::unordered_set<State> winning_states;
|
std::unordered_set<State> winning_states;
|
||||||
std::unordered_set<State> visited_states;
|
std::unordered_set<State> visited_states;
|
||||||
|
std::stack<State> history;
|
||||||
|
|
||||||
// Other stuff maps from mass to state :/
|
// Other stuff maps from mass to state :/
|
||||||
std::unordered_map<std::size_t, State> masses;
|
std::unordered_map<std::size_t, State> masses;
|
||||||
@ -86,6 +88,10 @@ public:
|
|||||||
|
|
||||||
auto GoToWorst() -> void;
|
auto GoToWorst() -> void;
|
||||||
|
|
||||||
|
auto GoToNearestTarget() -> void;
|
||||||
|
|
||||||
|
auto PopHistory() -> void;
|
||||||
|
|
||||||
auto CurrentMassIndex() const -> std::size_t;
|
auto CurrentMassIndex() const -> std::size_t;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -155,6 +155,10 @@ auto InputHandler::HandleKeys() -> void {
|
|||||||
state.NextPath();
|
state.NextPath();
|
||||||
} else if (IsKeyPressed(KEY_V)) {
|
} else if (IsKeyPressed(KEY_V)) {
|
||||||
state.GoToWorst();
|
state.GoToWorst();
|
||||||
|
} else if (IsKeyPressed(KEY_B)) {
|
||||||
|
state.GoToNearestTarget();
|
||||||
|
} else if (IsKeyPressed(KEY_BACKSPACE)) {
|
||||||
|
state.PopHistory();
|
||||||
} else if (IsKeyPressed(KEY_F)) {
|
} else if (IsKeyPressed(KEY_F)) {
|
||||||
state.current_state.ToggleRestricted();
|
state.current_state.ToggleRestricted();
|
||||||
state.ClearGraph();
|
state.ClearGraph();
|
||||||
|
|||||||
@ -50,7 +50,6 @@ auto main(int argc, char *argv[]) -> int {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Input update
|
// Input update
|
||||||
state.previous_state = state.current_state;
|
|
||||||
input.HandleInput();
|
input.HandleInput();
|
||||||
state.UpdateGraph(); // Add state added after user input
|
state.UpdateGraph(); // Add state added after user input
|
||||||
|
|
||||||
|
|||||||
@ -267,13 +267,20 @@ auto Renderer::DrawKlotski() -> void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Draw editing starting position
|
// Draw editing starting position
|
||||||
if (input.block_add_x >= 0 && input.block_add_y >= 0) {
|
if (input.block_add_x >= 0 && input.block_add_y >= 0 &&
|
||||||
DrawCircle(
|
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 +
|
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 +
|
y_offset + BOARD_PADDING + input.block_add_y * BLOCK_PADDING * 2 +
|
||||||
BLOCK_PADDING + input.block_add_y * block_size + block_size / 2,
|
BLOCK_PADDING + input.block_add_y * block_size,
|
||||||
block_size / 10.0, Fade(BLACK, 0.5));
|
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
|
// Draw board goal position
|
||||||
@ -316,42 +323,52 @@ auto Renderer::DrawMenu(const std::vector<Vector3> &masses) -> void {
|
|||||||
auto draw_btn = [&](int x, int y, std::string text, Color color) {
|
auto draw_btn = [&](int x, int y, std::string text, Color color) {
|
||||||
int posx = MENU_PAD + x * (MENU_PAD + btn_width);
|
int posx = MENU_PAD + x * (MENU_PAD + btn_width);
|
||||||
int posy = MENU_PAD + y * (MENU_PAD + btn_height);
|
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);
|
DrawRectangleLines(posx, posy, btn_width, btn_height, color);
|
||||||
DrawText(text.data(), posx + BUTTON_PAD, posy + BUTTON_PAD,
|
DrawText(text.data(), posx + BUTTON_PAD, posy + BUTTON_PAD,
|
||||||
btn_height - 2.0 * BUTTON_PAD, WHITE);
|
btn_height - 2.0 * BUTTON_PAD, WHITE);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Left column
|
||||||
draw_btn(0, 0,
|
draw_btn(0, 0,
|
||||||
std::format("States: {}, Transitions: {}, Winning: {}",
|
std::format("States: {} / Transitions: {} / Winning: {}",
|
||||||
masses.size(), state.springs.size(),
|
masses.size(), state.springs.size(),
|
||||||
state.winning_states.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);
|
DARKGREEN);
|
||||||
draw_btn(
|
draw_btn(
|
||||||
0, 1,
|
0, 3,
|
||||||
std::format("Camera Distance (SHIFT for Fast Zoom): {}", camera.distance),
|
|
||||||
DARKGREEN);
|
|
||||||
draw_btn(
|
|
||||||
0, 2,
|
|
||||||
std::format("Lock Camera to Current State (L): {}", camera.target_lock),
|
std::format("Lock Camera to Current State (L): {}", camera.target_lock),
|
||||||
DARKGREEN);
|
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, 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,
|
// Right column
|
||||||
std::format("Preset (M/N): {}, {} (F)", state.current_preset,
|
draw_btn(2, 0, std::format("Populate Graph (G), Clear Graph (C)"),
|
||||||
state.current_state.restricted ? "Restricted" : "Free"),
|
|
||||||
DARKPURPLE);
|
DARKPURPLE);
|
||||||
draw_btn(2, 1, std::format("Populate Graph (G), Clear Graph (C)"),
|
draw_btn(2, 1,
|
||||||
DARKPURPLE);
|
std::format("Path (U): {} / Goals (I): {} / Connect (O): {}",
|
||||||
draw_btn(2, 2,
|
|
||||||
std::format("Path (U): {} / Mark (I): {} / Connect (O): {}",
|
|
||||||
input.mark_path, input.mark_solutions,
|
input.mark_path, input.mark_solutions,
|
||||||
input.connect_solutions),
|
input.connect_solutions),
|
||||||
DARKPURPLE);
|
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);
|
DARKPURPLE);
|
||||||
|
|
||||||
DrawLine(0, MENU_HEIGHT - 1, GetScreenWidth(), MENU_HEIGHT - 1, BLACK);
|
DrawLine(0, MENU_HEIGHT - 1, GetScreenWidth(), MENU_HEIGHT - 1, BLACK);
|
||||||
|
|||||||
@ -133,7 +133,17 @@ auto StateManager::UpdateGraph() -> void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
visited_states.insert(current_state);
|
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();
|
FindTargetPath();
|
||||||
|
previous_state = current_state;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto StateManager::ClearGraph() -> void {
|
auto StateManager::ClearGraph() -> void {
|
||||||
@ -143,6 +153,7 @@ auto StateManager::ClearGraph() -> void {
|
|||||||
masses.clear();
|
masses.clear();
|
||||||
winning_path.clear();
|
winning_path.clear();
|
||||||
springs.clear();
|
springs.clear();
|
||||||
|
history = std::stack<State>();
|
||||||
target_distances.Clear();
|
target_distances.Clear();
|
||||||
physics.ClearCmd();
|
physics.ClearCmd();
|
||||||
|
|
||||||
@ -193,8 +204,8 @@ auto StateManager::FindTargetPath() -> void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
winning_path = GetPath(target_distances, CurrentMassIndex());
|
winning_path = GetPath(target_distances, CurrentMassIndex());
|
||||||
std::cout << "Nearest target is " << winning_path.size() << " moves away."
|
// std::cout << "Nearest target is " << winning_path.size() << " moves away."
|
||||||
<< std::endl;
|
// << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto StateManager::FindWorstState() -> State {
|
auto StateManager::FindWorstState() -> State {
|
||||||
@ -205,8 +216,8 @@ auto StateManager::FindWorstState() -> State {
|
|||||||
int max = 0;
|
int max = 0;
|
||||||
int index = 0;
|
int index = 0;
|
||||||
for (std::size_t i = 0; i < target_distances.distances.size(); ++i) {
|
for (std::size_t i = 0; i < target_distances.distances.size(); ++i) {
|
||||||
if (target_distances.distances[i] > max) {
|
if (target_distances.distances.at(i) > max) {
|
||||||
max = target_distances.distances[i];
|
max = target_distances.distances.at(i);
|
||||||
index = i;
|
index = i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -214,9 +225,24 @@ auto StateManager::FindWorstState() -> State {
|
|||||||
return masses.at(index);
|
return masses.at(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto StateManager::GoToWorst() -> void {
|
auto StateManager::GoToWorst() -> void { current_state = FindWorstState(); }
|
||||||
current_state = FindWorstState();
|
|
||||||
FindTargetPath();
|
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 {
|
auto StateManager::CurrentMassIndex() const -> std::size_t {
|
||||||
|
|||||||
Reference in New Issue
Block a user