hyperrogue/rogueviz/antidesitter.cpp

454 lines
14 KiB
C++

// In our world, you cannot tell whether you are moving or not.
// In the world of HyperRogue,
// https://www.quora.com/Are-there-variations-of-Conways-Game-of-Life-based-upon-a-triangular-or-hexagonal-grid
// https://arunarjunakani.github.io/HexagonalGameOfLife/
// B2 S34
// Creating a roguelike in 2+1D anti-de Sitter spacetime seems hard, so let's start with a cellular automaton. The rules are like Conway's Game of life, except that hex tile are born at 2 neighbors and overcrowded at 3. (1/5)
// ./ht srange=7 -shothud 1 -csc .4 -rotspace -canvas-random 10 -prodperiod 0 -ads-ca-check -ads-ca-view -noscr simspeed=3 -ads-keys -ads-keylist xaxwxsxdx -ads-keylist 0 -zoom .95 -shot-1000 -animvideo 1800 ads-ca.mp4
// However, it takes 4 iterations for neighbors (brown) to affect a cell (red). (Information cannot travel faster than c -- with <4 iterations cells would depend on their own future.) The blue 'plague' shows which cells are affected by the original source. (2/5)
// ./ht plague=221 srange=7 -shothud 1 -csc .4 -rotspace -canvas-random 10 -prodperiod 0 -ads-ca-check -ads-ca-view -noscr simspeed=3 -ads-keys -ads-keylist xaxwxsxdx -ads-keylist 0 -zoom .95 -shot-1000 -ads-highlight -animvideo 1800 ads-ca-plague.mp4 -exit
// Here is how the situation changes when we don't just sit on a star and try to travel. In this viz we see the situation "at the current time" (of course IRL we would see the past states). (It is more fun to play with when you can control it manually, of course.) (3/5)
// (Why would we want a roguelike in adS spacetime? In our world we do not feel whether we are moving on not; in the world of HyperRogue, this is not the case. https://twitter.com/ZenoRogue/status/1418957609269440512) (4/5)
// (This why in HyperRogue we have long but narrow snakes, and ivies which grow, but nothing wider than a Kraken could move. This is because in HyperRogue time is added in in Euclidean way; anti-de Sitter spacetime adds time to the hyperbolic plane in a "better" way.) (5/5)
#include "../rogueviz/rogueviz.h"
namespace hr {
namespace ads {
shiftpoint kz(shiftpoint x) { x.h = hr::kz(x.h); x.shift = hr::kz(x.shift); return x; }
shiftmatrix kz(shiftmatrix x) { x.T = hr::kz(x.T); x.shift = hr::kz(x.shift); return x; }
vector<hyperpoint> hs;
int sq = 1;
ld sqf = sq;
int lim = 10;
struct star {
hyperpoint a;
hyperpoint b;
color_t col;
};
vector<star> stars;
bool ord(hyperpoint a, hyperpoint b) {
for(int i=0; i<4; i++) if(a[i] != b[i]) return a[i] > b[i];
return false;
}
void gen_get() {
for(auto g: gmatrix) hs.push_back(unshift(g.second*C0));
}
void gen_rational() {
dynamicval<eGeometry> g(geometry, gCubeTiling);
map<int, vector<pair<int, int>>> roots;
for(int t=-lim; t<=lim; t++)
for(int u=-lim; u<=lim; u++) roots[t*t+u*u].emplace_back(t, u);
for(int x=-lim; x<=lim; x++)
for(int y=-lim; y<=lim; y++)
/* for(int u=-lim; u<=lim; u++)
if(t*t+u*u == x*x+y*y+sq*sq) */
for(auto [t, u]: roots[x*x+y*y+sq*sq])
hs.push_back(hyperpoint(x/sqf, y/sqf, t/sqf, u/sqf));
println(hlog, "hs size = ", isize(hs));
}
void gen_stars() {
map<hyperpoint, color_t> cols;
for(auto h: hs) cols[h] = 0x000000FF | (hrand(0x1000000) << 8);
for(auto h1: hs) if(ord(h1, Hypc))
for(auto h2: hs) if(ord(h2, Hypc)) {
hyperpoint h = h1 - h2;
if(h[0]*h[0]+h[1]*h[1]-h[2]*h[2]-h[3]*h[3] == -2) if(hrand(100) < 100) {
stars.emplace_back(star{h1, h2, gradient(cols[h1], cols[h2], 0, .5, 1)});
hyperpoint hm = h1 * h2[3] - h2 * h1[3];
ld d = hypot(h2[3], h1[3]);
if(d == 0) continue;
hm /= d;
if(hm[2] < 0) hm = -hm;
// println(hlog, hm);
if(abs(hm[0]) < .1 && abs(hm[1]) < .1 && abs(hm[2]-1) < .1 && abs(hm[3]) < .1)
println(hlog, h1, " & ", h2, " -> ", hm);
}
}
println(hlog, "stars size = ", isize(stars));
}
void init_ads() {
}
color_t color_of(ld z) {
z *= 2;
if(z < -1) return 0;
if(z > 1) return 0;
if(z > 0) return gradient(0xFFFFFFFF, 0xFF000000, 0, z, 1);
return gradient(0xFFFFFFFF, 0xFF00, 0, z, -1);
}
transmatrix lorentz(int a, int b, ld v) {
transmatrix T = Id;
T[a][a] = T[b][b] = cosh(v);
T[a][b] = T[b][a] = sinh(v);
return T;
}
transmatrix current = Id;
void draw_ads() {
ld t = ticks / anims::period * 2 * M_PI;
static ld last_t = t;
no_find_player = true;
if(1) {
dynamicval<eGeometry> g(geometry, gCubeTiling);
current = cspin(2, 3, t - last_t) * current;
}
last_t = t;
if(stars.empty()) for(hyperpoint h: hs) {
hyperpoint h1;
if(1) {
dynamicval<eGeometry> g(geometry, gCubeTiling);
h1 = current * h;
}
hyperpoint h2 = h1; h2[2] = sqrt(h2[2] * h2[2] + h2[3] * h2[3]);
if(h1[2] < 0) continue;
color_t c = color_of(h1[3]);
if(c)
queuepoly(shiftless(rgpushxto0(h2)), cgi.shGem[0], c);
}
for(auto st: stars) {
hyperpoint hm;
if(1) {
dynamicval<eGeometry> g(geometry, gCubeTiling);
hyperpoint h1 = current * st.a;
hyperpoint h2 = current * st.b;
hm = h1 * h2[3] - h2 * h1[3];
ld d = hypot(h2[3], h1[3]);
if(d == 0) continue;
hm /= d;
if(hm[2] < 0) hm = -hm;
// ld ii = hm[2]*hm[2] - hm[0]*hm[0] - hm[1]*hm[1];
}
queuepoly(shiftless(rgpushxto0(hm)), cgi.shGem[0], st.col);
}
}
bool handleKey(int sym, int uni) {
dynamicval<eGeometry> g(geometry, gCubeTiling);
if(uni == 'a') { current = lorentz(0, 3, +.1) * current; return true; }
if(uni == 'd') { current = lorentz(0, 3, -.1) * current; return true; }
if(uni == 'w') { current = lorentz(1, 3, +.1) * current; return true; }
if(uni == 's') { current = lorentz(1, 3, -.1) * current; return true; }
return false;
}
void run_ads() {
rogueviz::rv_hook(hooks_frame, 100, draw_ads);
rogueviz::rv_hook(hooks_handleKey, 0, handleKey);
}
void auto_shift() {
current = spin(30*degree) * lorentz(0, 3, 1) * spin(-30*degree) * current;
}
map<cell*, vector<pair<cell*, int>> > causality;
map<cell*, int> cell_id;
set<cell*> cs;
int ms = 4;
int srange = 7;
map<cell*, shiftmatrix> where_matrix;
void check(cell *c, const shiftmatrix& S) {
auto p = hybrid::get_where(c);
if(causality.count(p.first)) return;
auto& caus = causality[p.first];
auto c1 = c;
shiftmatrix S1 = S;
for(int i=0; i<ms; i++) {
c1 = c1->cmove(c->type - 2);
S1 = S1 * currentmap->adj(c1, c->type-2);
}
where_matrix[p.first] = S;
hybrid::in_underlying_geometry([&] {
setdist(p.first, 4, nullptr);
p.first->land = laIce;
});
hybrid::test_at(p.first);
// time increases
auto p1 = hybrid::get_where(c1);
println(hlog, p.second, " -> ", p1.second);
for(int i=0; i<c->type-2; i++) {
cell *c2 = c1->cmove(i);
auto p2 = hybrid::get_where(c2);
bool ok = cs.count(p2.first);
hybrid::in_underlying_geometry([&] {
ok = celldist(p2.first) <= srange;
});
if(!ok) continue;
caus.emplace_back(p2.first, p.second - p2.second);
// (p,x) (p2,0) -> okay
// minimum p1 above p2
check(c2, S1 * currentmap->adj(c1, i));
}
}
map<cell*, int> noncausal;
bool randlive() { return hrand(100) < 20 ? 1 : 0; }
vector<cell*> compute_order;
map<cell*, int> index;
void fine() {
compute_order.clear();
index.clear();
set<cell*> unknown;
for(auto& p: causality) unknown.insert(p.first);
while(true) {
vector<cell*> to_remove;
println(hlog, "unknown = ", isize(unknown));
if(unknown.empty()) break;
for(auto u: unknown) {
bool ok = true;
for(auto pp: causality[u]) {
int need = noncausal[u] + 1 - pp.second;
if(noncausal[pp.first] < need) ok = false;
}
if(ok) {
to_remove.push_back(u);
noncausal[u]++;
}
}
if(to_remove.empty()) { println(hlog, "failed to remove"); return; }
for(auto r: to_remove) unknown.erase(r), compute_order.push_back(r);
}
int ids = 0;
for(auto& p: compute_order) index[p] = ids++;
vector<vector<pair<int, int>>> relative(ids);
for(auto p: compute_order) {
int i = index[p];
for(auto& pp: causality[p]) {
int j = index[pp.first];
int k = noncausal[p] - pp.second - noncausal[pp.first];
relative[i].emplace_back(j, k);
}
}
println(hlog, relative);
vector<int> unknown_neighbors(ids);
for(auto& p: compute_order) unknown_neighbors[index[p]] = p->type - isize(relative[index[p]]);
vector<int> noncausal_indexed(ids);
for(auto& p: compute_order) noncausal_indexed.push_back(noncausal[p]);
vector<vector<int>> ca_states;
println(hlog, "generating initial random");
for(int t=0; t<ms; t++) {
ca_states.emplace_back();
ca_states.back().resize(ids);
for(int j=0; j<ids; j++) ca_states[t][j] = randlive();
}
vector<int> lifecounts;
int steps = 50;
for(int t=ms; t<steps; t++) {
println(hlog, "generating step t=", t);
ca_states.emplace_back();
ca_states.back().resize(ids);
int countlife = 0;
for(int j=0; j<ids; j++) {
int lives = 0;
bool early = false;
for(auto [id, d]: relative[j]) {
if(t+d < 0) early = true;
else lives += ca_states[t+d][id];
}
if(early) {
ca_states[t][j] = randlive();
continue;
}
for(int i=0; i<unknown_neighbors[j]; i++) lives += randlive();
int old = ca_states[t-ms][j];
int newstate;
if(old == 0) newstate = lives == 2 ? 1 : 0;
if(old == 1) newstate = (lives == 3 || lives == 4) ? 1 : 0;
ca_states[t][j] = newstate;
countlife += newstate;
}
lifecounts.push_back(countlife);
}
for(int t=0; t<steps; t++)
for(int j=0; j<ids; j++) {
cell *h = hybrid::get_at(compute_order[j], noncausal_indexed[j] + t);
setdist(h, 7, nullptr);
if(t < ms) h->land = laIce;
else if(t < 2*ms) h->land = laJungle;
else h->land = laAlchemist;
h->wall = ca_states[t][j] ? waPlatform : waNone;
}
println(hlog, lifecounts);
}
void reset_to_underlying(cell *where) {
hrmap *m;
hybrid::in_underlying_geometry([&] { m = currentmap; });
currentmap = m;
geometry = hybrid::underlying;
pmodel = mdDisk;
cwt = centerover = where; View = Id;
check_cgi();
resetview();
}
void ads_ca_check() {
auto pc = hybrid::get_where(cwt.at).first;
hybrid::in_underlying_geometry([&] {
cs.insert(pc);
cs.insert(pc->cmove(0));
cs.insert(pc->cmove(1));
});
for(auto cc: cs)
println(hlog, "SET = ", cc);
check(cwt.at, shiftless(Id));
println(hlog, causality);
// at step t, for (c,i) in noncausal, change (c,i+t)
for(auto p: causality) noncausal[p.first] = 0;
vector<int> ncs;
map<cell*, cell*> source;
vector<int> changelist;
for(int it=0; it<1000; it++) {
int changes = 0;
for(auto p: causality)
for(auto pp: p.second) {
auto& here = noncausal[p.first];
auto cand = noncausal[pp.first] + pp.second;
if(cand < here) {
source[p.first] = pp.first;
here = cand;
changes++;
}
}
ncs.push_back(noncausal[pc]);
if(it == 998) println(hlog, noncausal);
if(it == 999) println(hlog, noncausal);
changelist.push_back(changes);
if(!changes) break;
}
println(hlog, ncs);
cell *x = pc;
for(int it=0; it<1000; it++) if(x) x = source[x];
if(!x) { println(hlog, "all fine!"); fine(); return; }
auto x1 = x;
vector<cell*> circ;
int dsum = 0;
while(true) {
x->item = itDiamond;
for(auto pp: causality[x]) if(pp.first == source[x]) {
dsum += pp.second - ms;
println(hlog, "delta = ", pp.second);
}
circ.push_back(x);
x = source[x];
if(x == x1) break;
}
int circum = isize(circ);
println(hlog, "circum = ", circum);
int anglesum = 0;
for(int i=0; i<circum; i++) {
int d1 = neighborId(circ[i], circ[gmod(i+1, circum)]);
int d2 = neighborId(circ[i], circ[gmod(i-1, circum)]);
int d = gmod(d2 - d1, circ[i]->type);
// println(hlog, "angle = ", d);
anglesum += d;
}
println(hlog, "anglesum = ", anglesum);
int area = 7 * (circum - 2) - 2 * anglesum;
println(hlog, "area = ", area);
println(hlog, "dsum = ", dsum);
reset_to_underlying(x1);
}
hyperpoint findflat(shiftpoint S0) {
hyperpoint h = S0.h;
ld at = atan2(h[3], h[2]);
h = cspin(2, 3, at) * cspin(0, 1, at) * h;
return h;
}
bool view_ads_ca() {
if(1) {
dynamicval<eGeometry> g(geometry, gNormal);
dynamicval<eModel> p(pmodel, mdDisk);
check_cgi();
initquickqueue();
for(int i=0; i<isize(compute_order); i++) {
hyperpoint h;
if(1) {
dynamicval<eGeometry> b(geometry, gCubeTiling);
cell *c = compute_order[i];
shiftmatrix S = where_matrix[c];
h = findflat(S * C0);
}
queuepoly(shiftless(rgpushxto0(h)), cgi.shGem[0], 0xFFD500FF);
}
quickqueue();
}
check_cgi();
return true;
}
void run_ads_ca_view() {
nomap = true;
no_find_player = true;
rogueviz::rv_hook(hooks_prestats, 100, view_ads_ca);
}
auto shot_hooks = arg::add3("-ads", run_ads) + arg::add3("-ads-rat", gen_rational) + arg::add3("-ads-stars", gen_stars) + arg::add3("-ads-get", gen_get) + arg::add3("-ads-shift", auto_shift)
+ arg::add3("-ads-ca-check", ads_ca_check) + arg::add3("-ads-ca-view", run_ads_ca_view);
}
}