#ifndef GENERATOR_H_811386CE #define GENERATOR_H_811386CE #include #include #include #include #include #include #include enum Height { kTop, kMiddle, kBottom, kHeightCount }; enum Colour { kWhite, kBlack, kRed, kBlue, kPurple, kBrown, kYellow, kGreen, kOrange, kColourCount }; enum FilterDirection { kTowardSolution, kTowardQuestion }; enum PalindromeQuery { kPalindromeUnspecified, kForcePalindrome, kRejectPalindrome }; struct GenerateOptions { bool obscure_hint = false; int max_answer_len = 0; int max_hint_len = 0; int exact_len = 0; int max_len_diff = -1; // supported by single, double, paired, cohinted bool multiword = false; bool save_for_later = false; bool reuse_solution = false; // supported by single, double, and stack std::string unique_pool; // supported by single, double, and stack PalindromeQuery palindrome = kPalindromeUnspecified; // only important for middle black. supported by single and paired std::string copy_to; // supported by single and paired std::string copy_to2; // supported by paired std::string copy_hidden; // supported by single mid white bool force_two_words = false; bool must_be_broad = false; // the solution must also be gettable by bottom white/red/blue. supported by single and double bool allow_top_expansion = false; // this disables the triviality check that top puzzles can't also apply to middle }; class Wanderlust { public: Wanderlust(const std::string& words_filename, const std::string& puzzles_filename) { std::ifstream words_file(words_filename); std::string line; while (std::getline(words_file, line)) { words_.push_back(line); } std::ifstream puzzles_file(puzzles_filename); while (std::getline(puzzles_file, line)) { std::string line2; if (!std::getline(puzzles_file, line2)) { throw std::invalid_argument("Wanderlust file is malformed."); } puzzles_.emplace_back(line, line2); } } std::tuple GetPuzzle(std::mt19937& rng) const { return puzzles_.at(std::uniform_int_distribution(0, puzzles_.size()-1)(rng)); } const std::string& GetWord(std::mt19937& rng) const { return words_.at(std::uniform_int_distribution(0, words_.size()-1)(rng)); } private: std::vector> puzzles_; std::vector words_; }; class CrossTower { public: explicit CrossTower(std::string filename) { std::ifstream file(filename); std::string line; while (std::getline(file, line)) { sets_.push_back(hatkirby::split>(line, " ")); } } std::vector> GetPuzzleSet(std::mt19937& rng) const { std::vector> result = sets_; std::shuffle(result.begin(), result.end(), rng); result.resize(4); for (std::vector& set : result) { std::shuffle(set.begin(), set.end(), rng); } return result; } private: std::vector> sets_; }; class Generator { public: explicit Generator(unsigned int seed) : seed_(seed), rng_(seed) { database_ = std::make_unique("/Users/hatkirby/Dropbox/Programming/verbly-datafiles/d1.3_lingo7"); wanderlust_ = std::make_unique("../wanderlust_words.txt", "../wanderlust_puzzles.txt"); cross_tower_ = std::make_unique("../cross_tower.txt"); } // Querying bool IsPanelRandomized(const std::string& name) const { return panels_.count(name); } const std::tuple& GetPanel(const std::string& name) const { return panels_.at(name); } // Sets the panel's question and answer to static values. void GenerateStaticPanel(std::string name, std::string question, std::string answer = ""); // Generates a one-block puzzle with the given height and colour. void GenerateSinglePanel(std::string name, Height height, Colour colour, GenerateOptions options = {}); // Generates a one-block puzzle with the given height and colour, where there is a panel on two faces of the block. // Both puzzles will have the same answer. void GenerateDoublePanel(std::string name1, std::string name2, Height height, Colour colour, GenerateOptions options = {}); // Generates two puzzles with the same hint but different answers. void GenerateCohintedPanels(std::string name1, std::string name2, Height height, Colour colour, GenerateOptions options = {}); // Generates two panels at once, where the question for one is the answer to the other. // Colour must be white, black, or yellow. // Middle white is a special case; the puzzles will be antonyms of one another. void GeneratePairedPanels(std::string name1, std::string name2, Height height, Colour colour, GenerateOptions options = {}); // Generates a vertical stack of panels that all have the same answer. // If a name is left blank, that height will be ignored. void GeneratePanelStack(std::string top_name, Colour top_colour, std::string middle_name, Colour middle_colour, std::string bottom_name, Colour bottom_colour, GenerateOptions options = {}); // Generate an orange puzzle, with a number for the hint. void GenerateOrangeNumberPanel(std::string name); // Generate an orange puzzle, with a word for the hint. void GenerateOrangeWordPanel(std::string name); // Generate an orange puzzle, with an addition problem for the hint. void GenerateOrangeAdditionPanel(std::string name); // Generates the ONE ROAD MANY TURNS panel by combining the solutions to four other panels. void GenerateOneRoadManyTurns(std::string order_name, std::string part1_name, std::string part2_name, std::string part3_name, std::string part4_name); // Generates a hybrid puzzle where the solution is two words and each word is provided by a different colour/height puzzle. void GenerateComboPanel(std::string name, Height left_height, Colour left_colour, Height right_height, Colour right_colour, GenerateOptions options = {}); // Generates the cross tower sets-of-four puzzles. void 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); private: verbly::filter MakeHintFilter(verbly::filter subfilter, Height height, Colour colour, FilterDirection filter_direction) const; bool GenerateSinglePanelImpl(std::string name, Height height, Colour colour, GenerateOptions options); bool GenerateDoublePanelImpl(std::string name1, std::string name2, Height height, Colour colour, GenerateOptions options); bool GenerateCohintedPanelsImpl(std::string name1, std::string name2, Height height, Colour colour, GenerateOptions options); bool GeneratePairedPanelsImpl(std::string name1, std::string name2, Height height, Colour colour, GenerateOptions options); bool GeneratePanelStackImpl(std::string top_name, Colour top_colour, std::string middle_name, Colour middle_colour, std::string bottom_name, Colour bottom_colour, GenerateOptions options); bool GenerateComboPanelImpl(std::string name, Height left_height, Colour left_colour, Height right_height, Colour right_colour, GenerateOptions options); void SavePanel(std::string name, std::string question, std::string answer, GenerateOptions options = {}); verbly::filter GetWordFilter(FilterDirection direction, GenerateOptions options) const; bool IsClueTrivial(Height height, Colour colour, const verbly::form& clue, const verbly::form& solution, GenerateOptions options = {}) const; unsigned int seed_; std::mt19937 rng_; std::unique_ptr database_; std::unique_ptr wanderlust_; std::unique_ptr cross_tower_; // name, question, answer std::map> panels_; std::vector reusable_; std::map> pools_; }; #endif /* end of include guard: GENERATOR_H_811386CE */