mirror of https://github.com/dnomd343/klotski.git
Dnomd343
1 year ago
5 changed files with 179 additions and 189 deletions
@ -0,0 +1,74 @@ |
|||
#pragma once |
|||
|
|||
/// Based on the requirements of valid klotski, the 2x2 block that must exist
|
|||
/// and only one, witch will occupy 4 empty slots, and the remaining 16 slots
|
|||
/// will be allocated to 1x2, 2x1, 1x1 and space. Then, according to the rules
|
|||
/// of CommonCode, they are coded as `00`, `01`, `10`, `11` respectively, and
|
|||
/// the remaining positions are filled with 0 and stored as 32-bit variables.
|
|||
|
|||
/// As we all know, a space or 1x1 block will occupy 1 slot, 1x2 and 2x1 block
|
|||
/// will occupy 2 slots, and together they fill 16 positions, so all possible
|
|||
/// combinations can be calculated, this number is 204, each combination can
|
|||
/// produce different permutations. After mathematical calculations, there are
|
|||
/// a total of 7311921 possible permutations. The goal of BasicRanges is to
|
|||
/// find these permutations, sort them and store in a uint32_t array.
|
|||
|
|||
/// AllCases is used to generate all valid CommonCodes, its works based on all
|
|||
/// permutations generated by BasicRanges, which will use different 2x2 block
|
|||
/// positions to check respectively. On the 5x4 chessboard, it has 12 possible
|
|||
/// positions, and the numbers are distributed in 0 ~ 15, witch called `head`.
|
|||
/// For the convenience of calculation, here use an array of length 16 for
|
|||
/// storage, of course, the position of 3/7/11/15 will be empty.
|
|||
|
|||
/// 00 01 02 03
|
|||
/// 04 05 06 07 00 01 02
|
|||
/// 08 09 10 11 04 05 06 <- head of 2x2 block
|
|||
/// 12 13 14 15 08 09 10
|
|||
/// 16 17 18 19 12 13 14
|
|||
|
|||
/// After all the work is done, we will have 29334498 cases, distributed in 16
|
|||
/// arrays. Each of them is a uint32_t array storing the ranges, this is to
|
|||
/// save memory, otherwise the 64-bit length must be consumed.
|
|||
|
|||
/// By the way, due to the performance considerations of the checking process,
|
|||
/// the resulting data of BasicRanges will be flipped every 2-bit, which will
|
|||
/// not consume too much time, but it can almost double the speed of the case
|
|||
/// checking subsequent.
|
|||
|
|||
#include <mutex> |
|||
#include <vector> |
|||
#include <cstdint> |
|||
|
|||
namespace klotski { |
|||
namespace cases { |
|||
|
|||
typedef uint32_t Range; |
|||
typedef std::vector<Range> Ranges; |
|||
|
|||
const auto BASIC_RANGES_NUM = 7311921; |
|||
|
|||
class BasicRanges { |
|||
public: |
|||
void Build(); |
|||
const Ranges& Fetch() noexcept; |
|||
bool IsAvailable() const noexcept; |
|||
|
|||
BasicRanges(BasicRanges&&) = delete; |
|||
BasicRanges(const BasicRanges&) = delete; |
|||
BasicRanges& operator=(BasicRanges&&) = delete; |
|||
BasicRanges& operator=(const BasicRanges&) = delete; |
|||
|
|||
static BasicRanges& Instance() noexcept; |
|||
|
|||
private: |
|||
std::mutex building_; |
|||
bool available_ = false; |
|||
|
|||
BasicRanges() = default; |
|||
static Ranges& GetRanges() noexcept; |
|||
static void BuildRanges(Ranges &ranges); |
|||
static void SpawnRanges(Ranges&, int, int, int, int) noexcept; |
|||
}; |
|||
|
|||
} // namespace cases
|
|||
} // namespace klotski
|
@ -1,121 +1,120 @@ |
|||
#include <list> |
|||
#include <vector> |
|||
#include <algorithm> |
|||
|
|||
#include "basic_ranges.h" |
|||
#include "reverse.h" |
|||
#include "all_cases.h" |
|||
|
|||
#include <iostream> |
|||
namespace klotski { |
|||
namespace cases { |
|||
|
|||
typedef uint32_t Range; |
|||
typedef std::vector<Range> Ranges; |
|||
typedef std::vector<Range>::iterator RangeIter; |
|||
typedef std::tuple<int, int, int, int> RangeType; |
|||
|
|||
static const auto RangeTypeNum = 204; |
|||
|
|||
static const uint32_t BASIC_RANGES_NUM = 7311921; |
|||
|
|||
class BasicRanges { |
|||
public: |
|||
static void build_ranges(); |
|||
|
|||
static const Ranges& fetch(); |
|||
|
|||
private: |
|||
static Ranges data_; |
|||
|
|||
static void spawn_ranges(int, int, int, int); |
|||
}; |
|||
|
|||
Ranges BasicRanges::data_; |
|||
|
|||
static Range range_reverse(Range bin) noexcept { |
|||
#if defined(__GNUC__) || defined(__clang__) |
|||
bin = __builtin_bswap32(bin); |
|||
// TODO: using `std::byteswap` (c++23)
|
|||
#else |
|||
// FIXME: `_byteswap_ulong` under MSVC
|
|||
bin = ((bin << 16) & 0xFFFF0000) | ((bin >> 16) & 0x0000FFFF); |
|||
bin = ((bin << 8) & 0xFF00FF00) | ((bin >> 8) & 0x00FF00FF); |
|||
#endif |
|||
bin = ((bin << 4) & 0xF0F0F0F0) | ((bin >> 4) & 0x0F0F0F0F); |
|||
return ((bin << 2) & 0xCCCCCCCC) | ((bin >> 2) & 0x33333333); |
|||
} |
|||
|
|||
consteval static std::array<RangeType, RangeTypeNum> basic_types() { |
|||
std::array<RangeType, RangeTypeNum> data; |
|||
for (int i = 0, n = 0; n <= 7; ++n) // number of 1x2 and 2x1 block -> 0 ~ 7
|
|||
for (int n_2x1 = 0; n_2x1 <= n; ++n_2x1) // number of 2x1 block -> 0 ~ n
|
|||
for (int n_1x1 = 0; n_1x1 <= (14 - n * 2); ++n_1x1) // number of 1x1 block -> 0 ~ (14 - 2n)
|
|||
typedef std::array<RangeType, 204> RangeTypes; |
|||
|
|||
/// Calculate all possible basic-ranges permutations.
|
|||
consteval static RangeTypes range_types() { |
|||
RangeTypes data; |
|||
for (int i = 0, n = 0; n <= 7; ++n) // 1x2 and 2x1 -> 0 ~ 7
|
|||
for (int n_2x1 = 0; n_2x1 <= n; ++n_2x1) // 2x1 -> 0 ~ n
|
|||
for (int n_1x1 = 0; n_1x1 <= (14 - n * 2); ++n_1x1) // 1x1 -> 0 ~ (14 - 2n)
|
|||
data[i++] = {16 - n * 2 - n_1x1, n - n_2x1, n_2x1, n_1x1}; |
|||
return data; |
|||
} |
|||
|
|||
static void combine_sort(RangeIter begin, RangeIter mid, RangeIter end) { |
|||
Ranges tmp = {begin, mid}; |
|||
/// Combine two consecutive sorted arrays into one sorted arrays.
|
|||
static void combine_sort(RangeIter begin, RangeIter mid, RangeIter end) noexcept { |
|||
Ranges tmp = {begin, mid}; // left array backup
|
|||
auto p = tmp.begin(); |
|||
for (;;) { |
|||
if (*p < *mid) { |
|||
*(begin++) = *(p++); |
|||
if (p == tmp.end()) |
|||
if (*p <= *mid) { |
|||
*(begin++) = *(p++); // stored in original span
|
|||
if (p == tmp.end()) // left array is consumed
|
|||
return; |
|||
} else { |
|||
*(begin++) = *(mid++); |
|||
if (mid == end) { |
|||
std::copy(p, tmp.end(), begin); |
|||
return; |
|||
} |
|||
continue; |
|||
} |
|||
*(begin++) = *(mid++); // stored in original span
|
|||
if (mid == end) { // right array is consumed
|
|||
std::copy(p, tmp.end(), begin); // left array remaining
|
|||
return; |
|||
} |
|||
} |
|||
} |
|||
|
|||
void BasicRanges::spawn_ranges(int n1, int n2, int n3, int n4) { |
|||
/// Spawn all ranges of specified conditions.
|
|||
void BasicRanges::SpawnRanges(Ranges &ranges, int n1, int n2, int n3, int n4) noexcept { |
|||
auto num = n1 + n2 + n3 + n4; |
|||
auto offset = (16 - num) << 1; // offset of low bits
|
|||
|
|||
std::vector<int> series; |
|||
auto n = n1 + n2 + n3 + n4; |
|||
auto offset = (16 - n) << 1; |
|||
series.reserve(num); |
|||
series.insert(series.end(), n1, 0b00); |
|||
series.insert(series.end(), n2, 0b01); |
|||
series.insert(series.end(), n3, 0b10); |
|||
series.insert(series.end(), n4, 0b11); |
|||
|
|||
do { |
|||
do { // full permutation traversal
|
|||
uint32_t range = 0; |
|||
for (auto x : series) |
|||
for (auto x : series) // store every 2-bit
|
|||
(range <<= 2) |= x; |
|||
data_.emplace_back(range << offset); |
|||
ranges.emplace_back(range << offset); |
|||
} while (next_permutation(series.begin(), series.end())); |
|||
} |
|||
|
|||
void BasicRanges::build_ranges() { |
|||
data_.reserve(BASIC_RANGES_NUM); |
|||
std::list<RangeIter> flags {data_.begin()}; |
|||
/// Search and sort all possible basic-ranges permutations.
|
|||
void BasicRanges::BuildRanges(Ranges &ranges) { |
|||
ranges.clear(); |
|||
ranges.reserve(BASIC_RANGES_NUM); |
|||
std::list<RangeIter> flags {ranges.begin()}; // mark ordered interval
|
|||
|
|||
for (auto &t : basic_types()) { |
|||
spawn_ranges(std::get<0>(t), std::get<1>(t), std::get<2>(t), std::get<3>(t)); |
|||
flags.emplace_back(data_.end()); |
|||
for (auto &t : range_types()) { |
|||
SpawnRanges(ranges, std::get<0>(t), std::get<1>(t), std::get<2>(t), std::get<3>(t)); |
|||
flags.emplace_back(ranges.end()); |
|||
} |
|||
do { |
|||
std::list<RangeIter>::iterator begin = flags.begin(), mid, end; |
|||
decltype(flags.begin()) begin = flags.begin(), mid, end; |
|||
while (++(mid = begin) != flags.end() && ++(end = mid) != flags.end()) { |
|||
combine_sort(*begin, *mid, *end); |
|||
combine_sort(*begin, *mid, *end); // merge two ordered interval
|
|||
flags.erase(mid); |
|||
begin = end; |
|||
} |
|||
} while (flags.size() > 2); |
|||
} while (flags.size() > 2); // merge until only one interval remains
|
|||
|
|||
for (auto &x : ranges) { |
|||
x = range_reverse(x); // flip every 2-bit
|
|||
} |
|||
} |
|||
|
|||
for (auto &x : data_) { |
|||
x = range_reverse(x); |
|||
/// 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
|
|||
} |
|||
building_.unlock(); // release mutex
|
|||
} |
|||
} |
|||
|
|||
const Ranges& BasicRanges::fetch() { |
|||
return data_; |
|||
Ranges& BasicRanges::GetRanges() noexcept { |
|||
static Ranges ranges; |
|||
return ranges; |
|||
} |
|||
|
|||
void demo() { |
|||
BasicRanges::build_ranges(); |
|||
BasicRanges& BasicRanges::Instance() noexcept { |
|||
static BasicRanges instance; |
|||
return instance; |
|||
} |
|||
|
|||
for (auto x : BasicRanges::fetch()) { |
|||
printf("%08X\n", x); |
|||
} |
|||
const Ranges& BasicRanges::Fetch() noexcept { |
|||
Build(); |
|||
return GetRanges(); |
|||
} |
|||
|
|||
bool BasicRanges::IsAvailable() const noexcept { |
|||
return available_; |
|||
} |
|||
|
|||
} // namespace cases
|
|||
} // namespace klotski
|
|||
|
@ -1,80 +0,0 @@ |
|||
#pragma once |
|||
|
|||
/// Based on the requirements of valid klotski, the `2x2` block that must exist
|
|||
/// and only one, witch will occupy 4 empty slots, and the remaining 16 slots
|
|||
/// will be allocated to space, `1x2`, `2x1` and `1x1`. Then, according to the
|
|||
/// rules of CommonCode, they are coded as `00` `01` `10` `11` respectively, and
|
|||
/// the remaining positions are filled with `0` and stored as 32-bit variables.
|
|||
|
|||
/// As we all know, a space or `1x1` block will occupy 1 slot, `1x2` or `2x1`
|
|||
/// block will occupy 2 slots, and together they fill 16 positions, so all
|
|||
/// possible combinations can be calculated, this number is 204. Each combination
|
|||
/// can produce different permutations. After verification, there are a total of
|
|||
/// 7311921 possible permutations. The goal of BasicRanges is to find these
|
|||
/// permutations, sort them and store them in a `uint32_t` array.
|
|||
|
|||
/// In terms of algorithms, there are two options: the first is to generate
|
|||
/// out-of-order data and then quickly arrange them; the second is to generate
|
|||
/// ordered data for 204 combinations, and then merge and sort them. After testing,
|
|||
/// the former is faster in generation (consuming T time), but it will consume
|
|||
/// more time in sorting (about 7T), and the latter will cost about 2T in
|
|||
/// generation due to the loss of the tree structure queue. But it can save more
|
|||
/// time in sorting, which is about 2T, so the second solution will get the result
|
|||
/// faster.
|
|||
|
|||
/// Finally, due to the performance considerations of AllCases, the resulting data
|
|||
/// will be flipped every two bits, which will not consume too much time (less than
|
|||
/// 10% of T), but can almost double the speed of the subsequent `check_range`.
|
|||
|
|||
#include <mutex> |
|||
#include <vector> |
|||
#include <cstdint> |
|||
|
|||
void demo(); |
|||
|
|||
namespace klotski { |
|||
|
|||
/// basic ranges count
|
|||
const uint32_t BASIC_RANGES_SIZE = 7311921; |
|||
|
|||
// class BasicRanges {
|
|||
// public:
|
|||
// /// Three basic states, one-way transition.
|
|||
// /// {NOT_INIT} -> {BUILDING} -> {AVAILABLE}
|
|||
// enum Status {
|
|||
// NOT_INIT,
|
|||
// BUILDING,
|
|||
// AVAILABLE,
|
|||
// };
|
|||
// typedef std::vector<uint32_t> basic_ranges_t;
|
|||
//
|
|||
// /// Trigger the build process, from `NOT_INIT` to `BUILDING`.
|
|||
// static void build();
|
|||
//
|
|||
// /// Get current status of BasicRanges.
|
|||
// static Status status() noexcept;
|
|||
//
|
|||
// /// Blocking access to constructed data.
|
|||
// static const basic_ranges_t& fetch();
|
|||
//
|
|||
// private:
|
|||
// static bool available_;
|
|||
// static std::mutex building_;
|
|||
// static basic_ranges_t data_;
|
|||
//
|
|||
// static void build_data();
|
|||
//
|
|||
// public:
|
|||
// /// The number of types of blocks.
|
|||
// struct generate_t {
|
|||
// int n1; // number of `00` -> space
|
|||
// int n2; // number of `01` -> 1x2 block
|
|||
// int n3; // number of `10` -> 2x1 block
|
|||
// int n4; // number of `11` -> 1x1 block
|
|||
// };
|
|||
//
|
|||
// /// Generate all basic-ranges of the specified type.
|
|||
// static void generate(basic_ranges_t &release, generate_t info);
|
|||
// };
|
|||
|
|||
} // namespace klotski
|
@ -0,0 +1,20 @@ |
|||
#pragma once |
|||
|
|||
namespace klotski { |
|||
namespace cases { |
|||
|
|||
inline uint32_t range_reverse(uint32_t bin) noexcept { |
|||
#if defined(__GNUC__) || defined(__clang__) |
|||
bin = __builtin_bswap32(bin); |
|||
// TODO: using `std::byteswap` (c++23)
|
|||
#else |
|||
// FIXME: `_byteswap_ulong` under MSVC
|
|||
bin = ((bin << 16) & 0xFFFF0000) | ((bin >> 16) & 0x0000FFFF); |
|||
bin = ((bin << 8) & 0xFF00FF00) | ((bin >> 8) & 0x00FF00FF); |
|||
#endif |
|||
bin = ((bin << 4) & 0xF0F0F0F0) | ((bin >> 4) & 0x0F0F0F0F); |
|||
return ((bin << 2) & 0xCCCCCCCC) | ((bin >> 2) & 0x33333333); |
|||
} |
|||
|
|||
} // namespace cases
|
|||
} // namespace klotski
|
@ -1,43 +1,20 @@ |
|||
#include <iostream> |
|||
#include <algorithm> |
|||
|
|||
#include "all_cases/basic_ranges.h" |
|||
#include "all_cases/all_cases.h" |
|||
|
|||
int main() { |
|||
using klotski::cases::BasicRanges; |
|||
|
|||
int main() { |
|||
auto start = clock(); |
|||
|
|||
demo(); |
|||
|
|||
|
|||
// for (int n = 0; n <= 7; ++n) // number of 1x2 and 2x1 block -> 0 ~ 7
|
|||
// for (int n_2x1 = 0; n_2x1 <= n; ++n_2x1) // number of 2x1 block -> 0 ~ n
|
|||
// for (int n_1x1 = 0; n_1x1 <= (14 - n * 2); ++n_1x1) // number of 1x1 block -> 0 ~ (14 - 2n)
|
|||
// build(16 - n * 2 - n_1x1, n - n_2x1, n_2x1, n_1x1);
|
|||
|
|||
|
|||
|
|||
// generate(generate_t { // generate target ranges
|
|||
// .n1 = 16 - n * 2 - n_1x1, /// space -> 00
|
|||
// .n2 = n - n_2x1, /// 1x2 -> 01
|
|||
// .n3 = n_2x1, /// 2x1 -> 10
|
|||
// .n4 = n_1x1, /// 1x1 -> 11
|
|||
// });
|
|||
|
|||
|
|||
// std::stable_sort(result.begin(), result.end());
|
|||
|
|||
// build(4, 2, 1, 6);
|
|||
// std::cout << BasicRanges::Instance().IsAvailable() << std::endl;
|
|||
// BasicRanges::Instance().Build();
|
|||
// std::cout << BasicRanges::Instance().IsAvailable() << std::endl;
|
|||
for (auto x : BasicRanges::Instance().Fetch()) { |
|||
printf("%08X\n", x); |
|||
} |
|||
|
|||
std::cerr << ((clock() - start) * 1000 / CLOCKS_PER_SEC) << "ms" << std::endl; |
|||
|
|||
// std::cout << result.size() << std::endl;
|
|||
|
|||
// std::vector<uintmax_t> demo {0b00, 0b01, 0b10, 0b11};
|
|||
|
|||
// do {
|
|||
// std::cout << demo[0] << " " << demo[1] << " " << demo[2] << " " << demo[3] << std::endl;
|
|||
// } while (next_permutation(demo.begin(), demo.end()));
|
|||
|
|||
return 0; |
|||
} |
|||
|
Loading…
Reference in new issue