OGLplus  (0.59.0) a C++ wrapper for rendering APIs

sudoku.hpp
Go to the documentation of this file.
1 
9 #ifndef EAGINE_MESSAGE_BUS_SERVICE_SUDOKU_HPP
10 #define EAGINE_MESSAGE_BUS_SERVICE_SUDOKU_HPP
11 
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"
21 #include <algorithm>
22 #include <chrono>
23 #include <cmath>
24 #include <random>
25 #include <tuple>
26 #include <vector>
27 
28 namespace eagine::msgbus {
29 //------------------------------------------------------------------------------
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>> {
33  using base =
34  std::tuple<nothing_t, nothing_t, nothing_t, U<3>, U<4>, U<5>, U<6>>;
35 
36  sudoku_rank_tuple() = default;
37 
38  template <typename... Args>
39  sudoku_rank_tuple(const Args&... args)
40  : base{
41  nothing,
42  nothing,
43  nothing,
44  {args...},
45  {args...},
46  {args...},
47  {args...}} {}
48 
49  template <unsigned S>
50  auto get(unsigned_constant<S>) noexcept -> auto& {
51  return std::get<S>(*this);
52  }
53 
54  template <unsigned S>
55  auto get(unsigned_constant<S>) const noexcept -> const auto& {
56  return std::get<S>(*this);
57  }
58 };
59 //------------------------------------------------------------------------------
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)...);
66 }
67 //------------------------------------------------------------------------------
68 template <unsigned S>
69 static inline auto sudoku_search_msg(unsigned_constant<S>) noexcept {
70  if constexpr(S == 3) {
71  return EAGINE_MSG_ID(eagiSudoku, search3);
72  }
73  if constexpr(S == 4) {
74  return EAGINE_MSG_ID(eagiSudoku, search4);
75  }
76  if constexpr(S == 5) {
77  return EAGINE_MSG_ID(eagiSudoku, search5);
78  }
79  if constexpr(S == 6) {
80  return EAGINE_MSG_ID(eagiSudoku, search6);
81  }
82 }
83 //------------------------------------------------------------------------------
84 template <unsigned S>
85 static inline auto sudoku_alive_msg(unsigned_constant<S>) noexcept {
86  if constexpr(S == 3) {
87  return EAGINE_MSG_ID(eagiSudoku, alive3);
88  }
89  if constexpr(S == 4) {
90  return EAGINE_MSG_ID(eagiSudoku, alive4);
91  }
92  if constexpr(S == 5) {
93  return EAGINE_MSG_ID(eagiSudoku, alive5);
94  }
95  if constexpr(S == 6) {
96  return EAGINE_MSG_ID(eagiSudoku, alive6);
97  }
98 }
99 //------------------------------------------------------------------------------
100 template <unsigned S>
101 static inline auto sudoku_query_msg(unsigned_constant<S>) noexcept {
102  if constexpr(S == 3) {
103  return EAGINE_MSG_ID(eagiSudoku, query3);
104  }
105  if constexpr(S == 4) {
106  return EAGINE_MSG_ID(eagiSudoku, query4);
107  }
108  if constexpr(S == 5) {
109  return EAGINE_MSG_ID(eagiSudoku, query5);
110  }
111  if constexpr(S == 6) {
112  return EAGINE_MSG_ID(eagiSudoku, query6);
113  }
114 }
115 //------------------------------------------------------------------------------
116 template <unsigned S>
117 static inline auto sudoku_solved_msg(unsigned_constant<S>) noexcept
118  -> message_id {
119  if constexpr(S == 3) {
120  return EAGINE_MSG_ID(eagiSudoku, solved3);
121  }
122  if constexpr(S == 4) {
123  return EAGINE_MSG_ID(eagiSudoku, solved4);
124  }
125  if constexpr(S == 5) {
126  return EAGINE_MSG_ID(eagiSudoku, solved5);
127  }
128  if constexpr(S == 6) {
129  return EAGINE_MSG_ID(eagiSudoku, solved6);
130  }
131 }
132 //------------------------------------------------------------------------------
133 template <unsigned S>
134 static inline auto sudoku_candidate_msg(unsigned_constant<S>) noexcept
135  -> message_id {
136  if constexpr(S == 3) {
137  return EAGINE_MSG_ID(eagiSudoku, candidate3);
138  }
139  if constexpr(S == 4) {
140  return EAGINE_MSG_ID(eagiSudoku, candidate4);
141  }
142  if constexpr(S == 5) {
143  return EAGINE_MSG_ID(eagiSudoku, candidate5);
144  }
145  if constexpr(S == 6) {
146  return EAGINE_MSG_ID(eagiSudoku, candidate6);
147  }
148 }
149 //------------------------------------------------------------------------------
150 template <unsigned S>
151 static inline auto sudoku_done_msg(unsigned_constant<S>) noexcept
152  -> message_id {
153  if constexpr(S == 3) {
154  return EAGINE_MSG_ID(eagiSudoku, done3);
155  }
156  if constexpr(S == 4) {
157  return EAGINE_MSG_ID(eagiSudoku, done4);
158  }
159  if constexpr(S == 5) {
160  return EAGINE_MSG_ID(eagiSudoku, done5);
161  }
162  if constexpr(S == 6) {
163  return EAGINE_MSG_ID(eagiSudoku, done6);
164  }
165 }
166 //------------------------------------------------------------------------------
167 template <unsigned S>
168 static inline auto
169 sudoku_response_msg(unsigned_constant<S> rank, bool is_solved) noexcept
170  -> message_id {
171  return is_solved ? sudoku_solved_msg(rank) : sudoku_candidate_msg(rank);
172 }
173 //------------------------------------------------------------------------------
174 template <typename Base = subscriber>
175 class sudoku_helper : public Base {
176  using This = sudoku_helper;
177 
178 protected:
179  using Base::Base;
180 
181  void add_methods() {
182  Base::add_methods();
183 
184  sudoku_rank_tuple<unsigned_constant> ranks;
185  for_each_sudoku_rank_unit(
186  [&](auto rank) {
187  Base::add_method(this, _bind_handle_search(rank));
188  Base::add_method(this, _bind_handle_board(rank));
189  },
190  ranks);
191 
192  mark_activity();
193  }
194 
195 public:
196  auto update() -> bool {
197  some_true something_done{};
198  something_done(Base::update());
199 
200  for_each_sudoku_rank_unit(
201  [&](auto& info) {
202  if(info.update(this->bus(), _compressor)) {
203  something_done();
204  }
205  },
206  _infos);
207 
208  return something_done;
209  }
210 
211  void mark_activity() {
212  _activity_time = std::chrono::steady_clock::now();
213  }
214 
215  auto idle_time() const noexcept {
216  return std::chrono::steady_clock::now() - _activity_time;
217  }
218 
219 private:
220  template <unsigned S>
221  auto _handle_search(const message_context&, stored_message& message)
222  -> bool {
223  _infos.get(unsigned_constant<S>{}).on_search(message.source_id);
224  mark_activity();
225  return true;
226  }
227 
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)};
234  }
235 
236  template <unsigned S>
237  auto _handle_board(const message_context&, stored_message& message)
238  -> bool {
239  const unsigned_constant<S> rank{};
240  auto& info = _infos.get(rank);
241  basic_sudoku_board<S> board{info.traits};
242 
243  const auto serialized{
244  (S >= 4)
245  ? default_deserialize_packed(board, message.content(), _compressor)
246  : default_deserialize(board, message.content())};
247 
248  if(EAGINE_LIKELY(serialized)) {
249  info.add_board(
250  message.source_id, message.sequence_no, std::move(board));
251  mark_activity();
252  }
253  return true;
254  }
255 
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)};
262  }
263 
264  template <unsigned S>
265  struct rank_info {
266  default_sudoku_board_traits<S> traits;
267 
268  std::size_t counter{0U};
269  std::vector<
270  std::tuple<identifier_t, message_sequence_t, basic_sudoku_board<S>>>
271  boards;
272 
273  flat_set<identifier_t> searches;
274 
275  void on_search(identifier_t source_id) {
276  searches.insert(source_id);
277  }
278 
279  void add_board(
280  identifier_t source_id,
281  message_sequence_t sequence_no,
282  basic_sudoku_board<S> board) {
283  searches.insert(source_id);
284  boards.emplace_back(source_id, sequence_no, std::move(board));
285  }
286 
287  auto update(endpoint& bus, const data_compressor& compressor) -> bool {
288  const unsigned_constant<S> rank{};
289  some_true something_done;
290 
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);
295  something_done();
296  }
297  searches.clear();
298 
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());
303  boards.pop_back();
304 
305  auto process_candidate = [&](auto& candidate) {
306  ++counter;
307  const bool is_solved = candidate.is_solved();
308 
309  auto temp{default_serialize_buffer_for(candidate)};
310  const auto serialized{
311  (S >= 4) ? default_serialize_packed(
312  candidate, cover(temp), compressor)
313  : default_serialize(candidate, cover(temp))};
314  EAGINE_ASSERT(serialized);
315 
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);
320  };
321 
322  board.for_each_alternative(
323  board.find_unsolved(), process_candidate);
324 
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);
329  something_done();
330  }
331  return something_done;
332  }
333  };
334 
335  data_compressor _compressor{};
336 
337  sudoku_rank_tuple<rank_info> _infos;
338 
339  std::chrono::steady_clock::time_point _activity_time{
340  std::chrono::steady_clock::now()};
341 };
342 //------------------------------------------------------------------------------
343 template <typename Base = subscriber, typename Key = int>
344 class sudoku_solver : public Base {
345  using This = sudoku_solver;
346 
347 protected:
348  using Base::Base;
349 
350  void add_methods() {
351  Base::add_methods();
352 
353  sudoku_rank_tuple<unsigned_constant> ranks;
354  for_each_sudoku_rank_unit(
355  [&](auto rank) {
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));
360  },
361  ranks);
362  }
363 
364 public:
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));
369  return *this;
370  }
371 
372  auto has_work() const noexcept -> bool {
373  bool result = false;
374  for_each_sudoku_rank_unit(
375  [&](const auto& info) { result |= info.has_work(); }, _infos);
376 
377  return result;
378  }
379 
380  auto is_done() const noexcept -> bool {
381  return !has_work();
382  }
383 
384  auto update() -> bool {
385  some_true something_done{};
386  something_done(Base::update());
387 
388  for_each_sudoku_rank_unit(
389  [&](auto& info) {
390  something_done(info.handle_timeouted(*this));
391  something_done(info.send_boards(this->bus(), _compressor));
392  something_done(info.search_helpers(this->bus()));
393  },
394  _infos);
395 
396  return something_done;
397  }
398 
399  template <unsigned S>
400  auto reset(unsigned_constant<S> rank) noexcept -> auto& {
401  _infos.get(rank).reset(*this);
402  return *this;
403  }
404 
405  template <unsigned S>
406  auto has_enqueued(const Key& key, unsigned_constant<S> rank) -> bool {
407  return _infos.get(rank).has_enqueued(key);
408  }
409 
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);
415  }
416 
417  template <unsigned S>
418  auto solution_timeouted(unsigned_constant<S> rank) const noexcept -> bool {
419  return _infos.get(rank).solution_timeout.is_expired();
420  }
421 
422  virtual auto already_done(const Key&, unsigned_constant<3>) -> bool {
423  return false;
424  }
425  virtual auto already_done(const Key&, unsigned_constant<4>) -> bool {
426  return false;
427  }
428  virtual auto already_done(const Key&, unsigned_constant<5>) -> bool {
429  return false;
430  }
431  virtual auto already_done(const Key&, unsigned_constant<6>) -> bool {
432  return false;
433  }
434 
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>&) {}
439 
440 private:
441  template <unsigned S>
442  struct rank_info {
443  message_sequence_t query_sequence{0};
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)};
447 
448  flat_map<Key, std::vector<basic_sudoku_board<S>>> key_boards;
449 
450  struct pending_info {
451  pending_info(basic_sudoku_board<S> b)
452  : board{std::move(b)} {}
453 
454  basic_sudoku_board<S> board;
455  identifier_t used_helper{0U};
456  message_sequence_t sequence_no{0U};
457  Key key{};
458  timeout too_late{};
459  };
460  std::vector<pending_info> pending;
461 
462  flat_set<identifier_t> ready_helpers;
463  flat_set<identifier_t> used_helpers;
464 
465  std::default_random_engine randeng{std::random_device{}()};
466 
467  auto has_work() const noexcept {
468  return !key_boards.empty() || !pending.empty();
469  }
470 
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(
475  boards.begin(),
476  boards.end(),
477  alternative_count,
478  [=](const auto& entry, auto value) {
479  return entry.alternative_count() > value;
480  });
481  boards.emplace(pos, std::move(board));
482  }
483 
484  auto search_helpers(endpoint& bus) -> bool {
485  some_true something_done;
486  if(search_timeout) {
487  bus.broadcast(sudoku_search_msg(unsigned_constant<S>{}));
488  search_timeout.reset();
489  something_done();
490  }
491  return something_done;
492  }
493 
494  auto handle_timeouted(This& solver) -> bool {
495  span_size_t count = 0;
496  pending.erase(
497  std::remove_if(
498  pending.begin(),
499  pending.end(),
500  [&](auto& entry) {
501  if(entry.too_late) {
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()) {
509  solver.on_solved(
510  entry.used_helper,
511  entry.key,
512  candidate);
513  } else {
514  add_board(
515  std::move(entry.key),
516  std::move(candidate));
517  ++count;
518  }
519  });
520  }
521  return true;
522  }
523  return false;
524  }),
525  pending.end());
526  if(count > 0) {
527  solver.bus()
528  .log_warning("replacing ${count} timeouted boards")
529  .arg(EAGINE_ID(count), count)
530  .arg(EAGINE_ID(enqueued), key_boards.size())
531  .arg(EAGINE_ID(pending), pending.size())
532  .arg(EAGINE_ID(ready), ready_helpers.size())
533  .arg(EAGINE_ID(used), used_helpers.size())
534  .arg(EAGINE_ID(rank), S);
535  }
536  return count > 0;
537  }
538 
539  void handle_response(
540  This& parent,
541  message_id msg_id,
542  stored_message& message) {
543  const unsigned_constant<S> rank{};
544  basic_sudoku_board<S> board{traits};
545 
546  const auto deserialized{
547  (S >= 4) ? default_deserialize_packed(
548  board, message.content(), parent._compressor)
549  : default_deserialize(board, message.content())};
550 
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;
555  });
556 
557  if(pos != pending.end()) {
558 
559  if(msg_id == sudoku_solved_msg(rank)) {
560  EAGINE_ASSERT(board.is_solved());
561  key_boards.erase(
562  std::remove_if(
563  key_boards.begin(),
564  key_boards.end(),
565  [&](const auto& entry) {
566  return pos->key == std::get<0>(entry);
567  }),
568  key_boards.end());
569  parent.on_solved(pos->used_helper, pos->key, board);
570  solution_timeout.reset();
571  } else {
572  add_board(pos->key, std::move(board));
573  }
574  pos->too_late.reset();
575  }
576  }
577  }
578 
579  auto send_board_to(
580  endpoint& bus,
581  data_compressor& compressor,
582  identifier_t helper_id) -> bool {
583  if(!key_boards.empty()) {
584  auto kbpos =
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(
589  boards.size() - 1U,
590  math::blend(1.0, 0.8, std::exp(-boards.size())));
591 
592  auto pos = std::next(boards.begin(), dist(randeng));
593  auto& board = *pos;
594  auto temp{default_serialize_buffer_for(board)};
595  const auto serialized{
596  (S >= 4)
597  ? default_serialize_packed(board, cover(temp), compressor)
598  : default_serialize(board, cover(temp))};
599  EAGINE_ASSERT(serialized);
600 
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);
606 
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));
613  boards.erase(pos);
614  if(boards.empty()) {
615  key_boards.erase(kbpos);
616  }
617 
618  used_helpers.insert(helper_id);
619  ready_helpers.erase(helper_id);
620  return true;
621  }
622  return false;
623  }
624 
625  auto send_boards(endpoint& bus, data_compressor& compressor) -> bool {
626  some_true something_done;
627 
628  while(!ready_helpers.empty()) {
629  std::uniform_int_distribution<std::size_t> dist(
630  0U, ready_helpers.size() - 1U);
631  const auto pos =
632  std::next(ready_helpers.begin(), dist(randeng));
633 
634  if(!send_board_to(bus, compressor, *pos)) {
635  break;
636  }
637  something_done();
638  }
639  return something_done;
640  }
641 
642  void pending_done(message_sequence_t sequence_no) {
643  const auto pos = std::find_if(
644  pending.begin(), pending.end(), [&](const auto& entry) {
645  return entry.sequence_no == sequence_no;
646  });
647  if(pos != pending.end()) {
648  used_helpers.erase(pos->used_helper);
649  ready_helpers.insert(pos->used_helper);
650  pending.erase(pos);
651  }
652  }
653 
654  void helper_alive(identifier_t id) {
655  if(used_helpers.find(id) == used_helpers.end()) {
656  ready_helpers.insert(id);
657  }
658  }
659 
660  auto has_enqueued(const Key& key) -> bool {
661  return std::find_if(
662  key_boards.begin(),
663  key_boards.end(),
664  [&](const auto& entry) {
665  return std::get<0>(entry) == key;
666  }) != key_boards.end() ||
667  std::find_if(
668  pending.begin(), pending.end(), [&](const auto& entry) {
669  return entry.key == key;
670  }) != pending.end();
671  }
672 
673  void reset(This& parent) noexcept {
674  key_boards.clear();
675  pending.clear();
676  used_helpers.clear();
677  solution_timeout.reset();
678 
679  parent.bus()
680  .log_info("reset sudoku solution")
681  .arg(EAGINE_ID(rank), S);
682  }
683  };
684 
685  data_compressor _compressor{};
686 
687  sudoku_rank_tuple<rank_info> _infos;
688 
689  template <unsigned S>
690  auto _handle_alive(const message_context&, stored_message& message)
691  -> bool {
692  _infos.get(unsigned_constant<S>{}).helper_alive(message.source_id);
693  return true;
694  }
695 
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)};
702  }
703 
704  template <unsigned S>
705  auto _handle_board(const message_context& msg_ctx, stored_message& message)
706  -> bool {
707 
708  _infos.get(unsigned_constant<S>{})
709  .handle_response(*this, msg_ctx.msg_id(), message);
710  return true;
711  }
712 
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)};
719  }
720 
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)};
727  }
728 
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);
732  return true;
733  }
734 
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)};
740  }
741 };
742 //------------------------------------------------------------------------------
743 template <unsigned S>
744 class sudoku_tiles {
745 public:
746  using Coord = std::tuple<int, int>;
747 
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()) {
751  return &pos->second;
752  }
753  return nullptr;
754  }
755 
756  auto get_board(int x, int y) const noexcept {
757  return get_board({x, y});
758  }
759 
760  auto set_board(Coord coord, basic_sudoku_board<S> board) -> bool {
761  return _boards.try_emplace(std::move(coord), std::move(board)).second;
762  }
763 
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);
769  }
770 
771  void set_extent(Coord max) noexcept {
772  set_extent({0, 0}, max);
773  }
774 
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);
779  }
780 
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);
785  if(c < 0) {
786  return c / mult - ((-c % mult) ? 1 : 0);
787  }
788  return c / mult + (c % mult ? 1 : 0);
789  };
790  return {
791  conv(std::get<0>(min)),
792  conv(std::get<1>(min)),
793  conv(std::get<0>(max)),
794  conv(std::get<1>(max))};
795  }
796 
797  auto boards_extent() const {
798  return boards_extent({_minu, _minv}, {_maxu, _maxv});
799  }
800 
801  auto are_complete(Coord min, Coord max) const -> bool {
802  const auto [xmin, ymin, xmax, ymax] = boards_extent(min, max);
803  for(auto y : integer_range(ymin, ymax)) {
804  for(auto x : integer_range(xmin, xmax)) {
805  if(!get_board(x, y)) {
806  return false;
807  }
808  }
809  }
810  return true;
811  }
812 
813  auto are_complete() const -> bool {
814  return are_complete({_minu, _minv}, {_maxu, _maxv});
815  }
816 
817  auto print(
818  std::ostream& out,
819  Coord min,
820  Coord max,
821  const basic_sudoku_board_traits<S>& traits) const -> std::ostream& {
822  const auto [xmin, ymin, xmax, ymax] = boards_extent(min, max);
823 
824  for(auto y : integer_range(ymin, ymax)) {
825  for(auto by : integer_range(1U, S - 1U)) {
826  for(auto cy : integer_range(S)) {
827  for(auto x : integer_range(xmin, xmax)) {
828  auto board = get_board(x, y);
829  for(auto bx : integer_range(1U, S - 1U)) {
830  for(auto cx : integer_range(S)) {
831  if(board) {
832  traits.print(
833  out,
834  extract(board).get({bx, by, cx, cy}));
835  } else {
836  traits.print_empty(out);
837  }
838  }
839  }
840  }
841  out << '\n';
842  }
843  }
844  }
845  return out;
846  }
847 
848  auto print_progress(std::ostream& out, Coord min, Coord max) const
849  -> std::ostream& {
850  const auto [xmin, ymin, xmax, ymax] = boards_extent(min, max);
851 
852  for(auto y : integer_range(ymin, ymax)) {
853  for(auto x : integer_range(xmin, xmax)) {
854  if(get_board(x, y)) {
855  out << "██";
856  } else {
857  out << "▒▒";
858  }
859  }
860  out << '\n';
861  }
862  return out;
863  }
864 
865  auto print(std::ostream& out, Coord min, Coord max) const -> std::ostream& {
866  return print(out, min, max, _traits);
867  }
868 
869  auto
870  print(std::ostream& out, const basic_sudoku_board_traits<S>& traits) const
871  -> auto& {
872  return print(out, {_minu, _minv}, {_maxu, _maxv}, traits);
873  }
874 
875  auto print(std::ostream& out) const -> auto& {
876  return print(out, {_minu, _minv}, {_maxu, _maxv});
877  }
878 
879  auto print_progress(std::ostream& out) const -> auto& {
880  return print_progress(out, {_minu, _minv}, {_maxu, _maxv});
881  }
882 
883  auto reset() noexcept -> auto& {
884  _boards.clear();
885  return *this;
886  }
887 
888 protected:
889  auto new_board() noexcept -> basic_sudoku_board<S> {
890  return {_traits};
891  }
892 
893 private:
894  int _minu{0};
895  int _minv{0};
896  int _maxu{0};
897  int _maxv{0};
898  flat_map<Coord, basic_sudoku_board<S>> _boards;
899  default_sudoku_board_traits<S> _traits;
900 };
901 //------------------------------------------------------------------------------
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>;
907 
908 protected:
909  using base::base;
910 
911 public:
912  template <unsigned S>
913  auto
914  initialize(Coord min, Coord max, Coord coord, basic_sudoku_board<S> board)
915  -> auto& {
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));
920  return *this;
921  }
922 
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));
926  }
927 
928  template <unsigned S>
929  auto reset(unsigned_constant<S> rank) -> auto& {
930  base::reset(rank);
931  _infos.get(rank).reset();
932  return *this;
933  }
934 
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);
939  }
940 
941  template <unsigned S>
942  auto tiling_complete(unsigned_constant<S> rank) const noexcept -> bool {
943  return _infos.get(rank).are_complete();
944  }
945 
946  auto tiling_complete() const noexcept -> bool {
947  bool result = true;
948  sudoku_rank_tuple<unsigned_constant> ranks;
949  for_each_sudoku_rank_unit(
950  [&](auto rank) { result &= tiling_complete(rank); }, ranks);
951  return result;
952  }
953 
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>&) {}
958 
959  template <unsigned S>
960  auto log_contribution_histogram(unsigned_constant<S> rank) -> auto& {
961  _infos.get(rank).log_contribution_histogram(*this);
962  return *this;
963  }
964 
965 private:
966  template <unsigned S>
967  struct rank_info : sudoku_tiles<S> {
968 
969  void
970  initialize(This& solver, int x, int y, basic_sudoku_board<S> board) {
971  solver.enqueue({x, y}, std::move(board));
972  solver.bus()
973  .log_debug("enqueuing initial board (${x}, ${y})")
974  .arg(EAGINE_ID(x), x)
975  .arg(EAGINE_ID(y), y)
976  .arg(EAGINE_ID(rank), S);
977  }
978 
979  void do_enqueue(This& solver, int x, int y) {
980  auto board{this->new_board()};
981  bool should_enqueue = false;
982  if(y > 0) {
983  if(x > 0) {
984  auto left = this->get_board(x - 1, y);
985  auto down = this->get_board(x, y - 1);
986  if(left && down) {
987  for(auto by : integer_range(S - 1U)) {
988  board.set_block(
989  0U, by, extract(left).get_block(S - 1U, by));
990  }
991  for(auto bx : integer_range(1U, S)) {
992  board.set_block(
993  bx, S - 1U, extract(down).get_block(bx, 0U));
994  }
995  should_enqueue = true;
996  }
997  } else if(x < 0) {
998  auto right = this->get_board(x + 1, y);
999  auto down = this->get_board(x, y - 1);
1000  if(right && down) {
1001  for(auto by : integer_range(S - 1U)) {
1002  board.set_block(
1003  S - 1U, by, extract(right).get_block(0U, by));
1004  }
1005  for(auto bx : integer_range(S - 1U)) {
1006  board.set_block(
1007  bx, S - 1U, extract(down).get_block(bx, 0U));
1008  }
1009  should_enqueue = true;
1010  }
1011  } else {
1012  auto down = this->get_board(x, y - 1);
1013  if(down) {
1014  for(auto bx : integer_range(S)) {
1015  board.set_block(
1016  bx, S - 1U, extract(down).get_block(bx, 0U));
1017  }
1018  should_enqueue = true;
1019  }
1020  }
1021  } else if(y < 0) {
1022  if(x > 0) {
1023  auto left = this->get_board(x - 1, y);
1024  auto up = this->get_board(x, y + 1);
1025  if(left && up) {
1026  for(auto by : integer_range(1U, S)) {
1027  board.set_block(
1028  0U, by, extract(left).get_block(S - 1U, by));
1029  }
1030  for(auto bx : integer_range(1U, S)) {
1031  board.set_block(
1032  bx, 0U, extract(up).get_block(bx, S - 1U));
1033  }
1034  should_enqueue = true;
1035  }
1036  } else if(x < 0) {
1037  auto right = this->get_board(x + 1, y);
1038  auto up = this->get_board(x, y + 1);
1039  if(right && up) {
1040  for(auto by : integer_range(1U, S)) {
1041  board.set_block(
1042  S - 1U, by, extract(right).get_block(0U, by));
1043  }
1044  for(auto bx : integer_range(S - 1U)) {
1045  board.set_block(
1046  bx, 0U, extract(up).get_block(bx, S - 1U));
1047  }
1048  should_enqueue = true;
1049  }
1050  } else {
1051  auto up = this->get_board(x, y + 1);
1052  if(up) {
1053  for(auto bx : integer_range(S)) {
1054  board.set_block(
1055  bx, 0U, extract(up).get_block(bx, S - 1U));
1056  }
1057  should_enqueue = true;
1058  }
1059  }
1060  } else {
1061  if(x > 0) {
1062  auto left = this->get_board(x - 1, y);
1063  if(left) {
1064  for(auto by : integer_range(S)) {
1065  board.set_block(
1066  0U, by, extract(left).get_block(S - 1U, by));
1067  }
1068  should_enqueue = true;
1069  }
1070  } else if(x < 0) {
1071  auto right = this->get_board(x + 1, y);
1072  if(right) {
1073  for(auto by : integer_range(S)) {
1074  board.set_block(
1075  S - 1U, by, extract(right).get_block(0U, by));
1076  }
1077  should_enqueue = true;
1078  }
1079  }
1080  }
1081  if(should_enqueue) {
1082  solver.enqueue({x, y}, board.calculate_alternatives());
1083  solver.bus()
1084  .log_debug("enqueuing board (${x}, ${y})")
1085  .arg(EAGINE_ID(x), x)
1086  .arg(EAGINE_ID(y), y)
1087  .arg(EAGINE_ID(rank), S);
1088  }
1089  }
1090 
1091  void enqueue_incomplete(This& solver) {
1092  const unsigned_constant<S> rank{};
1093  const auto [xmin, ymin, xmax, ymax] = this->boards_extent();
1094  for(auto y : integer_range(ymin, ymax)) {
1095  for(auto x : integer_range(xmin, xmax)) {
1096  if(!this->get_board(x, y)) {
1097  if(!solver.has_enqueued({x, y}, rank)) {
1098  do_enqueue(solver, x, y);
1099  }
1100  }
1101  }
1102  }
1103  }
1104 
1105  void handle_solved(
1106  This& solver,
1107  identifier_t helper_id,
1108  Coord coord,
1109  basic_sudoku_board<S> board) {
1110 
1111  if(this->set_board(coord, std::move(board))) {
1112  solver.bus()
1113  .log_info("solved board (${x}, ${y})")
1114  .arg(EAGINE_ID(rank), S)
1115  .arg(EAGINE_ID(x), std::get<0>(coord))
1116  .arg(EAGINE_ID(y), std::get<1>(coord))
1117  .arg(EAGINE_ID(helper), helper_id);
1118 
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;
1122  }
1123  ++helper_pos->second;
1124  }
1125 
1126  enqueue_incomplete(solver);
1127 
1128  solver.on_tiles_generated(*this);
1129  }
1130 
1131  void log_contribution_histogram(This& solver) {
1132  span_size_t max_count = 0;
1133  for(const auto& p : helper_contrib) {
1134  max_count = std::max(max_count, std::get<1>(p));
1135  }
1136  solver.bus()
1137  .log_stat("solution contributions by helpers")
1138  .arg(EAGINE_ID(rank), S)
1139  .arg_func([this, max_count](logger_backend& backend) {
1140  for(const auto& [helper_id, count] : helper_contrib) {
1141  backend.add_float(
1142  EAGINE_ID(helper),
1143  EAGINE_ID(Histogram),
1144  float(0),
1145  float(count),
1146  float(max_count));
1147  }
1148  });
1149  }
1150 
1151  flat_map<identifier_t, span_size_t> helper_contrib;
1152  };
1153 
1154  sudoku_rank_tuple<rank_info> _infos;
1155 
1156  auto already_done(const Coord& coord, unsigned_constant<3> rank)
1157  -> bool final {
1158  return _is_already_done(coord, rank);
1159  }
1160  auto already_done(const Coord& coord, unsigned_constant<4> rank)
1161  -> bool final {
1162  return _is_already_done(coord, rank);
1163  }
1164  auto already_done(const Coord& coord, unsigned_constant<5> rank)
1165  -> bool final {
1166  return _is_already_done(coord, rank);
1167  }
1168  auto already_done(const Coord& coord, unsigned_constant<6> rank)
1169  -> bool final {
1170  return _is_already_done(coord, rank);
1171  }
1172 
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);
1177  }
1178 
1179  void on_solved(
1180  identifier_t helper_id,
1181  const Coord& coord,
1182  basic_sudoku_board<3>& board) final {
1183  _handle_solved(helper_id, coord, board);
1184  }
1185  void on_solved(
1186  identifier_t helper_id,
1187  const Coord& coord,
1188  basic_sudoku_board<4>& board) final {
1189  _handle_solved(helper_id, coord, board);
1190  }
1191  void on_solved(
1192  identifier_t helper_id,
1193  const Coord& coord,
1194  basic_sudoku_board<5>& board) final {
1195  _handle_solved(helper_id, coord, board);
1196  }
1197  void on_solved(
1198  identifier_t helper_id,
1199  const Coord& coord,
1200  basic_sudoku_board<6>& board) final {
1201  _handle_solved(helper_id, coord, board);
1202  }
1203 
1204  template <unsigned S>
1205  void _handle_solved(
1206  identifier_t helper_id,
1207  const Coord& coord,
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));
1211  }
1212 };
1213 //------------------------------------------------------------------------------
1214 } // namespace eagine::msgbus
1215 
1216 #endif // EAGINE_MESSAGE_BUS_SERVICE_SUDOKU_HPP
std::ptrdiff_t span_size_t
Signed span size type used by eagine.
Definition: types.hpp:36
#define EAGINE_ID(NAME)
Macro for constructing instances of eagine::identifier.
Definition: identifier.hpp:353
#define EAGINE_MSG_ID(API, NAME)
Macro for instantiating objects of static_message_id.
Definition: message_id.hpp:148
static constexpr auto cover(T *addr, S size) noexcept -> span_if_mutable< T >
Creates a span starting at the specified pointer and specified length.
Definition: span.hpp:465
static constexpr auto extract(api_result_value< Result, api_result_validity::never > &) noexcept -> Result &
Overload of extract for api_result_value.
Definition: c_api_wrap.hpp:270
@ info
Informational log entries.
auto update() const noexcept -> bool
Updates the internal endpoint state (should be called repeatedly).
Definition: subscriber.hpp:51
auto default_serialize_packed(T &value, memory::block blk, data_compressor compressor) -> serialization_result< memory::const_block >
Uses backend and compressor to serialize and pack a value into a memory block.
Definition: serialize.hpp:206
auto default_deserialize_packed(T &value, memory::const_block blk, data_compressor compressor) -> deserialization_result< memory::const_block >
Uses backend and compressor to deserialize and unpack a value from a block.
Definition: serialize.hpp:250
std::uint32_t message_sequence_t
Alias for message sequence number type.
Definition: types.hpp:22
auto default_serialize(T &value, memory::block blk) -> serialization_result< memory::const_block >
Uses the default backend to serialize a value into a memory block.
Definition: serialize.hpp:191
Message bus code is placed in this namespace.
Definition: eagine.hpp:58
auto default_deserialize(T &value, memory::const_block blk) -> deserialization_result< memory::const_block >
Uses the default backend to deserialize a value from a memory block.
Definition: serialize.hpp:235
static constexpr auto blend(T v1, T v2, A alpha) noexcept
Blends v1 and v2, using alpha as the blending factor.
Definition: functions.hpp:110
auto default_serialize_buffer_for(const T &inst)
Returns a suitable buffer for the serialization of the specified object.
Definition: serialize.hpp:46
std::uint64_t identifier_t
The underlying integer type for eagine::identifier.
Definition: identifier_t.hpp:19
integer_range(B, E) -> integer_range< std::common_type_t< B, E >>
Deduction guide for integer_range.
auto bus() noexcept -> auto &
Returns a reference to the associated endpoint.
Definition: subscriber.hpp:38
@ message_id
The message type id has been verified.
static constexpr nothing_t nothing
Constant of nothing_t type.
Definition: nothing.hpp:30

Copyright © 2015-2021 Matúš Chochlík.
<chochlik -at -gmail.com>
Documentation generated on Tue Apr 13 2021 by Doxygen (version 1.8.17).