// Hyperbolic Rogue -- the 'experiments with geometry' menu // Copyright (C) 2011-2018 Zeno Rogue, see 'hyper.cpp' for details // -- geometry menu -- namespace hr { int eupage = 0; int euperpage = 21; string euchelp = "If you want to know how much the gameplay is affected by the " "hyperbolic geometry in HyperRogue, this mode is for you!\n\n" "You can try many different geometries here. We start by gluing " "n-gons in such a way that k of them meet in every vertex. " "Depending on n and k, this either folds into a sphere, unfolds into a plane, " "or requires a hyperbolic space. The result may be then 'bitrunc' by " "replacing each vertex by a 2k-gon. Furthermore, you can play " "with quotient geometries. For example, the elliptic geometry is " "obtained from the sphere by making the antipodes be the same point, " "so you return to the same spot (but as a mirror image) after going there. " "Have fun experimenting! " "Achievements and leaderboards do not work in geometry experiments, " "except some specific ones.\n\n" "In standard geometry (bitrunc or not), you can play the full game, but in other geometries " "you select a particular land. Lands are unlocked by visiting them in this " "session, or permanently by collecting 25 treasure. Try Crossroads in Euclidean " "or chaos mode in non-standard non-quotient hyperbolic to visit many lands. " "Highlights:\n" "* Crystal World and Warped Coast can be understood as extra geometries.\n" "* Halloween is specially designed for spherical geometry.\n" "* To see the difference, try Hunting Grounds in Euclidean -- it is impossible.\n"; int ewhichscreen = 2; void showQuotientConfig() { using namespace fieldpattern; gamescreen(2); dialog::init(XLAT("advanced configuration")); fgeomextra& gxcur = fgeomextras[current_extra]; for(int i=0; i= 'a' && uni < 'a' + isize(fgeomextras)) current_extra = uni - 'a'; else if(uni >= 'A' && uni < 'A' + isize(gxcur.primes)) gxcur.current_prime_id = uni - 'A'; else if(uni == 'p') nextPrime(gxcur); else if(uni == 'x' || uni == '\n') { targetgeometry = gxcur.base; stop_game_and_switch_mode(rg::geometry); enableFieldChange(); targetgeometry = gFieldQuotient; stop_game_and_switch_mode(rg::geometry); start_game(); } else if(uni == 'c') { targetgeometry = gEuclid; stop_game_and_switch_mode(rg::geometry); fieldpattern::quotient_field_changed = false; targetgeometry = gFieldQuotient; stop_game_and_switch_mode(rg::geometry); start_game(); } else if(doexiton(sym, uni)) popScreen(); }; dialog::display(); } bool torus_bitrunc; void showTorusConfig() { cmode = sm::SIDE | sm::TORUSCONFIG; gamescreen(2); dialog::init(XLAT("advanced configuration")); auto& mode = torusconfig::tmodes[torusconfig::newmode]; dialog::addSelItem(XLAT("mode"), XLAT(mode.name), 'm'); bool single = (mode.flags & torusconfig::TF_SINGLE); bool square = (mode.flags & torusconfig::TF_SQUARE); bool simple = (mode.flags & torusconfig::TF_SIMPLE); if(single) { dialog::addSelItem(XLAT("number of cells (n)"), its(torusconfig::newqty), 'n'); if(mode.flags & torusconfig::TF_HEX) dialog::addSelItem(XLAT("cell bottom-right from 0 (d)"), its(torusconfig::newdy), 'd'); else dialog::addSelItem(XLAT("cell below 0 (d)"), its(torusconfig::newdy), 'd'); } else { if(torusconfig::newsdx < 1) torusconfig::newsdx = 1; if(torusconfig::newsdy < 1) torusconfig::newsdy = 1; dialog::addSelItem(XLAT("width (x)"), its(torusconfig::newsdx), 'x'); dialog::addSelItem(XLAT("height (y)"), its(torusconfig::newsdy), 'y'); } if(square) dialog::addBoolItem(XLAT("bitruncated"), !torus_bitrunc, 't'); else dialog::addInfo("", 100); int valid = 2; if(single) { if(square) { dialog::addInfo("this mode has bad patterns", 0x808080), valid = 1; if(!torus_bitrunc && valid == 1) dialog::addInfo("incompatible with bitruncating", 0x808080), valid = 0; } else { if(torusconfig::newqty % 3) dialog::addInfo(XLAT("best if %1 is divisible by %2", "n", "3"), 0x808080), valid = 1; if((torusconfig::newdy + 999999) % 3 != 2) dialog::addInfo(XLAT("best if %1 is divisible by %2", "d+1", "3"), 0x808080), valid = 1; } } else { if(square) { if(torusconfig::newsdx & 1) dialog::addInfo(XLAT("best if %1 is divisible by %2", "x", "2"), 0x808080), valid = 1; if(torusconfig::newsdy & 1) dialog::addInfo(XLAT("best if %1 is divisible by %2", "y", "2"), 0x808080), valid = 1; if(!torus_bitrunc && valid == 1) dialog::addInfo("incompatible with bitruncating", 0x808080), valid = 0; } else if(simple) { if(torusconfig::newsdx & 1) dialog::addInfo(XLAT("best if %1 is divisible by %2", "x", "3"), 0x808080), valid = 1; if(torusconfig::newsdy & 1) dialog::addInfo(XLAT("best if %1 is divisible by %2", "y", "3"), 0x808080), valid = 1; } else { if(torusconfig::newsdx & 1) dialog::addInfo(XLAT("best if %1 is divisible by %2", "x", "3"), 0x808080), valid = 1; if(torusconfig::newsdy & 1) dialog::addInfo(XLAT("best if %1 is divisible by %2", "y", "2"), 0x808080), valid = 0; } } dialog::addSelItem(XLAT("scale factor"), fts(vid.scale), 'z'); #if CAP_RUG dialog::addBoolItem(XLAT("hypersian rug mode"), (rug::rugged), 'u'); #endif dialog::addItem("activate", 'a'); dialog::addItem("default", 'c'); keyhandler = [=] (int sym, int uni) { if(uni == 'm') { int i = torusconfig::newmode + 1; if(i >= isize(torusconfig::tmodes)) i = 0; torusconfig::newmode = torusconfig::eTorusMode(i); } else if(uni == 'n' && single) dialog::editNumber(torusconfig::newqty, 0, 1000, 3, torusconfig::def_qty, XLAT("number of cells (n)"), ""); else if(uni == 'd' && single) dialog::editNumber(torusconfig::newdy, -1000, 1000, 3, -torusconfig::def_dy, XLAT("cell below 0 (d)"), ""); else if(uni == 'x' && !single) dialog::editNumber(torusconfig::newsdx, 0, 1000, square ? 2 : 3, 12, XLAT("width (x)"), ""); else if(uni == 'y' && !single) dialog::editNumber(torusconfig::newsdy, 0, 1000, square ? 2 : simple ? 3 : 2, 12, XLAT("height (y)"), ""); else if(uni == 't') torus_bitrunc = !torus_bitrunc; else if((uni == 'a' || uni == '\n') && torusconfig::newqty >= 3 && valid) { targetgeometry = gNormal; stop_game_and_switch_mode(rg::geometry); torusconfig::torus_mode = torusconfig::newmode; torusconfig::qty = torusconfig::newqty; torusconfig::dy = torusconfig::newdy; torusconfig::sdx = torusconfig::newsdx; torusconfig::sdy = torusconfig::newsdy; torusconfig::activate(); if((square && torus_bitrunc) != nonbitrunc) stop_game_and_switch_mode(rg::bitrunc); targetgeometry = gTorus; stop_game_and_switch_mode(rg::geometry); start_game(); } else if(uni == 'c') { targetgeometry = gEuclid; stop_game_and_switch_mode(rg::geometry); torusconfig::torus_mode = torusconfig::tmSingle; torusconfig::qty = torusconfig::def_qty; torusconfig::dy = torusconfig::def_dy; targetgeometry = gTorus; stop_game_and_switch_mode(rg::geometry); start_game(); } else if(uni == 'z') editScale(); #if CAP_RUG else if(uni == 'u') rug::select(); #endif else if(doexiton(sym, uni)) popScreen(); }; dialog::display(); } string bitruncnames[2] = {" (b)", " (n)"}; void validity_info() { int vccolors[4] = {0xFF0000, 0xFF8000, 0xFFFF00, 0x00FF00}; auto lv = land_validity(specialland); if(lv.flags & lv::display_error_message) dialog::addInfo(XLAT(lv.msg), vccolors[lv.quality_level]); else dialog::addBreak(100); } bool showquotients; void showEuclideanMenu() { cmode = sm::SIDE; gamescreen(0); if(cheater) for(int i=0; i= 25) landvisited[i] = true; landvisited[laCrossroads] = true; landvisited[laCrossroads4] = true; landvisited[laIce] = true; landvisited[laHunting] = true; landvisited[laMirrorOld] = true; landvisited[laPrincessQuest] = cheater || princess::everSaved; landvisited[laWildWest] = true; landvisited[laHalloween] = true; landvisited[laWarpCoast] = true; landvisited[laGraveyard] = true; landvisited[laDual] = true; landvisited[laDocks] |= landvisited[laWarpCoast]; landvisited[laSnakeNest] |= landvisited[laRedRock]; landvisited[laCA] = true; // for(int i=2; i cg(geometry, eGeometry(i)); if(!!(quotient || elliptic || torus) != showquotients) continue; dialog::addBoolItem(XLAT(ginf[i].name), on, 'a'+i); dialog::lastItem().value += validclasses[land_validity(specialland).quality_level]; } dialog::addBreak(50); dialog::addBoolItem(XLAT("show quotient spaces"), showquotients, 'z'); dialog::addBreak(50); if(ts == 6 && tv == 3) dialog::addSelItem(XLAT("variations"), XLAT("does not matter"), 'v'); else if(binarytiling) dialog::addSelItem(XLAT("width"), fts(vid.binary_width), 'v'); else { dialog::addBoolItem(XLAT("variations"), nonbitrunc, 'v'); dialog::lastItem().value = gp::operation_name(); } dialog::addBreak(25); validity_info(); dialog::addBreak(25); int worldsize; int gar = gp::on ? gp::area - 1 : nonbitrunc ? 0 : 2; switch(geometry) { case gTorus: worldsize = torusconfig::qty; break; case gZebraQuotient: worldsize = 12 + 14 * gar; break; case gFieldQuotient: worldsize = isize(currfp.matrices) / ts; worldsize = worldsize * (2*tv + ts * gar) / tv / 2; break; case gKleinQuartic: worldsize = 24 + 28 * gar; break; case gBolza: worldsize = 3 * (2*tv + ts * gar) / tv; break; case gBolza2: worldsize = 6 * (2*tv + ts * gar) / tv; break; default: worldsize = denom ? nom/denom : 0; break; } dialog::addSelItem(XLAT("sides per face"), archimedean ? "?" : its(ts), 0); dialog::addSelItem(XLAT("faces per vertex"), archimedean ? "?" : its(tv), 0); string qstring = "none"; if(tq & qZEBRA) qstring = "zebra"; else if(tq & qFIELD) qstring = "field"; else if((tq & qNONORIENTABLE) && sphere) qstring = "elliptic"; else if(tq & qTORUS) qstring = "torus"; else if(tq & qSMALL) qstring = ginf[geometry].shortname; dialog::addSelItem(XLAT("quotient space"), XLAT(qstring), 0); dialog::addSelItem(XLAT("size of the world"), (archimedean && euclid) ? "∞" : (archimedean && sphere) ? its(isize(currentmap->allcells())) : (archimedean && hyperbolic) ? "exp(∞)*?" : worldsize < 0 ? "exp(∞)*" + (nom%denom ? its(nom)+"/"+its(-denom) : its(-worldsize)): worldsize == 0 ? "∞" : its(worldsize), '3'); switch(ginf[geometry].cclass) { case 0: dialog::addSelItem(XLAT("Curvature"), XLAT("hyperbolic"), 0); break; case 1: dialog::addSelItem(XLAT("Curvature"), XLAT("flat"), 0); break; case 2: dialog::addSelItem(XLAT("Curvature"), XLAT("spherical"), 0); break; } if(sphere) dialog::addBoolItem(XLAT("stereographic/orthogonal"), vid.alpha>10, '1'); else dialog::addBoolItem(XLAT("Poincaré/Klein"), vid.alpha>.5, '1'); if(torus || geometry == gFieldQuotient) dialog::addItem(XLAT("advanced parameters"), '4'); dialog::addHelp(); dialog::addBack(); dialog::display(); keyhandler = [] (int sym, int uni) { dialog::handleNavigation(sym, uni); if(uni >= 'a' && uni < 'a'+gGUARD) { targetgeometry = eGeometry(uni - 'a'); if(targetgeometry == gArchimedean) pushScreen(arcm::show); else { stop_game_and_switch_mode(geometry == targetgeometry ? rg::nothing : rg::geometry); start_game(); } } else if(uni == 'z') showquotients = !showquotients; else if(uni == 'v') { if(euclid6) ; else if(binarytiling) { dialog::editNumber(vid.binary_width, 0, 2, 0.1, 1, XLAT("binary tiling width"), ""); dialog::reaction = [] () { resetGeometry(); texture::config.remap(); }; } else // if(S3 == 3) gp::configure(); /* else { stop_game_and_switch_mode(rg::bitrunc); start_game(); } */ } else if(uni == '2' || sym == SDLK_F1) gotoHelp(euchelp); else if(uni == '3') { viewdists = !viewdists; if(viewdists) popScreenAll(); } else if(uni == '1' && !euclid) { if(sphere) { if(vid.alpha < 10) { vid.alpha = 999; vid.scale = 998; } else {vid.alpha = 1; vid.scale = .4; } } else { if(vid.alpha > .5) { vid.alpha = 0; vid.scale = 1; } else {vid.alpha = 1; vid.scale = 1; } } } else if(uni == '5') ewhichscreen ^= 3; else if(uni == '4') { if(torus) torusconfig::newdy = torusconfig::dy, torusconfig::newqty = torusconfig::qty, torusconfig::newsdx = torusconfig::sdx, torusconfig::newsdy = torusconfig::sdy, torusconfig::newmode = torusconfig::torus_mode, torus_bitrunc = nonbitrunc, pushScreen(showTorusConfig); else if(geometry == gFieldQuotient) pushScreen(showQuotientConfig); } else if(doexiton(sym, uni)) popScreen(); }; } else { dialog::init(XLAT("experiment with geometry")); dialog::addSelItem(XLAT("geometry"), XLAT(ginf[geometry].name) + XLAT(bitruncnames[nonbitrunc]), '5'); dialog::addBreak(50); generateLandList([] (eLand l) { return land_validity(l).flags & lv::appears_in_geom_exp; }); stable_sort(landlist.begin(), landlist.end(), [] (eLand l1, eLand l2) { return land_validity(l1).quality_level > land_validity(l2).quality_level; }); for(int i=0; i= isize(landlist)) { dialog::addBreak(100); break; } eLand l = landlist[euperpage * eupage + i]; char ch; if(i < 26) ch = 'a' + i; else ch = 'A' + (i-26); string s = XLAT1(linf[l].name); if(landvisited[l]) { dialog::addBoolItem(s, l == specialland, ch); } else { dialog::addSelItem(s, XLAT("(locked)"), ch); } dialog::lastItem().color = linf[l].color; dialog::lastItem().value += validclasses[land_validity(l).quality_level]; } dialog::addBreak(50); if(chaosUnlocked && !quotient && !euclid && !sphere) dialog::addItem(XLAT("Chaos mode"), '1'); dialog::addItem(XLAT("next page"), '-'); dialog::addBreak(25); validity_info(); dialog::addBreak(25); dialog::addHelp(); dialog::addBack(); dialog::display(); keyhandler = [] (int sym, int uni) { dialog::handleNavigation(sym, uni); int lid; if(uni >= 'a' && uni <= 'z') lid = uni - 'a'; else if(uni >= 'A' && uni <= 'Z') lid = 26 + uni - 'A'; else lid = -1; if(lid >= 0) lid += euperpage * eupage; if(uni == '5') ewhichscreen ^= 3; else if(uni == '-' || uni == PSEUDOKEY_WHEELUP || uni == PSEUDOKEY_WHEELDOWN) { eupage++; if(eupage * euperpage >= isize(landlist)) eupage = 0; } else if(uni == '1') { if(chaosUnlocked) { stop_game_and_switch_mode(rg::chaos); start_game(); } } else if(lid >= 0 && lid < isize(landlist)) { eLand nland = landlist[lid]; if(landvisited[nland]) { stop_game_and_switch_mode(tactic::on ? rg::tactic : rg::nothing); firstland = specialland = nland; start_game(); } } else if(uni == '2' || sym == SDLK_F1) gotoHelp(euchelp); else if(doexiton(sym, uni)) popScreen(); }; } } void runGeometryExperiments() { if(!geometry) specialland = getLandForList(cwt.at); pushScreen(showEuclideanMenu); } #if CAP_COMMANDLINE int read_geom_args() { using namespace arg; if(argis("-qpar")) { int p; shift(); sscanf(argcs(), "%d,%d,%d", &p, "ientspace::rvadd, "ientspace::rvdir ); autocheat = true; currfp.init(p); } else if(argis("-qpar2")) { stop_game_and_switch_mode(rg::nothing); int a, b; shift(); sscanf(argcs(), "%d,%d", &a, &b); using namespace fieldpattern; current_extra = a; auto& gxcur = fgeomextras[current_extra]; while(b >= isize(gxcur.primes)) nextPrime(gxcur); fgeomextras[current_extra].current_prime_id = b; enableFieldChange(); if(geometry != gFieldQuotient) { targetgeometry = gFieldQuotient; stop_game_and_switch_mode(rg::geometry); } } else if(argis("-cs")) { shift(); cheat(); fieldpattern::matrix M = currfp.strtomatrix(args()); fieldpattern::subpathid = currfp.matcode[M]; fieldpattern::subpathorder = currfp.order(M); } else if(argis("-csp")) { cheat(); currfp.findsubpath(); } else if(argis("-tpar")) { torusconfig::torus_mode = torusconfig::tmSingle; shift(); sscanf(argcs(), "%d,%d,%d", &torusconfig::qty, &torusconfig::dx, &torusconfig::dy ); } else if(argis("-tparx")) { shift(); sscanf(argcs(), "%d,%d,%d", (int*) &torusconfig::torus_mode, &torusconfig::sdx, &torusconfig::sdy ); if(torusconfig::torus_mode == torusconfig::tmSingle) torusconfig::qty = torusconfig::sdx, torusconfig::dy = torusconfig::sdy; torusconfig::activate(); } TOGGLE('7', nonbitrunc, stop_game_and_switch_mode(rg::bitrunc)) else if(argis("-geo")) { PHASEFROM(2); shift(); targetgeometry = (eGeometry) argi(); if(targetgeometry != geometry) stop_game_and_switch_mode(rg::geometry); } else if(argis("-gp")) { PHASEFROM(2); stop_game_and_switch_mode(rg::nothing); shift(); gp::param.first = argi(); shift(); gp::param.second = argi(); stop_game_and_switch_mode(rg::gp); } else if(argis("-fi")) { fieldpattern::info(); exit(0); } else if(argis("-qs")) { cheat(); shift(); currfp.qpaths.push_back(args()); } else return 1; return 0; } auto ah_geom = addHook(hooks_args, 0, read_geom_args); #endif }