From be86554f2325427bb9421aa7274d135becab443c Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Fri, 9 Dec 2022 18:32:34 -0500 Subject: Added green hints --- .gitmodules | 3 ++ CMakeLists.txt | 6 ++-- imagenet.cpp | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ imagenet.h | 22 +++++++++++++++ lingo.cpp | 82 +++++++++++++++++++++++++++++++++++++++++++++++++++-- vendor/curlcpp | 1 + vendor/verbly | 2 +- 7 files changed, 199 insertions(+), 6 deletions(-) create mode 100644 imagenet.cpp create mode 100644 imagenet.h create mode 160000 vendor/curlcpp diff --git a/.gitmodules b/.gitmodules index 40a5694..d502d11 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "vendor/verbly"] path = vendor/verbly url = https://github.com/hatkirby/verbly.git +[submodule "vendor/curlcpp"] + path = vendor/curlcpp + url = https://github.com/JosephP91/curlcpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 38883b5..56082ac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,19 +7,21 @@ find_package(PkgConfig) pkg_check_modules(yaml-cpp yaml-cpp REQUIRED) pkg_check_modules(dpp dpp REQUIRED) +add_subdirectory(vendor/curlcpp) add_subdirectory(vendor/verbly) include_directories( ${dpp_INCLUDE_DIRS} vendor/verbly/lib ${yaml-cpp_INCLUDE_DIRS} + ${CURLCPP_SOURCE_DIR}/include vendor/json) link_directories( ${dpp_LIBRARY_DIRS} ${yaml-cpp_LIBRARY_DIRS}) -add_executable(lingo lingo.cpp) +add_executable(lingo lingo.cpp imagenet.cpp) set_property(TARGET lingo PROPERTY CXX_STANDARD 17) set_property(TARGET lingo PROPERTY CXX_STANDARD_REQUIRED ON) -target_link_libraries(lingo verbly ${dpp_LIBRARIES} ${yaml-cpp_LIBRARIES}) +target_link_libraries(lingo verbly ${dpp_LIBRARIES} ${yaml-cpp_LIBRARIES} curlcpp curl) diff --git a/imagenet.cpp b/imagenet.cpp new file mode 100644 index 0000000..3a107bd --- /dev/null +++ b/imagenet.cpp @@ -0,0 +1,89 @@ +#include "imagenet.h" +#include +#include +#include +#include +#include +#include + +imagenet::imagenet(std::string path) : path_(path) {} + +std::tuple imagenet::getImageForNotion(int notion_id, std::mt19937& rng) const +{ + std::filesystem::path filename = path_ / std::to_string(notion_id); + if (!std::filesystem::exists(filename)) + { + throw std::invalid_argument(std::string("File does not exist: ") + std::string(filename)); + } + + std::ifstream file(filename); + std::string line; + std::vector urls; + while (std::getline(file, line)) + { + if (!line.empty()) + { + urls.push_back(line); + } + } + + std::string output; + std::string extension; + while (!urls.empty()) + { + int index = std::uniform_int_distribution(0, urls.size()-1)(rng); + std::string url = urls.at(index); + urls.erase(std::begin(urls) + index); + + // willyfogg.com is a thumbnail generator known to return 200 even if the target image no longer exists + if (url.find("willyfogg.com/thumb.php") != std::string::npos) + { + continue; + } + + // Accept string from Google Chrome + std::string accept = "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"; + curl::curl_header headers; + headers.add(accept); + + std::ostringstream imgbuf; + curl::curl_ios imgios(imgbuf); + curl::curl_easy imghandle(imgios); + + imghandle.add(headers.get()); + imghandle.add(url.c_str()); + imghandle.add(30); + imghandle.add(300); + + try + { + imghandle.perform(); + } catch (const curl::curl_easy_exception& error) { + error.print_traceback(); + + continue; + } + + if (imghandle.get_info().get() != 200) + { + continue; + } + + std::string content_type = imghandle.get_info().get(); + if (content_type.substr(0, 6) != "image/") + { + continue; + } + + output = imgbuf.str(); + extension = url.substr(url.rfind(".") + 1); + break; + } + + if (output.empty()) + { + throw std::invalid_argument(std::string("No valid urls found for ") + std::string(filename)); + } + + return {output, extension}; +} diff --git a/imagenet.h b/imagenet.h new file mode 100644 index 0000000..e2c57de --- /dev/null +++ b/imagenet.h @@ -0,0 +1,22 @@ +#ifndef IMAGENET_H_41BE424F +#define IMAGENET_H_41BE424F + +#include +#include +#include +#include + +class imagenet { +public: + + explicit imagenet(std::string path); + + // returns bytedata, extension + std::tuple getImageForNotion(int notion_id, std::mt19937& rng) const; + +private: + + std::filesystem::path path_; +}; + +#endif /* end of include guard: IMAGENET_H_41BE424F */ diff --git a/lingo.cpp b/lingo.cpp index 439bab5..5f8a025 100644 --- a/lingo.cpp +++ b/lingo.cpp @@ -13,6 +13,7 @@ #include #include #include +#include "imagenet.h" #define ENABLE_BOT @@ -31,6 +32,7 @@ enum Colour { kPurple, kBrown, kYellow, + kGreen, kColourCount }; @@ -41,7 +43,8 @@ const std::string COLOUR_EMOJIS[kColourCount] = { "🟦", "🟪", "🟫", - "🟨" + "🟨", + "🟩" }; const std::string NONE_EMOTE = "<:xx:1047267830535557180>"; @@ -53,7 +56,8 @@ const std::string COLOUR_EMOTES[kColourCount] = { "<:bl:1047262138202325042>", "<:pr:1047262146926489691>", "<:bn:1047262139187998790>", - "<:yw:1047262152781737986>" + "<:yw:1047262152781737986>", + "<:gn:1047262141914304633>" }; enum FilterDirection { @@ -195,6 +199,50 @@ verbly::filter makeHintFilter(verbly::filter subfilter, Height height, Colour co } break; } + case kGreen: { + if (filter_direction == kTowardSolution) + { + verbly::filter whitelist = + (verbly::notion::wnid == 109287968) // Geological formations + || (verbly::notion::wnid == 109208496) // Asterisms (collections of stars) + || (verbly::notion::wnid == 109239740) // Celestial bodies + || (verbly::notion::wnid == 109277686) // Exterrestrial objects (comets and meteroids) + || (verbly::notion::wnid == 109403211) // Radiators (supposedly natural radiators but actually these are just pictures of radiators) + || (verbly::notion::wnid == 109416076) // Rocks + || (verbly::notion::wnid == 105442131) // Chromosomes + || (verbly::notion::wnid == 100324978) // Tightrope walking + || (verbly::notion::wnid == 100326094) // Rock climbing + || (verbly::notion::wnid == 100433458) // Contact sports + || (verbly::notion::wnid == 100433802) // Gymnastics + || (verbly::notion::wnid == 100439826) // Track and field + || (verbly::notion::wnid == 100440747) // Skiing + || (verbly::notion::wnid == 100441824) // Water sport + || (verbly::notion::wnid == 100445351) // Rowing + || (verbly::notion::wnid == 100446980) // Archery + // TODO: add more sports + || (verbly::notion::wnid == 100021939) // Artifacts + || (verbly::notion::wnid == 101471682) // Vertebrates + ; + + verbly::filter blacklist = + (verbly::notion::wnid == 106883725) // swastika + || (verbly::notion::wnid == 104416901) // tetraskele + || (verbly::notion::wnid == 102512053) // fish + || (verbly::notion::wnid == 103575691) // instrument of execution + || (verbly::notion::wnid == 103829563) // noose + || (verbly::notion::wnid == 103663910) // life support + ; + + return subfilter + && (verbly::notion::fullHypernyms %= whitelist) + && !(verbly::notion::fullHypernyms %= blacklist) + && (verbly::notion::partOfSpeech == verbly::part_of_speech::noun) + && (verbly::notion::numOfImages >= 1); + } else { + return (verbly::form::text == "picture"); + } + break; + } default: break; // Not supported yet. } return {}; @@ -255,6 +303,7 @@ public: dpp::snowflake channel(config["discord_channel"].as()); database_ = std::make_unique(config["verbly_datafile"].as()); + imagenet_ = std::make_unique(config["imagenet"].as()); for (;;) { @@ -278,10 +327,12 @@ private: {kMiddle, kRed}, {kMiddle, kBlue}, {kMiddle, kPurple}, + {kMiddle, kGreen}, {kBottom, kWhite}, {kBottom, kBlack}, {kBottom, kRed}, {kBottom, kBlue}, + {kBottom, kGreen}, }; std::set> expensive_hints = { @@ -313,6 +364,7 @@ private: int non_purple_uses = 0; int expensive_uses = 0; int moderate_uses = 0; + int green_uses = 0; std::array, kHeightCount> parts; for (int height = 0; height < static_cast(kHeightCount); height++) { if (std::bernoulli_distribution(0.5)(rng_)) { @@ -334,6 +386,10 @@ private: { moderate_uses++; } + if (colour == kGreen) + { + green_uses++; + } std::cout << COLOUR_EMOJIS[colour]; } else { @@ -359,6 +415,11 @@ private: std::cout << "Moderate hints can't be combined with an expensive hint." << std::endl; continue; } + if (green_uses != 1) + { + std::cout << "Too many green hints." << std::endl; + continue; + } verbly::filter forwardFilter = cleanFilter && (verbly::form::proper == false); for (int i=0; i(kHeightCount); i++) { @@ -401,10 +462,24 @@ private: std::cout << message_text << std::endl << std::endl << solution.getText() << std::endl; std::vector admissibleResults = database_->forms(admissible, {}, 10).all(); - if (admissibleResults.size() <= (hints == 1 ? 2 : 5)) + if (green_uses > 0 || (admissibleResults.size() <= (hints == 1 ? 2 : 5))) { #ifdef ENABLE_BOT dpp::message message(channel, message_text); + + if (green_uses > 0) + { + verbly::notion notion = database_->notions( + (verbly::notion::numOfImages > 1) && solution).first(); + auto [image, extension] = imagenet_->getImageForNotion(notion.getId(), rng_); + if (image.empty()) + { + throw std::runtime_error("Could not find image for green hint."); + } + + message.add_file(std::string("SPOILER_image.") + extension, image); + } + bot_->message_create(message, [this, &solution](const dpp::confirmation_callback_t& userdata) { const auto& posted_msg = std::get(userdata.value); std::lock_guard answer_lock(answers_mutex_); @@ -432,6 +507,7 @@ private: std::mt19937& rng_; std::unique_ptr bot_; std::unique_ptr database_; + std::unique_ptr imagenet_; std::map answer_by_message_; std::mutex answers_mutex_; }; diff --git a/vendor/curlcpp b/vendor/curlcpp new file mode 160000 index 0000000..f4b87d0 --- /dev/null +++ b/vendor/curlcpp @@ -0,0 +1 @@ +Subproject commit f4b87d02725065536bf6620c29aa8c13e15a1993 diff --git a/vendor/verbly b/vendor/verbly index f273132..06e4672 160000 --- a/vendor/verbly +++ b/vendor/verbly @@ -1 +1 @@ -Subproject commit f2731325f551c4cfea861e2e31d214936b9bd619 +Subproject commit 06e4672540094a851542b47abaf022f934b63b09 -- cgit 1.4.1