diff --git a/CMakeLists.txt b/CMakeLists.txt index 4062036..3ae3f0a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 @@ -133,4 +134,4 @@ if(supported) set_property(TARGET masssprings PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE) else() message(STATUS "IPO / LTO not supported") -endif() +endif() \ No newline at end of file diff --git a/include/bits.hpp b/include/bits.hpp new file mode 100644 index 0000000..994606c --- /dev/null +++ b/include/bits.hpp @@ -0,0 +1,69 @@ +#ifndef BITS_HPP_ +#define BITS_HPP_ + +#include "util.hpp" + +#include + +template + requires std::unsigned_integral +// 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(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 + requires std::unsigned_integral +// 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(first, last); + + bits = bits & ~mask; +} + +template + requires std::unsigned_integral && std::unsigned_integral +// 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(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(value) << first) & mask); +} + +template + requires std::unsigned_integral +// 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(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 \ No newline at end of file diff --git a/include/puzzle.hpp b/include/puzzle.hpp index 7be43c3..2df39eb 100644 --- a/include/puzzle.hpp +++ b/include/puzzle.hpp @@ -3,6 +3,7 @@ #include "config.hpp" #include "util.hpp" +#include "bits.hpp" #include #include @@ -418,12 +419,12 @@ public: [[nodiscard]] auto try_toggle_wall(uint8_t x, uint8_t y) const -> std::optional; // Playing - [[nodiscard]] auto try_move_block_at(uint8_t x, uint8_t y, direction dir) const -> std::optional; + [[nodiscard]] auto try_move_block_at(uint8_t x, uint8_t y, dir dir) const -> std::optional; // 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; [[nodiscard]] static auto sorted_replace(std::array 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 // 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 { 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(); diff --git a/include/util.hpp b/include/util.hpp index a5469d3..6bcf2ec 100644 --- a/include/util.hpp +++ b/include/util.hpp @@ -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(bold_bright), static_cast(color)); -} - -inline auto ansi_reset() -> std::string -{ - return std::format("\033[{}m", static_cast(reset)); -} - -// Bit shifting + masking - -template - requires std::unsigned_integral -// 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(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 - requires std::unsigned_integral -// 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(first, last); - - bits = bits & ~mask; -} - -template - requires std::unsigned_integral && std::unsigned_integral -// 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(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(value) << first) & mask); -} - -template - requires std::unsigned_integral -// 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(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(ctrl::bold_bright), static_cast(color)); +} + +inline auto ansi_reset() -> std::string +{ + return std::format("\033[{}m", static_cast(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 auto traceln(std::format_string 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)...) << std::endl; } template auto infoln(std::format_string 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)...) << std::endl; } template auto warnln(std::format_string 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)...) << std::endl; } template auto errln(std::format_string 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)...) << 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(get_bits(bitmap, y * w + x, y * h + x)) << " "; - } - std::cout << "\n"; - } - std::cout << std::flush; - traceln("{}", std::string(2 * w - 1, '=')); -} - #endif \ No newline at end of file diff --git a/src/bits.cpp b/src/bits.cpp new file mode 100644 index 0000000..3677139 --- /dev/null +++ b/src/bits.cpp @@ -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(get_bits(bitmap, y * w + x, y * h + x)) << " "; + } + std::cout << "\n"; + } + std::cout << std::flush; + traceln("{}", std::string(2 * w - 1, '=')); +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 187cf55..d84a1f7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -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* const> thread_pool = &threads; constexpr std::optional* 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; @@ -231,21 +230,21 @@ enum class runmode auto argparse(const int argc, char* argv[]) -> runmode { -#ifndef WIN32 + #ifndef WIN32 po::options_description desc("Allowed options"); - desc.add_options() // - ("help", "produce help message") // - - ("presets", po::value()->default_value("default.puzzle"), "load presets from file") // - - ("output", po::value()->default_value("clusters.puzzle"), "output file for generated clusters") // - ("space", po::value()->value_name("rh|klotski"), "generate puzzle space with ruleset") // - ("w", po::value()->default_value(6), "board width") // - ("h", po::value()->default_value(6), "board height") // - ("gx", po::value()->default_value(4), "board goal horizontal position") // - ("gy", po::value()->default_value(2), "board goal vertical position") // - ("free", "allow free block movement") // - ("blocks", po::value()->default_value(5), "block limit for puzzle space generation") // + desc.add_options() // + ("help", "produce help message") // + ("presets", po::value()->default_value(preset_file), "load presets from file") // + ("output", po::value()->default_value(output_file), "output file for generated clusters") // + ("space", po::value()->value_name("rh|klotski"), "generate puzzle space with ruleset") // + ("w", po::value()->default_value(board_width)->value_name("[3, 8]"), "board width") // + ("h", po::value()->default_value(board_height)->value_name("[3, 8"), "board height") // + ("gx", po::value()->default_value(goal_x)->value_name("[0, w-1]"), "board goal horizontal position") // + ("gy", po::value()->default_value(goal_y)->value_name("[0, h-1]"), "board goal vertical position") // + ("free", "allow free block movement") // + ("blocks", + po::value()->default_value(max_blocks)->value_name("[1, 15]"), + "block limit for puzzle space generation") // ; po::positional_options_description positional; @@ -308,7 +307,7 @@ auto argparse(const int argc, char* argv[]) -> runmode if (vm.contains("presets")) { preset_file = vm["presets"].as(); } -#endif + #endif return runmode::USER_INTERFACE; } @@ -346,4 +345,4 @@ auto main(const int argc, char* argv[]) -> int }; return 1; -} +} \ No newline at end of file diff --git a/src/puzzle.cpp b/src/puzzle.cpp index 143b50e..7d1fb7d 100644 --- a/src/puzzle.cpp +++ b/src/puzzle.cpp @@ -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 +auto puzzle::try_move_block_at(const uint8_t x, const uint8_t y, const dir dir) const -> std::optional { const std::optional b = try_get_block(x, y); const auto [bx, by, bw, bh, bt, bi] = b->unpack_repr(); diff --git a/test/bits.cpp b/test/bits.cpp index e7141bc..9a63b55 100644 --- a/test/bits.cpp +++ b/test/bits.cpp @@ -2,7 +2,7 @@ #include #include -#include "util.hpp" +#include "bits.hpp" // ============================================================================= // Catch2 diff --git a/test/puzzle.cpp b/test/puzzle.cpp index c9832db..5093a8c 100644 --- a/test/puzzle.cpp +++ b/test/puzzle.cpp @@ -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);