#include "generator.h" #include #include verbly::filter Generator::MakeHintFilter(verbly::filter subfilter, Height height, Colour colour, FilterDirection filter_direction) { switch (colour) { case kWhite: { switch (height) { case kBottom: { return (verbly::word::synonyms %= subfilter); } case kTop: { return (verbly::form::pronunciations %= verbly::filter("homophones", false, (verbly::pronunciation::forms %= (subfilter && verbly::filter( verbly::form::id, verbly::filter::comparison::field_does_not_equal, verbly::form::id))))); } case kMiddle: { return subfilter; } default: break; // Not supported yet. } break; } case kBlack: { switch (height) { case kBottom: { return (verbly::word::antonyms %= subfilter); } case kMiddle: { return (verbly::form::antogram %= subfilter); } case kTop: { return (verbly::pronunciation::antophone %= subfilter); } default: break; // Not supported yet. } break; } case kBrown: { break; // Not supported yet. } case kRed: { switch (height) { case kTop: { if (filter_direction == kTowardSolution) { return (verbly::pronunciation::merophones %= subfilter); } else { return (verbly::pronunciation::holophones %= subfilter); } } case kMiddle: { if (filter_direction == kTowardSolution) { return (verbly::form::merographs %= subfilter); } else { return (verbly::form::holographs %= subfilter); } } case kBottom: { if (filter_direction == kTowardSolution) { return (verbly::notion::partMeronyms %= verbly::filter("partMeronyms", false, subfilter && verbly::filter( verbly::form::id, verbly::filter::comparison::field_does_not_equal, verbly::form::id))); } else { return (verbly::notion::partHolonyms %= verbly::filter("partHolonyms", false, subfilter && verbly::filter( verbly::form::id, verbly::filter::comparison::field_does_not_equal, verbly::form::id))); } } default: break; // Not supported yet. } break; } case kBlue: { switch (height) { case kTop: { if (filter_direction == kTowardSolution) { return (verbly::pronunciation::holophones %= subfilter); } else { return (verbly::pronunciation::merophones %= subfilter); } } case kMiddle: { if (filter_direction == kTowardSolution) { return (verbly::form::holographs %= subfilter); } else { return (verbly::form::merographs %= subfilter); } } case kBottom: { if (filter_direction == kTowardSolution) { return (verbly::notion::partHolonyms %= verbly::filter("partHolonyms", false, subfilter && verbly::filter( verbly::form::id, verbly::filter::comparison::field_does_not_equal, verbly::form::id))); } else { return (verbly::notion::partMeronyms %= verbly::filter("partMeronyms", false, subfilter && verbly::filter( verbly::form::id, verbly::filter::comparison::field_does_not_equal, verbly::form::id))); } } default: break; // Not supported yet. } break; } case kPurple: { switch (height) { case kMiddle: { return (verbly::form::holographs %= verbly::filter("midpurp", false, (verbly::form::length >= 4 && (verbly::form::merographs %= (subfilter && verbly::filter( verbly::form::id, verbly::filter::comparison::field_does_not_equal, verbly::form::id)))))); } case kTop: { return (verbly::pronunciation::rhymes %= subfilter); } default: break; // Not supported yet. } break; } case kYellow: { switch (height) { case kTop: { return (verbly::pronunciation::anaphones %= (subfilter && verbly::filter( verbly::pronunciation::id, verbly::filter::comparison::field_does_not_equal, verbly::pronunciation::id))); } case kMiddle: { return (verbly::form::anagrams %= (subfilter && verbly::filter( verbly::form::id, verbly::filter::comparison::field_does_not_equal, verbly::form::id))); } default: break; // Not supported yet. } break; } case kGreen: { if (filter_direction == kTowardSolution) { switch (height) { case kBottom: { 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); } case kMiddle: { return subfilter; } default: break; // Never supported. } } else { return (verbly::form::text == "picture"); } break; } default: break; // Not supported yet. } return {}; } std::string ApplyWanderlust(const std::string& word) { std::string result; for (char ch : word) { if (ch == 'w') { result += '1'; } else if (ch == 'a') { result += '2'; } else if (ch == 'n') { result += '3'; } else if (ch == 'd') { result += '4'; } else if (ch == 'e') { result += '5'; } else if (ch == 'r') { result += '6'; } else if (ch == 'l') { result += '7'; } else if (ch == 'u') { result += '8'; } else if (ch == 's') { result += '9'; } else if (ch == 't') { result += '0'; } else if (ch == ' ') { result += ' '; } } return result; } void Generator::GenerateStaticPanel(std::string name, std::string question, std::string answer) { SavePanel(name, question, answer.empty() ? question : answer, {}); } void Generator::GenerateSinglePanel(std::string name, Height height, Colour colour, GenerateOptions options) { while (!GenerateSinglePanelImpl(name, height, colour, options)); } bool Generator::GenerateSinglePanelImpl(std::string name, Height height, Colour colour, GenerateOptions options) { verbly::form solution; if (options.reuse_solution) { const std::string& word = reusable_.at(std::uniform_int_distribution(0, reusable_.size()-1)(rng_)); if (options.max_answer_len > 0 && word.size() > options.max_answer_len) return false; solution = database_->forms(verbly::form::text == word).first(); } else { verbly::filter forward = MakeHintFilter({}, height, colour, kTowardSolution); std::vector solutions = database_->forms(forward && GetWordFilter(kTowardSolution, options)).all(); solution = solutions.front();//solutions.at(std::uniform_int_distribution(0, solutions.size())(rng_)); } if (!options.unique_pool.empty() && pools_[options.unique_pool].count(solution.getText())) { return false; } if (options.palindrome != kPalindromeUnspecified) { std::string reversed = solution.getText(); std::reverse(reversed.begin(), reversed.end()); if ((options.palindrome == kForcePalindrome && reversed != solution.getText()) || (options.palindrome == kRejectPalindrome && reversed == solution.getText())) { return false; } } // Finish early if this is a middle white. if (height == kMiddle && colour == kWhite) { SavePanel(name, solution.getText(), solution.getText(), options); return true; } verbly::filter questionFilter = MakeHintFilter(solution, height, colour, kTowardQuestion); std::vector questions = database_->forms(questionFilter && GetWordFilter(kTowardQuestion, options)).all();//, verbly::order(verbly::form::id), 0).all(); if (questions.size() < 1) return false; verbly::form question = questions.front();// questions.at(std::uniform_int_distribution(0, questions.size())(rng_)); if (IsClueTrivial(height, colour, question, solution)) { return false; } SavePanel(name, question.getText(), solution.getText(), options); if (!options.copy_to.empty()) { SavePanel(options.copy_to, question.getText(), solution.getText(), options); } return true; } void Generator::GenerateDoublePanel(std::string name1, std::string name2, Height height, Colour colour, GenerateOptions options) { while (!GenerateDoublePanelImpl(name1, name2, height, colour, options)); } bool Generator::GenerateDoublePanelImpl(std::string name1, std::string name2, Height height, Colour colour, GenerateOptions options) { verbly::form solution; if (options.reuse_solution) { const std::string& word = reusable_.at(std::uniform_int_distribution(0, reusable_.size()-1)(rng_)); if (options.max_answer_len > 0 && word.size() > options.max_answer_len) return false; solution = database_->forms(verbly::form::text == word).first(); } else { verbly::filter forward = MakeHintFilter({}, height, colour, kTowardSolution); std::vector solutions = database_->forms(forward && GetWordFilter(kTowardSolution, options)).all(); solution = solutions.front();//solutions.at(std::uniform_int_distribution(0, solutions.size())(rng_)); } if (!options.unique_pool.empty() && pools_[options.unique_pool].count(solution.getText())) { return false; } // Finish early if this is a middle white. if (height == kMiddle && colour == kWhite) { SavePanel(name1, solution.getText(), solution.getText(), options); SavePanel(name2, solution.getText(), solution.getText(), options); return true; } verbly::filter questionFilter = MakeHintFilter(solution, height, colour, kTowardQuestion); std::vector questions = database_->forms(questionFilter && GetWordFilter(kTowardQuestion, options), {}, 2).all(); if (questions.size() < 2) return false; //std::shuffle(questions.begin(), questions.end(), rng_); if (IsClueTrivial(height, colour, questions[0], solution) || IsClueTrivial(height, colour, questions[1], solution)) { return false; } SavePanel(name1, questions[0].getText(), solution.getText(), options); SavePanel(name2, questions[1].getText(), solution.getText(), options); return true; } void Generator::GenerateCohintedPanels(std::string name1, std::string name2, Height height, Colour colour, GenerateOptions options) { while (!GenerateCohintedPanelsImpl(name1, name2, height, colour, options)); } bool Generator::GenerateCohintedPanelsImpl(std::string name1, std::string name2, Height height, Colour colour, GenerateOptions options) { verbly::filter backward = MakeHintFilter({}, height, colour, kTowardQuestion); std::vector questions = database_->forms(backward && GetWordFilter(kTowardQuestion, options)).all();//, verbly::order(verbly::form::id), 0).all(); verbly::form question = questions.front();// solutions.at(std::uniform_int_distribution(0, solutions.size())(rng_)); // Finish early if this is a middle white. if (height == kMiddle && colour == kWhite) { SavePanel(name1, question.getText(), question.getText(), options); SavePanel(name2, question.getText(), question.getText(), options); return true; } verbly::filter solutionFilter = MakeHintFilter(question, height, colour, kTowardSolution); std::vector solutions = database_->forms(solutionFilter && GetWordFilter(kTowardSolution, options), {}, 2).all(); if (solutions.size() < 2) return false; //std::shuffle(questions.begin(), questions.end(), rng_); if (IsClueTrivial(height, colour, question, solutions[0]) || IsClueTrivial(height, colour, question, solutions[1])) { return false; } SavePanel(name1, question.getText(), solutions[0].getText(), options); SavePanel(name2, question.getText(), solutions[1].getText(), options); return true; } void Generator::GeneratePairedPanels(std::string name1, std::string name2, Height height, Colour colour, GenerateOptions options) { while (!GeneratePairedPanelsImpl(name1, name2, height, colour, options)); } bool Generator::GeneratePairedPanelsImpl(std::string name1, std::string name2, Height height, Colour colour, GenerateOptions options) { Colour effectiveColour = (height == kMiddle && colour == kWhite) ? kBlack : colour; Height effectiveHeight = (height == kMiddle && colour == kWhite) ? kBottom : height; verbly::filter forward = MakeHintFilter({}, effectiveHeight, effectiveColour, kTowardSolution); std::vector solutions = database_->forms(forward && GetWordFilter(kTowardSolution, options)).all(); verbly::form solution = solutions.front();//solutions.at(std::uniform_int_distribution(0, solutions.size())(rng_)); verbly::filter questionFilter = MakeHintFilter(solution, effectiveHeight, effectiveColour, kTowardQuestion); std::vector questions = database_->forms(questionFilter && GetWordFilter(kTowardQuestion, options)).all();//, verbly::order(verbly::form::id), 0).all(); if (questions.size() < 1) return false; verbly::form question = questions.front();// questions.at(std::uniform_int_distribution(0, questions.size())(rng_)); if (IsClueTrivial(height, effectiveColour, question, solution)) { return false; } if (options.palindrome != kPalindromeUnspecified) { std::string reversed = question.getText(); std::reverse(reversed.begin(), reversed.end()); if ((options.palindrome == kForcePalindrome && reversed != question.getText()) || (options.palindrome == kRejectPalindrome && reversed == question.getText())) { return false; } } if (height == kMiddle && colour == kWhite) { SavePanel(name1, question.getText(), question.getText(), options); SavePanel(name2, solution.getText(), solution.getText(), options); if (!options.copy_to.empty() && !options.copy_to2.empty()) { SavePanel(options.copy_to, question.getText(), question.getText(), options); SavePanel(options.copy_to2, solution.getText(), solution.getText(), options); } } else { SavePanel(name1, question.getText(), solution.getText(), options); SavePanel(name2, solution.getText(), question.getText(), options); if (!options.copy_to.empty() && !options.copy_to2.empty()) { SavePanel(options.copy_to, question.getText(), solution.getText(), options); SavePanel(options.copy_to2, solution.getText(), question.getText(), options); } } return true; } void Generator::GeneratePanelStack(std::string top_name, Colour top_colour, std::string middle_name, Colour middle_colour, std::string bottom_name, Colour bottom_colour, GenerateOptions options) { while (!GeneratePanelStackImpl(top_name, top_colour, middle_name, middle_colour, bottom_name, bottom_colour, options)); } bool Generator::GeneratePanelStackImpl(std::string top_name, Colour top_colour, std::string middle_name, Colour middle_colour, std::string bottom_name, Colour bottom_colour, GenerateOptions options) { verbly::form solution; if (options.reuse_solution) { const std::string& word = reusable_.at(std::uniform_int_distribution(0, reusable_.size()-1)(rng_)); if (options.max_answer_len > 0 && word.size() > options.max_answer_len) return false; solution = database_->forms(verbly::form::text == word).first(); } else { verbly::filter forward = GetWordFilter(kTowardSolution, options); if (!top_name.empty()) { forward &= MakeHintFilter({}, kTop, top_colour, kTowardSolution); } if (!middle_name.empty()) { forward &= MakeHintFilter({}, kMiddle, middle_colour, kTowardSolution); } if (!bottom_name.empty()) { forward &= MakeHintFilter({}, kBottom, bottom_colour, kTowardSolution); } std::vector solutions = database_->forms(forward).all(); solution = solutions.front();//solutions.at(std::uniform_int_distribution(0, solutions.size())(rng_)); } if (!options.unique_pool.empty() && pools_[options.unique_pool].count(solution.getText())) { return false; } std::string top_hint; std::string middle_hint; std::string bottom_hint; if (!top_name.empty()) { verbly::filter questionFilter = MakeHintFilter(solution, kTop, top_colour, kTowardQuestion); std::vector questions = database_->forms(questionFilter && GetWordFilter(kTowardQuestion, options), {}, 1).all(); if (questions.empty()) return false; top_hint = questions.front().getText(); if (IsClueTrivial(kTop, top_colour, questions.front(), solution)) { return false; } } if (!middle_name.empty()) { verbly::filter questionFilter = MakeHintFilter(solution, kMiddle, middle_colour, kTowardQuestion); std::vector questions = database_->forms(questionFilter && GetWordFilter(kTowardQuestion, options), {}, 1).all(); if (questions.empty()) return false; middle_hint = questions.front().getText(); if (IsClueTrivial(kMiddle, middle_colour, questions.front(), solution)) { return false; } } if (!bottom_name.empty()) { verbly::filter questionFilter = MakeHintFilter(solution, kBottom, bottom_colour, kTowardQuestion); std::vector questions = database_->forms(questionFilter && GetWordFilter(kTowardQuestion, options), {}, 1).all(); if (questions.empty()) return false; bottom_hint = questions.front().getText(); if (IsClueTrivial(kBottom, bottom_colour, questions.front(), solution)) { return false; } } if (!top_name.empty()) { SavePanel(top_name, top_hint, solution.getText(), options); } if (!middle_name.empty()) { SavePanel(middle_name, middle_hint, solution.getText(), options); } if (!bottom_name.empty()) { SavePanel(bottom_name, bottom_hint, solution.getText(), options); } return true; } void Generator::GenerateOrangeNumberPanel(std::string name) { std::string solution = wanderlust_->GetWord(rng_); std::string question = ApplyWanderlust(solution); SavePanel(name, question, solution); } void Generator::GenerateOrangeWordPanel(std::string name) { std::string question = wanderlust_->GetWord(rng_); std::string solution = ApplyWanderlust(question); SavePanel(name, question, solution); } void Generator::GenerateOrangeAdditionPanel(std::string name) { auto [question, solution] = wanderlust_->GetPuzzle(rng_); SavePanel(name, question, solution); } void Generator::GenerateOneRoadManyTurns(std::string order_name, std::string part1_name, std::string part2_name, std::string part3_name, std::string part4_name) { const auto& [part1_q, part1_a] = panels_.at(part1_name); const auto& [part2_q, part2_a] = panels_.at(part2_name); const auto& [part3_q, part3_a] = panels_.at(part3_name); const auto& [part4_q, part4_a] = panels_.at(part4_name); SavePanel(order_name, "order", part1_a + " " + part2_a + " " + part3_a + " " + part4_a); } void Generator::GenerateComboPanel(std::string name, Height left_height, Colour left_colour, Height right_height, Colour right_colour, GenerateOptions options) { while (!GenerateComboPanelImpl(name, left_height, left_colour, right_height, right_colour, options)); } bool Generator::GenerateComboPanelImpl(std::string name, Height left_height, Colour left_colour, Height right_height, Colour right_colour, GenerateOptions options) { options.force_two_words = true; verbly::form solution = database_->forms(GetWordFilter(kTowardSolution, options)).first(); std::string soltext = solution.getText(); int spacepos = soltext.find(" "); std::string leftword = soltext.substr(0, spacepos); std::string rightword = soltext.substr(spacepos+1); options.force_two_words = false; verbly::filter left_filter = MakeHintFilter(verbly::form::text == leftword, left_height, left_colour, kTowardQuestion); std::vector left_questions = database_->forms(left_filter && GetWordFilter(kTowardQuestion, options)).all(); if (left_questions.size() < 1) return false; verbly::form left_question = left_questions.front(); verbly::filter right_filter = MakeHintFilter(verbly::form::text == rightword, right_height, right_colour, kTowardQuestion); std::vector right_questions = database_->forms(right_filter && GetWordFilter(kTowardQuestion, options)).all(); if (right_questions.size() < 1) return false; verbly::form right_question = right_questions.front(); SavePanel(name, left_question.getText() + " " + right_question.getText(), soltext, options); return true; } void Generator::GenerateCrossTower( std::string north_tower_name, std::string south_tower_name, std::string east_tower_name, std::string west_tower_name, std::string north_lookout_name, std::string south_lookout_name, std::string east_lookout_name, std::string west_lookout_name, std::string north_other_name1, std::string north_other_name2, std::string north_other_name3, std::string south_other_name1, std::string south_other_name2, std::string south_other_name3, std::string east_other_name1, std::string east_other_name2, std::string east_other_name3, std::string west_other_name1, std::string west_other_name2, std::string west_other_name3) { std::vector> sets = cross_tower_->GetPuzzleSet(rng_); } void Generator::SavePanel(std::string name, std::string question, std::string answer, GenerateOptions options) { if (options.save_for_later) { reusable_.push_back(answer); } if (!options.unique_pool.empty()) { pools_[options.unique_pool].insert(answer); } if (options.obscure_hint) { int numToObscure = (question.size()/3 > 0) ? std::uniform_int_distribution(1, question.size()/3)(rng_) : 1; std::vector indicies(question.size()); std::iota(indicies.begin(), indicies.end(), 0); std::shuffle(indicies.begin(), indicies.end(), rng_); for (int i=0; i 0) { wordFilter &= (verbly::form::length == options.exact_len); } else if (dir == kTowardSolution && options.max_answer_len > 0) { wordFilter &= (verbly::form::length <= options.max_answer_len); } else if (dir == kTowardQuestion && options.max_hint_len > 0) { wordFilter &= (verbly::form::length <= options.max_hint_len); } else { wordFilter &= (verbly::form::length <= 11); } if (options.exact_len == 0) { wordFilter &= (verbly::form::length >= 3); } if (!options.multiword) { if (options.force_two_words) { wordFilter &= (verbly::form::complexity == 2); } else { wordFilter &= (verbly::form::complexity == 1); } } else { wordFilter &= ((verbly::form::complexity > 1) || (verbly::form::frequency > 2000000)); } return wordFilter; } bool Generator::IsClueTrivial(Height height, Colour colour, const verbly::form& clue, const verbly::form& solution) const { if (height == kTop && colour == kWhite) { return !database_->forms((verbly::filter)clue && (verbly::word::synonyms %= solution)).all().empty(); } else if (height == kBottom && colour == kWhite) { return !database_->forms((verbly::filter)clue && (verbly::form::pronunciations %= solution)).all().empty(); } else if (height == kBottom && colour == kBlack) { return !database_->forms((verbly::filter)clue && (verbly::form::merographs %= solution)).all().empty() || !database_->forms((verbly::filter)clue && (verbly::form::holographs %= solution)).all().empty(); } else if ((height == kMiddle || height == kTop) && colour == kPurple) { return (clue.getId() == solution.getId()) || !database_->forms((verbly::filter)clue && (verbly::form::merographs %= solution)).all().empty() || !database_->forms((verbly::filter)clue && (verbly::form::holographs %= solution)).all().empty(); } return false; }