namespace hr { namespace gp { bool on; loc param(1, 0); hyperpoint next; ld scale; ld alpha; int area; loc operator+(loc e1, loc e2) { return make_pair(e1.first+e2.first, e1.second+e2.second); } loc operator-(loc e1, loc e2) { return make_pair(e1.first-e2.first, e1.second-e2.second); } loc operator*(loc e1, loc e2) { return make_pair(e1.first*e2.first-e1.second*e2.second, e1.first*e2.second + e2.first*e1.second + e1.second*e2.second); } loc operator*(loc e1, int i) { return loc(e1.first*i, e1.second*i); } struct goldberg_mapping_t { cellwalker cw; signed char rdir; signed char mindir; }; loc eudir(int d) { d %= 6; if (d < 0) d += 6; switch(d) { case 0: return make_pair(1, 0); case 1: return make_pair(0, 1); case 2: return make_pair(-1, 1); case 3: return make_pair(-1, 0); case 4: return make_pair(0, -1); case 5: return make_pair(1, -1); default: return make_pair(0, 0); } } int get_code(const local_info& li) { return ((li.relative.first & 15) << 0) + ((li.relative.second & 15) << 4) + ((fix6(li.total_dir)) << 8) + ((li.last_dir & 15) << 12); } local_info get_local_info(cell *c) { local_info li; if(c == c->master->c7) { li.relative = loc(0,0); li.first_dir = -1; li.last_dir = -1; li.total_dir = -1; } else { vector dirs; while(c != c->master->c7) { dirs.push_back(c->spin(0)); c = c->mov[0]; } li.first_dir = dirs[0]; li.last_dir = dirs.back(); loc at(0,0); int dir = 0; at = at + eudir(dir); dirs.pop_back(); while(dirs.size()) { dir += dirs.back() + 3; dirs.pop_back(); at = at + eudir(dir); } li.relative = at; li.total_dir = dir + 3; } return li; } int last_dir(cell *c) { return get_local_info(c).last_dir; } loc get_coord(cell *c) { return get_local_info(c).relative; } int pseudohept_val(cell *c) { loc v = get_coord(c); return (v.first - v.second + MODFIXER)%3; } // mapping of the local equilateral triangle // goldberg_map[y][x].cw is the cellwalker in this triangle at position (x,y) // facing local direction 0 goldberg_mapping_t goldberg_map[32][32]; void clear_mapping() { for(int y=0; y<32; y++) for(int x=0; x<32; x++) { goldberg_map[y][x].cw.c = NULL; goldberg_map[y][x].rdir = -1; goldberg_map[y][x].mindir = 0; } } goldberg_mapping_t& get_mapping(loc c) { return goldberg_map[c.second&31][c.first&31]; } const char *disp(loc at) { static char bufs[16][16]; static int bufid; bufid++; bufid %= 16; snprintf(bufs[bufid], 16, "[%2d,%2d]", at.first, at.second); return bufs[bufid]; } const char *dcw(cellwalker cw) { static char bufs[16][32]; static int bufid; bufid++; bufid %= 16; snprintf(bufs[bufid], 32, "[%p/%2d:%d:%d]", cw.c, cw.c?cw.c->type:-1, cw.spin, cw.mirrored); return bufs[bufid]; } int spawn; #define WHD(x) // x bool operator != (cellwalker cw1, cellwalker cw2) { return cw1.c != cw2.c || cw1.spin != cw2.spin || cw1.mirrored != cw2.mirrored; } cell*& peek(cellwalker cw) { return cw.c->mov[cw.spin]; } cellwalker get_localwalk(const goldberg_mapping_t& wc, int dir) { if(dir < wc.mindir) dir += 6; if(dir >= wc.mindir + 6) dir -= 6; return wc.cw + dir; } void set_localwalk(goldberg_mapping_t& wc, int dir, const cellwalker& cw) { if(dir < wc.mindir) dir += 6; if(dir >= wc.mindir + 6) dir -= 6; wc.cw = cw - dir; } bool pull(loc at, int dir) { auto& wc = get_mapping(at); auto at1 = at + eudir(dir); int dir1 = fix6(dir+3); cellwalker wcw = get_localwalk(wc, dir); auto& wc1= get_mapping(at1); if(wc1.cw.c) { if(peek(wcw)) { auto wcw1 = get_localwalk(wc1, dir1); if(wcw + wstep != wcw1) { WHD( Xprintf("%s : %s / %s (pull error from %s :: %s)\n", disp(at1), dcw(wcw+wstep), dcw(wcw1), disp(at), dcw(wcw)); ) exit(1); } } return false; } if(peek(wcw)) { set_localwalk(wc1, dir1, wcw + wstep); WHD( Xprintf("%s : %s (pulled from %s :: %s)\n", disp(at1), dcw(wcw + wstep), disp(at), dcw(wcw)); ) return true; } return false; } void conn1(loc at, int dir, int dir1) { auto& wc = get_mapping(at); auto wcw = get_localwalk(wc, dir); auto& wc1 = get_mapping(at + eudir(dir)); WHD( Xprintf(" md:%d s:%d", wc.mindir, wc.cw.spin); ) WHD( Xprintf(" connection %s/%d %s=%s ~ %s/%d ", disp(at), dir, dcw(wc.cw+dir), dcw(wcw), disp(at+eudir(dir)), dir1); ) if(!wc1.cw.c) { if(peek(wcw)) { WHD( Xprintf("(pulled) "); ) set_localwalk(wc1, dir1, wcw + wstep); } else { peek(wcw) = newCell(6, wc.cw.c->master); tsetspin(wcw.c->spintable, wcw.spin, 0); set_localwalk(wc1, dir1, wcw + wstep); spawn++; WHD( Xprintf("(created) "); ) } } WHD( Xprintf("%s ", dcw(wc1.cw+dir1)); ) auto wcw1 = get_localwalk(wc1, dir1); if(peek(wcw)) { if(wcw+wstep != wcw1) { WHD( Xprintf("FAIL: %s / %s\n", dcw(wcw), dcw(wcw1)); exit(1); ) } else { WHD(Xprintf("(was there)\n");) } } else { WHD(Xprintf("ok\n"); ) peek(wcw) = wcw1.c; tsetspin(wcw.c->spintable, wcw.spin, wcw1.spin + (wcw.mirrored != wcw1.mirrored ? 8 : 0)); if(wcw+wstep != wcw1) { Xprintf("assertion failed\n"); exit(1); } } } void conn(loc at, int dir) { conn1(at, fix6(dir), fix6(dir+3)); conn1(at + eudir(dir), fix6(dir+3), fix6(dir)); } goldberg_mapping_t& set_heptspin(loc at, heptspin hs) { auto& ac0 = get_mapping(at); ac0.cw = cellwalker(hs.h->c7, hs.spin, hs.mirrored); WHD( Xprintf("%s : %s\n", disp(at), dcw(ac0.cw)); ) return ac0; } void extend_map(cell *c, int d) { WHD( Xprintf("EXTEND %p %d\n", c, d); ) if(c->master->c7 != c) { while(c->master->c7 != c) { WHD( Xprintf("%p direction 0 corresponds to %p direction %d\n", c, c->mov[0], c->spin(0)); ) d = c->spin(0); c = c->mov[0]; } // c move 0 equals c' move spin(0) extend_map(c, d); extend_map(c, fixdir(d-1, c)); extend_map(c, fixdir(d+1, c)); return; } clear_mapping(); // we generate a local map from an Euclidean grid to the // hyperbolic grid we build. // we fill the equilateral triangle with the following vertices: loc vc[3]; vc[0] = loc(0,0); vc[1] = param; vc[2] = param * loc(0,1); heptspin hs(c->master, d, false); auto& ac0 = set_heptspin(vc[0], hs); ac0.mindir = -1; auto& ac1 = set_heptspin(vc[1], hs + wstep - 3); ac1.mindir = 0; auto& ac2 = set_heptspin(vc[2], hs + 1 + wstep - 4); ac2.mindir = 1; if(nonorientable && param.first == param.second) { int x = param.first; if(ac1.cw.mirrored != hs.mirrored) ac1.cw--; if(ac2.cw.mirrored != hs.mirrored) ac2.cw--; for(int d=0; d<3; d++) for(int k=0; k<3; k++) for(int i=0; i= 2 && rel.first >= 2 - rel.second) { build(start, 0, true); build(end, 3, false); rel.first -= 2; } while(rel.second >= 2) { build(start, 1, true); build(end, 4, false); rel.second -= 2; } while(rel.second <= -2) { build(start, 5, true); build(end, 2, false); rel.second += 2; rel.first -= 2; } while((rel.first>0 && rel.second > 0) | (rel.first > 1 && rel.second < 0)) { build(start, 0, true); build(end, 3, false); rel.first -= 2; } for(int k=0; k<6; k++) if(start + eudir(k+2*i) == end) build(start, k, true); if(start != end) { Xprintf("assertion failed: start %s == end %s\n", disp(start), disp(end)); exit(1); } } // now we can fill the interior of our big equilateral triangle loc at = vc[0]; while(true) { auto& wc = get_mapping(at); int dx = wc.rdir; auto at1 = at + eudir(dx); auto& wc1 = get_mapping(at1); WHD( Xprintf("%s (%d) %s (%d)\n", disp(at), dx, disp(at1), wc1.rdir); ) int df = wc1.rdir - dx; if(df < 0) df += 6; if(df == 3) break; switch(df) { case 0: case 4: case 5: at = at1; continue; case 2: { conn(at, dx+1); wc.rdir = (dx+1) % 6; break; } case 1: { auto at2 = at + eudir(dx+1); auto& wc2 = get_mapping(at2); if(wc2.cw.c) { at = at1; continue; } wc.rdir = (dx+1) % 6; conn(at, (dx+1) % 6); conn(at1, (dx+2) % 6); conn(at2, (dx+0) % 6); wc1.rdir = -1; wc2.rdir = dx; break; } default: Xprintf("case unhandled %d\n", df); exit(1); } } WHD( Xprintf("DONE\n\n"); ) } hyperpoint loctoh_ort(loc at) { return hpxyz(at.first, at.second, 1); } hyperpoint corner_coords[7] = { hpxyz(2, -1, 0), hpxyz(1, 1, 0), hpxyz(-1, 2, 0), hpxyz(-2, 1, 0), hpxyz(-1, -1, 0), hpxyz(1, -2, 0), hpxyz(0, 0, 0) // center, not a corner }; hyperpoint cornmul(const transmatrix& corners, const hyperpoint& c) { if(sphere) { ld cmin = c[0] * c[1] * c[2] * (6 - S7); return corners * hpxyz(c[0] + cmin, c[1] + cmin, c[2] + cmin); } else return corners * c; } hyperpoint atz(const transmatrix& T, const transmatrix& corners, loc at, int cornerid = 6, ld cf = 3) { int sp = 0; again: auto corner = corners * hyperpoint_vec::operator+ (loctoh_ort(at), hyperpoint_vec::operator/ (corner_coords[cornerid], cf)); if(corner[1] < -1e-6 || corner[2] < -1e-6) { at = at * eudir(1); if(cornerid < 6) cornerid = (1 + cornerid) % 6; sp++; goto again; } if(sp>3) sp -= 6; return normalize(spin(2*M_PI*sp/S7) * cornmul(T, corner)); } transmatrix Tf[8][32][32][6]; transmatrix corners; transmatrix dir_matrix(int i) { cell cc; cc.type = S7; return spin(-alpha) * build_matrix( C0, ddspin(&cc, i) * xpush(tessf) * C0, ddspin(&cc, i+1) * xpush(tessf) * C0 ); } void prepare_matrices() { corners = inverse(build_matrix( loctoh_ort(loc(0,0)), loctoh_ort(param), loctoh_ort(param * loc(0,1)) )); for(int i=0; i, loc> center_locs; void compute_geometry() { center_locs.clear(); if(on) { int x = param.first; int y = param.second; area = ((2*x+y) * (2*x+y) + y*y*3) / 4; next = hpxyz(x+y/2., -y * sqrt(3) / 2, 0); scale = 1 / hypot2(next); crossf *= scale; hepvdist *= scale; rhexf *= scale; // spin = spintox(next); // ispin = rspintox(next); alpha = -atan2(next[1], next[0]) * 6 / S7; base_distlimit = (base_distlimit + log(scale) / log(2.618)) / scale; if(base_distlimit > 30) base_distlimit = 30; prepare_matrices(); if(debug_geometry) Xprintf("scale = " LDF "\n", scale); } else { scale = 1; alpha = 0; } } loc config; loc internal_representation(loc v) { int& x = v.first, &y = v.second; while(x < 0 || y < 0 || (x == 0 && y > 0)) v = v * loc(0, 1); if(x > 8) x = 8; if(y > 8) y = 8; if(y > x) v = v * loc(1, -1); return v; } loc human_representation(loc v) { int& x = v.first, &y = v.second; while(x < 0 || y < 0 || (x == 0 && y > 0)) v = v * loc(0, 1); return v; } string operation_name() { if(!gp::on) { if(nonbitrunc) return XLAT("OFF"); else return XLAT("bitruncated"); } else if(param == loc(1, 0)) return XLAT("OFF"); else if(param == loc(1, 1)) return XLAT("bitruncated"); else if(param == loc(2, 0)) return XLAT("chamfered"); else if(param == loc(3, 0)) return XLAT("2x bitruncated"); else { auto p = human_representation(param); return "GP(" + its(p.first) + "," + its(p.second) + ")"; } } void whirl_set(loc xy, bool texture_remap) { #if CAP_TEXTURE auto old_tstate = texture::config.tstate; auto old_tstate_max = texture::config.tstate_max; #endif xy = internal_representation(xy); if(xy.second && xy.second != xy.first && nonorientable) { addMessage(XLAT("This does not work in non-orientable geometries")); xy.second = 0; } config = human_representation(xy); auto g = screens; if(xy.first == 0 && xy.second == 0) xy.first = 1; if(xy.first == 1 && xy.second == 0) { if(gp::on) stop_game_and_switch_mode(rg::bitrunc); if(!nonbitrunc) stop_game_and_switch_mode(rg::bitrunc); } else if(xy.first == 1 && xy.second == 1) { if(gp::on) stop_game_and_switch_mode(rg::bitrunc); if(nonbitrunc) stop_game_and_switch_mode(rg::bitrunc); } else { if(nonbitrunc) stop_game_and_switch_mode(rg::bitrunc); param = xy; stop_game_and_switch_mode(rg::gp); } start_game(); #if CAP_TEXTURE if(texture_remap) texture::config.remap(old_tstate, old_tstate_max); #endif screens = g; } string helptext() { return XLAT( "Goldberg polyhedra are obtained by adding extra hexagons to a dodecahedron. " "GP(x,y) means that, to get to a nearest non-hex from any non-hex, you should move x " "cells in any direction, turn right 60 degrees, and move y cells. " "HyperRogue generalizes this to any tesselation with 3 faces per vertex. " "By default HyperRogue uses bitruncation, which corresponds to GP(1,1)." ); } void show(bool texture_remap) { cmode = sm::SIDE; gamescreen(0); dialog::init(XLAT("Goldberg")); bool show_nonthree = !(texture_remap && (S7&1)); bool show_bitrunc = !(texture_remap && !(S7&1)); if(show_nonthree) { dialog::addBoolItem(XLAT("OFF"), param == loc(1,0), 'a'); dialog::lastItem().value = "GP(1,0)"; } if(show_bitrunc) { dialog::addBoolItem(XLAT("bitruncated"), param == loc(1,1), 'b'); dialog::lastItem().value = "GP(1,1)"; } if(show_nonthree) { dialog::addBoolItem(XLAT("chamfered"), param == loc(2,0), 'c'); dialog::lastItem().value = "GP(2,0)"; } dialog::addBoolItem(XLAT("2x bitruncated"), param == loc(3,0), 'd'); dialog::lastItem().value = "GP(3,0)"; dialog::addBreak(100); dialog::addSelItem("x", its(config.first), 'x'); dialog::addSelItem("y", its(config.second), 'y'); if(config.second && config.second != config.first && nonorientable) { dialog::addInfo(XLAT("This does not work in non-orientable geometries")); } else if((config.first-config.second)%3 && !show_nonthree) dialog::addInfo(XLAT("This pattern needs x-y divisible by 3")); else if(config == loc(1,1) && !show_bitrunc) dialog::addInfo(XLAT("Select bitruncated from the previous menu")); else dialog::addBoolItem(XLAT("select"), param == internal_representation(config), 'f'); dialog::addBreak(100); dialog::addHelp(); dialog::addBack(); dialog::display(); keyhandler = [show_nonthree, show_bitrunc, texture_remap] (int sym, int uni) { dialog::handleNavigation(sym, uni); if(uni == 'a' && show_nonthree) whirl_set(loc(1, 0), texture_remap); else if(uni == 'b' && show_bitrunc) whirl_set(loc(1, 1), texture_remap); else if(uni == 'c' && show_nonthree) whirl_set(loc(2, 0), texture_remap); else if(uni == 'd') whirl_set(loc(3, 0), texture_remap); else if(uni == 'f' && (config == loc(1,1) ? show_bitrunc : (show_nonthree || (config.first-config.second)%3 == 0))) whirl_set(config, texture_remap); else if(uni == 'x') dialog::editNumber(config.first, 1, 10, 1, 1, "x", helptext()); else if(uni == 'y') dialog::editNumber(config.second, 1, 10, 1, 1, "y", helptext()); else if(uni == 'z') swap(config.first, config.second); else if(uni == '?' || sym == SDLK_F1 || uni == 'h' || uni == '2') gotoHelp(helptext()); else if(doexiton(sym, uni)) popScreen(); }; } loc univ_param() { if(on) return param; else if(nonbitrunc) return loc(1,0); else return loc(1,1); } void configure(bool texture_remap = false) { auto l = univ_param(); param = l; config = human_representation(l); pushScreen([texture_remap] () { gp::show(texture_remap); }); } void be_in_triangle(local_info& li) { int sp = 0; auto& at = li.relative; again: auto corner = corners * loctoh_ort(at); if(corner[1] < -1e-6 || corner[2] < -1e-6) { at = at * eudir(1); sp++; goto again; } if(sp>3) sp -= 6; li.last_dir = fix7(li.last_dir - sp); } int length(loc p) { return eudist(p.first, p.second); } // from some point X, (0,0) is in distance dmain, param is in distance d0, and param*z is in distance d1 // what is the distance of at from X? int solve_triangle(int dmain, int d0, int d1, loc at) { loc centerloc(0, 0); auto rel = make_pair(d0-dmain, d1-dmain); if(center_locs.count(rel)) centerloc = center_locs[rel]; else { bool found = false; for(int y=-20; y<=20; y++) for(int x=-20; x<=20; x++) { loc c(x, y); int cc = length(c); int c0 = length(c - param); int c1 = length(c - param*loc(0,1)); if(c0-cc == d0-dmain && c1-cc == d1-dmain) found = true, centerloc = c; } if(!found && !quotient) Xprintf("Warning: centerloc not found: %d,%d,%d\n", dmain, d0, d1); center_locs[rel] = centerloc; } return dmain + length(centerloc-at) - length(centerloc); } array get_masters(cell *c) { if(gp::on) { auto li = get_local_info(c); be_in_triangle(li); auto cm = c->master; int i = li.last_dir; return make_array(cm->c7, createStep(cm, i)->c7, createStep(cm, fix7(i+1))->c7); } else return make_array(c->mov[0], c->mov[2], c->mov[4]); } int compute_dist(cell *c, int master_function(cell*)) { auto li = get_local_info(c); be_in_triangle(li); cell *cm = c->master->c7; int i = li.last_dir; auto at = li.relative; auto dmain = master_function(cm); auto d0 = master_function(createStep(cm->master, i)->c7); auto d1 = master_function(createStep(cm->master, fixdir(i+1, cm))->c7); return solve_triangle(dmain, d0, d1, at); } int dist_2() { return length(univ_param()); } int dist_3() { return length(univ_param() * loc(1,1)); } int dist_1() { return dist_3() - dist_2(); } }}