diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index c5e3d9d..7c60b63 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -16,18 +16,13 @@ set(KLOTSKI_CORE_SRC short_code/internal/convert.cc short_code/internal/serialize.cc + + core/internal/core.cc ) add_library(klotski_core STATIC ${KLOTSKI_CORE_SRC}) target_compile_options(klotski_core PRIVATE -fno-rtti -fno-exceptions) -target_include_directories(klotski_core PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR} - ${CMAKE_CURRENT_SOURCE_DIR}/utils # TODO: remove item - ${CMAKE_CURRENT_SOURCE_DIR}/all_cases # TODO: remove item - ${CMAKE_CURRENT_SOURCE_DIR}/raw_code # TODO: remove item - ${CMAKE_CURRENT_SOURCE_DIR}/short_code # TODO: remove item - ${CMAKE_CURRENT_SOURCE_DIR}/common_code # TODO: remove item -) +target_include_directories(klotski_core PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) # TODO: just for dev testing add_executable(klotski_core_bin main.cc) diff --git a/src/core/core/core.h b/src/core/core/core.h new file mode 100644 index 0000000..a085e44 --- /dev/null +++ b/src/core/core/core.h @@ -0,0 +1,94 @@ +/// Klotski Engine by Dnomd343 @2024 + +// TODO: only copy from old implementation, the interfaces will change in future. + +/// The main purpose of klotski calculation is to obtain all the next steps in +/// order to carry out the BFS algorithm. The definition of the next step is +/// the result of moving an arbitrary block in the current layout for a limited +/// number of times, and the resulting layout is different from the original. + +/// Eg1: +/// % # # % % # # % % # # % % # # % % # # % +/// % # # % % # # % % # # % % # # % % # # % +/// @ $ $ @ --> @ $ $ @ @ $ $ @ @ $ $ @ @ $ $ @ +/// @ & * @ @ * @ @ * @ @ & @ @ & @ +/// * & * & & * & & * * & * * & +/// | +/// | % # # % % # # % % # # % % # # % +/// | % # # % % # # % % # # % % # # % +/// | ------> @ $ $ @ @ $ $ @ @ $ $ @ @ $ $ @ +/// @ & * @ @ & * @ @ & * @ @ & * @ +/// * & * & * & * & + +/// Eg2: +/// * @ & % * @ & % * @ & % * @ & % +/// # # $ % # # $ % # # $ % # # $ % +/// # # $ ^ --> # # $ ^ # # $ ^ # # $ ^ +/// ~ ~ ^ ~ ~ ^ ~ ~ ^ @ ~ ~ ^ +/// @ % % @ % % @ % % % % + +/// In order to achieve the purpose of searching for the next step, we should +/// find out the sub-layouts that can be derived from each block. Due to the +/// limited steps of moving, with the help of a sub-BFS search, the target can +/// be obtained. It can be shown that this step produces at most `15` derived +/// layouts, so it can be stored and computed in a queue which length 16. By +/// performing such a search on each block in the layout, we can get all the +/// next-step layouts, which have a minimum of `0` and a maximum of `68`. + +/// For a single block, search for the situation after it has moved one grid, +/// `addr` is its current position information, which is (0 ~ 19) * 3. When +/// moving up and down, judge the value of `addr` to confirm whether it is out +/// of bounds; when moving left and right, get its remainder to `4` to judge +/// whether it will be out of bounds. After confirming that it will not cross +/// the boundary, it is necessary to judge whether it will collide with other +/// blocks after moving, that is, the target position needs to be empty, which +/// is represented as `000` in the RawCode, and the template will be used to +/// perform bit operations here to confirm whether it is feasible. + +/// Finally, in order to improve efficiency, the `filter` option is added. For +/// the direction of the last movement, it will be ignored in the next search, +/// so as to optimize BFS. After confirming that it can be moved, the moved +/// layout can be directly obtained by means of bit operations, and a mask will +/// be obtained at the same time, which marks the moved block as `111`, which +/// will speed up subsequent calculations. The generated layout will be +/// inserted into the cache, and after the BFS search for each block is +/// completed, the core will use the callback function to output these +/// results. + +#pragma once + +#include +#include +#include + +namespace klotski::core { + +class Core { +public: + /// Release with code and mask + typedef std::function release_t; + + /// Core interface + void next_cases(uint64_t code, uint64_t mask); + explicit Core(release_t release_func) : release_(std::move(release_func)) {} + +private: + struct cache_t { + uint64_t code; + uint64_t mask; /// (000) or (111) + int filter; /// DIR_UP | DIR_DOWN | DIR_LEFT | DIR_RIGHT + int addr; /// (0 ~ 19) * 3 + }; + + int cache_size_ = 1; + cache_t cache_[16]{}; + release_t release_; // release function + + void move_1x1(uint64_t code, int addr); + void move_1x2(uint64_t code, int addr); + void move_2x1(uint64_t code, int addr); + void move_2x2(uint64_t code, int addr); + inline void cache_insert(cache_t next_case); +}; + +} // namespace klotski::core diff --git a/src/core/core/internal/core.cc b/src/core/core/internal/core.cc new file mode 100644 index 0000000..5be9af3 --- /dev/null +++ b/src/core/core/internal/core.cc @@ -0,0 +1,194 @@ +#include "core/core.h" +#include "utils/common.h" + +/// block move direction +#define DIR_UP (-4 * 3) +#define DIR_LEFT (-1 * 3) +#define DIR_DOWN (+4 * 3) +#define DIR_RIGHT (+1 * 3) + +/// block direction limit +#define ALLOW_UP (filter != -DIR_UP) +#define ALLOW_DOWN (filter != -DIR_DOWN) +#define ALLOW_LEFT (filter != -DIR_LEFT) +#define ALLOW_RIGHT (filter != -DIR_RIGHT) + +/// horizontal restraints +#define NOT_COLUMN_0 ((addr & 3) != 0b00) +#define NOT_COLUMN_2 ((addr & 3) != 0b10) +#define NOT_COLUMN_3 ((addr & 3) != 0b01) + +/// try to move block +#define MOVE_UP (next_addr = addr + DIR_UP) +#define MOVE_DOWN (next_addr = addr + DIR_DOWN) +#define MOVE_LEFT (next_addr = addr + DIR_LEFT) +#define MOVE_RIGHT (next_addr = addr + DIR_RIGHT) + +/// vertical restraints +#define TOP_LIMIT(ADDR) (addr >= ADDR * 3) +#define BOTTOM_LIMIT(ADDR) (addr <= ADDR * 3) + +/// check if the block can move +#define CHECK_UP(MASK) !(code >> MOVE_UP & MASK) +#define CHECK_DOWN(MASK) !(code >> MOVE_DOWN & MASK) +#define CHECK_LEFT(MASK) !(code >> MOVE_LEFT & MASK) +#define CHECK_RIGHT(MASK) !(code >> MOVE_RIGHT & MASK) + +/// release next code +#define RELEASE_1x1(FILTER) RELEASE(NEXT_CODE_1x1, FILTER) +#define RELEASE_1x2(FILTER) RELEASE(NEXT_CODE_1x2, FILTER) +#define RELEASE_2x1(FILTER) RELEASE(NEXT_CODE_2x1, FILTER) +#define RELEASE_2x2(FILTER) RELEASE(NEXT_CODE_2x2, FILTER) + +/// calculate next code +#define NEXT_CODE_1x1 ((code & ~(K_MASK_1x1_ << addr)) | (K_MASK_1x1 << next_addr)) +#define NEXT_CODE_1x2 ((code & ~(K_MASK_1x2_ << addr)) | (K_MASK_1x2 << next_addr)) +#define NEXT_CODE_2x1 ((code & ~(K_MASK_2x1_ << addr)) | (K_MASK_2x1 << next_addr)) +#define NEXT_CODE_2x2 ((code & ~(K_MASK_2x2_ << addr)) | (K_MASK_2x2 << next_addr)) + +/////////////////////////////////////////////// + +#define RELEASE(NEXT_CODE, FILTER) \ + cache_insert(cache_t { \ + .code = NEXT_CODE, \ + .mask = K_MASK_1x1_ << next_addr, \ + .filter = FILTER, \ + .addr = next_addr \ + }); + +/////////////////////////////////////////////// + +#define BFS_INIT \ + int next_addr; \ + int current = 0; \ + cache_[0].addr = addr; + +#define BFS_LOAD \ + code = cache_[current].code; \ + addr = cache_[current].addr; \ + int filter = cache_[current++].filter; + +#define BFS_STOP \ + (current == cache_size_) + +/////////////////////////////////////////////// + +using klotski::core::Core; + +inline void Core::cache_insert(cache_t next_case) { // try to insert into cache + auto *cache_ptr = cache_; + for (; cache_ptr < cache_ + cache_size_; ++cache_ptr) { + if (cache_ptr->code == next_case.code) { + return; // already exist -> insert failed + } + } + *cache_ptr = next_case; // cache push back + ++cache_size_; +} + +void Core::move_1x1(uint64_t code, int addr) { // try to move target 1x1 block + BFS_INIT + while (!BFS_STOP) { // bfs search process + BFS_LOAD + if (ALLOW_UP && TOP_LIMIT(4) && CHECK_UP(K_MASK_1x1_)) { + RELEASE_1x1(DIR_UP) // 1x1 block move up + } + if (ALLOW_DOWN && BOTTOM_LIMIT(15) && CHECK_DOWN(K_MASK_1x1_)) { + RELEASE_1x1(DIR_DOWN) // 1x1 block move down + } + if (ALLOW_LEFT && NOT_COLUMN_0 && CHECK_LEFT(K_MASK_1x1_)) { + RELEASE_1x1(DIR_LEFT) // 1x1 block move left + } + if (ALLOW_RIGHT && NOT_COLUMN_3 && CHECK_RIGHT(K_MASK_1x1_)) { + RELEASE_1x1(DIR_RIGHT) // 1x1 block move right + } + } +} + +void Core::move_1x2(uint64_t code, int addr) { // try to move target 1x2 block + BFS_INIT + while (!BFS_STOP) { // bfs search process + BFS_LOAD + if (ALLOW_UP && TOP_LIMIT(4) && CHECK_UP(K_MASK_1x2_)) { + RELEASE_1x2(DIR_UP) // 1x2 block move up + } + if (ALLOW_DOWN && BOTTOM_LIMIT(14) && CHECK_DOWN(K_MASK_1x2_)) { + RELEASE_1x2(DIR_DOWN) // 1x2 block move down + } + if (ALLOW_LEFT && NOT_COLUMN_0 && CHECK_LEFT(K_MASK_1x1_)) { + RELEASE_1x2(DIR_LEFT) // 1x2 block move left + } + if (ALLOW_RIGHT && NOT_COLUMN_2 && CHECK_RIGHT(K_MASK_1x1_R)) { + RELEASE_1x2(DIR_RIGHT) // 1x2 block move right + } + } +} + +void Core::move_2x1(uint64_t code, int addr) { // try to move target 2x1 block + BFS_INIT + while (!BFS_STOP) { // bfs search process + BFS_LOAD + if (ALLOW_UP && TOP_LIMIT(4) && CHECK_UP(K_MASK_1x1_)) { + RELEASE_2x1(DIR_UP) // 2x1 block move up + } + if (ALLOW_DOWN && BOTTOM_LIMIT(11) && CHECK_DOWN(K_MASK_1x1_D)) { + RELEASE_2x1(DIR_DOWN) // 2x1 block move down + } + if (ALLOW_LEFT && NOT_COLUMN_0 && CHECK_LEFT(K_MASK_2x1_)) { + RELEASE_2x1(DIR_LEFT) // 2x1 block move left + } + if (ALLOW_RIGHT && NOT_COLUMN_3 && CHECK_RIGHT(K_MASK_2x1_)) { + RELEASE_2x1(DIR_RIGHT) // 2x1 block move right + } + } +} + +void Core::move_2x2(uint64_t code, int addr) { // try to move target 2x2 block + BFS_INIT + while (!BFS_STOP) { // bfs search process + BFS_LOAD + if (ALLOW_UP && TOP_LIMIT(4) && CHECK_UP(K_MASK_1x2_)) { + RELEASE_2x2(DIR_UP) // 2x2 block move up + } + if (ALLOW_DOWN && BOTTOM_LIMIT(10) && CHECK_DOWN(K_MASK_1x2_D)) { + RELEASE_2x2(DIR_DOWN) // 2x2 block move down + } + if (ALLOW_LEFT && NOT_COLUMN_0 && CHECK_LEFT(K_MASK_2x1_)) { + RELEASE_2x2(DIR_LEFT) // 2x2 block move left + } + if (ALLOW_RIGHT && NOT_COLUMN_2 && CHECK_RIGHT(K_MASK_2x1_R)) { + RELEASE_2x2(DIR_RIGHT) // 2x2 block move right + } + } +} + +void Core::next_cases(uint64_t code, uint64_t mask) { // search next step cases + cache_[0].filter = 0; // without filter + cache_[0].code = code; // bfs root code + auto range = code | mask; + + for (int addr = 0; range; addr += 3, range >>= 3) { // traverse every 3-bits + switch (range & 0b111) { // match low 3-bits + case BLOCK_1x1: + move_1x1(code, addr); // try to move 1x1 block + break; + case BLOCK_1x2: + move_1x2(code, addr); // try to move 1x2 block + break; + case BLOCK_2x1: + move_2x1(code, addr); // try to move 2x1 block + break; + case BLOCK_2x2: + move_2x2(code, addr); // try to move 2x2 block + break; + default: + continue; // B_space or B_fill + } + if (cache_size_ != 1) { // found one or more next cases + for (int i = 1; i < cache_size_; ++i) { + release_(cache_[i].code, cache_[i].mask); // release next cases + } + cache_size_ = 1; // reset cache size + } + } +} diff --git a/src/core/main.cc b/src/core/main.cc index cbd67cf..82706de 100644 --- a/src/core/main.cc +++ b/src/core/main.cc @@ -1,11 +1,13 @@ #include #include -#include -#include "raw_code.h" -#include "all_cases.h" -#include "short_code.h" -#include "common_code.h" +#include "core/core.h" +#include "raw_code/raw_code.h" +#include "all_cases/all_cases.h" +#include "short_code/short_code.h" +#include "common_code/common_code.h" + +using klotski::core::Core; using klotski::cases::AllCases; using klotski::cases::BasicRanges; @@ -17,9 +19,14 @@ using klotski::codec::CommonCode; using klotski::codec::SHORT_CODE_LIMIT; int main() { + const auto start = clock(); + + auto core = Core([](const uint64_t code, uint64_t) { + std::cout << RawCode::unsafe_create(code); + std::cout << std::endl; + }); - AllCases::instance().build(); -// BasicRanges::instance().build(); + core.next_cases(RawCode::from_common_code(0x1A9BF0C00).value().unwrap(), 0x0); // std::vector common_codes; // common_codes.reserve(klotski::cases::ALL_CASES_NUM_); @@ -36,13 +43,11 @@ int main() { // common_codes_str.emplace_back(CommonCode::string_encode(x, false)); // } - ShortCode::speed_up(true); - - auto start = clock(); - - for (uint32_t short_code = 0; short_code < SHORT_CODE_LIMIT; ++short_code) { - ShortCode::unsafe_create(short_code).to_common_code(); - } + // ShortCode::speed_up(true); + // + // for (uint32_t short_code = 0; short_code < SHORT_CODE_LIMIT; ++short_code) { + // ShortCode::unsafe_create(short_code).to_common_code(); + // } // for (auto common_code : common_codes) { // printf("%llX\n", common_code); @@ -56,11 +61,6 @@ int main() { // CommonCode::string_decode(common_code_str); // } -// BasicRanges::instance().build(); -// AllCases::instance().build(); -// AllCases::instance().build_parallel([](auto f) {f();}); -// AllCases::instance().build_parallel_async([](auto f) {f();}, []() {}); - std::cerr << ((clock() - start) * 1000 / CLOCKS_PER_SEC) << "ms" << std::endl; return 0; diff --git a/src/core_test/CMakeLists.txt b/src/core_test/CMakeLists.txt index 2221c2c..e5f782e 100644 --- a/src/core_test/CMakeLists.txt +++ b/src/core_test/CMakeLists.txt @@ -10,6 +10,13 @@ set(KLOTSKI_TEST_DEPS klotski klotski_core include_directories(utility) +# TODO: just pass compile for now +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../core/utils) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../core/raw_code) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../core/all_cases) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../core/short_code) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../core/common_code) + # ------------------------------------------------------------------------------------ # set(KLOTSKI_TEST_CASES_SRC