diff options
Diffstat (limited to 'apps/plugins/puzzles/src/untangle.c')
-rw-r--r-- | apps/plugins/puzzles/src/untangle.c | 512 |
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 | |||
219 | typedef struct { | 223 | typedef 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 | |||
304 | typedef 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 | |||
310 | static 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 | ||
425 | static bool isedge(tree234 *edges, int a, int b) | 446 | static bool isedge(tree234 *edges, int a, int b) |
@@ -441,8 +462,8 @@ typedef struct vertex { | |||
441 | 462 | ||
442 | static int vertcmpC(const void *av, const void *bv) | 463 | static 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 | ||
1028 | static bool game_can_format_as_text_now(const game_params *params) | ||
1029 | { | ||
1030 | return true; | ||
1031 | } | ||
1032 | |||
1033 | static char *game_text_format(const game_state *state) | ||
1034 | { | ||
1035 | return NULL; | ||
1036 | } | ||
1037 | |||
1038 | struct game_ui { | 1038 | struct 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 | ||
1051 | static game_ui *new_ui(const game_state *state) | 1072 | static 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 | ||
1060 | static void free_ui(game_ui *ui) | 1084 | static 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 | ||
1065 | static char *encode_ui(const game_ui *ui) | 1113 | static 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 | ||
1070 | static void decode_ui(game_ui *ui, const char *encoding) | 1120 | static void free_ui(game_ui *ui) |
1071 | { | 1121 | { |
1122 | sfree(ui); | ||
1072 | } | 1123 | } |
1073 | 1124 | ||
1074 | static void game_changed_state(game_ui *ui, const game_state *oldstate, | 1125 | static 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 | ||
1139 | static 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 | |||
1195 | static float normsq(point pt) { | ||
1196 | return (pt.x * pt.x + pt.y * pt.y) / (pt.d * pt.d); | ||
1197 | } | ||
1198 | |||
1088 | static char *interpret_move(const game_state *state, game_ui *ui, | 1199 | static 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 | ||
1297 | static game_state *execute_move(const game_state *state, const char *move) | 1451 | static 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 | ||
1336 | static void game_compute_size(const game_params *params, int tilesize, | 1490 | static 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 | ||
1603 | static int game_status(const game_state *state) | 1762 | static 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 | ||
1608 | static bool game_timing_state(const game_state *state, game_ui *ui) | ||
1609 | { | ||
1610 | return true; | ||
1611 | } | ||
1612 | |||
1613 | static void game_print_size(const game_params *params, float *x, float *y) | ||
1614 | { | ||
1615 | } | ||
1616 | |||
1617 | static 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 | }; |