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