1 // Hyperbolic Rogue -- dual-geometry puzzle generator
2 // Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details
3 
4 /** \file dpgen.cpp
5  *  \brief dual geometry puzzle generator
6  */
7 
8 #include "hyper.h"
9 
10 namespace hr {
11 
12 EX namespace dpgen {
13 
14 EX bool in;
15 
16 typedef tuple<cell*, cell*, int> cpos;
17 
18 map<cpos, int> visited;
19 
20 vector<cpos> all;
21 vector<int> last;
22 
enqueue(cpos p,int d,int li)23 void enqueue(cpos p, int d, int li) {
24   if(visited.count(p)) return;
25   visited[p] = d;
26   all.push_back(p);
27   last.push_back(li);
28   }
29 
solve(cpos at)30 void solve(cpos at) {
31   visited.clear();
32   all.clear();
33   last.clear();
34   enqueue(at, 0, -1);
35   for(int i=0; i<isize(all); i++) {
36     auto next = all[i];
37 
38     auto c0 = get<0>(next);
39     auto c1 = get<1>(next);
40     auto d  = get<2>(next);
41 
42     int dist = visited[next];
43 
44     for(int k=0; k<4; k++) {
45       cell *ca0 = c0->move(k);
46       if(!ca0) continue;
47       if(ca0->wall != waNone) continue;
48 
49       cell *ca1 = c1->modmove(d+k);
50       if(!ca1) continue;
51       if(ca1->wall != waNone) continue;
52 
53       int s = (c1->c.spin((d+k)%4) - c0->c.spin(k)) & 3;
54       enqueue(make_tuple(ca0, ca1, s), dist+1, i);
55       }
56     }
57   }
58 
59 int last_elimit, last_hlimit;
60 
launch(int seed,int elimit,int hlimit)61 void launch(int seed, int elimit, int hlimit) {
62 
63   /* setup */
64   dual::enable();
65   stop_game();
66   dual::switch_to(0);
67   enable_canvas();
68   canvas_default_wall = waSea;
69   pconf.scale = .5;
70   dual::switch_to(1);
71   enable_canvas();
72   shrand(seed);
73   start_game();
74   in = true;
75 
76   cell *c0, *c1;
77   dual::switch_to(0);
78   vector<cell*> cl0, cl1;
79   if(1) {
80     c0 = cwt.at;
81     celllister cl(cwt.at, elimit, 999, nullptr);
82     cl0 = cl.lst;
83     for(cell *c: cl.lst) {
84       c->wall = waNone, c->land = laCanvas;
85       c->landparam = cl.getdist(c) % 2 ? 0x80C080 : 0x409040;
86       }
87     println(hlog, "c0 size = ", isize(cl.lst));
88     }
89   dual::switch_to(1);
90   if(1) {
91     c1 = cwt.at;
92     celllister cl(cwt.at, hlimit, 999, nullptr);
93     cl1 = cl.lst;
94     for(cell *c: cl.lst) {
95       c->wall = waNone, c->land = laCanvas;
96       #if CAP_ARCM
97       int id = arcm::current.tilegroup[arcm::id_of(c->master)];
98       color_t yellows[5] = { 0x80C080, 0x80C0C0, 0x8080C0, 0xC080C0, 0xC0C080 };
99       c->landparam = yellows[id];
100       #endif
101       }
102     println(hlog, "c1 size = ", isize(cl.lst));
103     }
104   cpos start = make_tuple(c0, c1, 0);
105   solve(start);
106   println(hlog, "queue size = ", isize(all));
107 
108   vector<cell*> clboth;
109 
110   pair<cell*, cell*> worst;
111   if(1) {
112     int wdist = -1, wdcount;
113     for(cell* x0: cl0) for(cell *x1: cl1) {
114       int x = 9999;
115       for(int d=0; d<4; d++)
116         if(visited.count(make_tuple(x0, x1, d)))
117           x = min(x, visited[make_tuple(x0, x1, d)]);
118       if(x == 9999) continue;
119       if(x > wdist) wdist = x, wdcount = 0;
120       if(wdist == x) { wdcount++; if(hrand(wdcount) == 0) worst = {x0, x1}; }
121       }
122     // println(hlog, "wdist = ", wdist, " x ", wdcount);
123     }
124 
125   clboth = cl0; for(cell *c: cl1) clboth.push_back(c);
126 
127   while(true) {
128     int wdist = -1, wdcount = 0;
129     cell *worst_block;
130     for(cell *c: clboth) if(c->wall == waNone && c != c0 && c != c1) {
131       c->wall = waSea;
132       solve(start);
133       c->wall = waNone;
134       int x = 9999;
135       for(int d=0; d<4; d++)
136         if(visited.count(make_tuple(worst.first, worst.second, d)))
137           x = min(x, visited[make_tuple(worst.first, worst.second, d)]);
138       if(x == 9999) continue;
139       if(x > wdist) wdist = x, wdcount = 0;
140       if(wdist == x) { wdcount++; if(hrand(wdcount) == 0) worst_block = c; }
141       }
142     println(hlog, "wdist = ", wdist, " x ", wdcount);
143     if(wdist == -1) break;
144     worst_block->wall = waSea;
145     }
146 
147   solve(start);
148 
149   println(hlog, "worst = ", worst);
150   for(int i=0; i<isize(all); i++) if(get<0>(all[i]) == worst.first && get<1>(all[i]) == worst.second) {
151     int at = i;
152     while(at != -1) {
153       // get<0>(all[at])->item = itDiamond;
154       // get<1>(all[at])->item = itDiamond;
155       at = last[at];
156       }
157     break;
158     }
159 
160   worst.first->wall = waOpenPlate;
161   worst.second->wall = waOpenPlate;
162   }
163 
164 struct puzzle {
165   string name;
166   int seed;
167   int el, hl;
168   };
169 
170 vector<puzzle> puzzles = {
171   {"easy 1", 1, 3, 2},
172   {"easy 2", 2, 3, 2},
173   {"easy 3", 5, 3, 2},
174   {"medium 1", 7, 3, 3},
175   {"medium 2", 11, 3, 3},
176   {"hard 1", 1, 4, 3},
177   {"hard 2", 1, 3, 4},
178   {"hard 3", 1, 3, 5},
179   };
180 
check()181 EX void check() {
182   if(in) {
183     int k = dual::currently_loaded;
184     dual::switch_to(1-k);
185     bool ok = cwt.at->wall == waOpenPlate;
186     dual::switch_to(k);
187     ok = ok && cwt.at->wall == waOpenPlate;
188     if(ok) addMessage("You won!");
189     }
190   }
191 
192 bool hide_random = false;
193 int last_seed = 0;
194 
show_menu()195 EX void show_menu() {
196   gamescreen(1);
197   dialog::init(XLAT("dual geometry puzzles"));
198   dialog::addHelp(XLAT("move both characters to marked squares at once!"));
199   dialog::addBreak(100);
200   char ch = 'a';
201   for(auto& p: puzzles) {
202     dialog::addItem(p.name, ch++);
203     dialog::add_action([p] {
204       launch(last_seed = p.seed, last_elimit = p.el, last_hlimit = p.hl);
205       popScreenAll();
206       });
207     }
208   dialog::addBreak(50);
209   if(last_elimit && !hide_random) {
210     dialog::addItem(XLAT("randomize"), 'r');
211     dialog::add_action([] {
212       last_seed = rand() % 1000000;
213       launch(last_seed, last_elimit, last_hlimit);
214       popScreenAll();
215       });
216 
217     dialog::addItem(XLAT("enter seed"), 's');
218     dialog::add_action([] {
219       dialog::editNumber(last_seed, 0, 1000000, 1, last_seed, XLAT("seed"), "");
220       dialog::reaction_final = [] {
221         launch(last_seed, last_elimit, last_hlimit);
222         popScreenAll();
223         };
224       dialog::extra_options = [] {
225         dialog::addSelItem("Euclidean size", its(last_elimit), 'E');
226         dialog::add_action([] { popScreen(); dialog::editNumber(last_elimit, 2, 10, 1, 3, XLAT("Euclidean size"), ""); });
227         dialog::addSelItem("hyperbolic size", its(last_hlimit), 'H');
228         dialog::add_action([] { popScreen(); dialog::editNumber(last_hlimit, 2, 10, 1, 2, XLAT("hyperbolic size"), ""); });
229         };
230       });
231     }
232   dialog::addBreak(50);
233   dialog::addBack();
234   dialog::display();
235   }
236 
237 #if CAP_COMMANDLINE
__anon70fe2a0b0802null238 auto sbhook = addHook(hooks_args, 100, [] {
239   using namespace arg;
240 
241   if(0) ;
242   else if(argis("-dpgen")) {
243     shift(); last_seed = argi();
244     shift(); last_elimit = argi();
245     shift(); last_hlimit = argi();
246     launch(last_seed, last_elimit, last_hlimit);
247     }
248   else if(argis("-d:dpgen")) {
249     pushScreen(show_menu);
250     }
251   else if(argis("-dph")) {
252     last_seed = 1;
253     last_elimit = 3;
254     last_hlimit = 2;
255     launch(1, 3, 2);
256     hide_random = true;
257     pushScreen(show_menu);
258     }
259   else return 1;
260   return 0;
261   }) + addHook(hooks_o_key, 91, [] (o_funcs& v) {
262     if(in) v.push_back(named_dialog(XLAT("select a puzzle"), show_menu));
263     });
264 #endif
265 
266 EX }
267 }