implement automatic graph traversal along shortest path to solution

This commit is contained in:
2026-02-25 02:58:30 +01:00
parent fd58f217c6
commit 271902ab1f
7 changed files with 62 additions and 23 deletions

View File

@ -56,17 +56,12 @@ auto CalculateDistances(
queue.pop();
for (std::size_t neighbor : adjacency[current]) {
// std::cout << "Visiting edge (" << current << "->" << neighbor << ")."
// << std::endl;
if (distances[neighbor] == -1) {
// If distance is -1 we haven't visited the node yet
distances[neighbor] = distances[current] + 1;
parents[neighbor] = current;
nearest_targets[neighbor] = nearest_targets[current];
// std::cout << "- Distance: " << distances[neighbor]
// << ", Parent: " << parents[neighbor]
// << ", Nearest Target: " << nearest_targets[neighbor] << "."
// << std::endl;
queue.push(neighbor);
}
}

View File

@ -103,19 +103,19 @@ auto InputHandler::HandleMouse() -> void {
}
auto InputHandler::HandleKeys() -> void {
if (IsKeyPressed(KEY_W) || IsKeyPressed(KEY_K)) {
if (IsKeyPressed(KEY_W)) {
if (state.current_state.MoveBlockAt(sel_x, sel_y, Direction::NOR)) {
sel_y--;
}
} else if (IsKeyPressed(KEY_A) || IsKeyPressed(KEY_H)) {
} else if (IsKeyPressed(KEY_A)) {
if (state.current_state.MoveBlockAt(sel_x, sel_y, Direction::WES)) {
sel_x--;
}
} else if (IsKeyPressed(KEY_S) || IsKeyPressed(KEY_J)) {
} else if (IsKeyPressed(KEY_S)) {
if (state.current_state.MoveBlockAt(sel_x, sel_y, Direction::SOU)) {
sel_y++;
}
} else if (IsKeyPressed(KEY_D) || IsKeyPressed(KEY_L)) {
} else if (IsKeyPressed(KEY_D)) {
if (state.current_state.MoveBlockAt(sel_x, sel_y, Direction::EAS)) {
sel_x++;
}
@ -150,6 +150,10 @@ auto InputHandler::HandleKeys() -> void {
mark_solutions = !mark_solutions;
} else if (IsKeyPressed(KEY_O)) {
connect_solutions = !connect_solutions;
} else if (IsKeyPressed(KEY_U)) {
mark_path = !mark_path;
} else if (IsKeyPressed(KEY_SPACE)) {
state.NextPath();
} else if (IsKeyPressed(KEY_F)) {
state.current_state.ToggleRestricted();
state.ClearGraph();

View File

@ -153,6 +153,17 @@ auto Renderer::DrawMassSprings(const std::vector<Vector3> &masses) -> void {
}
}
// Mark winning path
if (input.mark_path) {
for (const std::size_t &_state : state.winning_path) {
if (masses.size() > _state) {
const Vector3 &path_mass = masses.at(_state);
DrawCube(path_mass, VERTEX_SIZE * 1.75, VERTEX_SIZE * 1.75,
VERTEX_SIZE * 1.75, YELLOW);
}
}
}
// Mark starting state
std::size_t starting_index = state.states.at(state.starting_state);
if (masses.size() > starting_index) {
@ -308,7 +319,7 @@ auto Renderer::DrawMenu(const std::vector<Vector3> &masses) -> void {
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);
btn_height - 2.0 * BUTTON_PAD, WHITE);
};
draw_btn(0, 0,
@ -336,9 +347,11 @@ auto Renderer::DrawMenu(const std::vector<Vector3> &masses) -> void {
draw_btn(2, 1, std::format("Populate Graph (G), Clear Graph (C)"),
DARKPURPLE);
draw_btn(2, 2,
std::format("Mark (I): {} / Connect (O): {}", input.mark_solutions,
std::format("Path (U): {} / Mark (I): {} / Connect (O): {}",
input.mark_path, input.mark_solutions,
input.connect_solutions),
DARKPURPLE);
draw_btn(2, 3, std::format("Move along Path (Space)"), DARKPURPLE);
DrawLine(0, MENU_HEIGHT - 1, GetScreenWidth(), MENU_HEIGHT - 1, BLACK);
EndTextureMode();

View File

@ -50,6 +50,7 @@ auto StateManager::LoadPreset(int preset) -> void {
auto StateManager::ResetState() -> void {
current_state = presets.at(current_preset);
previous_state = current_state;
FindTargetPath();
if (edited) {
// We also need to clear the graph in case the state has been edited
// because the graph could contain states that are impossible to reach
@ -67,6 +68,23 @@ auto StateManager::NextPreset() -> void {
LoadPreset((current_preset + 1) % presets.size());
}
auto StateManager::NextPath() -> void {
if (target_distances.Empty()) {
return;
}
// Already there
if (target_distances.distances[CurrentMassIndex()] == 0) {
return;
}
std::size_t parent = target_distances.parents[CurrentMassIndex()];
// std::cout << "Parent of node " << CurrentMassIndex() << " is " << parent
// << std::endl;
current_state = masses.at(parent);
FindTargetPath();
}
auto StateManager::FillGraph() -> void {
ZoneScoped;
@ -80,7 +98,7 @@ auto StateManager::FillGraph() -> void {
physics.AddMassSpringsCmd(closure.first.size(), closure.second);
for (const State &state : closure.first) {
states.insert(std::make_pair(state, states.size()));
masses.insert(std::make_pair(masses.size(), state));
masses.insert(std::make_pair(states.size() - 1, state));
}
for (const auto &[from, to] : closure.second) {
springs.emplace_back(from, to);
@ -88,6 +106,12 @@ auto StateManager::FillGraph() -> void {
FindWinningStates();
FindTargetDistances();
FindTargetPath();
// Sanity check. Both values need to be equal
// for (const auto &[mass, state] : masses) {
// std::cout << "Masses: " << mass << ", States: " << states.at(state)
// << std::endl;
// }
}
auto StateManager::UpdateGraph() -> void {
@ -97,7 +121,7 @@ auto StateManager::UpdateGraph() -> void {
if (!states.contains(current_state)) {
states.insert(std::make_pair(current_state, states.size()));
masses.insert(std::make_pair(masses.size(), current_state));
masses.insert(std::make_pair(states.size() - 1, current_state));
springs.emplace_back(states.at(current_state), states.at(previous_state));
physics.AddMassCmd();
physics.AddSpringCmd(states.at(current_state), states.at(previous_state));
@ -124,7 +148,7 @@ auto StateManager::ClearGraph() -> void {
// Re-add the default stuff to the graph
states.insert(std::make_pair(current_state, states.size()));
masses.insert(std::make_pair(masses.size(), current_state));
masses.insert(std::make_pair(states.size() - 1, current_state));
visited_states.insert(current_state);
physics.AddMassCmd();
@ -158,8 +182,9 @@ auto StateManager::FindTargetDistances() -> void {
target_distances = CalculateDistances(states.size(), springs, targets);
std::cout << "Calculated " << target_distances.distances.size()
<< " distances to " << targets.size() << " targets." << std::endl;
// std::cout << "Calculated " << target_distances.distances.size()
// << " distances to " << targets.size() << " targets." <<
// std::endl;
}
auto StateManager::FindTargetPath() -> void {
@ -168,9 +193,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::CurrentMassIndex() const -> std::size_t {