// Hyperbolic Rogue -- expansion analyzer // Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details /** \file expansion.cpp * \brief exponential growth of hyperbolic geometries * * Calculations related to this exponential growth. * Screens which display this exponential growth (e.g. 'size of the world' in geometry experiments) are also implemented here. */ namespace hr { int subtype(cell *c) { return patterns::getpatterninfo(c, patterns::PAT_NONE, 0).id; } #if HDR struct bignum { static const int BASE = 1000000000; static const long long BASE2 = BASE * (long long)BASE; vector digits; bignum() {} bignum(int i) : digits() { digits.push_back(i); } void be(int i) { digits.resize(1); digits[0] = i; } bignum& operator +=(const bignum& b); void addmul(const bignum& b, int factor); string get_str(int max_length); bool operator < (const bignum&) const; ld leading() const { switch(isize(digits)) { case 0: return 0; case 1: return digits.back(); default: return digits.back() + ld(digits[isize(digits)-2]) / BASE; } } ld approx() const { return leading() * pow(BASE, isize(digits) - 1); } ld log_approx() const { return log(leading()) * log(BASE) * (isize(digits) - 1); } ld operator / (const bignum& b) const { return leading() / b.leading() * pow(BASE, isize(digits) - isize(b.digits)); } int approx_int() const { if(isize(digits) > 1) return BASE; if(digits.empty()) return 0; return digits[0]; } long long approx_ll() const { if(isize(digits) > 2) return BASE2; if(digits.empty()) return 0; if(isize(digits) == 1) return digits[0]; return digits[0] + digits[1] * (long long) BASE; } friend inline bignum operator +(bignum a, const bignum& b) { a.addmul(b, 1); return a; } friend inline bignum operator -(bignum a, const bignum& b) { a.addmul(b, -1); return a; } }; #endif bignum& bignum::operator +=(const bignum& b) { int K = isize(b.digits); if(K > isize(digits)) digits.resize(K); int carry = 0; for(int i=0; i= isize(digits)) digits.push_back(0); digits[i] += carry; if(i < K) digits[i] += b.digits[i]; if(digits[i] >= BASE) { digits[i] -= BASE; carry = 1; } else carry = 0; } return *this; } bool bignum::operator < (const bignum& b) const { if(isize(digits) != isize(b.digits)) return isize(digits) < isize(b.digits); for(int i = isize(digits)-1; i>=0; i--) if(digits[i] != b.digits[i]) return digits[i] < b.digits[i]; return false; } void bignum::addmul(const bignum& b, int factor) { int K = isize(b.digits); if(K > isize(digits)) digits.resize(K); int carry = 0; for(int i=0; i 0 || carry < -1) || (carry == -1 && i < isize(digits)); i++) { if(i >= isize(digits)) digits.push_back(0); long long l = digits[i]; l += carry; if(i < K) l += b.digits[i] * factor; carry = 0; if(l >= BASE) carry = l / BASE; if(l < 0) carry = -(BASE-1-l) / BASE; l -= carry * BASE; digits[i] = l; } if(carry < 0) digits.back() -= BASE; while(isize(digits) && digits.back() == 0) digits.pop_back(); } bignum hrand(bignum b) { bignum res; int d = isize(b.digits); while(true) { res.digits.resize(d); for(int i=0; i=0; i--) { if(isize(ret) > max_length && i) { ret += XLAT(" (%1 more digits)", its(9 * (i+1))); return ret; } ret += " "; string val = its(digits[i]); while(isize(val) < 9) val = "0" + val; ret += val; } return ret; } void canonicize(vector& t) { for(int i=2; i gettype(cell *c); int N; vector samples; map, int> codeid; vector > children; int rootid, diskid; int coefficients_known; vector coef; int valid_from, tested_to; ld growth; int sample_id(cell *c); void preliminary_grouping(); void reduce_grouping(); vector> descendants; bignum& get_descendants(int level); bignum& get_descendants(int level, int type); void find_coefficients(); void reset(); expansion_analyzer() { reset(); } string approximate_descendants(int d, int max_length); void view_distances_dialog(); ld get_growth(); private: bool verify(int id); int valid(int v, int step); }; #endif vector expansion_analyzer::gettype(cell *c) { vector res; res.push_back(subtype(c) * 4 + 2); int d = celldist(c); for(int i=0; itype; i++) { cell *c1 = c->cmove(i); int bonus = 0; if(binarytiling) bonus += 16 * (celldistAlt(c1) - celldistAlt(c)); res.push_back(bonus + subtype(c1) * 4 + celldist(c1) - d); } canonicize(res); return res; } int expansion_analyzer::sample_id(cell *c) { auto t = gettype(c); if(codeid.count(t)) return codeid[t]; auto &cit = codeid[t]; cit = isize(samples); samples.push_back(c); return cit; } template vector get_children_codes(cell *c, const T& distfun, const U& typefun) { vector res; int d = distfun(c); cellwalker cw(c, 0); if(d > 0) { forCellCM(c2, c) if(celldist(cw.peek()) < d) break; else cw++; } for(int k=0; ktype; k++) { cell *c1 = cw.cpeek(); cw++; if(distfun(c1) != d+1) continue; cell *c2 = cw.cpeek(); if(distfun(c2) != d+1) continue; res.push_back(typefun(c1)); } return res; } void expansion_analyzer::preliminary_grouping() { samples.clear(); codeid.clear(); children.clear(); sample_id(currentmap->gamestart()); // queue for, do not change to range-based for for(int i=0; i grouping; grouping.resize(N); int nogroups = 1; for(int i=0; i, int > > childgroups(N); for(int i=0; i groupsample(nogroups, -1); for(int i=0; i reorder(nogroups); for(int i=0; i inv_reorder(nogroups); for(int i=0; i> newchildren(nogroups); for(int i=0; i int size_upto(vector& v, int s) { int res = isize(v); if(res < s) v.resize(s); return res; } bignum& expansion_analyzer::get_descendants(int level) { if(!N) preliminary_grouping(), reduce_grouping(); return get_descendants(level, rootid); } bignum& expansion_analyzer::get_descendants(int level, int type) { auto& pd = descendants; size_upto(pd, level+1); for(int d=0; d<=level; d++) for(int i=size_upto(pd[d], N); i= bignum::BASE) return 0; ld matrix[100][128]; for(int i=0; i=0; k--) for(int l=k-1; l>=0; l--) if(matrix[l][k]) matrix[l][v] -= matrix[l][k] * matrix[k][v]; coef.resize(v); for(int i=0; i= 1) return growth; if(!N) preliminary_grouping(), reduce_grouping(); vector eigen(N, 1); ld total; for(int iter=0; iter<100000; iter++) { total = 0; vector neweigen(N, 0); for(int i=0; i res; res.push_back(subtype(c) * 4 + 2); int d = f(c); for(int i=0; itype; i++) { cell *c1 = c->cmove(i); int bonus = 0; if(binarytiling) bonus += 16 * (celldistAlt(c1) - celldistAlt(c)); res.push_back(bonus + subtype(c1) * 4 + f(c1) - d); } canonicize(res); if(ea.codeid.count(res)) return ea.codeid[res]; int ret = ea.N++; ea.codeid[res] = ret; ea.children.emplace_back(); ea.children[ret] = get_children_codes(c, f, [&ea, &f] (cell *c1) { return type_in(ea, c1, f); }); return ret; } int type_in_quick(expansion_analyzer& ea, cell *c, const cellfunction& f) { vector res; res.push_back(subtype(c) * 4 + 2); int d = f(c); for(int i=0; itype; i++) { cell *c1 = c->cmove(i); int dd = f(c1) - d; if(dd < -1 || dd > 1) return -1; res.push_back(subtype(c1) * 4 + dd); } canonicize(res); if(ea.codeid.count(res)) return ea.codeid[res]; return -1; } bool sizes_known() { if(GDIM == 3) return false; if(bounded) return false; // Castle Anthrax is infinite if(binarytiling) return false; // not implemented if(archimedean) return false; if(penrose) return false; return true; } bool trees_known() { return sizes_known() && !(BITRUNCATED && a4 && S7 <= 5); } string expansion_analyzer::approximate_descendants(int d, int max_length) { auto t = SDL_GetTicks(); while(isize(descendants) <= d && SDL_GetTicks() < t + 100) get_descendants(isize(descendants)); if(isize(descendants) > d) return get_descendants(d).get_str(max_length); int v = isize(descendants) - 1; bignum& b = get_descendants(v); if(b.digits.empty()) return "0"; ld log_10 = log(b.digits.back()) / log(10) + 9 * (isize(b.digits) - 1) + (d - v) * log(get_growth()) / log(10); int more_digits = int(log_10); return XLAT("about ") + fts(pow(10, log_10 - more_digits)) + "E" + its(more_digits); } enum eDistanceFrom { dfPlayer, dfStart, dfWorld }; string dfnames[3] = { "player", "start", "land" }; eDistanceFrom distance_from = dfPlayer; enum eNumberCoding { ncNone, ncDistance, ncType, ncDebug }; string ncnames[4] = { "NO", "distance", "type", "debug" }; eNumberCoding number_coding = ncDistance; bool mod_allowed() { return cheater || autocheat || archimedean || tour::on; } int curr_dist(cell *c) { switch(distance_from) { case dfPlayer: return c->cpdist < INFD ? c->cpdist : celldistance(cwt.at, c); case dfStart: return celldist(c); case dfWorld: if(!mod_allowed() && !among(c->land, laOcean, laIvoryTower, laEndorian, laDungeon, laTemple, laWhirlpool, laCanvas)) return 0; if(eubinary || c->master->alt) return celldistAlt(c); return inmirror(c) ? (c->landparam & 255) : c->landparam; } return 0; } int position; EX int type_in_reduced(expansion_analyzer& ea, cell *c, const cellfunction& f) { int a = ea.N; int t = type_in(ea, c, f); if(expansion.N != a) { expansion.reduce_grouping(); t = type_in(ea, c, f); } return t; } // which=1 => right, which=-1 => left EX int parent_id(cell *c, int which, const cellfunction& cf) { int d = cf(c)-1; for(int i=0; itype; i++) { if(cf(c->cmove(i)) == d) { int steps = 0; again: if(!which || steps == c->type) return i; int i2 = c->c.fix(i+which); if(cf(c->cmove(i2)) == d) { i = i2; steps++; goto again; } else return i; } } return -1; } // set which=1,bonus=1 to get right neighbor on level void generate_around(cell *c) { forCellCM(c2, c) if(c2->mpdist > BARLEV) setdist(c2, BARLEV, c); } EX namespace ts { EX cell *verified_add(cell *c, int which, int bonus, const cellfunction& cf) { int id = parent_id(c, which, cf); if(id == -1) return NULL; return c->cmodmove(id + bonus); } EX cell *verified_add_gen(cell *c, int which, int bonus, const cellfunction& cf) { return verified_add(c, which, bonus, cf); } EX cell *add(cell *c, int which, int bonus, const cellfunction& cf) { int pid = parent_id(c, which, cf); if(pid == -1) pid = 0; return c->cmodmove(pid + bonus); } EX cell *left_of(cell *c, const cellfunction& cf) { int pid = parent_id(c, 1, cf); if(pid == -1) return c; if(VALENCE == 3) return c->cmodmove(pid+1); else return (cellwalker(c, pid) + wstep - 1).cpeek(); } EX cell *right_of(cell *c, const cellfunction& cf) { int pid = parent_id(c, -1, cf); if(pid == -1) return c; if(VALENCE == 3) return c->cmodmove(pid-1); else return (cellwalker(c, pid) + wstep + 1).cpeek(); } EX cell *child_number(cell *c, int id, const cellfunction& cf) { int pid = parent_id(c, 1, cf); if(pid == -1) return c->cmove(id); return c->cmodmove(pid + (VALENCE == 3 ? 2 : 1) + id); } #if HDR inline cell *left_parent(cell *c, const cellfunction& cf) { return verified_add(c, 1, 0, cf); } inline cell *right_parent(cell *c, const cellfunction& cf) { return verified_add(c, -1, 0, cf); } #endif EX } bool viewdists = false, use_color_codes = true, use_analyzer = true, show_distance_lists = true; int first_distance = 0, scrolltime = 0; bool scrolling_distances = false; map expcolors; color_t distribute_color(int id) { if(expcolors.count(id)) return expcolors[id]; color_t v = forecolor; // 0xFFFFFF; for(int z=0; z<24; z++) if(id & (1<= 0) label = its(t), dc = distribute_color(t); break; } case ncDebug: { int d = distance_from == dfStart && cwt.at == currentmap->gamestart() && c->cpdist < INFD ? c->cpdist : celldistance(c, distance_from == dfPlayer ? cwt.at : currentmap->gamestart()); dc = (d != cd) ? 0xFF0000 : 0x00FF00; label = its(d); } case ncNone: ; } // string label = its(fieldpattern::getriverdistleft(c)) + its(fieldpattern::getriverdistright(c)); /* queuepolyat(V, shFloor[ct6], darkena(gradient(0, distcolors[cd&7], 0, .25, 1), fd, 0xC0), PPR::TEXT); */ if(label != "") queuestr(V, (isize(label) > 1 ? .6 : 1), label, 0xFF000000 + dc, 1); } void viewdist_configure_dialog() { dialog::init(""); cmode |= sm::SIDE | sm::MAYDARK | sm::EXPANSION; gamescreen(0); dialog::addSelItem(XLAT("which distance"), XLAT(dfnames[distance_from]), 'c'); dialog::add_action([] () { distance_from = mod_allowed() ? eDistanceFrom((distance_from + 1) % 3) : eDistanceFrom(2 - distance_from); }); dialog::addSelItem(XLAT("number codes"), XLAT(ncnames[number_coding]), 'n'); dialog::add_action([] () { number_coding = eNumberCoding((number_coding + 1) % (mod_allowed() ? 4 : 2)); }); dialog::addBoolItem_action(XLAT("color codes"), use_color_codes, 'u'); dialog::addSelItem(XLAT("display distances from"), its(first_distance), 'd'); dialog::add_action([] () { scrolling_distances = false; dialog::editNumber(first_distance, 0, 3000, 1, 0, XLAT("display distances from"), ""); }); int id = 0; for(auto& lp: linepatterns::patterns) { using namespace linepatterns; if(among(lp.id, patTriTree, patTriRings, patTriOther)) { dialog::addColorItem(XLAT(lp.lpname), lp.color, '1'+(id++)); dialog::add_action([&lp] () { dialog::openColorDialog(lp.color, NULL); dialog::dialogflags |= sm::MAYDARK | sm::SIDE | sm::EXPANSION; }); } } if(!mod_allowed()) { dialog::addItem(XLAT("enable the cheat mode for additional options"), 'C'); dialog::add_action(enable_cheat); } else dialog::addBreak(100); dialog::addBreak(100); dialog::addItem(XLAT("disable"), 'x'); dialog::add_action([] () { viewdists = false; popScreen(); }); dialog::addItem(XLAT("move the player"), 'm'); dialog::add_action([] () { show_distance_lists = false; popScreenAll(); }); dialog::addItem(XLAT(distance_from ? "show number of descendants by distance" : "show number of cells by distance"), 'l'); dialog::add_action([] () { show_distance_lists = true; popScreenAll(); }); dialog::display(); } bool is_descendant(cell *c) { if(c == cwt.at) return true; if(curr_dist(c) < curr_dist(cwt.at)) return false; return is_descendant(ts::right_parent(c, curr_dist)); } const int scrollspeed = 100; bool not_only_descendants = false; void expansion_analyzer::view_distances_dialog() { static int lastticks; if(scrolling_distances && !bounded) { scrolltime += SDL_GetTicks() - lastticks; first_distance += scrolltime / scrollspeed; scrolltime %= scrollspeed; } lastticks = SDL_GetTicks(); dynamicval dv(distcolors[0], forecolor); dialog::init(""); cmode |= sm::DIALOG_STRICT_X | sm::EXPANSION; int maxlen = bounded ? 128 : 16 + first_distance; vector qty(maxlen); bool really_use_analyzer = use_analyzer && sizes_known(); if(really_use_analyzer) { int t = type_in_reduced(expansion, cwt.at, curr_dist); for(int r=0; r= 0 && d < maxlen) qty[d]++; } else { celllister cl(cwt.at, bounded ? maxlen-1 : gamerange(), 100000, NULL); for(cell *c: cl.lst) if((not_only_descendants || is_descendant(c)) && curr_dist(c) < maxlen) qty[curr_dist(c)]++; } if(sizes_known() && !not_only_descendants) { find_coefficients(); if(gamerange()+1 >= valid_from && coefficients_known == 2) { for(int i=gamerange()+1; i 1) fmt += " + " + its(coef[i]); else if(coef[i] < -1) fmt += " - " + its(-coef[i]); fmt += "a(d"; if(i != isize(coef) - 1) fmt += "+" + its(isize(coef) - 1 - i); fmt += ")"; first = false; } fmt += " (d>" + its(valid_from-1) + ")"; dialog::addHelp(fmt); } else dialog::addBreak(100); dialog::addInfo("Θ(" + fts(get_growth(), 8) + "...ᵈ)", forecolor); } } dialog::addItem(XLAT("scroll"), 'S'); dialog::addItem(XLAT("configure"), 'C'); dialog::display(); } void enable_viewdists() { first_distance = 0; scrolltime = 0; viewdists = true; if(!mod_allowed()) { number_coding = ncDistance; distance_from = dfPlayer; } show_distance_lists = true; } bool expansion_handleKey(int sym, int uni) { if((cmode & sm::NORMAL) && viewdists) { if(uni == 'S' && (cmode & sm::EXPANSION)) scrolling_distances = !scrolling_distances; else if(uni == 'C') pushScreen(viewdist_configure_dialog); else if(uni == 'A' && (cmode & sm::EXPANSION)) use_analyzer = !use_analyzer; else if(sym == SDLK_ESCAPE) first_distance = 0, viewdists = false; else return false; return true; } return false; } int expansion_hook = addHook(hooks_handleKey, 0, expansion_handleKey); #if !ISMINI void compute_coefficients() { println(hlog, gp::operation_name(), " ", ginf[geometry].tiling_name); start_game(); printf(" sizes:"); for(int i=0; i<10; i++) printf(" %d", expansion.get_descendants(i).approx_int()); printf(" N = %d\n", expansion.N); expansion.find_coefficients(); if(expansion.coefficients_known == 2) { printf(" coefficients:"); for(int x: expansion.coef) printf(" %d", x); printf(" (tested on %d to %d)\n", expansion.valid_from, expansion.tested_to); } } #if CAP_COMMANDLINE int expansion_readArgs() { using namespace arg; if(0) ; else if(argis("-vap")) { PHASEFROM(2); start_game(); shift(); int radius = argi(); while(true) { string s = expansion.approximate_descendants(radius, 100); printf("s = %s\n", s.c_str()); if(isize(expansion.descendants) >= radius) break; } } else if(argis("-csizes")) { PHASEFROM(2); start_game(); expansion.get_growth(); shift(); for(int i=0; i 10) continue; stop_game(); gp::param = gp::loc(x, y); set_variation(eVariation::goldberg); compute_coefficients(); } } } #endif else if(argis("-expansion")) { cheat(); viewdists = true; shift(); distance_from = (eDistanceFrom) argi(); shift(); number_coding = (eNumberCoding) argi(); shift(); use_color_codes = argi() & 1; use_analyzer = argi() & 2; show_distance_lists = argi() & 4; not_only_descendants = argi() & 8; } else if(argis("-expansion-off")) { viewdists = false; } else return 1; return 0; } auto ea_hook = addHook(hooks_args, 100, expansion_readArgs); #endif #endif EX expansion_analyzer expansion; EX int sibling_limit = 0; EX void set_sibling_limit() { if(0) ; #if CAP_IRR else if(IRREGULAR) sibling_limit = 3; #endif #if CAP_BT else if(binarytiling) sibling_limit = 3; #endif #if CAP_GP else { auto p = gp::univ_param(); sibling_limit = 2 * p.first + p.second; } #else else sibling_limit = PURE ? 2 : 3; #endif } int celldist0(cell *c) { if(binarytiling) return celldistAlt(c); else return celldist(c); } bool in_segment(cell *left, cell *mid, cell *right) { while(true) { if(mid == left) return true; if(left == right) return false; left = ts::right_of(left, celldist0); } } int sibling_distance(cell *a, cell *b, int limit) { int counting = 0; while(true) { if(a == b) return counting; if(limit == 0) return INF; counting++; limit--; a = ts::right_of(a, celldist0); } } EX int hyperbolic_celldistance(cell *c1, cell *c2) { int found_distance = INF; int d = 0, d1 = celldist0(c1), d2 = celldist0(c2), sl_used = 0; cell *cl1=c1, *cr1=c1, *cl2=c2, *cr2=c2; while(true) { if(a45 && BITRUNCATED) { // some cells in this tiling have three parents, // making the usual algorithm fail if(d2 == d1+1) { swap(d1, d2); swap(cl1, cl2); swap(c1, c2); swap(cr1, cr2); } auto short_distances = [cl1, cr1, d, &found_distance] (cell *c) { celllister cl(c, 4, 1000, cl1); if(cl.listed(cl1)) found_distance = min(found_distance, d + cl.getdist(cl1)); if(cl.listed(cr1)) found_distance = min(found_distance, d + cl.getdist(cr1)); }; if(d1 <= d2+1) { short_distances(cl2); if(cl2 != cr2) short_distances(cr2); } } if(d >= found_distance) { if(sl_used == sibling_limit && IRREGULAR) { printf("sibling_limit used: %d\n", sibling_limit); sibling_limit++; } return found_distance; } if(d1 == d2) { if(cl1 == c1 && in_segment(cl2, c1, cr2)) return d; if(cl2 == c2 && in_segment(cl1, c2, cr1)) return d; if(VALENCE == 3) { int dx = min(sibling_distance(cr1, cl2, sibling_limit), sibling_distance(cr2, cl1, sibling_limit)); if(d + dx <= found_distance) { found_distance = d + dx; sl_used = dx; } } else { if(cl1 == cr2 || cr1 == cl2) found_distance = d; } } if(d >= found_distance) { if(sl_used == sibling_limit && IRREGULAR) { printf("sibling_limit used: %d\n", sibling_limit); sibling_limit++; } return found_distance; } if(d1 >= d2) { cl1 = ts::left_parent(cl1, celldist0); cr1 = ts::right_parent(cr1, celldist0); d++; d1--; } if(d1 < d2) { cl2 = ts::left_parent(cl2, celldist0); cr2 = ts::right_parent(cr2, celldist0); d++; d2--; } } } }