9 #ifndef EAGINE_MESSAGE_BUS_SERVICE_SUDOKU_HPP
10 #define EAGINE_MESSAGE_BUS_SERVICE_SUDOKU_HPP
12 #include "../../bool_aggregate.hpp"
13 #include "../../flat_set.hpp"
14 #include "../../int_constant.hpp"
15 #include "../../math/functions.hpp"
16 #include "../../maybe_unused.hpp"
17 #include "../../serialize/type/sudoku.hpp"
18 #include "../../sudoku.hpp"
19 #include "../serialize.hpp"
20 #include "../subscriber.hpp"
30 template <
template <
unsigned>
class U>
31 struct sudoku_rank_tuple
32 : std::tuple<nothing_t, nothing_t, nothing_t, U<3>, U<4>, U<5>, U<6>> {
34 std::tuple<nothing_t, nothing_t, nothing_t, U<3>, U<4>, U<5>, U<6>>;
36 sudoku_rank_tuple() =
default;
38 template <
typename... Args>
39 sudoku_rank_tuple(
const Args&... args)
50 auto get(unsigned_constant<S>) noexcept ->
auto& {
51 return std::get<S>(*
this);
55 auto get(unsigned_constant<S>)
const noexcept ->
const auto& {
56 return std::get<S>(*
this);
60 template <
typename Function,
typename... RankTuple>
61 static inline void for_each_sudoku_rank_unit(Function func, RankTuple&... t) {
62 func(std::get<3>(t)...);
63 func(std::get<4>(t)...);
64 func(std::get<5>(t)...);
65 func(std::get<6>(t)...);
69 static inline auto sudoku_search_msg(unsigned_constant<S>) noexcept {
70 if constexpr(S == 3) {
73 if constexpr(S == 4) {
76 if constexpr(S == 5) {
79 if constexpr(S == 6) {
85 static inline auto sudoku_alive_msg(unsigned_constant<S>) noexcept {
86 if constexpr(S == 3) {
89 if constexpr(S == 4) {
92 if constexpr(S == 5) {
95 if constexpr(S == 6) {
100 template <
unsigned S>
101 static inline auto sudoku_query_msg(unsigned_constant<S>) noexcept {
102 if constexpr(S == 3) {
105 if constexpr(S == 4) {
108 if constexpr(S == 5) {
111 if constexpr(S == 6) {
116 template <
unsigned S>
117 static inline auto sudoku_solved_msg(unsigned_constant<S>) noexcept
119 if constexpr(S == 3) {
122 if constexpr(S == 4) {
125 if constexpr(S == 5) {
128 if constexpr(S == 6) {
133 template <
unsigned S>
134 static inline auto sudoku_candidate_msg(unsigned_constant<S>) noexcept
136 if constexpr(S == 3) {
139 if constexpr(S == 4) {
142 if constexpr(S == 5) {
145 if constexpr(S == 6) {
150 template <
unsigned S>
151 static inline auto sudoku_done_msg(unsigned_constant<S>) noexcept
153 if constexpr(S == 3) {
156 if constexpr(S == 4) {
159 if constexpr(S == 5) {
162 if constexpr(S == 6) {
167 template <
unsigned S>
169 sudoku_response_msg(unsigned_constant<S> rank,
bool is_solved) noexcept
171 return is_solved ? sudoku_solved_msg(rank) : sudoku_candidate_msg(rank);
174 template <
typename Base = subscriber>
175 class sudoku_helper :
public Base {
176 using This = sudoku_helper;
184 sudoku_rank_tuple<unsigned_constant> ranks;
185 for_each_sudoku_rank_unit(
187 Base::add_method(
this, _bind_handle_search(rank));
188 Base::add_method(
this, _bind_handle_board(rank));
197 some_true something_done{};
198 something_done(Base::update());
200 for_each_sudoku_rank_unit(
202 if(
info.update(this->bus(), _compressor)) {
208 return something_done;
211 void mark_activity() {
212 _activity_time = std::chrono::steady_clock::now();
215 auto idle_time() const noexcept {
216 return std::chrono::steady_clock::now() - _activity_time;
220 template <
unsigned S>
221 auto _handle_search(
const message_context&, stored_message& message)
223 _infos.get(unsigned_constant<S>{}).on_search(
message.source_id);
228 template <
unsigned S>
229 static constexpr
auto
230 _bind_handle_search(unsigned_constant<S> rank) noexcept {
231 return message_handler_map<member_function_constant<
232 bool (This::*)(
const message_context&, stored_message&),
233 &This::_handle_search<S>>>{sudoku_search_msg(rank)};
236 template <
unsigned S>
237 auto _handle_board(
const message_context&, stored_message& message)
239 const unsigned_constant<S> rank{};
240 auto&
info = _infos.get(rank);
241 basic_sudoku_board<S> board{
info.traits};
243 const auto serialized{
248 if(EAGINE_LIKELY(serialized)) {
256 template <
unsigned S>
257 static constexpr
auto
258 _bind_handle_board(unsigned_constant<S> rank) noexcept {
259 return message_handler_map<member_function_constant<
260 bool (This::*)(
const message_context&, stored_message&),
261 &This::_handle_board<S>>>{sudoku_query_msg(rank)};
264 template <
unsigned S>
266 default_sudoku_board_traits<S> traits;
268 std::size_t counter{0U};
270 std::tuple<identifier_t, message_sequence_t, basic_sudoku_board<S>>>
273 flat_set<identifier_t> searches;
276 searches.insert(source_id);
282 basic_sudoku_board<S> board) {
283 searches.insert(source_id);
284 boards.emplace_back(source_id, sequence_no, std::move(board));
287 auto update(endpoint& bus,
const data_compressor& compressor) ->
bool {
288 const unsigned_constant<S> rank{};
289 some_true something_done;
291 for(
auto target_id : searches) {
292 message_view response{};
293 response.set_target_id(target_id);
294 bus.post(sudoku_alive_msg(rank), response);
299 if(!boards.empty()) {
300 const auto target_id = std::get<0>(boards.back());
301 const auto sequence_no = std::get<1>(boards.back());
302 auto board = std::get<2>(boards.back());
305 auto process_candidate = [&](
auto& candidate) {
307 const bool is_solved = candidate.is_solved();
310 const auto serialized{
312 candidate,
cover(temp), compressor)
314 EAGINE_ASSERT(serialized);
316 message_view response{
extract(serialized)};
317 response.set_target_id(target_id);
318 response.set_sequence_no(sequence_no);
319 bus.post(sudoku_response_msg(rank, is_solved), response);
322 board.for_each_alternative(
323 board.find_unsolved(), process_candidate);
325 message_view response{};
326 response.set_target_id(target_id);
327 response.set_sequence_no(sequence_no);
328 bus.post(sudoku_done_msg(rank), response);
331 return something_done;
335 data_compressor _compressor{};
337 sudoku_rank_tuple<rank_info> _infos;
339 std::chrono::steady_clock::time_point _activity_time{
340 std::chrono::steady_clock::now()};
343 template <
typename Base = subscriber,
typename Key =
int>
344 class sudoku_solver :
public Base {
345 using This = sudoku_solver;
353 sudoku_rank_tuple<unsigned_constant> ranks;
354 for_each_sudoku_rank_unit(
356 Base::add_method(
this, _bind_handle_alive(rank));
357 Base::add_method(
this, _bind_handle_candidate(rank));
358 Base::add_method(
this, _bind_handle_solved(rank));
359 Base::add_method(
this, _bind_handle_done(rank));
365 template <
unsigned S>
366 auto enqueue(Key key, basic_sudoku_board<S> board) ->
auto& {
367 _infos.get(unsigned_constant<S>{})
368 .add_board(std::move(key), std::move(board));
372 auto has_work() const noexcept ->
bool {
374 for_each_sudoku_rank_unit(
375 [&](
const auto& info) { result |=
info.has_work(); }, _infos);
380 auto is_done() const noexcept ->
bool {
385 some_true something_done{};
386 something_done(Base::update());
388 for_each_sudoku_rank_unit(
390 something_done(
info.handle_timeouted(*
this));
391 something_done(
info.send_boards(this->bus(), _compressor));
392 something_done(
info.search_helpers(this->bus()));
396 return something_done;
399 template <
unsigned S>
400 auto reset(unsigned_constant<S> rank) noexcept ->
auto& {
401 _infos.get(rank).reset(*
this);
405 template <
unsigned S>
406 auto has_enqueued(
const Key& key, unsigned_constant<S> rank) ->
bool {
407 return _infos.get(rank).has_enqueued(key);
410 template <
unsigned S>
411 auto set_solution_timeout(
412 unsigned_constant<S> rank,
413 std::chrono::seconds sec) noexcept ->
bool {
414 return _infos.get(rank).solution_timeout.reset(sec);
417 template <
unsigned S>
418 auto solution_timeouted(unsigned_constant<S> rank)
const noexcept ->
bool {
419 return _infos.get(rank).solution_timeout.is_expired();
422 virtual auto already_done(
const Key&, unsigned_constant<3>) ->
bool {
425 virtual auto already_done(
const Key&, unsigned_constant<4>) ->
bool {
428 virtual auto already_done(
const Key&, unsigned_constant<5>) ->
bool {
431 virtual auto already_done(
const Key&, unsigned_constant<6>) ->
bool {
435 virtual void on_solved(
identifier_t,
const Key&, basic_sudoku_board<3>&) {}
436 virtual void on_solved(
identifier_t,
const Key&, basic_sudoku_board<4>&) {}
437 virtual void on_solved(
identifier_t,
const Key&, basic_sudoku_board<5>&) {}
438 virtual void on_solved(
identifier_t,
const Key&, basic_sudoku_board<6>&) {}
441 template <
unsigned S>
444 default_sudoku_board_traits<S> traits;
445 timeout search_timeout{std::chrono::seconds(3),
nothing};
446 timeout solution_timeout{std::chrono::seconds(S * S * S * S)};
448 flat_map<Key, std::vector<basic_sudoku_board<S>>> key_boards;
450 struct pending_info {
451 pending_info(basic_sudoku_board<S> b)
452 : board{std::move(b)} {}
454 basic_sudoku_board<S> board;
460 std::vector<pending_info> pending;
462 flat_set<identifier_t> ready_helpers;
463 flat_set<identifier_t> used_helpers;
465 std::default_random_engine randeng{std::random_device{}()};
467 auto has_work() const noexcept {
468 return !key_boards.empty() || !pending.empty();
471 void add_board(Key key, basic_sudoku_board<S> board) {
472 const auto alternative_count = board.alternative_count();
473 auto& boards = key_boards[key];
474 auto pos = std::lower_bound(
478 [=](
const auto& entry,
auto value) {
479 return entry.alternative_count() > value;
481 boards.emplace(pos, std::move(board));
484 auto search_helpers(endpoint& bus) ->
bool {
485 some_true something_done;
487 bus.broadcast(sudoku_search_msg(unsigned_constant<S>{}));
488 search_timeout.reset();
491 return something_done;
494 auto handle_timeouted(This& solver) ->
bool {
502 used_helpers.erase(entry.used_helper);
503 const unsigned_constant<S> rank{};
504 if(!solver.already_done(entry.key, rank)) {
505 entry.board.for_each_alternative(
506 entry.board.find_unsolved(),
507 [&](auto& candidate) {
508 if(candidate.is_solved()) {
515 std::move(entry.key),
516 std::move(candidate));
528 .log_warning(
"replacing ${count} timeouted boards")
530 .arg(
EAGINE_ID(enqueued), key_boards.size())
532 .arg(
EAGINE_ID(ready), ready_helpers.size())
533 .arg(
EAGINE_ID(used), used_helpers.size())
539 void handle_response(
542 stored_message& message) {
543 const unsigned_constant<S> rank{};
544 basic_sudoku_board<S> board{traits};
546 const auto deserialized{
548 board,
message.content(), parent._compressor)
551 if(EAGINE_LIKELY(deserialized)) {
552 const auto pos = std::find_if(
553 pending.begin(), pending.end(), [&](
const auto& entry) {
554 return entry.sequence_no == message.sequence_no;
557 if(pos != pending.end()) {
559 if(msg_id == sudoku_solved_msg(rank)) {
560 EAGINE_ASSERT(board.is_solved());
565 [&](
const auto& entry) {
566 return pos->key == std::get<0>(entry);
569 parent.on_solved(pos->used_helper, pos->key, board);
570 solution_timeout.reset();
572 add_board(pos->key, std::move(board));
574 pos->too_late.reset();
581 data_compressor& compressor,
583 if(!key_boards.empty()) {
585 key_boards.begin() + (query_sequence % key_boards.size());
586 EAGINE_ASSERT(kbpos < key_boards.end());
587 auto& [key, boards] = *kbpos;
588 std::binomial_distribution dist(
592 auto pos = std::next(boards.begin(), dist(randeng));
595 const auto serialized{
599 EAGINE_ASSERT(serialized);
601 const auto sequence_no = query_sequence++;
602 message_view response{
extract(serialized)};
603 response.set_target_id(helper_id);
604 response.set_sequence_no(sequence_no);
605 bus.post(sudoku_query_msg(unsigned_constant<S>{}), response);
607 pending.emplace_back(std::move(board));
608 auto& query = pending.back();
609 query.used_helper = helper_id;
610 query.sequence_no = sequence_no;
611 query.key = std::move(key);
612 query.too_late.reset(std::chrono::seconds(S * S));
615 key_boards.erase(kbpos);
618 used_helpers.insert(helper_id);
619 ready_helpers.erase(helper_id);
625 auto send_boards(endpoint& bus, data_compressor& compressor) ->
bool {
626 some_true something_done;
628 while(!ready_helpers.empty()) {
629 std::uniform_int_distribution<std::size_t> dist(
630 0U, ready_helpers.size() - 1U);
632 std::next(ready_helpers.begin(), dist(randeng));
634 if(!send_board_to(bus, compressor, *pos)) {
639 return something_done;
643 const auto pos = std::find_if(
644 pending.begin(), pending.end(), [&](
const auto& entry) {
645 return entry.sequence_no == sequence_no;
647 if(pos != pending.end()) {
648 used_helpers.erase(pos->used_helper);
649 ready_helpers.insert(pos->used_helper);
655 if(used_helpers.find(
id) == used_helpers.end()) {
656 ready_helpers.insert(
id);
660 auto has_enqueued(
const Key& key) ->
bool {
664 [&](
const auto& entry) {
665 return std::get<0>(entry) == key;
666 }) != key_boards.end() ||
668 pending.begin(), pending.end(), [&](
const auto& entry) {
669 return entry.key == key;
673 void reset(This& parent) noexcept {
676 used_helpers.clear();
677 solution_timeout.reset();
680 .log_info(
"reset sudoku solution")
685 data_compressor _compressor{};
687 sudoku_rank_tuple<rank_info> _infos;
689 template <
unsigned S>
690 auto _handle_alive(
const message_context&, stored_message& message)
692 _infos.get(unsigned_constant<S>{}).helper_alive(
message.source_id);
696 template <
unsigned S>
697 static constexpr
auto
698 _bind_handle_alive(unsigned_constant<S> rank) noexcept {
699 return message_handler_map<member_function_constant<
700 bool (This::*)(
const message_context&, stored_message&),
701 &This::_handle_alive<S>>>{sudoku_alive_msg(rank)};
704 template <
unsigned S>
705 auto _handle_board(
const message_context& msg_ctx, stored_message& message)
708 _infos.get(unsigned_constant<S>{})
709 .handle_response(*
this, msg_ctx.msg_id(), message);
713 template <
unsigned S>
714 static constexpr
auto
715 _bind_handle_candidate(unsigned_constant<S> rank) noexcept {
716 return message_handler_map<member_function_constant<
717 bool (This::*)(
const message_context&, stored_message&),
718 &This::_handle_board<S>>>{sudoku_candidate_msg(rank)};
721 template <
unsigned S>
722 static constexpr
auto
723 _bind_handle_solved(unsigned_constant<S> rank) noexcept {
724 return message_handler_map<member_function_constant<
725 bool (This::*)(
const message_context&, stored_message&),
726 &This::_handle_board<S>>>{sudoku_solved_msg(rank)};
729 template <
unsigned S>
730 auto _handle_done(
const message_context&, stored_message& message) ->
bool {
731 _infos.get(unsigned_constant<S>{}).pending_done(
message.sequence_no);
735 template <
unsigned S>
736 static constexpr
auto _bind_handle_done(unsigned_constant<S> rank) noexcept {
737 return message_handler_map<member_function_constant<
738 bool (This::*)(
const message_context&, stored_message&),
739 &This::_handle_done<S>>>{sudoku_done_msg(rank)};
743 template <
unsigned S>
746 using Coord = std::tuple<int, int>;
748 auto get_board(Coord coord)
const noexcept ->
const basic_sudoku_board<S>* {
749 const auto pos = _boards.find(coord);
750 if(pos != _boards.end()) {
756 auto get_board(
int x,
int y)
const noexcept {
757 return get_board({x, y});
760 auto set_board(Coord coord, basic_sudoku_board<S> board) ->
bool {
761 return _boards.try_emplace(std::move(coord), std::move(board)).second;
764 void set_extent(Coord min, Coord max) noexcept {
765 _minu = std::get<0>(min);
766 _minv = std::get<1>(min);
767 _maxu = std::get<0>(max);
768 _maxv = std::get<1>(max);
771 void set_extent(Coord max) noexcept {
772 set_extent({0, 0}, max);
775 auto is_in_extent(
int x,
int y)
const noexcept ->
bool {
776 const int u = x * S * (S - 2);
777 const int v = y * S * (S - 2);
778 return (u >= _minu) && (u < _maxu) && (v >= _minv) && (v < _maxv);
781 auto boards_extent(Coord min, Coord max)
const
782 -> std::tuple<int, int, int, int> {
783 const auto conv = [](
int c) {
784 const auto mult = S * (S - 2);
786 return c / mult - ((-c % mult) ? 1 : 0);
788 return c / mult + (c % mult ? 1 : 0);
791 conv(std::get<0>(min)),
792 conv(std::get<1>(min)),
793 conv(std::get<0>(max)),
794 conv(std::get<1>(max))};
797 auto boards_extent()
const {
798 return boards_extent({_minu, _minv}, {_maxu, _maxv});
801 auto are_complete(Coord min, Coord max)
const ->
bool {
802 const auto [xmin, ymin, xmax, ymax] = boards_extent(min, max);
805 if(!get_board(x, y)) {
813 auto are_complete() const ->
bool {
814 return are_complete({_minu, _minv}, {_maxu, _maxv});
821 const basic_sudoku_board_traits<S>& traits)
const -> std::ostream& {
822 const auto [xmin, ymin, xmax, ymax] = boards_extent(min, max);
828 auto board = get_board(x, y);
834 extract(board).get({bx, by, cx, cy}));
836 traits.print_empty(out);
848 auto print_progress(std::ostream& out, Coord min, Coord max)
const
850 const auto [xmin, ymin, xmax, ymax] = boards_extent(min, max);
854 if(get_board(x, y)) {
865 auto print(std::ostream& out, Coord min, Coord max)
const -> std::ostream& {
866 return print(out, min, max, _traits);
870 print(std::ostream& out,
const basic_sudoku_board_traits<S>& traits)
const
872 return print(out, {_minu, _minv}, {_maxu, _maxv}, traits);
875 auto print(std::ostream& out)
const ->
auto& {
876 return print(out, {_minu, _minv}, {_maxu, _maxv});
879 auto print_progress(std::ostream& out)
const ->
auto& {
880 return print_progress(out, {_minu, _minv}, {_maxu, _maxv});
883 auto reset() noexcept -> auto& {
889 auto new_board() noexcept -> basic_sudoku_board<S> {
898 flat_map<Coord, basic_sudoku_board<S>> _boards;
899 default_sudoku_board_traits<S> _traits;
902 template <
typename Base = subscriber>
903 class sudoku_tiling :
public sudoku_solver<Base, std::tuple<int, int>> {
904 using base = sudoku_solver<Base, std::tuple<int, int>>;
905 using This = sudoku_tiling;
906 using Coord = std::tuple<int, int>;
912 template <
unsigned S>
914 initialize(Coord min, Coord max, Coord coord, basic_sudoku_board<S> board)
916 const auto [x, y] = coord;
917 auto&
info = _infos.get(unsigned_constant<S>{});
918 info.set_extent(min, max);
919 info.initialize(*
this, x, y, std::move(board));
923 template <
unsigned S>
924 auto initialize(Coord max, basic_sudoku_board<S> board) ->
auto& {
925 return initialize({0, 0}, max, {0, 0}, std::move(board));
928 template <
unsigned S>
929 auto reset(unsigned_constant<S> rank) ->
auto& {
931 _infos.get(rank).reset();
935 template <
unsigned S>
936 auto reinitialize(Coord max, basic_sudoku_board<S> board) ->
auto& {
937 reset(unsigned_constant<S>{});
938 return initialize(max, board);
941 template <
unsigned S>
942 auto tiling_complete(unsigned_constant<S> rank)
const noexcept ->
bool {
943 return _infos.get(rank).are_complete();
946 auto tiling_complete() const noexcept ->
bool {
948 sudoku_rank_tuple<unsigned_constant> ranks;
949 for_each_sudoku_rank_unit(
950 [&](
auto rank) { result &= tiling_complete(rank); }, ranks);
954 virtual void on_tiles_generated(
const sudoku_tiles<3>&) {}
955 virtual void on_tiles_generated(
const sudoku_tiles<4>&) {}
956 virtual void on_tiles_generated(
const sudoku_tiles<5>&) {}
957 virtual void on_tiles_generated(
const sudoku_tiles<6>&) {}
959 template <
unsigned S>
960 auto log_contribution_histogram(unsigned_constant<S> rank) ->
auto& {
961 _infos.get(rank).log_contribution_histogram(*
this);
966 template <
unsigned S>
967 struct rank_info : sudoku_tiles<S> {
970 initialize(This& solver,
int x,
int y, basic_sudoku_board<S> board) {
971 solver.enqueue({x, y}, std::move(board));
973 .log_debug(
"enqueuing initial board (${x}, ${y})")
979 void do_enqueue(This& solver,
int x,
int y) {
980 auto board{this->new_board()};
981 bool should_enqueue =
false;
984 auto left = this->get_board(x - 1, y);
985 auto down = this->get_board(x, y - 1);
989 0U, by,
extract(left).get_block(S - 1U, by));
993 bx, S - 1U,
extract(down).get_block(bx, 0U));
995 should_enqueue =
true;
998 auto right = this->get_board(x + 1, y);
999 auto down = this->get_board(x, y - 1);
1003 S - 1U, by,
extract(right).get_block(0U, by));
1007 bx, S - 1U,
extract(down).get_block(bx, 0U));
1009 should_enqueue =
true;
1012 auto down = this->get_board(x, y - 1);
1016 bx, S - 1U,
extract(down).get_block(bx, 0U));
1018 should_enqueue =
true;
1023 auto left = this->get_board(x - 1, y);
1024 auto up = this->get_board(x, y + 1);
1028 0U, by,
extract(left).get_block(S - 1U, by));
1032 bx, 0U,
extract(up).get_block(bx, S - 1U));
1034 should_enqueue =
true;
1037 auto right = this->get_board(x + 1, y);
1038 auto up = this->get_board(x, y + 1);
1042 S - 1U, by,
extract(right).get_block(0U, by));
1046 bx, 0U,
extract(up).get_block(bx, S - 1U));
1048 should_enqueue =
true;
1051 auto up = this->get_board(x, y + 1);
1055 bx, 0U,
extract(up).get_block(bx, S - 1U));
1057 should_enqueue =
true;
1062 auto left = this->get_board(x - 1, y);
1066 0U, by,
extract(left).get_block(S - 1U, by));
1068 should_enqueue =
true;
1071 auto right = this->get_board(x + 1, y);
1075 S - 1U, by,
extract(right).get_block(0U, by));
1077 should_enqueue =
true;
1081 if(should_enqueue) {
1082 solver.enqueue({x, y}, board.calculate_alternatives());
1084 .log_debug(
"enqueuing board (${x}, ${y})")
1091 void enqueue_incomplete(This& solver) {
1092 const unsigned_constant<S> rank{};
1093 const auto [xmin, ymin, xmax, ymax] = this->boards_extent();
1096 if(!this->get_board(x, y)) {
1097 if(!solver.has_enqueued({x, y}, rank)) {
1098 do_enqueue(solver, x, y);
1109 basic_sudoku_board<S> board) {
1111 if(this->set_board(coord, std::move(board))) {
1113 .log_info(
"solved board (${x}, ${y})")
1119 auto helper_pos = helper_contrib.find(helper_id);
1120 if(helper_pos == helper_contrib.end()) {
1121 helper_pos = helper_contrib.emplace(helper_id, 0).first;
1123 ++helper_pos->second;
1126 enqueue_incomplete(solver);
1128 solver.on_tiles_generated(*
this);
1131 void log_contribution_histogram(This& solver) {
1133 for(
const auto& p : helper_contrib) {
1134 max_count = std::max(max_count, std::get<1>(p));
1137 .log_stat(
"solution contributions by helpers")
1139 .arg_func([
this, max_count](logger_backend& backend) {
1140 for(
const auto& [helper_id, count] : helper_contrib) {
1151 flat_map<identifier_t, span_size_t> helper_contrib;
1154 sudoku_rank_tuple<rank_info> _infos;
1156 auto already_done(
const Coord& coord, unsigned_constant<3> rank)
1158 return _is_already_done(coord, rank);
1160 auto already_done(
const Coord& coord, unsigned_constant<4> rank)
1162 return _is_already_done(coord, rank);
1164 auto already_done(
const Coord& coord, unsigned_constant<5> rank)
1166 return _is_already_done(coord, rank);
1168 auto already_done(
const Coord& coord, unsigned_constant<6> rank)
1170 return _is_already_done(coord, rank);
1173 template <
unsigned S>
1174 auto _is_already_done(
const Coord& coord, unsigned_constant<S>& rank)
1175 const noexcept ->
bool {
1176 return _infos.get(rank).get_board(coord);
1182 basic_sudoku_board<3>& board)
final {
1183 _handle_solved(helper_id, coord, board);
1188 basic_sudoku_board<4>& board)
final {
1189 _handle_solved(helper_id, coord, board);
1194 basic_sudoku_board<5>& board)
final {
1195 _handle_solved(helper_id, coord, board);
1200 basic_sudoku_board<6>& board)
final {
1201 _handle_solved(helper_id, coord, board);
1204 template <
unsigned S>
1205 void _handle_solved(
1208 basic_sudoku_board<S>& board) {
1209 auto&
info = _infos.get(unsigned_constant<S>{});
1210 info.handle_solved(*
this, helper_id, coord, std::move(board));
1216 #endif // EAGINE_MESSAGE_BUS_SERVICE_SUDOKU_HPP