1 // Hyperbolic Rogue -- Crystal geometries
2 // Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details
3 
4 /** \file crystal.cpp
5  *  \brief Multi-dimensional (aka crystal) geometries.
6  */
7 
8 #include "hyper.h"
9 namespace hr {
10 
11 EX namespace crystal {
12 
13 #if HDR
14 static const int MAXDIM = 7;
15 static const int MAX_EDGE_CRYSTAL = 2 * MAXDIM;
16 
17 struct coord : public array<int, MAXDIM> {
operator +hr::crystal::coord18   coord operator + (coord b) { for(int i=0; i<MAXDIM; i++) b[i] += self[i]; return b; }
operator -hr::crystal::coord19   coord operator - (coord b) { for(int i=0; i<MAXDIM; i++) b[i] = self[i] - b[i]; return b; }
operator *hr::crystal::coord20   coord operator * (int x) { coord res; for(int i=0; i<MAXDIM; i++) res[i] = x * self[i]; return res; }
21   };
22 
23 static const coord c0 = {};
24 
25 struct ldcoord : public array<ld, MAXDIM> {
operator +(ldcoord a,ldcoord b)26   friend ldcoord operator + (ldcoord a, ldcoord b) { ldcoord r; for(int i=0; i<MAXDIM; i++) r[i] = a[i] + b[i]; return r; }
operator -(ldcoord a,ldcoord b)27   friend ldcoord operator - (ldcoord a, ldcoord b) { ldcoord r; for(int i=0; i<MAXDIM; i++) r[i] = a[i] - b[i]; return r; }
operator *(ldcoord a,ld v)28   friend ldcoord operator * (ldcoord a, ld v) { ldcoord r; for(int i=0; i<MAXDIM; i++) r[i] = a[i] * v; return r; }
operator /(ldcoord a,ld v)29   friend ldcoord operator / (ldcoord a, ld v) { ldcoord r; for(int i=0; i<MAXDIM; i++) r[i] = a[i] / v; return r; }
operator |(ldcoord a,ldcoord b)30   friend ld operator | (ldcoord a, ldcoord b) { ld r=0; for(int i=0; i<MAXDIM; i++) r += a[i] * b[i]; return r; }
31   };
32 
33 static const ldcoord ldc0 = {};
34 #endif
35 
36 #if CAP_CRYSTAL
37 
38 /** Crystal can be bitruncated either by changing variation to bitruncated.
39  *  In case of the 4D Crystal, the standard HyperRogue bitruncation becomes
40  *  confused by having both the original and new vertices of degree 8.
41  *  Hence Crystal implements its own bitruncation, which is selected/checked
42  *  by setting ginf[gCrystal].vertex to 3. Additionally, this lets us double
43  *  bitruncate.
44  *  Function pure() checks for both kinds of bitruncation (or any other variations).
45  */
46 
pure()47 EX bool pure() {
48   return PURE && ginf[gCrystal].vertex == 4;
49   }
50 
51 EX bool view_coordinates = false;
52 bool view_east = false;
53 
54 EX bool used_compass_inside;
55 
told(coord c)56 EX ldcoord told(coord c) { ldcoord a; for(int i=0; i<MAXDIM; i++) a[i] = c[i]; return a; }
57 // strange number to prevent weird acting in case of precision errors
roundcoord(ldcoord c)58 coord roundcoord(ldcoord c) { coord a; for(int i=0; i<MAXDIM; i++) a[i] = floor(c[i] + .5136); return a; }
59 
60 EX ld compass_probability = 1;
61 
tocode(int cname)62 int tocode(int cname) { return (1 << (cname >> 1)); }
63 
resize2(vector<vector<int>> & v,int a,int b,int z)64 void resize2(vector<vector<int>>& v, int a, int b, int z) {
65   v.clear();
66   v.resize(a);
67   for(auto& w: v) w.resize(b, z);
68   }
69 
70 /** in the "pure" form, the adjacent vertices are internaly spaced by 2 */
71 const int FULLSTEP = 2;
72 
73 /** to make space for the additional vertices which are added in the bitruncated version */
74 const int HALFSTEP = 1;
75 
76 /** with variations, the connections of the vertex at coordinate v+FULLSTEP mirror the connections
77  *  of the vertex at coordinate v. Therefore, the period of our construction is actually 2*FULLSTEP. */
78 const int PERIOD = 2 * FULLSTEP;
79 
80 struct crystal_structure {
81   int dir;
82   int dim;
83 
84   vector<vector<int>> cmap;
85   vector<vector<int>> next;
86   vector<vector<int>> prev;
87   vector<vector<int>> order;
88 
coord_to_nexthr::crystal::crystal_structure89   void coord_to_next() {
90     resize2(next, 1<<dim, 2*dim, -1);
91     for(int a=0; a<(1<<dim); a++)
92       for(int b=0; b<dir; b++)
93         next[a][cmap[a][b]] = cmap[a][(b+1)%dir];
94     println(hlog, next);
95     }
96 
next_to_coordhr::crystal::crystal_structure97   void next_to_coord() {
98     resize2(cmap, 1<<dim, dir, -1);
99     for(int a=0; a<(1<<dim); a++) {
100       int at = 0;
101       for(int b=0; b<dir; b++) {
102         cmap[a][b] = at;
103         at = next[a][at];
104         }
105       }
106     println(hlog, "coordinate map is:\n", cmap);
107     }
108 
next_to_prevhr::crystal::crystal_structure109   void next_to_prev() {
110     resize2(prev, 1<<dim, 2*dim, -1);
111     for(int a=0; a<(1<<dim); a++)
112       for(int b=0; b<dir; b++) {
113         if(next[a][b] != -1)
114           prev[a][next[a][b]] = b;
115         }
116     }
117 
coord_to_orderhr::crystal::crystal_structure118   void coord_to_order() {
119     println(hlog, dir, dim);
120     resize2(order, 1<<dim, 2*dim, -1);
121     for(int a=0; a<(1<<dim); a++)
122     for(int b=0; b<dir; b++)
123       order[a][cmap[a][b]] = b;
124     println(hlog, order);
125     }
126 
count_bugshr::crystal::crystal_structure127   int count_bugs() {
128     int bugcount = 0;
129 
130     for(int a=0; a<(1<<dim); a++)
131     for(int b=0; b<2*dim; b++) {
132       if(next[a][b] == -1) continue;
133       int qa = a, qb = b;
134       for(int i=0; i<4; i++) {
135         if(i == 2 && (qb != (b^1))) bugcount++;
136         qa ^= tocode(qb);
137         qb ^= 1;
138         qb = next[qa][qb];
139         }
140       if(a != qa || b != qb) bugcount++;
141       }
142 
143     return bugcount;
144     }
145 
next_inserthr::crystal::crystal_structure146   void next_insert(int a, int at, int val) {
147     int pd = next[a].size();
148 
149     next[a].resize(pd + 2);
150     next[a][val] = next[a][at];
151     next[a][at] = val;
152     next[a][val^1] = next[a][at^1];
153     next[a][at^1] = val^1;
154 
155     prev[a].resize(pd + 2);
156     prev[a][val] = at;
157     prev[a][next[a][val]] = val;
158     prev[a][val^1] = at^1;
159     prev[a][next[a][val^1]] = val^1;
160     }
161 
prev_inserthr::crystal::crystal_structure162   void prev_insert(int a, int at, int val) {
163     next_insert(a, prev[a][at], val);
164     }
165 
166   int errors;
167 
crystal_structurehr::crystal::crystal_structure168   crystal_structure() { errors = 0; }
169 
may_next_inserthr::crystal::crystal_structure170   bool may_next_insert(int a, int at, int val) {
171     if(isize(next[a]) != dir) {
172       next_insert(a, at, val);
173       return true;
174       }
175     else if(next[a][at] != val) errors++;
176     return false;
177     }
178 
may_prev_inserthr::crystal::crystal_structure179   bool may_prev_insert(int a, int at, int val) {
180     if(isize(prev[a]) != dir) {
181       prev_insert(a, at, val);
182       return true;
183       }
184     else if(prev[a][at] != val) errors++;
185     return false;
186     }
187 
add_dimension_tohr::crystal::crystal_structure188   void add_dimension_to(crystal_structure& poor) {
189     dir = poor.dir + 2;
190     dim = poor.dim + 1;
191 
192     printf("Building dimension %d\n", dim);
193 
194     next.resize(1<<dim);
195     prev.resize(1<<dim);
196     int mask = (1<<poor.dim) - 1;
197 
198     int mm = tocode(poor.dir);
199 
200     for(int i=0; i<(1<<dim); i++) {
201       if(i < mm)
202         next[i] = poor.next[i&mask], prev[i] = poor.prev[i&mask];
203       else
204         next[i] = poor.prev[i&mask], prev[i] = poor.next[i&mask];
205       }
206 
207     next_insert(0, 0, poor.dir);
208 
209     for(int s=2; s<1<<(dim-2); s+=2) {
210       if(next[s][0] < 4)
211         prev_insert(s, 0, poor.dir);
212       else
213         next_insert(s, 0, poor.dir);
214       }
215     // printf("next[%d][%d] = %d\n", 4, 2, next[4][2]);
216 
217     for(int s=0; s<8; s++) for(int a=0; a<(1<<dim); a++) if(isize(next[a]) > poor.dir) {
218       int which = next[a][poor.dir];
219       int a1 = a ^ tocode(which);
220 
221       may_next_insert(a1, which^1, poor.dir);
222       may_next_insert(a ^ mm, which, poor.dir^1);
223       which = prev[a][poor.dir];
224       a1 = a ^ tocode(which);
225       may_prev_insert(a1, which^1, poor.dir);
226       }
227 
228     // println(hlog, next);
229 
230     if(errors) { printf("errors: %d\n", errors); exit(1);; }
231 
232     int unf = 0;
233     for(int a=0; a<(1<<dim); a++) if(isize(next[a]) == poor.dir) {
234       if(!unf) printf("unf: ");
235       printf("%d ", a);
236       unf ++;
237       }
238 
239     if(unf) { printf("\n"); exit(2); }
240 
241     for(int a=0; a<(1<<dim); a++) for(int b=0; b<dir; b++)
242       if(prev[a][next[a][b]] != b) {
243         println(hlog, next[a], prev[a]);
244         printf("next/prev %d\n", a);
245         exit(3);
246         }
247 
248     if(count_bugs()) {
249       printf("bugs reported: %d\n", count_bugs());
250       exit(4);
251       }
252     }
253 
remove_half_dimensionhr::crystal::crystal_structure254   void remove_half_dimension() {
255     dir--;
256     for(int i=0; i<(1<<dim); i++) {
257       int take_what = dir-1;
258       if(i >= (1<<(dim-1))) take_what = dir;
259       next[i][prev[i][take_what]] = next[i][take_what],
260       prev[i][next[i][take_what]] = prev[i][take_what],
261       next[i].resize(dir),
262       prev[i].resize(dir);
263       }
264     }
265 
buildhr::crystal::crystal_structure266   void build() {
267     dir = 4;
268     dim = 2;
269     next.resize(4, {2,3,1,0});
270     next_to_prev();
271     while(dir < S7) {
272       crystal_structure csx = move(*this);
273       add_dimension_to(csx);
274       }
275     if(dir > S7) remove_half_dimension();
276 
277     next_to_coord();
278 
279     coord_to_order();
280     coord_to_next();
281     if(count_bugs()) {
282       printf("bugs found\n");
283       }
284     if(dir > MAX_EDGE_CRYSTAL || dim > MAXDIM) {
285       printf("Dimension or directions exceeded -- I have generated it, but won't play");
286       exit(0);
287       }
288     }
289 
290   };
291 
292 struct lwalker {
293   crystal_structure& cs;
294   int id;
295   int spin;
lwalkerhr::crystal::lwalker296   lwalker(crystal_structure& cs) : cs(cs) {}
operator =hr::crystal::lwalker297   void operator = (const lwalker& x) { id = x.id; spin = x.spin; }
lwalkerhr::crystal::lwalker298   constexpr lwalker(const lwalker& l) : cs(l.cs), id(l.id), spin(l.spin) {}
299   };
300 
operator +(lwalker a,int v)301 lwalker operator +(lwalker a, int v) { a.spin = gmod(a.spin + v, a.cs.dir); return a; }
302 
operator +(lwalker a,wstep_t)303 lwalker operator +(lwalker a, wstep_t) {
304   a.spin = a.cs.cmap[a.id][a.spin];
305   a.id ^= tocode(a.spin);
306   a.spin = a.cs.order[a.id][a.spin^1];
307   return a;
308   }
309 
add(coord c,lwalker a,int val)310 coord add(coord c, lwalker a, int val) {
311   int code = a.cs.cmap[a.id][a.spin];
312   c[code>>1] += ((code&1) ? val : -val);
313   return c;
314   }
315 
add(coord c,int cname,int val)316 coord add(coord c, int cname, int val) {
317   int dim = (cname>>1);
318   c[dim] = (c[dim] + (cname&1?val:-val));
319   return c;
320   }
321 
sqhypot2(crystal_structure & cs,ldcoord co1,ldcoord co2)322 ld sqhypot2(crystal_structure& cs, ldcoord co1, ldcoord co2) {
323   int result = 0;
324   for(int a=0; a<cs.dim; a++) result += (co1[a] - co2[a]) * (co1[a] - co2[a]);
325   return result;
326   }
327 
328 static const int Modval = 64;
329 
330 struct east_structure {
331   map<coord, int> data;
332   int Xmod, cycle;
333   int zeroshift;
334   int coordid;
335   };
336 
fiftyrule(coord c)337 int fiftyrule(coord c) {
338   int res[256] = {
339      1,-1,32,-1,-1, 2,-1,35,32,-1, 1,-1,-1,35,-1, 2,
340     -1,-1,-1,-1, 4,-1,36,-1,-1,-1,-1,-1,36,-1, 4,-1,
341     32,-1, 1,-1,-1,34,-1, 3, 1,-1,32,-1,-1, 3,-1,34,
342     -1,-1,-1,-1,36,-1, 4,-1,-1,-1,-1,-1, 4,-1,36,-1,
343     -1, 4,-1,36,-1,-1,-1,-1,-1,36,-1, 4,-1,-1,-1,-1,
344      3,-1,35,-1,-1,-1,-1,-1,35,-1, 3,-1,-1,-1,-1,-1,
345     -1,36,-1, 4,-1,-1,-1,-1,-1, 4,-1,36,-1,-1,-1,-1,
346     34,-1, 2,-1,-1,-1,-1,-1, 2,-1,34,-1,-1,-1,-1,-1,
347     32,-1, 1,-1,-1,34,-1, 3, 1,-1,32,-1,-1, 3,-1,34,
348     -1,-1,-1,-1,36,-1, 4,-1,-1,-1,-1,-1, 4,-1,36,-1,
349      1,-1,32,-1,-1, 2,-1,35,32,-1, 1,-1,-1,35,-1, 2,
350     -1,-1,-1,-1, 4,-1,36,-1,-1,-1,-1,-1,36,-1, 4,-1,
351     -1,36,-1, 4,-1,-1,-1,-1,-1, 4,-1,36,-1,-1,-1,-1,
352     34,-1, 2,-1,-1,-1,-1,-1, 2,-1,34,-1,-1,-1,-1,-1,
353     -1, 4,-1,36,-1,-1,-1,-1,-1,36,-1, 4,-1,-1,-1,-1,
354      3,-1,35,-1,-1,-1,-1,-1,35,-1, 3,-1,-1,-1,-1,-1,
355      };
356 
357   int index = 0;
358   for(int i=0; i<4; i++) index += (c[i] & 3) << (2 * i);
359 
360   if(res[index] == -1) exit(1);
361 
362   return res[index];
363   }
364 
365 bool is_bi(crystal_structure& cs, coord co);
366 
367 #if MAXMDIM >= 4
368 typedef array<coord, 12> shifttable;
369 
370 int ctable[64][6] = {
371    {0, 1, 2, 3, 4, 5, },
372    {6, 1, 5, 4, 3, 2, },
373    {0, 7, 5, 4, 3, 2, },
374    {6, 7, 2, 3, 4, 5, },
375    {0, 1, 5, 4, 3, 8, },
376    {6, 1, 8, 3, 4, 5, },
377    {0, 7, 8, 3, 4, 5, },
378    {6, 7, 5, 4, 3, 8, },
379    {0, 1, 5, 4, 9, 2, },
380    {6, 1, 2, 9, 4, 5, },
381    {0, 7, 2, 9, 4, 5, },
382    {6, 7, 5, 4, 9, 2, },
383    {0, 1, 8, 9, 4, 5, },
384    {6, 1, 5, 4, 9, 8, },
385    {0, 7, 5, 4, 9, 8, },
386    {6, 7, 8, 9, 4, 5, },
387    {0, 1, 5, 10, 3, 2, },
388    {6, 1, 2, 3, 10, 5, },
389    {0, 7, 2, 3, 10, 5, },
390    {6, 7, 5, 10, 3, 2, },
391    {0, 1, 8, 3, 10, 5, },
392    {6, 1, 5, 10, 3, 8, },
393    {0, 7, 5, 10, 3, 8, },
394    {6, 7, 8, 3, 10, 5, },
395    {0, 1, 2, 9, 10, 5, },
396    {6, 1, 5, 10, 9, 2, },
397    {0, 7, 5, 10, 9, 2, },
398    {6, 7, 2, 9, 10, 5, },
399    {0, 1, 5, 10, 9, 8, },
400    {6, 1, 8, 9, 10, 5, },
401    {0, 7, 8, 9, 10, 5, },
402    {6, 7, 5, 10, 9, 8, },
403    {0, 1, 11, 4, 3, 2, },
404    {6, 1, 2, 3, 4, 11, },
405    {0, 7, 2, 3, 4, 11, },
406    {6, 7, 11, 4, 3, 2, },
407    {0, 1, 8, 3, 4, 11, },
408    {6, 1, 11, 4, 3, 8, },
409    {0, 7, 11, 4, 3, 8, },
410    {6, 7, 8, 3, 4, 11, },
411    {0, 1, 2, 9, 4, 11, },
412    {6, 1, 11, 4, 9, 2, },
413    {0, 7, 11, 4, 9, 2, },
414    {6, 7, 2, 9, 4, 11, },
415    {0, 1, 11, 4, 9, 8, },
416    {6, 1, 8, 9, 4, 11, },
417    {0, 7, 8, 9, 4, 11, },
418    {6, 7, 11, 4, 9, 8, },
419    {0, 1, 2, 3, 10, 11, },
420    {6, 1, 11, 10, 3, 2, },
421    {0, 7, 11, 10, 3, 2, },
422    {6, 7, 2, 3, 10, 11, },
423    {0, 1, 11, 10, 3, 8, },
424    {6, 1, 8, 3, 10, 11, },
425    {0, 7, 8, 3, 10, 11, },
426    {6, 7, 11, 10, 3, 8, },
427    {0, 1, 11, 10, 9, 2, },
428    {6, 1, 2, 9, 10, 11, },
429    {0, 7, 2, 9, 10, 11, },
430    {6, 7, 11, 10, 9, 2, },
431    {0, 1, 8, 9, 10, 11, },
432    {6, 1, 11, 10, 9, 8, },
433    {0, 7, 11, 10, 9, 8, },
434    {6, 7, 8, 9, 10, 11, },
435    };
436 
get_canonical(coord co)437 shifttable get_canonical(coord co) {
438   shifttable res;
439   if(S7 == 12) {
440     int eid = 0;
441     for(int a=0; a<6; a++) if(co[a] & 2) eid += (1<<a);
442     for(int i=0; i<12; i++) res[i] = c0;
443     for(int i=0; i<6; i++) {
444       int c = ctable[eid][i];
445       res[i][c % 6] = (c>=6) ? -2 : 2;
446       res[6+i][c % 6] = (c>=6) ? 2 : -2;
447       }
448     }
449   else {
450     for(int i=0; i<4; i++) {
451       res[i] = c0;
452       res[i][i] = 2;
453       res[i+4] = c0;
454       res[i+4][i] = -2;
455       }
456     for(int a=0; a<4; a++) if(co[a] & 2) swap(res[a], res[a+4]);
457     int bts = 0;
458     for(int a=0; a<4; a++) if(co[a] & 2) bts++;
459     if(bts & 1) swap(res[2], res[3]), swap(res[6], res[7]);
460     }
461   return res;
462   }
463 #endif
464 
465 EX int crystal_period = 0;
466 
467 struct hrmap_crystal : hrmap_standard {
getOriginhr::crystal::hrmap_crystal468   heptagon *getOrigin() override { return get_heptagon_at(c0, S7); }
469 
470   map<heptagon*, coord> hcoords;
471   map<coord, heptagon*> heptagon_at;
472   map<int, eLand> landmemo;
473   map<coord, eLand> landmemo4;
474   map<cell*, map<cell*, int>> distmemo;
475   map<cell*, ldcoord> sgc;
476   cell *camelot_center;
477   ldcoord camelot_coord;
478   ld camelot_mul;
479 
480   crystal_structure cs;
481   east_structure east;
482 
makewalkerhr::crystal::hrmap_crystal483   lwalker makewalker(coord c, int d) {
484     lwalker a(cs);
485     a.id = 0;
486     for(int i=0; i<cs.dim; i++) if(c[i] & FULLSTEP) a.id += (1<<i);
487     a.spin = d;
488     return a;
489     }
490 
crystal3hr::crystal::hrmap_crystal491   bool crystal3() { return WDIM == 3; }
492 
hrmap_crystalhr::crystal::hrmap_crystal493   hrmap_crystal() {
494 #if MAXMDIM >= 4
495     if(crystal3()) reg3::generate(), cs.dim = S7 / 2; else
496 #endif
497     cs.build();
498 
499     camelot_center = NULL;
500     }
501 
~hrmap_crystalhr::crystal::hrmap_crystal502   ~hrmap_crystal() {
503     clearfrom(getOrigin());
504     }
505 
get_heptagon_athr::crystal::hrmap_crystal506   heptagon *get_heptagon_at(coord c, int deg) {
507     if(heptagon_at.count(c)) return heptagon_at[c];
508     heptagon*& h = heptagon_at[c];
509     h = init_heptagon(deg);
510     h->c7 = newCell(deg, h);
511 
512     /* in {6,4} we need emeraldval for some patterns, including (bitruncated) football and (bitruncated) three-color */
513     h->emeraldval = (c[0] ^ c[1] ^ c[2]) & 2;
514     h->emeraldval ^= (c[1] & 4);
515     h->emeraldval ^= (c[0] & 4);
516     h->emeraldval ^= (c[2] & 4);
517     h->emeraldval ^= ((c[2] & 2) << 1);
518     if(c[0] & 2) h->emeraldval ^= 1;
519 
520     if(ginf[gCrystal].vertex == 3)
521       h->fiftyval = fiftyrule(c);
522     for(int i=0; i<cs.dim; i++) h->distance += abs(c[i]);
523     h->distance /= 2;
524     hcoords[h] = c;
525     // for(int i=0; i<6; i++) crystalstep(h, i);
526     return h;
527     }
528 
get_coordhr::crystal::hrmap_crystal529   ldcoord get_coord(cell *c) {
530     // in C++14?
531     // auto b = sgc.emplace(c, ldc0);
532     // ldcoord& res = b.first->second;
533     if(sgc.count(c)) return sgc[c];
534     ldcoord& res = (sgc[c] = ldc0);
535     { // if(b.second) {
536       if(BITRUNCATED && c->master->c7 != c) {
537         for(int i=0; i<c->type; i+=2)
538           res = res + told(hcoords[c->cmove(i)->master]);
539         res = res * 2 / c->type;
540         }
541       else if(GOLDBERG && c->master->c7 != c) {
542         auto m = gp::get_masters(c);
543         auto H = gp::get_master_coordinates(c);
544         for(int i=0; i<cs.dim; i++)
545           res = res + told(hcoords[m[i]]) * H[i];
546         }
547       else
548         res = told(hcoords[c->master]);
549       }
550     return res;
551     }
552 
553   coord long_representant(cell *c);
554 
555   int get_east(cell *c);
556 
557   void build_east(int cid);
558 
verifyhr::crystal::hrmap_crystal559   void verify() override { }
560 
561   void prepare_east();
562 
apply_periodhr::crystal::hrmap_crystal563   void apply_period(coord& co) {
564     for(int a=0; a<cs.dim; a++)
565       co[a] = szgmod(co[a], 2*crystal_period);
566     }
567 
create_stephr::crystal::hrmap_crystal568   heptagon *create_step(heptagon *h, int d) override {
569     if(!hcoords.count(h)) {
570       printf("not found\n");
571       return NULL;
572       }
573     auto co = hcoords[h];
574 
575     #if MAXMDIM >= 4
576     if(crystal3()) {
577       auto st = get_canonical(co);
578       auto co1 = co + st[d];
579       apply_period(co1);
580       auto h1 = get_heptagon_at(co1, S7);
581       auto st1 = get_canonical(co1);
582 
583       for(int d1=0; d1<S7; d1++) if(st1[d1] == st[d])
584         h->c.connect(d, h1, (d1+S7/2) % S7, false);
585 
586       return h1;
587       }
588     #endif
589 
590     if(is_bi(cs, co)) {
591       heptspin hs(h, d);
592       (hs + 1 + wstep + 1).cpeek();
593       return h->move(d);
594       }
595 
596     auto lw = makewalker(co, d);
597 
598     if(ginf[gCrystal].vertex == 4) {
599       auto c1 = add(co, lw, FULLSTEP);
600       auto lw1 = lw+wstep;
601       apply_period(c1);
602 
603       h->c.connect(d, heptspin(get_heptagon_at(c1, S7), lw1.spin));
604       }
605     else {
606       auto coc = add(add(co, lw, HALFSTEP), lw+1, HALFSTEP);
607       auto hc = get_heptagon_at(coc, 8);
608       apply_period(coc);
609       for(int a=0; a<8; a+=2) {
610         hc->c.connect(a, heptspin(h, lw.spin));
611         if(h->modmove(lw.spin-1)) {
612           hc->c.connect(a+1, heptspin(h, lw.spin) - 1 + wstep - 1);
613           }
614         co = add(co, lw, FULLSTEP);
615         apply_period(co);
616         lw = lw + wstep + (-1);
617         h = get_heptagon_at(co, S7);
618         }
619       }
620     return h->move(d);
621     }
622 
623   #if MAXMDIM >= 4
624   map<int, transmatrix> adjs;
625 
adjhr::crystal::hrmap_crystal626   transmatrix adj(heptagon *h, int d) override {
627     if(!crystal3()) return hrmap_standard::adj(h, d);
628     auto co = hcoords[h];
629     int id = 0;
630     for(int a=0; a<S7/2; a++) id = (2*id) + ((co[a]>>1) & 1);
631     id = S7*id + d;
632     if(adjs.count(id)) return adjs[id];
633     transmatrix T = cgi.adjmoves[d];
634     reg3::generate_cellrotations();
635     auto st = get_canonical(co);
636     auto co1 = co + st[d];
637     auto st1 = get_canonical(co1);
638     int qty = 0;
639     transmatrix res;
640     ld gdist = S7 == 12 ? hdist0(tC0(cgi.adjmoves[0])) : cgi.strafedist;
641 
642     for(auto& cr: cgi.cellrotations) {
643 
644       transmatrix U = T * cr.M;
645 
646       ld go = hdist0(U * tC0(cgi.adjmoves[h->c.spin(d)]));
647       if(go > 1e-2) continue;
648 
649       for(int s=0; s<S7; s++)
650         if(cgi.heptshape->dirdist[d][s] == 1)
651           for(int t=0; t<S7; t++)
652             if(st1[t] == st[s]) {
653               if(hdist(U * tC0(cgi.adjmoves[t]), tC0(cgi.adjmoves[s])) > gdist + .1)
654                 goto wrong;
655               }
656       res = U;
657       qty++;
658       wrong: ;
659       }
660     adjs[id] = res;
661     if(qty == 1) return res;
662     println(hlog, "qty = ", qty);
663     exit(1);
664     }
665 
adjhr::crystal::hrmap_crystal666   transmatrix adj(cell *c, int d) override {
667     if(crystal3()) return adj(c->master, d);
668     return hrmap_standard::adj(c, d);
669     }
670 
draw_athr::crystal::hrmap_crystal671   void draw_at(cell *at, const shiftmatrix& where) override {
672     if(!crystal3()) { hrmap_standard::draw_at(at, where); return; }
673     else hrmap::draw_at(at, where);
674     }
675 
relative_matrixchr::crystal::hrmap_crystal676   transmatrix relative_matrixc(cell *h2, cell *h1, const hyperpoint& hint) override {
677     if(!crystal3()) return hrmap_standard::relative_matrix(h2, h1, hint);
678     if(h2 == h1) return Id;
679     for(int i=0; i<S7; i++) if(h2 == h1->move(i)) return adj(h1->master, i);
680     if(gmatrix0.count(h2) && gmatrix0.count(h1))
681       return inverse_shift(gmatrix0[h1], gmatrix0[h2]);
682     println(hlog, "unknown relmatrix, distance = ", celldistance(h1, h2));
683     return xpush(999);
684     }
685 
relative_matrixhhr::crystal::hrmap_crystal686   transmatrix relative_matrixh(heptagon *h2, heptagon *h1, const hyperpoint& hint) override {
687     if(!crystal3()) return hrmap::relative_matrix(h2, h1, hint);
688     return relative_matrix(h2->c7, h1->c7, hint);
689     }
690   #endif
691   };
692 
crystal_map()693 hrmap_crystal *crystal_map() {
694   return (hrmap_crystal*) currentmap;
695   }
696 
get_heptagon_at(coord c)697 EX heptagon *get_heptagon_at(coord c) { return crystal_map()->get_heptagon_at(c, S7); }
get_coord(heptagon * h)698 EX coord get_coord(heptagon *h) { return crystal_map()->hcoords[h]; }
get_ldcoord(cell * c)699 EX ldcoord get_ldcoord(cell *c) { return crystal_map()->get_coord(c); }
700 
get_dim()701 EX int get_dim() { return crystal_map()->cs.dim; }
702 
703 #if MAXMDIM >= 4
get_adj(heptagon * h,int d)704 EX transmatrix get_adj(heptagon *h, int d) { return crystal_map()->adj(h, d); }
705 #endif
706 
is_bi(crystal_structure & cs,coord co)707 bool is_bi(crystal_structure& cs, coord co) {
708   for(int i=0; i<cs.dim; i++) if(co[i] & HALFSTEP) return true;
709   return false;
710   }
711 
712 array<array<int,2>, MAX_EDGE_CRYSTAL> distlimit_table = {{
713   {{SEE_ALL,SEE_ALL}}, {{SEE_ALL,SEE_ALL}}, {{SEE_ALL,SEE_ALL}}, {{SEE_ALL,SEE_ALL}}, {{15, 10}},
714   {{6, 4}}, {{5, 3}}, {{4, 3}}, {{4, 3}}, {{3, 2}}, {{3, 2}}, {{3, 2}}, {{3, 2}}, {{3, 2}}
715   }};
716 
colorize(cell * c,char whichCanvas)717 EX color_t colorize(cell *c, char whichCanvas) {
718   auto m = crystal_map();
719   ldcoord co = ldc0;
720   int dim = 3;
721   if(cryst) co = m->get_coord(c), dim = m->cs.dim;
722   #if MAXMDIM >= 4
723   else if(geometry == gSpace344) {
724     co = told(reg3::decode_coord(c->master->fieldval)), dim = 4;
725     for(int a=0; a<4; a++) if(co[a] > 4) co[a] -= 8;
726     }
727   else if(geometry == gSeifertCover) {
728     int i = c->master->fieldval;
729     for(int a=0; a<3; a++) co[a] = i%5, i /= 5;
730     }
731   #endif
732   else if(euc::in()) {
733     auto tab = euc::get_ispacemap()[c->master];
734     for(int a=0; a<3; a++) co[a] = tab[a];
735     if(PURE) for(int a=0; a<3; a++) co[a] *= 2;
736     dim = 3;
737     }
738 
739   color_t res = 0;
740   coord ico = roundcoord(co);
741 
742   int ones = 0;
743   for(int i=0; i<dim; i++) if((ico[i] & 2) == 2) ones++;
744 
745   switch(whichCanvas) {
746     case 'K':
747       for(int i=0; i<3; i++)
748         res |= ((int)(((i == 2 && S7 == 5) ? (128 + co[i] * 50) : (255&int(128 + co[i] * 25))))) << (8*i);
749       return res;
750 
751     case '@': {
752       if(ico[dim-1] == 2 && (ones & 1)) return 0x1C0FFC0;
753       if(ico[dim-1] == 2 && !(ones & 1)) return 0x180FF80;
754       if(ico[dim-1] == -4 && (ones & 1)) return 0x180C0FF;
755       if(ico[dim-1] == -4 && !(ones & 1)) return 0x14080FF;
756       return 0x101010;
757       }
758 
759     case '=':
760       if(ico[dim-1] == 2 && (ones & 1)) return 0x1C0FFC0;
761       if(ico[dim-1] == 2 && !(ones & 1)) return 0x180FF80;
762       if(ico[dim-1] == -2 && (ones & 1)) return 0x180C0FF;
763       if(ico[dim-1] == -2 && !(ones & 1)) return 0x14080FF;
764 
765       return 0x101010;
766 
767     case '#': {
768       bool grid = false;
769       ico[dim-1] -= 2;
770       for(int d=dim; d<MAXDIM; d++) ico[d] = 0;
771       for(int i=0; i<dim; i++) if((ico[i] & 6) == 4) grid = true;
772 
773       for(int i=0; i<3; i++) part(res, i) = 0xFF + 0x18 * (ico[i]/2-2);
774       if(grid) res |= 0x1000000;
775       else if(GDIM == 2) res = (res & 0xFEFEFE) >> 1;
776       if(ico == c0) res = 0x1FFD500;
777       return res;
778       }
779 
780     case 'O': {
781       for(int i=0; i<3; i++) part(res, i) = 0xFF + 0x18 * (ico[i]/2-2);
782       c->landparam = res;
783       if(ones == dim-1) res |= 0x1000000;
784       else if(GDIM == 2) res = (res & 0xFEFEFE) >> 1;
785       return res;
786       }
787 
788     case '/': {
789       int s = 0;
790       for(int a=0; a<dim; a++) s += ico[a];
791       if(s > 0) return 0x1FF20FF;
792       else if (s < -2) return 0x1C0C0C0;
793       }
794     }
795   return res;
796   }
797 
798 EX colortable coordcolors = {0xD04040, 0x40D040, 0x4040D0, 0xFFD500, 0xF000F0, 0x00F0F0, 0xF0F0F0 };
799 
compass_angle()800 EX ld compass_angle() {
801   bool bitr = ginf[gCrystal].vertex == 3;
802   return (bitr ? M_PI/8 : 0) - master_to_c7_angle();
803   }
804 
crystal_cell(cell * c,shiftmatrix V)805 EX bool crystal_cell(cell *c, shiftmatrix V) {
806 
807   if(!cryst) return false;
808 
809   if(view_east && cheater) {
810     int d = dist_alt(c);
811     queuestr(V, 0.3, its(d), 0xFFFFFF, 1);
812     }
813 
814   if(view_coordinates && WDIM == 2 && cheater) {
815 
816     auto m = crystal_map();
817 
818     if(c->master->c7 == c && !is_bi(m->cs, m->hcoords[c->master])) {
819 
820       ld dist = cellgfxdist(c, 0);
821 
822       for(int i=0; i<S7; i++)  {
823         shiftmatrix T = V * spin(compass_angle() - 2 * M_PI * i / S7) * xpush(dist*.3);
824 
825         auto co = m->hcoords[c->master];
826         auto lw = m->makewalker(co, i);
827         int cx = m->cs.cmap[lw.id][i];
828 
829         queuestr(T, 0.3, its(co[cx>>1] / FULLSTEP), coordcolors[cx>>1], 1);
830         }
831       }
832     }
833   return false;
834   }
835 
build_shortest_path(cell * c1,cell * c2)836 EX vector<cell*> build_shortest_path(cell *c1, cell *c2) {
837   auto m = crystal_map();
838   ldcoord co1 = m->get_coord(c1);
839   ldcoord co2 = m->get_coord(c2) - co1;
840 
841   // draw a cylinder from co1 to co2, and find the solution by going through that cylinder
842 
843   ldcoord mul = co2 / sqrt(co2|co2);
844 
845   ld mmax = (co2|mul);
846 
847   vector<cell*> p;
848   vector<int> parent_id;
849 
850   manual_celllister cl;
851   cl.add(c2);
852   parent_id.push_back(-1);
853 
854   int steps = 0;
855   int nextsteps = 1;
856 
857   for(int i=0; i<isize(cl.lst); i++) {
858     if(i == nextsteps) steps++, nextsteps = isize(cl.lst);
859     cell *c = cl.lst[i];
860     forCellCM(c3, c) if(!cl.listed(c3)) {
861       if(c3 == c1) {
862         p.push_back(c1);
863         while(c3 != c2) {
864           while(i) {
865             p.push_back(c3);
866             i = parent_id[i];
867             c3 = cl.lst[i];
868             }
869           }
870         p.push_back(c3);
871         return p;
872         }
873 
874       auto h = m->get_coord(c3) - co1;
875       ld dot = (h|mul);
876       if(dot > mmax + PERIOD/2 + .1) continue;
877 
878       for(int k=0; k<m->cs.dim; k++) if(abs(h[k] - dot * mul[k]) > PERIOD + .1) goto next3;
879       cl.add(c3);
880       parent_id.push_back(i);
881       next3: ;
882       }
883     }
884 
885   println(hlog, "Error: path not found");
886   return p;
887   }
888 
precise_distance(cell * c1,cell * c2)889 EX int precise_distance(cell *c1, cell *c2) {
890   if(c1 == c2) return 0;
891   auto m = crystal_map();
892   if(pure()) {
893     coord co1 = m->hcoords[c1->master];
894     coord co2 = m->hcoords[c2->master];
895     int result = 0;
896     for(int a=0; a<m->cs.dim; a++) result += abs(co1[a] - co2[a]);
897     return result / FULLSTEP;
898     }
899 
900   auto& distmemo = m->distmemo;
901 
902   if(c2 == currentmap->gamestart()) swap(c1, c2);
903   else if(isize(distmemo[c2]) > isize(distmemo[c1])) swap(c1, c2);
904 
905   if(distmemo[c1].count(c2)) return distmemo[c1][c2];
906 
907   int zmin = 999999, zmax = -99;
908   forCellEx(c3, c2) if(distmemo[c1].count(c3)) {
909      int d = distmemo[c1][c3];
910      if(d < zmin) zmin = d;
911      if(d > zmax) zmax = d;
912      }
913   if(zmin+1 < zmax-1) println(hlog, "zmin < zmax");
914   if(zmin+1 == zmax-1) return distmemo[c1][c2] = zmin+1;
915 
916   ldcoord co1 = m->get_coord(c1);
917   ldcoord co2 = m->get_coord(c2) - co1;
918 
919   // draw a cylinder from co1 to co2, and find the solution by going through that cylinder
920 
921   ldcoord mul = co2 / sqrt(co2|co2);
922 
923   ld mmax = (co2|mul);
924 
925   manual_celllister cl;
926   cl.add(c2);
927 
928   int steps = 0;
929   int nextsteps = 1;
930 
931   for(int i=0; i<isize(cl.lst); i++) {
932     if(i == nextsteps) steps++, nextsteps = isize(cl.lst);
933     cell *c = cl.lst[i];
934     forCellCM(c3, c) if(!cl.listed(c3)) {
935       if(c3 == c1) {
936         return distmemo[c1][c2] = distmemo[c2][c1] = 1 + steps;
937         }
938 
939       auto h = m->get_coord(c3) - co1;
940       ld dot = (h|mul);
941       if(dot > mmax + PERIOD/2 + .1) continue;
942 
943       for(int k=0; k<m->cs.dim; k++) if(abs(h[k] - dot * mul[k]) > PERIOD + .1) goto next3;
944       cl.add(c3);
945       next3: ;
946       }
947     }
948 
949   println(hlog, "Error: distance not found");
950   return 999999;
951   }
952 
space_distance(cell * c1,cell * c2)953 EX ld space_distance(cell *c1, cell *c2) {
954   auto m = crystal_map();
955   ldcoord co1 = m->get_coord(c1);
956   ldcoord co2 = m->get_coord(c2);
957   return sqrt(sqhypot2(m->cs, co1, co2));
958   }
959 
space_distance_camelot(cell * c)960 EX ld space_distance_camelot(cell *c) {
961   auto m = crystal_map();
962   return m->camelot_mul * sqrt(sqhypot2(m->cs, m->get_coord(c), m->camelot_coord));
963   }
964 
dist_relative(cell * c)965 EX int dist_relative(cell *c) {
966   auto m = crystal_map();
967   auto& cc = m->camelot_center;
968   int r = roundTableRadius(NULL);
969   cell *start = m->gamestart();
970   if(!cc) {
971     println(hlog, "Finding Camelot center...");
972     cc = start;
973     while(precise_distance(cc, start) < r + 5)
974       cc = cc->cmove(hrand(cc->type));
975 
976     m->camelot_coord = m->get_coord(m->camelot_center);
977     if(m->cs.dir % 2)
978       m->camelot_coord[m->cs.dim-1] = 1;
979 
980     m->camelot_mul = 1;
981     m->camelot_mul *= (r+5) / space_distance_camelot(start);
982     }
983 
984   if(pure())
985     return precise_distance(c, cc) - r;
986 
987   ld dis = space_distance_camelot(c);
988   if(dis < r)
989     return int(dis) - r;
990   else {
991     forCellCM(c1, c) if(space_distance_camelot(c1) < r)
992       return 0;
993     return int(dis) + 1 - r;
994     }
995   }
996 
long_representant(cell * c)997 coord hrmap_crystal::long_representant(cell *c) {
998   auto& coordid = east.coordid;
999   auto co = roundcoord(get_coord(c) * Modval/PERIOD);
1000   for(int s=0; s<coordid; s++) co[s] = gmod(co[s], Modval);
1001   for(int s=coordid+1; s<cs.dim; s++) {
1002     int v = gdiv(co[s], Modval);
1003     co[s] -= v * Modval;
1004     co[coordid] += v * Modval;
1005     }
1006   return co;
1007   }
1008 
get_east(cell * c)1009 int hrmap_crystal::get_east(cell *c) {
1010   auto& coordid = east.coordid;
1011   auto& Xmod = east.Xmod;
1012   auto& data = east.data;
1013   auto& cycle = east.cycle;
1014 
1015   coord co = long_representant(c);
1016   int cycles = gdiv(co[coordid], Xmod);
1017   co[coordid] -= cycles * Xmod;
1018   return data[co] + cycle * cycles;
1019   }
1020 
build_east(int cid)1021 void hrmap_crystal::build_east(int cid) {
1022   auto& coordid = east.coordid;
1023   auto& Xmod = east.Xmod;
1024   auto& data = east.data;
1025   auto& cycle = east.cycle;
1026 
1027   coordid = cid;
1028   map<coord, int> full_data;
1029   manual_celllister cl;
1030 
1031   for(int i=0; i<(1<<cid); i++) {
1032     auto co = c0;
1033     for(int j=0; j<cid; j++) co[j] = ((i>>j)&1) * 2;
1034     cell *cc = get_heptagon_at(co, cs.dir)->c7;
1035     cl.add(cc);
1036     }
1037 
1038   map<coord, int> stepat;
1039 
1040   int steps = 0, nextstep = isize(cl.lst);
1041 
1042   cycle = 0;
1043   int incycle = 0;
1044   int needcycle = 16 + nextstep;
1045   int elongcycle = 0;
1046 
1047   Xmod = Modval;
1048 
1049   int modmul = 1;
1050 
1051   for(int i=0; i<isize(cl.lst); i++) {
1052     if(incycle > needcycle * modmul) break;
1053     if(i == nextstep) steps++, nextstep = isize(cl.lst);
1054     cell *c = cl.lst[i];
1055 
1056     auto co = long_representant(c);
1057     if(co[coordid] < -Modval) continue;
1058     if(full_data.count(co)) continue;
1059     full_data[co] = steps;
1060 
1061     auto co1 = co; co1[coordid] -= Xmod;
1062     auto co2 = co; co2[coordid] = gmod(co2[coordid], Xmod);
1063 
1064     if(full_data.count(co1)) {
1065       int ncycle = steps - full_data[co1];
1066       if(ncycle != cycle) incycle = 1, cycle = ncycle;
1067       else incycle++;
1068       int dd = gdiv(co[coordid], Xmod);
1069       // println(hlog, co, " set data at ", co2, " from ", data[co2], " to ", steps - dd * cycle, " at step ", steps);
1070       data[co2] = steps - dd * cycle;
1071       elongcycle++;
1072       if(elongcycle > 2 * needcycle * modmul) Xmod += Modval, elongcycle = 0, modmul++;
1073       }
1074     else incycle = 0, needcycle++, elongcycle = 0;
1075     forCellCM(c1, c) cl.add(c1);
1076     }
1077 
1078   east.zeroshift = 0;
1079   east.zeroshift = -get_east(cl.lst[0]);
1080 
1081   println(hlog, "cycle found: ", cycle, " Xmod = ", Xmod, " on list: ", isize(cl.lst), " zeroshift: ", east.zeroshift);
1082   }
1083 
prepare_east()1084 void hrmap_crystal::prepare_east() {
1085   if(east.data.empty()) build_east(1);
1086   }
1087 
dist_alt(cell * c)1088 EX int dist_alt(cell *c) {
1089   auto m = crystal_map();
1090   if(specialland == laCamelot && m->camelot_center) {
1091     if(pure())
1092       return precise_distance(c, m->camelot_center);
1093     if(c == m->camelot_center) return 0;
1094     return 1 + int(2 * space_distance_camelot(c));
1095     }
1096   else {
1097     m->prepare_east();
1098     return m->get_east(c);
1099     }
1100   }
1101 
1102 array<array<ld, MAXDIM>, MAXDIM> crug_rotation;
1103 
1104 int ho = 1;
1105 
1106 ldcoord rug_center;
1107 bool draw_cut = false;
1108 ld cut_level = 0;
1109 
init_rotation()1110 EX void init_rotation() {
1111   for(int i=0; i<MAXDIM; i++)
1112   for(int j=0; j<MAXDIM; j++)
1113     crug_rotation[i][j] = i == j ? 1/2. : 0;
1114 
1115   auto& cs = crystal_map()->cs;
1116 
1117   if(ho & 1) {
1118     for(int i=(draw_cut ? 2 : cs.dim-1); i>=1; i--) {
1119       ld c = cos(M_PI / 2 / (i+1));
1120       ld s = sin(M_PI / 2 / (i+1));
1121       for(int j=0; j<cs.dim; j++)
1122         tie(crug_rotation[j][0], crug_rotation[j][i]) =
1123           make_pair(
1124              crug_rotation[j][0] * s + crug_rotation[j][i] * c,
1125             -crug_rotation[j][i] * s + crug_rotation[j][0] * c
1126             );
1127       }
1128     }
1129   }
1130 
random_rotation()1131 EX void random_rotation() {
1132   auto& cs = crystal_map()->cs;
1133   for(int i=0; i<100; i++) {
1134     int a = hrand(cs.dim);
1135     int b = hrand(cs.dim);
1136     if(a == b) continue;
1137     ld alpha = hrand(1000);
1138     ld c = cos(alpha);
1139     ld s = sin(alpha);
1140     for(int u=0; u<cs.dim; u++) {
1141       auto& x = crug_rotation[u][a];
1142       auto& y = crug_rotation[u][b];
1143       tie(x,y) = make_pair(x * c + y * s, y * c - x * s);
1144       }
1145     }
1146   }
1147 
1148 
next_home_orientation()1149 EX void next_home_orientation() {
1150   ho++;
1151   init_rotation();
1152   }
1153 
flip_z()1154 EX void flip_z() {
1155   for(int i=0; i<MAXDIM; i++)
1156     crug_rotation[i][2] *= -1;
1157   }
1158 
1159 #if CAP_RUG
1160 #if MAXMDIM >= 4
coord_to_flat(ldcoord co,int dim=3)1161 hyperpoint coord_to_flat(ldcoord co, int dim = 3) {
1162   auto& cs = crystal_map()->cs;
1163   hyperpoint res = Hypc;
1164   co = co - rug_center;
1165   for(int a=0; a<cs.dim; a++)
1166     for(int b=0; b<dim; b++)
1167       res[b] += crug_rotation[b][a] * co[a] * rug::modelscale;
1168   return res;
1169   }
1170 #endif
1171 
switch_z_coordinate()1172 EX void switch_z_coordinate() {
1173   auto& cs = crystal_map()->cs;
1174   for(int i=0; i<cs.dim; i++) {
1175     ld tmp = crug_rotation[i][2];
1176     for(int u=2; u<cs.dim-1; u++) crug_rotation[i][u] = crug_rotation[i][u+1];
1177     crug_rotation[i][cs.dim-1] = tmp;
1178     }
1179   }
1180 
apply_rotation(const transmatrix t)1181 EX void apply_rotation(const transmatrix t) {
1182   auto& cs = crystal_map()->cs;
1183   for(int i=0; i<cs.dim; i++) {
1184     hyperpoint h;
1185     for(int j=0; j<3; j++) h[j] = crug_rotation[i][j];
1186     h = t * h;
1187     for(int j=0; j<3; j++) crug_rotation[i][j] = h[j];
1188     }
1189   }
1190 
centerrug(ld aspd)1191 EX void centerrug(ld aspd) {
1192   if(vid.sspeed >= 4.99) aspd = 1000;
1193 
1194   auto m = crystal_map();
1195   ldcoord at = m->get_coord(cwt.at) - rug_center;
1196 
1197   ld R = sqrt(at|at);
1198 
1199   if(R < 1e-9) rug_center = m->get_coord(cwt.at);
1200   else {
1201     aspd *= (2+3*R*R);
1202     if(aspd > R) aspd = R;
1203     rug_center = rug_center + at * aspd / R;
1204     }
1205   }
1206 
cut_triangle2(const hyperpoint pa,const hyperpoint pb,const hyperpoint pc,const hyperpoint ha,const hyperpoint hb,const hyperpoint hc)1207 void cut_triangle2(const hyperpoint pa, const hyperpoint pb, const hyperpoint pc, const hyperpoint ha, const hyperpoint hb, const hyperpoint hc) {
1208   ld zac = pc[3] / (pc[3] - pa[3]);
1209   hyperpoint pac = pa * zac + pc * (1-zac);
1210   hyperpoint hac = ha * zac + hc * (1-zac);
1211 
1212   ld zbc = pc[3] / (pc[3] - pb[3]);
1213   hyperpoint pbc = pb * zbc + pc * (1-zbc);
1214   hyperpoint hbc = hb * zbc + hc * (1-zbc);
1215 
1216   pac[3] = pbc[3] = 1;
1217 
1218   rug::rugpoint *rac = rug::addRugpoint(shiftless(hac), 0);
1219   rug::rugpoint *rbc = rug::addRugpoint(shiftless(hbc), 0);
1220   rac->native = pac;
1221   rbc->native = pbc;
1222   rac->valid = true;
1223   rbc->valid = true;
1224   rug::triangles.push_back(rug::triangle(rac, rbc, NULL));
1225   }
1226 
cut_triangle(const hyperpoint pa,const hyperpoint pb,const hyperpoint pc,const hyperpoint ha,const hyperpoint hb,const hyperpoint hc)1227 void cut_triangle(const hyperpoint pa, const hyperpoint pb, const hyperpoint pc, const hyperpoint ha, const hyperpoint hb, const hyperpoint hc) {
1228   if((pa[3] >= 0) == (pb[3] >= 0))
1229     cut_triangle2(pa, pb, pc, ha, hb, hc);
1230   else if((pa[3] >= 0) == (pc[3] >= 0))
1231     cut_triangle2(pc, pa, pb, hc, ha, hb);
1232   else
1233     cut_triangle2(pb, pc, pa, hb, hc, ha);
1234   }
1235 
1236 #if MAXMDIM >= 4
build_rugdata()1237 EX void build_rugdata() {
1238   using namespace rug;
1239   rug::clear_model();
1240   rug::good_shape = true;
1241   rug::vertex_limit = 0;
1242   auto m = crystal_map();
1243 
1244   for(const auto& gp: gmatrix) {
1245 
1246     cell *c = gp.first;
1247     if(c->wall == waInvisibleFloor) continue;
1248     const shiftmatrix& V = gp.second;
1249 
1250     auto co = m->get_coord(c);
1251     vector<ldcoord> vcoord(c->type);
1252 
1253     for(int i=0; i<c->type; i++)
1254       if(valence() == 4)
1255         vcoord[i] = ((m->get_coord(c->cmove(i)) + m->get_coord(c->cmodmove(i-1))) / 2);
1256       else
1257         vcoord[i] = ((m->get_coord(c->cmove(i)) + m->get_coord(c->cmodmove(i-1)) + co) / 3);
1258 
1259     if(!draw_cut) {
1260       rugpoint *v = addRugpoint(tC0(V), 0);
1261       v->native = coord_to_flat(co);
1262       v->valid = true;
1263 
1264       rugpoint *p[MAX_EDGE_CRYSTAL];
1265 
1266       for(int i=0; i<c->type; i++) {
1267         p[i] = addRugpoint(V * get_corner_position(c, i), 0);
1268         p[i]->valid = true;
1269         p[i]->native = coord_to_flat(vcoord[i]);
1270         }
1271 
1272       for(int i=0; i<c->type; i++) addTriangle(v, p[i], p[(i+1) % c->type]);
1273       }
1274 
1275     else {
1276       hyperpoint hco = coord_to_flat(co, 4);
1277       hco[3] -= cut_level * rug::modelscale;
1278       vector<hyperpoint> vco(c->type);
1279       for(int i=0; i<c->type; i++) {
1280         vco[i] = coord_to_flat(vcoord[i], 4);
1281         vco[i][3] -= cut_level * rug::modelscale;
1282         }
1283 
1284       for(int i=0; i<c->type; i++) {
1285         int j = (i+1) % c->type;
1286         if((vco[i][3] >= 0) != (hco[3] >= 0) || (vco[j][3] >= 0) != (hco[3] >= 0)) {
1287           cut_triangle(hco, vco[i], vco[j], unshift(tC0(V)), unshift(V * get_corner_position(c, i)), unshift(V * get_corner_position(c, j)));
1288           }
1289         }
1290       }
1291     }
1292 
1293   println(hlog, "cut ", cut_level, "r ", crug_rotation);
1294   }
1295 #endif
1296 #endif
1297 
set_land(cell * c)1298 EX void set_land(cell *c) {
1299   setland(c, specialland);
1300   auto m = crystal_map();
1301 
1302   auto co = m->get_coord(c);
1303   auto co1 = roundcoord(co * 60);
1304 
1305   coord cx = roundcoord(co / 8);
1306   int hash = 0;
1307   for(int a=0; a<m->cs.dim; a++) hash = 1317 * hash + cx[a];
1308 
1309   set_euland3(c, co1[0], co1[1], dist_alt(c), hash);
1310   }
1311 
set_crystal(int sides)1312 EX void set_crystal(int sides) {
1313   stop_game();
1314   set_geometry(gCrystal);
1315   set_variation(eVariation::pure);
1316   ginf[gCrystal].sides = sides;
1317   ginf[gCrystal].vertex = 4;
1318   static char buf[20];
1319   sprintf(buf, "{%d,4}", sides);
1320   ginf[gCrystal].tiling_name = buf;
1321   ginf[gCrystal].distlimit = distlimit_table[min(sides, MAX_EDGE_CRYSTAL-1)];
1322   }
1323 
test_crt()1324 void test_crt() {
1325   start_game();
1326   auto m = crystal_map();
1327   manual_celllister cl;
1328   cl.add(m->camelot_center);
1329   for(int i=0; i<isize(cl.lst); i++)
1330     forCellCM(c1, cl.lst[i]) {
1331       setdist(c1, 7, m->gamestart());
1332       if(c1->land == laCamelot && c1->wall == waNone)
1333         cl.add(c1);
1334       }
1335   println(hlog, "actual = ", isize(cl.lst), " algorithm = ", get_table_volume());
1336   if(its(isize(cl.lst)) != get_table_volume()) exit(1);
1337   }
1338 
unit_test_tables()1339 void unit_test_tables() {
1340   stop_game();
1341   specialland = laCamelot;
1342   set_crystal(5);
1343   test_crt();
1344   set_crystal(6);
1345   test_crt();
1346   set_crystal(5); set_variation(eVariation::bitruncated);
1347   test_crt();
1348   set_crystal(6); set_variation(eVariation::bitruncated);
1349   test_crt();
1350   set_crystal(8); set_variation(eVariation::bitruncated); set_variation(eVariation::bitruncated);
1351   test_crt();
1352   }
1353 
set_crystal_period_flags()1354 EX void set_crystal_period_flags() {
1355   crystal_period &= ~1;
1356   for(auto& g: ginf)
1357     if(g.flags & qCRYSTAL) {
1358       set_flag(ginf[gNil].flags, qSMALL, crystal_period && crystal_period <= 8);
1359       set_flag(ginf[gNil].flags, qBOUNDED, crystal_period);
1360       }
1361   }
1362 
1363 #if CAP_COMMANDLINE
readArgs()1364 int readArgs() {
1365   using namespace arg;
1366 
1367   if(0) ;
1368   else if(argis("-crystal")) {
1369     PHASEFROM(2);
1370     shift(); set_crystal(argi());
1371     }
1372   else if(argis("-cview")) {
1373     view_coordinates = true;
1374     }
1375   else if(argis("-ceast")) {
1376     view_east = true;
1377     }
1378   else if(argis("-cprob")) {
1379     PHASEFROM(2); shift_arg_formula(compass_probability);
1380     }
1381   else if(argis("-ccut")) {
1382     draw_cut = true;
1383     PHASEFROM(2); shift_arg_formula(cut_level);
1384     }
1385   else if(argis("-ccutoff")) {
1386     draw_cut = false;
1387     }
1388   else if(argis("-cho")) {
1389     shift(); ho = argi();
1390     init_rotation();
1391     }
1392   else if(argis("-chrr")) {
1393     random_rotation();
1394     }
1395   else if(argis("-test:crt")) {
1396     test_crt();
1397     }
1398   else if(argis("-crystal_period")) {
1399     if(cryst) stop_game();
1400     shift(); crystal_period = argi();
1401     set_crystal_period_flags();
1402     }
1403   else if(argis("-d:crystal"))
1404     launch_dialog(show);
1405   else if(argis("-cvcol")) {
1406     shift(); int d = argi();
1407     shift(); coordcolors[d] = arghex();
1408     }
1409   else return 1;
1410   return 0;
1411   }
1412 #endif
1413 
new_map()1414 EX hrmap *new_map() {
1415   return new hrmap_crystal;
1416   }
1417 
compass_help()1418 EX string compass_help() {
1419   return XLAT(
1420     "Lands in this geometry are usually built on North-South or West-East axis. "
1421     "Compasses always point North, and all the cardinal directions to the right from compass North are East (this is not "
1422     "true in general, but it is true for the cells where compasses are generated). "
1423     "North is the first coordinate, while East is the sum of other coordinates."
1424     );
1425   }
1426 
make_help()1427 string make_help() {
1428   return XLAT(
1429     "This space essentially lets you play in a d-dimensional grid. Pick three "
1430     "dimensions and '3D display' to see how it works -- we are essentially playing on a periodic surface in "
1431     "three dimensions, made of hexagons; each hexagon connects to six other hexagons, in each of the 6 "
1432     "possible directions. Normally, the game visualizes this from the point of view of a creature living inside "
1433     "the surface (regularized and smoothened somewhat), assuming that light rays are also restricted to the surface -- "
1434     "this will look exactly like the {2d,4} tiling, except that the light rays may thus "
1435     "sometimes make a loop, causing you to see images of yourself in some directions (in other words, "
1436     "the d-dimensional grid is a quotient of the hyperbolic plane). The same construction works in other dimensions. "
1437     "Half dimensions are interpreted in the following way: the extra dimension only has two 'levels', for example 2.5D "
1438     "has a top plane and a bottom plane.\n\n"
1439     "You may also bitruncate this tessellation -- which makes it work better with the rules of HyperRogue, but a bit harder to understand."
1440     );
1441   }
1442 
crystal_knight_help()1443 EX void crystal_knight_help() {
1444   gamescreen(1);
1445   dialog::init();
1446 
1447   dialog::addHelp(XLAT(
1448     "This is a representation of four-dimensional geometry. Can you find the Holy Grail in the center of the Round Table?\n\n"
1449     "In 'Knight of the 16-Cell Table', each cell has 8 adjacent cells, "
1450     "which correspond to 8 adjacent points in the four-dimensional grid. The Round Table has the shape of a 16-cell.\n\n"
1451     "In 'Knight of the 3-Spherical Table', it is the same map, but double bitruncated. The Round Table has the shape of a hypersphere.\n\n"
1452     ));
1453 
1454   dialog::addItem(XLAT("let me understand how the coordinates work"), 'e');
1455   dialog::add_action([] { cheater = true; view_coordinates = true; compass_probability = 1; restart_game(); popScreenAll(); });
1456 
1457   dialog::addItem(XLAT("thanks, I need no hints (achievement)"), 't');
1458   dialog::add_action([] { view_coordinates = false; compass_probability = 0; restart_game(); popScreenAll(); });
1459 
1460   dialog::addItem(XLAT("more about this geometry..."), 'm');
1461   dialog::add_action([] { popScreenAll(); pushScreen(show); });
1462 
1463   dialog::display();
1464   }
1465 
show()1466 EX void show() {
1467   cmode = sm::SIDE | sm::MAYDARK;
1468   gamescreen(0);
1469   dialog::init(XLAT("dimensional crystal"));
1470   for(int i=5; i<=14; i++) {
1471     string s;
1472     if(i % 2) s = its(i/2) + ".5D";
1473     else s = its(i/2) + "D";
1474     dialog::addBoolItem(s, cryst && ginf[gCrystal].sides == i && ginf[gCrystal].vertex == 4, 'a' + i - 5);
1475     dialog::add_action(dialog::add_confirmation([i]() { set_crystal(i); start_game(); }));
1476     }
1477   dialog::addBoolItem(XLAT("4D double bitruncated"), ginf[gCrystal].vertex == 3, 'D');
1478   dialog::add_action(dialog::add_confirmation([]() { set_crystal(8); set_variation(eVariation::bitruncated); set_variation(eVariation::bitruncated); start_game(); }));
1479   dialog::addBreak(50);
1480   dialog::addBoolItem_action(XLAT("view coordinates in the cheat mode"), view_coordinates, 'v');
1481   dialog::addSelItem(XLAT("compass probability"), fts(compass_probability), 'p');
1482   if(geometry == gCrystal344) dialog::lastItem().value += " (N/A)";
1483   dialog::add_action([]() {
1484     dialog::editNumber(compass_probability, 0, 1, 0.1, 1, XLAT("compass probability"), compass_help());
1485     dialog::bound_low(0);
1486     });
1487 #if CAP_RUG
1488   if(cryst && WDIM == 2) {
1489     dialog::addBoolItem(XLAT("3D display"), rug::rugged, 'r');
1490     dialog::add_action_push(rug::show);
1491     }
1492   else
1493     dialog::addBreak(100);
1494   if(rug::rugged && cryst && ginf[gCrystal].sides == 8 && WDIM == 2) {
1495     dialog::addBoolItem(XLAT("render a cut"), draw_cut, 'x');
1496     dialog::add_action([]() {
1497       draw_cut = true;
1498       dialog::editNumber(cut_level, -1, 1, 0.1, 0, XLAT("cut level"), "");
1499       dialog::extra_options = [] {
1500         dialog::addItem(XLAT("disable"), 'D');
1501         dialog::add_action([] { draw_cut = false; popScreen(); });
1502         };
1503       });
1504     }
1505   else dialog::addBreak(100);
1506 #endif
1507   dialog::addSelItem(XLAT("Crystal torus"), its(crystal_period), 'C');
1508   dialog::add_action([] {
1509     dialog::editNumber(crystal_period, 0, 16, 2, 0, XLAT("Crystal torus"),
1510       XLAT("Z_k^d instead of Z^d. Only works with k even."));
1511     dialog::reaction_final = [] {
1512       if(cryst) stop_game();
1513       set_crystal_period_flags();
1514       if(cryst) start_game();
1515       };
1516     });
1517   dialog::addBack();
1518   dialog::addHelp();
1519   dialog::add_action([] { gotoHelp(make_help()); });
1520   dialog::display();
1521   }
1522 
1523 auto crystalhook =
1524 #if CAP_COMMANDLINE
1525     addHook(hooks_args, 100, readArgs)
1526 #endif
1527   + addHook(hooks_drawcell, 100, crystal_cell)
1528   + addHook(hooks_tests, 200, unit_test_tables);
1529 
1530 map<pair<int, int>, bignum> volume_memo;
1531 
compute_volume(int dim,int rad)1532 bignum& compute_volume(int dim, int rad) {
1533   auto p = make_pair(dim, rad);
1534   int is = volume_memo.count(p);
1535   auto& m = volume_memo[p];
1536   if(is) return m;
1537   if(dim == 0) { m = 1; return m; }
1538   m = compute_volume(dim-1, rad);
1539   for(int r=0; r<rad; r++)
1540     m.addmul(compute_volume(dim-1, r), 2);
1541   return m;
1542   }
1543 
1544 // shift_data_zero.children[x1].children[x2]....children[xk].result[r2]
1545 // is the number of grid points in distance at most sqrt(r2) from (x1,x2,...,xk)
1546 
1547 struct eps_comparer {
operator ()hr::crystal::eps_comparer1548   bool operator() (ld a, ld b) const { return a < b-1e-6; }
1549   };
1550 
1551 struct shift_data {
1552   shift_data *parent;
1553   ld shift;
1554   map<ld, shift_data, eps_comparer> children;
1555   map<ld, bignum, eps_comparer> result;
1556 
shift_datahr::crystal::shift_data1557   shift_data() { parent = NULL; }
1558 
computehr::crystal::shift_data1559   bignum& compute(ld rad2) {
1560     if(result.count(rad2)) return result[rad2];
1561     // println(hlog, "compute ", format("%p", this), " [shift=", shift, "], r2 = ", rad2);
1562     // indenter i(2);
1563     auto& b = result[rad2];
1564     if(!parent) {
1565       if(rad2 >= 0) b = 1;
1566       }
1567     else if(rad2 >= 0) {
1568       for(int x = -2-sqrt(rad2); x <= sqrt(rad2)+2; x++) {
1569         ld ax = x - shift;
1570         if(ax*ax <= rad2)
1571           b.addmul(parent->compute(rad2 - (ax*ax)), 1);
1572         }
1573       }
1574     // println(hlog, "result = ", b.get_str(100));
1575     return b;
1576     }
1577   };
1578 
1579 shift_data shift_data_zero;
1580 
get_table_volume()1581 EX string get_table_volume() {
1582   if(!pure()) {
1583     auto m = crystal_map();
1584     bignum res;
1585     manual_celllister cl;
1586     cl.add(m->gamestart());
1587     ld rad2 = pow(roundTableRadius(NULL) / m->camelot_mul / PERIOD, 2) + 1e-4;
1588     for(int i=0; i<isize(cl.lst); i++) {
1589       cell *c = cl.lst[i];
1590       ld mincoord = 9, maxcoord = -9;
1591       auto co = m->get_coord(c);
1592       for(int i=0; i<m->cs.dim; i++) {
1593         if(co[i] < mincoord) mincoord = co[i];
1594         if(co[i] > maxcoord) maxcoord = co[i];
1595         }
1596       static const ld eps = 1e-4;
1597       if(mincoord >= 0-eps && maxcoord < PERIOD-eps) {
1598         ld my_rad2 = rad2;
1599         auto cshift = (co - m->camelot_coord) / PERIOD;
1600         auto sd = &shift_data_zero;
1601         for(int i=0; i<m->cs.dim; i++) {
1602           if(i == m->cs.dim-1 && (m->cs.dir&1)) {
1603             my_rad2 -= pow(cshift[i] / m->camelot_mul, 2);
1604             }
1605           else {
1606             ld val = cshift[i] - floor(cshift[i]);
1607             if(!sd->children.count(val)) {
1608               sd->children[val].parent = sd;
1609               sd->children[val].shift = val;
1610               }
1611             sd = &sd->children[val];
1612             }
1613           }
1614         res.addmul(sd->compute(my_rad2), 1);
1615         }
1616       if(mincoord < -2 || maxcoord > 6) continue;
1617       forCellCM(c2, c) cl.add(c2);
1618       }
1619     return res.get_str(100);
1620     }
1621   int s = ginf[gCrystal].sides;
1622   int r = roundTableRadius(NULL);
1623   if(s % 2 == 0)
1624     return compute_volume(s/2, r-1).get_str(100);
1625   else
1626     return (compute_volume(s/2, r-1) + compute_volume(s/2, r-2)).get_str(100);
1627   }
1628 
get_table_boundary()1629 EX string get_table_boundary() {
1630   if(!pure()) return "";
1631   int r = roundTableRadius(NULL);
1632   int s = ginf[gCrystal].sides;
1633   if(s % 2 == 0)
1634     return (compute_volume(s/2, r) - compute_volume(s/2, r-1)).get_str(100);
1635   else
1636     return (compute_volume(s/2, r) - compute_volume(s/2, r-2)).get_str(100);
1637   }
1638 
may_place_compass(cell * c)1639 EX void may_place_compass(cell *c) {
1640   if(c != c->master->c7) return;
1641   if(WDIM == 3) return;
1642   auto m = crystal_map();
1643   auto co = m->hcoords[c->master];
1644   for(int i=0; i<m->cs.dim; i++)
1645     if(co[i] % PERIOD)
1646       return;
1647   if(hrandf() < compass_probability)
1648     c->item = itCompass;
1649   }
1650 #endif
1651 
1652 #if CAP_CRYSTAL && MAXMDIM >= 4
1653 
crystal_to_euclid(coord x)1654 euc::coord crystal_to_euclid(coord x) {
1655   return euc::coord(x[0]/2, x[1]/2, x[2]/2);
1656   }
1657 
euclid3_to_crystal(euc::coord x)1658 coord euclid3_to_crystal(euc::coord x) {
1659   coord res;
1660   for(int i=0; i<3; i++) res[i] = x[i] * 2;
1661   for(int i=3; i<MAXDIM; i++) res[i] = 0;
1662   return res;
1663   }
1664 
1665 
transform_crystal_to_euclid()1666 void transform_crystal_to_euclid () {
1667   euc::clear_torus3();
1668   geometry = gCubeTiling;
1669   auto e = euc::new_map();
1670   auto m = crystal_map();
1671   auto infront = cwt.cpeek();
1672 
1673   auto& spacemap = euc::get_spacemap();
1674   auto& ispacemap = euc::get_ispacemap();
1675   auto& camelot_center = euc::get_camelot_center();
1676   auto& shifttable = euc::get_current_shifttable();
1677 
1678   for(auto& p: m->hcoords) {
1679     auto co = crystal_to_euclid(p.second);
1680     spacemap[co] = p.first;
1681     ispacemap[p.first] = co;
1682 
1683     cell* c = p.first->c7;
1684 
1685     // rearrange the monster directions
1686     if(c->mondir < S7 && c->move(c->mondir)) {
1687       auto co1 = crystal_to_euclid(m->hcoords[c->move(c->mondir)->master]) - co;
1688       for(int i=0; i<6; i++)
1689         if(co1 == shifttable[i])
1690           c->mondir = i;
1691       }
1692 
1693     for(int i=0; i<S7; i++) c->move(i) = NULL;
1694     }
1695 
1696   if(m->camelot_center)
1697     camelot_center = spacemap[crystal_to_euclid(m->hcoords[m->camelot_center->master])]->c7;
1698 
1699   // clean hcoords and heptagon_at so that the map is not deleted when we delete m
1700   m->hcoords.clear();
1701   m->heptagon_at.clear();
1702   delete m;
1703 
1704   for(int i=0; i<isize(allmaps); i++)
1705     if(allmaps[i] == m)
1706       allmaps[i] = e;
1707 
1708   currentmap = e;
1709 
1710   // connect the cubes
1711   for(auto& p: spacemap) {
1712     auto& co = p.first;
1713     auto& h = p.second;
1714     for(int i=0; i<S7; i++)
1715       if(spacemap.count(co + shifttable[i]))
1716         h->move(i) = spacemap[co + shifttable[i]],
1717         h->c.setspin(i, (i + 3) % 6, false),
1718         h->c7->move(i) = h->move(i)->c7,
1719         h->c7->c.setspin(i, (i + 3) % 6, false);
1720     }
1721 
1722   clearAnimations();
1723   cwt.spin = neighborId(cwt.at, infront);
1724   View = iddspin(cwt.at, cwt.spin, M_PI/2);
1725   if(!flipplayer) View = cspin(0, 2, M_PI) * View;
1726 
1727   if(pmodel == mdDisk) pmodel = mdPerspective;
1728   }
1729 
transform_euclid_to_crystal()1730 void transform_euclid_to_crystal () {
1731   geometry = gCrystal;
1732   ginf[gCrystal].sides = 6;
1733   ginf[gCrystal].vertex = 4;
1734   ginf[gCrystal].tiling_name = "{6,4}";
1735   ginf[gCrystal].distlimit = distlimit_table[6];
1736 
1737   auto e = currentmap;
1738   auto m = new hrmap_crystal;
1739   auto infront = cwt.cpeek();
1740 
1741   auto& spacemap = euc::get_spacemap();
1742   auto& ispacemap = euc::get_ispacemap();
1743   auto& camelot_center = euc::get_camelot_center();
1744 
1745   for(auto& p: ispacemap) {
1746     auto co = euclid3_to_crystal(p.second);
1747     m->heptagon_at[co] = p.first;
1748     m->hcoords[p.first] = co;
1749     }
1750 
1751   for(auto& p: ispacemap) {
1752     cell *c = p.first->c7;
1753     if(c->mondir < S7 && c->move(c->mondir)) {
1754       auto co = euclid3_to_crystal(p.second);
1755       for(int d=0; d<S7; d++) {
1756         auto lw = m->makewalker(co, d);
1757         auto co1 = add(co, lw, FULLSTEP);
1758         if(m->heptagon_at.count(co1) && m->heptagon_at[co1] == c->move(c->mondir)->master)
1759           c->mondir = d;
1760         }
1761       }
1762     for(int i=0; i<S7; i++) c->move(i) = NULL;
1763     }
1764 
1765   if(camelot_center)
1766     m->camelot_center = m->heptagon_at[euclid3_to_crystal(ispacemap[camelot_center->master])]->c7;
1767 
1768   spacemap.clear();
1769   ispacemap.clear();
1770   delete e;
1771 
1772   for(int i=0; i<isize(allmaps); i++)
1773     if(allmaps[i] == e)
1774       allmaps[i] = m;
1775 
1776   currentmap = m;
1777 
1778   // connect the cubes
1779   for(auto& p: m->heptagon_at) {
1780     auto& co = p.first;
1781     auto& h = p.second;
1782     for(int i=0; i<S7; i++) {
1783       auto lw = m->makewalker(co, i);
1784       auto co1 = add(co, lw, FULLSTEP);
1785       if(m->heptagon_at.count(co1)) {
1786         auto lw1 = lw+wstep;
1787         h->move(i) = m->heptagon_at[co1],
1788         h->c.setspin(i, lw1.spin, false),
1789         h->c7->move(i) = h->move(i)->c7;
1790         h->c7->c.setspin(i, h->c.spin(i), false);
1791         }
1792       }
1793     }
1794 
1795   View = Id;
1796   clearAnimations();
1797   cwt.spin = neighborId(cwt.at, infront);
1798   if(pmodel == mdPerspective) pmodel = mdDisk;
1799   }
1800 
add_crystal_transform(char c)1801 EX void add_crystal_transform(char c) {
1802   if(shmup::on) return;
1803   if(cryst && ginf[gCrystal].sides == 6 && geometry != gCrystal344) {
1804     dialog::addItem("convert Crystal to 3D", c);
1805     dialog::add_action(transform_crystal_to_euclid);
1806     }
1807   if(geometry == gCubeTiling && !quotient) {
1808     dialog::addItem("convert 3D to Crystal", c);
1809     dialog::add_action(transform_euclid_to_crystal);
1810     }
1811   }
1812 
1813 #endif
1814 
1815 }
1816 
1817 }
1818 
1819 
1820