small refactor
This commit is contained in:
@ -1,31 +1,30 @@
|
||||
#ifndef __CAMERA_HPP_
|
||||
#define __CAMERA_HPP_
|
||||
#ifndef CAMERA_HPP_
|
||||
#define CAMERA_HPP_
|
||||
|
||||
#include "config.hpp"
|
||||
|
||||
#include <raylib.h>
|
||||
#include <raymath.h>
|
||||
|
||||
class OrbitCamera3D {
|
||||
class orbit_camera
|
||||
{
|
||||
public:
|
||||
Vector3 position = Vector3Zero();
|
||||
Vector3 target = Vector3Zero();
|
||||
float distance = CAMERA_DISTANCE;
|
||||
float fov = CAMERA_FOV;
|
||||
CameraProjection projection = CAMERA_PERSPECTIVE;
|
||||
float angle_x = 0.0;
|
||||
float angle_y = 0.0;
|
||||
Vector3 position = Vector3Zero();
|
||||
Vector3 target = Vector3Zero();
|
||||
float distance = CAMERA_DISTANCE;
|
||||
float fov = CAMERA_FOV;
|
||||
CameraProjection projection = CAMERA_PERSPECTIVE;
|
||||
float angle_x = 0.0;
|
||||
float angle_y = 0.0;
|
||||
|
||||
Camera camera = Camera{Vector3(0, 0, -distance), target, Vector3(0, 1.0, 0),
|
||||
fov, projection};
|
||||
Camera camera = Camera{Vector3(0, 0, -distance), target, Vector3(0, 1.0, 0), fov, projection};
|
||||
|
||||
public:
|
||||
auto Rotate(Vector2 last_mouse, Vector2 mouse) -> void;
|
||||
auto rotate(Vector2 last_mouse, Vector2 mouse) -> void;
|
||||
|
||||
auto Pan(Vector2 last_mouse, Vector2 mouse) -> void;
|
||||
auto pan(Vector2 last_mouse, Vector2 mouse) -> void;
|
||||
|
||||
auto Update(const Vector3 ¤t_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
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
#ifndef __CONFIG_HPP_
|
||||
#define __CONFIG_HPP_
|
||||
#ifndef CONFIG_HPP_
|
||||
#define CONFIG_HPP_
|
||||
|
||||
#include <raylib.h>
|
||||
|
||||
@ -12,14 +12,16 @@
|
||||
// Window
|
||||
constexpr int INITIAL_WIDTH = 600;
|
||||
constexpr int INITIAL_HEIGHT = 600;
|
||||
constexpr int MENU_HEIGHT = 350;
|
||||
constexpr int MENU_HEIGHT = 300;
|
||||
constexpr int POPUP_WIDTH = 450;
|
||||
constexpr int POPUP_HEIGHT = 150;
|
||||
|
||||
// Menu
|
||||
constexpr int MENU_PAD = 5;
|
||||
constexpr int BUTTON_PAD = 12;
|
||||
constexpr int MENU_ROWS = 7;
|
||||
constexpr int MENU_COLS = 3;
|
||||
constexpr const char *FONT = "fonts/SpaceMono.ttf";
|
||||
constexpr const char* FONT = "fonts/SpaceMono.ttf";
|
||||
constexpr int FONT_SIZE = 26;
|
||||
|
||||
// Camera Controls
|
||||
|
||||
@ -1,32 +1,24 @@
|
||||
#ifndef __DISTANCE_HPP_
|
||||
#define __DISTANCE_HPP_
|
||||
|
||||
#include "config.hpp"
|
||||
#ifndef DISTANCE_HPP_
|
||||
#define DISTANCE_HPP_
|
||||
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
struct DistanceResult {
|
||||
// distances[n] = distance from n to target
|
||||
std::vector<int> distances;
|
||||
class graph_distances
|
||||
{
|
||||
public:
|
||||
std::vector<int> distances; // distances[n] = distance from node n to target
|
||||
std::vector<size_t> parents; // parents[n] = next node on the path from node n to target
|
||||
std::vector<size_t> nearest_targets; // nearest_target[n] = closest target node to node n
|
||||
|
||||
// parents[n] = next node on the path from n to target
|
||||
std::vector<std::size_t> parents;
|
||||
public:
|
||||
auto clear() -> void;
|
||||
[[nodiscard]] auto empty() const -> bool;
|
||||
|
||||
// nearest_target[n] = closest target node to n
|
||||
std::vector<std::size_t> nearest_targets;
|
||||
auto calculate_distances(size_t node_count, const std::vector<std::pair<size_t, size_t>>& edges,
|
||||
const std::vector<size_t>& targets) -> void;
|
||||
|
||||
auto Clear() -> void;
|
||||
|
||||
auto Empty() -> bool;
|
||||
[[nodiscard]] auto get_shortest_path(size_t source) const -> std::vector<size_t>;
|
||||
};
|
||||
|
||||
auto CalculateDistances(
|
||||
std::size_t node_count,
|
||||
const std::vector<std::pair<std::size_t, std::size_t>> &edges,
|
||||
const std::vector<std::size_t> &targets) -> DistanceResult;
|
||||
|
||||
auto GetPath(const DistanceResult &result, std::size_t source)
|
||||
-> std::vector<std::size_t>;
|
||||
|
||||
#endif
|
||||
|
||||
184
include/gui.hpp
184
include/gui.hpp
@ -1,184 +0,0 @@
|
||||
#ifndef __GUI_HPP_
|
||||
#define __GUI_HPP_
|
||||
|
||||
#include "camera.hpp"
|
||||
#include "config.hpp"
|
||||
#include "input.hpp"
|
||||
#include "state.hpp"
|
||||
|
||||
#include <raygui.h>
|
||||
#include <raylib.h>
|
||||
|
||||
class Grid {
|
||||
public:
|
||||
int x;
|
||||
int y;
|
||||
int width;
|
||||
int height;
|
||||
int columns;
|
||||
int rows;
|
||||
const int padding;
|
||||
|
||||
public:
|
||||
Grid(int _x, int _y, int _width, int _height, int _columns, int _rows,
|
||||
int _padding)
|
||||
: x(_x), y(_y), width(_width), height(_height), columns(_columns),
|
||||
rows(_rows), padding(_padding) {}
|
||||
|
||||
public:
|
||||
auto UpdateBounds(int _x, int _y, int _width, int _height, int _columns,
|
||||
int _rows) -> void;
|
||||
|
||||
auto UpdateBounds(int _x, int _y, int _width, int _height) -> void;
|
||||
|
||||
auto UpdateBounds(int _x, int _y) -> void;
|
||||
|
||||
auto Bounds() const -> Rectangle;
|
||||
|
||||
auto Bounds(int _x, int _y, int _width, int _height) const -> Rectangle;
|
||||
|
||||
auto SquareBounds() const -> Rectangle;
|
||||
|
||||
auto SquareBounds(int _x, int _y, int _width, int _height) const -> Rectangle;
|
||||
};
|
||||
|
||||
class Gui {
|
||||
struct Style {
|
||||
int border_color_normal;
|
||||
int base_color_normal;
|
||||
int text_color_normal;
|
||||
|
||||
int border_color_focused;
|
||||
int base_color_focused;
|
||||
int text_color_focused;
|
||||
|
||||
int border_color_pressed;
|
||||
int base_color_pressed;
|
||||
int text_color_pressed;
|
||||
|
||||
int border_color_disabled;
|
||||
int base_color_disabled;
|
||||
int text_color_disabled;
|
||||
};
|
||||
|
||||
struct DefaultStyle : Style {
|
||||
int background_color;
|
||||
int line_color;
|
||||
|
||||
int text_size;
|
||||
int text_spacing;
|
||||
int text_line_spacing;
|
||||
int text_alignment_vertical;
|
||||
int text_wrap_mode;
|
||||
};
|
||||
|
||||
struct ComponentStyle : Style {
|
||||
int border_width;
|
||||
int text_padding;
|
||||
int text_alignment;
|
||||
};
|
||||
|
||||
private:
|
||||
InputHandler &input;
|
||||
StateManager &state;
|
||||
const OrbitCamera3D &camera;
|
||||
|
||||
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.current_state.width, state.current_state.height, BOARD_PADDING);
|
||||
|
||||
Grid graph_overlay_grid =
|
||||
Grid(GetScreenWidth() / 2, MENU_HEIGHT, 200, 100, 1, 4, MENU_PAD);
|
||||
|
||||
bool save_window = false;
|
||||
std::array<char, 256> preset_name = {0};
|
||||
bool help_window = false;
|
||||
|
||||
public:
|
||||
Gui(InputHandler &_input, StateManager &_state, const OrbitCamera3D &_camera)
|
||||
: input(_input), state(_state), camera(_camera) {
|
||||
Init();
|
||||
}
|
||||
|
||||
Gui(const Gui ©) = delete;
|
||||
Gui &operator=(const Gui ©) = delete;
|
||||
Gui(Gui &&move) = delete;
|
||||
Gui &operator=(Gui &&move) = delete;
|
||||
|
||||
private:
|
||||
auto Init() const -> void;
|
||||
|
||||
auto ApplyColor(Style &style, Color color) const -> void;
|
||||
|
||||
auto ApplyBlockColor(Style &style, Color color) const -> void;
|
||||
|
||||
auto ApplyTextColor(Style &style, Color color) const -> void;
|
||||
|
||||
auto GetDefaultStyle() const -> DefaultStyle;
|
||||
|
||||
auto SetDefaultStyle(const DefaultStyle &style) const -> void;
|
||||
|
||||
auto GetComponentStyle(int component) const -> ComponentStyle;
|
||||
|
||||
auto SetComponentStyle(int component, const ComponentStyle &style) const
|
||||
-> void;
|
||||
|
||||
auto DrawButton(Rectangle bounds, const std::string &label, Color color,
|
||||
bool enabled = true, int font_size = FONT_SIZE) const -> int;
|
||||
|
||||
auto DrawMenuButton(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 DrawToggleSlider(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 DrawMenuToggleSlider(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;
|
||||
|
||||
auto DrawSpinner(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 DrawMenuSpinner(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 DrawLabel(Rectangle bounds, const std::string &text, Color color,
|
||||
bool enabled = true, int font_size = FONT_SIZE) const -> int;
|
||||
|
||||
auto DrawBoardBlock(int x, int y, int width, int height, Color color,
|
||||
bool enabled = true) const -> bool;
|
||||
|
||||
auto WindowOpen() const -> bool;
|
||||
|
||||
// Different menu sections
|
||||
auto DrawMenuHeader(Color color) const -> void;
|
||||
auto DrawGraphInfo(Color color) const -> void;
|
||||
auto DrawGraphControls(Color color) const -> void;
|
||||
auto DrawCameraControls(Color color) const -> void;
|
||||
auto DrawPuzzleControls(Color color) const -> void;
|
||||
auto DrawEditControls(Color color) const -> void;
|
||||
auto DrawMenuFooter(Color color) -> void;
|
||||
|
||||
public:
|
||||
auto GetBackgroundColor() const -> Color;
|
||||
auto HelpPopup() -> void;
|
||||
auto DrawSavePresetPopup() -> void;
|
||||
auto DrawMainMenu() -> void;
|
||||
auto DrawPuzzleBoard() -> void;
|
||||
auto DrawGraphOverlay(int fps, int ups) -> void;
|
||||
auto Update() -> void;
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -1,158 +1,151 @@
|
||||
#ifndef __INPUT_HPP_
|
||||
#define __INPUT_HPP_
|
||||
#ifndef INPUT_HPP_
|
||||
#define INPUT_HPP_
|
||||
|
||||
#include "camera.hpp"
|
||||
#include "config.hpp"
|
||||
#include "state.hpp"
|
||||
#include "state_manager.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <raylib.h>
|
||||
#include <raymath.h>
|
||||
|
||||
class InputHandler {
|
||||
struct GenericHandler {
|
||||
std::function<void(InputHandler &)> handler;
|
||||
};
|
||||
class input_handler
|
||||
{
|
||||
struct generic_handler
|
||||
{
|
||||
std::function<void(input_handler&)> handler;
|
||||
};
|
||||
|
||||
struct MouseHandler : GenericHandler {
|
||||
MouseButton button;
|
||||
};
|
||||
struct mouse_handler : generic_handler
|
||||
{
|
||||
MouseButton button;
|
||||
};
|
||||
|
||||
struct KeyboardHandler : GenericHandler {
|
||||
KeyboardKey key;
|
||||
};
|
||||
struct keyboard_handler : generic_handler
|
||||
{
|
||||
KeyboardKey key;
|
||||
};
|
||||
|
||||
private:
|
||||
std::vector<GenericHandler> generic_handlers;
|
||||
std::vector<MouseHandler> mouse_pressed_handlers;
|
||||
std::vector<MouseHandler> mouse_released_handlers;
|
||||
std::vector<KeyboardHandler> key_pressed_handlers;
|
||||
std::vector<KeyboardHandler> key_released_handlers;
|
||||
std::vector<generic_handler> generic_handlers;
|
||||
std::vector<mouse_handler> mouse_pressed_handlers;
|
||||
std::vector<mouse_handler> mouse_released_handlers;
|
||||
std::vector<keyboard_handler> key_pressed_handlers;
|
||||
std::vector<keyboard_handler> key_released_handlers;
|
||||
|
||||
public:
|
||||
StateManager &state;
|
||||
OrbitCamera3D &camera;
|
||||
state_manager& state;
|
||||
orbit_camera& camera;
|
||||
|
||||
bool disable = false;
|
||||
bool disable = false;
|
||||
|
||||
// Block selection
|
||||
int hov_x = -1;
|
||||
int hov_y = -1;
|
||||
int sel_x = 0;
|
||||
int sel_y = 0;
|
||||
// Block selection
|
||||
int hov_x = -1;
|
||||
int hov_y = -1;
|
||||
int sel_x = 0;
|
||||
int sel_y = 0;
|
||||
|
||||
// Editing
|
||||
bool editing = false;
|
||||
bool has_block_add_xy = false;
|
||||
int block_add_x = -1;
|
||||
int block_add_y = -1;
|
||||
// Editing
|
||||
bool editing = false;
|
||||
bool has_block_add_xy = false;
|
||||
int block_add_x = -1;
|
||||
int block_add_y = -1;
|
||||
|
||||
// Graph display
|
||||
bool mark_path = false;
|
||||
bool mark_solutions = false;
|
||||
bool connect_solutions = false;
|
||||
// Graph display
|
||||
bool mark_path = false;
|
||||
bool mark_solutions = false;
|
||||
bool connect_solutions = false;
|
||||
|
||||
// Camera
|
||||
bool camera_lock = true;
|
||||
bool camera_mass_center_lock = false;
|
||||
bool camera_panning = false;
|
||||
bool camera_rotating = false;
|
||||
// Camera
|
||||
bool camera_lock = true;
|
||||
bool camera_mass_center_lock = false;
|
||||
bool camera_panning = false;
|
||||
bool camera_rotating = false;
|
||||
|
||||
// Mouse dragging
|
||||
Vector2 mouse = Vector2Zero();
|
||||
Vector2 last_mouse = Vector2Zero();
|
||||
// Mouse dragging
|
||||
Vector2 mouse = Vector2Zero();
|
||||
Vector2 last_mouse = Vector2Zero();
|
||||
|
||||
public:
|
||||
InputHandler(StateManager &_state, OrbitCamera3D &_camera)
|
||||
: state(_state), camera(_camera) {
|
||||
InitHandlers();
|
||||
}
|
||||
input_handler(state_manager& _state, orbit_camera& _camera) : state(_state), camera(_camera)
|
||||
{
|
||||
init_handlers();
|
||||
}
|
||||
|
||||
InputHandler(const InputHandler ©) = delete;
|
||||
InputHandler &operator=(const InputHandler ©) = delete;
|
||||
InputHandler(InputHandler &&move) = delete;
|
||||
InputHandler &operator=(InputHandler &&move) = delete;
|
||||
|
||||
~InputHandler() {}
|
||||
input_handler(const input_handler& copy) = delete;
|
||||
auto operator=(const input_handler& copy) -> input_handler& = delete;
|
||||
input_handler(input_handler&& move) = delete;
|
||||
auto operator=(input_handler&& move) -> input_handler& = delete;
|
||||
|
||||
private:
|
||||
auto InitHandlers() -> void;
|
||||
auto init_handlers() -> void;
|
||||
|
||||
public:
|
||||
// Helpers
|
||||
auto MouseInMenuPane() -> bool;
|
||||
auto MouseInBoardPane() -> bool;
|
||||
auto MouseInGraphPane() -> bool;
|
||||
// Helpers
|
||||
[[nodiscard]] auto mouse_in_menu_pane() const -> bool;
|
||||
[[nodiscard]] auto mouse_in_board_pane() const -> bool;
|
||||
[[nodiscard]] auto mouse_in_graph_pane() const -> bool;
|
||||
|
||||
// Mouse actions
|
||||
auto MouseHover() -> void;
|
||||
auto CameraStartPan() -> void;
|
||||
auto CameraPan() -> void;
|
||||
auto CameraStopPan() -> void;
|
||||
auto CameraStartRotate() -> void;
|
||||
auto CameraRotate() -> void;
|
||||
auto CameraStopRotate() -> void;
|
||||
auto CameraZoom() -> void;
|
||||
auto CameraFov() -> void;
|
||||
auto SelectBlock() -> void;
|
||||
auto StartAddBlock() -> void;
|
||||
auto ClearAddBlock() -> void;
|
||||
auto AddBlock() -> void;
|
||||
auto RemoveBlock() -> void;
|
||||
auto PlaceGoal() -> void;
|
||||
// Mouse actions
|
||||
auto mouse_hover() -> void;
|
||||
auto camera_start_pan() -> void;
|
||||
auto camera_pan() const -> void;
|
||||
auto camera_stop_pan() -> void;
|
||||
auto camera_start_rotate() -> void;
|
||||
auto camera_rotate() const -> void;
|
||||
auto camera_stop_rotate() -> void;
|
||||
auto camera_zoom() const -> void;
|
||||
auto camera_fov() const -> void;
|
||||
auto select_block() -> void;
|
||||
auto start_add_block() -> void;
|
||||
auto clear_add_block() -> void;
|
||||
auto add_block() -> void;
|
||||
auto remove_block() -> void;
|
||||
auto place_goal() const -> void;
|
||||
|
||||
// Key actions
|
||||
auto ToggleCameraLock() -> void;
|
||||
auto ToggleCameraMassCenterLock() -> void;
|
||||
auto ToggleCameraProjection() -> void;
|
||||
auto MoveBlockNor() -> void;
|
||||
auto MoveBlockWes() -> void;
|
||||
auto MoveBlockSou() -> void;
|
||||
auto MoveBlockEas() -> void;
|
||||
auto PrintState() const -> void;
|
||||
auto PreviousPreset() -> void;
|
||||
auto NextPreset() -> void;
|
||||
auto ResetState() -> void;
|
||||
auto FillGraph() -> void;
|
||||
auto ClearGraph() -> void;
|
||||
auto ToggleMarkSolutions() -> void;
|
||||
auto ToggleConnectSolutions() -> void;
|
||||
auto ToggleMarkPath() -> void;
|
||||
auto MakeOptimalMove() -> void;
|
||||
auto GoToWorstState() -> void;
|
||||
auto GoToNearestTarget() -> void;
|
||||
auto UndoLastMove() -> void;
|
||||
auto ToggleRestrictedMovement() -> void;
|
||||
auto ToggleTargetBlock() -> void;
|
||||
auto ToggleWallBlock() -> void;
|
||||
auto RemoveBoardColumn() -> void;
|
||||
auto AddBoardColumn() -> void;
|
||||
auto RemoveBoardRow() -> void;
|
||||
auto AddBoardRow() -> void;
|
||||
auto ToggleEditing() -> void;
|
||||
auto ClearGoal() -> void;
|
||||
// Key actions
|
||||
auto toggle_camera_lock() -> void;
|
||||
auto toggle_camera_mass_center_lock() -> void;
|
||||
auto toggle_camera_projection() const -> void;
|
||||
auto move_block_nor() -> void;
|
||||
auto move_block_wes() -> void;
|
||||
auto move_block_sou() -> void;
|
||||
auto move_block_eas() -> void;
|
||||
auto print_state() const -> void;
|
||||
auto load_previous_preset() -> void;
|
||||
auto load_next_preset() -> void;
|
||||
auto goto_starting_state() -> void;
|
||||
auto populate_graph() const -> void;
|
||||
auto clear_graph() const -> void;
|
||||
auto toggle_mark_solutions() -> void;
|
||||
auto toggle_connect_solutions() -> void;
|
||||
auto toggle_mark_path() -> void;
|
||||
auto goto_optimal_next_state() const -> void;
|
||||
auto goto_most_distant_state() const -> void;
|
||||
auto goto_closest_target_state() const -> void;
|
||||
auto goto_previous_state() const -> void;
|
||||
auto toggle_restricted_movement() const -> void;
|
||||
auto toggle_target_block() const -> void;
|
||||
auto toggle_wall_block() const -> void;
|
||||
auto remove_board_column() const -> void;
|
||||
auto add_board_column() const -> void;
|
||||
auto remove_board_row() const -> void;
|
||||
auto add_board_row() const -> void;
|
||||
auto toggle_editing() -> void;
|
||||
auto clear_goal() const -> void;
|
||||
|
||||
// General
|
||||
auto RegisterGenericHandler(std::function<void(InputHandler &)> handler)
|
||||
-> void;
|
||||
// General
|
||||
auto register_generic_handler(const std::function<void(input_handler&)>& handler) -> void;
|
||||
|
||||
auto RegisterMousePressedHandler(MouseButton button,
|
||||
std::function<void(InputHandler &)> handler)
|
||||
-> void;
|
||||
auto register_mouse_pressed_handler(MouseButton button, const std::function<void(input_handler&)>& handler) -> void;
|
||||
|
||||
auto RegisterMouseReleasedHandler(MouseButton button,
|
||||
std::function<void(InputHandler &)> handler)
|
||||
-> void;
|
||||
auto register_mouse_released_handler(MouseButton button, const std::function<void(input_handler&)>& handler)
|
||||
-> void;
|
||||
|
||||
auto RegisterKeyPressedHandler(KeyboardKey key,
|
||||
std::function<void(InputHandler &)> handler)
|
||||
-> void;
|
||||
auto register_key_pressed_handler(KeyboardKey key, const std::function<void(input_handler&)>& handler) -> void;
|
||||
|
||||
auto RegisterKeyReleasedHandler(KeyboardKey key,
|
||||
std::function<void(InputHandler &)> handler)
|
||||
-> void;
|
||||
auto register_key_released_handler(KeyboardKey key, const std::function<void(input_handler&)>& handler) -> void;
|
||||
|
||||
auto HandleInput() -> void;
|
||||
auto handle_input() -> void;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@ -1,54 +1,51 @@
|
||||
#ifndef __OCTREE_HPP_
|
||||
#define __OCTREE_HPP_
|
||||
|
||||
#include "config.hpp"
|
||||
#ifndef OCTREE_HPP_
|
||||
#define OCTREE_HPP_
|
||||
|
||||
#include <array>
|
||||
#include <raylib.h>
|
||||
#include <raymath.h>
|
||||
#include <vector>
|
||||
|
||||
class OctreeNode {
|
||||
public:
|
||||
Vector3 mass_center;
|
||||
float mass_total;
|
||||
Vector3 box_min; // area start
|
||||
Vector3 box_max; // area end
|
||||
int children[8];
|
||||
int mass_id;
|
||||
bool leaf;
|
||||
class octree
|
||||
{
|
||||
class node
|
||||
{
|
||||
public:
|
||||
Vector3 mass_center = Vector3Zero();
|
||||
float mass_total = 0.0;
|
||||
Vector3 box_min = Vector3Zero(); // area start
|
||||
Vector3 box_max = Vector3Zero(); // area end
|
||||
std::array<int, 8> children = {-1, -1, -1, -1, -1, -1, -1, -1};
|
||||
int mass_id = -1;
|
||||
bool leaf = true;
|
||||
|
||||
public:
|
||||
[[nodiscard]] auto child_count() const -> int;
|
||||
};
|
||||
|
||||
public:
|
||||
OctreeNode()
|
||||
: mass_center(Vector3Zero()), mass_total(0.0),
|
||||
children(-1, -1, -1, -1, -1, -1, -1, -1), mass_id(-1), leaf(true) {}
|
||||
static constexpr int MAX_DEPTH = 20;
|
||||
|
||||
std::vector<node> nodes;
|
||||
|
||||
public:
|
||||
auto ChildCount() const -> int;
|
||||
};
|
||||
octree() = default;
|
||||
|
||||
class Octree {
|
||||
public:
|
||||
std::vector<OctreeNode> nodes;
|
||||
octree(const octree& copy) = delete;
|
||||
auto operator=(const octree& copy) -> octree& = delete;
|
||||
octree(octree&& move) = delete;
|
||||
auto operator=(octree&& move) -> octree& = delete;
|
||||
|
||||
public:
|
||||
Octree() {}
|
||||
auto create_empty_leaf(const Vector3& box_min, const Vector3& box_max) -> int;
|
||||
|
||||
Octree(const Octree ©) = delete;
|
||||
Octree &operator=(const Octree ©) = delete;
|
||||
Octree(Octree &&move) = delete;
|
||||
Octree &operator=(Octree &&move) = delete;
|
||||
[[nodiscard]] auto get_octant(int node_idx, const Vector3& pos) const -> int;
|
||||
|
||||
public:
|
||||
auto CreateNode(const Vector3 &box_min, const Vector3 &box_max) -> int;
|
||||
[[nodiscard]] auto get_child_bounds(int node_idx, int octant) const -> std::pair<Vector3, Vector3>;
|
||||
|
||||
auto GetOctant(int node_idx, const Vector3 &pos) -> int;
|
||||
auto insert(int node_idx, int mass_id, const Vector3& pos, float mass, int depth) -> void;
|
||||
|
||||
auto GetChildBounds(int node_idx, int octant) -> std::pair<Vector3, Vector3>;
|
||||
|
||||
auto Insert(int node_idx, int mass_id, const Vector3 &pos, float mass)
|
||||
-> void;
|
||||
|
||||
auto CalculateForce(int node_idx, const Vector3 &pos) const -> Vector3;
|
||||
[[nodiscard]] auto calculate_force(int node_idx, const Vector3& pos) const -> Vector3;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@ -1,12 +1,11 @@
|
||||
#ifndef __PHYSICS_HPP_
|
||||
#define __PHYSICS_HPP_
|
||||
#ifndef PHYSICS_HPP_
|
||||
#define PHYSICS_HPP_
|
||||
|
||||
#include "config.hpp"
|
||||
#include "octree.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <cstddef>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <raylib.h>
|
||||
@ -15,190 +14,198 @@
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "util.hpp"
|
||||
|
||||
#ifdef THREADPOOL
|
||||
#if defined(_WIN32)
|
||||
#define NOGDI // All GDI defines and routines
|
||||
#define NOUSER // All USER defines and routines
|
||||
#endif
|
||||
#define BS_THREAD_POOL_NATIVE_EXTENSIONS
|
||||
#include <BS_thread_pool.hpp>
|
||||
#if defined(_WIN32) // raylib uses these names as function parameters
|
||||
#undef near
|
||||
#undef far
|
||||
#endif
|
||||
#if defined(_WIN32)
|
||||
#define NOGDI // All GDI defines and routines
|
||||
#define NOUSER // All USER defines and routines
|
||||
#endif
|
||||
#define BS_THREAD_POOL_NATIVE_EXTENSIONS
|
||||
#include <BS_thread_pool.hpp>
|
||||
#if defined(_WIN32) // raylib uses these names as function parameters
|
||||
#undef near
|
||||
#undef far
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef TRACY
|
||||
#include <tracy/Tracy.hpp>
|
||||
#include <tracy/Tracy.hpp>
|
||||
#endif
|
||||
|
||||
class Mass {
|
||||
class mass
|
||||
{
|
||||
public:
|
||||
Vector3 position;
|
||||
Vector3 previous_position; // for verlet integration
|
||||
Vector3 velocity;
|
||||
Vector3 force;
|
||||
Vector3 position = Vector3Zero();
|
||||
Vector3 previous_position = Vector3Zero(); // for verlet integration
|
||||
Vector3 velocity = Vector3Zero();
|
||||
Vector3 force = Vector3Zero();
|
||||
|
||||
public:
|
||||
Mass(Vector3 _position)
|
||||
: position(_position), previous_position(_position),
|
||||
velocity(Vector3Zero()), force(Vector3Zero()) {}
|
||||
mass() = delete;
|
||||
|
||||
explicit mass(const Vector3 _position) : position(_position), previous_position(_position)
|
||||
{}
|
||||
|
||||
public:
|
||||
auto ClearForce() -> void;
|
||||
|
||||
auto CalculateVelocity(const float delta_time) -> void;
|
||||
|
||||
auto CalculatePosition(const float delta_time) -> void;
|
||||
|
||||
auto VerletUpdate(const float delta_time) -> void;
|
||||
auto clear_force() -> void;
|
||||
auto calculate_velocity(float delta_time) -> void;
|
||||
auto calculate_position(float delta_time) -> void;
|
||||
auto verlet_update(float delta_time) -> void;
|
||||
};
|
||||
|
||||
class Spring {
|
||||
class spring
|
||||
{
|
||||
public:
|
||||
std::size_t a;
|
||||
std::size_t b;
|
||||
size_t a;
|
||||
size_t b;
|
||||
|
||||
public:
|
||||
Spring(std::size_t _a, std::size_t _b) : a(_a), b(_b) {}
|
||||
spring(const size_t _a, const size_t _b) : a(_a), b(_b)
|
||||
{}
|
||||
|
||||
public:
|
||||
auto CalculateSpringForce(Mass &_a, Mass &_b) const -> void;
|
||||
static auto calculate_spring_force(mass& _a, mass& _b) -> void;
|
||||
};
|
||||
|
||||
class MassSpringSystem {
|
||||
class mass_spring_system
|
||||
{
|
||||
private:
|
||||
#ifdef THREADPOOL
|
||||
BS::thread_pool<BS::tp::none> threads;
|
||||
BS::thread_pool<> threads;
|
||||
#endif
|
||||
|
||||
public:
|
||||
Octree octree;
|
||||
octree tree;
|
||||
|
||||
// This is the main ownership of all the states/masses/springs.
|
||||
std::vector<Mass> masses;
|
||||
std::vector<Spring> springs;
|
||||
// This is the main ownership of all the states/masses/springs.
|
||||
std::vector<mass> masses;
|
||||
std::vector<spring> springs;
|
||||
|
||||
public:
|
||||
MassSpringSystem()
|
||||
mass_spring_system()
|
||||
#ifdef THREADPOOL
|
||||
: threads(std::thread::hardware_concurrency() - 1, SetThreadName)
|
||||
: threads(std::thread::hardware_concurrency() - 1, set_thread_name)
|
||||
#endif
|
||||
{
|
||||
std::cout << std::format(
|
||||
"Using Barnes-Hut + Octree repulsion force calculation.")
|
||||
<< std::endl;
|
||||
{
|
||||
infoln("Using Barnes-Hut + Octree repulsion force calculation.");
|
||||
|
||||
#ifdef THREADPOOL
|
||||
std::cout << std::format("Thread-pool: {} threads.",
|
||||
threads.get_thread_count())
|
||||
<< std::endl;
|
||||
infoln("Thread-pool: {} threads.", threads.get_thread_count());
|
||||
#else
|
||||
std::cout << std::format("Thread-pool: Disabled.") << std::endl;
|
||||
infoln("Thread-pool: Disabled.");
|
||||
#endif
|
||||
};
|
||||
}
|
||||
|
||||
MassSpringSystem(const MassSpringSystem ©) = delete;
|
||||
MassSpringSystem &operator=(const MassSpringSystem ©) = delete;
|
||||
MassSpringSystem(MassSpringSystem &move) = delete;
|
||||
MassSpringSystem &operator=(MassSpringSystem &&move) = delete;
|
||||
mass_spring_system(const mass_spring_system& copy) = delete;
|
||||
auto operator=(const mass_spring_system& copy) -> mass_spring_system& = delete;
|
||||
mass_spring_system(mass_spring_system& move) = delete;
|
||||
auto operator=(mass_spring_system&& move) -> mass_spring_system& = delete;
|
||||
|
||||
private:
|
||||
#ifdef THREADPOOL
|
||||
static auto SetThreadName(std::size_t idx) -> void;
|
||||
static auto set_thread_name(size_t idx) -> void;
|
||||
#endif
|
||||
|
||||
auto BuildOctree() -> void;
|
||||
auto build_octree() -> void;
|
||||
|
||||
public:
|
||||
auto AddMass() -> void;
|
||||
auto clear() -> void;
|
||||
auto add_mass() -> void;
|
||||
auto add_spring(size_t a, size_t b) -> void;
|
||||
|
||||
auto AddSpring(int a, int b) -> void;
|
||||
auto clear_forces() -> void;
|
||||
auto calculate_spring_forces() -> void;
|
||||
auto calculate_repulsion_forces() -> void;
|
||||
auto verlet_update(float delta_time) -> void;
|
||||
|
||||
auto Clear() -> void;
|
||||
|
||||
auto ClearForces() -> void;
|
||||
|
||||
auto CalculateSpringForces() -> void;
|
||||
|
||||
auto CalculateRepulsionForces() -> void;
|
||||
|
||||
auto VerletUpdate(float delta_time) -> void;
|
||||
auto center_masses() -> void;
|
||||
};
|
||||
|
||||
class ThreadedPhysics {
|
||||
struct AddMass {};
|
||||
struct AddSpring {
|
||||
std::size_t a;
|
||||
std::size_t b;
|
||||
};
|
||||
struct ClearGraph {};
|
||||
class threaded_physics
|
||||
{
|
||||
struct add_mass
|
||||
{};
|
||||
|
||||
using Command = std::variant<AddMass, AddSpring, ClearGraph>;
|
||||
struct add_spring
|
||||
{
|
||||
size_t a;
|
||||
size_t b;
|
||||
};
|
||||
|
||||
struct PhysicsState {
|
||||
struct clear_graph
|
||||
{};
|
||||
|
||||
using command = std::variant<add_mass, add_spring, clear_graph>;
|
||||
|
||||
struct physics_state
|
||||
{
|
||||
#ifdef TRACY
|
||||
TracyLockable(std::mutex, command_mtx);
|
||||
TracyLockable(std::mutex, command_mtx);
|
||||
#else
|
||||
std::mutex command_mtx;
|
||||
std::mutex command_mtx;
|
||||
#endif
|
||||
std::queue<Command> pending_commands;
|
||||
std::queue<command> pending_commands;
|
||||
|
||||
#ifdef TRACY
|
||||
TracyLockable(std::mutex, data_mtx);
|
||||
TracyLockable(std::mutex, data_mtx);
|
||||
#else
|
||||
std::mutex data_mtx;
|
||||
std::mutex data_mtx;
|
||||
#endif
|
||||
std::condition_variable_any data_ready_cnd;
|
||||
std::condition_variable_any data_consumed_cnd;
|
||||
Vector3 mass_center = Vector3Zero();
|
||||
unsigned int ups = 0;
|
||||
std::vector<Vector3> masses; // Read by renderer
|
||||
bool data_ready = false;
|
||||
bool data_consumed = true;
|
||||
std::condition_variable_any data_ready_cnd;
|
||||
std::condition_variable_any data_consumed_cnd;
|
||||
Vector3 mass_center = Vector3Zero();
|
||||
int ups = 0;
|
||||
size_t mass_count = 0; // For debug
|
||||
size_t spring_count = 0; // For debug
|
||||
std::vector<Vector3> masses; // Read by renderer
|
||||
bool data_ready = false;
|
||||
bool data_consumed = true;
|
||||
|
||||
std::atomic<bool> running{true};
|
||||
};
|
||||
std::atomic<bool> running{true};
|
||||
};
|
||||
|
||||
private:
|
||||
std::thread physics;
|
||||
std::thread physics;
|
||||
|
||||
public:
|
||||
PhysicsState state;
|
||||
physics_state state;
|
||||
|
||||
public:
|
||||
ThreadedPhysics() : physics(PhysicsThread, std::ref(state)) {}
|
||||
threaded_physics() : physics(physics_thread, std::ref(state))
|
||||
{}
|
||||
|
||||
ThreadedPhysics(const ThreadedPhysics ©) = delete;
|
||||
ThreadedPhysics &operator=(const ThreadedPhysics ©) = delete;
|
||||
ThreadedPhysics(ThreadedPhysics &&move) = delete;
|
||||
ThreadedPhysics &operator=(ThreadedPhysics &&move) = delete;
|
||||
threaded_physics(const threaded_physics& copy) = delete;
|
||||
auto operator=(const threaded_physics& copy) -> threaded_physics& = delete;
|
||||
threaded_physics(threaded_physics&& move) = delete;
|
||||
auto operator=(threaded_physics&& move) -> threaded_physics& = delete;
|
||||
|
||||
~ThreadedPhysics() {
|
||||
state.running = false;
|
||||
state.data_ready_cnd.notify_all();
|
||||
state.data_consumed_cnd.notify_all();
|
||||
physics.join();
|
||||
}
|
||||
~threaded_physics()
|
||||
{
|
||||
state.running = false;
|
||||
state.data_ready_cnd.notify_all();
|
||||
state.data_consumed_cnd.notify_all();
|
||||
physics.join();
|
||||
}
|
||||
|
||||
private:
|
||||
static auto PhysicsThread(PhysicsState &state) -> void;
|
||||
static auto physics_thread(physics_state& state) -> void;
|
||||
|
||||
public:
|
||||
auto AddMassCmd() -> void;
|
||||
auto add_mass_cmd() -> void;
|
||||
|
||||
auto AddSpringCmd(std::size_t a, std::size_t b) -> void;
|
||||
auto add_spring_cmd(size_t a, size_t b) -> void;
|
||||
|
||||
auto ClearCmd() -> void;
|
||||
auto clear_cmd() -> void;
|
||||
|
||||
auto AddMassSpringsCmd(
|
||||
std::size_t num_masses,
|
||||
const std::vector<std::pair<std::size_t, std::size_t>> &springs) -> 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()...;
|
||||
template <class... Ts>
|
||||
struct overloads : Ts...
|
||||
{
|
||||
using Ts::operator()...;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@ -1,128 +1,21 @@
|
||||
#ifndef __PUZZLE_HPP_
|
||||
#define __PUZZLE_HPP_
|
||||
#ifndef PUZZLE_HPP_
|
||||
#define PUZZLE_HPP_
|
||||
|
||||
#include "config.hpp"
|
||||
#include "util.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <format>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
enum Direction {
|
||||
NOR = 1 << 0,
|
||||
EAS = 1 << 1,
|
||||
SOU = 1 << 2,
|
||||
WES = 1 << 3,
|
||||
};
|
||||
|
||||
// A block is represented as a 2-digit string "wh", where w is the block width
|
||||
// and h the block height.
|
||||
// The target block (to remove from the board) is represented as a 2-letter
|
||||
// lower-case string "xy", where x is the block width and y the block height and
|
||||
// width/height are represented by [abcdefghi] (~= [123456789]).
|
||||
// Immovable blocks are represented as a 2-letter upper-case string "XY", where
|
||||
// X is the block width and Y the block height and width/height are represented
|
||||
// by [ABCDEFGHI] (~= [123456789]).
|
||||
class Block {
|
||||
public:
|
||||
int x;
|
||||
int y;
|
||||
int width;
|
||||
int height;
|
||||
bool target;
|
||||
bool immovable;
|
||||
|
||||
public:
|
||||
Block(int _x, int _y, int _width, int _height, bool _target, bool _immovable)
|
||||
: x(_x), y(_y), width(_width), height(_height), target(_target),
|
||||
immovable(_immovable) {
|
||||
if (_x < 0 || _x + _width >= 10 || _y < 0 || _y + _height >= 10) {
|
||||
std::cout << std::format("Block must fit in a 9x9 board!") << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
Block(int _x, int _y, int _width, int _height, bool _target)
|
||||
: Block(_x, _y, _width, _height, _target, false) {}
|
||||
|
||||
Block(int _x, int _y, std::string block) : x(_x), y(_y) {
|
||||
if (block == "..") {
|
||||
this->x = 0;
|
||||
this->y = 0;
|
||||
width = 0;
|
||||
height = 0;
|
||||
target = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const std::array<char, 9> target_chars{'a', 'b', 'c', 'd', 'e',
|
||||
'f', 'g', 'h', 'i'};
|
||||
|
||||
target = false;
|
||||
for (const char c : target_chars) {
|
||||
if (block.contains(c)) {
|
||||
target = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const std::array<char, 9> immovable_chars{'A', 'B', 'C', 'D', 'E',
|
||||
'F', 'G', 'H', 'I'};
|
||||
|
||||
immovable = false;
|
||||
for (const char c : immovable_chars) {
|
||||
if (block.contains(c)) {
|
||||
immovable = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (target) {
|
||||
width = static_cast<int>(block.at(0)) - static_cast<int>('a') + 1;
|
||||
height = static_cast<int>(block.at(1)) - static_cast<int>('a') + 1;
|
||||
} else if (immovable) {
|
||||
width = static_cast<int>(block.at(0)) - static_cast<int>('A') + 1;
|
||||
height = static_cast<int>(block.at(1)) - static_cast<int>('A') + 1;
|
||||
} else {
|
||||
width = std::stoi(block.substr(0, 1));
|
||||
height = std::stoi(block.substr(1, 1));
|
||||
}
|
||||
|
||||
if (_x < 0 || _x + width >= 10 || _y < 0 || _y + height >= 10) {
|
||||
std::cout << std::format("Block must fit in a 9x9 board!") << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
if (block.length() != 2) {
|
||||
std::cout << std::format("Block representation must have length 2!")
|
||||
<< std::endl;
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
bool operator==(const Block &other) {
|
||||
return x == other.x && y == other.y && width && other.width &&
|
||||
target == other.target && immovable == other.immovable;
|
||||
}
|
||||
|
||||
bool operator!=(const Block &other) { return !(*this == other); }
|
||||
|
||||
public:
|
||||
auto Hash() const -> std::size_t;
|
||||
|
||||
static auto Invalid() -> Block;
|
||||
|
||||
auto IsValid() const -> bool;
|
||||
|
||||
auto ToString() const -> std::string;
|
||||
|
||||
auto GetPrincipalDirs() const -> int;
|
||||
|
||||
auto Covers(int xx, int yy) const -> bool;
|
||||
|
||||
auto Collides(const Block &other) const -> bool;
|
||||
enum direction
|
||||
{
|
||||
nor = 1 << 0,
|
||||
eas = 1 << 1,
|
||||
sou = 1 << 2,
|
||||
wes = 1 << 3,
|
||||
};
|
||||
|
||||
// A state is represented by a string "MWHXYblocks", where M is "R"
|
||||
@ -133,189 +26,319 @@ public:
|
||||
// representation with length 5 + 3*3 * 2). The board's cells are enumerated
|
||||
// from top-left to bottom-right with each block's pivot being its top-left
|
||||
// corner.
|
||||
class State {
|
||||
class puzzle
|
||||
{
|
||||
public:
|
||||
static constexpr int prefix = 5;
|
||||
// A block is represented as a 2-digit string "wh", where w is the block width
|
||||
// and h the block height.
|
||||
// The target block (to remove from the board) is represented as a 2-letter
|
||||
// lower-case string "xy", where x is the block width and y the block height and
|
||||
// width/height are represented by [abcdefghi] (~= [123456789]).
|
||||
// Immovable blocks are represented as a 2-letter upper-case string "XY", where
|
||||
// X is the block width and Y the block height and width/height are represented
|
||||
// by [ABCDEFGHI] (~= [123456789]).
|
||||
class block
|
||||
{
|
||||
public:
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
bool target = false;
|
||||
bool immovable = false;
|
||||
|
||||
int width;
|
||||
int height;
|
||||
int target_x;
|
||||
int target_y;
|
||||
bool restricted; // Only allow blocks to move in their principal direction
|
||||
std::string state;
|
||||
public:
|
||||
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) {
|
||||
errln("Block must fit in a 9x9 board!");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// https://en.cppreference.com/w/cpp/iterator/input_iterator.html
|
||||
class BlockIterator {
|
||||
public:
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using value_type = Block;
|
||||
block(const int _x, const int _y, const std::string& b) : x(_x), y(_y)
|
||||
{
|
||||
if (b.length() != 2) {
|
||||
errln("Block representation must have length 2!");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
private:
|
||||
const State &state;
|
||||
int current_pos;
|
||||
if (b == "..") {
|
||||
this->x = 0;
|
||||
this->y = 0;
|
||||
width = 0;
|
||||
height = 0;
|
||||
target = false;
|
||||
return;
|
||||
}
|
||||
|
||||
public:
|
||||
BlockIterator(const State &_state) : state(_state), current_pos(0) {}
|
||||
target = false;
|
||||
constexpr std::array<char, 9> target_chars{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'};
|
||||
for (const char c : target_chars) {
|
||||
if (b.contains(c)) {
|
||||
target = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
BlockIterator(const State &_state, int _current_pos)
|
||||
: state(_state), current_pos(_current_pos) {}
|
||||
immovable = false;
|
||||
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;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Block operator*() const {
|
||||
return Block(current_pos % state.width, current_pos / state.width,
|
||||
state.state.substr(current_pos * 2 + prefix, 2));
|
||||
}
|
||||
if (target) {
|
||||
width = static_cast<int>(b.at(0)) - static_cast<int>('a') + 1;
|
||||
height = static_cast<int>(b.at(1)) - static_cast<int>('a') + 1;
|
||||
} else if (immovable) {
|
||||
width = static_cast<int>(b.at(0)) - static_cast<int>('A') + 1;
|
||||
height = static_cast<int>(b.at(1)) - static_cast<int>('A') + 1;
|
||||
} else {
|
||||
// println("Parsing block string '{}' at ({}, {})", b, _x, _y);
|
||||
width = std::stoi(b.substr(0, 1));
|
||||
height = std::stoi(b.substr(1, 1));
|
||||
}
|
||||
|
||||
BlockIterator &operator++() {
|
||||
do {
|
||||
current_pos++;
|
||||
} while (state.state.substr(current_pos * 2 + prefix, 2) == "..");
|
||||
return *this;
|
||||
}
|
||||
if (_x < 0 || _x + width > 9 || _y < 0 || _y + height > 9) {
|
||||
errln("Block must fit in a 9x9 board!");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
bool operator==(const BlockIterator &other) {
|
||||
return state == other.state && current_pos == other.current_pos;
|
||||
}
|
||||
block() = delete;
|
||||
|
||||
bool operator!=(const BlockIterator &other) { return !(*this == other); }
|
||||
};
|
||||
auto operator==(const block& other) const -> bool
|
||||
{
|
||||
return x == other.x && y == other.y && width && other.width && target == other.target &&
|
||||
immovable == other.immovable;
|
||||
}
|
||||
|
||||
auto operator!=(const block& other) const -> bool
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
public:
|
||||
[[nodiscard]] auto hash() const -> size_t;
|
||||
[[nodiscard]] auto valid() const -> bool;
|
||||
[[nodiscard]] auto string() const -> std::string;
|
||||
|
||||
// Movement
|
||||
|
||||
[[nodiscard]] auto principal_dirs() const -> int;
|
||||
[[nodiscard]] auto covers(int _x, int _y) const -> bool;
|
||||
[[nodiscard]] auto collides(const block& b) const -> bool;
|
||||
};
|
||||
|
||||
private:
|
||||
// https://en.cppreference.com/w/cpp/iterator/input_iterator.html
|
||||
class block_iterator
|
||||
{
|
||||
public:
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using value_type = block;
|
||||
|
||||
private:
|
||||
const puzzle& state;
|
||||
int current_pos;
|
||||
|
||||
public:
|
||||
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)
|
||||
{}
|
||||
|
||||
auto operator*() const -> block
|
||||
{
|
||||
return {current_pos % state.width, current_pos / state.width,
|
||||
state.state.substr(current_pos * 2 + PREFIX, 2)};
|
||||
}
|
||||
|
||||
auto operator++() -> block_iterator&
|
||||
{
|
||||
do {
|
||||
current_pos++;
|
||||
} while (current_pos < state.width * state.height &&
|
||||
state.state.substr(current_pos * 2 + PREFIX, 2) == "..");
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto operator==(const block_iterator& other) const -> bool
|
||||
{
|
||||
return state == other.state && current_pos == other.current_pos;
|
||||
}
|
||||
|
||||
auto operator!=(const block_iterator& other) const -> bool
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
State(int _width, int _height, int _target_x, int _target_y, bool _restricted)
|
||||
: width(_width), height(_height), target_x(_target_x),
|
||||
target_y(_target_y), restricted(_restricted),
|
||||
state(std::format("{}{}{}{}{}{}", _restricted ? "R" : "F", _width,
|
||||
_height, _target_x, _target_y,
|
||||
std::string(_width * _height * 2, '.'))) {
|
||||
if (_width < 1 || _width > 9 || _height < 1 || _height > 9) {
|
||||
std::cout << std::format("State width/height must be in [1, 9]!")
|
||||
<< std::endl;
|
||||
exit(1);
|
||||
}
|
||||
if (_target_x < 0 || _target_x >= 9 || _target_y < 0 || _target_y >= 9) {
|
||||
if (_target_x != 9 && _target_y != 9) {
|
||||
std::cout << std::format(
|
||||
"State target must be within the board bounds!")
|
||||
<< std::endl;
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
static constexpr int PREFIX = 5;
|
||||
static constexpr int MIN_WIDTH = 3;
|
||||
static constexpr int MIN_HEIGHT = 3;
|
||||
static constexpr int MAX_WIDTH = 9;
|
||||
static constexpr int MAX_HEIGHT = 9;
|
||||
|
||||
State(int _width, int _height, bool _restricted)
|
||||
: State(_width, _height, 9, 9, _restricted) {}
|
||||
|
||||
State() : State(4, 5, 9, 9, false) {}
|
||||
|
||||
explicit State(std::string _state)
|
||||
: width(std::stoi(_state.substr(1, 1))),
|
||||
height(std::stoi(_state.substr(2, 1))),
|
||||
target_x(std::stoi(_state.substr(3, 1))),
|
||||
target_y(std::stoi(_state.substr(4, 1))),
|
||||
restricted(_state.substr(0, 1) == "R"), state(_state) {
|
||||
if (width < 1 || width > 9 || height < 1 || height > 9) {
|
||||
std::cout << std::format("State width/height must be in [1, 9]!")
|
||||
<< std::endl;
|
||||
exit(1);
|
||||
}
|
||||
if (target_x < 0 || target_x >= 9 || target_y < 0 || target_y >= 9) {
|
||||
if (target_x != 9 && target_y != 9) {
|
||||
std::cout << std::format(
|
||||
"State target must be within the board bounds!")
|
||||
<< std::endl;
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
if (static_cast<int>(_state.length()) != width * height * 2 + prefix) {
|
||||
std::cout << std::format("State representation must have length width * "
|
||||
"height * 2 + {}!",
|
||||
prefix)
|
||||
<< std::endl;
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
bool operator==(const State &other) const { return state == other.state; }
|
||||
|
||||
bool operator!=(const State &other) const { return !(*this == other); }
|
||||
|
||||
BlockIterator begin() const {
|
||||
BlockIterator it = BlockIterator(*this);
|
||||
if (!(*it).IsValid()) {
|
||||
++it;
|
||||
}
|
||||
return it;
|
||||
}
|
||||
|
||||
BlockIterator end() const { return BlockIterator(*this, width * height); }
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
int target_x = 9;
|
||||
int target_y = 9;
|
||||
bool restricted = false; // Only allow blocks to move in their principal direction
|
||||
std::string state;
|
||||
|
||||
public:
|
||||
auto Hash() const -> std::size_t;
|
||||
puzzle() = delete;
|
||||
|
||||
auto HasWinCondition() const -> bool;
|
||||
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, '.')))
|
||||
{
|
||||
if (w < 1 || w > 9 || h < 1 || h > 9) {
|
||||
errln("State width/height must be in [1, 9]!");
|
||||
exit(1);
|
||||
}
|
||||
if (tx < 0 || tx >= 9 || ty < 0 || ty >= 9) {
|
||||
if (tx != 9 && ty != 9) {
|
||||
errln("State target must be within the board bounds!");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto IsWon() const -> bool;
|
||||
puzzle(const int w, const int h, const bool r) : puzzle(w, h, 9, 9, r)
|
||||
{}
|
||||
|
||||
auto SetGoal(int x, int y) -> bool;
|
||||
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)
|
||||
{
|
||||
if (width < 1 || width > 9 || height < 1 || height > 9) {
|
||||
errln("State width/height must be in [1, 9]!");
|
||||
exit(1);
|
||||
}
|
||||
if (target_x < 0 || target_x >= 9 || target_y < 0 || target_y >= 9) {
|
||||
if (target_x != 9 && target_y != 9) {
|
||||
errln("State target must be within the board bounds!");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
if (static_cast<int>(s.length()) != width * height * 2 + PREFIX) {
|
||||
errln("State representation must have length width * "
|
||||
"height * 2 + {}!",
|
||||
PREFIX);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
auto ClearGoal() -> void;
|
||||
public:
|
||||
auto operator==(const puzzle& other) const -> bool
|
||||
{
|
||||
return state == other.state;
|
||||
}
|
||||
|
||||
auto AddColumn() const -> State;
|
||||
auto operator!=(const puzzle& other) const -> bool
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
auto RemoveColumn() const -> State;
|
||||
[[nodiscard]] auto begin() const -> block_iterator
|
||||
{
|
||||
block_iterator it{*this};
|
||||
if (!(*it).valid()) {
|
||||
++it;
|
||||
}
|
||||
return it;
|
||||
}
|
||||
|
||||
auto AddRow() const -> State;
|
||||
[[nodiscard]] auto end() const -> block_iterator
|
||||
{
|
||||
return {*this, width * height};
|
||||
}
|
||||
|
||||
auto RemoveRow() const -> State;
|
||||
private:
|
||||
[[nodiscard]] auto get_index(int x, int y) const -> int;
|
||||
|
||||
auto AddBlock(const Block &block) -> bool;
|
||||
public:
|
||||
[[nodiscard]] auto hash() const -> size_t;
|
||||
[[nodiscard]] auto has_win_condition() const -> bool;
|
||||
[[nodiscard]] auto won() const -> bool;
|
||||
[[nodiscard]] auto valid() const -> bool;
|
||||
[[nodiscard]] auto valid_thorough() const -> bool;
|
||||
|
||||
auto GetBlock(int x, int y) const -> Block;
|
||||
// Repr helpers
|
||||
|
||||
auto GetBlockAt(int x, int y) const -> std::string;
|
||||
[[nodiscard]] auto try_get_block(int x, int y) const -> std::optional<block>;
|
||||
[[nodiscard]] auto try_get_target_block() const -> std::optional<block>;
|
||||
[[nodiscard]] auto covers(int x, int y, int w, int h) const -> bool;
|
||||
[[nodiscard]] auto covers(int x, int y) const -> bool;
|
||||
[[nodiscard]] auto covers(const block& b) const -> bool;
|
||||
|
||||
auto GetTargetBlock() const -> Block;
|
||||
// Editing
|
||||
|
||||
auto GetIndex(int x, int y) const -> int;
|
||||
[[nodiscard]] auto try_toggle_restricted() const -> std::optional<puzzle>;
|
||||
[[nodiscard]] auto try_set_goal(int x, int y) const -> std::optional<puzzle>;
|
||||
[[nodiscard]] auto try_clear_goal() const -> std::optional<puzzle>;
|
||||
[[nodiscard]] auto try_add_column() const -> std::optional<puzzle>;
|
||||
[[nodiscard]] auto try_remove_column() const -> std::optional<puzzle>;
|
||||
[[nodiscard]] auto try_add_row() const -> std::optional<puzzle>;
|
||||
[[nodiscard]] auto try_remove_row() const -> std::optional<puzzle>;
|
||||
[[nodiscard]] auto try_add_block(const block& b) const -> std::optional<puzzle>;
|
||||
[[nodiscard]] auto try_remove_block(int x, int y) const -> std::optional<puzzle>;
|
||||
[[nodiscard]] auto try_toggle_target(int x, int y) const -> std::optional<puzzle>;
|
||||
[[nodiscard]] auto try_toggle_wall(int x, int y) const -> std::optional<puzzle>;
|
||||
|
||||
auto RemoveBlock(int x, int y) -> bool;
|
||||
// Playing
|
||||
|
||||
auto ToggleTarget(int x, int y) -> bool;
|
||||
[[nodiscard]] auto try_move_block_at(int x, int y, direction dir) const -> std::optional<puzzle>;
|
||||
|
||||
auto ToggleWall(int x, int y) -> bool;
|
||||
// Statespace
|
||||
|
||||
auto ToggleRestricted() -> void;
|
||||
|
||||
auto MoveBlockAt(int x, int y, Direction dir) -> bool;
|
||||
|
||||
auto GetNextStates() const -> std::vector<State>;
|
||||
|
||||
auto Closure() const
|
||||
-> std::pair<std::vector<State>,
|
||||
std::vector<std::pair<std::size_t, std::size_t>>>;
|
||||
[[nodiscard]] auto find_adjacent_puzzles() const -> std::vector<puzzle>;
|
||||
[[nodiscard]] auto explore_state_space() const
|
||||
-> std::pair<std::vector<puzzle>, std::vector<std::pair<size_t, size_t>>>;
|
||||
};
|
||||
|
||||
// Provide hash functions so we can use State and <State, State> as hash-set
|
||||
// keys for masses and springs.
|
||||
template <> struct std::hash<State> {
|
||||
std::size_t operator()(const State &s) const noexcept { return s.Hash(); }
|
||||
template <>
|
||||
struct std::hash<puzzle>
|
||||
{
|
||||
auto operator()(const puzzle& s) const noexcept -> size_t
|
||||
{
|
||||
return s.hash();
|
||||
}
|
||||
};
|
||||
|
||||
template <> struct std::hash<std::pair<State, State>> {
|
||||
std::size_t operator()(const std::pair<State, State> &s) const noexcept {
|
||||
auto h1 = std::hash<State>{}(s.first);
|
||||
auto h2 = std::hash<State>{}(s.second);
|
||||
return h1 + h2 + (h1 * h2);
|
||||
}
|
||||
template <>
|
||||
struct std::hash<std::pair<puzzle, puzzle>>
|
||||
{
|
||||
auto operator()(const std::pair<puzzle, puzzle>& s) const noexcept -> size_t
|
||||
{
|
||||
const size_t h1 = std::hash<puzzle>{}(s.first);
|
||||
const size_t h2 = std::hash<puzzle>{}(s.second);
|
||||
|
||||
return h1 + h2 + h1 * h2;
|
||||
// return (h1 ^ h2) + 0x9e3779b9 + (std::min(h1, h2) << 6) + (std::max(h1, h2) >> 2);
|
||||
}
|
||||
};
|
||||
|
||||
template <> struct std::equal_to<std::pair<State, State>> {
|
||||
bool operator()(const std::pair<State, State> &a,
|
||||
const std::pair<State, State> &b) const noexcept {
|
||||
return (a.first == b.first && a.second == b.second) ||
|
||||
(a.first == b.second && a.second == b.first);
|
||||
}
|
||||
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
|
||||
{
|
||||
return (a.first == b.first && a.second == b.second) || (a.first == b.second && a.second == b.first);
|
||||
}
|
||||
};
|
||||
|
||||
using WinCondition = std::function<bool(const State &)>;
|
||||
using win_condition = std::function<bool(const puzzle&)>;
|
||||
|
||||
#endif
|
||||
|
||||
@ -1,80 +1,100 @@
|
||||
#ifndef __RENDERER_HPP_
|
||||
#define __RENDERER_HPP_
|
||||
#ifndef RENDERER_HPP_
|
||||
#define RENDERER_HPP_
|
||||
|
||||
#include "camera.hpp"
|
||||
#include "config.hpp"
|
||||
#include "gui.hpp"
|
||||
#include "input.hpp"
|
||||
#include "state.hpp"
|
||||
#include "state_manager.hpp"
|
||||
#include "user_interface.hpp"
|
||||
|
||||
#include <raylib.h>
|
||||
#include <raymath.h>
|
||||
#include <rlgl.h>
|
||||
|
||||
class Renderer {
|
||||
class renderer
|
||||
{
|
||||
private:
|
||||
const StateManager &state;
|
||||
const InputHandler &input;
|
||||
Gui &gui;
|
||||
const state_manager& state;
|
||||
const input_handler& input;
|
||||
user_interface& gui;
|
||||
|
||||
const OrbitCamera3D &camera;
|
||||
RenderTexture render_target;
|
||||
RenderTexture klotski_target;
|
||||
RenderTexture menu_target;
|
||||
const orbit_camera& camera;
|
||||
RenderTexture render_target = LoadRenderTexture(GetScreenWidth() / 2, GetScreenHeight() - MENU_HEIGHT);
|
||||
RenderTexture klotski_target = LoadRenderTexture(GetScreenWidth() / 2, GetScreenHeight() - MENU_HEIGHT);
|
||||
RenderTexture menu_target = LoadRenderTexture(GetScreenWidth(), MENU_HEIGHT);
|
||||
|
||||
// Instancing
|
||||
Material vertex_mat;
|
||||
std::size_t transforms_size = 0;
|
||||
Matrix *transforms = nullptr;
|
||||
Mesh cube_instance;
|
||||
Shader instancing_shader;
|
||||
|
||||
public:
|
||||
Renderer(const OrbitCamera3D &_camera, const StateManager &_state,
|
||||
const InputHandler &_input, Gui &_gui)
|
||||
: state(_state), input(_input), gui(_gui), camera(_camera) {
|
||||
render_target = LoadRenderTexture(GetScreenWidth() / 2.0,
|
||||
GetScreenHeight() - MENU_HEIGHT);
|
||||
klotski_target = LoadRenderTexture(GetScreenWidth() / 2.0,
|
||||
GetScreenHeight() - MENU_HEIGHT);
|
||||
menu_target = LoadRenderTexture(GetScreenWidth(), MENU_HEIGHT);
|
||||
}
|
||||
|
||||
Renderer(const Renderer ©) = delete;
|
||||
Renderer &operator=(const Renderer ©) = delete;
|
||||
Renderer(Renderer &&move) = delete;
|
||||
Renderer &operator=(Renderer &&move) = delete;
|
||||
|
||||
~Renderer() {
|
||||
UnloadRenderTexture(render_target);
|
||||
UnloadRenderTexture(klotski_target);
|
||||
UnloadRenderTexture(menu_target);
|
||||
// Batching
|
||||
std::vector<std::pair<Vector3, Vector3>> connections;
|
||||
|
||||
// Instancing
|
||||
if (transforms != nullptr) {
|
||||
UnloadMaterial(vertex_mat);
|
||||
MemFree(transforms);
|
||||
UnloadMesh(cube_instance);
|
||||
static constexpr int INSTANCE_COLOR_ATTR = 5;
|
||||
std::vector<Matrix> transforms;
|
||||
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");
|
||||
|
||||
// I think the shader already gets unloaded with the material?
|
||||
// UnloadShader(instancing_shader);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
auto AllocateGraphInstancing(std::size_t size) -> void;
|
||||
|
||||
auto ReallocateGraphInstancingIfNecessary(std::size_t size) -> void;
|
||||
unsigned int color_vbo_id = 0;
|
||||
|
||||
public:
|
||||
auto UpdateTextureSizes() -> void;
|
||||
renderer(const orbit_camera& _camera, const state_manager& _state, const input_handler& _input,
|
||||
user_interface& _gui)
|
||||
: state(_state), input(_input), gui(_gui), camera(_camera)
|
||||
{
|
||||
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");
|
||||
|
||||
auto DrawMassSprings(const std::vector<Vector3> &masses) -> void;
|
||||
infoln("LOC vertexPosition: {}", rlGetLocationAttrib(instancing_shader.id, "vertexPosition"));
|
||||
infoln("LOC instanceTransform: {}", rlGetLocationAttrib(instancing_shader.id, "instanceTransform"));
|
||||
infoln("LOC instanceColor: {}", rlGetLocationAttrib(instancing_shader.id, "instanceColor"));
|
||||
|
||||
auto DrawKlotski() -> void;
|
||||
// vertex_mat.maps[MATERIAL_MAP_DIFFUSE].color = VERTEX_COLOR;
|
||||
vertex_mat.shader = instancing_shader;
|
||||
|
||||
auto DrawMenu(const std::vector<Vector3> &masses) -> void;
|
||||
transforms.reserve(DRAW_VERTICES_LIMIT);
|
||||
colors.reserve(DRAW_VERTICES_LIMIT);
|
||||
color_vbo_id = rlLoadVertexBuffer(colors.data(), DRAW_VERTICES_LIMIT * sizeof(Color), true);
|
||||
|
||||
auto DrawTextures(int fps, int ups) -> void;
|
||||
rlEnableVertexArray(cube_instance.vaoId);
|
||||
rlEnableVertexBuffer(color_vbo_id);
|
||||
rlSetVertexAttribute(INSTANCE_COLOR_ATTR, 4, RL_UNSIGNED_BYTE, true, 0, 0);
|
||||
rlEnableVertexAttribute(INSTANCE_COLOR_ATTR);
|
||||
rlSetVertexAttributeDivisor(INSTANCE_COLOR_ATTR, 1);
|
||||
|
||||
rlDisableVertexBuffer();
|
||||
rlDisableVertexArray();
|
||||
}
|
||||
|
||||
renderer(const renderer& copy) = delete;
|
||||
auto operator=(const renderer& copy) -> renderer& = delete;
|
||||
renderer(renderer&& move) = delete;
|
||||
auto operator=(renderer&& move) -> renderer& = delete;
|
||||
|
||||
~renderer()
|
||||
{
|
||||
UnloadRenderTexture(render_target);
|
||||
UnloadRenderTexture(klotski_target);
|
||||
UnloadRenderTexture(menu_target);
|
||||
|
||||
// Instancing
|
||||
UnloadMaterial(vertex_mat);
|
||||
UnloadMesh(cube_instance);
|
||||
|
||||
// I think the shader already gets unloaded with the material?
|
||||
// UnloadShader(instancing_shader);
|
||||
}
|
||||
|
||||
private:
|
||||
auto update_texture_sizes() -> void;
|
||||
|
||||
auto draw_mass_springs(const std::vector<Vector3>& masses) -> void;
|
||||
auto draw_klotski() const -> void;
|
||||
auto draw_menu() const -> void;
|
||||
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;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@ -1,103 +0,0 @@
|
||||
#ifndef __STATE_HPP_
|
||||
#define __STATE_HPP_
|
||||
|
||||
#include "config.hpp"
|
||||
#include "distance.hpp"
|
||||
#include "physics.hpp"
|
||||
#include "puzzle.hpp"
|
||||
|
||||
#include <raymath.h>
|
||||
#include <stack>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
class StateManager {
|
||||
public:
|
||||
ThreadedPhysics &physics;
|
||||
|
||||
std::vector<State> presets = {State()};
|
||||
std::vector<std::string> comments = {"Empty"};
|
||||
|
||||
// Some stuff is faster to map from state to mass (e.g. in the renderer)
|
||||
std::unordered_map<State, std::size_t> states; // State to mass id
|
||||
std::unordered_set<State> winning_states;
|
||||
std::unordered_map<State, int> visited_states; // How often we've been here
|
||||
std::stack<State> history;
|
||||
|
||||
// Other stuff maps from mass to state :/
|
||||
std::unordered_map<std::size_t, State> masses; // Mass id to state
|
||||
std::vector<std::size_t> winning_path;
|
||||
|
||||
// Fuck it, duplicate the springs too, we don't even need to copy them from
|
||||
// the physics thread then...
|
||||
std::vector<std::pair<std::size_t, std::size_t>> springs;
|
||||
|
||||
// Distance calculation result can be buffered and reused to calculate a new
|
||||
// path on the same graph
|
||||
DistanceResult target_distances;
|
||||
|
||||
std::string preset_file;
|
||||
|
||||
int total_moves = 0;
|
||||
int current_preset = 0;
|
||||
State starting_state;
|
||||
State current_state;
|
||||
State previous_state;
|
||||
|
||||
bool edited = false;
|
||||
|
||||
public:
|
||||
StateManager(ThreadedPhysics &_physics, const std::string &_preset_file)
|
||||
: physics(_physics) {
|
||||
ParsePresetFile(_preset_file);
|
||||
current_state = presets.at(current_preset);
|
||||
ClearGraph();
|
||||
}
|
||||
|
||||
StateManager(const StateManager ©) = delete;
|
||||
StateManager &operator=(const StateManager ©) = delete;
|
||||
StateManager(StateManager &&move) = delete;
|
||||
StateManager &operator=(StateManager &&move) = delete;
|
||||
|
||||
~StateManager() {}
|
||||
|
||||
private:
|
||||
auto ParsePresetFile(const std::string &_preset_file) -> bool;
|
||||
|
||||
public:
|
||||
auto AppendPresetFile(const std::string preset_name) -> void;
|
||||
|
||||
auto LoadPreset(int preset) -> void;
|
||||
|
||||
auto ResetState() -> void;
|
||||
|
||||
auto PreviousPreset() -> void;
|
||||
|
||||
auto NextPreset() -> void;
|
||||
|
||||
auto NextPath() -> void;
|
||||
|
||||
auto FillGraph() -> void;
|
||||
|
||||
auto UpdateGraph() -> void;
|
||||
|
||||
auto ClearGraph() -> void;
|
||||
|
||||
auto FindWinningStates() -> void;
|
||||
|
||||
auto FindTargetDistances() -> void;
|
||||
|
||||
auto FindTargetPath() -> void;
|
||||
|
||||
auto FindWorstState() -> State;
|
||||
|
||||
auto GoToWorst() -> void;
|
||||
|
||||
auto GoToNearestTarget() -> void;
|
||||
|
||||
auto PopHistory() -> void;
|
||||
|
||||
auto CurrentMassIndex() const -> std::size_t;
|
||||
};
|
||||
|
||||
#endif
|
||||
151
include/state_manager.hpp
Normal file
151
include/state_manager.hpp
Normal file
@ -0,0 +1,151 @@
|
||||
#ifndef STATE_MANAGER_HPP_
|
||||
#define STATE_MANAGER_HPP_
|
||||
|
||||
#include "distance.hpp"
|
||||
#include "physics.hpp"
|
||||
#include "puzzle.hpp"
|
||||
|
||||
#include <stack>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
class state_manager
|
||||
{
|
||||
private:
|
||||
threaded_physics& physics;
|
||||
|
||||
std::string preset_file;
|
||||
size_t current_preset = 0;
|
||||
std::vector<puzzle> preset_states = {puzzle(4, 5, 9, 9, false)};
|
||||
std::vector<std::string> preset_comments = {"Empty"};
|
||||
|
||||
// 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::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
|
||||
|
||||
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::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;
|
||||
size_t current_state_index = 0;
|
||||
size_t previous_state_index = 0;
|
||||
|
||||
int total_moves = 0;
|
||||
|
||||
public:
|
||||
state_manager(threaded_physics& _physics, const std::string& _preset_file) : physics(_physics)
|
||||
{
|
||||
parse_preset_file(_preset_file);
|
||||
load_preset(0);
|
||||
}
|
||||
|
||||
state_manager(const state_manager& copy) = delete;
|
||||
auto operator=(const state_manager& copy) -> state_manager& = delete;
|
||||
state_manager(state_manager&& move) = delete;
|
||||
auto operator=(state_manager&& move) -> state_manager& = delete;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Inserts a board state into the state_manager and the physics system.
|
||||
* States should only be inserted using this function to keep both systems in sync.
|
||||
* The function checks for duplicates before insertion.
|
||||
*
|
||||
* @param state State to insert
|
||||
* @return Index of insertion (or existing index if duplicate)
|
||||
*/
|
||||
auto synced_try_insert_state(const puzzle& state) -> size_t;
|
||||
|
||||
/**
|
||||
* Inserts a state link into the state_manager and the physics system.
|
||||
* Links should only be inserted using this function to keep both systems in sync.
|
||||
* The function does not check for duplicates before insertion.
|
||||
*
|
||||
* @param first_index Index of the first linked state
|
||||
* @param second_index Index of the second linked state
|
||||
*/
|
||||
auto synced_insert_link(size_t first_index, size_t second_index) -> void;
|
||||
|
||||
/**
|
||||
* Inserts an entire statespace into the state_manager and the physics system.
|
||||
* If inserting many states and links in bulk, this function should always be used
|
||||
* to not stress the physics command mutex.
|
||||
* The function does not check for duplicates before insertion.
|
||||
*
|
||||
* @param states List of states to insert
|
||||
* @param _links List of links to insert
|
||||
*/
|
||||
auto synced_insert_statespace(const std::vector<puzzle>& states,
|
||||
const std::vector<std::pair<size_t, size_t>>& _links) -> void;
|
||||
|
||||
/**
|
||||
* Clears all states and links (and related) from the state_manager and the physics system.
|
||||
* Note that this leaves any dangling indices (e.g., current_state_index) in an invalid state.
|
||||
*/
|
||||
auto synced_clear_statespace() -> void;
|
||||
|
||||
public:
|
||||
// Presets
|
||||
|
||||
auto parse_preset_file(const std::string& _preset_file) -> bool;
|
||||
auto append_preset_file(const std::string& preset_name) -> bool;
|
||||
auto load_preset(size_t preset) -> void;
|
||||
auto load_previous_preset() -> void;
|
||||
auto load_next_preset() -> void;
|
||||
|
||||
// Update current_state
|
||||
|
||||
auto update_current_state(const puzzle& p) -> void;
|
||||
auto edit_starting_state(const puzzle& p) -> void;
|
||||
auto goto_starting_state() -> void;
|
||||
auto goto_optimal_next_state() -> void;
|
||||
auto goto_previous_state() -> void;
|
||||
auto goto_most_distant_state() -> void;
|
||||
auto goto_closest_target_state() -> void;
|
||||
|
||||
// Update graph
|
||||
|
||||
auto populate_graph() -> void;
|
||||
auto clear_graph_and_add_current(const puzzle& p) -> void;
|
||||
auto clear_graph_and_add_current() -> void;
|
||||
auto populate_winning_indices() -> void;
|
||||
auto populate_node_target_distances() -> void;
|
||||
auto populate_winning_path() -> void;
|
||||
|
||||
// Index mapping
|
||||
|
||||
[[nodiscard]] auto get_index(const puzzle& state) const -> size_t;
|
||||
[[nodiscard]] auto get_current_index() const -> size_t;
|
||||
[[nodiscard]] auto get_starting_index() const -> size_t;
|
||||
[[nodiscard]] auto get_state(size_t index) const -> const puzzle&;
|
||||
[[nodiscard]] auto get_current_state() const -> const puzzle&;
|
||||
[[nodiscard]] auto get_starting_state() const -> const puzzle&;
|
||||
|
||||
// Access
|
||||
[[nodiscard]] auto get_state_count() const -> size_t;
|
||||
[[nodiscard]] auto get_target_count() const -> size_t;
|
||||
[[nodiscard]] auto get_link_count() const -> size_t;
|
||||
[[nodiscard]] auto get_path_length() const -> size_t;
|
||||
[[nodiscard]] auto get_links() const -> const std::vector<std::pair<size_t, size_t>>&;
|
||||
[[nodiscard]] auto get_winning_indices() const -> const std::unordered_set<size_t>&;
|
||||
[[nodiscard]] auto get_visit_counts() const -> const std::unordered_map<size_t, int>&;
|
||||
[[nodiscard]] auto get_winning_path() const -> const std::vector<size_t>&;
|
||||
[[nodiscard]] auto get_path_indices() const -> const std::unordered_set<size_t>&;
|
||||
[[nodiscard]] auto get_current_visits() const -> int;
|
||||
[[nodiscard]] auto get_current_preset() const -> size_t;
|
||||
[[nodiscard]] auto get_preset_count() const -> size_t;
|
||||
[[nodiscard]] auto get_current_preset_comment() const -> const std::string&;
|
||||
[[nodiscard]] auto has_history() const -> bool;
|
||||
[[nodiscard]] auto has_distances() const -> bool;
|
||||
[[nodiscard]] auto get_total_moves() const -> size_t;
|
||||
[[nodiscard]] auto was_edited() const -> bool;
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -1,16 +0,0 @@
|
||||
#ifndef __TRACY_HPP_
|
||||
#define __TRACY_HPP_
|
||||
|
||||
#include "config.hpp"
|
||||
|
||||
#ifdef TRACY
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
void *operator new(std::size_t count);
|
||||
void operator delete(void *ptr) noexcept;
|
||||
void operator delete(void *ptr, std::size_t count) noexcept;
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
167
include/user_interface.hpp
Normal file
167
include/user_interface.hpp
Normal file
@ -0,0 +1,167 @@
|
||||
#ifndef GUI_HPP_
|
||||
#define GUI_HPP_
|
||||
|
||||
#include "camera.hpp"
|
||||
#include "config.hpp"
|
||||
#include "input.hpp"
|
||||
#include "state_manager.hpp"
|
||||
|
||||
#include <raylib.h>
|
||||
|
||||
class user_interface
|
||||
{
|
||||
class grid
|
||||
{
|
||||
public:
|
||||
int x;
|
||||
int y;
|
||||
int width;
|
||||
int height;
|
||||
int columns;
|
||||
int rows;
|
||||
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)
|
||||
{}
|
||||
|
||||
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) -> void;
|
||||
auto update_bounds(int _x, int _y) -> void;
|
||||
|
||||
[[nodiscard]] auto bounds() const -> Rectangle;
|
||||
[[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;
|
||||
};
|
||||
|
||||
struct style
|
||||
{
|
||||
int border_color_normal;
|
||||
int base_color_normal;
|
||||
int text_color_normal;
|
||||
|
||||
int border_color_focused;
|
||||
int base_color_focused;
|
||||
int text_color_focused;
|
||||
|
||||
int border_color_pressed;
|
||||
int base_color_pressed;
|
||||
int text_color_pressed;
|
||||
|
||||
int border_color_disabled;
|
||||
int base_color_disabled;
|
||||
int text_color_disabled;
|
||||
};
|
||||
|
||||
struct default_style : style
|
||||
{
|
||||
int background_color;
|
||||
int line_color;
|
||||
|
||||
int text_size;
|
||||
int text_spacing;
|
||||
int text_line_spacing;
|
||||
int text_alignment_vertical;
|
||||
int text_wrap_mode;
|
||||
};
|
||||
|
||||
struct component_style : style
|
||||
{
|
||||
int border_width;
|
||||
int text_padding;
|
||||
int text_alignment;
|
||||
};
|
||||
|
||||
private:
|
||||
input_handler& input;
|
||||
state_manager& state;
|
||||
const orbit_camera& camera;
|
||||
|
||||
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 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);
|
||||
|
||||
bool save_window = false;
|
||||
std::array<char, 256> preset_name = {};
|
||||
bool help_window = false;
|
||||
|
||||
public:
|
||||
user_interface(input_handler& _input, state_manager& _state, const orbit_camera& _camera)
|
||||
: input(_input), state(_state), camera(_camera)
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
user_interface(const user_interface& copy) = delete;
|
||||
auto operator=(const user_interface& copy) -> user_interface& = delete;
|
||||
user_interface(user_interface&& move) = delete;
|
||||
auto operator=(user_interface&& move) -> user_interface& = delete;
|
||||
|
||||
private:
|
||||
static auto init() -> void;
|
||||
|
||||
static auto apply_color(style& style, Color color) -> void;
|
||||
static auto apply_block_color(style& style, Color color) -> void;
|
||||
static auto apply_text_color(style& style, Color color) -> void;
|
||||
|
||||
static auto get_default_style() -> default_style;
|
||||
static auto set_default_style(const default_style& style) -> void;
|
||||
static auto get_component_style(int component) -> component_style;
|
||||
static auto set_component_style(int component, const component_style& style) -> void;
|
||||
|
||||
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_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;
|
||||
|
||||
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_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;
|
||||
|
||||
[[nodiscard]] auto window_open() const -> bool;
|
||||
|
||||
// Different menu sections
|
||||
auto draw_menu_header(Color color) const -> void;
|
||||
auto draw_graph_info(Color color) const -> void;
|
||||
auto draw_graph_controls(Color color) const -> void;
|
||||
auto draw_camera_controls(Color color) const -> void;
|
||||
auto draw_puzzle_controls(Color color) const -> void;
|
||||
auto draw_edit_controls(Color color) const -> void;
|
||||
auto draw_menu_footer(Color color) -> void;
|
||||
|
||||
public:
|
||||
static auto get_background_color() -> Color;
|
||||
auto help_popup() -> void;
|
||||
auto draw_save_preset_popup() -> 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;
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -1,20 +1,87 @@
|
||||
#ifndef __UTIL_HPP_
|
||||
#define __UTIL_HPP_
|
||||
|
||||
#include "config.hpp"
|
||||
#ifndef UTIL_HPP_
|
||||
#define UTIL_HPP_
|
||||
|
||||
#include <iostream>
|
||||
#include <raylib.h>
|
||||
#include <raymath.h>
|
||||
|
||||
inline std::ostream &operator<<(std::ostream &os, const Vector2 &v) {
|
||||
os << "(" << v.x << ", " << v.y << ")";
|
||||
return os;
|
||||
inline auto operator<<(std::ostream& os, const Vector2& v) -> std::ostream&
|
||||
{
|
||||
os << "(" << v.x << ", " << v.y << ")";
|
||||
return os;
|
||||
}
|
||||
|
||||
inline std::ostream &operator<<(std::ostream &os, const Vector3 &v) {
|
||||
os << "(" << v.x << ", " << v.y << ", " << v.z << ")";
|
||||
return os;
|
||||
inline auto operator<<(std::ostream& os, const Vector3& v) -> std::ostream&
|
||||
{
|
||||
os << "(" << v.x << ", " << v.y << ", " << v.z << ")";
|
||||
return os;
|
||||
}
|
||||
|
||||
enum ctrl
|
||||
{
|
||||
reset = 0,
|
||||
bold_bright = 1,
|
||||
underline = 4,
|
||||
inverse = 7,
|
||||
bold_bright_off = 21,
|
||||
underline_off = 24,
|
||||
inverse_off = 27
|
||||
};
|
||||
|
||||
enum fg
|
||||
{
|
||||
fg_black = 30,
|
||||
fg_red = 31,
|
||||
fg_green = 32,
|
||||
fg_yellow = 33,
|
||||
fg_blue = 34,
|
||||
fg_magenta = 35,
|
||||
fg_cyan = 36,
|
||||
fg_white = 37
|
||||
};
|
||||
|
||||
enum bg
|
||||
{
|
||||
bg_black = 40,
|
||||
bg_red = 41,
|
||||
bg_green = 42,
|
||||
bg_yellow = 43,
|
||||
bg_blue = 44,
|
||||
bg_magenta = 45,
|
||||
bg_cyan = 46,
|
||||
bg_white = 47
|
||||
};
|
||||
|
||||
inline auto ansi_bold_fg(const fg color) -> std::string
|
||||
{
|
||||
return std::format("\033[1;{}m", static_cast<int>(color));
|
||||
}
|
||||
|
||||
inline auto ansi_reset() -> std::string
|
||||
{
|
||||
return "\033[0m";
|
||||
}
|
||||
|
||||
// std::println doesn't work with mingw
|
||||
template <typename... Args>
|
||||
auto infoln(std::format_string<Args...> fmt, Args&&... args) -> void
|
||||
{
|
||||
std::cout << std::format("[{}INFO{}]: ", ansi_bold_fg(fg_blue), ansi_reset())
|
||||
<< std::format(fmt, std::forward<Args>(args)...) << std::endl;
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
auto warnln(std::format_string<Args...> fmt, Args&&... args) -> void
|
||||
{
|
||||
std::cout << std::format("[{}WARNING{}]: ", ansi_bold_fg(fg_yellow), ansi_reset())
|
||||
<< std::format(fmt, std::forward<Args>(args)...) << std::endl;
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
auto errln(std::format_string<Args...> fmt, Args&&... args) -> void
|
||||
{
|
||||
std::cout << std::format("[{}ERROR{}]: ", ansi_bold_fg(fg_red), ansi_reset())
|
||||
<< std::format(fmt, std::forward<Args>(args)...) << std::endl;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user