hyperrogue/geom-exp.cpp

408 lines
14 KiB
C++
Raw Normal View History

2017-11-06 20:18:40 +00:00
// -- geometry menu --
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 '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;
2017-11-06 23:40:46 +00:00
// extra information for field quotient extra configuration
struct primeinfo {
int p;
int cells;
bool squared;
};
struct fgeomextra {
eGeometry base;
vector<primeinfo> primes;
int current_prime_id;
fgeomextra(eGeometry b, int i) : base(b), current_prime_id(i) {}
};
vector<fgeomextra> fgeomextras = {
fgeomextra(gNormal, 3),
fgeomextra(gOctagon, 1),
fgeomextra(g45, 0),
fgeomextra(g46, 3),
fgeomextra(g47, 0)
};
int current_extra = 0;
void nextPrime(fgeomextra& ex) {
dynamicval<eGeometry> g(geometry, ex.base);
int nextprime;
if(size(ex.primes))
nextprime = ex.primes.back().p + 1;
else
nextprime = 2;
while(true) {
fieldpattern::fpattern fp(0);
fp.Prime = nextprime;
if(fp.solve() == 0) {
fp.build();
2017-11-07 12:35:59 +00:00
ex.primes.emplace_back(primeinfo{nextprime, size(fp.matrices) / S7, (bool) fp.wsquare});
2017-11-06 23:40:46 +00:00
break;
}
nextprime++;
}
}
void nextPrimes(fgeomextra& ex) {
while(size(ex.primes) < 4)
nextPrime(ex);
}
void enableFieldChange() {
fgeomextra& gxcur = fgeomextras[current_extra];
fieldpattern::quotient_field_changed = true;
nextPrimes(gxcur);
dynamicval<eGeometry> g(geometry, gQuotient2);
ginf[geometry].sides = ginf[gxcur.base].sides;
ginf[geometry].vertex = ginf[gxcur.base].vertex;
ginf[geometry].distlimit = ginf[gxcur.base].distlimit;
fieldpattern::current_quotient_field.init(gxcur.primes[gxcur.current_prime_id].p);
}
void showQuotientConfig() {
gamescreen(2);
dialog::init(XLAT("advanced configuration"));
fgeomextra& gxcur = fgeomextras[current_extra];
for(int i=0; i<size(fgeomextras); i++) {
auto& g = fgeomextras[i];
dialog::addBoolItem(XLAT(ginf[g.base].name), g.base == gxcur.base, 'a'+i);
}
nextPrimes(gxcur);
for(int i=0; i<size(gxcur.primes); i++) {
auto& p = gxcur.primes[i];
dialog::addBoolItem(XLAT("order %1%2 (non-truncated cells: %3)", its(p.p), p.squared ? "²" : "", its(p.cells)), i == gxcur.current_prime_id, 'A'+i);
}
if(size(gxcur.primes) < 6) {
dialog::addBreak(100);
dialog::addHelp(
"This geometry is obtained by applying the same 'generators' which "
"lead to creating the given basic hyperbolic geometry, "
"but using a fixed finite field instead of the field of reals. "
"It can be also interpreted as a quotient of the given basic geometry. "
"Warning: field patterns based on large primes might generate for a long time."
);
dialog::addBreak(100);
}
dialog::addItem("find the next prime", 'p');
dialog::addItem("activate", 'x');
dialog::addItem("default", 'c');
keyhandler = [&gxcur] (int sym, int uni) {
if(uni >= 'a' && uni < 'a' + size(fgeomextras))
current_extra = uni - 'a';
else if(uni >= 'A' && uni < 'A' + size(gxcur.primes))
gxcur.current_prime_id = uni - 'A';
else if(uni == 'p')
nextPrime(gxcur);
else if(uni == 'x' || uni == '\n') {
targetgeometry = gxcur.base; restartGame('g');
enableFieldChange();
targetgeometry = gQuotient2; restartGame('g');
}
else if(uni == 'c') {
targetgeometry = gEuclid; restartGame('g');
fieldpattern::quotient_field_changed = false;
targetgeometry = gQuotient2; restartGame('g');
}
else if(doexiton(sym, uni))
popScreen();
};
dialog::display();
}
2017-11-06 20:18:40 +00:00
void showTorusConfig() {
cmode = sm::SIDE | sm::TORUSCONFIG;
gamescreen(2);
2017-11-06 23:40:46 +00:00
dialog::init(XLAT("advanced configuration"));
2017-11-06 20:18:40 +00:00
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();
2017-11-07 12:09:46 +00:00
#if CAP_RUG
2017-11-06 20:18:40 +00:00
else if(uni == 'u') rug::select();
2017-11-07 12:09:46 +00:00
#endif
2017-11-06 20:18:40 +00:00
else if(doexiton(sym, uni))
popScreen();
};
dialog::display();
}
void showEuclideanMenu() {
cmode = sm::SIDE;
gamescreen(0);
if(cheater) for(int i=0; i<landtypes; i++) landvisited[i] = true;
for(int i=0; i<landtypes; i++)
if(hiitemsMax(treasureType(eLand(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; i<lt; i++) landvisited[i] = true;
if(geometry == gNormal || ewhichscreen == 2) {
dialog::init(XLAT("experiment with geometry"));
int ts = ginf[geometry].sides;
int tv = ginf[geometry].vertex;
int tq = ginf[geometry].quotientstyle;
int nom = (nontruncated ? tv : tv+ts) * ((tq & qELLIP) ? 2 : 4);
int denom = (2*ts + 2*tv - ts * tv);
dialog::addSelItem(XLAT("land"), XLAT1(linf[specialland].name), '5');
dialog::addBreak(50);
for(int i=0; i<gGUARD; i++)
dialog::addBoolItem(XLAT(ginf[i].name), geometry == i, 'a'+i);
dialog::addBreak(50);
if(ts == 6 && tv == 3)
dialog::addSelItem(XLAT("truncated"), XLAT("does not matter"), 't');
else
dialog::addBoolItem(XLAT("truncated"), !nontruncated, 't');
dialog::addBreak(50);
int worldsize = denom ? nom/denom : 0;
if(tq & qTORUS) worldsize = torusconfig::qty;
if(tq & qZEBRA) worldsize = nontruncated ? 12 : 40;
if(tq & qFIELD) {
worldsize = size(currfp.matrices) / ts;
if(!nontruncated) worldsize = ((ts+tv)*worldsize) / tv;
}
dialog::addSelItem(XLAT("sides per face"), its(ts), 0);
dialog::addSelItem(XLAT("faces per vertex"), its(tv), 0);
string qstring = "none";
if(tq & qZEBRA) qstring = "zebra";
else if(tq & qFIELD) qstring = "field";
else if(tq & qELLIP) qstring = "torus";
else if(tq & qTORUS) qstring = "torus";
dialog::addSelItem(XLAT("quotient space"), XLAT(qstring), 0);
dialog::addSelItem(XLAT("size of the world"),
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 || 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<euperpage; i++) {
if(euperpage * eupage + 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);
}