mirror of https://github.com/dnomd343/klotski.git
				
				
			
				 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