// Hyperbolic Rogue -- basic utility functions // Copyright (C) 2011-2018 Zeno Rogue, see 'hyper.cpp' for details /** \file util.cpp * \brief basic utility functions: maths, parsing expressions */ #include "hyper.h" namespace hr { #if CAP_TIMEOFDAY #if !CAP_SDL int lastusec; int uticks; EX int SDL_GetTicks() { struct timeval tim; gettimeofday(&tim, NULL); int newusec = tim.tv_usec; uticks += newusec - lastusec; if(newusec <= lastusec) uticks += 1000000; lastusec = newusec; return uticks / 1000; } #endif #endif EX long double sqr(long double x) { return x*x; } EX ld round_nearest(ld x) { if(x > 0) return int(x+.5); else return -int(.5-x); } EX ld round_nearest(ld x, ld multiple_of) { return multiple_of * round_nearest(x / multiple_of); } EX int gcd(int i, int j) { return i ? gcd(j%i, i) : j; } EX int gmod(int i, int j) { i %= j; if(i<0) i += j; return i; } EX int zgmod(int a, int b) { return b ? gmod(a, b) : a; } EX int szgmod(int a, int b) { if(!b) return a; a = gmod(a, b); if(2*a >= b) return a - b; return a; } EX int gdiv(int i, int j) { return (i - gmod(i, j)) / j; } EX ld frac(ld x) { x -= int(x); if(x < 0) x++; return x; } EX ld lerp(ld a0, ld a1, ld x) { return a0 + (a1-a0) * x; } EX cld lerp(cld a0, cld a1, ld x) { return a0 + (a1-a0) * x; } EX ld ilerp(ld a0, ld a1, ld x) { return (x-a0) / (a1-a0); } EX purehookset hooks_tests; EX string simplify(const string& s) { string res; for(char c: s) if(isalnum(c)) res += c; return res; } EX bool appears(const string& haystack, const string& needle) { return simplify(haystack).find(simplify(needle)) != string::npos; } #if HDR struct hr_parse_exception : hr_exception { string s; hr_parse_exception(const string& z) : s(z) {} ~hr_parse_exception() noexcept(true) {} }; struct exp_parser { string s; int at; int line_number, last_line; exp_parser() { at = 0; line_number = 1; last_line = 0; } string where() { if(s.find('\n')) return "(line " + its(line_number) + ", pos " + its(at-last_line) + ")"; else return "(pos " + its(at) + ")"; } map extra_params; bool ok() { return at == isize(s); } char next(int step=0) { if(at >= isize(s)-step) return 0; else return s[at+step]; } char eatchar() { return s[at++]; } bool eat(const char *c) { int orig_at = at; while(*c && *c == next()) at++, c++; if(*c == 0) return true; else at = orig_at; return false; } void skip_white(); string next_token(); char snext(int step=0) { skip_white(); return next(step); } vector> parse_with_reps(); cld parse(int prio = 0); ld rparse(int prio = 0) { return validate_real(parse(prio)); } int iparse(int prio = 0) { return int(floor(rparse(prio) + .5)); } cld parsepar() { cld res = parse(); force_eat(")"); return res; } ld validate_real(cld x) { if(kz(imag(x))) throw hr_parse_exception("expected real number but " + lalign(-1, x) + " found at " + where()); return real(x); } void force_eat(const char *c) { skip_white(); if(!eat(c)) throw hr_parse_exception("expected: " + string(c) + " at " + where()); } }; #endif void exp_parser::skip_white() { while(next() == ' ' || next() == '\n' || next() == '\r' || next() == '\t') { if(next() == '\r') last_line++; if(next() == '\n') { line_number++, last_line = at; } at++; } } string exp_parser::next_token() { skip_white(); string token; while(true) { char c = next(); if((c >= '0' && c <= '9') || (c == '.' && next(1) != '.') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_') token += c, at++; else break; } return token; } vector> exp_parser::parse_with_reps() { vector> vals; vals.emplace_back(rparse(0), 1); while(true) { skip_white(); if(eat(":^")) { ld rep = rparse(0); vals.back().second *= rep; } if(eat(",")) vals.emplace_back(rparse(0), 1); else break; } return vals; } cld exp_parser::parse(int prio) { cld res; skip_white(); if(eat("sin(")) res = sin(parsepar()); else if(eat("cos(")) res = cos(parsepar()); else if(eat("sinh(")) res = sinh(parsepar()); else if(eat("cosh(")) res = cosh(parsepar()); else if(eat("asin(")) res = asin(parsepar()); else if(eat("acos(")) res = acos(parsepar()); else if(eat("asinh(")) res = asinh(parsepar()); else if(eat("acosh(")) res = acosh(parsepar()); else if(eat("exp(")) res = exp(parsepar()); else if(eat("sqrt(")) res = sqrt(parsepar()); else if(eat("log(")) res = log(parsepar()); else if(eat("tan(")) res = tan(parsepar()); else if(eat("tanh(")) res = tanh(parsepar()); else if(eat("atan(")) res = atan(parsepar()); else if(eat("atanh(")) res = atanh(parsepar()); else if(eat("abs(")) res = abs(parsepar()); else if(eat("re(")) res = real(parsepar()); else if(eat("im(")) res = imag(parsepar()); else if(eat("conj(")) res = std::conj(parsepar()); else if(eat("floor(")) res = floor(validate_real(parsepar())); else if(eat("frac(")) { res = parsepar(); res = res - floor(validate_real(res)); } else if(eat("to01(")) { res = parsepar(); return atan(res) / ld(M_PI) + ld(0.5); } else if(eat("min(")) { ld a = rparse(0); while(skip_white(), eat(",")) a = min(a, rparse(0)); force_eat(")"); res = a; } else if(eat("max(")) { ld a = rparse(0); while(skip_white(), eat(",")) a = max(a, rparse(0)); force_eat(")"); res = a; } else if(eat("edge(")) { ld a = rparse(0); force_eat(","); ld b = rparse(0); force_eat(")"); res = edge_of_triangle_with_angles(2*M_PI/a, M_PI/b, M_PI/b); } else if(eat("edge_angles(")) { cld a = rparse(0); force_eat(","); cld b = rparse(0); force_eat(","); cld c = rparse(0); force_eat(")"); if (auto *angleunit = hr::at_or_null(extra_params, "angleunit")) { a *= *angleunit; b *= *angleunit; c *= *angleunit; } return edge_of_triangle_with_angles(real(a), real(b), real(c)); } else if(eat("regradius(")) { ld a = rparse(0); force_eat(","); ld b = rparse(0); force_eat(")"); res = edge_of_triangle_with_angles(M_PI/2, M_PI/a, M_PI/b); } #if CAP_ARCM else if(eat("arcmedge(")) { if(!hyperbolic && !sphere && !euclid) throw hr_parse_exception("arcmedge works only in isotropic geometry"); vector> vals = parse_with_reps(); force_eat(")"); res = euclid ? 1 : arcm::compute_edgelength(vals); if(real(res) < 1e-10) throw hr_parse_exception("wrong geometry for this arcmedge"); if (auto *distunit = hr::at_or_null(extra_params, "distunit")) res /= *distunit; } else if(eat("arcmcurv(")) { vector> vals = parse_with_reps(); force_eat(")"); ld total = 0; for(auto p: vals) total += p.second * (180 - 360 / p.first); total = (360 - total) * degree; if(abs(total) < 1e-10) total = 0; res = total; } #endif else if(eat("ideal_angle(")) { ld edges = rparse(0); force_eat(")"); return arb::rep_ideal(edges).second; } else if(eat("ideal_edge(")) { ld edges = rparse(0); force_eat(")"); return arb::rep_ideal(edges).first; } else if(eat("regangle(")) { cld edgelen = parse(0); if (auto *distunit = hr::at_or_null(extra_params, "distunit")) { edgelen *= *distunit; } force_eat(","); ld edges = rparse(0); force_eat(")"); ld alpha = M_PI / edges; if(isinf(edges)) { ld u = sqrt(cosh(validate_real(edgelen)) * 2 - 2); ld a = atan2(1, u/2); res = 2 * a; } else { ld c = asin_auto(sin_auto(validate_real(edgelen)/2) / sin(alpha)); hyperpoint h = xpush(c) * spin(M_PI - 2*alpha) * xpush0(c); ld result = 2 * atan2(h); if(result < 0) result = -result; while(result > 2 * M_PI) result -= 2 * M_PI; if(result > M_PI) result = 2 * M_PI - result; res = result; } if (auto *angleunit = hr::at_or_null(extra_params, "angleunit")) res /= *angleunit; } else if(eat("test(")) { res = parsepar(); println(hlog, "res = ", res, ": ", fts(real(res), 10), ",", fts(imag(res), 10)); } else if(eat("ifp(")) { cld cond = parse(0); force_eat(","); cld yes = parse(0); force_eat(","); cld no = parsepar(); res = real(cond) > 0 ? yes : no; } else if(eat("wallif(")) { cld val0 = parse(0); force_eat(","); cld val1 = parsepar(); if(real(extra_params["p"]) >= 3.5) res = val0; else res = val1; } else if(eat("rgb(")) { cld val0 = parse(0); force_eat(","); cld val1 = parse(0); force_eat(","); cld val2 = parsepar(); switch(int(real(extra_params["p"]) + .5)) { case 1: res = val0; break; case 2: res = val1; break; case 3: res = val2; break; default: res = 0; } } else if(eat("let(")) { string name = next_token(); force_eat("="); cld val = parse(0); force_eat(","); dynamicval d(extra_params[name], val); res = parsepar(); } #if CAP_TEXTURE else if(eat("txp(")) { cld val = parsepar(); res = texture::get_txp(real(val), imag(val), int(real(extra_params["p"]) + .5)-1); } #endif else if(next() == '(') at++, res = parsepar(); else { string number = next_token(); if (auto *p = hr::at_or_null(extra_params, number)) res = *p; else if (auto *p = hr::at_or_null(params, number)) res = (*p)->get_cld(); else if(number == "e") res = exp(1); else if(number == "i") res = cld(0, 1); else if(number == "inf") res = HUGE_VAL; else if(number == "p" || number == "pi") res = M_PI; else if(number == "" && next() == '-') { at++; res = -parse(20); } else if(number == "") throw hr_parse_exception("number missing, " + where()); else if(number == "s") res = ticks / 1000.; else if(number == "ms") res = ticks; else if(number[0] == '0' && number[1] == 'x') res = strtoll(number.c_str()+2, NULL, 16); else if(number == "mousex") res = mousex; else if(number == "deg") res = degree; else if(number == "ultra_mirror_dist") res = cgi.ultra_mirror_dist; else if(number == "psl_steps") res = cgi.psl_steps; else if(number == "single_step") res = cgi.single_step; else if(number == "step") res = hdist0(tC0(currentmap->adj(cwt.at, 0))); else if(number == "edgelen") res = hdist(get_corner_position(cwt.at, 0), get_corner_position(cwt.at, 1)); else if(number == "mousey") res = mousey; else if(number == "random") res = randd(); else if(number == "mousez") res = cld(mousex - current_display->xcenter, mousey - current_display->ycenter) / cld(current_display->radius, 0); else if(number == "shot") res = inHighQual ? 1 : 0; else if(number == "fake_edgelength") res = arcm::fake_current.edgelength; else if(number[0] >= 'a' && number[0] <= 'z') throw hr_parse_exception("unknown value: " + number); else if(number[0] >= 'A' && number[0] <= 'Z') throw hr_parse_exception("unknown value: " + number); else if(number[0] == '_') throw hr_parse_exception("unknown value: " + number); else { std::stringstream ss; res = 0; ss << number; ss >> res; } } while(true) { skip_white(); #if CAP_ANIMATIONS if(next() == '.' && next(1) == '.' && prio == 0) { static const cld NO_DERIVATIVE(3.1, 2.5); vector> rest = { make_array(res, NO_DERIVATIVE, res, NO_DERIVATIVE) }; bool second = true; while(next() == '.' && next(1) == '.') { /* spline interpolation */ if(next(2) == '/') { at += 3; rest.back()[second ? 3 : 1] = parse(10); continue; } /* sharp end */ else if(next(2) == '|') { at += 3; rest.back()[2] = parse(10); rest.back()[3] = NO_DERIVATIVE; second = true; continue; } at += 2; auto val = parse(10); rest.emplace_back(make_array(val, NO_DERIVATIVE, val, NO_DERIVATIVE)); second = false; } ld v = ticks * (isize(rest)-1.) / anims::period; int vf = v; v -= vf; if(isize(rest) == 1) rest.push_back(rest[0]); vf %= (isize(rest)-1); auto& lft = rest[vf]; auto& rgt = rest[vf+1]; if(lft[3] == NO_DERIVATIVE && rgt[1] == NO_DERIVATIVE) res = lerp(lft[2], rgt[0], v); else if(rgt[1] == NO_DERIVATIVE) res = lerp(lft[2] + lft[3] * v, rgt[0], v*v); else if(lft[3] == NO_DERIVATIVE) res = lerp(lft[2], rgt[0] + rgt[1] * (v-1), (2-v)*v); else { res = lerp(lft[2] + lft[3] * v, rgt[0] + rgt[1] * (v-1), v*v*(3-2*v)); } return res; } else #endif if(next() == '+' && prio <= 10) at++, res = res + parse(20); else if(next() == '-' && prio <= 10) at++, res = res - parse(20); else if(next() == '*' && prio <= 20) at++, res = res * parse(30); else if(next() == '/' && prio <= 20) at++, res = res / parse(30); else if(next() == '^') at++, res = pow(res, parse(40)); else break; } return res; } EX ld parseld(const string& s) { exp_parser ep; ep.s = s; return ep.rparse(); } EX int parseint(const string& s) { exp_parser ep; ep.s = s; return ep.iparse(); } EX string available_functions() { return "(a)sin(h), (a)cos(h), (a)tan(h), exp, log, abs, re, im, conj, let(t=...,...t...), floor, frac, sqrt, to01, random, edge(7,3), regradius(7,3), ifp(a,v,w) [if positive]"; } EX string available_constants() { return "e, i, pi, s, ms, mousex, mousey, mousez, shot [1 if taking screenshot/animation]"; } #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) const; bignum(ld d); bool operator < (const bignum&) const; bool operator > (const bignum& b) const { return b < self; } 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 approx_div(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]; } bool nonzero() { return approx_ld() != 0; } bignum randomized_div(int x) const; ld approx_ld() const { ld res = 0; for(int i=0; i 2) return BASE2; if(digits.empty()) return 0; if(isize(digits) == 1) return digits[0]; return digits[0] + digits[1] * (long long) BASE; } #if CAP_GMP mpq_class as_mpq() const { string s = get_str(999999); string t; for(char c: s) if(c != ' ') t += c; return mpq_class(t); } #endif 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; } bignum bignum::randomized_div(int x) const { bignum res = self; long long carry = 0; int K = isize(res.digits); for(int i=K-1; i>=0; i--) { carry *= BASE; carry += digits[i]; // strange compiler buug: // if I do / and %, function 'divmod' is called, and it complains on launch that divmod is unimplemented res.digits[i] = int(carry / x); carry -= res.digits[i] * (long long)(x); } while(isize(res.digits) && res.digits.back() == 0) res.digits.pop_back(); if(rand() % x < carry) res += 1; return res; } 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 = int(l / BASE); if(l < 0) carry = -int((BASE-1-l) / BASE); l -= carry * BASE; digits[i] = int(l); } if(carry < 0) digits.back() -= BASE; while(isize(digits) && digits.back() == 0) digits.pop_back(); } EX 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; } EX string short_form(bignum b) { if(b < 0) return "-" + short_form(0-b); else if(b < 100000) return its(b.approx_int()); else { long long val; int q; if(isize(b.digits) >= 2) { q = max(isize(b.digits) - 2, 0); val = b.digits[q] + (long long)(bignum::BASE) * b.digits[q+1]; } else { q = 0; val = b.digits[0]; } int digits = q * 9; while(val >= 1000) { val /= 10; digits++; } string str = its(int(val)) + "E" + its(digits + 2); str.insert(1, "."); return str; } } bignum::bignum(ld d) { if(d == 0) return; int n = 1; while(d > BASE) d /= BASE, n++; digits.resize(n); n--; while(n >= 0) { digits[n] = int(d); d -= digits[n]; d *= BASE; n--; } } /** in s, replace occurences of a with b */ EX void replace_str(string& s, string a, string b) { while(s.find(a) != string::npos) s.replace(s.find(a), isize(a), b); } #if CAP_ZLIB /* compression/decompression */ EX string compress_string(string s) { z_stream strm; strm.zalloc = Z_NULL; strm.zfree = Z_NULL; strm.opaque = Z_NULL; println(hlog, "pre init"); auto ret = deflateInit(&strm, 9); if(ret != Z_OK) throw hr_exception("z-error"); println(hlog, "init ok"); strm.avail_in = isize(s); strm.next_in = (Bytef*) &s[0]; vector buf(10000000, 0); strm.avail_out = 10000000; strm.next_out = (Bytef*) &buf[0]; if(deflate(&strm, Z_FINISH) != Z_STREAM_END) throw hr_exception("z-error-2"); println(hlog, "deflate ok"); string out(&buf[0], (char*)(strm.next_out) - &buf[0]); println(hlog, isize(s), " -> ", isize(out)); return out; } EX string decompress_string(string s) { z_stream strm; strm.zalloc = Z_NULL; strm.zfree = Z_NULL; strm.opaque = Z_NULL; auto ret = inflateInit(&strm); if(ret != Z_OK) throw hr_exception("z-error"); strm.avail_in = isize(s); strm.next_in = (Bytef*) &s[0]; vector buf(10000000, 0); strm.avail_out = 10000000; strm.next_out = (Bytef*) &buf[0]; if(inflate(&strm, Z_FINISH) != Z_STREAM_END) throw hr_exception("z-error-2"); string out(&buf[0], (char*)(strm.next_out) - &buf[0]); println(hlog, isize(s), " -> ", isize(out)); return out; } #endif EX bool file_exists(string fname) { return access(fname.c_str(), F_OK) != -1; } EX void open_url(string s) { #if ISWEB EM_ASM_({ window.open(UTF8ToString($0, 1000)); }, s.c_str()); #else #ifdef WINDOWS ShellExecute(0, 0, s.c_str(), 0, 0, SW_SHOW); #endif #ifdef LINUX ignore(system(("xdg-open "+s).c_str())); #endif #ifdef MAC ignore(system(("open "+s).c_str())); #endif #endif } const char *urlhex = "0123456789ABCDEF"; EX void open_wiki(const char *title) { // Since "Crossroads" is ambiguous, we use the direct link to Crossroads I. if (!strcmp(title, "Crossroads")) { title = "Crossroads (Land)"; } string url = "https://hyperrogue.miraheze.org/wiki/"; unsigned char c; for (size_t i = 0; (c = title[i]); ++i) { if (('0' <= c && c <= '9') || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '_' || c == '-') url += c; else if (c == ' ') url += "_"; else url += string("%") + urlhex[c/16] + urlhex[c%16]; } open_url(url); } EX void floyd_warshall(vector>& v) { int N = isize(v); for(int k=0; k(v[i][j], v[i][k] + v[k][j]); } }