move bits functions to separate file + fix missing defaults with disabled program_options on windows

This commit is contained in:
2026-03-05 19:13:44 +01:00
parent d4f83e11db
commit 025cbfdf3b
9 changed files with 166 additions and 155 deletions

View File

@ -18,6 +18,7 @@ option(DISABLE_BENCH "Disable building benchmarks" OFF)
# Headers + Sources (excluding main.cpp)
set(SOURCES
src/backward.cpp
src/bits.cpp
src/graph_distances.cpp
src/input_handler.cpp
src/load_save.cpp

69
include/bits.hpp Normal file
View File

@ -0,0 +1,69 @@
#ifndef BITS_HPP_
#define BITS_HPP_
#include "util.hpp"
#include <concepts>
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
{
// If the mask width is equal the type width return all 1s instead of shifting
// as shifting by type-width is undefined behavior.
if (static_cast<size_t>(last - first + 1) >= sizeof(T) * 8) {
return ~T{0};
}
// Example: first=4, last=7, 7-4+1=4
// 1 << 4 = 0b00010000
// 32 - 1 = 0b00001111
// 31 << 4 = 0b11110000
// Subtracting 1 generates a consecutive mask.
return ((T{1} << (last - first + 1)) - 1) << first;
}
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
{
const T mask = create_mask<T>(first, last);
bits = bits & ~mask;
}
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
{
const T mask = create_mask<T>(first, last);
// Example: first=4, last=6, value=0b1110, bits = 0b 01111110
// mask = 0b 01110000
// bits & ~mask = 0b 00001110
// value << 4 = 0b 11100000
// (value << 4) & mask = 0b 01100000
// (bits & ~mask) | (value << 4) & mask = 0b 01101110
// Insert position: ^^^
// First clear the bits, then | with the value positioned at the insertion point.
// The value may be larger than [first, last], extra bits are ignored.
bits = (bits & ~mask) | ((static_cast<T>(value) << first) & mask);
}
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
{
const T mask = create_mask<T>(first, last);
// We can >> without sign extension because T is unsigned_integral
return (bits & mask) >> first;
}
auto print_bitmap(uint64_t bitmap, uint8_t w, uint8_t h, const std::string& title) -> void;
#endif

View File

@ -3,6 +3,7 @@
#include "config.hpp"
#include "util.hpp"
#include "bits.hpp"
#include <array>
#include <cstddef>
@ -418,12 +419,12 @@ public:
[[nodiscard]] auto try_toggle_wall(uint8_t x, uint8_t y) const -> std::optional<puzzle>;
// Playing
[[nodiscard]] auto try_move_block_at(uint8_t x, uint8_t y, direction dir) const -> std::optional<puzzle>;
[[nodiscard]] auto try_move_block_at(uint8_t x, uint8_t y, dir dir) const -> std::optional<puzzle>;
// Statespace
[[nodiscard]] INLINE inline auto try_move_block_at_fast(uint64_t bitmap,
uint8_t block_idx,
direction dir,
dir dir,
bool check_collision = true) const -> std::optional<puzzle>;
[[nodiscard]] static auto sorted_replace(std::array<uint16_t, MAX_BLOCKS> blocks,
uint8_t idx,
@ -456,7 +457,7 @@ public:
* @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, direction dir) const -> bool;
[[nodiscard]] INLINE inline auto bitmap_check_collision(uint64_t bitmap, block b, dir dir) const -> bool;
template <typename F>
// ReSharper disable once CppRedundantInlineSpecifier
@ -473,7 +474,7 @@ public:
continue;
}
const int dirs = r ? b.principal_dirs() : nor | eas | sou | wes;
for (const direction d : {nor, eas, sou, wes}) {
for (const dir d : {nor, eas, sou, wes}) {
if (dirs & d) {
if (auto moved = try_move_block_at_fast(bitmap, idx, d)) {
callback(*moved);
@ -717,7 +718,7 @@ inline auto puzzle::get_goal_y() const -> uint8_t
INLINE inline auto puzzle::try_move_block_at_fast(uint64_t bitmap,
const uint8_t block_idx,
const direction dir,
const dir dir,
const bool check_collision) const -> std::optional<puzzle>
{
const block b = block(repr.cooked.blocks[block_idx]);
@ -853,7 +854,7 @@ INLINE inline auto puzzle::bitmap_check_collision(const uint64_t bitmap, const b
INLINE inline auto puzzle::bitmap_check_collision(const uint64_t bitmap,
const block b,
const direction dir) const -> bool
const dir dir) const -> bool
{
const auto [x, y, w, h, t, i] = b.unpack_repr();
const uint8_t width = get_width();

View File

@ -7,112 +7,6 @@
#define INLINE __attribute__((always_inline))
#define PACKED __attribute__((packed))
enum ctrl
{
reset = 0,
bold_bright = 1,
underline = 4,
inverse = 7,
bold_bright_off = 21,
underline_off = 24,
inverse_off = 27
};
enum fg
{
fg_black = 30,
fg_red = 31,
fg_green = 32,
fg_yellow = 33,
fg_blue = 34,
fg_magenta = 35,
fg_cyan = 36,
fg_white = 37
};
enum bg
{
bg_black = 40,
bg_red = 41,
bg_green = 42,
bg_yellow = 43,
bg_blue = 44,
bg_magenta = 45,
bg_cyan = 46,
bg_white = 47
};
inline auto ansi_bold_fg(const fg color) -> std::string
{
return std::format("\033[{};{}m", static_cast<int>(bold_bright), static_cast<int>(color));
}
inline auto ansi_reset() -> std::string
{
return std::format("\033[{}m", static_cast<int>(reset));
}
// Bit shifting + masking
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
{
// If the mask width is equal the type width return all 1s instead of shifting
// as shifting by type-width is undefined behavior.
if (static_cast<size_t>(last - first + 1) >= sizeof(T) * 8) {
return ~T{0};
}
// Example: first=4, last=7, 7-4+1=4
// 1 << 4 = 0b00010000
// 32 - 1 = 0b00001111
// 31 << 4 = 0b11110000
// Subtracting 1 generates a consecutive mask.
return ((T{1} << (last - first + 1)) - 1) << first;
}
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
{
const T mask = create_mask<T>(first, last);
bits = bits & ~mask;
}
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
{
const T mask = create_mask<T>(first, last);
// Example: first=4, last=6, value=0b1110, bits = 0b 01111110
// mask = 0b 01110000
// bits & ~mask = 0b 00001110
// value << 4 = 0b 11100000
// (value << 4) & mask = 0b 01100000
// (bits & ~mask) | (value << 4) & mask = 0b 01101110
// Insert position: ^^^
// First clear the bits, then | with the value positioned at the insertion point.
// The value may be larger than [first, last], extra bits are ignored.
bits = (bits & ~mask) | ((static_cast<T>(value) << first) & mask);
}
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
{
const T mask = create_mask<T>(first, last);
// We can >> without sign extension because T is unsigned_integral
return (bits & mask) >> first;
}
// std::variant visitor
// https://en.cppreference.com/w/cpp/utility/variant/visit
@ -124,7 +18,7 @@ struct overloads : Ts...
// Enums
enum direction
enum dir : uint8_t
{
nor = 1 << 0,
eas = 1 << 1,
@ -132,6 +26,53 @@ enum direction
wes = 1 << 3,
};
// Ansi
enum class ctrl : uint8_t
{
reset = 0,
bold_bright = 1,
underline = 4,
inverse = 7,
bold_bright_off = 21,
underline_off = 24,
inverse_off = 27
};
enum class fg : uint8_t
{
black = 30,
red = 31,
green = 32,
yellow = 33,
blue = 34,
magenta = 35,
cyan = 36,
white = 37
};
enum class bg : uint8_t
{
black = 40,
red = 41,
green = 42,
yellow = 43,
blue = 44,
magenta = 45,
cyan = 46,
white = 47
};
inline auto ansi_bold_fg(const fg color) -> std::string
{
return std::format("\033[{};{}m", static_cast<int>(ctrl::bold_bright), static_cast<int>(color));
}
inline auto ansi_reset() -> std::string
{
return std::format("\033[{}m", static_cast<int>(ctrl::reset));
}
// Output
inline auto operator<<(std::ostream& os, const Vector2& v) -> std::ostream&
@ -150,44 +91,29 @@ inline auto operator<<(std::ostream& os, const Vector3& v) -> std::ostream&
template <typename... Args>
auto traceln(std::format_string<Args...> fmt, Args&&... args) -> void
{
std::cout << std::format("[{}TRACE{}]: ", ansi_bold_fg(fg_cyan), ansi_reset()) << std::format(
std::cout << std::format("[{}TRACE{}]: ", ansi_bold_fg(fg::cyan), ansi_reset()) << std::format(
fmt, std::forward<Args>(args)...) << std::endl;
}
template <typename... Args>
auto infoln(std::format_string<Args...> fmt, Args&&... args) -> void
{
std::cout << std::format("[{}INFO{}]: ", ansi_bold_fg(fg_blue), ansi_reset()) << std::format(
std::cout << std::format("[{}INFO{}]: ", ansi_bold_fg(fg::blue), ansi_reset()) << std::format(
fmt, std::forward<Args>(args)...) << std::endl;
}
template <typename... Args>
auto warnln(std::format_string<Args...> fmt, Args&&... args) -> void
{
std::cout << std::format("[{}WARNING{}]: ", ansi_bold_fg(fg_yellow), ansi_reset()) << std::format(
std::cout << std::format("[{}WARNING{}]: ", ansi_bold_fg(fg::yellow), ansi_reset()) << std::format(
fmt, std::forward<Args>(args)...) << std::endl;
}
template <typename... Args>
auto errln(std::format_string<Args...> fmt, Args&&... args) -> void
{
std::cout << std::format("[{}ERROR{}]: ", ansi_bold_fg(fg_red), ansi_reset()) << std::format(
std::cout << std::format("[{}ERROR{}]: ", ansi_bold_fg(fg::red), ansi_reset()) << std::format(
fmt, std::forward<Args>(args)...) << std::endl;
}
inline auto print_bitmap(const uint64_t bitmap, const uint8_t w, const uint8_t h, const std::string& title) -> void
{
traceln("{}:", title);
traceln("{}", std::string(2 * w - 1, '='));
for (size_t y = 0; y < w; ++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)) << " ";
}
std::cout << "\n";
}
std::cout << std::flush;
traceln("{}", std::string(2 * w - 1, '='));
}
#endif

15
src/bits.cpp Normal file
View File

@ -0,0 +1,15 @@
#include "bits.hpp"
auto print_bitmap(const uint64_t bitmap, const uint8_t w, const uint8_t h, const std::string& title) -> void {
traceln("{}:", title);
traceln("{}", std::string(2 * w - 1, '='));
for (size_t y = 0; y < w; ++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)) << " ";
}
std::cout << "\n";
}
std::cout << std::flush;
traceln("{}", std::string(2 * w - 1, '='));
}

View File

@ -16,7 +16,6 @@
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
@ -47,8 +46,8 @@ constexpr std::optional<BS::thread_pool<>* const> thread_pool = &threads;
constexpr std::optional<BS::thread_pool<>* const> thread_pool = std::nullopt;
#endif
std::string preset_file;
std::string output_file;
std::string preset_file = "default.puzzle";
std::string output_file = "clusters.puzzle";
int max_blocks = 5;
int board_width = 6;
int board_height = 6;
@ -235,17 +234,17 @@ auto argparse(const int argc, char* argv[]) -> runmode
po::options_description desc("Allowed options");
desc.add_options() //
("help", "produce help message") //
("presets", po::value<std::string>()->default_value("default.puzzle"), "load presets from file") //
("output", po::value<std::string>()->default_value("clusters.puzzle"), "output file for generated clusters") //
("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(6), "board width") //
("h", po::value<int>()->default_value(6), "board height") //
("gx", po::value<int>()->default_value(4), "board goal horizontal position") //
("gy", po::value<int>()->default_value(2), "board goal vertical position") //
("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(5), "block limit for puzzle space generation") //
("blocks",
po::value<int>()->default_value(max_blocks)->value_name("[1, 15]"),
"block limit for puzzle space generation") //
;
po::positional_options_description positional;

View File

@ -682,7 +682,7 @@ 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::try_move_block_at(const uint8_t x, const uint8_t y, const direction 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 auto [bx, by, bw, bh, bt, bi] = b->unpack_repr();

View File

@ -2,7 +2,7 @@
#include <catch2/catch_template_test_macros.hpp>
#include <cstdint>
#include "util.hpp"
#include "bits.hpp"
// =============================================================================
// Catch2

View File

@ -668,7 +668,7 @@ TEST_CASE("try_move_block_at_fast matches try_move_block_at", "[move]")
uint64_t bm = p3->blocks_bitmap();
for (const direction d : {nor, eas, sou, wes}) {
for (const dir d : {nor, eas, sou, wes}) {
// Block 0 is the first in sorted order
auto slow = p3->try_move_block_at(1, 1, d);
auto fast = p3->try_move_block_at_fast(bm, 0, d);