summary refs log tree commit diff stats
path: root/generator.h
blob: 1381cd32f024d61a14657b46d05e3afe43648667 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
#ifndef GENERATOR_H_811386CE
#define GENERATOR_H_811386CE

#include <string>
#include <random>
#include <map>
#include <tuple>
#include <memory>
#include <verbly.h>
#include <hkutil/string.h>

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;
  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
  bool force_two_words = false;
};

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<std::string, std::string> GetPuzzle(std::mt19937& rng) const {
    return puzzles_.at(std::uniform_int_distribution<int>(0, puzzles_.size()-1)(rng));
  }

  const std::string& GetWord(std::mt19937& rng) const {
    return words_.at(std::uniform_int_distribution<int>(0, words_.size()-1)(rng));
  }

private:
  std::vector<std::tuple<std::string, std::string>> puzzles_;
  std::vector<std::string> 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<std::vector<std::string>>(line, " "));
    }
  }

  std::vector<std::vector<std::string>> GetPuzzleSet(std::mt19937& rng) const {
    std::vector<std::vector<std::string>> result = sets_;
    std::shuffle(result.begin(), result.end(), rng);
    result.resize(4);

    for (std::vector<std::string>& set : result) {
      std::shuffle(set.begin(), set.end(), rng);
    }

    return result;
  }

private:
  std::vector<std::vector<std::string>> sets_;
};

class Generator {
public:

  explicit Generator(unsigned int seed) : seed_(seed), rng_(seed) {
    database_ = std::make_unique<verbly::database>("/Users/hatkirby/Dropbox/Programming/verbly-datafiles/d1.3_lingo7");
    wanderlust_ = std::make_unique<Wanderlust>("../wanderlust_words.txt", "../wanderlust_puzzles.txt");
    cross_tower_ = std::make_unique<CrossTower>("../cross_tower.txt");
  }

  // Querying
  bool IsPanelRandomized(const std::string& name) const {
    return panels_.count(name);
  }

  const std::tuple<std::string, std::string>& 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);

  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) const;

  unsigned int seed_;
  std::mt19937 rng_;
  std::unique_ptr<verbly::database> database_;
  std::unique_ptr<Wanderlust> wanderlust_;
  std::unique_ptr<CrossTower> cross_tower_;

  // name, question, answer
  std::map<std::string, std::tuple<std::string, std::string>> panels_;

  std::vector<std::string> reusable_;
  std::map<std::string, std::set<std::string>> pools_;
};

#endif /* end of include guard: GENERATOR_H_811386CE */