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