diff --git a/rogueviz/ads/ads-game.cpp b/rogueviz/ads/ads-game.cpp new file mode 100644 index 00000000..8001086c --- /dev/null +++ b/rogueviz/ads/ads-game.cpp @@ -0,0 +1,64 @@ +#include "../rogueviz.h" + +#include "math.cpp" +#include "globals.cpp" +#include "shapes.cpp" +#include "map.cpp" +#include "control.cpp" +#include "display.cpp" +#include "menu.cpp" + +namespace hr { + +namespace ads_game { + +void change_default_key(int key, int val) { + char* t = multi::scfg.keyaction; + t[key] = val; + #if CAP_CONFIG + set_saver_default(t[key]); + #endif + } + +void run_ads_game() { + + change_default_key('s', 16 + 0); + change_default_key('a', 16 + 1); + change_default_key('w', 16 + 2); + change_default_key('d', 16 + 3); + change_default_key('f', 16 + 4); + change_default_key('p', 16 + 5); + change_default_key('t', 16 + 6); + change_default_key('o', 16 + 7); + change_default_key('m', 16 + 8); + + nomap = true; + no_find_player = true; + vctr = cwt.at; + cell *c = hybrid::get_where(vctr).first; + hybrid::in_underlying_geometry([&] { + gen_terrain(c, ci_at[c], -2); + forCellEx(c1, c) ci_at[c1].type = wtNone; + ci_at[c].type = wtNone; + }); + vctrV = ads_matrix(Id, 0); + rogueviz::rv_hook(hooks_prestats, 100, view_ads_game); + rogueviz::rv_hook(hooks_handleKey, 0, handleKey); + rogueviz::rv_hook(shmup::hooks_turn, 0, ads_turn); + } + +auto shot_hooks = + arg::add3("-ads-game", run_ads_game) ++ addHook(hooks_configfile, 100, [] { + param_f(simspeed, "ads_game_simspeed") + -> editable(0, 2*TAU, TAU/4, "game speed", "Controls the speed of the game.", 's'); + param_f(accel, "ads_game_accel") + -> editable(0, 30, 1, "acceleration", "Controls the speed of your ship's acceleration.", 'a'); + param_b(auto_rotate, "ads_auto_rotate") + -> editable("automatically rotate the screen", 'r'); + param_b(view_proper_times, "ads_display") + -> editable("display the proper times", 't'); + }); + +} +} diff --git a/rogueviz/ads/control.cpp b/rogueviz/ads/control.cpp new file mode 100644 index 00000000..f6f1194d --- /dev/null +++ b/rogueviz/ads/control.cpp @@ -0,0 +1,160 @@ +namespace hr { + +namespace ads_game { + +vector move_names = { "acc down", "acc left", "acc up", "acc right", "fire", "pause", "display times", "switch spin", "menu" }; + +void fire() { + auto g = hybrid::get_where(vctr); + auto c = g.first; + if(g.second != 0) println(hlog, "WARNING: vctr not zeroed"); + + ads_matrix S0 = ads_inverse(current * vctrV) * spin(ang*degree); + + ads_matrix S1 = S0 * lorentz(0, 2, 3); // 0.995c + + auto& ro = ci_at[c].rocks; + ro.emplace_back(rockinfo{1, S1, 0xC0C0FFFF }); + auto& r = ro.back(); + + ads_matrix Scell(Id, 0); + cell *lcell = vctr; + auto wcell = hybrid::get_where(lcell); + + int steps = 0; + + compute_life(vctr, unshift(r.at), [&] (cell *c1, ld t) { + if(true) for(int i=0; itype; i++) { + auto lcell1 = lcell->cmove(i); + auto wcell1 = hybrid::get_where(lcell1); + if(wcell1.first == c1) { + Scell = Scell * currentmap->adj(lcell, i); + optimize_shift(Scell); + lcell = lcell1; + wcell = wcell1; + adjust_to_zero(Scell, wcell, cgi.plevel); + steps++; + lcell = hybrid::get_at(wcell.first, 0); + break; + } + } + if(true) if(wcell.first != c1) { + println(hlog, "warning: got lost after ", steps, " steps"); + println(hlog, wcell); + println(hlog, c1); + println(hlog, "their distance is ", PIU(celldistance(wcell.first, c1))); + return true; + } + auto& ci = ci_at[c1]; + hybrid::in_underlying_geometry([&] { + gen_terrain(c1, ci); + gen_rocks(c1, ci, 2); + }); + if(among(ci.type, wtSolid, wtDestructible)) { + r.life_end = t; + + auto Scell_inv = ads_inverse(Scell); + Scell_inv = Scell_inv * r.at; + Scell_inv = Scell_inv * ads_matrix(Id, t); + optimize_shift(Scell_inv); + + auto X = ads_inverse(Scell); + X = X * (r.at * ads_matrix(Id, t)); + optimize_shift(X); + + ads_matrix prel = ads_inverse(S0) * r.at * ads_matrix(Id, t); + + println(hlog, "crashed: proper time = ", t/TAU, " wall time = ", Scell_inv.shift / TAU, " player time = ", (prel.shift+ship_pt) / TAU, " start = ", ship_pt / TAU); + if(abs(X.shift - Scell_inv.shift) > .2) { + println(hlog, "INTRANSITIVITY ERROR! ", X.shift, " vs ", Scell_inv.shift); + exit(1); + } + + return true; + } + return false; + }); + } + +bool handleKey(int sym, int uni) { + /* + if(uni == 'p') paused = !paused; + + if(among(uni, 'a', 'd', 's', 'w')) return true; + + if(uni == 't') { view_proper_times = !view_proper_times; return true; } + if(uni == 'o') { auto_rotate = !auto_rotate; return true; } + + if(uni == 'f') fire(); + */ + + if(sym > 0 && sym < 512 && (cmode & sm::NORMAL)) { + char* t = multi::scfg.keyaction; + if(t[sym] >= 16 && t[sym] < 32) return true; + } + + return false; + } + +void apply_lorentz(transmatrix lor) { + current = ads_matrix(lor, 0) * current; + } + +bool ads_turn(int idelta) { + multi::handleInput(idelta); + ld delta = idelta / anims::period; + + if(!(cmode & sm::NORMAL)) return false; + + auto& a = multi::actionspressed; + auto& la = multi::lactionpressed; + + vector ap; + for(int i=0; i g(geometry, geometry == gRotSpace ? geometry : gCubeTiling); + + /* proper time passed */ + ld pt = delta * simspeed; + + bool left = a[16+1]; + bool right = a[16+3]; + bool up = a[16+2]; + bool down = a[16]; + + if(left) apply_lorentz(lorentz(0, 2, delta*accel)), ang = 180; + if(right) apply_lorentz(lorentz(0, 2, -delta*accel)), ang = 0; + if(up) apply_lorentz(lorentz(1, 2, delta*accel)), ang = 90; + if(down) apply_lorentz(lorentz(1, 2, -delta*accel)), ang = 270; + + if(left && up) ang = 135; + if(left && down) ang = 225; + if(right && up) ang = 45; + if(right && down) ang = 315; + + current.T = cspin(3, 2, pt) * current.T; + optimize_shift(current); + hassert(eqmatrix(chg_shift(current.shift) * current.T, unshift(current))); + + if(auto_rotate) + current.T = cspin(1, 0, pt) * current.T; + else + ang += pt / degree; + + ship_pt += pt; + } + + fixmatrix_ads(current.T); + fixmatrix_ads(vctrV.T); + + return true; + } + +}} diff --git a/rogueviz/ads/display.cpp b/rogueviz/ads/display.cpp new file mode 100644 index 00000000..be90882d --- /dev/null +++ b/rogueviz/ads/display.cpp @@ -0,0 +1,225 @@ +namespace hr { + +namespace ads_game { + +flatresult findflat(shiftpoint h) { + return cross0(current * rgpushxto0(h)); + } + +void draw_game_cell(cell *cs, ads_matrix V, ld plev) { + auto g = PIA( hybrid::get_where(cs) ); + adjust_to_zero(V, g, plev); + auto c = g.first; + + flatresult center; + vector hlist; + + hybrid::in_actual([&]{ + for(int i=0; i<=c->type; i++) { + hyperpoint ha = hybrid::get_corner(c, i, 2, 0); + hlist.push_back(findflat(V * ha)); + } + center = findflat(V * C0); + }); + + if(1) { + ld d = hdist0(center.h); + if(d < vctr_dist) vctr_dist = d, vctr = PIA( hybrid::get_at(c, 0) ), vctrV = V; + } + + auto& ci = ci_at[c]; + if(ci.mpd_terrain > 0) { + if(!gen_budget) return; + gen_budget--; + } + gen_terrain(c, ci); + gen_rocks(c, ci, 0); + + auto& t = ci.type; + + if(t == wtGate) { + ld minv = hlist[0].shift; + ld maxv = hlist[0].shift; + for(auto& h: hlist) { + ld v = h.shift; + if(v < minv) minv = v; + if(v > maxv) maxv = v; + } + + auto draw_slice = [&] (ld a, ld b, color_t col) { + vector slice; + for(int i=0; itype; i++) { + if(hlist[i].shift >= a && hlist[i].shift <= b) + slice.push_back(hlist[i].h); + if((hlist[i].shift < a) ^ (hlist[i+1].shift < a)) { + ld p = ilerp(hlist[i].shift, hlist[i+1].shift, a); + hyperpoint h1 = lerp(hlist[i].h, hlist[i+1].h, p); + slice.push_back(h1); + } + if((hlist[i].shift > b) ^ (hlist[i+1].shift > b)) { + ld p = ilerp(hlist[i].shift, hlist[i+1].shift, b); + hyperpoint h1 = lerp(hlist[i].h, hlist[i+1].h, p); + slice.push_back(h1); + } + if(hlist[i+1].shift < a && hlist[i].shift > b) + swap((&slice.back())[-1], slice.back()); + } + if(isize(slice) < 3) return; + for(auto e: slice) curvepoint(e); + curvepoint(slice[0]); + queuecurve(shiftless(Id), 0xFFFFFFFF, col, PPR::LINE); + }; + + for(int v=floor(minv); v> 8, 0); + queuecurve(shiftless(Id), 0x101010FF, col, PPR::WALL); + } + + if(view_proper_times) { + string str = format(tformat, center.shift / TAU); + queuestr(shiftless(rgpushxto0(center.h)), .1, str, 0xFF4040, 8); + } + + for(auto& rock: ci.rocks) { + + vector pts; + + vector& shape = rock.type ? shape_missile : shape_rock; + + flatresult fr_main; + if(1) hybrid::in_actual([&]{ + dynamicval b(geometry, gRotSpace); + auto h = V * rock.at; + fr_main = cross0(current * h); + }); + if(fr_main.shift < rock.life_start || fr_main.shift > rock.life_end) continue; + + for(int i=0; i p(pmodel, mdDisk); + check_cgi(); + cgi.require_basics(); + cgi.require_shapes(); + ptds.clear(); + calcparam(); + clearaura(); + make_shape(); + + set visited; + queue> dq; + auto visit = [&] (cell *c, const ads_matrix& V) { + auto w = hybrid::get_where(c); + if(visited.count(w.first)) return; + visited.insert(w.first); + dq.emplace(c, V); + }; + + hybrid::in_actual([&] { + dynamicval b(geometry, gRotSpace); + visit(vctr, vctrV); + vctr_dist = HUGE_VAL; + }); + + int i = 0; + while(!dq.empty()) { + i++; if(i > 1000) break; + auto& p = dq.front(); + cell *c = p.first; + ads_matrix V = p.second; + dq.pop(); + + draw_game_cell(c, V, plev); + + hybrid::in_actual([&] { + for(int i=0; itype-2; i++) { + cell *c2 = c->cmove(i); + auto V1 = V * currentmap->adj(c, i); + optimize_shift(V1); + visit(c2, V1); + } + }); + } + + if(true) { + poly_outline = 0xFF; + queuepolyat(shiftless(spin(ang*degree) * Id), shShip, 0x2020FFFF, PPR::LINE); + + if(view_proper_times) { + string str = format(tformat, ship_pt / TAU); + queuestr(shiftless(Id), .1, str, 0xFFFFFF, 8); + } + } + + if(false) queuepolyat(shiftless(rgpushxto0(base.h)), cgi.shGem[0], 0x2020FFFF, PPR::LINE); + + drawqueue(); + drawaura(); + }); + check_cgi(); + return true; + } + +}} diff --git a/rogueviz/ads/globals.cpp b/rogueviz/ads/globals.cpp new file mode 100644 index 00000000..56969e81 --- /dev/null +++ b/rogueviz/ads/globals.cpp @@ -0,0 +1,43 @@ +namespace hr { + +namespace ads_game { + +/** simulation speed */ +ld simspeed = TAU; + +/** by how much do WAS keys accelerate */ +ld accel = 6; + +/** transform world coordinates to ship coordinates */ +ads_matrix current; + +/** SL cell closest to the ship */ +cell *vctr; + +/** world coordinates of vctr -- technically, this is a shiftmatrix */ +ads_matrix vctrV; + +/** how far is vctr from the ship */ +ld vctr_dist; + +/** how is the ship shape rotated */ +ld ang = 0; + +/** ship's current proper time */ +ld ship_pt; + +/** is the game paused */ +bool paused; + +/** auto-rotate the screen */ +bool auto_rotate = false; + +/** should we display the proper times of all objects */ +bool view_proper_times = false; + +/** format for displaying time */ +const char *tformat = "%.2f"; + +void game_menu(); + +}} diff --git a/rogueviz/ads/map.cpp b/rogueviz/ads/map.cpp new file mode 100644 index 00000000..d2b954cd --- /dev/null +++ b/rogueviz/ads/map.cpp @@ -0,0 +1,141 @@ +namespace hr { + +namespace ads_game { + +struct rockinfo { + int type; + ads_matrix at; + color_t col; + + ld life_start, life_end; + + rockinfo(int t, const ads_matrix& T, color_t _col) : type(t), at(T), col(_col) { + life_start = -HUGE_VAL; + life_end = HUGE_VAL; + } + }; + +enum eWalltype { wtNone, wtDestructible, wtSolid, wtGate }; + +struct cellinfo { + int mpd_terrain; /* 0 = fully generated terrain */ + int rock_dist; /* rocks generated in this radius */ + vector rocks; + eWalltype type; + cellinfo() { + mpd_terrain = 4; + rock_dist = -1; + type = wtNone; + } + }; + +std::unordered_map ci_at; + +using worldline_visitor = std::function; + +void compute_life(cell *c, transmatrix S1, const worldline_visitor& wv) { + ld t = 0; + + int iter = 0; + cell *cur_c = c; + auto cur_w = hybrid::get_where(c); + while(t < 2 * M_PI) { + iter++; + auto last_w = cur_w; + auto next_w = cur_w; + transmatrix next_S1; + ld next_t; + ld last_time = t; + cell *next_c = nullptr; + binsearch(t, t+M_PI/2, [&] (ld t1) { + S1 = S1 * chg_shift(t1 - last_time); + last_time = t1; + virtualRebase(cur_c, S1); + cur_w = hybrid::get_where(cur_c); + if(cur_w.first != last_w.first) { + next_c = cur_c; + next_w = cur_w; + next_S1 = S1; + next_t = t1; + return true; + } + return false; + }, 20); + if(!next_c) return; + S1 = next_S1; + cur_w = next_w; + t = next_t; + cur_c = next_c; + if(iter > 1000) { + println(hlog, "compute_life c=", cur_c, " w=", cur_w, "t=", t, " S1=", S1); + fixmatrix_ads(S1); + } + if(iter > 1100) break; + if(wv(cur_w.first, t)) break; + } + } + +map genstats; + +int gen_budget; + +void gen_terrain(cell *c, cellinfo& ci, int level = 0) { + if(level >= ci.mpd_terrain) return; + if(ci.mpd_terrain > level + 1) gen_terrain(c, ci, level+1); + forCellCM(c1, c) gen_terrain(c1, ci_at[c1], level+1); + genstats[level]++; + + if(level == 2) { + int r = hrand(100); + if(r < 5) { + forCellCM(c1, c) if(hrand(100) < 50) + forCellCM(c2, c1) if(hrand(100) < 50) + if(ci_at[c2].type == wtNone) ci_at[c2].type = wtDestructible; + } + else if(r < 10) { + forCellCM(c1, c) if(hrand(100) < 50) + forCellCM(c2, c1) if(hrand(100) < 50) + if(ci_at[c2].type < wtSolid) + ci_at[c2].type = wtSolid; + } + else if(r < 12) + ci_at[c].type = wtGate; + } + ci.mpd_terrain = level; + } + +void gen_rocks(cell *c, cellinfo& ci, int radius) { + if(radius <= ci.rock_dist) return; + if(ci.rock_dist < radius - 1) gen_rocks(c, ci, radius-1); + forCellCM(c1, c) gen_rocks(c1, ci_at[c1], radius-1); + if(geometry != gNormal) { println(hlog, "wrong geometry detected in gen_rocks 1!"); exit(1); } + + if(radius == 0) { + hybrid::in_actual([&] { + int q = rpoisson(.05); + + auto add_rock = [&] (rockinfo&& r) { + if(geometry != gRotSpace) { println(hlog, "wrong geometry detected in gen_rocks 2!"); exit(1); } + compute_life(hybrid::get_at(c, 0), unshift(r.at), [&] (cell *c, ld t) { + auto& ci = ci_at[c]; + hybrid::in_underlying_geometry([&] { gen_terrain(c, ci); }); + ci.type = wtNone; + return false; + }); + ci.rocks.emplace_back(r); + }; + + for(int i=0; i=0; y--) { + ld dp = 0; + for(int z=0; z<4; z++) dp += T[z][x] * T[z][y] * sig(z); + + if(y == x) dp = 1 - sqrt(sig(x)/dp); + else dp *= sig(y); + + for(int z=0; z<4; z++) T[z][x] -= dp * T[z][y]; + } + } + +/* get_at(g) is at V; adjust g.second==0 and V accordingly */ +void adjust_to_zero(ads_matrix& V, pair& g, ld plev) { + V.shift -= plev * g.second; + g.second = 0; + } + +/** by how many cycles should we shift */ +ld get_shift_cycles(ld shift) { + return floor(shift / TAU + .5) * TAU; + } + +/** this is uzpush(-x) */ +transmatrix chg_shift(ld x) { + return cspin(2, 3, x) * cspin(0, 1, x); + } + +ads_point ads_matrix::operator*(ads_point h) { + auto& T = *this; + optimize_shift(h); + ld sh = get_shift_cycles(h.shift); + h.shift -= sh; + auto res0 = T; + optimize_shift(res0); + auto res1 = res0 * chg_shift(h.shift); + optimize_shift(res1); + res1.shift += get_shift_cycles(res0.shift - res1.shift); + auto res2 = res1 * h.h; + optimize_shift(res2); + res2.shift += get_shift_cycles(res1.shift - res2.shift); + res2.shift += sh; + return res2; + } + +ads_matrix ads_matrix::operator*(ads_matrix h) { + auto& T = *this; + optimize_shift(h); + ld sh = get_shift_cycles(h.shift); + h.shift -= sh; + + auto res0 = T; + optimize_shift(res0); + auto res1 = res0 * chg_shift(h.shift); + optimize_shift(res1); + res1.shift += get_shift_cycles(res0.shift - res1.shift); + auto res2 = res1 * h.T; + optimize_shift(res2); + res2.shift += get_shift_cycles(res1.shift - res2.shift); + res2.shift += sh; + return res2; + } + +ads_matrix ads_inverse(const ads_matrix& T) { + ads_matrix res(inverse(unshift(T)), 0); + ads_matrix m = res * T; + optimize_shift(m); + res.shift -= m.shift; + return res; + } + +struct flatresult { + hyperpoint h; + ld shift; + }; + +extern ads_matrix current; + +/** T represents a worldline of some object; find when does this worldline cross the time=0 slice. + * shift is T's proper time at the point of crossing, and h=(x,y,z) is the Minkowski hyperboloid point where it crosses. + **/ + +flatresult cross0(ads_matrix hz) { + + transmatrix deg90 = chg_shift(90*degree); + hyperpoint uhz = unshift(hz * C0); + hyperpoint uhz1 = unshift(hz * deg90 * C0); + + ld cost, sint, tant; + ld t; + + if(uhz1[2]) { + tant = - uhz[2] / uhz1[2]; + cost = 1 / sqrt(1 + tant * tant); + sint = tant * cost; + t = atan2(sint, cost); + } + else { + cost = 0; + sint = 1; + t = 90*degree; + } + + hyperpoint uhzt = unshift(hz * chg_shift(t) * C0); + if(uhzt[3] < 0) { t += 180*degree; uhzt = -uhzt; } + + tie(uhzt[2], uhzt[3]) = make_pair(uhzt[3], -uhzt[2]); + t += get_shift_cycles(-hz.shift-t); + + return flatresult{uhzt, t}; + } + +/** sample from Poisson distribution */ +int rpoisson(ld lambda) { + ld prob = randd(); + ld poisson = exp(-lambda); + int cnt = 0; + while(cnt < 2*lambda+100) { + if(prob < poisson) break; + prob -= poisson; + cnt++; + poisson *= lambda / cnt; + } + return cnt; + } + +} +} diff --git a/rogueviz/ads/menu.cpp b/rogueviz/ads/menu.cpp new file mode 100644 index 00000000..c0b06635 --- /dev/null +++ b/rogueviz/ads/menu.cpp @@ -0,0 +1,39 @@ +namespace hr { + +namespace ads_game { + +void game_menu() { + dialog::init(XLAT("AdS game settings"), 0xC0C0FFFF, 150, 100); + + add_edit(simspeed); + add_edit(accel); + add_edit(view_proper_times); + add_edit(auto_rotate); + + dialog::addItem("configure keys", 'k'); + dialog::add_action_push(multi::get_key_configurer(1, move_names, "Nilrider keys")); + + #if CAP_AUDIO + add_edit(effvolume); + add_edit(musicvolume); + #endif + + dialog::addItem("RogueViz settings", 'r'); + dialog::add_key_action('r', [] { + pushScreen(showSettings); + }); + + #if CAP_FILES && !ISWEB + dialog::addItem("save the current config", 's'); + dialog::add_action([] { + dynamicval g(geometry, gNormal); + saveConfig(); + }); + #endif + + dialog::addBreak(100); + dialog::addBack(); + dialog::display(); + } + +}} diff --git a/rogueviz/ads/shapes.cpp b/rogueviz/ads/shapes.cpp new file mode 100644 index 00000000..b83a976e --- /dev/null +++ b/rogueviz/ads/shapes.cpp @@ -0,0 +1,31 @@ +namespace hr { + +namespace ads_game { + +hpcshape shShip; +bool made; + +vector shape_rock = { -0.0176894, 0.0952504, 0.0278998, 0.0966286, 0.0686721, 0.0455547, 0.110983, 0.0122558, 0.0994024, -0.0483395, 0.0517039, -0.0802772, -0.00271848, -0.0706804, -0.0564861, -0.08575, -0.100087, -0.0483411, -0.100031, -0.0102072, -0.0761486, 0.0292356, -0.0639653, 0.077575 }; +vector shape_missile = { + +/* 0.201073, 0.00513228, 0.161595, 0.0253201, -0.135796, 0.0245209, -0.194281, 0.0372705, + -0.194281, -0.0372705, -0.135796, -0.0245209, 0.161595, -0.0253201, 0.201073, -0.00513228 */ + 0.04, 0, 0.01, 0.02, -0.02, 0.02, -0.02, -0.02, 0.01, -0.02, + }; + +void make_shape() { + if(made) return; + made = true; + vector ship = { + 0.100145, 0.00834541, 0.0810058, 0.0190602, 0.0356926, 0.0237951, 0.0619128, 0.0309564, 0.0631121, 0.0369146, 0.0333191, 0.038079, -0.0333481, 0.0702692, -0.0321308, 0.041651, -0.0380849, 0.0416554, -0.0380749, 0.0261765, -0.0273608, 0.023792 + }; + cgi.bshape(shShip, PPR::MONSTER_BODY); + int N = isize(ship); + for(int i=0; i=0; i-=2) cgi.hpcpush(hpxy(ship[i] - .03, -ship[i+1])); + cgi.hpcpush(hpxy(ship[0] - .03, ship[1])); + cgi.finishshape(); + cgi.extra_vertices(); + } + +}}