diff --git a/bigstuff.cpp b/bigstuff.cpp index 3d8ae937..6265ad2d 100644 --- a/bigstuff.cpp +++ b/bigstuff.cpp @@ -140,6 +140,7 @@ EX bool grailWasFound(cell *c) { } EX int default_levs() { + if(arb::in()) return 2; if(IRREGULAR) return 1; if(S3 >= OINF) @@ -1376,7 +1377,7 @@ EX int wallchance(cell *c, bool deepOcean) { /** \brief should we generate the horocycles in the current geometry? */ EX bool horo_ok() { if(INVERSE) return false; - if(currentmap->strict_tree_rules()) return false; + if(currentmap->strict_tree_rules()) return true; return hyperbolic && !bt::in() && !arcm::in() && !kite::in() && !experimental && !hybri && !arb::in() && !quotient; } @@ -1931,7 +1932,6 @@ EX void gen_temple(cell *c) { } EX void moreBigStuff(cell *c) { - if(currentmap->strict_tree_rules()) return; if((bearsCamelot(c->land) && !euclid && !quotient && !nil) || c->land == laCamelot) if(have_alt(c)) if(!(bt::in() && specialland != laCamelot)) diff --git a/cell.cpp b/cell.cpp index 8a9ba0da..9346ce04 100644 --- a/cell.cpp +++ b/cell.cpp @@ -231,6 +231,8 @@ EX hrmap *newAltMap(heptagon *o) { if(reg3::in_rule()) return reg3::new_alt_map(o); #endif + if(currentmap->strict_tree_rules()) + return rulegen::new_hrmap_rulegen_alt(o); return new hrmap_hyperbolic(o); } // --- hyperbolic geometry --- diff --git a/expansion.cpp b/expansion.cpp index c9f2779a..9a232c58 100644 --- a/expansion.cpp +++ b/expansion.cpp @@ -99,12 +99,20 @@ template vector get_children_codes(cell *c, const T& dist } return res; } - + void expansion_analyzer::preliminary_grouping() { samples.clear(); codeid.clear(); - children.clear(); - if(reg3::in_rule()) { + children.clear(); + if(currentmap->strict_tree_rules()) { + N = isize(rulegen::treestates); + children.resize(N); + rootid = rulegen::rule_root; + for(int i=0; i= 0) children[i].push_back(v); + } + else if(reg3::in_rule()) { #if MAXMDIM >= 4 rootid = reg3::rule_get_root(0); auto& chi = reg3::rule_get_children(); @@ -387,6 +395,8 @@ EX bool sizes_known() { // not implemented if(arcm::in()) return false; if(kite::in()) return false; + if(currentmap->strict_tree_rules()) return true; + if(arb::in()) return false; return true; } @@ -442,6 +452,8 @@ EX int curr_dist(cell *c) { int position; EX int type_in_reduced(expansion_analyzer& ea, cell *c, const cellfunction& f) { + if(currentmap->strict_tree_rules()) + return rulegen::get_state(c); int a = ea.N; int t = type_in(ea, c, f); if(expansion.N != a) { @@ -703,7 +715,7 @@ void expansion_analyzer::view_distances_dialog() { if(really_use_analyzer) { int t; - if(reg3::in_rule()) { + if(reg3::in_rule() || currentmap->strict_tree_rules()) { if(!N) preliminary_grouping(); t = rootid; } @@ -743,7 +755,7 @@ void expansion_analyzer::view_distances_dialog() { dialog::addBreak(100 * scrolltime / scrollspeed); if(sizes_known() || bt::in()) { - if(euclid) { + if(euclid && !arb::in()) { dialog::addBreak(200); dialog::addInfo("a(d) = " + its(get_descendants(10).approx_int() - get_descendants(9).approx_int()) + "d", forecolor); } diff --git a/floorshapes.cpp b/floorshapes.cpp index 5b4be578..ad3b9f1d 100644 --- a/floorshapes.cpp +++ b/floorshapes.cpp @@ -777,6 +777,7 @@ void geometry_information::generate_floorshapes() { for(int i=0; imaster)/DUALMUL, c->type); #endif + if(currentmap->strict_tree_rules()) { + i = rulegen::get_arb_dir(c, i); + } draw_shapevec(c, V2, qfi.fshape->gpside[sidepar][i], col, prio); return false; } diff --git a/hyper.cpp b/hyper.cpp index df8597bf..81ff951f 100644 --- a/hyper.cpp +++ b/hyper.cpp @@ -39,6 +39,7 @@ #include "kite.cpp" #include "archimedean.cpp" #include "arbitrile.cpp" +#include "rulegen.cpp" #include "euclid.cpp" #include "sphere.cpp" #include "fake.cpp" diff --git a/rulegen.cpp b/rulegen.cpp new file mode 100644 index 00000000..5200a513 --- /dev/null +++ b/rulegen.cpp @@ -0,0 +1,1568 @@ +// Hyperbolic Rogue -- rule generator +// Copyright (C) 2011-2021 Zeno Rogue, see 'hyper.cpp' for details + +/** \file rulegen.cpp + * \brief An algorithm to create strict tree rules for arb tessellations + */ + +#include "hyper.h" + +namespace hr { + +EX namespace rulegen { + +/* limits */ +EX int max_retries = 999; +EX int max_tcellcount = 8000000; +EX int max_adv_steps = 100; +EX int max_examine_branch = 5040; +EX int max_bdata = 1000; + +/* other parameters */ +EX int dlbonus = 0; + +#if HDR +/** exception thrown by this algoritm in case of any problems */ +struct rulegen_failure : hr_exception { + rulegen_failure(string _s) : hr_exception(_s) {} + }; + +/** this exception is thrown if we want to restart the computation -- this is normal, but if thrown more than max_retries times, just surrender */ +struct rulegen_retry : rulegen_failure { + rulegen_retry(string _s) : rulegen_failure(_s) {} + }; + +/** this exception is thrown in case if we run into a special case that is not implemented yet */ +struct rulegen_surrender : rulegen_failure { + rulegen_surrender(string _s) : rulegen_failure(_s) {} + }; + +const int MYSTERY = 31999; +const int MYSTERY_DIST = 31998; +#endif + +/* === tcell === */ + +/** number of tcells created */ +EX int tcellcount = 0; +/** number of tcells united into other tcells */ +EX int tunified = 0; + +#if HDR +struct tcell* tmove(tcell *c, int d); + +/** rulegen algorithm works on tcells which have their own map generation */ +struct tcell { + /** tcells form a list */ + tcell *next; + /** shape ID in arb::current */ + int id; + /** degree */ + int type; + /** distance from the root */ + short dist; + /** cached code */ + short code; + /** direction to the parent in the tree */ + short parent_dir; + /** can we assume that dist is correct? if we assumed that the dist is correct but then find out it was wrong, throw an error */ + bool is_solid; + bool distance_fixed; + /** sometimes we find out that multiple tcells represent the same actual cell -- in this case we unify them; unified_to is used for the union-find algorithm */ + walker unified_to; + int degree() { return type; } + connection_table c; + tcell*& move(int d) { return c.move(d); } + tcell*& modmove(int d) { return c.modmove(d); } + tcell* cmove(int d) { return tmove(this, d); } + tcell* cmodmove(int d) { return tmove(this, c.fix(d)); } + tcell() { } + }; + +inline void print(hstream& hs, tcell* h) { print(hs, "P", index_pointer(h)); } + +using twalker = walker; +#endif + +queue fix_queue; + +bool in_fixing = false; + +void unify_distances(tcell *c1, tcell *c2); +void handle_distance_errors(); + +void process_fix_queue() { + if(in_fixing) return; + in_fixing = true; + while(!fix_queue.empty()) { + fix_queue.front()(); + fix_queue.pop(); + } + in_fixing = false; + handle_distance_errors(); + } + +void ufind(twalker& p) { + if(p.at->unified_to.at == p.at) return; + twalker p1 = p.at->unified_to; + ufind(p1); + p.at->unified_to = p1; + p = p1 + p.spin; + } + +EX tcell *first_tcell = nullptr; + +void connect_and_check(twalker p1, twalker p2); +void unify(twalker pw1, twalker pw2); + +tcell *gen_tcell(int id) { + int d = isize(arb::current.shapes[id].connections); + auto c = tailored_alloc (d); + c->id = id; + c->next = first_tcell; + c->unified_to = twalker(c, 0); + c->is_solid = false; + c->distance_fixed = false; + c->dist = MYSTERY; + c->code = MYSTERY; + c->parent_dir = MYSTERY; + first_tcell = c; + // println(hlog, c, " is a new tcell of id ", id); + tcellcount++; + return c; + } + +tcell* tmove(tcell *c, int d) { + if(c->move(d)) return c->move(d); + auto& co = arb::current.shapes[c->id].connections[d]; + auto cd = twalker(c, d); + ufind(cd); + tcell *c1 = gen_tcell(co.sid); + connect_and_check(cd, twalker(c1, co.eid)); + return c1; + } + +/** check whether we have completed the vertex to the right of edge d of c */ +void check_loops(twalker pw) { + ufind(pw); + auto& shs = arb::current.shapes; + int id = pw.at->id; + int valence = shs[id].vertex_valence[pw.spin]; + + int steps = 0; + twalker pwf = pw; + twalker pwb = pw; + while(true) { + if(!pwb.peek()) break; + pwb = pwb + wstep - 1; + steps++; + if(pwb == pwf) { + if(steps == valence) return; /* that's great, we already know this loop */ + else throw hr_exception("vertex valence too small"); + } + if(steps == valence) { + fix_queue.push([=] { unify(pwf, pwb); }); + return; + } + } + + while(true) { + pwf++; + if(!pwf.peek()) break; + pwf += wstep; + steps++; + if(pwb == pwf) { + if(steps == valence) return; /* that's great, we already know this loop */ + else throw hr_exception("vertex valence too small"); + } + if(steps == valence) { + fix_queue.push([=] { unify(pwf, pwb); }); + return; + } + } + + if(steps == valence - 1) { + connect_and_check(pwb, pwf); + } + } + +void connect_and_check(twalker p1, twalker p2) { + p1.at->c.connect(p1.spin, p2.at, p2.spin, false); + fix_queue.push([=] { check_loops(p1); }); + fix_queue.push([=] { check_loops(p2); }); + process_fix_queue(); + } + +void unify(twalker pw1, twalker pw2) { + ufind(pw1); + ufind(pw2); + if(pw1.at->unified_to.at != pw1.at) + throw hr_exception("not unified to itself"); + if(pw2.at->unified_to.at != pw2.at) + throw hr_exception("not unified to itself"); + + if(pw1.at == pw2.at) { + if(pw1.spin != pw2.spin) throw hr_exception("called unify with self and wrong direction"); + return; + } + + if(pw1.at->id != pw2.at->id) + throw hr_exception("unifying two cells of different id's"); + + auto& shs = arb::current.shapes; + int id = pw1.at->id; + for(int i=0; iunified_to = pw1 - pw2.spin; + tunified++; + unify_distances(pw1.at, pw2.at); + } + +EX tcell *t_origin; + +void delete_tmap() { + while(first_tcell) { + auto second = first_tcell->next; + tailored_delete(first_tcell); + first_tcell = second; + } + tcellcount = 0; + tunified = 0; + t_origin = nullptr; + } + +/* used in the debugger */ +EX vector debuglist; + +/* === distances === */ + +bool no_errors = false; + +struct hr_solid_error : rulegen_retry { + hr_solid_error() : rulegen_retry("solid error") {} + }; + +int solid_errors; + +void fix_distances_rec(tcell *c) { + c->distance_fixed = true; + vector q = {c}; + + for(int qi=0; qidist; + restart: + for(int i=0; itype; i++) { + tcell *c1 = c->cmove(i); + if(c1->dist == MYSTERY) continue; + auto& d1 = c1->dist; + if(d > d1+1) { d = d1+1; goto restart; } + if(d1 > d+1) { + if(c1->is_solid) { + if(debugflags & DF_GEOM) + println(hlog, "solid ", c1, "changes ", d1, " to ", d+1); + solid_errors++; + } + d1 = d+1; + q.push_back(c1); + } + } + } + } + +EX int prepare_around_radius; + +void fix_distances(tcell *c) { + fix_distances_rec(c); + handle_distance_errors(); + } + +void calc_distances(tcell *c) { + if(c->dist != MYSTERY) return; + c->dist = MYSTERY_DIST; + fix_distances(c); + } + +void prepare_around(tcell *c) { + vector q; + set visited; + auto visit = [&] (tcell *x) { + if(visited.count(x)) return; + visited.insert(x); + q.push_back(x); + }; + visit(c); + int next_lev = 1; + int cdist = 0; + for(int i=0; itype; d++) visit(cx->cmove(d)); + } + for(auto cx: q) calc_distances(cx); + } + +void unify_distances(tcell *c1, tcell *c2) { + int d1 = c1->dist; + int d2 = c2->dist; + int d = min(d1, d2); + c1->dist = d; + c2->dist = d; + if(c1->is_solid && d != d1) solid_errors++; + if(c2->is_solid && d != d2) solid_errors++; + c1->distance_fixed = c1->distance_fixed || c2->distance_fixed; + c1->is_solid = c1->is_solid || c2->is_solid; + if(c1->dist < MYSTERY) + fix_distances_rec(c1); + } + +void handle_distance_errors() { + bool b = solid_errors; + solid_errors = 0; + if(b && !no_errors) { + prepare_around_radius++; + if(debugflags & DF_GEOM) + println(hlog, "increased prepare_around_radius to ", prepare_around_radius); + throw hr_solid_error(); + } + } + +/** make sure that we know c->dist */ +void be_solid(tcell *c) { + if(c->is_solid) return; + if(tcellcount >= max_tcellcount) + throw rulegen_surrender("max_tcellcount exceeded"); + prepare_around(c); + c->is_solid = true; + } + +/** which neighbor will become the parent of c */ + +int get_parent_dir(tcell *c) { + if(c->parent_dir != MYSTERY) return c->parent_dir; + int bestd = -1; + vector bestrootpath; + + be_solid(c); + + if(c->dist > 0) { + auto& sh = arb::current.shapes[c->id]; + int n = sh.size(); + int k = sh.cycle_length; + vector olen; + for(int i=0; i nearer; + for(int j=0; jcmove(i+j*k); + be_solid(c1); + olen.push_back(c1->dist); + if(c1->dist < c->dist) { + nearer.push_back(j); + } + } + if(nearer.size() == 1) {bestd = i+nearer[0]*k; break; } + if(nearer.size() == 2 && nearer[1] == nearer[0] + 1) { + bestd = i + nearer[0] * k; + break; + } + if(nearer.size() == 2 && nearer[0] == 0 && nearer[1] == n/k-1) { + bestd = i + nearer[1] * k; + break; + } + if(nearer.size() > 1) throw rulegen_failure("still confused"); + } + if(bestd == -1) throw rulegen_failure("should not happen"); + } + + c->parent_dir = bestd; + return bestd; + } + +/** determine states for tcells */ + +#if HDR +using aid_t = pair; + +struct analyzer { + vector spread; + vector parent_id; + vector spin; + void add_step(int pid, int s) { + twalker cw = spread[pid]; + cw = cw + s + wstep; + spread.push_back(cw); + parent_id.push_back(pid); + spin.push_back(s); + } + }; +#endif + +map analyzers; + +EX aid_t get_aid(twalker cw) { + ufind(cw); + auto ide = cw.at->id; + return {ide, gmod(cw.to_spin(0), arb::current.shapes[ide].cycle_length)}; + } + +EX analyzer& get_analyzer(twalker cw) { + auto aid = get_aid(cw); + auto& a = analyzers[aid]; + if(a.spread.empty()) { + a.spread.push_back(cw); + a.parent_id.push_back(-1); + a.spin.push_back(-1); + for(int i=0; itype; i++) + a.add_step(0, i); + } + return a; + } + +EX vector spread(analyzer& a, twalker cw) { + vector res; + int N = isize(a.spread); + res.reserve(N); + res.push_back(cw); + for(int i=1; i >; + +struct treestate { + int id; + bool known; + vector rules; + twalker giver; + int sid; + int parent_dir; + tcell* where_seen; + code_t code; + bool is_live; + bool is_possible_parent; + vector> possible_parents; + }; + +static const int C_IGNORE = 0; +static const int C_CHILD = 1; +static const int C_UNCLE = 2; +static const int C_EQUAL = 4; +static const int C_NEPHEW = 6; +static const int C_PARENT = 8; +#endif + +EX vector treestates; + +set sideswap; + +/** is what on the left side, or the right side, of to_what? */ + +int get_side(tcell *what, tcell *to_what) { + twalker w(what, -1); + twalker tw(to_what, -1); + auto adv = [] (twalker& cw) { + int d = get_parent_dir(cw.at); + cw.spin = d; + cw += wstep; + }; + while(w.at != tw.at) { + ufind(w); ufind(tw); + if(w.at->dist > tw.at->dist) + adv(w); + else if(w.at->dist < tw.at->dist) + adv(tw); + else { + adv(w); adv(tw); + } + } + if(w.spin == -1 || tw.spin == -1) return 0; + int d = get_parent_dir(w.at); + + if(d >= 0) { + twalker last(w.at, d); + return last.to_spin(w.spin) - last.to_spin(tw.spin); + } + + // failed to solve this in the simple way (ended at the root) -- go around the tree + twalker wl(what, get_parent_dir(what)); + twalker wr = wl; + auto go = [&] (twalker& cw, int delta) { + int d = get_parent_dir(cw.at); + if(cw.spin == d || get_parent_dir(cw.peek()) == (cw+wstep).spin) + cw += wstep; + cw+=delta; + }; + while(true) { + go(wl, -1); + go(wr, +1); + if(wl.at == to_what) return +1; + if(wr.at == to_what) return -1; + } + } + +code_t id_at_spin(twalker cw) { + code_t res; + ufind(cw); + res.first = get_aid(cw); + auto& a = get_analyzer(cw); + vector sprawl = spread(a, cw); + int id = 0; + for(auto cs: sprawl) { + be_solid(cs.at); + be_solid(cw.at); + ufind(cw); + ufind(cs); + int x; + int pid = a.parent_id[id]; + if(pid > -1 && (res.second[pid] != C_CHILD)) { + x = C_IGNORE; + } + else { + int p = get_parent_dir(cs.at); + if(p >= 0 && get_parent_dir(cs.at) == cs.spin) + x = C_CHILD; + else { + int y = cs.at->dist - cs.peek()->dist; + if(y == 1) x = C_NEPHEW; + else if(y == 0) x = C_EQUAL; + else if(y == -1) x = C_UNCLE; + else throw rulegen_failure("distance problem"); + auto gs = get_side(cs.at, cs.peek()); + if(gs == 0 && x == C_UNCLE) x = C_PARENT; + if(gs > 0) x++; + } + } + res.second.push_back(x); + id++; + } + return res; + } + +map code_to_id; + +EX pair get_code(tcell *c) { + if(c->code != MYSTERY) { + int bestd = c->parent_dir; + if(bestd == -1) bestd = 0; + return {bestd, c->code}; + } + + be_solid(c); + + for(int i=0; itype; i++) { + twalker cw0(c, i); + twalker cw(c, i); + cw+=wstep; + int val = 0; + while(cw.at != cw0.at) { + ufind(cw0); ufind(cw); + be_solid(cw.at); + cw++; + cw+=wstep; + val++; + if(val > 1000) + throw rulegen_failure("excessive valence problem"); + } + } + + int bestd = get_parent_dir(c); + if(bestd == -1) bestd = 0; + indenter ind(2); + + code_t v = id_at_spin(twalker(c, bestd)); + + if(code_to_id.count(v)) { + c->code = code_to_id[v]; + return {bestd, code_to_id[v]}; + } + + int id = isize(treestates); + code_to_id[v] = id; + if(c->code != MYSTERY && (c->code != id || c->parent_dir != bestd)) exit(1); + c->code = id; + + treestates.emplace_back(); + auto& nts = treestates.back(); + + nts.id = id; + nts.code = v; + nts.where_seen = c; + nts.known = false; + nts.is_live = true; + + return {bestd, id}; + } + +/* == rule generation == */ + +struct mismatch_error : rulegen_retry { + mismatch_error() : rulegen_retry("mismatch error") {} + }; + +struct double_edges: rulegen_surrender { + double_edges() : rulegen_surrender("double edges detected") {} + }; + +EX int rule_root; + +vector gen_rule(twalker cwmain); + +EX int try_count; +vector important; + +vector cq; + +#if HDR +/* special codes */ +static const int DIR_UNKNOWN = -1; +static const int DIR_MULTI_GO_LEFT = -2; +static const int DIR_MULTI_GO_RIGHT = -3; +static const int DIR_LEFT = -4; +static const int DIR_RIGHT = -5; +static const int DIR_PARENT = -6; +#endif + +vector gen_rule(twalker cwmain) { + vector cids; + for(int a=0; atype; a++) { + auto front = cwmain+a; + tcell *c1 = front.cpeek(); + be_solid(c1); + if(a == 0 && cwmain.at->dist) { cids.push_back(DIR_PARENT); continue; } + if(c1->dist <= cwmain.at->dist) { cids.push_back(DIR_UNKNOWN); continue; } + auto co = get_code(c1); + auto& d1 = co.first; + auto& id1 = co.second; + if(c1->cmove(d1) != cwmain.at || c1->c.spin(d1) != front.spin) { + cids.push_back(DIR_UNKNOWN); continue; + } + cids.push_back(id1); + } + return cids; + } + +void rules_iteration_for(tcell *c) { + indenter ri(2); + auto co = get_code(c); + auto& d = co.first; + auto& id = co.second; + twalker cwmain(c,d); + ufind(cwmain); + + vector cids = gen_rule(cwmain); + auto& ts = treestates[id]; + + if(!ts.known) { + ts.known = true; + ts.rules = cids; + ts.giver = cwmain; + ts.sid = cwmain.at->id; + ts.parent_dir = cwmain.spin; + for(int d=0; dtype; d++) + cq.push_back(c->cmove(d)); + } + else if(ts.rules != cids) { + auto& r = ts.rules; + if(debugflags & DF_GEOM) { + println(hlog, "merging ", ts.rules, " vs ", cids); + println(hlog, "C ", treestates[id].code, " [", id, "]"); + } + int mismatches = 0; + for(int z=0; z new_id(next_id); + + map new_id_of; + + int new_ids = 0; + + for(int id=0; id last_new_ids && new_ids < next_id) { + + last_new_ids = new_ids; + + map, int> hashes; + + new_ids = 0; + + auto last_new_id = new_id; + + for(int id=0; id hash; + hash.push_back(last_new_id[id]); + auto& ts = treestates[id]; + for(auto& r: ts.rules) + if(r >= 0) hash.push_back(last_new_id[r]); + else hash.push_back(r); + if(!hashes.count(hash)) + hashes[hash] = new_ids++; + + new_id[id] = hashes[hash]; + } + } + + if(debugflags & DF_GEOM) + println(hlog, "final new_ids = ", new_ids, " / ", next_id); + + if(1) { + vector old_id(new_ids, -1); + for(int i=0; i= 0) r = new_id[r]; + } + + for(auto& p: code_to_id) p.second = new_id[p.second]; + } + } + +void find_possible_parents() { + + for(auto& ts: treestates) { + ts.is_possible_parent = false; + for(int r: ts.rules) + if(r == DIR_PARENT) + ts.is_possible_parent = true; + } + while(true) { + int changes = 0; + for(auto& ts: treestates) ts.possible_parents.clear(); + for(auto& ts: treestates) + if(ts.is_possible_parent) { + int rid = 0; + for(int r: ts.rules) { + if(r >= 0) treestates[r].possible_parents.emplace_back(ts.id, rid); + rid++; + } + } + for(auto& ts: treestates) + if(ts.is_possible_parent && ts.possible_parents.empty()) { + ts.is_possible_parent = false; + changes++; + } + if(!changes) break; + } + + int pp = 0; + for(auto& ts: treestates) if(ts.is_possible_parent) pp++; + if(debugflags & DF_GEOM) + println(hlog, pp, " of ", isize(treestates), " states are possible_parents"); + } + +/* == branch testing == */ + +struct bad_tree : rulegen_retry { + bad_tree() : rulegen_retry("bad tree") {} + }; + +bool equiv(twalker w1, twalker w2); + +inline bool IS_DIR_MULTI(int d) { return among(d, DIR_MULTI_GO_LEFT, DIR_MULTI_GO_RIGHT); } +struct branchdata { + int id; + int dir; + twalker at; + int temporary; + void step() { + if(treestates[id].rules[dir] < 0) + throw rulegen_failure("invalid step"); + id = treestates[id].rules[dir]; dir = 0; at += wstep; + auto co = get_code(at.at); + auto& d1 = co.first; + auto& id1 = co.second; + if(id != id1 || d1 != at.spin) { + important.push_back(at.at); + if(debugflags & DF_GEOM) + println(hlog, "expected ", id, " found ", id1, " at ", at); + important.push_back(at.at->cmove(get_parent_dir(at.at))); + throw bad_tree(); + } + } + void spin(int i) { + at += i; + dir += i; + dir = gmod(dir, isize(treestates[id].rules)); + } + void spin_full(int i) { + spin(i); + while(IS_DIR_MULTI(treestates[id].rules[dir])) + spin(i); + } + }; + +inline void print(hstream& hs, const branchdata& bd) { print(hs, "[", bd.id,":",bd.dir, " ", bd.at, ":", bd.temporary, "]"); } + +/* we need to be careful with multiple edges */ + +bool paired(twalker w1, twalker w2) { + if(w1 + wstep == w2) return true; + if(w1.cpeek() == w2.at && w2.cpeek() == w1.at) { + return true; + } + return false; + } + +bool equiv(twalker w1, twalker w2) { + if(w1 == w2) return true; + if(w1.at == w2.at && w1.cpeek() == w2.cpeek()) { + return true; + } + return false; + } + +void advance(vector& bdata, branchdata at, int dir, bool start_forward, bool stack, int distlimit) { + if(start_forward) { + at.step(); + at.spin_full(dir); + } + else { + at.spin_full(dir); + } + vector b; + int steps = 0; + while(true) { + steps++; if(steps == max_adv_steps) + throw rulegen_failure("max_adv_steps exceeded"); + auto& ts = treestates[at.id]; + auto r = ts.rules[at.dir]; + if(r < 0) { + at.temporary = 0; + b.push_back(at); + break; + } + else if(!treestates[r].is_live) { + advance(b, at, dir, true, false, distlimit); + if(b.back().dir == 0) + b.pop_back(); + else + advance(b, at, -dir, true, true, distlimit); + at.spin_full(dir); + } + else { + at.step(); + if(at.at.at->dist < distlimit || !ts.is_live) at.spin_full(dir); + else { + at.temporary = dir; + b.push_back(at); + break; + } + } + } + if(stack) { + while(b.size()) { bdata.push_back(b.back()); b.pop_back(); } + } + else { + for(auto& bd: b) bdata.push_back(bd); + } + } + +map split; + +void assign_lr(branchdata bd, int dir) { + if(dir) { bd.spin_full(dir); if(bd.dir == 0) bd.dir = bd.at.at->type; } + auto& r = treestates[bd.id].rules; + for(int i=0; itype, " was ", split[bd.id]); + println(hlog, important); + } + important.push_back(bd.at.at); + important.push_back(split[bd.id].at.at); + throw mismatch_error(); + } + } + split[bd.id] = bd; + } + +set > branch_hashes; + +void examine_branch(int id, int left, int right) { + auto rg = treestates[id].giver; + if(debugflags & DF_GEOM) + println(hlog, "need to examine branches ", tie(left, right), " of ", id, " starting from ", rg); + vector bdata; + int dist_at = rg.at->dist; + while(left != right) { + /* can be false in case of multi-edges */ + if(treestates[id].rules[left] >= 0) { + + if(bdata.size() && bdata.back().dir == 0) + bdata.pop_back(); + else { + auto bl = branchdata{id, left, rg+left, dist_at+dlbonus}; + advance(bdata, bl, -1, true, true, dist_at+5); + } + } + left++; + if(left == rg.at->type) left = 0; + if(treestates[id].rules[left] >= 0) { + auto br = branchdata{id, left, rg+left, dist_at+dlbonus}; + advance(bdata, br, +1, true, false, dist_at+5); + } + } + int steps = 0; + while(true) { + steps++; + if(steps == max_examine_branch) + throw rulegen_failure("max_examine_branch exceeded"); + + if(isize(bdata) > max_bdata) + throw rulegen_failure("max_bdata exceeded"); + + /* advance both */ + vector bdata2; + int advcount = 0, eatcount = 0; + for(int i=0; idist, bdata[i+1].at.at->dist) <= dist_at) { + advcount++; + if(bdata2.size() && !bdata2.back().temporary && equiv(bdata2.back().at, bdata[i].at)) { + assign_lr(bdata[i], 0); + eatcount++; bdata2.pop_back(); + } + else + advance(bdata2, bdata[i], -1, false, true, dist_at+dlbonus); + if(i+2 < isize(bdata) && !bdata[i+1].temporary && !bdata[i+2].temporary && equiv(bdata[i+1].at, bdata[i+2].at)) { + assign_lr(bdata[i+1], +1); + eatcount++; i += 2; bdata2.push_back(bdata[i+1]); + } + else + advance(bdata2, bdata[i+1], +1, false, false, dist_at+dlbonus); + } + else { + if(bdata[i].temporary && bdata[i].at.at->dist <= dist_at+dlbonus-2) { + advcount++; + advance(bdata2, bdata[i], bdata[i].temporary, false, bdata[i].temporary < 0, dist_at+dlbonus); + } + else bdata2.push_back(bdata[i]); + + if(bdata[i+1].temporary && bdata[i+1].at.at->dist <= dist_at+3) { + advcount++; + advance(bdata2, bdata[i+1], bdata[i+1].temporary, false, bdata[i+1].temporary < 0, dist_at+dlbonus); + } + else bdata2.push_back(bdata[i+1]); + } + } + bdata = bdata2; + if(!advcount) dist_at++; + if(advcount) { + vector hash; + for(int i=0; idist - dist_at); + } + if(branch_hashes.count(hash)) { + return; + } + branch_hashes.insert(hash); + } + } + } + +/* == main algorithm == */ + +void clear_codes() { + treestates.clear(); + code_to_id.clear(); + auto c = first_tcell; + while(c) { + c->code = MYSTERY; + c = c->next; + } + } + +void rules_iteration() { + clear_codes(); + + cq = important; + + if(debugflags & DF_GEOM) + println(hlog, "important = ", cq); + + for(int i=0; i= 0 && treestates[i].is_live) children++; + if(!children) + treestates[id].is_live = false, new_deadends++; + } + + if(debugflags & DF_GEOM) + println(hlog, "deadend states found: ", new_deadends); + } + + for(int id=0; id= (p?DIR_UNKNOWN:0) || r[i] == DIR_PARENT || r[i] == DIR_MULTI_GO_LEFT)) + r[i1] = DIR_MULTI_GO_LEFT; + if(r[i] == DIR_UNKNOWN && (r[i1] >= 0 || r[i1] == DIR_PARENT || r[i+1] == DIR_MULTI_GO_RIGHT)) + r[i] = DIR_MULTI_GO_RIGHT; + } + } + } + } + + // print_rules(); + + branch_hashes.clear(); + + for(int id=0; id= 0 && treestates[r[i]].is_live) { + if(first_live_branch == -1) first_live_branch = i; + if(last_live_branch >= 0) + examine_branch(id, last_live_branch, i); + else for(int a=0; ais_solid = false; + c->dist = MYSTERY; + c->parent_dir = MYSTERY; + c->code = MYSTERY; + c->distance_fixed = false; + c = c->next; + } + if(t_origin) t_origin->dist = 0; + } + +void cleanup() { + clear_tcell_data(); + analyzers.clear(); + code_to_id.clear(); + split.clear(); + important.clear(); + } + +void clear_all() { + treestates.clear(); + cleanup(); + } + +EX string current_geometry_name() { + return arb::in() ? arb::current.filename : full_geometry_name(); + } + +bool double_edges_check(cell *c, set& visited) { + int i = shvid(c); + if(visited.count(i)) return false; + visited.insert(i); + for(int j=0; jtype; j++) { + cellwalker cw(c, j); + bool on = true; + if(double_edges_check(cw.cpeek(), visited)) return true; + int qty = 0; + for(int k=0; k<=c->type; k++) { + bool on2 = (cw+k).cpeek() == cw.cpeek(); + if(on != on2) qty++; + on = on2; + } + if(qty > 2) return true; + } + return false; + } + +EX void generate_rules() { + + delete_tmap(); + + if(!arb::in()) + throw rulegen_surrender("rulegen algorithm works only on arb tessellations"); + + prepare_around_radius = 1; + + clear_all(); + + analyzers.clear(); + split.clear(); + + t_origin = gen_tcell(0); + t_origin->dist = 0; + + set visited; + if(double_edges_check(currentmap->gamestart(), visited)) + throw double_edges(); + + try_count = 0; + + important = { t_origin }; + + retry: + try { + rules_iteration(); + } + catch(rulegen_retry& e) { + try_count++; + if(try_count >= max_retries) + throw; + if(debugflags & DF_GEOM) println(hlog, "attempt: ", try_count); + auto c = first_tcell; + while(c) { + c->is_solid = false; + c->parent_dir = MYSTERY; + c->code = MYSTERY; + c = c->next; + } + goto retry; + } + } + +int reclevel; + +void build_test(); + +/* == hrmap_rulegen == */ + +struct hrmap_rulegen : hrmap { + hrmap *base; + heptagon *origin; + + heptagon* gen(int s, int d, bool c7) { + int t = arb::current.shapes[treestates[s].sid].size(); + heptagon *h = init_heptagon(t); + if(c7) h->c7 = newCell(t, h); + h->distance = d; + h->fieldval = s; + h->zebraval = treestates[s].sid; + return h; + } + + ~hrmap_rulegen() { + clearfrom(origin); + } + + hrmap_rulegen() { + origin = gen(rule_root, 0, true); + } + + hrmap_rulegen(heptagon *h) { + origin = h; + } + + heptagon *getOrigin() override { + return origin; + } + + int get_rule(heptspin hs) { + int s = hs.at->fieldval; + return treestates[s].rules[hs.spin]; + } + + static void hsconnect(heptspin a, heptspin b) { + a.at->c.connect(a.spin, b.at, b.spin, false); + } + + void group_connect(heptspin a, heptspin b) { + /* go leftmost with a */ + while(get_rule(a) == DIR_MULTI_GO_LEFT || get_rule(a-1) == DIR_MULTI_GO_RIGHT) + a--; + /* go rightmost with b */ + while(get_rule(b) == DIR_MULTI_GO_RIGHT || get_rule(b+1) == DIR_MULTI_GO_LEFT) + b++; + int gr = 0; + // verify_connection(a, b); + while(true) { + hsconnect(a, b); gr++; + bool can_a = get_rule(a) == DIR_MULTI_GO_RIGHT || get_rule(a+1) == DIR_MULTI_GO_LEFT; + if(can_a) a++; + bool can_b = get_rule(b) == DIR_MULTI_GO_LEFT || get_rule(b-1) == DIR_MULTI_GO_RIGHT; + if(can_b) b--; + if(can_a && can_b) continue; + if(can_a || can_b) + throw rulegen_failure("multi disagreement"); + break; + } + } + + heptagon *create_step(heptagon *h, int d) override { + heptspin hs(h, d); + int r = get_rule(hs); + indenter ind(2); + if(hlog.indentation >= 6000) + throw rulegen_failure("failed to create_step"); + if(r >= 0) { + auto h1 = gen(r, h->distance + 1, h->c7); + auto hs1 = heptspin(h1, 0); + // verify_connection(hs, hs1); + hsconnect(hs, hs1); + return h1; + } + else if(r == DIR_PARENT) { + auto& hts = treestates[h->fieldval]; + auto& choices = hts.possible_parents; + if(choices.empty()) throw rulegen_failure("no possible parents"); + auto selected = hrand_elt(choices); + auto h1 = gen(selected.first, h->distance - 1, h->c7); + auto hs1 = heptspin(h1, selected.second); + hsconnect(hs, hs1); + return h1; + } + else if(r == DIR_UNKNOWN) + throw rulegen_failure("UNKNOWN rule remained"); + else if(r == DIR_MULTI_GO_LEFT) { + // hs = (hs - 1) + wstep; + hsconnect(hs, hs - 1 + wstep - 1); + return h->move(d); + } + else if(r == DIR_MULTI_GO_RIGHT) { + // hs = (hs + 1) + wstep; + hsconnect(hs, hs + 1 + wstep + 1); + return h->move(d); + } + else if(r == DIR_LEFT || r == DIR_RIGHT) { + heptspin hs1 = hs; + int delta = r == DIR_LEFT ? -1 : 1; + int rev = (DIR_LEFT ^ DIR_RIGHT ^ r); + while(IS_DIR_MULTI(get_rule(hs1))) hs1 += delta; + hs1 += delta; + while(true) { + int r1 = get_rule(hs1); + if(r1 == rev) { + group_connect(hs, hs1); + return hs1.at; + } + else if(IS_DIR_MULTI(r1)) { + hs1 += delta; + } + else if(r1 == r || r1 == DIR_PARENT || r1 >= 0) { + hs1 += wstep; + while(get_rule(hs1) == (r == DIR_RIGHT ? DIR_MULTI_GO_RIGHT : DIR_MULTI_GO_LEFT)) { + hs1 += delta; + } + hs1 += delta; + } + else throw rulegen_failure("bad R1"); + } + } + else throw rulegen_failure("bad R"); + throw rulegen_failure("impossible"); + } + + int get_arb_dir(int s, int dir) { + int sid = treestates[s].sid; + int N = arb::current.shapes[sid].size(); + return gmod(dir + treestates[s].parent_dir, N); + } + + transmatrix adj(heptagon *h, int dir) override { + int s = h->fieldval; + int dir0 = get_arb_dir(s, dir); + + int dir1 = -1; + + if(h->c.move(dir)) { + int s1 = h->c.move(dir)->fieldval; + dir1 = get_arb_dir(s1, h->c.spin(dir)); + } + + return arb::get_adj(arb::current_or_slided(), treestates[s].sid, dir0, dir1); + } + + int shvid(cell *c) override { + return c->master->zebraval; + } + + hyperpoint get_corner(cell *c, int cid, ld cf) { + if(c->master->fieldval == -1) { + auto& sh = arb::current_or_slided().shapes[c->master->zebraval]; + return normalize(C0 + (sh.vertices[cid] - C0) * 3 / cf); + } + int s = c->master->fieldval; + auto& sh = arb::current_or_slided().shapes[c->master->zebraval]; + auto dir = get_arb_dir(s, cid); + + return normalize(C0 + (sh.vertices[dir] - C0) * 3 / cf); + } + + void find_cell_connection(cell *c, int d) override { + if(c->master->cmove(d) == &oob) { + c->c.connect(d, &out_of_bounds, 0, false); + } + else hrmap::find_cell_connection(c, d); + } + + bool strict_tree_rules() { return true; } + + virtual bool link_alt(heptagon *h, heptagon *alt, hstate firststate, int dir) override { + auto& hts = treestates[h->fieldval]; + int psid = hts.sid; + + if(firststate == hsOrigin) { + alt->fiftyval = rule_root; + // fix this + return psid == 0; + } + + int odir = hts.parent_dir + dir; + + int cl = arb::current.shapes[psid].cycle_length; + + vector choices; + for(auto& ts: treestates) + if(ts.is_possible_parent && ts.sid == psid) + if(gmod(ts.parent_dir - odir, cl) == 0) + choices.push_back(ts.id); + alt->fieldval = hrand_elt(choices, -1); + if(alt->fieldval == -1) return false; + altmap::relspin(alt) = dir; + return true; + } + }; + +EX int get_arb_dir(cell *c, int dir) { + return ((hrmap_rulegen*)currentmap)->get_arb_dir(c->master->fieldval, dir); + } + +EX hrmap *new_hrmap_rulegen_alt(heptagon *h) { + return new hrmap_rulegen(h); + } + +EX hrmap *new_hrmap_rulegen() { return new hrmap_rulegen(); } + +EX int get_state(cell *c) { + return c->master->fieldval; + } + +EX void restart_game_on(hrmap *m) { + stop_game(); + int a = addHook(hooks_newmap, 0, [m] { return m;}); + start_game(); + delHook(hooks_newmap, a); + } + +string rules_known_for; +string rule_status; + +bool prepare_rules() { + if(rules_known_for == arb::current.name) return true; + try { + if(!arb::in()) + throw rulegen_failure("rulegen in wrong tessellation"); + generate_rules(); + rules_known_for = arb::current.name; + rule_status = XLAT("rules generated successfully"); + return true; + } + catch(rulegen_retry& e) { + rule_status = XLAT("too difficult: %1", e.what()); + } + catch(rulegen_surrender& e) { + rule_status = XLAT("too difficult: %1", e.what()); + } + catch(rulegen_failure& e) { + rule_status = XLAT("bug: %1", e.what()); + } + return false; + } + +int args() { + using namespace arg; + + if(0) ; + + else if(argis("-rulegen")) { + PHASEFROM(3); + prepare_rules(); + } + else if(argis("-rulegen-cleanup")) + cleanup(); + else if(argis("-rulegen-play")) { + PHASEFROM(3); + if(prepare_rules()) + restart_game_on(new hrmap_rulegen); + } + else return 1; + return 0; + } + +auto hooks = + addHook(hooks_args, 100, args) + + addHook(hooks_configfile, 100, [] { + param_i(max_retries, "max_retries"); + param_i(max_tcellcount, "max_tcellcount"); + param_i(max_adv_steps, "max_adv_steps"); + param_i(max_examine_branch, "max_examine_branch"); + param_i(max_bdata, "max_bdata"); + param_i(dlbonus, "dlbonus"); + }); + +EX } +}