diff options
| -rw-r--r-- | lingo.cpp | 153 |
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 | ||
| 305 | struct 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 | |||
| 305 | class lingo { | 312 | class lingo { |
| 306 | public: | 313 | public: |
| 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 | ||
| 428 | private: | 429 | private: |
| 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 | ||
| 658 | int main(int argc, char** argv) | 749 | int main(int argc, char** argv) |
