summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--lingo.cpp153
1 files changed, 122 insertions, 31 deletions
diff --git a/lingo.cpp b/lingo.cpp index 1cb13ce..04c2921 100644 --- a/lingo.cpp +++ b/lingo.cpp
@@ -302,6 +302,13 @@ private:
302 std::vector<std::tuple<std::string, std::string>> puzzles_; 302 std::vector<std::tuple<std::string, std::string>> puzzles_;
303}; 303};
304 304
305struct puzzle {
306 std::string message; // exact text to be posted to discord
307 std::string solution; // pre-canonicalized for answer checking
308 std::string attachment_name; // only populated for green clues
309 std::string attachment_content; // ^
310};
311
305class lingo { 312class lingo {
306public: 313public:
307 lingo(std::mt19937& rng) : rng_(rng) {} 314 lingo(std::mt19937& rng) : rng_(rng) {}
@@ -325,8 +332,7 @@ public:
325 if (event.command.get_command_name() == "newpuzzle") { 332 if (event.command.get_command_name() == "newpuzzle") {
326 event.thinking(true); 333 event.thinking(true);
327 334
328 std::thread genpuzz(&lingo::generatePuzzle, this, event.command.get_channel().id); 335 sendPuzzle(event.command.get_channel().id);
329 genpuzz.detach();
330 } 336 }
331 }); 337 });
332 338
@@ -335,7 +341,7 @@ public:
335 uint64_t puzzle_id = static_cast<uint64_t>(event.msg.message_reference.message_id); 341 uint64_t puzzle_id = static_cast<uint64_t>(event.msg.message_reference.message_id);
336 if (answer_by_message_.count(puzzle_id)) 342 if (answer_by_message_.count(puzzle_id))
337 { 343 {
338 std::string canonical_answer = hatkirby::lowercase(answer_by_message_[puzzle_id]); 344 std::string canonical_answer = answer_by_message_[puzzle_id];
339 std::string canonical_attempt = hatkirby::lowercase(event.msg.content); 345 std::string canonical_attempt = hatkirby::lowercase(event.msg.content);
340 while (canonical_attempt.find("||") != std::string::npos) 346 while (canonical_attempt.find("||") != std::string::npos)
341 { 347 {
@@ -345,10 +351,6 @@ public:
345 { 351 {
346 canonical_attempt.erase(canonical_attempt.find(" "), 1); 352 canonical_attempt.erase(canonical_attempt.find(" "), 1);
347 } 353 }
348 while (canonical_answer.find(" ") != std::string::npos)
349 {
350 canonical_answer.erase(canonical_answer.find(" "), 1);
351 }
352 354
353 std::cout << "\"" << canonical_attempt << "\"" << std::endl; 355 std::cout << "\"" << canonical_attempt << "\"" << std::endl;
354 if (canonical_attempt == canonical_answer) 356 if (canonical_attempt == canonical_answer)
@@ -418,17 +420,18 @@ public:
418 420
419 for (;;) 421 for (;;)
420 { 422 {
421 std::thread genpuzz(&lingo::generatePuzzle, this, channel); 423 sendPuzzle(channel);
422 genpuzz.detach();
423 424
424 std::this_thread::sleep_for(std::chrono::hours(24)); 425 std::this_thread::sleep_for(std::chrono::hours(4));
425 } 426 }
426 } 427 }
427 428
428private: 429private:
429 430
430 void generatePuzzle(dpp::snowflake channel) 431 void generatePuzzle()
431 { 432 {
433 std::cout << "Generating puzzle..." << std::endl;
434
432 std::set<std::tuple<Height, Colour>> filters = { 435 std::set<std::tuple<Height, Colour>> filters = {
433 {kTop, kPurple}, 436 {kTop, kPurple},
434 {kTop, kWhite}, 437 {kTop, kWhite},
@@ -469,8 +472,8 @@ private:
469 !(verbly::word::usageDomains %= (verbly::notion::wnid == 106718862)) // ethnic slurs 472 !(verbly::word::usageDomains %= (verbly::notion::wnid == 106718862)) // ethnic slurs
470 && !(verbly::notion::wnid == 110630093); // "spastic" 473 && !(verbly::notion::wnid == 110630093); // "spastic"
471 474
472 bool generated = false; 475 std::unique_ptr<puzzle> genpuzzle;
473 while (!generated) 476 for (;;)
474 { 477 {
475 std::cout << "Generating... " << std::endl; 478 std::cout << "Generating... " << std::endl;
476 try 479 try
@@ -603,8 +606,13 @@ private:
603 std::vector<verbly::form> admissibleResults = database_->forms(admissible, {}, 10).all(); 606 std::vector<verbly::form> admissibleResults = database_->forms(admissible, {}, 10).all();
604 if (green_uses > 0 || (admissibleResults.size() <= (hints == 1 ? 2 : 5))) 607 if (green_uses > 0 || (admissibleResults.size() <= (hints == 1 ? 2 : 5)))
605 { 608 {
606#ifdef ENABLE_BOT 609 genpuzzle = std::make_unique<puzzle>();
607 dpp::message message(channel, message_text); 610 genpuzzle->message = message_text;
611 genpuzzle->solution = hatkirby::lowercase(solution.getText());
612 while (genpuzzle->solution.find(" ") != std::string::npos)
613 {
614 genpuzzle->solution.erase(genpuzzle->solution.find(" "), 1);
615 }
608 616
609 if (green_uses > 0) 617 if (green_uses > 0)
610 { 618 {
@@ -616,21 +624,11 @@ private:
616 throw std::runtime_error("Could not find image for green hint."); 624 throw std::runtime_error("Could not find image for green hint.");
617 } 625 }
618 626
619 message.add_file(std::string("SPOILER_image.") + extension, image); 627 genpuzzle->attachment_name = std::string("SPOILER_image.") + extension;
628 genpuzzle->attachment_content = std::move(image);
620 } 629 }
621 630
622 bot_->message_create(message, [this, &solution](const dpp::confirmation_callback_t& userdata) { 631 break;
623 const auto& posted_msg = std::get<dpp::message>(userdata.value);
624 std::lock_guard answer_lock(answers_mutex_);
625 if (answer_by_message_.size() > 3000)
626 {
627 answer_by_message_.clear();
628 }
629 answer_by_message_[posted_msg.id] = solution.getText();
630 });
631#endif
632
633 generated = true;
634 } else { 632 } else {
635 std::cout << "Too many (" << admissibleResults.size() << ") results." << std::endl; 633 std::cout << "Too many (" << admissibleResults.size() << ") results." << std::endl;
636 } 634 }
@@ -641,18 +639,111 @@ private:
641 std::cout << "Waiting five seconds then trying again..." << std::endl; 639 std::cout << "Waiting five seconds then trying again..." << std::endl;
642 std::this_thread::sleep_for(std::chrono::seconds(5)); 640 std::this_thread::sleep_for(std::chrono::seconds(5));
643 } 641 }
642
643 // generatePuzzle is only called when there is no cached puzzle and there
644 // is no other puzzle being generated. therefore, we do not need to worry
645 // about a race condition with another generatePuzzle. however, we do not
646 // want to interfere with a sendPuzzle. lock the cached puzzle. if
647 // sendPuzzle gets it first, it will see that there is no cached puzzle,
648 // it will queue its channel, and then it will return because a puzzle is
649 // already being generated. if generatePuzzle gets it first and there are
650 // no queued channels, it will store the puzzle and return, which means
651 // that a waiting sendPuzzle can immediately grab it. if there is a
652 // queued channel, generatePuzzle dequeues it and and calls sendPuzzle on
653 // it. which sendPuzzle gets the lock is undetermined, but it does not
654 // matter.
655 std::optional<dpp::snowflake> send_channel;
656 {
657 std::lock_guard cache_lock(cache_mutex_);
658 cached_puzzle_ = std::move(genpuzzle);
659 generating_puzzle_ = false;
660
661 if (!queued_channels_.empty())
662 {
663 send_channel = queued_channels_.front();
664 queued_channels_.pop_front();
665 }
666 }
667
668 if (send_channel)
669 {
670 sendPuzzle(*send_channel);
671 }
672 }
673
674 // called when the bot starts (no cached puzzle)
675 // called when /newpuzzle is run (maybe a cached puzzle)
676 // called at the end of generatePuzzle if channel queue is non-empty (definitely a cached puzzle)
677 //
678 // if there is a cached puzzle, send it immediately (removes cached puzzle). if not, add it to the channels queue.
679 // at this point there is no cached puzzle. if there is no puzzle being generated right now, spin off a thread to do so
680 void sendPuzzle(dpp::snowflake channel)
681 {
682 // lock the cached puzzle so
683 // 1) sendPuzzle in another thread doesn't steal our puzzle
684 // 2) generatePuzzle in another thread doesn't provide a puzzle
685 // before we can add something to the channel queue
686 std::lock_guard cache_lock(cache_mutex_);
687 if (cached_puzzle_ != nullptr)
688 {
689 std::cout << "Sending to " << static_cast<uint64_t>(channel) << std::endl;
690#ifdef ENABLE_BOT
691 dpp::message message(channel, cached_puzzle_->message);
692 if (!cached_puzzle_->attachment_content.empty())
693 {
694 message.add_file(cached_puzzle_->attachment_name, cached_puzzle_->attachment_content);
695 }
696
697 std::string solution = cached_puzzle_->solution;
698 bot_->message_create(message, [this, solution](const dpp::confirmation_callback_t& userdata) {
699 const auto& posted_msg = std::get<dpp::message>(userdata.value);
700 std::lock_guard answer_lock(answers_mutex_);
701 if (answer_by_message_.size() > 3000)
702 {
703 answer_by_message_.clear();
704 }
705 answer_by_message_[posted_msg.id] = solution;
706 });
707#endif
708
709 cached_puzzle_.reset();
710 } else {
711 std::cout << "Queued " << static_cast<uint64_t>(channel) << std::endl;
712 queued_channels_.push_back(channel);
713 }
714
715 // at this point, we guarantee that there are no puzzles. a generation
716 // thread may already be running, though, so we will not spin up
717 // another one if there is.
718 if (generating_puzzle_)
719 {
720 return;
721 }
722
723 generating_puzzle_ = true;
724
725 std::thread generation_thread([this](){
726 generatePuzzle();
727 });
728 generation_thread.detach();
644 } 729 }
645 730
646 std::mt19937& rng_; 731 std::mt19937& rng_;
647 std::unique_ptr<dpp::cluster> bot_; 732 std::unique_ptr<dpp::cluster> bot_;
648 std::unique_ptr<verbly::database> database_; 733 std::unique_ptr<verbly::database> database_;
649 std::unique_ptr<imagenet> imagenet_; 734 std::unique_ptr<imagenet> imagenet_;
650 std::map<uint64_t, std::string> answer_by_message_;
651 std::set<uint64_t> solved_puzzles_;
652 std::mutex answers_mutex_;
653 std::string scoreboard_endpoint_; 735 std::string scoreboard_endpoint_;
654 std::string scoreboard_secret_code_; 736 std::string scoreboard_secret_code_;
655 std::unique_ptr<wanderlust> wanderlust_; 737 std::unique_ptr<wanderlust> wanderlust_;
738
739 std::map<uint64_t, std::string> answer_by_message_;
740 std::set<uint64_t> solved_puzzles_;
741 std::mutex answers_mutex_;
742
743 bool generating_puzzle_ = false;
744 std::unique_ptr<puzzle> cached_puzzle_;
745 std::deque<dpp::snowflake> queued_channels_;
746 std::mutex cache_mutex_;
656}; 747};
657 748
658int main(int argc, char** argv) 749int main(int argc, char** argv)