summaryrefslogtreecommitdiff
path: root/apps/plugins/puzzles/src/untangle.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/plugins/puzzles/src/untangle.c')
-rw-r--r--apps/plugins/puzzles/src/untangle.c512
1 files changed, 330 insertions, 182 deletions
diff --git a/apps/plugins/puzzles/src/untangle.c b/apps/plugins/puzzles/src/untangle.c
index 0d3e0e6135..f0647c83e9 100644
--- a/apps/plugins/puzzles/src/untangle.c
+++ b/apps/plugins/puzzles/src/untangle.c
@@ -20,10 +20,6 @@
20 * requirements are adequately expressed by a single scalar tile 20 * requirements are adequately expressed by a single scalar tile
21 * size), and probably complicate the rest of the puzzles' API as a 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. 22 * result. So I'm not sure I really want to do it.
23 *
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.
27 */ 23 */
28 24
29#include <stdio.h> 25#include <stdio.h>
@@ -31,7 +27,15 @@
31#include <string.h> 27#include <string.h>
32#include <assert.h> 28#include <assert.h>
33#include <ctype.h> 29#include <ctype.h>
34#include <math.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
35 39
36#include "puzzles.h" 40#include "puzzles.h"
37#include "tree234.h" 41#include "tree234.h"
@@ -39,7 +43,6 @@
39#define CIRCLE_RADIUS 6 43#define CIRCLE_RADIUS 6
40#define DRAG_THRESHOLD (CIRCLE_RADIUS * 2) 44#define DRAG_THRESHOLD (CIRCLE_RADIUS * 2)
41#define PREFERRED_TILESIZE 64 45#define PREFERRED_TILESIZE 64
42#define CURSOR_GRANULARITY 5
43 46
44#define FLASH_TIME 0.30F 47#define FLASH_TIME 0.30F
45#define ANIM_TIME 0.13F 48#define ANIM_TIME 0.13F
@@ -49,9 +52,7 @@ enum {
49 COL_SYSBACKGROUND, 52 COL_SYSBACKGROUND,
50 COL_BACKGROUND, 53 COL_BACKGROUND,
51 COL_LINE, 54 COL_LINE,
52#ifdef SHOW_CROSSINGS
53 COL_CROSSEDLINE, 55 COL_CROSSEDLINE,
54#endif
55 COL_OUTLINE, 56 COL_OUTLINE,
56 COL_POINT, 57 COL_POINT,
57 COL_DRAGPOINT, 58 COL_DRAGPOINT,
@@ -93,9 +94,7 @@ struct game_state {
93 game_params params; 94 game_params params;
94 int w, h; /* extent of coordinate system only */ 95 int w, h; /* extent of coordinate system only */
95 point *pts; 96 point *pts;
96#ifdef SHOW_CROSSINGS
97 int *crosses; /* mark edges which are crossed */ 97 int *crosses; /* mark edges which are crossed */
98#endif
99 struct graph *graph; 98 struct graph *graph;
100 bool completed, cheated, just_solved; 99 bool completed, cheated, just_solved;
101}; 100};
@@ -208,6 +207,8 @@ static const char *validate_params(const game_params *params, bool full)
208{ 207{
209 if (params->n < 4) 208 if (params->n < 4)
210 return "Number of points must be at least four"; 209 return "Number of points must be at least four";
210 if (params->n > INT_MAX / 3)
211 return "Number of points must not be unreasonably large";
211 return NULL; 212 return NULL;
212} 213}
213 214
@@ -216,6 +217,9 @@ static const char *validate_params(const game_params *params, bool full)
216 * integer overflow at the very core of cross(). 217 * integer overflow at the very core of cross().
217 */ 218 */
218 219
220#if !HAVE_STDINT_H
221/* For prehistoric C implementations, do this the hard way */
222
219typedef struct { 223typedef struct {
220 long hi; 224 long hi;
221 unsigned long lo; 225 unsigned long lo;
@@ -295,6 +299,21 @@ static int64 dotprod64(long a, long b, long p, long q)
295 return ab; 299 return ab;
296} 300}
297 301
302#else /* HAVE_STDINT_H */
303
304typedef int64_t int64;
305#define greater64(i,j) ((i) > (j))
306#define sign64(i) ((i) < 0 ? -1 : (i)==0 ? 0 : +1)
307#define mulu32to64(x,y) ((int64_t)(unsigned long)(x) * (unsigned long)(y))
308#define mul32to64(x,y) ((int64_t)(long)(x) * (long)(y))
309
310static int64 dotprod64(long a, long b, long p, long q)
311{
312 return (int64)a * b + (int64)p * q;
313}
314
315#endif /* HAVE_STDINT_H */
316
298/* 317/*
299 * Determine whether the line segments between a1 and a2, and 318 * Determine whether the line segments between a1 and a2, and
300 * between b1 and b2, intersect. We count it as an intersection if 319 * between b1 and b2, intersect. We count it as an intersection if
@@ -419,7 +438,9 @@ static void addedge(tree234 *edges, int a, int b)
419 e->a = min(a, b); 438 e->a = min(a, b);
420 e->b = max(a, b); 439 e->b = max(a, b);
421 440
422 add234(edges, e); 441 if (add234(edges, e) != e)
442 /* Duplicate edge. */
443 sfree(e);
423} 444}
424 445
425static bool isedge(tree234 *edges, int a, int b) 446static bool isedge(tree234 *edges, int a, int b)
@@ -441,8 +462,8 @@ typedef struct vertex {
441 462
442static int vertcmpC(const void *av, const void *bv) 463static int vertcmpC(const void *av, const void *bv)
443{ 464{
444 const vertex *a = (vertex *)av; 465 const vertex *a = (const vertex *)av;
445 const vertex *b = (vertex *)bv; 466 const vertex *b = (const vertex *)bv;
446 467
447 if (a->param < b->param) 468 if (a->param < b->param)
448 return -1; 469 return -1;
@@ -754,6 +775,8 @@ static const char *validate_desc(const game_params *params, const char *desc)
754 return "Expected ',' after number in game description"; 775 return "Expected ',' after number in game description";
755 desc++; /* eat comma */ 776 desc++; /* eat comma */
756 } 777 }
778 if (a == b)
779 return "Node linked to itself in game description";
757 } 780 }
758 781
759 return NULL; 782 return NULL;
@@ -765,10 +788,8 @@ static void mark_crossings(game_state *state)
765 int i, j; 788 int i, j;
766 edge *e, *e2; 789 edge *e, *e2;
767 790
768#ifdef SHOW_CROSSINGS
769 for (i = 0; (e = index234(state->graph->edges, i)) != NULL; i++) 791 for (i = 0; (e = index234(state->graph->edges, i)) != NULL; i++)
770 state->crosses[i] = false; 792 state->crosses[i] = false;
771#endif
772 793
773 /* 794 /*
774 * Check correctness: for every pair of edges, see whether they 795 * Check correctness: for every pair of edges, see whether they
@@ -782,11 +803,7 @@ static void mark_crossings(game_state *state)
782 if (cross(state->pts[e2->a], state->pts[e2->b], 803 if (cross(state->pts[e2->a], state->pts[e2->b],
783 state->pts[e->a], state->pts[e->b])) { 804 state->pts[e->a], state->pts[e->b])) {
784 ok = false; 805 ok = false;
785#ifdef SHOW_CROSSINGS
786 state->crosses[i] = state->crosses[j] = true; 806 state->crosses[i] = state->crosses[j] = true;
787#else
788 goto done; /* multi-level break - sorry */
789#endif
790 } 807 }
791 } 808 }
792 } 809 }
@@ -795,9 +812,6 @@ static void mark_crossings(game_state *state)
795 * e == NULL if we've gone through all the edge pairs 812 * e == NULL if we've gone through all the edge pairs
796 * without finding a crossing. 813 * without finding a crossing.
797 */ 814 */
798#ifndef SHOW_CROSSINGS
799 done:
800#endif
801 if (ok) 815 if (ok)
802 state->completed = true; 816 state->completed = true;
803} 817}
@@ -834,10 +848,8 @@ static game_state *new_game(midend *me, const game_params *params,
834 addedge(state->graph->edges, a, b); 848 addedge(state->graph->edges, a, b);
835 } 849 }
836 850
837#ifdef SHOW_CROSSINGS
838 state->crosses = snewn(count234(state->graph->edges), int); 851 state->crosses = snewn(count234(state->graph->edges), int);
839 mark_crossings(state); /* sets up `crosses' and `completed' */ 852 mark_crossings(state); /* sets up `crosses' and `completed' */
840#endif
841 853
842 return state; 854 return state;
843} 855}
@@ -857,11 +869,9 @@ static game_state *dup_game(const game_state *state)
857 ret->completed = state->completed; 869 ret->completed = state->completed;
858 ret->cheated = state->cheated; 870 ret->cheated = state->cheated;
859 ret->just_solved = state->just_solved; 871 ret->just_solved = state->just_solved;
860#ifdef SHOW_CROSSINGS
861 ret->crosses = snewn(count234(ret->graph->edges), int); 872 ret->crosses = snewn(count234(ret->graph->edges), int);
862 memcpy(ret->crosses, state->crosses, 873 memcpy(ret->crosses, state->crosses,
863 count234(ret->graph->edges) * sizeof(int)); 874 count234(ret->graph->edges) * sizeof(int));
864#endif
865 875
866 return ret; 876 return ret;
867} 877}
@@ -1025,19 +1035,10 @@ static char *solve_game(const game_state *state, const game_state *currstate,
1025 return ret; 1035 return ret;
1026} 1036}
1027 1037
1028static bool game_can_format_as_text_now(const game_params *params)
1029{
1030 return true;
1031}
1032
1033static char *game_text_format(const game_state *state)
1034{
1035 return NULL;
1036}
1037
1038struct game_ui { 1038struct game_ui {
1039 /* Invariant: at most one of {dragpoint, cursorpoint} may be valid
1040 * at any time. */
1039 int dragpoint; /* point being dragged; -1 if none */ 1041 int dragpoint; /* point being dragged; -1 if none */
1040
1041 int cursorpoint; /* point being highlighted, but 1042 int cursorpoint; /* point being highlighted, but
1042 * not dragged by the cursor, 1043 * not dragged by the cursor,
1043 * again -1 if none */ 1044 * again -1 if none */
@@ -1046,6 +1047,26 @@ struct game_ui {
1046 bool just_dragged; /* reset in game_changed_state */ 1047 bool just_dragged; /* reset in game_changed_state */
1047 bool just_moved; /* _set_ in game_changed_state */ 1048 bool just_moved; /* _set_ in game_changed_state */
1048 float anim_length; 1049 float anim_length;
1050
1051 /*
1052 * User preference option to snap dragged points to a coarse-ish
1053 * grid. Requested by a user who otherwise found themself spending
1054 * too much time struggling to get lines nicely horizontal or
1055 * vertical.
1056 */
1057 bool snap_to_grid;
1058
1059 /*
1060 * User preference option to highlight graph edges involved in a
1061 * crossing.
1062 */
1063 bool show_crossed_edges;
1064
1065 /*
1066 * User preference option to show vertices as numbers instead of
1067 * circular blobs, so you can easily tell them apart.
1068 */
1069 bool vertex_numbers;
1049}; 1070};
1050 1071
1051static game_ui *new_ui(const game_state *state) 1072static game_ui *new_ui(const game_state *state)
@@ -1054,21 +1075,51 @@ static game_ui *new_ui(const game_state *state)
1054 ui->dragpoint = -1; 1075 ui->dragpoint = -1;
1055 ui->cursorpoint = -1; 1076 ui->cursorpoint = -1;
1056 ui->just_moved = ui->just_dragged = false; 1077 ui->just_moved = ui->just_dragged = false;
1078 ui->snap_to_grid = false;
1079 ui->show_crossed_edges = false;
1080 ui->vertex_numbers = false;
1057 return ui; 1081 return ui;
1058} 1082}
1059 1083
1060static void free_ui(game_ui *ui) 1084static config_item *get_prefs(game_ui *ui)
1061{ 1085{
1062 sfree(ui); 1086 config_item *cfg;
1087
1088 cfg = snewn(4, config_item);
1089
1090 cfg[0].name = "Snap points to a grid";
1091 cfg[0].kw = "snap-to-grid";
1092 cfg[0].type = C_BOOLEAN;
1093 cfg[0].u.boolean.bval = ui->snap_to_grid;
1094
1095 cfg[1].name = "Show edges that cross another edge";
1096 cfg[1].kw = "show-crossed-edges";
1097 cfg[1].type = C_BOOLEAN;
1098 cfg[1].u.boolean.bval = ui->show_crossed_edges;
1099
1100 cfg[2].name = "Display style for vertices";
1101 cfg[2].kw = "vertex-style";
1102 cfg[2].type = C_CHOICES;
1103 cfg[2].u.choices.choicenames = ":Circles:Numbers";
1104 cfg[2].u.choices.choicekws = ":circle:number";
1105 cfg[2].u.choices.selected = ui->vertex_numbers;
1106
1107 cfg[3].name = NULL;
1108 cfg[3].type = C_END;
1109
1110 return cfg;
1063} 1111}
1064 1112
1065static char *encode_ui(const game_ui *ui) 1113static void set_prefs(game_ui *ui, const config_item *cfg)
1066{ 1114{
1067 return NULL; 1115 ui->snap_to_grid = cfg[0].u.boolean.bval;
1116 ui->show_crossed_edges = cfg[1].u.boolean.bval;
1117 ui->vertex_numbers = cfg[2].u.choices.selected;
1068} 1118}
1069 1119
1070static void decode_ui(game_ui *ui, const char *encoding) 1120static void free_ui(game_ui *ui)
1071{ 1121{
1122 sfree(ui);
1072} 1123}
1073 1124
1074static void game_changed_state(game_ui *ui, const game_state *oldstate, 1125static void game_changed_state(game_ui *ui, const game_state *oldstate,
@@ -1085,6 +1136,66 @@ struct game_drawstate {
1085 long *x, *y; 1136 long *x, *y;
1086}; 1137};
1087 1138
1139static void place_dragged_point(const game_state *state, game_ui *ui,
1140 const game_drawstate *ds, int x, int y)
1141{
1142 if (ui->snap_to_grid) {
1143 /*
1144 * We snap points to a grid that has n-1 vertices on each
1145 * side. This should be large enough to permit a straight-
1146 * line drawing of any n-vertex planar graph, and moreover,
1147 * any specific planar embedding of that graph.
1148 *
1149 * Source: David Eppstein's book 'Forbidden Configurations in
1150 * Discrete Geometry' mentions (section 16.3, page 182) that
1151 * the point configuration he describes as GRID(n-1,n-1) -
1152 * that is, the vertices of a square grid with n-1 vertices on
1153 * each side - is universal for n-vertex planar graphs. In
1154 * other words (from definitions earlier in the chapter), if a
1155 * graph G admits any drawing in the plane at all, then it can
1156 * be drawn with straight lines, and with all vertices being
1157 * vertices of that grid.
1158 *
1159 * That fact by itself only says that _some_ planar embedding
1160 * of G can be drawn in this grid. We'd prefer that _all_
1161 * embeddings of G can be so drawn, because 'snap to grid' is
1162 * supposed to be a UI affordance, not an extra puzzle
1163 * challenge, so we don't want to constrain the player's
1164 * choice of planar embedding.
1165 *
1166 * But it doesn't make a difference. Proof: given a specific
1167 * planar embedding of G, triangulate it, by adding extra
1168 * edges to every face of degree > 3. When this process
1169 * terminates with every face a triangle, we have a new graph
1170 * G' such that no edge can be added without it ceasing to be
1171 * planar. Standard theorems say that a maximal planar graph
1172 * is 3-connected, and that a 3-connected planar graph has a
1173 * _unique_ embedding. So any drawing of G' in the plane can
1174 * be converted into a drawing of G in the desired embedding,
1175 * by simply removing all the extra edges that we added to
1176 * turn G into G'. And G' is still an n-vertex planar graph,
1177 * hence it can be drawn in GRID(n-1,n-1). []
1178 */
1179 int d = state->params.n - 1;
1180
1181 /* Calculate position in terms of the snapping grid. */
1182 x = d * x / (state->w * ds->tilesize);
1183 y = d * y / (state->h * ds->tilesize);
1184 /* Convert to standard co-ordinates, applying a half-square offset. */
1185 ui->newpoint.x = (x * 2 + 1) * state->w;
1186 ui->newpoint.y = (y * 2 + 1) * state->h;
1187 ui->newpoint.d = d * 2;
1188 } else {
1189 ui->newpoint.x = x;
1190 ui->newpoint.y = y;
1191 ui->newpoint.d = ds->tilesize;
1192 }
1193}
1194
1195static float normsq(point pt) {
1196 return (pt.x * pt.x + pt.y * pt.y) / (pt.d * pt.d);
1197}
1198
1088static char *interpret_move(const game_state *state, game_ui *ui, 1199static char *interpret_move(const game_state *state, game_ui *ui,
1089 const game_drawstate *ds, 1200 const game_drawstate *ds,
1090 int x, int y, int button) 1201 int x, int y, int button)
@@ -1119,23 +1230,20 @@ static char *interpret_move(const game_state *state, game_ui *ui,
1119 1230
1120 if (bestd <= DRAG_THRESHOLD * DRAG_THRESHOLD) { 1231 if (bestd <= DRAG_THRESHOLD * DRAG_THRESHOLD) {
1121 ui->dragpoint = best; 1232 ui->dragpoint = best;
1122 ui->cursorpoint = -1; 1233 ui->cursorpoint = -1; /* eliminate the cursor point, if any */
1123 ui->newpoint.x = x; 1234 place_dragged_point(state, ui, ds, x, y);
1124 ui->newpoint.y = y; 1235 return MOVE_UI_UPDATE;
1125 ui->newpoint.d = ds->tilesize;
1126 return UI_UPDATE;
1127 } 1236 }
1128 1237 return MOVE_NO_EFFECT;
1129 } else if (IS_MOUSE_DRAG(button) && ui->dragpoint >= 0) { 1238 } else if (IS_MOUSE_DRAG(button) && ui->dragpoint >= 0) {
1130 ui->newpoint.x = x; 1239 place_dragged_point(state, ui, ds, x, y);
1131 ui->newpoint.y = y; 1240 return MOVE_UI_UPDATE;
1132 ui->newpoint.d = ds->tilesize;
1133 return UI_UPDATE;
1134 } else if (IS_MOUSE_RELEASE(button) && ui->dragpoint >= 0) { 1241 } else if (IS_MOUSE_RELEASE(button) && ui->dragpoint >= 0) {
1135 int p = ui->dragpoint; 1242 int p = ui->dragpoint;
1136 char buf[80]; 1243 char buf[80];
1137 1244
1138 ui->dragpoint = -1; /* terminate drag, no matter what */ 1245 ui->dragpoint = -1; /* terminate drag, no matter what */
1246 ui->cursorpoint = -1; /* also eliminate the cursor point */
1139 1247
1140 /* 1248 /*
1141 * First, see if we're within range. The user can cancel a 1249 * First, see if we're within range. The user can cancel a
@@ -1145,7 +1253,7 @@ static char *interpret_move(const game_state *state, game_ui *ui,
1145 ui->newpoint.x >= (long)state->w*ui->newpoint.d || 1253 ui->newpoint.x >= (long)state->w*ui->newpoint.d ||
1146 ui->newpoint.y < 0 || 1254 ui->newpoint.y < 0 ||
1147 ui->newpoint.y >= (long)state->h*ui->newpoint.d) 1255 ui->newpoint.y >= (long)state->h*ui->newpoint.d)
1148 return UI_UPDATE; 1256 return MOVE_UI_UPDATE;
1149 1257
1150 /* 1258 /*
1151 * We aren't cancelling the drag. Construct a move string 1259 * We aren't cancelling the drag. Construct a move string
@@ -1155,109 +1263,140 @@ static char *interpret_move(const game_state *state, game_ui *ui,
1155 ui->newpoint.x, ui->newpoint.y, ui->newpoint.d); 1263 ui->newpoint.x, ui->newpoint.y, ui->newpoint.d);
1156 ui->just_dragged = true; 1264 ui->just_dragged = true;
1157 return dupstr(buf); 1265 return dupstr(buf);
1266 } else if (IS_MOUSE_DRAG(button) || IS_MOUSE_RELEASE(button)) {
1267 return MOVE_NO_EFFECT;
1158 } 1268 }
1159 else if(IS_CURSOR_MOVE(button)) 1269 else if(IS_CURSOR_MOVE(button)) {
1160 { 1270 if(ui->dragpoint < 0) {
1161 if(ui->dragpoint < 0) 1271 /*
1162 { 1272 * We're selecting a point with the cursor keys.
1163 /* We're selecting a point here. */ 1273 *
1164 /* Search all the points and find the closest one (2-D) in 1274 * If no point is currently highlighted, we assume the "0"
1165 * the given direction. */ 1275 * point is highlighted to begin. Then, we search all the
1166 int i, best; 1276 * points and find the nearest one (by Euclidean distance)
1167 long bestd; 1277 * in the quadrant corresponding to the cursor key
1168 1278 * direction. A point is in the right quadrant if and only
1169 if(ui->cursorpoint < 0) 1279 * if the azimuth angle to that point from the cursor
1170 { 1280 * point is within a [-45 deg, +45 deg] interval from the
1281 * direction vector of the cursor key.
1282 *
1283 * An important corner case here is if another point is in
1284 * the exact same location as the currently highlighted
1285 * point (which is a possibility with the "snap to grid"
1286 * preference). In this case, we do not consider the other
1287 * point as a candidate point, so as to prevent the cursor
1288 * from being "stuck" on any point. The player can still
1289 * select the overlapped point by dragging the highlighted
1290 * point away and then navigating back.
1291 */
1292 int i, best = -1;
1293 float bestd = 0;
1294
1295 if(ui->cursorpoint < 0) {
1171 ui->cursorpoint = 0; 1296 ui->cursorpoint = 0;
1172 } 1297 }
1173 1298
1174 /* 1299 point cur = state->pts[ui->cursorpoint];
1175 * Begin drag. We drag the vertex _nearest_ to the pointer,
1176 * just in case one is nearly on top of another and we want
1177 * to drag the latter. However, we drag nothing at all if
1178 * the nearest vertex is outside DRAG_THRESHOLD.
1179 */
1180 best = -1;
1181 bestd = 0;
1182 1300
1183 for (i = 0; i < n; i++) { 1301 for (i = 0; i < n; i++) {
1184 long px, py, dx, dy, d; 1302 point delta;
1185 float angle; 1303 float distsq;
1186 int right_direction; 1304 point p = state->pts[i];
1305 int right_direction = false;
1306
1187 if(i == ui->cursorpoint) 1307 if(i == ui->cursorpoint)
1188 continue; 1308 continue;
1189 1309
1190 px = state->pts[i].x * ds->tilesize / state->pts[i].d; 1310 /* Compute the vector p - cur. Check that it lies in
1191 py = state->pts[i].y * ds->tilesize / state->pts[i].d; 1311 * the correct quadrant. */
1192 dx = px - state->pts[ui->cursorpoint].x * ds->tilesize / state->pts[ui->cursorpoint].d; 1312 delta.x = p.x * cur.d - cur.x * p.d;
1193 dy = py - state->pts[ui->cursorpoint].y * ds->tilesize / state->pts[ui->cursorpoint].d; 1313 delta.y = p.y * cur.d - cur.y * p.d;
1194 d = dx*dx + dy*dy; 1314 delta.d = cur.d * p.d;
1195 1315
1196 /* Figure out if this point falls into a 90 degree 1316 if(delta.x == 0 && delta.y == 0)
1197 * range extending from the current point */ 1317 continue; /* overlaps cursor point - skip */
1198 1318
1199 angle = atan2(-dy, dx); /* negate y to adjust for raster coordinates */ 1319 switch(button) {
1200 1320 case CURSOR_UP:
1201 /* offset to [0..2*PI] */ 1321 right_direction = (delta.y <= -delta.x) && (delta.y <= delta.x);
1202 if(angle < 0) 1322 break;
1203 angle += 2*PI; 1323 case CURSOR_DOWN:
1324 right_direction = (delta.y >= -delta.x) && (delta.y >= delta.x);
1325 break;
1326 case CURSOR_LEFT:
1327 right_direction = (delta.y >= delta.x) && (delta.y <= -delta.x);
1328 break;
1329 case CURSOR_RIGHT:
1330 right_direction = (delta.y <= delta.x) && (delta.y >= -delta.x);
1331 break;
1332 }
1204 1333
1205 right_direction = false; 1334 if(!right_direction)
1335 continue;
1206 1336
1207 if((button == CURSOR_UP && (1*PI/4 <= angle && angle <= 3*PI/4)) || 1337 /* Compute squared Euclidean distance */
1208 (button == CURSOR_LEFT && (3*PI/4 <= angle && angle <= 5*PI/4)) || 1338 distsq = normsq(delta);
1209 (button == CURSOR_DOWN && (5*PI/4 <= angle && angle <= 7*PI/4)) ||
1210 (button == CURSOR_RIGHT && (angle >= 7*PI/4 || angle <= 1*PI/4)))
1211 right_direction = true;
1212 1339
1213 if ((best == -1 || bestd > d) && right_direction) { 1340 if (best == -1 || distsq < bestd) {
1214 best = i; 1341 best = i;
1215 bestd = d; 1342 bestd = distsq;
1216 } 1343 }
1217 } 1344 }
1218 1345
1219 if(best >= 0) 1346 if(best >= 0) {
1220 {
1221 ui->cursorpoint = best; 1347 ui->cursorpoint = best;
1222 return UI_UPDATE; 1348 return MOVE_UI_UPDATE;
1223 } 1349 }
1224 } 1350 }
1225 else if(ui->dragpoint >= 0) 1351 else if(ui->dragpoint >= 0) {
1226 { 1352 /* Dragging a point with the cursor keys. */
1227 /* dragging */ 1353 int movement_increment = ds->tilesize / 2;
1228 switch(button) 1354 int dx = 0, dy = 0;
1229 { 1355
1356 switch(button) {
1230 case CURSOR_UP: 1357 case CURSOR_UP:
1231 ui->newpoint.y -= ds->tilesize / CURSOR_GRANULARITY; 1358 dy = -movement_increment;
1232 return UI_UPDATE; 1359 break;
1233 case CURSOR_DOWN: 1360 case CURSOR_DOWN:
1234 ui->newpoint.y += ds->tilesize / CURSOR_GRANULARITY; 1361 dy = movement_increment;
1235 return UI_UPDATE; 1362 break;
1236 case CURSOR_LEFT: 1363 case CURSOR_LEFT:
1237 ui->newpoint.x -= ds->tilesize / CURSOR_GRANULARITY; 1364 dx = -movement_increment;
1238 return UI_UPDATE; 1365 break;
1239 case CURSOR_RIGHT: 1366 case CURSOR_RIGHT:
1240 ui->newpoint.x += ds->tilesize / CURSOR_GRANULARITY; 1367 dx = movement_increment;
1241 return UI_UPDATE; 1368 break;
1242 default: 1369 default:
1243 break; 1370 break;
1244 } 1371 }
1372
1373 /* This code has a slightly inconvenient interaction with
1374 * the snap to grid feature: if the point being dragged
1375 * originates on a non-grid point which is in the bottom
1376 * half or right half (or both) of a grid cell (a 75%
1377 * probability), then dragging point right (if it
1378 * originates from the right half) or down (if it
1379 * originates from the bottom half) will cause the point
1380 * to move one more grid cell than intended in that
1381 * direction. I (F. Wei) it wasn't worth handling this
1382 * corner case - if anyone feels inclined, please feel
1383 * free to do so. */
1384 place_dragged_point(state, ui, ds,
1385 ui->newpoint.x * ds->tilesize / ui->newpoint.d + dx,
1386 ui->newpoint.y * ds->tilesize / ui->newpoint.d + dy);
1387 return MOVE_UI_UPDATE;
1245 } 1388 }
1246 } 1389 } else if(button == CURSOR_SELECT) {
1247 else if(IS_CURSOR_SELECT(button)) 1390 if(ui->dragpoint < 0 && ui->cursorpoint >= 0) {
1248 {
1249 if(ui->dragpoint < 0 && ui->cursorpoint >= 0)
1250 {
1251 /* begin drag */ 1391 /* begin drag */
1252 ui->dragpoint = ui->cursorpoint; 1392 ui->dragpoint = ui->cursorpoint;
1253 ui->cursorpoint = -1; 1393 ui->cursorpoint = -1;
1254 ui->newpoint.x = state->pts[ui->dragpoint].x * ds->tilesize / state->pts[ui->dragpoint].d; 1394 ui->newpoint.x = state->pts[ui->dragpoint].x * ds->tilesize / state->pts[ui->dragpoint].d;
1255 ui->newpoint.y = state->pts[ui->dragpoint].y * ds->tilesize / state->pts[ui->dragpoint].d; 1395 ui->newpoint.y = state->pts[ui->dragpoint].y * ds->tilesize / state->pts[ui->dragpoint].d;
1256 ui->newpoint.d = ds->tilesize; 1396 ui->newpoint.d = ds->tilesize;
1257 return UI_UPDATE; 1397 return MOVE_UI_UPDATE;
1258 } 1398 }
1259 else if(ui->dragpoint >= 0) 1399 else if(ui->dragpoint >= 0) {
1260 {
1261 /* end drag */ 1400 /* end drag */
1262 int p = ui->dragpoint; 1401 int p = ui->dragpoint;
1263 char buf[80]; 1402 char buf[80];
@@ -1273,7 +1412,7 @@ static char *interpret_move(const game_state *state, game_ui *ui,
1273 ui->newpoint.x >= (long)state->w*ui->newpoint.d || 1412 ui->newpoint.x >= (long)state->w*ui->newpoint.d ||
1274 ui->newpoint.y < 0 || 1413 ui->newpoint.y < 0 ||
1275 ui->newpoint.y >= (long)state->h*ui->newpoint.d) 1414 ui->newpoint.y >= (long)state->h*ui->newpoint.d)
1276 return UI_UPDATE; 1415 return MOVE_UI_UPDATE;
1277 1416
1278 /* 1417 /*
1279 * We aren't cancelling the drag. Construct a move string 1418 * We aren't cancelling the drag. Construct a move string
@@ -1284,14 +1423,29 @@ static char *interpret_move(const game_state *state, game_ui *ui,
1284 ui->just_dragged = true; 1423 ui->just_dragged = true;
1285 return dupstr(buf); 1424 return dupstr(buf);
1286 } 1425 }
1287 else if(ui->cursorpoint < 0) 1426 else if(ui->cursorpoint < 0) {
1288 {
1289 ui->cursorpoint = 0; 1427 ui->cursorpoint = 0;
1290 return UI_UPDATE; 1428 return MOVE_UI_UPDATE;
1291 } 1429 }
1430 } else if(STRIP_BUTTON_MODIFIERS(button) == CURSOR_SELECT2 ||
1431 STRIP_BUTTON_MODIFIERS(button) == '\t') {
1432 /* Use spacebar or tab to cycle through the points. Shift
1433 * reverses cycle direction. */
1434 if(ui->dragpoint >= 0)
1435 return MOVE_NO_EFFECT;
1436 if(ui->cursorpoint < 0) {
1437 ui->cursorpoint = 0;
1438 return MOVE_UI_UPDATE;
1439 }
1440 assert(ui->cursorpoint >= 0);
1441
1442 /* cursorpoint is valid - increment it */
1443 int direction = (button & MOD_SHFT) ? -1 : 1;
1444 ui->cursorpoint = (ui->cursorpoint + direction + state->params.n) % state->params.n;
1445 return MOVE_UI_UPDATE;
1292 } 1446 }
1293 1447
1294 return NULL; 1448 return MOVE_UNUSED;
1295} 1449}
1296 1450
1297static game_state *execute_move(const game_state *state, const char *move) 1451static game_state *execute_move(const game_state *state, const char *move)
@@ -1334,7 +1488,7 @@ static game_state *execute_move(const game_state *state, const char *move)
1334 */ 1488 */
1335 1489
1336static void game_compute_size(const game_params *params, int tilesize, 1490static void game_compute_size(const game_params *params, int tilesize,
1337 int *x, int *y) 1491 const game_ui *ui, int *x, int *y)
1338{ 1492{
1339 *x = *y = COORDLIMIT(params->n) * tilesize; 1493 *x = *y = COORDLIMIT(params->n) * tilesize;
1340} 1494}
@@ -1363,11 +1517,9 @@ static float *game_colours(frontend *fe, int *ncolours)
1363 ret[COL_LINE * 3 + 1] = 0.0F; 1517 ret[COL_LINE * 3 + 1] = 0.0F;
1364 ret[COL_LINE * 3 + 2] = 0.0F; 1518 ret[COL_LINE * 3 + 2] = 0.0F;
1365 1519
1366#ifdef SHOW_CROSSINGS
1367 ret[COL_CROSSEDLINE * 3 + 0] = 1.0F; 1520 ret[COL_CROSSEDLINE * 3 + 0] = 1.0F;
1368 ret[COL_CROSSEDLINE * 3 + 1] = 0.0F; 1521 ret[COL_CROSSEDLINE * 3 + 1] = 0.0F;
1369 ret[COL_CROSSEDLINE * 3 + 2] = 0.0F; 1522 ret[COL_CROSSEDLINE * 3 + 2] = 0.0F;
1370#endif
1371 1523
1372 ret[COL_OUTLINE * 3 + 0] = 0.0F; 1524 ret[COL_OUTLINE * 3 + 0] = 0.0F;
1373 ret[COL_OUTLINE * 3 + 1] = 0.0F; 1525 ret[COL_OUTLINE * 3 + 1] = 0.0F;
@@ -1491,15 +1643,16 @@ static void game_redraw(drawing *dr, game_drawstate *ds,
1491 ds->y[i] = y; 1643 ds->y[i] = y;
1492 } 1644 }
1493 1645
1494 if (ds->bg == bg && ds->dragpoint == ui->dragpoint && ds->cursorpoint == ui->cursorpoint && !points_moved) 1646 if (ds->bg == bg &&
1647 ds->dragpoint == ui->dragpoint &&
1648 ds->cursorpoint == ui->cursorpoint && !points_moved)
1495 return; /* nothing to do */ 1649 return; /* nothing to do */
1496 1650
1497 ds->dragpoint = ui->dragpoint; 1651 ds->dragpoint = ui->dragpoint;
1652 ds->cursorpoint = ui->cursorpoint;
1498 ds->bg = bg; 1653 ds->bg = bg;
1499 1654
1500 game_compute_size(&state->params, ds->tilesize, &w, &h); 1655 game_compute_size(&state->params, ds->tilesize, ui, &w, &h);
1501
1502 clip(dr, 0, 0, w, h);
1503 draw_rect(dr, 0, 0, w, h, bg); 1656 draw_rect(dr, 0, 0, w, h, bg);
1504 1657
1505 /* 1658 /*
@@ -1508,23 +1661,28 @@ static void game_redraw(drawing *dr, game_drawstate *ds,
1508 1661
1509 for (i = 0; (e = index234(state->graph->edges, i)) != NULL; i++) { 1662 for (i = 0; (e = index234(state->graph->edges, i)) != NULL; i++) {
1510 draw_line(dr, ds->x[e->a], ds->y[e->a], ds->x[e->b], ds->y[e->b], 1663 draw_line(dr, ds->x[e->a], ds->y[e->a], ds->x[e->b], ds->y[e->b],
1511#ifdef SHOW_CROSSINGS 1664 ui->show_crossed_edges &&
1512 (oldstate?oldstate:state)->crosses[i] ? 1665 (oldstate?oldstate:state)->crosses[i] ?
1513 COL_CROSSEDLINE : 1666 COL_CROSSEDLINE : COL_LINE);
1514#endif
1515 COL_LINE);
1516 } 1667 }
1517 1668
1518 /* 1669 /*
1519 * Draw the points. 1670 * Draw the points.
1520 * 1671 *
1521 * When dragging, we should not only vary the colours, but 1672 * When dragging, we vary the point colours to highlight the drag
1522 * leave the point being dragged until last. 1673 * point and neighbour points. The draw order is defined so that
1674 * the most relevant points (i.e., the dragged point and cursor
1675 * point) are drawn last, so they appear on top of other points.
1523 */ 1676 */
1677 static const int draw_order[] = {
1678 COL_POINT,
1679 COL_NEIGHBOUR,
1680 COL_CURSORPOINT,
1681 COL_DRAGPOINT
1682 };
1683
1524 for (j = 0; j < 4; j++) { 1684 for (j = 0; j < 4; j++) {
1525 int thisc = (j == 0 ? COL_POINT : 1685 int thisc = draw_order[j];
1526 (j == 1 ? COL_NEIGHBOUR :
1527 j == 2 ? COL_CURSORPOINT : COL_DRAGPOINT));
1528 for (i = 0; i < state->params.n; i++) { 1686 for (i = 0; i < state->params.n; i++) {
1529 int c; 1687 int c;
1530 1688
@@ -1540,19 +1698,17 @@ static void game_redraw(drawing *dr, game_drawstate *ds,
1540 } 1698 }
1541 1699
1542 if (c == thisc) { 1700 if (c == thisc) {
1543#ifdef VERTEX_NUMBERS 1701 if (ui->vertex_numbers) {
1544 draw_circle(dr, ds->x[i], ds->y[i], DRAG_THRESHOLD, bg, bg); 1702 char buf[80];
1545 { 1703 draw_circle(dr, ds->x[i], ds->y[i], DRAG_THRESHOLD, bg, bg);
1546 char buf[80]; 1704 sprintf(buf, "%d", i);
1547 sprintf(buf, "%d", i); 1705 draw_text(dr, ds->x[i], ds->y[i], FONT_VARIABLE,
1548 draw_text(dr, ds->x[i], ds->y[i], FONT_VARIABLE,
1549 DRAG_THRESHOLD*3/2, 1706 DRAG_THRESHOLD*3/2,
1550 ALIGN_VCENTRE|ALIGN_HCENTRE, c, buf); 1707 ALIGN_VCENTRE|ALIGN_HCENTRE, c, buf);
1551 } 1708 } else {
1552#else 1709 draw_circle(dr, ds->x[i], ds->y[i], CIRCLE_RADIUS,
1553 draw_circle(dr, ds->x[i], ds->y[i], CIRCLE_RADIUS, 1710 c, COL_OUTLINE);
1554 c, COL_OUTLINE); 1711 }
1555#endif
1556 } 1712 }
1557 } 1713 }
1558 } 1714 }
@@ -1587,17 +1743,20 @@ static void game_get_cursor_location(const game_ui *ui,
1587 const game_params *params, 1743 const game_params *params,
1588 int *x, int *y, int *w, int *h) 1744 int *x, int *y, int *w, int *h)
1589{ 1745{
1590 if(ui->dragpoint >= 0 || ui->cursorpoint >= 0) { 1746 point pt;
1591 int idx = (ui->dragpoint >= 0) ? ui->dragpoint : ui->cursorpoint; 1747 if(ui->dragpoint >= 0)
1748 pt = ui->newpoint;
1749 else if(ui->cursorpoint >= 0)
1750 pt = state->pts[ui->cursorpoint];
1751 else
1752 return;
1592 1753
1593 int cx, cy; 1754 int cx = ds->tilesize * pt.x / pt.d;
1594 cx = ds->x[idx]; 1755 int cy = ds->tilesize * pt.y / pt.d;
1595 cy = ds->y[idx];
1596 1756
1597 *x = cx - CIRCLE_RADIUS; 1757 *x = cx - CIRCLE_RADIUS;
1598 *y = cy - CIRCLE_RADIUS; 1758 *y = cy - CIRCLE_RADIUS;
1599 *w = *h = 2 * CIRCLE_RADIUS + 1; 1759 *w = *h = 2 * CIRCLE_RADIUS + 1;
1600 }
1601} 1760}
1602 1761
1603static int game_status(const game_state *state) 1762static int game_status(const game_state *state)
@@ -1605,19 +1764,6 @@ static int game_status(const game_state *state)
1605 return state->completed ? +1 : 0; 1764 return state->completed ? +1 : 0;
1606} 1765}
1607 1766
1608static bool game_timing_state(const game_state *state, game_ui *ui)
1609{
1610 return true;
1611}
1612
1613static void game_print_size(const game_params *params, float *x, float *y)
1614{
1615}
1616
1617static void game_print(drawing *dr, const game_state *state, int tilesize)
1618{
1619}
1620
1621#ifdef COMBINED 1767#ifdef COMBINED
1622#define thegame untangle 1768#define thegame untangle
1623#endif 1769#endif
@@ -1638,13 +1784,15 @@ const struct game thegame = {
1638 dup_game, 1784 dup_game,
1639 free_game, 1785 free_game,
1640 true, solve_game, 1786 true, solve_game,
1641 false, game_can_format_as_text_now, game_text_format, 1787 false, NULL, NULL, /* can_format_as_text_now, text_format */
1788 get_prefs, set_prefs,
1642 new_ui, 1789 new_ui,
1643 free_ui, 1790 free_ui,
1644 encode_ui, 1791 NULL, /* encode_ui */
1645 decode_ui, 1792 NULL, /* decode_ui */
1646 NULL, /* game_request_keys */ 1793 NULL, /* game_request_keys */
1647 game_changed_state, 1794 game_changed_state,
1795 NULL, /* current_key_label */
1648 interpret_move, 1796 interpret_move,
1649 execute_move, 1797 execute_move,
1650 PREFERRED_TILESIZE, game_compute_size, game_set_size, 1798 PREFERRED_TILESIZE, game_compute_size, game_set_size,
@@ -1656,8 +1804,8 @@ const struct game thegame = {
1656 game_flash_length, 1804 game_flash_length,
1657 game_get_cursor_location, 1805 game_get_cursor_location,
1658 game_status, 1806 game_status,
1659 false, false, game_print_size, game_print, 1807 false, false, NULL, NULL, /* print_size, print */
1660 false, /* wants_statusbar */ 1808 false, /* wants_statusbar */
1661 false, game_timing_state, 1809 false, NULL, /* timing_state */
1662 SOLVE_ANIMATES, /* flags */ 1810 SOLVE_ANIMATES, /* flags */
1663}; 1811};