add popups to certain user actions
This commit is contained in:
@ -12,7 +12,7 @@ AllowShortIfStatementsOnASingleLine: true
|
||||
AllowShortLoopsOnASingleLine: true
|
||||
AlwaysBreakAfterDefinitionReturnType: false
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
||||
BraceWrapping:
|
||||
BraceWrapping:
|
||||
AfterCaseLabel: true
|
||||
AfterClass: true
|
||||
AfterControlStatement: false
|
||||
@ -30,8 +30,8 @@ BraceWrapping:
|
||||
SplitEmptyRecord: false
|
||||
SplitEmptyNamespace: false
|
||||
BreakBeforeBraces: Custom
|
||||
ColumnLimit: 120
|
||||
IncludeCategories:
|
||||
ColumnLimit: 100
|
||||
IncludeCategories:
|
||||
- Regex: '^<.*'
|
||||
Priority: 1
|
||||
- Regex: '^".*'
|
||||
|
||||
@ -24,7 +24,8 @@ public:
|
||||
|
||||
auto pan(Vector2 last_mouse, Vector2 mouse) -> void;
|
||||
|
||||
auto update(const Vector3& current_target, const Vector3& mass_center, bool lock, bool mass_center_lock) -> void;
|
||||
auto update(const Vector3& current_target, const Vector3& mass_center, bool lock,
|
||||
bool mass_center_lock) -> void;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@ -5,9 +5,28 @@
|
||||
#include "state_manager.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <queue>
|
||||
#include <raylib.h>
|
||||
#include <raymath.h>
|
||||
|
||||
struct show_ok_message
|
||||
{
|
||||
std::string title;
|
||||
std::string message;
|
||||
};
|
||||
|
||||
struct show_yes_no_message
|
||||
{
|
||||
std::string title;
|
||||
std::string message;
|
||||
std::function<void(void)> on_yes;
|
||||
};
|
||||
|
||||
struct show_save_preset_window
|
||||
{};
|
||||
|
||||
using ui_command = std::variant<show_ok_message, show_yes_no_message, show_save_preset_window>;
|
||||
|
||||
class input_handler
|
||||
{
|
||||
struct generic_handler
|
||||
@ -36,6 +55,8 @@ public:
|
||||
state_manager& state;
|
||||
orbit_camera& camera;
|
||||
|
||||
std::queue<ui_command> ui_commands;
|
||||
|
||||
bool disable = false;
|
||||
|
||||
// Block selection
|
||||
@ -115,7 +136,7 @@ public:
|
||||
auto load_next_preset() -> void;
|
||||
auto goto_starting_state() -> void;
|
||||
auto populate_graph() const -> void;
|
||||
auto clear_graph() const -> void;
|
||||
auto clear_graph() -> void;
|
||||
auto toggle_mark_solutions() -> void;
|
||||
auto toggle_connect_solutions() -> void;
|
||||
auto toggle_mark_path() -> void;
|
||||
@ -132,20 +153,25 @@ public:
|
||||
auto add_board_row() const -> void;
|
||||
auto toggle_editing() -> void;
|
||||
auto clear_goal() const -> void;
|
||||
auto save_preset() -> void;
|
||||
|
||||
// General
|
||||
auto register_generic_handler(const std::function<void(input_handler&)>& handler) -> void;
|
||||
|
||||
auto register_mouse_pressed_handler(MouseButton button, const std::function<void(input_handler&)>& handler) -> void;
|
||||
auto register_mouse_pressed_handler(MouseButton button,
|
||||
const std::function<void(input_handler&)>& handler) -> void;
|
||||
|
||||
auto register_mouse_released_handler(MouseButton button, const std::function<void(input_handler&)>& handler)
|
||||
auto register_mouse_released_handler(MouseButton button,
|
||||
const std::function<void(input_handler&)>& handler)
|
||||
-> void;
|
||||
|
||||
auto register_key_pressed_handler(KeyboardKey key, const std::function<void(input_handler&)>& handler) -> void;
|
||||
auto register_key_pressed_handler(KeyboardKey key,
|
||||
const std::function<void(input_handler&)>& handler) -> void;
|
||||
|
||||
auto register_key_released_handler(KeyboardKey key, const std::function<void(input_handler&)>& handler) -> void;
|
||||
auto register_key_released_handler(KeyboardKey key,
|
||||
const std::function<void(input_handler&)>& handler) -> void;
|
||||
|
||||
auto handle_input() -> void;
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@ -41,7 +41,8 @@ public:
|
||||
|
||||
[[nodiscard]] auto get_octant(int node_idx, const Vector3& pos) const -> int;
|
||||
|
||||
[[nodiscard]] auto get_child_bounds(int node_idx, int octant) const -> std::pair<Vector3, Vector3>;
|
||||
[[nodiscard]] auto get_child_bounds(int node_idx, int octant) const
|
||||
-> std::pair<Vector3, Vector3>;
|
||||
|
||||
auto insert(int node_idx, int mass_id, const Vector3& pos, float mass, int depth) -> void;
|
||||
|
||||
|
||||
@ -198,14 +198,8 @@ public:
|
||||
|
||||
auto clear_cmd() -> void;
|
||||
|
||||
auto add_mass_springs_cmd(size_t num_masses, const std::vector<std::pair<size_t, size_t>>& springs) -> void;
|
||||
};
|
||||
|
||||
// https://en.cppreference.com/w/cpp/utility/variant/visit
|
||||
template <class... Ts>
|
||||
struct overloads : Ts...
|
||||
{
|
||||
using Ts::operator()...;
|
||||
auto add_mass_springs_cmd(size_t num_masses,
|
||||
const std::vector<std::pair<size_t, size_t>>& springs) -> void;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@ -48,8 +48,8 @@ public:
|
||||
bool immovable = false;
|
||||
|
||||
public:
|
||||
block(const int _x, const int _y, const int _width, const int _height, const bool _target = false,
|
||||
const bool _immovable = false)
|
||||
block(const int _x, const int _y, const int _width, const int _height,
|
||||
const bool _target = false, const bool _immovable = false)
|
||||
: x(_x), y(_y), width(_width), height(_height), target(_target), immovable(_immovable)
|
||||
{
|
||||
if (_x < 0 || _x + _width > 9 || _y < 0 || _y + _height > 9) {
|
||||
@ -84,7 +84,8 @@ public:
|
||||
}
|
||||
|
||||
immovable = false;
|
||||
constexpr std::array<char, 9> immovable_chars{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'};
|
||||
constexpr std::array<char, 9> immovable_chars{'A', 'B', 'C', 'D', 'E',
|
||||
'F', 'G', 'H', 'I'};
|
||||
for (const char c : immovable_chars) {
|
||||
if (b.contains(c)) {
|
||||
immovable = true;
|
||||
@ -151,7 +152,8 @@ private:
|
||||
explicit block_iterator(const puzzle& _state) : state(_state), current_pos(0)
|
||||
{}
|
||||
|
||||
block_iterator(const puzzle& _state, const int _current_pos) : state(_state), current_pos(_current_pos)
|
||||
block_iterator(const puzzle& _state, const int _current_pos)
|
||||
: state(_state), current_pos(_current_pos)
|
||||
{}
|
||||
|
||||
auto operator*() const -> block
|
||||
@ -199,7 +201,8 @@ public:
|
||||
|
||||
puzzle(const int w, const int h, const int tx, const int ty, const bool r)
|
||||
: width(w), height(h), target_x(tx), target_y(ty), restricted(r),
|
||||
state(std::format("{}{}{}{}{}{}", r ? "R" : "F", w, h, tx, ty, std::string(w * h * 2, '.')))
|
||||
state(
|
||||
std::format("{}{}{}{}{}{}", r ? "R" : "F", w, h, tx, ty, std::string(w * h * 2, '.')))
|
||||
{
|
||||
if (w < 1 || w > 9 || h < 1 || h > 9) {
|
||||
errln("State width/height must be in [1, 9]!");
|
||||
@ -217,8 +220,9 @@ public:
|
||||
{}
|
||||
|
||||
explicit puzzle(const std::string& s)
|
||||
: width(std::stoi(s.substr(1, 1))), height(std::stoi(s.substr(2, 1))), target_x(std::stoi(s.substr(3, 1))),
|
||||
target_y(std::stoi(s.substr(4, 1))), restricted(s.substr(0, 1) == "R"), state(s)
|
||||
: width(std::stoi(s.substr(1, 1))), height(std::stoi(s.substr(2, 1))),
|
||||
target_x(std::stoi(s.substr(3, 1))), target_y(std::stoi(s.substr(4, 1))),
|
||||
restricted(s.substr(0, 1) == "R"), state(s)
|
||||
{
|
||||
if (width < 1 || width > 9 || height < 1 || height > 9) {
|
||||
errln("State width/height must be in [1, 9]!");
|
||||
@ -271,7 +275,7 @@ public:
|
||||
[[nodiscard]] auto has_win_condition() const -> bool;
|
||||
[[nodiscard]] auto won() const -> bool;
|
||||
[[nodiscard]] auto valid() const -> bool;
|
||||
[[nodiscard]] auto valid_thorough() const -> bool;
|
||||
[[nodiscard]] auto try_get_invalid_reason() const -> std::optional<std::string>;
|
||||
|
||||
// Repr helpers
|
||||
|
||||
@ -297,7 +301,8 @@ public:
|
||||
|
||||
// Playing
|
||||
|
||||
[[nodiscard]] auto try_move_block_at(int x, int y, direction dir) const -> std::optional<puzzle>;
|
||||
[[nodiscard]] auto try_move_block_at(int x, int y, direction dir) const
|
||||
-> std::optional<puzzle>;
|
||||
|
||||
// Statespace
|
||||
|
||||
@ -333,9 +338,11 @@ struct std::hash<std::pair<puzzle, puzzle>>
|
||||
template <>
|
||||
struct std::equal_to<std::pair<puzzle, puzzle>>
|
||||
{
|
||||
auto operator()(const std::pair<puzzle, puzzle>& a, const std::pair<puzzle, puzzle>& b) const noexcept -> bool
|
||||
auto operator()(const std::pair<puzzle, puzzle>& a,
|
||||
const std::pair<puzzle, puzzle>& b) const noexcept -> bool
|
||||
{
|
||||
return (a.first == b.first && a.second == b.second) || (a.first == b.second && a.second == b.first);
|
||||
return (a.first == b.first && a.second == b.second) ||
|
||||
(a.first == b.second && a.second == b.first);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -18,8 +18,12 @@ private:
|
||||
user_interface& gui;
|
||||
|
||||
const orbit_camera& camera;
|
||||
RenderTexture render_target = LoadRenderTexture(GetScreenWidth() / 2, GetScreenHeight() - MENU_HEIGHT);
|
||||
RenderTexture klotski_target = LoadRenderTexture(GetScreenWidth() / 2, GetScreenHeight() - MENU_HEIGHT);
|
||||
RenderTexture render_target =
|
||||
LoadRenderTexture(GetScreenWidth() / 2, GetScreenHeight() - MENU_HEIGHT);
|
||||
|
||||
// TODO: Those should be moved to the user_interface.h
|
||||
RenderTexture klotski_target =
|
||||
LoadRenderTexture(GetScreenWidth() / 2, GetScreenHeight() - MENU_HEIGHT);
|
||||
RenderTexture menu_target = LoadRenderTexture(GetScreenWidth(), MENU_HEIGHT);
|
||||
|
||||
// Batching
|
||||
@ -31,7 +35,8 @@ private:
|
||||
std::vector<Color> colors;
|
||||
Material vertex_mat = LoadMaterialDefault();
|
||||
Mesh cube_instance = GenMeshCube(VERTEX_SIZE, VERTEX_SIZE, VERTEX_SIZE);
|
||||
Shader instancing_shader = LoadShader("shader/instancing_vertex.glsl", "shader/instancing_fragment.glsl");
|
||||
Shader instancing_shader =
|
||||
LoadShader("shader/instancing_vertex.glsl", "shader/instancing_fragment.glsl");
|
||||
|
||||
unsigned int color_vbo_id = 0;
|
||||
|
||||
@ -43,10 +48,13 @@ public:
|
||||
instancing_shader.locs[SHADER_LOC_MATRIX_MVP] = GetShaderLocation(instancing_shader, "mvp");
|
||||
instancing_shader.locs[SHADER_LOC_MATRIX_MODEL] =
|
||||
GetShaderLocationAttrib(instancing_shader, "instanceTransform");
|
||||
instancing_shader.locs[SHADER_LOC_VECTOR_VIEW] = GetShaderLocation(instancing_shader, "viewPos");
|
||||
instancing_shader.locs[SHADER_LOC_VECTOR_VIEW] =
|
||||
GetShaderLocation(instancing_shader, "viewPos");
|
||||
|
||||
infoln("LOC vertexPosition: {}", rlGetLocationAttrib(instancing_shader.id, "vertexPosition"));
|
||||
infoln("LOC instanceTransform: {}", rlGetLocationAttrib(instancing_shader.id, "instanceTransform"));
|
||||
infoln("LOC vertexPosition: {}",
|
||||
rlGetLocationAttrib(instancing_shader.id, "vertexPosition"));
|
||||
infoln("LOC instanceTransform: {}",
|
||||
rlGetLocationAttrib(instancing_shader.id, "instanceTransform"));
|
||||
infoln("LOC instanceColor: {}", rlGetLocationAttrib(instancing_shader.id, "instanceColor"));
|
||||
|
||||
// vertex_mat.maps[MATERIAL_MAP_DIFFUSE].color = VERTEX_COLOR;
|
||||
@ -94,7 +102,8 @@ private:
|
||||
auto draw_textures(int fps, int ups, size_t mass_count, size_t spring_count) const -> void;
|
||||
|
||||
public:
|
||||
auto render(const std::vector<Vector3>& masses, int fps, int ups, size_t mass_count, size_t spring_count) -> void;
|
||||
auto render(const std::vector<Vector3>& masses, int fps, int ups, size_t mass_count,
|
||||
size_t spring_count) -> void;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@ -22,16 +22,19 @@ private:
|
||||
// State storage (store states twice for bidirectional lookup).
|
||||
// Everything else should only store indices to state_pool.
|
||||
|
||||
std::vector<puzzle> state_pool; // Indices are equal to mass_springs mass indices
|
||||
std::vector<puzzle> state_pool; // Indices are equal to mass_springs mass indices
|
||||
std::unordered_map<puzzle, size_t> state_indices; // Maps states to indices
|
||||
std::vector<std::pair<size_t, size_t>> links; // Indices are equal to mass_springs springs indices
|
||||
std::vector<std::pair<size_t, size_t>>
|
||||
links; // Indices are equal to mass_springs springs indices
|
||||
|
||||
graph_distances node_target_distances; // Buffered and reused if the graph doesn't change
|
||||
std::unordered_set<size_t> winning_indices; // Indices of all states where the board is solved
|
||||
std::vector<size_t> winning_path; // Ordered list of node indices leading to the nearest solved state
|
||||
std::unordered_set<size_t> path_indices; // For faster lookup if a vertex is part of the path in renderer
|
||||
std::vector<size_t>
|
||||
winning_path; // Ordered list of node indices leading to the nearest solved state
|
||||
std::unordered_set<size_t>
|
||||
path_indices; // For faster lookup if a vertex is part of the path in renderer
|
||||
|
||||
std::stack<size_t> move_history; // Moves between the starting state and the current state
|
||||
std::stack<size_t> move_history; // Moves between the starting state and the current state
|
||||
std::unordered_map<size_t, int> visit_counts; // How often each state was visited
|
||||
|
||||
size_t starting_state_index = 0;
|
||||
|
||||
@ -22,13 +22,15 @@ class user_interface
|
||||
const int padding;
|
||||
|
||||
public:
|
||||
grid(const int _x, const int _y, const int _width, const int _height, const int _columns, const int _rows,
|
||||
const int _padding)
|
||||
: x(_x), y(_y), width(_width), height(_height), columns(_columns), rows(_rows), padding(_padding)
|
||||
grid(const int _x, const int _y, const int _width, const int _height, const int _columns,
|
||||
const int _rows, const int _padding)
|
||||
: x(_x), y(_y), width(_width), height(_height), columns(_columns), rows(_rows),
|
||||
padding(_padding)
|
||||
{}
|
||||
|
||||
public:
|
||||
auto update_bounds(int _x, int _y, int _width, int _height, int _columns, int _rows) -> void;
|
||||
auto update_bounds(int _x, int _y, int _width, int _height, int _columns, int _rows)
|
||||
-> void;
|
||||
auto update_bounds(int _x, int _y, int _width, int _height) -> void;
|
||||
auto update_bounds(int _x, int _y) -> void;
|
||||
|
||||
@ -36,7 +38,8 @@ class user_interface
|
||||
[[nodiscard]] auto bounds(int _x, int _y, int _width, int _height) const -> Rectangle;
|
||||
|
||||
[[nodiscard]] auto square_bounds() const -> Rectangle;
|
||||
[[nodiscard]] auto square_bounds(int _x, int _y, int _width, int _height) const -> Rectangle;
|
||||
[[nodiscard]] auto square_bounds(int _x, int _y, int _width, int _height) const
|
||||
-> Rectangle;
|
||||
};
|
||||
|
||||
struct style
|
||||
@ -84,13 +87,22 @@ private:
|
||||
|
||||
grid menu_grid = grid(0, 0, GetScreenWidth(), MENU_HEIGHT, MENU_COLS, MENU_ROWS, MENU_PAD);
|
||||
|
||||
grid board_grid = grid(0, MENU_HEIGHT, GetScreenWidth() / 2, GetScreenHeight() - MENU_HEIGHT,
|
||||
state.get_current_state().width, state.get_current_state().height, BOARD_PADDING);
|
||||
grid board_grid =
|
||||
grid(0, MENU_HEIGHT, GetScreenWidth() / 2, GetScreenHeight() - MENU_HEIGHT,
|
||||
state.get_current_state().width, state.get_current_state().height, BOARD_PADDING);
|
||||
|
||||
grid graph_overlay_grid = grid(GetScreenWidth() / 2, MENU_HEIGHT, 200, 100, 1, 4, MENU_PAD);
|
||||
|
||||
grid debug_overlay_grid = grid(GetScreenWidth() / 2, GetScreenHeight() - 75, 200, 75, 1, 3, MENU_PAD);
|
||||
grid debug_overlay_grid =
|
||||
grid(GetScreenWidth() / 2, GetScreenHeight() - 75, 200, 75, 1, 3, MENU_PAD);
|
||||
|
||||
// Windows
|
||||
|
||||
std::string message_title;
|
||||
std::string message_message;
|
||||
std::function<void(void)> yes_no_handler;
|
||||
bool ok_message = false;
|
||||
bool yes_no_message = false;
|
||||
bool save_window = false;
|
||||
std::array<char, 256> preset_name = {};
|
||||
bool help_window = false;
|
||||
@ -119,29 +131,34 @@ private:
|
||||
static auto get_component_style(int component) -> component_style;
|
||||
static auto set_component_style(int component, const component_style& style) -> void;
|
||||
|
||||
[[nodiscard]] static auto popup_bounds() -> Rectangle;
|
||||
|
||||
auto draw_button(Rectangle bounds, const std::string& label, Color color, bool enabled = true,
|
||||
int font_size = FONT_SIZE) const -> int;
|
||||
|
||||
auto draw_menu_button(int x, int y, int width, int height, const std::string& label, Color color,
|
||||
bool enabled = true, int font_size = FONT_SIZE) const -> int;
|
||||
auto draw_menu_button(int x, int y, int width, int height, const std::string& label,
|
||||
Color color, bool enabled = true, int font_size = FONT_SIZE) const -> int;
|
||||
|
||||
auto draw_toggle_slider(Rectangle bounds, const std::string& off_label, const std::string& on_label, int* active,
|
||||
Color color, bool enabled = true, int font_size = FONT_SIZE) const -> int;
|
||||
auto draw_toggle_slider(Rectangle bounds, const std::string& off_label,
|
||||
const std::string& on_label, int* active, Color color,
|
||||
bool enabled = true, int font_size = FONT_SIZE) const -> int;
|
||||
|
||||
auto draw_menu_toggle_slider(int x, int y, int width, int height, const std::string& off_label,
|
||||
const std::string& on_label, int* active, Color color, bool enabled = true,
|
||||
int font_size = FONT_SIZE) const -> int;
|
||||
const std::string& on_label, int* active, Color color,
|
||||
bool enabled = true, int font_size = FONT_SIZE) const -> int;
|
||||
|
||||
auto draw_spinner(Rectangle bounds, const std::string& label, int* value, int min, int max, Color color,
|
||||
bool enabled = true, int font_size = FONT_SIZE) const -> int;
|
||||
auto draw_spinner(Rectangle bounds, const std::string& label, int* value, int min, int max,
|
||||
Color color, bool enabled = true, int font_size = FONT_SIZE) const -> int;
|
||||
|
||||
auto draw_menu_spinner(int x, int y, int width, int height, const std::string& label, int* value, int min, int max,
|
||||
Color color, bool enabled = true, int font_size = FONT_SIZE) const -> int;
|
||||
auto draw_menu_spinner(int x, int y, int width, int height, const std::string& label,
|
||||
int* value, int min, int max, Color color, bool enabled = true,
|
||||
int font_size = FONT_SIZE) const -> int;
|
||||
|
||||
auto draw_label(Rectangle bounds, const std::string& text, Color color, bool enabled = true,
|
||||
int font_size = FONT_SIZE) const -> int;
|
||||
|
||||
auto draw_board_block(int x, int y, int width, int height, Color color, bool enabled = true) const -> bool;
|
||||
auto draw_board_block(int x, int y, int width, int height, Color color,
|
||||
bool enabled = true) const -> bool;
|
||||
|
||||
[[nodiscard]] auto window_open() const -> bool;
|
||||
|
||||
@ -158,10 +175,12 @@ public:
|
||||
static auto get_background_color() -> Color;
|
||||
auto help_popup() -> void;
|
||||
auto draw_save_preset_popup() -> void;
|
||||
auto draw_ok_message_box() -> void;
|
||||
auto draw_yes_no_message_box() -> void;
|
||||
auto draw_main_menu() -> void;
|
||||
auto draw_puzzle_board() -> void;
|
||||
auto draw_graph_overlay(int fps, int ups, size_t mass_count, size_t spring_count) -> void;
|
||||
auto update() const -> void;
|
||||
auto draw(int fps, int ups, size_t mass_count, size_t spring_count) -> void;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@ -17,6 +17,13 @@ inline auto operator<<(std::ostream& os, const Vector3& v) -> std::ostream&
|
||||
return os;
|
||||
}
|
||||
|
||||
// https://en.cppreference.com/w/cpp/utility/variant/visit
|
||||
template <class... Ts>
|
||||
struct overloads : Ts...
|
||||
{
|
||||
using Ts::operator()...;
|
||||
};
|
||||
|
||||
enum ctrl
|
||||
{
|
||||
reset = 0,
|
||||
|
||||
@ -35,13 +35,14 @@ auto orbit_camera::pan(const Vector2 last_mouse, const Vector2 mouse) -> void
|
||||
const Vector3 right = Vector3Normalize(Vector3CrossProduct(forward, camera.up));
|
||||
const Vector3 up = Vector3Normalize(Vector3CrossProduct(right, forward));
|
||||
|
||||
const Vector3 offset = Vector3Add(Vector3Scale(right, -dx * speed), Vector3Scale(up, dy * speed));
|
||||
const Vector3 offset =
|
||||
Vector3Add(Vector3Scale(right, -dx * speed), Vector3Scale(up, dy * speed));
|
||||
|
||||
target = Vector3Add(target, offset);
|
||||
}
|
||||
|
||||
auto orbit_camera::update(const Vector3& current_target, const Vector3& mass_center, const bool lock,
|
||||
const bool mass_center_lock) -> void
|
||||
auto orbit_camera::update(const Vector3& current_target, const Vector3& mass_center,
|
||||
const bool lock, const bool mass_center_lock) -> void
|
||||
{
|
||||
if (lock) {
|
||||
if (mass_center_lock) {
|
||||
|
||||
@ -18,7 +18,8 @@ auto graph_distances::empty() const -> bool
|
||||
return distances.empty() || parents.empty() || nearest_targets.empty();
|
||||
}
|
||||
|
||||
auto graph_distances::calculate_distances(const size_t node_count, const std::vector<std::pair<size_t, size_t>>& edges,
|
||||
auto graph_distances::calculate_distances(const size_t node_count,
|
||||
const std::vector<std::pair<size_t, size_t>>& edges,
|
||||
const std::vector<size_t>& targets) -> void
|
||||
{
|
||||
// Build a list of adjacent nodes to speed up BFS
|
||||
|
||||
@ -53,6 +53,7 @@ auto input_handler::init_handlers() -> void
|
||||
register_key_pressed_handler(KEY_LEFT, &input_handler::remove_board_column);
|
||||
register_key_pressed_handler(KEY_X, &input_handler::clear_goal);
|
||||
register_key_pressed_handler(KEY_P, &input_handler::print_state);
|
||||
register_key_pressed_handler(KEY_S, &input_handler::save_preset); // + CTRL
|
||||
|
||||
register_key_pressed_handler(KEY_L, &input_handler::toggle_camera_lock);
|
||||
register_key_pressed_handler(KEY_LEFT_ALT, &input_handler::toggle_camera_projection);
|
||||
@ -87,8 +88,8 @@ auto input_handler::camera_start_pan() -> void
|
||||
}
|
||||
|
||||
camera_panning = true;
|
||||
// Enable this if the camera should be pannable even when locked (releasing the lock in the process):
|
||||
// camera_lock = false;
|
||||
// Enable this if the camera should be pannable even when locked (releasing the lock in the
|
||||
// process): camera_lock = false;
|
||||
}
|
||||
|
||||
auto input_handler::camera_pan() const -> void
|
||||
@ -197,8 +198,8 @@ auto input_handler::add_block() -> void
|
||||
block_add_y = -1;
|
||||
has_block_add_xy = false;
|
||||
} else if (current.covers(block_add_x, block_add_y, block_add_width, block_add_height)) {
|
||||
const std::optional<puzzle>& next =
|
||||
current.try_add_block(puzzle::block(block_add_x, block_add_y, block_add_width, block_add_height, false));
|
||||
const std::optional<puzzle>& next = current.try_add_block(
|
||||
puzzle::block(block_add_x, block_add_y, block_add_width, block_add_height, false));
|
||||
|
||||
if (next) {
|
||||
sel_x = block_add_x;
|
||||
@ -331,10 +332,19 @@ auto input_handler::load_previous_preset() -> void
|
||||
return;
|
||||
}
|
||||
|
||||
block_add_x = -1;
|
||||
block_add_y = -1;
|
||||
has_block_add_xy = false;
|
||||
state.load_previous_preset();
|
||||
const auto handler = [&]()
|
||||
{
|
||||
block_add_x = -1;
|
||||
block_add_y = -1;
|
||||
has_block_add_xy = false;
|
||||
state.load_previous_preset();
|
||||
};
|
||||
|
||||
if (state.was_edited()) {
|
||||
ui_commands.emplace(show_yes_no_message{"Switch Preset?", "Edits Will Be Lost.", handler});
|
||||
} else {
|
||||
handler();
|
||||
}
|
||||
}
|
||||
|
||||
auto input_handler::load_next_preset() -> void
|
||||
@ -343,17 +353,31 @@ auto input_handler::load_next_preset() -> void
|
||||
return;
|
||||
}
|
||||
|
||||
block_add_x = -1;
|
||||
block_add_y = -1;
|
||||
has_block_add_xy = false;
|
||||
state.load_next_preset();
|
||||
const auto handler = [&]()
|
||||
{
|
||||
block_add_x = -1;
|
||||
block_add_y = -1;
|
||||
has_block_add_xy = false;
|
||||
state.load_next_preset();
|
||||
};
|
||||
|
||||
if (state.was_edited()) {
|
||||
ui_commands.emplace(show_yes_no_message{"Switch Preset?", "Edits Will Be Lost.", handler});
|
||||
} else {
|
||||
handler();
|
||||
}
|
||||
}
|
||||
|
||||
auto input_handler::goto_starting_state() -> void
|
||||
{
|
||||
state.goto_starting_state();
|
||||
sel_x = 0;
|
||||
sel_y = 0;
|
||||
const auto handler = [&]()
|
||||
{
|
||||
state.goto_starting_state();
|
||||
sel_x = 0;
|
||||
sel_y = 0;
|
||||
};
|
||||
|
||||
ui_commands.emplace(show_yes_no_message{"Reset Board?", "This Clears the Move History.", handler});
|
||||
}
|
||||
|
||||
auto input_handler::populate_graph() const -> void
|
||||
@ -361,9 +385,14 @@ auto input_handler::populate_graph() const -> void
|
||||
state.populate_graph();
|
||||
}
|
||||
|
||||
auto input_handler::clear_graph() const -> void
|
||||
auto input_handler::clear_graph() -> void
|
||||
{
|
||||
state.clear_graph_and_add_current();
|
||||
const auto handler = [&]()
|
||||
{
|
||||
state.clear_graph_and_add_current();
|
||||
};
|
||||
|
||||
ui_commands.emplace(show_yes_no_message{"Clear Graph?", "This Clears the Move History.", handler});
|
||||
}
|
||||
|
||||
auto input_handler::toggle_mark_solutions() -> void
|
||||
@ -508,6 +537,19 @@ auto input_handler::clear_goal() const -> void
|
||||
state.edit_starting_state(*next);
|
||||
}
|
||||
|
||||
auto input_handler::save_preset() -> void
|
||||
{
|
||||
if (!IsKeyDown(KEY_LEFT_CONTROL)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (const std::optional<std::string>& reason = state.get_current_state().try_get_invalid_reason()) {
|
||||
ui_commands.emplace(show_ok_message{"Can't Save Preset", std::format("Invalid Board: {}.", *reason)});
|
||||
} else {
|
||||
ui_commands.emplace(show_save_preset_window{});
|
||||
}
|
||||
}
|
||||
|
||||
auto input_handler::register_generic_handler(const std::function<void(input_handler&)>& handler) -> void
|
||||
{
|
||||
generic_handlers.push_back({handler});
|
||||
@ -570,4 +612,4 @@ auto input_handler::handle_input() -> void
|
||||
handler.handler(*this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
17
src/main.cpp
17
src/main.cpp
@ -13,17 +13,10 @@
|
||||
#include <tracy/Tracy.hpp>
|
||||
#endif
|
||||
|
||||
// TODO: Click states in the graph to display them in the board
|
||||
// TODO: Move selection accordingly when undoing moves (need to diff two states
|
||||
// and get the moved blocks)
|
||||
|
||||
// TODO: Add some popups (my split between input.cpp/gui.cpp makes this ugly)
|
||||
// - Next move, goto target, goto worst: Notify that the graph needs to be
|
||||
// populated
|
||||
// - Clear graph: Notify that this will clear the visited states and move
|
||||
// history
|
||||
// - Reset state: Notify that this will reset the move count
|
||||
// - Next/Previous preset: Notify that this will clear all edits
|
||||
|
||||
// TODO: Reduce memory usage
|
||||
// - The memory model of the puzzle board is terrible (bitboards?)
|
||||
@ -38,6 +31,11 @@
|
||||
// given the initial state
|
||||
// - Would allow to generate random puzzles with a certain move count
|
||||
|
||||
// TODO: Move selection accordingly when undoing moves (need to diff two states
|
||||
// and get the moved blocks)
|
||||
|
||||
// TODO: Click states in the graph to display them in the board
|
||||
|
||||
// NOTE: Tracy uses a huge amount of memory. For longer testing disable Tracy.
|
||||
|
||||
auto main(int argc, char* argv[]) -> int
|
||||
@ -139,7 +137,8 @@ auto main(int argc, char* argv[]) -> int
|
||||
size_t current_index = state.get_current_index();
|
||||
if (masses.size() > current_index) {
|
||||
const mass& current_mass = mass(masses.at(current_index));
|
||||
camera.update(current_mass.position, mass_center, input.camera_lock, input.camera_mass_center_lock);
|
||||
camera.update(current_mass.position, mass_center, input.camera_lock,
|
||||
input.camera_mass_center_lock);
|
||||
}
|
||||
|
||||
// Rendering
|
||||
@ -162,4 +161,4 @@ auto main(int argc, char* argv[]) -> int
|
||||
CloseWindow();
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@ -32,8 +32,9 @@ auto octree::create_empty_leaf(const Vector3& box_min, const Vector3& box_max) -
|
||||
auto octree::get_octant(const int node_idx, const Vector3& pos) const -> int
|
||||
{
|
||||
const node& n = nodes[node_idx];
|
||||
auto [cx, cy, cz] = Vector3((n.box_min.x + n.box_max.x) / 2.0f, (n.box_min.y + n.box_max.y) / 2.0f,
|
||||
(n.box_min.z + n.box_max.z) / 2.0f);
|
||||
auto [cx, cy, cz] =
|
||||
Vector3((n.box_min.x + n.box_max.x) / 2.0f, (n.box_min.y + n.box_max.y) / 2.0f,
|
||||
(n.box_min.z + n.box_max.z) / 2.0f);
|
||||
|
||||
// The octant is encoded as a 3-bit integer "zyx". The node area is split
|
||||
// along all 3 axes, if a position is right of an axis, this bit is set to 1.
|
||||
@ -53,11 +54,13 @@ auto octree::get_octant(const int node_idx, const Vector3& pos) const -> int
|
||||
return octant;
|
||||
}
|
||||
|
||||
auto octree::get_child_bounds(const int node_idx, const int octant) const -> std::pair<Vector3, Vector3>
|
||||
auto octree::get_child_bounds(const int node_idx, const int octant) const
|
||||
-> std::pair<Vector3, Vector3>
|
||||
{
|
||||
const node& n = nodes[node_idx];
|
||||
auto [cx, cy, cz] = Vector3((n.box_min.x + n.box_max.x) / 2.0f, (n.box_min.y + n.box_max.y) / 2.0f,
|
||||
(n.box_min.z + n.box_max.z) / 2.0f);
|
||||
auto [cx, cy, cz] =
|
||||
Vector3((n.box_min.x + n.box_max.x) / 2.0f, (n.box_min.y + n.box_max.y) / 2.0f,
|
||||
(n.box_min.z + n.box_max.z) / 2.0f);
|
||||
|
||||
Vector3 min = Vector3Zero();
|
||||
Vector3 max = Vector3Zero();
|
||||
@ -75,21 +78,23 @@ auto octree::get_child_bounds(const int node_idx, const int octant) const -> std
|
||||
return std::make_pair(min, max);
|
||||
}
|
||||
|
||||
auto octree::insert(const int node_idx, const int mass_id, const Vector3& pos, const float mass, const int depth)
|
||||
-> void
|
||||
auto octree::insert(const int node_idx, const int mass_id, const Vector3& pos, const float mass,
|
||||
const int depth) -> void
|
||||
{
|
||||
// infoln("Inserting position ({}, {}, {}) into octree at node {} (depth {})", pos.x, pos.y, pos.z, node_idx,
|
||||
// depth);
|
||||
// infoln("Inserting position ({}, {}, {}) into octree at node {} (depth {})", pos.x, pos.y,
|
||||
// pos.z, node_idx, depth);
|
||||
if (depth > MAX_DEPTH) {
|
||||
errln("MAX_DEPTH! node={} box_min=({},{},{}) box_max=({},{},{}) pos=({},{},{})", node_idx,
|
||||
nodes[node_idx].box_min.x, nodes[node_idx].box_min.y, nodes[node_idx].box_min.z,
|
||||
nodes[node_idx].box_max.x, nodes[node_idx].box_max.y, nodes[node_idx].box_max.z, pos.x, pos.y, pos.z);
|
||||
nodes[node_idx].box_max.x, nodes[node_idx].box_max.y, nodes[node_idx].box_max.z,
|
||||
pos.x, pos.y, pos.z);
|
||||
|
||||
// This runs from inside the physics thread so it won't exit cleanly
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// NOTE: Do not store a nodes[node_idx] reference as the nodes vector might reallocate during this function
|
||||
// NOTE: Do not store a nodes[node_idx] reference as the nodes vector might reallocate during
|
||||
// this function
|
||||
|
||||
// We can place the particle in the empty leaf
|
||||
if (nodes[node_idx].leaf && nodes[node_idx].mass_id == -1) {
|
||||
@ -116,9 +121,8 @@ auto octree::insert(const int node_idx, const int mass_id, const Vector3& pos, c
|
||||
insert(node_idx, mass_id, jittered, mass, depth);
|
||||
return;
|
||||
|
||||
// Could also merge them, but that leads to the octree having less leafs than we have masses
|
||||
// nodes[node_idx].mass_total += mass;
|
||||
// return;
|
||||
// Could also merge them, but that leads to the octree having less leafs than we have
|
||||
// masses nodes[node_idx].mass_total += mass; return;
|
||||
}
|
||||
|
||||
// Convert the leaf to an internal node
|
||||
@ -148,9 +152,12 @@ auto octree::insert(const int node_idx, const int mass_id, const Vector3& pos, c
|
||||
|
||||
// Update the center of mass
|
||||
const float new_mass = nodes[node_idx].mass_total + mass;
|
||||
nodes[node_idx].mass_center.x = (nodes[node_idx].mass_center.x * nodes[node_idx].mass_total + pos.x) / new_mass;
|
||||
nodes[node_idx].mass_center.y = (nodes[node_idx].mass_center.y * nodes[node_idx].mass_total + pos.y) / new_mass;
|
||||
nodes[node_idx].mass_center.z = (nodes[node_idx].mass_center.z * nodes[node_idx].mass_total + pos.z) / new_mass;
|
||||
nodes[node_idx].mass_center.x =
|
||||
(nodes[node_idx].mass_center.x * nodes[node_idx].mass_total + pos.x) / new_mass;
|
||||
nodes[node_idx].mass_center.y =
|
||||
(nodes[node_idx].mass_center.y * nodes[node_idx].mass_total + pos.y) / new_mass;
|
||||
nodes[node_idx].mass_center.z =
|
||||
(nodes[node_idx].mass_center.z * nodes[node_idx].mass_total + pos.z) / new_mass;
|
||||
nodes[node_idx].mass_total = new_mass;
|
||||
}
|
||||
|
||||
|
||||
@ -57,7 +57,8 @@ auto spring::calculate_spring_force(mass& _a, mass& _b) -> void
|
||||
const Vector3 delta_velocity = Vector3Subtract(_a.velocity, _b.velocity);
|
||||
|
||||
const float hooke = SPRING_CONSTANT * (current_length - REST_LENGTH);
|
||||
const float dampening = DAMPENING_CONSTANT * Vector3DotProduct(delta_velocity, delta_position) * inv_current_length;
|
||||
const float dampening =
|
||||
DAMPENING_CONSTANT * Vector3DotProduct(delta_velocity, delta_position) * inv_current_length;
|
||||
|
||||
const Vector3 force_a = Vector3Scale(delta_position, -(hooke + dampening) * inv_current_length);
|
||||
const Vector3 force_b = Vector3Scale(force_a, -1.0);
|
||||
@ -72,8 +73,8 @@ auto mass_spring_system::add_mass() -> void
|
||||
|
||||
// Done when adding springs
|
||||
// Vector3 position{
|
||||
// static_cast<float>(GetRandomValue(-100, 100)), static_cast<float>(GetRandomValue(-100, 100)),
|
||||
// static_cast<float>(GetRandomValue(-100, 100))
|
||||
// static_cast<float>(GetRandomValue(-100, 100)), static_cast<float>(GetRandomValue(-100,
|
||||
// 100)), static_cast<float>(GetRandomValue(-100, 100))
|
||||
// };
|
||||
// position = Vector3Scale(Vector3Normalize(position), REST_LENGTH * 2.0);
|
||||
|
||||
@ -86,13 +87,15 @@ auto mass_spring_system::add_spring(size_t a, size_t b) -> void
|
||||
const mass& mass_a = masses.at(a);
|
||||
mass& mass_b = masses.at(b);
|
||||
|
||||
Vector3 offset{static_cast<float>(GetRandomValue(-100, 100)), static_cast<float>(GetRandomValue(-100, 100)),
|
||||
Vector3 offset{static_cast<float>(GetRandomValue(-100, 100)),
|
||||
static_cast<float>(GetRandomValue(-100, 100)),
|
||||
static_cast<float>(GetRandomValue(-100, 100))};
|
||||
offset = Vector3Normalize(offset) * REST_LENGTH;
|
||||
|
||||
// If the offset moves the mass closer to the current center of mass, flip it
|
||||
if (!tree.nodes.empty()) {
|
||||
const Vector3 mass_center_direction = Vector3Subtract(mass_a.position, tree.nodes.at(0).mass_center);
|
||||
const Vector3 mass_center_direction =
|
||||
Vector3Subtract(mass_a.position, tree.nodes.at(0).mass_center);
|
||||
const float mass_center_distance = Vector3Length(mass_center_direction);
|
||||
|
||||
if (mass_center_distance > 0 && Vector3DotProduct(offset, mass_center_direction) < 0.0f) {
|
||||
@ -103,7 +106,8 @@ auto mass_spring_system::add_spring(size_t a, size_t b) -> void
|
||||
mass_b.position = mass_a.position + offset;
|
||||
mass_b.previous_position = mass_b.position;
|
||||
|
||||
// infoln("Adding spring: ({}, {}, {})->({}, {}, {})", mass_a.position.x, mass_a.position.y, mass_a.position.z,
|
||||
// infoln("Adding spring: ({}, {}, {})->({}, {}, {})", mass_a.position.x, mass_a.position.y,
|
||||
// mass_a.position.z,
|
||||
// mass_b.position.x, mass_b.position.y, mass_b.position.z);
|
||||
|
||||
springs.emplace_back(a, b);
|
||||
@ -201,7 +205,8 @@ auto mass_spring_system::calculate_repulsion_forces() -> void
|
||||
|
||||
// Calculate forces using Barnes-Hut
|
||||
#ifdef THREADPOOL
|
||||
const BS::multi_future<void> loop_future = threads.submit_loop(0, masses.size(), solve_octree, 256);
|
||||
const BS::multi_future<void> loop_future =
|
||||
threads.submit_loop(0, masses.size(), solve_octree, 256);
|
||||
loop_future.wait();
|
||||
#else
|
||||
for (size_t i = 0; i < masses.size(); ++i) {
|
||||
@ -291,9 +296,9 @@ auto threaded_physics::physics_thread(physics_state& state) -> void
|
||||
mass_springs.calculate_repulsion_forces();
|
||||
mass_springs.verlet_update(TIMESTEP * SIM_SPEED);
|
||||
|
||||
// This is only helpful if we're drawing a grid at (0, 0, 0). Otherwise, it's just expensive
|
||||
// and yields no benefit since we can lock the camera to the center of mass cheaply.
|
||||
// mass_springs.center_masses();
|
||||
// This is only helpful if we're drawing a grid at (0, 0, 0). Otherwise, it's just
|
||||
// expensive and yields no benefit since we can lock the camera to the center of mass
|
||||
// cheaply. mass_springs.center_masses();
|
||||
|
||||
++loop_iterations;
|
||||
physics_accumulator -= std::chrono::duration<double>(TIMESTEP);
|
||||
@ -309,7 +314,8 @@ auto threaded_physics::physics_thread(physics_state& state) -> void
|
||||
#else
|
||||
std::unique_lock<std::mutex> lock(state.data_mtx);
|
||||
#endif
|
||||
state.data_consumed_cnd.wait(lock, [&] { return state.data_consumed || !state.running.load(); });
|
||||
state.data_consumed_cnd.wait(lock, [&]
|
||||
{ return state.data_consumed || !state.running.load(); });
|
||||
if (!state.running.load()) {
|
||||
// Running turned false while we were waiting for the condition
|
||||
break;
|
||||
@ -386,7 +392,8 @@ auto threaded_physics::clear_cmd() -> void
|
||||
}
|
||||
|
||||
auto threaded_physics::add_mass_springs_cmd(const size_t num_masses,
|
||||
const std::vector<std::pair<size_t, size_t>>& springs) -> void
|
||||
const std::vector<std::pair<size_t, size_t>>& springs)
|
||||
-> void
|
||||
{
|
||||
{
|
||||
#ifdef TRACY
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
#include "puzzle.hpp"
|
||||
|
||||
#include <optional>
|
||||
#include <unordered_set>
|
||||
|
||||
#ifdef TRACY
|
||||
@ -85,45 +86,64 @@ auto puzzle::valid() const -> bool
|
||||
return width >= MIN_WIDTH && width <= MAX_WIDTH && height >= MIN_HEIGHT && height <= MAX_HEIGHT;
|
||||
}
|
||||
|
||||
auto puzzle::valid_thorough() const -> bool
|
||||
auto puzzle::try_get_invalid_reason() const -> std::optional<std::string>
|
||||
{
|
||||
if (has_win_condition() && !try_get_target_block()) {
|
||||
return false;
|
||||
const std::optional<block>& b = try_get_target_block();
|
||||
if (has_win_condition() && !b) {
|
||||
return "Goal Without Target";
|
||||
}
|
||||
if (!has_win_condition() && b) {
|
||||
return "Target Without Goal";
|
||||
}
|
||||
|
||||
if (has_win_condition() && b && restricted) {
|
||||
const int dirs = b->principal_dirs();
|
||||
if ((dirs & nor && b->x != target_x) || (dirs & eas && b->y != target_y)) {
|
||||
return "Goal Unreachable";
|
||||
}
|
||||
}
|
||||
|
||||
if (target_x > 0 && target_x + b->width < width && target_y > 0 && target_y + b->height < height) {
|
||||
return "Goal Inside";
|
||||
}
|
||||
|
||||
infoln("Validating puzzle {}", state);
|
||||
|
||||
if (static_cast<int>(state.length()) != width * height * 2 + PREFIX) {
|
||||
infoln("Puzzle invalid: Representation length {} doesn't match {}x{} board", state.length(), width, height);
|
||||
return false;
|
||||
infoln("Puzzle invalid: Representation length {} doesn't match {}x{} board", state.length(),
|
||||
width, height);
|
||||
return "Invalid Repr Length";
|
||||
}
|
||||
|
||||
// Check prefix
|
||||
if (!std::string("FR").contains(state[0])) {
|
||||
infoln("Puzzle invalid: Representation[0] {} doesn't match [FR]", state[0]);
|
||||
return false;
|
||||
return "Invalid Restricted Repr";
|
||||
}
|
||||
if (restricted && state[0] != 'R') {
|
||||
infoln("Puzzle invalid: Representation[0] {} doesn't match restricted={}", state[0], restricted);
|
||||
return false;
|
||||
infoln("Puzzle invalid: Representation[0] {} doesn't match restricted={}", state[0],
|
||||
restricted);
|
||||
return "Restricted != Restricted Repr";
|
||||
}
|
||||
if (!std::string("3456789").contains(state[1]) || !std::string("3456789").contains(state[2])) {
|
||||
if (!std::string("0123456789").contains(state[1]) ||
|
||||
!std::string("0123456789").contains(state[2])) {
|
||||
infoln("Puzzle invalid: Representation[1/2] {}/{} doesn't match [3-9]", state[1], state[2]);
|
||||
return false;
|
||||
return "Invalid Dims Repr";
|
||||
}
|
||||
if (std::stoi(state.substr(1, 1)) != width || std::stoi(state.substr(2, 1)) != height) {
|
||||
infoln("Puzzle invalid: Representation[1/2] {}/{} doesn't match {}x{} board", state[1], state[2], width,
|
||||
height);
|
||||
return false;
|
||||
infoln("Puzzle invalid: Representation[1/2] {}/{} doesn't match {}x{} board", state[1],
|
||||
state[2], width, height);
|
||||
return "Dims != Dims Repr";
|
||||
}
|
||||
if (!std::string("012345678").contains(state[3]) || !std::string("012345678").contains(state[4])) {
|
||||
if (!std::string("0123456789").contains(state[3]) ||
|
||||
!std::string("0123456789").contains(state[4])) {
|
||||
infoln("Puzzle invalid: Representation[3/4] {}/{} doesn't match [1-9]", state[3], state[4]);
|
||||
return false;
|
||||
return "Invalid Goal Repr";
|
||||
}
|
||||
if (std::stoi(state.substr(3, 1)) != target_x || std::stoi(state.substr(4, 1)) != target_y) {
|
||||
infoln("Puzzle invalid: Representation[3/4] {}/{} doesn't match target ({}, {})", state[3], state[4], target_x,
|
||||
target_y);
|
||||
return false;
|
||||
infoln("Puzzle invalid: Representation[3/4] {}/{} doesn't match target ({}, {})", state[3],
|
||||
state[4], target_x, target_y);
|
||||
return "Goal != Goal Repr";
|
||||
}
|
||||
|
||||
// Check blocks
|
||||
@ -131,17 +151,19 @@ auto puzzle::valid_thorough() const -> bool
|
||||
for (const char c : state.substr(PREFIX, state.length() - PREFIX)) {
|
||||
if (!allowed_chars.contains(c)) {
|
||||
infoln("Puzzle invalid: Block {} has invalid character", c);
|
||||
return false;
|
||||
return "Invalid Block Repr";
|
||||
}
|
||||
}
|
||||
|
||||
const bool success = width >= MIN_WIDTH && width <= MAX_WIDTH && height >= MIN_HEIGHT && height <= MAX_HEIGHT;
|
||||
const bool success =
|
||||
width >= MIN_WIDTH && width <= MAX_WIDTH && height >= MIN_HEIGHT && height <= MAX_HEIGHT;
|
||||
|
||||
if (!success) {
|
||||
infoln("Puzzle invalid: Board size {}x{} not in range [3-9]", width, height);
|
||||
return "Invalid Dims";
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
auto puzzle::try_get_block(const int x, const int y) const -> std::optional<block>
|
||||
@ -365,7 +387,8 @@ auto puzzle::try_toggle_wall(const int x, const int y) const -> std::optional<pu
|
||||
return p;
|
||||
}
|
||||
|
||||
auto puzzle::try_move_block_at(const int x, const int y, const direction dir) const -> std::optional<puzzle>
|
||||
auto puzzle::try_move_block_at(const int x, const int y, const direction dir) const
|
||||
-> std::optional<puzzle>
|
||||
{
|
||||
const std::optional<block>& b = try_get_block(x, y);
|
||||
if (!b || b->immovable) {
|
||||
@ -468,7 +491,8 @@ auto puzzle::find_adjacent_puzzles() const -> std::vector<puzzle>
|
||||
return puzzles;
|
||||
}
|
||||
|
||||
auto puzzle::explore_state_space() const -> std::pair<std::vector<puzzle>, std::vector<std::pair<size_t, size_t>>>
|
||||
auto puzzle::explore_state_space() const
|
||||
-> std::pair<std::vector<puzzle>, std::vector<std::pair<size_t, size_t>>>
|
||||
{
|
||||
#ifdef TRACY
|
||||
ZoneScoped;
|
||||
@ -506,4 +530,4 @@ auto puzzle::explore_state_space() const -> std::pair<std::vector<puzzle>, std::
|
||||
infoln("State space has size {} with {} transitions.", state_pool.size(), links.size());
|
||||
|
||||
return std::make_pair(state_pool, links);
|
||||
}
|
||||
}
|
||||
@ -73,10 +73,12 @@ auto renderer::draw_mass_springs(const std::vector<Vector3>& masses) -> void
|
||||
|
||||
// Normal vertex
|
||||
Color c = VERTEX_COLOR;
|
||||
if ((input.mark_solutions || input.mark_path) && state.get_winning_indices().contains(mass)) {
|
||||
if ((input.mark_solutions || input.mark_path) &&
|
||||
state.get_winning_indices().contains(mass)) {
|
||||
// Winning vertex
|
||||
c = VERTEX_TARGET_COLOR;
|
||||
} else if ((input.mark_solutions || input.mark_path) && state.get_path_indices().contains(mass)) {
|
||||
} else if ((input.mark_solutions || input.mark_path) &&
|
||||
state.get_path_indices().contains(mass)) {
|
||||
// Path vertex
|
||||
c = VERTEX_PATH_COLOR;
|
||||
} else if (mass == state.get_starting_index()) {
|
||||
@ -151,7 +153,8 @@ auto renderer::draw_mass_springs(const std::vector<Vector3>& masses) -> void
|
||||
const size_t current_index = state.get_current_index();
|
||||
if (masses.size() > current_index) {
|
||||
const Vector3& current_mass = masses.at(current_index);
|
||||
DrawCube(current_mass, VERTEX_SIZE * 2, VERTEX_SIZE * 2, VERTEX_SIZE * 2, VERTEX_CURRENT_COLOR);
|
||||
DrawCube(current_mass, VERTEX_SIZE * 2, VERTEX_SIZE * 2, VERTEX_SIZE * 2,
|
||||
VERTEX_CURRENT_COLOR);
|
||||
}
|
||||
|
||||
EndMode3D();
|
||||
@ -186,36 +189,37 @@ auto renderer::draw_menu() const -> void
|
||||
EndTextureMode();
|
||||
}
|
||||
|
||||
auto renderer::draw_textures(const int fps, const int ups, const size_t mass_count, const size_t spring_count) const
|
||||
-> void
|
||||
auto renderer::draw_textures(const int fps, const int ups, const size_t mass_count,
|
||||
const size_t spring_count) const -> void
|
||||
{
|
||||
BeginDrawing();
|
||||
|
||||
DrawTextureRec(menu_target.texture, Rectangle(0, 0, menu_target.texture.width, -menu_target.texture.height),
|
||||
DrawTextureRec(menu_target.texture,
|
||||
Rectangle(0, 0, menu_target.texture.width, -menu_target.texture.height),
|
||||
Vector2(0, 0), WHITE);
|
||||
DrawTextureRec(klotski_target.texture,
|
||||
Rectangle(0, 0, klotski_target.texture.width, -klotski_target.texture.height),
|
||||
Vector2(0, MENU_HEIGHT), WHITE);
|
||||
DrawTextureRec(render_target.texture, Rectangle(0, 0, render_target.texture.width, -render_target.texture.height),
|
||||
DrawTextureRec(render_target.texture,
|
||||
Rectangle(0, 0, render_target.texture.width, -render_target.texture.height),
|
||||
Vector2(GetScreenWidth() / 2.0f, MENU_HEIGHT), WHITE);
|
||||
|
||||
// Draw borders
|
||||
DrawRectangleLinesEx(Rectangle(0, 0, GetScreenWidth(), MENU_HEIGHT), 1.0f, BLACK);
|
||||
DrawRectangleLinesEx(Rectangle(0, MENU_HEIGHT, GetScreenWidth() / 2.0f, GetScreenHeight() - MENU_HEIGHT), 1.0f,
|
||||
BLACK);
|
||||
DrawRectangleLinesEx(
|
||||
Rectangle(GetScreenWidth() / 2.0f, MENU_HEIGHT, GetScreenWidth() / 2.0f, GetScreenHeight() - MENU_HEIGHT), 1.0f,
|
||||
Rectangle(0, MENU_HEIGHT, GetScreenWidth() / 2.0f, GetScreenHeight() - MENU_HEIGHT), 1.0f,
|
||||
BLACK);
|
||||
DrawRectangleLinesEx(Rectangle(GetScreenWidth() / 2.0f, MENU_HEIGHT, GetScreenWidth() / 2.0f,
|
||||
GetScreenHeight() - MENU_HEIGHT),
|
||||
1.0f, BLACK);
|
||||
|
||||
gui.draw_graph_overlay(fps, ups, mass_count, spring_count);
|
||||
gui.draw_save_preset_popup();
|
||||
gui.update();
|
||||
gui.draw(fps, ups, mass_count, spring_count);
|
||||
|
||||
EndDrawing();
|
||||
}
|
||||
|
||||
auto renderer::render(const std::vector<Vector3>& masses, const int fps, const int ups, const size_t mass_count,
|
||||
const size_t spring_count) -> void
|
||||
auto renderer::render(const std::vector<Vector3>& masses, const int fps, const int ups,
|
||||
const size_t mass_count, const size_t spring_count) -> void
|
||||
{
|
||||
update_texture_sizes();
|
||||
|
||||
|
||||
@ -35,7 +35,8 @@ auto state_manager::synced_insert_link(size_t first_index, size_t second_index)
|
||||
}
|
||||
|
||||
auto state_manager::synced_insert_statespace(const std::vector<puzzle>& states,
|
||||
const std::vector<std::pair<size_t, size_t>>& _links) -> void
|
||||
const std::vector<std::pair<size_t, size_t>>& _links)
|
||||
-> void
|
||||
{
|
||||
if (!state_pool.empty() || !state_indices.empty() || !links.empty()) {
|
||||
warnln("Inserting statespace but collections haven't been cleared");
|
||||
@ -70,7 +71,7 @@ auto state_manager::synced_clear_statespace() -> void
|
||||
winning_path.clear();
|
||||
path_indices.clear();
|
||||
|
||||
// move_history does not get cleared here, but when resetting the board
|
||||
move_history = std::stack<size_t>();
|
||||
visit_counts.clear();
|
||||
|
||||
// Queue an update to the physics engine state to keep in sync
|
||||
@ -107,9 +108,9 @@ auto state_manager::parse_preset_file(const std::string& _preset_file) -> bool
|
||||
for (const auto& preset : preset_lines) {
|
||||
const puzzle& p = puzzle(preset);
|
||||
|
||||
if (!p.valid_thorough()) {
|
||||
if (const std::optional<std::string>& reason = p.try_get_invalid_reason()) {
|
||||
preset_states = {puzzle(4, 5, 9, 9, false)};
|
||||
infoln("Preset file \"{}\" contained invalid presets.", preset_file);
|
||||
infoln("Preset file \"{}\" contained invalid presets: {}", preset_file, *reason);
|
||||
return false;
|
||||
}
|
||||
preset_states.emplace_back(p);
|
||||
@ -125,7 +126,7 @@ auto state_manager::append_preset_file(const std::string& preset_name) -> bool
|
||||
{
|
||||
infoln(R"(Saving preset "{}" to "{}")", preset_name, preset_file);
|
||||
|
||||
if (!get_current_state().valid_thorough()) {
|
||||
if (get_current_state().try_get_invalid_reason()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -176,7 +177,8 @@ auto state_manager::update_current_state(const puzzle& p) -> void
|
||||
// Because synced_insert_link does not check for duplicates we do it here,
|
||||
// if the size grows, it was not a duplicate, and we can add the spring
|
||||
if (state_pool.size() > size_before) {
|
||||
// The order is important, as the position of the second mass will be updated depending on the first
|
||||
// The order is important, as the position of the second mass will be updated depending on
|
||||
// the first
|
||||
synced_insert_link(current_state_index, index);
|
||||
}
|
||||
|
||||
@ -312,12 +314,12 @@ auto state_manager::populate_graph() -> void
|
||||
auto state_manager::clear_graph_and_add_current(const puzzle& p) -> void
|
||||
{
|
||||
// Do we need to make a copy before clearing the state_pool?
|
||||
// const puzzle _p = p;
|
||||
const puzzle _p = p; // NOLINT(*-unnecessary-copy-initialization)
|
||||
|
||||
synced_clear_statespace();
|
||||
|
||||
// Re-add the current state
|
||||
current_state_index = synced_try_insert_state(p);
|
||||
current_state_index = synced_try_insert_state(_p);
|
||||
|
||||
// These states are no longer in the graph
|
||||
previous_state_index = current_state_index;
|
||||
@ -482,4 +484,4 @@ auto state_manager::get_total_moves() const -> size_t
|
||||
auto state_manager::was_edited() const -> bool
|
||||
{
|
||||
return preset_states.at(current_preset) != get_state(starting_state_index);
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
#include "user_interface.hpp"
|
||||
#include "config.hpp"
|
||||
#include "input.hpp"
|
||||
|
||||
#include <raylib.h>
|
||||
|
||||
@ -10,8 +11,9 @@
|
||||
#include <tracy/Tracy.hpp>
|
||||
#endif
|
||||
|
||||
auto user_interface::grid::update_bounds(const int _x, const int _y, const int _width, const int _height,
|
||||
const int _columns, const int _rows) -> void
|
||||
auto user_interface::grid::update_bounds(const int _x, const int _y, const int _width,
|
||||
const int _height, const int _columns, const int _rows)
|
||||
-> void
|
||||
{
|
||||
x = _x;
|
||||
y = _y;
|
||||
@ -21,7 +23,8 @@ auto user_interface::grid::update_bounds(const int _x, const int _y, const int _
|
||||
rows = _rows;
|
||||
}
|
||||
|
||||
auto user_interface::grid::update_bounds(const int _x, const int _y, const int _width, const int _height) -> void
|
||||
auto user_interface::grid::update_bounds(const int _x, const int _y, const int _width,
|
||||
const int _height) -> void
|
||||
{
|
||||
x = _x;
|
||||
y = _y;
|
||||
@ -45,7 +48,8 @@ auto user_interface::grid::bounds() const -> Rectangle
|
||||
return bounds;
|
||||
}
|
||||
|
||||
auto user_interface::grid::bounds(const int _x, const int _y, const int _width, const int _height) const -> Rectangle
|
||||
auto user_interface::grid::bounds(const int _x, const int _y, const int _width,
|
||||
const int _height) const -> Rectangle
|
||||
{
|
||||
if (_x < 0 || _x + _width > columns || _y < 0 || _y + _height > rows) {
|
||||
errln("Grid bounds are outside range.");
|
||||
@ -55,8 +59,8 @@ auto user_interface::grid::bounds(const int _x, const int _y, const int _width,
|
||||
const int cell_width = (width - padding) / columns;
|
||||
const int cell_height = (height - padding) / rows;
|
||||
|
||||
return Rectangle(x + _x * cell_width + padding, y + _y * cell_height + padding, _width * cell_width - padding,
|
||||
_height * cell_height - padding);
|
||||
return Rectangle(x + _x * cell_width + padding, y + _y * cell_height + padding,
|
||||
_width * cell_width - padding, _height * cell_height - padding);
|
||||
}
|
||||
|
||||
auto user_interface::grid::square_bounds() const -> Rectangle
|
||||
@ -69,8 +73,8 @@ auto user_interface::grid::square_bounds() const -> Rectangle
|
||||
return bounds;
|
||||
}
|
||||
|
||||
auto user_interface::grid::square_bounds(const int _x, const int _y, const int _width, const int _height) const
|
||||
-> Rectangle
|
||||
auto user_interface::grid::square_bounds(const int _x, const int _y, const int _width,
|
||||
const int _height) const -> Rectangle
|
||||
{
|
||||
// Assumes each cell is square, so either width or height are not completely
|
||||
// filled
|
||||
@ -89,8 +93,10 @@ auto user_interface::grid::square_bounds(const int _x, const int _y, const int _
|
||||
const int x_offset = (width - grid_width) / 2;
|
||||
const int y_offset = (height - grid_height) / 2;
|
||||
|
||||
return Rectangle(x_offset + _x * (cell_size + padding) + padding, y_offset + _y * (cell_size + padding) + padding,
|
||||
_width * cell_size + padding * (_width - 1), _height * cell_size + padding * (_height - 1));
|
||||
return Rectangle(x_offset + _x * (cell_size + padding) + padding,
|
||||
y_offset + _y * (cell_size + padding) + padding,
|
||||
_width * cell_size + padding * (_width - 1),
|
||||
_height * cell_size + padding * (_height - 1));
|
||||
}
|
||||
|
||||
auto user_interface::init() -> void
|
||||
@ -194,15 +200,16 @@ auto user_interface::set_default_style(const default_style& style) -> void
|
||||
|
||||
auto user_interface::get_component_style(const int component) -> component_style
|
||||
{
|
||||
return {{GuiGetStyle(component, BORDER_COLOR_NORMAL), GuiGetStyle(component, BASE_COLOR_NORMAL),
|
||||
GuiGetStyle(component, TEXT_COLOR_NORMAL), GuiGetStyle(component, BORDER_COLOR_FOCUSED),
|
||||
GuiGetStyle(component, BASE_COLOR_FOCUSED), GuiGetStyle(component, TEXT_COLOR_FOCUSED),
|
||||
GuiGetStyle(component, BORDER_COLOR_PRESSED), GuiGetStyle(component, BASE_COLOR_PRESSED),
|
||||
GuiGetStyle(component, TEXT_COLOR_PRESSED), GuiGetStyle(component, BORDER_COLOR_DISABLED),
|
||||
GuiGetStyle(component, BASE_COLOR_DISABLED), GuiGetStyle(component, TEXT_COLOR_DISABLED)},
|
||||
GuiGetStyle(component, BORDER_WIDTH),
|
||||
GuiGetStyle(component, TEXT_PADDING),
|
||||
GuiGetStyle(component, TEXT_ALIGNMENT)};
|
||||
return {
|
||||
{GuiGetStyle(component, BORDER_COLOR_NORMAL), GuiGetStyle(component, BASE_COLOR_NORMAL),
|
||||
GuiGetStyle(component, TEXT_COLOR_NORMAL), GuiGetStyle(component, BORDER_COLOR_FOCUSED),
|
||||
GuiGetStyle(component, BASE_COLOR_FOCUSED), GuiGetStyle(component, TEXT_COLOR_FOCUSED),
|
||||
GuiGetStyle(component, BORDER_COLOR_PRESSED), GuiGetStyle(component, BASE_COLOR_PRESSED),
|
||||
GuiGetStyle(component, TEXT_COLOR_PRESSED), GuiGetStyle(component, BORDER_COLOR_DISABLED),
|
||||
GuiGetStyle(component, BASE_COLOR_DISABLED), GuiGetStyle(component, TEXT_COLOR_DISABLED)},
|
||||
GuiGetStyle(component, BORDER_WIDTH),
|
||||
GuiGetStyle(component, TEXT_PADDING),
|
||||
GuiGetStyle(component, TEXT_ALIGNMENT)};
|
||||
}
|
||||
|
||||
auto user_interface::set_component_style(const int component, const component_style& style) -> void
|
||||
@ -228,8 +235,16 @@ auto user_interface::set_component_style(const int component, const component_st
|
||||
GuiSetStyle(component, TEXT_ALIGNMENT, style.text_alignment);
|
||||
}
|
||||
|
||||
auto user_interface::draw_button(const Rectangle bounds, const std::string& label, const Color color,
|
||||
const bool enabled, const int font_size) const -> int
|
||||
auto user_interface::popup_bounds() -> Rectangle
|
||||
{
|
||||
return Rectangle(static_cast<float>(GetScreenWidth()) / 2.0f - POPUP_WIDTH / 2.0f,
|
||||
static_cast<float>(GetScreenHeight()) / 2.0f - POPUP_HEIGHT / 2.0f,
|
||||
POPUP_WIDTH, POPUP_HEIGHT);
|
||||
}
|
||||
|
||||
auto user_interface::draw_button(const Rectangle bounds, const std::string& label,
|
||||
const Color color, const bool enabled, const int font_size) const
|
||||
-> int
|
||||
{
|
||||
// Save original styling
|
||||
const default_style original_default = get_default_style();
|
||||
@ -260,16 +275,16 @@ auto user_interface::draw_button(const Rectangle bounds, const std::string& labe
|
||||
}
|
||||
|
||||
auto user_interface::draw_menu_button(const int x, const int y, const int width, const int height,
|
||||
const std::string& label, const Color color, const bool enabled,
|
||||
const int font_size) const -> int
|
||||
const std::string& label, const Color color,
|
||||
const bool enabled, const int font_size) const -> int
|
||||
{
|
||||
const Rectangle bounds = menu_grid.bounds(x, y, width, height);
|
||||
return draw_button(bounds, label, color, enabled, font_size);
|
||||
}
|
||||
|
||||
auto user_interface::draw_toggle_slider(const Rectangle bounds, const std::string& off_label,
|
||||
const std::string& on_label, int* active, Color color, bool enabled,
|
||||
int font_size) const -> int
|
||||
const std::string& on_label, int* active, Color color,
|
||||
bool enabled, int font_size) const -> int
|
||||
{
|
||||
// Save original styling
|
||||
const default_style original_default = get_default_style();
|
||||
@ -304,16 +319,18 @@ auto user_interface::draw_toggle_slider(const Rectangle bounds, const std::strin
|
||||
return pressed;
|
||||
}
|
||||
|
||||
auto user_interface::draw_menu_toggle_slider(const int x, const int y, const int width, const int height,
|
||||
const std::string& off_label, const std::string& on_label, int* active,
|
||||
const Color color, const bool enabled, const int font_size) const -> int
|
||||
auto user_interface::draw_menu_toggle_slider(const int x, const int y, const int width,
|
||||
const int height, const std::string& off_label,
|
||||
const std::string& on_label, int* active,
|
||||
const Color color, const bool enabled,
|
||||
const int font_size) const -> int
|
||||
{
|
||||
const Rectangle bounds = menu_grid.bounds(x, y, width, height);
|
||||
return draw_toggle_slider(bounds, off_label, on_label, active, color, enabled, font_size);
|
||||
}
|
||||
|
||||
auto user_interface::draw_spinner(Rectangle bounds, const std::string& label, int* value, int min, int max, Color color,
|
||||
bool enabled, int font_size) const -> int
|
||||
auto user_interface::draw_spinner(Rectangle bounds, const std::string& label, int* value, int min,
|
||||
int max, Color color, bool enabled, int font_size) const -> int
|
||||
{
|
||||
// Save original styling
|
||||
const default_style original_default = get_default_style();
|
||||
@ -349,15 +366,16 @@ auto user_interface::draw_spinner(Rectangle bounds, const std::string& label, in
|
||||
}
|
||||
|
||||
auto user_interface::draw_menu_spinner(const int x, const int y, const int width, const int height,
|
||||
const std::string& label, int* value, const int min, const int max,
|
||||
const Color color, const bool enabled, const int font_size) const -> int
|
||||
const std::string& label, int* value, const int min,
|
||||
const int max, const Color color, const bool enabled,
|
||||
const int font_size) const -> int
|
||||
{
|
||||
const Rectangle bounds = menu_grid.bounds(x, y, width, height);
|
||||
return draw_spinner(bounds, label, value, min, max, color, enabled, font_size);
|
||||
}
|
||||
|
||||
auto user_interface::draw_label(const Rectangle bounds, const std::string& text, const Color color, const bool enabled,
|
||||
const int font_size) const -> int
|
||||
auto user_interface::draw_label(const Rectangle bounds, const std::string& text, const Color color,
|
||||
const bool enabled, const int font_size) const -> int
|
||||
{
|
||||
// Save original styling
|
||||
const default_style original_default = get_default_style();
|
||||
@ -387,8 +405,8 @@ auto user_interface::draw_label(const Rectangle bounds, const std::string& text,
|
||||
return pressed;
|
||||
}
|
||||
|
||||
auto user_interface::draw_board_block(const int x, const int y, const int width, const int height, const Color color,
|
||||
const bool enabled) const -> bool
|
||||
auto user_interface::draw_board_block(const int x, const int y, const int width, const int height,
|
||||
const Color color, const bool enabled) const -> bool
|
||||
{
|
||||
component_style s = get_component_style(BUTTON);
|
||||
apply_block_color(s, color);
|
||||
@ -428,13 +446,14 @@ auto user_interface::draw_board_block(const int x, const int y, const int width,
|
||||
|
||||
auto user_interface::window_open() const -> bool
|
||||
{
|
||||
return save_window || help_window;
|
||||
return save_window || help_window || ok_message || yes_no_message;
|
||||
}
|
||||
|
||||
auto user_interface::draw_menu_header(const Color color) const -> void
|
||||
{
|
||||
int preset = state.get_current_preset();
|
||||
draw_menu_spinner(0, 0, 1, 1, "Preset: ", &preset, -1, state.get_preset_count(), color, !input.editing);
|
||||
int preset = static_cast<int>(state.get_current_preset());
|
||||
draw_menu_spinner(0, 0, 1, 1, "Preset: ", &preset, -1,
|
||||
static_cast<int>(state.get_preset_count()), color, !input.editing);
|
||||
if (preset > static_cast<int>(state.get_current_preset())) {
|
||||
input.load_next_preset();
|
||||
} else if (preset < static_cast<int>(state.get_current_preset())) {
|
||||
@ -456,15 +475,17 @@ auto user_interface::draw_menu_header(const Color color) const -> void
|
||||
auto user_interface::draw_graph_info(const Color color) const -> void
|
||||
{
|
||||
draw_menu_button(0, 1, 1, 1,
|
||||
std::format("Found {} States ({} Winning)", state.get_state_count(), state.get_target_count()),
|
||||
std::format("Found {} States ({} Winning)", state.get_state_count(),
|
||||
state.get_target_count()),
|
||||
color);
|
||||
|
||||
draw_menu_button(1, 1, 1, 1, std::format("Found {} Transitions", state.get_link_count()), color);
|
||||
draw_menu_button(1, 1, 1, 1, std::format("Found {} Transitions", state.get_link_count()),
|
||||
color);
|
||||
|
||||
draw_menu_button(
|
||||
2, 1, 1, 1,
|
||||
std::format("{} Moves to Nearest Solution", state.get_path_length() > 0 ? state.get_path_length() - 1 : 0),
|
||||
color);
|
||||
draw_menu_button(2, 1, 1, 1,
|
||||
std::format("{} Moves to Nearest Solution",
|
||||
state.get_path_length() > 0 ? state.get_path_length() - 1 : 0),
|
||||
color);
|
||||
}
|
||||
|
||||
auto user_interface::draw_graph_controls(const Color color) const -> void
|
||||
@ -485,7 +506,8 @@ auto user_interface::draw_graph_controls(const Color color) const -> void
|
||||
}
|
||||
|
||||
int mark_solutions = input.mark_solutions;
|
||||
draw_menu_toggle_slider(2, 2, 1, 1, "Solution Hidden (I)", "Solution Shown (I)", &mark_solutions, color);
|
||||
draw_menu_toggle_slider(2, 2, 1, 1, "Solution Hidden (I)", "Solution Shown (I)",
|
||||
&mark_solutions, color);
|
||||
if (mark_solutions != input.mark_solutions) {
|
||||
input.toggle_mark_solutions();
|
||||
}
|
||||
@ -495,20 +517,22 @@ auto user_interface::draw_graph_controls(const Color color) const -> void
|
||||
auto user_interface::draw_camera_controls(const Color color) const -> void
|
||||
{
|
||||
int lock_camera = input.camera_lock;
|
||||
draw_menu_toggle_slider(0, 3, 1, 1, "Free Camera (L)", "Locked Camera (L)", &lock_camera, color);
|
||||
draw_menu_toggle_slider(0, 3, 1, 1, "Free Camera (L)", "Locked Camera (L)", &lock_camera,
|
||||
color);
|
||||
if (lock_camera != input.camera_lock) {
|
||||
input.toggle_camera_lock();
|
||||
}
|
||||
|
||||
int lock_camera_mass_center = input.camera_mass_center_lock;
|
||||
draw_menu_toggle_slider(1, 3, 1, 1, "Current Block (U)", "Graph Center (U)", &lock_camera_mass_center, color,
|
||||
input.camera_lock);
|
||||
draw_menu_toggle_slider(1, 3, 1, 1, "Current Block (U)", "Graph Center (U)",
|
||||
&lock_camera_mass_center, color, input.camera_lock);
|
||||
if (lock_camera_mass_center != input.camera_mass_center_lock) {
|
||||
input.toggle_camera_mass_center_lock();
|
||||
}
|
||||
|
||||
int projection = camera.projection == CAMERA_ORTHOGRAPHIC;
|
||||
draw_menu_toggle_slider(2, 3, 1, 1, "Perspective (Alt)", "Orthographic (Alt)", &projection, color);
|
||||
draw_menu_toggle_slider(2, 3, 1, 1, "Perspective (Alt)", "Orthographic (Alt)", &projection,
|
||||
color);
|
||||
if (projection != (camera.projection == CAMERA_ORTHOGRAPHIC)) {
|
||||
input.toggle_camera_projection();
|
||||
}
|
||||
@ -535,7 +559,8 @@ auto user_interface::draw_puzzle_controls(const Color color) const -> void
|
||||
|
||||
const int visits = state.get_current_visits();
|
||||
draw_menu_button(0, 4, 1, 1,
|
||||
std::format("{} Moves ({}{} Time at this State)", state.get_total_moves(), visits, nth(visits)),
|
||||
std::format("{} Moves ({}{} Time at this State)", state.get_total_moves(),
|
||||
visits, nth(visits)),
|
||||
color);
|
||||
|
||||
if (draw_menu_button(1, 4, 1, 1, "Make Optimal Move (Space)", color, state.has_distances())) {
|
||||
@ -606,10 +631,18 @@ auto user_interface::draw_edit_controls(const Color color) const -> void
|
||||
|
||||
auto user_interface::draw_menu_footer(const Color color) -> void
|
||||
{
|
||||
draw_menu_button(0, 6, 2, 1, std::format("State: \"{}\"", state.get_current_state().state), color);
|
||||
draw_menu_button(0, 6, 2, 1, std::format("State: \"{}\"", state.get_current_state().state),
|
||||
color);
|
||||
|
||||
if (draw_menu_button(2, 6, 1, 1, "Save as Preset", color)) {
|
||||
save_window = true;
|
||||
if (const std::optional<std::string>& reason =
|
||||
state.get_current_state().try_get_invalid_reason()) {
|
||||
message_title = "Can't Save Preset";
|
||||
message_message = std::format("Invalid Board: {}.", *reason);
|
||||
ok_message = true;
|
||||
} else {
|
||||
save_window = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -628,10 +661,8 @@ auto user_interface::draw_save_preset_popup() -> void
|
||||
}
|
||||
|
||||
// Returns the pressed button index
|
||||
const int button =
|
||||
GuiTextInputBox(Rectangle((GetScreenWidth() - POPUP_WIDTH) / 2.0f, (GetScreenHeight() - POPUP_HEIGHT) / 2.0f,
|
||||
POPUP_WIDTH, POPUP_HEIGHT),
|
||||
"Save as Preset", "Enter Preset Name", "Ok;Cancel", preset_name.data(), 255, nullptr);
|
||||
const int button = GuiTextInputBox(popup_bounds(), "Save as Preset", "Enter Preset Name",
|
||||
"Ok;Cancel", preset_name.data(), 255, nullptr);
|
||||
if (button == 1) {
|
||||
state.append_preset_file(preset_name.data());
|
||||
}
|
||||
@ -641,6 +672,39 @@ auto user_interface::draw_save_preset_popup() -> void
|
||||
}
|
||||
}
|
||||
|
||||
auto user_interface::draw_ok_message_box() -> void
|
||||
{
|
||||
if (!ok_message) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int button =
|
||||
GuiMessageBox(popup_bounds(), message_title.data(), message_message.data(), "Ok");
|
||||
if (button == 0 || button == 1) {
|
||||
message_title = "";
|
||||
message_message = "";
|
||||
ok_message = false;
|
||||
}
|
||||
}
|
||||
|
||||
auto user_interface::draw_yes_no_message_box() -> void
|
||||
{
|
||||
if (!yes_no_message) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int button =
|
||||
GuiMessageBox(popup_bounds(), message_title.data(), message_message.data(), "Yes;No");
|
||||
if (button == 1) {
|
||||
yes_no_handler();
|
||||
}
|
||||
if (button == 0 || button == 1 || button == 2) {
|
||||
message_title = "";
|
||||
message_message = "";
|
||||
yes_no_message = false;
|
||||
}
|
||||
}
|
||||
|
||||
auto user_interface::draw_main_menu() -> void
|
||||
{
|
||||
menu_grid.update_bounds(0, 0, GetScreenWidth(), MENU_HEIGHT);
|
||||
@ -663,27 +727,30 @@ auto user_interface::draw_puzzle_board() -> void
|
||||
{
|
||||
const puzzle& current = state.get_current_state();
|
||||
|
||||
board_grid.update_bounds(0, MENU_HEIGHT, GetScreenWidth() / 2, GetScreenHeight() - MENU_HEIGHT, current.width,
|
||||
current.height);
|
||||
board_grid.update_bounds(0, MENU_HEIGHT, GetScreenWidth() / 2, GetScreenHeight() - MENU_HEIGHT,
|
||||
current.width, current.height);
|
||||
|
||||
// Draw outer border
|
||||
const Rectangle bounds = board_grid.square_bounds();
|
||||
DrawRectangleRec(bounds, current.won() ? BOARD_COLOR_WON : BOARD_COLOR_RESTRICTED);
|
||||
|
||||
// Draw inner borders
|
||||
DrawRectangle(bounds.x + BOARD_PADDING, bounds.y + BOARD_PADDING, bounds.width - 2 * BOARD_PADDING,
|
||||
bounds.height - 2 * BOARD_PADDING, current.restricted ? BOARD_COLOR_RESTRICTED : BOARD_COLOR_FREE);
|
||||
DrawRectangle(bounds.x + BOARD_PADDING, bounds.y + BOARD_PADDING,
|
||||
bounds.width - 2 * BOARD_PADDING, bounds.height - 2 * BOARD_PADDING,
|
||||
current.restricted ? BOARD_COLOR_RESTRICTED : BOARD_COLOR_FREE);
|
||||
|
||||
// Draw target opening
|
||||
// TODO: Only draw single direction (in corner) if restricted (use target block principal direction)
|
||||
// TODO: Only draw single direction (in corner) if restricted (use target block principal
|
||||
// direction)
|
||||
const std::optional<puzzle::block> target_block = current.try_get_target_block();
|
||||
if (current.has_win_condition() && target_block.has_value()) {
|
||||
const int target_x = current.target_x;
|
||||
const int target_y = current.target_y;
|
||||
auto [x, y, width, height] =
|
||||
board_grid.square_bounds(target_x, target_y, target_block.value().width, target_block.value().height);
|
||||
const int target_x = current.target_x;
|
||||
const int target_y = current.target_y;
|
||||
if (current.has_win_condition() && target_block) {
|
||||
auto [x, y, width, height] = board_grid.square_bounds(
|
||||
target_x, target_y, target_block.value().width, target_block.value().height);
|
||||
|
||||
const Color opening_color = Fade(current.won() ? BOARD_COLOR_WON : BOARD_COLOR_RESTRICTED, 0.3);
|
||||
const Color opening_color =
|
||||
Fade(current.won() ? BOARD_COLOR_WON : BOARD_COLOR_RESTRICTED, 0.3);
|
||||
|
||||
if (target_x == 0) {
|
||||
// Left opening
|
||||
@ -745,41 +812,82 @@ auto user_interface::draw_puzzle_board() -> void
|
||||
|
||||
// Draw block placing
|
||||
if (input.editing && input.has_block_add_xy) {
|
||||
if (current.covers(input.block_add_x, input.block_add_y) && input.hov_x >= input.block_add_x &&
|
||||
input.hov_y >= input.block_add_y) {
|
||||
if (current.covers(input.block_add_x, input.block_add_y) &&
|
||||
input.hov_x >= input.block_add_x && input.hov_y >= input.block_add_y) {
|
||||
bool collides = false;
|
||||
for (const puzzle::block& b : current) {
|
||||
if (b.collides(puzzle::block(input.block_add_x, input.block_add_y, input.hov_x - input.block_add_x + 1,
|
||||
if (b.collides(puzzle::block(input.block_add_x, input.block_add_y,
|
||||
input.hov_x - input.block_add_x + 1,
|
||||
input.hov_y - input.block_add_y + 1, false))) {
|
||||
collides = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!collides) {
|
||||
draw_board_block(input.block_add_x, input.block_add_y, input.hov_x - input.block_add_x + 1,
|
||||
draw_board_block(input.block_add_x, input.block_add_y,
|
||||
input.hov_x - input.block_add_x + 1,
|
||||
input.hov_y - input.block_add_y + 1, PURPLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw goal boundaries when editing
|
||||
if (input.editing) {
|
||||
DrawRectangleLinesEx(board_grid.square_bounds(target_x, target_y, target_block->width, target_block->height), 2.0, TARGET_BLOCK_COLOR);
|
||||
}
|
||||
}
|
||||
|
||||
auto user_interface::draw_graph_overlay(int fps, int ups, size_t mass_count, size_t spring_count) -> void
|
||||
auto user_interface::draw_graph_overlay(int fps, int ups, size_t mass_count, size_t spring_count)
|
||||
-> void
|
||||
{
|
||||
graph_overlay_grid.update_bounds(GetScreenWidth() / 2, MENU_HEIGHT);
|
||||
debug_overlay_grid.update_bounds(GetScreenWidth() / 2, GetScreenHeight() - 75);
|
||||
|
||||
draw_label(graph_overlay_grid.bounds(0, 0, 1, 1), std::format("Dist: {:0>7.2f}", camera.distance), BLACK);
|
||||
draw_label(graph_overlay_grid.bounds(0, 1, 1, 1), std::format("FoV: {:0>6.2f}", camera.fov), BLACK);
|
||||
draw_label(graph_overlay_grid.bounds(0, 0, 1, 1),
|
||||
std::format("Dist: {:0>7.2f}", camera.distance), BLACK);
|
||||
draw_label(graph_overlay_grid.bounds(0, 1, 1, 1), std::format("FoV: {:0>6.2f}", camera.fov),
|
||||
BLACK);
|
||||
draw_label(graph_overlay_grid.bounds(0, 2, 1, 1), std::format("FPS: {:0>3}", fps), LIME);
|
||||
draw_label(graph_overlay_grid.bounds(0, 3, 1, 1), std::format("UPS: {:0>3}", ups), ORANGE);
|
||||
|
||||
// Debug
|
||||
draw_label(debug_overlay_grid.bounds(0, 0, 1, 1), std::format("Debug:"), BLACK);
|
||||
draw_label(debug_overlay_grid.bounds(0, 1, 1, 1), std::format("Masses: {}", mass_count), BLACK);
|
||||
draw_label(debug_overlay_grid.bounds(0, 2, 1, 1), std::format("Springs: {}", spring_count), BLACK);
|
||||
draw_label(debug_overlay_grid.bounds(0, 0, 1, 1), std::format("Physics Debug:"), BLACK);
|
||||
draw_label(debug_overlay_grid.bounds(0, 1, 1, 1), std::format("Masses: {}", mass_count),
|
||||
BLACK);
|
||||
draw_label(debug_overlay_grid.bounds(0, 2, 1, 1), std::format("Springs: {}", spring_count),
|
||||
BLACK);
|
||||
}
|
||||
|
||||
auto user_interface::update() const -> void
|
||||
auto user_interface::draw(const int fps, const int ups, const size_t mass_count,
|
||||
const size_t spring_count) -> void
|
||||
{
|
||||
const auto visitor = overloads{[&](const show_ok_message& msg)
|
||||
{
|
||||
message_title = msg.title;
|
||||
message_message = msg.message;
|
||||
ok_message = true;
|
||||
},
|
||||
[&](const show_yes_no_message& msg)
|
||||
{
|
||||
message_title = msg.title;
|
||||
message_message = msg.message;
|
||||
yes_no_handler = msg.on_yes;
|
||||
yes_no_message = true;
|
||||
},
|
||||
[&](const show_save_preset_window& msg) { save_window = true; }};
|
||||
|
||||
while (!input.ui_commands.empty()) {
|
||||
const ui_command& cmd = input.ui_commands.front();
|
||||
|
||||
cmd.visit(visitor);
|
||||
|
||||
input.ui_commands.pop();
|
||||
}
|
||||
|
||||
input.disable = window_open();
|
||||
}
|
||||
|
||||
draw_graph_overlay(fps, ups, mass_count, spring_count);
|
||||
draw_save_preset_popup();
|
||||
draw_ok_message_box();
|
||||
draw_yes_no_message_box();
|
||||
}
|
||||
Reference in New Issue
Block a user