complete rework of the user interface (using raygui)
This commit is contained in:
@ -32,6 +32,7 @@ set(SOURCES
|
|||||||
src/tracy.cpp
|
src/tracy.cpp
|
||||||
src/backward.cpp
|
src/backward.cpp
|
||||||
src/distance.cpp
|
src/distance.cpp
|
||||||
|
src/gui.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
# Main target
|
# Main target
|
||||||
|
|||||||
@ -7,43 +7,24 @@
|
|||||||
#include <raymath.h>
|
#include <raymath.h>
|
||||||
|
|
||||||
class OrbitCamera3D {
|
class OrbitCamera3D {
|
||||||
friend class Renderer;
|
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;
|
||||||
|
|
||||||
private:
|
Camera camera = Camera{Vector3(0, 0, -distance), target, Vector3(0, 1.0, 0),
|
||||||
Camera camera;
|
fov, projection};
|
||||||
|
|
||||||
Vector3 position;
|
|
||||||
Vector3 target;
|
|
||||||
float distance;
|
|
||||||
float angle_x;
|
|
||||||
float angle_y;
|
|
||||||
|
|
||||||
// Input
|
|
||||||
Vector2 last_mouse;
|
|
||||||
bool rotating;
|
|
||||||
bool panning;
|
|
||||||
bool target_lock;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
OrbitCamera3D()
|
auto Rotate(Vector2 last_mouse, Vector2 mouse) -> void;
|
||||||
: camera(Camera(Vector3Zero(), Vector3Zero(), Vector3Zero(), 0.0, 0)),
|
|
||||||
position(Vector3Zero()), target(Vector3Zero()),
|
|
||||||
distance(CAMERA_DISTANCE), angle_x(0.0), angle_y(0.0),
|
|
||||||
last_mouse(Vector2Zero()), rotating(false), panning(false),
|
|
||||||
target_lock(true) {
|
|
||||||
camera.position = Vector3(0, 0, -1.0 * distance);
|
|
||||||
camera.target = target;
|
|
||||||
camera.up = Vector3(0, 1.0, 0);
|
|
||||||
camera.fovy = CAMERA_FOV;
|
|
||||||
camera.projection = CAMERA_PERSPECTIVE;
|
|
||||||
}
|
|
||||||
|
|
||||||
~OrbitCamera3D() {}
|
auto Pan(Vector2 last_mouse, Vector2 mouse) -> void;
|
||||||
|
|
||||||
public:
|
auto Update(const Vector3 ¤t_target, bool lock) -> void;
|
||||||
auto HandleCameraInput() -> Vector2;
|
|
||||||
|
|
||||||
auto Update(const Vector3 ¤t_target) -> void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -10,15 +10,17 @@
|
|||||||
// #define TRACY // Enable tracy profiling support
|
// #define TRACY // Enable tracy profiling support
|
||||||
|
|
||||||
// Window
|
// Window
|
||||||
constexpr int INITIAL_WIDTH = 800;
|
constexpr int INITIAL_WIDTH = 600;
|
||||||
constexpr int INITIAL_HEIGHT = 800;
|
constexpr int INITIAL_HEIGHT = 600;
|
||||||
constexpr int MENU_HEIGHT = 250;
|
constexpr int MENU_HEIGHT = 350;
|
||||||
|
|
||||||
// Menu
|
// Menu
|
||||||
constexpr int MENU_PAD = 5;
|
constexpr int MENU_PAD = 5;
|
||||||
constexpr int BUTTON_PAD = 12;
|
constexpr int BUTTON_PAD = 12;
|
||||||
constexpr int MENU_ROWS = 5;
|
constexpr int MENU_ROWS = 7;
|
||||||
constexpr int MENU_COLS = 3;
|
constexpr int MENU_COLS = 3;
|
||||||
|
constexpr const char *FONT = "fonts/SpaceMono.ttf";
|
||||||
|
constexpr int FONT_SIZE = 26;
|
||||||
|
|
||||||
// Camera Controls
|
// Camera Controls
|
||||||
constexpr float CAMERA_FOV = 120.0;
|
constexpr float CAMERA_FOV = 120.0;
|
||||||
@ -26,6 +28,7 @@ constexpr float CAMERA_DISTANCE = 20.0;
|
|||||||
constexpr float MIN_CAMERA_DISTANCE = 2.0;
|
constexpr float MIN_CAMERA_DISTANCE = 2.0;
|
||||||
constexpr float MAX_CAMERA_DISTANCE = 2000.0;
|
constexpr float MAX_CAMERA_DISTANCE = 2000.0;
|
||||||
constexpr float ZOOM_SPEED = 2.5;
|
constexpr float ZOOM_SPEED = 2.5;
|
||||||
|
constexpr float FOV_SPEED = 1.0;
|
||||||
constexpr float ZOOM_MULTIPLIER = 4.0;
|
constexpr float ZOOM_MULTIPLIER = 4.0;
|
||||||
constexpr float PAN_SPEED = 2.0;
|
constexpr float PAN_SPEED = 2.0;
|
||||||
constexpr float PAN_MULTIPLIER = 10.0;
|
constexpr float PAN_MULTIPLIER = 10.0;
|
||||||
@ -52,13 +55,13 @@ constexpr Color EDGE_COLOR = DARKGREEN;
|
|||||||
constexpr int DRAW_VERTICES_LIMIT = 1000000;
|
constexpr int DRAW_VERTICES_LIMIT = 1000000;
|
||||||
|
|
||||||
// Klotski Drawing
|
// Klotski Drawing
|
||||||
constexpr int BOARD_PADDING = 5;
|
constexpr int BOARD_PADDING = 10;
|
||||||
constexpr int BLOCK_PADDING = 5;
|
constexpr Color BOARD_COLOR_WON = DARKGREEN;
|
||||||
constexpr Color BLOCK_COLOR = DARKGREEN;
|
constexpr Color BOARD_COLOR_RESTRICTED = GRAY;
|
||||||
constexpr Color HL_BLOCK_COLOR = GREEN;
|
constexpr Color BOARD_COLOR_FREE = RAYWHITE;
|
||||||
|
constexpr Color BLOCK_COLOR = DARKBLUE;
|
||||||
constexpr Color TARGET_BLOCK_COLOR = RED;
|
constexpr Color TARGET_BLOCK_COLOR = RED;
|
||||||
constexpr Color HL_TARGET_BLOCK_COLOR = ORANGE;
|
|
||||||
constexpr Color WALL_COLOR = BLACK;
|
constexpr Color WALL_COLOR = BLACK;
|
||||||
constexpr Color HL_WALL_COLOR = DARKGRAY;
|
constexpr Color GOAL_COLOR = ORANGE;
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
190
include/gui.hpp
Normal file
190
include/gui.hpp
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
#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,31 +1,71 @@
|
|||||||
#ifndef __INPUT_HPP_
|
#ifndef __INPUT_HPP_
|
||||||
#define __INPUT_HPP_
|
#define __INPUT_HPP_
|
||||||
|
|
||||||
|
#include "camera.hpp"
|
||||||
#include "config.hpp"
|
#include "config.hpp"
|
||||||
#include "state.hpp"
|
#include "state.hpp"
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <raylib.h>
|
||||||
|
#include <raymath.h>
|
||||||
|
|
||||||
class InputHandler {
|
class InputHandler {
|
||||||
|
struct GenericHandler {
|
||||||
|
std::function<void(InputHandler &)> handler;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MouseHandler : GenericHandler {
|
||||||
|
MouseButton button;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct KeyboardHandler : GenericHandler {
|
||||||
|
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;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
StateManager &state;
|
StateManager &state;
|
||||||
|
OrbitCamera3D &camera;
|
||||||
|
|
||||||
int hov_x;
|
bool disable = false;
|
||||||
int hov_y;
|
|
||||||
int sel_x;
|
|
||||||
int sel_y;
|
|
||||||
|
|
||||||
bool has_block_add_xy;
|
// Block selection
|
||||||
int block_add_x;
|
int hov_x = -1;
|
||||||
int block_add_y;
|
int hov_y = -1;
|
||||||
|
int sel_x = 0;
|
||||||
|
int sel_y = 0;
|
||||||
|
|
||||||
bool mark_path;
|
// Editing
|
||||||
bool mark_solutions;
|
bool editing = false;
|
||||||
bool connect_solutions;
|
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;
|
||||||
|
|
||||||
|
// Camera
|
||||||
|
bool camera_lock = true;
|
||||||
|
bool camera_panning = false;
|
||||||
|
bool camera_rotating = false;
|
||||||
|
|
||||||
|
// Mouse dragging
|
||||||
|
Vector2 mouse = Vector2Zero();
|
||||||
|
Vector2 last_mouse = Vector2Zero();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
InputHandler(StateManager &_state)
|
InputHandler(StateManager &_state, OrbitCamera3D &_camera)
|
||||||
: state(_state), hov_x(-1), hov_y(-1), sel_x(0), sel_y(0),
|
: state(_state), camera(_camera) {
|
||||||
has_block_add_xy(false), block_add_x(-1), block_add_y(-1),
|
InitHandlers();
|
||||||
mark_path(false), mark_solutions(false), connect_solutions(false) {}
|
}
|
||||||
|
|
||||||
InputHandler(const InputHandler ©) = delete;
|
InputHandler(const InputHandler ©) = delete;
|
||||||
InputHandler &operator=(const InputHandler ©) = delete;
|
InputHandler &operator=(const InputHandler ©) = delete;
|
||||||
@ -34,12 +74,81 @@ public:
|
|||||||
|
|
||||||
~InputHandler() {}
|
~InputHandler() {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
auto InitHandlers() -> void;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
auto HandleMouseHover() -> void;
|
// Helpers
|
||||||
|
auto MouseInMenuPane() -> bool;
|
||||||
|
auto MouseInBoardPane() -> bool;
|
||||||
|
auto MouseInGraphPane() -> bool;
|
||||||
|
|
||||||
auto HandleMouse() -> void;
|
// 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;
|
||||||
|
|
||||||
auto HandleKeys() -> void;
|
// Key actions
|
||||||
|
auto ToggleCameraLock() -> 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;
|
||||||
|
|
||||||
|
// General
|
||||||
|
auto RegisterGenericHandler(std::function<void(InputHandler &)> handler)
|
||||||
|
-> void;
|
||||||
|
|
||||||
|
auto RegisterMousePressedHandler(MouseButton button,
|
||||||
|
std::function<void(InputHandler &)> handler)
|
||||||
|
-> void;
|
||||||
|
|
||||||
|
auto RegisterMouseReleasedHandler(MouseButton button,
|
||||||
|
std::function<void(InputHandler &)> handler)
|
||||||
|
-> void;
|
||||||
|
|
||||||
|
auto RegisterKeyPressedHandler(KeyboardKey key,
|
||||||
|
std::function<void(InputHandler &)> handler)
|
||||||
|
-> void;
|
||||||
|
|
||||||
|
auto RegisterKeyReleasedHandler(KeyboardKey key,
|
||||||
|
std::function<void(InputHandler &)> handler)
|
||||||
|
-> void;
|
||||||
|
|
||||||
auto HandleInput() -> void;
|
auto HandleInput() -> void;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -49,8 +49,6 @@ public:
|
|||||||
-> void;
|
-> void;
|
||||||
|
|
||||||
auto CalculateForce(int node_idx, const Vector3 &pos) const -> Vector3;
|
auto CalculateForce(int node_idx, const Vector3 &pos) const -> Vector3;
|
||||||
|
|
||||||
auto Print() const -> void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -7,8 +7,8 @@
|
|||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <iostream>
|
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <print>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include <raylib.h>
|
#include <raylib.h>
|
||||||
#include <raymath.h>
|
#include <raymath.h>
|
||||||
@ -78,12 +78,10 @@ public:
|
|||||||
: threads(std::thread::hardware_concurrency() - 1, SetThreadName)
|
: threads(std::thread::hardware_concurrency() - 1, SetThreadName)
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
std::cout << "Using Barnes-Hut + octree repulsion force calculation."
|
std::println("Using Barnes-Hut + Octree repulsion force calculation.");
|
||||||
<< std::endl;
|
|
||||||
|
|
||||||
#ifdef THREADPOOL
|
#ifdef THREADPOOL
|
||||||
std::cout << "Thread-Pool: " << threads.get_thread_count() << " threads."
|
std::println("Thread-pool: {} threads.", threads.get_thread_count());
|
||||||
<< std::endl;
|
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <format>
|
#include <format>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <iostream>
|
#include <print>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@ -40,7 +40,7 @@ public:
|
|||||||
: x(_x), y(_y), width(_width), height(_height), target(_target),
|
: x(_x), y(_y), width(_width), height(_height), target(_target),
|
||||||
immovable(_immovable) {
|
immovable(_immovable) {
|
||||||
if (_x < 0 || _x + _width >= 10 || _y < 0 || _y + _height >= 10) {
|
if (_x < 0 || _x + _width >= 10 || _y < 0 || _y + _height >= 10) {
|
||||||
std::cerr << "Block must fit on a 9x9 board!" << std::endl;
|
std::println("Block must fit in a 9x9 board!");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -92,11 +92,11 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (_x < 0 || _x + width >= 10 || _y < 0 || _y + height >= 10) {
|
if (_x < 0 || _x + width >= 10 || _y < 0 || _y + height >= 10) {
|
||||||
std::cerr << "Block must fit on a 9x9 board!" << std::endl;
|
std::println("Block must fit in a 9x9 board!");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
if (block.length() != 2) {
|
if (block.length() != 2) {
|
||||||
std::cerr << "Block representation must have length [2]!" << std::endl;
|
std::println("Block representation must have length 2!");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -109,7 +109,7 @@ public:
|
|||||||
bool operator!=(const Block &other) { return !(*this == other); }
|
bool operator!=(const Block &other) { return !(*this == other); }
|
||||||
|
|
||||||
public:
|
public:
|
||||||
auto Hash() const -> int;
|
auto Hash() const -> std::size_t;
|
||||||
|
|
||||||
static auto Invalid() -> Block;
|
static auto Invalid() -> Block;
|
||||||
|
|
||||||
@ -186,13 +186,12 @@ public:
|
|||||||
_height, _target_x, _target_y,
|
_height, _target_x, _target_y,
|
||||||
std::string(_width * _height * 2, '.'))) {
|
std::string(_width * _height * 2, '.'))) {
|
||||||
if (_width < 1 || _width > 9 || _height < 1 || _height > 9) {
|
if (_width < 1 || _width > 9 || _height < 1 || _height > 9) {
|
||||||
std::cerr << "State width/height must be in [1, 9]!" << std::endl;
|
std::println("State width/height must be in [1, 9]!");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
if (_target_x < 0 || _target_x >= 9 || _target_y < 0 || _target_y >= 9) {
|
if (_target_x < 0 || _target_x >= 9 || _target_y < 0 || _target_y >= 9) {
|
||||||
if (_target_x != 9 && _target_y != 9) {
|
if (_target_x != 9 && _target_y != 9) {
|
||||||
std::cerr << "State target must be within the board bounds!"
|
std::println("State target must be within the board bounds!");
|
||||||
<< std::endl;
|
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -210,20 +209,19 @@ public:
|
|||||||
target_y(std::stoi(_state.substr(4, 1))),
|
target_y(std::stoi(_state.substr(4, 1))),
|
||||||
restricted(_state.substr(0, 1) == "R"), state(_state) {
|
restricted(_state.substr(0, 1) == "R"), state(_state) {
|
||||||
if (width < 1 || width > 9 || height < 1 || height > 9) {
|
if (width < 1 || width > 9 || height < 1 || height > 9) {
|
||||||
std::cerr << "State width/height must be in [1, 9]!" << std::endl;
|
std::println("State width/height must be in [1, 9]!");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
if (target_x < 0 || target_x >= 9 || target_y < 0 || target_y >= 9) {
|
if (target_x < 0 || target_x >= 9 || target_y < 0 || target_y >= 9) {
|
||||||
if (target_x != 9 && target_y != 9) {
|
if (target_x != 9 && target_y != 9) {
|
||||||
std::cerr << "State target must be within the board bounds!"
|
std::println("State target must be within the board bounds!");
|
||||||
<< std::endl;
|
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (static_cast<int>(_state.length()) != width * height * 2 + prefix) {
|
if (static_cast<int>(_state.length()) != width * height * 2 + prefix) {
|
||||||
std::cerr
|
std::println(
|
||||||
<< "State representation must have length [width * height * 2 + "
|
"State representation must have length width * height * 2 + {}!",
|
||||||
<< prefix << "]!" << std::endl;
|
prefix);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -243,7 +241,7 @@ public:
|
|||||||
BlockIterator end() const { return BlockIterator(*this, width * height); }
|
BlockIterator end() const { return BlockIterator(*this, width * height); }
|
||||||
|
|
||||||
public:
|
public:
|
||||||
auto Hash() const -> int;
|
auto Hash() const -> std::size_t;
|
||||||
|
|
||||||
auto HasWinCondition() const -> bool;
|
auto HasWinCondition() const -> bool;
|
||||||
|
|
||||||
@ -251,6 +249,8 @@ public:
|
|||||||
|
|
||||||
auto SetGoal(int x, int y) -> bool;
|
auto SetGoal(int x, int y) -> bool;
|
||||||
|
|
||||||
|
auto ClearGoal() -> void;
|
||||||
|
|
||||||
auto AddColumn() const -> State;
|
auto AddColumn() const -> State;
|
||||||
|
|
||||||
auto RemoveColumn() const -> State;
|
auto RemoveColumn() const -> State;
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#include "camera.hpp"
|
#include "camera.hpp"
|
||||||
#include "config.hpp"
|
#include "config.hpp"
|
||||||
|
#include "gui.hpp"
|
||||||
#include "input.hpp"
|
#include "input.hpp"
|
||||||
#include "state.hpp"
|
#include "state.hpp"
|
||||||
|
|
||||||
@ -13,6 +14,7 @@ class Renderer {
|
|||||||
private:
|
private:
|
||||||
const StateManager &state;
|
const StateManager &state;
|
||||||
const InputHandler &input;
|
const InputHandler &input;
|
||||||
|
Gui &gui;
|
||||||
|
|
||||||
const OrbitCamera3D &camera;
|
const OrbitCamera3D &camera;
|
||||||
RenderTexture render_target;
|
RenderTexture render_target;
|
||||||
@ -21,16 +23,15 @@ private:
|
|||||||
|
|
||||||
// Instancing
|
// Instancing
|
||||||
Material vertex_mat;
|
Material vertex_mat;
|
||||||
std::size_t transforms_size;
|
std::size_t transforms_size = 0;
|
||||||
Matrix *transforms;
|
Matrix *transforms = nullptr;
|
||||||
Mesh cube_instance;
|
Mesh cube_instance;
|
||||||
Shader instancing_shader;
|
Shader instancing_shader;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Renderer(const OrbitCamera3D &_camera, const StateManager &_state,
|
Renderer(const OrbitCamera3D &_camera, const StateManager &_state,
|
||||||
const InputHandler &_input)
|
const InputHandler &_input, Gui &_gui)
|
||||||
: state(_state), input(_input), camera(_camera), transforms_size(0),
|
: state(_state), input(_input), gui(_gui), camera(_camera) {
|
||||||
transforms(nullptr) {
|
|
||||||
render_target = LoadRenderTexture(GetScreenWidth() / 2.0,
|
render_target = LoadRenderTexture(GetScreenWidth() / 2.0,
|
||||||
GetScreenHeight() - MENU_HEIGHT);
|
GetScreenHeight() - MENU_HEIGHT);
|
||||||
klotski_target = LoadRenderTexture(GetScreenWidth() / 2.0,
|
klotski_target = LoadRenderTexture(GetScreenWidth() / 2.0,
|
||||||
@ -73,7 +74,7 @@ public:
|
|||||||
|
|
||||||
auto DrawMenu(const std::vector<Vector3> &masses) -> void;
|
auto DrawMenu(const std::vector<Vector3> &masses) -> void;
|
||||||
|
|
||||||
auto DrawTextures(float ups) -> void;
|
auto DrawTextures(int fps, int ups) -> void;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -15,17 +15,17 @@ class StateManager {
|
|||||||
public:
|
public:
|
||||||
ThreadedPhysics &physics;
|
ThreadedPhysics &physics;
|
||||||
|
|
||||||
std::vector<State> presets;
|
std::vector<State> presets = {State()};
|
||||||
std::vector<std::string> comments;
|
std::vector<std::string> comments = {"Empty"};
|
||||||
|
|
||||||
// Some stuff is faster to map from state to mass (e.g. in the renderer)
|
// Some stuff is faster to map from state to mass (e.g. in the renderer)
|
||||||
std::unordered_map<State, std::size_t> states;
|
std::unordered_map<State, std::size_t> states; // State to mass id
|
||||||
std::unordered_set<State> winning_states;
|
std::unordered_set<State> winning_states;
|
||||||
std::unordered_set<State> visited_states;
|
std::unordered_map<State, int> visited_states; // How often we've been here
|
||||||
std::stack<State> history;
|
std::stack<State> history;
|
||||||
|
|
||||||
// Other stuff maps from mass to state :/
|
// Other stuff maps from mass to state :/
|
||||||
std::unordered_map<std::size_t, State> masses;
|
std::unordered_map<std::size_t, State> masses; // Mass id to state
|
||||||
std::vector<std::size_t> winning_path;
|
std::vector<std::size_t> winning_path;
|
||||||
|
|
||||||
// Fuck it, duplicate the springs too, we don't even need to copy them from
|
// Fuck it, duplicate the springs too, we don't even need to copy them from
|
||||||
@ -36,7 +36,10 @@ public:
|
|||||||
// path on the same graph
|
// path on the same graph
|
||||||
DistanceResult target_distances;
|
DistanceResult target_distances;
|
||||||
|
|
||||||
int current_preset;
|
std::string preset_file;
|
||||||
|
|
||||||
|
int total_moves = 0;
|
||||||
|
int current_preset = 0;
|
||||||
State starting_state;
|
State starting_state;
|
||||||
State current_state;
|
State current_state;
|
||||||
State previous_state;
|
State previous_state;
|
||||||
@ -45,8 +48,7 @@ public:
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
StateManager(ThreadedPhysics &_physics, const std::string &preset_file)
|
StateManager(ThreadedPhysics &_physics, const std::string &preset_file)
|
||||||
: physics(_physics), presets({State()}), current_preset(0),
|
: physics(_physics) {
|
||||||
edited(false) {
|
|
||||||
ParsePresetFile(preset_file);
|
ParsePresetFile(preset_file);
|
||||||
current_state = presets.at(current_preset);
|
current_state = presets.at(current_preset);
|
||||||
ClearGraph();
|
ClearGraph();
|
||||||
@ -60,9 +62,11 @@ public:
|
|||||||
~StateManager() {}
|
~StateManager() {}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
auto ParsePresetFile(const std::string &preset_file) -> void;
|
auto ParsePresetFile(const std::string &_preset_file) -> bool;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
auto AppendPresetFile(const std::string preset_name) -> void;
|
||||||
|
|
||||||
auto LoadPreset(int preset) -> void;
|
auto LoadPreset(int preset) -> void;
|
||||||
|
|
||||||
auto ResetState() -> void;
|
auto ResetState() -> void;
|
||||||
|
|||||||
@ -9,46 +9,8 @@
|
|||||||
#include <tracy/Tracy.hpp>
|
#include <tracy/Tracy.hpp>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
auto OrbitCamera3D::HandleCameraInput() -> Vector2 {
|
auto OrbitCamera3D::Rotate(Vector2 last_mouse, Vector2 mouse) -> void {
|
||||||
Vector2 mouse = GetMousePosition();
|
|
||||||
if (mouse.x >= GetScreenWidth() / 2.0 && mouse.y >= MENU_HEIGHT) {
|
|
||||||
if (IsMouseButtonPressed(MOUSE_RIGHT_BUTTON)) {
|
|
||||||
rotating = true;
|
|
||||||
last_mouse = mouse;
|
|
||||||
} else if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
|
|
||||||
panning = true;
|
|
||||||
target_lock = false;
|
|
||||||
last_mouse = mouse;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Zoom
|
|
||||||
float wheel = GetMouseWheelMove();
|
|
||||||
if (IsKeyDown(KEY_LEFT_SHIFT)) {
|
|
||||||
distance -= wheel * ZOOM_SPEED * ZOOM_MULTIPLIER;
|
|
||||||
} else {
|
|
||||||
distance -= wheel * ZOOM_SPEED;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IsMouseButtonReleased(MOUSE_RIGHT_BUTTON)) {
|
|
||||||
rotating = false;
|
|
||||||
}
|
|
||||||
if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) {
|
|
||||||
panning = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IsKeyPressed(KEY_L)) {
|
|
||||||
target_lock = !target_lock;
|
|
||||||
}
|
|
||||||
return mouse;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto OrbitCamera3D::Update(const Vector3 ¤t_target) -> void {
|
|
||||||
Vector2 mouse = HandleCameraInput();
|
|
||||||
|
|
||||||
if (rotating) {
|
|
||||||
Vector2 dx = Vector2Subtract(mouse, last_mouse);
|
Vector2 dx = Vector2Subtract(mouse, last_mouse);
|
||||||
last_mouse = mouse;
|
|
||||||
|
|
||||||
angle_x -= dx.x * ROT_SPEED / 200.0;
|
angle_x -= dx.x * ROT_SPEED / 200.0;
|
||||||
angle_y += dx.y * ROT_SPEED / 200.0;
|
angle_y += dx.y * ROT_SPEED / 200.0;
|
||||||
@ -56,9 +18,8 @@ auto OrbitCamera3D::Update(const Vector3 ¤t_target) -> void {
|
|||||||
angle_y = Clamp(angle_y, -1.5, 1.5); // Prevent flipping
|
angle_y = Clamp(angle_y, -1.5, 1.5); // Prevent flipping
|
||||||
}
|
}
|
||||||
|
|
||||||
if (panning) {
|
auto OrbitCamera3D::Pan(Vector2 last_mouse, Vector2 mouse) -> void {
|
||||||
Vector2 dx = Vector2Subtract(mouse, last_mouse);
|
Vector2 dx = Vector2Subtract(mouse, last_mouse);
|
||||||
last_mouse = mouse;
|
|
||||||
|
|
||||||
float speed;
|
float speed;
|
||||||
if (IsKeyDown(KEY_LEFT_SHIFT)) {
|
if (IsKeyDown(KEY_LEFT_SHIFT)) {
|
||||||
@ -80,7 +41,8 @@ auto OrbitCamera3D::Update(const Vector3 ¤t_target) -> void {
|
|||||||
target = Vector3Add(target, offset);
|
target = Vector3Add(target, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (target_lock) {
|
auto OrbitCamera3D::Update(const Vector3 ¤t_target, bool lock) -> void {
|
||||||
|
if (lock) {
|
||||||
target = Vector3MoveTowards(
|
target = Vector3MoveTowards(
|
||||||
target, current_target,
|
target, current_target,
|
||||||
CAMERA_SMOOTH_SPEED * GetFrameTime() *
|
CAMERA_SMOOTH_SPEED * GetFrameTime() *
|
||||||
@ -88,12 +50,20 @@ auto OrbitCamera3D::Update(const Vector3 ¤t_target) -> void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
distance = Clamp(distance, MIN_CAMERA_DISTANCE, MAX_CAMERA_DISTANCE);
|
distance = Clamp(distance, MIN_CAMERA_DISTANCE, MAX_CAMERA_DISTANCE);
|
||||||
|
int actual_distance = distance;
|
||||||
|
if (projection == CAMERA_ORTHOGRAPHIC) {
|
||||||
|
actual_distance = MAX_CAMERA_DISTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
// Spherical coordinates
|
// Spherical coordinates
|
||||||
float x = cos(angle_y) * sin(angle_x) * distance;
|
float x = cos(angle_y) * sin(angle_x) * actual_distance;
|
||||||
float y = sin(angle_y) * distance;
|
float y = sin(angle_y) * actual_distance;
|
||||||
float z = cos(angle_y) * cos(angle_x) * distance;
|
float z = cos(angle_y) * cos(angle_x) * actual_distance;
|
||||||
|
|
||||||
|
fov = Clamp(fov, 25.0, 155.0);
|
||||||
|
|
||||||
camera.position = Vector3Add(target, Vector3(x, y, z));
|
camera.position = Vector3Add(target, Vector3(x, y, z));
|
||||||
camera.target = target;
|
camera.target = target;
|
||||||
|
camera.fovy = fov;
|
||||||
|
camera.projection = projection;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,14 +30,6 @@ auto CalculateDistances(
|
|||||||
adjacency[from].push_back(to);
|
adjacency[from].push_back(to);
|
||||||
adjacency[to].push_back(from);
|
adjacency[to].push_back(from);
|
||||||
}
|
}
|
||||||
// for (size_t i = 0; i < adjacency.size(); ++i) {
|
|
||||||
// std::cout << "Node " << i << "'s neighbors: ";
|
|
||||||
// for (const auto &neighbor : adjacency[i]) {
|
|
||||||
// std::cout << neighbor;
|
|
||||||
// }
|
|
||||||
// std::cout << "\n";
|
|
||||||
// }
|
|
||||||
// std::cout << std::endl;
|
|
||||||
|
|
||||||
std::vector<int> distances(node_count, -1);
|
std::vector<int> distances(node_count, -1);
|
||||||
std::vector<std::size_t> parents(node_count, -1);
|
std::vector<std::size_t> parents(node_count, -1);
|
||||||
|
|||||||
792
src/gui.cpp
Normal file
792
src/gui.cpp
Normal file
@ -0,0 +1,792 @@
|
|||||||
|
#include "gui.hpp"
|
||||||
|
#include "config.hpp"
|
||||||
|
|
||||||
|
#include <raylib.h>
|
||||||
|
|
||||||
|
#define RAYGUI_IMPLEMENTATION
|
||||||
|
#include <raygui.h>
|
||||||
|
|
||||||
|
#ifdef TRACY
|
||||||
|
#include "tracy.hpp"
|
||||||
|
#include <tracy/Tracy.hpp>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
auto Grid::UpdateBounds(int _x, int _y, int _width, int _height, int _columns,
|
||||||
|
int _rows) -> void {
|
||||||
|
x = _x;
|
||||||
|
y = _y;
|
||||||
|
width = _width;
|
||||||
|
height = _height;
|
||||||
|
columns = _columns;
|
||||||
|
rows = _rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Grid::UpdateBounds(int _x, int _y, int _width, int _height) -> void {
|
||||||
|
x = _x;
|
||||||
|
y = _y;
|
||||||
|
width = _width;
|
||||||
|
height = _height;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Grid::UpdateBounds(int _x, int _y) -> void {
|
||||||
|
x = _x;
|
||||||
|
y = _y;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Grid::Bounds() const -> Rectangle {
|
||||||
|
Rectangle bounds = Bounds(0, 0, columns, rows);
|
||||||
|
bounds.x -= padding;
|
||||||
|
bounds.y -= padding;
|
||||||
|
bounds.width += 2 * padding;
|
||||||
|
bounds.height += 2 * padding;
|
||||||
|
return bounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Grid::Bounds(int _x, int _y, int _width, int _height) const -> Rectangle {
|
||||||
|
if (_x < 0 || _x + _width > columns || _y < 0 || _y + _height > rows) {
|
||||||
|
std::println("Grid bounds are outside range.");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int cell_width = (width - padding) / columns;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Grid::SquareBounds() const -> Rectangle {
|
||||||
|
Rectangle bounds = SquareBounds(0, 0, columns, rows);
|
||||||
|
bounds.x -= padding;
|
||||||
|
bounds.y -= padding;
|
||||||
|
bounds.width += 2 * padding;
|
||||||
|
bounds.height += 2 * padding;
|
||||||
|
return bounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Grid::SquareBounds(int _x, int _y, int _width, int _height) const
|
||||||
|
-> Rectangle {
|
||||||
|
// Assumes each cell is square, so either width or height are not completely
|
||||||
|
// filled
|
||||||
|
|
||||||
|
if (_x < 0 || _x + _width > columns || _y < 0 || _y + _height > rows) {
|
||||||
|
std::println("Grid bounds are outside range.");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int available_width = width - padding * (columns + 1);
|
||||||
|
int available_height = height - padding * (rows + 1);
|
||||||
|
int cell_size = std::min(available_width / columns, available_height / rows);
|
||||||
|
|
||||||
|
int grid_width = cell_size * columns + padding * (columns + 1);
|
||||||
|
int grid_height = cell_size * rows + padding * (rows + 1);
|
||||||
|
int x_offset = (width - grid_width) / 2;
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Gui::Init() const -> void {
|
||||||
|
Font font = LoadFontEx(FONT, FONT_SIZE, 0, 0);
|
||||||
|
SetTextureFilter(font.texture, TEXTURE_FILTER_BILINEAR);
|
||||||
|
GuiSetFont(font);
|
||||||
|
|
||||||
|
DefaultStyle style = GetDefaultStyle();
|
||||||
|
style.text_size = FONT_SIZE;
|
||||||
|
ApplyColor(style, GRAY);
|
||||||
|
|
||||||
|
SetDefaultStyle(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Gui::ApplyColor(Style &style, Color color) const -> void {
|
||||||
|
style.base_color_normal = ColorToInt(Fade(color, 0.5));
|
||||||
|
style.base_color_focused = ColorToInt(Fade(color, 0.3));
|
||||||
|
style.base_color_pressed = ColorToInt(Fade(color, 0.8));
|
||||||
|
style.base_color_disabled = style.base_color_normal;
|
||||||
|
|
||||||
|
style.border_color_normal = ColorToInt(Fade(color, 1.0));
|
||||||
|
style.border_color_focused = ColorToInt(Fade(color, 0.7));
|
||||||
|
style.border_color_pressed = ColorToInt(Fade(color, 1.0));
|
||||||
|
style.border_color_disabled = style.base_color_normal;
|
||||||
|
|
||||||
|
style.text_color_normal = ColorToInt(Fade(BLACK, 0.7));
|
||||||
|
style.text_color_focused = ColorToInt(Fade(BLACK, 0.7));
|
||||||
|
style.text_color_pressed = ColorToInt(Fade(BLACK, 0.7));
|
||||||
|
style.text_color_disabled = style.text_color_normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Gui::ApplyBlockColor(Style &style, Color color) const -> void {
|
||||||
|
style.base_color_normal = ColorToInt(Fade(color, 0.5));
|
||||||
|
style.base_color_focused = ColorToInt(Fade(color, 0.3));
|
||||||
|
style.base_color_pressed = ColorToInt(Fade(color, 0.8));
|
||||||
|
style.base_color_disabled = style.base_color_normal;
|
||||||
|
|
||||||
|
style.border_color_normal = ColorToInt(Fade(color, 1.0));
|
||||||
|
style.border_color_focused = ColorToInt(Fade(color, 0.7));
|
||||||
|
style.border_color_pressed = ColorToInt(Fade(color, 1.0));
|
||||||
|
style.border_color_disabled = style.base_color_normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Gui::ApplyTextColor(Style &style, Color color) const -> void {
|
||||||
|
style.text_color_normal = ColorToInt(Fade(color, 1.0));
|
||||||
|
style.text_color_focused = ColorToInt(Fade(color, 1.0));
|
||||||
|
style.text_color_pressed = ColorToInt(Fade(color, 1.0));
|
||||||
|
style.text_color_disabled = ColorToInt(Fade(BLACK, 0.7));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Gui::GetDefaultStyle() const -> DefaultStyle {
|
||||||
|
// Could've iterated over the values but then it wouldn't be as nice to
|
||||||
|
// access...
|
||||||
|
return {GuiGetStyle(DEFAULT, BORDER_COLOR_NORMAL),
|
||||||
|
GuiGetStyle(DEFAULT, BASE_COLOR_NORMAL),
|
||||||
|
GuiGetStyle(DEFAULT, TEXT_COLOR_NORMAL),
|
||||||
|
|
||||||
|
GuiGetStyle(DEFAULT, BORDER_COLOR_FOCUSED),
|
||||||
|
GuiGetStyle(DEFAULT, BASE_COLOR_FOCUSED),
|
||||||
|
GuiGetStyle(DEFAULT, TEXT_COLOR_FOCUSED),
|
||||||
|
|
||||||
|
GuiGetStyle(DEFAULT, BORDER_COLOR_PRESSED),
|
||||||
|
GuiGetStyle(DEFAULT, BASE_COLOR_PRESSED),
|
||||||
|
GuiGetStyle(DEFAULT, TEXT_COLOR_PRESSED),
|
||||||
|
|
||||||
|
GuiGetStyle(DEFAULT, BORDER_COLOR_DISABLED),
|
||||||
|
GuiGetStyle(DEFAULT, BASE_COLOR_DISABLED),
|
||||||
|
GuiGetStyle(DEFAULT, TEXT_COLOR_DISABLED),
|
||||||
|
|
||||||
|
GuiGetStyle(DEFAULT, BACKGROUND_COLOR),
|
||||||
|
GuiGetStyle(DEFAULT, LINE_COLOR),
|
||||||
|
|
||||||
|
GuiGetStyle(DEFAULT, TEXT_SIZE),
|
||||||
|
GuiGetStyle(DEFAULT, TEXT_SPACING),
|
||||||
|
GuiGetStyle(DEFAULT, TEXT_LINE_SPACING),
|
||||||
|
GuiGetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL),
|
||||||
|
GuiGetStyle(DEFAULT, TEXT_WRAP_MODE)};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Gui::SetDefaultStyle(const DefaultStyle &style) const -> void {
|
||||||
|
GuiSetStyle(DEFAULT, BORDER_COLOR_NORMAL, style.border_color_normal);
|
||||||
|
GuiSetStyle(DEFAULT, BASE_COLOR_NORMAL, style.base_color_normal);
|
||||||
|
GuiSetStyle(DEFAULT, TEXT_COLOR_NORMAL, style.text_color_normal);
|
||||||
|
|
||||||
|
GuiSetStyle(DEFAULT, BORDER_COLOR_FOCUSED, style.border_color_focused);
|
||||||
|
GuiSetStyle(DEFAULT, BASE_COLOR_FOCUSED, style.base_color_focused);
|
||||||
|
GuiSetStyle(DEFAULT, TEXT_COLOR_FOCUSED, style.text_color_focused);
|
||||||
|
|
||||||
|
GuiSetStyle(DEFAULT, BORDER_COLOR_PRESSED, style.border_color_pressed);
|
||||||
|
GuiSetStyle(DEFAULT, BASE_COLOR_PRESSED, style.base_color_pressed);
|
||||||
|
GuiSetStyle(DEFAULT, TEXT_COLOR_PRESSED, style.text_color_pressed);
|
||||||
|
|
||||||
|
GuiSetStyle(DEFAULT, BORDER_COLOR_DISABLED, style.border_color_disabled);
|
||||||
|
GuiSetStyle(DEFAULT, BASE_COLOR_DISABLED, style.base_color_disabled);
|
||||||
|
GuiSetStyle(DEFAULT, TEXT_COLOR_DISABLED, style.text_color_disabled);
|
||||||
|
|
||||||
|
GuiSetStyle(DEFAULT, BACKGROUND_COLOR, style.background_color);
|
||||||
|
GuiSetStyle(DEFAULT, LINE_COLOR, style.line_color);
|
||||||
|
|
||||||
|
GuiSetStyle(DEFAULT, TEXT_SIZE, style.text_size);
|
||||||
|
GuiSetStyle(DEFAULT, TEXT_SPACING, style.text_spacing);
|
||||||
|
GuiSetStyle(DEFAULT, TEXT_LINE_SPACING, style.text_line_spacing);
|
||||||
|
GuiSetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL, style.text_alignment_vertical);
|
||||||
|
GuiSetStyle(DEFAULT, TEXT_WRAP_MODE, style.text_wrap_mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Gui::GetComponentStyle(int component) const -> ComponentStyle {
|
||||||
|
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 Gui::SetComponentStyle(int component, const ComponentStyle &style) const
|
||||||
|
-> void {
|
||||||
|
GuiSetStyle(component, BORDER_COLOR_NORMAL, style.border_color_normal);
|
||||||
|
GuiSetStyle(component, BASE_COLOR_NORMAL, style.base_color_normal);
|
||||||
|
GuiSetStyle(component, TEXT_COLOR_NORMAL, style.text_color_normal);
|
||||||
|
|
||||||
|
GuiSetStyle(component, BORDER_COLOR_FOCUSED, style.border_color_focused);
|
||||||
|
GuiSetStyle(component, BASE_COLOR_FOCUSED, style.base_color_focused);
|
||||||
|
GuiSetStyle(component, TEXT_COLOR_FOCUSED, style.text_color_focused);
|
||||||
|
|
||||||
|
GuiSetStyle(component, BORDER_COLOR_PRESSED, style.border_color_pressed);
|
||||||
|
GuiSetStyle(component, BASE_COLOR_PRESSED, style.base_color_pressed);
|
||||||
|
GuiSetStyle(component, TEXT_COLOR_PRESSED, style.text_color_pressed);
|
||||||
|
|
||||||
|
GuiSetStyle(component, BORDER_COLOR_DISABLED, style.border_color_disabled);
|
||||||
|
GuiSetStyle(component, BASE_COLOR_DISABLED, style.base_color_disabled);
|
||||||
|
GuiSetStyle(component, TEXT_COLOR_DISABLED, style.text_color_disabled);
|
||||||
|
|
||||||
|
GuiSetStyle(component, BORDER_WIDTH, style.border_width);
|
||||||
|
GuiSetStyle(component, TEXT_PADDING, style.text_padding);
|
||||||
|
GuiSetStyle(component, TEXT_ALIGNMENT, style.text_alignment);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Gui::DrawButton(Rectangle bounds, const std::string &label, Color color,
|
||||||
|
bool enabled, int font_size) const -> int {
|
||||||
|
// Save original styling
|
||||||
|
const DefaultStyle original_default = GetDefaultStyle();
|
||||||
|
const ComponentStyle original_button = GetComponentStyle(BUTTON);
|
||||||
|
|
||||||
|
// Change styling
|
||||||
|
DefaultStyle style_default = original_default;
|
||||||
|
ComponentStyle style_button = original_button;
|
||||||
|
style_default.text_size = font_size;
|
||||||
|
ApplyColor(style_button, color);
|
||||||
|
SetDefaultStyle(style_default);
|
||||||
|
SetComponentStyle(BUTTON, style_button);
|
||||||
|
|
||||||
|
const int state = GuiGetState();
|
||||||
|
if (!enabled || WindowOpen()) {
|
||||||
|
GuiSetState(STATE_DISABLED);
|
||||||
|
}
|
||||||
|
int pressed = GuiButton(bounds, label.data());
|
||||||
|
if (!enabled || WindowOpen()) {
|
||||||
|
GuiSetState(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore original styling
|
||||||
|
SetDefaultStyle(original_default);
|
||||||
|
SetComponentStyle(BUTTON, original_button);
|
||||||
|
|
||||||
|
return pressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Gui::DrawMenuButton(int x, int y, int width, int height,
|
||||||
|
const std::string &label, Color color, bool enabled,
|
||||||
|
int font_size) const -> int {
|
||||||
|
Rectangle bounds = menu_grid.Bounds(x, y, width, height);
|
||||||
|
return DrawButton(bounds, label, color, enabled, font_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Gui::DrawToggleSlider(Rectangle bounds, const std::string &off_label,
|
||||||
|
const std::string &on_label, int *active,
|
||||||
|
Color color, bool enabled, int font_size) const
|
||||||
|
-> int {
|
||||||
|
// Save original styling
|
||||||
|
const DefaultStyle original_default = GetDefaultStyle();
|
||||||
|
const ComponentStyle original_slider = GetComponentStyle(SLIDER);
|
||||||
|
const ComponentStyle original_toggle = GetComponentStyle(TOGGLE);
|
||||||
|
|
||||||
|
// Change styling
|
||||||
|
DefaultStyle style_default = original_default;
|
||||||
|
ComponentStyle style_slider = original_slider;
|
||||||
|
ComponentStyle style_toggle = original_toggle;
|
||||||
|
style_default.text_size = font_size;
|
||||||
|
ApplyColor(style_slider, color);
|
||||||
|
ApplyColor(style_toggle, color);
|
||||||
|
SetDefaultStyle(style_default);
|
||||||
|
SetComponentStyle(SLIDER, style_slider);
|
||||||
|
SetComponentStyle(TOGGLE, style_toggle);
|
||||||
|
|
||||||
|
const int state = GuiGetState();
|
||||||
|
if (!enabled || WindowOpen()) {
|
||||||
|
GuiSetState(STATE_DISABLED);
|
||||||
|
}
|
||||||
|
int pressed = GuiToggleSlider(
|
||||||
|
bounds, std::format("{};{}", off_label, on_label).data(), active);
|
||||||
|
if (!enabled || WindowOpen()) {
|
||||||
|
GuiSetState(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore original styling
|
||||||
|
SetDefaultStyle(original_default);
|
||||||
|
SetComponentStyle(SLIDER, original_slider);
|
||||||
|
SetComponentStyle(TOGGLE, original_toggle);
|
||||||
|
|
||||||
|
return pressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Gui::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, int font_size) const
|
||||||
|
-> int {
|
||||||
|
Rectangle bounds = menu_grid.Bounds(x, y, width, height);
|
||||||
|
return DrawToggleSlider(bounds, off_label, on_label, active, color, enabled,
|
||||||
|
font_size);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto Gui::DrawSpinner(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 DefaultStyle original_default = GetDefaultStyle();
|
||||||
|
const ComponentStyle original_valuebox = GetComponentStyle(VALUEBOX);
|
||||||
|
const ComponentStyle original_button = GetComponentStyle(BUTTON);
|
||||||
|
|
||||||
|
// Change styling
|
||||||
|
DefaultStyle style_default = original_default;
|
||||||
|
ComponentStyle style_valuebox = original_valuebox;
|
||||||
|
ComponentStyle style_button = original_button;
|
||||||
|
style_default.text_size = font_size;
|
||||||
|
ApplyColor(style_valuebox, color);
|
||||||
|
ApplyColor(style_button, color);
|
||||||
|
SetDefaultStyle(style_default);
|
||||||
|
SetComponentStyle(VALUEBOX, style_valuebox);
|
||||||
|
SetComponentStyle(BUTTON, style_button);
|
||||||
|
|
||||||
|
const int state = GuiGetState();
|
||||||
|
if (!enabled || WindowOpen()) {
|
||||||
|
GuiSetState(STATE_DISABLED);
|
||||||
|
}
|
||||||
|
int pressed = GuiSpinner(bounds, "", label.data(), value, min, max, false);
|
||||||
|
if (!enabled || WindowOpen()) {
|
||||||
|
GuiSetState(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore original styling
|
||||||
|
SetDefaultStyle(original_default);
|
||||||
|
SetComponentStyle(VALUEBOX, original_valuebox);
|
||||||
|
SetComponentStyle(BUTTON, style_button);
|
||||||
|
|
||||||
|
return pressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Gui::DrawMenuSpinner(int x, int y, int width, int height,
|
||||||
|
const std::string &label, int *value, int min,
|
||||||
|
int max, Color color, bool enabled,
|
||||||
|
int font_size) const -> int {
|
||||||
|
Rectangle bounds = menu_grid.Bounds(x, y, width, height);
|
||||||
|
return DrawSpinner(bounds, label, value, min, max, color, enabled, font_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Gui::DrawLabel(Rectangle bounds, const std::string &text, Color color,
|
||||||
|
bool enabled, int font_size) const -> int {
|
||||||
|
// Save original styling
|
||||||
|
const DefaultStyle original_default = GetDefaultStyle();
|
||||||
|
const ComponentStyle original_label = GetComponentStyle(LABEL);
|
||||||
|
|
||||||
|
// Change styling
|
||||||
|
DefaultStyle style_default = original_default;
|
||||||
|
ComponentStyle style_label = original_label;
|
||||||
|
style_default.text_size = font_size;
|
||||||
|
ApplyTextColor(style_label, color);
|
||||||
|
SetDefaultStyle(style_default);
|
||||||
|
SetComponentStyle(LABEL, style_label);
|
||||||
|
|
||||||
|
const int state = GuiGetState();
|
||||||
|
if (!enabled || WindowOpen()) {
|
||||||
|
GuiSetState(STATE_DISABLED);
|
||||||
|
}
|
||||||
|
int pressed = GuiLabel(bounds, text.data());
|
||||||
|
if (!enabled || WindowOpen()) {
|
||||||
|
GuiSetState(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore original styling
|
||||||
|
SetDefaultStyle(original_default);
|
||||||
|
SetComponentStyle(LABEL, original_label);
|
||||||
|
|
||||||
|
return pressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Gui::DrawBoardBlock(int x, int y, int width, int height, Color color,
|
||||||
|
bool enabled) const -> bool {
|
||||||
|
ComponentStyle style = GetComponentStyle(BUTTON);
|
||||||
|
ApplyBlockColor(style, color);
|
||||||
|
|
||||||
|
Rectangle bounds = board_grid.SquareBounds(x, y, width, height);
|
||||||
|
|
||||||
|
bool focused =
|
||||||
|
CheckCollisionPointRec(input.mouse - Vector2(0, MENU_HEIGHT), bounds);
|
||||||
|
bool pressed =
|
||||||
|
Block(x, y, width, height, false).Covers(input.sel_x, input.sel_y);
|
||||||
|
|
||||||
|
// Background to make faded colors work
|
||||||
|
DrawRectangleRec(bounds, RAYWHITE);
|
||||||
|
|
||||||
|
Color base = GetColor(style.base_color_normal);
|
||||||
|
Color border = GetColor(style.base_color_normal);
|
||||||
|
if (pressed) {
|
||||||
|
base = GetColor(style.base_color_pressed);
|
||||||
|
border = GetColor(style.base_color_pressed);
|
||||||
|
}
|
||||||
|
if (focused) {
|
||||||
|
base = GetColor(style.base_color_focused);
|
||||||
|
border = GetColor(style.base_color_focused);
|
||||||
|
}
|
||||||
|
if (focused && IsMouseButtonDown(MOUSE_BUTTON_LEFT)) {
|
||||||
|
base = GetColor(style.base_color_pressed);
|
||||||
|
border = GetColor(style.base_color_pressed);
|
||||||
|
}
|
||||||
|
if (!enabled) {
|
||||||
|
base = BOARD_COLOR_RESTRICTED;
|
||||||
|
}
|
||||||
|
DrawRectangleRec(bounds, base);
|
||||||
|
if (enabled) {
|
||||||
|
DrawRectangleLinesEx(bounds, 2.0, border);
|
||||||
|
}
|
||||||
|
|
||||||
|
return focused && enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Gui::WindowOpen() const -> bool { return save_window || help_window; }
|
||||||
|
|
||||||
|
auto Gui::DrawMenuHeader(Color color) const -> void {
|
||||||
|
int preset = state.current_preset;
|
||||||
|
DrawMenuSpinner(0, 0, 1, 1, "Preset: ", &preset, -1, state.presets.size(),
|
||||||
|
color, !input.editing);
|
||||||
|
if (preset > state.current_preset) {
|
||||||
|
input.NextPreset();
|
||||||
|
} else if (preset < state.current_preset) {
|
||||||
|
input.PreviousPreset();
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawMenuButton(1, 0, 1, 1,
|
||||||
|
std::format("Puzzle: \"{}\"",
|
||||||
|
state.comments.at(state.current_preset).substr(2)),
|
||||||
|
color, false);
|
||||||
|
|
||||||
|
int editing = input.editing;
|
||||||
|
DrawMenuToggleSlider(2, 0, 1, 1, "Puzzle Mode (Tab)", "Edit Mode (Tab)",
|
||||||
|
&editing, color);
|
||||||
|
if (editing != input.editing) {
|
||||||
|
input.ToggleEditing();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Gui::DrawGraphInfo(Color color) const -> void {
|
||||||
|
DrawMenuButton(0, 1, 1, 1,
|
||||||
|
std::format("Found {} States ({} Winning)",
|
||||||
|
state.states.size(), state.winning_states.size()),
|
||||||
|
color, false);
|
||||||
|
|
||||||
|
DrawMenuButton(1, 1, 1, 1,
|
||||||
|
std::format("Found {} Transitions", state.springs.size()),
|
||||||
|
color, false);
|
||||||
|
|
||||||
|
DrawMenuButton(2, 1, 1, 1,
|
||||||
|
std::format("{} Moves to Nearest Solution",
|
||||||
|
state.winning_path.size() > 0
|
||||||
|
? state.winning_path.size() - 1
|
||||||
|
: 0),
|
||||||
|
color, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Gui::DrawGraphControls(Color color) const -> void {
|
||||||
|
if (DrawMenuButton(1, 2, 1, 1, "Populate Graph (G)", color)) {
|
||||||
|
input.FillGraph();
|
||||||
|
}
|
||||||
|
|
||||||
|
int mark_path = input.mark_path;
|
||||||
|
DrawMenuToggleSlider(2, 2, 1, 1, "Path Hidden (U)", "Path Shown (U)",
|
||||||
|
&mark_path, color);
|
||||||
|
if (mark_path != input.mark_path) {
|
||||||
|
input.ToggleMarkPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DrawMenuButton(1, 3, 1, 1, "Clear Graph (C)", color)) {
|
||||||
|
input.ClearGraph();
|
||||||
|
}
|
||||||
|
|
||||||
|
int mark_solutions = input.mark_solutions;
|
||||||
|
DrawMenuToggleSlider(2, 3, 1, 1, "Solutions Hidden (I)",
|
||||||
|
"Solutions Shown (I)", &mark_solutions, color);
|
||||||
|
if (mark_solutions != input.mark_solutions) {
|
||||||
|
input.ToggleMarkSolutions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Gui::DrawCameraControls(Color color) const -> void {
|
||||||
|
int lock_camera = input.camera_lock;
|
||||||
|
DrawMenuToggleSlider(0, 2, 1, 1, "Free Camera (L)", "Locked Camera (L)",
|
||||||
|
&lock_camera, color);
|
||||||
|
if (lock_camera != input.camera_lock) {
|
||||||
|
input.ToggleCameraLock();
|
||||||
|
}
|
||||||
|
|
||||||
|
int projection = camera.projection == CAMERA_ORTHOGRAPHIC;
|
||||||
|
DrawMenuToggleSlider(0, 3, 1, 1, "Perspective (Alt)", "Orthographic (Alt)",
|
||||||
|
&projection, color);
|
||||||
|
if (projection != camera.projection == CAMERA_ORTHOGRAPHIC) {
|
||||||
|
input.ToggleCameraProjection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Gui::DrawPuzzleControls(Color color) const -> void {
|
||||||
|
auto nth = [&](int n) {
|
||||||
|
if (n == 11 || n == 12 || n == 13)
|
||||||
|
return "th";
|
||||||
|
if (n % 10 == 1)
|
||||||
|
return "st";
|
||||||
|
if (n % 10 == 2)
|
||||||
|
return "nd";
|
||||||
|
if (n % 10 == 3)
|
||||||
|
return "rd";
|
||||||
|
return "th";
|
||||||
|
};
|
||||||
|
int visits = state.visited_states.at(state.current_state);
|
||||||
|
DrawMenuButton(0, 4, 1, 1,
|
||||||
|
std::format("{} Moves ({}{} Time at this State)",
|
||||||
|
state.total_moves, visits, nth(visits)),
|
||||||
|
color, false);
|
||||||
|
|
||||||
|
if (DrawMenuButton(1, 4, 1, 1, "Make Optimal Move (Space)", color)) {
|
||||||
|
input.MakeOptimalMove();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DrawMenuButton(2, 4, 1, 1, "Undo Last Move (Backspace)", color)) {
|
||||||
|
input.UndoLastMove();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DrawMenuButton(0, 5, 1, 1, "Go to Nearest Solution (B)", color)) {
|
||||||
|
input.GoToNearestTarget();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DrawMenuButton(1, 5, 1, 1, "Go to Worst State (V)", color)) {
|
||||||
|
input.GoToWorstState();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DrawMenuButton(2, 5, 1, 1, "Go to Starting State (R)", color)) {
|
||||||
|
input.ResetState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Gui::DrawEditControls(Color color) const -> void {
|
||||||
|
// Toggle Target Block
|
||||||
|
if (DrawMenuButton(0, 4, 1, 1, "Toggle Target Block (T)", color)) {
|
||||||
|
input.ToggleTargetBlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle Wall Block
|
||||||
|
if (DrawMenuButton(0, 5, 1, 1, "Toggle Wall Block (Y)", color)) {
|
||||||
|
input.ToggleWallBlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle Restricted/Free Block Movement
|
||||||
|
int free = !state.current_state.restricted;
|
||||||
|
DrawMenuToggleSlider(1, 4, 1, 1, "Restricted (F)", "Free (F)", &free, color);
|
||||||
|
if (free != !state.current_state.restricted) {
|
||||||
|
input.ToggleRestrictedMovement();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear Goal
|
||||||
|
if (DrawMenuButton(1, 5, 1, 1, "Clear Goal (X)", color)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Column Count Spinner
|
||||||
|
int columns = state.current_state.width;
|
||||||
|
DrawMenuSpinner(2, 4, 1, 1, "Cols: ", &columns, 1, 9, color);
|
||||||
|
if (columns > state.current_state.width) {
|
||||||
|
input.AddBoardColumn();
|
||||||
|
} else if (columns < state.current_state.width) {
|
||||||
|
input.RemoveBoardColumn();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Row Count Spinner
|
||||||
|
int rows = state.current_state.height;
|
||||||
|
DrawMenuSpinner(2, 5, 1, 1, "Rows: ", &rows, 1, 9, color);
|
||||||
|
if (rows > state.current_state.height) {
|
||||||
|
input.AddBoardRow();
|
||||||
|
} else if (rows < state.current_state.height) {
|
||||||
|
input.RemoveBoardRow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Gui::DrawMenuFooter(Color color) -> void {
|
||||||
|
DrawMenuButton(0, 6, 2, 1,
|
||||||
|
std::format("State: \"{}\"", state.current_state.state), color,
|
||||||
|
false);
|
||||||
|
|
||||||
|
if (DrawMenuButton(2, 6, 1, 1, "Save as Preset", color)) {
|
||||||
|
save_window = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Gui::GetBackgroundColor() const -> Color {
|
||||||
|
return GetColor(GuiGetStyle(DEFAULT, BACKGROUND_COLOR));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Gui::HelpPopup() -> void {}
|
||||||
|
|
||||||
|
auto Gui::DrawSavePresetPopup() -> void {
|
||||||
|
if (!save_window) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int width = 450;
|
||||||
|
int height = 150;
|
||||||
|
// Returns the pressed button index
|
||||||
|
int button = GuiTextInputBox(Rectangle((GetScreenWidth() - width) / 2.0,
|
||||||
|
(GetScreenHeight() - height) / 2.0,
|
||||||
|
width, height),
|
||||||
|
"Save as Preset", "Enter Preset Name",
|
||||||
|
"Ok;Cancel", preset_name.data(), 255, NULL);
|
||||||
|
if (button == 1) {
|
||||||
|
state.AppendPresetFile(preset_name.data());
|
||||||
|
}
|
||||||
|
if ((button == 0) || (button == 1) || (button == 2)) {
|
||||||
|
save_window = false;
|
||||||
|
TextCopy(preset_name.data(), "\0");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Gui::DrawMainMenu() -> void {
|
||||||
|
menu_grid.UpdateBounds(0, 0, GetScreenWidth(), MENU_HEIGHT);
|
||||||
|
|
||||||
|
DrawMenuHeader(GRAY);
|
||||||
|
DrawGraphInfo(ORANGE);
|
||||||
|
DrawGraphControls(RED);
|
||||||
|
DrawCameraControls(DARKGREEN);
|
||||||
|
|
||||||
|
if (input.editing) {
|
||||||
|
DrawEditControls(PURPLE);
|
||||||
|
} else {
|
||||||
|
DrawPuzzleControls(BLUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawMenuFooter(GRAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Gui::DrawPuzzleBoard() -> void {
|
||||||
|
board_grid.UpdateBounds(
|
||||||
|
0, MENU_HEIGHT, GetScreenWidth() / 2, GetScreenHeight() - MENU_HEIGHT,
|
||||||
|
state.current_state.width, state.current_state.height);
|
||||||
|
|
||||||
|
// Draw outer border
|
||||||
|
Rectangle bounds = board_grid.SquareBounds();
|
||||||
|
DrawRectangleRec(bounds, state.current_state.IsWon()
|
||||||
|
? 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,
|
||||||
|
state.current_state.restricted ? BOARD_COLOR_RESTRICTED
|
||||||
|
: BOARD_COLOR_FREE);
|
||||||
|
|
||||||
|
// Draw target opening
|
||||||
|
if (state.current_state.HasWinCondition()) {
|
||||||
|
int target_x = state.current_state.target_x;
|
||||||
|
int target_y = state.current_state.target_y;
|
||||||
|
Block target_block = state.current_state.GetTargetBlock();
|
||||||
|
Rectangle target_bounds = board_grid.SquareBounds(
|
||||||
|
target_x, target_y, target_block.width, target_block.height);
|
||||||
|
|
||||||
|
Color opening_color = Fade(
|
||||||
|
state.current_state.IsWon() ? BOARD_COLOR_WON : BOARD_COLOR_RESTRICTED,
|
||||||
|
0.3);
|
||||||
|
|
||||||
|
if (target_x == 0) {
|
||||||
|
|
||||||
|
// Left opening
|
||||||
|
DrawRectangle(target_bounds.x - BOARD_PADDING, target_bounds.y,
|
||||||
|
BOARD_PADDING, target_bounds.height, RAYWHITE);
|
||||||
|
DrawRectangle(target_bounds.x - BOARD_PADDING, target_bounds.y,
|
||||||
|
BOARD_PADDING, target_bounds.height, opening_color);
|
||||||
|
}
|
||||||
|
if (target_x + target_block.width == state.current_state.width) {
|
||||||
|
|
||||||
|
// Right opening
|
||||||
|
DrawRectangle(target_bounds.x + target_bounds.width, target_bounds.y,
|
||||||
|
BOARD_PADDING, target_bounds.height, RAYWHITE);
|
||||||
|
DrawRectangle(target_bounds.x + target_bounds.width, target_bounds.y,
|
||||||
|
BOARD_PADDING, target_bounds.height, opening_color);
|
||||||
|
}
|
||||||
|
if (target_y == 0) {
|
||||||
|
|
||||||
|
// Top opening
|
||||||
|
DrawRectangle(target_bounds.x, target_bounds.y - BOARD_PADDING,
|
||||||
|
target_bounds.width, BOARD_PADDING, RAYWHITE);
|
||||||
|
DrawRectangle(target_bounds.x, target_bounds.y - BOARD_PADDING,
|
||||||
|
target_bounds.width, BOARD_PADDING, opening_color);
|
||||||
|
}
|
||||||
|
if (target_y + target_block.height == state.current_state.height) {
|
||||||
|
|
||||||
|
// Bottom opening
|
||||||
|
DrawRectangle(target_bounds.x, target_bounds.y + target_bounds.height,
|
||||||
|
target_bounds.width, BOARD_PADDING, RAYWHITE);
|
||||||
|
DrawRectangle(target_bounds.x, target_bounds.y + target_bounds.height,
|
||||||
|
target_bounds.width, BOARD_PADDING, opening_color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw empty cells. Also set hovered blocks
|
||||||
|
input.hov_x = -1;
|
||||||
|
input.hov_y = -1;
|
||||||
|
for (int x = 0; x < board_grid.columns; ++x) {
|
||||||
|
for (int y = 0; y < board_grid.rows; ++y) {
|
||||||
|
DrawRectangleRec(board_grid.SquareBounds(x, y, 1, 1), RAYWHITE);
|
||||||
|
|
||||||
|
Rectangle hov_bounds = board_grid.SquareBounds(x, y, 1, 1);
|
||||||
|
hov_bounds.x -= BOARD_PADDING;
|
||||||
|
hov_bounds.y -= BOARD_PADDING;
|
||||||
|
hov_bounds.width += BOARD_PADDING;
|
||||||
|
hov_bounds.height += BOARD_PADDING;
|
||||||
|
if (CheckCollisionPointRec(GetMousePosition() - Vector2(0, MENU_HEIGHT),
|
||||||
|
hov_bounds)) {
|
||||||
|
input.hov_x = x;
|
||||||
|
input.hov_y = y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw blocks
|
||||||
|
for (const Block &block : state.current_state) {
|
||||||
|
Color c = BLOCK_COLOR;
|
||||||
|
if (block.target) {
|
||||||
|
c = TARGET_BLOCK_COLOR;
|
||||||
|
} else if (block.immovable) {
|
||||||
|
c = WALL_COLOR;
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawBoardBlock(block.x, block.y, block.width, block.height, c,
|
||||||
|
!block.immovable);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw block placing
|
||||||
|
if (input.editing && input.has_block_add_xy) {
|
||||||
|
if (input.hov_x >= 0 && input.hov_x < state.current_state.width &&
|
||||||
|
input.hov_y >= 0 && input.hov_y < state.current_state.height &&
|
||||||
|
input.hov_x >= input.block_add_x && input.hov_y >= input.block_add_y) {
|
||||||
|
bool collides = false;
|
||||||
|
for (const Block &block : state.current_state) {
|
||||||
|
if (block.Collides(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) {
|
||||||
|
DrawBoardBlock(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, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Gui::DrawGraphOverlay(int fps, int ups) -> void {
|
||||||
|
graph_overlay_grid.UpdateBounds(GetScreenWidth() / 2, MENU_HEIGHT);
|
||||||
|
|
||||||
|
DrawLabel(graph_overlay_grid.Bounds(0, 0, 1, 1),
|
||||||
|
std::format("Dist: {:0>7.2f}", camera.distance), BLACK);
|
||||||
|
DrawLabel(graph_overlay_grid.Bounds(0, 1, 1, 1),
|
||||||
|
std::format("FoV: {:0>6.2f}", camera.fov), BLACK);
|
||||||
|
DrawLabel(graph_overlay_grid.Bounds(0, 2, 1, 1),
|
||||||
|
std::format("FPS: {:0>3}", fps), LIME);
|
||||||
|
DrawLabel(graph_overlay_grid.Bounds(0, 3, 1, 1),
|
||||||
|
std::format("UPS: {:0>3}", ups), ORANGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Gui::Update() -> void { input.disable = WindowOpen(); }
|
||||||
476
src/input.cpp
476
src/input.cpp
@ -2,6 +2,7 @@
|
|||||||
#include "config.hpp"
|
#include "config.hpp"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <print>
|
||||||
#include <raylib.h>
|
#include <raylib.h>
|
||||||
|
|
||||||
#ifdef TRACY
|
#ifdef TRACY
|
||||||
@ -9,43 +10,161 @@
|
|||||||
#include <tracy/Tracy.hpp>
|
#include <tracy/Tracy.hpp>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
auto InputHandler::HandleMouseHover() -> void {
|
auto InputHandler::InitHandlers() -> void {
|
||||||
const int board_width = GetScreenWidth() / 2.0 - 2 * BOARD_PADDING;
|
// The order matters if multiple handlers are registered to the same key
|
||||||
const int board_height = GetScreenHeight() - MENU_HEIGHT - 2 * BOARD_PADDING;
|
|
||||||
int block_size = std::min(board_width / state.current_state.width,
|
|
||||||
board_height / state.current_state.height) -
|
|
||||||
2 * BLOCK_PADDING;
|
|
||||||
int x_offset = (board_width - (block_size + 2 * BLOCK_PADDING) *
|
|
||||||
state.current_state.width) /
|
|
||||||
2.0;
|
|
||||||
int y_offset = (board_height - (block_size + 2 * BLOCK_PADDING) *
|
|
||||||
state.current_state.height) /
|
|
||||||
2.0;
|
|
||||||
|
|
||||||
Vector2 m = GetMousePosition();
|
RegisterGenericHandler(&InputHandler::CameraPan);
|
||||||
if (m.x < x_offset) {
|
RegisterGenericHandler(&InputHandler::CameraRotate);
|
||||||
hov_x = 100;
|
RegisterGenericHandler(&InputHandler::CameraZoom);
|
||||||
} else {
|
RegisterGenericHandler(&InputHandler::CameraFov);
|
||||||
hov_x = (m.x - x_offset) / (block_size + 2 * BLOCK_PADDING);
|
RegisterGenericHandler(&InputHandler::MouseHover);
|
||||||
|
|
||||||
|
RegisterMousePressedHandler(MOUSE_BUTTON_LEFT, &InputHandler::CameraStartPan);
|
||||||
|
RegisterMousePressedHandler(MOUSE_BUTTON_LEFT, &InputHandler::SelectBlock);
|
||||||
|
RegisterMousePressedHandler(MOUSE_BUTTON_LEFT, &InputHandler::AddBlock);
|
||||||
|
RegisterMousePressedHandler(MOUSE_BUTTON_LEFT, &InputHandler::StartAddBlock);
|
||||||
|
RegisterMousePressedHandler(MOUSE_BUTTON_RIGHT,
|
||||||
|
&InputHandler::CameraStartRotate);
|
||||||
|
RegisterMousePressedHandler(MOUSE_BUTTON_RIGHT, &InputHandler::RemoveBlock);
|
||||||
|
RegisterMousePressedHandler(MOUSE_BUTTON_RIGHT, &InputHandler::ClearAddBlock);
|
||||||
|
RegisterMousePressedHandler(MOUSE_BUTTON_MIDDLE, &InputHandler::PlaceGoal);
|
||||||
|
|
||||||
|
RegisterMouseReleasedHandler(MOUSE_BUTTON_LEFT, &InputHandler::CameraStopPan);
|
||||||
|
RegisterMouseReleasedHandler(MOUSE_BUTTON_RIGHT,
|
||||||
|
&InputHandler::CameraStopRotate);
|
||||||
|
|
||||||
|
RegisterKeyPressedHandler(KEY_W, &InputHandler::MoveBlockNor);
|
||||||
|
RegisterKeyPressedHandler(KEY_D, &InputHandler::MoveBlockEas);
|
||||||
|
RegisterKeyPressedHandler(KEY_S, &InputHandler::MoveBlockSou);
|
||||||
|
RegisterKeyPressedHandler(KEY_A, &InputHandler::MoveBlockWes);
|
||||||
|
RegisterKeyPressedHandler(KEY_P, &InputHandler::PrintState);
|
||||||
|
RegisterKeyPressedHandler(KEY_N, &InputHandler::PreviousPreset);
|
||||||
|
RegisterKeyPressedHandler(KEY_M, &InputHandler::NextPreset);
|
||||||
|
RegisterKeyPressedHandler(KEY_R, &InputHandler::ResetState);
|
||||||
|
RegisterKeyPressedHandler(KEY_G, &InputHandler::FillGraph);
|
||||||
|
RegisterKeyPressedHandler(KEY_C, &InputHandler::ClearGraph);
|
||||||
|
RegisterKeyPressedHandler(KEY_I, &InputHandler::ToggleMarkSolutions);
|
||||||
|
RegisterKeyPressedHandler(KEY_O, &InputHandler::ToggleConnectSolutions);
|
||||||
|
RegisterKeyPressedHandler(KEY_U, &InputHandler::ToggleMarkPath);
|
||||||
|
RegisterKeyPressedHandler(KEY_SPACE, &InputHandler::MakeOptimalMove);
|
||||||
|
RegisterKeyPressedHandler(KEY_V, &InputHandler::GoToWorstState);
|
||||||
|
RegisterKeyPressedHandler(KEY_B, &InputHandler::GoToNearestTarget);
|
||||||
|
RegisterKeyPressedHandler(KEY_BACKSPACE, &InputHandler::UndoLastMove);
|
||||||
|
RegisterKeyPressedHandler(KEY_F, &InputHandler::ToggleRestrictedMovement);
|
||||||
|
RegisterKeyPressedHandler(KEY_T, &InputHandler::ToggleTargetBlock);
|
||||||
|
RegisterKeyPressedHandler(KEY_Y, &InputHandler::ToggleWallBlock);
|
||||||
|
RegisterKeyPressedHandler(KEY_UP, &InputHandler::AddBoardRow);
|
||||||
|
RegisterKeyPressedHandler(KEY_RIGHT, &InputHandler::AddBoardColumn);
|
||||||
|
RegisterKeyPressedHandler(KEY_DOWN, &InputHandler::RemoveBoardRow);
|
||||||
|
RegisterKeyPressedHandler(KEY_LEFT, &InputHandler::RemoveBoardColumn);
|
||||||
|
RegisterKeyPressedHandler(KEY_TAB, &InputHandler::ToggleEditing);
|
||||||
|
RegisterKeyPressedHandler(KEY_L, &InputHandler::ToggleCameraLock);
|
||||||
|
RegisterKeyPressedHandler(KEY_LEFT_ALT,
|
||||||
|
&InputHandler::ToggleCameraProjection);
|
||||||
|
RegisterKeyPressedHandler(KEY_X, &InputHandler::ClearGoal);
|
||||||
}
|
}
|
||||||
if (m.y - MENU_HEIGHT < y_offset) {
|
|
||||||
hov_y = 100;
|
auto InputHandler::MouseInMenuPane() -> bool { return mouse.y < MENU_HEIGHT; }
|
||||||
} else {
|
|
||||||
hov_y = (m.y - MENU_HEIGHT - y_offset) / (block_size + 2 * BLOCK_PADDING);
|
auto InputHandler::MouseInBoardPane() -> bool {
|
||||||
|
return mouse.x < GetScreenWidth() / 2.0 && mouse.y >= MENU_HEIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto InputHandler::MouseInGraphPane() -> bool {
|
||||||
|
return mouse.x >= GetScreenWidth() / 2.0 && mouse.y >= MENU_HEIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto InputHandler::MouseHover() -> void {
|
||||||
|
last_mouse = mouse;
|
||||||
|
mouse = GetMousePosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto InputHandler::CameraStartPan() -> void {
|
||||||
|
if (!MouseInGraphPane()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
camera_panning = true;
|
||||||
|
// Enable this if the camera should be pannable even when locked (releasing
|
||||||
|
// the lock in the process):
|
||||||
|
// camera_lock = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto InputHandler::CameraPan() -> void {
|
||||||
|
if (camera_panning) {
|
||||||
|
camera.Pan(last_mouse, mouse);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto InputHandler::HandleMouse() -> void {
|
auto InputHandler::CameraStopPan() -> void { camera_panning = false; }
|
||||||
if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) {
|
|
||||||
// If we clicked a block...
|
auto InputHandler::CameraStartRotate() -> void {
|
||||||
|
if (!MouseInGraphPane()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
camera_rotating = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto InputHandler::CameraRotate() -> void {
|
||||||
|
if (camera_rotating) {
|
||||||
|
camera.Rotate(last_mouse, mouse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto InputHandler::CameraStopRotate() -> void { camera_rotating = false; }
|
||||||
|
|
||||||
|
auto InputHandler::CameraZoom() -> void {
|
||||||
|
if (!MouseInGraphPane() || IsKeyDown(KEY_LEFT_CONTROL)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float wheel = GetMouseWheelMove();
|
||||||
|
|
||||||
|
if (IsKeyDown(KEY_LEFT_SHIFT)) {
|
||||||
|
camera.distance -= wheel * ZOOM_SPEED * ZOOM_MULTIPLIER;
|
||||||
|
} else {
|
||||||
|
camera.distance -= wheel * ZOOM_SPEED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto InputHandler::CameraFov() -> void {
|
||||||
|
if (!MouseInGraphPane() || !IsKeyDown(KEY_LEFT_CONTROL) ||
|
||||||
|
IsKeyDown(KEY_LEFT_SHIFT)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float wheel = GetMouseWheelMove();
|
||||||
|
camera.fov -= wheel * FOV_SPEED;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto InputHandler::ToggleCameraLock() -> void {
|
||||||
|
if (!camera_lock) {
|
||||||
|
camera_panning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
camera_lock = !camera_lock;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto InputHandler::ToggleCameraProjection() -> void {
|
||||||
|
camera.projection = camera.projection == CAMERA_PERSPECTIVE
|
||||||
|
? CAMERA_ORTHOGRAPHIC
|
||||||
|
: CAMERA_PERSPECTIVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto InputHandler::SelectBlock() -> void {
|
||||||
if (state.current_state.GetBlock(hov_x, hov_y).IsValid()) {
|
if (state.current_state.GetBlock(hov_x, hov_y).IsValid()) {
|
||||||
sel_x = hov_x;
|
sel_x = hov_x;
|
||||||
sel_y = hov_y;
|
sel_y = hov_y;
|
||||||
}
|
}
|
||||||
// If we clicked empty space...
|
}
|
||||||
else {
|
|
||||||
// Select a position
|
auto InputHandler::StartAddBlock() -> void {
|
||||||
if (!has_block_add_xy) {
|
if (!editing || state.current_state.GetBlock(hov_x, hov_y).IsValid() ||
|
||||||
|
has_block_add_xy) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (hov_x >= 0 && hov_x < state.current_state.width && hov_y >= 0 &&
|
if (hov_x >= 0 && hov_x < state.current_state.width && hov_y >= 0 &&
|
||||||
hov_y < state.current_state.height) {
|
hov_y < state.current_state.height) {
|
||||||
block_add_x = hov_x;
|
block_add_x = hov_x;
|
||||||
@ -53,8 +172,23 @@ auto InputHandler::HandleMouse() -> void {
|
|||||||
has_block_add_xy = true;
|
has_block_add_xy = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If we have already selected a position
|
|
||||||
else {
|
auto InputHandler::ClearAddBlock() -> void {
|
||||||
|
if (!editing || !has_block_add_xy) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
block_add_x = -1;
|
||||||
|
block_add_y = -1;
|
||||||
|
has_block_add_xy = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto InputHandler::AddBlock() -> void {
|
||||||
|
if (!editing || state.current_state.GetBlock(hov_x, hov_y).IsValid() ||
|
||||||
|
!has_block_add_xy) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int block_add_width = hov_x - block_add_x + 1;
|
int block_add_width = hov_x - block_add_x + 1;
|
||||||
int block_add_height = hov_y - block_add_y + 1;
|
int block_add_height = hov_y - block_add_y + 1;
|
||||||
if (block_add_width <= 0 || block_add_height <= 0) {
|
if (block_add_width <= 0 || block_add_height <= 0) {
|
||||||
@ -64,13 +198,13 @@ auto InputHandler::HandleMouse() -> void {
|
|||||||
} else if (block_add_x >= 0 &&
|
} else if (block_add_x >= 0 &&
|
||||||
block_add_x + block_add_width <= state.current_state.width &&
|
block_add_x + block_add_width <= state.current_state.width &&
|
||||||
block_add_y >= 0 &&
|
block_add_y >= 0 &&
|
||||||
block_add_y + block_add_height <=
|
block_add_y + block_add_height <= state.current_state.height) {
|
||||||
state.current_state.height) {
|
bool success = state.current_state.AddBlock(Block(
|
||||||
bool success = state.current_state.AddBlock(
|
block_add_x, block_add_y, block_add_width, block_add_height, false));
|
||||||
Block(block_add_x, block_add_y, block_add_width, block_add_height,
|
|
||||||
false));
|
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
|
sel_x = block_add_x;
|
||||||
|
sel_y = block_add_y;
|
||||||
block_add_x = -1;
|
block_add_x = -1;
|
||||||
block_add_y = -1;
|
block_add_y = -1;
|
||||||
has_block_add_xy = false;
|
has_block_add_xy = false;
|
||||||
@ -79,119 +213,271 @@ auto InputHandler::HandleMouse() -> void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto InputHandler::RemoveBlock() -> void {
|
||||||
|
Block block = state.current_state.GetBlock(hov_x, hov_y);
|
||||||
|
if (!editing || has_block_add_xy || !block.IsValid() ||
|
||||||
|
!state.current_state.RemoveBlock(hov_x, hov_y)) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
} else if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT)) {
|
|
||||||
if (state.current_state.RemoveBlock(hov_x, hov_y)) {
|
if (block.Covers(sel_x, sel_y)) {
|
||||||
|
sel_x = 0;
|
||||||
|
sel_y = 0;
|
||||||
|
}
|
||||||
|
|
||||||
state.ClearGraph();
|
state.ClearGraph();
|
||||||
state.edited = true;
|
state.edited = true;
|
||||||
} else if (has_block_add_xy) {
|
|
||||||
block_add_x = -1;
|
|
||||||
block_add_y = -1;
|
|
||||||
has_block_add_xy = false;
|
|
||||||
}
|
}
|
||||||
} else if (IsMouseButtonPressed(MOUSE_BUTTON_MIDDLE)) {
|
|
||||||
if (hov_x >= 0 && hov_x < state.current_state.width && hov_y >= 0 &&
|
auto InputHandler::PlaceGoal() -> void {
|
||||||
hov_y < state.current_state.height) {
|
if (!editing || hov_x < 0 || hov_x >= state.current_state.width ||
|
||||||
|
hov_y < 0 || hov_y >= state.current_state.height) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (state.current_state.SetGoal(hov_x, hov_y)) {
|
if (state.current_state.SetGoal(hov_x, hov_y)) {
|
||||||
// We can't just call state.FindWinningStates() because the
|
// We can't just call state.FindWinningStates() because the
|
||||||
// state is entirely different if it has a different win condition.
|
// state is entirely different if it has a different win condition.
|
||||||
state.ClearGraph();
|
state.ClearGraph();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto InputHandler::HandleKeys() -> void {
|
auto InputHandler::MoveBlockNor() -> void {
|
||||||
if (IsKeyPressed(KEY_W)) {
|
|
||||||
if (state.current_state.MoveBlockAt(sel_x, sel_y, Direction::NOR)) {
|
if (state.current_state.MoveBlockAt(sel_x, sel_y, Direction::NOR)) {
|
||||||
sel_y--;
|
sel_y--;
|
||||||
}
|
}
|
||||||
} else if (IsKeyPressed(KEY_A)) {
|
|
||||||
if (state.current_state.MoveBlockAt(sel_x, sel_y, Direction::WES)) {
|
|
||||||
sel_x--;
|
|
||||||
}
|
}
|
||||||
} else if (IsKeyPressed(KEY_S)) {
|
|
||||||
if (state.current_state.MoveBlockAt(sel_x, sel_y, Direction::SOU)) {
|
auto InputHandler::MoveBlockEas() -> void {
|
||||||
sel_y++;
|
|
||||||
}
|
|
||||||
} else if (IsKeyPressed(KEY_D)) {
|
|
||||||
if (state.current_state.MoveBlockAt(sel_x, sel_y, Direction::EAS)) {
|
if (state.current_state.MoveBlockAt(sel_x, sel_y, Direction::EAS)) {
|
||||||
sel_x++;
|
sel_x++;
|
||||||
}
|
}
|
||||||
} else if (IsKeyPressed(KEY_P)) {
|
|
||||||
std::cout << "State: " << state.current_state.state << std::endl;
|
|
||||||
Block sel = state.current_state.GetBlock(sel_x, sel_y);
|
|
||||||
int idx = state.current_state.GetIndex(sel.x, sel.y) - 5;
|
|
||||||
if (sel.IsValid()) {
|
|
||||||
std::cout << "Sel: " << state.current_state.state.substr(0, 5)
|
|
||||||
<< std::string(idx, '.') << sel.ToString()
|
|
||||||
<< std::string(state.current_state.state.length() - idx - 7,
|
|
||||||
'.')
|
|
||||||
<< std::endl;
|
|
||||||
}
|
}
|
||||||
} else if (IsKeyPressed(KEY_N)) {
|
|
||||||
|
auto InputHandler::MoveBlockSou() -> void {
|
||||||
|
if (state.current_state.MoveBlockAt(sel_x, sel_y, Direction::SOU)) {
|
||||||
|
sel_y++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto InputHandler::MoveBlockWes() -> void {
|
||||||
|
if (state.current_state.MoveBlockAt(sel_x, sel_y, Direction::WES)) {
|
||||||
|
sel_x--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto InputHandler::PrintState() const -> void {
|
||||||
|
std::println("State: \"{}\"", state.current_state.state);
|
||||||
|
Block sel = state.current_state.GetBlock(sel_x, sel_y);
|
||||||
|
int idx = state.current_state.GetIndex(sel.x, sel.y) - State::prefix;
|
||||||
|
if (sel.IsValid()) {
|
||||||
|
std::println("Sel: \"{}{}{}{}\"",
|
||||||
|
state.current_state.state.substr(0, State::prefix),
|
||||||
|
std::string(idx, '.'), sel.ToString(),
|
||||||
|
std::string(state.current_state.state.length() - idx -
|
||||||
|
State::prefix - 2,
|
||||||
|
'.'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto InputHandler::PreviousPreset() -> void {
|
||||||
|
if (editing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
block_add_x = -1;
|
block_add_x = -1;
|
||||||
block_add_y = -1;
|
block_add_y = -1;
|
||||||
has_block_add_xy = false;
|
has_block_add_xy = false;
|
||||||
state.PreviousPreset();
|
state.PreviousPreset();
|
||||||
} else if (IsKeyPressed(KEY_M)) {
|
}
|
||||||
|
|
||||||
|
auto InputHandler::NextPreset() -> void {
|
||||||
|
if (editing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
block_add_x = -1;
|
block_add_x = -1;
|
||||||
block_add_y = -1;
|
block_add_y = -1;
|
||||||
has_block_add_xy = false;
|
has_block_add_xy = false;
|
||||||
state.NextPreset();
|
state.NextPreset();
|
||||||
} else if (IsKeyPressed(KEY_R)) {
|
}
|
||||||
|
|
||||||
|
auto InputHandler::ResetState() -> void {
|
||||||
|
if (editing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
state.ResetState();
|
state.ResetState();
|
||||||
} else if (IsKeyPressed(KEY_G)) {
|
}
|
||||||
state.FillGraph();
|
|
||||||
} else if (IsKeyPressed(KEY_C)) {
|
auto InputHandler::FillGraph() -> void { state.FillGraph(); }
|
||||||
state.ClearGraph();
|
|
||||||
} else if (IsKeyPressed(KEY_I)) {
|
auto InputHandler::ClearGraph() -> void { state.ClearGraph(); }
|
||||||
|
|
||||||
|
auto InputHandler::ToggleMarkSolutions() -> void {
|
||||||
mark_solutions = !mark_solutions;
|
mark_solutions = !mark_solutions;
|
||||||
} else if (IsKeyPressed(KEY_O)) {
|
}
|
||||||
|
|
||||||
|
auto InputHandler::ToggleConnectSolutions() -> void {
|
||||||
connect_solutions = !connect_solutions;
|
connect_solutions = !connect_solutions;
|
||||||
} else if (IsKeyPressed(KEY_U)) {
|
}
|
||||||
mark_path = !mark_path;
|
|
||||||
} else if (IsKeyPressed(KEY_SPACE)) {
|
auto InputHandler::ToggleMarkPath() -> void { mark_path = !mark_path; }
|
||||||
state.NextPath();
|
|
||||||
} else if (IsKeyPressed(KEY_V)) {
|
auto InputHandler::MakeOptimalMove() -> void { state.NextPath(); }
|
||||||
state.GoToWorst();
|
|
||||||
} else if (IsKeyPressed(KEY_B)) {
|
auto InputHandler::GoToWorstState() -> void { state.GoToWorst(); }
|
||||||
state.GoToNearestTarget();
|
|
||||||
} else if (IsKeyPressed(KEY_BACKSPACE)) {
|
auto InputHandler::GoToNearestTarget() -> void { state.GoToNearestTarget(); }
|
||||||
state.PopHistory();
|
|
||||||
} else if (IsKeyPressed(KEY_F)) {
|
auto InputHandler::UndoLastMove() -> void { state.PopHistory(); }
|
||||||
|
|
||||||
|
auto InputHandler::ToggleRestrictedMovement() -> void {
|
||||||
|
if (!editing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
state.current_state.ToggleRestricted();
|
state.current_state.ToggleRestricted();
|
||||||
state.ClearGraph();
|
state.ClearGraph();
|
||||||
state.edited = true;
|
state.edited = true;
|
||||||
} else if (IsKeyPressed(KEY_T)) {
|
}
|
||||||
|
|
||||||
|
auto InputHandler::ToggleTargetBlock() -> void {
|
||||||
|
if (!editing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
state.current_state.ToggleTarget(sel_x, sel_y);
|
state.current_state.ToggleTarget(sel_x, sel_y);
|
||||||
state.ClearGraph();
|
state.ClearGraph();
|
||||||
state.edited = true;
|
state.edited = true;
|
||||||
} else if (IsKeyPressed(KEY_Y)) {
|
}
|
||||||
|
|
||||||
|
auto InputHandler::ToggleWallBlock() -> void {
|
||||||
|
if (!editing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
state.current_state.ToggleWall(sel_x, sel_y);
|
state.current_state.ToggleWall(sel_x, sel_y);
|
||||||
state.ClearGraph();
|
state.ClearGraph();
|
||||||
state.edited = true;
|
state.edited = true;
|
||||||
} else if (IsKeyPressed(KEY_LEFT) && state.current_state.width > 1) {
|
}
|
||||||
|
|
||||||
|
auto InputHandler::RemoveBoardColumn() -> void {
|
||||||
|
if (!editing || state.current_state.width <= 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
state.current_state = state.current_state.RemoveColumn();
|
state.current_state = state.current_state.RemoveColumn();
|
||||||
state.ClearGraph();
|
state.ClearGraph();
|
||||||
state.edited = true;
|
state.edited = true;
|
||||||
} else if (IsKeyPressed(KEY_RIGHT) && state.current_state.width < 9) {
|
}
|
||||||
|
|
||||||
|
auto InputHandler::AddBoardColumn() -> void {
|
||||||
|
if (!editing || state.current_state.width >= 9) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
state.current_state = state.current_state.AddColumn();
|
state.current_state = state.current_state.AddColumn();
|
||||||
state.ClearGraph();
|
state.ClearGraph();
|
||||||
state.edited = true;
|
state.edited = true;
|
||||||
} else if (IsKeyPressed(KEY_UP) && state.current_state.height > 1) {
|
}
|
||||||
|
|
||||||
|
auto InputHandler::RemoveBoardRow() -> void {
|
||||||
|
if (!editing || state.current_state.height <= 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
state.current_state = state.current_state.RemoveRow();
|
state.current_state = state.current_state.RemoveRow();
|
||||||
state.ClearGraph();
|
state.ClearGraph();
|
||||||
state.edited = true;
|
state.edited = true;
|
||||||
} else if (IsKeyPressed(KEY_DOWN) && state.current_state.height < 9) {
|
}
|
||||||
|
|
||||||
|
auto InputHandler::AddBoardRow() -> void {
|
||||||
|
if (!editing || state.current_state.height >= 9) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
state.current_state = state.current_state.AddRow();
|
state.current_state = state.current_state.AddRow();
|
||||||
state.ClearGraph();
|
state.ClearGraph();
|
||||||
state.edited = true;
|
state.edited = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto InputHandler::ToggleEditing() -> void {
|
||||||
|
if (editing) {
|
||||||
|
has_block_add_xy = false;
|
||||||
|
block_add_x = -1;
|
||||||
|
block_add_y = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
editing = !editing;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto InputHandler::ClearGoal() -> void {
|
||||||
|
if (!editing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.current_state.ClearGoal();
|
||||||
|
state.ClearGraph();
|
||||||
|
state.edited = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto InputHandler::RegisterGenericHandler(
|
||||||
|
std::function<void(InputHandler &)> handler) -> void {
|
||||||
|
generic_handlers.push_back({handler});
|
||||||
|
}
|
||||||
|
|
||||||
|
auto InputHandler::RegisterMousePressedHandler(
|
||||||
|
MouseButton button, std::function<void(InputHandler &)> handler) -> void {
|
||||||
|
mouse_pressed_handlers.push_back({handler, button});
|
||||||
|
}
|
||||||
|
|
||||||
|
auto InputHandler::RegisterMouseReleasedHandler(
|
||||||
|
MouseButton button, std::function<void(InputHandler &)> handler) -> void {
|
||||||
|
mouse_released_handlers.push_back({handler, button});
|
||||||
|
}
|
||||||
|
|
||||||
|
auto InputHandler::RegisterKeyPressedHandler(
|
||||||
|
KeyboardKey key, std::function<void(InputHandler &)> handler) -> void {
|
||||||
|
key_pressed_handlers.push_back({handler, key});
|
||||||
|
}
|
||||||
|
|
||||||
|
auto InputHandler::RegisterKeyReleasedHandler(
|
||||||
|
KeyboardKey key, std::function<void(InputHandler &)> handler) -> void {
|
||||||
|
key_released_handlers.push_back({handler, key});
|
||||||
}
|
}
|
||||||
|
|
||||||
auto InputHandler::HandleInput() -> void {
|
auto InputHandler::HandleInput() -> void {
|
||||||
HandleMouseHover();
|
if (disable) {
|
||||||
HandleMouse();
|
return;
|
||||||
HandleKeys();
|
}
|
||||||
|
|
||||||
|
for (const GenericHandler &handler : generic_handlers) {
|
||||||
|
handler.handler(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const MouseHandler &handler : mouse_pressed_handlers) {
|
||||||
|
if (IsMouseButtonPressed(handler.button)) {
|
||||||
|
handler.handler(*this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const MouseHandler &handler : mouse_released_handlers) {
|
||||||
|
if (IsMouseButtonReleased(handler.button)) {
|
||||||
|
handler.handler(*this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const KeyboardHandler &handler : key_pressed_handlers) {
|
||||||
|
if (IsKeyPressed(handler.key)) {
|
||||||
|
handler.handler(*this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const KeyboardHandler &handler : key_released_handlers) {
|
||||||
|
if (IsKeyReleased(handler.key)) {
|
||||||
|
handler.handler(*this);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
35
src/main.cpp
35
src/main.cpp
@ -1,8 +1,10 @@
|
|||||||
|
#include <chrono>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <raylib.h>
|
#include <raylib.h>
|
||||||
#include <raymath.h>
|
#include <raymath.h>
|
||||||
|
|
||||||
#include "config.hpp"
|
#include "config.hpp"
|
||||||
|
#include "gui.hpp"
|
||||||
#include "input.hpp"
|
#include "input.hpp"
|
||||||
#include "physics.hpp"
|
#include "physics.hpp"
|
||||||
#include "renderer.hpp"
|
#include "renderer.hpp"
|
||||||
@ -27,7 +29,7 @@ auto main(int argc, char *argv[]) -> int {
|
|||||||
|
|
||||||
// RayLib window setup
|
// RayLib window setup
|
||||||
SetTraceLogLevel(LOG_ERROR);
|
SetTraceLogLevel(LOG_ERROR);
|
||||||
// SetConfigFlags(FLAG_VSYNC_HINT);
|
SetConfigFlags(FLAG_VSYNC_HINT);
|
||||||
SetConfigFlags(FLAG_MSAA_4X_HINT);
|
SetConfigFlags(FLAG_MSAA_4X_HINT);
|
||||||
SetConfigFlags(FLAG_WINDOW_RESIZABLE);
|
SetConfigFlags(FLAG_WINDOW_RESIZABLE);
|
||||||
SetConfigFlags(FLAG_WINDOW_ALWAYS_RUN);
|
SetConfigFlags(FLAG_WINDOW_ALWAYS_RUN);
|
||||||
@ -36,11 +38,17 @@ auto main(int argc, char *argv[]) -> int {
|
|||||||
// Game setup
|
// Game setup
|
||||||
ThreadedPhysics physics;
|
ThreadedPhysics physics;
|
||||||
StateManager state(physics, preset_file);
|
StateManager state(physics, preset_file);
|
||||||
InputHandler input(state);
|
|
||||||
OrbitCamera3D camera;
|
OrbitCamera3D camera;
|
||||||
Renderer renderer(camera, state, input);
|
InputHandler input(state, camera);
|
||||||
|
Gui gui(input, state, camera);
|
||||||
|
Renderer renderer(camera, state, input, gui);
|
||||||
|
|
||||||
unsigned int ups;
|
std::chrono::time_point last = std::chrono::high_resolution_clock::now();
|
||||||
|
std::chrono::duration<double> fps_accumulator(0);
|
||||||
|
unsigned int loop_iterations = 0;
|
||||||
|
|
||||||
|
unsigned int fps = 0;
|
||||||
|
unsigned int ups = 0; // Read from physics
|
||||||
std::vector<Vector3> masses; // Read from physics
|
std::vector<Vector3> masses; // Read from physics
|
||||||
|
|
||||||
// Game loop
|
// Game loop
|
||||||
@ -49,6 +57,12 @@ auto main(int argc, char *argv[]) -> int {
|
|||||||
FrameMarkStart("MainThread");
|
FrameMarkStart("MainThread");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Time tracking
|
||||||
|
std::chrono::time_point now = std::chrono::high_resolution_clock::now();
|
||||||
|
std::chrono::duration<double> deltatime = now - last;
|
||||||
|
fps_accumulator += deltatime;
|
||||||
|
last = now;
|
||||||
|
|
||||||
// Input update
|
// Input update
|
||||||
input.HandleInput();
|
input.HandleInput();
|
||||||
state.UpdateGraph(); // Add state added after user input
|
state.UpdateGraph(); // Add state added after user input
|
||||||
@ -86,7 +100,7 @@ auto main(int argc, char *argv[]) -> int {
|
|||||||
std::size_t current_index = state.CurrentMassIndex();
|
std::size_t current_index = state.CurrentMassIndex();
|
||||||
if (masses.size() > current_index) {
|
if (masses.size() > current_index) {
|
||||||
const Mass ¤t_mass = masses.at(current_index);
|
const Mass ¤t_mass = masses.at(current_index);
|
||||||
camera.Update(current_mass.position);
|
camera.Update(current_mass.position, input.camera_lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rendering
|
// Rendering
|
||||||
@ -94,7 +108,16 @@ auto main(int argc, char *argv[]) -> int {
|
|||||||
renderer.DrawMassSprings(masses);
|
renderer.DrawMassSprings(masses);
|
||||||
renderer.DrawKlotski();
|
renderer.DrawKlotski();
|
||||||
renderer.DrawMenu(masses);
|
renderer.DrawMenu(masses);
|
||||||
renderer.DrawTextures(ups);
|
renderer.DrawTextures(fps, ups);
|
||||||
|
|
||||||
|
if (fps_accumulator.count() > 1.0) {
|
||||||
|
// Update each second
|
||||||
|
fps = loop_iterations;
|
||||||
|
loop_iterations = 0;
|
||||||
|
fps_accumulator = std::chrono::duration<double>(0);
|
||||||
|
}
|
||||||
|
++loop_iterations;
|
||||||
|
|
||||||
#ifdef TRACY
|
#ifdef TRACY
|
||||||
FrameMark;
|
FrameMark;
|
||||||
FrameMarkEnd("MainThread");
|
FrameMarkEnd("MainThread");
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
#include "octree.hpp"
|
#include "octree.hpp"
|
||||||
#include "config.hpp"
|
#include "config.hpp"
|
||||||
#include "util.hpp"
|
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#include <raymath.h>
|
#include <raymath.h>
|
||||||
|
|
||||||
#ifdef TRACY
|
#ifdef TRACY
|
||||||
@ -168,11 +166,3 @@ auto Octree::CalculateForce(int node_idx, const Vector3 &pos) const -> Vector3 {
|
|||||||
|
|
||||||
return force;
|
return force;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Octree::Print() const -> void {
|
|
||||||
std::cout << "Octree Start ===========================" << std::endl;
|
|
||||||
for (const auto &node : nodes) {
|
|
||||||
std::cout << "Center: " << node.mass_center << ", Mass: " << node.mass_total
|
|
||||||
<< ", Direct Children: " << node.ChildCount() << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -212,9 +212,9 @@ auto ThreadedPhysics::PhysicsThread(ThreadedPhysics::PhysicsState &state)
|
|||||||
};
|
};
|
||||||
|
|
||||||
std::chrono::time_point last = std::chrono::high_resolution_clock::now();
|
std::chrono::time_point last = std::chrono::high_resolution_clock::now();
|
||||||
std::chrono::duration<double> accumulator(0);
|
std::chrono::duration<double> physics_accumulator(0);
|
||||||
std::chrono::duration<double> update_accumulator(0);
|
std::chrono::duration<double> ups_accumulator(0);
|
||||||
unsigned int updates = 0;
|
unsigned int loop_iterations = 0;
|
||||||
|
|
||||||
while (state.running.load()) {
|
while (state.running.load()) {
|
||||||
#ifdef TRACY
|
#ifdef TRACY
|
||||||
@ -224,8 +224,8 @@ auto ThreadedPhysics::PhysicsThread(ThreadedPhysics::PhysicsState &state)
|
|||||||
// Time tracking
|
// Time tracking
|
||||||
std::chrono::time_point now = std::chrono::high_resolution_clock::now();
|
std::chrono::time_point now = std::chrono::high_resolution_clock::now();
|
||||||
std::chrono::duration<double> deltatime = now - last;
|
std::chrono::duration<double> deltatime = now - last;
|
||||||
accumulator += deltatime;
|
physics_accumulator += deltatime;
|
||||||
update_accumulator += deltatime;
|
ups_accumulator += deltatime;
|
||||||
last = now;
|
last = now;
|
||||||
|
|
||||||
// Handle queued commands
|
// Handle queued commands
|
||||||
@ -248,14 +248,14 @@ auto ThreadedPhysics::PhysicsThread(ThreadedPhysics::PhysicsState &state)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Physics update
|
// Physics update
|
||||||
if (accumulator.count() > TIMESTEP) {
|
if (physics_accumulator.count() > TIMESTEP) {
|
||||||
mass_springs.ClearForces();
|
mass_springs.ClearForces();
|
||||||
mass_springs.CalculateSpringForces();
|
mass_springs.CalculateSpringForces();
|
||||||
mass_springs.CalculateRepulsionForces();
|
mass_springs.CalculateRepulsionForces();
|
||||||
mass_springs.VerletUpdate(TIMESTEP * SIM_SPEED);
|
mass_springs.VerletUpdate(TIMESTEP * SIM_SPEED);
|
||||||
|
|
||||||
++updates;
|
++loop_iterations;
|
||||||
accumulator -= std::chrono::duration<double>(TIMESTEP);
|
physics_accumulator -= std::chrono::duration<double>(TIMESTEP);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Publish the positions for the renderer (copy)
|
// Publish the positions for the renderer (copy)
|
||||||
@ -275,11 +275,11 @@ auto ThreadedPhysics::PhysicsThread(ThreadedPhysics::PhysicsState &state)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (update_accumulator.count() > 1.0) {
|
if (ups_accumulator.count() > 1.0) {
|
||||||
// Update each second
|
// Update each second
|
||||||
state.ups = updates;
|
state.ups = loop_iterations;
|
||||||
updates = 0;
|
loop_iterations = 0;
|
||||||
update_accumulator = std::chrono::duration<double>(0);
|
ups_accumulator = std::chrono::duration<double>(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
state.masses.clear();
|
state.masses.clear();
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
#include "puzzle.hpp"
|
#include "puzzle.hpp"
|
||||||
#include "config.hpp"
|
#include "config.hpp"
|
||||||
|
|
||||||
|
#include <print>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
|
||||||
#ifdef TRACY
|
#ifdef TRACY
|
||||||
@ -8,7 +9,7 @@
|
|||||||
#include <tracy/Tracy.hpp>
|
#include <tracy/Tracy.hpp>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
auto Block::Hash() const -> int {
|
auto Block::Hash() const -> std::size_t {
|
||||||
std::string s = std::format("{},{},{},{}", x, y, width, height);
|
std::string s = std::format("{},{},{},{}", x, y, width, height);
|
||||||
return std::hash<std::string>{}(s);
|
return std::hash<std::string>{}(s);
|
||||||
}
|
}
|
||||||
@ -59,7 +60,9 @@ auto Block::Collides(const Block &other) const -> bool {
|
|||||||
y < other.y + other.height && y + height > other.y;
|
y < other.y + other.height && y + height > other.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto State::Hash() const -> int { return std::hash<std::string>{}(state); }
|
auto State::Hash() const -> std::size_t {
|
||||||
|
return std::hash<std::string>{}(state);
|
||||||
|
}
|
||||||
|
|
||||||
auto State::HasWinCondition() const -> bool {
|
auto State::HasWinCondition() const -> bool {
|
||||||
return target_x != 9 && target_y != 9;
|
return target_x != 9 && target_y != 9;
|
||||||
@ -80,8 +83,9 @@ auto State::IsWon() const -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto State::SetGoal(int x, int y) -> bool {
|
auto State::SetGoal(int x, int y) -> bool {
|
||||||
if (x < 0 || x >= width || y < 0 || y >= height ||
|
Block target_block = GetTargetBlock();
|
||||||
!GetTargetBlock().IsValid()) {
|
if (!target_block.IsValid() || x < 0 || x + target_block.width > width ||
|
||||||
|
y < 0 || y + target_block.height > height) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,6 +103,12 @@ auto State::SetGoal(int x, int y) -> bool {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto State::ClearGoal() -> void {
|
||||||
|
target_x = 9;
|
||||||
|
target_y = 9;
|
||||||
|
state.replace(3, 2, "99");
|
||||||
|
}
|
||||||
|
|
||||||
auto State::AddColumn() const -> State {
|
auto State::AddColumn() const -> State {
|
||||||
State newstate = State(width + 1, height, restricted);
|
State newstate = State(width + 1, height, restricted);
|
||||||
|
|
||||||
@ -379,8 +389,8 @@ auto State::Closure() const
|
|||||||
}
|
}
|
||||||
} while (remaining_states.size() > 0);
|
} while (remaining_states.size() > 0);
|
||||||
|
|
||||||
std::cout << "Closure contains " << states.size() << " states with "
|
std::println("State space has size {} with {} transitions.", states.size(),
|
||||||
<< links.size() << " links." << std::endl;
|
links.size());
|
||||||
|
|
||||||
return std::make_pair(states, links);
|
return std::make_pair(states, links);
|
||||||
}
|
}
|
||||||
|
|||||||
216
src/renderer.cpp
216
src/renderer.cpp
@ -3,7 +3,6 @@
|
|||||||
#include "puzzle.hpp"
|
#include "puzzle.hpp"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <format>
|
|
||||||
#include <raylib.h>
|
#include <raylib.h>
|
||||||
#include <raymath.h>
|
#include <raymath.h>
|
||||||
#include <rlgl.h>
|
#include <rlgl.h>
|
||||||
@ -143,7 +142,7 @@ auto Renderer::DrawMassSprings(const std::vector<Vector3> &masses) -> void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Mark visited states
|
// Mark visited states
|
||||||
for (const State &_state : state.visited_states) {
|
for (const auto &[_state, visits] : state.visited_states) {
|
||||||
std::size_t visited_index = state.states.at(_state);
|
std::size_t visited_index = state.states.at(_state);
|
||||||
|
|
||||||
if (masses.size() > visited_index) {
|
if (masses.size() > visited_index) {
|
||||||
@ -180,13 +179,7 @@ auto Renderer::DrawMassSprings(const std::vector<Vector3> &masses) -> void {
|
|||||||
BLUE);
|
BLUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// DrawCubeWires(current_mass.position, REPULSION_RANGE, REPULSION_RANGE,
|
|
||||||
// REPULSION_RANGE, BLACK);
|
|
||||||
// DrawGrid(100, 1.0);
|
|
||||||
// DrawSphere(camera.target, VERTEX_SIZE, ORANGE);
|
|
||||||
EndMode3D();
|
EndMode3D();
|
||||||
|
|
||||||
DrawLine(0, 0, 0, GetScreenHeight() - MENU_HEIGHT, BLACK);
|
|
||||||
EndTextureMode();
|
EndTextureMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,116 +191,8 @@ auto Renderer::DrawKlotski() -> void {
|
|||||||
BeginTextureMode(klotski_target);
|
BeginTextureMode(klotski_target);
|
||||||
ClearBackground(RAYWHITE);
|
ClearBackground(RAYWHITE);
|
||||||
|
|
||||||
// Draw Board
|
gui.DrawPuzzleBoard();
|
||||||
const int board_width = GetScreenWidth() / 2 - 2 * BOARD_PADDING;
|
|
||||||
const int board_height = GetScreenHeight() - MENU_HEIGHT - 2 * BOARD_PADDING;
|
|
||||||
int block_size = std::min(board_width / state.current_state.width,
|
|
||||||
board_height / state.current_state.height) -
|
|
||||||
2 * BLOCK_PADDING;
|
|
||||||
int x_offset = (board_width - (block_size + 2 * BLOCK_PADDING) *
|
|
||||||
state.current_state.width) /
|
|
||||||
2.0;
|
|
||||||
int y_offset = (board_height - (block_size + 2 * BLOCK_PADDING) *
|
|
||||||
state.current_state.height) /
|
|
||||||
2.0;
|
|
||||||
|
|
||||||
DrawRectangle(0, 0, GetScreenWidth() / 2, GetScreenHeight() - MENU_HEIGHT,
|
|
||||||
RAYWHITE);
|
|
||||||
DrawRectangle(x_offset, y_offset,
|
|
||||||
board_width - 2 * x_offset + 2 * BOARD_PADDING,
|
|
||||||
board_height - 2 * y_offset + 2 * BOARD_PADDING,
|
|
||||||
state.current_state.IsWon()
|
|
||||||
? GREEN
|
|
||||||
: (state.current_state.restricted ? DARKGRAY : LIGHTGRAY));
|
|
||||||
for (int x = 0; x < state.current_state.width; ++x) {
|
|
||||||
for (int y = 0; y < state.current_state.height; ++y) {
|
|
||||||
DrawRectangle(x_offset + BOARD_PADDING + x * BLOCK_PADDING * 2 +
|
|
||||||
BLOCK_PADDING + x * block_size,
|
|
||||||
y_offset + BOARD_PADDING + y * BLOCK_PADDING * 2 +
|
|
||||||
BLOCK_PADDING + y * block_size,
|
|
||||||
block_size, block_size, WHITE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw Blocks
|
|
||||||
for (Block block : state.current_state) {
|
|
||||||
Color c = BLOCK_COLOR;
|
|
||||||
if (block.Covers(input.sel_x, input.sel_y)) {
|
|
||||||
c = HL_BLOCK_COLOR;
|
|
||||||
}
|
|
||||||
if (block.target) {
|
|
||||||
if (block.Covers(input.sel_x, input.sel_y)) {
|
|
||||||
c = HL_TARGET_BLOCK_COLOR;
|
|
||||||
} else {
|
|
||||||
c = TARGET_BLOCK_COLOR;
|
|
||||||
}
|
|
||||||
} else if (block.immovable) {
|
|
||||||
if (block.Covers(input.sel_x, input.sel_y)) {
|
|
||||||
c = HL_WALL_COLOR;
|
|
||||||
} else {
|
|
||||||
c = WALL_COLOR;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DrawRectangle(x_offset + BOARD_PADDING + block.x * BLOCK_PADDING * 2 +
|
|
||||||
BLOCK_PADDING + block.x * block_size,
|
|
||||||
y_offset + BOARD_PADDING + block.y * BLOCK_PADDING * 2 +
|
|
||||||
BLOCK_PADDING + block.y * block_size,
|
|
||||||
block.width * block_size + block.width * 2 * BLOCK_PADDING -
|
|
||||||
2 * BLOCK_PADDING,
|
|
||||||
block.height * block_size + block.height * 2 * BLOCK_PADDING -
|
|
||||||
2 * BLOCK_PADDING,
|
|
||||||
c);
|
|
||||||
|
|
||||||
if (block.Covers(input.hov_x, input.hov_y)) {
|
|
||||||
DrawRectangleLinesEx(
|
|
||||||
Rectangle(x_offset + BOARD_PADDING + block.x * BLOCK_PADDING * 2 +
|
|
||||||
BLOCK_PADDING + block.x * block_size,
|
|
||||||
y_offset + BOARD_PADDING + block.y * BLOCK_PADDING * 2 +
|
|
||||||
BLOCK_PADDING + block.y * block_size,
|
|
||||||
block.width * block_size + block.width * 2 * BLOCK_PADDING -
|
|
||||||
2 * BLOCK_PADDING,
|
|
||||||
block.height * block_size +
|
|
||||||
block.height * 2 * BLOCK_PADDING - 2 * BLOCK_PADDING),
|
|
||||||
2.0, BLACK);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw editing starting position
|
|
||||||
if (input.block_add_x >= 0 && input.block_add_y >= 0 &&
|
|
||||||
input.hov_x >= input.block_add_x && input.hov_y >= input.block_add_y) {
|
|
||||||
int block_width = input.hov_x - input.block_add_x + 1;
|
|
||||||
int block_height = input.hov_y - input.block_add_y + 1;
|
|
||||||
DrawRectangle(
|
|
||||||
x_offset + BOARD_PADDING + input.block_add_x * BLOCK_PADDING * 2 +
|
|
||||||
BLOCK_PADDING + input.block_add_x * block_size,
|
|
||||||
y_offset + BOARD_PADDING + input.block_add_y * BLOCK_PADDING * 2 +
|
|
||||||
BLOCK_PADDING + input.block_add_y * block_size,
|
|
||||||
block_width * block_size + block_width * 2 * BLOCK_PADDING -
|
|
||||||
2 * BLOCK_PADDING,
|
|
||||||
block_height * block_size + block_height * 2 * BLOCK_PADDING -
|
|
||||||
2 * BLOCK_PADDING,
|
|
||||||
Fade(BLOCK_COLOR, 0.5));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw board goal position
|
|
||||||
const Block target = state.current_state.GetTargetBlock();
|
|
||||||
if (target.IsValid() && state.current_state.HasWinCondition()) {
|
|
||||||
int target_x = state.current_state.target_x;
|
|
||||||
int target_y = state.current_state.target_y;
|
|
||||||
DrawRectangleLinesEx(
|
|
||||||
Rectangle(x_offset + BOARD_PADDING + target_x * BLOCK_PADDING * 2 +
|
|
||||||
BLOCK_PADDING + target_x * block_size,
|
|
||||||
y_offset + BOARD_PADDING + target_y * BLOCK_PADDING * 2 +
|
|
||||||
BLOCK_PADDING + target_y * block_size,
|
|
||||||
target.width * block_size + target.width * 2 * BLOCK_PADDING -
|
|
||||||
2 * BLOCK_PADDING,
|
|
||||||
target.height * block_size +
|
|
||||||
target.height * 2 * BLOCK_PADDING - 2 * BLOCK_PADDING),
|
|
||||||
2.0, TARGET_BLOCK_COLOR);
|
|
||||||
}
|
|
||||||
|
|
||||||
DrawLine(GetScreenWidth() / 2 - 1, 0, GetScreenWidth() / 2 - 1,
|
|
||||||
GetScreenHeight() - MENU_HEIGHT, BLACK);
|
|
||||||
EndTextureMode();
|
EndTextureMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -319,87 +204,14 @@ auto Renderer::DrawMenu(const std::vector<Vector3> &masses) -> void {
|
|||||||
BeginTextureMode(menu_target);
|
BeginTextureMode(menu_target);
|
||||||
ClearBackground(RAYWHITE);
|
ClearBackground(RAYWHITE);
|
||||||
|
|
||||||
float btn_width =
|
gui.DrawMainMenu();
|
||||||
static_cast<float>(GetScreenWidth() - (MENU_COLS * MENU_PAD + MENU_PAD)) /
|
|
||||||
MENU_COLS;
|
|
||||||
float btn_height =
|
|
||||||
static_cast<float>(MENU_HEIGHT - (MENU_ROWS * MENU_PAD + MENU_PAD)) /
|
|
||||||
MENU_ROWS;
|
|
||||||
|
|
||||||
auto draw_btn = [&](int x, int y, std::string text, Color color) {
|
|
||||||
int posx = MENU_PAD + x * (MENU_PAD + btn_width);
|
|
||||||
int posy = MENU_PAD + (y + 1) * (MENU_PAD + btn_height);
|
|
||||||
DrawRectangle(posx, posy, btn_width, btn_height, Fade(color, 0.7));
|
|
||||||
DrawRectangleLines(posx, posy, btn_width, btn_height, color);
|
|
||||||
DrawText(text.data(), posx + BUTTON_PAD, posy + BUTTON_PAD,
|
|
||||||
btn_height - 2.0 * BUTTON_PAD, WHITE);
|
|
||||||
};
|
|
||||||
|
|
||||||
auto draw_subtitle = [&](std::string text, Color color) {
|
|
||||||
int posx = MENU_PAD;
|
|
||||||
int posy = MENU_PAD;
|
|
||||||
DrawRectangle(posx, posy,
|
|
||||||
btn_width * MENU_COLS + MENU_PAD * (MENU_COLS - 1),
|
|
||||||
btn_height, Fade(color, 0.7));
|
|
||||||
DrawRectangleLines(posx, posy,
|
|
||||||
btn_width * MENU_COLS + MENU_PAD * (MENU_COLS - 1),
|
|
||||||
btn_height, color);
|
|
||||||
DrawText(text.data(), posx + BUTTON_PAD, posy + BUTTON_PAD,
|
|
||||||
btn_height = 2.0 * BUTTON_PAD, WHITE);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Left column
|
|
||||||
draw_btn(0, 0,
|
|
||||||
std::format("States: {} / Transitions: {} / Winning: {}",
|
|
||||||
masses.size(), state.springs.size(),
|
|
||||||
state.winning_states.size()),
|
|
||||||
ORANGE);
|
|
||||||
draw_btn(0, 1,
|
|
||||||
std::format("Preset (M/N) / {} (F)",
|
|
||||||
state.current_state.restricted ? "Restricted" : "Free"),
|
|
||||||
ORANGE);
|
|
||||||
draw_btn(0, 2, std::format("Pan (LMB) / Rotate (RMB) / Zoom (Wheel)"),
|
|
||||||
DARKGREEN);
|
|
||||||
draw_btn(
|
|
||||||
0, 3,
|
|
||||||
std::format("Lock Camera to Current State (L): {}", camera.target_lock),
|
|
||||||
DARKGREEN);
|
|
||||||
|
|
||||||
// Center column
|
|
||||||
draw_btn(1, 0, std::format("Select (LMB) / Move (WASD) / Target/Wall (T/Y)"),
|
|
||||||
DARKBLUE);
|
|
||||||
draw_btn(1, 1, std::format("Add/Remove Col/Row (Arrow Keys)"), DARKBLUE);
|
|
||||||
draw_btn(1, 2, std::format("Add/Remove Block (LMB/RMB) / Set Goal (MMB)"),
|
|
||||||
DARKBLUE);
|
|
||||||
draw_btn(1, 3, std::format("Print State (P) / Reset State (R)"), DARKBLUE);
|
|
||||||
|
|
||||||
// Right column
|
|
||||||
draw_btn(2, 0, std::format("Populate Graph (G) / Clear Graph (C)"),
|
|
||||||
DARKPURPLE);
|
|
||||||
draw_btn(2, 1,
|
|
||||||
std::format("Path (U): {} / Goals (I): {} / Lines (O): {}",
|
|
||||||
input.mark_path, input.mark_solutions,
|
|
||||||
input.connect_solutions),
|
|
||||||
DARKPURPLE);
|
|
||||||
draw_btn(2, 2, std::format("Best move (Space) / Move back (Backspace)"),
|
|
||||||
DARKPURPLE);
|
|
||||||
draw_btn(2, 3,
|
|
||||||
std::format("Worst (V) / Target (B) / Distance: {}",
|
|
||||||
state.winning_path.size() > 0
|
|
||||||
? state.winning_path.size() - 1
|
|
||||||
: 0),
|
|
||||||
DARKPURPLE);
|
|
||||||
|
|
||||||
draw_subtitle(std::format("Puzzle {}: {}", state.current_preset + 1,
|
|
||||||
state.comments.at(state.current_preset)),
|
|
||||||
BLACK);
|
|
||||||
|
|
||||||
DrawLine(0, MENU_HEIGHT - 1, GetScreenWidth(), MENU_HEIGHT - 1, BLACK);
|
|
||||||
EndTextureMode();
|
EndTextureMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Renderer::DrawTextures(float ups) -> void {
|
auto Renderer::DrawTextures(int fps, int ups) -> void {
|
||||||
BeginDrawing();
|
BeginDrawing();
|
||||||
|
|
||||||
DrawTextureRec(menu_target.texture,
|
DrawTextureRec(menu_target.texture,
|
||||||
Rectangle(0, 0, menu_target.texture.width,
|
Rectangle(0, 0, menu_target.texture.width,
|
||||||
-1 * menu_target.texture.height),
|
-1 * menu_target.texture.height),
|
||||||
@ -413,8 +225,20 @@ auto Renderer::DrawTextures(float ups) -> void {
|
|||||||
-1 * render_target.texture.height),
|
-1 * render_target.texture.height),
|
||||||
Vector2(GetScreenWidth() / 2.0, MENU_HEIGHT), WHITE);
|
Vector2(GetScreenWidth() / 2.0, MENU_HEIGHT), WHITE);
|
||||||
|
|
||||||
DrawFPS(GetScreenWidth() / 2 + 10, MENU_HEIGHT + 10);
|
// Draw borders
|
||||||
DrawText(TextFormat("%.0f UPS", ups), GetScreenWidth() / 2 + 120,
|
DrawRectangleLinesEx(Rectangle(0, 0, GetScreenWidth(), MENU_HEIGHT), 1.0,
|
||||||
MENU_HEIGHT + 10, 20, ORANGE);
|
BLACK);
|
||||||
|
DrawRectangleLinesEx(Rectangle(0, MENU_HEIGHT, GetScreenWidth() / 2.0,
|
||||||
|
GetScreenHeight() - MENU_HEIGHT),
|
||||||
|
1.0, BLACK);
|
||||||
|
DrawRectangleLinesEx(Rectangle(GetScreenWidth() / 2.0, MENU_HEIGHT,
|
||||||
|
GetScreenWidth() / 2.0,
|
||||||
|
GetScreenHeight() - MENU_HEIGHT),
|
||||||
|
1.0, BLACK);
|
||||||
|
|
||||||
|
gui.DrawGraphOverlay(fps, ups);
|
||||||
|
gui.DrawSavePresetPopup();
|
||||||
|
gui.Update();
|
||||||
|
|
||||||
EndDrawing();
|
EndDrawing();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <ios>
|
#include <ios>
|
||||||
|
#include <print>
|
||||||
#include <raymath.h>
|
#include <raymath.h>
|
||||||
|
|
||||||
#ifdef TRACY
|
#ifdef TRACY
|
||||||
@ -11,12 +12,13 @@
|
|||||||
#include <tracy/Tracy.hpp>
|
#include <tracy/Tracy.hpp>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
auto StateManager::ParsePresetFile(const std::string &preset_file) -> void {
|
auto StateManager::ParsePresetFile(const std::string &_preset_file) -> bool {
|
||||||
|
preset_file = _preset_file;
|
||||||
|
|
||||||
std::ifstream file(preset_file);
|
std::ifstream file(preset_file);
|
||||||
if (!file) {
|
if (!file) {
|
||||||
std::cout << "Preset file \"" << preset_file << "\" couldn't be loaded."
|
std::println("Preset file \"{}\" couldn't be loaded.", preset_file);
|
||||||
<< std::endl;
|
return false;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string line;
|
std::string line;
|
||||||
@ -31,9 +33,8 @@ auto StateManager::ParsePresetFile(const std::string &preset_file) -> void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (preset_lines.size() == 0 || comment_lines.size() != preset_lines.size()) {
|
if (preset_lines.size() == 0 || comment_lines.size() != preset_lines.size()) {
|
||||||
std::cout << "Preset file \"" << preset_file << "\" couldn't be loaded."
|
std::println("Preset file \"{}\" couldn't be loaded.", preset_file);
|
||||||
<< std::endl;
|
return false;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
presets.clear();
|
presets.clear();
|
||||||
@ -42,7 +43,27 @@ auto StateManager::ParsePresetFile(const std::string &preset_file) -> void {
|
|||||||
}
|
}
|
||||||
comments = comment_lines;
|
comments = comment_lines;
|
||||||
|
|
||||||
std::cout << "Loaded " << preset_lines.size() << " presets." << std::endl;
|
std::println("Loaded {} presets from \"{}\".", preset_lines.size(),
|
||||||
|
preset_file);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto StateManager::AppendPresetFile(const std::string preset_name) -> void {
|
||||||
|
std::println("Saving preset \"{}\" to \"{}\"", preset_name, preset_file);
|
||||||
|
|
||||||
|
std::ofstream file(preset_file, std::ios_base::app | std::ios_base::out);
|
||||||
|
if (!file) {
|
||||||
|
std::println("Preset file \"{}\" couldn't be loaded.", preset_file);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
file << "\n# " << preset_name << "\n" << current_state.state << std::flush;
|
||||||
|
|
||||||
|
std::println("Refreshing presets...");
|
||||||
|
if (ParsePresetFile(preset_file)) {
|
||||||
|
LoadPreset(presets.size() - 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto StateManager::LoadPreset(int preset) -> void {
|
auto StateManager::LoadPreset(int preset) -> void {
|
||||||
@ -55,6 +76,11 @@ auto StateManager::LoadPreset(int preset) -> void {
|
|||||||
auto StateManager::ResetState() -> void {
|
auto StateManager::ResetState() -> void {
|
||||||
current_state = presets.at(current_preset);
|
current_state = presets.at(current_preset);
|
||||||
previous_state = current_state;
|
previous_state = current_state;
|
||||||
|
for (auto &[state, visits] : visited_states) {
|
||||||
|
visits = 0;
|
||||||
|
}
|
||||||
|
visited_states[current_state]++;
|
||||||
|
total_moves = 0;
|
||||||
if (edited) {
|
if (edited) {
|
||||||
// We also need to clear the graph in case the state has been edited
|
// We also need to clear the graph in case the state has been edited
|
||||||
// because the graph could contain states that are impossible to reach
|
// because the graph could contain states that are impossible to reach
|
||||||
@ -83,8 +109,6 @@ auto StateManager::NextPath() -> void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::size_t parent = target_distances.parents[CurrentMassIndex()];
|
std::size_t parent = target_distances.parents[CurrentMassIndex()];
|
||||||
// std::cout << "Parent of node " << CurrentMassIndex() << " is " << parent
|
|
||||||
// << std::endl;
|
|
||||||
current_state = masses.at(parent);
|
current_state = masses.at(parent);
|
||||||
FindTargetPath();
|
FindTargetPath();
|
||||||
}
|
}
|
||||||
@ -115,8 +139,7 @@ auto StateManager::FillGraph() -> void {
|
|||||||
|
|
||||||
// Sanity check. Both values need to be equal
|
// Sanity check. Both values need to be equal
|
||||||
// for (const auto &[mass, state] : masses) {
|
// for (const auto &[mass, state] : masses) {
|
||||||
// std::cout << "Masses: " << mass << ", States: " << states.at(state)
|
// std::println("Masses: {}, States: {}", mass, states.at(state));
|
||||||
// << std::endl;
|
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,7 +161,9 @@ auto StateManager::UpdateGraph() -> void {
|
|||||||
FindTargetDistances();
|
FindTargetDistances();
|
||||||
}
|
}
|
||||||
|
|
||||||
visited_states.insert(current_state);
|
// Adds the element with 0 if it doesn't exist
|
||||||
|
visited_states[current_state]++;
|
||||||
|
total_moves++;
|
||||||
|
|
||||||
if (history.size() > 0 && history.top() == current_state) {
|
if (history.size() > 0 && history.top() == current_state) {
|
||||||
// We don't pop the stack when moving backwards to indicate if we need to
|
// We don't pop the stack when moving backwards to indicate if we need to
|
||||||
@ -166,7 +191,7 @@ auto StateManager::ClearGraph() -> void {
|
|||||||
// Re-add the default stuff to the graph
|
// Re-add the default stuff to the graph
|
||||||
states.insert(std::make_pair(current_state, states.size()));
|
states.insert(std::make_pair(current_state, states.size()));
|
||||||
masses.insert(std::make_pair(states.size() - 1, current_state));
|
masses.insert(std::make_pair(states.size() - 1, current_state));
|
||||||
visited_states.insert(current_state);
|
visited_states.insert(std::make_pair(current_state, 1));
|
||||||
physics.AddMassCmd();
|
physics.AddMassCmd();
|
||||||
|
|
||||||
// These states are no longer in the graph
|
// These states are no longer in the graph
|
||||||
@ -201,9 +226,8 @@ auto StateManager::FindTargetDistances() -> void {
|
|||||||
|
|
||||||
target_distances = CalculateDistances(states.size(), springs, targets);
|
target_distances = CalculateDistances(states.size(), springs, targets);
|
||||||
|
|
||||||
// std::cout << "Calculated " << target_distances.distances.size()
|
// std::println("Calculated {} distances to {} targets.",
|
||||||
// << " distances to " << targets.size() << " targets." <<
|
// target_distances.distances.size(), targets.size());
|
||||||
// std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto StateManager::FindTargetPath() -> void {
|
auto StateManager::FindTargetPath() -> void {
|
||||||
@ -212,8 +236,7 @@ auto StateManager::FindTargetPath() -> void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
winning_path = GetPath(target_distances, CurrentMassIndex());
|
winning_path = GetPath(target_distances, CurrentMassIndex());
|
||||||
// std::cout << "Nearest target is " << winning_path.size() << " moves away."
|
// std::println("Nearest target is {} moves away.", winning_path.size());
|
||||||
// << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto StateManager::FindWorstState() -> State {
|
auto StateManager::FindWorstState() -> State {
|
||||||
|
|||||||
Reference in New Issue
Block a user