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); } struct goldberg_mapping_t { cellwalker cw; char rdir; }; 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_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, "[%d,%d]", 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/%d:%d:%d]", cw.c, cw.c?cw.c->type:-1, cw.spin, cw.mirrored); return bufs[bufid]; } int spawn; #define WHD(x) // x void conn1(loc at, int dir, int dir1) { auto& wc = get_mapping(at); auto& wc1 = get_mapping(at + eudir(dir)); int cdir = (wc.cw + dir).spin; WHD( printf(" connection %s/%d %s ", disp(at), dir, dcw(wc.cw+dir)); ) if(!wc1.cw.c) { wc1.cw.c = wc.cw.c->mov[cdir]; if(wc1.cw.c) { // wc1.c/wc.c->spin(cdir) == dir1 wc1.cw = (wc.cw + dir + wstep - dir1); WHD( printf("(pulled) "); ) } if(!wc1.cw.c) { wc1.cw.c = newCell(6, wc.cw.c->master); spawn++; // 0 for wc1.c should be dir1 wc1.cw.mirrored = wc.cw.mirrored; wc1.cw.spin = fix6(wc.cw.mirrored ? dir1 : -dir1); WHD( printf("(created) "); ) } } int cdir1 = (wc1.cw + dir1).spin; WHD( printf("%s ", dcw(wc1.cw+dir1)); ) if(wc.cw.c->mov[cdir] && wc.cw.c->mov[cdir] != wc1.cw.c) { WHD( printf("FAIL: %p\n", wc.cw.c->mov[cdir]); exit(1); ) } if(wc.cw.c->mov[cdir]) { if(wc.cw.c->spin(cdir) != cdir1) { printf("warning: wrong spin: %d vs %d\n", wc.cw.c->spin(cdir), cdir1); exit(1); } } WHD(printf("ok\n"); ) wc.cw.c->mov[cdir] = wc1.cw.c; tsetspin(wc.cw.c->spintable, cdir, cdir1 + (wc.cw.mirrored != wc1.cw.mirrored ? 8 : 0)); } void conn(loc at, int dir) { conn1(at, fix6(dir), fix6(dir+3)); conn1(at + eudir(dir), fix6(dir+3), fix6(dir)); } void extend_map(cell *c, int d) { WHD( printf("EXTEND %p %d\n", c, d); ) if(c->master->c7 != c) { while(c->master->c7 != c) { WHD( printf("%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); // get_mapping(loc) gives our local map. We set the vertices first { auto h = c->master; auto& ac0 = get_mapping(vc[0]); ac0.cw = cellwalker(h->c7, d); WHD( printf("%s : %s\n", disp(vc[0]), dcw(ac0.cw)); ) // 3 ~ h->spin(d) auto& ac1 = get_mapping(vc[1]); ac1.cw = cellwalker(createStep(h, d)->c7, fix7(h->spin(d) - (h->mirror(d) ? -3 : 3)), h->mirror(d)); WHD( printf("%s : %s\n", disp(vc[1]), dcw(ac1.cw)); ) auto& ac2 = get_mapping(vc[2]); int d1 = (d+1)%S7; ac2.cw = cellwalker(createStep(h, d1)->c7, fix7(h->spin(d1) - (h->mirror(d1) ? -4 : 4)), h->mirror(d1)); WHD( printf("%s : %s\n", disp(vc[2]), dcw(ac2.cw)); ) // 4 ~ h->spin(d+1) } // then we set the edges of our big equilateral triangle (in a symmetric way) for(int i=0; i<3; i++) { loc start = vc[i]; loc end = vc[(i+1)%3]; WHD( printf("from %s to %s\n", disp(start), disp(end)); ) loc rel = param; auto build = [&] (loc& at, int dx, bool forward) { int dx1 = dx + 2*i; WHD( printf("%s %d .. %s %d\n", disp(at), dx1, disp(at + eudir(dx1)), fix6(dx1+3)); ) conn(at, dx1); if(forward) get_mapping(at).rdir = fix6(dx1); else get_mapping(at+eudir(dx1)).rdir = fix6(dx1+3); at = at + eudir(dx1); }; while(rel.first >= 2) { 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.first>0 && rel.second) { 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) { printf("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( printf("%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: printf("case unhandled %d\n", df); exit(1); } } WHD( printf("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 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) * 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]); base_distlimit = (base_distlimit + log(scale) / log(2.618)) / scale; if(base_distlimit > 30) base_distlimit = 30; prepare_matrices(); } else { scale = 1; alpha = 0; } } 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 return "GP(" + its(param.first) + "," + its(param.second) + ")"; } int config_x, config_y; void whirl_set(int x, int y, bool texture_remap) { auto old_tstate = texture::config.tstate; auto old_tstate_max = texture::config.tstate_max; if(y < 0) { y = -y; x -= y; } if(x < 0) { x = -x; y = -y; } if(x < y) swap(x, y); if(x > 8) x = 8; if(y > 8) y = 8; if(y && elliptic) { if(x==y) addMessage("GP(x,x) not implemented yet for elliptic geometry"); else addMessage("This does not work in elliptic geometry"); y = 0; } config_x = x; config_y = y; param = loc(x, y); auto g = screens; if(x == 1 && y == 0) { if(gp::on) restartGame(rg::bitrunc); if(!nonbitrunc) restartGame(rg::bitrunc); } else if(x == 1 && y == 1) { if(gp::on) restartGame(rg::bitrunc); if(nonbitrunc) restartGame(rg::bitrunc); } else { if(nonbitrunc) restartGame(rg::bitrunc); param = loc(x, y); restartGame(rg::gp); } if(texture_remap) texture::config.remap(old_tstate, old_tstate_max); screens = g; } string helptext() { return "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)); if(show_nonthree) { dialog::addBoolItem(XLAT("OFF"), param == loc(1,0), 'a'); dialog::lastItem().value = "GP(1,0)"; } 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_x), 'x'); dialog::addSelItem("y", its(config_y), 'y'); if((config_x-config_y)%3 && !show_nonthree) dialog::addInfo("This pattern needs x-y divisible by 3"); else dialog::addBoolItem(XLAT("select"), param == loc(config_x, config_y), 'f'); dialog::addBreak(100); dialog::addItem(XLAT("help"), SDLK_F1); dialog::addItem(XLAT("back"), '0'); dialog::display(); keyhandler = [show_nonthree, texture_remap] (int sym, int uni) { dialog::handleNavigation(sym, uni); if(uni == 'a' && show_nonthree) whirl_set(1, 0, texture_remap); else if(uni == 'b') whirl_set(1, 1, texture_remap); else if(uni == 'c' && show_nonthree) whirl_set(2, 0, texture_remap); else if(uni == 'd') whirl_set(3, 0, texture_remap); else if(uni == 'f' && (show_nonthree || (config_x-config_y)%3 == 0)) whirl_set(config_x, config_y, texture_remap); else if(uni == 'x') dialog::editNumber(config_x, 1, 10, 1, 1, "x", helptext()); else if(uni == 'y') dialog::editNumber(config_y, 1, 10, 1, 1, "y", helptext()); 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(); config_x = l.first, config_y = l.second; param = loc(config_x, config_y); 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) printf("Warning: centerloc not found: %d,%d,%d\n", dmain, d0, d1); center_locs[rel] = centerloc; } return dmain + length(centerloc-at) - length(centerloc); } 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(); } }