Browse Source

feat: migrate core implementation code

master
Dnomd343 6 months ago
parent
commit
2a977cb5ea
  1. 11
      src/core/CMakeLists.txt
  2. 94
      src/core/core/core.h
  3. 194
      src/core/core/internal/core.cc
  4. 38
      src/core/main.cc
  5. 7
      src/core_test/CMakeLists.txt

11
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)

94
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 <cstdint>
#include <utility>
#include <functional>
namespace klotski::core {
class Core {
public:
/// Release with code and mask
typedef std::function<void(uint64_t, uint64_t)> 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

194
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
}
}
}

38
src/core/main.cc

@ -1,11 +1,13 @@
#include <thread>
#include <iostream>
#include <algorithm>
#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<uint64_t> 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;

7
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

Loading…
Cancel
Save