mirror of https://github.com/dnomd343/klotski.git
Dnomd343
6 months ago
5 changed files with 317 additions and 27 deletions
@ -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
|
@ -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
|
|||
} |
|||
} |
|||
} |
Loading…
Reference in new issue