A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 2067 lines 56 kB view raw
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 * - 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. 23 */ 24 25#include <stdio.h> 26#include <stdlib.h> 27#include <string.h> 28#include <assert.h> 29#include <ctype.h> 30#include <limits.h> 31#ifdef NO_TGMATH_H 32# include <math.h> 33#else 34# include <tgmath.h> 35#endif 36#if HAVE_STDINT_H 37# include <stdint.h> 38#endif 39 40#include "puzzles.h" 41#include "tree234.h" 42 43#ifndef CIRCLE_RADIUS 44# define CIRCLE_RADIUS 6 45#endif 46 47#ifndef DRAG_THRESHOLD 48# define DRAG_THRESHOLD (CIRCLE_RADIUS * 2) 49#endif 50 51#define PREFERRED_TILESIZE 64 52 53#define FLASH_TIME 0.30F 54#define ANIM_TIME 0.13F 55#define SOLVEANIM_TIME 0.50F 56 57enum { 58 COL_SYSBACKGROUND, 59 COL_BACKGROUND, 60 COL_LINE, 61 COL_CROSSEDLINE, 62 COL_OUTLINE, 63 COL_POINT, 64 COL_DRAGPOINT, 65 COL_CURSORPOINT, 66 COL_NEIGHBOUR, 67 COL_FLASH1, 68 COL_FLASH2, 69 NCOLOURS 70}; 71 72enum { 73 PREF_SNAP_TO_GRID, 74 PREF_SHOW_CROSSED_EDGES, 75 PREF_VERTEX_STYLE, 76 N_PREF_ITEMS 77}; 78 79typedef struct point { 80 /* 81 * Points are stored using rational coordinates, with the same 82 * denominator for both coordinates. 83 */ 84 long x, y, d; 85} point; 86 87typedef struct edge { 88 /* 89 * This structure is implicitly associated with a particular 90 * point set, so all it has to do is to store two point 91 * indices. It is required to store them in the order (lower, 92 * higher), i.e. a < b always. 93 */ 94 int a, b; 95} edge; 96 97struct game_params { 98 int n; /* number of points */ 99}; 100 101struct graph { 102 int refcount; /* for deallocation */ 103 tree234 *edges; /* stores `edge' structures */ 104}; 105 106struct game_state { 107 game_params params; 108 int w, h; /* extent of coordinate system only */ 109 point *pts; 110 struct graph *graph; 111#ifndef EDITOR 112 int *crosses; /* mark edges which are crossed */ 113 bool completed, cheated, just_solved; 114#endif 115}; 116 117static int edgecmpC(const void *av, const void *bv) 118{ 119 const edge *a = (const edge *)av; 120 const edge *b = (const edge *)bv; 121 122 if (a->a < b->a) 123 return -1; 124 else if (a->a > b->a) 125 return +1; 126 else if (a->b < b->b) 127 return -1; 128 else if (a->b > b->b) 129 return +1; 130 return 0; 131} 132 133static int edgecmp(void *av, void *bv) { return edgecmpC(av, bv); } 134 135static game_params *default_params(void) 136{ 137 game_params *ret = snew(game_params); 138 139 ret->n = 10; 140 141 return ret; 142} 143 144static bool game_fetch_preset(int i, char **name, game_params **params) 145{ 146 game_params *ret; 147 int n; 148 char buf[80]; 149 150 switch (i) { 151 case 0: n = 6; break; 152 case 1: n = 10; break; 153 case 2: n = 15; break; 154 case 3: n = 20; break; 155 case 4: n = 25; break; 156 default: return false; 157 } 158 159 sprintf(buf, "%d points", n); 160 *name = dupstr(buf); 161 162 *params = ret = snew(game_params); 163 ret->n = n; 164 165 return true; 166} 167 168static void free_params(game_params *params) 169{ 170 sfree(params); 171} 172 173static game_params *dup_params(const game_params *params) 174{ 175 game_params *ret = snew(game_params); 176 *ret = *params; /* structure copy */ 177 return ret; 178} 179 180static void decode_params(game_params *params, char const *string) 181{ 182 params->n = atoi(string); 183} 184 185static char *encode_params(const game_params *params, bool full) 186{ 187 char buf[80]; 188 189 sprintf(buf, "%d", params->n); 190 191 return dupstr(buf); 192} 193 194static config_item *game_configure(const game_params *params) 195{ 196 config_item *ret; 197 char buf[80]; 198 199 ret = snewn(3, config_item); 200 201 ret[0].name = "Number of points"; 202 ret[0].type = C_STRING; 203 sprintf(buf, "%d", params->n); 204 ret[0].u.string.sval = dupstr(buf); 205 206 ret[1].name = NULL; 207 ret[1].type = C_END; 208 209 return ret; 210} 211 212static game_params *custom_params(const config_item *cfg) 213{ 214 game_params *ret = snew(game_params); 215 216 ret->n = atoi(cfg[0].u.string.sval); 217 218 return ret; 219} 220 221static const char *validate_params(const game_params *params, bool full) 222{ 223#ifndef EDITOR 224 if (params->n < 4) 225 return "Number of points must be at least four"; 226#else 227 if (params->n < 1) 228 return "Number of points must be at least one"; 229#endif 230 if (params->n > INT_MAX / 3) 231 return "Number of points must not be unreasonably large"; 232 return NULL; 233} 234 235#ifndef EDITOR 236/* ---------------------------------------------------------------------- 237 * Small number of 64-bit integer arithmetic operations, to prevent 238 * integer overflow at the very core of cross(). 239 */ 240 241#if !HAVE_STDINT_H 242/* For prehistoric C implementations, do this the hard way */ 243 244typedef struct { 245 long hi; 246 unsigned long lo; 247} int64; 248 249#define greater64(i,j) ( (i).hi>(j).hi || ((i).hi==(j).hi && (i).lo>(j).lo)) 250#define sign64(i) ((i).hi < 0 ? -1 : (i).hi==0 && (i).lo==0 ? 0 : +1) 251 252static int64 mulu32to64(unsigned long x, unsigned long y) 253{ 254 unsigned long a, b, c, d, t; 255 int64 ret; 256 257 a = (x & 0xFFFF) * (y & 0xFFFF); 258 b = (x & 0xFFFF) * (y >> 16); 259 c = (x >> 16) * (y & 0xFFFF); 260 d = (x >> 16) * (y >> 16); 261 262 ret.lo = a; 263 ret.hi = d + (b >> 16) + (c >> 16); 264 t = (b & 0xFFFF) << 16; 265 ret.lo += t; 266 if (ret.lo < t) 267 ret.hi++; 268 t = (c & 0xFFFF) << 16; 269 ret.lo += t; 270 if (ret.lo < t) 271 ret.hi++; 272 273#ifdef DIAGNOSTIC_VIA_LONGLONG 274 assert(((unsigned long long)ret.hi << 32) + ret.lo == 275 (unsigned long long)x * y); 276#endif 277 278 return ret; 279} 280 281static int64 mul32to64(long x, long y) 282{ 283 int sign = +1; 284 int64 ret; 285#ifdef DIAGNOSTIC_VIA_LONGLONG 286 long long realret = (long long)x * y; 287#endif 288 289 if (x < 0) 290 x = -x, sign = -sign; 291 if (y < 0) 292 y = -y, sign = -sign; 293 294 ret = mulu32to64(x, y); 295 296 if (sign < 0) { 297 ret.hi = -ret.hi; 298 ret.lo = -ret.lo; 299 if (ret.lo) 300 ret.hi--; 301 } 302 303#ifdef DIAGNOSTIC_VIA_LONGLONG 304 assert(((unsigned long long)ret.hi << 32) + ret.lo == realret); 305#endif 306 307 return ret; 308} 309 310static int64 dotprod64(long a, long b, long p, long q) 311{ 312 int64 ab, pq; 313 314 ab = mul32to64(a, b); 315 pq = mul32to64(p, q); 316 ab.hi += pq.hi; 317 ab.lo += pq.lo; 318 if (ab.lo < pq.lo) 319 ab.hi++; 320 return ab; 321} 322 323#else /* HAVE_STDINT_H */ 324 325typedef int64_t int64; 326#define greater64(i,j) ((i) > (j)) 327#define sign64(i) ((i) < 0 ? -1 : (i)==0 ? 0 : +1) 328#define mulu32to64(x,y) ((int64_t)(unsigned long)(x) * (unsigned long)(y)) 329#define mul32to64(x,y) ((int64_t)(long)(x) * (long)(y)) 330 331static int64 dotprod64(long a, long b, long p, long q) 332{ 333 return (int64)a * b + (int64)p * q; 334} 335 336#endif /* HAVE_STDINT_H */ 337 338/* 339 * Determine whether the line segments between a1 and a2, and 340 * between b1 and b2, intersect. We count it as an intersection if 341 * any of the endpoints lies _on_ the other line. 342 */ 343static bool cross(point a1, point a2, point b1, point b2) 344{ 345 long b1x, b1y, b2x, b2y, px, py; 346 int64 d1, d2, d3; 347 348 /* 349 * The condition for crossing is that b1 and b2 are on opposite 350 * sides of the line a1-a2, and vice versa. We determine this 351 * by taking the dot product of b1-a1 with a vector 352 * perpendicular to a2-a1, and similarly with b2-a1, and seeing 353 * if they have different signs. 354 */ 355 356 /* 357 * Construct the vector b1-a1. We don't have to worry too much 358 * about the denominator, because we're only going to check the 359 * sign of this vector; we just need to get the numerator 360 * right. 361 */ 362 b1x = b1.x * a1.d - a1.x * b1.d; 363 b1y = b1.y * a1.d - a1.y * b1.d; 364 /* Now construct b2-a1, and a vector perpendicular to a2-a1, 365 * in the same way. */ 366 b2x = b2.x * a1.d - a1.x * b2.d; 367 b2y = b2.y * a1.d - a1.y * b2.d; 368 px = a1.y * a2.d - a2.y * a1.d; 369 py = a2.x * a1.d - a1.x * a2.d; 370 /* Take the dot products. Here we resort to 64-bit arithmetic. */ 371 d1 = dotprod64(b1x, px, b1y, py); 372 d2 = dotprod64(b2x, px, b2y, py); 373 /* If they have the same non-zero sign, the lines do not cross. */ 374 if ((sign64(d1) > 0 && sign64(d2) > 0) || 375 (sign64(d1) < 0 && sign64(d2) < 0)) 376 return false; 377 378 /* 379 * If the dot products are both exactly zero, then the two line 380 * segments are collinear. At this point the intersection 381 * condition becomes whether or not they overlap within their 382 * line. 383 */ 384 if (sign64(d1) == 0 && sign64(d2) == 0) { 385 /* Construct the vector a2-a1. */ 386 px = a2.x * a1.d - a1.x * a2.d; 387 py = a2.y * a1.d - a1.y * a2.d; 388 /* Determine the dot products of b1-a1 and b2-a1 with this. */ 389 d1 = dotprod64(b1x, px, b1y, py); 390 d2 = dotprod64(b2x, px, b2y, py); 391 /* If they're both strictly negative, the lines do not cross. */ 392 if (sign64(d1) < 0 && sign64(d2) < 0) 393 return false; 394 /* Otherwise, take the dot product of a2-a1 with itself. If 395 * the other two dot products both exceed this, the lines do 396 * not cross. */ 397 d3 = dotprod64(px, px, py, py); 398 if (greater64(d1, d3) && greater64(d2, d3)) 399 return false; 400 } 401 402 /* 403 * We've eliminated the only important special case, and we 404 * have determined that b1 and b2 are on opposite sides of the 405 * line a1-a2. Now do the same thing the other way round and 406 * we're done. 407 */ 408 b1x = a1.x * b1.d - b1.x * a1.d; 409 b1y = a1.y * b1.d - b1.y * a1.d; 410 b2x = a2.x * b1.d - b1.x * a2.d; 411 b2y = a2.y * b1.d - b1.y * a2.d; 412 px = b1.y * b2.d - b2.y * b1.d; 413 py = b2.x * b1.d - b1.x * b2.d; 414 d1 = dotprod64(b1x, px, b1y, py); 415 d2 = dotprod64(b2x, px, b2y, py); 416 if ((sign64(d1) > 0 && sign64(d2) > 0) || 417 (sign64(d1) < 0 && sign64(d2) < 0)) 418 return false; 419 420 /* 421 * The lines must cross. 422 */ 423 return true; 424} 425 426#endif /* EDITOR */ 427 428static unsigned long squarert(unsigned long n) { 429 unsigned long d, a, b, di; 430 431 d = n; 432 a = 0; 433 b = 1L << 30; /* largest available power of 4 */ 434 do { 435 a >>= 1; 436 di = 2*a + b; 437 if (di <= d) { 438 d -= di; 439 a += b; 440 } 441 b >>= 2; 442 } while (b); 443 444 return a; 445} 446 447/* 448 * Our solutions are arranged on a square grid big enough that n 449 * points occupy about 1/POINTDENSITY of the grid. 450 */ 451#define POINTDENSITY 3 452#define MAXDEGREE 4 453#define COORDLIMIT(n) squarert((n) * POINTDENSITY) 454 455static void addedge(tree234 *edges, int a, int b) 456{ 457 edge *e = snew(edge); 458 459 assert(a != b); 460 461 e->a = min(a, b); 462 e->b = max(a, b); 463 464 if (add234(edges, e) != e) 465 /* Duplicate edge. */ 466 sfree(e); 467} 468 469#ifdef EDITOR 470static void deledge(tree234 *edges, int a, int b) 471{ 472 edge e, *found; 473 474 assert(a != b); 475 476 e.a = min(a, b); 477 e.b = max(a, b); 478 479 found = del234(edges, &e); 480 sfree(found); 481} 482#endif 483 484static bool isedge(tree234 *edges, int a, int b) 485{ 486 edge e; 487 488 assert(a != b); 489 490 e.a = min(a, b); 491 e.b = max(a, b); 492 493 return find234(edges, &e, NULL) != NULL; 494} 495 496typedef struct vertex { 497 int param; 498 int vindex; 499} vertex; 500 501#ifndef EDITOR 502static int vertcmpC(const void *av, const void *bv) 503{ 504 const vertex *a = (const vertex *)av; 505 const vertex *b = (const vertex *)bv; 506 507 if (a->param < b->param) 508 return -1; 509 else if (a->param > b->param) 510 return +1; 511 else if (a->vindex < b->vindex) 512 return -1; 513 else if (a->vindex > b->vindex) 514 return +1; 515 return 0; 516} 517static int vertcmp(void *av, void *bv) { return vertcmpC(av, bv); } 518#endif 519 520/* 521 * Construct point coordinates for n points arranged in a circle, 522 * within the bounding box (0,0) to (w,w). 523 */ 524static void make_circle(point *pts, int n, int w) 525{ 526 long d, r, c, i; 527 528 /* 529 * First, decide on a denominator. Although in principle it 530 * would be nice to set this really high so as to finely 531 * distinguish all the points on the circle, I'm going to set 532 * it at a fixed size to prevent integer overflow problems. 533 */ 534 d = PREFERRED_TILESIZE; 535 536 /* 537 * Leave a little space outside the circle. 538 */ 539 c = d * w / 2; 540 r = d * w * 3 / 7; 541 542 /* 543 * Place the points. 544 */ 545 for (i = 0; i < n; i++) { 546 double angle = i * 2 * PI / n; 547 double x = r * sin(angle), y = - r * cos(angle); 548 pts[i].x = (long)(c + x + 0.5); 549 pts[i].y = (long)(c + y + 0.5); 550 pts[i].d = d; 551 } 552} 553 554/* 555 * Encode a graph in Untangle's game id: a comma-separated list of 556 * dash-separated vertex number pairs, numbered from zero. 557 * 558 * If params != NULL, then the number of vertices is prefixed to the 559 * front to make a full Untangle game id. Otherwise, we return just 560 * the game description part. 561 * 562 * If mapping != NULL, then it is expected to be a mapping from the 563 * graph's original vertex numbers to output vertex numbers. 564 */ 565static char *encode_graph(const game_params *params, tree234 *edges, 566 const long *mapping) 567{ 568 const char *sep; 569 char buf[80]; 570 int i, k, m, retlen; 571 edge *e, *ea; 572 char *ret; 573 574 retlen = 0; 575 if (params) 576 retlen += sprintf(buf, "%d:", params->n); 577 578 m = count234(edges); 579 ea = snewn(m, edge); 580 for (i = 0; (e = index234(edges, i)) != NULL; i++) { 581 int ma, mb; 582 assert(i < m); 583 if (mapping) { 584 ma = mapping[e->a]; 585 mb = mapping[e->b]; 586 } else { 587 ma = e->a; 588 mb = e->b; 589 } 590 ea[i].a = min(ma, mb); 591 ea[i].b = max(ma, mb); 592 if (i > 0) 593 retlen++; /* comma separator after the previous edge */ 594 retlen += sprintf(buf, "%d-%d", ea[i].a, ea[i].b); 595 } 596 assert(i == m); 597 /* Re-sort to prevent side channels, if mapping was used */ 598 qsort(ea, m, sizeof(*ea), edgecmpC); 599 600 ret = snewn(retlen + 1, char); 601 sep = ""; 602 k = 0; 603 if (params) 604 k += sprintf(ret + k, "%d:", params->n); 605 606 for (i = 0; i < m; i++) { 607 k += sprintf(ret + k, "%s%d-%d", sep, ea[i].a, ea[i].b); 608 sep = ","; 609 } 610 assert(k == retlen); 611 612 sfree(ea); 613 614 return ret; 615} 616 617static char *new_game_desc(const game_params *params, random_state *rs, 618 char **aux, bool interactive) 619{ 620#ifndef EDITOR 621 int n = params->n, i; 622 long w, h, j, k, m; 623 point *pts, *pts2; 624 long *tmp; 625 tree234 *edges, *vertices; 626 edge *e, *e2; 627 vertex *v, *vs, *vlist; 628 char *ret; 629 630 w = h = COORDLIMIT(n); 631 632 /* 633 * Choose n points from this grid. 634 */ 635 pts = snewn(n, point); 636 tmp = snewn(w*h, long); 637 for (i = 0; i < w*h; i++) 638 tmp[i] = i; 639 shuffle(tmp, w*h, sizeof(*tmp), rs); 640 for (i = 0; i < n; i++) { 641 pts[i].x = tmp[i] % w; 642 pts[i].y = tmp[i] / w; 643 pts[i].d = 1; 644 } 645 sfree(tmp); 646 647 /* 648 * Now start adding edges between the points. 649 * 650 * At all times, we attempt to add an edge to the lowest-degree 651 * vertex we currently have, and we try the other vertices as 652 * candidate second endpoints in order of distance from this 653 * one. We stop as soon as we find an edge which 654 * 655 * (a) does not increase any vertex's degree beyond MAXDEGREE 656 * (b) does not cross any existing edges 657 * (c) does not intersect any actual point. 658 */ 659 vs = snewn(n, vertex); 660 vertices = newtree234(vertcmp); 661 for (i = 0; i < n; i++) { 662 v = vs + i; 663 v->param = 0; /* in this tree, param is the degree */ 664 v->vindex = i; 665 add234(vertices, v); 666 } 667 edges = newtree234(edgecmp); 668 vlist = snewn(n, vertex); 669 while (1) { 670 bool added = false; 671 672 for (i = 0; i < n; i++) { 673 v = index234(vertices, i); 674 j = v->vindex; 675 676 if (v->param >= MAXDEGREE) 677 break; /* nothing left to add! */ 678 679 /* 680 * Sort the other vertices into order of their distance 681 * from this one. Don't bother looking below i, because 682 * we've already tried those edges the other way round. 683 * Also here we rule out target vertices with too high 684 * a degree, and (of course) ones to which we already 685 * have an edge. 686 */ 687 m = 0; 688 for (k = i+1; k < n; k++) { 689 vertex *kv = index234(vertices, k); 690 int ki = kv->vindex; 691 int dx, dy; 692 693 if (kv->param >= MAXDEGREE || isedge(edges, ki, j)) 694 continue; 695 696 vlist[m].vindex = ki; 697 dx = pts[ki].x - pts[j].x; 698 dy = pts[ki].y - pts[j].y; 699 vlist[m].param = dx*dx + dy*dy; 700 m++; 701 } 702 703 qsort(vlist, m, sizeof(*vlist), vertcmpC); 704 705 for (k = 0; k < m; k++) { 706 int p; 707 int ki = vlist[k].vindex; 708 709 /* 710 * Check to see whether this edge intersects any 711 * existing edge or point. 712 */ 713 for (p = 0; p < n; p++) 714 if (p != ki && p != j && cross(pts[ki], pts[j], 715 pts[p], pts[p])) 716 break; 717 if (p < n) 718 continue; 719 for (p = 0; (e = index234(edges, p)) != NULL; p++) 720 if (e->a != ki && e->a != j && 721 e->b != ki && e->b != j && 722 cross(pts[ki], pts[j], pts[e->a], pts[e->b])) 723 break; 724 if (e) 725 continue; 726 727 /* 728 * We're done! Add this edge, modify the degrees of 729 * the two vertices involved, and break. 730 */ 731 addedge(edges, j, ki); 732 added = true; 733 del234(vertices, vs+j); 734 vs[j].param++; 735 add234(vertices, vs+j); 736 del234(vertices, vs+ki); 737 vs[ki].param++; 738 add234(vertices, vs+ki); 739 break; 740 } 741 742 if (k < m) 743 break; 744 } 745 746 if (!added) 747 break; /* we're done. */ 748 } 749 750 /* 751 * That's our graph. Now shuffle the points, making sure that 752 * they come out with at least one crossed line when arranged 753 * in a circle (so that the puzzle isn't immediately solved!). 754 */ 755 tmp = snewn(n, long); 756 for (i = 0; i < n; i++) 757 tmp[i] = i; 758 pts2 = snewn(n, point); 759 make_circle(pts2, n, w); 760 while (1) { 761 shuffle(tmp, n, sizeof(*tmp), rs); 762 for (i = 0; (e = index234(edges, i)) != NULL; i++) { 763 for (j = i+1; (e2 = index234(edges, j)) != NULL; j++) { 764 if (e2->a == e->a || e2->a == e->b || 765 e2->b == e->a || e2->b == e->b) 766 continue; 767 if (cross(pts2[tmp[e2->a]], pts2[tmp[e2->b]], 768 pts2[tmp[e->a]], pts2[tmp[e->b]])) 769 break; 770 } 771 if (e2) 772 break; 773 } 774 if (e) 775 break; /* we've found a crossing */ 776 } 777 778 /* 779 * We're done. Encode the output graph as a string. 780 */ 781 ret = encode_graph(NULL, edges, tmp); 782 783 /* 784 * Encode the solution we started with as an aux_info string. 785 */ 786 { 787 char buf[80]; 788 char *auxstr; 789 int auxlen; 790 791 auxlen = 2; /* leading 'S' and trailing '\0' */ 792 for (i = 0; i < n; i++) { 793 j = tmp[i]; 794 pts2[j] = pts[i]; 795 if (pts2[j].d & 1) { 796 pts2[j].x *= 2; 797 pts2[j].y *= 2; 798 pts2[j].d *= 2; 799 } 800 pts2[j].x += pts2[j].d / 2; 801 pts2[j].y += pts2[j].d / 2; 802 auxlen += sprintf(buf, ";P%d:%ld,%ld/%ld", i, 803 pts2[j].x, pts2[j].y, pts2[j].d); 804 } 805 k = 0; 806 auxstr = snewn(auxlen, char); 807 auxstr[k++] = 'S'; 808 for (i = 0; i < n; i++) 809 k += sprintf(auxstr+k, ";P%d:%ld,%ld/%ld", i, 810 pts2[i].x, pts2[i].y, pts2[i].d); 811 assert(k < auxlen); 812 *aux = auxstr; 813 } 814 sfree(pts2); 815 816 sfree(tmp); 817 sfree(vlist); 818 freetree234(vertices); 819 sfree(vs); 820 while ((e = delpos234(edges, 0)) != NULL) 821 sfree(e); 822 freetree234(edges); 823 sfree(pts); 824 825 return ret; 826#else 827 return dupstr(""); 828#endif 829} 830 831static const char *validate_desc(const game_params *params, const char *desc) 832{ 833 int a, b; 834 835 while (*desc) { 836 a = atoi(desc); 837 if (a < 0 || a >= params->n) 838 return "Number out of range in game description"; 839 while (*desc && isdigit((unsigned char)*desc)) desc++; 840 if (*desc != '-') 841 return "Expected '-' after number in game description"; 842 desc++; /* eat dash */ 843 b = atoi(desc); 844 if (b < 0 || b >= params->n) 845 return "Number out of range in game description"; 846 while (*desc && isdigit((unsigned char)*desc)) desc++; 847 if (*desc) { 848 if (*desc != ',') 849 return "Expected ',' after number in game description"; 850 desc++; /* eat comma */ 851 } 852 if (a == b) 853 return "Node linked to itself in game description"; 854 } 855 856 return NULL; 857} 858 859#ifndef EDITOR 860static void mark_crossings(game_state *state) 861{ 862 bool ok = true; 863 int i, j; 864 edge *e, *e2; 865 866 for (i = 0; (e = index234(state->graph->edges, i)) != NULL; i++) 867 state->crosses[i] = false; 868 869 /* 870 * Check correctness: for every pair of edges, see whether they 871 * cross. 872 */ 873 for (i = 0; (e = index234(state->graph->edges, i)) != NULL; i++) { 874 for (j = i+1; (e2 = index234(state->graph->edges, j)) != NULL; j++) { 875 if (e2->a == e->a || e2->a == e->b || 876 e2->b == e->a || e2->b == e->b) 877 continue; 878 if (cross(state->pts[e2->a], state->pts[e2->b], 879 state->pts[e->a], state->pts[e->b])) { 880 ok = false; 881 state->crosses[i] = state->crosses[j] = true; 882 } 883 } 884 } 885 886 /* 887 * e == NULL if we've gone through all the edge pairs 888 * without finding a crossing. 889 */ 890 if (ok) 891 state->completed = true; 892} 893#endif 894 895static game_state *new_game(midend *me, const game_params *params, 896 const char *desc) 897{ 898 int n = params->n; 899 game_state *state = snew(game_state); 900 int a, b; 901 902 state->params = *params; 903 state->w = state->h = COORDLIMIT(n); 904 state->pts = snewn(n, point); 905 make_circle(state->pts, n, state->w); 906 state->graph = snew(struct graph); 907 state->graph->refcount = 1; 908 state->graph->edges = newtree234(edgecmp); 909#ifndef EDITOR 910 state->completed = state->cheated = state->just_solved = false; 911#endif 912 913 while (*desc) { 914 a = atoi(desc); 915 assert(a >= 0 && a < params->n); 916 while (*desc && isdigit((unsigned char)*desc)) desc++; 917 assert(*desc == '-'); 918 desc++; /* eat dash */ 919 b = atoi(desc); 920 assert(b >= 0 && b < params->n); 921 while (*desc && isdigit((unsigned char)*desc)) desc++; 922 if (*desc) { 923 assert(*desc == ','); 924 desc++; /* eat comma */ 925 } 926 addedge(state->graph->edges, a, b); 927 } 928 929#ifndef EDITOR 930 state->crosses = snewn(count234(state->graph->edges), int); 931 mark_crossings(state); /* sets up `crosses' and `completed' */ 932#endif 933 934 return state; 935} 936 937static game_state *dup_game(const game_state *state) 938{ 939 int n = state->params.n; 940 game_state *ret = snew(game_state); 941 942 ret->params = state->params; 943 ret->w = state->w; 944 ret->h = state->h; 945 ret->pts = snewn(n, point); 946 memcpy(ret->pts, state->pts, n * sizeof(point)); 947#ifndef EDITOR 948 ret->graph = state->graph; 949 ret->graph->refcount++; 950 ret->completed = state->completed; 951 ret->cheated = state->cheated; 952 ret->just_solved = state->just_solved; 953 ret->crosses = snewn(count234(ret->graph->edges), int); 954 memcpy(ret->crosses, state->crosses, 955 count234(ret->graph->edges) * sizeof(int)); 956#else 957 /* For the graph editor, we must clone the whole graph */ 958 ret->graph = snew(struct graph); 959 ret->graph->refcount = 1; 960 ret->graph->edges = newtree234(edgecmp); 961 { 962 int i; 963 struct edge *edge; 964 for (i = 0; (edge = index234(state->graph->edges, i)) != NULL; i++) { 965 addedge(ret->graph->edges, edge->a, edge->b); 966 } 967 } 968#endif 969 970 return ret; 971} 972 973static void free_game(game_state *state) 974{ 975 if (--state->graph->refcount <= 0) { 976 edge *e; 977 while ((e = delpos234(state->graph->edges, 0)) != NULL) 978 sfree(e); 979 freetree234(state->graph->edges); 980 sfree(state->graph); 981 } 982#ifndef EDITOR 983 sfree(state->crosses); 984#endif 985 sfree(state->pts); 986 sfree(state); 987} 988 989#ifndef EDITOR 990static char *solve_game(const game_state *state, const game_state *currstate, 991 const char *aux, const char **error) 992{ 993 int n = state->params.n; 994 int matrix[4]; 995 point *pts; 996 int i, j, besti; 997 float bestd; 998 char buf[80], *ret; 999 int retlen, retsize; 1000 1001 if (!aux) { 1002 *error = "Solution not known for this puzzle"; 1003 return NULL; 1004 } 1005 1006 /* 1007 * Decode the aux_info to get the original point positions. 1008 */ 1009 pts = snewn(n, point); 1010 aux++; /* eat 'S' */ 1011 for (i = 0; i < n; i++) { 1012 int p, k; 1013 long x, y, d; 1014 int ret = sscanf(aux, ";P%d:%ld,%ld/%ld%n", &p, &x, &y, &d, &k); 1015 if (ret != 4 || p != i) { 1016 *error = "Internal error: aux_info badly formatted"; 1017 sfree(pts); 1018 return NULL; 1019 } 1020 pts[i].x = x; 1021 pts[i].y = y; 1022 pts[i].d = d; 1023 aux += k; 1024 } 1025 1026 /* 1027 * Now go through eight possible symmetries of the point set. 1028 * For each one, work out the sum of the Euclidean distances 1029 * between the points' current positions and their new ones. 1030 * 1031 * We're squaring distances here, which means we're at risk of 1032 * integer overflow. Fortunately, there's no real need to be 1033 * massively careful about rounding errors, since this is a 1034 * non-essential bit of the code; so I'll just work in floats 1035 * internally. 1036 */ 1037 besti = -1; 1038 bestd = 0.0F; 1039 1040 for (i = 0; i < 8; i++) { 1041 float d; 1042 1043 matrix[0] = matrix[1] = matrix[2] = matrix[3] = 0; 1044 matrix[i & 1] = (i & 2) ? +1 : -1; 1045 matrix[3-(i&1)] = (i & 4) ? +1 : -1; 1046 1047 d = 0.0F; 1048 for (j = 0; j < n; j++) { 1049 float px = (float)pts[j].x / pts[j].d; 1050 float py = (float)pts[j].y / pts[j].d; 1051 float sx = (float)currstate->pts[j].x / currstate->pts[j].d; 1052 float sy = (float)currstate->pts[j].y / currstate->pts[j].d; 1053 float cx = (float)currstate->w / 2; 1054 float cy = (float)currstate->h / 2; 1055 float ox, oy, dx, dy; 1056 1057 px -= cx; 1058 py -= cy; 1059 1060 ox = matrix[0] * px + matrix[1] * py; 1061 oy = matrix[2] * px + matrix[3] * py; 1062 1063 ox += cx; 1064 oy += cy; 1065 1066 dx = ox - sx; 1067 dy = oy - sy; 1068 1069 d += dx*dx + dy*dy; 1070 } 1071 1072 if (besti < 0 || bestd > d) { 1073 besti = i; 1074 bestd = d; 1075 } 1076 } 1077 1078 assert(besti >= 0); 1079 1080 /* 1081 * Now we know which symmetry is closest to the points' current 1082 * positions. Use it. 1083 */ 1084 matrix[0] = matrix[1] = matrix[2] = matrix[3] = 0; 1085 matrix[besti & 1] = (besti & 2) ? +1 : -1; 1086 matrix[3-(besti&1)] = (besti & 4) ? +1 : -1; 1087 1088 retsize = 256; 1089 ret = snewn(retsize, char); 1090 retlen = 0; 1091 ret[retlen++] = 'S'; 1092 ret[retlen] = '\0'; 1093 1094 for (i = 0; i < n; i++) { 1095 float px = (float)pts[i].x / pts[i].d; 1096 float py = (float)pts[i].y / pts[i].d; 1097 float cx = (float)currstate->w / 2; 1098 float cy = (float)currstate->h / 2; 1099 float ox, oy; 1100 int extra; 1101 1102 px -= cx; 1103 py -= cy; 1104 1105 ox = matrix[0] * px + matrix[1] * py; 1106 oy = matrix[2] * px + matrix[3] * py; 1107 1108 ox += cx; 1109 oy += cy; 1110 1111 /* 1112 * Use a fixed denominator of 2, because we know the 1113 * original points were on an integer grid offset by 1/2. 1114 */ 1115 pts[i].d = 2; 1116 ox *= pts[i].d; 1117 oy *= pts[i].d; 1118 pts[i].x = (long)(ox + 0.5F); 1119 pts[i].y = (long)(oy + 0.5F); 1120 1121 extra = sprintf(buf, ";P%d:%ld,%ld/%ld", i, 1122 pts[i].x, pts[i].y, pts[i].d); 1123 if (retlen + extra >= retsize) { 1124 retsize = retlen + extra + 256; 1125 ret = sresize(ret, retsize, char); 1126 } 1127 strcpy(ret + retlen, buf); 1128 retlen += extra; 1129 } 1130 1131 sfree(pts); 1132 1133 return ret; 1134} 1135#endif /* EDITOR */ 1136 1137#ifdef EDITOR 1138static bool game_can_format_as_text_now(const game_params *params) 1139{ 1140 return true; 1141} 1142 1143static char *game_text_format(const game_state *state) 1144{ 1145 return encode_graph(&state->params, state->graph->edges, NULL); 1146} 1147#endif /* EDITOR */ 1148 1149typedef enum DragType { DRAG_MOVE_POINT, DRAG_TOGGLE_EDGE } DragType; 1150 1151struct game_ui { 1152 /* Invariant: at most one of {dragpoint, cursorpoint} may be valid 1153 * at any time. */ 1154 int dragpoint; /* point being dragged; -1 if none */ 1155 int cursorpoint; /* point being highlighted, but 1156 * not dragged by the cursor, 1157 * again -1 if none */ 1158 1159 point newpoint; /* where it's been dragged to so far */ 1160 bool just_dragged; /* reset in game_changed_state */ 1161 bool just_moved; /* _set_ in game_changed_state */ 1162 float anim_length; 1163 1164 DragType dragtype; 1165 1166 /* 1167 * User preference option to snap dragged points to a coarse-ish 1168 * grid. Requested by a user who otherwise found themself spending 1169 * too much time struggling to get lines nicely horizontal or 1170 * vertical. 1171 */ 1172 bool snap_to_grid; 1173 1174 /* 1175 * User preference option to highlight graph edges involved in a 1176 * crossing. 1177 */ 1178 bool show_crossed_edges; 1179 1180 /* 1181 * User preference option to show vertices as numbers instead of 1182 * circular blobs, so you can easily tell them apart. 1183 */ 1184 bool vertex_numbers; 1185}; 1186 1187static game_ui *new_ui(const game_state *state) 1188{ 1189 game_ui *ui = snew(game_ui); 1190 ui->dragpoint = -1; 1191 ui->cursorpoint = -1; 1192 ui->just_moved = ui->just_dragged = false; 1193 ui->snap_to_grid = false; 1194 ui->show_crossed_edges = false; 1195 ui->vertex_numbers = false; 1196 return ui; 1197} 1198 1199static config_item *get_prefs(game_ui *ui) 1200{ 1201 config_item *cfg; 1202 1203 cfg = snewn(N_PREF_ITEMS+1, config_item); 1204 1205 cfg[PREF_SNAP_TO_GRID].name = "Snap points to a grid"; 1206 cfg[PREF_SNAP_TO_GRID].kw = "snap-to-grid"; 1207 cfg[PREF_SNAP_TO_GRID].type = C_BOOLEAN; 1208 cfg[PREF_SNAP_TO_GRID].u.boolean.bval = ui->snap_to_grid; 1209 1210 cfg[PREF_SHOW_CROSSED_EDGES].name = "Show edges that cross another edge"; 1211 cfg[PREF_SHOW_CROSSED_EDGES].kw = "show-crossed-edges"; 1212 cfg[PREF_SHOW_CROSSED_EDGES].type = C_BOOLEAN; 1213 cfg[PREF_SHOW_CROSSED_EDGES].u.boolean.bval = ui->show_crossed_edges; 1214 1215 cfg[PREF_VERTEX_STYLE].name = "Display style for vertices"; 1216 cfg[PREF_VERTEX_STYLE].kw = "vertex-style"; 1217 cfg[PREF_VERTEX_STYLE].type = C_CHOICES; 1218 cfg[PREF_VERTEX_STYLE].u.choices.choicenames = ":Circles:Numbers"; 1219 cfg[PREF_VERTEX_STYLE].u.choices.choicekws = ":circle:number"; 1220 cfg[PREF_VERTEX_STYLE].u.choices.selected = ui->vertex_numbers; 1221 1222 cfg[N_PREF_ITEMS].name = NULL; 1223 cfg[N_PREF_ITEMS].type = C_END; 1224 1225 return cfg; 1226} 1227 1228static void set_prefs(game_ui *ui, const config_item *cfg) 1229{ 1230 ui->snap_to_grid = cfg[PREF_SNAP_TO_GRID].u.boolean.bval; 1231 ui->show_crossed_edges = cfg[PREF_SHOW_CROSSED_EDGES].u.boolean.bval; 1232 ui->vertex_numbers = cfg[PREF_VERTEX_STYLE].u.choices.selected; 1233} 1234 1235static void free_ui(game_ui *ui) 1236{ 1237 sfree(ui); 1238} 1239 1240static void game_changed_state(game_ui *ui, const game_state *oldstate, 1241 const game_state *newstate) 1242{ 1243 ui->dragpoint = -1; 1244 ui->just_moved = ui->just_dragged; 1245 ui->just_dragged = false; 1246} 1247 1248struct game_drawstate { 1249 long tilesize; 1250 int bg, dragpoint, cursorpoint; 1251 long *x, *y; 1252}; 1253 1254static void place_dragged_point(const game_state *state, game_ui *ui, 1255 const game_drawstate *ds, int x, int y) 1256{ 1257 if (ui->snap_to_grid) { 1258 /* 1259 * We snap points to a grid that has n-1 vertices on each 1260 * side. This should be large enough to permit a straight- 1261 * line drawing of any n-vertex planar graph, and moreover, 1262 * any specific planar embedding of that graph. 1263 * 1264 * Source: David Eppstein's book 'Forbidden Configurations in 1265 * Discrete Geometry' mentions (section 16.3, page 182) that 1266 * the point configuration he describes as GRID(n-1,n-1) - 1267 * that is, the vertices of a square grid with n-1 vertices on 1268 * each side - is universal for n-vertex planar graphs. In 1269 * other words (from definitions earlier in the chapter), if a 1270 * graph G admits any drawing in the plane at all, then it can 1271 * be drawn with straight lines, and with all vertices being 1272 * vertices of that grid. 1273 * 1274 * That fact by itself only says that _some_ planar embedding 1275 * of G can be drawn in this grid. We'd prefer that _all_ 1276 * embeddings of G can be so drawn, because 'snap to grid' is 1277 * supposed to be a UI affordance, not an extra puzzle 1278 * challenge, so we don't want to constrain the player's 1279 * choice of planar embedding. 1280 * 1281 * But it doesn't make a difference. Proof: given a specific 1282 * planar embedding of G, triangulate it, by adding extra 1283 * edges to every face of degree > 3. When this process 1284 * terminates with every face a triangle, we have a new graph 1285 * G' such that no edge can be added without it ceasing to be 1286 * planar. Standard theorems say that a maximal planar graph 1287 * is 3-connected, and that a 3-connected planar graph has a 1288 * _unique_ embedding. So any drawing of G' in the plane can 1289 * be converted into a drawing of G in the desired embedding, 1290 * by simply removing all the extra edges that we added to 1291 * turn G into G'. And G' is still an n-vertex planar graph, 1292 * hence it can be drawn in GRID(n-1,n-1). [] 1293 */ 1294 int d = state->params.n - 1; 1295 1296 /* Calculate position in terms of the snapping grid. */ 1297 x = d * x / (state->w * ds->tilesize); 1298 y = d * y / (state->h * ds->tilesize); 1299 /* Convert to standard co-ordinates, applying a half-square offset. */ 1300 ui->newpoint.x = (x * 2 + 1) * state->w; 1301 ui->newpoint.y = (y * 2 + 1) * state->h; 1302 ui->newpoint.d = d * 2; 1303 } else { 1304 ui->newpoint.x = x; 1305 ui->newpoint.y = y; 1306 ui->newpoint.d = ds->tilesize; 1307 } 1308} 1309 1310static float normsq(point pt) { 1311 return (pt.x * pt.x + pt.y * pt.y) / (pt.d * pt.d); 1312} 1313 1314/* 1315 * Find a vertex within DRAG_THRESHOLD of the pointer, or return -1 if 1316 * no such point exists. In case of more than one, we return the one 1317 * _nearest_ to the pointer, so that if two points are very close it's 1318 * still possible to drag a specific one of them. 1319 */ 1320static int point_under_mouse(const game_state *state, 1321 const game_drawstate *ds, int x, int y) 1322{ 1323 int n = state->params.n; 1324 int i, best; 1325 long bestd; 1326 1327 best = -1; 1328 bestd = 0; 1329 1330 for (i = 0; i < n; i++) { 1331 long px = state->pts[i].x * ds->tilesize / state->pts[i].d; 1332 long py = state->pts[i].y * ds->tilesize / state->pts[i].d; 1333 long dx = px - x; 1334 long dy = py - y; 1335 long d = dx*dx + dy*dy; 1336 1337 if (best == -1 || bestd > d) { 1338 best = i; 1339 bestd = d; 1340 } 1341 } 1342 1343 if (bestd <= DRAG_THRESHOLD * DRAG_THRESHOLD) 1344 return best; 1345 1346 return -1; 1347} 1348 1349static char *interpret_move(const game_state *state, game_ui *ui, 1350 const game_drawstate *ds, 1351 int x, int y, int button) 1352{ 1353 int n = state->params.n; 1354 1355 if (IS_MOUSE_DOWN(button)) { 1356 int p = point_under_mouse(state, ds, x, y); 1357 if (p >= 0) { 1358 ui->dragtype = DRAG_MOVE_POINT; 1359#ifdef EDITOR 1360 if (button == RIGHT_BUTTON) 1361 ui->dragtype = DRAG_TOGGLE_EDGE; 1362#endif 1363 1364 ui->dragpoint = p; 1365 ui->cursorpoint = -1; /* eliminate the cursor point, if any */ 1366 if (ui->dragtype == DRAG_MOVE_POINT) 1367 place_dragged_point(state, ui, ds, x, y); 1368 return MOVE_UI_UPDATE; 1369 } 1370 return MOVE_NO_EFFECT; 1371 } else if (IS_MOUSE_DRAG(button) && ui->dragpoint >= 0 && 1372 ui->dragtype == DRAG_MOVE_POINT) { 1373 place_dragged_point(state, ui, ds, x, y); 1374 return MOVE_UI_UPDATE; 1375 } else if (IS_MOUSE_RELEASE(button) && ui->dragpoint >= 0 && 1376 ui->dragtype == DRAG_MOVE_POINT) { 1377 int p = ui->dragpoint; 1378 char buf[80]; 1379 1380 ui->dragpoint = -1; /* terminate drag, no matter what */ 1381 ui->cursorpoint = -1; /* also eliminate the cursor point */ 1382 1383 /* 1384 * First, see if we're within range. The user can cancel a 1385 * drag by dragging the point right off the window. 1386 */ 1387 if (ui->newpoint.x < 0 || 1388 ui->newpoint.x >= (long)state->w*ui->newpoint.d || 1389 ui->newpoint.y < 0 || 1390 ui->newpoint.y >= (long)state->h*ui->newpoint.d) 1391 return MOVE_UI_UPDATE; 1392 1393 /* 1394 * We aren't cancelling the drag. Construct a move string 1395 * indicating where this point is going to. 1396 */ 1397 sprintf(buf, "P%d:%ld,%ld/%ld", p, 1398 ui->newpoint.x, ui->newpoint.y, ui->newpoint.d); 1399 ui->just_dragged = true; 1400 return dupstr(buf); 1401#ifdef EDITOR 1402 } else if (IS_MOUSE_DRAG(button) && ui->dragpoint >= 0 && 1403 ui->dragtype == DRAG_TOGGLE_EDGE) { 1404 ui->cursorpoint = point_under_mouse(state, ds, x, y); 1405 return MOVE_UI_UPDATE; 1406 } else if (IS_MOUSE_RELEASE(button) && ui->dragpoint >= 0 && 1407 ui->dragtype == DRAG_TOGGLE_EDGE) { 1408 int p = ui->dragpoint; 1409 int q = point_under_mouse(state, ds, x, y); 1410 char buf[80]; 1411 1412 ui->dragpoint = -1; /* terminate drag, no matter what */ 1413 ui->cursorpoint = -1; /* also eliminate the cursor point */ 1414 1415 if (q < 0 || p == q) 1416 return MOVE_UI_UPDATE; 1417 1418 sprintf(buf, "E%c%d,%d", 1419 isedge(state->graph->edges, p, q) ? 'D' : 'A', 1420 p, q); 1421 return dupstr(buf); 1422#endif /* EDITOR */ 1423 } else if (IS_MOUSE_DRAG(button)) { 1424 return MOVE_NO_EFFECT; 1425 } else if (IS_MOUSE_RELEASE(button)) { 1426 assert(ui->dragpoint == -1); 1427 return MOVE_NO_EFFECT; 1428 } 1429 else if(IS_CURSOR_MOVE(button)) { 1430 if(ui->dragpoint < 0) { 1431 /* 1432 * We're selecting a point with the cursor keys. 1433 * 1434 * If no point is currently highlighted, we assume the "0" 1435 * point is highlighted to begin. Then, we search all the 1436 * points and find the nearest one (by Euclidean distance) 1437 * in the quadrant corresponding to the cursor key 1438 * direction. A point is in the right quadrant if and only 1439 * if the azimuth angle to that point from the cursor 1440 * point is within a [-45 deg, +45 deg] interval from the 1441 * direction vector of the cursor key. 1442 * 1443 * An important corner case here is if another point is in 1444 * the exact same location as the currently highlighted 1445 * point (which is a possibility with the "snap to grid" 1446 * preference). In this case, we do not consider the other 1447 * point as a candidate point, so as to prevent the cursor 1448 * from being "stuck" on any point. The player can still 1449 * select the overlapped point by dragging the highlighted 1450 * point away and then navigating back. 1451 */ 1452 int i, best = -1; 1453 float bestd = 0; 1454 1455 if(ui->cursorpoint < 0) { 1456 ui->cursorpoint = 0; 1457 } 1458 1459 point cur = state->pts[ui->cursorpoint]; 1460 1461 for (i = 0; i < n; i++) { 1462 point delta; 1463 float distsq; 1464 point p = state->pts[i]; 1465 int right_direction = false; 1466 1467 if(i == ui->cursorpoint) 1468 continue; 1469 1470 /* Compute the vector p - cur. Check that it lies in 1471 * the correct quadrant. */ 1472 delta.x = p.x * cur.d - cur.x * p.d; 1473 delta.y = p.y * cur.d - cur.y * p.d; 1474 delta.d = cur.d * p.d; 1475 1476 if(delta.x == 0 && delta.y == 0) 1477 continue; /* overlaps cursor point - skip */ 1478 1479 switch(button) { 1480 case CURSOR_UP: 1481 right_direction = (delta.y <= -delta.x) && (delta.y <= delta.x); 1482 break; 1483 case CURSOR_DOWN: 1484 right_direction = (delta.y >= -delta.x) && (delta.y >= delta.x); 1485 break; 1486 case CURSOR_LEFT: 1487 right_direction = (delta.y >= delta.x) && (delta.y <= -delta.x); 1488 break; 1489 case CURSOR_RIGHT: 1490 right_direction = (delta.y <= delta.x) && (delta.y >= -delta.x); 1491 break; 1492 } 1493 1494 if(!right_direction) 1495 continue; 1496 1497 /* Compute squared Euclidean distance */ 1498 distsq = normsq(delta); 1499 1500 if (best == -1 || distsq < bestd) { 1501 best = i; 1502 bestd = distsq; 1503 } 1504 } 1505 1506 if(best >= 0) { 1507 ui->cursorpoint = best; 1508 return MOVE_UI_UPDATE; 1509 } 1510 } 1511 else if(ui->dragpoint >= 0) { 1512 /* Dragging a point with the cursor keys. */ 1513 int movement_increment = ds->tilesize / 2; 1514 int dx = 0, dy = 0; 1515 1516 switch(button) { 1517 case CURSOR_UP: 1518 dy = -movement_increment; 1519 break; 1520 case CURSOR_DOWN: 1521 dy = movement_increment; 1522 break; 1523 case CURSOR_LEFT: 1524 dx = -movement_increment; 1525 break; 1526 case CURSOR_RIGHT: 1527 dx = movement_increment; 1528 break; 1529 default: 1530 break; 1531 } 1532 1533 /* This code has a slightly inconvenient interaction with 1534 * the snap to grid feature: if the point being dragged 1535 * originates on a non-grid point which is in the bottom 1536 * half or right half (or both) of a grid cell (a 75% 1537 * probability), then dragging point right (if it 1538 * originates from the right half) or down (if it 1539 * originates from the bottom half) will cause the point 1540 * to move one more grid cell than intended in that 1541 * direction. I (F. Wei) it wasn't worth handling this 1542 * corner case - if anyone feels inclined, please feel 1543 * free to do so. */ 1544 place_dragged_point(state, ui, ds, 1545 ui->newpoint.x * ds->tilesize / ui->newpoint.d + dx, 1546 ui->newpoint.y * ds->tilesize / ui->newpoint.d + dy); 1547 return MOVE_UI_UPDATE; 1548 } 1549 } else if(button == CURSOR_SELECT) { 1550 if(ui->dragpoint < 0 && ui->cursorpoint >= 0) { 1551 /* begin drag */ 1552 ui->dragtype = DRAG_MOVE_POINT; 1553 ui->dragpoint = ui->cursorpoint; 1554 ui->cursorpoint = -1; 1555 ui->newpoint.x = state->pts[ui->dragpoint].x * ds->tilesize / state->pts[ui->dragpoint].d; 1556 ui->newpoint.y = state->pts[ui->dragpoint].y * ds->tilesize / state->pts[ui->dragpoint].d; 1557 ui->newpoint.d = ds->tilesize; 1558 return MOVE_UI_UPDATE; 1559 } 1560 else if(ui->dragpoint >= 0) { 1561 /* end drag */ 1562 int p = ui->dragpoint; 1563 char buf[80]; 1564 1565 ui->cursorpoint = ui->dragpoint; 1566 ui->dragpoint = -1; /* terminate drag, no matter what */ 1567 1568 /* 1569 * First, see if we're within range. The user can cancel a 1570 * drag by dragging the point right off the window. 1571 */ 1572 if (ui->newpoint.x < 0 || 1573 ui->newpoint.x >= (long)state->w*ui->newpoint.d || 1574 ui->newpoint.y < 0 || 1575 ui->newpoint.y >= (long)state->h*ui->newpoint.d) 1576 return MOVE_UI_UPDATE; 1577 1578 /* 1579 * We aren't cancelling the drag. Construct a move string 1580 * indicating where this point is going to. 1581 */ 1582 sprintf(buf, "P%d:%ld,%ld/%ld", p, 1583 ui->newpoint.x, ui->newpoint.y, ui->newpoint.d); 1584 ui->just_dragged = true; 1585 return dupstr(buf); 1586 } 1587 else if(ui->cursorpoint < 0) { 1588 ui->cursorpoint = 0; 1589 return MOVE_UI_UPDATE; 1590 } 1591 } else if(STRIP_BUTTON_MODIFIERS(button) == CURSOR_SELECT2 || 1592 STRIP_BUTTON_MODIFIERS(button) == '\t') { 1593 /* Use spacebar or tab to cycle through the points. Shift 1594 * reverses cycle direction. */ 1595 if(ui->dragpoint >= 0) 1596 return MOVE_NO_EFFECT; 1597 if(ui->cursorpoint < 0) { 1598 ui->cursorpoint = 0; 1599 return MOVE_UI_UPDATE; 1600 } 1601 assert(ui->cursorpoint >= 0); 1602 1603 /* cursorpoint is valid - increment it */ 1604 int direction = (button & MOD_SHFT) ? -1 : 1; 1605 ui->cursorpoint = (ui->cursorpoint + direction + state->params.n) % state->params.n; 1606 return MOVE_UI_UPDATE; 1607 } 1608 1609 return MOVE_UNUSED; 1610} 1611 1612static game_state *execute_move(const game_state *state, const char *move) 1613{ 1614 int n = state->params.n; 1615 int p, k; 1616 long x, y, d; 1617 game_state *ret = dup_game(state); 1618 1619#ifndef EDITOR 1620 ret->just_solved = false; 1621#endif 1622 1623#ifdef EDITOR 1624 if (*move == 'E') { 1625 bool add; 1626 int a, b; 1627 1628 move++; 1629 if (*move == 'A') 1630 add = true; 1631 else if (*move == 'D') 1632 add = false; 1633 else { 1634 free_game(ret); 1635 return NULL; 1636 } 1637 1638 move++; 1639 a = atoi(move); 1640 while (*move && isdigit((unsigned char)*move)) 1641 move++; 1642 1643 if (*move != ',') { 1644 free_game(ret); 1645 return NULL; 1646 } 1647 move++; 1648 1649 b = atoi(move); 1650 1651 if (a >= 0 && a < n && b >= 0 && b < n && a != b) { 1652 if (add) 1653 addedge(ret->graph->edges, a, b); 1654 else 1655 deledge(ret->graph->edges, a, b); 1656 return ret; 1657 } else { 1658 free_game(ret); 1659 return NULL; 1660 } 1661 } 1662#endif 1663 1664 while (*move) { 1665#ifndef EDITOR 1666 if (*move == 'S') { 1667 move++; 1668 if (*move == ';') move++; 1669 ret->cheated = ret->just_solved = true; 1670 } 1671#endif 1672 if (*move == 'P' && 1673 sscanf(move+1, "%d:%ld,%ld/%ld%n", &p, &x, &y, &d, &k) == 4 && 1674 p >= 0 && p < n && d > 0) { 1675 ret->pts[p].x = x; 1676 ret->pts[p].y = y; 1677 ret->pts[p].d = d; 1678 1679 move += k+1; 1680 if (*move == ';') move++; 1681 } else { 1682 free_game(ret); 1683 return NULL; 1684 } 1685 } 1686 1687#ifndef EDITOR 1688 mark_crossings(ret); 1689#endif 1690 1691 return ret; 1692} 1693 1694/* ---------------------------------------------------------------------- 1695 * Drawing routines. 1696 */ 1697 1698static void game_compute_size(const game_params *params, int tilesize, 1699 const game_ui *ui, int *x, int *y) 1700{ 1701 *x = *y = COORDLIMIT(params->n) * tilesize; 1702} 1703 1704static void game_set_size(drawing *dr, game_drawstate *ds, 1705 const game_params *params, int tilesize) 1706{ 1707 ds->tilesize = tilesize; 1708} 1709 1710static float *game_colours(frontend *fe, int *ncolours) 1711{ 1712 float *ret = snewn(3 * NCOLOURS, float); 1713 1714 /* 1715 * COL_BACKGROUND is what we use as the normal background colour. 1716 * Unusually, though, it isn't colour #0: COL_SYSBACKGROUND, a bit 1717 * darker, takes that place. This means that if the user resizes 1718 * an Untangle window so as to change its aspect ratio, the 1719 * still-square playable area will be distinguished from the dead 1720 * space around it. 1721 */ 1722 game_mkhighlight(fe, ret, COL_BACKGROUND, -1, COL_SYSBACKGROUND); 1723 1724 ret[COL_LINE * 3 + 0] = 0.0F; 1725 ret[COL_LINE * 3 + 1] = 0.0F; 1726 ret[COL_LINE * 3 + 2] = 0.0F; 1727 1728 ret[COL_CROSSEDLINE * 3 + 0] = 1.0F; 1729 ret[COL_CROSSEDLINE * 3 + 1] = 0.0F; 1730 ret[COL_CROSSEDLINE * 3 + 2] = 0.0F; 1731 1732 ret[COL_OUTLINE * 3 + 0] = 0.0F; 1733 ret[COL_OUTLINE * 3 + 1] = 0.0F; 1734 ret[COL_OUTLINE * 3 + 2] = 0.0F; 1735 1736 ret[COL_POINT * 3 + 0] = 0.0F; 1737 ret[COL_POINT * 3 + 1] = 0.0F; 1738 ret[COL_POINT * 3 + 2] = 1.0F; 1739 1740 ret[COL_DRAGPOINT * 3 + 0] = 1.0F; 1741 ret[COL_DRAGPOINT * 3 + 1] = 1.0F; 1742 ret[COL_DRAGPOINT * 3 + 2] = 1.0F; 1743 1744 ret[COL_CURSORPOINT * 3 + 0] = 0.5F; 1745 ret[COL_CURSORPOINT * 3 + 1] = 0.5F; 1746 ret[COL_CURSORPOINT * 3 + 2] = 0.5F; 1747 1748 ret[COL_NEIGHBOUR * 3 + 0] = 1.0F; 1749 ret[COL_NEIGHBOUR * 3 + 1] = 0.0F; 1750 ret[COL_NEIGHBOUR * 3 + 2] = 0.0F; 1751 1752 ret[COL_FLASH1 * 3 + 0] = 0.5F; 1753 ret[COL_FLASH1 * 3 + 1] = 0.5F; 1754 ret[COL_FLASH1 * 3 + 2] = 0.5F; 1755 1756 ret[COL_FLASH2 * 3 + 0] = 1.0F; 1757 ret[COL_FLASH2 * 3 + 1] = 1.0F; 1758 ret[COL_FLASH2 * 3 + 2] = 1.0F; 1759 1760 *ncolours = NCOLOURS; 1761 return ret; 1762} 1763 1764static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) 1765{ 1766 struct game_drawstate *ds = snew(struct game_drawstate); 1767 int i; 1768 1769 ds->tilesize = 0; 1770 ds->x = snewn(state->params.n, long); 1771 ds->y = snewn(state->params.n, long); 1772 for (i = 0; i < state->params.n; i++) 1773 ds->x[i] = ds->y[i] = -1; 1774 ds->bg = -1; 1775 ds->dragpoint = -1; 1776 ds->cursorpoint = -1; 1777 1778 return ds; 1779} 1780 1781static void game_free_drawstate(drawing *dr, game_drawstate *ds) 1782{ 1783 sfree(ds->y); 1784 sfree(ds->x); 1785 sfree(ds); 1786} 1787 1788static point mix(point a, point b, float distance) 1789{ 1790 point ret; 1791 1792 ret.d = a.d * b.d; 1793 ret.x = (long)(a.x * b.d + distance * (b.x * a.d - a.x * b.d)); 1794 ret.y = (long)(a.y * b.d + distance * (b.y * a.d - a.y * b.d)); 1795 1796 return ret; 1797} 1798 1799static void game_redraw(drawing *dr, game_drawstate *ds, 1800 const game_state *oldstate, const game_state *state, 1801 int dir, const game_ui *ui, 1802 float animtime, float flashtime) 1803{ 1804 int w, h; 1805 edge *e; 1806 int i, j; 1807 int bg; 1808 bool points_moved; 1809#ifdef EDITOR 1810 bool edges_changed; 1811#endif 1812 1813 /* 1814 * There's no terribly sensible way to do partial redraws of 1815 * this game, so I'm going to have to resort to redrawing the 1816 * whole thing every time. 1817 */ 1818 1819 if (flashtime == 0) 1820 bg = COL_BACKGROUND; 1821 else if ((int)(flashtime * 4 / FLASH_TIME) % 2 == 0) 1822 bg = COL_FLASH1; 1823 else 1824 bg = COL_FLASH2; 1825 1826 /* 1827 * To prevent excessive spinning on redraw during a completion 1828 * flash, we first check to see if _either_ the flash 1829 * background colour has changed _or_ at least one point has 1830 * moved _or_ a drag has begun or ended, and abandon the redraw 1831 * if neither is the case. 1832 * 1833 * Also in this loop we work out the coordinates of all the 1834 * points for this redraw. 1835 */ 1836 points_moved = false; 1837 for (i = 0; i < state->params.n; i++) { 1838 point p = state->pts[i]; 1839 long x, y; 1840 1841 if (ui->dragpoint == i && ui->dragtype == DRAG_MOVE_POINT) 1842 p = ui->newpoint; 1843 1844 if (oldstate) 1845 p = mix(oldstate->pts[i], p, animtime / ui->anim_length); 1846 1847 x = p.x * ds->tilesize / p.d; 1848 y = p.y * ds->tilesize / p.d; 1849 1850 if (ds->x[i] != x || ds->y[i] != y) 1851 points_moved = true; 1852 1853 ds->x[i] = x; 1854 ds->y[i] = y; 1855 } 1856 1857#ifdef EDITOR 1858 edges_changed = false; 1859 if (oldstate) { 1860 for (i = 0;; i++) { 1861 edge *eold = index234(oldstate->graph->edges, i); 1862 edge *enew = index234(state->graph->edges, i); 1863 if (!eold && !enew) 1864 break; 1865 if (!eold || !enew) { 1866 edges_changed = true; 1867 break; 1868 } 1869 if (eold->a != enew->a || eold->b != enew->b) { 1870 edges_changed = true; 1871 break; 1872 } 1873 } 1874 } 1875#endif 1876 1877 if (ds->bg == bg && 1878 ds->dragpoint == ui->dragpoint && 1879 ds->cursorpoint == ui->cursorpoint && 1880#ifdef EDITOR 1881 !edges_changed && 1882#endif 1883 !points_moved) 1884 return; /* nothing to do */ 1885 1886 ds->dragpoint = ui->dragpoint; 1887 ds->cursorpoint = ui->cursorpoint; 1888 ds->bg = bg; 1889 1890 game_compute_size(&state->params, ds->tilesize, ui, &w, &h); 1891 draw_rect(dr, 0, 0, w, h, bg); 1892 1893 /* 1894 * Draw the edges. 1895 */ 1896 1897 for (i = 0; (e = index234(state->graph->edges, i)) != NULL; i++) { 1898#ifndef EDITOR 1899 int colour = ui->show_crossed_edges && 1900 (oldstate?oldstate:state)->crosses[i] ? 1901 COL_CROSSEDLINE : COL_LINE; 1902#else 1903 int colour = COL_LINE; 1904#endif 1905 1906 draw_line(dr, ds->x[e->a], ds->y[e->a], ds->x[e->b], ds->y[e->b], 1907 colour); 1908 } 1909 1910 /* 1911 * Draw the points. 1912 * 1913 * When dragging, we vary the point colours to highlight the drag 1914 * point and neighbour points. The draw order is defined so that 1915 * the most relevant points (i.e., the dragged point and cursor 1916 * point) are drawn last, so they appear on top of other points. 1917 */ 1918 static const int draw_order[] = { 1919 COL_POINT, 1920 COL_NEIGHBOUR, 1921 COL_CURSORPOINT, 1922 COL_DRAGPOINT 1923 }; 1924 1925 for (j = 0; j < 4; j++) { 1926 int thisc = draw_order[j]; 1927 for (i = 0; i < state->params.n; i++) { 1928 int c; 1929 1930 if (ui->dragpoint == i) { 1931 c = COL_DRAGPOINT; 1932 } else if(ui->cursorpoint == i) { 1933 c = COL_CURSORPOINT; 1934 } else if (ui->dragpoint >= 0 && 1935 isedge(state->graph->edges, ui->dragpoint, i)) { 1936 c = COL_NEIGHBOUR; 1937 } else { 1938 c = COL_POINT; 1939 } 1940 1941 if (c == thisc) { 1942 if (ui->vertex_numbers) { 1943 char buf[80]; 1944 draw_circle(dr, ds->x[i], ds->y[i], DRAG_THRESHOLD, bg, bg); 1945 sprintf(buf, "%d", i); 1946 draw_text(dr, ds->x[i], ds->y[i], FONT_VARIABLE, 1947 DRAG_THRESHOLD*3/2, 1948 ALIGN_VCENTRE|ALIGN_HCENTRE, c, buf); 1949 } else { 1950 draw_circle(dr, ds->x[i], ds->y[i], CIRCLE_RADIUS, 1951 c, COL_OUTLINE); 1952 } 1953 } 1954 } 1955 } 1956 1957 draw_update(dr, 0, 0, w, h); 1958} 1959 1960static float game_anim_length(const game_state *oldstate, 1961 const game_state *newstate, int dir, game_ui *ui) 1962{ 1963 if (ui->just_moved) 1964 return 0.0F; 1965#ifndef EDITOR 1966 if ((dir < 0 ? oldstate : newstate)->just_solved) 1967 ui->anim_length = SOLVEANIM_TIME; 1968 else 1969 ui->anim_length = ANIM_TIME; 1970#else 1971 ui->anim_length = ANIM_TIME; 1972#endif 1973 return ui->anim_length; 1974} 1975 1976static float game_flash_length(const game_state *oldstate, 1977 const game_state *newstate, int dir, game_ui *ui) 1978{ 1979#ifndef EDITOR 1980 if (!oldstate->completed && newstate->completed && 1981 !oldstate->cheated && !newstate->cheated) 1982 return FLASH_TIME; 1983#endif 1984 return 0.0F; 1985} 1986 1987static void game_get_cursor_location(const game_ui *ui, 1988 const game_drawstate *ds, 1989 const game_state *state, 1990 const game_params *params, 1991 int *x, int *y, int *w, int *h) 1992{ 1993 point pt; 1994 if (ui->dragpoint >= 0 && ui->dragtype == DRAG_MOVE_POINT) 1995 pt = ui->newpoint; 1996 else if(ui->cursorpoint >= 0) 1997 pt = state->pts[ui->cursorpoint]; 1998 else 1999 return; 2000 2001 int cx = ds->tilesize * pt.x / pt.d; 2002 int cy = ds->tilesize * pt.y / pt.d; 2003 2004 *x = cx - CIRCLE_RADIUS; 2005 *y = cy - CIRCLE_RADIUS; 2006 *w = *h = 2 * CIRCLE_RADIUS + 1; 2007} 2008 2009static int game_status(const game_state *state) 2010{ 2011#ifdef EDITOR 2012 return 0; 2013#else 2014 return state->completed ? +1 : 0; 2015#endif 2016} 2017 2018#ifdef COMBINED 2019#define thegame untangle 2020#endif 2021 2022const struct game thegame = { 2023 "Untangle", "games.untangle", "untangle", 2024 default_params, 2025 game_fetch_preset, NULL, 2026 decode_params, 2027 encode_params, 2028 free_params, 2029 dup_params, 2030 true, game_configure, custom_params, 2031 validate_params, 2032 new_game_desc, 2033 validate_desc, 2034 new_game, 2035 dup_game, 2036 free_game, 2037#ifndef EDITOR 2038 true, solve_game, 2039 false, NULL, NULL, /* can_format_as_text_now, text_format */ 2040#else 2041 false, NULL, 2042 true, game_can_format_as_text_now, game_text_format, 2043#endif 2044 get_prefs, set_prefs, 2045 new_ui, 2046 free_ui, 2047 NULL, /* encode_ui */ 2048 NULL, /* decode_ui */ 2049 NULL, /* game_request_keys */ 2050 game_changed_state, 2051 NULL, /* current_key_label */ 2052 interpret_move, 2053 execute_move, 2054 PREFERRED_TILESIZE, game_compute_size, game_set_size, 2055 game_colours, 2056 game_new_drawstate, 2057 game_free_drawstate, 2058 game_redraw, 2059 game_anim_length, 2060 game_flash_length, 2061 game_get_cursor_location, 2062 game_status, 2063 false, false, NULL, NULL, /* print_size, print */ 2064 false, /* wants_statusbar */ 2065 false, NULL, /* timing_state */ 2066 SOLVE_ANIMATES, /* flags */ 2067};