summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorStar Rauchenberger <fefferburbia@gmail.com>2022-12-09 18:32:34 -0500
committerStar Rauchenberger <fefferburbia@gmail.com>2022-12-09 18:32:34 -0500
commitbe86554f2325427bb9421aa7274d135becab443c (patch)
treee10394ee21c757b34a6d6e943a16b7e12db38f01
parent1e0fa0b5b760a23a82194a2aecfd8ba3e4114eb9 (diff)
downloadlingo-be86554f2325427bb9421aa7274d135becab443c.tar.gz
lingo-be86554f2325427bb9421aa7274d135becab443c.tar.bz2
lingo-be86554f2325427bb9421aa7274d135becab443c.zip
Added green hints
-rw-r--r--.gitmodules3
-rw-r--r--CMakeLists.txt6
-rw-r--r--imagenet.cpp89
-rw-r--r--imagenet.h22
-rw-r--r--lingo.cpp82
m---------vendor/curlcpp0
m---------vendor/verbly0
7 files changed, 197 insertions, 5 deletions
diff --git a/.gitmodules b/.gitmodules index 40a5694..d502d11 100644 --- a/.gitmodules +++ b/.gitmodules
@@ -1,3 +1,6 @@
1[submodule "vendor/verbly"] 1[submodule "vendor/verbly"]
2 path = vendor/verbly 2 path = vendor/verbly
3 url = https://github.com/hatkirby/verbly.git 3 url = https://github.com/hatkirby/verbly.git
4[submodule "vendor/curlcpp"]
5 path = vendor/curlcpp
6 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)
7pkg_check_modules(yaml-cpp yaml-cpp REQUIRED) 7pkg_check_modules(yaml-cpp yaml-cpp REQUIRED)
8pkg_check_modules(dpp dpp REQUIRED) 8pkg_check_modules(dpp dpp REQUIRED)
9 9
10add_subdirectory(vendor/curlcpp)
10add_subdirectory(vendor/verbly) 11add_subdirectory(vendor/verbly)
11 12
12include_directories( 13include_directories(
13 ${dpp_INCLUDE_DIRS} 14 ${dpp_INCLUDE_DIRS}
14 vendor/verbly/lib 15 vendor/verbly/lib
15 ${yaml-cpp_INCLUDE_DIRS} 16 ${yaml-cpp_INCLUDE_DIRS}
17 ${CURLCPP_SOURCE_DIR}/include
16 vendor/json) 18 vendor/json)
17 19
18link_directories( 20link_directories(
19 ${dpp_LIBRARY_DIRS} 21 ${dpp_LIBRARY_DIRS}
20 ${yaml-cpp_LIBRARY_DIRS}) 22 ${yaml-cpp_LIBRARY_DIRS})
21 23
22add_executable(lingo lingo.cpp) 24add_executable(lingo lingo.cpp imagenet.cpp)
23set_property(TARGET lingo PROPERTY CXX_STANDARD 17) 25set_property(TARGET lingo PROPERTY CXX_STANDARD 17)
24set_property(TARGET lingo PROPERTY CXX_STANDARD_REQUIRED ON) 26set_property(TARGET lingo PROPERTY CXX_STANDARD_REQUIRED ON)
25target_link_libraries(lingo verbly ${dpp_LIBRARIES} ${yaml-cpp_LIBRARIES}) 27target_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 @@
1#include "imagenet.h"
2#include <fstream>
3#include <stdexcept>
4#include <sstream>
5#include <vector>
6#include <curl_easy.h>
7#include <curl_header.h>
8
9imagenet::imagenet(std::string path) : path_(path) {}
10
11std::tuple<std::string, std::string> imagenet::getImageForNotion(int notion_id, std::mt19937& rng) const
12{
13 std::filesystem::path filename = path_ / std::to_string(notion_id);
14 if (!std::filesystem::exists(filename))
15 {
16 throw std::invalid_argument(std::string("File does not exist: ") + std::string(filename));
17 }
18
19 std::ifstream file(filename);
20 std::string line;
21 std::vector<std::string> urls;
22 while (std::getline(file, line))
23 {
24 if (!line.empty())
25 {
26 urls.push_back(line);
27 }
28 }
29
30 std::string output;
31 std::string extension;
32 while (!urls.empty())
33 {
34 int index = std::uniform_int_distribution<int>(0, urls.size()-1)(rng);
35 std::string url = urls.at(index);
36 urls.erase(std::begin(urls) + index);
37
38 // willyfogg.com is a thumbnail generator known to return 200 even if the target image no longer exists
39 if (url.find("willyfogg.com/thumb.php") != std::string::npos)
40 {
41 continue;
42 }
43
44 // Accept string from Google Chrome
45 std::string accept = "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8";
46 curl::curl_header headers;
47 headers.add(accept);
48
49 std::ostringstream imgbuf;
50 curl::curl_ios<std::ostringstream> imgios(imgbuf);
51 curl::curl_easy imghandle(imgios);
52
53 imghandle.add<CURLOPT_HTTPHEADER>(headers.get());
54 imghandle.add<CURLOPT_URL>(url.c_str());
55 imghandle.add<CURLOPT_CONNECTTIMEOUT>(30);
56 imghandle.add<CURLOPT_TIMEOUT>(300);
57
58 try
59 {
60 imghandle.perform();
61 } catch (const curl::curl_easy_exception& error) {
62 error.print_traceback();
63
64 continue;
65 }
66
67 if (imghandle.get_info<CURLINFO_RESPONSE_CODE>().get() != 200)
68 {
69 continue;
70 }
71
72 std::string content_type = imghandle.get_info<CURLINFO_CONTENT_TYPE>().get();
73 if (content_type.substr(0, 6) != "image/")
74 {
75 continue;
76 }
77
78 output = imgbuf.str();
79 extension = url.substr(url.rfind(".") + 1);
80 break;
81 }
82
83 if (output.empty())
84 {
85 throw std::invalid_argument(std::string("No valid urls found for ") + std::string(filename));
86 }
87
88 return {output, extension};
89}
diff --git a/imagenet.h b/imagenet.h new file mode 100644 index 0000000..e2c57de --- /dev/null +++ b/imagenet.h
@@ -0,0 +1,22 @@
1#ifndef IMAGENET_H_41BE424F
2#define IMAGENET_H_41BE424F
3
4#include <string>
5#include <filesystem>
6#include <random>
7#include <tuple>
8
9class imagenet {
10public:
11
12 explicit imagenet(std::string path);
13
14 // returns bytedata, extension
15 std::tuple<std::string, std::string> getImageForNotion(int notion_id, std::mt19937& rng) const;
16
17private:
18
19 std::filesystem::path path_;
20};
21
22#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 @@
13#include <optional> 13#include <optional>
14#include <map> 14#include <map>
15#include <array> 15#include <array>
16#include "imagenet.h"
16 17
17#define ENABLE_BOT 18#define ENABLE_BOT
18 19
@@ -31,6 +32,7 @@ enum Colour {
31 kPurple, 32 kPurple,
32 kBrown, 33 kBrown,
33 kYellow, 34 kYellow,
35 kGreen,
34 kColourCount 36 kColourCount
35}; 37};
36 38
@@ -41,7 +43,8 @@ const std::string COLOUR_EMOJIS[kColourCount] = {
41 "🟦", 43 "🟦",
42 "🟪", 44 "🟪",
43 "🟫", 45 "🟫",
44 "🟨" 46 "🟨",
47 "🟩"
45}; 48};
46 49
47const std::string NONE_EMOTE = "<:xx:1047267830535557180>"; 50const std::string NONE_EMOTE = "<:xx:1047267830535557180>";
@@ -53,7 +56,8 @@ const std::string COLOUR_EMOTES[kColourCount] = {
53 "<:bl:1047262138202325042>", 56 "<:bl:1047262138202325042>",
54 "<:pr:1047262146926489691>", 57 "<:pr:1047262146926489691>",
55 "<:bn:1047262139187998790>", 58 "<:bn:1047262139187998790>",
56 "<:yw:1047262152781737986>" 59 "<:yw:1047262152781737986>",
60 "<:gn:1047262141914304633>"
57}; 61};
58 62
59enum FilterDirection { 63enum FilterDirection {
@@ -195,6 +199,50 @@ verbly::filter makeHintFilter(verbly::filter subfilter, Height height, Colour co
195 } 199 }
196 break; 200 break;
197 } 201 }
202 case kGreen: {
203 if (filter_direction == kTowardSolution)
204 {
205 verbly::filter whitelist =
206 (verbly::notion::wnid == 109287968) // Geological formations
207 || (verbly::notion::wnid == 109208496) // Asterisms (collections of stars)
208 || (verbly::notion::wnid == 109239740) // Celestial bodies
209 || (verbly::notion::wnid == 109277686) // Exterrestrial objects (comets and meteroids)
210 || (verbly::notion::wnid == 109403211) // Radiators (supposedly natural radiators but actually these are just pictures of radiators)
211 || (verbly::notion::wnid == 109416076) // Rocks
212 || (verbly::notion::wnid == 105442131) // Chromosomes
213 || (verbly::notion::wnid == 100324978) // Tightrope walking
214 || (verbly::notion::wnid == 100326094) // Rock climbing
215 || (verbly::notion::wnid == 100433458) // Contact sports
216 || (verbly::notion::wnid == 100433802) // Gymnastics
217 || (verbly::notion::wnid == 100439826) // Track and field
218 || (verbly::notion::wnid == 100440747) // Skiing
219 || (verbly::notion::wnid == 100441824) // Water sport
220 || (verbly::notion::wnid == 100445351) // Rowing
221 || (verbly::notion::wnid == 100446980) // Archery
222 // TODO: add more sports
223 || (verbly::notion::wnid == 100021939) // Artifacts
224 || (verbly::notion::wnid == 101471682) // Vertebrates
225 ;
226
227 verbly::filter blacklist =
228 (verbly::notion::wnid == 106883725) // swastika
229 || (verbly::notion::wnid == 104416901) // tetraskele
230 || (verbly::notion::wnid == 102512053) // fish
231 || (verbly::notion::wnid == 103575691) // instrument of execution
232 || (verbly::notion::wnid == 103829563) // noose
233 || (verbly::notion::wnid == 103663910) // life support
234 ;
235
236 return subfilter
237 && (verbly::notion::fullHypernyms %= whitelist)
238 && !(verbly::notion::fullHypernyms %= blacklist)
239 && (verbly::notion::partOfSpeech == verbly::part_of_speech::noun)
240 && (verbly::notion::numOfImages >= 1);
241 } else {
242 return (verbly::form::text == "picture");
243 }
244 break;
245 }
198 default: break; // Not supported yet. 246 default: break; // Not supported yet.
199 } 247 }
200 return {}; 248 return {};
@@ -255,6 +303,7 @@ public:
255 dpp::snowflake channel(config["discord_channel"].as<uint64_t>()); 303 dpp::snowflake channel(config["discord_channel"].as<uint64_t>());
256 304
257 database_ = std::make_unique<verbly::database>(config["verbly_datafile"].as<std::string>()); 305 database_ = std::make_unique<verbly::database>(config["verbly_datafile"].as<std::string>());
306 imagenet_ = std::make_unique<imagenet>(config["imagenet"].as<std::string>());
258 307
259 for (;;) 308 for (;;)
260 { 309 {
@@ -278,10 +327,12 @@ private:
278 {kMiddle, kRed}, 327 {kMiddle, kRed},
279 {kMiddle, kBlue}, 328 {kMiddle, kBlue},
280 {kMiddle, kPurple}, 329 {kMiddle, kPurple},
330 {kMiddle, kGreen},
281 {kBottom, kWhite}, 331 {kBottom, kWhite},
282 {kBottom, kBlack}, 332 {kBottom, kBlack},
283 {kBottom, kRed}, 333 {kBottom, kRed},
284 {kBottom, kBlue}, 334 {kBottom, kBlue},
335 {kBottom, kGreen},
285 }; 336 };
286 337
287 std::set<std::tuple<Height, Colour>> expensive_hints = { 338 std::set<std::tuple<Height, Colour>> expensive_hints = {
@@ -313,6 +364,7 @@ private:
313 int non_purple_uses = 0; 364 int non_purple_uses = 0;
314 int expensive_uses = 0; 365 int expensive_uses = 0;
315 int moderate_uses = 0; 366 int moderate_uses = 0;
367 int green_uses = 0;
316 std::array<std::optional<Colour>, kHeightCount> parts; 368 std::array<std::optional<Colour>, kHeightCount> parts;
317 for (int height = 0; height < static_cast<int>(kHeightCount); height++) { 369 for (int height = 0; height < static_cast<int>(kHeightCount); height++) {
318 if (std::bernoulli_distribution(0.5)(rng_)) { 370 if (std::bernoulli_distribution(0.5)(rng_)) {
@@ -334,6 +386,10 @@ private:
334 { 386 {
335 moderate_uses++; 387 moderate_uses++;
336 } 388 }
389 if (colour == kGreen)
390 {
391 green_uses++;
392 }
337 393
338 std::cout << COLOUR_EMOJIS[colour]; 394 std::cout << COLOUR_EMOJIS[colour];
339 } else { 395 } else {
@@ -359,6 +415,11 @@ private:
359 std::cout << "Moderate hints can't be combined with an expensive hint." << std::endl; 415 std::cout << "Moderate hints can't be combined with an expensive hint." << std::endl;
360 continue; 416 continue;
361 } 417 }
418 if (green_uses != 1)
419 {
420 std::cout << "Too many green hints." << std::endl;
421 continue;
422 }
362 423
363 verbly::filter forwardFilter = cleanFilter && (verbly::form::proper == false); 424 verbly::filter forwardFilter = cleanFilter && (verbly::form::proper == false);
364 for (int i=0; i<static_cast<int>(kHeightCount); i++) { 425 for (int i=0; i<static_cast<int>(kHeightCount); i++) {
@@ -401,10 +462,24 @@ private:
401 std::cout << message_text << std::endl << std::endl << solution.getText() << std::endl; 462 std::cout << message_text << std::endl << std::endl << solution.getText() << std::endl;
402 463
403 std::vector<verbly::form> admissibleResults = database_->forms(admissible, {}, 10).all(); 464 std::vector<verbly::form> admissibleResults = database_->forms(admissible, {}, 10).all();
404 if (admissibleResults.size() <= (hints == 1 ? 2 : 5)) 465 if (green_uses > 0 || (admissibleResults.size() <= (hints == 1 ? 2 : 5)))
405 { 466 {
406#ifdef ENABLE_BOT 467#ifdef ENABLE_BOT
407 dpp::message message(channel, message_text); 468 dpp::message message(channel, message_text);
469
470 if (green_uses > 0)
471 {
472 verbly::notion notion = database_->notions(
473 (verbly::notion::numOfImages > 1) && solution).first();
474 auto [image, extension] = imagenet_->getImageForNotion(notion.getId(), rng_);
475 if (image.empty())
476 {
477 throw std::runtime_error("Could not find image for green hint.");
478 }
479
480 message.add_file(std::string("SPOILER_image.") + extension, image);
481 }
482
408 bot_->message_create(message, [this, &solution](const dpp::confirmation_callback_t& userdata) { 483 bot_->message_create(message, [this, &solution](const dpp::confirmation_callback_t& userdata) {
409 const auto& posted_msg = std::get<dpp::message>(userdata.value); 484 const auto& posted_msg = std::get<dpp::message>(userdata.value);
410 std::lock_guard answer_lock(answers_mutex_); 485 std::lock_guard answer_lock(answers_mutex_);
@@ -432,6 +507,7 @@ private:
432 std::mt19937& rng_; 507 std::mt19937& rng_;
433 std::unique_ptr<dpp::cluster> bot_; 508 std::unique_ptr<dpp::cluster> bot_;
434 std::unique_ptr<verbly::database> database_; 509 std::unique_ptr<verbly::database> database_;
510 std::unique_ptr<imagenet> imagenet_;
435 std::map<uint64_t, std::string> answer_by_message_; 511 std::map<uint64_t, std::string> answer_by_message_;
436 std::mutex answers_mutex_; 512 std::mutex answers_mutex_;
437}; 513};
diff --git a/vendor/curlcpp b/vendor/curlcpp new file mode 160000
Subproject f4b87d02725065536bf6620c29aa8c13e15a199
diff --git a/vendor/verbly b/vendor/verbly
Subproject f2731325f551c4cfea861e2e31d214936b9bd61 Subproject 06e4672540094a851542b47abaf022f934b63b0