diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 4013eb9..4f45db7 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -6,4 +6,6 @@ project(klotski-core VERSION 0.1.2 LANGUAGES CXX) add_compile_options(-fno-exceptions) +include_directories(utils) + add_executable(${PROJECT_NAME} main.cc all_cases/basic_ranges.cc all_cases/all_cases.cc) diff --git a/src/core/all_cases/all_cases.cc b/src/core/all_cases/all_cases.cc index 18ce5fe..e0e010d 100644 --- a/src/core/all_cases/all_cases.cc +++ b/src/core/all_cases/all_cases.cc @@ -1,21 +1,22 @@ -#include - -#include "reverse.h" +#include "utility.h" #include "all_cases.h" -//typedef uint32_t Range; - -using klotski::cases::BasicRanges; -using klotski::cases::range_reverse; +namespace klotski { +namespace cases { -inline int low_zero_num(uint32_t bin) { - return __builtin_ctzl(bin); - - // WARN: be aware of serious performance issues - // return __builtin_popcount(~(bin ^ -bin)) - 1; +/// Calculate all possible klotski heads. +static consteval std::array case_heads() { + std::array heads = {}; + for (int i = 0, head = 0; head < 15; ++head) { + if (head % 4 != 3) { + heads[i++] = head; + } + } + return heads; } -int check_range(int head, uint32_t range) noexcept { +/// Check whether the combination of head and range is valid. +static int check_range(int head, uint32_t range) noexcept { constexpr uint32_t MASK_1x1 = 0b00000001; constexpr uint32_t MASK_1x2 = 0b00000011; constexpr uint32_t MASK_2x1 = 0b00010001; @@ -48,69 +49,59 @@ int check_range(int head, uint32_t range) noexcept { return 0; // pass check } -void demo() { - -// std::cout << __builtin_popcount(~(6 ^ -6)) - 1 << std::endl; -// return; - - constexpr std::array heads = { - 0x0, 0x1, 0x2, - 0x4, 0x5, 0x6, - 0x8, 0x9, 0xA, - 0xC, 0xD, 0xE, - }; - - auto num = 0; - - std::array, 16> data; - - // TODO: vector reserve - - auto basic_ranges = BasicRanges::Instance().Fetch(); - -// std::vector reversed {basic_ranges.begin(), basic_ranges.end()}; -// for (auto &x : reversed) { -// x = range_reverse(x); -// } - - for (auto head : heads) { - for (uint32_t index = 0; index < basic_ranges.size(); ++index) { - auto offset = check_range(head, basic_ranges[index]); - if (offset) { // invalid case - auto tmp = (uint32_t)0b1 << (32 - offset * 2); // distance to next possible range - /// !! <- broken - /// ( xx xx xx ) xx xx xx ... [reversed range] - /// +1 00 00 00 ... (delta) - tmp += range_reverse(basic_ranges[index]) & ~(tmp - 1); - while (range_reverse(basic_ranges[++index]) < tmp); // located next range - --index; - -// next += reversed[index] & ~(next - 1); -// auto kk = std::lower_bound(reversed.begin() + index, reversed.end(), next); -// index = kk - reversed.begin() - 1; - - continue; - } - - data[head].emplace_back(range_reverse(basic_ranges[index])); - -// ++index; - - ++num; -// volatile auto r = range_reverse(basic_ranges[index]); // release valid cases +/// Build all valid ranges of the specified head. +void AllCases::BuildCases(int head, Ranges &basic_ranges, Ranges &release) noexcept { + release.reserve(ALL_CASES_NUM[head]); + for (uint32_t index = 0; index < basic_ranges.size(); ++index) { + auto offset = check_range(head, basic_ranges[index]); + if (offset) { // invalid case + auto tmp = (uint32_t)0b1 << (32 - offset * 2); // distance to next possible range + /// !! <- broken + /// ( xx xx xx ) xx xx xx ... [reversed range] + /// +1 00 00 00 ... (delta) + tmp += range_reverse(basic_ranges[index]) & ~(tmp - 1); + while (range_reverse(basic_ranges[++index]) < tmp); // located next range + --index; + continue; } + release.emplace_back(range_reverse(basic_ranges[index])); // release valid case } +} -// for (auto &x : data) { -// std::cout << x.size() << std::endl; -// } - - for (uint64_t head = 0; head < 15; ++head) { - for (auto x : data[head]) { - printf("%09llX\n", (head << 32) | x); +void AllCases::Build() noexcept { + if (available_) { // data already available + return; + } + if (building_.try_lock()) { // mutex lock success + if (available_) { + building_.unlock(); + return; + } + auto basic_ranges = BasicRanges::Instance().Fetch(); + for (auto head : case_heads()) { + BuildCases(head, basic_ranges, GetCases()[head]); } + available_ = true; + } else { + building_.lock(); // blocking waiting } + building_.unlock(); // release mutex +} -// std::cout << num << std::endl; +AllRanges& AllCases::GetCases() noexcept { + static std::array cases; + return cases; +} +AllCases& AllCases::Instance() noexcept { + static AllCases instance; + return instance; } + +AllRanges& AllCases::Fetch() noexcept { + this->Build(); + return GetCases(); +} + +} // namespace cases +} // namespace klotski diff --git a/src/core/all_cases/all_cases.h b/src/core/all_cases/all_cases.h index 69700f4..3e528cd 100644 --- a/src/core/all_cases/all_cases.h +++ b/src/core/all_cases/all_cases.h @@ -38,20 +38,31 @@ #include #include #include - -void demo(); +#include namespace klotski { namespace cases { typedef uint32_t Range; typedef std::vector Ranges; +typedef std::array AllRanges; + +constexpr auto BASIC_RANGES_NUM = 7311921; + +constexpr std::array ALL_CASES_NUM { + 2942906, 2260392, 2942906, 0, + 2322050, 1876945, 2322050, 0, + 2322050, 1876945, 2322050, 0, + 2942906, 2260392, 2942906, 0, +}; -const auto BASIC_RANGES_NUM = 7311921; +constexpr auto ALL_CASES_NUM_ = std::accumulate( + ALL_CASES_NUM.begin(), ALL_CASES_NUM.end(), 0 +); class BasicRanges { public: - void Build(); + void Build() noexcept; const Ranges& Fetch() noexcept; bool IsAvailable() const noexcept; @@ -68,9 +79,29 @@ private: BasicRanges() = default; static Ranges& GetRanges() noexcept; - static void BuildRanges(Ranges &ranges); - static void SpawnRanges(Ranges&, int, int, int, int) noexcept; + static void BuildRanges(Ranges &ranges) noexcept; + static void SpawnRanges(Ranges &ranges, int, int, int, int) noexcept; +}; + +class AllCases { +public: + +// void Build(); + + void Build() noexcept; + AllRanges& Fetch() noexcept; + + static AllCases& Instance() noexcept; + +private: + std::mutex building_; + bool available_ = false; + + static AllRanges& GetCases() noexcept; + static void BuildCases(int head, Ranges &basic_ranges, Ranges &release) noexcept; }; +void demo(); + } // namespace cases } // namespace klotski diff --git a/src/core/all_cases/basic_ranges.cc b/src/core/all_cases/basic_ranges.cc index f661823..a5764c5 100644 --- a/src/core/all_cases/basic_ranges.cc +++ b/src/core/all_cases/basic_ranges.cc @@ -1,7 +1,7 @@ #include #include -#include "reverse.h" +#include "utility.h" #include "all_cases.h" namespace klotski { @@ -61,7 +61,7 @@ void BasicRanges::SpawnRanges(Ranges &ranges, int n1, int n2, int n3, int n4) no } /// Search and sort all possible basic-ranges permutations. -void BasicRanges::BuildRanges(Ranges &ranges) { +void BasicRanges::BuildRanges(Ranges &ranges) noexcept { ranges.clear(); ranges.reserve(BASIC_RANGES_NUM); std::list flags {ranges.begin()}; // mark ordered interval @@ -85,16 +85,21 @@ void BasicRanges::BuildRanges(Ranges &ranges) { } /// Execute the build process and ensure thread safety. -void BasicRanges::Build() { - if (!available_) { - if (building_.try_lock()) { // mutex lock success - BuildRanges(GetRanges()); - available_ = true; - } else { - building_.lock(); // blocking waiting +void BasicRanges::Build() noexcept { + if (available_) { + return; // reduce consumption of mutex + } + if (building_.try_lock()) { // mutex lock success + if (available_) { + building_.unlock(); + return; } - building_.unlock(); // release mutex + BuildRanges(GetRanges()); + available_ = true; + } else { + building_.lock(); // blocking waiting } + building_.unlock(); // release mutex } Ranges& BasicRanges::GetRanges() noexcept { @@ -108,7 +113,7 @@ BasicRanges& BasicRanges::Instance() noexcept { } const Ranges& BasicRanges::Fetch() noexcept { - Build(); + this->Build(); return GetRanges(); } diff --git a/src/core/main.cc b/src/core/main.cc index 679d69b..8bbb0cc 100644 --- a/src/core/main.cc +++ b/src/core/main.cc @@ -2,6 +2,7 @@ #include "all_cases/all_cases.h" +using klotski::cases::AllCases; using klotski::cases::BasicRanges; int main() { @@ -10,8 +11,6 @@ int main() { auto start = clock(); - demo(); - // std::cout << BasicRanges::Instance().IsAvailable() << std::endl; // BasicRanges::Instance().Build(); // std::cout << BasicRanges::Instance().IsAvailable() << std::endl; @@ -19,6 +18,13 @@ int main() { // printf("%08X\n", x); // } + AllCases::Instance().Build(); + for (uint64_t head = 0; head < 15; ++head) { + for (auto range : AllCases::Instance().Fetch()[head]) { + printf("%09llX\n", head << 32 | range); + } + } + std::cerr << ((clock() - start) * 1000 / CLOCKS_PER_SEC) << "ms" << std::endl; return 0; diff --git a/src/core/all_cases/reverse.h b/src/core/utils/utility.h similarity index 73% rename from src/core/all_cases/reverse.h rename to src/core/utils/utility.h index dfcaa13..0394fe7 100644 --- a/src/core/all_cases/reverse.h +++ b/src/core/utils/utility.h @@ -1,7 +1,15 @@ #pragma once +#include + namespace klotski { -namespace cases { + +inline int low_zero_num(uint32_t bin) { + return __builtin_ctzl(bin); + + // WARN: be aware of serious performance issues + // return __builtin_popcount(~(bin ^ -bin)) - 1; +} inline uint32_t range_reverse(uint32_t bin) noexcept { #if defined(__GNUC__) || defined(__clang__) @@ -16,5 +24,4 @@ inline uint32_t range_reverse(uint32_t bin) noexcept { return ((bin << 2) & 0xCCCCCCCC) | ((bin >> 2) & 0x33333333); } -} // namespace cases } // namespace klotski