Compare commits

..

2 Commits

Author SHA1 Message Date
c8d6541221 add hashset benchmarks 2026-03-05 20:03:26 +01:00
950da499f0 reorder puzzle functions 2026-03-05 20:03:16 +01:00
5 changed files with 305 additions and 200 deletions

2
.gitignore vendored
View File

@ -9,3 +9,5 @@ cmake-build-release
/perf.data /perf.data
/perf.data.old /perf.data.old
/clusters.puzzle /clusters.puzzle
/benchs.json
/benchs.old.json

View File

@ -1,6 +1,10 @@
// ReSharper disable CppTooWideScope
#include "puzzle.hpp" #include "puzzle.hpp"
#include <random>
#include <unordered_set>
#include <benchmark/benchmark.h> #include <benchmark/benchmark.h>
#include <boost/unordered/unordered_flat_map.hpp>
static std::vector<std::string> puzzles = { static std::vector<std::string> puzzles = {
// 0: RushHour 1 // 0: RushHour 1
@ -35,6 +39,113 @@ static std::vector<std::string> puzzles = {
"S:[4x5] G:[1,3] M:[F] B:[{_ 2X2 _ _} {1x1 _ _ 1x1} {1x2 2x1 _ 1x2} {_ 2x1 _ _} {1x1 2x1 _ 1x1}]", "S:[4x5] G:[1,3] M:[F] B:[{_ 2X2 _ _} {1x1 _ _ 1x1} {1x2 2x1 _ 1x2} {_ 2x1 _ _} {1x1 2x1 _ 1x1}]",
}; };
template <uint8_t N>
struct uint_hasher
{
int64_t nums;
auto operator()(const std::array<uint64_t, N>& ints) const noexcept -> size_t
{
size_t h = 0;
for (size_t i = 0; i < N; ++i) {
puzzle::hash_combine(h, ints[i]);
}
return h;
}
};
template <uint8_t 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::unordered_set<std::array<uint64_t, N>, uint_hasher<N>> set;
std::array<uint64_t, N> ints;
for (size_t i = 0; i < N; ++i) {
ints[i] = distribution(generator);
}
for (auto _ : state) {
for (size_t i = 0; i < 100000; ++i) {
set.emplace(ints);
}
benchmark::DoNotOptimize(set);
}
}
template <uint8_t 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()
);
boost::unordered_flat_set<std::array<uint64_t, N>, uint_hasher<N>> set;
std::array<uint64_t, N> ints;
for (size_t i = 0; i < N; ++i) {
ints[i] = distribution(generator);
}
for (auto _ : state) {
for (size_t i = 0; i < 100000; ++i) {
set.emplace(ints);
}
benchmark::DoNotOptimize(set);
}
}
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);
for (auto _ : state) {
for (size_t i = 0; i < 100000; ++i) {
set.emplace(b);
}
benchmark::DoNotOptimize(set);
}
}
static auto unordered_falt_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);
for (auto _ : state) {
for (size_t i = 0; i < 100000; ++i) {
set.emplace(b);
}
benchmark::DoNotOptimize(set);
}
}
static auto unordered_flat_set_puzzle_hasher(benchmark::State& state) -> void
{
boost::unordered_flat_set<puzzle, puzzle_hasher> set;
const puzzle p = puzzle(puzzles[0]);
for (auto _ : state) {
for (size_t i = 0; i < 100000; ++i) {
set.emplace(p);
}
benchmark::DoNotOptimize(set);
}
}
static auto explore_state_space(benchmark::State& state) -> void static auto explore_state_space(benchmark::State& state) -> void
{ {
const puzzle p = puzzle(puzzles[state.range(0)]); const puzzle p = puzzle(puzzles[state.range(0)]);
@ -48,7 +159,6 @@ static auto explore_state_space(benchmark::State& state) -> void
static auto explore_rush_hour_puzzle_space(benchmark::State& state) -> void static auto explore_rush_hour_puzzle_space(benchmark::State& state) -> void
{ {
// ReSharper disable once CppTooWideScope
constexpr uint8_t max_blocks = 5; constexpr uint8_t max_blocks = 5;
constexpr uint8_t board_width = 4; constexpr uint8_t board_width = 4;
@ -85,6 +195,15 @@ static auto explore_rush_hour_puzzle_space(benchmark::State& state) -> void
} }
} }
BENCHMARK(unordered_set_uint64<4>)->Unit(benchmark::kMicrosecond);
BENCHMARK(unordered_set_uint64<8>)->Unit(benchmark::kMicrosecond);
BENCHMARK(unordered_set_uint64<16>)->Unit(benchmark::kMicrosecond);
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_puzzle_hasher)->Unit(benchmark::kMicrosecond);
BENCHMARK(explore_state_space)->DenseRange(0, puzzles.size() - 1)->Unit(benchmark::kMicrosecond); BENCHMARK(explore_state_space)->DenseRange(0, puzzles.size() - 1)->Unit(benchmark::kMicrosecond);
BENCHMARK(explore_rush_hour_puzzle_space)->Unit(benchmark::kSecond); BENCHMARK(explore_rush_hour_puzzle_space)->Unit(benchmark::kSecond);

View File

@ -113,7 +113,7 @@ rec {
abbr -a run "${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 run-clusters "${buildRelease} && ./cmake-build-release/masssprings --output=clusters.puzzle --space=rh --w=6 --h=6 --gx=4 --gy=2 --blocks=4"
abbr -a runtests "${buildDebug} && ./cmake-build-debug/tests" abbr -a runtests "${buildDebug} && ./cmake-build-debug/tests"
abbr -a runbenchs "${buildRelease} && sudo cpupower frequency-set --governor performance && ./cmake-build-release/benchmarks; sudo cpupower frequency-set --governor powersave" abbr -a runbenchs "mv 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" abbr -a rungdb "${buildDebug} && gdb --tui ./cmake-build-debug/masssprings"
abbr -a runvalgrind "${buildDebug} && valgrind --leak-check=full --show-reachable=no --show-leak-kinds=definite,indirect,possible --track-origins=no --suppressions=valgrind.supp --log-file=valgrind.log ./cmake-build-debug/masssprings && cat valgrind.log" abbr -a runvalgrind "${buildDebug} && valgrind --leak-check=full --show-reachable=no --show-leak-kinds=definite,indirect,possible --track-origins=no --suppressions=valgrind.supp --log-file=valgrind.log ./cmake-build-debug/masssprings && cat valgrind.log"
abbr -a runperf "${buildRelease} && perf record -g ./cmake-build-release/masssprings && hotspot ./perf.data" abbr -a runperf "${buildRelease} && perf record -g ./cmake-build-release/masssprings && hotspot ./perf.data"
@ -394,4 +394,4 @@ rec {
}; };
} }
); );
} }

View File

@ -418,6 +418,21 @@ public:
[[nodiscard]] auto try_toggle_target(uint8_t x, uint8_t y) const -> std::optional<puzzle>; [[nodiscard]] auto try_toggle_target(uint8_t x, uint8_t y) const -> std::optional<puzzle>;
[[nodiscard]] auto try_toggle_wall(uint8_t x, uint8_t y) const -> std::optional<puzzle>; [[nodiscard]] auto try_toggle_wall(uint8_t x, uint8_t y) const -> std::optional<puzzle>;
// Bitmap
[[nodiscard]] auto blocks_bitmap() const -> uint64_t;
[[nodiscard]] auto blocks_bitmap_h() const -> uint64_t;
[[nodiscard]] auto blocks_bitmap_v() const -> uint64_t;
static INLINE inline auto bitmap_clear_bit(uint64_t& bitmap, uint8_t w, uint8_t x, uint8_t y) -> void;
static INLINE inline auto bitmap_set_bit(uint64_t& bitmap, uint8_t w, uint8_t x, uint8_t y) -> void;
[[nodiscard]] static INLINE inline auto bitmap_get_bit(uint64_t bitmap, uint8_t w, uint8_t x, uint8_t y) -> bool;
INLINE inline auto bitmap_clear_block(uint64_t& bitmap, block b) const -> void;
INLINE inline auto bitmap_set_block(uint64_t& bitmap, block b) const -> void;
[[nodiscard]] INLINE inline auto bitmap_is_empty(uint64_t bitmap) const -> bool;
[[nodiscard]] INLINE inline auto bitmap_is_full(uint64_t bitmap) const -> bool;
[[nodiscard]] INLINE inline auto bitmap_check_collision(uint64_t bitmap, block b) const -> bool;
[[nodiscard]] INLINE inline auto bitmap_check_collision(uint64_t bitmap, block b, dir dir) const -> bool;
[[nodiscard]] auto bitmap_find_first_empty(uint64_t bitmap, int& x, int& y) const -> bool;
// Playing // Playing
[[nodiscard]] auto try_move_block_at(uint8_t x, uint8_t y, dir dir) const -> std::optional<puzzle>; [[nodiscard]] auto try_move_block_at(uint8_t x, uint8_t y, dir dir) const -> std::optional<puzzle>;
@ -429,35 +444,6 @@ public:
[[nodiscard]] static auto sorted_replace(std::array<uint16_t, MAX_BLOCKS> blocks, [[nodiscard]] static auto sorted_replace(std::array<uint16_t, MAX_BLOCKS> blocks,
uint8_t idx, uint8_t idx,
uint16_t new_val) -> std::array<uint16_t, MAX_BLOCKS>; uint16_t new_val) -> std::array<uint16_t, MAX_BLOCKS>;
[[nodiscard]] auto blocks_bitmap() const -> uint64_t;
[[nodiscard]] auto blocks_bitmap_h() const -> uint64_t;
[[nodiscard]] auto blocks_bitmap_v() const -> uint64_t;
static INLINE inline auto bitmap_clear_bit(uint64_t& bitmap, uint8_t w, uint8_t x, uint8_t y) -> void;
static INLINE inline auto bitmap_set_bit(uint64_t& bitmap, uint8_t w, uint8_t x, uint8_t y) -> void;
[[nodiscard]] static INLINE inline auto bitmap_get_bit(uint64_t bitmap, uint8_t w, uint8_t x, uint8_t y) -> bool;
INLINE inline auto bitmap_clear_block(uint64_t& bitmap, block b) const -> void;
INLINE inline auto bitmap_set_block(uint64_t& bitmap, block b) const -> void;
[[nodiscard]] INLINE inline auto bitmap_is_empty(uint64_t bitmap) const -> bool;
[[nodiscard]] INLINE inline auto bitmap_is_full(uint64_t bitmap) const -> bool;
/**
* Checks if b would collide with any block on the board.
*
* @param bitmap Board occupancy map
* @param b Hypothetical block to check collision with
* @return True if b would collide with any other block on the board
*/
[[nodiscard]] INLINE inline auto bitmap_check_collision(uint64_t bitmap, block b) const -> bool;
/**
* Checks if b would collide with any block on the board after moving in direction dir.
*
* @param bitmap Board occupancy map
* @param b Existing block to check collision with
* @param dir Direction in which the block should be moved
* @return True if b would collide with any other block on the board after moving in direction dir
*/
[[nodiscard]] INLINE inline auto bitmap_check_collision(uint64_t bitmap, block b, dir dir) const -> bool;
template <typename F> template <typename F>
// ReSharper disable once CppRedundantInlineSpecifier // ReSharper disable once CppRedundantInlineSpecifier
@ -491,8 +477,6 @@ public:
// state with the numerically smallest binary representation. // state with the numerically smallest binary representation.
[[nodiscard]] auto get_cluster_id_and_solution() const -> std::pair<puzzle, bool>; [[nodiscard]] auto get_cluster_id_and_solution() const -> std::pair<puzzle, bool>;
[[nodiscard]] auto bitmap_find_first_empty(uint64_t bitmap, int& x, int& y) const -> bool;
static auto generate_block_sequences( static auto generate_block_sequences(
const boost::unordered_flat_set<block, block_hasher2, block_equal2>& permitted_blocks, const boost::unordered_flat_set<block, block_hasher2, block_equal2>& permitted_blocks,
block target_block, block target_block,
@ -716,72 +700,6 @@ inline auto puzzle::get_goal_y() const -> uint8_t
return get_bits(repr.cooked.meta, GOAL_Y_S, GOAL_Y_E); return get_bits(repr.cooked.meta, GOAL_Y_S, GOAL_Y_E);
} }
INLINE inline auto puzzle::try_move_block_at_fast(uint64_t bitmap,
const uint8_t block_idx,
const dir dir,
const bool check_collision) const -> std::optional<puzzle>
{
const block b = block(repr.cooked.blocks[block_idx]);
const auto [bx, by, bw, bh, bt, bi] = b.unpack_repr();
if (bi) {
return std::nullopt;
}
const auto [w, h, gx, gy, r, g] = unpack_meta();
const int dirs = r ? b.principal_dirs() : nor | eas | sou | wes;
// Get target block
int _target_x = bx;
int _target_y = by;
switch (dir) {
case nor:
if (!(dirs & nor) || _target_y < 1) {
return std::nullopt;
}
--_target_y;
break;
case eas:
if (!(dirs & eas) || _target_x + bw >= w) {
return std::nullopt;
}
++_target_x;
break;
case sou:
if (!(dirs & sou) || _target_y + bh >= h) {
return std::nullopt;
}
++_target_y;
break;
case wes:
if (!(dirs & wes) || _target_x < 1) {
return std::nullopt;
}
--_target_x;
break;
}
// Check collisions
if (check_collision) {
bitmap_clear_block(bitmap, b);
if (bitmap_check_collision(bitmap, b, dir)) {
return std::nullopt;
}
}
// Replace block
const std::array<uint16_t, MAX_BLOCKS> blocks = sorted_replace(repr.cooked.blocks,
block_idx,
block::create_repr(
_target_x,
_target_y,
bw,
bh,
bt));
// This constructor doesn't sort
return puzzle(std::make_tuple(w, h, gx, gy, r, g), blocks);
}
INLINE inline auto puzzle::bitmap_clear_bit(uint64_t& bitmap, const uint8_t w, const uint8_t x, const uint8_t y) -> void INLINE inline auto puzzle::bitmap_clear_bit(uint64_t& bitmap, const uint8_t w, const uint8_t x, const uint8_t y) -> void
{ {
set_bits(bitmap, y * w + x, y * w + x, 0u); set_bits(bitmap, y * w + x, y * w + x, 0u);
@ -892,6 +810,72 @@ INLINE inline auto puzzle::bitmap_check_collision(const uint64_t bitmap,
return false; return false;
} }
INLINE inline auto puzzle::try_move_block_at_fast(uint64_t bitmap,
const uint8_t block_idx,
const dir dir,
const bool check_collision) const -> std::optional<puzzle>
{
const block b = block(repr.cooked.blocks[block_idx]);
const auto [bx, by, bw, bh, bt, bi] = b.unpack_repr();
if (bi) {
return std::nullopt;
}
const auto [w, h, gx, gy, r, g] = unpack_meta();
const int dirs = r ? b.principal_dirs() : nor | eas | sou | wes;
// Get target block
int _target_x = bx;
int _target_y = by;
switch (dir) {
case nor:
if (!(dirs & nor) || _target_y < 1) {
return std::nullopt;
}
--_target_y;
break;
case eas:
if (!(dirs & eas) || _target_x + bw >= w) {
return std::nullopt;
}
++_target_x;
break;
case sou:
if (!(dirs & sou) || _target_y + bh >= h) {
return std::nullopt;
}
++_target_y;
break;
case wes:
if (!(dirs & wes) || _target_x < 1) {
return std::nullopt;
}
--_target_x;
break;
}
// Check collisions
if (check_collision) {
bitmap_clear_block(bitmap, b);
if (bitmap_check_collision(bitmap, b, dir)) {
return std::nullopt;
}
}
// Replace block
const std::array<uint16_t, MAX_BLOCKS> blocks = sorted_replace(repr.cooked.blocks,
block_idx,
block::create_repr(
_target_x,
_target_y,
bw,
bh,
bt));
// This constructor doesn't sort
return puzzle(std::make_tuple(w, h, gx, gy, r, g), blocks);
}
#endif #endif
// Hash functions for sets and maps. // Hash functions for sets and maps.

View File

@ -682,6 +682,105 @@ 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); return puzzle(w, h, gx, gy, r, g, blocks);
} }
auto puzzle::blocks_bitmap() const -> uint64_t
{
uint64_t bitmap = 0;
for (uint8_t 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();
for (int dy = 0; dy < h; ++dy) {
for (int dx = 0; dx < w; ++dx) {
bitmap_set_bit(bitmap, width, x + dx, y + dy);
}
}
}
return bitmap;
}
auto puzzle::blocks_bitmap_h() const -> uint64_t
{
uint64_t bitmap = 0;
for (uint8_t i = 0; i < MAX_BLOCKS; ++i) {
block b(repr.cooked.blocks[i]);
if (!b.valid()) {
break;
}
const int dirs = b.principal_dirs();
if (!(dirs & eas)) {
continue;
}
auto [x, y, w, h, t, im] = b.unpack_repr();
const uint8_t width = get_width();
for (int dy = 0; dy < h; ++dy) {
for (int dx = 0; dx < w; ++dx) {
bitmap_set_bit(bitmap, width, x + dx, y + dy);
}
}
}
return bitmap;
}
auto puzzle::blocks_bitmap_v() const -> uint64_t
{
uint64_t bitmap = 0;
for (uint8_t i = 0; i < MAX_BLOCKS; ++i) {
block b(repr.cooked.blocks[i]);
if (!b.valid()) {
break;
}
const int dirs = b.principal_dirs();
if (!(dirs & sou)) {
continue;
}
auto [x, y, w, h, t, im] = b.unpack_repr();
const uint8_t width = get_width();
for (int dy = 0; dy < h; ++dy) {
for (int dx = 0; dx < w; ++dx) {
bitmap_set_bit(bitmap, width, x + dx, y + dy);
}
}
}
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 uint8_t x, const uint8_t y, const dir dir) const -> std::optional<puzzle>
{ {
const std::optional<block> b = try_get_block(x, y); const std::optional<block> b = try_get_block(x, y);
@ -769,81 +868,10 @@ auto puzzle::sorted_replace(std::array<uint16_t, MAX_BLOCKS> blocks,
return blocks; return blocks;
} }
auto puzzle::blocks_bitmap() const -> uint64_t
{
uint64_t bitmap = 0;
for (uint8_t 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();
for (int dy = 0; dy < h; ++dy) {
for (int dx = 0; dx < w; ++dx) {
bitmap_set_bit(bitmap, width, x + dx, y + dy);
}
}
}
return bitmap;
}
auto puzzle::blocks_bitmap_h() const -> uint64_t
{
uint64_t bitmap = 0;
for (uint8_t i = 0; i < MAX_BLOCKS; ++i) {
block b(repr.cooked.blocks[i]);
if (!b.valid()) {
break;
}
const int dirs = b.principal_dirs();
if (!(dirs & eas)) {
continue;
}
auto [x, y, w, h, t, im] = b.unpack_repr();
const uint8_t width = get_width();
for (int dy = 0; dy < h; ++dy) {
for (int dx = 0; dx < w; ++dx) {
bitmap_set_bit(bitmap, width, x + dx, y + dy);
}
}
}
return bitmap;
}
auto puzzle::blocks_bitmap_v() const -> uint64_t
{
uint64_t bitmap = 0;
for (uint8_t i = 0; i < MAX_BLOCKS; ++i) {
block b(repr.cooked.blocks[i]);
if (!b.valid()) {
break;
}
const int dirs = b.principal_dirs();
if (!(dirs & sou)) {
continue;
}
auto [x, y, w, h, t, im] = b.unpack_repr();
const uint8_t width = get_width();
for (int dy = 0; dy < h; ++dy) {
for (int dx = 0; dx < w; ++dx) {
bitmap_set_bit(bitmap, width, x + dx, y + dy);
}
}
}
return bitmap;
}
auto puzzle::explore_state_space() const -> std::pair<std::vector<puzzle>, std::vector<std::pair<size_t, size_t>>> auto puzzle::explore_state_space() const -> std::pair<std::vector<puzzle>, std::vector<std::pair<size_t, size_t>>>
{ {
std::vector<puzzle> state_pool; std::vector<puzzle> state_pool;
boost::unordered_flat_map<puzzle, std::size_t, puzzle_hasher> state_indices; boost::unordered_flat_map<puzzle, size_t, puzzle_hasher> state_indices;
std::vector<std::pair<size_t, size_t>> links; std::vector<std::pair<size_t, size_t>> links;
// Buffer for all states we want to call GetNextStates() on // Buffer for all states we want to call GetNextStates() on
@ -914,34 +942,6 @@ auto puzzle::get_cluster_id_and_solution() const -> std::pair<puzzle, bool>
return {min, solution}; return {min, solution};
} }
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::generate_block_sequences( auto puzzle::generate_block_sequences(
const boost::unordered_flat_set<block, block_hasher2, block_equal2>& permitted_blocks, const boost::unordered_flat_set<block, block_hasher2, block_equal2>& permitted_blocks,
const block target_block, const block target_block,