1 // Hyperbolic Rogue -- the 'experiments with geometry' menu
2 // Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details
3
4 /** \file geom-exp.cpp
5 * \brief The 'experiments with geometry' menu
6 *
7 * Implementation of this menu, and computation of the statistics shown there
8 */
9
10 #include "hyper.h"
11 namespace hr {
12
13 int eupage = 0;
14 int euperpage = 9;
15
16 string euchelp =
17 "If you want to know how much the gameplay is affected by the "
18 "hyperbolic geometry in HyperRogue, this mode is for you!\n\n"
19
20 "You can try many different geometries here. We start by gluing "
21 "n-gons in such a way that k of them meet in every vertex. "
22 "Depending on n and k, this either folds into a sphere, unfolds into a plane, "
23 "or requires a hyperbolic space. The result may be then 'bitruncated' by "
24 "replacing each vertex by a 2k-gon. Furthermore, you can play "
25 "with quotient geometries. For example, the elliptic geometry is "
26 "obtained from the sphere by making the antipodes be the same point, "
27 "so you return to the same spot (but as a mirror image) after going there. "
28 "Have fun experimenting! "
29 "Achievements and leaderboards do not work in geometry experiments, "
30 "except some specific ones.\n\n"
31 "In standard geometry (bitruncated or not), you can play the full game, but in other geometries "
32 "you select a particular land. Lands are unlocked by visiting them in this "
33 "session, or permanently by collecting 25 treasure. Try Crossroads in Euclidean "
34 "or chaos mode in non-standard non-quotient hyperbolic to visit many lands. "
35 "Highlights:\n"
36 "* Crystal World and Warped Coast can be understood as extra geometries.\n"
37 "* Halloween is specially designed for spherical geometry.\n"
38 "* To see the difference, try Hunting Grounds in Euclidean -- it is impossible.\n";
39
40 #if CAP_FIELD
showQuotientConfig()41 void showQuotientConfig() {
42 using namespace fieldpattern;
43 gamescreen(2);
44 dialog::init(XLAT("field quotient"));
45 fgeomextra& gxcur = fgeomextras[current_extra];
46 for(int i=0; i<isize(fgeomextras); i++) {
47 auto& g = fgeomextras[i];
48 dialog::addBoolItem(ginf[g.base].tiling_name, g.base == gxcur.base, 'a'+i);
49 dialog::add_action([i] { current_extra = i; });
50 }
51
52 dialog::addBreak(100);
53
54 nextPrimes(gxcur);
55 string stars[3] = {"", "*", "**"};
56 for(int i=0; i<isize(gxcur.primes); i++) {
57 auto& p = gxcur.primes[i];
58
59 int sides = ginf[gxcur.base].sides;
60 bool sa = shapename_available(sides);
61 string s = sa ? "order %1%2 (%3 %4s)" : "order %1%2 (%3 %4-gons)";
62
63 s = XLAT(s, its(p.p), p.squared ? "²" : "", its(p.cells), sa ? shapename(sides) : its(sides));
64
65 s += stars[gxcur.dualval[i]];
66
67 dialog::addBoolItem(s, i == gxcur.current_prime_id, 'A'+i);
68 dialog::add_action([&gxcur, i] { gxcur.current_prime_id = i; });
69 }
70
71 if(isize(gxcur.primes) <= 6) {
72 dialog::addBreak(100);
73 dialog::addHelp(
74 "This geometry is obtained by applying the same 'generators' which "
75 "lead to creating the given basic hyperbolic geometry, "
76 "but using a fixed finite field instead of the field of reals. "
77 "It can be also interpreted as a quotient of the given basic geometry. "
78 "Warning: field patterns based on large primes might generate for a long time."
79 );
80 dialog::addBreak(100);
81 }
82
83 dialog::addItem("find the next prime", 'p');
84 dialog::add_action([&gxcur] { nextPrime(gxcur); });
85
86 dialog::addItem("activate", 'x');
87 dialog::add_action_confirmed([&gxcur] {
88 set_geometry(gxcur.base);
89 enableFieldChange();
90 set_geometry(gFieldQuotient);
91 start_game();
92 });
93
94 dialog::addItem("find alternate manifolds", 'y');
95 dialog::add_action_confirmed([&gxcur] {
96 set_geometry(gxcur.base);
97 triplet_id = 0;
98 enableFieldChange();
99 set_geometry(gFieldQuotient);
100 start_game();
101
102 auto& cfp = currfp;
103 auto triplets = cfp.find_triplets();
104 pushScreen([triplets] {
105 gamescreen(2);
106 dialog::init(XLAT("alternate manifolds"));
107 int id = 0;
108 for(auto t: triplets) {
109 dialog::addItem(XLAT("generators (%1,%2), size %3", its(t.i), its(t.j), its(t.size/S7)), 'a'+id);
110 dialog::add_action([id] {
111 stop_game();
112 triplet_id = id;
113 fieldpattern::enableFieldChange();
114 start_game();
115 });
116 id++;
117 }
118 dialog::addBreak(100);
119 dialog::addHelp(XLAT(
120 "This option finds alternate solutions. For example, there are three {7,3} manifolds with 156 heptagons each (\"first Hurwitz triplet\").")
121 );
122 dialog::display();
123 });
124 });
125
126 dialog::addItem("default", 'z');
127 dialog::add_action_confirmed([] {
128 set_geometry(gEuclid);
129 fieldpattern::quotient_field_changed = false;
130 set_geometry(gFieldQuotient);
131 start_game();
132 });
133
134 dialog::addBack();
135 dialog::display();
136 }
137 #endif
138
139 EX string bitruncnames[5] = {" (b)", " (n)", " (g)", " (i)", " (d)"};
140
validity_info()141 void validity_info() {
142 int vccolors[4] = {0xFF0000, 0xFF8000, 0xFFFF00, 0x00FF00};
143 auto lv = land_validity(specialland);
144 if(lv.flags & lv::display_error_message)
145 dialog::addInfo(XLAT(lv.msg), vccolors[lv.quality_level]);
146 else
147 dialog::addBreak(100);
148 }
149
150 EX bool showquotients;
151
152 string validclasses[4] = {" (X)", " (½)", "", " (!)"};
153
ge_land_selection()154 EX void ge_land_selection() {
155 cmode = sm::SIDE | sm::MAYDARK;
156 gamescreen(0);
157
158 if(cheater) for(int i=0; i<landtypes; i++) landvisited[i] = true;
159
160 for(int i=0; i<landtypes; i++)
161 if(hiitemsMax(treasureType(eLand(i))) >= 25) landvisited[i] = true;
162 landvisited[laCrossroads] = true;
163 landvisited[laCrossroads4] = true;
164 landvisited[laIce] = true;
165 landvisited[laHunting] = true;
166 landvisited[laMirrorOld] = true;
167 landvisited[laPrincessQuest] = cheater || princess::everSaved;
168 landvisited[laWildWest] = true;
169 landvisited[laHalloween] = true;
170 landvisited[laWarpCoast] = true;
171 landvisited[laGraveyard] = true;
172 landvisited[laDual] = true;
173 landvisited[laDocks] |= landvisited[laWarpCoast];
174 landvisited[laSnakeNest] |= landvisited[laRedRock];
175 landvisited[laCamelot] |= hiitemsMax(treasureType(laCamelot)) >= 1;
176 landvisited[laCA] = true;
177 landvisited[laAsteroids] = true;
178
179 dialog::init(XLAT("select the starting land"));
180 if(dialog::infix != "") mouseovers = dialog::infix;
181
182 generateLandList([] (eLand l) {
183 if(dialog::infix != "" && !dialog::hasInfix(linf[l].name)) return false;
184 return !!(land_validity(l).flags & lv::appears_in_geom_exp);
185 });
186 stable_sort(landlist.begin(), landlist.end(), [] (eLand l1, eLand l2) { return land_validity(l1).quality_level > land_validity(l2).quality_level; });
187
188 for(int i=0; i<euperpage; i++) {
189 if(euperpage * eupage + i >= isize(landlist)) { dialog::addBreak(100); break; }
190 eLand l = landlist[euperpage * eupage + i];
191 char ch = '1'+i;
192 string s = XLAT1(linf[l].name);
193
194 if(landvisited[l]) {
195 dialog::addBoolItem(s, l == specialland, ch);
196 }
197 else {
198 dialog::addSelItem(s, XLAT("(locked)"), ch);
199 }
200
201 dialog::lastItem().color = linf[l].color;
202 dialog::lastItem().value += validclasses[land_validity(l).quality_level];
203 dialog::add_action([l] {
204 if(landvisited[l]) dialog::do_if_confirmed(dual::mayboth([l] {
205 stop_game_and_switch_mode(tactic::on ? rg::tactic : rg::nothing);
206 firstland = specialland = l;
207 if(l == laCanvas || l == laAsteroids)
208 land_structure = lsSingle;
209 else if(among(l, laCrossroads, laCrossroads2))
210 land_structure = lsNiceWalls;
211 else if(among(l, laCrossroads4))
212 land_structure = lsNoWalls;
213 start_game();
214 popScreen();
215 }));
216 });
217 }
218 dialog::addItem(XLAT("next page"), '-');
219 dialog::addInfo(XLAT("press letters to search"));
220
221 dialog::addBreak(25);
222 validity_info();
223 dialog::addBreak(25);
224
225 dual::add_choice();
226 dialog::addBack();
227 dialog::display();
228
229 keyhandler = [] (int sym, int uni) {
230 dialog::handleNavigation(sym, uni);
231
232 if(uni == '-' || uni == PSEUDOKEY_WHEELUP || uni == PSEUDOKEY_WHEELDOWN) {
233 eupage++;
234 if(eupage * euperpage >= isize(landlist)) eupage = 0;
235 }
236 else if(dialog::editInfix(uni)) eupage = 0;
237 else if(doexiton(sym, uni)) popScreen();
238 };
239 }
240
activate_ge_land_selection()241 EX void activate_ge_land_selection() {
242 dialog::infix = "";
243 eupage = 0;
244 pushScreen(ge_land_selection);
245 }
246
247 #if HDR
248 struct geometry_filter {
249 string name;
250 /** test if the current geometry matches the filter */
251 function<bool()> test;
252 };
253 #endif
254
255 EX geometry_filter *current_filter;
256
forced_quotient()257 bool forced_quotient() { return quotient && !(cgflags & qOPTQ); }
258
__anon29482a1f0e02null259 EX geometry_filter gf_hyperbolic = {"hyperbolic", [] { return (arcm::in() || arb::in() || hyperbolic) && !forced_quotient(); }};
__anon29482a1f0f02null260 EX geometry_filter gf_spherical = {"spherical", [] { return (arcm::in() || arb::in() || sphere) && !forced_quotient(); }};
__anon29482a1f1002null261 EX geometry_filter gf_euclidean = {"Euclidean", [] { return (arcm::in() || arb::in() || euclid) && !forced_quotient(); }};
__anon29482a1f1102null262 EX geometry_filter gf_other = {"non-isotropic", [] { return prod || nonisotropic; }};
__anon29482a1f1202null263 EX geometry_filter gf_regular_2d = {"regular 2D tesselations", [] {
264 return standard_tiling() && WDIM == 2 && !forced_quotient();
265 }};
__anon29482a1f1302null266 EX geometry_filter gf_regular_3d = {"regular 3D honeycombs", [] {
267 if(euclid) return geometry == gCubeTiling;
268 return !bt::in() && !kite::in() && WDIM == 3 && !forced_quotient() && !nonisotropic && !prod;
269 }};
__anon29482a1f1402null270 EX geometry_filter gf_quotient = {"interesting quotient spaces", [] {
271 return forced_quotient() && !elliptic;
272 }};
273
274 EX vector<geometry_filter*> available_filters = { &gf_hyperbolic, &gf_spherical, &gf_euclidean, &gf_other, &gf_regular_2d, &gf_regular_3d, &gf_quotient };
275
ge_select_filter()276 void ge_select_filter() {
277 cmode = sm::SIDE | sm::MAYDARK;
278 gamescreen(2);
279
280 dialog::init(XLAT("geometries"));
281
282 char x = 'a';
283 for(auto f: available_filters) {
284 if(current_filter)
285 dialog::addBoolItem(XLAT(f->name), f == current_filter, x++);
286 else
287 dialog::addItem(XLAT(f->name), x++);
288 dialog::add_action([f] { current_filter = f; popScreen(); });
289 }
290
291 dialog::addBack();
292 dialog::display();
293 }
294
set_default_filter()295 void set_default_filter() {
296 current_filter = hyperbolic ? &gf_hyperbolic : euclid ? &gf_euclidean : sphere ? &gf_spherical : &gf_other;
297 for(auto f: available_filters) if(f->test()) current_filter = f;
298 }
299
set_or_configure_geometry(eGeometry g)300 void set_or_configure_geometry(eGeometry g) {
301 if(0) ;
302 #if CAP_CRYSTAL
303 else if(g == gCrystal)
304 pushScreen(crystal::show);
305 #endif
306 #if CAP_ARCM
307 else if(g == gArchimedean)
308 pushScreen(arcm::show);
309 #endif
310 else if(g == gArbitrary)
311 arb::choose();
312 else {
313 if(among(g, gProduct, gRotSpace)) {
314 if(WDIM == 3 || (g == gRotSpace && euclid)) {
315 addMessage(
316 g == gRotSpace ?
317 XLAT("Only works with 2D non-Euclidean geometries")
318 : XLAT("Only works with 2D geometries")
319 );
320 return;
321 }
322 if(g == gRotSpace) {
323 bool ok = true;
324 if(arcm::in()) ok = PURE;
325 else if(bt::in() || kite::in()) ok = false;
326 else ok = PURE || BITRUNCATED;
327 if(!ok) {
328 addMessage(XLAT("Only works with (semi-)regular tilings"));
329 return;
330 }
331 #if CAP_ARCM
332 if(arcm::in()) {
333 int steps, single_step;
334 if(!arcm::current.get_step_values(steps, single_step)) {
335 addMessage(XLAT("That would have %1/%2 levels", its(steps), its(single_step)));
336 return;
337 }
338 }
339 #endif
340 }
341 }
342 dual::may_split_or_do([g] { set_geometry(g); });
343 start_game();
344 }
345 }
346
347 /** is g2 the same tiling as the current geometry (geometry)? */
same_tiling(eGeometry g2)348 bool same_tiling(eGeometry g2) {
349 if(g2 == gCrystal)
350 return S3 == 4;
351 if(g2 == gFieldQuotient && hyperbolic && standard_tiling())
352 return true;
353 if(g2 == gFieldQuotient && geometry != gFieldQuotient) {
354 int ce = 0;
355 for(auto& ge: fieldpattern::fgeomextras) {
356 if(ginf[ge.base].tiling_name == ginf[geometry].tiling_name) {
357 fieldpattern::current_extra = ce;
358 return true;
359 }
360 ce++;
361 }
362 }
363 return ginf[g2].tiling_name == ginf[geometry].tiling_name;
364 }
365
ge_select_tiling()366 void ge_select_tiling() {
367 cmode = sm::SIDE | sm::MAYDARK;
368 gamescreen(0);
369
370 if(!current_filter) { popScreen(); return; }
371 dialog::init();
372 dialog::addItem(XLAT(current_filter->name), 'x');
373 dialog::add_action_push(ge_select_filter);
374
375 vector<eGeometry> geometries;
376
377 dialog::addBreak(100);
378
379 char letter = 'a';
380 for(int i=0; i<isize(ginf); i++) {
381 eGeometry g = eGeometry(i);
382 if(among(g, gProduct, gRotSpace)) hybrid::configure(g);
383 bool orig_el = elliptic;
384 bool on = geometry == g;
385 bool in_2d = WDIM == 2;
386 dynamicval<eGeometry> cg(geometry, g);
387 if(cgflags & qDEPRECATED) continue;
388 if(arcm::in() && !CAP_ARCM) continue;
389 if(cryst && !CAP_CRYSTAL) continue;
390 if(sol && !CAP_SOLV) continue;
391 if(arb::in() && (ISMOBILE || ISWEB)) continue;
392 if(WDIM == 3 && MAXMDIM == 3) continue;
393 if(geometry == gFieldQuotient && !CAP_FIELD) continue;
394 if(geometry == gFake) continue;
395 if(!current_filter->test()) continue;
396 if(orig_el) {
397 for(int j=0; j<isize(ginf); j++)
398 if(ginf[j].tiling_name == ginf[i].tiling_name)
399 geometry = g = eGeometry(j);
400 }
401
402 bool is_product = (geometry == gProduct && in_2d);
403 bool is_rotspace = (geometry == gRotSpace && in_2d);
404 dialog::addBoolItem(
405 is_product ? XLAT("current geometry x E") :
406 is_rotspace ? XLAT("space of rotations in current geometry") :
407 XLAT(ginf[g].menu_displayed_name), on, letter++);
408 dialog::lastItem().value += validclasses[land_validity(specialland).quality_level];
409 dialog::add_action([g] { set_or_configure_geometry(g); });
410 }
411
412 dialog::addBreak(100);
413 dual::add_choice();
414 dialog::addBack();
415 dialog::display();
416 }
417
current_proj_name()418 EX string current_proj_name() {
419 bool h = hyperbolic || sn::in();
420 if(vpconf.model == mdPanini && vpconf.alpha == 1)
421 return XLAT("stereographic Panini");
422 else if(vpconf.model != mdDisk)
423 return models::get_model_name(vpconf.model);
424 else if(h && vpconf.alpha == 1)
425 return XLAT("Poincaré model");
426 else if(h && vpconf.alpha == 0)
427 return XLAT("Klein-Beltrami model");
428 else if(h && vpconf.alpha == -1)
429 return XLAT("inverted Poincaré model");
430 else if(sphere && vpconf.alpha == 1)
431 return XLAT("stereographic projection");
432 else if(sphere && vpconf.alpha == 0)
433 return XLAT("gnomonic projection");
434 else if(sphere && vpconf.alpha >= 999)
435 return XLAT("orthographic projection");
436 else if(h && vpconf.alpha >= 999)
437 return XLAT("Gans model");
438 else
439 return XLAT("general perspective");
440 }
441
dim_name()442 EX string dim_name() {
443 return " (" + its(WDIM) + "D)";
444 }
445
446 #if CAP_THREAD && MAXMDIM >= 4
showQuotientConfig3()447 EX void showQuotientConfig3() {
448
449 using namespace fieldpattern;
450 gamescreen(2);
451 dialog::init(XLAT("field quotient"));
452
453 auto& ds = discoveries[cginf.tiling_name];
454
455 if(!ds.discoverer) {
456 dialog::addItem("start discovery", 's');
457 dialog::add_action([&ds] { ds.activate(); });
458 }
459 else if(ds.is_suspended) {
460 dialog::addItem("resume discovery", 's');
461 dialog::add_action([&ds] { ds.activate(); });
462 }
463 else {
464 dialog::addItem("suspend discovery", 's');
465 dialog::add_action([&ds] { ds.suspend(); });
466 }
467
468 auto& e = ds.experiment;
469 if(!e.Prime)
470 dialog::addBreak(100);
471 else {
472 string s = its(e.Prime);
473 if(e.wsquare) s += "²";
474 dialog::addInfo(s);
475 }
476
477 dialog::addBreak(100);
478
479 if(1) {
480 std::unique_lock<std::mutex> lk(ds.lock);
481 auto&l = ds.hashes_found;
482 for(auto& v: l) {
483 char x = 'a';
484 string s = XLAT("#%1, cells: %2, p=%3", itsh(v.first), its(get<5>(v.second)), its(get<0>(v.second)) + (get<1>(v.second) ? "²" : ""));
485 dialog::addItem(s, x++);
486 dialog::add_action([&v] {
487 stop_game();
488 int tmp;
489 tie(currfp.Prime, currfp.wsquare, currfp.R, currfp.P, currfp.X, tmp) = v.second;
490 currfp.Field = currfp.wsquare ? currfp.Prime * currfp.Prime : currfp.Prime;
491 currfp.generate_all3();
492 currfp.analyze();
493 start_game();
494 });
495 }
496 }
497
498 dialog::addBreak(100);
499
500 dialog::addBack();
501
502 dialog::display();
503 }
504 #endif
505
geometry_name()506 EX string geometry_name() {
507 switch(ginf[geometry].cclass) {
508 case gcHyperbolic:
509 return XLAT("hyperbolic") + dim_name();
510
511 case gcEuclid:
512 if(cgflags & qAFFINE)
513 return XLAT("affine") + dim_name();
514 return XLAT("flat") + dim_name();
515
516 case gcSphere:
517 return XLAT("spherical") + dim_name();
518
519 case gcSolNIH:
520 #if CAP_SOLV
521 switch(sn::geom()) {
522 case gSol:
523 return XLAT("Sol");
524 case gNIH:
525 return XLAT("hyperbolic (3:2)");
526 case gSolN:
527 return XLAT("Sol (3:2)");
528 default:
529 return "unknown";
530 }
531 #else
532 return XLAT("Sol");
533 #endif
534
535 case gcNil:
536 return XLAT("Nil");
537
538 case gcSL2:
539 return XLAT("~SL(2,R)~");
540
541 case gcProduct:
542 return XLAT("%1 x E", PIU(geometry_name()));
543 }
544 return "?";
545 }
546
select_quotient_screen()547 EX void select_quotient_screen() {
548 cmode = sm::SIDE | sm::MAYDARK;
549 gamescreen(0);
550
551 dialog::init(XLAT("quotient spaces in ") + ginf[geometry].tiling_name);
552 char key = 'a';
553 for(int i=0; i<isize(ginf); i++) {
554 auto g = eGeometry(i);
555 if(ginf[g].flags & qDEPRECATED) continue;
556 if(same_tiling(g)) {
557 dialog::addBoolItem(
558 (ginf[g].flags & qANYQ) ?
559 XLAT(ginf[g].menu_displayed_name) :
560 "no quotient",
561 g == geometry, key++);
562 dialog::add_action([g] {
563 if(g == gFieldQuotient && WDIM == 3) {
564 if(geometry != gFieldQuotient) {
565 stop_game();
566 fieldpattern::field_from_current();
567 set_geometry(gFieldQuotient);
568 for(int p=2;; p++) { currfp.Prime = p; currfp.force_hash = 0; if(!currfp.solve()) break; }
569 println(hlog, "set prime = ", currfp.Prime);
570 start_game();
571 }
572 #if CAP_THREAD && MAXMDIM >= 4
573 pushScreen(showQuotientConfig3);
574 #endif
575 }
576 else if(g == gFieldQuotient)
577 pushScreen(showQuotientConfig);
578 #if CAP_CRYSTAL
579 else if(g == gCrystal)
580 pushScreen(crystal::show);
581 #endif
582 else {
583 dual::may_split_or_do([g] { set_geometry(g); });
584 start_game();
585 }
586 });
587 }
588 }
589
590 dialog::addBack();
591 dialog::display();
592 }
593
select_quotient()594 EX void select_quotient() {
595 if(euclid && !kite::in() && !arcm::in()) {
596 euc::prepare_torus3();
597 pushScreen(euc::show_torus3);
598 }
599 else if(nil) {
600 nilv::prepare_niltorus3(),
601 pushScreen(nilv::show_niltorus3);
602 }
603 #if CAP_SOLV
604 else if(asonov::in()) {
605 asonov::prepare_config();
606 pushScreen(asonov::show_config);
607 }
608 #endif
609 else if(prod)
610 pushScreen(product::show_config);
611 else if(rotspace)
612 hybrid::configure_period();
613 else {
614 vector<eGeometry> choices;
615 for(int i=0; i<isize(ginf); i++) if(same_tiling(eGeometry(i))) choices.push_back(eGeometry(i));
616
617 println(hlog, "choices = ", choices);
618
619 if(isize(choices) > 1)
620 pushScreen(select_quotient_screen);
621 /* else if(isize(choices) > 1) {
622 set_geometry(choices[choices[0] == geometry ? 1 : 0]);
623 start_game();
624 } */
625 else
626 addMessage("No quotient spaces available in the current tiling.");
627 }
628 }
629
full_geometry_name()630 EX string full_geometry_name() {
631 string qstring = ginf[geometry].quotient_name;
632 bool variable =
633 !(prod || hybri || bt::in() || (WDIM == 3 && !reg3::in()) || kite::in() || arb::in());
634
635 string fgname = XLAT(ginf[geometry].tiling_name);
636 if(qstring != "none") fgname += " " + XLAT(qstring);
637 if(arcm::in()) fgname = arcm::current.symbol;
638 if(variable) fgname = gp::operation_name() + " " + fgname;
639 return fgname;
640 }
641
action_change_variation()642 void action_change_variation() {
643 if(0) ;
644 #if CAP_ARCM
645 else if(arcm::in()) arcm::next_variation();
646 #endif
647 #if MAXMDIM >= 4
648 else if(reg3::in()) reg3::configure_variation();
649 #endif
650 else if(euc::in(2,4) || !CAP_GP) dialog::do_if_confirmed([] {
651 set_variation(PURE ? eVariation::bitruncated : eVariation::pure);
652 start_game();
653 });
654 #if CAP_GP
655 else // if(S3 == 3)
656 gp::configure();
657 #endif
658 }
659
menuitem_change_variation(char key)660 EX void menuitem_change_variation(char key) {
661 dialog::addSelItem(XLAT("variations"), gp::operation_name(), key);
662 dialog::add_action(action_change_variation);
663 }
664
menuitem_change_geometry(char key)665 EX void menuitem_change_geometry(char key) {
666 dialog::addSelItem(XLAT("geometry/topology/tiling"), full_geometry_name(), key);
667 dialog::add_action_push(current_filter ? ge_select_tiling : ge_select_filter);
668 }
669
menuitem_projection(char key)670 EX void menuitem_projection(char key) {
671 dialog::addSelItem(XLAT("projection"), current_proj_name(), key);
672 dialog::add_action_push(models::model_menu);
673 }
674
menuitem_binary_width(char key)675 EX void menuitem_binary_width(char key) {
676 dialog::addSelItem(XLAT("binary tiling width"), fts(vid.binary_width), key);
677 dialog::add_action([] {
678 dialog::editNumber(vid.binary_width, 0, 2, 0.1, 1, XLAT("binary tiling width"), "");
679 dialog::reaction = [] () {
680 #if CAP_TEXTURE
681 texture::config.remap();
682 #endif
683 #if CAP_SOLV
684 if(asonov::in()) asonov::prepare();
685 #endif
686 };
687 });
688 }
689
menuitem_nilwidth(char key)690 EX void menuitem_nilwidth(char key) {
691 dialog::addSelItem(XLAT("Nil width"), fts(nilv::nilwidth), key);
692 dialog::add_action([] {
693 dialog::editNumber(nilv::nilwidth, 0.01, 2, 0.1, 1, XLAT("Nil width"), "");
694 dialog::reaction = ray::reset_raycaster;
695 dialog::bound_low(0.01);
696 });
697 }
698
edit_stretch()699 EX void edit_stretch() {
700 stretch::mstretch = false;
701 ray::reset_raycaster();
702 dialog::editNumber(stretch::factor, -1, 9, 0.1, 0, XLAT("stretched geometry"),
703 XLAT(
704 "Stretch the metric along the fibers. This can currently be done in rotation spaces and in 8-cell, 24-cell and 120-cell. "
705 "Value of 0 means not stretched, -1 means S2xE or H2xE (works only in the limit). (Must be > -1)"
706 )
707 );
708 dialog::reaction = [] { if(abs(stretch::factor+1) < 1e-3) stretch::factor = -.9; ray::reset_raycaster(); };
709 }
710
showEuclideanMenu()711 EX void showEuclideanMenu() {
712 // for(int i=2; i<lt; i++) landvisited[i] = true;
713
714 cmode = sm::SIDE | sm::MAYDARK;
715 gamescreen(0);
716
717 dialog::init(XLAT("experiment with geometry"));
718
719 dialog::addSelItem(XLAT("geometry"), geometry_name(), 'd');
720 dialog::add_action([] { pushScreen(ge_select_tiling); pushScreen(ge_select_filter); });
721
722 dialog::addSelItem(XLAT("basic tiling"), XLAT(ginf[geometry].tiling_name), 't');
723 dialog::add_action([] {
724 if(!current_filter || !current_filter->test()) set_default_filter();
725 pushScreen(ge_select_tiling);
726 });
727
728 int ts = ginf[geometry].sides;
729 int tv = ginf[geometry].vertex;
730 int nom = (BITRUNCATED ? tv+ts : tv) * 4;
731 int denom = (2*ts + 2*tv - ts * tv);
732
733 #if CAP_GP
734 if(GOLDBERG || INVERSE) {
735 ld area = PIU(cgi.gpdata->area);
736
737 if(GOLDBERG || WARPED) {
738 nom = 2 * (2*tv + (S3-2) * ts * (area-1));
739 }
740 else if(UNRECTIFIED) {
741 if((gp::param.first + gp::param.second) % 2 == 0)
742 nom = ts * 2 * area;
743 else
744 nom = (2*tv + (S3-2) * ts * (area-1));
745 }
746 else if(UNTRUNCATED) {
747 if((gp::param.first - gp::param.second) % 3 == 0) {
748 nom = ts * 4 * area;
749 denom *= 3;
750 }
751 else {
752 nom = 2 * (2*tv + (S3-2) * ts * (area-1));
753 denom *= 3;
754 }
755 }
756 }
757 #endif
758
759 int worldsize;
760
761 int euler = 0;
762 if(euclid) euler = 0;
763 else if(sphere && nonorientable) euler = 1;
764 else if(sphere) euler = 2;
765 else if(!bounded) euler = -2;
766 else if(WDIM == 3) euler = 0;
767 else switch(geometry) {
768 case gFieldQuotient:
769 worldsize = isize(currentmap->allcells());
770 euler = 2 * worldsize * denom / nom;
771 break;
772
773 case gMinimal:
774 euler = -1;
775 break;
776
777 case gZebraQuotient:
778 case gBolza:
779 euler = -2;
780 break;
781
782 case gKleinQuartic:
783 case gSchmutzM2:
784 case gBolza2:
785 euler = -4;
786 break;
787
788 case gSchmutzM3:
789 case gBring:
790 euler = -6;
791 break;
792
793 case gMacbeath:
794 euler = -12;
795 break;
796
797 default:
798 worldsize = isize(currentmap->allcells());
799 println(hlog, "warning: Euler characteristics unknown, worldsize = ", worldsize);
800 euler = 2 * worldsize * denom / nom;
801 break;
802 }
803
804 nom *= euler;
805 denom *= 2;
806
807 if(hybri) nom *= hybrid::csteps, denom *= cgi.single_step;
808
809 int g = gcd(nom, denom);
810 if(g) {
811 nom /= g;
812 denom /= g;
813 }
814
815 if(euclid && bounded) {
816 worldsize = euc::eu.det;
817 if(BITRUNCATED) worldsize *= (a4 ? 2 : 3);
818 if(GOLDBERG) worldsize *= cgi.gpdata->area;
819 #if CAP_IRR
820 if(IRREGULAR) worldsize *= isize(irr::cells) / isize(irr::cells_of_heptagon);
821 #endif
822 }
823 else
824 worldsize = denom ? nom / denom : 0;
825
826 if(euler < 0 && !bounded)
827 worldsize = -worldsize;
828
829 string spf = its(ts);
830 if(0) ;
831 #if CAP_ARCM
832 else if(arcm::in()) {
833 spf = "";
834 for(int i: arcm::current.faces) {
835 if(spf != "") spf += ",";
836 spf += its(i);
837 }
838 if(BITRUNCATED) spf = "[" + spf + "]," + its(arcm::current.N * 2) + "," + its(arcm::current.N * 2);
839 if(DUAL) spf = its(arcm::current.N) + "^[" + spf + "]";
840 }
841 #endif
842 #if CAP_BT
843 else if(bt::in()) switch(geometry) {
844 case gBinaryTiling: spf = "6,[6,7],7"; break;
845 case gBinary4: spf = "5,5,5[,5]"; break;
846 case gTernary: spf = "6,6,6[,6]"; break;
847 default: spf = "?"; break;
848 }
849 #endif
850 else if(BITRUNCATED)
851 spf = spf + "," + its(S6) + "," + its(S6);
852 #if CAP_IRR
853 else if(IRREGULAR && irr::bitruncations_performed)
854 spf = "[4..8],6,6";
855 else if(IRREGULAR)
856 spf = "[4..8]^3";
857 #endif
858 #if CAP_GP
859 else if(UNRECTIFIED || UNTRUNCATED) {
860 if(UNRECTIFIED && (gp::param.first-gp::param.second) % 2 != 0)
861 spf = "(?)";
862 else if(UNTRUNCATED && (gp::param.first-gp::param.second) % 3 != 0)
863 spf = "(?)";
864 else {
865 string each = UNRECTIFIED ? "4" : "3";
866 int first = UNRECTIFIED ? 4 : 6;
867 int second = S7;
868 if(gp::param == gp::loc(1, 1))
869 first = second;
870 else if(second < first)
871 swap(first, second);
872 spf = each;
873 for(int z=1; z<first; z++) spf += "," + each;
874 if(first != second) {
875 spf += "[";
876 for(int z=first; z<second; z++) spf += "," + each;
877 spf += "]";
878 }
879 }
880 }
881 else if(GOLDBERG && S3 == 4 && gp::param == gp::loc(1, 1))
882 spf = spf + ",4," + spf + ",4";
883 else if(GOLDBERG && S3 == 4 && gp::param == gp::loc(2, 0))
884 spf = spf + ",4,4,4";
885 else if(GOLDBERG && S3 == 4)
886 spf = "[" + spf + ",4],4,4,4";
887 else if(WARPED && S3 == 3 && gp::param == gp::loc(1,1))
888 spf = spf + ",3,3";
889 else if(WARPED && S3 == 3)
890 spf = "[" + spf + ",6],3,3";
891 else if(GOLDBERG && S3 == 3)
892 spf = "[" + spf + ",6],6,6";
893 #endif
894 else {
895 string spf0 = spf;
896 for(int z=1; z<S3; z++) spf = spf + "," + spf0;
897 }
898
899 string qstring = ginf[geometry].quotient_name;
900
901 if(qstring == "none")
902 dialog::addBoolItem(XLAT("quotient space"), false, 'q');
903 else
904 dialog::addSelItem(XLAT("quotient space"), XLAT(qstring), 'q');
905
906 dialog::add_action(select_quotient);
907
908 #if CAP_ARCM
909 if(arcm::in()) {
910 dialog::addItem(XLAT("advanced parameters"), '4');
911 dialog::add_action_push(arcm::show);
912 }
913 #endif
914
915 #if CAP_CRYSTAL
916 if(cryst) {
917 dialog::addItem(XLAT("advanced parameters"), '4');
918 dialog::add_action_push(crystal::show);
919 }
920 #endif
921
922 if(fake::available()) {
923 dialog::addItem(XLAT("fake curvature"), '4');
924
925 dialog::add_action([] {
926 if(fake::in()) fake::configure();
927 else dialog::cheat_if_confirmed(
928 fake::configure
929 );
930 });
931 }
932
933 if(arb::in() && !arb::current.sliders.empty()) {
934 dialog::addItem(XLAT("tessellation sliders"), '4');
935 dialog::add_action_push(arb::set_sliders);
936 }
937
938 #if CAP_IRR
939 if(hyperbolic && IRREGULAR) {
940 nom = isize(irr::cells);
941 // both Klein Quartic and Bolza2 are double the Zebra quotiennt
942 denom = -2;
943 if(!quotient) worldsize = nom / denom;
944 }
945 #endif
946
947 #if MAXMDIM >= 4
948 if(cgflags & qULTRA) {
949 dialog::addBoolItem(XLAT("truncate ultra-vertices with mirrors"), reg3::ultra_mirror_on, 'Z');
950 dialog::add_action([] {
951 reg3::ultra_mirror_on = !reg3::ultra_mirror_on;
952 ray::reset_raycaster();
953 });
954 }
955 #endif
956
957 if(prod) {
958 dialog::addSelItem(XLAT("Z-level height factor"), fts(vid.plevel_factor), 'Z');
959 dialog::add_action([] {
960 dialog::editNumber(vid.plevel_factor, 0, 2, 0.1, 0.7, XLAT("Z-level height factor"), "");
961 });
962 }
963 else if(hybri) {
964 dialog::addSelItem(XLAT("number of levels"), its(hybrid::csteps / cgi.single_step), 'L');
965 dialog::add_action(hybrid::configure_period);
966 }
967 else if(bt::in()) {
968 menuitem_binary_width('v');
969 add_edit_wall_quality('W');
970 }
971 else if(nil) {
972 menuitem_nilwidth('v');
973 }
974 else if((WDIM == 3 || kite::in() || arb::in()) && !reg3::in()) dialog::addBreak(100);
975 else
976 menuitem_change_variation('v');
977
978 if(in_s2xe()) {
979 dialog::addSelItem(XLAT("precision of S2xE rings"), its(s2xe::qrings), '5');
980 dialog::add_action([] {
981 dialog::editNumber(s2xe::qrings, 1, 256, 4, 32, XLAT("precision of S2xE rings"),
982 XLAT(
983 "In S2xE, objects at spherical distances which are multiples of π will look like "
984 "rings, and objects close to these will look like crescents. "
985 "This setting controls the quality of rendering these rings and crescents.")
986 );
987 dialog::bound_low(1);
988 dialog::bound_up(256);
989 });
990 }
991
992 #if MAXMDIM >= 4
993 if(hybri) {
994 auto r = rots::underlying_scale;
995 dialog::addSelItem(XLAT("view the underlying geometry"), r > 0 ? fts(r)+"x" : ONOFF(false), '6');
996 dialog::add_action([] {
997 dialog::editNumber(rots::underlying_scale, 0, 1, 0.05, 0.25, XLAT("view the underlying geometry"),
998 geometry == gRotSpace ?
999 XLAT("The space you are currently in is the space of rotations of the underlying hyperbolic or spherical geometry. ")
1000 : XLAT("You are currently in a product space.") +
1001 XLAT(
1002 "This option lets you see the underlying space. Lands and some walls (e.g. in the Graveyard) are based on "
1003 "the respective features in the underlying world, but details such as monsters or items are ignored."
1004 )
1005 );
1006 dialog::bound_low(0);
1007 dialog::bound_up(1);
1008 dialog::extra_options = [] () { rots::draw_underlying(true); };
1009 });
1010 }
1011 #endif
1012
1013 if(stretch::applicable()) {
1014 dialog::addSelItem(XLAT("stretched geometry"), fts(stretch::factor), 'S');
1015 dialog::add_action(edit_stretch);
1016 }
1017
1018 dialog::addBreak(100);
1019 menuitem_land_structure('l');
1020
1021 if(specialland == laMinefield && bounded) {
1022 dialog::addSelItem(XLAT("number of mines"), its(bounded_mine_quantity), 'm');
1023 dialog::add_action([] {
1024 dialog::editNumber(bounded_mine_quantity, 0, bounded_mine_max, 1, (bounded_mine_max+5)/10,
1025 XLAT("number of mines"), "");
1026 dialog::reaction = [] {
1027 if(bounded_mine_quantity < 0) bounded_mine_quantity = 0;
1028 if(bounded_mine_quantity > bounded_mine_max) bounded_mine_quantity = bounded_mine_max;
1029 };
1030 dialog::reaction_final = [] {
1031 bounded_mine_percentage = bounded_mine_quantity * 1. / bounded_mine_max;
1032 stop_game();
1033 start_game();
1034 };
1035 });
1036 }
1037
1038 if(geometry_has_alt_mine_rule()) {
1039 dialog::addSelItem(XLAT("adjacency rule"), mine_adjacency_rule ? XLAT("vertex") : WDIM == 3 ? XLAT("face") : XLAT("edge"), 'M');
1040 dialog::add_action([] {
1041 stop_game();
1042 mine_adjacency_rule = !mine_adjacency_rule;
1043 start_game();
1044 addMessage(XLAT("Note: adjacency rule affects environmental effects, but not movement."));
1045 });
1046 }
1047
1048 dialog::addBoolItem(XLAT("pattern"), specialland == laCanvas, 'p');
1049 if(specialland == laCanvas) dialog::lastItem().value = patterns::whichCanvas;
1050 dialog::add_action_push(patterns::showPrePattern);
1051 validity_info();
1052 if(WDIM == 3) {
1053 dialog::addItem(XLAT("3D configuration"), '9');
1054 dialog::add_action_push(show3D);
1055 }
1056 menuitem_projection('1');
1057 if(nonisotropic && !sl2)
1058 dialog::addBoolItem_action(XLAT("geodesic movement in Sol/Nil"), nisot::geodesic_movement, 'G');
1059 #if CAP_CRYSTAL && MAXMDIM >= 4
1060 crystal::add_crystal_transform('x');
1061 #endif
1062
1063 dialog::addBreak(50);
1064
1065 #if CAP_SHOT
1066 dialog::addItem(XLAT("take screenshot"), 's');
1067 dialog::add_action_push(shot::menu);
1068 #endif
1069
1070 dialog::addHelp();
1071 dialog::addBack();
1072
1073 dialog::addBreak(150);
1074
1075 dialog::addTitle(XLAT("info about: %1", full_geometry_name()), 0xFFFFFF, 150);
1076
1077 if(WDIM == 2 && !arb::in() && !kite::in()) dialog::addSelItem(XLAT("faces per vertex"), spf, 0);
1078
1079 if(arb::in() && arb::current.comment != "") {
1080 dialog::addBreak(100);
1081 dialog::addHelp(arb::current.comment);
1082 }
1083
1084 dialog::addSelItem(XLAT("size of the world"),
1085 #if CAP_BT
1086 bt::in() ? fts(8 * M_PI * sqrt(2) * log(2) / pow(vid.binary_width, WDIM-1), 4) + " exp(∞)" :
1087 #endif
1088 #if CAP_ARCM
1089 arcm::in() && (WDIM == 2) ? arcm::current.world_size() :
1090 (arcm::in() && sphere) ? its(isize(currentmap->allcells())) :
1091 #endif
1092 #if CAP_CRYSTAL
1093 cryst ? "∞^" + its(ts/2) :
1094 #endif
1095 WDIM == 3 && bounded ? its(isize(currentmap->allcells())) :
1096 WDIM == 3 && euclid ? "∞" :
1097 worldsize < 0 ? (nom%denom ? its(nom)+"/"+its(denom) : its(-worldsize)) + " exp(∞)":
1098 (euclid && quotient && !bounded) ? "∞" :
1099 worldsize == 0 ? "∞²" :
1100 its(worldsize),
1101 '3');
1102
1103 if(WDIM == 2 || reg3::in_rule()) dialog::add_action([] {
1104 if(!viewdists) { enable_viewdists(); pushScreen(viewdist_configure_dialog); }
1105 else if(viewdists) viewdists = false;
1106 });
1107
1108 if(bounded) {
1109 if(WDIM == 3) euler = 0;
1110 dialog::addSelItem(XLAT("Euler characteristics"), its(euler), 0);
1111 if(WDIM == 3) ;
1112 else if(nonorientable)
1113 dialog::addSelItem(XLAT("demigenus"), its(2-euler), 0);
1114 else
1115 dialog::addSelItem(XLAT("genus"), its((2-euler)/2), 0);
1116 }
1117 else dialog::addBreak(200);
1118
1119 dialog::display();
1120 }
1121
runGeometryExperiments()1122 EX void runGeometryExperiments() {
1123 if(!geometry && specialland == laIce)
1124 specialland = getLandForList(cwt.at);
1125 pushScreen(showEuclideanMenu);
1126 }
1127
1128 #if CAP_COMMANDLINE
1129
readGeo(const string & ss)1130 EX eGeometry readGeo(const string& ss) {
1131 for(int i=0; i<isize(ginf); i++) if(ginf[i].shortname == ss) return eGeometry(i);
1132 bool numeric = true;
1133 for(char c: ss) if(c < '0' || c > '9') numeric = false;
1134 if(numeric) return eGeometry(atoi(ss.c_str()));
1135 for(int i=0; i<isize(ginf); i++) if(appears(ginf[i].menu_displayed_name, ss)) {
1136 return eGeometry(i);
1137 break;
1138 }
1139 return gNormal;
1140 }
1141
field_quotient_3d(int p,unsigned hash)1142 EX void field_quotient_3d(int p, unsigned hash) {
1143 check_cgi();
1144 cgi.require_basics();
1145 stop_game_and_switch_mode(rg::nothing);
1146 fieldpattern::field_from_current();
1147 set_geometry(gFieldQuotient);
1148 for(;; p++) {
1149 println(hlog, "trying p = ", p);
1150 currfp.Prime = p; currfp.force_hash = hash; if(!currfp.solve()) break;
1151 }
1152 println(hlog, "set prime = ", currfp.Prime);
1153 }
1154
field_quotient_2d(int group,int id,int triplet)1155 EX void field_quotient_2d(int group, int id, int triplet) {
1156 using namespace fieldpattern;
1157 current_extra = group;
1158
1159 auto& gxcur = fgeomextras[current_extra];
1160 while(id >= isize(gxcur.primes)) nextPrime(gxcur);
1161
1162 fgeomextras[current_extra].current_prime_id = id;
1163 if(triplet != -1)
1164 triplet_id = triplet;
1165 enableFieldChange();
1166 set_geometry(gFieldQuotient);
1167 }
1168
read_geom_args()1169 int read_geom_args() {
1170 using namespace arg;
1171 if(0) ;
1172 #if CAP_FIELD
1173 else if(argis("-qpar")) {
1174 int p;
1175 shift(); sscanf(argcs(), "%d,%d,%d",
1176 &p, "ientspace::rvadd, "ientspace::rvdir
1177 );
1178 autocheat = true;
1179 currfp.init(p);
1180 }
1181 else if(argis("-qpar2")) {
1182 stop_game_and_switch_mode(rg::nothing);
1183 int a, b;
1184 shift(); sscanf(argcs(), "%d,%d", &a, &b);
1185 field_quotient_2d(a, b, -1);
1186 }
1187 else if(argis("-triplet")) {
1188 stop_game();
1189 shift(); fieldpattern::triplet_id = argi();
1190 fieldpattern::enableFieldChange();
1191 }
1192 else if(argis("-to-fq")) {
1193 int p = 2;
1194 shift();
1195 if(args() == "p") {
1196 shift(); p = argi();
1197 shift();
1198 }
1199 unsigned hash = arghex();
1200 field_quotient_3d(p, hash);
1201 }
1202 else if(argis("-cs")) {
1203 shift(); cheat();
1204 fieldpattern::matrix M = currfp.strtomatrix(args());
1205 fieldpattern::subpathid = currfp.matcode[M];
1206 fieldpattern::subpathorder = currfp.order(M);
1207 }
1208 else if(argis("-fwrite")) {
1209 shstream hs;
1210 hwrite_fpattern(hs, currfp);
1211 println(hlog, "current fieldpattern hash: ", currfp.hashv);
1212 println(hlog, "current fieldpattern: ", as_cstring(hs.s));
1213 }
1214 else if(argis("-csp")) {
1215 cheat();
1216 currfp.findsubpath();
1217 }
1218 #endif
1219 else if(argis("-mineadj")) {
1220 shift(); mine_adjacency_rule = argi();
1221 }
1222 TOGGLE('7', PURE, set_variation(PURE ? eVariation::bitruncated : eVariation::pure))
1223 else if(argis("-geo")) {
1224 PHASEFROM(2);
1225 shift();
1226 set_geometry(readGeo(args()));
1227 }
1228 #if CAP_GP
1229 else if(argis("-gp")) {
1230 PHASEFROM(2);
1231 shift(); gp::param.first = argi();
1232 shift(); gp::param.second = argi();
1233 set_variation(eVariation::goldberg);
1234 }
1235 else if(argis("-unrectified")) {
1236 PHASEFROM(2);
1237 set_variation(eVariation::unrectified);
1238 }
1239 else if(argis("-untruncated")) {
1240 PHASEFROM(2);
1241 set_variation(eVariation::untruncated);
1242 }
1243 else if(argis("-warped")) {
1244 PHASEFROM(2);
1245 set_variation(eVariation::warped);
1246 }
1247 else if(argis("-subcubes")) {
1248 PHASEFROM(2);
1249 stop_game();
1250 set_variation(eVariation::subcubes);
1251 shift(); reg3::subcube_count = argi();
1252 }
1253 else if(argis("-dual-subcubes")) {
1254 PHASEFROM(2);
1255 stop_game();
1256 set_variation(eVariation::dual_subcubes);
1257 shift(); reg3::subcube_count = argi();
1258 }
1259 else if(argis("-bch-subcubes")) {
1260 PHASEFROM(2);
1261 stop_game();
1262 set_variation(eVariation::bch);
1263 shift(); reg3::subcube_count = argi();
1264 }
1265 else if(argis("-bch-oct")) {
1266 PHASEFROM(2);
1267 stop_game();
1268 set_variation(eVariation::bch_oct);
1269 shift(); reg3::subcube_count = argi();
1270 }
1271 else if(argis("-coxeter")) {
1272 PHASEFROM(2);
1273 stop_game();
1274 set_variation(eVariation::coxeter);
1275 shift(); reg3::coxeter_param = argi();
1276 }
1277 #endif
1278 #if CAP_FIELD
1279 else if(argis("-fi")) {
1280 fieldpattern::info();
1281 exit(0);
1282 }
1283 else if(argis("-fi-at")) {
1284 geometry = gNormal;
1285 shift(); dynamicval<int> s7(S7, argi());
1286 shift(); dynamicval<int> s3(S3, argi());
1287 fieldpattern::info();
1288 exit(0);
1289 }
1290 else if(argis("-fi-geo")) {
1291 fieldpattern::info();
1292 exit(0);
1293 }
1294 else if(argis("-qs")) {
1295 cheat();
1296 shift(); currfp.qpaths.push_back(args());
1297 }
1298 #if MAXMDIM >= 4
1299 else if(argis("-truncate-ultra")) {
1300 shift(); reg3::ultra_mirror_on = argi();
1301 }
1302 #endif
1303 else if(argis("-d:quotient"))
1304 launch_dialog(showQuotientConfig);
1305 else if(argis("-uqf"))
1306 fieldpattern::use_quotient_fp = true;
1307 #endif
1308 else if(argis("-d:geom"))
1309 launch_dialog(showEuclideanMenu);
1310 else return 1;
1311 return 0;
1312 }
1313
1314 auto ah_geom = addHook(hooks_args, 0, read_geom_args);
1315 #endif
1316
1317 }
1318