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