Quite a few instances of the Cardinal Error of Ctype were turned up
[sgt/puzzles] / untangle.c
CommitLineData
9d6c3859 1/*
2 * untangle.c: Game about planar graphs. You are given a graph
3 * represented by points and straight lines, with some lines
4 * crossing; your task is to drag the points into a configuration
5 * where none of the lines cross.
6 *
7 * Cloned from a Flash game called `Planarity', by John Tantalo.
8 * <http://home.cwru.edu/~jnt5/Planarity> at the time of writing
9 * this. The Flash game had a fixed set of levels; my added value,
10 * as usual, is automatic generation of random games to order.
11 */
12
13/*
14 * TODO:
15 *
16 * - Docs and checklist etc
17 * - Any way we can speed up redraws on GTK? Uck.
18 */
19
20#include <stdio.h>
21#include <stdlib.h>
22#include <string.h>
23#include <assert.h>
24#include <ctype.h>
25#include <math.h>
26
27#include "puzzles.h"
28#include "tree234.h"
29
30#define CIRCLE_RADIUS 6
31#define DRAG_THRESHOLD (CIRCLE_RADIUS * 2)
32#define PREFERRED_TILESIZE 64
33
34#define FLASH_TIME 0.13F
35#define ANIM_TIME 0.13F
36#define SOLVEANIM_TIME 0.50F
37
38enum {
39 COL_BACKGROUND,
40 COL_LINE,
41 COL_OUTLINE,
42 COL_POINT,
43 COL_DRAGPOINT,
44 COL_NEIGHBOUR,
45 NCOLOURS
46};
47
48typedef struct point {
49 /*
50 * Points are stored using rational coordinates, with the same
51 * denominator for both coordinates.
52 */
53 int x, y, d;
54} point;
55
56typedef struct edge {
57 /*
58 * This structure is implicitly associated with a particular
59 * point set, so all it has to do is to store two point
60 * indices. It is required to store them in the order (lower,
61 * higher), i.e. a < b always.
62 */
63 int a, b;
64} edge;
65
66struct game_params {
67 int n; /* number of points */
68};
69
70struct graph {
71 int refcount; /* for deallocation */
72 tree234 *edges; /* stores `edge' structures */
73};
74
75struct game_state {
76 game_params params;
77 int w, h; /* extent of coordinate system only */
78 point *pts;
79 struct graph *graph;
80 int completed, cheated, just_solved;
81};
82
83static int edgecmpC(const void *av, const void *bv)
84{
85 const edge *a = (const edge *)av;
86 const edge *b = (const edge *)bv;
87
88 if (a->a < b->a)
89 return -1;
90 else if (a->a > b->a)
91 return +1;
92 else if (a->b < b->b)
93 return -1;
94 else if (a->b > b->b)
95 return +1;
96 return 0;
97}
98
99static int edgecmp(void *av, void *bv) { return edgecmpC(av, bv); }
100
101static game_params *default_params(void)
102{
103 game_params *ret = snew(game_params);
104
105 ret->n = 10;
106
107 return ret;
108}
109
110static int game_fetch_preset(int i, char **name, game_params **params)
111{
112 game_params *ret;
113 int n;
114 char buf[80];
115
116 switch (i) {
117 case 0: n = 6; break;
118 case 1: n = 10; break;
119 case 2: n = 15; break;
120 case 3: n = 20; break;
121 case 4: n = 25; break;
122 default: return FALSE;
123 }
124
125 sprintf(buf, "%d points", n);
126 *name = dupstr(buf);
127
128 *params = ret = snew(game_params);
129 ret->n = n;
130
131 return TRUE;
132}
133
134static void free_params(game_params *params)
135{
136 sfree(params);
137}
138
139static game_params *dup_params(game_params *params)
140{
141 game_params *ret = snew(game_params);
142 *ret = *params; /* structure copy */
143 return ret;
144}
145
146static void decode_params(game_params *params, char const *string)
147{
148 params->n = atoi(string);
149}
150
151static char *encode_params(game_params *params, int full)
152{
153 char buf[80];
154
155 sprintf(buf, "%d", params->n);
156
157 return dupstr(buf);
158}
159
160static config_item *game_configure(game_params *params)
161{
162 config_item *ret;
163 char buf[80];
164
165 ret = snewn(3, config_item);
166
167 ret[0].name = "Number of points";
168 ret[0].type = C_STRING;
169 sprintf(buf, "%d", params->n);
170 ret[0].sval = dupstr(buf);
171 ret[0].ival = 0;
172
173 ret[1].name = NULL;
174 ret[1].type = C_END;
175 ret[1].sval = NULL;
176 ret[1].ival = 0;
177
178 return ret;
179}
180
181static game_params *custom_params(config_item *cfg)
182{
183 game_params *ret = snew(game_params);
184
185 ret->n = atoi(cfg[0].sval);
186
187 return ret;
188}
189
190static char *validate_params(game_params *params, int full)
191{
192 if (params->n < 4)
193 return "Number of points must be at least four";
194 return NULL;
195}
196
197/*
198 * Determine whether the line segments between a1 and a2, and
199 * between b1 and b2, intersect. We count it as an intersection if
200 * any of the endpoints lies _on_ the other line.
201 */
202static int cross(point a1, point a2, point b1, point b2)
203{
204 int b1x, b1y, b2x, b2y, px, py, d1, d2, d3;
205
206 /*
207 * The condition for crossing is that b1 and b2 are on opposite
208 * sides of the line a1-a2, and vice versa. We determine this
209 * by taking the dot product of b1-a1 with a vector
210 * perpendicular to a2-a1, and similarly with b2-a1, and seeing
211 * if they have different signs.
212 */
213
214 /*
215 * Construct the vector b1-a1. We don't have to worry too much
216 * about the denominator, because we're only going to check the
217 * sign of this vector; we just need to get the numerator
218 * right.
219 */
220 b1x = b1.x * a1.d - a1.x * b1.d;
221 b1y = b1.y * a1.d - a1.y * b1.d;
222 /* Now construct b2-a1, and a vector perpendicular to a2-a1,
223 * in the same way. */
224 b2x = b2.x * a1.d - a1.x * b2.d;
225 b2y = b2.y * a1.d - a1.y * b2.d;
226 px = a1.y * a2.d - a2.y * a1.d;
227 py = a2.x * a1.d - a1.x * a2.d;
228 /* Take the dot products. */
229 d1 = b1x * px + b1y * py;
230 d2 = b2x * px + b2y * py;
231 /* If they have the same non-zero sign, the lines do not cross. */
232 if ((d1 > 0 && d2 > 0) || (d1 < 0 && d2 < 0))
233 return FALSE;
234
235 /*
236 * If the dot products are both exactly zero, then the two line
237 * segments are collinear. At this point the intersection
238 * condition becomes whether or not they overlap within their
239 * line.
240 */
241 if (d1 == 0 && d2 == 0) {
242 /* Construct the vector a2-a1. */
243 px = a2.x * a1.d - a1.x * a2.d;
244 py = a2.y * a1.d - a1.y * a2.d;
245 /* Determine the dot products of b1-a1 and b2-a1 with this. */
246 d1 = b1x * px + b1y * py;
247 d2 = b2x * px + b2y * py;
248 /* If they're both strictly negative, the lines do not cross. */
249 if (d1 < 0 && d2 < 0)
250 return FALSE;
251 /* Otherwise, take the dot product of a2-a1 with itself. If
252 * the other two dot products both exceed this, the lines do
253 * not cross. */
254 d3 = px * px + py * py;
255 if (d1 > d3 && d2 > d3)
256 return FALSE;
257 }
258
259 /*
260 * We've eliminated the only important special case, and we
261 * have determined that b1 and b2 are on opposite sides of the
262 * line a1-a2. Now do the same thing the other way round and
263 * we're done.
264 */
265 b1x = a1.x * b1.d - b1.x * a1.d;
266 b1y = a1.y * b1.d - b1.y * a1.d;
267 b2x = a2.x * b1.d - b1.x * a2.d;
268 b2y = a2.y * b1.d - b1.y * a2.d;
269 px = b1.y * b2.d - b2.y * b1.d;
270 py = b2.x * b1.d - b1.x * b2.d;
271 d1 = b1x * px + b1y * py;
272 d2 = b2x * px + b2y * py;
273 if ((d1 > 0 && d2 > 0) || (d1 < 0 && d2 < 0))
274 return FALSE;
275
276 /*
277 * The lines must cross.
278 */
279 return TRUE;
280}
281
282static unsigned long squarert(unsigned long n) {
283 unsigned long d, a, b, di;
284
285 d = n;
286 a = 0;
1ad942e7 287 b = 1L << 30; /* largest available power of 4 */
9d6c3859 288 do {
289 a >>= 1;
290 di = 2*a + b;
291 if (di <= d) {
292 d -= di;
293 a += b;
294 }
295 b >>= 2;
296 } while (b);
297
298 return a;
299}
300
301/*
302 * Our solutions are arranged on a square grid big enough that n
303 * points occupy about 1/POINTDENSITY of the grid.
304 */
305#define POINTDENSITY 3
306#define MAXDEGREE 4
307#define COORDLIMIT(n) squarert((n) * POINTDENSITY)
308
309static void addedge(tree234 *edges, int a, int b)
310{
311 edge *e = snew(edge);
312
313 assert(a != b);
314
315 e->a = min(a, b);
316 e->b = max(a, b);
317
318 add234(edges, e);
319}
320
321static int isedge(tree234 *edges, int a, int b)
322{
323 edge e;
324
325 assert(a != b);
326
327 e.a = min(a, b);
328 e.b = max(a, b);
329
330 return find234(edges, &e, NULL) != NULL;
331}
332
333typedef struct vertex {
334 int param;
335 int vindex;
336} vertex;
337
338static int vertcmpC(const void *av, const void *bv)
339{
340 const vertex *a = (vertex *)av;
341 const vertex *b = (vertex *)bv;
342
343 if (a->param < b->param)
344 return -1;
345 else if (a->param > b->param)
346 return +1;
347 else if (a->vindex < b->vindex)
348 return -1;
349 else if (a->vindex > b->vindex)
350 return +1;
351 return 0;
352}
353static int vertcmp(void *av, void *bv) { return vertcmpC(av, bv); }
354
355/*
356 * Construct point coordinates for n points arranged in a circle,
357 * within the bounding box (0,0) to (w,w).
358 */
359static void make_circle(point *pts, int n, int w)
360{
361 int d, r, c, i;
362
363 /*
364 * First, decide on a denominator. Although in principle it
365 * would be nice to set this really high so as to finely
366 * distinguish all the points on the circle, I'm going to set
367 * it at a fixed size to prevent integer overflow problems.
368 */
369 d = PREFERRED_TILESIZE;
370
371 /*
372 * Leave a little space outside the circle.
373 */
374 c = d * w / 2;
375 r = d * w * 3 / 7;
376
377 /*
378 * Place the points.
379 */
380 for (i = 0; i < n; i++) {
381 double angle = i * 2 * PI / n;
382 double x = r * sin(angle), y = - r * cos(angle);
383 pts[i].x = (int)(c + x + 0.5);
384 pts[i].y = (int)(c + y + 0.5);
385 pts[i].d = d;
386 }
387}
388
389static char *new_game_desc(game_params *params, random_state *rs,
390 char **aux, int interactive)
391{
392 int n = params->n;
393 int w, h, i, j, k, m;
394 point *pts, *pts2;
395 int *tmp;
396 tree234 *edges, *vertices;
397 edge *e, *e2;
398 vertex *v, *vs, *vlist;
399 char *ret;
400
401 w = h = COORDLIMIT(n);
402
403 /*
404 * Choose n points from this grid.
405 */
406 pts = snewn(n, point);
407 tmp = snewn(w*h, int);
408 for (i = 0; i < w*h; i++)
409 tmp[i] = i;
410 shuffle(tmp, w*h, sizeof(*tmp), rs);
411 for (i = 0; i < n; i++) {
412 pts[i].x = tmp[i] % w;
413 pts[i].y = tmp[i] / w;
414 pts[i].d = 1;
415 }
416 sfree(tmp);
417
418 /*
419 * Now start adding edges between the points.
420 *
421 * At all times, we attempt to add an edge to the lowest-degree
422 * vertex we currently have, and we try the other vertices as
423 * candidate second endpoints in order of distance from this
424 * one. We stop as soon as we find an edge which
425 *
426 * (a) does not increase any vertex's degree beyond MAXDEGREE
427 * (b) does not cross any existing edges
428 * (c) does not intersect any actual point.
429 */
430 vs = snewn(n, vertex);
431 vertices = newtree234(vertcmp);
432 for (i = 0; i < n; i++) {
433 v = vs + i;
434 v->param = 0; /* in this tree, param is the degree */
435 v->vindex = i;
436 add234(vertices, v);
437 }
438 edges = newtree234(edgecmp);
439 vlist = snewn(n, vertex);
440 while (1) {
441 int added = FALSE;
442
443 for (i = 0; i < n; i++) {
444 v = index234(vertices, i);
445 j = v->vindex;
446
447 if (v->param >= MAXDEGREE)
448 break; /* nothing left to add! */
449
450 /*
451 * Sort the other vertices into order of their distance
452 * from this one. Don't bother looking below i, because
453 * we've already tried those edges the other way round.
454 * Also here we rule out target vertices with too high
455 * a degree, and (of course) ones to which we already
456 * have an edge.
457 */
458 m = 0;
459 for (k = i+1; k < n; k++) {
460 vertex *kv = index234(vertices, k);
461 int ki = kv->vindex;
462 int dx, dy;
463
464 if (kv->param >= MAXDEGREE || isedge(edges, ki, j))
465 continue;
466
467 vlist[m].vindex = ki;
468 dx = pts[ki].x - pts[j].x;
469 dy = pts[ki].y - pts[j].y;
470 vlist[m].param = dx*dx + dy*dy;
471 m++;
472 }
473
474 qsort(vlist, m, sizeof(*vlist), vertcmpC);
475
476 for (k = 0; k < m; k++) {
477 int p;
478 int ki = vlist[k].vindex;
479
480 /*
481 * Check to see whether this edge intersects any
482 * existing edge or point.
483 */
484 for (p = 0; p < n; p++)
485 if (p != ki && p != j && cross(pts[ki], pts[j],
486 pts[p], pts[p]))
487 break;
488 if (p < n)
489 continue;
490 for (p = 0; (e = index234(edges, p)) != NULL; p++)
491 if (e->a != ki && e->a != j &&
492 e->b != ki && e->b != j &&
493 cross(pts[ki], pts[j], pts[e->a], pts[e->b]))
494 break;
495 if (e)
496 continue;
497
498 /*
499 * We're done! Add this edge, modify the degrees of
500 * the two vertices involved, and break.
501 */
502 addedge(edges, j, ki);
503 added = TRUE;
504 del234(vertices, vs+j);
505 vs[j].param++;
506 add234(vertices, vs+j);
507 del234(vertices, vs+ki);
508 vs[ki].param++;
509 add234(vertices, vs+ki);
510 break;
511 }
512
513 if (k < m)
514 break;
515 }
516
517 if (!added)
518 break; /* we're done. */
519 }
520
521 /*
522 * That's our graph. Now shuffle the points, making sure that
523 * they come out with at least one crossed line when arranged
524 * in a circle (so that the puzzle isn't immediately solved!).
525 */
526 tmp = snewn(n, int);
527 for (i = 0; i < n; i++)
528 tmp[i] = i;
529 pts2 = snewn(n, point);
530 make_circle(pts2, n, w);
531 while (1) {
532 shuffle(tmp, n, sizeof(*tmp), rs);
533 for (i = 0; (e = index234(edges, i)) != NULL; i++) {
534 for (j = i+1; (e2 = index234(edges, j)) != NULL; j++) {
535 if (e2->a == e->a || e2->a == e->b ||
536 e2->b == e->a || e2->b == e->b)
537 continue;
538 if (cross(pts2[tmp[e2->a]], pts2[tmp[e2->b]],
539 pts2[tmp[e->a]], pts2[tmp[e->b]]))
540 break;
541 }
542 if (e2)
543 break;
544 }
545 if (e)
546 break; /* we've found a crossing */
547 }
548
549 /*
550 * We're done. Now encode the graph in a string format. Let's
551 * use a comma-separated list of dash-separated vertex number
552 * pairs, numbered from zero. We'll sort the list to prevent
553 * side channels.
554 */
555 ret = NULL;
556 {
557 char *sep;
558 char buf[80];
559 int retlen;
560 edge *ea;
561
562 retlen = 0;
563 m = count234(edges);
564 ea = snewn(m, edge);
565 for (i = 0; (e = index234(edges, i)) != NULL; i++) {
566 assert(i < m);
567 ea[i].a = min(tmp[e->a], tmp[e->b]);
568 ea[i].b = max(tmp[e->a], tmp[e->b]);
569 retlen += 1 + sprintf(buf, "%d-%d", ea[i].a, ea[i].b);
570 }
571 assert(i == m);
572 qsort(ea, m, sizeof(*ea), edgecmpC);
573
574 ret = snewn(retlen, char);
575 sep = "";
576 k = 0;
577
578 for (i = 0; i < m; i++) {
579 k += sprintf(ret + k, "%s%d-%d", sep, ea[i].a, ea[i].b);
580 sep = ",";
581 }
582 assert(k < retlen);
583
584 sfree(ea);
585 }
586
587 /*
588 * Encode the solution we started with as an aux_info string.
589 */
590 {
591 char buf[80];
592 char *auxstr;
593 int auxlen;
594
595 auxlen = 2; /* leading 'S' and trailing '\0' */
596 for (i = 0; i < n; i++) {
597 j = tmp[i];
598 pts2[j] = pts[i];
599 if (pts2[j].d & 1) {
600 pts2[j].x *= 2;
601 pts2[j].y *= 2;
602 pts2[j].d *= 2;
603 }
604 pts2[j].x += pts2[j].d / 2;
605 pts2[j].y += pts2[j].d / 2;
606 auxlen += sprintf(buf, ";P%d:%d,%d/%d", i,
607 pts2[j].x, pts2[j].y, pts2[j].d);
608 }
609 k = 0;
610 auxstr = snewn(auxlen, char);
611 auxstr[k++] = 'S';
612 for (i = 0; i < n; i++)
613 k += sprintf(auxstr+k, ";P%d:%d,%d/%d", i,
614 pts2[i].x, pts2[i].y, pts2[i].d);
615 assert(k < auxlen);
616 *aux = auxstr;
617 }
618 sfree(pts2);
619
620 sfree(tmp);
621 sfree(vlist);
622 freetree234(vertices);
623 sfree(vs);
624 while ((e = delpos234(edges, 0)) != NULL)
625 sfree(e);
626 freetree234(edges);
627 sfree(pts);
628
629 return ret;
630}
631
632static char *validate_desc(game_params *params, char *desc)
633{
634 int a, b;
635
636 while (*desc) {
637 a = atoi(desc);
638 if (a < 0 || a >= params->n)
639 return "Number out of range in game description";
640 while (*desc && isdigit((unsigned char)*desc)) desc++;
641 if (*desc != '-')
642 return "Expected '-' after number in game description";
643 desc++; /* eat dash */
644 b = atoi(desc);
645 if (b < 0 || b >= params->n)
646 return "Number out of range in game description";
647 while (*desc && isdigit((unsigned char)*desc)) desc++;
648 if (*desc) {
649 if (*desc != ',')
650 return "Expected ',' after number in game description";
651 desc++; /* eat comma */
652 }
653 }
654
655 return NULL;
656}
657
658static game_state *new_game(midend_data *me, game_params *params, char *desc)
659{
660 int n = params->n;
661 game_state *state = snew(game_state);
662 int a, b;
663
664 state->params = *params;
665 state->w = state->h = COORDLIMIT(n);
666 state->pts = snewn(n, point);
667 make_circle(state->pts, n, state->w);
668 state->graph = snew(struct graph);
669 state->graph->refcount = 1;
670 state->graph->edges = newtree234(edgecmp);
671 state->completed = state->cheated = state->just_solved = FALSE;
672
673 while (*desc) {
674 a = atoi(desc);
675 assert(a >= 0 && a < params->n);
676 while (*desc && isdigit((unsigned char)*desc)) desc++;
677 assert(*desc == '-');
678 desc++; /* eat dash */
679 b = atoi(desc);
680 assert(b >= 0 && b < params->n);
681 while (*desc && isdigit((unsigned char)*desc)) desc++;
682 if (*desc) {
683 assert(*desc == ',');
684 desc++; /* eat comma */
685 }
686 addedge(state->graph->edges, a, b);
687 }
688
689 return state;
690}
691
692static game_state *dup_game(game_state *state)
693{
694 int n = state->params.n;
695 game_state *ret = snew(game_state);
696
697 ret->params = state->params;
698 ret->w = state->w;
699 ret->h = state->h;
700 ret->pts = snewn(n, point);
701 memcpy(ret->pts, state->pts, n * sizeof(point));
702 ret->graph = state->graph;
703 ret->graph->refcount++;
704 ret->completed = state->completed;
705 ret->cheated = state->cheated;
706 ret->just_solved = state->just_solved;
707
708 return ret;
709}
710
711static void free_game(game_state *state)
712{
713 if (--state->graph->refcount <= 0) {
714 edge *e;
715 while ((e = delpos234(state->graph->edges, 0)) != NULL)
716 sfree(e);
717 freetree234(state->graph->edges);
718 sfree(state->graph);
719 }
720 sfree(state->pts);
721 sfree(state);
722}
723
724static char *solve_game(game_state *state, game_state *currstate,
725 char *aux, char **error)
726{
727 if (!aux) {
728 *error = "Solution not known for this puzzle";
729 return NULL;
730 }
731
732 return dupstr(aux);
733}
734
735static char *game_text_format(game_state *state)
736{
737 return NULL;
738}
739
740struct game_ui {
741 int dragpoint; /* point being dragged; -1 if none */
742 point newpoint; /* where it's been dragged to so far */
743 int just_dragged; /* reset in game_changed_state */
744 int just_moved; /* _set_ in game_changed_state */
745 float anim_length;
746};
747
748static game_ui *new_ui(game_state *state)
749{
750 game_ui *ui = snew(game_ui);
751 ui->dragpoint = -1;
752 ui->just_moved = ui->just_dragged = FALSE;
753 return ui;
754}
755
756static void free_ui(game_ui *ui)
757{
758 sfree(ui);
759}
760
761static char *encode_ui(game_ui *ui)
762{
763 return NULL;
764}
765
766static void decode_ui(game_ui *ui, char *encoding)
767{
768}
769
770static void game_changed_state(game_ui *ui, game_state *oldstate,
771 game_state *newstate)
772{
773 ui->dragpoint = -1;
774 ui->just_moved = ui->just_dragged;
775 ui->just_dragged = FALSE;
776}
777
778struct game_drawstate {
779 int tilesize;
780};
781
782static char *interpret_move(game_state *state, game_ui *ui, game_drawstate *ds,
783 int x, int y, int button)
784{
785 int n = state->params.n;
786
787 if (button == LEFT_BUTTON) {
788 int i, best, bestd;
789
790 /*
791 * Begin drag. We drag the vertex _nearest_ to the pointer,
792 * just in case one is nearly on top of another and we want
793 * to drag the latter. However, we drag nothing at all if
794 * the nearest vertex is outside DRAG_THRESHOLD.
795 */
796 best = -1;
797 bestd = 0;
798
799 for (i = 0; i < n; i++) {
800 int px = state->pts[i].x * ds->tilesize / state->pts[i].d;
801 int py = state->pts[i].y * ds->tilesize / state->pts[i].d;
802 int dx = px - x;
803 int dy = py - y;
804 int d = dx*dx + dy*dy;
805
806 if (best == -1 || bestd > d) {
807 best = i;
808 bestd = d;
809 }
810 }
811
812 if (bestd <= DRAG_THRESHOLD * DRAG_THRESHOLD) {
813 ui->dragpoint = best;
814 ui->newpoint.x = x;
815 ui->newpoint.y = y;
816 ui->newpoint.d = ds->tilesize;
817 return "";
818 }
819
820 } else if (button == LEFT_DRAG && ui->dragpoint >= 0) {
821 ui->newpoint.x = x;
822 ui->newpoint.y = y;
823 ui->newpoint.d = ds->tilesize;
824 return "";
825 } else if (button == LEFT_RELEASE && ui->dragpoint >= 0) {
826 int p = ui->dragpoint;
827 char buf[80];
828
829 ui->dragpoint = -1; /* terminate drag, no matter what */
830
831 /*
832 * First, see if we're within range. The user can cancel a
833 * drag by dragging the point right off the window.
834 */
835 if (ui->newpoint.x < 0 || ui->newpoint.x >= state->w*ui->newpoint.d ||
836 ui->newpoint.y < 0 || ui->newpoint.y >= state->h*ui->newpoint.d)
837 return "";
838
839 /*
840 * We aren't cancelling the drag. Construct a move string
841 * indicating where this point is going to.
842 */
843 sprintf(buf, "P%d:%d,%d/%d", p,
844 ui->newpoint.x, ui->newpoint.y, ui->newpoint.d);
845 ui->just_dragged = TRUE;
846 return dupstr(buf);
847 }
848
849 return NULL;
850}
851
852static game_state *execute_move(game_state *state, char *move)
853{
854 int n = state->params.n;
855 int p, x, y, d, k;
856 game_state *ret = dup_game(state);
857
858 ret->just_solved = FALSE;
859
860 while (*move) {
861 if (*move == 'S') {
862 move++;
863 if (*move == ';') move++;
864 ret->cheated = ret->just_solved = TRUE;
865 }
866 if (*move == 'P' &&
867 sscanf(move+1, "%d:%d,%d/%d%n", &p, &x, &y, &d, &k) == 4 &&
868 p >= 0 && p < n && d > 0) {
869 ret->pts[p].x = x;
870 ret->pts[p].y = y;
871 ret->pts[p].d = d;
872
873 move += k+1;
874 if (*move == ';') move++;
875 } else {
876 free_game(ret);
877 return NULL;
878 }
879 }
880
881 /*
882 * Check correctness: for every pair of edges, see whether they
883 * cross.
884 */
885 if (!ret->completed) {
886 int i, j;
887 edge *e, *e2;
888
889 for (i = 0; (e = index234(ret->graph->edges, i)) != NULL; i++) {
890 for (j = i+1; (e2 = index234(ret->graph->edges, j)) != NULL; j++) {
891 if (e2->a == e->a || e2->a == e->b ||
892 e2->b == e->a || e2->b == e->b)
893 continue;
894 if (cross(ret->pts[e2->a], ret->pts[e2->b],
895 ret->pts[e->a], ret->pts[e->b]))
896 break;
897 }
898 if (e2)
899 break;
900 }
901
902 /*
903 * e == NULL if we've gone through all the edge pairs
904 * without finding a crossing.
905 */
906 ret->completed = (e == NULL);
907 }
908
909 return ret;
910}
911
912/* ----------------------------------------------------------------------
913 * Drawing routines.
914 */
915
916static void game_compute_size(game_params *params, int tilesize,
917 int *x, int *y)
918{
919 *x = *y = COORDLIMIT(params->n) * tilesize;
920}
921
922static void game_set_size(game_drawstate *ds, game_params *params,
923 int tilesize)
924{
925 ds->tilesize = tilesize;
926}
927
928static float *game_colours(frontend *fe, game_state *state, int *ncolours)
929{
930 float *ret = snewn(3 * NCOLOURS, float);
931
932 frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]);
933
934 ret[COL_LINE * 3 + 0] = 0.0F;
935 ret[COL_LINE * 3 + 1] = 0.0F;
936 ret[COL_LINE * 3 + 2] = 0.0F;
937
938 ret[COL_OUTLINE * 3 + 0] = 0.0F;
939 ret[COL_OUTLINE * 3 + 1] = 0.0F;
940 ret[COL_OUTLINE * 3 + 2] = 0.0F;
941
942 ret[COL_POINT * 3 + 0] = 0.0F;
943 ret[COL_POINT * 3 + 1] = 0.0F;
944 ret[COL_POINT * 3 + 2] = 1.0F;
945
946 ret[COL_DRAGPOINT * 3 + 0] = 1.0F;
947 ret[COL_DRAGPOINT * 3 + 1] = 1.0F;
948 ret[COL_DRAGPOINT * 3 + 2] = 1.0F;
949
950 ret[COL_NEIGHBOUR * 3 + 0] = 1.0F;
951 ret[COL_NEIGHBOUR * 3 + 1] = 0.0F;
952 ret[COL_NEIGHBOUR * 3 + 2] = 0.0F;
953
954 *ncolours = NCOLOURS;
955 return ret;
956}
957
958static game_drawstate *game_new_drawstate(game_state *state)
959{
960 struct game_drawstate *ds = snew(struct game_drawstate);
961
962 ds->tilesize = 0;
963
964 return ds;
965}
966
967static void game_free_drawstate(game_drawstate *ds)
968{
969 sfree(ds);
970}
971
972static point mix(point a, point b, float distance)
973{
974 point ret;
975
976 ret.d = a.d * b.d;
977 ret.x = a.x * b.d + distance * (b.x * a.d - a.x * b.d);
978 ret.y = a.y * b.d + distance * (b.y * a.d - a.y * b.d);
979
980 return ret;
981}
982
983static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
984 game_state *state, int dir, game_ui *ui,
985 float animtime, float flashtime)
986{
987 int w, h;
988 edge *e;
989 int i, j;
990 int bg;
991
992 /*
993 * There's no terribly sensible way to do partial redraws of
994 * this game, so I'm going to have to resort to redrawing the
995 * whole thing every time.
996 */
997
998 bg = (flashtime != 0 ? COL_DRAGPOINT : COL_BACKGROUND);
999 game_compute_size(&state->params, ds->tilesize, &w, &h);
1000 draw_rect(fe, 0, 0, w, h, bg);
1001
1002 /*
1003 * Draw the edges.
1004 */
1005
1006 for (i = 0; (e = index234(state->graph->edges, i)) != NULL; i++) {
1007 point p1, p2;
1008 int x1, y1, x2, y2;
1009
1010 p1 = state->pts[e->a];
1011 p2 = state->pts[e->b];
1012 if (ui->dragpoint == e->a)
1013 p1 = ui->newpoint;
1014 else if (ui->dragpoint == e->b)
1015 p2 = ui->newpoint;
1016
1017 if (oldstate) {
1018 p1 = mix(oldstate->pts[e->a], p1, animtime / ui->anim_length);
1019 p2 = mix(oldstate->pts[e->b], p2, animtime / ui->anim_length);
1020 }
1021
1022 x1 = p1.x * ds->tilesize / p1.d;
1023 y1 = p1.y * ds->tilesize / p1.d;
1024 x2 = p2.x * ds->tilesize / p2.d;
1025 y2 = p2.y * ds->tilesize / p2.d;
1026
1027 draw_line(fe, x1, y1, x2, y2, COL_LINE);
1028 }
1029
1030 /*
1031 * Draw the points.
1032 *
1033 * When dragging, we should not only vary the colours, but
1034 * leave the point being dragged until last.
1035 */
1036 for (j = 0; j < 3; j++) {
1037 int thisc = (j == 0 ? COL_POINT :
1038 j == 1 ? COL_NEIGHBOUR : COL_DRAGPOINT);
1039 for (i = 0; i < state->params.n; i++) {
1040 int x, y, c;
1041 point p = state->pts[i];
1042
1043 if (ui->dragpoint == i) {
1044 p = ui->newpoint;
1045 c = COL_DRAGPOINT;
1046 } else if (ui->dragpoint >= 0 &&
1047 isedge(state->graph->edges, ui->dragpoint, i)) {
1048 c = COL_NEIGHBOUR;
1049 } else {
1050 c = COL_POINT;
1051 }
1052
1053 if (oldstate)
1054 p = mix(oldstate->pts[i], p, animtime / ui->anim_length);
1055
1056 if (c == thisc) {
1057 x = p.x * ds->tilesize / p.d;
1058 y = p.y * ds->tilesize / p.d;
1059
1060#ifdef VERTEX_NUMBERS
1061 draw_circle(fe, x, y, DRAG_THRESHOLD, bg, bg);
1062 {
1063 char buf[80];
1064 sprintf(buf, "%d", i);
1065 draw_text(fe, x, y, FONT_VARIABLE, DRAG_THRESHOLD*3/2,
1066 ALIGN_VCENTRE|ALIGN_HCENTRE, c, buf);
1067 }
1068#else
1069 draw_circle(fe, x, y, CIRCLE_RADIUS, c, COL_OUTLINE);
1070#endif
1071 }
1072 }
1073 }
1074
1075 draw_update(fe, 0, 0, w, h);
1076}
1077
1078static float game_anim_length(game_state *oldstate, game_state *newstate,
1079 int dir, game_ui *ui)
1080{
1081 if (ui->just_moved)
1082 return 0.0F;
1083 if ((dir < 0 ? oldstate : newstate)->just_solved)
1084 ui->anim_length = SOLVEANIM_TIME;
1085 else
1086 ui->anim_length = ANIM_TIME;
1087 return ui->anim_length;
1088}
1089
1090static float game_flash_length(game_state *oldstate, game_state *newstate,
1091 int dir, game_ui *ui)
1092{
1093 if (!oldstate->completed && newstate->completed &&
1094 !oldstate->cheated && !newstate->cheated)
1095 return FLASH_TIME;
1096 return 0.0F;
1097}
1098
1099static int game_wants_statusbar(void)
1100{
1101 return FALSE;
1102}
1103
1104static int game_timing_state(game_state *state, game_ui *ui)
1105{
1106 return TRUE;
1107}
1108
1109#ifdef COMBINED
1110#define thegame untangle
1111#endif
1112
1113const struct game thegame = {
1114 "Untangle", "games.untangle",
1115 default_params,
1116 game_fetch_preset,
1117 decode_params,
1118 encode_params,
1119 free_params,
1120 dup_params,
1121 TRUE, game_configure, custom_params,
1122 validate_params,
1123 new_game_desc,
1124 validate_desc,
1125 new_game,
1126 dup_game,
1127 free_game,
1128 TRUE, solve_game,
1129 FALSE, game_text_format,
1130 new_ui,
1131 free_ui,
1132 encode_ui,
1133 decode_ui,
1134 game_changed_state,
1135 interpret_move,
1136 execute_move,
1137 PREFERRED_TILESIZE, game_compute_size, game_set_size,
1138 game_colours,
1139 game_new_drawstate,
1140 game_free_drawstate,
1141 game_redraw,
1142 game_anim_length,
1143 game_flash_length,
1144 game_wants_statusbar,
1145 FALSE, game_timing_state,
1146 SOLVE_ANIMATES, /* mouse_priorities */
1147};