diff options
author | Star Rauchenberger <fefferburbia@gmail.com> | 2023-02-03 10:52:42 -0500 |
---|---|---|
committer | Star Rauchenberger <fefferburbia@gmail.com> | 2023-02-03 10:52:42 -0500 |
commit | ca6f051c611a378e772994fd83e92bc50c87d46b (patch) | |
tree | e57cbcc6643f60ac4c256cbced8785f382b918cc | |
parent | 6de0b5ce131746c88b7040ddde1e9b84f29cece3 (diff) | |
download | lingo-ca6f051c611a378e772994fd83e92bc50c87d46b.tar.gz lingo-ca6f051c611a378e772994fd83e92bc50c87d46b.tar.bz2 lingo-ca6f051c611a378e772994fd83e92bc50c87d46b.zip |
Middle green is now like a captcha
refs #12
-rw-r--r-- | CMakeLists.txt | 9 | ||||
-rwxr-xr-x | coolvetica.ttf | bin | 0 -> 135916 bytes | |||
-rw-r--r-- | lingo.cpp | 173 |
3 files changed, 131 insertions, 51 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 56082ac..a67107b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt | |||
@@ -6,6 +6,7 @@ set(CMAKE_BUILD_TYPE Debug) | |||
6 | find_package(PkgConfig) | 6 | find_package(PkgConfig) |
7 | pkg_check_modules(yaml-cpp yaml-cpp REQUIRED) | 7 | pkg_check_modules(yaml-cpp yaml-cpp REQUIRED) |
8 | pkg_check_modules(dpp dpp REQUIRED) | 8 | pkg_check_modules(dpp dpp REQUIRED) |
9 | pkg_check_modules(GraphicsMagick GraphicsMagick++ REQUIRED) | ||
9 | 10 | ||
10 | add_subdirectory(vendor/curlcpp) | 11 | add_subdirectory(vendor/curlcpp) |
11 | add_subdirectory(vendor/verbly) | 12 | add_subdirectory(vendor/verbly) |
@@ -15,13 +16,15 @@ include_directories( | |||
15 | vendor/verbly/lib | 16 | vendor/verbly/lib |
16 | ${yaml-cpp_INCLUDE_DIRS} | 17 | ${yaml-cpp_INCLUDE_DIRS} |
17 | ${CURLCPP_SOURCE_DIR}/include | 18 | ${CURLCPP_SOURCE_DIR}/include |
18 | vendor/json) | 19 | vendor/json |
20 | ${GraphicsMagick_INCLUDE_DIRS}) | ||
19 | 21 | ||
20 | link_directories( | 22 | link_directories( |
21 | ${dpp_LIBRARY_DIRS} | 23 | ${dpp_LIBRARY_DIRS} |
22 | ${yaml-cpp_LIBRARY_DIRS}) | 24 | ${yaml-cpp_LIBRARY_DIRS} |
25 | ${GraphicsMagick_LIBRARY_DIRS}) | ||
23 | 26 | ||
24 | add_executable(lingo lingo.cpp imagenet.cpp) | 27 | add_executable(lingo lingo.cpp imagenet.cpp) |
25 | set_property(TARGET lingo PROPERTY CXX_STANDARD 17) | 28 | set_property(TARGET lingo PROPERTY CXX_STANDARD 17) |
26 | set_property(TARGET lingo PROPERTY CXX_STANDARD_REQUIRED ON) | 29 | set_property(TARGET lingo PROPERTY CXX_STANDARD_REQUIRED ON) |
27 | target_link_libraries(lingo verbly ${dpp_LIBRARIES} ${yaml-cpp_LIBRARIES} curlcpp curl) | 30 | target_link_libraries(lingo verbly ${dpp_LIBRARIES} ${yaml-cpp_LIBRARIES} curlcpp curl ${GraphicsMagick_LIBRARIES}) |
diff --git a/coolvetica.ttf b/coolvetica.ttf new file mode 100755 index 0000000..410ca31 --- /dev/null +++ b/coolvetica.ttf | |||
Binary files differ | |||
diff --git a/lingo.cpp b/lingo.cpp index be58e1c..61a1f71 100644 --- a/lingo.cpp +++ b/lingo.cpp | |||
@@ -17,6 +17,7 @@ | |||
17 | #include <optional> | 17 | #include <optional> |
18 | #include <map> | 18 | #include <map> |
19 | #include <array> | 19 | #include <array> |
20 | #include <Magick++.h> | ||
20 | #include "imagenet.h" | 21 | #include "imagenet.h" |
21 | 22 | ||
22 | #define ENABLE_BOT | 23 | #define ENABLE_BOT |
@@ -229,42 +230,50 @@ verbly::filter makeHintFilter(verbly::filter subfilter, Height height, Colour co | |||
229 | case kGreen: { | 230 | case kGreen: { |
230 | if (filter_direction == kTowardSolution) | 231 | if (filter_direction == kTowardSolution) |
231 | { | 232 | { |
232 | verbly::filter whitelist = | 233 | switch (height) { |
233 | (verbly::notion::wnid == 109287968) // Geological formations | 234 | case kBottom: { |
234 | || (verbly::notion::wnid == 109208496) // Asterisms (collections of stars) | 235 | verbly::filter whitelist = |
235 | || (verbly::notion::wnid == 109239740) // Celestial bodies | 236 | (verbly::notion::wnid == 109287968) // Geological formations |
236 | || (verbly::notion::wnid == 109277686) // Exterrestrial objects (comets and meteroids) | 237 | || (verbly::notion::wnid == 109208496) // Asterisms (collections of stars) |
237 | || (verbly::notion::wnid == 109403211) // Radiators (supposedly natural radiators but actually these are just pictures of radiators) | 238 | || (verbly::notion::wnid == 109239740) // Celestial bodies |
238 | || (verbly::notion::wnid == 109416076) // Rocks | 239 | || (verbly::notion::wnid == 109277686) // Exterrestrial objects (comets and meteroids) |
239 | || (verbly::notion::wnid == 105442131) // Chromosomes | 240 | || (verbly::notion::wnid == 109403211) // Radiators (supposedly natural radiators but actually these are just pictures of radiators) |
240 | || (verbly::notion::wnid == 100324978) // Tightrope walking | 241 | || (verbly::notion::wnid == 109416076) // Rocks |
241 | || (verbly::notion::wnid == 100326094) // Rock climbing | 242 | || (verbly::notion::wnid == 105442131) // Chromosomes |
242 | || (verbly::notion::wnid == 100433458) // Contact sports | 243 | || (verbly::notion::wnid == 100324978) // Tightrope walking |
243 | || (verbly::notion::wnid == 100433802) // Gymnastics | 244 | || (verbly::notion::wnid == 100326094) // Rock climbing |
244 | || (verbly::notion::wnid == 100439826) // Track and field | 245 | || (verbly::notion::wnid == 100433458) // Contact sports |
245 | || (verbly::notion::wnid == 100440747) // Skiing | 246 | || (verbly::notion::wnid == 100433802) // Gymnastics |
246 | || (verbly::notion::wnid == 100441824) // Water sport | 247 | || (verbly::notion::wnid == 100439826) // Track and field |
247 | || (verbly::notion::wnid == 100445351) // Rowing | 248 | || (verbly::notion::wnid == 100440747) // Skiing |
248 | || (verbly::notion::wnid == 100446980) // Archery | 249 | || (verbly::notion::wnid == 100441824) // Water sport |
249 | // TODO: add more sports | 250 | || (verbly::notion::wnid == 100445351) // Rowing |
250 | || (verbly::notion::wnid == 100021939) // Artifacts | 251 | || (verbly::notion::wnid == 100446980) // Archery |
251 | || (verbly::notion::wnid == 101471682) // Vertebrates | 252 | // TODO: add more sports |
252 | ; | 253 | || (verbly::notion::wnid == 100021939) // Artifacts |
253 | 254 | || (verbly::notion::wnid == 101471682) // Vertebrates | |
254 | verbly::filter blacklist = | 255 | ; |
255 | (verbly::notion::wnid == 106883725) // swastika | 256 | |
256 | || (verbly::notion::wnid == 104416901) // tetraskele | 257 | verbly::filter blacklist = |
257 | || (verbly::notion::wnid == 102512053) // fish | 258 | (verbly::notion::wnid == 106883725) // swastika |
258 | || (verbly::notion::wnid == 103575691) // instrument of execution | 259 | || (verbly::notion::wnid == 104416901) // tetraskele |
259 | || (verbly::notion::wnid == 103829563) // noose | 260 | || (verbly::notion::wnid == 102512053) // fish |
260 | || (verbly::notion::wnid == 103663910) // life support | 261 | || (verbly::notion::wnid == 103575691) // instrument of execution |
261 | ; | 262 | || (verbly::notion::wnid == 103829563) // noose |
262 | 263 | || (verbly::notion::wnid == 103663910) // life support | |
263 | return subfilter | 264 | ; |
264 | && (verbly::notion::fullHypernyms %= whitelist) | 265 | |
265 | && !(verbly::notion::fullHypernyms %= blacklist) | 266 | return subfilter |
266 | && (verbly::notion::partOfSpeech == verbly::part_of_speech::noun) | 267 | && (verbly::notion::fullHypernyms %= whitelist) |
267 | && (verbly::notion::numOfImages >= 1); | 268 | && !(verbly::notion::fullHypernyms %= blacklist) |
269 | && (verbly::notion::partOfSpeech == verbly::part_of_speech::noun) | ||
270 | && (verbly::notion::numOfImages >= 1); | ||
271 | } | ||
272 | case kMiddle: { | ||
273 | return subfilter; | ||
274 | } | ||
275 | default: break; // Never supported. | ||
276 | } | ||
268 | } else { | 277 | } else { |
269 | return (verbly::form::text == "picture"); | 278 | return (verbly::form::text == "picture"); |
270 | } | 279 | } |
@@ -420,6 +429,7 @@ public: | |||
420 | database_ = std::make_unique<verbly::database>(config["verbly_datafile"].as<std::string>()); | 429 | database_ = std::make_unique<verbly::database>(config["verbly_datafile"].as<std::string>()); |
421 | imagenet_ = std::make_unique<imagenet>(config["imagenet"].as<std::string>()); | 430 | imagenet_ = std::make_unique<imagenet>(config["imagenet"].as<std::string>()); |
422 | wanderlust_ = std::make_unique<wanderlust>(config["wanderlust"].as<std::string>()); | 431 | wanderlust_ = std::make_unique<wanderlust>(config["wanderlust"].as<std::string>()); |
432 | fontpath_ = config["font"].as<std::string>(); | ||
423 | 433 | ||
424 | scoreboard_endpoint_ = config["scoreboard_endpoint"].as<std::string>(); | 434 | scoreboard_endpoint_ = config["scoreboard_endpoint"].as<std::string>(); |
425 | scoreboard_secret_code_ = config["scoreboard_secret_code"].as<std::string>(); | 435 | scoreboard_secret_code_ = config["scoreboard_secret_code"].as<std::string>(); |
@@ -519,6 +529,7 @@ private: | |||
519 | int moderate_uses = 0; | 529 | int moderate_uses = 0; |
520 | int green_uses = 0; | 530 | int green_uses = 0; |
521 | int orange_uses = 0; | 531 | int orange_uses = 0; |
532 | bool green_is_bottom = false; | ||
522 | std::array<std::optional<Colour>, kHeightCount> parts; | 533 | std::array<std::optional<Colour>, kHeightCount> parts; |
523 | for (int height = 0; height < static_cast<int>(kHeightCount); height++) { | 534 | for (int height = 0; height < static_cast<int>(kHeightCount); height++) { |
524 | if (std::bernoulli_distribution(0.5)(rng_)) { | 535 | if (std::bernoulli_distribution(0.5)(rng_)) { |
@@ -547,6 +558,10 @@ private: | |||
547 | if (colour == kGreen) | 558 | if (colour == kGreen) |
548 | { | 559 | { |
549 | green_uses++; | 560 | green_uses++; |
561 | if (height == kBottom) | ||
562 | { | ||
563 | green_is_bottom = true; | ||
564 | } | ||
550 | } | 565 | } |
551 | if (colour == kOrange) | 566 | if (colour == kOrange) |
552 | { | 567 | { |
@@ -563,9 +578,9 @@ private: | |||
563 | } | 578 | } |
564 | std::cout << std::endl; | 579 | std::cout << std::endl; |
565 | 580 | ||
566 | if (non_purple_uses < 1 || non_green_uses < 1) | 581 | if (non_purple_uses < 1 || (non_green_uses < 1 && green_is_bottom)) |
567 | { | 582 | { |
568 | std::cout << "No hints (or only purple or only green hints)." << std::endl; | 583 | std::cout << "No hints (or only purple or only green bottom hints)." << std::endl; |
569 | continue; | 584 | continue; |
570 | } | 585 | } |
571 | if (expensive_uses > 1) | 586 | if (expensive_uses > 1) |
@@ -672,16 +687,7 @@ private: | |||
672 | 687 | ||
673 | if (green_uses > 0) | 688 | if (green_uses > 0) |
674 | { | 689 | { |
675 | verbly::notion notion = database_->notions( | 690 | generateGreenPuzzle(solution, green_is_bottom, *genpuzzle); |
676 | (verbly::notion::numOfImages > 1) && solution).first(); | ||
677 | auto [image, extension] = imagenet_->getImageForNotion(notion.getId(), rng_); | ||
678 | if (image.empty()) | ||
679 | { | ||
680 | throw std::runtime_error("Could not find image for green hint."); | ||
681 | } | ||
682 | |||
683 | genpuzzle->attachment_name = std::string("SPOILER_image.") + extension; | ||
684 | genpuzzle->attachment_content = std::move(image); | ||
685 | } | 691 | } |
686 | 692 | ||
687 | made_puzzle = true; | 693 | made_puzzle = true; |
@@ -790,6 +796,74 @@ private: | |||
790 | generation_thread.detach(); | 796 | generation_thread.detach(); |
791 | } | 797 | } |
792 | 798 | ||
799 | void generateGreenPuzzle(const verbly::form& solution, bool is_bottom, puzzle& genpuzzle) | ||
800 | { | ||
801 | if (is_bottom) | ||
802 | { | ||
803 | verbly::notion notion = database_->notions( | ||
804 | (verbly::notion::numOfImages > 1) && solution).first(); | ||
805 | auto [image, extension] = imagenet_->getImageForNotion(notion.getId(), rng_); | ||
806 | if (image.empty()) | ||
807 | { | ||
808 | throw std::runtime_error("Could not find image for green hint."); | ||
809 | } | ||
810 | |||
811 | genpuzzle.attachment_name = std::string("SPOILER_image.") + extension; | ||
812 | genpuzzle.attachment_content = std::move(image); | ||
813 | } else { | ||
814 | double fontsize = 72; | ||
815 | std::string renderWord = solution.getText(); | ||
816 | |||
817 | Magick::Image tester(Magick::Geometry(1, 1), "white"); | ||
818 | tester.font(fontpath_); | ||
819 | tester.fontPointsize(fontsize); | ||
820 | |||
821 | Magick::TypeMetric metric; | ||
822 | tester.fontTypeMetrics(renderWord, &metric); | ||
823 | |||
824 | double imgHeight = metric.textHeight() * 2; | ||
825 | double imgWidth = metric.textWidth() * 2; | ||
826 | |||
827 | Magick::Image result(Magick::Geometry(imgWidth, imgHeight), "white"); | ||
828 | result.font(fontpath_); | ||
829 | result.fontPointsize(fontsize); | ||
830 | result.draw(Magick::DrawableText(metric.textWidth() / 2, metric.textHeight() * 1.25, renderWord)); | ||
831 | result.charcoal(2, 2); | ||
832 | |||
833 | Magick::Image lineimage(Magick::Geometry(imgWidth, imgHeight), "transparent"); | ||
834 | lineimage.strokeColor("black"); | ||
835 | lineimage.fillColor("black"); | ||
836 | |||
837 | int lines = std::uniform_int_distribution<int>(1, 3)(rng_); | ||
838 | for (int i=0; i<lines; i++) { | ||
839 | lineimage.strokeWidth(std::uniform_int_distribution<int>(2, 6)(rng_)); | ||
840 | lineimage.draw(Magick::DrawableLine( | ||
841 | std::uniform_int_distribution<int>(imgWidth / 10, imgWidth / 8)(rng_), | ||
842 | std::uniform_int_distribution<int>(imgHeight / 10, imgHeight - (imgHeight / 10))(rng_), | ||
843 | imgWidth - std::uniform_int_distribution<int>(imgWidth / 10, imgWidth / 8)(rng_), | ||
844 | std::uniform_int_distribution<int>(imgHeight / 10, imgHeight - (imgHeight / 10))(rng_))); | ||
845 | } | ||
846 | |||
847 | lineimage.gaussianBlur(std::uniform_int_distribution<int>(2,4)(rng_), std::uniform_int_distribution<int>(1,3)(rng_)); | ||
848 | result.composite(lineimage, 0, 0, Magick::OverCompositeOp); | ||
849 | |||
850 | result.swirl(std::uniform_int_distribution<int>(45, 100)(rng_)); | ||
851 | result.addNoise(Magick::NoiseType::GaussianNoise); | ||
852 | result.motionBlur( | ||
853 | std::uniform_int_distribution<int>(1,4)(rng_), | ||
854 | std::uniform_int_distribution<int>(1,4)(rng_), | ||
855 | std::uniform_int_distribution<int>(0, 360)(rng_)); | ||
856 | |||
857 | result.magick("png"); | ||
858 | |||
859 | Magick::Blob outputBlob; | ||
860 | result.write(&outputBlob); | ||
861 | |||
862 | genpuzzle.attachment_name = "image.png"; | ||
863 | genpuzzle.attachment_content = std::string((const char*) outputBlob.data(), outputBlob.length()); | ||
864 | } | ||
865 | } | ||
866 | |||
793 | std::mt19937& rng_; | 867 | std::mt19937& rng_; |
794 | std::unique_ptr<dpp::cluster> bot_; | 868 | std::unique_ptr<dpp::cluster> bot_; |
795 | std::unique_ptr<verbly::database> database_; | 869 | std::unique_ptr<verbly::database> database_; |
@@ -797,6 +871,7 @@ private: | |||
797 | std::string scoreboard_endpoint_; | 871 | std::string scoreboard_endpoint_; |
798 | std::string scoreboard_secret_code_; | 872 | std::string scoreboard_secret_code_; |
799 | std::unique_ptr<wanderlust> wanderlust_; | 873 | std::unique_ptr<wanderlust> wanderlust_; |
874 | std::string fontpath_; | ||
800 | 875 | ||
801 | std::map<uint64_t, std::string> answer_by_message_; | 876 | std::map<uint64_t, std::string> answer_by_message_; |
802 | std::set<uint64_t> solved_puzzles_; | 877 | std::set<uint64_t> solved_puzzles_; |
@@ -810,6 +885,8 @@ private: | |||
810 | 885 | ||
811 | int main(int argc, char** argv) | 886 | int main(int argc, char** argv) |
812 | { | 887 | { |
888 | Magick::InitializeMagick(nullptr); | ||
889 | |||
813 | std::random_device randomDevice; | 890 | std::random_device randomDevice; |
814 | std::mt19937 rng{randomDevice()}; | 891 | std::mt19937 rng{randomDevice()}; |
815 | 892 | ||