diff --git a/cell.cpp b/cell.cpp index 502c899b..929d86be 100644 --- a/cell.cpp +++ b/cell.cpp @@ -221,7 +221,12 @@ namespace torusconfig { // values as the default -- otherwise the three-color // pattern breaks. Also, they should have no common // prime divisor. - int qty = 127*3, dx = 1, dy = -11*2; + int def_qty = 127*3, dx = 1, def_dy = -11*2; + int qty = def_qty, dy = def_dy; + + // new values to change + int newqty, newdy; + int torus_cx, torus_cy; } int decodeId(heptagon* h); diff --git a/compileunits.h b/compileunits.h index fcb625b5..993a01f7 100644 --- a/compileunits.h +++ b/compileunits.h @@ -59,6 +59,7 @@ bool inv::on; #include "config.cpp" #include "scores.cpp" #include "menus.cpp" +#include "geom-exp.cpp" #include "quit.cpp" #include "shmup.cpp" #if CAP_ROGUEVIZ diff --git a/geom-exp.cpp b/geom-exp.cpp new file mode 100644 index 00000000..c81175f6 --- /dev/null +++ b/geom-exp.cpp @@ -0,0 +1,297 @@ +// -- geometry menu -- + +int eupage = 0; +int euperpage = 21; + +const char *curvenames[8] = { + "0", "1", "2", "extremely hyperbolic", "strongly hyperbolic", "strongly hyperbolic", "moderately hyperbolic", "weakly hyperbolic" + }; + +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 'truncated' 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 (truncated 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() {} + +void showTorusConfig() { + cmode = sm::SIDE | sm::TORUSCONFIG; + gamescreen(2); + + dialog::init(XLAT("advanced concfiguration")); + + dialog::addSelItem(XLAT("number of cells (n)"), its(torusconfig::newqty), 'n'); + dialog::addSelItem(XLAT("cell bottom-right from 0 (d)"), its(torusconfig::newdy), 'd'); + + if(torusconfig::newqty % 3) + dialog::addInfo("best if n is divisible by 3", 0x808080); + + if((torusconfig::newdy + 999999) % 3 != 2) + dialog::addInfo("best if d+1 is divisible by 3", 0x808080); + + 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 == 'n') + dialog::editNumber(torusconfig::newqty, 0, 1000, 3, torusconfig::def_qty, XLAT("number of cells (n)"), ""); + else if(uni == 'd') + dialog::editNumber(torusconfig::newdy, -1000, 1000, 3, -torusconfig::def_dy, XLAT("cell bottom-right from 0 (d)"), ""); + else if((uni == 'a' || uni == '\n') && torusconfig::newqty >= 3 && abs(torusconfig::newdy) < torusconfig::newqty ) { + targetgeometry = gEuclid; restartGame('g'); + torusconfig::qty = torusconfig::newqty; + torusconfig::dy = torusconfig::newdy; + targetgeometry = gTorus; restartGame('g'); + } + else if(uni == 'c') { + targetgeometry = gEuclid; restartGame('g'); + torusconfig::qty = torusconfig::def_qty; + torusconfig::dy = torusconfig::def_dy; + targetgeometry = gTorus; restartGame('g'); + } + else if(uni == 'z') editScale(); + else if(uni == 'u') rug::select(); + else if(doexiton(sym, uni)) + popScreen(); + }; + + dialog::display(); + } + +void showEuclideanMenu() { + cmode = sm::SIDE; + gamescreen(0); + if(cheater) for(int i=0; i= 25) landvisited[i] = true; + landvisited[laCrossroads] = 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[laCA] = true; + // for(int i=2; i10, '1'); + else + dialog::addBoolItem(XLAT("Poincaré/Klein"), vid.alpha>.5, '1'); + if(torus || quotient == 2) + dialog::addItem(XLAT("advanced parameters"), '4'); + dialog::addItem(XLAT("help"), SDLK_F1); + dialog::addItem(XLAT("back"), '0'); + dialog::display(); + + keyhandler = [] (int sym, int uni) { + dialog::handleNavigation(sym, uni); + if(uni >= 'a' && uni < 'a'+gGUARD) { + targetgeometry = eGeometry(uni - 'a'); + restartGame(geometry == targetgeometry ? 0 : 'g'); + pushScreen(showEuclideanMenu); + } + else if(uni == 't') { + if(!euclid) { + restartGame('7'); + pushScreen(showEuclideanMenu); + } + } + 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, + pushScreen(showTorusConfig); + if(quotient==2) pushScreen(showQuotientConfig); + } + else if(doexiton(sym, uni)) + popScreen(); + }; + } + else { + dialog::init(XLAT("experiment with geometry")); + string truncatenames[2] = {" (t)", " (n)"}; + + dialog::addSelItem(XLAT("geometry"), XLAT(ginf[geometry].name) + XLAT(truncatenames[nontruncated]), '5'); + dialog::addBreak(50); + + generateLandList(isLandValid); + + for(int i=0; i= size(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 validclasses[4] = {"", " (½)", "", " (!)"}; + 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[isLandValid(l)]; + } + dialog::addBreak(50); + if(chaosUnlocked && !quotient && !euclid && !sphere) + dialog::addItem(XLAT("Chaos mode"), '1'); + dialog::addItem(XLAT("next page"), '-'); + + dialog::addItem(XLAT("help"), SDLK_F1); + dialog::addItem(XLAT("back"), '0'); + 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 >= size(landlist)) eupage = 0; + } + else if(uni == '1') { + if(chaosUnlocked) { + restartGame('C'); + pushScreen(showEuclideanMenu); + } + } + else if(lid >= 0 && lid < size(landlist)) { + eLand nland = landlist[lid]; + if(landvisited[nland]) { + specialland = nland; + restartGame(tactic::on ? 't' : 0); + pushScreen(showEuclideanMenu); + } + } + else if(uni == '2' || sym == SDLK_F1) gotoHelp(euchelp); + else if(doexiton(sym, uni)) popScreen(); + }; + } + + } + +void runGeometryExperiments() { + specialland = getLandForList(cwt.c); + pushScreen(showEuclideanMenu); + } + diff --git a/graph.cpp b/graph.cpp index f2311d52..45eb4216 100644 --- a/graph.cpp +++ b/graph.cpp @@ -3067,6 +3067,7 @@ bool dodrawcell(cell *c) { if(c->mpdist > 7 && !cheater) return false; // always show on the torus rug if(rug::rugged && torus) return true; + if(cmode & sm::TORUSCONFIG) return true; // in the Yendor Challenge, scrolling back is forbidden if(c->cpdist > 7 && (yendor::on && !cheater)) return false; // (incorrect comment) too far, no bugs nearby @@ -3343,6 +3344,14 @@ void drawcell(cell *c, transmatrix V, int spinv, bool mirrored) { PPR_TEXT); */ queuestr(V, (cd > 9 ? .6 : 1) * .2, label, 0xFF000000 + distcolors[cd&7], 1); } + + if(cmode & sm::TORUSCONFIG) { + using namespace torusconfig; + int cd = torus_cx * dx + torus_cy * newdy; + cd %= newqty; if(cd<0) cd += newqty; + string label = its(cd); + queuestr(V, cd ? .2 : .6, label, cd == 0 ? 0xFFFF0040 : 0xFFFFFFD0, 1); + } asciicol = wcol; diff --git a/hyper.h b/hyper.h index a315ebbc..c1e09496 100644 --- a/hyper.h +++ b/hyper.h @@ -1443,6 +1443,7 @@ namespace sm { static const int CENTER = 1024; static const int A3 = 2048; // affects poly static const int ZOOMABLE = 4096; + static const int TORUSCONFIG = 8192; }; namespace linepatterns { diff --git a/hypgraph.cpp b/hypgraph.cpp index f4f84283..2b8444db 100644 --- a/hypgraph.cpp +++ b/hypgraph.cpp @@ -390,6 +390,10 @@ transmatrix eumovedir(int d) { return eumove(0,0); } +ld matrixnorm(const transmatrix& Mat) { + return Mat[0][2] * Mat[0][2] + Mat[1][2] * Mat[1][2]; + } + void drawEuclidean() { DEBB(DF_GRAPH, (debugfile,"drawEuclidean\n")); eucoord px=0, py=0; @@ -402,18 +406,20 @@ void drawEuclidean() { pid = decodeId(centerover->master); else decodeMaster(centerover->master, px, py); - + int minsx = mindx-1, maxsx=maxdx+1, minsy=mindy-1, maxsy=maxdy+1; mindx=maxdx=mindy=maxdy=0; - static ld centerd; - transmatrix View0 = View; ld cellrad = vid.radius / (EUCSCALE + vid.alphax); + ld centerd = matrixnorm(View0); + for(int dx=minsx; dx<=maxsx; dx++) for(int dy=minsy; dy<=maxsy; dy++) { + torusconfig::torus_cx = dx; + torusconfig::torus_cy = dy; reclevel = eudist(dx, dy); cell *c; transmatrix Mat; @@ -432,9 +438,8 @@ void drawEuclidean() { Mat = View0 * Mat; if(torus) { - ld locald = (Mat[0][2] * Mat[0][2] + Mat[1][2] * Mat[1][2]); - if(c == centerover) centerd = locald; - else if(locald < centerd) centerd = locald, centerover = c, View = View0 * eumove(dx, dy); + ld locald = matrixnorm(Mat); + if(locald < centerd) centerd = locald, centerover = c, View = View0 * eumove(dx, dy); } // Mat[0][0] = -1; diff --git a/menus.cpp b/menus.cpp index 18f186fe..626527e8 100644 --- a/menus.cpp +++ b/menus.cpp @@ -305,6 +305,12 @@ void showMainMenu() { // -- display modes -- +void editScale() { + dialog::editNumber(vid.scale, .001, 1000, .1, 1, XLAT("scale factor"), + XLAT("Scale the displayed model.")); + dialog::scaleLog(); + } + void showDisplayMode() { cmode = sm::SIDE; gamescreen(0); @@ -355,11 +361,7 @@ void showDisplayMode() { if(xuni == 'p') projectionDialog(); - if(xuni == 'z') { - dialog::editNumber(vid.scale, .001, 1000, .1, 1, XLAT("scale factor"), - XLAT("Scale the displayed model.")); - dialog::scaleLog(); - } + if(xuni == 'z') editScale(); if(xuni == 'm') { vid.monmode += 60 + (shiftmul > 0 ? 1 : -1); vid.monmode %= 6; } @@ -543,244 +545,6 @@ void showChangeMode() { }; } -// -- geometry menu -- - -int eupage = 0; -int euperpage = 21; - -const char *curvenames[8] = { - "0", "1", "2", "extremely hyperbolic", "strongly hyperbolic", "strongly hyperbolic", "moderately hyperbolic", "weakly hyperbolic" - }; - -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 'truncated' 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 (truncated 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 showEuclideanMenu() { - cmode = sm::SIDE; - gamescreen(0); - int s = vid.fsize; - if(cheater) for(int i=0; i= 25) landvisited[i] = true; - landvisited[laCrossroads] = 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[laCA] = true; - // for(int i=2; i10, '1'); - else - dialog::addBoolItem(XLAT("Poincaré/Klein"), vid.alpha>.5, '1'); - dialog::addItem(XLAT("help"), SDLK_F1); - dialog::addItem(XLAT("back"), '0'); - dialog::display(); - - keyhandler = [] (int sym, int uni) { - dialog::handleNavigation(sym, uni); - if(uni >= 'a' && uni < 'a'+gGUARD) { - targetgeometry = eGeometry(uni - 'a'); - restartGame(geometry == targetgeometry ? 0 : 'g'); - pushScreen(showEuclideanMenu); - } - else if(uni == 't') { - if(!euclid) { - restartGame('7'); - pushScreen(showEuclideanMenu); - } - } - 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(doexiton(sym, uni)) - popScreen(); - }; - } - else { - dialog::init(XLAT("experiment with geometry")); - string truncatenames[2] = {" (t)", " (n)"}; - - dialog::addSelItem(XLAT("geometry"), XLAT(ginf[geometry].name) + XLAT(truncatenames[nontruncated]), '5'); - dialog::addBreak(50); - - generateLandList(isLandValid); - - for(int i=0; i= size(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 validclasses[4] = {"", " (½)", "", " (!)"}; - 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[isLandValid(l)]; - } - dialog::addBreak(50); - if(chaosUnlocked && !quotient && !euclid && !sphere) - dialog::addItem(XLAT("Chaos mode"), '1'); - dialog::addItem(XLAT("next page"), '-'); - - dialog::addItem(XLAT("help"), SDLK_F1); - dialog::addItem(XLAT("back"), '0'); - dialog::display(); - - vid.fsize = s; - 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 >= size(landlist)) eupage = 0; - } - else if(uni == '1') { - if(chaosUnlocked) { - restartGame('C'); - pushScreen(showEuclideanMenu); - } - } - else if(lid >= 0 && lid < size(landlist)) { - eLand nland = landlist[lid]; - if(landvisited[nland]) { - specialland = nland; - restartGame(tactic::on ? 't' : 0); - pushScreen(showEuclideanMenu); - } - } - else if(uni == '2' || sym == SDLK_F1) gotoHelp(euchelp); - else if(doexiton(sym, uni)) popScreen(); - }; - } - - } - -void runGeometryExperiments() { - specialland = getLandForList(cwt.c); - pushScreen(showEuclideanMenu); - } - bool showstartmenu; bool showHalloween() {