259 lines
6.5 KiB
C++
259 lines
6.5 KiB
C++
#include "state.hpp"
|
|
#include "config.hpp"
|
|
#include "distance.hpp"
|
|
|
|
#include <fstream>
|
|
#include <ios>
|
|
#include <raymath.h>
|
|
|
|
#ifdef TRACY
|
|
#include "tracy.hpp"
|
|
#include <tracy/Tracy.hpp>
|
|
#endif
|
|
|
|
auto StateManager::ParsePresetFile(const std::string &preset_file) -> void {
|
|
std::ifstream file(preset_file);
|
|
if (!file) {
|
|
std::cout << "Preset file \"" << preset_file << "\" couldn't be loaded."
|
|
<< std::endl;
|
|
return;
|
|
}
|
|
|
|
std::string line;
|
|
std::vector<std::string> comment_lines;
|
|
std::vector<std::string> preset_lines;
|
|
while (std::getline(file, line)) {
|
|
if (line.starts_with("F") || line.starts_with("R")) {
|
|
preset_lines.push_back(line);
|
|
} else if (line.starts_with("#")) {
|
|
comment_lines.push_back(line);
|
|
}
|
|
}
|
|
|
|
if (preset_lines.size() == 0 || comment_lines.size() != preset_lines.size()) {
|
|
std::cout << "Preset file \"" << preset_file << "\" couldn't be loaded."
|
|
<< std::endl;
|
|
return;
|
|
}
|
|
|
|
presets.clear();
|
|
for (const auto &preset : preset_lines) {
|
|
presets.emplace_back(preset);
|
|
}
|
|
comments = comment_lines;
|
|
|
|
std::cout << "Loaded " << preset_lines.size() << " presets." << std::endl;
|
|
}
|
|
|
|
auto StateManager::LoadPreset(int preset) -> void {
|
|
current_preset = preset;
|
|
current_state = presets.at(current_preset);
|
|
ClearGraph();
|
|
edited = false;
|
|
}
|
|
|
|
auto StateManager::ResetState() -> void {
|
|
current_state = presets.at(current_preset);
|
|
previous_state = current_state;
|
|
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
|
|
// now.
|
|
ClearGraph();
|
|
edited = false;
|
|
}
|
|
}
|
|
|
|
auto StateManager::PreviousPreset() -> void {
|
|
LoadPreset((presets.size() + current_preset - 1) % presets.size());
|
|
}
|
|
|
|
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 {
|
|
#ifdef TRACY
|
|
ZoneScoped;
|
|
#endif
|
|
|
|
ClearGraph();
|
|
|
|
std::pair<std::vector<State>,
|
|
std::vector<std::pair<std::size_t, std::size_t>>>
|
|
closure = current_state.Closure();
|
|
|
|
physics.ClearCmd();
|
|
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(states.size() - 1, state));
|
|
}
|
|
for (const auto &[from, to] : closure.second) {
|
|
springs.emplace_back(from, to);
|
|
}
|
|
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 {
|
|
if (previous_state == current_state) {
|
|
return;
|
|
}
|
|
|
|
if (!states.contains(current_state)) {
|
|
states.insert(std::make_pair(current_state, states.size()));
|
|
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));
|
|
|
|
if (current_state.IsWon()) {
|
|
winning_states.insert(current_state);
|
|
}
|
|
FindTargetDistances();
|
|
}
|
|
|
|
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 {
|
|
states.clear();
|
|
winning_states.clear();
|
|
visited_states.clear();
|
|
masses.clear();
|
|
winning_path.clear();
|
|
springs.clear();
|
|
history = std::stack<State>();
|
|
target_distances.Clear();
|
|
physics.ClearCmd();
|
|
|
|
// Re-add the default stuff to the graph
|
|
states.insert(std::make_pair(current_state, states.size()));
|
|
masses.insert(std::make_pair(states.size() - 1, current_state));
|
|
visited_states.insert(current_state);
|
|
physics.AddMassCmd();
|
|
|
|
// These states are no longer in the graph
|
|
previous_state = current_state;
|
|
starting_state = current_state;
|
|
}
|
|
|
|
auto StateManager::FindWinningStates() -> void {
|
|
winning_states.clear();
|
|
for (const auto &[state, mass] : states) {
|
|
if (state.IsWon()) {
|
|
winning_states.insert(state);
|
|
}
|
|
}
|
|
}
|
|
|
|
auto StateManager::FindTargetDistances() -> void {
|
|
#ifdef TRACY
|
|
ZoneScoped;
|
|
#endif
|
|
|
|
if (springs.size() == 0 || winning_states.size() == 0) {
|
|
return;
|
|
}
|
|
|
|
// Find target indices
|
|
std::vector<std::size_t> targets;
|
|
targets.reserve(winning_states.size());
|
|
for (const auto &_state : winning_states) {
|
|
targets.push_back(states.at(_state));
|
|
}
|
|
|
|
target_distances = CalculateDistances(states.size(), springs, targets);
|
|
|
|
// std::cout << "Calculated " << target_distances.distances.size()
|
|
// << " distances to " << targets.size() << " targets." <<
|
|
// std::endl;
|
|
}
|
|
|
|
auto StateManager::FindTargetPath() -> void {
|
|
if (target_distances.Empty()) {
|
|
return;
|
|
}
|
|
|
|
winning_path = GetPath(target_distances, CurrentMassIndex());
|
|
// std::cout << "Nearest target is " << winning_path.size() << " moves away."
|
|
// << std::endl;
|
|
}
|
|
|
|
auto StateManager::FindWorstState() -> State {
|
|
if (target_distances.Empty()) {
|
|
return current_state;
|
|
}
|
|
|
|
int max = 0;
|
|
int index = 0;
|
|
for (std::size_t i = 0; i < target_distances.distances.size(); ++i) {
|
|
if (target_distances.distances.at(i) > max) {
|
|
max = target_distances.distances.at(i);
|
|
index = i;
|
|
}
|
|
}
|
|
|
|
return masses.at(index);
|
|
}
|
|
|
|
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 {
|
|
return states.at(current_state);
|
|
}
|