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.
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.
16 * - This puzzle, perhaps uniquely among the collection, could use
17 * support for non-aspect-ratio-preserving resizes. This would
18 * require some sort of fairly large redesign, unfortunately (since
19 * it would invalidate the basic assumption that puzzles' size
20 * requirements are adequately expressed by a single scalar tile
21 * size), and probably complicate the rest of the puzzles' API as a
22 * result. So I'm not sure I really want to do it.
24 * - It would be nice if we could somehow auto-detect a real `long
25 * long' type on the host platform and use it in place of my
26 * hand-hacked int64s. It'd be faster and more reliable.
39 #define CIRCLE_RADIUS 6
40 #define DRAG_THRESHOLD (CIRCLE_RADIUS * 2)
41 #define PREFERRED_TILESIZE 64
43 #define FLASH_TIME 0.30F
44 #define ANIM_TIME 0.13F
45 #define SOLVEANIM_TIME 0.50F
63 typedef struct point
{
65 * Points are stored using rational coordinates, with the same
66 * denominator for both coordinates.
73 * This structure is implicitly associated with a particular
74 * point set, so all it has to do is to store two point
75 * indices. It is required to store them in the order (lower,
76 * higher), i.e. a < b always.
82 int n
; /* number of points */
86 int refcount
; /* for deallocation */
87 tree234
*edges
; /* stores `edge' structures */
92 int w
, h
; /* extent of coordinate system only */
95 int *crosses
; /* mark edges which are crossed */
98 int completed
, cheated
, just_solved
;
101 static int edgecmpC(const void *av
, const void *bv
)
103 const edge
*a
= (const edge
*)av
;
104 const edge
*b
= (const edge
*)bv
;
108 else if (a
->a
> b
->a
)
110 else if (a
->b
< b
->b
)
112 else if (a
->b
> b
->b
)
117 static int edgecmp(void *av
, void *bv
) { return edgecmpC(av
, bv
); }
119 static game_params
*default_params(void)
121 game_params
*ret
= snew(game_params
);
128 static int game_fetch_preset(int i
, char **name
, game_params
**params
)
135 case 0: n
= 6; break;
136 case 1: n
= 10; break;
137 case 2: n
= 15; break;
138 case 3: n
= 20; break;
139 case 4: n
= 25; break;
140 default: return FALSE
;
143 sprintf(buf
, "%d points", n
);
146 *params
= ret
= snew(game_params
);
152 static void free_params(game_params
*params
)
157 static game_params
*dup_params(const game_params
*params
)
159 game_params
*ret
= snew(game_params
);
160 *ret
= *params
; /* structure copy */
164 static void decode_params(game_params
*params
, char const *string
)
166 params
->n
= atoi(string
);
169 static char *encode_params(const game_params
*params
, int full
)
173 sprintf(buf
, "%d", params
->n
);
178 static config_item
*game_configure(const game_params
*params
)
183 ret
= snewn(3, config_item
);
185 ret
[0].name
= "Number of points";
186 ret
[0].type
= C_STRING
;
187 sprintf(buf
, "%d", params
->n
);
188 ret
[0].sval
= dupstr(buf
);
199 static game_params
*custom_params(const config_item
*cfg
)
201 game_params
*ret
= snew(game_params
);
203 ret
->n
= atoi(cfg
[0].sval
);
208 static char *validate_params(const game_params
*params
, int full
)
211 return "Number of points must be at least four";
215 /* ----------------------------------------------------------------------
216 * Small number of 64-bit integer arithmetic operations, to prevent
217 * integer overflow at the very core of cross().
225 #define greater64(i,j) ( (i).hi>(j).hi || ((i).hi==(j).hi && (i).lo>(j).lo))
226 #define sign64(i) ((i).hi < 0 ? -1 : (i).hi==0 && (i).lo==0 ? 0 : +1)
228 static int64
mulu32to64(unsigned long x
, unsigned long y
)
230 unsigned long a
, b
, c
, d
, t
;
233 a
= (x
& 0xFFFF) * (y
& 0xFFFF);
234 b
= (x
& 0xFFFF) * (y
>> 16);
235 c
= (x
>> 16) * (y
& 0xFFFF);
236 d
= (x
>> 16) * (y
>> 16);
239 ret
.hi
= d
+ (b
>> 16) + (c
>> 16);
240 t
= (b
& 0xFFFF) << 16;
244 t
= (c
& 0xFFFF) << 16;
249 #ifdef DIAGNOSTIC_VIA_LONGLONG
250 assert(((unsigned long long)ret
.hi
<< 32) + ret
.lo
==
251 (unsigned long long)x
* y
);
257 static int64
mul32to64(long x
, long y
)
261 #ifdef DIAGNOSTIC_VIA_LONGLONG
262 long long realret
= (long long)x
* y
;
266 x
= -x
, sign
= -sign
;
268 y
= -y
, sign
= -sign
;
270 ret
= mulu32to64(x
, y
);
279 #ifdef DIAGNOSTIC_VIA_LONGLONG
280 assert(((unsigned long long)ret
.hi
<< 32) + ret
.lo
== realret
);
286 static int64
dotprod64(long a
, long b
, long p
, long q
)
290 ab
= mul32to64(a
, b
);
291 pq
= mul32to64(p
, q
);
300 * Determine whether the line segments between a1 and a2, and
301 * between b1 and b2, intersect. We count it as an intersection if
302 * any of the endpoints lies _on_ the other line.
304 static int cross(point a1
, point a2
, point b1
, point b2
)
306 long b1x
, b1y
, b2x
, b2y
, px
, py
;
310 * The condition for crossing is that b1 and b2 are on opposite
311 * sides of the line a1-a2, and vice versa. We determine this
312 * by taking the dot product of b1-a1 with a vector
313 * perpendicular to a2-a1, and similarly with b2-a1, and seeing
314 * if they have different signs.
318 * Construct the vector b1-a1. We don't have to worry too much
319 * about the denominator, because we're only going to check the
320 * sign of this vector; we just need to get the numerator
323 b1x
= b1
.x
* a1
.d
- a1
.x
* b1
.d
;
324 b1y
= b1
.y
* a1
.d
- a1
.y
* b1
.d
;
325 /* Now construct b2-a1, and a vector perpendicular to a2-a1,
326 * in the same way. */
327 b2x
= b2
.x
* a1
.d
- a1
.x
* b2
.d
;
328 b2y
= b2
.y
* a1
.d
- a1
.y
* b2
.d
;
329 px
= a1
.y
* a2
.d
- a2
.y
* a1
.d
;
330 py
= a2
.x
* a1
.d
- a1
.x
* a2
.d
;
331 /* Take the dot products. Here we resort to 64-bit arithmetic. */
332 d1
= dotprod64(b1x
, px
, b1y
, py
);
333 d2
= dotprod64(b2x
, px
, b2y
, py
);
334 /* If they have the same non-zero sign, the lines do not cross. */
335 if ((sign64(d1
) > 0 && sign64(d2
) > 0) ||
336 (sign64(d1
) < 0 && sign64(d2
) < 0))
340 * If the dot products are both exactly zero, then the two line
341 * segments are collinear. At this point the intersection
342 * condition becomes whether or not they overlap within their
345 if (sign64(d1
) == 0 && sign64(d2
) == 0) {
346 /* Construct the vector a2-a1. */
347 px
= a2
.x
* a1
.d
- a1
.x
* a2
.d
;
348 py
= a2
.y
* a1
.d
- a1
.y
* a2
.d
;
349 /* Determine the dot products of b1-a1 and b2-a1 with this. */
350 d1
= dotprod64(b1x
, px
, b1y
, py
);
351 d2
= dotprod64(b2x
, px
, b2y
, py
);
352 /* If they're both strictly negative, the lines do not cross. */
353 if (sign64(d1
) < 0 && sign64(d2
) < 0)
355 /* Otherwise, take the dot product of a2-a1 with itself. If
356 * the other two dot products both exceed this, the lines do
358 d3
= dotprod64(px
, px
, py
, py
);
359 if (greater64(d1
, d3
) && greater64(d2
, d3
))
364 * We've eliminated the only important special case, and we
365 * have determined that b1 and b2 are on opposite sides of the
366 * line a1-a2. Now do the same thing the other way round and
369 b1x
= a1
.x
* b1
.d
- b1
.x
* a1
.d
;
370 b1y
= a1
.y
* b1
.d
- b1
.y
* a1
.d
;
371 b2x
= a2
.x
* b1
.d
- b1
.x
* a2
.d
;
372 b2y
= a2
.y
* b1
.d
- b1
.y
* a2
.d
;
373 px
= b1
.y
* b2
.d
- b2
.y
* b1
.d
;
374 py
= b2
.x
* b1
.d
- b1
.x
* b2
.d
;
375 d1
= dotprod64(b1x
, px
, b1y
, py
);
376 d2
= dotprod64(b2x
, px
, b2y
, py
);
377 if ((sign64(d1
) > 0 && sign64(d2
) > 0) ||
378 (sign64(d1
) < 0 && sign64(d2
) < 0))
382 * The lines must cross.
387 static unsigned long squarert(unsigned long n
) {
388 unsigned long d
, a
, b
, di
;
392 b
= 1L << 30; /* largest available power of 4 */
407 * Our solutions are arranged on a square grid big enough that n
408 * points occupy about 1/POINTDENSITY of the grid.
410 #define POINTDENSITY 3
412 #define COORDLIMIT(n) squarert((n) * POINTDENSITY)
414 static void addedge(tree234
*edges
, int a
, int b
)
416 edge
*e
= snew(edge
);
426 static int isedge(tree234
*edges
, int a
, int b
)
435 return find234(edges
, &e
, NULL
) != NULL
;
438 typedef struct vertex
{
443 static int vertcmpC(const void *av
, const void *bv
)
445 const vertex
*a
= (vertex
*)av
;
446 const vertex
*b
= (vertex
*)bv
;
448 if (a
->param
< b
->param
)
450 else if (a
->param
> b
->param
)
452 else if (a
->vindex
< b
->vindex
)
454 else if (a
->vindex
> b
->vindex
)
458 static int vertcmp(void *av
, void *bv
) { return vertcmpC(av
, bv
); }
461 * Construct point coordinates for n points arranged in a circle,
462 * within the bounding box (0,0) to (w,w).
464 static void make_circle(point
*pts
, int n
, int w
)
469 * First, decide on a denominator. Although in principle it
470 * would be nice to set this really high so as to finely
471 * distinguish all the points on the circle, I'm going to set
472 * it at a fixed size to prevent integer overflow problems.
474 d
= PREFERRED_TILESIZE
;
477 * Leave a little space outside the circle.
485 for (i
= 0; i
< n
; i
++) {
486 double angle
= i
* 2 * PI
/ n
;
487 double x
= r
* sin(angle
), y
= - r
* cos(angle
);
488 pts
[i
].x
= (long)(c
+ x
+ 0.5);
489 pts
[i
].y
= (long)(c
+ y
+ 0.5);
494 static char *new_game_desc(const game_params
*params
, random_state
*rs
,
495 char **aux
, int interactive
)
497 int n
= params
->n
, i
;
501 tree234
*edges
, *vertices
;
503 vertex
*v
, *vs
, *vlist
;
506 w
= h
= COORDLIMIT(n
);
509 * Choose n points from this grid.
511 pts
= snewn(n
, point
);
512 tmp
= snewn(w
*h
, long);
513 for (i
= 0; i
< w
*h
; i
++)
515 shuffle(tmp
, w
*h
, sizeof(*tmp
), rs
);
516 for (i
= 0; i
< n
; i
++) {
517 pts
[i
].x
= tmp
[i
] % w
;
518 pts
[i
].y
= tmp
[i
] / w
;
524 * Now start adding edges between the points.
526 * At all times, we attempt to add an edge to the lowest-degree
527 * vertex we currently have, and we try the other vertices as
528 * candidate second endpoints in order of distance from this
529 * one. We stop as soon as we find an edge which
531 * (a) does not increase any vertex's degree beyond MAXDEGREE
532 * (b) does not cross any existing edges
533 * (c) does not intersect any actual point.
535 vs
= snewn(n
, vertex
);
536 vertices
= newtree234(vertcmp
);
537 for (i
= 0; i
< n
; i
++) {
539 v
->param
= 0; /* in this tree, param is the degree */
543 edges
= newtree234(edgecmp
);
544 vlist
= snewn(n
, vertex
);
548 for (i
= 0; i
< n
; i
++) {
549 v
= index234(vertices
, i
);
552 if (v
->param
>= MAXDEGREE
)
553 break; /* nothing left to add! */
556 * Sort the other vertices into order of their distance
557 * from this one. Don't bother looking below i, because
558 * we've already tried those edges the other way round.
559 * Also here we rule out target vertices with too high
560 * a degree, and (of course) ones to which we already
564 for (k
= i
+1; k
< n
; k
++) {
565 vertex
*kv
= index234(vertices
, k
);
569 if (kv
->param
>= MAXDEGREE
|| isedge(edges
, ki
, j
))
572 vlist
[m
].vindex
= ki
;
573 dx
= pts
[ki
].x
- pts
[j
].x
;
574 dy
= pts
[ki
].y
- pts
[j
].y
;
575 vlist
[m
].param
= dx
*dx
+ dy
*dy
;
579 qsort(vlist
, m
, sizeof(*vlist
), vertcmpC
);
581 for (k
= 0; k
< m
; k
++) {
583 int ki
= vlist
[k
].vindex
;
586 * Check to see whether this edge intersects any
587 * existing edge or point.
589 for (p
= 0; p
< n
; p
++)
590 if (p
!= ki
&& p
!= j
&& cross(pts
[ki
], pts
[j
],
595 for (p
= 0; (e
= index234(edges
, p
)) != NULL
; p
++)
596 if (e
->a
!= ki
&& e
->a
!= j
&&
597 e
->b
!= ki
&& e
->b
!= j
&&
598 cross(pts
[ki
], pts
[j
], pts
[e
->a
], pts
[e
->b
]))
604 * We're done! Add this edge, modify the degrees of
605 * the two vertices involved, and break.
607 addedge(edges
, j
, ki
);
609 del234(vertices
, vs
+j
);
611 add234(vertices
, vs
+j
);
612 del234(vertices
, vs
+ki
);
614 add234(vertices
, vs
+ki
);
623 break; /* we're done. */
627 * That's our graph. Now shuffle the points, making sure that
628 * they come out with at least one crossed line when arranged
629 * in a circle (so that the puzzle isn't immediately solved!).
631 tmp
= snewn(n
, long);
632 for (i
= 0; i
< n
; i
++)
634 pts2
= snewn(n
, point
);
635 make_circle(pts2
, n
, w
);
637 shuffle(tmp
, n
, sizeof(*tmp
), rs
);
638 for (i
= 0; (e
= index234(edges
, i
)) != NULL
; i
++) {
639 for (j
= i
+1; (e2
= index234(edges
, j
)) != NULL
; j
++) {
640 if (e2
->a
== e
->a
|| e2
->a
== e
->b
||
641 e2
->b
== e
->a
|| e2
->b
== e
->b
)
643 if (cross(pts2
[tmp
[e2
->a
]], pts2
[tmp
[e2
->b
]],
644 pts2
[tmp
[e
->a
]], pts2
[tmp
[e
->b
]]))
651 break; /* we've found a crossing */
655 * We're done. Now encode the graph in a string format. Let's
656 * use a comma-separated list of dash-separated vertex number
657 * pairs, numbered from zero. We'll sort the list to prevent
670 for (i
= 0; (e
= index234(edges
, i
)) != NULL
; i
++) {
672 ea
[i
].a
= min(tmp
[e
->a
], tmp
[e
->b
]);
673 ea
[i
].b
= max(tmp
[e
->a
], tmp
[e
->b
]);
674 retlen
+= 1 + sprintf(buf
, "%d-%d", ea
[i
].a
, ea
[i
].b
);
677 qsort(ea
, m
, sizeof(*ea
), edgecmpC
);
679 ret
= snewn(retlen
, char);
683 for (i
= 0; i
< m
; i
++) {
684 k
+= sprintf(ret
+ k
, "%s%d-%d", sep
, ea
[i
].a
, ea
[i
].b
);
693 * Encode the solution we started with as an aux_info string.
700 auxlen
= 2; /* leading 'S' and trailing '\0' */
701 for (i
= 0; i
< n
; i
++) {
709 pts2
[j
].x
+= pts2
[j
].d
/ 2;
710 pts2
[j
].y
+= pts2
[j
].d
/ 2;
711 auxlen
+= sprintf(buf
, ";P%d:%ld,%ld/%ld", i
,
712 pts2
[j
].x
, pts2
[j
].y
, pts2
[j
].d
);
715 auxstr
= snewn(auxlen
, char);
717 for (i
= 0; i
< n
; i
++)
718 k
+= sprintf(auxstr
+k
, ";P%d:%ld,%ld/%ld", i
,
719 pts2
[i
].x
, pts2
[i
].y
, pts2
[i
].d
);
727 freetree234(vertices
);
729 while ((e
= delpos234(edges
, 0)) != NULL
)
737 static char *validate_desc(const game_params
*params
, const char *desc
)
743 if (a
< 0 || a
>= params
->n
)
744 return "Number out of range in game description";
745 while (*desc
&& isdigit((unsigned char)*desc
)) desc
++;
747 return "Expected '-' after number in game description";
748 desc
++; /* eat dash */
750 if (b
< 0 || b
>= params
->n
)
751 return "Number out of range in game description";
752 while (*desc
&& isdigit((unsigned char)*desc
)) desc
++;
755 return "Expected ',' after number in game description";
756 desc
++; /* eat comma */
763 static void mark_crossings(game_state
*state
)
769 #ifdef SHOW_CROSSINGS
770 for (i
= 0; (e
= index234(state
->graph
->edges
, i
)) != NULL
; i
++)
771 state
->crosses
[i
] = FALSE
;
775 * Check correctness: for every pair of edges, see whether they
778 for (i
= 0; (e
= index234(state
->graph
->edges
, i
)) != NULL
; i
++) {
779 for (j
= i
+1; (e2
= index234(state
->graph
->edges
, j
)) != NULL
; j
++) {
780 if (e2
->a
== e
->a
|| e2
->a
== e
->b
||
781 e2
->b
== e
->a
|| e2
->b
== e
->b
)
783 if (cross(state
->pts
[e2
->a
], state
->pts
[e2
->b
],
784 state
->pts
[e
->a
], state
->pts
[e
->b
])) {
786 #ifdef SHOW_CROSSINGS
787 state
->crosses
[i
] = state
->crosses
[j
] = TRUE
;
789 goto done
; /* multi-level break - sorry */
796 * e == NULL if we've gone through all the edge pairs
797 * without finding a crossing.
799 #ifndef SHOW_CROSSINGS
803 state
->completed
= TRUE
;
806 static game_state
*new_game(midend
*me
, const game_params
*params
,
810 game_state
*state
= snew(game_state
);
813 state
->params
= *params
;
814 state
->w
= state
->h
= COORDLIMIT(n
);
815 state
->pts
= snewn(n
, point
);
816 make_circle(state
->pts
, n
, state
->w
);
817 state
->graph
= snew(struct graph
);
818 state
->graph
->refcount
= 1;
819 state
->graph
->edges
= newtree234(edgecmp
);
820 state
->completed
= state
->cheated
= state
->just_solved
= FALSE
;
824 assert(a
>= 0 && a
< params
->n
);
825 while (*desc
&& isdigit((unsigned char)*desc
)) desc
++;
826 assert(*desc
== '-');
827 desc
++; /* eat dash */
829 assert(b
>= 0 && b
< params
->n
);
830 while (*desc
&& isdigit((unsigned char)*desc
)) desc
++;
832 assert(*desc
== ',');
833 desc
++; /* eat comma */
835 addedge(state
->graph
->edges
, a
, b
);
838 #ifdef SHOW_CROSSINGS
839 state
->crosses
= snewn(count234(state
->graph
->edges
), int);
840 mark_crossings(state
); /* sets up `crosses' and `completed' */
846 static game_state
*dup_game(const game_state
*state
)
848 int n
= state
->params
.n
;
849 game_state
*ret
= snew(game_state
);
851 ret
->params
= state
->params
;
854 ret
->pts
= snewn(n
, point
);
855 memcpy(ret
->pts
, state
->pts
, n
* sizeof(point
));
856 ret
->graph
= state
->graph
;
857 ret
->graph
->refcount
++;
858 ret
->completed
= state
->completed
;
859 ret
->cheated
= state
->cheated
;
860 ret
->just_solved
= state
->just_solved
;
861 #ifdef SHOW_CROSSINGS
862 ret
->crosses
= snewn(count234(ret
->graph
->edges
), int);
863 memcpy(ret
->crosses
, state
->crosses
,
864 count234(ret
->graph
->edges
) * sizeof(int));
870 static void free_game(game_state
*state
)
872 if (--state
->graph
->refcount
<= 0) {
874 while ((e
= delpos234(state
->graph
->edges
, 0)) != NULL
)
876 freetree234(state
->graph
->edges
);
883 static char *solve_game(const game_state
*state
, const game_state
*currstate
,
884 const char *aux
, char **error
)
886 int n
= state
->params
.n
;
895 *error
= "Solution not known for this puzzle";
900 * Decode the aux_info to get the original point positions.
902 pts
= snewn(n
, point
);
904 for (i
= 0; i
< n
; i
++) {
907 int ret
= sscanf(aux
, ";P%d:%ld,%ld/%ld%n", &p
, &x
, &y
, &d
, &k
);
908 if (ret
!= 4 || p
!= i
) {
909 *error
= "Internal error: aux_info badly formatted";
920 * Now go through eight possible symmetries of the point set.
921 * For each one, work out the sum of the Euclidean distances
922 * between the points' current positions and their new ones.
924 * We're squaring distances here, which means we're at risk of
925 * integer overflow. Fortunately, there's no real need to be
926 * massively careful about rounding errors, since this is a
927 * non-essential bit of the code; so I'll just work in floats
933 for (i
= 0; i
< 8; i
++) {
936 matrix
[0] = matrix
[1] = matrix
[2] = matrix
[3] = 0;
937 matrix
[i
& 1] = (i
& 2) ? +1 : -1;
938 matrix
[3-(i
&1)] = (i
& 4) ? +1 : -1;
941 for (j
= 0; j
< n
; j
++) {
942 float px
= (float)pts
[j
].x
/ pts
[j
].d
;
943 float py
= (float)pts
[j
].y
/ pts
[j
].d
;
944 float sx
= (float)currstate
->pts
[j
].x
/ currstate
->pts
[j
].d
;
945 float sy
= (float)currstate
->pts
[j
].y
/ currstate
->pts
[j
].d
;
946 float cx
= (float)currstate
->w
/ 2;
947 float cy
= (float)currstate
->h
/ 2;
948 float ox
, oy
, dx
, dy
;
953 ox
= matrix
[0] * px
+ matrix
[1] * py
;
954 oy
= matrix
[2] * px
+ matrix
[3] * py
;
965 if (besti
< 0 || bestd
> d
) {
974 * Now we know which symmetry is closest to the points' current
977 matrix
[0] = matrix
[1] = matrix
[2] = matrix
[3] = 0;
978 matrix
[besti
& 1] = (besti
& 2) ? +1 : -1;
979 matrix
[3-(besti
&1)] = (besti
& 4) ? +1 : -1;
982 ret
= snewn(retsize
, char);
987 for (i
= 0; i
< n
; i
++) {
988 float px
= (float)pts
[i
].x
/ pts
[i
].d
;
989 float py
= (float)pts
[i
].y
/ pts
[i
].d
;
990 float cx
= (float)currstate
->w
/ 2;
991 float cy
= (float)currstate
->h
/ 2;
998 ox
= matrix
[0] * px
+ matrix
[1] * py
;
999 oy
= matrix
[2] * px
+ matrix
[3] * py
;
1005 * Use a fixed denominator of 2, because we know the
1006 * original points were on an integer grid offset by 1/2.
1011 pts
[i
].x
= (long)(ox
+ 0.5F
);
1012 pts
[i
].y
= (long)(oy
+ 0.5F
);
1014 extra
= sprintf(buf
, ";P%d:%ld,%ld/%ld", i
,
1015 pts
[i
].x
, pts
[i
].y
, pts
[i
].d
);
1016 if (retlen
+ extra
>= retsize
) {
1017 retsize
= retlen
+ extra
+ 256;
1018 ret
= sresize(ret
, retsize
, char);
1020 strcpy(ret
+ retlen
, buf
);
1029 static int game_can_format_as_text_now(const game_params
*params
)
1034 static char *game_text_format(const game_state
*state
)
1040 int dragpoint
; /* point being dragged; -1 if none */
1041 point newpoint
; /* where it's been dragged to so far */
1042 int just_dragged
; /* reset in game_changed_state */
1043 int just_moved
; /* _set_ in game_changed_state */
1047 static game_ui
*new_ui(const game_state
*state
)
1049 game_ui
*ui
= snew(game_ui
);
1051 ui
->just_moved
= ui
->just_dragged
= FALSE
;
1055 static void free_ui(game_ui
*ui
)
1060 static char *encode_ui(const game_ui
*ui
)
1065 static void decode_ui(game_ui
*ui
, const char *encoding
)
1069 static void game_changed_state(game_ui
*ui
, const game_state
*oldstate
,
1070 const game_state
*newstate
)
1073 ui
->just_moved
= ui
->just_dragged
;
1074 ui
->just_dragged
= FALSE
;
1077 struct game_drawstate
{
1083 static char *interpret_move(const game_state
*state
, game_ui
*ui
,
1084 const game_drawstate
*ds
,
1085 int x
, int y
, int button
)
1087 int n
= state
->params
.n
;
1089 if (IS_MOUSE_DOWN(button
)) {
1094 * Begin drag. We drag the vertex _nearest_ to the pointer,
1095 * just in case one is nearly on top of another and we want
1096 * to drag the latter. However, we drag nothing at all if
1097 * the nearest vertex is outside DRAG_THRESHOLD.
1102 for (i
= 0; i
< n
; i
++) {
1103 long px
= state
->pts
[i
].x
* ds
->tilesize
/ state
->pts
[i
].d
;
1104 long py
= state
->pts
[i
].y
* ds
->tilesize
/ state
->pts
[i
].d
;
1107 long d
= dx
*dx
+ dy
*dy
;
1109 if (best
== -1 || bestd
> d
) {
1115 if (bestd
<= DRAG_THRESHOLD
* DRAG_THRESHOLD
) {
1116 ui
->dragpoint
= best
;
1119 ui
->newpoint
.d
= ds
->tilesize
;
1123 } else if (IS_MOUSE_DRAG(button
) && ui
->dragpoint
>= 0) {
1126 ui
->newpoint
.d
= ds
->tilesize
;
1128 } else if (IS_MOUSE_RELEASE(button
) && ui
->dragpoint
>= 0) {
1129 int p
= ui
->dragpoint
;
1132 ui
->dragpoint
= -1; /* terminate drag, no matter what */
1135 * First, see if we're within range. The user can cancel a
1136 * drag by dragging the point right off the window.
1138 if (ui
->newpoint
.x
< 0 ||
1139 ui
->newpoint
.x
>= (long)state
->w
*ui
->newpoint
.d
||
1140 ui
->newpoint
.y
< 0 ||
1141 ui
->newpoint
.y
>= (long)state
->h
*ui
->newpoint
.d
)
1145 * We aren't cancelling the drag. Construct a move string
1146 * indicating where this point is going to.
1148 sprintf(buf
, "P%d:%ld,%ld/%ld", p
,
1149 ui
->newpoint
.x
, ui
->newpoint
.y
, ui
->newpoint
.d
);
1150 ui
->just_dragged
= TRUE
;
1157 static game_state
*execute_move(const game_state
*state
, const char *move
)
1159 int n
= state
->params
.n
;
1162 game_state
*ret
= dup_game(state
);
1164 ret
->just_solved
= FALSE
;
1169 if (*move
== ';') move
++;
1170 ret
->cheated
= ret
->just_solved
= TRUE
;
1173 sscanf(move
+1, "%d:%ld,%ld/%ld%n", &p
, &x
, &y
, &d
, &k
) == 4 &&
1174 p
>= 0 && p
< n
&& d
> 0) {
1180 if (*move
== ';') move
++;
1187 mark_crossings(ret
);
1192 /* ----------------------------------------------------------------------
1196 static void game_compute_size(const game_params
*params
, int tilesize
,
1199 *x
= *y
= COORDLIMIT(params
->n
) * tilesize
;
1202 static void game_set_size(drawing
*dr
, game_drawstate
*ds
,
1203 const game_params
*params
, int tilesize
)
1205 ds
->tilesize
= tilesize
;
1208 static float *game_colours(frontend
*fe
, int *ncolours
)
1210 float *ret
= snewn(3 * NCOLOURS
, float);
1213 * COL_BACKGROUND is what we use as the normal background colour.
1214 * Unusually, though, it isn't colour #0: COL_SYSBACKGROUND, a bit
1215 * darker, takes that place. This means that if the user resizes
1216 * an Untangle window so as to change its aspect ratio, the
1217 * still-square playable area will be distinguished from the dead
1220 game_mkhighlight(fe
, ret
, COL_BACKGROUND
, -1, COL_SYSBACKGROUND
);
1222 ret
[COL_LINE
* 3 + 0] = 0.0F
;
1223 ret
[COL_LINE
* 3 + 1] = 0.0F
;
1224 ret
[COL_LINE
* 3 + 2] = 0.0F
;
1226 #ifdef SHOW_CROSSINGS
1227 ret
[COL_CROSSEDLINE
* 3 + 0] = 1.0F
;
1228 ret
[COL_CROSSEDLINE
* 3 + 1] = 0.0F
;
1229 ret
[COL_CROSSEDLINE
* 3 + 2] = 0.0F
;
1232 ret
[COL_OUTLINE
* 3 + 0] = 0.0F
;
1233 ret
[COL_OUTLINE
* 3 + 1] = 0.0F
;
1234 ret
[COL_OUTLINE
* 3 + 2] = 0.0F
;
1236 ret
[COL_POINT
* 3 + 0] = 0.0F
;
1237 ret
[COL_POINT
* 3 + 1] = 0.0F
;
1238 ret
[COL_POINT
* 3 + 2] = 1.0F
;
1240 ret
[COL_DRAGPOINT
* 3 + 0] = 1.0F
;
1241 ret
[COL_DRAGPOINT
* 3 + 1] = 1.0F
;
1242 ret
[COL_DRAGPOINT
* 3 + 2] = 1.0F
;
1244 ret
[COL_NEIGHBOUR
* 3 + 0] = 1.0F
;
1245 ret
[COL_NEIGHBOUR
* 3 + 1] = 0.0F
;
1246 ret
[COL_NEIGHBOUR
* 3 + 2] = 0.0F
;
1248 ret
[COL_FLASH1
* 3 + 0] = 0.5F
;
1249 ret
[COL_FLASH1
* 3 + 1] = 0.5F
;
1250 ret
[COL_FLASH1
* 3 + 2] = 0.5F
;
1252 ret
[COL_FLASH2
* 3 + 0] = 1.0F
;
1253 ret
[COL_FLASH2
* 3 + 1] = 1.0F
;
1254 ret
[COL_FLASH2
* 3 + 2] = 1.0F
;
1256 *ncolours
= NCOLOURS
;
1260 static game_drawstate
*game_new_drawstate(drawing
*dr
, const game_state
*state
)
1262 struct game_drawstate
*ds
= snew(struct game_drawstate
);
1266 ds
->x
= snewn(state
->params
.n
, long);
1267 ds
->y
= snewn(state
->params
.n
, long);
1268 for (i
= 0; i
< state
->params
.n
; i
++)
1269 ds
->x
[i
] = ds
->y
[i
] = -1;
1276 static void game_free_drawstate(drawing
*dr
, game_drawstate
*ds
)
1283 static point
mix(point a
, point b
, float distance
)
1288 ret
.x
= (long)(a
.x
* b
.d
+ distance
* (b
.x
* a
.d
- a
.x
* b
.d
));
1289 ret
.y
= (long)(a
.y
* b
.d
+ distance
* (b
.y
* a
.d
- a
.y
* b
.d
));
1294 static void game_redraw(drawing
*dr
, game_drawstate
*ds
,
1295 const game_state
*oldstate
, const game_state
*state
,
1296 int dir
, const game_ui
*ui
,
1297 float animtime
, float flashtime
)
1302 int bg
, points_moved
;
1305 * There's no terribly sensible way to do partial redraws of
1306 * this game, so I'm going to have to resort to redrawing the
1307 * whole thing every time.
1311 bg
= COL_BACKGROUND
;
1312 else if ((int)(flashtime
* 4 / FLASH_TIME
) % 2 == 0)
1318 * To prevent excessive spinning on redraw during a completion
1319 * flash, we first check to see if _either_ the flash
1320 * background colour has changed _or_ at least one point has
1321 * moved _or_ a drag has begun or ended, and abandon the redraw
1322 * if neither is the case.
1324 * Also in this loop we work out the coordinates of all the
1325 * points for this redraw.
1327 points_moved
= FALSE
;
1328 for (i
= 0; i
< state
->params
.n
; i
++) {
1329 point p
= state
->pts
[i
];
1332 if (ui
->dragpoint
== i
)
1336 p
= mix(oldstate
->pts
[i
], p
, animtime
/ ui
->anim_length
);
1338 x
= p
.x
* ds
->tilesize
/ p
.d
;
1339 y
= p
.y
* ds
->tilesize
/ p
.d
;
1341 if (ds
->x
[i
] != x
|| ds
->y
[i
] != y
)
1342 points_moved
= TRUE
;
1348 if (ds
->bg
== bg
&& ds
->dragpoint
== ui
->dragpoint
&& !points_moved
)
1349 return; /* nothing to do */
1351 ds
->dragpoint
= ui
->dragpoint
;
1354 game_compute_size(&state
->params
, ds
->tilesize
, &w
, &h
);
1355 draw_rect(dr
, 0, 0, w
, h
, bg
);
1361 for (i
= 0; (e
= index234(state
->graph
->edges
, i
)) != NULL
; i
++) {
1362 draw_line(dr
, ds
->x
[e
->a
], ds
->y
[e
->a
], ds
->x
[e
->b
], ds
->y
[e
->b
],
1363 #ifdef SHOW_CROSSINGS
1364 (oldstate
?oldstate
:state
)->crosses
[i
] ?
1373 * When dragging, we should not only vary the colours, but
1374 * leave the point being dragged until last.
1376 for (j
= 0; j
< 3; j
++) {
1377 int thisc
= (j
== 0 ? COL_POINT
:
1378 j
== 1 ? COL_NEIGHBOUR
: COL_DRAGPOINT
);
1379 for (i
= 0; i
< state
->params
.n
; i
++) {
1382 if (ui
->dragpoint
== i
) {
1384 } else if (ui
->dragpoint
>= 0 &&
1385 isedge(state
->graph
->edges
, ui
->dragpoint
, i
)) {
1392 #ifdef VERTEX_NUMBERS
1393 draw_circle(dr
, ds
->x
[i
], ds
->y
[i
], DRAG_THRESHOLD
, bg
, bg
);
1396 sprintf(buf
, "%d", i
);
1397 draw_text(dr
, ds
->x
[i
], ds
->y
[i
], FONT_VARIABLE
,
1399 ALIGN_VCENTRE
|ALIGN_HCENTRE
, c
, buf
);
1402 draw_circle(dr
, ds
->x
[i
], ds
->y
[i
], CIRCLE_RADIUS
,
1409 draw_update(dr
, 0, 0, w
, h
);
1412 static float game_anim_length(const game_state
*oldstate
,
1413 const game_state
*newstate
, int dir
, game_ui
*ui
)
1417 if ((dir
< 0 ? oldstate
: newstate
)->just_solved
)
1418 ui
->anim_length
= SOLVEANIM_TIME
;
1420 ui
->anim_length
= ANIM_TIME
;
1421 return ui
->anim_length
;
1424 static float game_flash_length(const game_state
*oldstate
,
1425 const game_state
*newstate
, int dir
, game_ui
*ui
)
1427 if (!oldstate
->completed
&& newstate
->completed
&&
1428 !oldstate
->cheated
&& !newstate
->cheated
)
1433 static int game_status(const game_state
*state
)
1435 return state
->completed
? +1 : 0;
1438 static int game_timing_state(const game_state
*state
, game_ui
*ui
)
1443 static void game_print_size(const game_params
*params
, float *x
, float *y
)
1447 static void game_print(drawing
*dr
, const game_state
*state
, int tilesize
)
1452 #define thegame untangle
1455 const struct game thegame
= {
1456 "Untangle", "games.untangle", "untangle",
1458 game_fetch_preset
, NULL
,
1463 TRUE
, game_configure
, custom_params
,
1471 FALSE
, game_can_format_as_text_now
, game_text_format
,
1479 PREFERRED_TILESIZE
, game_compute_size
, game_set_size
,
1482 game_free_drawstate
,
1487 FALSE
, FALSE
, game_print_size
, game_print
,
1488 FALSE
, /* wants_statusbar */
1489 FALSE
, game_timing_state
,
1490 SOLVE_ANIMATES
, /* flags */