restructure puzzle space generation (for boards up to 5x5)
- uses a huge global seen-states-cache. Not scalable without more filtering
This commit is contained in:
@ -113,7 +113,7 @@ if(NOT DISABLE_TESTS AND NOT WIN32)
|
||||
|
||||
add_executable(tests ${TEST_SOURCES} ${SOURCES})
|
||||
target_include_directories(tests PRIVATE include)
|
||||
target_link_libraries(tests Catch2::Catch2WithMain raylib)
|
||||
target_link_libraries(tests Catch2::Catch2WithMain raylib GLEW::GLEW)
|
||||
|
||||
include(Catch)
|
||||
catch_discover_tests(tests)
|
||||
@ -131,7 +131,7 @@ if(NOT DISABLE_BENCH AND NOT WIN32)
|
||||
|
||||
add_executable(benchmarks ${BENCH_SOURCES} ${SOURCES})
|
||||
target_include_directories(benchmarks PRIVATE include)
|
||||
target_link_libraries(benchmarks benchmark raylib)
|
||||
target_link_libraries(benchmarks benchmark raylib GLEW::GLEW)
|
||||
|
||||
message("-- BENCHMARKS: Enabled")
|
||||
endif()
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
// ReSharper disable CppTooWideScope
|
||||
// ReSharper disable CppDFAUnreadVariable
|
||||
#include "puzzle.hpp"
|
||||
|
||||
#include <random>
|
||||
@ -39,12 +40,12 @@ static std::vector<std::string> puzzles = {
|
||||
"S:[4x5] G:[1,3] M:[F] B:[{_ 2X2 _ _} {1x1 _ _ 1x1} {1x2 2x1 _ 1x2} {_ 2x1 _ _} {1x1 2x1 _ 1x1}]",
|
||||
};
|
||||
|
||||
template <uint8_t N>
|
||||
template <u8 N>
|
||||
struct uint_hasher
|
||||
{
|
||||
int64_t nums;
|
||||
|
||||
auto operator()(const std::array<uint64_t, N>& ints) const noexcept -> size_t
|
||||
auto operator()(const std::array<u64, N>& ints) const noexcept -> size_t
|
||||
{
|
||||
size_t h = 0;
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
@ -54,18 +55,18 @@ struct uint_hasher
|
||||
}
|
||||
};
|
||||
|
||||
template <uint8_t N>
|
||||
template <u8 N>
|
||||
static auto unordered_set_uint64(benchmark::State& state) -> void
|
||||
{
|
||||
std::random_device random_device;
|
||||
std::mt19937 generator(random_device());
|
||||
std::uniform_int_distribution<uint64_t> distribution(
|
||||
std::numeric_limits<std::uint64_t>::min(),
|
||||
std::numeric_limits<std::uint64_t>::max()
|
||||
std::uniform_int_distribution<u64> distribution(
|
||||
std::numeric_limits<u64>::min(),
|
||||
std::numeric_limits<u64>::max()
|
||||
);
|
||||
|
||||
std::unordered_set<std::array<uint64_t, N>, uint_hasher<N>> set;
|
||||
std::array<uint64_t, N> ints;
|
||||
std::unordered_set<std::array<u64, N>, uint_hasher<N>> set;
|
||||
std::array<u64, N> ints;
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
ints[i] = distribution(generator);
|
||||
}
|
||||
@ -79,18 +80,18 @@ static auto unordered_set_uint64(benchmark::State& state) -> void
|
||||
}
|
||||
}
|
||||
|
||||
template <uint8_t N>
|
||||
template <u8 N>
|
||||
static auto unordered_flat_set_uint64(benchmark::State& state) -> void
|
||||
{
|
||||
std::random_device random_device;
|
||||
std::mt19937 generator(random_device());
|
||||
std::uniform_int_distribution<uint64_t> distribution(
|
||||
std::numeric_limits<std::uint64_t>::min(),
|
||||
std::numeric_limits<std::uint64_t>::max()
|
||||
std::uniform_int_distribution<u64> distribution(
|
||||
std::numeric_limits<u64>::min(),
|
||||
std::numeric_limits<u64>::max()
|
||||
);
|
||||
|
||||
boost::unordered_flat_set<std::array<uint64_t, N>, uint_hasher<N>> set;
|
||||
std::array<uint64_t, N> ints;
|
||||
boost::unordered_flat_set<std::array<u64, N>, uint_hasher<N>> set;
|
||||
std::array<u64, N> ints;
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
ints[i] = distribution(generator);
|
||||
}
|
||||
@ -106,8 +107,8 @@ static auto unordered_flat_set_uint64(benchmark::State& state) -> void
|
||||
|
||||
static auto unordered_flat_set_block_hasher(benchmark::State& state) -> void
|
||||
{
|
||||
boost::unordered_flat_set<puzzle::block, block_hasher> set;
|
||||
const puzzle::block b = puzzle::block(2, 3, 1, 2, true, false);
|
||||
blockset set;
|
||||
const block b = block(2, 3, 1, 2, true, false);
|
||||
|
||||
for (auto _ : state) {
|
||||
for (size_t i = 0; i < 100000; ++i) {
|
||||
@ -118,10 +119,10 @@ static auto unordered_flat_set_block_hasher(benchmark::State& state) -> void
|
||||
}
|
||||
}
|
||||
|
||||
static auto unordered_falt_set_block_hasher2(benchmark::State& state) -> void
|
||||
static auto unordered_flat_set_block_hasher2(benchmark::State& state) -> void
|
||||
{
|
||||
boost::unordered_flat_set<puzzle::block, block_hasher2, block_equal2> set;
|
||||
const puzzle::block b = puzzle::block(2, 3, 1, 2, true, false);
|
||||
blockset2 set;
|
||||
const block b = block(2, 3, 1, 2, true, false);
|
||||
|
||||
for (auto _ : state) {
|
||||
for (size_t i = 0; i < 100000; ++i) {
|
||||
@ -134,7 +135,7 @@ static auto unordered_falt_set_block_hasher2(benchmark::State& state) -> void
|
||||
|
||||
static auto unordered_flat_set_puzzle_hasher(benchmark::State& state) -> void
|
||||
{
|
||||
boost::unordered_flat_set<puzzle, puzzle_hasher> set;
|
||||
puzzleset set;
|
||||
const puzzle p = puzzle(puzzles[0]);
|
||||
|
||||
for (auto _ : state) {
|
||||
@ -159,22 +160,22 @@ static auto explore_state_space(benchmark::State& state) -> void
|
||||
|
||||
static auto explore_rush_hour_puzzle_space(benchmark::State& state) -> void
|
||||
{
|
||||
constexpr uint8_t max_blocks = 5;
|
||||
constexpr u8 max_blocks = 5;
|
||||
|
||||
constexpr uint8_t board_width = 4;
|
||||
constexpr uint8_t board_height = 5;
|
||||
constexpr uint8_t goal_x = board_width - 1;
|
||||
constexpr uint8_t goal_y = 2;
|
||||
constexpr u8 board_width = 4;
|
||||
constexpr u8 board_height = 5;
|
||||
constexpr u8 goal_x = board_width - 1;
|
||||
constexpr u8 goal_y = 2;
|
||||
constexpr bool restricted = true;
|
||||
|
||||
const boost::unordered_flat_set<puzzle::block, block_hasher2, block_equal2> permitted_blocks = {
|
||||
puzzle::block(0, 0, 2, 1, false, false),
|
||||
puzzle::block(0, 0, 3, 1, false, false),
|
||||
puzzle::block(0, 0, 1, 2, false, false),
|
||||
puzzle::block(0, 0, 1, 3, false, false)
|
||||
const blockset2 permitted_blocks = {
|
||||
block(0, 0, 2, 1, false, false),
|
||||
block(0, 0, 3, 1, false, false),
|
||||
block(0, 0, 1, 2, false, false),
|
||||
block(0, 0, 1, 3, false, false)
|
||||
};
|
||||
const puzzle::block target_block = puzzle::block(0, 0, 2, 1, true, false);
|
||||
constexpr std::tuple<uint8_t, uint8_t, uint8_t, uint8_t> target_block_pos_range = {
|
||||
const block target_block = block(0, 0, 2, 1, true, false);
|
||||
constexpr std::tuple<u8, u8, u8, u8> target_block_pos_range = {
|
||||
0,
|
||||
goal_y,
|
||||
goal_x,
|
||||
@ -184,11 +185,12 @@ static auto explore_rush_hour_puzzle_space(benchmark::State& state) -> void
|
||||
const puzzle p = puzzle(board_width, board_height, goal_x, goal_y, restricted, true);
|
||||
|
||||
for (auto _ : state) {
|
||||
boost::unordered_flat_set<puzzle, puzzle_hasher> result = p.explore_puzzle_space(
|
||||
puzzleset result = p.explore_puzzle_space(
|
||||
permitted_blocks,
|
||||
target_block,
|
||||
target_block_pos_range,
|
||||
max_blocks,
|
||||
0,
|
||||
std::nullopt);
|
||||
|
||||
benchmark::DoNotOptimize(result);
|
||||
@ -202,7 +204,7 @@ BENCHMARK(unordered_flat_set_uint64<4>)->Unit(benchmark::kMicrosecond);
|
||||
BENCHMARK(unordered_flat_set_uint64<8>)->Unit(benchmark::kMicrosecond);
|
||||
BENCHMARK(unordered_flat_set_uint64<16>)->Unit(benchmark::kMicrosecond);
|
||||
BENCHMARK(unordered_flat_set_block_hasher)->Unit(benchmark::kMicrosecond);
|
||||
BENCHMARK(unordered_falt_set_block_hasher2)->Unit(benchmark::kMicrosecond);
|
||||
BENCHMARK(unordered_flat_set_block_hasher2)->Unit(benchmark::kMicrosecond);
|
||||
BENCHMARK(unordered_flat_set_puzzle_hasher)->Unit(benchmark::kMicrosecond);
|
||||
BENCHMARK(explore_state_space)->DenseRange(0, puzzles.size() - 1)->Unit(benchmark::kMicrosecond);
|
||||
BENCHMARK(explore_rush_hour_puzzle_space)->Unit(benchmark::kSecond);
|
||||
|
||||
@ -111,7 +111,7 @@ rec {
|
||||
abbr -a release "${buildRelease} && ./cmake-build-release/masssprings"
|
||||
|
||||
abbr -a run "${buildRelease} && ./cmake-build-release/masssprings"
|
||||
abbr -a run-clusters "${buildRelease} && ./cmake-build-release/masssprings --output=clusters.puzzle --space=rh --w=6 --h=6 --gx=4 --gy=2 --blocks=4"
|
||||
abbr -a runclusters "${buildRelease} && ./cmake-build-release/masssprings --output=clusters.puzzle --space=rh --moves=10 --blocks=4"
|
||||
abbr -a runtests "${buildDebug} && ./cmake-build-debug/tests"
|
||||
abbr -a runbenchs "mv -f benchs.json benchs.old.json; ${buildRelease} && sudo cpupower frequency-set --governor performance && ./cmake-build-release/benchmarks --benchmark_out=benchs.json --benchmark_out_format=console; sudo cpupower frequency-set --governor powersave"
|
||||
abbr -a rungdb "${buildDebug} && gdb --tui ./cmake-build-debug/masssprings"
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
template <class T>
|
||||
requires std::unsigned_integral<T>
|
||||
// ReSharper disable once CppRedundantInlineSpecifier
|
||||
INLINE inline auto create_mask(const uint8_t first, const uint8_t last) -> T
|
||||
INLINE inline auto create_mask(const u8 first, const u8 last) -> T
|
||||
{
|
||||
// If the mask width is equal the type width return all 1s instead of shifting
|
||||
// as shifting by type-width is undefined behavior.
|
||||
@ -27,7 +27,7 @@ INLINE inline auto create_mask(const uint8_t first, const uint8_t last) -> T
|
||||
template <class T>
|
||||
requires std::unsigned_integral<T>
|
||||
// ReSharper disable once CppRedundantInlineSpecifier
|
||||
INLINE inline auto clear_bits(T& bits, const uint8_t first, const uint8_t last) -> void
|
||||
INLINE inline auto clear_bits(T& bits, const u8 first, const u8 last) -> void
|
||||
{
|
||||
const T mask = create_mask<T>(first, last);
|
||||
|
||||
@ -37,7 +37,7 @@ INLINE inline auto clear_bits(T& bits, const uint8_t first, const uint8_t last)
|
||||
template <class T, class U>
|
||||
requires std::unsigned_integral<T> && std::unsigned_integral<U>
|
||||
// ReSharper disable once CppRedundantInlineSpecifier
|
||||
INLINE inline auto set_bits(T& bits, const uint8_t first, const uint8_t last, const U value) -> void
|
||||
INLINE inline auto set_bits(T& bits, const u8 first, const u8 last, const U value) -> void
|
||||
{
|
||||
const T mask = create_mask<T>(first, last);
|
||||
|
||||
@ -56,7 +56,7 @@ INLINE inline auto set_bits(T& bits, const uint8_t first, const uint8_t last, co
|
||||
template <class T>
|
||||
requires std::unsigned_integral<T>
|
||||
// ReSharper disable once CppRedundantInlineSpecifier
|
||||
INLINE inline auto get_bits(const T bits, const uint8_t first, const uint8_t last) -> T
|
||||
INLINE inline auto get_bits(const T bits, const u8 first, const u8 last) -> T
|
||||
{
|
||||
const T mask = create_mask<T>(first, last);
|
||||
|
||||
@ -64,6 +64,6 @@ INLINE inline auto get_bits(const T bits, const uint8_t first, const uint8_t las
|
||||
return (bits & mask) >> first;
|
||||
}
|
||||
|
||||
auto print_bitmap(uint64_t bitmap, uint8_t w, uint8_t h, const std::string& title) -> void;
|
||||
auto print_bitmap(u64 bitmap, u8 w, u8 h, const std::string& title) -> void;
|
||||
|
||||
#endif
|
||||
@ -23,6 +23,7 @@
|
||||
#define BS_THREAD_POOL_NATIVE_EXTENSIONS
|
||||
// ReSharper disable once CppUnusedIncludeDirective
|
||||
#include <BS_thread_pool.hpp>
|
||||
using threadpool = std::optional<BS::thread_pool<>* const>;
|
||||
#if defined(_WIN32) // raylib uses these names as function parameters
|
||||
#undef near
|
||||
#undef far
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
#define PHYSICS_HPP_
|
||||
|
||||
#include "config.hpp"
|
||||
#include "cpu_spring_system.hpp"
|
||||
#include "util.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
@ -57,7 +59,7 @@ class cpu_layout_engine
|
||||
};
|
||||
|
||||
private:
|
||||
std::optional<BS::thread_pool<>* const> thread_pool;
|
||||
threadpool thread_pool;
|
||||
std::thread physics;
|
||||
|
||||
public:
|
||||
@ -65,14 +67,11 @@ public:
|
||||
|
||||
public:
|
||||
explicit cpu_layout_engine(
|
||||
const std::optional<BS::thread_pool<>* const> _thread_pool = std::nullopt)
|
||||
const threadpool _thread_pool = std::nullopt)
|
||||
: thread_pool(_thread_pool), physics(physics_thread, std::ref(state), std::ref(thread_pool))
|
||||
{}
|
||||
|
||||
cpu_layout_engine(const cpu_layout_engine& copy) = delete;
|
||||
auto operator=(const cpu_layout_engine& copy) -> cpu_layout_engine& = delete;
|
||||
cpu_layout_engine(cpu_layout_engine&& move) = delete;
|
||||
auto operator=(cpu_layout_engine&& move) -> cpu_layout_engine& = delete;
|
||||
NO_COPY_NO_MOVE(cpu_layout_engine);
|
||||
|
||||
~cpu_layout_engine()
|
||||
{
|
||||
@ -88,14 +87,14 @@ private:
|
||||
#endif
|
||||
|
||||
static auto physics_thread(physics_state& state,
|
||||
std::optional<BS::thread_pool<>* const> thread_pool) -> void;
|
||||
threadpool thread_pool) -> void;
|
||||
|
||||
public:
|
||||
auto clear_cmd() -> void;
|
||||
auto add_mass_cmd() -> void;
|
||||
auto add_spring_cmd(size_t a, size_t b) -> void;
|
||||
auto add_mass_springs_cmd(size_t num_masses,
|
||||
const std::vector<std::pair<size_t, size_t>>& springs) -> void;
|
||||
const std::vector<spring>& springs) -> void;
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -7,20 +7,10 @@
|
||||
#include <optional>
|
||||
#include <raylib.h>
|
||||
|
||||
using spring = std::pair<size_t, size_t>;
|
||||
|
||||
class cpu_spring_system
|
||||
{
|
||||
public:
|
||||
class spring
|
||||
{
|
||||
public:
|
||||
size_t a;
|
||||
size_t b;
|
||||
|
||||
public:
|
||||
spring(const size_t _a, const size_t _b)
|
||||
: a(_a), b(_b) {}
|
||||
};
|
||||
|
||||
public:
|
||||
octree tree;
|
||||
|
||||
@ -35,10 +25,7 @@ public:
|
||||
public:
|
||||
cpu_spring_system() {}
|
||||
|
||||
cpu_spring_system(const cpu_spring_system& copy) = delete;
|
||||
auto operator=(const cpu_spring_system& copy) -> cpu_spring_system& = delete;
|
||||
cpu_spring_system(cpu_spring_system& move) = delete;
|
||||
auto operator=(cpu_spring_system&& move) -> cpu_spring_system& = delete;
|
||||
NO_COPY_NO_MOVE(cpu_spring_system);
|
||||
|
||||
public:
|
||||
auto clear() -> void;
|
||||
@ -47,14 +34,14 @@ public:
|
||||
|
||||
auto clear_forces() -> void;
|
||||
auto calculate_spring_force(size_t s) -> void;
|
||||
auto calculate_spring_forces(std::optional<BS::thread_pool<>* const> thread_pool = std::nullopt) -> void;
|
||||
auto calculate_repulsion_forces(std::optional<BS::thread_pool<>* const> thread_pool = std::nullopt) -> void;
|
||||
auto calculate_spring_forces(threadpool thread_pool = std::nullopt) -> void;
|
||||
auto calculate_repulsion_forces(threadpool thread_pool = std::nullopt) -> void;
|
||||
auto integrate_velocity(size_t m, float dt) -> void;
|
||||
auto integrate_position(size_t m, float dt) -> void;
|
||||
auto verlet_update(size_t m, float dt) -> void;
|
||||
auto update(float dt, std::optional<BS::thread_pool<>* const> thread_pool = std::nullopt) -> void;
|
||||
auto update(float dt, threadpool thread_pool = std::nullopt) -> void;
|
||||
|
||||
auto center_masses(std::optional<BS::thread_pool<>* const> thread_pool = std::nullopt) -> void;
|
||||
auto center_masses(threadpool thread_pool = std::nullopt) -> void;
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -1,7 +1,8 @@
|
||||
#ifndef DISTANCE_HPP_
|
||||
#define DISTANCE_HPP_
|
||||
|
||||
#include <cstddef>
|
||||
#include "cpu_spring_system.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
class graph_distances
|
||||
@ -15,7 +16,8 @@ public:
|
||||
auto clear() -> void;
|
||||
[[nodiscard]] auto empty() const -> bool;
|
||||
|
||||
auto calculate_distances(size_t node_count, const std::vector<std::pair<size_t, size_t>>& edges,
|
||||
auto calculate_distances(size_t node_count,
|
||||
const std::vector<spring>& edges,
|
||||
const std::vector<size_t>& targets) -> void;
|
||||
|
||||
[[nodiscard]] auto get_shortest_path(size_t source) const -> std::vector<size_t>;
|
||||
|
||||
@ -22,8 +22,7 @@ struct show_yes_no_message
|
||||
std::function<void()> on_yes;
|
||||
};
|
||||
|
||||
struct show_save_preset_window
|
||||
{};
|
||||
struct show_save_preset_window {};
|
||||
|
||||
using ui_command = std::variant<show_ok_message, show_yes_no_message, show_save_preset_window>;
|
||||
|
||||
@ -91,15 +90,13 @@ public:
|
||||
size_t collision_mass = -1;
|
||||
|
||||
public:
|
||||
input_handler(state_manager& _state, orbit_camera& _camera) : state(_state), camera(_camera)
|
||||
input_handler(state_manager& _state, orbit_camera& _camera)
|
||||
: state(_state), camera(_camera)
|
||||
{
|
||||
init_handlers();
|
||||
}
|
||||
|
||||
input_handler(const input_handler& copy) = delete;
|
||||
auto operator=(const input_handler& copy) -> input_handler& = delete;
|
||||
input_handler(input_handler&& move) = delete;
|
||||
auto operator=(input_handler&& move) -> input_handler& = delete;
|
||||
NO_COPY_NO_MOVE(input_handler);
|
||||
|
||||
private:
|
||||
auto init_handlers() -> void;
|
||||
@ -164,18 +161,14 @@ public:
|
||||
// General
|
||||
auto register_generic_handler(const std::function<void(input_handler&)>& handler) -> void;
|
||||
|
||||
auto register_mouse_pressed_handler(MouseButton button,
|
||||
const std::function<void(input_handler&)>& handler) -> void;
|
||||
auto register_mouse_pressed_handler(MouseButton button, const std::function<void(input_handler&)>& handler) -> void;
|
||||
|
||||
auto register_mouse_released_handler(MouseButton button,
|
||||
const std::function<void(input_handler&)>& handler)
|
||||
-> void;
|
||||
const std::function<void(input_handler&)>& handler) -> void;
|
||||
|
||||
auto register_key_pressed_handler(KeyboardKey key,
|
||||
const std::function<void(input_handler&)>& handler) -> void;
|
||||
auto register_key_pressed_handler(KeyboardKey key, const std::function<void(input_handler&)>& handler) -> void;
|
||||
|
||||
auto register_key_released_handler(KeyboardKey key,
|
||||
const std::function<void(input_handler&)>& handler) -> void;
|
||||
auto register_key_released_handler(KeyboardKey key, const std::function<void(input_handler&)>& handler) -> void;
|
||||
|
||||
auto handle_input() -> void;
|
||||
};
|
||||
|
||||
@ -18,7 +18,7 @@ class octree
|
||||
public:
|
||||
Vector3 mass_center = Vector3Zero();
|
||||
float mass_total = 0.0;
|
||||
uint8_t depth = 0;
|
||||
u8 depth = 0;
|
||||
float size = 0.0f; // Because our octree cells are cubic we don't need to store the bounds
|
||||
std::array<int, 8> children = {-1, -1, -1, -1, -1, -1, -1, -1};
|
||||
int mass_id = -1;
|
||||
@ -26,7 +26,7 @@ class octree
|
||||
};
|
||||
|
||||
private:
|
||||
// 21 * 3 = 63, fits in uint64_t for combined x/y/z morton-code
|
||||
// 21 * 3 = 63, fits in u64 for combined x/y/z morton-code
|
||||
static constexpr int MAX_DEPTH = 21;
|
||||
|
||||
std::vector<node> nodes;
|
||||
@ -39,15 +39,13 @@ private:
|
||||
// std::vector<Vector3> box_maxs;
|
||||
// std::vector<std::array<int, 8>> childrens;
|
||||
// std::vector<int> mass_ids;
|
||||
// std::vector<uint8_t> leafs; // bitpacked std::vector<bool> is a lot slower
|
||||
// std::vector<u8> leafs; // bitpacked std::vector<bool> is a lot slower
|
||||
|
||||
public:
|
||||
octree() = default;
|
||||
|
||||
// octree(const octree& copy) = delete;
|
||||
// auto operator=(const octree& copy) -> octree& = delete;
|
||||
// octree(octree&& move) = delete;
|
||||
// auto operator=(octree&& move) -> octree& = delete;
|
||||
// Required for async octree
|
||||
// NO_COPY_NO_MOVE(octree);
|
||||
|
||||
private:
|
||||
[[nodiscard]] INLINE static inline auto get_octant(const Vector3& box_min,
|
||||
@ -62,14 +60,14 @@ private:
|
||||
[[nodiscard]] INLINE static inline auto quantize_axis(float coordinate,
|
||||
float box_min,
|
||||
float box_max,
|
||||
int bits) -> uint32_t;
|
||||
int bits) -> u32;
|
||||
|
||||
[[nodiscard]] INLINE static inline auto pos_to_morton(const Vector3& p,
|
||||
const Vector3& root_min,
|
||||
const Vector3& root_max) -> uint64_t;
|
||||
const Vector3& root_max) -> u64;
|
||||
|
||||
[[nodiscard]] INLINE static inline auto jitter_pos(Vector3 p,
|
||||
uint32_t seed,
|
||||
u32 seed,
|
||||
const Vector3& root_min,
|
||||
const Vector3& root_max,
|
||||
float root_extent) -> Vector3;
|
||||
@ -82,7 +80,7 @@ private:
|
||||
// - [101 110 100]
|
||||
// - [101 110]
|
||||
// - [101] (root)
|
||||
[[nodiscard]] INLINE static inline auto path_to_ancestor(uint64_t leaf_code, int leaf_depth, int depth) -> uint64_t;
|
||||
[[nodiscard]] INLINE static inline auto path_to_ancestor(u64 leaf_code, int leaf_depth, int depth) -> u64;
|
||||
|
||||
// Use this to obtain the octant a leaf node is contained in (on any level).
|
||||
// The 3 interleaved bits in the morten code encode the octant [0, 7].
|
||||
@ -90,7 +88,7 @@ private:
|
||||
// - [100] (Level 2)
|
||||
// - [110] (Level 1)
|
||||
// - [101] (Level 0)
|
||||
[[nodiscard]] INLINE static inline auto octant_at_level(uint64_t leaf_code, int level, int leaf_depth) -> int;
|
||||
[[nodiscard]] INLINE static inline auto octant_at_level(u64 leaf_code, int level, int leaf_depth) -> int;
|
||||
|
||||
public:
|
||||
auto clear() -> void;
|
||||
@ -141,7 +139,7 @@ INLINE inline auto octree::get_child_bounds(const Vector3& box_min,
|
||||
INLINE inline auto octree::quantize_axis(const float coordinate,
|
||||
const float box_min,
|
||||
const float box_max,
|
||||
const int bits) -> uint32_t
|
||||
const int bits) -> u32
|
||||
{
|
||||
const float extent = box_max - box_min;
|
||||
if (extent <= 0.0f) {
|
||||
@ -152,20 +150,20 @@ INLINE inline auto octree::quantize_axis(const float coordinate,
|
||||
normalized = std::max(0.0f, std::min(normalized, std::nextafter(1.0f, 0.0f))); // avoid exactly 1.0
|
||||
|
||||
// bits up to 21 => (1u << bits) safe in 32-bit
|
||||
const uint32_t grid_max = (1u << bits) - 1u;
|
||||
return static_cast<uint32_t>(normalized * static_cast<float>(grid_max));
|
||||
const u32 grid_max = (1u << bits) - 1u;
|
||||
return static_cast<u32>(normalized * static_cast<float>(grid_max));
|
||||
}
|
||||
|
||||
INLINE inline auto octree::pos_to_morton(const Vector3& p, const Vector3& root_min, const Vector3& root_max) -> uint64_t
|
||||
INLINE inline auto octree::pos_to_morton(const Vector3& p, const Vector3& root_min, const Vector3& root_max) -> u64
|
||||
{
|
||||
const uint32_t x = quantize_axis(p.x, root_min.x, root_max.x, MAX_DEPTH);
|
||||
const uint32_t y = quantize_axis(p.y, root_min.y, root_max.y, MAX_DEPTH);
|
||||
const uint32_t z = quantize_axis(p.z, root_min.z, root_max.z, MAX_DEPTH);
|
||||
const u32 x = quantize_axis(p.x, root_min.x, root_max.x, MAX_DEPTH);
|
||||
const u32 y = quantize_axis(p.y, root_min.y, root_max.y, MAX_DEPTH);
|
||||
const u32 z = quantize_axis(p.z, root_min.z, root_max.z, MAX_DEPTH);
|
||||
return libmorton::morton3D_64_encode(x, y, z);
|
||||
}
|
||||
|
||||
INLINE inline auto octree::jitter_pos(Vector3 p,
|
||||
const uint32_t seed,
|
||||
const u32 seed,
|
||||
const Vector3& root_min,
|
||||
const Vector3& root_max,
|
||||
const float root_extent) -> Vector3
|
||||
@ -173,7 +171,7 @@ INLINE inline auto octree::jitter_pos(Vector3 p,
|
||||
// Use a hash to calculate a deterministic jitter: The same position should always get the same jitter.
|
||||
// We want this to get stable physics, particles at the same position shouldn't get different jitters
|
||||
// across frames...
|
||||
uint32_t h = (seed ^ 61u) ^ (seed >> 16);
|
||||
u32 h = (seed ^ 61u) ^ (seed >> 16);
|
||||
h *= 9u;
|
||||
h = h ^ (h >> 4);
|
||||
h *= 0x27d4eb2du;
|
||||
@ -195,14 +193,14 @@ INLINE inline auto octree::jitter_pos(Vector3 p,
|
||||
return p;
|
||||
}
|
||||
|
||||
INLINE inline auto octree::path_to_ancestor(const uint64_t leaf_code, const int leaf_depth, const int depth) -> uint64_t
|
||||
INLINE inline auto octree::path_to_ancestor(const u64 leaf_code, const int leaf_depth, const int depth) -> u64
|
||||
{
|
||||
// keep top 3*depth bits; drop the rest
|
||||
const int drop = 3 * (leaf_depth - depth);
|
||||
return (drop > 0) ? (leaf_code >> drop) : leaf_code;
|
||||
}
|
||||
|
||||
INLINE inline auto octree::octant_at_level(const uint64_t leaf_code, const int level, const int leaf_depth) -> int
|
||||
INLINE inline auto octree::octant_at_level(const u64 leaf_code, const int level, const int leaf_depth) -> int
|
||||
{
|
||||
// level 1 => child of root => topmost 3 bits
|
||||
const int shift = 3 * (leaf_depth - level);
|
||||
|
||||
@ -24,8 +24,7 @@ public:
|
||||
|
||||
auto pan(Vector2 last_mouse, Vector2 mouse) -> void;
|
||||
|
||||
auto update(const Vector3& current_target, const Vector3& mass_center, bool lock,
|
||||
bool mass_center_lock) -> void;
|
||||
auto update(const Vector3& current_target, const Vector3& mass_center, bool lock, bool mass_center_lock) -> void;
|
||||
};
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@ -92,10 +92,7 @@ public:
|
||||
rlDisableVertexArray();
|
||||
}
|
||||
|
||||
renderer(const renderer& copy) = delete;
|
||||
auto operator=(const renderer& copy) -> renderer& = delete;
|
||||
renderer(renderer&& move) = delete;
|
||||
auto operator=(renderer&& move) -> renderer& = delete;
|
||||
NO_COPY_NO_MOVE(renderer);
|
||||
|
||||
~renderer()
|
||||
{
|
||||
|
||||
@ -22,9 +22,9 @@ private:
|
||||
// State storage (store states twice for bidirectional lookup).
|
||||
// Everything else should only store indices to state_pool.
|
||||
|
||||
std::vector<puzzle> state_pool; // Indices are equal to mass_springs mass indices
|
||||
boost::unordered_flat_map<puzzle, size_t, puzzle_hasher> state_indices; // Maps states to indices
|
||||
std::vector<std::pair<size_t, size_t>> links; // Indices are equal to mass_springs springs indices
|
||||
std::vector<puzzle> state_pool; // Indices are equal to mass_springs mass indices
|
||||
puzzlemap<size_t> state_indices; // Maps states to indices
|
||||
std::vector<spring> links; // Indices are equal to mass_springs springs indices
|
||||
|
||||
graph_distances node_target_distances; // Buffered and reused if the graph doesn't change
|
||||
boost::unordered_flat_set<size_t> winning_indices; // Indices of all states where the board is solved
|
||||
@ -48,10 +48,7 @@ public:
|
||||
reload_preset_file();
|
||||
}
|
||||
|
||||
state_manager(const state_manager& copy) = delete;
|
||||
auto operator=(const state_manager& copy) -> state_manager& = delete;
|
||||
state_manager(state_manager&& move) = delete;
|
||||
auto operator=(state_manager&& move) -> state_manager& = delete;
|
||||
NO_COPY_NO_MOVE(state_manager);
|
||||
|
||||
private:
|
||||
/**
|
||||
@ -83,8 +80,7 @@ private:
|
||||
* @param states List of states to insert
|
||||
* @param _links List of links to insert
|
||||
*/
|
||||
auto synced_insert_statespace(const std::vector<puzzle>& states,
|
||||
const std::vector<std::pair<size_t, size_t>>& _links) -> void;
|
||||
auto synced_insert_statespace(const std::vector<puzzle>& states, const std::vector<spring>& _links) -> void;
|
||||
|
||||
/**
|
||||
* Clears all states and links (and related) from the state_manager and the physics system.
|
||||
@ -130,7 +126,7 @@ public:
|
||||
[[nodiscard]] auto get_target_count() const -> size_t;
|
||||
[[nodiscard]] auto get_link_count() const -> size_t;
|
||||
[[nodiscard]] auto get_path_length() const -> size_t;
|
||||
[[nodiscard]] auto get_links() const -> const std::vector<std::pair<size_t, size_t>>&;
|
||||
[[nodiscard]] auto get_links() const -> const std::vector<spring>&;
|
||||
[[nodiscard]] auto get_winning_indices() const -> const boost::unordered_flat_set<size_t>&;
|
||||
[[nodiscard]] auto get_visit_counts() const -> const boost::unordered_flat_map<size_t, int>&;
|
||||
[[nodiscard]] auto get_winning_path() const -> const std::vector<size_t>&;
|
||||
|
||||
@ -22,15 +22,17 @@ class user_interface
|
||||
const int padding;
|
||||
|
||||
public:
|
||||
grid(const int _x, const int _y, const int _width, const int _height, const int _columns,
|
||||
const int _rows, const int _padding)
|
||||
: x(_x), y(_y), width(_width), height(_height), columns(_columns), rows(_rows),
|
||||
padding(_padding)
|
||||
{}
|
||||
grid(const int _x,
|
||||
const int _y,
|
||||
const int _width,
|
||||
const int _height,
|
||||
const int _columns,
|
||||
const int _rows,
|
||||
const int _padding)
|
||||
: x(_x), y(_y), width(_width), height(_height), columns(_columns), rows(_rows), padding(_padding) {}
|
||||
|
||||
public:
|
||||
auto update_bounds(int _x, int _y, int _width, int _height, int _columns, int _rows)
|
||||
-> void;
|
||||
auto update_bounds(int _x, int _y, int _width, int _height, int _columns, int _rows) -> void;
|
||||
auto update_bounds(int _x, int _y, int _width, int _height) -> void;
|
||||
auto update_bounds(int _x, int _y) -> void;
|
||||
|
||||
@ -38,8 +40,7 @@ class user_interface
|
||||
[[nodiscard]] auto bounds(int _x, int _y, int _width, int _height) const -> Rectangle;
|
||||
|
||||
[[nodiscard]] auto square_bounds() const -> Rectangle;
|
||||
[[nodiscard]] auto square_bounds(int _x, int _y, int _width, int _height) const
|
||||
-> Rectangle;
|
||||
[[nodiscard]] auto square_bounds(int _x, int _y, int _width, int _height) const -> Rectangle;
|
||||
};
|
||||
|
||||
struct style
|
||||
@ -87,14 +88,17 @@ private:
|
||||
|
||||
grid menu_grid = grid(0, 0, GetScreenWidth(), MENU_HEIGHT, MENU_COLS, MENU_ROWS, MENU_PAD);
|
||||
|
||||
grid board_grid =
|
||||
grid(0, MENU_HEIGHT, GetScreenWidth() / 2, GetScreenHeight() - MENU_HEIGHT,
|
||||
state.get_current_state().get_width(), state.get_current_state().get_height(), BOARD_PADDING);
|
||||
grid board_grid = grid(0,
|
||||
MENU_HEIGHT,
|
||||
GetScreenWidth() / 2,
|
||||
GetScreenHeight() - MENU_HEIGHT,
|
||||
state.get_current_state().get_width(),
|
||||
state.get_current_state().get_height(),
|
||||
BOARD_PADDING);
|
||||
|
||||
grid graph_overlay_grid = grid(GetScreenWidth() / 2, MENU_HEIGHT, 200, 100, 1, 4, MENU_PAD);
|
||||
|
||||
grid debug_overlay_grid =
|
||||
grid(GetScreenWidth() / 2, GetScreenHeight() - 75, 200, 75, 1, 3, MENU_PAD);
|
||||
grid debug_overlay_grid = grid(GetScreenWidth() / 2, GetScreenHeight() - 75, 200, 75, 1, 3, MENU_PAD);
|
||||
|
||||
// Windows
|
||||
|
||||
@ -114,10 +118,7 @@ public:
|
||||
init();
|
||||
}
|
||||
|
||||
user_interface(const user_interface& copy) = delete;
|
||||
auto operator=(const user_interface& copy) -> user_interface& = delete;
|
||||
user_interface(user_interface&& move) = delete;
|
||||
auto operator=(user_interface&& move) -> user_interface& = delete;
|
||||
NO_COPY_NO_MOVE(user_interface);
|
||||
|
||||
private:
|
||||
static auto init() -> void;
|
||||
@ -133,32 +134,68 @@ private:
|
||||
|
||||
[[nodiscard]] static auto popup_bounds() -> Rectangle;
|
||||
|
||||
auto draw_button(Rectangle bounds, const std::string& label, Color color, bool enabled = true,
|
||||
auto draw_button(Rectangle bounds,
|
||||
const std::string& label,
|
||||
Color color,
|
||||
bool enabled = true,
|
||||
int font_size = FONT_SIZE) const -> int;
|
||||
|
||||
auto draw_menu_button(int x, int y, int width, int height, const std::string& label,
|
||||
Color color, bool enabled = true, int font_size = FONT_SIZE) const -> int;
|
||||
auto draw_menu_button(int x,
|
||||
int y,
|
||||
int width,
|
||||
int height,
|
||||
const std::string& label,
|
||||
Color color,
|
||||
bool enabled = true,
|
||||
int font_size = FONT_SIZE) const -> int;
|
||||
|
||||
auto draw_toggle_slider(Rectangle bounds, const std::string& off_label,
|
||||
const std::string& on_label, int* active, Color color,
|
||||
bool enabled = true, int font_size = FONT_SIZE) const -> int;
|
||||
auto draw_toggle_slider(Rectangle bounds,
|
||||
const std::string& off_label,
|
||||
const std::string& on_label,
|
||||
int* active,
|
||||
Color color,
|
||||
bool enabled = true,
|
||||
int font_size = FONT_SIZE) const -> int;
|
||||
|
||||
auto draw_menu_toggle_slider(int x, int y, int width, int height, const std::string& off_label,
|
||||
const std::string& on_label, int* active, Color color,
|
||||
bool enabled = true, int font_size = FONT_SIZE) const -> int;
|
||||
auto draw_menu_toggle_slider(int x,
|
||||
int y,
|
||||
int width,
|
||||
int height,
|
||||
const std::string& off_label,
|
||||
const std::string& on_label,
|
||||
int* active,
|
||||
Color color,
|
||||
bool enabled = true,
|
||||
int font_size = FONT_SIZE) const -> int;
|
||||
|
||||
auto draw_spinner(Rectangle bounds, const std::string& label, int* value, int min, int max,
|
||||
Color color, bool enabled = true, int font_size = FONT_SIZE) const -> int;
|
||||
auto draw_spinner(Rectangle bounds,
|
||||
const std::string& label,
|
||||
int* value,
|
||||
int min,
|
||||
int max,
|
||||
Color color,
|
||||
bool enabled = true,
|
||||
int font_size = FONT_SIZE) const -> int;
|
||||
|
||||
auto draw_menu_spinner(int x, int y, int width, int height, const std::string& label,
|
||||
int* value, int min, int max, Color color, bool enabled = true,
|
||||
auto draw_menu_spinner(int x,
|
||||
int y,
|
||||
int width,
|
||||
int height,
|
||||
const std::string& label,
|
||||
int* value,
|
||||
int min,
|
||||
int max,
|
||||
Color color,
|
||||
bool enabled = true,
|
||||
int font_size = FONT_SIZE) const -> int;
|
||||
|
||||
auto draw_label(Rectangle bounds, const std::string& text, Color color, bool enabled = true,
|
||||
auto draw_label(Rectangle bounds,
|
||||
const std::string& text,
|
||||
Color color,
|
||||
bool enabled = true,
|
||||
int font_size = FONT_SIZE) const -> int;
|
||||
|
||||
auto draw_board_block(int x, int y, int width, int height, Color color,
|
||||
bool enabled = true) const -> bool;
|
||||
auto draw_board_block(int x, int y, int width, int height, Color color, bool enabled = true) const -> bool;
|
||||
|
||||
[[nodiscard]] auto window_open() const -> bool;
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
#ifndef UTIL_HPP_
|
||||
#define UTIL_HPP_
|
||||
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <raylib.h>
|
||||
|
||||
@ -8,10 +9,26 @@
|
||||
#define PACKED __attribute__((packed))
|
||||
|
||||
#define STARTTIME const auto start = std::chrono::high_resolution_clock::now()
|
||||
#define ENDTIME(msg) const auto end = std::chrono::high_resolution_clock::now(); \
|
||||
infoln("{}. Took {}ms.", msg, std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count())
|
||||
#define ENDTIME(msg, cast, unit) const auto end = std::chrono::high_resolution_clock::now(); \
|
||||
infoln("{}. Took {}{}.", msg, std::chrono::duration_cast<cast>(end - start).count(), unit)
|
||||
|
||||
// std::variant visitor
|
||||
#define COMMENT if (false)
|
||||
|
||||
#define NO_COPY_NO_MOVE(typename) \
|
||||
typename(const typename& copy) = delete; \
|
||||
auto operator=(const typename& copy) -> typename& = delete; \
|
||||
typename(typename&& move) = delete; \
|
||||
auto operator=(typename&& move) -> typename& = delete;
|
||||
|
||||
using u8 = uint8_t;
|
||||
using u16 = uint16_t;
|
||||
using u32 = uint32_t;
|
||||
using u64 = uint64_t;
|
||||
|
||||
using i8 = int8_t;
|
||||
using i16 = int16_t;
|
||||
using i32 = int32_t;
|
||||
using i64 = int64_t;
|
||||
|
||||
// https://en.cppreference.com/w/cpp/utility/variant/visit
|
||||
template <class... Ts>
|
||||
@ -20,9 +37,21 @@ struct overloads : Ts...
|
||||
using Ts::operator()...;
|
||||
};
|
||||
|
||||
inline auto binom(const int n, const int k) -> int
|
||||
{
|
||||
std::vector<int> solutions(k);
|
||||
solutions[0] = n - k + 1;
|
||||
|
||||
for (int i = 1; i < k; ++i) {
|
||||
solutions[i] = solutions[i - 1] * (n - k + 1 + i) / (i + 1);
|
||||
}
|
||||
|
||||
return solutions[k - 1];
|
||||
}
|
||||
|
||||
// Enums
|
||||
|
||||
enum dir : uint8_t
|
||||
enum dir : u8
|
||||
{
|
||||
nor = 1 << 0,
|
||||
eas = 1 << 1,
|
||||
@ -32,7 +61,7 @@ enum dir : uint8_t
|
||||
|
||||
// Ansi
|
||||
|
||||
enum class ctrl : uint8_t
|
||||
enum class ctrl : u8
|
||||
{
|
||||
reset = 0,
|
||||
bold_bright = 1,
|
||||
@ -43,7 +72,7 @@ enum class ctrl : uint8_t
|
||||
inverse_off = 27
|
||||
};
|
||||
|
||||
enum class fg : uint8_t
|
||||
enum class fg : u8
|
||||
{
|
||||
black = 30,
|
||||
red = 31,
|
||||
@ -55,7 +84,7 @@ enum class fg : uint8_t
|
||||
white = 37
|
||||
};
|
||||
|
||||
enum class bg : uint8_t
|
||||
enum class bg : u8
|
||||
{
|
||||
black = 40,
|
||||
red = 41,
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
#include "bits.hpp"
|
||||
|
||||
auto print_bitmap(const uint64_t bitmap, const uint8_t w, const uint8_t h, const std::string& title) -> void {
|
||||
auto print_bitmap(const u64 bitmap, const u8 w, const u8 h, const std::string& title) -> void {
|
||||
traceln("{}:", title);
|
||||
traceln("{}", std::string(2 * w - 1, '='));
|
||||
for (size_t y = 0; y < w; ++y) {
|
||||
for (size_t y = 0; y < h; ++y) {
|
||||
std::cout << " ";
|
||||
for (size_t x = 0; x < h; ++x) {
|
||||
std::cout << static_cast<int>(get_bits(bitmap, y * w + x, y * h + x)) << " ";
|
||||
for (size_t x = 0; x < w; ++x) {
|
||||
std::cout << static_cast<int>(get_bits(bitmap, y * w + x, y * w + x)) << " ";
|
||||
}
|
||||
std::cout << "\n";
|
||||
}
|
||||
|
||||
@ -17,7 +17,7 @@ auto cpu_layout_engine::set_octree_pool_thread_name(size_t idx) -> void
|
||||
}
|
||||
#endif
|
||||
|
||||
auto cpu_layout_engine::physics_thread(physics_state& state, const std::optional<BS::thread_pool<>* const> thread_pool) -> void
|
||||
auto cpu_layout_engine::physics_thread(physics_state& state, const threadpool thread_pool) -> void
|
||||
{
|
||||
cpu_spring_system mass_springs;
|
||||
|
||||
@ -138,10 +138,11 @@ auto cpu_layout_engine::physics_thread(physics_state& state, const std::optional
|
||||
#else
|
||||
std::unique_lock<std::mutex> lock(state.data_mtx);
|
||||
#endif
|
||||
state.data_consumed_cnd.wait(lock, [&]
|
||||
{
|
||||
return state.data_consumed || !state.running.load();
|
||||
});
|
||||
state.data_consumed_cnd.wait(lock,
|
||||
[&]
|
||||
{
|
||||
return state.data_consumed || !state.running.load();
|
||||
});
|
||||
if (!state.running.load()) {
|
||||
// Running turned false while we were waiting for the condition
|
||||
break;
|
||||
@ -175,7 +176,8 @@ auto cpu_layout_engine::physics_thread(physics_state& state, const std::optional
|
||||
state.data_ready_cnd.notify_all();
|
||||
|
||||
#ifdef TRACY
|
||||
FrameMarkEnd("PhysicsThreadProduceLock"); FrameMarkEnd("PhysicsThread");
|
||||
FrameMarkEnd("PhysicsThreadProduceLock");
|
||||
FrameMarkEnd("PhysicsThread");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@ -216,8 +218,7 @@ auto cpu_layout_engine::add_spring_cmd(const size_t a, const size_t b) -> void
|
||||
}
|
||||
}
|
||||
|
||||
auto cpu_layout_engine::add_mass_springs_cmd(const size_t num_masses,
|
||||
const std::vector<std::pair<size_t, size_t>>& springs) -> void
|
||||
auto cpu_layout_engine::add_mass_springs_cmd(const size_t num_masses, const std::vector<spring>& springs) -> void
|
||||
{
|
||||
{
|
||||
#ifdef TRACY
|
||||
|
||||
@ -37,17 +37,18 @@ auto cpu_spring_system::add_spring(size_t a, size_t b) -> void
|
||||
const Vector3& mass_a = positions[a];
|
||||
const Vector3& mass_b = positions[b];
|
||||
|
||||
Vector3 offset{static_cast<float>(GetRandomValue(-100, 100)),
|
||||
Vector3 offset{
|
||||
static_cast<float>(GetRandomValue(-100, 100)),
|
||||
static_cast<float>(GetRandomValue(-100, 100))};
|
||||
static_cast<float>(GetRandomValue(-100, 100)),
|
||||
static_cast<float>(GetRandomValue(-100, 100))
|
||||
};
|
||||
|
||||
// By spawning the masses close together, we "explode" them naturally, so they cluster faster (also looks cool)
|
||||
offset = Vector3Normalize(offset) * REST_LENGTH * 0.1;
|
||||
|
||||
// If the offset moves the mass closer to the current center of mass, flip it
|
||||
if (!tree.empty()) {
|
||||
const Vector3 mass_center_direction =
|
||||
Vector3Subtract(positions[a], tree.root().mass_center);
|
||||
const Vector3 mass_center_direction = Vector3Subtract(positions[a], tree.root().mass_center);
|
||||
const float mass_center_distance = Vector3Length(mass_center_direction);
|
||||
|
||||
if (mass_center_distance > 0 && Vector3DotProduct(offset, mass_center_direction) < 0.0f) {
|
||||
@ -77,10 +78,10 @@ auto cpu_spring_system::clear_forces() -> void
|
||||
auto cpu_spring_system::calculate_spring_force(const size_t s) -> void
|
||||
{
|
||||
const spring _s = springs[s];
|
||||
const Vector3 a_pos = positions[_s.a];
|
||||
const Vector3 b_pos = positions[_s.b];
|
||||
const Vector3 a_vel = velocities[_s.a];
|
||||
const Vector3 b_vel = velocities[_s.b];
|
||||
const Vector3 a_pos = positions[_s.first];
|
||||
const Vector3 b_pos = positions[_s.second];
|
||||
const Vector3 a_vel = velocities[_s.first];
|
||||
const Vector3 b_vel = velocities[_s.second];
|
||||
|
||||
const Vector3 delta_pos = a_pos - b_pos;
|
||||
const Vector3 delta_vel = a_vel - b_vel;
|
||||
@ -95,23 +96,23 @@ auto cpu_spring_system::calculate_spring_force(const size_t s) -> void
|
||||
const Vector3 a_force = Vector3Scale(delta_pos, -(hooke + dampening) * inv_len);
|
||||
const Vector3 b_force = a_force * -1.0f;
|
||||
|
||||
forces[_s.a] += a_force;
|
||||
forces[_s.b] += b_force;
|
||||
forces[_s.first] += a_force;
|
||||
forces[_s.second] += b_force;
|
||||
}
|
||||
|
||||
auto cpu_spring_system::calculate_spring_forces(
|
||||
const std::optional<BS::thread_pool<>* const> thread_pool) -> void
|
||||
auto cpu_spring_system::calculate_spring_forces(const threadpool thread_pool) -> void
|
||||
{
|
||||
#ifdef TRACY
|
||||
ZoneScoped;
|
||||
#endif
|
||||
|
||||
const auto solve_spring_force = [&](const int i) { calculate_spring_force(i); };
|
||||
const auto solve_spring_force = [&](const int i)
|
||||
{
|
||||
calculate_spring_force(i);
|
||||
};
|
||||
|
||||
if (thread_pool) {
|
||||
(*thread_pool)
|
||||
->submit_loop(0, springs.size(), solve_spring_force, SMALL_TASK_BLOCK_SIZE)
|
||||
.wait();
|
||||
(*thread_pool)->submit_loop(0, springs.size(), solve_spring_force, SMALL_TASK_BLOCK_SIZE).wait();
|
||||
} else {
|
||||
for (size_t i = 0; i < springs.size(); ++i) {
|
||||
solve_spring_force(i);
|
||||
@ -119,8 +120,7 @@ auto cpu_spring_system::calculate_spring_forces(
|
||||
}
|
||||
}
|
||||
|
||||
auto cpu_spring_system::calculate_repulsion_forces(
|
||||
const std::optional<BS::thread_pool<>* const> thread_pool) -> void
|
||||
auto cpu_spring_system::calculate_repulsion_forces(const threadpool thread_pool) -> void
|
||||
{
|
||||
#ifdef TRACY
|
||||
ZoneScoped;
|
||||
@ -134,9 +134,7 @@ auto cpu_spring_system::calculate_repulsion_forces(
|
||||
|
||||
// Calculate forces using Barnes-Hut
|
||||
if (thread_pool) {
|
||||
(*thread_pool)
|
||||
->submit_loop(0, positions.size(), solve_octree, LARGE_TASK_BLOCK_SIZE)
|
||||
.wait();
|
||||
(*thread_pool)->submit_loop(0, positions.size(), solve_octree, LARGE_TASK_BLOCK_SIZE).wait();
|
||||
} else {
|
||||
for (size_t i = 0; i < positions.size(); ++i) {
|
||||
solve_octree(i);
|
||||
@ -168,14 +166,16 @@ auto cpu_spring_system::verlet_update(const size_t m, const float dt) -> void
|
||||
previous_positions[m] = pos;
|
||||
}
|
||||
|
||||
auto cpu_spring_system::update(const float dt,
|
||||
const std::optional<BS::thread_pool<>* const> thread_pool) -> void
|
||||
auto cpu_spring_system::update(const float dt, const threadpool thread_pool) -> void
|
||||
{
|
||||
#ifdef TRACY
|
||||
ZoneScoped;
|
||||
#endif
|
||||
|
||||
const auto update = [&](const int i) { verlet_update(i, dt); };
|
||||
const auto update = [&](const int i)
|
||||
{
|
||||
verlet_update(i, dt);
|
||||
};
|
||||
|
||||
if (thread_pool) {
|
||||
(*thread_pool)->submit_loop(0, positions.size(), update, SMALL_TASK_BLOCK_SIZE).wait();
|
||||
@ -186,8 +186,7 @@ auto cpu_spring_system::update(const float dt,
|
||||
}
|
||||
}
|
||||
|
||||
auto cpu_spring_system::center_masses(const std::optional<BS::thread_pool<>* const> thread_pool)
|
||||
-> void
|
||||
auto cpu_spring_system::center_masses(const threadpool thread_pool) -> void
|
||||
{
|
||||
Vector3 mean = Vector3Zero();
|
||||
for (const Vector3& pos : positions) {
|
||||
@ -195,7 +194,10 @@ auto cpu_spring_system::center_masses(const std::optional<BS::thread_pool<>* con
|
||||
}
|
||||
mean /= static_cast<float>(positions.size());
|
||||
|
||||
const auto center_mass = [&](const int i) { positions[i] -= mean; };
|
||||
const auto center_mass = [&](const int i)
|
||||
{
|
||||
positions[i] -= mean;
|
||||
};
|
||||
|
||||
if (thread_pool) {
|
||||
(*thread_pool)->submit_loop(0, positions.size(), center_mass, SMALL_TASK_BLOCK_SIZE).wait();
|
||||
|
||||
@ -15,7 +15,7 @@ auto graph_distances::empty() const -> bool
|
||||
}
|
||||
|
||||
auto graph_distances::calculate_distances(const size_t node_count,
|
||||
const std::vector<std::pair<size_t, size_t>>& edges,
|
||||
const std::vector<spring>& edges,
|
||||
const std::vector<size_t>& targets) -> void
|
||||
{
|
||||
// Build a list of adjacent nodes to speed up BFS
|
||||
|
||||
@ -208,7 +208,7 @@ auto input_handler::add_block() -> void
|
||||
has_block_add_xy = false;
|
||||
} else if (current.covers(block_add_x, block_add_y, block_add_width, block_add_height)) {
|
||||
const std::optional<puzzle>& next = current.try_add_block(
|
||||
puzzle::block(block_add_x, block_add_y, block_add_width, block_add_height, false));
|
||||
block(block_add_x, block_add_y, block_add_width, block_add_height, false));
|
||||
|
||||
if (next) {
|
||||
sel_x = block_add_x;
|
||||
@ -224,7 +224,7 @@ auto input_handler::add_block() -> void
|
||||
auto input_handler::remove_block() -> void
|
||||
{
|
||||
const puzzle& current = state.get_current_state();
|
||||
const std::optional<puzzle::block>& b = current.try_get_block(hov_x, hov_y);
|
||||
const std::optional<block>& b = current.try_get_block(hov_x, hov_y);
|
||||
if (!editing || has_block_add_xy || !b) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -62,7 +62,10 @@ auto append_preset_file(const std::string& preset_file, const std::string& prese
|
||||
return true;
|
||||
}
|
||||
|
||||
auto append_preset_file_quiet(const std::string& preset_file, const std::string& preset_name, const puzzle& p, const bool validate) -> bool
|
||||
auto append_preset_file_quiet(const std::string& preset_file,
|
||||
const std::string& preset_name,
|
||||
const puzzle& p,
|
||||
const bool validate) -> bool
|
||||
{
|
||||
if (validate && p.try_get_invalid_reason()) {
|
||||
return false;
|
||||
|
||||
224
src/main.cpp
224
src/main.cpp
@ -16,10 +16,35 @@
|
||||
namespace po = boost::program_options;
|
||||
#endif
|
||||
|
||||
// TODO: Implement state discovery/enumeration
|
||||
// - Find all possible initial board states (single one for each possible statespace).
|
||||
// Currently wer're just finding all states given the initial state
|
||||
// - Would allow to generate random puzzles with a certain move count
|
||||
// Threadpool setup
|
||||
#ifdef THREADPOOL
|
||||
auto set_pool_thread_name(size_t idx) -> void
|
||||
{
|
||||
BS::this_thread::set_os_thread_name(std::format("worker-{}", idx));
|
||||
}
|
||||
|
||||
BS::thread_pool<> threads(std::thread::hardware_concurrency() - 2, set_pool_thread_name);
|
||||
constexpr threadpool thread_pool = &threads;
|
||||
#else
|
||||
constexpr threadpool thread_pool = std::nullopt;
|
||||
#endif
|
||||
|
||||
// Argparse defaults
|
||||
std::string preset_file = "default.puzzle";
|
||||
std::string output_file = "clusters.puzzle";
|
||||
int max_blocks = 5;
|
||||
int min_moves = 10;
|
||||
|
||||
// Puzzle space setup
|
||||
int board_width;
|
||||
int board_height;
|
||||
int goal_x;
|
||||
int goal_y;
|
||||
bool restricted;
|
||||
blockset2 permitted_blocks;
|
||||
block target_block;
|
||||
std::tuple<u8, u8, u8, u8> target_block_pos_range;
|
||||
|
||||
// TODO: Export cluster to graphviz
|
||||
// TODO: Fix naming:
|
||||
// - Target: The block that has to leave the board to win
|
||||
@ -32,29 +57,6 @@ namespace po = boost::program_options;
|
||||
// TODO: Add state space generation time to debug overlay
|
||||
// TODO: Move selection accordingly when undoing moves (need to diff two states and get the moved blocks)
|
||||
|
||||
// TODO: Click states in the graph to display them in the board
|
||||
|
||||
#ifdef THREADPOOL
|
||||
auto set_pool_thread_name(size_t idx) -> void
|
||||
{
|
||||
BS::this_thread::set_os_thread_name(std::format("worker-{}", idx));
|
||||
}
|
||||
|
||||
BS::thread_pool<> threads(std::thread::hardware_concurrency() - 2, set_pool_thread_name);
|
||||
constexpr std::optional<BS::thread_pool<>* const> thread_pool = &threads;
|
||||
#else
|
||||
constexpr std::optional<BS::thread_pool<>* const> thread_pool = std::nullopt;
|
||||
#endif
|
||||
|
||||
std::string preset_file = "default.puzzle";
|
||||
std::string output_file = "clusters.puzzle";
|
||||
int max_blocks = 5;
|
||||
int board_width = 6;
|
||||
int board_height = 6;
|
||||
int goal_x = 4;
|
||||
int goal_y = 2;
|
||||
bool restricted = true;
|
||||
|
||||
auto ui_mode() -> int
|
||||
{
|
||||
// RayLib window setup
|
||||
@ -167,56 +169,87 @@ auto ui_mode() -> int
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto rush_hour_puzzle_space() -> int
|
||||
auto rush_hour_puzzle_space() -> void
|
||||
{
|
||||
const boost::unordered_flat_set<puzzle::block, block_hasher2, block_equal2> permitted_blocks = {
|
||||
puzzle::block(0, 0, 2, 1, false, false),
|
||||
puzzle::block(0, 0, 3, 1, false, false),
|
||||
puzzle::block(0, 0, 1, 2, false, false),
|
||||
puzzle::block(0, 0, 1, 3, false, false)
|
||||
};
|
||||
const puzzle::block target_block = puzzle::block(0, 0, 2, 1, true, false);
|
||||
const std::tuple<uint8_t, uint8_t, uint8_t, uint8_t> target_block_pos_range = {
|
||||
0,
|
||||
goal_y,
|
||||
board_width - target_block.get_width() - 1,
|
||||
goal_y
|
||||
board_width = 6;
|
||||
board_height = 6;
|
||||
goal_x = 4;
|
||||
goal_y = 2;
|
||||
restricted = true;
|
||||
permitted_blocks = {
|
||||
block(0, 0, 2, 1, false, false),
|
||||
block(0, 0, 3, 1, false, false),
|
||||
block(0, 0, 1, 2, false, false),
|
||||
block(0, 0, 1, 3, false, false)
|
||||
};
|
||||
target_block = block(0, 0, 2, 1, true, false);
|
||||
target_block_pos_range = {0, goal_y, board_width - target_block.get_width(), goal_y};
|
||||
}
|
||||
|
||||
infoln("Exploring Rush-Hour puzzle space:");
|
||||
auto klotski_puzzle_space() -> void
|
||||
{
|
||||
board_width = 4;
|
||||
board_height = 5;
|
||||
goal_x = 1;
|
||||
goal_y = 3;
|
||||
restricted = false;
|
||||
permitted_blocks = {
|
||||
block(0, 0, 1, 1, false, false),
|
||||
block(0, 0, 1, 2, false, false),
|
||||
block(0, 0, 2, 1, false, false),
|
||||
};
|
||||
target_block = block(0, 0, 2, 2, true, false);
|
||||
target_block_pos_range = {
|
||||
0,
|
||||
0,
|
||||
board_width - target_block.get_width(),
|
||||
board_height - target_block.get_height(),
|
||||
};
|
||||
}
|
||||
|
||||
auto puzzle_space() -> int
|
||||
{
|
||||
// We don't only pick max_blocks out of n (with duplicates), but also 1 out of n, 2, 3, ... max_blocks-1 out of n
|
||||
int upper_set_count = 0;
|
||||
for (int i = 1; i <= max_blocks; ++i) {
|
||||
upper_set_count += binom(permitted_blocks.size() + i - 1, i);
|
||||
}
|
||||
|
||||
infoln("Exploring puzzle space:");
|
||||
infoln("- Size: {}x{}", board_width, board_height);
|
||||
infoln("- Goal: {},{}", goal_x, goal_y);
|
||||
infoln("- Restricted: {}", restricted);
|
||||
infoln("- Max Blocks: {}", max_blocks);
|
||||
infoln("- Min Moves: {}", min_moves);
|
||||
infoln("- Target: {}x{}", target_block.get_width(), target_block.get_height());
|
||||
infoln("- Max Sets: {}", upper_set_count);
|
||||
infoln("- Permitted block sizes:");
|
||||
std::cout << " ";
|
||||
for (const puzzle::block b : permitted_blocks) {
|
||||
for (const block b : permitted_blocks) {
|
||||
std::cout << std::format(" {}x{},", b.get_width(), b.get_height());
|
||||
}
|
||||
std::cout << std::endl;
|
||||
const std::chrono::high_resolution_clock::time_point start = std::chrono::high_resolution_clock::now();
|
||||
|
||||
const puzzle p = puzzle(board_width, board_height, goal_x, goal_y, restricted, true);
|
||||
const boost::unordered_flat_set<puzzle, puzzle_hasher> result = p.explore_puzzle_space(
|
||||
|
||||
STARTTIME;
|
||||
const puzzleset result = p.explore_puzzle_space(
|
||||
permitted_blocks,
|
||||
target_block,
|
||||
target_block_pos_range,
|
||||
max_blocks,
|
||||
min_moves,
|
||||
thread_pool);
|
||||
ENDTIME(std::format("Found {} different clusters", result.size()), std::chrono::seconds, "s");
|
||||
|
||||
const std::chrono::high_resolution_clock::time_point end = std::chrono::high_resolution_clock::now();
|
||||
infoln("Found {} different clusters. Took {}s.",
|
||||
result.size(),
|
||||
std::chrono::duration_cast<std::chrono::seconds>(end - start).count());
|
||||
// TODO: The exported clusters are the numerically smallest state of the cluster.
|
||||
// Not the state with the longest path.
|
||||
|
||||
infoln("Sorting clusters...");
|
||||
std::vector<puzzle> result_sorted{result.begin(), result.end()};
|
||||
std::ranges::sort(result_sorted, std::ranges::greater{});
|
||||
// for (const puzzle& _p : result_sorted) {
|
||||
// traceln("{}", _p.string_repr());
|
||||
// }
|
||||
|
||||
infoln("Saving clusters...");
|
||||
size_t i = 0;
|
||||
size_t success = 0;
|
||||
std::filesystem::remove(output_file);
|
||||
@ -237,26 +270,32 @@ auto rush_hour_puzzle_space() -> int
|
||||
|
||||
enum class runmode
|
||||
{
|
||||
USER_INTERFACE, RUSH_HOUR_PUZZLE_SPACE, EXIT,
|
||||
USER_INTERFACE,
|
||||
RUSH_HOUR_PUZZLE_SPACE,
|
||||
KLOTSKI_PUZZLE_SPACE,
|
||||
EXIT,
|
||||
};
|
||||
|
||||
auto argparse(const int argc, char* argv[]) -> runmode
|
||||
{
|
||||
#if not defined(_WIN32)
|
||||
po::options_description desc("Allowed options");
|
||||
desc.add_options() //
|
||||
("help", "produce help message") //
|
||||
("presets", po::value<std::string>()->default_value(preset_file), "load presets from file") //
|
||||
("output", po::value<std::string>()->default_value(output_file), "output file for generated clusters") //
|
||||
("space", po::value<std::string>()->value_name("rh|klotski"), "generate puzzle space with ruleset") //
|
||||
("w", po::value<int>()->default_value(board_width)->value_name("[3, 8]"), "board width") //
|
||||
("h", po::value<int>()->default_value(board_height)->value_name("[3, 8"), "board height") //
|
||||
("gx", po::value<int>()->default_value(goal_x)->value_name("[0, w-1]"), "board goal horizontal position") //
|
||||
("gy", po::value<int>()->default_value(goal_y)->value_name("[0, h-1]"), "board goal vertical position") //
|
||||
("free", "allow free block movement") //
|
||||
desc.add_options() //
|
||||
("help", "produce help message") //
|
||||
("presets", po::value<std::string>()->default_value(preset_file), "load presets from file") //
|
||||
("output", po::value<std::string>()->default_value(output_file), "output file for generated clusters") //
|
||||
("space", po::value<std::string>()->value_name("rh|klotski"), "generate puzzle space with ruleset") //
|
||||
// ("w", po::value<int>()->default_value(board_width)->value_name("[3, 8]"), "board width") //
|
||||
// ("h", po::value<int>()->default_value(board_height)->value_name("[3, 8"), "board height") //
|
||||
// ("gx", po::value<int>()->default_value(goal_x)->value_name("[0, w-1]"), "board goal horizontal position") //
|
||||
// ("gy", po::value<int>()->default_value(goal_y)->value_name("[0, h-1]"), "board goal vertical position") //
|
||||
// ("free", "allow free block movement") //
|
||||
("blocks",
|
||||
po::value<int>()->default_value(max_blocks)->value_name("[1, 15]"),
|
||||
"block limit for puzzle space generation") //
|
||||
("moves",
|
||||
po::value<int>()->default_value(min_moves),
|
||||
"only save puzzles with at least this many required moves") //
|
||||
;
|
||||
|
||||
po::positional_options_description positional;
|
||||
@ -270,52 +309,49 @@ auto argparse(const int argc, char* argv[]) -> runmode
|
||||
std::cout << desc << std::endl;
|
||||
return runmode::EXIT;
|
||||
}
|
||||
|
||||
if (vm.contains("output")) {
|
||||
output_file = vm["output"].as<std::string>();
|
||||
}
|
||||
|
||||
if (vm.contains("w")) {
|
||||
board_width = vm["w"].as<int>();
|
||||
board_width = std::max(static_cast<int>(puzzle::MIN_WIDTH),
|
||||
std::min(board_width, static_cast<int>(puzzle::MAX_WIDTH)));
|
||||
}
|
||||
|
||||
if (vm.contains("h")) {
|
||||
board_height = vm["h"].as<int>();
|
||||
board_height = std::max(static_cast<int>(puzzle::MIN_HEIGHT),
|
||||
std::min(board_height, static_cast<int>(puzzle::MAX_HEIGHT)));
|
||||
}
|
||||
|
||||
if (vm.contains("gx")) {
|
||||
goal_x = vm["gx"].as<int>();
|
||||
goal_x = std::max(0, std::min(goal_x, static_cast<int>(puzzle::MAX_WIDTH) - 1));
|
||||
}
|
||||
|
||||
if (vm.contains("gy")) {
|
||||
goal_y = vm["gy"].as<int>();
|
||||
goal_y = std::max(0, std::min(goal_y, static_cast<int>(puzzle::MAX_HEIGHT) - 1));
|
||||
}
|
||||
|
||||
if (vm.contains("free")) {
|
||||
restricted = false;
|
||||
}
|
||||
// if (vm.contains("w")) {
|
||||
// board_width = vm["w"].as<int>();
|
||||
// board_width = std::max(static_cast<int>(puzzle::MIN_WIDTH),
|
||||
// std::min(board_width, static_cast<int>(puzzle::MAX_WIDTH)));
|
||||
// }
|
||||
// if (vm.contains("h")) {
|
||||
// board_height = vm["h"].as<int>();
|
||||
// board_height = std::max(static_cast<int>(puzzle::MIN_HEIGHT),
|
||||
// std::min(board_height, static_cast<int>(puzzle::MAX_HEIGHT)));
|
||||
// }
|
||||
// if (vm.contains("gx")) {
|
||||
// goal_x = vm["gx"].as<int>();
|
||||
// goal_x = std::max(0, std::min(goal_x, static_cast<int>(puzzle::MAX_WIDTH) - 1));
|
||||
// }
|
||||
// if (vm.contains("gy")) {
|
||||
// goal_y = vm["gy"].as<int>();
|
||||
// goal_y = std::max(0, std::min(goal_y, static_cast<int>(puzzle::MAX_HEIGHT) - 1));
|
||||
// }
|
||||
// if (vm.contains("free")) {
|
||||
// restricted = false;
|
||||
// }
|
||||
|
||||
if (vm.contains("blocks")) {
|
||||
max_blocks = vm["blocks"].as<int>();
|
||||
max_blocks = std::max(1, std::min(max_blocks, static_cast<int>(puzzle::MAX_BLOCKS)));
|
||||
}
|
||||
|
||||
if (vm.contains("moves")) {
|
||||
min_moves = vm["moves"].as<int>();
|
||||
min_moves = std::max(0, min_moves);
|
||||
}
|
||||
if (vm.contains("space")) {
|
||||
const std::string ruleset = vm["space"].as<std::string>();
|
||||
if (ruleset == "rh") {
|
||||
return runmode::RUSH_HOUR_PUZZLE_SPACE;
|
||||
}
|
||||
if (ruleset == "klotski") {
|
||||
throw std::runtime_error("Not implemented");
|
||||
return runmode::KLOTSKI_PUZZLE_SPACE;
|
||||
}
|
||||
}
|
||||
|
||||
if (vm.contains("presets")) {
|
||||
preset_file = vm["presets"].as<std::string>();
|
||||
}
|
||||
@ -357,7 +393,11 @@ auto main(const int argc, char* argv[]) -> int
|
||||
case runmode::USER_INTERFACE:
|
||||
return ui_mode();
|
||||
case runmode::RUSH_HOUR_PUZZLE_SPACE:
|
||||
return rush_hour_puzzle_space();
|
||||
rush_hour_puzzle_space();
|
||||
return puzzle_space();
|
||||
case runmode::KLOTSKI_PUZZLE_SPACE:
|
||||
klotski_puzzle_space();
|
||||
return puzzle_space();
|
||||
case runmode::EXIT:
|
||||
return 0;
|
||||
};
|
||||
|
||||
@ -62,8 +62,8 @@ auto octree::build_octree_morton(octree& t,
|
||||
// Container for building the particle list before sorting by morton code
|
||||
struct sort_node
|
||||
{
|
||||
uint64_t code;
|
||||
uint32_t id;
|
||||
u64 code;
|
||||
u32 id;
|
||||
Vector3 pos;
|
||||
};
|
||||
|
||||
@ -71,7 +71,7 @@ auto octree::build_octree_morton(octree& t,
|
||||
std::vector<sort_node> sort_container;
|
||||
sort_container.resize(positions.size());
|
||||
|
||||
const auto calculate_morton = [&](const uint32_t i)
|
||||
const auto calculate_morton = [&](const u32 i)
|
||||
{
|
||||
sort_container[i] = {pos_to_morton(positions[i], root_min, root_max), i, positions[i]};
|
||||
};
|
||||
@ -79,7 +79,7 @@ auto octree::build_octree_morton(octree& t,
|
||||
if (thread_pool) {
|
||||
(*thread_pool)->submit_loop(0, positions.size(), calculate_morton, SMALL_TASK_BLOCK_SIZE).wait();
|
||||
} else {
|
||||
for (uint32_t i = 0; i < positions.size(); ++i) {
|
||||
for (u32 i = 0; i < positions.size(); ++i) {
|
||||
calculate_morton(i);
|
||||
}
|
||||
}
|
||||
@ -154,7 +154,7 @@ auto octree::build_octree_morton(octree& t,
|
||||
// For grouping, store a nodes local index in its level.
|
||||
struct leaf
|
||||
{
|
||||
uint64_t leaf_code;
|
||||
u64 leaf_code;
|
||||
int depth;
|
||||
int level_index;
|
||||
};
|
||||
@ -177,7 +177,7 @@ auto octree::build_octree_morton(octree& t,
|
||||
|
||||
size_t i = 0;
|
||||
while (i < leaves.size()) {
|
||||
const uint64_t key = path_to_ancestor(leaves[i].leaf_code, MAX_DEPTH, current_depth);
|
||||
const u64 key = path_to_ancestor(leaves[i].leaf_code, MAX_DEPTH, current_depth);
|
||||
|
||||
size_t j = i + 1;
|
||||
while (j < leaves.size() && path_to_ancestor(leaves[j].leaf_code, MAX_DEPTH, current_depth) == key) {
|
||||
|
||||
@ -31,24 +31,27 @@ auto orbit_camera::pan(const Vector2 last_mouse, const Vector2 mouse) -> void
|
||||
const Vector3 right = Vector3Normalize(Vector3CrossProduct(forward, camera.up));
|
||||
const Vector3 up = Vector3Normalize(Vector3CrossProduct(right, forward));
|
||||
|
||||
const Vector3 offset =
|
||||
Vector3Add(Vector3Scale(right, -dx * speed), Vector3Scale(up, dy * speed));
|
||||
const Vector3 offset = Vector3Add(Vector3Scale(right, -dx * speed), Vector3Scale(up, dy * speed));
|
||||
|
||||
target = Vector3Add(target, offset);
|
||||
}
|
||||
|
||||
auto orbit_camera::update(const Vector3& current_target, const Vector3& mass_center,
|
||||
const bool lock, const bool mass_center_lock) -> void
|
||||
auto orbit_camera::update(const Vector3& current_target,
|
||||
const Vector3& mass_center,
|
||||
const bool lock,
|
||||
const bool mass_center_lock) -> void
|
||||
{
|
||||
if (lock) {
|
||||
if (mass_center_lock) {
|
||||
target = Vector3MoveTowards(target, mass_center,
|
||||
CAMERA_SMOOTH_SPEED * GetFrameTime() *
|
||||
Vector3Length(Vector3Subtract(target, mass_center)));
|
||||
target = Vector3MoveTowards(target,
|
||||
mass_center,
|
||||
CAMERA_SMOOTH_SPEED * GetFrameTime() * Vector3Length(
|
||||
Vector3Subtract(target, mass_center)));
|
||||
} else {
|
||||
target = Vector3MoveTowards(target, current_target,
|
||||
CAMERA_SMOOTH_SPEED * GetFrameTime() *
|
||||
Vector3Length(Vector3Subtract(target, current_target)));
|
||||
target = Vector3MoveTowards(target,
|
||||
current_target,
|
||||
CAMERA_SMOOTH_SPEED * GetFrameTime() * Vector3Length(
|
||||
Vector3Subtract(target, current_target)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
632
src/puzzle.cpp
632
src/puzzle.cpp
@ -1,44 +1,43 @@
|
||||
#include "puzzle.hpp"
|
||||
|
||||
#include "graph_distances.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <set>
|
||||
#include <boost/program_options/detail/cmdline.hpp>
|
||||
#include <boost/unordered/unordered_flat_map.hpp>
|
||||
|
||||
auto puzzle::block::create_repr(const uint8_t x,
|
||||
const uint8_t y,
|
||||
const uint8_t w,
|
||||
const uint8_t h,
|
||||
const bool t,
|
||||
const bool i) -> uint16_t
|
||||
auto block::create_repr(const u8 x, const u8 y, const u8 w, const u8 h, const bool t, const bool i) -> u16
|
||||
{
|
||||
return block().set_x(x).set_y(y).set_width(w).set_height(h).set_target(t).set_immovable(i).repr & ~INVALID;
|
||||
}
|
||||
|
||||
auto puzzle::block::unpack_repr() const -> std::tuple<uint8_t, uint8_t, uint8_t, uint8_t, bool, bool>
|
||||
auto block::unpack_repr() const -> std::tuple<u8, u8, u8, u8, bool, bool>
|
||||
{
|
||||
const uint8_t x = get_x();
|
||||
const uint8_t y = get_y();
|
||||
const uint8_t w = get_width();
|
||||
const uint8_t h = get_height();
|
||||
const u8 x = get_x();
|
||||
const u8 y = get_y();
|
||||
const u8 w = get_width();
|
||||
const u8 h = get_height();
|
||||
const bool t = get_target();
|
||||
const bool i = get_immovable();
|
||||
|
||||
return {x, y, w, h, t, i};
|
||||
}
|
||||
|
||||
auto puzzle::block::hash() const -> size_t
|
||||
auto block::hash() const -> size_t
|
||||
{
|
||||
return std::hash<uint16_t>{}(repr);
|
||||
return std::hash<u16>{}(repr);
|
||||
}
|
||||
|
||||
auto puzzle::block::position_independent_hash() const -> size_t
|
||||
auto block::position_independent_hash() const -> size_t
|
||||
{
|
||||
uint16_t r = repr;
|
||||
u16 r = repr;
|
||||
clear_bits(r, X_S, X_E);
|
||||
clear_bits(r, Y_S, Y_E);
|
||||
return std::hash<uint16_t>{}(r);
|
||||
return std::hash<u16>{}(r);
|
||||
}
|
||||
|
||||
auto puzzle::block::valid() const -> bool
|
||||
auto block::valid() const -> bool
|
||||
{
|
||||
// This means the first bit is set, marking the block as empty
|
||||
if (repr & INVALID) {
|
||||
@ -54,7 +53,7 @@ auto puzzle::block::valid() const -> bool
|
||||
return w > 0 && h > 0 && x + w <= MAX_WIDTH && y + h <= MAX_HEIGHT;
|
||||
}
|
||||
|
||||
auto puzzle::block::principal_dirs() const -> uint8_t
|
||||
auto block::principal_dirs() const -> u8
|
||||
{
|
||||
const auto [x, y, w, h, t, i] = unpack_repr();
|
||||
|
||||
@ -71,14 +70,14 @@ auto puzzle::block::principal_dirs() const -> uint8_t
|
||||
return nor | eas | sou | wes;
|
||||
}
|
||||
|
||||
auto puzzle::block::covers(const int _x, const int _y) const -> bool
|
||||
auto block::covers(const int _x, const int _y) const -> bool
|
||||
{
|
||||
const auto [x, y, w, h, t, i] = unpack_repr();
|
||||
|
||||
return _x >= x && _x < x + w && _y >= y && _y < y + h;
|
||||
}
|
||||
|
||||
auto puzzle::block::collides(const block b) const -> bool
|
||||
auto block::collides(const block b) const -> bool
|
||||
{
|
||||
const auto [x, y, w, h, t, i] = unpack_repr();
|
||||
const auto [bx, by, bw, bh, bt, bi] = b.unpack_repr();
|
||||
@ -86,11 +85,11 @@ auto puzzle::block::collides(const block b) const -> bool
|
||||
return x < bx + bw && x + w > bx && y < by + bh && y + h > by;
|
||||
}
|
||||
|
||||
auto puzzle::create_meta(const std::tuple<uint8_t, uint8_t, uint8_t, uint8_t, bool, bool>& meta) -> uint16_t
|
||||
auto puzzle::create_meta(const std::tuple<u8, u8, u8, u8, bool, bool>& meta) -> u16
|
||||
{
|
||||
const auto [w, h, gx, gy, r, g] = meta;
|
||||
|
||||
uint16_t m = 0;
|
||||
u16 m = 0;
|
||||
set_bits(m, WIDTH_S, WIDTH_E, w - 1u);
|
||||
set_bits(m, HEIGHT_S, HEIGHT_E, h - 1u);
|
||||
set_bits(m, GOAL_X_S, GOAL_X_E, gx);
|
||||
@ -100,13 +99,13 @@ auto puzzle::create_meta(const std::tuple<uint8_t, uint8_t, uint8_t, uint8_t, bo
|
||||
return m;
|
||||
}
|
||||
|
||||
auto puzzle::create_repr(const uint8_t w,
|
||||
const uint8_t h,
|
||||
const uint8_t tx,
|
||||
const uint8_t ty,
|
||||
auto puzzle::create_repr(const u8 w,
|
||||
const u8 h,
|
||||
const u8 tx,
|
||||
const u8 ty,
|
||||
const bool r,
|
||||
const bool g,
|
||||
const std::array<uint16_t, MAX_BLOCKS>& b) -> repr_cooked
|
||||
const std::array<u16, MAX_BLOCKS>& b) -> repr_cooked
|
||||
{
|
||||
repr_cooked repr = puzzle().set_width(w).set_height(h).set_goal_x(tx).set_goal_y(ty).set_restricted(r).set_goal(g).
|
||||
set_blocks(b).repr.cooked;
|
||||
@ -114,13 +113,10 @@ auto puzzle::create_repr(const uint8_t w,
|
||||
return repr;
|
||||
}
|
||||
|
||||
auto puzzle::create_repr(const uint64_t byte_0,
|
||||
const uint64_t byte_1,
|
||||
const uint64_t byte_2,
|
||||
const uint64_t byte_3) -> repr_cooked
|
||||
auto puzzle::create_repr(const u64 byte_0, const u64 byte_1, const u64 byte_2, const u64 byte_3) -> repr_cooked
|
||||
{
|
||||
repr_u repr{};
|
||||
repr.raw = std::array<uint64_t, 4>{byte_0, byte_1, byte_2, byte_3};
|
||||
repr.raw = std::array<u64, 4>{byte_0, byte_1, byte_2, byte_3};
|
||||
|
||||
return repr.cooked;
|
||||
}
|
||||
@ -135,7 +131,7 @@ auto puzzle::create_repr(const std::string& string_repr) -> repr_cooked
|
||||
return *repr;
|
||||
}
|
||||
|
||||
auto puzzle::set_blocks(std::array<uint16_t, MAX_BLOCKS> blocks) const -> puzzle
|
||||
auto puzzle::set_blocks(std::array<u16, MAX_BLOCKS> blocks) const -> puzzle
|
||||
{
|
||||
puzzle p = *this;
|
||||
std::ranges::sort(blocks);
|
||||
@ -143,12 +139,12 @@ auto puzzle::set_blocks(std::array<uint16_t, MAX_BLOCKS> blocks) const -> puzzle
|
||||
return p;
|
||||
}
|
||||
|
||||
auto puzzle::unpack_meta() const -> std::tuple<uint8_t, uint8_t, uint8_t, uint8_t, bool, bool>
|
||||
auto puzzle::unpack_meta() const -> std::tuple<u8, u8, u8, u8, bool, bool>
|
||||
{
|
||||
const uint8_t w = get_width();
|
||||
const uint8_t h = get_height();
|
||||
const uint8_t tx = get_goal_x();
|
||||
const uint8_t ty = get_goal_y();
|
||||
const u8 w = get_width();
|
||||
const u8 h = get_height();
|
||||
const u8 tx = get_goal_x();
|
||||
const u8 ty = get_goal_y();
|
||||
const bool r = get_restricted();
|
||||
const bool g = get_goal();
|
||||
|
||||
@ -205,17 +201,17 @@ auto puzzle::string_repr() const -> std::string
|
||||
auto puzzle::try_parse_string_repr(const std::string& string_repr) -> std::optional<repr_cooked>
|
||||
{
|
||||
bool parsed_size = false;
|
||||
std::pair<uint8_t, uint8_t> size{0, 0};
|
||||
std::pair<u8, u8> size{0, 0};
|
||||
|
||||
bool parsed_goal = false;
|
||||
std::pair<uint8_t, uint8_t> goal{0, 0};
|
||||
std::pair<u8, u8> goal{0, 0};
|
||||
|
||||
bool parsed_restricted = false;
|
||||
bool restricted = true;
|
||||
|
||||
bool parsed_blocks = false;
|
||||
std::vector<uint16_t> bs;
|
||||
std::array<uint16_t, MAX_BLOCKS> blocks = invalid_blocks();
|
||||
std::vector<u16> bs;
|
||||
std::array<u16, MAX_BLOCKS> blocks = invalid_blocks();
|
||||
|
||||
const auto digit = [&](const char c)
|
||||
{
|
||||
@ -225,8 +221,8 @@ auto puzzle::try_parse_string_repr(const std::string& string_repr) -> std::optio
|
||||
// S:[3x3]
|
||||
const auto parse_size = [&](size_t& pos)
|
||||
{
|
||||
uint8_t w = std::stoi(string_repr.substr(pos + 3, 1));
|
||||
uint8_t h = std::stoi(string_repr.substr(pos + 5, 1));
|
||||
u8 w = std::stoi(string_repr.substr(pos + 3, 1));
|
||||
u8 h = std::stoi(string_repr.substr(pos + 5, 1));
|
||||
size = std::make_pair(w, h);
|
||||
|
||||
parsed_size = true;
|
||||
@ -236,8 +232,8 @@ auto puzzle::try_parse_string_repr(const std::string& string_repr) -> std::optio
|
||||
// G:[1,1] (optional)
|
||||
const auto parse_goal = [&](size_t& pos)
|
||||
{
|
||||
uint8_t gx = std::stoi(string_repr.substr(pos + 3, 1));
|
||||
uint8_t gy = std::stoi(string_repr.substr(pos + 5, 1));
|
||||
u8 gx = std::stoi(string_repr.substr(pos + 3, 1));
|
||||
u8 gy = std::stoi(string_repr.substr(pos + 5, 1));
|
||||
goal = std::make_pair(gx, gy);
|
||||
|
||||
parsed_goal = true;
|
||||
@ -260,14 +256,14 @@ auto puzzle::try_parse_string_repr(const std::string& string_repr) -> std::optio
|
||||
};
|
||||
|
||||
// 1x1 or 1X1 or 1*1 or _
|
||||
const auto parse_block = [&](size_t& pos, const uint8_t x, const uint8_t y)
|
||||
const auto parse_block = [&](size_t& pos, const u8 x, const u8 y)
|
||||
{
|
||||
if (string_repr[pos] == '_') {
|
||||
return block();
|
||||
}
|
||||
|
||||
const uint8_t w = std::stoi(string_repr.substr(pos, 1));
|
||||
const uint8_t h = std::stoi(string_repr.substr(pos + 2, 1));
|
||||
const u8 w = std::stoi(string_repr.substr(pos, 1));
|
||||
const u8 h = std::stoi(string_repr.substr(pos + 2, 1));
|
||||
const bool t = string_repr[pos + 1] == 'X';
|
||||
const bool i = string_repr[pos + 1] == '*';
|
||||
|
||||
@ -276,11 +272,11 @@ auto puzzle::try_parse_string_repr(const std::string& string_repr) -> std::optio
|
||||
};
|
||||
|
||||
// {1x1 _ _}
|
||||
const auto parse_row = [&](size_t& pos, const uint8_t y)
|
||||
const auto parse_row = [&](size_t& pos, const u8 y)
|
||||
{
|
||||
std::vector<uint16_t> row;
|
||||
std::vector<u16> row;
|
||||
|
||||
uint8_t x = 0;
|
||||
u8 x = 0;
|
||||
++pos; // Skip {
|
||||
while (string_repr[pos] != '}') {
|
||||
if (digit(string_repr[pos]) || string_repr[pos] == '_') {
|
||||
@ -299,13 +295,13 @@ auto puzzle::try_parse_string_repr(const std::string& string_repr) -> std::optio
|
||||
// B:[{1x1 _ _} {2X1 _ _} {_ _ _}]
|
||||
const auto parse_blocks = [&](size_t& pos)
|
||||
{
|
||||
std::vector<uint16_t> rows;
|
||||
std::vector<u16> rows;
|
||||
|
||||
uint8_t y = 0;
|
||||
u8 y = 0;
|
||||
++pos; // Skip [
|
||||
while (string_repr[pos] != ']') {
|
||||
if (string_repr[pos] == '{') {
|
||||
std::vector<uint16_t> row = parse_row(pos, y);
|
||||
std::vector<u16> row = parse_row(pos, y);
|
||||
rows.insert(rows.end(), row.begin(), row.end());
|
||||
++y;
|
||||
}
|
||||
@ -418,10 +414,10 @@ auto puzzle::try_get_invalid_reason() const -> std::optional<std::string>
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto puzzle::block_count() const -> uint8_t
|
||||
auto puzzle::block_count() const -> u8
|
||||
{
|
||||
uint8_t count = 0;
|
||||
for (const uint16_t b : repr.cooked.blocks) {
|
||||
u8 count = 0;
|
||||
for (const u16 b : repr.cooked.blocks) {
|
||||
if (block(b).valid()) {
|
||||
++count;
|
||||
}
|
||||
@ -435,7 +431,7 @@ auto puzzle::goal_reached() const -> bool
|
||||
return get_goal() && b && b->get_x() == get_goal_x() && b->get_y() == get_goal_y();
|
||||
}
|
||||
|
||||
auto puzzle::try_get_block(const uint8_t x, const uint8_t y) const -> std::optional<block>
|
||||
auto puzzle::try_get_block(const u8 x, const u8 y) const -> std::optional<block>
|
||||
{
|
||||
if (!covers(x, y)) {
|
||||
return std::nullopt;
|
||||
@ -461,12 +457,12 @@ auto puzzle::try_get_target_block() const -> std::optional<block>
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto puzzle::covers(const uint8_t x, const uint8_t y, const uint8_t _w, const uint8_t _h) const -> bool
|
||||
auto puzzle::covers(const u8 x, const u8 y, const u8 _w, const u8 _h) const -> bool
|
||||
{
|
||||
return x + _w <= get_width() && y + _h <= get_height();
|
||||
}
|
||||
|
||||
auto puzzle::covers(const uint8_t x, const uint8_t y) const -> bool
|
||||
auto puzzle::covers(const u8 x, const u8 y) const -> bool
|
||||
{
|
||||
return covers(x, y, 1, 1);
|
||||
}
|
||||
@ -481,7 +477,7 @@ auto puzzle::toggle_restricted() const -> puzzle
|
||||
return set_restricted(!get_restricted());
|
||||
}
|
||||
|
||||
auto puzzle::try_set_goal(const uint8_t x, const uint8_t y) const -> std::optional<puzzle>
|
||||
auto puzzle::try_set_goal(const u8 x, const u8 y) const -> std::optional<puzzle>
|
||||
{
|
||||
const std::optional<block>& b = try_get_target_block();
|
||||
if (!b || !covers(x, y, b->get_width(), b->get_height())) {
|
||||
@ -502,7 +498,7 @@ auto puzzle::clear_goal() const -> puzzle
|
||||
|
||||
auto puzzle::try_add_column() const -> std::optional<puzzle>
|
||||
{
|
||||
const uint8_t w = get_width();
|
||||
const u8 w = get_width();
|
||||
if (w >= MAX_WIDTH) {
|
||||
return std::nullopt;
|
||||
}
|
||||
@ -517,7 +513,7 @@ auto puzzle::try_remove_column() const -> std::optional<puzzle>
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
puzzle p{static_cast<uint8_t>(w - 1), h, 0, 0, r, g};
|
||||
puzzle p{static_cast<u8>(w - 1), h, 0, 0, r, g};
|
||||
|
||||
// Re-add all the blocks, blocks no longer fitting won't be added
|
||||
for (const block b : block_view()) {
|
||||
@ -541,7 +537,7 @@ auto puzzle::try_remove_column() const -> std::optional<puzzle>
|
||||
|
||||
auto puzzle::try_add_row() const -> std::optional<puzzle>
|
||||
{
|
||||
const uint8_t h = get_height();
|
||||
const u8 h = get_height();
|
||||
if (h >= MAX_HEIGHT) {
|
||||
return std::nullopt;
|
||||
}
|
||||
@ -556,7 +552,7 @@ auto puzzle::try_remove_row() const -> std::optional<puzzle>
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
puzzle p{w, static_cast<uint8_t>(h - 1), gx, gy, r, g};
|
||||
puzzle p{w, static_cast<u8>(h - 1), gx, gy, r, g};
|
||||
|
||||
// Re-add all the blocks, blocks no longer fitting won't be added
|
||||
for (const block b : block_view()) {
|
||||
@ -580,7 +576,7 @@ auto puzzle::try_remove_row() const -> std::optional<puzzle>
|
||||
|
||||
auto puzzle::try_add_block(const block b) const -> std::optional<puzzle>
|
||||
{
|
||||
const uint8_t count = block_count();
|
||||
const u8 count = block_count();
|
||||
if (count == MAX_BLOCKS) {
|
||||
return std::nullopt;
|
||||
}
|
||||
@ -596,7 +592,7 @@ auto puzzle::try_add_block(const block b) const -> std::optional<puzzle>
|
||||
}
|
||||
|
||||
const auto [w, h, gx, gy, r, g] = unpack_meta();
|
||||
std::array<uint16_t, MAX_BLOCKS> blocks = repr.cooked.blocks;
|
||||
std::array<u16, MAX_BLOCKS> blocks = repr.cooked.blocks;
|
||||
|
||||
// This requires all empty blocks being at the end of the array (otherwise we might overwrite).
|
||||
// This is the case because empty blocks' most significant bit is 1 and the array is sorted.
|
||||
@ -605,7 +601,7 @@ auto puzzle::try_add_block(const block b) const -> std::optional<puzzle>
|
||||
return puzzle(w, h, gx, gy, r, g, blocks);
|
||||
}
|
||||
|
||||
auto puzzle::try_remove_block(const uint8_t x, const uint8_t y) const -> std::optional<puzzle>
|
||||
auto puzzle::try_remove_block(const u8 x, const u8 y) const -> std::optional<puzzle>
|
||||
{
|
||||
const std::optional<block>& b = try_get_block(x, y);
|
||||
if (!b) {
|
||||
@ -613,8 +609,8 @@ auto puzzle::try_remove_block(const uint8_t x, const uint8_t y) const -> std::op
|
||||
}
|
||||
|
||||
const auto [w, h, gx, gy, r, g] = unpack_meta();
|
||||
std::array<uint16_t, MAX_BLOCKS> blocks = repr.cooked.blocks;
|
||||
for (uint16_t& _b : blocks) {
|
||||
std::array<u16, MAX_BLOCKS> blocks = repr.cooked.blocks;
|
||||
for (u16& _b : blocks) {
|
||||
if (_b == b->repr) {
|
||||
_b = block().repr;
|
||||
}
|
||||
@ -623,7 +619,7 @@ auto puzzle::try_remove_block(const uint8_t x, const uint8_t y) const -> std::op
|
||||
return puzzle(w, h, gx, gy, r, g, blocks);
|
||||
}
|
||||
|
||||
auto puzzle::try_toggle_target(const uint8_t x, const uint8_t y) const -> std::optional<puzzle>
|
||||
auto puzzle::try_toggle_target(const u8 x, const u8 y) const -> std::optional<puzzle>
|
||||
{
|
||||
const std::optional<block> b = try_get_block(x, y);
|
||||
if (!b || b->get_immovable()) {
|
||||
@ -631,9 +627,9 @@ auto puzzle::try_toggle_target(const uint8_t x, const uint8_t y) const -> std::o
|
||||
}
|
||||
|
||||
const auto [w, h, gx, gy, r, g] = unpack_meta();
|
||||
std::array<uint16_t, MAX_BLOCKS> blocks = repr.cooked.blocks;
|
||||
std::array<u16, MAX_BLOCKS> blocks = repr.cooked.blocks;
|
||||
|
||||
for (uint16_t& _b : blocks) {
|
||||
for (u16& _b : blocks) {
|
||||
if (!block(_b).valid()) {
|
||||
// Empty blocks are at the end
|
||||
break;
|
||||
@ -657,7 +653,7 @@ auto puzzle::try_toggle_target(const uint8_t x, const uint8_t y) const -> std::o
|
||||
return puzzle(w, h, 0, 0, r, g, blocks);
|
||||
}
|
||||
|
||||
auto puzzle::try_toggle_wall(const uint8_t x, const uint8_t y) const -> std::optional<puzzle>
|
||||
auto puzzle::try_toggle_wall(const u8 x, const u8 y) const -> std::optional<puzzle>
|
||||
{
|
||||
const std::optional<block> b = try_get_block(x, y);
|
||||
if (!b || b->get_target()) {
|
||||
@ -665,9 +661,9 @@ auto puzzle::try_toggle_wall(const uint8_t x, const uint8_t y) const -> std::opt
|
||||
}
|
||||
|
||||
const auto [w, h, gx, gy, r, g] = unpack_meta();
|
||||
std::array<uint16_t, MAX_BLOCKS> blocks = repr.cooked.blocks;
|
||||
std::array<u16, MAX_BLOCKS> blocks = repr.cooked.blocks;
|
||||
|
||||
for (uint16_t& _b : blocks) {
|
||||
for (u16& _b : blocks) {
|
||||
if (!block(_b).valid()) {
|
||||
// Empty blocks are at the end
|
||||
break;
|
||||
@ -682,17 +678,17 @@ auto puzzle::try_toggle_wall(const uint8_t x, const uint8_t y) const -> std::opt
|
||||
return puzzle(w, h, gx, gy, r, g, blocks);
|
||||
}
|
||||
|
||||
auto puzzle::blocks_bitmap() const -> uint64_t
|
||||
auto puzzle::blocks_bitmap() const -> u64
|
||||
{
|
||||
uint64_t bitmap = 0;
|
||||
for (uint8_t i = 0; i < MAX_BLOCKS; ++i) {
|
||||
u64 bitmap = 0;
|
||||
for (u8 i = 0; i < MAX_BLOCKS; ++i) {
|
||||
block b(repr.cooked.blocks[i]);
|
||||
if (!b.valid()) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto [x, y, w, h, t, im] = b.unpack_repr();
|
||||
const uint8_t width = get_width();
|
||||
const u8 width = get_width();
|
||||
|
||||
for (int dy = 0; dy < h; ++dy) {
|
||||
for (int dx = 0; dx < w; ++dx) {
|
||||
@ -703,10 +699,10 @@ auto puzzle::blocks_bitmap() const -> uint64_t
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
auto puzzle::blocks_bitmap_h() const -> uint64_t
|
||||
auto puzzle::blocks_bitmap_h() const -> u64
|
||||
{
|
||||
uint64_t bitmap = 0;
|
||||
for (uint8_t i = 0; i < MAX_BLOCKS; ++i) {
|
||||
u64 bitmap = 0;
|
||||
for (u8 i = 0; i < MAX_BLOCKS; ++i) {
|
||||
block b(repr.cooked.blocks[i]);
|
||||
if (!b.valid()) {
|
||||
break;
|
||||
@ -717,7 +713,7 @@ auto puzzle::blocks_bitmap_h() const -> uint64_t
|
||||
}
|
||||
|
||||
auto [x, y, w, h, t, im] = b.unpack_repr();
|
||||
const uint8_t width = get_width();
|
||||
const u8 width = get_width();
|
||||
|
||||
for (int dy = 0; dy < h; ++dy) {
|
||||
for (int dx = 0; dx < w; ++dx) {
|
||||
@ -728,10 +724,10 @@ auto puzzle::blocks_bitmap_h() const -> uint64_t
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
auto puzzle::blocks_bitmap_v() const -> uint64_t
|
||||
auto puzzle::blocks_bitmap_v() const -> u64
|
||||
{
|
||||
uint64_t bitmap = 0;
|
||||
for (uint8_t i = 0; i < MAX_BLOCKS; ++i) {
|
||||
u64 bitmap = 0;
|
||||
for (u8 i = 0; i < MAX_BLOCKS; ++i) {
|
||||
block b(repr.cooked.blocks[i]);
|
||||
if (!b.valid()) {
|
||||
break;
|
||||
@ -742,7 +738,7 @@ auto puzzle::blocks_bitmap_v() const -> uint64_t
|
||||
}
|
||||
|
||||
auto [x, y, w, h, t, im] = b.unpack_repr();
|
||||
const uint8_t width = get_width();
|
||||
const u8 width = get_width();
|
||||
|
||||
for (int dy = 0; dy < h; ++dy) {
|
||||
for (int dx = 0; dx < w; ++dx) {
|
||||
@ -753,35 +749,7 @@ auto puzzle::blocks_bitmap_v() const -> uint64_t
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
auto puzzle::bitmap_find_first_empty(const uint64_t bitmap, int& x, int& y) const -> bool
|
||||
{
|
||||
x = 0;
|
||||
y = 0;
|
||||
|
||||
// Bitmap is empty of first slot is empty
|
||||
if (bitmap_is_empty(bitmap) || !(bitmap & 1u)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Bitmap is full
|
||||
if (bitmap_is_full(bitmap)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find the next more significant empty bit (we know the first slot is full)
|
||||
int ls_set = 0;
|
||||
bool next_set = true;
|
||||
while (next_set && ls_set < get_width() * get_height() - 1) {
|
||||
next_set = bitmap & (1ul << (ls_set + 1));
|
||||
++ls_set;
|
||||
}
|
||||
|
||||
x = ls_set % get_width();
|
||||
y = ls_set / get_width();
|
||||
return true;
|
||||
}
|
||||
|
||||
auto puzzle::try_move_block_at(const uint8_t x, const uint8_t y, const dir dir) const -> std::optional<puzzle>
|
||||
auto puzzle::try_move_block_at(const u8 x, const u8 y, const dir dir) const -> std::optional<puzzle>
|
||||
{
|
||||
const std::optional<block> b = try_get_block(x, y);
|
||||
const auto [bx, by, bw, bh, bt, bi] = b->unpack_repr();
|
||||
@ -843,45 +811,15 @@ auto puzzle::try_move_block_at(const uint8_t x, const uint8_t y, const dir dir)
|
||||
return p;
|
||||
}
|
||||
|
||||
auto puzzle::sorted_replace(std::array<uint16_t, MAX_BLOCKS> blocks,
|
||||
const uint8_t idx,
|
||||
const uint16_t new_val) -> std::array<uint16_t, MAX_BLOCKS>
|
||||
{
|
||||
// Remove old entry
|
||||
for (uint8_t i = idx; i < MAX_BLOCKS - 1; ++i) {
|
||||
blocks[i] = blocks[i + 1];
|
||||
}
|
||||
blocks[MAX_BLOCKS - 1] = block::INVALID;
|
||||
|
||||
// Find insertion point for new_val
|
||||
uint8_t insert_at = 0;
|
||||
while (insert_at < MAX_BLOCKS && blocks[insert_at] < new_val) {
|
||||
++insert_at;
|
||||
}
|
||||
|
||||
// Shift right and insert
|
||||
for (uint8_t i = MAX_BLOCKS - 1; i > insert_at; --i) {
|
||||
blocks[i] = blocks[i - 1];
|
||||
}
|
||||
blocks[insert_at] = new_val;
|
||||
|
||||
return blocks;
|
||||
}
|
||||
|
||||
auto puzzle::explore_state_space() const -> std::pair<std::vector<puzzle>, std::vector<std::pair<size_t, size_t>>>
|
||||
auto puzzle::explore_state_space() const -> std::pair<std::vector<puzzle>, std::vector<spring>>
|
||||
{
|
||||
std::vector<puzzle> state_pool;
|
||||
boost::unordered_flat_map<puzzle, size_t, puzzle_hasher> state_indices;
|
||||
std::vector<std::pair<size_t, size_t>> links;
|
||||
puzzlemap<size_t> state_indices;
|
||||
std::vector<spring> links;
|
||||
|
||||
// Buffer for all states we want to call GetNextStates() on
|
||||
std::vector<size_t> queue; // indices into state_pool
|
||||
|
||||
#ifdef WIP
|
||||
// Store an index to the blocks array of a state for each occupied bitmap cell
|
||||
std::array<uint8_t, 64> bitmap_block_indices;
|
||||
#endif
|
||||
|
||||
// Start with the current state
|
||||
state_indices.emplace(*this, 0);
|
||||
state_pool.push_back(*this);
|
||||
@ -894,24 +832,6 @@ auto puzzle::explore_state_space() const -> std::pair<std::vector<puzzle>, std::
|
||||
// Make a copy because references might be invalidated when inserting into the vector
|
||||
const puzzle current = state_pool[current_idx];
|
||||
|
||||
#ifdef WIP
|
||||
// Build bitmap-block indices
|
||||
for (size_t i = 0; i < MAX_BLOCKS; ++i) {
|
||||
const block b = block(current.repr.cooked.blocks[i]);
|
||||
const auto [bx, by, bw, bh, bt, bi] = b.unpack_repr();
|
||||
if (!b.valid()) {
|
||||
break;
|
||||
}
|
||||
|
||||
for (uint8_t x = bx; x < bx + bw; ++x) {
|
||||
for (uint8_t y = by; y < by + bh; ++y) {
|
||||
bitmap_block_indices[y * current.get_width() + x] = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// TODO: I can just dispatch different functions depending on if the board is restricted or contains walls
|
||||
current.for_each_adjacent([&](const puzzle& p)
|
||||
{
|
||||
auto [it, inserted] = state_indices.emplace(p, state_pool.size());
|
||||
@ -926,160 +846,140 @@ auto puzzle::explore_state_space() const -> std::pair<std::vector<puzzle>, std::
|
||||
return {std::move(state_pool), std::move(links)};
|
||||
}
|
||||
|
||||
auto puzzle::get_cluster_id_and_solution() const -> std::pair<puzzle, bool>
|
||||
auto puzzle::get_cluster_id_and_solution() const -> std::tuple<puzzle, std::vector<puzzle>, bool, int>
|
||||
{
|
||||
const auto& [puzzles, moves] = explore_state_space();
|
||||
bool solution = false;
|
||||
|
||||
std::vector<size_t> solutions;
|
||||
puzzle min = puzzles[0];
|
||||
for (size_t i = 0; i < puzzles.size(); ++i) {
|
||||
if (puzzles[i] < min) {
|
||||
min = puzzles[i];
|
||||
}
|
||||
if (puzzles[i].goal_reached()) {
|
||||
solution = true;
|
||||
solutions.emplace_back(i);
|
||||
}
|
||||
}
|
||||
return {min, solution};
|
||||
|
||||
// TODO: We only need to search until a distance equal to min_moves is found.
|
||||
// Maybe DFS instead of BFS could even be faster on average?
|
||||
// TODO: Split moves from solvable calculation. We only need to count the moves if the cluster is solvable at all.
|
||||
graph_distances distances;
|
||||
distances.calculate_distances(puzzles.size(), moves, solutions);
|
||||
|
||||
int max_distance = 0;
|
||||
for (size_t i = 0; i < distances.distances.size(); ++i) {
|
||||
if (distances.distances[i] > max_distance) {
|
||||
max_distance = distances.distances[i];
|
||||
}
|
||||
}
|
||||
|
||||
return {min, puzzles, !solutions.empty(), max_distance};
|
||||
}
|
||||
|
||||
auto puzzle::generate_block_sequences(
|
||||
const boost::unordered_flat_set<block, block_hasher2, block_equal2>& permitted_blocks,
|
||||
const block target_block,
|
||||
const size_t max_blocks,
|
||||
std::vector<block>& current_sequence,
|
||||
const int current_area,
|
||||
const int board_area,
|
||||
const std::function<void(const std::vector<block>&)>& callback) -> void
|
||||
auto puzzle::generate_block_sets(const puzzle& p,
|
||||
const blockset2& permitted_blocks,
|
||||
const block target_block,
|
||||
const u8 max_blocks) -> std::vector<blockmap2<u8>>
|
||||
{
|
||||
if (!current_sequence.empty()) {
|
||||
callback(current_sequence);
|
||||
std::vector<blockmap2<u8>> result;
|
||||
|
||||
const u8 board_area = p.get_width() * p.get_height();
|
||||
blockmap2<u8> max_counts;
|
||||
|
||||
blockmap2<u8> block_areas;
|
||||
for (const block& b : permitted_blocks) {
|
||||
const u8 block_area = b.get_width() * b.get_height();
|
||||
block_areas[b] = block_area;
|
||||
}
|
||||
|
||||
if (current_sequence.size() == max_blocks) {
|
||||
return;
|
||||
const u8 target_area = target_block.get_width() * target_block.get_height();
|
||||
for (const block& b : permitted_blocks) {
|
||||
const u8 block_area = block_areas[b];
|
||||
max_counts[b] = std::min(max_blocks, static_cast<u8>((board_area - target_area) / block_area));
|
||||
}
|
||||
|
||||
for (const block b : permitted_blocks) {
|
||||
const int new_area = current_area + b.get_width() * b.get_height();
|
||||
if (new_area > board_area) {
|
||||
continue;
|
||||
blockmap2<u8> current_set;
|
||||
while (true) {
|
||||
// Check if we have generated a valid set
|
||||
int used_blocks = 0;
|
||||
int used_area = 0;
|
||||
for (const block& b : permitted_blocks) {
|
||||
used_blocks += current_set[b];
|
||||
used_area += current_set[b] * block_areas[b];
|
||||
}
|
||||
if (used_blocks > 0 && used_blocks <= max_blocks && used_area <= board_area - target_area) {
|
||||
result.push_back(current_set);
|
||||
}
|
||||
|
||||
// Explore all sequences with the block placed, then continue the loop
|
||||
current_sequence.push_back(b);
|
||||
generate_block_sequences(permitted_blocks,
|
||||
target_block,
|
||||
max_blocks,
|
||||
current_sequence,
|
||||
new_area,
|
||||
board_area,
|
||||
callback);
|
||||
current_sequence.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
auto puzzle::place_block_sequence(const puzzle& p,
|
||||
const uint64_t& bitmap,
|
||||
const std::tuple<uint8_t, uint8_t, uint8_t, uint8_t, bool, bool>& p_repr,
|
||||
const std::vector<block>& sequence,
|
||||
const block target_block,
|
||||
const std::tuple<uint8_t, uint8_t, uint8_t, uint8_t>& target_block_pos_range,
|
||||
const bool has_target,
|
||||
const size_t index,
|
||||
const std::function<void(const puzzle&)>& callback) -> void
|
||||
{
|
||||
if (index == sequence.size()) {
|
||||
// All blocks placed
|
||||
callback(p);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!has_target && p.get_restricted()) {
|
||||
// Place target block (restricted movement)
|
||||
const auto [txs, tys, txe, tye] = target_block_pos_range;
|
||||
for (int tx = txs; tx <= txe; ++tx) {
|
||||
for (int ty = tys; ty <= tye; ++ty) {
|
||||
block t = target_block;
|
||||
t = t.set_x(tx);
|
||||
t = t.set_y(ty);
|
||||
|
||||
if (!p.covers(t)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::array<uint16_t, MAX_BLOCKS> blocks = sorted_replace(p.repr.cooked.blocks, 0, t.repr);
|
||||
const puzzle next_p = puzzle(p_repr, blocks);
|
||||
|
||||
uint64_t next_bm = bitmap;
|
||||
next_p.bitmap_set_block(next_bm, t);
|
||||
|
||||
// Place the remaining blocks for each possible target block configuration
|
||||
// traceln("Generating block sequence for target at {},{}", tx, ty);
|
||||
next_p.place_block_sequence(next_p,
|
||||
next_bm,
|
||||
p_repr,
|
||||
sequence,
|
||||
target_block,
|
||||
target_block_pos_range,
|
||||
true,
|
||||
index,
|
||||
callback);
|
||||
// Increase counter
|
||||
size_t pos = 0;
|
||||
for (const block& b : permitted_blocks) {
|
||||
++current_set[b];
|
||||
if (current_set[b] <= max_counts[b]) {
|
||||
break;
|
||||
}
|
||||
|
||||
// The counter overflowed
|
||||
current_set[b] = 0;
|
||||
++pos;
|
||||
}
|
||||
|
||||
return;
|
||||
// All counters overflowed, finished
|
||||
if (pos == permitted_blocks.size()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!has_target && !p.get_restricted()) {
|
||||
// Place target block (free movement)
|
||||
|
||||
// TODO
|
||||
}
|
||||
|
||||
int x, y;
|
||||
if (!p.bitmap_find_first_empty(bitmap, x, y)) {
|
||||
// No space remaining
|
||||
callback(p);
|
||||
return;
|
||||
}
|
||||
|
||||
block b = sequence[index];
|
||||
b = b.set_x(static_cast<uint8_t>(x));
|
||||
b = b.set_y(static_cast<uint8_t>(y));
|
||||
|
||||
// Place the next block and call the resulting subtree, then remove the block and continue here
|
||||
if (!p.bitmap_check_collision(bitmap, b) && p.covers(b)) {
|
||||
// Shift the sequence by 1 (index + 1), because the target block is inserted separately
|
||||
const std::array<uint16_t, MAX_BLOCKS> blocks = sorted_replace(p.repr.cooked.blocks, index + 1, b.repr);
|
||||
const puzzle next_p = puzzle(p_repr, blocks);
|
||||
|
||||
uint64_t next_bm = bitmap;
|
||||
next_p.bitmap_set_block(next_bm, b);
|
||||
|
||||
next_p.place_block_sequence(next_p,
|
||||
next_bm,
|
||||
p_repr,
|
||||
sequence,
|
||||
target_block,
|
||||
target_block_pos_range,
|
||||
true,
|
||||
index + 1,
|
||||
callback);
|
||||
}
|
||||
|
||||
// Create an empty cell and call the resulting subtree (without advancing the block index)
|
||||
uint64_t next_bm = bitmap;
|
||||
bitmap_set_bit(next_bm, p.get_width(), b.get_x(), b.get_y());
|
||||
p.place_block_sequence(p, next_bm, p_repr, sequence, target_block, target_block_pos_range, true, index, callback);
|
||||
return result;
|
||||
}
|
||||
|
||||
auto puzzle::explore_puzzle_space(const boost::unordered_flat_set<block, block_hasher2, block_equal2>& permitted_blocks,
|
||||
auto puzzle::generate_initial_puzzles(const puzzle& p,
|
||||
const block target_block,
|
||||
const std::tuple<u8, u8, u8, u8>& target_block_pos_range) -> std::vector<puzzle>
|
||||
{
|
||||
std::vector<puzzle> result;
|
||||
|
||||
const auto [txs, tys, txe, tye] = target_block_pos_range;
|
||||
|
||||
for (int tx = txs; tx <= txe; ++tx) {
|
||||
for (int ty = tys; ty <= tye; ++ty) {
|
||||
block t = target_block;
|
||||
t = t.set_x(tx);
|
||||
t = t.set_y(ty);
|
||||
|
||||
// This cannot happen if the target_block_pos_range is set correctly
|
||||
#ifdef RUNTIME_CHECKS
|
||||
if (!p.covers(t)) {
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
|
||||
std::array<u16, MAX_BLOCKS> blocks = invalid_blocks();
|
||||
blocks[0] = t.repr;
|
||||
|
||||
// Don't exclude already won configurations, as min_moves is based on the
|
||||
// max distance from the entire cluster, not only the initial state
|
||||
|
||||
// puzzle _p = puzzle(p.repr.cooked.meta, blocks);
|
||||
// if (_p.goal_reached()) {
|
||||
// continue;
|
||||
// }
|
||||
// result.emplace_back(_p);
|
||||
|
||||
result.emplace_back(p.repr.cooked.meta, blocks);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
auto puzzle::explore_puzzle_space(const blockset2& permitted_blocks,
|
||||
const block target_block,
|
||||
const std::tuple<uint8_t, uint8_t, uint8_t, uint8_t>& target_block_pos_range,
|
||||
const std::tuple<u8, u8, u8, u8>& target_block_pos_range,
|
||||
const size_t max_blocks,
|
||||
const std::optional<BS::thread_pool<>* const> thread_pool) const ->
|
||||
boost::unordered_flat_set<puzzle, puzzle_hasher>
|
||||
const int min_moves,
|
||||
const threadpool thread_pool) const -> puzzleset
|
||||
{
|
||||
const auto [w, h, gx, gy, r, g] = unpack_meta();
|
||||
|
||||
@ -1091,43 +991,117 @@ auto puzzle::explore_puzzle_space(const boost::unordered_flat_set<block, block_h
|
||||
// this could save some state space calculations.
|
||||
// 3. Add it to visited_clusters if unseen
|
||||
|
||||
// TODO: Use thread local resources instead of this fuckery (or concurrent map?)
|
||||
std::mutex mtx;
|
||||
|
||||
boost::unordered_flat_set<puzzle, puzzle_hasher> visited_clusters;
|
||||
|
||||
// TODO: Can't even parallelize this. Or just start at different initial puzzles?
|
||||
std::mutex cache_mtx;
|
||||
std::mutex print_mtx;
|
||||
puzzleset visited_clusters;
|
||||
const puzzle empty_puzzle = puzzle(w, h, gx, gy, r, g);
|
||||
const auto board_repr = std::make_tuple(w, h, gx, gy, r, g);
|
||||
std::vector<block> current_sequence;
|
||||
int total = 0;
|
||||
generate_block_sequences(permitted_blocks,
|
||||
target_block,
|
||||
max_blocks - 1, // Make space for the target block
|
||||
current_sequence,
|
||||
target_block.get_width() * target_block.get_height(), // Starting area
|
||||
get_width() * get_height(),
|
||||
[&](const std::vector<block>& sequence)
|
||||
{
|
||||
place_block_sequence(empty_puzzle,
|
||||
0,
|
||||
board_repr,
|
||||
sequence,
|
||||
target_block,
|
||||
target_block_pos_range,
|
||||
false,
|
||||
0,
|
||||
[&](const puzzle& p)
|
||||
{
|
||||
const auto [cluster_id, winnable] = p.get_cluster_id_and_solution();
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
++total;
|
||||
if (winnable) {
|
||||
visited_clusters.emplace(cluster_id);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// infoln("Found {} of {} clusters with a solution", visited_clusters.size(), total);
|
||||
// TODO: Don't use a hashmap for this, use denser vector representation for better recursion performance
|
||||
// Call with max_blocks - 1 because the target is already placed
|
||||
const std::vector<blockmap2<u8>> sets = generate_block_sets(empty_puzzle,
|
||||
permitted_blocks,
|
||||
target_block,
|
||||
max_blocks - 1);
|
||||
traceln("Generated {} block inventories", sets.size());
|
||||
|
||||
const std::vector<puzzle> puzzles = generate_initial_puzzles(empty_puzzle, target_block, target_block_pos_range);
|
||||
traceln("Generated {} starting configurations", puzzles.size());
|
||||
|
||||
int total = 0;
|
||||
int duplicates = 0;
|
||||
|
||||
// TODO: This cache can easily fill system ram
|
||||
// - Only store hashes, not puzzles?
|
||||
// - Use an LRU cache to have a memory consumption bound?
|
||||
puzzleset seen_statespaces;
|
||||
|
||||
const auto place_blocks = [&](const size_t i)
|
||||
{
|
||||
const size_t current_inventory = i / puzzles.size();
|
||||
const size_t current_puzzle = i % puzzles.size();
|
||||
|
||||
const blockmap2<u8> set = sets[current_inventory];
|
||||
const puzzle& p = puzzles[current_puzzle];
|
||||
const u64 bitmap = p.blocks_bitmap();
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(print_mtx);
|
||||
traceln("Placing inventory {} for starting configuration {}", current_inventory, current_puzzle);
|
||||
}
|
||||
place_block_set(p,
|
||||
set,
|
||||
bitmap,
|
||||
1,
|
||||
[&](const puzzle& _p)
|
||||
{
|
||||
// Sort the blocks, so identical puzzles get the same hash
|
||||
const puzzle& sorted = _p.set_blocks(_p.repr.cooked.blocks);
|
||||
|
||||
// TODO: Pre-filter using a coarse key:
|
||||
// - If two puzzles are in the same cluster, this key must match
|
||||
// - If the key matches, puzzles might still be in different clusters
|
||||
// If a new puzzle arrives, do a bidirectional BFS from the new
|
||||
// puzzle to all puzzles with the same key, if both directions meet,
|
||||
// we already had this cluster.
|
||||
// If nothing meets, we have to do the full statespace generation.
|
||||
//
|
||||
// Key could contain:
|
||||
// - counts of block types (inventory)
|
||||
// - which row/column pieces live in
|
||||
// - ordering of pieces in a row or column
|
||||
|
||||
bool inserted;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(cache_mtx);
|
||||
const auto& [it, _inserted] = seen_statespaces.insert(sorted);
|
||||
inserted = _inserted;
|
||||
|
||||
++total;
|
||||
}
|
||||
|
||||
// Only scan statespace if we haven't seen the state
|
||||
if (!inserted) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: This is the main issue. Need a lot of prefiltering/as much caching as I have memory
|
||||
const auto [cluster_id, statespace, winnable, max_moves] = sorted.
|
||||
get_cluster_id_and_solution();
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(cache_mtx);
|
||||
for (const puzzle& seen : statespace) {
|
||||
seen_statespaces.insert(seen);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
if (visited_clusters.contains(cluster_id)) {
|
||||
++duplicates;
|
||||
} else if (winnable && max_moves >= min_moves) {
|
||||
// print_bitmap(cluster_id.blocks_bitmap(), _p.get_width(), _p.get_height(), "Found Cluster");
|
||||
visited_clusters.emplace(cluster_id);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Iterate through all starting configurations + inventories
|
||||
if (thread_pool) {
|
||||
(*thread_pool)->submit_loop(0, puzzles.size() * sets.size(), place_blocks, LARGE_TASK_BLOCK_SIZE).wait();
|
||||
} else {
|
||||
for (size_t i = 0; i < puzzles.size() * sets.size(); ++i) {
|
||||
place_blocks(i);
|
||||
}
|
||||
}
|
||||
|
||||
infoln("Found {} solvable clusters with at least {} required moves", visited_clusters.size(), min_moves);
|
||||
traceln("- Scanned {} puzzles", total);
|
||||
traceln("- {} belong to duplicate clusters", duplicates);
|
||||
traceln("- {} don't match the requirements", total - duplicates - visited_clusters.size());
|
||||
|
||||
return visited_clusters;
|
||||
}
|
||||
@ -128,10 +128,10 @@ auto renderer::draw_mass_springs(const std::vector<Vector3>& masses) -> void
|
||||
const float weight = 1.0 - static_cast<float>(distance) / max_distance;
|
||||
|
||||
Color result;
|
||||
result.r = static_cast<uint8_t>((1 - weight) * from.r + weight * to.r);
|
||||
result.g = static_cast<uint8_t>((1 - weight) * from.g + weight * to.g);
|
||||
result.b = static_cast<uint8_t>((1 - weight) * from.b + weight * to.b);
|
||||
result.a = static_cast<uint8_t>((1 - weight) * from.a + weight * to.a);
|
||||
result.r = static_cast<u8>((1 - weight) * from.r + weight * to.r);
|
||||
result.g = static_cast<u8>((1 - weight) * from.g + weight * to.g);
|
||||
result.b = static_cast<u8>((1 - weight) * from.b + weight * to.b);
|
||||
result.a = static_cast<u8>((1 - weight) * from.a + weight * to.a);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
@ -28,7 +28,7 @@ auto state_manager::synced_insert_link(size_t first_index, size_t second_index)
|
||||
}
|
||||
|
||||
auto state_manager::synced_insert_statespace(const std::vector<puzzle>& states,
|
||||
const std::vector<std::pair<size_t, size_t>>& _links) -> void
|
||||
const std::vector<spring>& _links) -> void
|
||||
{
|
||||
if (!state_pool.empty() || !state_indices.empty() || !links.empty()) {
|
||||
warnln("Inserting statespace but collections haven't been cleared");
|
||||
@ -374,7 +374,7 @@ auto state_manager::get_path_length() const -> size_t
|
||||
return winning_path.size();
|
||||
}
|
||||
|
||||
auto state_manager::get_links() const -> const std::vector<std::pair<size_t, size_t>>&
|
||||
auto state_manager::get_links() const -> const std::vector<spring>&
|
||||
{
|
||||
return links;
|
||||
}
|
||||
|
||||
@ -7,8 +7,12 @@
|
||||
#define RAYGUI_IMPLEMENTATION
|
||||
#include <raygui.h>
|
||||
|
||||
auto user_interface::grid::update_bounds(const int _x, const int _y, const int _width, const int _height,
|
||||
const int _columns, const int _rows) -> void
|
||||
auto user_interface::grid::update_bounds(const int _x,
|
||||
const int _y,
|
||||
const int _width,
|
||||
const int _height,
|
||||
const int _columns,
|
||||
const int _rows) -> void
|
||||
{
|
||||
x = _x;
|
||||
y = _y;
|
||||
@ -51,7 +55,9 @@ auto user_interface::grid::bounds(const int _x, const int _y, const int _width,
|
||||
const int cell_width = (width - padding) / columns;
|
||||
const int cell_height = (height - padding) / rows;
|
||||
|
||||
return Rectangle(x + _x * cell_width + padding, y + _y * cell_height + padding, _width * cell_width - padding,
|
||||
return Rectangle(x + _x * cell_width + padding,
|
||||
y + _y * cell_height + padding,
|
||||
_width * cell_width - padding,
|
||||
_height * cell_height - padding);
|
||||
}
|
||||
|
||||
@ -65,7 +71,9 @@ auto user_interface::grid::square_bounds() const -> Rectangle
|
||||
return bounds;
|
||||
}
|
||||
|
||||
auto user_interface::grid::square_bounds(const int _x, const int _y, const int _width,
|
||||
auto user_interface::grid::square_bounds(const int _x,
|
||||
const int _y,
|
||||
const int _width,
|
||||
const int _height) const -> Rectangle
|
||||
{
|
||||
// Assumes each cell is square, so either width or height are not completely
|
||||
@ -84,8 +92,10 @@ auto user_interface::grid::square_bounds(const int _x, const int _y, const int _
|
||||
const int x_offset = (width - grid_width) / 2;
|
||||
const int y_offset = (height - grid_height) / 2;
|
||||
|
||||
return Rectangle(x_offset + _x * (cell_size + padding) + padding, y_offset + _y * (cell_size + padding) + padding,
|
||||
_width * cell_size + padding * (_width - 1), _height * cell_size + padding * (_height - 1));
|
||||
return Rectangle(x_offset + _x * (cell_size + padding) + padding,
|
||||
y_offset + _y * (cell_size + padding) + padding,
|
||||
_width * cell_size + padding * (_width - 1),
|
||||
_height * cell_size + padding * (_height - 1));
|
||||
}
|
||||
|
||||
auto user_interface::init() -> void
|
||||
@ -146,16 +156,26 @@ auto user_interface::get_default_style() -> default_style
|
||||
// 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, 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)
|
||||
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)
|
||||
};
|
||||
}
|
||||
|
||||
@ -191,14 +211,21 @@ auto user_interface::get_component_style(const int component) -> component_style
|
||||
{
|
||||
return {
|
||||
{
|
||||
GuiGetStyle(component, BORDER_COLOR_NORMAL), GuiGetStyle(component, BASE_COLOR_NORMAL),
|
||||
GuiGetStyle(component, TEXT_COLOR_NORMAL), GuiGetStyle(component, BORDER_COLOR_FOCUSED),
|
||||
GuiGetStyle(component, BASE_COLOR_FOCUSED), GuiGetStyle(component, TEXT_COLOR_FOCUSED),
|
||||
GuiGetStyle(component, BORDER_COLOR_PRESSED), GuiGetStyle(component, BASE_COLOR_PRESSED),
|
||||
GuiGetStyle(component, TEXT_COLOR_PRESSED), GuiGetStyle(component, BORDER_COLOR_DISABLED),
|
||||
GuiGetStyle(component, BASE_COLOR_DISABLED), GuiGetStyle(component, TEXT_COLOR_DISABLED)
|
||||
GuiGetStyle(component, BORDER_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, BORDER_WIDTH),
|
||||
GuiGetStyle(component, TEXT_PADDING),
|
||||
GuiGetStyle(component, TEXT_ALIGNMENT)
|
||||
};
|
||||
}
|
||||
@ -229,11 +256,16 @@ auto user_interface::set_component_style(const int component, const component_st
|
||||
auto user_interface::popup_bounds() -> Rectangle
|
||||
{
|
||||
return Rectangle(static_cast<float>(GetScreenWidth()) / 2.0f - POPUP_WIDTH / 2.0f,
|
||||
static_cast<float>(GetScreenHeight()) / 2.0f - POPUP_HEIGHT / 2.0f, POPUP_WIDTH, POPUP_HEIGHT);
|
||||
static_cast<float>(GetScreenHeight()) / 2.0f - POPUP_HEIGHT / 2.0f,
|
||||
POPUP_WIDTH,
|
||||
POPUP_HEIGHT);
|
||||
}
|
||||
|
||||
auto user_interface::draw_button(const Rectangle bounds, const std::string& label, const Color color,
|
||||
const bool enabled, const int font_size) const -> int
|
||||
auto user_interface::draw_button(const Rectangle bounds,
|
||||
const std::string& label,
|
||||
const Color color,
|
||||
const bool enabled,
|
||||
const int font_size) const -> int
|
||||
{
|
||||
// Save original styling
|
||||
const default_style original_default = get_default_style();
|
||||
@ -263,16 +295,25 @@ auto user_interface::draw_button(const Rectangle bounds, const std::string& labe
|
||||
return pressed;
|
||||
}
|
||||
|
||||
auto user_interface::draw_menu_button(const int x, const int y, const int width, const int height,
|
||||
const std::string& label, const Color color, const bool enabled,
|
||||
auto user_interface::draw_menu_button(const int x,
|
||||
const int y,
|
||||
const int width,
|
||||
const int height,
|
||||
const std::string& label,
|
||||
const Color color,
|
||||
const bool enabled,
|
||||
const int font_size) const -> int
|
||||
{
|
||||
const Rectangle bounds = menu_grid.bounds(x, y, width, height);
|
||||
return draw_button(bounds, label, color, enabled, font_size);
|
||||
}
|
||||
|
||||
auto user_interface::draw_toggle_slider(const Rectangle bounds, const std::string& off_label,
|
||||
const std::string& on_label, int* active, Color color, bool enabled,
|
||||
auto user_interface::draw_toggle_slider(const Rectangle bounds,
|
||||
const std::string& off_label,
|
||||
const std::string& on_label,
|
||||
int* active,
|
||||
Color color,
|
||||
bool enabled,
|
||||
int font_size) const -> int
|
||||
{
|
||||
// Save original styling
|
||||
@ -308,16 +349,29 @@ auto user_interface::draw_toggle_slider(const Rectangle bounds, const std::strin
|
||||
return pressed;
|
||||
}
|
||||
|
||||
auto user_interface::draw_menu_toggle_slider(const int x, const int y, const int width, const int height,
|
||||
const std::string& off_label, const std::string& on_label, int* active,
|
||||
const Color color, const bool enabled, const int font_size) const -> int
|
||||
auto user_interface::draw_menu_toggle_slider(const int x,
|
||||
const int y,
|
||||
const int width,
|
||||
const int height,
|
||||
const std::string& off_label,
|
||||
const std::string& on_label,
|
||||
int* active,
|
||||
const Color color,
|
||||
const bool enabled,
|
||||
const int font_size) const -> int
|
||||
{
|
||||
const Rectangle bounds = menu_grid.bounds(x, y, width, height);
|
||||
return draw_toggle_slider(bounds, off_label, on_label, active, color, enabled, font_size);
|
||||
}
|
||||
|
||||
auto user_interface::draw_spinner(Rectangle bounds, const std::string& label, int* value, int min, int max, Color color,
|
||||
bool enabled, int font_size) const -> int
|
||||
auto user_interface::draw_spinner(Rectangle bounds,
|
||||
const std::string& label,
|
||||
int* value,
|
||||
int min,
|
||||
int max,
|
||||
Color color,
|
||||
bool enabled,
|
||||
int font_size) const -> int
|
||||
{
|
||||
// Save original styling
|
||||
const default_style original_default = get_default_style();
|
||||
@ -352,15 +406,26 @@ auto user_interface::draw_spinner(Rectangle bounds, const std::string& label, in
|
||||
return pressed;
|
||||
}
|
||||
|
||||
auto user_interface::draw_menu_spinner(const int x, const int y, const int width, const int height,
|
||||
const std::string& label, int* value, const int min, const int max,
|
||||
const Color color, const bool enabled, const int font_size) const -> int
|
||||
auto user_interface::draw_menu_spinner(const int x,
|
||||
const int y,
|
||||
const int width,
|
||||
const int height,
|
||||
const std::string& label,
|
||||
int* value,
|
||||
const int min,
|
||||
const int max,
|
||||
const Color color,
|
||||
const bool enabled,
|
||||
const int font_size) const -> int
|
||||
{
|
||||
const Rectangle bounds = menu_grid.bounds(x, y, width, height);
|
||||
return draw_spinner(bounds, label, value, min, max, color, enabled, font_size);
|
||||
}
|
||||
|
||||
auto user_interface::draw_label(const Rectangle bounds, const std::string& text, const Color color, const bool enabled,
|
||||
auto user_interface::draw_label(const Rectangle bounds,
|
||||
const std::string& text,
|
||||
const Color color,
|
||||
const bool enabled,
|
||||
const int font_size) const -> int
|
||||
{
|
||||
// Save original styling
|
||||
@ -391,7 +456,11 @@ auto user_interface::draw_label(const Rectangle bounds, const std::string& text,
|
||||
return pressed;
|
||||
}
|
||||
|
||||
auto user_interface::draw_board_block(const int x, const int y, const int width, const int height, const Color color,
|
||||
auto user_interface::draw_board_block(const int x,
|
||||
const int y,
|
||||
const int width,
|
||||
const int height,
|
||||
const Color color,
|
||||
const bool enabled) const -> bool
|
||||
{
|
||||
component_style s = get_component_style(BUTTON);
|
||||
@ -400,7 +469,7 @@ auto user_interface::draw_board_block(const int x, const int y, const int width,
|
||||
const Rectangle bounds = board_grid.square_bounds(x, y, width, height);
|
||||
|
||||
const bool focused = CheckCollisionPointRec(input.mouse - Vector2(0, MENU_HEIGHT), bounds);
|
||||
const bool pressed = puzzle::block(x, y, width, height, false).covers(input.sel_x, input.sel_y);
|
||||
const bool pressed = block(x, y, width, height, false).covers(input.sel_x, input.sel_y);
|
||||
|
||||
// Background to make faded colors work
|
||||
DrawRectangleRec(bounds, RAYWHITE);
|
||||
@ -438,7 +507,15 @@ auto user_interface::window_open() const -> bool
|
||||
auto user_interface::draw_menu_header(const Color color) const -> void
|
||||
{
|
||||
int preset = static_cast<int>(state.get_current_preset());
|
||||
draw_menu_spinner(0, 0, 1, 1, "Preset: ", &preset, -1, static_cast<int>(state.get_preset_count()), color,
|
||||
draw_menu_spinner(0,
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
"Preset: ",
|
||||
&preset,
|
||||
-1,
|
||||
static_cast<int>(state.get_preset_count()),
|
||||
color,
|
||||
!input.editing);
|
||||
if (preset > static_cast<int>(state.get_current_preset())) {
|
||||
input.load_next_preset();
|
||||
@ -446,11 +523,17 @@ auto user_interface::draw_menu_header(const Color color) const -> void
|
||||
input.load_previous_preset();
|
||||
}
|
||||
|
||||
draw_menu_button(1, 0, 1, 1, std::format("{}: {}/{} Blocks",
|
||||
state.was_edited()
|
||||
? "Modified"
|
||||
: std::format("\"{}\"", state.get_current_preset_comment().substr(2)),
|
||||
state.get_current_state().block_count(), puzzle::MAX_BLOCKS), color);
|
||||
draw_menu_button(1,
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
std::format("{}: {}/{} Blocks",
|
||||
state.was_edited()
|
||||
? "Modified"
|
||||
: std::format("\"{}\"", state.get_current_preset_comment().substr(2)),
|
||||
state.get_current_state().block_count(),
|
||||
puzzle::MAX_BLOCKS),
|
||||
color);
|
||||
|
||||
int editing = input.editing;
|
||||
draw_menu_toggle_slider(2, 0, 1, 1, "Puzzle Mode (Tab)", "Edit Mode (Tab)", &editing, color);
|
||||
@ -461,13 +544,22 @@ auto user_interface::draw_menu_header(const Color color) const -> void
|
||||
|
||||
auto user_interface::draw_graph_info(const Color color) const -> void
|
||||
{
|
||||
draw_menu_button(0, 1, 1, 1, std::format("Found {} States ({} Winning)", state.get_state_count(),
|
||||
state.get_target_count()), color);
|
||||
draw_menu_button(0,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
std::format("Found {} States ({} Winning)", state.get_state_count(), state.get_target_count()),
|
||||
color);
|
||||
|
||||
draw_menu_button(1, 1, 1, 1, std::format("Found {} Transitions", state.get_link_count()), color);
|
||||
|
||||
draw_menu_button(2, 1, 1, 1, std::format("{} Moves to Nearest Solution",
|
||||
state.get_path_length() > 0 ? state.get_path_length() - 1 : 0), color);
|
||||
draw_menu_button(2,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
std::format("{} Moves to Nearest Solution",
|
||||
state.get_path_length() > 0 ? state.get_path_length() - 1 : 0),
|
||||
color);
|
||||
}
|
||||
|
||||
auto user_interface::draw_graph_controls(const Color color) const -> void
|
||||
@ -504,7 +596,14 @@ auto user_interface::draw_camera_controls(const Color color) const -> void
|
||||
}
|
||||
|
||||
int lock_camera_mass_center = input.camera_mass_center_lock;
|
||||
draw_menu_toggle_slider(1, 3, 1, 1, "Current Block (U)", "Graph Center (U)", &lock_camera_mass_center, color,
|
||||
draw_menu_toggle_slider(1,
|
||||
3,
|
||||
1,
|
||||
1,
|
||||
"Current Block (U)",
|
||||
"Graph Center (U)",
|
||||
&lock_camera_mass_center,
|
||||
color,
|
||||
input.camera_lock);
|
||||
if (lock_camera_mass_center != input.camera_mass_center_lock) {
|
||||
input.toggle_camera_mass_center_lock();
|
||||
@ -537,8 +636,12 @@ auto user_interface::draw_puzzle_controls(const Color color) const -> void
|
||||
};
|
||||
|
||||
const int visits = state.get_current_visits();
|
||||
draw_menu_button(0, 4, 1, 1, std::format("{} Moves ({}{} Time at this State)", state.get_total_moves(), visits,
|
||||
nth(visits)), color);
|
||||
draw_menu_button(0,
|
||||
4,
|
||||
1,
|
||||
1,
|
||||
std::format("{} Moves ({}{} Time at this State)", state.get_total_moves(), visits, nth(visits)),
|
||||
color);
|
||||
|
||||
if (draw_menu_button(1, 4, 1, 1, "Make Optimal Move (Space)", color, state.has_distances())) {
|
||||
input.goto_optimal_next_state();
|
||||
@ -556,7 +659,12 @@ auto user_interface::draw_puzzle_controls(const Color color) const -> void
|
||||
input.goto_most_distant_state();
|
||||
}
|
||||
|
||||
if (draw_menu_button(2, 5, 1, 1, "Go to Starting State (R)", color,
|
||||
if (draw_menu_button(2,
|
||||
5,
|
||||
1,
|
||||
1,
|
||||
"Go to Starting State (R)",
|
||||
color,
|
||||
state.get_current_index() != state.get_starting_index())) {
|
||||
input.goto_starting_state();
|
||||
}
|
||||
@ -634,8 +742,13 @@ auto user_interface::draw_save_preset_popup() -> void
|
||||
}
|
||||
|
||||
// Returns the pressed button index
|
||||
const int button = GuiTextInputBox(popup_bounds(), "Save as Preset", "Enter Preset Name", "Ok;Cancel",
|
||||
preset_comment.data(), 255, nullptr);
|
||||
const int button = GuiTextInputBox(popup_bounds(),
|
||||
"Save as Preset",
|
||||
"Enter Preset Name",
|
||||
"Ok;Cancel",
|
||||
preset_comment.data(),
|
||||
255,
|
||||
nullptr);
|
||||
if (button == 1) {
|
||||
state.save_current_to_preset_file(preset_comment.data());
|
||||
}
|
||||
@ -698,7 +811,11 @@ auto user_interface::draw_puzzle_board() -> void
|
||||
{
|
||||
const puzzle& current = state.get_current_state();
|
||||
|
||||
board_grid.update_bounds(0, MENU_HEIGHT, GetScreenWidth() / 2, GetScreenHeight() - MENU_HEIGHT, current.get_width(),
|
||||
board_grid.update_bounds(0,
|
||||
MENU_HEIGHT,
|
||||
GetScreenWidth() / 2,
|
||||
GetScreenHeight() - MENU_HEIGHT,
|
||||
current.get_width(),
|
||||
current.get_height());
|
||||
|
||||
// Draw outer border
|
||||
@ -706,18 +823,22 @@ auto user_interface::draw_puzzle_board() -> void
|
||||
DrawRectangleRec(bounds, current.goal_reached() ? BOARD_COLOR_WON : BOARD_COLOR_RESTRICTED);
|
||||
|
||||
// Draw inner borders
|
||||
DrawRectangle(bounds.x + BOARD_PADDING, bounds.y + BOARD_PADDING, bounds.width - 2 * BOARD_PADDING,
|
||||
DrawRectangle(bounds.x + BOARD_PADDING,
|
||||
bounds.y + BOARD_PADDING,
|
||||
bounds.width - 2 * BOARD_PADDING,
|
||||
bounds.height - 2 * BOARD_PADDING,
|
||||
current.get_restricted() ? BOARD_COLOR_RESTRICTED : BOARD_COLOR_FREE);
|
||||
|
||||
// Draw target opening
|
||||
// TODO: Only draw single direction (in corner) if restricted (use target block principal
|
||||
// direction)
|
||||
const std::optional<puzzle::block> target_block = current.try_get_target_block();
|
||||
const std::optional<block> target_block = current.try_get_target_block();
|
||||
const int target_x = current.get_goal_x();
|
||||
const int target_y = current.get_goal_y();
|
||||
if (current.get_goal() && target_block) {
|
||||
auto [x, y, width, height] = board_grid.square_bounds(target_x, target_y, target_block->get_width(),
|
||||
auto [x, y, width, height] = board_grid.square_bounds(target_x,
|
||||
target_y,
|
||||
target_block->get_width(),
|
||||
target_block->get_height());
|
||||
|
||||
const Color opening_color = Fade(current.goal_reached() ? BOARD_COLOR_WON : BOARD_COLOR_RESTRICTED, 0.3);
|
||||
@ -764,7 +885,7 @@ auto user_interface::draw_puzzle_board() -> void
|
||||
}
|
||||
|
||||
// Draw blocks
|
||||
for (const puzzle::block b : current.block_view()) {
|
||||
for (const block b : current.block_view()) {
|
||||
Color c = BLOCK_COLOR;
|
||||
if (b.get_target()) {
|
||||
c = TARGET_BLOCK_COLOR;
|
||||
@ -782,16 +903,22 @@ auto user_interface::draw_puzzle_board() -> void
|
||||
if (current.covers(input.block_add_x, input.block_add_y) && input.hov_x >= input.block_add_x && input.hov_y >=
|
||||
input.block_add_y) {
|
||||
bool collides = false;
|
||||
for (const puzzle::block b : current.block_view()) {
|
||||
if (b.collides(puzzle::block(input.block_add_x, input.block_add_y, input.hov_x - input.block_add_x + 1,
|
||||
input.hov_y - input.block_add_y + 1, false))) {
|
||||
for (const block b : current.block_view()) {
|
||||
if (b.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) {
|
||||
draw_board_block(input.block_add_x, input.block_add_y, input.hov_x - input.block_add_x + 1,
|
||||
input.hov_y - input.block_add_y + 1, PURPLE);
|
||||
draw_board_block(input.block_add_x,
|
||||
input.block_add_y,
|
||||
input.hov_x - input.block_add_x + 1,
|
||||
input.hov_y - input.block_add_y + 1,
|
||||
PURPLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -803,7 +930,8 @@ auto user_interface::draw_puzzle_board() -> void
|
||||
// Draw goal boundaries when editing
|
||||
if (input.editing && current.get_goal() && target_block) {
|
||||
DrawRectangleLinesEx(
|
||||
board_grid.square_bounds(target_x, target_y, target_block->get_width(), target_block->get_height()), 2.0,
|
||||
board_grid.square_bounds(target_x, target_y, target_block->get_width(), target_block->get_height()),
|
||||
2.0,
|
||||
TARGET_BLOCK_COLOR);
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ TEST_CASE("bitmap_is_full all bits set", "[puzzle][board]")
|
||||
puzzle p2(3, 4);
|
||||
puzzle p3(5, 4);
|
||||
puzzle p4(3, 7);
|
||||
uint64_t bitmap = -1;
|
||||
u64 bitmap = -1;
|
||||
|
||||
REQUIRE(p1.bitmap_is_full(bitmap));
|
||||
REQUIRE(p2.bitmap_is_full(bitmap));
|
||||
@ -24,7 +24,7 @@ TEST_CASE("bitmap_is_full no bits set", "[puzzle][board]")
|
||||
puzzle p2(3, 4);
|
||||
puzzle p3(5, 4);
|
||||
puzzle p4(3, 7);
|
||||
uint64_t bitmap = 0;
|
||||
u64 bitmap = 0;
|
||||
|
||||
REQUIRE_FALSE(p1.bitmap_is_full(bitmap));
|
||||
REQUIRE_FALSE(p2.bitmap_is_full(bitmap));
|
||||
@ -39,10 +39,10 @@ TEST_CASE("bitmap_is_full necessary bits set", "[puzzle][board]")
|
||||
puzzle p3(5, 4);
|
||||
puzzle p4(3, 7);
|
||||
|
||||
uint64_t bitmap1 = (1ull << 25) - 1; // 5 * 5
|
||||
uint64_t bitmap2 = (1ull << 12) - 1; // 3 * 4
|
||||
uint64_t bitmap3 = (1ull << 20) - 1; // 5 * 4
|
||||
uint64_t bitmap4 = (1ull << 21) - 1; // 3 * 7
|
||||
u64 bitmap1 = (1ull << 25) - 1; // 5 * 5
|
||||
u64 bitmap2 = (1ull << 12) - 1; // 3 * 4
|
||||
u64 bitmap3 = (1ull << 20) - 1; // 5 * 4
|
||||
u64 bitmap4 = (1ull << 21) - 1; // 3 * 7
|
||||
|
||||
REQUIRE(p1.bitmap_is_full(bitmap1));
|
||||
REQUIRE(p2.bitmap_is_full(bitmap2));
|
||||
@ -57,10 +57,10 @@ TEST_CASE("bitmap_is_full necessary bits not set", "[puzzle][board]")
|
||||
puzzle p3(5, 4);
|
||||
puzzle p4(3, 7);
|
||||
|
||||
uint64_t bitmap1 = (1ull << 25) - 1; // 5 * 5
|
||||
uint64_t bitmap2 = (1ull << 12) - 1; // 3 * 4
|
||||
uint64_t bitmap3 = (1ull << 20) - 1; // 5 * 4
|
||||
uint64_t bitmap4 = (1ull << 21) - 1; // 3 * 7
|
||||
u64 bitmap1 = (1ull << 25) - 1; // 5 * 5
|
||||
u64 bitmap2 = (1ull << 12) - 1; // 3 * 4
|
||||
u64 bitmap3 = (1ull << 20) - 1; // 5 * 4
|
||||
u64 bitmap4 = (1ull << 21) - 1; // 3 * 7
|
||||
|
||||
bitmap1 &= ~(1ull << 12);
|
||||
bitmap2 &= ~(1ull << 6);
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/generators/catch_generators.hpp>
|
||||
|
||||
static auto board_mask(const int w, const int h) -> uint64_t
|
||||
static auto board_mask(const int w, const int h) -> u64
|
||||
{
|
||||
const int cells = w * h;
|
||||
if (cells == 64) {
|
||||
@ -31,7 +31,7 @@ TEST_CASE("Full board detection respects width*height only", "[puzzle][board]")
|
||||
|
||||
puzzle p(w, h);
|
||||
|
||||
uint64_t mask = board_mask(w, h);
|
||||
u64 mask = board_mask(w, h);
|
||||
|
||||
int x = -1, y = -1;
|
||||
|
||||
@ -51,7 +51,7 @@ TEST_CASE("Single empty cell at various positions", "[puzzle][board]")
|
||||
|
||||
auto empty_index = GENERATE_COPY(values<int>({ 0, cells / 2, cells - 1}));
|
||||
|
||||
uint64_t bitmap = board_mask(w, h);
|
||||
u64 bitmap = board_mask(w, h);
|
||||
bitmap &= ~(1ULL << empty_index);
|
||||
|
||||
int x = -1, y = -1;
|
||||
@ -65,10 +65,10 @@ TEST_CASE("Bits outside board are ignored", "[puzzle][board]")
|
||||
{
|
||||
puzzle p(3, 3); // 9 cells
|
||||
|
||||
uint64_t mask = board_mask(3, 3);
|
||||
u64 mask = board_mask(3, 3);
|
||||
|
||||
// Board is full
|
||||
uint64_t bitmap = mask;
|
||||
u64 bitmap = mask;
|
||||
|
||||
// Set extra bits outside 9 cells
|
||||
bitmap |= (1ULL << 20);
|
||||
@ -83,7 +83,7 @@ TEST_CASE("First empty found in forward search branch", "[puzzle][branch]")
|
||||
puzzle p(4, 4); // 16 cells
|
||||
|
||||
// Only MSB (within board) set
|
||||
uint64_t bitmap = (1ULL << 15);
|
||||
u64 bitmap = (1ULL << 15);
|
||||
|
||||
int x = -1, y = -1;
|
||||
REQUIRE(p.bitmap_find_first_empty(bitmap, x, y));
|
||||
@ -97,7 +97,7 @@ TEST_CASE("Backward search branch finds gap before MSB cluster", "[puzzle][branc
|
||||
puzzle p(4, 4); // 16 cells
|
||||
|
||||
// Set bits 15,14,13 but leave 12 empty
|
||||
uint64_t bitmap = (1ULL << 15) | (1ULL << 14) | (1ULL << 13);
|
||||
u64 bitmap = (1ULL << 15) | (1ULL << 14) | (1ULL << 13);
|
||||
|
||||
int x = -1, y = -1;
|
||||
REQUIRE(p.bitmap_find_first_empty(bitmap, x, y));
|
||||
@ -112,7 +112,7 @@ TEST_CASE("Rectangular board coordinate mapping", "[puzzle][rect]")
|
||||
|
||||
int empty_index = 11;
|
||||
|
||||
uint64_t bitmap = board_mask(5, 3);
|
||||
u64 bitmap = board_mask(5, 3);
|
||||
bitmap &= ~(1ULL << empty_index);
|
||||
|
||||
int x = -1, y = -1;
|
||||
@ -126,7 +126,7 @@ TEST_CASE("Non-64-sized board near limit", "[puzzle][limit]")
|
||||
{
|
||||
puzzle p(7, 8); // 56 cells
|
||||
|
||||
uint64_t mask = board_mask(7, 8);
|
||||
u64 mask = board_mask(7, 8);
|
||||
|
||||
// Full board should return false
|
||||
int x = -1, y = -1;
|
||||
@ -142,7 +142,7 @@ TEST_CASE("Non-64-sized board near limit", "[puzzle][limit]")
|
||||
}
|
||||
|
||||
// --- Oracle: find first zero bit inside board ---
|
||||
static auto oracle_find_first_empty(uint64_t bitmap, int w, int h, int& x, int& y) -> bool
|
||||
static auto oracle_find_first_empty(u64 bitmap, int w, int h, int& x, int& y) -> bool
|
||||
{
|
||||
int cells = w * h;
|
||||
|
||||
@ -164,13 +164,13 @@ TEST_CASE("Oracle validation across board sizes 3x3 to 8x8", "[puzzle][oracle]")
|
||||
|
||||
puzzle p(w, h);
|
||||
|
||||
uint64_t mask = board_mask(w, h);
|
||||
u64 mask = board_mask(w, h);
|
||||
|
||||
std::mt19937_64 rng(12345);
|
||||
std::uniform_int_distribution<uint64_t> dist(0, UINT64_MAX);
|
||||
std::uniform_int_distribution<u64> dist(0, UINT64_MAX);
|
||||
|
||||
for (int iteration = 0; iteration < 200; ++iteration) {
|
||||
uint64_t bitmap = dist(rng);
|
||||
u64 bitmap = dist(rng);
|
||||
|
||||
int ox = -1, oy = -1;
|
||||
bool oracle_result = oracle_find_first_empty(bitmap, w, h, ox, oy);
|
||||
@ -191,7 +191,7 @@ TEST_CASE("Bits set outside board only behaves as empty board", "[puzzle][outsid
|
||||
{
|
||||
puzzle p(3, 3); // 9 cells
|
||||
|
||||
uint64_t bitmap = (1ULL << 40) | (1ULL << 63);
|
||||
u64 bitmap = (1ULL << 40) | (1ULL << 63);
|
||||
|
||||
int x = -1, y = -1;
|
||||
REQUIRE(p.bitmap_find_first_empty(bitmap, x, y));
|
||||
@ -207,7 +207,7 @@ TEST_CASE("Last valid cell empty stresses upper bound", "[puzzle][boundary]")
|
||||
puzzle p(w, h);
|
||||
|
||||
int cells = w * h;
|
||||
uint64_t bitmap = board_mask(w, h);
|
||||
u64 bitmap = board_mask(w, h);
|
||||
|
||||
// Clear last valid bit
|
||||
bitmap &= ~(1ULL << (cells - 1));
|
||||
@ -235,7 +235,7 @@ TEST_CASE("Board sizes between 33 and 63 cells", "[puzzle][midrange]")
|
||||
for (int index : {31, 32, cells - 2}) {
|
||||
if (index >= cells) continue;
|
||||
|
||||
uint64_t bitmap = board_mask(w, h);
|
||||
u64 bitmap = board_mask(w, h);
|
||||
bitmap &= ~(1ULL << index);
|
||||
|
||||
int x = -1, y = -1;
|
||||
@ -250,7 +250,7 @@ TEST_CASE("Multiple holes choose lowest index", "[puzzle][multiple]")
|
||||
{
|
||||
puzzle p(5, 5);
|
||||
|
||||
uint64_t bitmap = board_mask(5, 5);
|
||||
u64 bitmap = board_mask(5, 5);
|
||||
|
||||
// Clear several positions
|
||||
bitmap &= ~(1ULL << 3);
|
||||
|
||||
@ -31,8 +31,8 @@
|
||||
// 4. TEMPLATE_TEST_CASE(name, tags, Type1, Type2, ...)
|
||||
// A parameterised test that is instantiated once for each type listed.
|
||||
// Inside the test body, the alias `TestType` refers to the current type.
|
||||
// This avoids duplicating identical logic for uint8_t, uint16_t, uint32_t,
|
||||
// and uint64_t. Catch2 automatically appends the type name to the test name
|
||||
// This avoids duplicating identical logic for u8, u16, u32,
|
||||
// and u64. Catch2 automatically appends the type name to the test name
|
||||
// in the output so you can see which instantiation failed.
|
||||
//
|
||||
// 5. Tags (e.g. "[create_mask]", "[round-trip]")
|
||||
@ -49,7 +49,7 @@
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEMPLATE_TEST_CASE("create_mask produces correct masks", "[create_mask]",
|
||||
uint8_t, uint16_t, uint32_t, uint64_t)
|
||||
u8, u16, u32, u64)
|
||||
{
|
||||
SECTION("single bit mask at bit 0") {
|
||||
auto m = create_mask<TestType>(0, 0);
|
||||
@ -72,16 +72,16 @@ TEMPLATE_TEST_CASE("create_mask produces correct masks", "[create_mask]",
|
||||
}
|
||||
|
||||
SECTION("full-width mask returns all ones") {
|
||||
constexpr uint8_t last = sizeof(TestType) * 8 - 1;
|
||||
constexpr u8 last = sizeof(TestType) * 8 - 1;
|
||||
auto m = create_mask<TestType>(0, last);
|
||||
REQUIRE(m == static_cast<TestType>(~TestType{0}));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("create_mask 32-bit specific cases", "[create_mask]") {
|
||||
REQUIRE(create_mask<uint32_t>(0, 15) == 0x0000FFFF);
|
||||
REQUIRE(create_mask<uint32_t>(0, 31) == 0xFFFFFFFF);
|
||||
REQUIRE(create_mask<uint32_t>(16, 31) == 0xFFFF0000);
|
||||
REQUIRE(create_mask<u32>(0, 15) == 0x0000FFFF);
|
||||
REQUIRE(create_mask<u32>(0, 31) == 0xFFFFFFFF);
|
||||
REQUIRE(create_mask<u32>(16, 31) == 0xFFFF0000);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -89,11 +89,11 @@ TEST_CASE("create_mask 32-bit specific cases", "[create_mask]") {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEMPLATE_TEST_CASE("clear_bits zeroes the specified range", "[clear_bits]",
|
||||
uint8_t, uint16_t, uint32_t, uint64_t)
|
||||
u8, u16, u32, u64)
|
||||
{
|
||||
SECTION("clear all bits") {
|
||||
TestType val = static_cast<TestType>(~TestType{0});
|
||||
constexpr uint8_t last = sizeof(TestType) * 8 - 1;
|
||||
constexpr u8 last = sizeof(TestType) * 8 - 1;
|
||||
clear_bits(val, 0, last);
|
||||
REQUIRE(val == TestType{0});
|
||||
}
|
||||
@ -128,55 +128,55 @@ TEMPLATE_TEST_CASE("clear_bits zeroes the specified range", "[clear_bits]",
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEMPLATE_TEST_CASE("set_bits writes value into the specified range", "[set_bits]",
|
||||
uint8_t, uint16_t, uint32_t, uint64_t)
|
||||
u8, u16, u32, u64)
|
||||
{
|
||||
SECTION("set lower nibble on zero") {
|
||||
TestType val = TestType{0};
|
||||
set_bits(val, uint8_t{0}, uint8_t{3}, static_cast<TestType>(0xA));
|
||||
set_bits(val, u8{0}, u8{3}, static_cast<TestType>(0xA));
|
||||
REQUIRE(val == static_cast<TestType>(0x0A));
|
||||
}
|
||||
|
||||
SECTION("set upper nibble on zero") {
|
||||
TestType val = TestType{0};
|
||||
set_bits(val, uint8_t{4}, uint8_t{7}, static_cast<TestType>(0xB));
|
||||
set_bits(val, u8{4}, u8{7}, static_cast<TestType>(0xB));
|
||||
REQUIRE(val == static_cast<TestType>(0xB0));
|
||||
}
|
||||
|
||||
SECTION("set_bits replaces existing bits") {
|
||||
TestType val = static_cast<TestType>(0xFF);
|
||||
set_bits(val, uint8_t{0}, uint8_t{3}, static_cast<TestType>(0x5));
|
||||
set_bits(val, u8{0}, u8{3}, static_cast<TestType>(0x5));
|
||||
REQUIRE(val == static_cast<TestType>(0xF5));
|
||||
}
|
||||
|
||||
SECTION("set single bit to 1") {
|
||||
TestType val = TestType{0};
|
||||
set_bits(val, uint8_t{3}, uint8_t{3}, static_cast<TestType>(1));
|
||||
set_bits(val, u8{3}, u8{3}, static_cast<TestType>(1));
|
||||
REQUIRE(val == static_cast<TestType>(0x08));
|
||||
}
|
||||
|
||||
SECTION("set single bit to 0") {
|
||||
TestType val = static_cast<TestType>(0xFF);
|
||||
set_bits(val, uint8_t{3}, uint8_t{3}, static_cast<TestType>(0));
|
||||
set_bits(val, u8{3}, u8{3}, static_cast<TestType>(0));
|
||||
REQUIRE(val == static_cast<TestType>(0xF7));
|
||||
}
|
||||
|
||||
SECTION("setting value 0 clears the range") {
|
||||
TestType val = static_cast<TestType>(0xFF);
|
||||
set_bits(val, uint8_t{0}, uint8_t{7}, static_cast<TestType>(0));
|
||||
set_bits(val, u8{0}, u8{7}, static_cast<TestType>(0));
|
||||
REQUIRE(val == TestType{0});
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("set_bits with different value type (U != T)", "[set_bits]") {
|
||||
uint32_t val = 0;
|
||||
constexpr uint8_t small_val = 0x3F;
|
||||
set_bits(val, uint8_t{8}, uint8_t{13}, small_val);
|
||||
REQUIRE(val == (uint32_t{0x3F} << 8));
|
||||
u32 val = 0;
|
||||
constexpr u8 small_val = 0x3F;
|
||||
set_bits(val, u8{8}, u8{13}, small_val);
|
||||
REQUIRE(val == (u32{0x3F} << 8));
|
||||
}
|
||||
|
||||
TEST_CASE("set_bits preserves surrounding bits in 32-bit", "[set_bits]") {
|
||||
uint32_t val = 0xDEADBEEF;
|
||||
set_bits(val, uint8_t{8}, uint8_t{15}, uint32_t{0x42});
|
||||
u32 val = 0xDEADBEEF;
|
||||
set_bits(val, u8{8}, u8{15}, u32{0x42});
|
||||
REQUIRE(val == 0xDEAD42EF);
|
||||
}
|
||||
|
||||
@ -185,53 +185,53 @@ TEST_CASE("set_bits preserves surrounding bits in 32-bit", "[set_bits]") {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEMPLATE_TEST_CASE("get_bits extracts the specified range", "[get_bits]",
|
||||
uint8_t, uint16_t, uint32_t, uint64_t)
|
||||
u8, u16, u32, u64)
|
||||
{
|
||||
SECTION("get lower nibble") {
|
||||
TestType val = static_cast<TestType>(0xAB);
|
||||
auto result = get_bits(val, uint8_t{0}, uint8_t{3});
|
||||
auto result = get_bits(val, u8{0}, u8{3});
|
||||
REQUIRE(result == TestType{0xB});
|
||||
}
|
||||
|
||||
SECTION("get upper nibble") {
|
||||
TestType val = static_cast<TestType>(0xAB);
|
||||
auto result = get_bits(val, uint8_t{4}, uint8_t{7});
|
||||
auto result = get_bits(val, u8{4}, u8{7});
|
||||
REQUIRE(result == TestType{0xA});
|
||||
}
|
||||
|
||||
SECTION("get single bit that is set") {
|
||||
TestType val = static_cast<TestType>(0x08);
|
||||
auto result = get_bits(val, uint8_t{3}, uint8_t{3});
|
||||
auto result = get_bits(val, u8{3}, u8{3});
|
||||
REQUIRE(result == TestType{1});
|
||||
}
|
||||
|
||||
SECTION("get single bit that is clear") {
|
||||
TestType val = static_cast<TestType>(0xF7);
|
||||
auto result = get_bits(val, uint8_t{3}, uint8_t{3});
|
||||
auto result = get_bits(val, u8{3}, u8{3});
|
||||
REQUIRE(result == TestType{0});
|
||||
}
|
||||
|
||||
SECTION("get all bits") {
|
||||
TestType val = static_cast<TestType>(~TestType{0});
|
||||
constexpr uint8_t last = sizeof(TestType) * 8 - 1;
|
||||
auto result = get_bits(val, uint8_t{0}, last);
|
||||
constexpr u8 last = sizeof(TestType) * 8 - 1;
|
||||
auto result = get_bits(val, u8{0}, last);
|
||||
REQUIRE(result == val);
|
||||
}
|
||||
|
||||
SECTION("get from zero returns zero") {
|
||||
TestType val = TestType{0};
|
||||
auto result = get_bits(val, uint8_t{0}, uint8_t{7});
|
||||
auto result = get_bits(val, u8{0}, u8{7});
|
||||
REQUIRE(result == TestType{0});
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("get_bits 32-bit specific extractions", "[get_bits]") {
|
||||
constexpr uint32_t val = 0xDEADBEEF;
|
||||
constexpr u32 val = 0xDEADBEEF;
|
||||
|
||||
REQUIRE(get_bits(val, uint8_t{0}, uint8_t{7}) == 0xEF);
|
||||
REQUIRE(get_bits(val, uint8_t{8}, uint8_t{15}) == 0xBE);
|
||||
REQUIRE(get_bits(val, uint8_t{16}, uint8_t{23}) == 0xAD);
|
||||
REQUIRE(get_bits(val, uint8_t{24}, uint8_t{31}) == 0xDE);
|
||||
REQUIRE(get_bits(val, u8{0}, u8{7}) == 0xEF);
|
||||
REQUIRE(get_bits(val, u8{8}, u8{15}) == 0xBE);
|
||||
REQUIRE(get_bits(val, u8{16}, u8{23}) == 0xAD);
|
||||
REQUIRE(get_bits(val, u8{24}, u8{31}) == 0xDE);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -239,29 +239,29 @@ TEST_CASE("get_bits 32-bit specific extractions", "[get_bits]") {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST_CASE("set_bits then get_bits round-trips correctly", "[round-trip]") {
|
||||
uint32_t reg = 0;
|
||||
u32 reg = 0;
|
||||
|
||||
set_bits(reg, uint8_t{4}, uint8_t{11}, uint32_t{0xAB});
|
||||
REQUIRE(get_bits(reg, uint8_t{4}, uint8_t{11}) == 0xAB);
|
||||
set_bits(reg, u8{4}, u8{11}, u32{0xAB});
|
||||
REQUIRE(get_bits(reg, u8{4}, u8{11}) == 0xAB);
|
||||
|
||||
REQUIRE(get_bits(reg, uint8_t{0}, uint8_t{3}) == 0x0);
|
||||
REQUIRE(get_bits(reg, uint8_t{12}, uint8_t{31}) == 0x0);
|
||||
REQUIRE(get_bits(reg, u8{0}, u8{3}) == 0x0);
|
||||
REQUIRE(get_bits(reg, u8{12}, u8{31}) == 0x0);
|
||||
}
|
||||
|
||||
TEST_CASE("multiple set_bits on different ranges", "[round-trip]") {
|
||||
uint32_t reg = 0;
|
||||
u32 reg = 0;
|
||||
|
||||
set_bits(reg, uint8_t{0}, uint8_t{7}, uint32_t{0x01});
|
||||
set_bits(reg, uint8_t{8}, uint8_t{15}, uint32_t{0x02});
|
||||
set_bits(reg, uint8_t{16}, uint8_t{23}, uint32_t{0x03});
|
||||
set_bits(reg, uint8_t{24}, uint8_t{31}, uint32_t{0x04});
|
||||
set_bits(reg, u8{0}, u8{7}, u32{0x01});
|
||||
set_bits(reg, u8{8}, u8{15}, u32{0x02});
|
||||
set_bits(reg, u8{16}, u8{23}, u32{0x03});
|
||||
set_bits(reg, u8{24}, u8{31}, u32{0x04});
|
||||
|
||||
REQUIRE(reg == 0x04030201);
|
||||
}
|
||||
|
||||
TEST_CASE("64-bit round-trip", "[round-trip]") {
|
||||
uint64_t reg = 0;
|
||||
set_bits(reg, uint8_t{32}, uint8_t{63}, uint64_t{0xCAFEBABE});
|
||||
REQUIRE(get_bits(reg, uint8_t{32}, uint8_t{63}) == uint64_t{0xCAFEBABE});
|
||||
REQUIRE(get_bits(reg, uint8_t{0}, uint8_t{31}) == uint64_t{0});
|
||||
u64 reg = 0;
|
||||
set_bits(reg, u8{32}, u8{63}, u64{0xCAFEBABE});
|
||||
REQUIRE(get_bits(reg, u8{32}, u8{63}) == u64{0xCAFEBABE});
|
||||
REQUIRE(get_bits(reg, u8{0}, u8{31}) == u64{0});
|
||||
}
|
||||
304
test/puzzle.cpp
304
test/puzzle.cpp
@ -12,7 +12,7 @@
|
||||
|
||||
TEST_CASE("Block creation and field access", "[block]")
|
||||
{
|
||||
puzzle::block b(1, 2, 3, 4, true, false);
|
||||
block b(1, 2, 3, 4, true, false);
|
||||
|
||||
CHECK(b.get_x() == 1);
|
||||
CHECK(b.get_y() == 2);
|
||||
@ -25,16 +25,16 @@ TEST_CASE("Block creation and field access", "[block]")
|
||||
|
||||
TEST_CASE("Block invalid by default", "[block]")
|
||||
{
|
||||
puzzle::block b;
|
||||
block b;
|
||||
CHECK_FALSE(b.valid());
|
||||
}
|
||||
|
||||
TEST_CASE("Block comparison ordering", "[block]")
|
||||
{
|
||||
// Row-major: (0,0) < (1,0) < (0,1)
|
||||
puzzle::block a(0, 0, 1, 1);
|
||||
puzzle::block b(1, 0, 1, 1);
|
||||
puzzle::block c(0, 1, 1, 1);
|
||||
block a(0, 0, 1, 1);
|
||||
block b(1, 0, 1, 1);
|
||||
block c(0, 1, 1, 1);
|
||||
|
||||
CHECK(a < b);
|
||||
CHECK(b < c);
|
||||
@ -43,7 +43,7 @@ TEST_CASE("Block comparison ordering", "[block]")
|
||||
|
||||
TEST_CASE("Block principal_dirs for horizontal block", "[block]")
|
||||
{
|
||||
puzzle::block b(0, 0, 3, 1); // wider than tall
|
||||
block b(0, 0, 3, 1); // wider than tall
|
||||
CHECK((b.principal_dirs() & eas));
|
||||
CHECK((b.principal_dirs() & wes));
|
||||
CHECK_FALSE((b.principal_dirs() & nor));
|
||||
@ -52,7 +52,7 @@ TEST_CASE("Block principal_dirs for horizontal block", "[block]")
|
||||
|
||||
TEST_CASE("Block principal_dirs for vertical block", "[block]")
|
||||
{
|
||||
puzzle::block b(0, 0, 1, 3); // taller than wide
|
||||
block b(0, 0, 1, 3); // taller than wide
|
||||
CHECK((b.principal_dirs() & nor));
|
||||
CHECK((b.principal_dirs() & sou));
|
||||
CHECK_FALSE((b.principal_dirs() & eas));
|
||||
@ -61,7 +61,7 @@ TEST_CASE("Block principal_dirs for vertical block", "[block]")
|
||||
|
||||
TEST_CASE("Block principal_dirs for square block", "[block]")
|
||||
{
|
||||
puzzle::block b(0, 0, 2, 2);
|
||||
block b(0, 0, 2, 2);
|
||||
CHECK((b.principal_dirs() & nor));
|
||||
CHECK((b.principal_dirs() & sou));
|
||||
CHECK((b.principal_dirs() & eas));
|
||||
@ -70,7 +70,7 @@ TEST_CASE("Block principal_dirs for square block", "[block]")
|
||||
|
||||
TEST_CASE("Block covers", "[block]")
|
||||
{
|
||||
puzzle::block b(1, 2, 3, 2);
|
||||
block b(1, 2, 3, 2);
|
||||
// Covers (1,2) to (3,3)
|
||||
CHECK(b.covers(1, 2));
|
||||
CHECK(b.covers(3, 3));
|
||||
@ -83,10 +83,10 @@ TEST_CASE("Block covers", "[block]")
|
||||
|
||||
TEST_CASE("Block collides", "[block]")
|
||||
{
|
||||
puzzle::block a(0, 0, 2, 2);
|
||||
puzzle::block b(1, 1, 2, 2);
|
||||
puzzle::block c(2, 2, 1, 1);
|
||||
puzzle::block d(3, 0, 1, 1);
|
||||
block a(0, 0, 2, 2);
|
||||
block b(1, 1, 2, 2);
|
||||
block c(2, 2, 1, 1);
|
||||
block d(3, 0, 1, 1);
|
||||
|
||||
CHECK(a.collides(b));
|
||||
CHECK(b.collides(a));
|
||||
@ -114,7 +114,7 @@ TEST_CASE("Puzzle creation and meta access", "[puzzle]")
|
||||
TEST_CASE("Puzzle add and remove block", "[puzzle]")
|
||||
{
|
||||
puzzle p(4, 4, 0, 0, false, false);
|
||||
puzzle::block b(0, 0, 2, 1);
|
||||
block b(0, 0, 2, 1);
|
||||
|
||||
auto p2 = p.try_add_block(b);
|
||||
REQUIRE(p2.has_value());
|
||||
@ -127,8 +127,8 @@ TEST_CASE("Puzzle add and remove block", "[puzzle]")
|
||||
|
||||
TEST_CASE("Puzzle meta roundtrip for all valid sizes", "[puzzle]")
|
||||
{
|
||||
for (uint8_t w = 3; w <= 8; ++w) {
|
||||
for (uint8_t h = 3; h <= 8; ++h) {
|
||||
for (u8 w = 3; w <= 8; ++w) {
|
||||
for (u8 h = 3; h <= 8; ++h) {
|
||||
puzzle p(w, h, 0, 0, true, false);
|
||||
CHECK(p.get_width() == w);
|
||||
CHECK(p.get_height() == h);
|
||||
@ -151,11 +151,11 @@ TEST_CASE("Puzzle block_count", "[puzzle]")
|
||||
puzzle p(4, 4, 0, 0, false, false);
|
||||
CHECK(p.block_count() == 0);
|
||||
|
||||
auto p2 = p.try_add_block(puzzle::block(0, 0, 1, 1));
|
||||
auto p2 = p.try_add_block(block(0, 0, 1, 1));
|
||||
REQUIRE(p2);
|
||||
CHECK(p2->block_count() == 1);
|
||||
|
||||
auto p3 = p2->try_add_block(puzzle::block(2, 2, 1, 1));
|
||||
auto p3 = p2->try_add_block(block(2, 2, 1, 1));
|
||||
REQUIRE(p3);
|
||||
CHECK(p3->block_count() == 2);
|
||||
}
|
||||
@ -163,18 +163,18 @@ TEST_CASE("Puzzle block_count", "[puzzle]")
|
||||
TEST_CASE("Puzzle cannot add overlapping block", "[puzzle]")
|
||||
{
|
||||
puzzle p(4, 4, 0, 0, false, false);
|
||||
auto p2 = p.try_add_block(puzzle::block(0, 0, 2, 2));
|
||||
auto p2 = p.try_add_block(block(0, 0, 2, 2));
|
||||
REQUIRE(p2);
|
||||
|
||||
// Overlapping
|
||||
auto p3 = p2->try_add_block(puzzle::block(1, 1, 2, 2));
|
||||
auto p3 = p2->try_add_block(block(1, 1, 2, 2));
|
||||
CHECK_FALSE(p3.has_value());
|
||||
}
|
||||
|
||||
TEST_CASE("Puzzle cannot add block outside board", "[puzzle]")
|
||||
{
|
||||
puzzle p(4, 4, 0, 0, false, false);
|
||||
auto p2 = p.try_add_block(puzzle::block(3, 3, 2, 2));
|
||||
auto p2 = p.try_add_block(block(3, 3, 2, 2));
|
||||
CHECK_FALSE(p2.has_value());
|
||||
}
|
||||
|
||||
@ -184,17 +184,17 @@ TEST_CASE("Puzzle cannot add block outside board", "[puzzle]")
|
||||
|
||||
TEST_CASE("bitmap_set_bit and bitmap_get_bit roundtrip", "[bitmap]")
|
||||
{
|
||||
for (uint8_t w = 3; w <= 8; ++w) {
|
||||
uint64_t bm = 0;
|
||||
for (u8 w = 3; w <= 8; ++w) {
|
||||
u64 bm = 0;
|
||||
// Set every cell on a w x w board
|
||||
for (uint8_t y = 0; y < w; ++y) {
|
||||
for (uint8_t x = 0; x < w; ++x) {
|
||||
for (u8 y = 0; y < w; ++y) {
|
||||
for (u8 x = 0; x < w; ++x) {
|
||||
puzzle::bitmap_set_bit(bm, w, x, y);
|
||||
}
|
||||
}
|
||||
// Verify all set
|
||||
for (uint8_t y = 0; y < w; ++y) {
|
||||
for (uint8_t x = 0; x < w; ++x) {
|
||||
for (u8 y = 0; y < w; ++y) {
|
||||
for (u8 x = 0; x < w; ++x) {
|
||||
CHECK(puzzle::bitmap_get_bit(bm, w, x, y));
|
||||
}
|
||||
}
|
||||
@ -203,12 +203,12 @@ TEST_CASE("bitmap_set_bit and bitmap_get_bit roundtrip", "[bitmap]")
|
||||
|
||||
TEST_CASE("bitmap_set_bit sets only the target bit", "[bitmap]")
|
||||
{
|
||||
for (uint8_t w = 3; w <= 8; ++w) {
|
||||
for (uint8_t y = 0; y < w; ++y) {
|
||||
for (uint8_t x = 0; x < w; ++x) {
|
||||
uint64_t bm = 0;
|
||||
for (u8 w = 3; w <= 8; ++w) {
|
||||
for (u8 y = 0; y < w; ++y) {
|
||||
for (u8 x = 0; x < w; ++x) {
|
||||
u64 bm = 0;
|
||||
puzzle::bitmap_set_bit(bm, w, x, y);
|
||||
CHECK(bm == (uint64_t(1) << (y * w + x)));
|
||||
CHECK(bm == (u64(1) << (y * w + x)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -216,11 +216,11 @@ TEST_CASE("bitmap_set_bit sets only the target bit", "[bitmap]")
|
||||
|
||||
TEST_CASE("bitmap_clear_bit clears only the target bit", "[bitmap]")
|
||||
{
|
||||
uint8_t w = 6;
|
||||
uint64_t bm = 0;
|
||||
u8 w = 6;
|
||||
u64 bm = 0;
|
||||
puzzle::bitmap_set_bit(bm, w, 2, 3);
|
||||
puzzle::bitmap_set_bit(bm, w, 4, 1);
|
||||
uint64_t before = bm;
|
||||
u64 before = bm;
|
||||
|
||||
puzzle::bitmap_clear_bit(bm, w, 2, 3);
|
||||
CHECK_FALSE(puzzle::bitmap_get_bit(bm, w, 2, 3));
|
||||
@ -230,10 +230,10 @@ TEST_CASE("bitmap_clear_bit clears only the target bit", "[bitmap]")
|
||||
TEST_CASE("blocks_bitmap for single block", "[bitmap]")
|
||||
{
|
||||
puzzle p(6, 6, 0, 0, false, false);
|
||||
auto p2 = p.try_add_block(puzzle::block(1, 2, 3, 1));
|
||||
auto p2 = p.try_add_block(block(1, 2, 3, 1));
|
||||
REQUIRE(p2);
|
||||
|
||||
uint64_t bm = p2->blocks_bitmap();
|
||||
u64 bm = p2->blocks_bitmap();
|
||||
// Block at (1,2) width 3 height 1 on a 6-wide board
|
||||
// Bits: row 2, cols 1,2,3 -> positions 2*6+1=13, 14, 15
|
||||
CHECK(puzzle::bitmap_get_bit(bm, 6, 1, 2));
|
||||
@ -248,10 +248,10 @@ TEST_CASE("blocks_bitmap for single block", "[bitmap]")
|
||||
TEST_CASE("blocks_bitmap for 2x2 block", "[bitmap]")
|
||||
{
|
||||
puzzle p(5, 5, 0, 0, false, false);
|
||||
auto p2 = p.try_add_block(puzzle::block(1, 1, 2, 2));
|
||||
auto p2 = p.try_add_block(block(1, 1, 2, 2));
|
||||
REQUIRE(p2);
|
||||
|
||||
uint64_t bm = p2->blocks_bitmap();
|
||||
u64 bm = p2->blocks_bitmap();
|
||||
CHECK(puzzle::bitmap_get_bit(bm, 5, 1, 1));
|
||||
CHECK(puzzle::bitmap_get_bit(bm, 5, 2, 1));
|
||||
CHECK(puzzle::bitmap_get_bit(bm, 5, 1, 2));
|
||||
@ -264,13 +264,13 @@ TEST_CASE("blocks_bitmap_h only includes horizontal/square blocks", "[bitmap]")
|
||||
{
|
||||
puzzle p(6, 6, 0, 0, true, false);
|
||||
// Horizontal block (2x1) -> principal_dirs has eas
|
||||
auto p2 = p.try_add_block(puzzle::block(0, 0, 2, 1));
|
||||
auto p2 = p.try_add_block(block(0, 0, 2, 1));
|
||||
REQUIRE(p2);
|
||||
// Vertical block (1x2) -> principal_dirs has sou, not eas
|
||||
auto p3 = p2->try_add_block(puzzle::block(4, 0, 1, 2));
|
||||
auto p3 = p2->try_add_block(block(4, 0, 1, 2));
|
||||
REQUIRE(p3);
|
||||
|
||||
uint64_t bm_h = p3->blocks_bitmap_h();
|
||||
u64 bm_h = p3->blocks_bitmap_h();
|
||||
// Horizontal block should be in bitmap_h
|
||||
CHECK(puzzle::bitmap_get_bit(bm_h, 6, 0, 0));
|
||||
CHECK(puzzle::bitmap_get_bit(bm_h, 6, 1, 0));
|
||||
@ -283,13 +283,13 @@ TEST_CASE("blocks_bitmap_v only includes vertical/square blocks", "[bitmap]")
|
||||
{
|
||||
puzzle p(6, 6, 0, 0, true, false);
|
||||
// Vertical block (1x2)
|
||||
auto p2 = p.try_add_block(puzzle::block(0, 0, 1, 2));
|
||||
auto p2 = p.try_add_block(block(0, 0, 1, 2));
|
||||
REQUIRE(p2);
|
||||
// Horizontal block (2x1)
|
||||
auto p3 = p2->try_add_block(puzzle::block(4, 0, 2, 1));
|
||||
auto p3 = p2->try_add_block(block(4, 0, 2, 1));
|
||||
REQUIRE(p3);
|
||||
|
||||
uint64_t bm_v = p3->blocks_bitmap_v();
|
||||
u64 bm_v = p3->blocks_bitmap_v();
|
||||
// Vertical block should be in bitmap_v
|
||||
CHECK(puzzle::bitmap_get_bit(bm_v, 6, 0, 0));
|
||||
CHECK(puzzle::bitmap_get_bit(bm_v, 6, 0, 1));
|
||||
@ -301,11 +301,11 @@ TEST_CASE("blocks_bitmap_v only includes vertical/square blocks", "[bitmap]")
|
||||
TEST_CASE("blocks_bitmap_h and blocks_bitmap_v both include square blocks", "[bitmap]")
|
||||
{
|
||||
puzzle p(6, 6, 0, 0, true, false);
|
||||
auto p2 = p.try_add_block(puzzle::block(1, 1, 2, 2));
|
||||
auto p2 = p.try_add_block(block(1, 1, 2, 2));
|
||||
REQUIRE(p2);
|
||||
|
||||
uint64_t bm_h = p2->blocks_bitmap_h();
|
||||
uint64_t bm_v = p2->blocks_bitmap_v();
|
||||
u64 bm_h = p2->blocks_bitmap_h();
|
||||
u64 bm_v = p2->blocks_bitmap_v();
|
||||
|
||||
// Square block should appear in both
|
||||
CHECK(puzzle::bitmap_get_bit(bm_h, 6, 1, 1));
|
||||
@ -320,12 +320,12 @@ TEST_CASE("bitmap_newly_occupied north - single vertical block with space above"
|
||||
{
|
||||
// 4x4 board, vertical 1x2 block at (1,2)
|
||||
puzzle p(4, 4, 0, 0, true, false);
|
||||
auto p2 = p.try_add_block(puzzle::block(1, 2, 1, 2));
|
||||
auto p2 = p.try_add_block(block(1, 2, 1, 2));
|
||||
REQUIRE(p2);
|
||||
|
||||
uint64_t combined = p2->blocks_bitmap();
|
||||
uint64_t vert = p2->blocks_bitmap_v();
|
||||
uint64_t bm = combined;
|
||||
u64 combined = p2->blocks_bitmap();
|
||||
u64 vert = p2->blocks_bitmap_v();
|
||||
u64 bm = combined;
|
||||
|
||||
p2->bitmap_newly_occupied_after_move(bm, vert, nor);
|
||||
|
||||
@ -346,12 +346,12 @@ TEST_CASE("bitmap_newly_occupied south - single vertical block with space below"
|
||||
{
|
||||
// 4x4 board, vertical 1x2 block at (1,0)
|
||||
puzzle p(4, 4, 0, 0, true, false);
|
||||
auto p2 = p.try_add_block(puzzle::block(1, 0, 1, 2));
|
||||
auto p2 = p.try_add_block(block(1, 0, 1, 2));
|
||||
REQUIRE(p2);
|
||||
|
||||
uint64_t combined = p2->blocks_bitmap();
|
||||
uint64_t vert = p2->blocks_bitmap_v();
|
||||
uint64_t bm = combined;
|
||||
u64 combined = p2->blocks_bitmap();
|
||||
u64 vert = p2->blocks_bitmap_v();
|
||||
u64 bm = combined;
|
||||
|
||||
p2->bitmap_newly_occupied_after_move(bm, vert, sou);
|
||||
|
||||
@ -366,12 +366,12 @@ TEST_CASE("bitmap_newly_occupied east - single horizontal block with space right
|
||||
{
|
||||
// 5x4 board, horizontal 2x1 block at (0,1)
|
||||
puzzle p(5, 4, 0, 0, true, false);
|
||||
auto p2 = p.try_add_block(puzzle::block(0, 1, 2, 1));
|
||||
auto p2 = p.try_add_block(block(0, 1, 2, 1));
|
||||
REQUIRE(p2);
|
||||
|
||||
uint64_t combined = p2->blocks_bitmap();
|
||||
uint64_t horiz = p2->blocks_bitmap_h();
|
||||
uint64_t bm = combined;
|
||||
u64 combined = p2->blocks_bitmap();
|
||||
u64 horiz = p2->blocks_bitmap_h();
|
||||
u64 bm = combined;
|
||||
|
||||
p2->bitmap_newly_occupied_after_move(bm, horiz, eas);
|
||||
|
||||
@ -386,12 +386,12 @@ TEST_CASE("bitmap_newly_occupied west - single horizontal block with space left"
|
||||
{
|
||||
// 5x4 board, horizontal 2x1 block at (2,1)
|
||||
puzzle p(5, 4, 0, 0, true, false);
|
||||
auto p2 = p.try_add_block(puzzle::block(2, 1, 2, 1));
|
||||
auto p2 = p.try_add_block(block(2, 1, 2, 1));
|
||||
REQUIRE(p2);
|
||||
|
||||
uint64_t combined = p2->blocks_bitmap();
|
||||
uint64_t horiz = p2->blocks_bitmap_h();
|
||||
uint64_t bm = combined;
|
||||
u64 combined = p2->blocks_bitmap();
|
||||
u64 horiz = p2->blocks_bitmap_h();
|
||||
u64 bm = combined;
|
||||
|
||||
p2->bitmap_newly_occupied_after_move(bm, horiz, wes);
|
||||
|
||||
@ -406,12 +406,12 @@ TEST_CASE("bitmap_newly_occupied does not wrap east across rows", "[bitmap_move]
|
||||
{
|
||||
// 4x4 board, horizontal 2x1 block at (2,0) - rightmost position
|
||||
puzzle p(4, 4, 0, 0, true, false);
|
||||
auto p2 = p.try_add_block(puzzle::block(2, 0, 2, 1));
|
||||
auto p2 = p.try_add_block(block(2, 0, 2, 1));
|
||||
REQUIRE(p2);
|
||||
|
||||
uint64_t combined = p2->blocks_bitmap();
|
||||
uint64_t horiz = p2->blocks_bitmap_h();
|
||||
uint64_t bm = combined;
|
||||
u64 combined = p2->blocks_bitmap();
|
||||
u64 horiz = p2->blocks_bitmap_h();
|
||||
u64 bm = combined;
|
||||
|
||||
p2->bitmap_newly_occupied_after_move(bm, horiz, eas);
|
||||
|
||||
@ -424,12 +424,12 @@ TEST_CASE("bitmap_newly_occupied does not wrap west across rows", "[bitmap_move]
|
||||
{
|
||||
// 4x4 board, horizontal 2x1 block at (0,1)
|
||||
puzzle p(4, 4, 0, 0, true, false);
|
||||
auto p2 = p.try_add_block(puzzle::block(0, 1, 2, 1));
|
||||
auto p2 = p.try_add_block(block(0, 1, 2, 1));
|
||||
REQUIRE(p2);
|
||||
|
||||
uint64_t combined = p2->blocks_bitmap();
|
||||
uint64_t horiz = p2->blocks_bitmap_h();
|
||||
uint64_t bm = combined;
|
||||
u64 combined = p2->blocks_bitmap();
|
||||
u64 horiz = p2->blocks_bitmap_h();
|
||||
u64 bm = combined;
|
||||
|
||||
p2->bitmap_newly_occupied_after_move(bm, horiz, wes);
|
||||
|
||||
@ -441,14 +441,14 @@ TEST_CASE("bitmap_newly_occupied blocked by another block", "[bitmap_move]")
|
||||
{
|
||||
// 6x4 board, two horizontal blocks side by side
|
||||
puzzle p(6, 4, 0, 0, true, false);
|
||||
auto p2 = p.try_add_block(puzzle::block(0, 0, 2, 1));
|
||||
auto p2 = p.try_add_block(block(0, 0, 2, 1));
|
||||
REQUIRE(p2);
|
||||
auto p3 = p2->try_add_block(puzzle::block(2, 0, 2, 1));
|
||||
auto p3 = p2->try_add_block(block(2, 0, 2, 1));
|
||||
REQUIRE(p3);
|
||||
|
||||
uint64_t combined = p3->blocks_bitmap();
|
||||
uint64_t horiz = p3->blocks_bitmap_h();
|
||||
uint64_t bm = combined;
|
||||
u64 combined = p3->blocks_bitmap();
|
||||
u64 horiz = p3->blocks_bitmap_h();
|
||||
u64 bm = combined;
|
||||
|
||||
p3->bitmap_newly_occupied_after_move(bm, horiz, eas);
|
||||
|
||||
@ -468,11 +468,11 @@ TEST_CASE("restricted_bitmap_get_moves north - returns correct source cell", "[g
|
||||
{
|
||||
// 4x4 board, vertical 1x2 block at (1,2)
|
||||
puzzle p(4, 4, 0, 0, true, false);
|
||||
auto p2 = p.try_add_block(puzzle::block(1, 2, 1, 2));
|
||||
auto p2 = p.try_add_block(block(1, 2, 1, 2));
|
||||
REQUIRE(p2);
|
||||
|
||||
uint64_t combined = p2->blocks_bitmap();
|
||||
uint64_t vert = p2->blocks_bitmap_v();
|
||||
u64 combined = p2->blocks_bitmap();
|
||||
u64 vert = p2->blocks_bitmap_v();
|
||||
|
||||
auto moves = p2->restricted_bitmap_get_moves(combined, vert, nor);
|
||||
|
||||
@ -487,11 +487,11 @@ TEST_CASE("restricted_bitmap_get_moves south - returns correct source cell", "[g
|
||||
{
|
||||
// 4x4 board, vertical 1x2 block at (1,0)
|
||||
puzzle p(4, 4, 0, 0, true, false);
|
||||
auto p2 = p.try_add_block(puzzle::block(1, 0, 1, 2));
|
||||
auto p2 = p.try_add_block(block(1, 0, 1, 2));
|
||||
REQUIRE(p2);
|
||||
|
||||
uint64_t combined = p2->blocks_bitmap();
|
||||
uint64_t vert = p2->blocks_bitmap_v();
|
||||
u64 combined = p2->blocks_bitmap();
|
||||
u64 vert = p2->blocks_bitmap_v();
|
||||
|
||||
auto moves = p2->restricted_bitmap_get_moves(combined, vert, sou);
|
||||
|
||||
@ -511,11 +511,11 @@ TEST_CASE("restricted_bitmap_get_moves east - returns correct source cell", "[ge
|
||||
{
|
||||
// 5x4 board, horizontal 2x1 block at (0,1)
|
||||
puzzle p(5, 4, 0, 0, true, false);
|
||||
auto p2 = p.try_add_block(puzzle::block(0, 1, 2, 1));
|
||||
auto p2 = p.try_add_block(block(0, 1, 2, 1));
|
||||
REQUIRE(p2);
|
||||
|
||||
uint64_t combined = p2->blocks_bitmap();
|
||||
uint64_t horiz = p2->blocks_bitmap_h();
|
||||
u64 combined = p2->blocks_bitmap();
|
||||
u64 horiz = p2->blocks_bitmap_h();
|
||||
|
||||
auto moves = p2->restricted_bitmap_get_moves(combined, horiz, eas);
|
||||
|
||||
@ -530,11 +530,11 @@ TEST_CASE("restricted_bitmap_get_moves west - returns correct source cell", "[ge
|
||||
{
|
||||
// 5x4 board, horizontal 2x1 block at (2,1)
|
||||
puzzle p(5, 4, 0, 0, true, false);
|
||||
auto p2 = p.try_add_block(puzzle::block(2, 1, 2, 1));
|
||||
auto p2 = p.try_add_block(block(2, 1, 2, 1));
|
||||
REQUIRE(p2);
|
||||
|
||||
uint64_t combined = p2->blocks_bitmap();
|
||||
uint64_t horiz = p2->blocks_bitmap_h();
|
||||
u64 combined = p2->blocks_bitmap();
|
||||
u64 horiz = p2->blocks_bitmap_h();
|
||||
|
||||
auto moves = p2->restricted_bitmap_get_moves(combined, horiz, wes);
|
||||
|
||||
@ -549,11 +549,11 @@ TEST_CASE("restricted_bitmap_get_moves returns empty when blocked", "[get_moves]
|
||||
{
|
||||
// 4x4 board, vertical 1x2 block at (1,0) - can't move north (at top edge)
|
||||
puzzle p(4, 4, 0, 0, true, false);
|
||||
auto p2 = p.try_add_block(puzzle::block(1, 0, 1, 2));
|
||||
auto p2 = p.try_add_block(block(1, 0, 1, 2));
|
||||
REQUIRE(p2);
|
||||
|
||||
uint64_t combined = p2->blocks_bitmap();
|
||||
uint64_t vert = p2->blocks_bitmap_v();
|
||||
u64 combined = p2->blocks_bitmap();
|
||||
u64 vert = p2->blocks_bitmap_v();
|
||||
|
||||
auto moves = p2->restricted_bitmap_get_moves(combined, vert, nor);
|
||||
|
||||
@ -569,11 +569,11 @@ TEST_CASE("restricted_bitmap_get_moves with width 6 board", "[get_moves]")
|
||||
{
|
||||
// 6x6 board, vertical 1x2 block at (2,3)
|
||||
puzzle p(6, 6, 0, 0, true, false);
|
||||
auto p2 = p.try_add_block(puzzle::block(2, 3, 1, 2));
|
||||
auto p2 = p.try_add_block(block(2, 3, 1, 2));
|
||||
REQUIRE(p2);
|
||||
|
||||
uint64_t combined = p2->blocks_bitmap();
|
||||
uint64_t vert = p2->blocks_bitmap_v();
|
||||
u64 combined = p2->blocks_bitmap();
|
||||
u64 vert = p2->blocks_bitmap_v();
|
||||
|
||||
auto moves = p2->restricted_bitmap_get_moves(combined, vert, nor);
|
||||
|
||||
@ -588,20 +588,20 @@ TEST_CASE("restricted_bitmap_get_moves multiple blocks can move", "[get_moves]")
|
||||
{
|
||||
// 6x6 board, two vertical blocks with space above
|
||||
puzzle p(6, 6, 0, 0, true, false);
|
||||
auto p2 = p.try_add_block(puzzle::block(1, 2, 1, 2));
|
||||
auto p2 = p.try_add_block(block(1, 2, 1, 2));
|
||||
REQUIRE(p2);
|
||||
auto p3 = p2->try_add_block(puzzle::block(4, 3, 1, 2));
|
||||
auto p3 = p2->try_add_block(block(4, 3, 1, 2));
|
||||
REQUIRE(p3);
|
||||
|
||||
uint64_t combined = p3->blocks_bitmap();
|
||||
uint64_t vert = p3->blocks_bitmap_v();
|
||||
u64 combined = p3->blocks_bitmap();
|
||||
u64 vert = p3->blocks_bitmap_v();
|
||||
|
||||
auto moves = p3->restricted_bitmap_get_moves(combined, vert, nor);
|
||||
|
||||
// Both blocks can move north
|
||||
// Block 1: newly occupied (1,1), source (1,2) = 1+2*6 = 13
|
||||
// Block 2: newly occupied (4,2), source (4,3) = 4+3*6 = 22
|
||||
std::set<uint8_t> move_set;
|
||||
std::set<u8> move_set;
|
||||
for (int i = 0; i < puzzle::MAX_BLOCKS && moves[i] != 0xFF; ++i) {
|
||||
move_set.insert(moves[i]);
|
||||
}
|
||||
@ -617,7 +617,7 @@ TEST_CASE("restricted_bitmap_get_moves multiple blocks can move", "[get_moves]")
|
||||
TEST_CASE("try_move_block_at basic moves", "[move]")
|
||||
{
|
||||
puzzle p(4, 4, 0, 0, false, false);
|
||||
auto p2 = p.try_add_block(puzzle::block(1, 1, 1, 1));
|
||||
auto p2 = p.try_add_block(block(1, 1, 1, 1));
|
||||
REQUIRE(p2);
|
||||
|
||||
auto north = p2->try_move_block_at(1, 1, nor);
|
||||
@ -640,7 +640,7 @@ TEST_CASE("try_move_block_at basic moves", "[move]")
|
||||
TEST_CASE("try_move_block_at blocked by edge", "[move]")
|
||||
{
|
||||
puzzle p(4, 4, 0, 0, false, false);
|
||||
auto p2 = p.try_add_block(puzzle::block(0, 0, 1, 1));
|
||||
auto p2 = p.try_add_block(block(0, 0, 1, 1));
|
||||
REQUIRE(p2);
|
||||
|
||||
CHECK_FALSE(p2->try_move_block_at(0, 0, nor).has_value());
|
||||
@ -650,9 +650,9 @@ TEST_CASE("try_move_block_at blocked by edge", "[move]")
|
||||
TEST_CASE("try_move_block_at blocked by collision", "[move]")
|
||||
{
|
||||
puzzle p(4, 4, 0, 0, false, false);
|
||||
auto p2 = p.try_add_block(puzzle::block(0, 0, 1, 1));
|
||||
auto p2 = p.try_add_block(block(0, 0, 1, 1));
|
||||
REQUIRE(p2);
|
||||
auto p3 = p2->try_add_block(puzzle::block(1, 0, 1, 1));
|
||||
auto p3 = p2->try_add_block(block(1, 0, 1, 1));
|
||||
REQUIRE(p3);
|
||||
|
||||
CHECK_FALSE(p3->try_move_block_at(0, 0, eas).has_value());
|
||||
@ -661,12 +661,12 @@ TEST_CASE("try_move_block_at blocked by collision", "[move]")
|
||||
TEST_CASE("try_move_block_at_fast matches try_move_block_at", "[move]")
|
||||
{
|
||||
puzzle p(5, 5, 0, 0, false, false);
|
||||
auto p2 = p.try_add_block(puzzle::block(1, 1, 2, 1));
|
||||
auto p2 = p.try_add_block(block(1, 1, 2, 1));
|
||||
REQUIRE(p2);
|
||||
auto p3 = p2->try_add_block(puzzle::block(0, 3, 1, 2));
|
||||
auto p3 = p2->try_add_block(block(0, 3, 1, 2));
|
||||
REQUIRE(p3);
|
||||
|
||||
uint64_t bm = p3->blocks_bitmap();
|
||||
u64 bm = p3->blocks_bitmap();
|
||||
|
||||
for (const dir d : {nor, eas, sou, wes}) {
|
||||
// Block 0 is the first in sorted order
|
||||
@ -686,7 +686,7 @@ TEST_CASE("try_move_block_at restricted mode respects principal dirs", "[move]")
|
||||
{
|
||||
puzzle p(5, 5, 0, 0, true, false); // restricted
|
||||
// Horizontal block 2x1 at (1,2)
|
||||
auto p2 = p.try_add_block(puzzle::block(1, 2, 2, 1));
|
||||
auto p2 = p.try_add_block(block(1, 2, 2, 1));
|
||||
REQUIRE(p2);
|
||||
|
||||
// In restricted mode, horizontal block can only move east/west
|
||||
@ -760,16 +760,16 @@ static auto collect_adjacent_simple(const puzzle& p) -> std::set<size_t>
|
||||
// Helper: collect all adjacent states using for_each_adjacent_restricted
|
||||
static auto collect_adjacent_restricted(const puzzle& p) -> std::set<size_t>
|
||||
{
|
||||
const uint8_t w = p.get_width();
|
||||
std::array<uint8_t, 64> bitmap_block_indices{};
|
||||
const u8 w = p.get_width();
|
||||
std::array<u8, 64> bitmap_block_indices{};
|
||||
for (size_t i = 0; i < puzzle::MAX_BLOCKS; ++i) {
|
||||
const puzzle::block b(p.repr_view().data()[i]);
|
||||
const block b(p.repr_view().data()[i]);
|
||||
if (i >= static_cast<size_t>(p.block_count())) {
|
||||
break;
|
||||
}
|
||||
const auto [bx, by, bw, bh, bt, bi] = b.unpack_repr();
|
||||
for (uint8_t x = bx; x < bx + bw; ++x) {
|
||||
for (uint8_t y = by; y < by + bh; ++y) {
|
||||
for (u8 x = bx; x < bx + bw; ++x) {
|
||||
for (u8 y = by; y < by + bh; ++y) {
|
||||
bitmap_block_indices[y * w + x] = i;
|
||||
}
|
||||
}
|
||||
@ -786,7 +786,7 @@ static auto collect_adjacent_restricted(const puzzle& p) -> std::set<size_t>
|
||||
TEST_CASE("for_each_adjacent_restricted matches for_each_adjacent - single block", "[cross_validate]")
|
||||
{
|
||||
puzzle p(4, 4, 0, 0, true, false);
|
||||
auto p2 = p.try_add_block(puzzle::block(1, 1, 1, 1));
|
||||
auto p2 = p.try_add_block(block(1, 1, 1, 1));
|
||||
REQUIRE(p2);
|
||||
|
||||
auto simple = collect_adjacent_simple(*p2);
|
||||
@ -798,7 +798,7 @@ TEST_CASE("for_each_adjacent_restricted matches for_each_adjacent - single block
|
||||
TEST_CASE("for_each_adjacent_restricted matches for_each_adjacent - horizontal block", "[cross_validate]")
|
||||
{
|
||||
puzzle p(5, 4, 0, 0, true, false);
|
||||
auto p2 = p.try_add_block(puzzle::block(1, 1, 2, 1));
|
||||
auto p2 = p.try_add_block(block(1, 1, 2, 1));
|
||||
REQUIRE(p2);
|
||||
|
||||
auto simple = collect_adjacent_simple(*p2);
|
||||
@ -810,7 +810,7 @@ TEST_CASE("for_each_adjacent_restricted matches for_each_adjacent - horizontal b
|
||||
TEST_CASE("for_each_adjacent_restricted matches for_each_adjacent - vertical block", "[cross_validate]")
|
||||
{
|
||||
puzzle p(4, 5, 0, 0, true, false);
|
||||
auto p2 = p.try_add_block(puzzle::block(1, 1, 1, 2));
|
||||
auto p2 = p.try_add_block(block(1, 1, 1, 2));
|
||||
REQUIRE(p2);
|
||||
|
||||
auto simple = collect_adjacent_simple(*p2);
|
||||
@ -822,9 +822,9 @@ TEST_CASE("for_each_adjacent_restricted matches for_each_adjacent - vertical blo
|
||||
TEST_CASE("for_each_adjacent_restricted matches for_each_adjacent - two blocks", "[cross_validate]")
|
||||
{
|
||||
puzzle p(5, 5, 0, 0, true, false);
|
||||
auto p2 = p.try_add_block(puzzle::block(0, 0, 2, 1)); // horizontal
|
||||
auto p2 = p.try_add_block(block(0, 0, 2, 1)); // horizontal
|
||||
REQUIRE(p2);
|
||||
auto p3 = p2->try_add_block(puzzle::block(4, 1, 1, 2)); // vertical
|
||||
auto p3 = p2->try_add_block(block(4, 1, 1, 2)); // vertical
|
||||
REQUIRE(p3);
|
||||
|
||||
auto simple = collect_adjacent_simple(*p3);
|
||||
@ -836,13 +836,13 @@ TEST_CASE("for_each_adjacent_restricted matches for_each_adjacent - two blocks",
|
||||
TEST_CASE("for_each_adjacent_restricted matches for_each_adjacent - crowded board", "[cross_validate]")
|
||||
{
|
||||
puzzle p(5, 5, 0, 0, true, false);
|
||||
auto p2 = p.try_add_block(puzzle::block(0, 0, 2, 1));
|
||||
auto p2 = p.try_add_block(block(0, 0, 2, 1));
|
||||
REQUIRE(p2);
|
||||
auto p3 = p2->try_add_block(puzzle::block(3, 0, 1, 2));
|
||||
auto p3 = p2->try_add_block(block(3, 0, 1, 2));
|
||||
REQUIRE(p3);
|
||||
auto p4 = p3->try_add_block(puzzle::block(0, 2, 1, 3));
|
||||
auto p4 = p3->try_add_block(block(0, 2, 1, 3));
|
||||
REQUIRE(p4);
|
||||
auto p5 = p4->try_add_block(puzzle::block(2, 3, 2, 1));
|
||||
auto p5 = p4->try_add_block(block(2, 3, 2, 1));
|
||||
REQUIRE(p5);
|
||||
|
||||
auto simple = collect_adjacent_simple(*p5);
|
||||
@ -854,13 +854,13 @@ TEST_CASE("for_each_adjacent_restricted matches for_each_adjacent - crowded boar
|
||||
TEST_CASE("for_each_adjacent_restricted matches for_each_adjacent - 6x6 board", "[cross_validate]")
|
||||
{
|
||||
puzzle p(6, 6, 0, 0, true, false);
|
||||
auto p2 = p.try_add_block(puzzle::block(0, 0, 3, 1)); // horizontal
|
||||
auto p2 = p.try_add_block(block(0, 0, 3, 1)); // horizontal
|
||||
REQUIRE(p2);
|
||||
auto p3 = p2->try_add_block(puzzle::block(5, 0, 1, 2)); // vertical
|
||||
auto p3 = p2->try_add_block(block(5, 0, 1, 2)); // vertical
|
||||
REQUIRE(p3);
|
||||
auto p4 = p3->try_add_block(puzzle::block(2, 2, 1, 2)); // vertical
|
||||
auto p4 = p3->try_add_block(block(2, 2, 1, 2)); // vertical
|
||||
REQUIRE(p4);
|
||||
auto p5 = p4->try_add_block(puzzle::block(3, 2, 2, 1)); // horizontal
|
||||
auto p5 = p4->try_add_block(block(3, 2, 2, 1)); // horizontal
|
||||
REQUIRE(p5);
|
||||
|
||||
auto simple = collect_adjacent_simple(*p5);
|
||||
@ -889,7 +889,7 @@ TEST_CASE("for_each_adjacent_restricted matches for_each_adjacent - the main puz
|
||||
TEST_CASE("explore_state_space simple case - single 1x1 block", "[explore]")
|
||||
{
|
||||
puzzle p(3, 3, 0, 0, false, false);
|
||||
auto p2 = p.try_add_block(puzzle::block(1, 1, 1, 1));
|
||||
auto p2 = p.try_add_block(block(1, 1, 1, 1));
|
||||
REQUIRE(p2);
|
||||
|
||||
auto [states, transitions] = p2->explore_state_space();
|
||||
@ -901,7 +901,7 @@ TEST_CASE("explore_state_space simple case - single 1x1 block", "[explore]")
|
||||
TEST_CASE("explore_state_space restricted - vertical block", "[explore]")
|
||||
{
|
||||
puzzle p(3, 4, 0, 0, true, false);
|
||||
auto p2 = p.try_add_block(puzzle::block(1, 0, 1, 2));
|
||||
auto p2 = p.try_add_block(block(1, 0, 1, 2));
|
||||
REQUIRE(p2);
|
||||
|
||||
auto [states, transitions] = p2->explore_state_space();
|
||||
@ -914,7 +914,7 @@ TEST_CASE("explore_state_space restricted - vertical block", "[explore]")
|
||||
TEST_CASE("explore_state_space restricted - horizontal block", "[explore]")
|
||||
{
|
||||
puzzle p(4, 3, 0, 0, true, false);
|
||||
auto p2 = p.try_add_block(puzzle::block(0, 1, 2, 1));
|
||||
auto p2 = p.try_add_block(block(0, 1, 2, 1));
|
||||
REQUIRE(p2);
|
||||
|
||||
auto [states, transitions] = p2->explore_state_space();
|
||||
@ -927,7 +927,7 @@ TEST_CASE("explore_state_space restricted - horizontal block", "[explore]")
|
||||
TEST_CASE("explore_state_space restricted - square block", "[explore]")
|
||||
{
|
||||
puzzle p(3, 3, 0, 0, true, false);
|
||||
auto p2 = p.try_add_block(puzzle::block(0, 0, 1, 1));
|
||||
auto p2 = p.try_add_block(block(0, 0, 1, 1));
|
||||
REQUIRE(p2);
|
||||
|
||||
auto [states, transitions] = p2->explore_state_space();
|
||||
@ -939,7 +939,7 @@ TEST_CASE("explore_state_space restricted - square block", "[explore]")
|
||||
TEST_CASE("explore_state_space preserves board metadata", "[explore]")
|
||||
{
|
||||
puzzle p(5, 4, 2, 1, true, true);
|
||||
auto p2 = p.try_add_block(puzzle::block(0, 0, 1, 1, true));
|
||||
auto p2 = p.try_add_block(block(0, 0, 1, 1, true));
|
||||
REQUIRE(p2);
|
||||
|
||||
auto [states, transitions] = p2->explore_state_space();
|
||||
@ -959,9 +959,9 @@ TEST_CASE("explore_state_space two blocks restricted", "[explore]")
|
||||
// 4x4 board, restricted
|
||||
// Horizontal 2x1 at (0,0) and vertical 1x2 at (3,0)
|
||||
puzzle p(4, 4, 0, 0, true, false);
|
||||
auto p2 = p.try_add_block(puzzle::block(0, 0, 2, 1));
|
||||
auto p2 = p.try_add_block(block(0, 0, 2, 1));
|
||||
REQUIRE(p2);
|
||||
auto p3 = p2->try_add_block(puzzle::block(3, 0, 1, 2));
|
||||
auto p3 = p2->try_add_block(block(3, 0, 1, 2));
|
||||
REQUIRE(p3);
|
||||
|
||||
auto [states, transitions] = p3->explore_state_space();
|
||||
@ -988,15 +988,15 @@ TEST_CASE("explore_state_space two blocks restricted", "[explore]")
|
||||
|
||||
TEST_CASE("East move does not wrap for various board widths", "[bitmap_move]")
|
||||
{
|
||||
for (uint8_t w = 3; w <= 8; ++w) {
|
||||
for (u8 w = 3; w <= 8; ++w) {
|
||||
puzzle p(w, 3, 0, 0, true, false);
|
||||
// Place a 1x1 block at rightmost column, row 0
|
||||
auto p2 = p.try_add_block(puzzle::block(w - 1, 0, 1, 1));
|
||||
auto p2 = p.try_add_block(block(w - 1, 0, 1, 1));
|
||||
REQUIRE(p2);
|
||||
|
||||
uint64_t combined = p2->blocks_bitmap();
|
||||
uint64_t horiz = p2->blocks_bitmap_h(); // 1x1 is square, so in both h and v
|
||||
uint64_t bm = combined;
|
||||
u64 combined = p2->blocks_bitmap();
|
||||
u64 horiz = p2->blocks_bitmap_h(); // 1x1 is square, so in both h and v
|
||||
u64 bm = combined;
|
||||
|
||||
p2->bitmap_newly_occupied_after_move(bm, horiz, eas);
|
||||
|
||||
@ -1009,15 +1009,15 @@ TEST_CASE("East move does not wrap for various board widths", "[bitmap_move]")
|
||||
|
||||
TEST_CASE("West move does not wrap for various board widths", "[bitmap_move]")
|
||||
{
|
||||
for (uint8_t w = 3; w <= 8; ++w) {
|
||||
for (u8 w = 3; w <= 8; ++w) {
|
||||
puzzle p(w, 3, 0, 0, true, false);
|
||||
// Place a 1x1 block at leftmost column, row 1
|
||||
auto p2 = p.try_add_block(puzzle::block(0, 1, 1, 1));
|
||||
auto p2 = p.try_add_block(block(0, 1, 1, 1));
|
||||
REQUIRE(p2);
|
||||
|
||||
uint64_t combined = p2->blocks_bitmap();
|
||||
uint64_t horiz = p2->blocks_bitmap_h();
|
||||
uint64_t bm = combined;
|
||||
u64 combined = p2->blocks_bitmap();
|
||||
u64 horiz = p2->blocks_bitmap_h();
|
||||
u64 bm = combined;
|
||||
|
||||
p2->bitmap_newly_occupied_after_move(bm, horiz, wes);
|
||||
|
||||
@ -1034,34 +1034,34 @@ TEST_CASE("West move does not wrap for various board widths", "[bitmap_move]")
|
||||
TEST_CASE("bitmap_check_collision detects collision", "[collision]")
|
||||
{
|
||||
puzzle p(5, 5, 0, 0, false, false);
|
||||
auto p2 = p.try_add_block(puzzle::block(2, 2, 1, 1));
|
||||
auto p2 = p.try_add_block(block(2, 2, 1, 1));
|
||||
REQUIRE(p2);
|
||||
auto p3 = p2->try_add_block(puzzle::block(0, 0, 2, 1));
|
||||
auto p3 = p2->try_add_block(block(0, 0, 2, 1));
|
||||
REQUIRE(p3);
|
||||
|
||||
uint64_t bm = p3->blocks_bitmap();
|
||||
u64 bm = p3->blocks_bitmap();
|
||||
|
||||
// Check if a hypothetical block at (1,2) 2x1 collides
|
||||
puzzle::block hyp(1, 2, 2, 1);
|
||||
block hyp(1, 2, 2, 1);
|
||||
CHECK(p3->bitmap_check_collision(bm, hyp)); // overlaps with (2,2)
|
||||
|
||||
// Check if a hypothetical block at (3,3) 1x1 collides
|
||||
puzzle::block hyp2(3, 3, 1, 1);
|
||||
block hyp2(3, 3, 1, 1);
|
||||
CHECK_FALSE(p3->bitmap_check_collision(bm, hyp2));
|
||||
}
|
||||
|
||||
TEST_CASE("bitmap_check_collision directional", "[collision]")
|
||||
{
|
||||
puzzle p(5, 5, 0, 0, false, false);
|
||||
auto p2 = p.try_add_block(puzzle::block(2, 0, 1, 1));
|
||||
auto p2 = p.try_add_block(block(2, 0, 1, 1));
|
||||
REQUIRE(p2);
|
||||
auto p3 = p2->try_add_block(puzzle::block(2, 1, 1, 1));
|
||||
auto p3 = p2->try_add_block(block(2, 1, 1, 1));
|
||||
REQUIRE(p3);
|
||||
|
||||
uint64_t bm = p3->blocks_bitmap();
|
||||
u64 bm = p3->blocks_bitmap();
|
||||
|
||||
// Clear block at (2,1) from bitmap to check if (2,1) can move north
|
||||
puzzle::block b(2, 1, 1, 1);
|
||||
block b(2, 1, 1, 1);
|
||||
p3->bitmap_clear_block(bm, b);
|
||||
|
||||
// (2,1) moving north -> check (2,0) which has a block
|
||||
|
||||
Reference in New Issue
Block a user