spectre.c 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600
  1. /*
  2. * Code to generate patches of the aperiodic 'spectre' tiling
  3. * discovered in 2023.
  4. *
  5. * Resources about the tiling from its discoverers:
  6. * https://cs.uwaterloo.ca/~csk/spectre/
  7. * https://arxiv.org/abs/2305.17743
  8. *
  9. * Writeup of the generation algorithm:
  10. * https://www.chiark.greenend.org.uk/~sgtatham/quasiblog/aperiodic-spectre/
  11. */
  12. #include <assert.h>
  13. #include <string.h>
  14. #include "puzzles.h"
  15. #include "tree234.h"
  16. #include "spectre-internal.h"
  17. #include "spectre-tables-manual.h"
  18. #include "spectre-tables-auto.h"
  19. static const char *const letters =
  20. #define STRINGIFY(x) #x
  21. HEX_LETTERS(STRINGIFY)
  22. #undef STRINGIFY
  23. ;
  24. bool spectre_valid_hex_letter(char letter)
  25. {
  26. return strchr(letters, letter) != NULL;
  27. }
  28. static Hex hex_from_letter(char letter)
  29. {
  30. char buf[2];
  31. buf[0] = letter;
  32. buf[1] = '\0';
  33. return strcspn(letters, buf);
  34. }
  35. static Hex hex_to_letter(unsigned char letter)
  36. {
  37. return letters[letter];
  38. }
  39. struct HexData {
  40. const struct MapEntry *hexmap, *hexin, *specmap, *specin;
  41. const struct MapEdge *hexedges, *specedges;
  42. const Hex *subhexes;
  43. const struct Possibility *poss;
  44. size_t nposs;
  45. };
  46. static const struct HexData hexdata[] = {
  47. #define HEXDATA_ENTRY(x) { hexmap_##x, hexin_##x, specmap_##x, \
  48. specin_##x, hexedges_##x, specedges_##x, subhexes_##x, \
  49. poss_##x, lenof(poss_##x) },
  50. HEX_LETTERS(HEXDATA_ENTRY)
  51. #undef HEXDATA_ENTRY
  52. };
  53. static const struct Possibility *choose_poss(
  54. random_state *rs, const struct Possibility *poss, size_t nposs)
  55. {
  56. /*
  57. * If we needed to do this _efficiently_, we'd rewrite all those
  58. * tables above as cumulative frequency tables and use binary
  59. * search. But this happens about log n times in a grid of area n,
  60. * so it hardly matters, and it's easier to keep the tables
  61. * legible.
  62. */
  63. unsigned long limit = 0, value;
  64. size_t i;
  65. for (i = 0; i < nposs; i++)
  66. limit += poss[i].prob;
  67. value = random_upto(rs, limit);
  68. for (i = 0; i+1 < nposs; i++) {
  69. if (value < poss[i].prob)
  70. return &poss[i];
  71. value -= poss[i].prob;
  72. }
  73. assert(i == nposs - 1);
  74. assert(value < poss[i].prob);
  75. return &poss[i];
  76. }
  77. SpectreCoords *spectre_coords_new(void)
  78. {
  79. SpectreCoords *sc = snew(SpectreCoords);
  80. sc->nc = sc->csize = 0;
  81. sc->c = NULL;
  82. return sc;
  83. }
  84. void spectre_coords_free(SpectreCoords *sc)
  85. {
  86. if (sc) {
  87. sfree(sc->c);
  88. sfree(sc);
  89. }
  90. }
  91. void spectre_coords_make_space(SpectreCoords *sc, size_t size)
  92. {
  93. if (sc->csize < size) {
  94. sc->csize = sc->csize * 5 / 4 + 16;
  95. if (sc->csize < size)
  96. sc->csize = size;
  97. sc->c = sresize(sc->c, sc->csize, HexCoord);
  98. }
  99. }
  100. SpectreCoords *spectre_coords_copy(SpectreCoords *sc_in)
  101. {
  102. SpectreCoords *sc_out = spectre_coords_new();
  103. spectre_coords_make_space(sc_out, sc_in->nc);
  104. memcpy(sc_out->c, sc_in->c, sc_in->nc * sizeof(*sc_out->c));
  105. sc_out->nc = sc_in->nc;
  106. sc_out->index = sc_in->index;
  107. sc_out->hex_colour = sc_in->hex_colour;
  108. sc_out->prev_hex_colour = sc_in->prev_hex_colour;
  109. sc_out->incoming_hex_edge = sc_in->incoming_hex_edge;
  110. return sc_out;
  111. }
  112. void spectre_place(Spectre *spec, Point u, Point v, int index_of_u)
  113. {
  114. size_t i;
  115. Point disp;
  116. /* Vector from u to v */
  117. disp = point_sub(v, u);
  118. for (i = 0; i < 14; i++) {
  119. spec->vertices[(i + index_of_u) % 14] = u;
  120. u = point_add(u, disp);
  121. disp = point_mul(disp, point_rot(
  122. spectre_angles[(i + 1 + index_of_u) % 14]));
  123. }
  124. }
  125. Spectre *spectre_initial(SpectreContext *ctx)
  126. {
  127. Spectre *spec = snew(Spectre);
  128. spectre_place(spec, ctx->start_vertices[0], ctx->start_vertices[1], 0);
  129. spec->sc = spectre_coords_copy(ctx->prototype);
  130. return spec;
  131. }
  132. Spectre *spectre_adjacent(SpectreContext *ctx, const Spectre *src_spec,
  133. unsigned src_edge, unsigned *dst_edge_out)
  134. {
  135. unsigned dst_edge;
  136. Spectre *dst_spec = snew(Spectre);
  137. dst_spec->sc = spectre_coords_copy(src_spec->sc);
  138. spectrectx_step(ctx, dst_spec->sc, src_edge, &dst_edge);
  139. spectre_place(dst_spec, src_spec->vertices[(src_edge+1) % 14],
  140. src_spec->vertices[src_edge], dst_edge);
  141. if (dst_edge_out)
  142. *dst_edge_out = dst_edge;
  143. return dst_spec;
  144. }
  145. static int spectre_cmp(void *av, void *bv)
  146. {
  147. Spectre *a = (Spectre *)av, *b = (Spectre *)bv;
  148. size_t i, j;
  149. /* We should only ever need to compare the first two vertices of
  150. * any Spectre, because those force the rest */
  151. for (i = 0; i < 2; i++) {
  152. for (j = 0; j < 4; j++) {
  153. int ac = a->vertices[i].coeffs[j], bc = b->vertices[i].coeffs[j];
  154. if (ac < bc)
  155. return -1;
  156. if (ac > bc)
  157. return +1;
  158. }
  159. }
  160. return 0;
  161. }
  162. void spectre_free(Spectre *spec)
  163. {
  164. spectre_coords_free(spec->sc);
  165. sfree(spec);
  166. }
  167. static void spectrectx_start_vertices(SpectreContext *ctx, int orientation)
  168. {
  169. Point minus_sqrt3 = point_add(point_rot(5), point_rot(-5));
  170. Point basicedge = point_mul(point_add(point_rot(0), point_rot(-3)),
  171. point_rot(orientation));
  172. Point diagonal = point_add(basicedge, point_mul(basicedge, point_rot(-3)));
  173. ctx->start_vertices[0] = point_mul(diagonal, minus_sqrt3);
  174. ctx->start_vertices[1] = point_add(ctx->start_vertices[0], basicedge);
  175. ctx->orientation = orientation;
  176. }
  177. void spectrectx_init_random(SpectreContext *ctx, random_state *rs)
  178. {
  179. const struct Possibility *poss;
  180. ctx->rs = rs;
  181. ctx->must_free_rs = false;
  182. ctx->prototype = spectre_coords_new();
  183. spectre_coords_make_space(ctx->prototype, 1);
  184. poss = choose_poss(rs, poss_spectre, lenof(poss_spectre));
  185. ctx->prototype->index = poss->lo;
  186. ctx->prototype->c[0].type = poss->hi;
  187. ctx->prototype->c[0].index = -1;
  188. ctx->prototype->nc = 1;
  189. /*
  190. * Choose a random orientation for the starting Spectre.
  191. *
  192. * The obvious thing is to choose the orientation out of all 12
  193. * possibilities. But we do it a more complicated way.
  194. *
  195. * The Spectres in a tiling can be partitioned into two
  196. * equivalence classes under the relation 'orientation differs by
  197. * a multiple of 1/6 turn'. One class is much more common than the
  198. * other class: the 'odd'-orientation Spectres occur rarely (very
  199. * like the rare reflected hats in the hats tiling).
  200. *
  201. * I think it's nicer to arrange that there's a consistent
  202. * orientation for the _common_ class of Spectres, so that there
  203. * will always be plenty of them in the 'canonical' orientation
  204. * with the head upwards. So if the starting Spectre is in the
  205. * even class, we pick an even orientation for it, and if it's in
  206. * the odd class, we pick an odd orientation.
  207. *
  208. * An odd-class Spectre is easy to identify from SpectreCoords.
  209. * They're precisely the ones expanded from a G hex with index 1,
  210. * which means they're the ones that have index 1 _at all_.
  211. */
  212. spectrectx_start_vertices(ctx, random_upto(rs, 6) * 2 +
  213. ctx->prototype->index);
  214. /* Initialiise the colouring fields deterministically but unhelpfully.
  215. * spectre-test will set these up properly if it wants to */
  216. ctx->prototype->hex_colour = 0;
  217. ctx->prototype->prev_hex_colour = 0;
  218. ctx->prototype->incoming_hex_edge = 0;
  219. }
  220. void spectrectx_init_from_params(
  221. SpectreContext *ctx, const struct SpectrePatchParams *ps)
  222. {
  223. size_t i;
  224. ctx->rs = NULL;
  225. ctx->must_free_rs = false;
  226. ctx->prototype = spectre_coords_new();
  227. spectre_coords_make_space(ctx->prototype, ps->ncoords);
  228. ctx->prototype->index = ps->coords[0];
  229. for (i = 1; i < ps->ncoords; i++)
  230. ctx->prototype->c[i-1].index = ps->coords[i];
  231. ctx->prototype->c[ps->ncoords-1].index = -1;
  232. ctx->prototype->nc = ps->ncoords;
  233. ctx->prototype->c[ps->ncoords-1].type = hex_from_letter(ps->final_hex);
  234. for (i = ps->ncoords - 1; i-- > 0 ;) {
  235. const struct HexData *h = &hexdata[ctx->prototype->c[i+1].type];
  236. ctx->prototype->c[i].type = h->subhexes[ctx->prototype->c[i].index];
  237. }
  238. spectrectx_start_vertices(ctx, ps->orientation);
  239. ctx->prototype->hex_colour = 0;
  240. ctx->prototype->prev_hex_colour = 0;
  241. ctx->prototype->incoming_hex_edge = 0;
  242. }
  243. void spectrectx_cleanup(SpectreContext *ctx)
  244. {
  245. if (ctx->must_free_rs)
  246. random_free(ctx->rs);
  247. spectre_coords_free(ctx->prototype);
  248. }
  249. SpectreCoords *spectrectx_initial_coords(SpectreContext *ctx)
  250. {
  251. return spectre_coords_copy(ctx->prototype);
  252. }
  253. /*
  254. * Extend sc until it has at least n coordinates in, by copying from
  255. * ctx->prototype if needed, and extending ctx->prototype if needed in
  256. * order to do that.
  257. */
  258. void spectrectx_extend_coords(SpectreContext *ctx, SpectreCoords *sc, size_t n)
  259. {
  260. if (ctx->prototype->nc < n) {
  261. spectre_coords_make_space(ctx->prototype, n);
  262. while (ctx->prototype->nc < n) {
  263. const struct HexData *h = &hexdata[
  264. ctx->prototype->c[ctx->prototype->nc-1].type];
  265. const struct Possibility *poss;
  266. if (!ctx->rs) {
  267. /*
  268. * If there's no random_state available, it must be
  269. * because we were given an explicit coordinate string
  270. * and ran off the end of it.
  271. *
  272. * The obvious thing to do here would be to make up an
  273. * answer non-randomly. But in fact there's a danger
  274. * that this leads to endless recursion within a
  275. * single coordinate step, if the hex edge we were
  276. * trying to traverse turns into another copy of
  277. * itself at the higher level. That happened in early
  278. * testing before I put the random_state in at all.
  279. *
  280. * To avoid that risk, in this situation - which
  281. * _shouldn't_ come up at all in sensibly play - we
  282. * make up a random_state, and free it when the
  283. * context goes away.
  284. */
  285. ctx->rs = random_new("dummy", 5);
  286. ctx->must_free_rs = true;
  287. }
  288. poss = choose_poss(ctx->rs, h->poss, h->nposs);
  289. ctx->prototype->c[ctx->prototype->nc-1].index = poss->lo;
  290. ctx->prototype->c[ctx->prototype->nc].type = poss->hi;
  291. ctx->prototype->c[ctx->prototype->nc].index = -1;
  292. ctx->prototype->nc++;
  293. }
  294. }
  295. spectre_coords_make_space(sc, n);
  296. while (sc->nc < n) {
  297. assert(sc->c[sc->nc - 1].index == -1);
  298. assert(sc->c[sc->nc - 1].type == ctx->prototype->c[sc->nc - 1].type);
  299. sc->c[sc->nc - 1].index = ctx->prototype->c[sc->nc - 1].index;
  300. sc->c[sc->nc].index = -1;
  301. sc->c[sc->nc].type = ctx->prototype->c[sc->nc].type;
  302. sc->nc++;
  303. }
  304. }
  305. void spectrectx_step_hex(SpectreContext *ctx, SpectreCoords *sc,
  306. size_t depth, unsigned edge, unsigned *outedge)
  307. {
  308. const struct HexData *h;
  309. const struct MapEntry *m;
  310. spectrectx_extend_coords(ctx, sc, depth+2);
  311. assert(0 <= sc->c[depth].index);
  312. assert(sc->c[depth].index < num_subhexes(sc->c[depth].type));
  313. assert(0 <= edge);
  314. assert(edge < 6);
  315. h = &hexdata[sc->c[depth+1].type];
  316. m = &h->hexmap[6 * sc->c[depth].index + edge];
  317. if (!m->internal) {
  318. unsigned recedge;
  319. const struct MapEdge *me;
  320. spectrectx_step_hex(ctx, sc, depth+1, m->hi, &recedge);
  321. assert(recedge < 6);
  322. h = &hexdata[sc->c[depth+1].type];
  323. me = &h->hexedges[recedge];
  324. assert(m->lo < me->len);
  325. m = &h->hexin[me->startindex + me->len - 1 - m->lo];
  326. assert(m->internal);
  327. }
  328. sc->c[depth].index = m->hi;
  329. sc->c[depth].type = h->subhexes[sc->c[depth].index];
  330. *outedge = m->lo;
  331. if (depth == 0) {
  332. /*
  333. * Update the colouring fields to track the colour of the new
  334. * hexagon.
  335. */
  336. unsigned char new_hex_colour;
  337. if (!((edge ^ sc->incoming_hex_edge) & 1)) {
  338. /* We're going out via the same parity of edge we came in
  339. * on, so the new hex colour is the same as the previous
  340. * one. */
  341. new_hex_colour = sc->prev_hex_colour;
  342. } else {
  343. /* We're going out via the opposite parity of edge, so the
  344. * new colour is the one of {0,1,2} that is neither this
  345. * _nor_ the previous colour. */
  346. new_hex_colour = 0+1+2 - sc->hex_colour - sc->prev_hex_colour;
  347. }
  348. sc->prev_hex_colour = sc->hex_colour;
  349. sc->hex_colour = new_hex_colour;
  350. sc->incoming_hex_edge = m->lo;
  351. }
  352. }
  353. void spectrectx_step(SpectreContext *ctx, SpectreCoords *sc,
  354. unsigned edge, unsigned *outedge)
  355. {
  356. const struct HexData *h;
  357. const struct MapEntry *m;
  358. assert(0 <= sc->index);
  359. assert(sc->index < num_spectres(sc->c[0].type));
  360. assert(0 <= edge);
  361. assert(edge < 14);
  362. h = &hexdata[sc->c[0].type];
  363. m = &h->specmap[14 * sc->index + edge];
  364. while (!m->internal) {
  365. unsigned recedge;
  366. const struct MapEdge *me;
  367. spectrectx_step_hex(ctx, sc, 0, m->hi, &recedge);
  368. assert(recedge < 6);
  369. h = &hexdata[sc->c[0].type];
  370. me = &h->specedges[recedge];
  371. assert(m->lo < me->len);
  372. m = &h->specin[me->startindex + me->len - 1 - m->lo];
  373. }
  374. sc->index = m->hi;
  375. *outedge = m->lo;
  376. }
  377. void spectrectx_generate(SpectreContext *ctx,
  378. bool (*callback)(void *cbctx, const Spectre *spec),
  379. void *cbctx)
  380. {
  381. tree234 *placed = newtree234(spectre_cmp);
  382. Spectre *qhead = NULL, *qtail = NULL;
  383. {
  384. Spectre *spec = spectre_initial(ctx);
  385. add234(placed, spec);
  386. spec->next = NULL;
  387. if (callback(cbctx, spec))
  388. qhead = qtail = spec;
  389. }
  390. while (qhead) {
  391. unsigned edge;
  392. Spectre *spec = qhead;
  393. for (edge = 0; edge < 14; edge++) {
  394. Spectre *new_spec;
  395. new_spec = spectre_adjacent(ctx, spec, edge, NULL);
  396. if (find234(placed, new_spec, NULL)) {
  397. spectre_free(new_spec);
  398. continue;
  399. }
  400. if (!callback(cbctx, new_spec)) {
  401. spectre_free(new_spec);
  402. continue;
  403. }
  404. add234(placed, new_spec);
  405. qtail->next = new_spec;
  406. qtail = new_spec;
  407. new_spec->next = NULL;
  408. }
  409. qhead = qhead->next;
  410. }
  411. {
  412. Spectre *spec;
  413. while ((spec = delpos234(placed, 0)) != NULL)
  414. spectre_free(spec);
  415. freetree234(placed);
  416. }
  417. }
  418. const char *spectre_tiling_params_invalid(
  419. const struct SpectrePatchParams *params)
  420. {
  421. size_t i;
  422. Hex h;
  423. if (params->ncoords == 0)
  424. return "expected at least one numeric coordinate";
  425. if (!spectre_valid_hex_letter(params->final_hex))
  426. return "invalid final hexagon type";
  427. h = hex_from_letter(params->final_hex);
  428. for (i = params->ncoords; i-- > 0 ;) {
  429. unsigned limit = (i == 0) ? num_spectres(h) : num_subhexes(h);
  430. if (params->coords[i] >= limit)
  431. return "coordinate out of range";
  432. if (i > 0)
  433. h = hexdata[h].subhexes[params->coords[i]];
  434. }
  435. return NULL;
  436. }
  437. struct SpectreCallbackContext {
  438. int xoff, yoff;
  439. Coord xmin, xmax, ymin, ymax;
  440. spectre_tile_callback_fn external_cb;
  441. void *external_cbctx;
  442. };
  443. static bool spectre_internal_callback(void *vctx, const Spectre *spec)
  444. {
  445. struct SpectreCallbackContext *ctx = (struct SpectreCallbackContext *)vctx;
  446. size_t i;
  447. int output_coords[4*14];
  448. for (i = 0; i < 14; i++) {
  449. Point p = spec->vertices[i];
  450. Coord x = point_x(p), y = point_y(p);
  451. if (coord_cmp(x, ctx->xmin) < 0 || coord_cmp(x, ctx->xmax) > 0 ||
  452. coord_cmp(y, ctx->ymin) < 0 || coord_cmp(y, ctx->ymax) > 0)
  453. return false;
  454. output_coords[4*i + 0] = ctx->xoff + x.c1;
  455. output_coords[4*i + 1] = x.cr3;
  456. output_coords[4*i + 2] = ctx->yoff - y.c1;
  457. output_coords[4*i + 3] = -y.cr3;
  458. }
  459. if (ctx->external_cb)
  460. ctx->external_cb(ctx->external_cbctx, output_coords);
  461. return true;
  462. }
  463. static void spectre_set_bounds(struct SpectreCallbackContext *cbctx,
  464. int w, int h)
  465. {
  466. cbctx->xoff = w/2;
  467. cbctx->yoff = h/2;
  468. cbctx->xmin.c1 = -cbctx->xoff;
  469. cbctx->xmax.c1 = -cbctx->xoff + w;
  470. cbctx->ymin.c1 = cbctx->yoff - h;
  471. cbctx->ymax.c1 = cbctx->yoff;
  472. cbctx->xmin.cr3 = 0;
  473. cbctx->xmax.cr3 = 0;
  474. cbctx->ymin.cr3 = 0;
  475. cbctx->ymax.cr3 = 0;
  476. }
  477. void spectre_tiling_randomise(struct SpectrePatchParams *ps, int w, int h,
  478. random_state *rs)
  479. {
  480. SpectreContext ctx[1];
  481. struct SpectreCallbackContext cbctx[1];
  482. size_t i;
  483. spectre_set_bounds(cbctx, w, h);
  484. cbctx->external_cb = NULL;
  485. cbctx->external_cbctx = NULL;
  486. spectrectx_init_random(ctx, rs);
  487. spectrectx_generate(ctx, spectre_internal_callback, cbctx);
  488. ps->orientation = ctx->orientation;
  489. ps->ncoords = ctx->prototype->nc;
  490. ps->coords = snewn(ps->ncoords, unsigned char);
  491. ps->coords[0] = ctx->prototype->index;
  492. for (i = 1; i < ps->ncoords; i++)
  493. ps->coords[i] = ctx->prototype->c[i-1].index;
  494. ps->final_hex = hex_to_letter(ctx->prototype->c[ps->ncoords-1].type);
  495. spectrectx_cleanup(ctx);
  496. }
  497. void spectre_tiling_generate(
  498. const struct SpectrePatchParams *params, int w, int h,
  499. spectre_tile_callback_fn external_cb, void *external_cbctx)
  500. {
  501. SpectreContext ctx[1];
  502. struct SpectreCallbackContext cbctx[1];
  503. spectre_set_bounds(cbctx, w, h);
  504. cbctx->external_cb = external_cb;
  505. cbctx->external_cbctx = external_cbctx;
  506. spectrectx_init_from_params(ctx, params);
  507. spectrectx_generate(ctx, spectre_internal_callback, cbctx);
  508. spectrectx_cleanup(ctx);
  509. }