mirror of https://github.com/dnomd343/klotski.git
				
				
			
			You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							149 lines
						
					
					
						
							4.8 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							149 lines
						
					
					
						
							4.8 KiB
						
					
					
				| #include <future> | |
| #include "all_cases.h" | |
|  | |
| namespace klotski { | |
| namespace cases { | |
| 
 | |
| /// Calculate all possible klotski heads. | |
| static consteval std::array<int, 12> case_heads() { | |
|     std::array<int, 12> heads = {}; | |
|     for (int i = 0, head = 0; head < 15; ++head) { | |
|         if (head % 4 != 3) { | |
|             heads[i++] = head; | |
|         } | |
|     } | |
|     return heads; | |
| } | |
| 
 | |
| /// Check whether the combination of head and range is valid. | |
| static int check_range(int head, uint32_t range) noexcept { | |
|     constexpr uint32_t MASK_1x1 = 0b00000001; | |
|     constexpr uint32_t MASK_1x2 = 0b00000011; | |
|     constexpr uint32_t MASK_2x1 = 0b00010001; | |
|     constexpr uint32_t MASK_2x2 = 0b00110011; | |
| 
 | |
|     uint32_t flags = MASK_2x2 << head; // fill 2x2 block | |
|     for (int addr = 0, offset = 1; range; range >>= 2, ++offset) { // traverse every 2-bit | |
|         auto num = low_zero_num(~flags); | |
|         addr += num; // next unfilled block | |
|         flags >>= num; | |
|         switch (range & 0b11) { | |
|             case 0b00: // space | |
|             case 0b11: // 1x1 block | |
|                 flags |= MASK_1x1; | |
|                 continue; | |
|             case 0b10: // 2x1 block | |
|                 if ((flags >> 4) & 0b1 || addr > 15) { // invalid case | |
|                     return offset; // broken offset | |
|                 } | |
|                 flags |= MASK_2x1; | |
|                 continue; | |
|             case 0b01: // 1x2 block | |
|                 if ((flags >> 1) & 0b1 || (addr & 0b11) == 0b11) { // invalid case | |
|                     return offset; // broken offset | |
|                 } | |
|                 flags |= MASK_1x2; | |
|                 continue; | |
|         } | |
|     } | |
|     return 0; // pass check | |
| } | |
| 
 | |
| /// Build all valid ranges of the specified head. | |
| void AllCases::BuildCases(int head, Ranges &release) noexcept { | |
|     release.reserve(ALL_CASES_NUM[head]); | |
|     auto &basic_ranges = BasicRanges::Instance().Fetch(); | |
|     for (uint32_t index = 0; index < basic_ranges.size(); ++index) { | |
|         auto offset = check_range(head, basic_ranges[index]); | |
|         if (offset) { // invalid case | |
|             auto tmp = (uint32_t)0b1 << (32 - offset * 2); // distance to next possible range | |
|             ///         !! <- broken | |
|             /// ( xx xx xx ) xx xx xx ... [reversed range] | |
|             ///         +1   00 00 00 ...     (delta) | |
|             tmp += range_reverse(basic_ranges[index]) & ~(tmp - 1); | |
|             while (range_reverse(basic_ranges[++index]) < tmp); // located next range | |
|             --index; | |
|             continue; | |
|         } | |
|         release.emplace_back(range_reverse(basic_ranges[index])); // release valid case | |
|     } | |
| } | |
| 
 | |
| /// Execute the build process and ensure thread safety. | |
| void AllCases::Build() noexcept { | |
|     BuildParallel([](auto &&func) { | |
|         func(); | |
|     }); | |
| } | |
| 
 | |
| /// Execute the build process in parallel without blocking. | |
| void AllCases::BuildParallelAsync(Executor &&executor, Notifier &&callback) noexcept { | |
|     if (available_) { | |
|         return; // reduce consumption of mutex | |
|     } | |
|     building_.lock(); | |
|     if (available_) { | |
|         building_.unlock(); | |
|         return; // data is already available | |
|     } | |
|     auto counter = std::make_shared<std::atomic<int>>(0); | |
|     auto all_done = std::make_shared<Notifier>(std::move(callback)); | |
|     for (auto head : case_heads()) { | |
|         executor([this, head, counter, all_done]() { | |
|             BuildCases(head, GetCases()[head]); | |
|             if (counter->fetch_add(1) == case_heads().size() - 1) { | |
|                 available_ = true; | |
|                 building_.unlock(); // release building mutex | |
|                 all_done->operator()(); // trigger callback | |
|             } | |
|         }); | |
|     } | |
| } | |
| 
 | |
| /// Execute the build process with parallel support and ensure thread safety. | |
| void AllCases::BuildParallel(Executor &&executor) noexcept { | |
|     if (available_) { | |
|         return; // reduce consumption of mutex | |
|     } | |
|     std::lock_guard<std::mutex> guard(building_); | |
|     if (available_) { | |
|         return; // data is already available | |
|     } | |
|     std::vector<std::future<void>> futures; | |
|     for (auto head : case_heads()) { | |
|         // TODO: using std::move_only_function in C++23 | |
|         //       -> avoid using std::shared_ptr<std::promise<void>> | |
|         auto promise = std::make_shared<std::promise<void>>(); | |
|         futures.emplace_back(promise->get_future()); | |
|         executor([head, promise = std::move(promise)]() { | |
|             BuildCases(head, GetCases()[head]); | |
|             promise->set_value(); // subtask completed notification | |
|         }); | |
|     } | |
|     for (auto &x : futures) { | |
|         x.get(); // wait until all subtasks completed | |
|     } | |
|     available_ = true; | |
| } | |
| 
 | |
| RangesUnion& AllCases::GetCases() noexcept { | |
|     static RangesUnion cases; | |
|     return cases; | |
| } | |
| 
 | |
| AllCases& AllCases::Instance() noexcept { | |
|     static AllCases instance; | |
|     return instance; | |
| } | |
| 
 | |
| const RangesUnion& AllCases::Fetch() noexcept { | |
|     this->Build(); | |
|     return GetCases(); | |
| } | |
| 
 | |
| bool AllCases::IsAvailable() const noexcept { | |
|     return available_; | |
| } | |
| 
 | |
| } // namespace cases | |
| } // namespace klotski
 | |
| 
 |