diff options
Diffstat (limited to 'apps/plugins/puzzles/src/cube.c')
-rw-r--r-- | apps/plugins/puzzles/src/cube.c | 1773 |
1 files changed, 1773 insertions, 0 deletions
diff --git a/apps/plugins/puzzles/src/cube.c b/apps/plugins/puzzles/src/cube.c new file mode 100644 index 0000000000..a30dc10b3f --- /dev/null +++ b/apps/plugins/puzzles/src/cube.c | |||
@@ -0,0 +1,1773 @@ | |||
1 | /* | ||
2 | * cube.c: Cube game. | ||
3 | */ | ||
4 | |||
5 | #include <stdio.h> | ||
6 | #include <stdlib.h> | ||
7 | #include <string.h> | ||
8 | #include <assert.h> | ||
9 | #include <ctype.h> | ||
10 | #include <math.h> | ||
11 | |||
12 | #include "puzzles.h" | ||
13 | |||
14 | #define MAXVERTICES 20 | ||
15 | #define MAXFACES 20 | ||
16 | #define MAXORDER 4 | ||
17 | struct solid { | ||
18 | int nvertices; | ||
19 | float vertices[MAXVERTICES * 3]; /* 3*npoints coordinates */ | ||
20 | int order; | ||
21 | int nfaces; | ||
22 | int faces[MAXFACES * MAXORDER]; /* order*nfaces point indices */ | ||
23 | float normals[MAXFACES * 3]; /* 3*npoints vector components */ | ||
24 | float shear; /* isometric shear for nice drawing */ | ||
25 | float border; /* border required around arena */ | ||
26 | }; | ||
27 | |||
28 | static const struct solid s_tetrahedron = { | ||
29 | 4, | ||
30 | { | ||
31 | 0.0F, -0.57735026919F, -0.20412414523F, | ||
32 | -0.5F, 0.28867513459F, -0.20412414523F, | ||
33 | 0.0F, -0.0F, 0.6123724357F, | ||
34 | 0.5F, 0.28867513459F, -0.20412414523F, | ||
35 | }, | ||
36 | 3, 4, | ||
37 | { | ||
38 | 0,2,1, 3,1,2, 2,0,3, 1,3,0 | ||
39 | }, | ||
40 | { | ||
41 | -0.816496580928F, -0.471404520791F, 0.333333333334F, | ||
42 | 0.0F, 0.942809041583F, 0.333333333333F, | ||
43 | 0.816496580928F, -0.471404520791F, 0.333333333334F, | ||
44 | 0.0F, 0.0F, -1.0F, | ||
45 | }, | ||
46 | 0.0F, 0.3F | ||
47 | }; | ||
48 | |||
49 | static const struct solid s_cube = { | ||
50 | 8, | ||
51 | { | ||
52 | -0.5F,-0.5F,-0.5F, -0.5F,-0.5F,+0.5F, | ||
53 | -0.5F,+0.5F,-0.5F, -0.5F,+0.5F,+0.5F, | ||
54 | +0.5F,-0.5F,-0.5F, +0.5F,-0.5F,+0.5F, | ||
55 | +0.5F,+0.5F,-0.5F, +0.5F,+0.5F,+0.5F, | ||
56 | }, | ||
57 | 4, 6, | ||
58 | { | ||
59 | 0,1,3,2, 1,5,7,3, 5,4,6,7, 4,0,2,6, 0,4,5,1, 3,7,6,2 | ||
60 | }, | ||
61 | { | ||
62 | -1.0F,0.0F,0.0F, 0.0F,0.0F,+1.0F, | ||
63 | +1.0F,0.0F,0.0F, 0.0F,0.0F,-1.0F, | ||
64 | 0.0F,-1.0F,0.0F, 0.0F,+1.0F,0.0F | ||
65 | }, | ||
66 | 0.3F, 0.5F | ||
67 | }; | ||
68 | |||
69 | static const struct solid s_octahedron = { | ||
70 | 6, | ||
71 | { | ||
72 | -0.5F, -0.28867513459472505F, 0.4082482904638664F, | ||
73 | 0.5F, 0.28867513459472505F, -0.4082482904638664F, | ||
74 | -0.5F, 0.28867513459472505F, -0.4082482904638664F, | ||
75 | 0.5F, -0.28867513459472505F, 0.4082482904638664F, | ||
76 | 0.0F, -0.57735026918945009F, -0.4082482904638664F, | ||
77 | 0.0F, 0.57735026918945009F, 0.4082482904638664F, | ||
78 | }, | ||
79 | 3, 8, | ||
80 | { | ||
81 | 4,0,2, 0,5,2, 0,4,3, 5,0,3, 1,4,2, 5,1,2, 4,1,3, 1,5,3 | ||
82 | }, | ||
83 | { | ||
84 | -0.816496580928F, -0.471404520791F, -0.333333333334F, | ||
85 | -0.816496580928F, 0.471404520791F, 0.333333333334F, | ||
86 | 0.0F, -0.942809041583F, 0.333333333333F, | ||
87 | 0.0F, 0.0F, 1.0F, | ||
88 | 0.0F, 0.0F, -1.0F, | ||
89 | 0.0F, 0.942809041583F, -0.333333333333F, | ||
90 | 0.816496580928F, -0.471404520791F, -0.333333333334F, | ||
91 | 0.816496580928F, 0.471404520791F, 0.333333333334F, | ||
92 | }, | ||
93 | 0.0F, 0.5F | ||
94 | }; | ||
95 | |||
96 | static const struct solid s_icosahedron = { | ||
97 | 12, | ||
98 | { | ||
99 | 0.0F, 0.57735026919F, 0.75576131408F, | ||
100 | 0.0F, -0.93417235896F, 0.17841104489F, | ||
101 | 0.0F, 0.93417235896F, -0.17841104489F, | ||
102 | 0.0F, -0.57735026919F, -0.75576131408F, | ||
103 | -0.5F, -0.28867513459F, 0.75576131408F, | ||
104 | -0.5F, 0.28867513459F, -0.75576131408F, | ||
105 | 0.5F, -0.28867513459F, 0.75576131408F, | ||
106 | 0.5F, 0.28867513459F, -0.75576131408F, | ||
107 | -0.80901699437F, 0.46708617948F, 0.17841104489F, | ||
108 | 0.80901699437F, 0.46708617948F, 0.17841104489F, | ||
109 | -0.80901699437F, -0.46708617948F, -0.17841104489F, | ||
110 | 0.80901699437F, -0.46708617948F, -0.17841104489F, | ||
111 | }, | ||
112 | 3, 20, | ||
113 | { | ||
114 | 8,0,2, 0,9,2, 1,10,3, 11,1,3, 0,4,6, | ||
115 | 4,1,6, 5,2,7, 3,5,7, 4,8,10, 8,5,10, | ||
116 | 9,6,11, 7,9,11, 0,8,4, 9,0,6, 10,1,4, | ||
117 | 1,11,6, 8,2,5, 2,9,7, 3,10,5, 11,3,7, | ||
118 | }, | ||
119 | { | ||
120 | -0.356822089773F, 0.87267799625F, 0.333333333333F, | ||
121 | 0.356822089773F, 0.87267799625F, 0.333333333333F, | ||
122 | -0.356822089773F, -0.87267799625F, -0.333333333333F, | ||
123 | 0.356822089773F, -0.87267799625F, -0.333333333333F, | ||
124 | -0.0F, 0.0F, 1.0F, | ||
125 | 0.0F, -0.666666666667F, 0.745355992501F, | ||
126 | 0.0F, 0.666666666667F, -0.745355992501F, | ||
127 | 0.0F, 0.0F, -1.0F, | ||
128 | -0.934172358963F, -0.12732200375F, 0.333333333333F, | ||
129 | -0.934172358963F, 0.12732200375F, -0.333333333333F, | ||
130 | 0.934172358963F, -0.12732200375F, 0.333333333333F, | ||
131 | 0.934172358963F, 0.12732200375F, -0.333333333333F, | ||
132 | -0.57735026919F, 0.333333333334F, 0.745355992501F, | ||
133 | 0.57735026919F, 0.333333333334F, 0.745355992501F, | ||
134 | -0.57735026919F, -0.745355992501F, 0.333333333334F, | ||
135 | 0.57735026919F, -0.745355992501F, 0.333333333334F, | ||
136 | -0.57735026919F, 0.745355992501F, -0.333333333334F, | ||
137 | 0.57735026919F, 0.745355992501F, -0.333333333334F, | ||
138 | -0.57735026919F, -0.333333333334F, -0.745355992501F, | ||
139 | 0.57735026919F, -0.333333333334F, -0.745355992501F, | ||
140 | }, | ||
141 | 0.0F, 0.8F | ||
142 | }; | ||
143 | |||
144 | enum { | ||
145 | TETRAHEDRON, CUBE, OCTAHEDRON, ICOSAHEDRON | ||
146 | }; | ||
147 | static const struct solid *solids[] = { | ||
148 | &s_tetrahedron, &s_cube, &s_octahedron, &s_icosahedron | ||
149 | }; | ||
150 | |||
151 | enum { | ||
152 | COL_BACKGROUND, | ||
153 | COL_BORDER, | ||
154 | COL_BLUE, | ||
155 | NCOLOURS | ||
156 | }; | ||
157 | |||
158 | enum { LEFT, RIGHT, UP, DOWN, UP_LEFT, UP_RIGHT, DOWN_LEFT, DOWN_RIGHT }; | ||
159 | |||
160 | #define PREFERRED_GRID_SCALE 48 | ||
161 | #define GRID_SCALE (ds->gridscale) | ||
162 | #define ROLLTIME 0.13F | ||
163 | |||
164 | #define SQ(x) ( (x) * (x) ) | ||
165 | |||
166 | #define MATMUL(ra,m,a) do { \ | ||
167 | float rx, ry, rz, xx = (a)[0], yy = (a)[1], zz = (a)[2], *mat = (m); \ | ||
168 | rx = mat[0] * xx + mat[3] * yy + mat[6] * zz; \ | ||
169 | ry = mat[1] * xx + mat[4] * yy + mat[7] * zz; \ | ||
170 | rz = mat[2] * xx + mat[5] * yy + mat[8] * zz; \ | ||
171 | (ra)[0] = rx; (ra)[1] = ry; (ra)[2] = rz; \ | ||
172 | } while (0) | ||
173 | |||
174 | #define APPROXEQ(x,y) ( SQ(x-y) < 0.1 ) | ||
175 | |||
176 | struct grid_square { | ||
177 | float x, y; | ||
178 | int npoints; | ||
179 | float points[8]; /* maximum */ | ||
180 | int directions[8]; /* bit masks showing point pairs */ | ||
181 | int flip; | ||
182 | int tetra_class; | ||
183 | }; | ||
184 | |||
185 | struct game_params { | ||
186 | int solid; | ||
187 | /* | ||
188 | * Grid dimensions. For a square grid these are width and | ||
189 | * height respectively; otherwise the grid is a hexagon, with | ||
190 | * the top side and the two lower diagonals having length d1 | ||
191 | * and the remaining three sides having length d2 (so that | ||
192 | * d1==d2 gives a regular hexagon, and d2==0 gives a triangle). | ||
193 | */ | ||
194 | int d1, d2; | ||
195 | }; | ||
196 | |||
197 | typedef struct game_grid game_grid; | ||
198 | struct game_grid { | ||
199 | int refcount; | ||
200 | struct grid_square *squares; | ||
201 | int nsquares; | ||
202 | }; | ||
203 | |||
204 | #define SET_SQUARE(state, i, val) \ | ||
205 | ((state)->bluemask[(i)/32] &= ~(1 << ((i)%32)), \ | ||
206 | (state)->bluemask[(i)/32] |= ((!!val) << ((i)%32))) | ||
207 | #define GET_SQUARE(state, i) \ | ||
208 | (((state)->bluemask[(i)/32] >> ((i)%32)) & 1) | ||
209 | |||
210 | struct game_state { | ||
211 | struct game_params params; | ||
212 | const struct solid *solid; | ||
213 | int *facecolours; | ||
214 | game_grid *grid; | ||
215 | unsigned long *bluemask; | ||
216 | int current; /* index of current grid square */ | ||
217 | int sgkey[2]; /* key-point indices into grid sq */ | ||
218 | int dgkey[2]; /* key-point indices into grid sq */ | ||
219 | int spkey[2]; /* key-point indices into polyhedron */ | ||
220 | int dpkey[2]; /* key-point indices into polyhedron */ | ||
221 | int previous; | ||
222 | float angle; | ||
223 | int completed; | ||
224 | int movecount; | ||
225 | }; | ||
226 | |||
227 | static game_params *default_params(void) | ||
228 | { | ||
229 | game_params *ret = snew(game_params); | ||
230 | |||
231 | ret->solid = CUBE; | ||
232 | ret->d1 = 4; | ||
233 | ret->d2 = 4; | ||
234 | |||
235 | return ret; | ||
236 | } | ||
237 | |||
238 | static int game_fetch_preset(int i, char **name, game_params **params) | ||
239 | { | ||
240 | game_params *ret = snew(game_params); | ||
241 | char *str; | ||
242 | |||
243 | switch (i) { | ||
244 | case 0: | ||
245 | str = "Cube"; | ||
246 | ret->solid = CUBE; | ||
247 | ret->d1 = 4; | ||
248 | ret->d2 = 4; | ||
249 | break; | ||
250 | case 1: | ||
251 | str = "Tetrahedron"; | ||
252 | ret->solid = TETRAHEDRON; | ||
253 | ret->d1 = 1; | ||
254 | ret->d2 = 2; | ||
255 | break; | ||
256 | case 2: | ||
257 | str = "Octahedron"; | ||
258 | ret->solid = OCTAHEDRON; | ||
259 | ret->d1 = 2; | ||
260 | ret->d2 = 2; | ||
261 | break; | ||
262 | case 3: | ||
263 | str = "Icosahedron"; | ||
264 | ret->solid = ICOSAHEDRON; | ||
265 | ret->d1 = 3; | ||
266 | ret->d2 = 3; | ||
267 | break; | ||
268 | default: | ||
269 | sfree(ret); | ||
270 | return FALSE; | ||
271 | } | ||
272 | |||
273 | *name = dupstr(str); | ||
274 | *params = ret; | ||
275 | return TRUE; | ||
276 | } | ||
277 | |||
278 | static void free_params(game_params *params) | ||
279 | { | ||
280 | sfree(params); | ||
281 | } | ||
282 | |||
283 | static game_params *dup_params(const game_params *params) | ||
284 | { | ||
285 | game_params *ret = snew(game_params); | ||
286 | *ret = *params; /* structure copy */ | ||
287 | return ret; | ||
288 | } | ||
289 | |||
290 | static void decode_params(game_params *ret, char const *string) | ||
291 | { | ||
292 | switch (*string) { | ||
293 | case 't': ret->solid = TETRAHEDRON; string++; break; | ||
294 | case 'c': ret->solid = CUBE; string++; break; | ||
295 | case 'o': ret->solid = OCTAHEDRON; string++; break; | ||
296 | case 'i': ret->solid = ICOSAHEDRON; string++; break; | ||
297 | default: break; | ||
298 | } | ||
299 | ret->d1 = ret->d2 = atoi(string); | ||
300 | while (*string && isdigit((unsigned char)*string)) string++; | ||
301 | if (*string == 'x') { | ||
302 | string++; | ||
303 | ret->d2 = atoi(string); | ||
304 | } | ||
305 | } | ||
306 | |||
307 | static char *encode_params(const game_params *params, int full) | ||
308 | { | ||
309 | char data[256]; | ||
310 | |||
311 | assert(params->solid >= 0 && params->solid < 4); | ||
312 | sprintf(data, "%c%dx%d", "tcoi"[params->solid], params->d1, params->d2); | ||
313 | |||
314 | return dupstr(data); | ||
315 | } | ||
316 | typedef void (*egc_callback)(void *, struct grid_square *); | ||
317 | |||
318 | static void enum_grid_squares(const game_params *params, egc_callback callback, | ||
319 | void *ctx) | ||
320 | { | ||
321 | const struct solid *solid = solids[params->solid]; | ||
322 | |||
323 | if (solid->order == 4) { | ||
324 | int x, y; | ||
325 | |||
326 | for (y = 0; y < params->d2; y++) | ||
327 | for (x = 0; x < params->d1; x++) { | ||
328 | struct grid_square sq; | ||
329 | |||
330 | sq.x = (float)x; | ||
331 | sq.y = (float)y; | ||
332 | sq.points[0] = x - 0.5F; | ||
333 | sq.points[1] = y - 0.5F; | ||
334 | sq.points[2] = x - 0.5F; | ||
335 | sq.points[3] = y + 0.5F; | ||
336 | sq.points[4] = x + 0.5F; | ||
337 | sq.points[5] = y + 0.5F; | ||
338 | sq.points[6] = x + 0.5F; | ||
339 | sq.points[7] = y - 0.5F; | ||
340 | sq.npoints = 4; | ||
341 | |||
342 | sq.directions[LEFT] = 0x03; /* 0,1 */ | ||
343 | sq.directions[RIGHT] = 0x0C; /* 2,3 */ | ||
344 | sq.directions[UP] = 0x09; /* 0,3 */ | ||
345 | sq.directions[DOWN] = 0x06; /* 1,2 */ | ||
346 | sq.directions[UP_LEFT] = 0; /* no diagonals in a square */ | ||
347 | sq.directions[UP_RIGHT] = 0; /* no diagonals in a square */ | ||
348 | sq.directions[DOWN_LEFT] = 0; /* no diagonals in a square */ | ||
349 | sq.directions[DOWN_RIGHT] = 0; /* no diagonals in a square */ | ||
350 | |||
351 | sq.flip = FALSE; | ||
352 | |||
353 | /* | ||
354 | * This is supremely irrelevant, but just to avoid | ||
355 | * having any uninitialised structure members... | ||
356 | */ | ||
357 | sq.tetra_class = 0; | ||
358 | |||
359 | callback(ctx, &sq); | ||
360 | } | ||
361 | } else { | ||
362 | int row, rowlen, other, i, firstix = -1; | ||
363 | float theight = (float)(sqrt(3) / 2.0); | ||
364 | |||
365 | for (row = 0; row < params->d1 + params->d2; row++) { | ||
366 | if (row < params->d2) { | ||
367 | other = +1; | ||
368 | rowlen = row + params->d1; | ||
369 | } else { | ||
370 | other = -1; | ||
371 | rowlen = 2*params->d2 + params->d1 - row; | ||
372 | } | ||
373 | |||
374 | /* | ||
375 | * There are `rowlen' down-pointing triangles. | ||
376 | */ | ||
377 | for (i = 0; i < rowlen; i++) { | ||
378 | struct grid_square sq; | ||
379 | int ix; | ||
380 | float x, y; | ||
381 | |||
382 | ix = (2 * i - (rowlen-1)); | ||
383 | x = ix * 0.5F; | ||
384 | y = theight * row; | ||
385 | sq.x = x; | ||
386 | sq.y = y + theight / 3; | ||
387 | sq.points[0] = x - 0.5F; | ||
388 | sq.points[1] = y; | ||
389 | sq.points[2] = x; | ||
390 | sq.points[3] = y + theight; | ||
391 | sq.points[4] = x + 0.5F; | ||
392 | sq.points[5] = y; | ||
393 | sq.npoints = 3; | ||
394 | |||
395 | sq.directions[LEFT] = 0x03; /* 0,1 */ | ||
396 | sq.directions[RIGHT] = 0x06; /* 1,2 */ | ||
397 | sq.directions[UP] = 0x05; /* 0,2 */ | ||
398 | sq.directions[DOWN] = 0; /* invalid move */ | ||
399 | |||
400 | /* | ||
401 | * Down-pointing triangle: both the up diagonals go | ||
402 | * up, and the down ones go left and right. | ||
403 | */ | ||
404 | sq.directions[UP_LEFT] = sq.directions[UP_RIGHT] = | ||
405 | sq.directions[UP]; | ||
406 | sq.directions[DOWN_LEFT] = sq.directions[LEFT]; | ||
407 | sq.directions[DOWN_RIGHT] = sq.directions[RIGHT]; | ||
408 | |||
409 | sq.flip = TRUE; | ||
410 | |||
411 | if (firstix < 0) | ||
412 | firstix = ix & 3; | ||
413 | ix -= firstix; | ||
414 | sq.tetra_class = ((row+(ix&1)) & 2) ^ (ix & 3); | ||
415 | |||
416 | callback(ctx, &sq); | ||
417 | } | ||
418 | |||
419 | /* | ||
420 | * There are `rowlen+other' up-pointing triangles. | ||
421 | */ | ||
422 | for (i = 0; i < rowlen+other; i++) { | ||
423 | struct grid_square sq; | ||
424 | int ix; | ||
425 | float x, y; | ||
426 | |||
427 | ix = (2 * i - (rowlen+other-1)); | ||
428 | x = ix * 0.5F; | ||
429 | y = theight * row; | ||
430 | sq.x = x; | ||
431 | sq.y = y + 2*theight / 3; | ||
432 | sq.points[0] = x + 0.5F; | ||
433 | sq.points[1] = y + theight; | ||
434 | sq.points[2] = x; | ||
435 | sq.points[3] = y; | ||
436 | sq.points[4] = x - 0.5F; | ||
437 | sq.points[5] = y + theight; | ||
438 | sq.npoints = 3; | ||
439 | |||
440 | sq.directions[LEFT] = 0x06; /* 1,2 */ | ||
441 | sq.directions[RIGHT] = 0x03; /* 0,1 */ | ||
442 | sq.directions[DOWN] = 0x05; /* 0,2 */ | ||
443 | sq.directions[UP] = 0; /* invalid move */ | ||
444 | |||
445 | /* | ||
446 | * Up-pointing triangle: both the down diagonals go | ||
447 | * down, and the up ones go left and right. | ||
448 | */ | ||
449 | sq.directions[DOWN_LEFT] = sq.directions[DOWN_RIGHT] = | ||
450 | sq.directions[DOWN]; | ||
451 | sq.directions[UP_LEFT] = sq.directions[LEFT]; | ||
452 | sq.directions[UP_RIGHT] = sq.directions[RIGHT]; | ||
453 | |||
454 | sq.flip = FALSE; | ||
455 | |||
456 | if (firstix < 0) | ||
457 | firstix = (ix - 1) & 3; | ||
458 | ix -= firstix; | ||
459 | sq.tetra_class = ((row+(ix&1)) & 2) ^ (ix & 3); | ||
460 | |||
461 | callback(ctx, &sq); | ||
462 | } | ||
463 | } | ||
464 | } | ||
465 | } | ||
466 | |||
467 | static int grid_area(int d1, int d2, int order) | ||
468 | { | ||
469 | /* | ||
470 | * An NxM grid of squares has NM squares in it. | ||
471 | * | ||
472 | * A grid of triangles with dimensions A and B has a total of | ||
473 | * A^2 + B^2 + 4AB triangles in it. (You can divide it up into | ||
474 | * a side-A triangle containing A^2 subtriangles, a side-B | ||
475 | * triangle containing B^2, and two congruent parallelograms, | ||
476 | * each with side lengths A and B, each therefore containing AB | ||
477 | * two-triangle rhombuses.) | ||
478 | */ | ||
479 | if (order == 4) | ||
480 | return d1 * d2; | ||
481 | else | ||
482 | return d1*d1 + d2*d2 + 4*d1*d2; | ||
483 | } | ||
484 | |||
485 | static config_item *game_configure(const game_params *params) | ||
486 | { | ||
487 | config_item *ret = snewn(4, config_item); | ||
488 | char buf[80]; | ||
489 | |||
490 | ret[0].name = "Type of solid"; | ||
491 | ret[0].type = C_CHOICES; | ||
492 | ret[0].sval = ":Tetrahedron:Cube:Octahedron:Icosahedron"; | ||
493 | ret[0].ival = params->solid; | ||
494 | |||
495 | ret[1].name = "Width / top"; | ||
496 | ret[1].type = C_STRING; | ||
497 | sprintf(buf, "%d", params->d1); | ||
498 | ret[1].sval = dupstr(buf); | ||
499 | ret[1].ival = 0; | ||
500 | |||
501 | ret[2].name = "Height / bottom"; | ||
502 | ret[2].type = C_STRING; | ||
503 | sprintf(buf, "%d", params->d2); | ||
504 | ret[2].sval = dupstr(buf); | ||
505 | ret[2].ival = 0; | ||
506 | |||
507 | ret[3].name = NULL; | ||
508 | ret[3].type = C_END; | ||
509 | ret[3].sval = NULL; | ||
510 | ret[3].ival = 0; | ||
511 | |||
512 | return ret; | ||
513 | } | ||
514 | |||
515 | static game_params *custom_params(const config_item *cfg) | ||
516 | { | ||
517 | game_params *ret = snew(game_params); | ||
518 | |||
519 | ret->solid = cfg[0].ival; | ||
520 | ret->d1 = atoi(cfg[1].sval); | ||
521 | ret->d2 = atoi(cfg[2].sval); | ||
522 | |||
523 | return ret; | ||
524 | } | ||
525 | |||
526 | static void count_grid_square_callback(void *ctx, struct grid_square *sq) | ||
527 | { | ||
528 | int *classes = (int *)ctx; | ||
529 | int thisclass; | ||
530 | |||
531 | if (classes[4] == 4) | ||
532 | thisclass = sq->tetra_class; | ||
533 | else if (classes[4] == 2) | ||
534 | thisclass = sq->flip; | ||
535 | else | ||
536 | thisclass = 0; | ||
537 | |||
538 | classes[thisclass]++; | ||
539 | } | ||
540 | |||
541 | static char *validate_params(const game_params *params, int full) | ||
542 | { | ||
543 | int classes[5]; | ||
544 | int i; | ||
545 | |||
546 | if (params->solid < 0 || params->solid >= lenof(solids)) | ||
547 | return "Unrecognised solid type"; | ||
548 | |||
549 | if (solids[params->solid]->order == 4) { | ||
550 | if (params->d1 <= 0 || params->d2 <= 0) | ||
551 | return "Both grid dimensions must be greater than zero"; | ||
552 | } else { | ||
553 | if (params->d1 <= 0 && params->d2 <= 0) | ||
554 | return "At least one grid dimension must be greater than zero"; | ||
555 | } | ||
556 | |||
557 | for (i = 0; i < 4; i++) | ||
558 | classes[i] = 0; | ||
559 | if (params->solid == TETRAHEDRON) | ||
560 | classes[4] = 4; | ||
561 | else if (params->solid == OCTAHEDRON) | ||
562 | classes[4] = 2; | ||
563 | else | ||
564 | classes[4] = 1; | ||
565 | enum_grid_squares(params, count_grid_square_callback, classes); | ||
566 | |||
567 | for (i = 0; i < classes[4]; i++) | ||
568 | if (classes[i] < solids[params->solid]->nfaces / classes[4]) | ||
569 | return "Not enough grid space to place all blue faces"; | ||
570 | |||
571 | if (grid_area(params->d1, params->d2, solids[params->solid]->order) < | ||
572 | solids[params->solid]->nfaces + 1) | ||
573 | return "Not enough space to place the solid on an empty square"; | ||
574 | |||
575 | return NULL; | ||
576 | } | ||
577 | |||
578 | struct grid_data { | ||
579 | int *gridptrs[4]; | ||
580 | int nsquares[4]; | ||
581 | int nclasses; | ||
582 | int squareindex; | ||
583 | }; | ||
584 | |||
585 | static void classify_grid_square_callback(void *ctx, struct grid_square *sq) | ||
586 | { | ||
587 | struct grid_data *data = (struct grid_data *)ctx; | ||
588 | int thisclass; | ||
589 | |||
590 | if (data->nclasses == 4) | ||
591 | thisclass = sq->tetra_class; | ||
592 | else if (data->nclasses == 2) | ||
593 | thisclass = sq->flip; | ||
594 | else | ||
595 | thisclass = 0; | ||
596 | |||
597 | data->gridptrs[thisclass][data->nsquares[thisclass]++] = | ||
598 | data->squareindex++; | ||
599 | } | ||
600 | |||
601 | static char *new_game_desc(const game_params *params, random_state *rs, | ||
602 | char **aux, int interactive) | ||
603 | { | ||
604 | struct grid_data data; | ||
605 | int i, j, k, m, area, facesperclass; | ||
606 | int *flags; | ||
607 | char *desc, *p; | ||
608 | |||
609 | /* | ||
610 | * Enumerate the grid squares, dividing them into equivalence | ||
611 | * classes as appropriate. (For the tetrahedron, there is one | ||
612 | * equivalence class for each face; for the octahedron there | ||
613 | * are two classes; for the other two solids there's only one.) | ||
614 | */ | ||
615 | |||
616 | area = grid_area(params->d1, params->d2, solids[params->solid]->order); | ||
617 | if (params->solid == TETRAHEDRON) | ||
618 | data.nclasses = 4; | ||
619 | else if (params->solid == OCTAHEDRON) | ||
620 | data.nclasses = 2; | ||
621 | else | ||
622 | data.nclasses = 1; | ||
623 | data.gridptrs[0] = snewn(data.nclasses * area, int); | ||
624 | for (i = 0; i < data.nclasses; i++) { | ||
625 | data.gridptrs[i] = data.gridptrs[0] + i * area; | ||
626 | data.nsquares[i] = 0; | ||
627 | } | ||
628 | data.squareindex = 0; | ||
629 | enum_grid_squares(params, classify_grid_square_callback, &data); | ||
630 | |||
631 | facesperclass = solids[params->solid]->nfaces / data.nclasses; | ||
632 | |||
633 | for (i = 0; i < data.nclasses; i++) | ||
634 | assert(data.nsquares[i] >= facesperclass); | ||
635 | assert(data.squareindex == area); | ||
636 | |||
637 | /* | ||
638 | * So now we know how many faces to allocate in each class. Get | ||
639 | * on with it. | ||
640 | */ | ||
641 | flags = snewn(area, int); | ||
642 | for (i = 0; i < area; i++) | ||
643 | flags[i] = FALSE; | ||
644 | |||
645 | for (i = 0; i < data.nclasses; i++) { | ||
646 | for (j = 0; j < facesperclass; j++) { | ||
647 | int n = random_upto(rs, data.nsquares[i]); | ||
648 | |||
649 | assert(!flags[data.gridptrs[i][n]]); | ||
650 | flags[data.gridptrs[i][n]] = TRUE; | ||
651 | |||
652 | /* | ||
653 | * Move everything else up the array. I ought to use a | ||
654 | * better data structure for this, but for such small | ||
655 | * numbers it hardly seems worth the effort. | ||
656 | */ | ||
657 | while (n < data.nsquares[i]-1) { | ||
658 | data.gridptrs[i][n] = data.gridptrs[i][n+1]; | ||
659 | n++; | ||
660 | } | ||
661 | data.nsquares[i]--; | ||
662 | } | ||
663 | } | ||
664 | |||
665 | /* | ||
666 | * Now we know precisely which squares are blue. Encode this | ||
667 | * information in hex. While we're looping over this, collect | ||
668 | * the non-blue squares into a list in the now-unused gridptrs | ||
669 | * array. | ||
670 | */ | ||
671 | desc = snewn(area / 4 + 40, char); | ||
672 | p = desc; | ||
673 | j = 0; | ||
674 | k = 8; | ||
675 | m = 0; | ||
676 | for (i = 0; i < area; i++) { | ||
677 | if (flags[i]) { | ||
678 | j |= k; | ||
679 | } else { | ||
680 | data.gridptrs[0][m++] = i; | ||
681 | } | ||
682 | k >>= 1; | ||
683 | if (!k) { | ||
684 | *p++ = "0123456789ABCDEF"[j]; | ||
685 | k = 8; | ||
686 | j = 0; | ||
687 | } | ||
688 | } | ||
689 | if (k != 8) | ||
690 | *p++ = "0123456789ABCDEF"[j]; | ||
691 | |||
692 | /* | ||
693 | * Choose a non-blue square for the polyhedron. | ||
694 | */ | ||
695 | sprintf(p, ",%d", data.gridptrs[0][random_upto(rs, m)]); | ||
696 | |||
697 | sfree(data.gridptrs[0]); | ||
698 | sfree(flags); | ||
699 | |||
700 | return desc; | ||
701 | } | ||
702 | |||
703 | static void add_grid_square_callback(void *ctx, struct grid_square *sq) | ||
704 | { | ||
705 | game_grid *grid = (game_grid *)ctx; | ||
706 | |||
707 | grid->squares[grid->nsquares++] = *sq; /* structure copy */ | ||
708 | } | ||
709 | |||
710 | static int lowest_face(const struct solid *solid) | ||
711 | { | ||
712 | int i, j, best; | ||
713 | float zmin; | ||
714 | |||
715 | best = 0; | ||
716 | zmin = 0.0; | ||
717 | for (i = 0; i < solid->nfaces; i++) { | ||
718 | float z = 0; | ||
719 | |||
720 | for (j = 0; j < solid->order; j++) { | ||
721 | int f = solid->faces[i*solid->order + j]; | ||
722 | z += solid->vertices[f*3+2]; | ||
723 | } | ||
724 | |||
725 | if (i == 0 || zmin > z) { | ||
726 | zmin = z; | ||
727 | best = i; | ||
728 | } | ||
729 | } | ||
730 | |||
731 | return best; | ||
732 | } | ||
733 | |||
734 | static int align_poly(const struct solid *solid, struct grid_square *sq, | ||
735 | int *pkey) | ||
736 | { | ||
737 | float zmin; | ||
738 | int i, j; | ||
739 | int flip = (sq->flip ? -1 : +1); | ||
740 | |||
741 | /* | ||
742 | * First, find the lowest z-coordinate present in the solid. | ||
743 | */ | ||
744 | zmin = 0.0; | ||
745 | for (i = 0; i < solid->nvertices; i++) | ||
746 | if (zmin > solid->vertices[i*3+2]) | ||
747 | zmin = solid->vertices[i*3+2]; | ||
748 | |||
749 | /* | ||
750 | * Now go round the grid square. For each point in the grid | ||
751 | * square, we're looking for a point of the polyhedron with the | ||
752 | * same x- and y-coordinates (relative to the square's centre), | ||
753 | * and z-coordinate equal to zmin (near enough). | ||
754 | */ | ||
755 | for (j = 0; j < sq->npoints; j++) { | ||
756 | int matches, index; | ||
757 | |||
758 | matches = 0; | ||
759 | index = -1; | ||
760 | |||
761 | for (i = 0; i < solid->nvertices; i++) { | ||
762 | float dist = 0; | ||
763 | |||
764 | dist += SQ(solid->vertices[i*3+0] * flip - sq->points[j*2+0] + sq->x); | ||
765 | dist += SQ(solid->vertices[i*3+1] * flip - sq->points[j*2+1] + sq->y); | ||
766 | dist += SQ(solid->vertices[i*3+2] - zmin); | ||
767 | |||
768 | if (dist < 0.1) { | ||
769 | matches++; | ||
770 | index = i; | ||
771 | } | ||
772 | } | ||
773 | |||
774 | if (matches != 1 || index < 0) | ||
775 | return FALSE; | ||
776 | pkey[j] = index; | ||
777 | } | ||
778 | |||
779 | return TRUE; | ||
780 | } | ||
781 | |||
782 | static void flip_poly(struct solid *solid, int flip) | ||
783 | { | ||
784 | int i; | ||
785 | |||
786 | if (flip) { | ||
787 | for (i = 0; i < solid->nvertices; i++) { | ||
788 | solid->vertices[i*3+0] *= -1; | ||
789 | solid->vertices[i*3+1] *= -1; | ||
790 | } | ||
791 | for (i = 0; i < solid->nfaces; i++) { | ||
792 | solid->normals[i*3+0] *= -1; | ||
793 | solid->normals[i*3+1] *= -1; | ||
794 | } | ||
795 | } | ||
796 | } | ||
797 | |||
798 | static struct solid *transform_poly(const struct solid *solid, int flip, | ||
799 | int key0, int key1, float angle) | ||
800 | { | ||
801 | struct solid *ret = snew(struct solid); | ||
802 | float vx, vy, ax, ay; | ||
803 | float vmatrix[9], amatrix[9], vmatrix2[9]; | ||
804 | int i; | ||
805 | |||
806 | *ret = *solid; /* structure copy */ | ||
807 | |||
808 | flip_poly(ret, flip); | ||
809 | |||
810 | /* | ||
811 | * Now rotate the polyhedron through the given angle. We must | ||
812 | * rotate about the Z-axis to bring the two vertices key0 and | ||
813 | * key1 into horizontal alignment, then rotate about the | ||
814 | * X-axis, then rotate back again. | ||
815 | */ | ||
816 | vx = ret->vertices[key1*3+0] - ret->vertices[key0*3+0]; | ||
817 | vy = ret->vertices[key1*3+1] - ret->vertices[key0*3+1]; | ||
818 | assert(APPROXEQ(vx*vx + vy*vy, 1.0)); | ||
819 | |||
820 | vmatrix[0] = vx; vmatrix[3] = vy; vmatrix[6] = 0; | ||
821 | vmatrix[1] = -vy; vmatrix[4] = vx; vmatrix[7] = 0; | ||
822 | vmatrix[2] = 0; vmatrix[5] = 0; vmatrix[8] = 1; | ||
823 | |||
824 | ax = (float)cos(angle); | ||
825 | ay = (float)sin(angle); | ||
826 | |||
827 | amatrix[0] = 1; amatrix[3] = 0; amatrix[6] = 0; | ||
828 | amatrix[1] = 0; amatrix[4] = ax; amatrix[7] = ay; | ||
829 | amatrix[2] = 0; amatrix[5] = -ay; amatrix[8] = ax; | ||
830 | |||
831 | memcpy(vmatrix2, vmatrix, sizeof(vmatrix)); | ||
832 | vmatrix2[1] = vy; | ||
833 | vmatrix2[3] = -vy; | ||
834 | |||
835 | for (i = 0; i < ret->nvertices; i++) { | ||
836 | MATMUL(ret->vertices + 3*i, vmatrix, ret->vertices + 3*i); | ||
837 | MATMUL(ret->vertices + 3*i, amatrix, ret->vertices + 3*i); | ||
838 | MATMUL(ret->vertices + 3*i, vmatrix2, ret->vertices + 3*i); | ||
839 | } | ||
840 | for (i = 0; i < ret->nfaces; i++) { | ||
841 | MATMUL(ret->normals + 3*i, vmatrix, ret->normals + 3*i); | ||
842 | MATMUL(ret->normals + 3*i, amatrix, ret->normals + 3*i); | ||
843 | MATMUL(ret->normals + 3*i, vmatrix2, ret->normals + 3*i); | ||
844 | } | ||
845 | |||
846 | return ret; | ||
847 | } | ||
848 | |||
849 | static char *validate_desc(const game_params *params, const char *desc) | ||
850 | { | ||
851 | int area = grid_area(params->d1, params->d2, solids[params->solid]->order); | ||
852 | int i, j; | ||
853 | |||
854 | i = (area + 3) / 4; | ||
855 | for (j = 0; j < i; j++) { | ||
856 | int c = desc[j]; | ||
857 | if (c >= '0' && c <= '9') continue; | ||
858 | if (c >= 'A' && c <= 'F') continue; | ||
859 | if (c >= 'a' && c <= 'f') continue; | ||
860 | return "Not enough hex digits at start of string"; | ||
861 | /* NB if desc[j]=='\0' that will also be caught here, so we're safe */ | ||
862 | } | ||
863 | |||
864 | if (desc[i] != ',') | ||
865 | return "Expected ',' after hex digits"; | ||
866 | |||
867 | i++; | ||
868 | do { | ||
869 | if (desc[i] < '0' || desc[i] > '9') | ||
870 | return "Expected decimal integer after ','"; | ||
871 | i++; | ||
872 | } while (desc[i]); | ||
873 | |||
874 | return NULL; | ||
875 | } | ||
876 | |||
877 | static game_state *new_game(midend *me, const game_params *params, | ||
878 | const char *desc) | ||
879 | { | ||
880 | game_grid *grid = snew(game_grid); | ||
881 | game_state *state = snew(game_state); | ||
882 | int area; | ||
883 | |||
884 | state->params = *params; /* structure copy */ | ||
885 | state->solid = solids[params->solid]; | ||
886 | |||
887 | area = grid_area(params->d1, params->d2, state->solid->order); | ||
888 | grid->squares = snewn(area, struct grid_square); | ||
889 | grid->nsquares = 0; | ||
890 | enum_grid_squares(params, add_grid_square_callback, grid); | ||
891 | assert(grid->nsquares == area); | ||
892 | state->grid = grid; | ||
893 | grid->refcount = 1; | ||
894 | |||
895 | state->facecolours = snewn(state->solid->nfaces, int); | ||
896 | memset(state->facecolours, 0, state->solid->nfaces * sizeof(int)); | ||
897 | |||
898 | state->bluemask = snewn((state->grid->nsquares + 31) / 32, unsigned long); | ||
899 | memset(state->bluemask, 0, (state->grid->nsquares + 31) / 32 * | ||
900 | sizeof(unsigned long)); | ||
901 | |||
902 | /* | ||
903 | * Set up the blue squares and polyhedron position according to | ||
904 | * the game description. | ||
905 | */ | ||
906 | { | ||
907 | const char *p = desc; | ||
908 | int i, j, v; | ||
909 | |||
910 | j = 8; | ||
911 | v = 0; | ||
912 | for (i = 0; i < state->grid->nsquares; i++) { | ||
913 | if (j == 8) { | ||
914 | v = *p++; | ||
915 | if (v >= '0' && v <= '9') | ||
916 | v -= '0'; | ||
917 | else if (v >= 'A' && v <= 'F') | ||
918 | v -= 'A' - 10; | ||
919 | else if (v >= 'a' && v <= 'f') | ||
920 | v -= 'a' - 10; | ||
921 | else | ||
922 | break; | ||
923 | } | ||
924 | if (v & j) | ||
925 | SET_SQUARE(state, i, TRUE); | ||
926 | j >>= 1; | ||
927 | if (j == 0) | ||
928 | j = 8; | ||
929 | } | ||
930 | |||
931 | if (*p == ',') | ||
932 | p++; | ||
933 | |||
934 | state->current = atoi(p); | ||
935 | if (state->current < 0 || state->current >= state->grid->nsquares) | ||
936 | state->current = 0; /* got to do _something_ */ | ||
937 | } | ||
938 | |||
939 | /* | ||
940 | * Align the polyhedron with its grid square and determine | ||
941 | * initial key points. | ||
942 | */ | ||
943 | { | ||
944 | int pkey[4]; | ||
945 | int ret; | ||
946 | |||
947 | ret = align_poly(state->solid, &state->grid->squares[state->current], pkey); | ||
948 | assert(ret); | ||
949 | |||
950 | state->dpkey[0] = state->spkey[0] = pkey[0]; | ||
951 | state->dpkey[1] = state->spkey[0] = pkey[1]; | ||
952 | state->dgkey[0] = state->sgkey[0] = 0; | ||
953 | state->dgkey[1] = state->sgkey[0] = 1; | ||
954 | } | ||
955 | |||
956 | state->previous = state->current; | ||
957 | state->angle = 0.0; | ||
958 | state->completed = 0; | ||
959 | state->movecount = 0; | ||
960 | |||
961 | return state; | ||
962 | } | ||
963 | |||
964 | static game_state *dup_game(const game_state *state) | ||
965 | { | ||
966 | game_state *ret = snew(game_state); | ||
967 | |||
968 | ret->params = state->params; /* structure copy */ | ||
969 | ret->solid = state->solid; | ||
970 | ret->facecolours = snewn(ret->solid->nfaces, int); | ||
971 | memcpy(ret->facecolours, state->facecolours, | ||
972 | ret->solid->nfaces * sizeof(int)); | ||
973 | ret->current = state->current; | ||
974 | ret->grid = state->grid; | ||
975 | ret->grid->refcount++; | ||
976 | ret->bluemask = snewn((ret->grid->nsquares + 31) / 32, unsigned long); | ||
977 | memcpy(ret->bluemask, state->bluemask, (ret->grid->nsquares + 31) / 32 * | ||
978 | sizeof(unsigned long)); | ||
979 | ret->dpkey[0] = state->dpkey[0]; | ||
980 | ret->dpkey[1] = state->dpkey[1]; | ||
981 | ret->dgkey[0] = state->dgkey[0]; | ||
982 | ret->dgkey[1] = state->dgkey[1]; | ||
983 | ret->spkey[0] = state->spkey[0]; | ||
984 | ret->spkey[1] = state->spkey[1]; | ||
985 | ret->sgkey[0] = state->sgkey[0]; | ||
986 | ret->sgkey[1] = state->sgkey[1]; | ||
987 | ret->previous = state->previous; | ||
988 | ret->angle = state->angle; | ||
989 | ret->completed = state->completed; | ||
990 | ret->movecount = state->movecount; | ||
991 | |||
992 | return ret; | ||
993 | } | ||
994 | |||
995 | static void free_game(game_state *state) | ||
996 | { | ||
997 | if (--state->grid->refcount <= 0) { | ||
998 | sfree(state->grid->squares); | ||
999 | sfree(state->grid); | ||
1000 | } | ||
1001 | sfree(state->bluemask); | ||
1002 | sfree(state->facecolours); | ||
1003 | sfree(state); | ||
1004 | } | ||
1005 | |||
1006 | static char *solve_game(const game_state *state, const game_state *currstate, | ||
1007 | const char *aux, char **error) | ||
1008 | { | ||
1009 | return NULL; | ||
1010 | } | ||
1011 | |||
1012 | static int game_can_format_as_text_now(const game_params *params) | ||
1013 | { | ||
1014 | return TRUE; | ||
1015 | } | ||
1016 | |||
1017 | static char *game_text_format(const game_state *state) | ||
1018 | { | ||
1019 | return NULL; | ||
1020 | } | ||
1021 | |||
1022 | static game_ui *new_ui(const game_state *state) | ||
1023 | { | ||
1024 | return NULL; | ||
1025 | } | ||
1026 | |||
1027 | static void free_ui(game_ui *ui) | ||
1028 | { | ||
1029 | } | ||
1030 | |||
1031 | static char *encode_ui(const game_ui *ui) | ||
1032 | { | ||
1033 | return NULL; | ||
1034 | } | ||
1035 | |||
1036 | static void decode_ui(game_ui *ui, const char *encoding) | ||
1037 | { | ||
1038 | } | ||
1039 | |||
1040 | static void game_changed_state(game_ui *ui, const game_state *oldstate, | ||
1041 | const game_state *newstate) | ||
1042 | { | ||
1043 | } | ||
1044 | |||
1045 | struct game_drawstate { | ||
1046 | float gridscale; | ||
1047 | int ox, oy; /* pixel position of float origin */ | ||
1048 | }; | ||
1049 | |||
1050 | /* | ||
1051 | * Code shared between interpret_move() and execute_move(). | ||
1052 | */ | ||
1053 | static int find_move_dest(const game_state *from, int direction, | ||
1054 | int *skey, int *dkey) | ||
1055 | { | ||
1056 | int mask, dest, i, j; | ||
1057 | float points[4]; | ||
1058 | |||
1059 | /* | ||
1060 | * Find the two points in the current grid square which | ||
1061 | * correspond to this move. | ||
1062 | */ | ||
1063 | mask = from->grid->squares[from->current].directions[direction]; | ||
1064 | if (mask == 0) | ||
1065 | return -1; | ||
1066 | for (i = j = 0; i < from->grid->squares[from->current].npoints; i++) | ||
1067 | if (mask & (1 << i)) { | ||
1068 | points[j*2] = from->grid->squares[from->current].points[i*2]; | ||
1069 | points[j*2+1] = from->grid->squares[from->current].points[i*2+1]; | ||
1070 | skey[j] = i; | ||
1071 | j++; | ||
1072 | } | ||
1073 | assert(j == 2); | ||
1074 | |||
1075 | /* | ||
1076 | * Now find the other grid square which shares those points. | ||
1077 | * This is our move destination. | ||
1078 | */ | ||
1079 | dest = -1; | ||
1080 | for (i = 0; i < from->grid->nsquares; i++) | ||
1081 | if (i != from->current) { | ||
1082 | int match = 0; | ||
1083 | float dist; | ||
1084 | |||
1085 | for (j = 0; j < from->grid->squares[i].npoints; j++) { | ||
1086 | dist = (SQ(from->grid->squares[i].points[j*2] - points[0]) + | ||
1087 | SQ(from->grid->squares[i].points[j*2+1] - points[1])); | ||
1088 | if (dist < 0.1) | ||
1089 | dkey[match++] = j; | ||
1090 | dist = (SQ(from->grid->squares[i].points[j*2] - points[2]) + | ||
1091 | SQ(from->grid->squares[i].points[j*2+1] - points[3])); | ||
1092 | if (dist < 0.1) | ||
1093 | dkey[match++] = j; | ||
1094 | } | ||
1095 | |||
1096 | if (match == 2) { | ||
1097 | dest = i; | ||
1098 | break; | ||
1099 | } | ||
1100 | } | ||
1101 | |||
1102 | return dest; | ||
1103 | } | ||
1104 | |||
1105 | static char *interpret_move(const game_state *state, game_ui *ui, | ||
1106 | const game_drawstate *ds, | ||
1107 | int x, int y, int button) | ||
1108 | { | ||
1109 | int direction, mask, i; | ||
1110 | int skey[2], dkey[2]; | ||
1111 | |||
1112 | button = button & (~MOD_MASK | MOD_NUM_KEYPAD); | ||
1113 | |||
1114 | /* | ||
1115 | * Moves can be made with the cursor keys or numeric keypad, or | ||
1116 | * alternatively you can left-click and the polyhedron will | ||
1117 | * move in the general direction of the mouse pointer. | ||
1118 | */ | ||
1119 | if (button == CURSOR_UP || button == (MOD_NUM_KEYPAD | '8')) | ||
1120 | direction = UP; | ||
1121 | else if (button == CURSOR_DOWN || button == (MOD_NUM_KEYPAD | '2')) | ||
1122 | direction = DOWN; | ||
1123 | else if (button == CURSOR_LEFT || button == (MOD_NUM_KEYPAD | '4')) | ||
1124 | direction = LEFT; | ||
1125 | else if (button == CURSOR_RIGHT || button == (MOD_NUM_KEYPAD | '6')) | ||
1126 | direction = RIGHT; | ||
1127 | else if (button == (MOD_NUM_KEYPAD | '7')) | ||
1128 | direction = UP_LEFT; | ||
1129 | else if (button == (MOD_NUM_KEYPAD | '1')) | ||
1130 | direction = DOWN_LEFT; | ||
1131 | else if (button == (MOD_NUM_KEYPAD | '9')) | ||
1132 | direction = UP_RIGHT; | ||
1133 | else if (button == (MOD_NUM_KEYPAD | '3')) | ||
1134 | direction = DOWN_RIGHT; | ||
1135 | else if (button == LEFT_BUTTON) { | ||
1136 | /* | ||
1137 | * Find the bearing of the click point from the current | ||
1138 | * square's centre. | ||
1139 | */ | ||
1140 | int cx, cy; | ||
1141 | double angle; | ||
1142 | |||
1143 | cx = (int)(state->grid->squares[state->current].x * GRID_SCALE) + ds->ox; | ||
1144 | cy = (int)(state->grid->squares[state->current].y * GRID_SCALE) + ds->oy; | ||
1145 | |||
1146 | if (x == cx && y == cy) | ||
1147 | return NULL; /* clicked in exact centre! */ | ||
1148 | angle = atan2(y - cy, x - cx); | ||
1149 | |||
1150 | /* | ||
1151 | * There are three possibilities. | ||
1152 | * | ||
1153 | * - This square is a square, so we choose between UP, | ||
1154 | * DOWN, LEFT and RIGHT by dividing the available angle | ||
1155 | * at the 45-degree points. | ||
1156 | * | ||
1157 | * - This square is an up-pointing triangle, so we choose | ||
1158 | * between DOWN, LEFT and RIGHT by dividing into | ||
1159 | * 120-degree arcs. | ||
1160 | * | ||
1161 | * - This square is a down-pointing triangle, so we choose | ||
1162 | * between UP, LEFT and RIGHT in the inverse manner. | ||
1163 | * | ||
1164 | * Don't forget that since our y-coordinates increase | ||
1165 | * downwards, `angle' is measured _clockwise_ from the | ||
1166 | * x-axis, not anticlockwise as most mathematicians would | ||
1167 | * instinctively assume. | ||
1168 | */ | ||
1169 | if (state->grid->squares[state->current].npoints == 4) { | ||
1170 | /* Square. */ | ||
1171 | if (fabs(angle) > 3*PI/4) | ||
1172 | direction = LEFT; | ||
1173 | else if (fabs(angle) < PI/4) | ||
1174 | direction = RIGHT; | ||
1175 | else if (angle > 0) | ||
1176 | direction = DOWN; | ||
1177 | else | ||
1178 | direction = UP; | ||
1179 | } else if (state->grid->squares[state->current].directions[UP] == 0) { | ||
1180 | /* Up-pointing triangle. */ | ||
1181 | if (angle < -PI/2 || angle > 5*PI/6) | ||
1182 | direction = LEFT; | ||
1183 | else if (angle > PI/6) | ||
1184 | direction = DOWN; | ||
1185 | else | ||
1186 | direction = RIGHT; | ||
1187 | } else { | ||
1188 | /* Down-pointing triangle. */ | ||
1189 | assert(state->grid->squares[state->current].directions[DOWN] == 0); | ||
1190 | if (angle > PI/2 || angle < -5*PI/6) | ||
1191 | direction = LEFT; | ||
1192 | else if (angle < -PI/6) | ||
1193 | direction = UP; | ||
1194 | else | ||
1195 | direction = RIGHT; | ||
1196 | } | ||
1197 | } else | ||
1198 | return NULL; | ||
1199 | |||
1200 | mask = state->grid->squares[state->current].directions[direction]; | ||
1201 | if (mask == 0) | ||
1202 | return NULL; | ||
1203 | |||
1204 | /* | ||
1205 | * Translate diagonal directions into orthogonal ones. | ||
1206 | */ | ||
1207 | if (direction > DOWN) { | ||
1208 | for (i = LEFT; i <= DOWN; i++) | ||
1209 | if (state->grid->squares[state->current].directions[i] == mask) { | ||
1210 | direction = i; | ||
1211 | break; | ||
1212 | } | ||
1213 | assert(direction <= DOWN); | ||
1214 | } | ||
1215 | |||
1216 | if (find_move_dest(state, direction, skey, dkey) < 0) | ||
1217 | return NULL; | ||
1218 | |||
1219 | if (direction == LEFT) return dupstr("L"); | ||
1220 | if (direction == RIGHT) return dupstr("R"); | ||
1221 | if (direction == UP) return dupstr("U"); | ||
1222 | if (direction == DOWN) return dupstr("D"); | ||
1223 | |||
1224 | return NULL; /* should never happen */ | ||
1225 | } | ||
1226 | |||
1227 | static game_state *execute_move(const game_state *from, const char *move) | ||
1228 | { | ||
1229 | game_state *ret; | ||
1230 | float angle; | ||
1231 | struct solid *poly; | ||
1232 | int pkey[2]; | ||
1233 | int skey[2], dkey[2]; | ||
1234 | int i, j, dest; | ||
1235 | int direction; | ||
1236 | |||
1237 | switch (*move) { | ||
1238 | case 'L': direction = LEFT; break; | ||
1239 | case 'R': direction = RIGHT; break; | ||
1240 | case 'U': direction = UP; break; | ||
1241 | case 'D': direction = DOWN; break; | ||
1242 | default: return NULL; | ||
1243 | } | ||
1244 | |||
1245 | dest = find_move_dest(from, direction, skey, dkey); | ||
1246 | if (dest < 0) | ||
1247 | return NULL; | ||
1248 | |||
1249 | ret = dup_game(from); | ||
1250 | ret->current = dest; | ||
1251 | |||
1252 | /* | ||
1253 | * So we know what grid square we're aiming for, and we also | ||
1254 | * know the two key points (as indices in both the source and | ||
1255 | * destination grid squares) which are invariant between source | ||
1256 | * and destination. | ||
1257 | * | ||
1258 | * Next we must roll the polyhedron on to that square. So we | ||
1259 | * find the indices of the key points within the polyhedron's | ||
1260 | * vertex array, then use those in a call to transform_poly, | ||
1261 | * and align the result on the new grid square. | ||
1262 | */ | ||
1263 | { | ||
1264 | int all_pkey[4]; | ||
1265 | align_poly(from->solid, &from->grid->squares[from->current], all_pkey); | ||
1266 | pkey[0] = all_pkey[skey[0]]; | ||
1267 | pkey[1] = all_pkey[skey[1]]; | ||
1268 | /* | ||
1269 | * Now pkey[0] corresponds to skey[0] and dkey[0], and | ||
1270 | * likewise [1]. | ||
1271 | */ | ||
1272 | } | ||
1273 | |||
1274 | /* | ||
1275 | * Now find the angle through which to rotate the polyhedron. | ||
1276 | * Do this by finding the two faces that share the two vertices | ||
1277 | * we've found, and taking the dot product of their normals. | ||
1278 | */ | ||
1279 | { | ||
1280 | int f[2], nf = 0; | ||
1281 | float dp; | ||
1282 | |||
1283 | for (i = 0; i < from->solid->nfaces; i++) { | ||
1284 | int match = 0; | ||
1285 | for (j = 0; j < from->solid->order; j++) | ||
1286 | if (from->solid->faces[i*from->solid->order + j] == pkey[0] || | ||
1287 | from->solid->faces[i*from->solid->order + j] == pkey[1]) | ||
1288 | match++; | ||
1289 | if (match == 2) { | ||
1290 | assert(nf < 2); | ||
1291 | f[nf++] = i; | ||
1292 | } | ||
1293 | } | ||
1294 | |||
1295 | assert(nf == 2); | ||
1296 | |||
1297 | dp = 0; | ||
1298 | for (i = 0; i < 3; i++) | ||
1299 | dp += (from->solid->normals[f[0]*3+i] * | ||
1300 | from->solid->normals[f[1]*3+i]); | ||
1301 | angle = (float)acos(dp); | ||
1302 | } | ||
1303 | |||
1304 | /* | ||
1305 | * Now transform the polyhedron. We aren't entirely sure | ||
1306 | * whether we need to rotate through angle or -angle, and the | ||
1307 | * simplest way round this is to try both and see which one | ||
1308 | * aligns successfully! | ||
1309 | * | ||
1310 | * Unfortunately, _both_ will align successfully if this is a | ||
1311 | * cube, which won't tell us anything much. So for that | ||
1312 | * particular case, I resort to gross hackery: I simply negate | ||
1313 | * the angle before trying the alignment, depending on the | ||
1314 | * direction. Which directions work which way is determined by | ||
1315 | * pure trial and error. I said it was gross :-/ | ||
1316 | */ | ||
1317 | { | ||
1318 | int all_pkey[4]; | ||
1319 | int success; | ||
1320 | |||
1321 | if (from->solid->order == 4 && direction == UP) | ||
1322 | angle = -angle; /* HACK */ | ||
1323 | |||
1324 | poly = transform_poly(from->solid, | ||
1325 | from->grid->squares[from->current].flip, | ||
1326 | pkey[0], pkey[1], angle); | ||
1327 | flip_poly(poly, from->grid->squares[ret->current].flip); | ||
1328 | success = align_poly(poly, &from->grid->squares[ret->current], all_pkey); | ||
1329 | |||
1330 | if (!success) { | ||
1331 | sfree(poly); | ||
1332 | angle = -angle; | ||
1333 | poly = transform_poly(from->solid, | ||
1334 | from->grid->squares[from->current].flip, | ||
1335 | pkey[0], pkey[1], angle); | ||
1336 | flip_poly(poly, from->grid->squares[ret->current].flip); | ||
1337 | success = align_poly(poly, &from->grid->squares[ret->current], all_pkey); | ||
1338 | } | ||
1339 | |||
1340 | assert(success); | ||
1341 | } | ||
1342 | |||
1343 | /* | ||
1344 | * Now we have our rotated polyhedron, which we expect to be | ||
1345 | * exactly congruent to the one we started with - but with the | ||
1346 | * faces permuted. So we map that congruence and thereby figure | ||
1347 | * out how to permute the faces as a result of the polyhedron | ||
1348 | * having rolled. | ||
1349 | */ | ||
1350 | { | ||
1351 | int *newcolours = snewn(from->solid->nfaces, int); | ||
1352 | |||
1353 | for (i = 0; i < from->solid->nfaces; i++) | ||
1354 | newcolours[i] = -1; | ||
1355 | |||
1356 | for (i = 0; i < from->solid->nfaces; i++) { | ||
1357 | int nmatch = 0; | ||
1358 | |||
1359 | /* | ||
1360 | * Now go through the transformed polyhedron's faces | ||
1361 | * and figure out which one's normal is approximately | ||
1362 | * equal to this one. | ||
1363 | */ | ||
1364 | for (j = 0; j < poly->nfaces; j++) { | ||
1365 | float dist; | ||
1366 | int k; | ||
1367 | |||
1368 | dist = 0; | ||
1369 | |||
1370 | for (k = 0; k < 3; k++) | ||
1371 | dist += SQ(poly->normals[j*3+k] - | ||
1372 | from->solid->normals[i*3+k]); | ||
1373 | |||
1374 | if (APPROXEQ(dist, 0)) { | ||
1375 | nmatch++; | ||
1376 | newcolours[i] = ret->facecolours[j]; | ||
1377 | } | ||
1378 | } | ||
1379 | |||
1380 | assert(nmatch == 1); | ||
1381 | } | ||
1382 | |||
1383 | for (i = 0; i < from->solid->nfaces; i++) | ||
1384 | assert(newcolours[i] != -1); | ||
1385 | |||
1386 | sfree(ret->facecolours); | ||
1387 | ret->facecolours = newcolours; | ||
1388 | } | ||
1389 | |||
1390 | ret->movecount++; | ||
1391 | |||
1392 | /* | ||
1393 | * And finally, swap the colour between the bottom face of the | ||
1394 | * polyhedron and the face we've just landed on. | ||
1395 | * | ||
1396 | * We don't do this if the game is already complete, since we | ||
1397 | * allow the user to roll the fully blue polyhedron around the | ||
1398 | * grid as a feeble reward. | ||
1399 | */ | ||
1400 | if (!ret->completed) { | ||
1401 | i = lowest_face(from->solid); | ||
1402 | j = ret->facecolours[i]; | ||
1403 | ret->facecolours[i] = GET_SQUARE(ret, ret->current); | ||
1404 | SET_SQUARE(ret, ret->current, j); | ||
1405 | |||
1406 | /* | ||
1407 | * Detect game completion. | ||
1408 | */ | ||
1409 | j = 0; | ||
1410 | for (i = 0; i < ret->solid->nfaces; i++) | ||
1411 | if (ret->facecolours[i]) | ||
1412 | j++; | ||
1413 | if (j == ret->solid->nfaces) | ||
1414 | ret->completed = ret->movecount; | ||
1415 | } | ||
1416 | |||
1417 | sfree(poly); | ||
1418 | |||
1419 | /* | ||
1420 | * Align the normal polyhedron with its grid square, to get key | ||
1421 | * points for non-animated display. | ||
1422 | */ | ||
1423 | { | ||
1424 | int pkey[4]; | ||
1425 | int success; | ||
1426 | |||
1427 | success = align_poly(ret->solid, &ret->grid->squares[ret->current], pkey); | ||
1428 | assert(success); | ||
1429 | |||
1430 | ret->dpkey[0] = pkey[0]; | ||
1431 | ret->dpkey[1] = pkey[1]; | ||
1432 | ret->dgkey[0] = 0; | ||
1433 | ret->dgkey[1] = 1; | ||
1434 | } | ||
1435 | |||
1436 | |||
1437 | ret->spkey[0] = pkey[0]; | ||
1438 | ret->spkey[1] = pkey[1]; | ||
1439 | ret->sgkey[0] = skey[0]; | ||
1440 | ret->sgkey[1] = skey[1]; | ||
1441 | ret->previous = from->current; | ||
1442 | ret->angle = angle; | ||
1443 | |||
1444 | return ret; | ||
1445 | } | ||
1446 | |||
1447 | /* ---------------------------------------------------------------------- | ||
1448 | * Drawing routines. | ||
1449 | */ | ||
1450 | |||
1451 | struct bbox { | ||
1452 | float l, r, u, d; | ||
1453 | }; | ||
1454 | |||
1455 | static void find_bbox_callback(void *ctx, struct grid_square *sq) | ||
1456 | { | ||
1457 | struct bbox *bb = (struct bbox *)ctx; | ||
1458 | int i; | ||
1459 | |||
1460 | for (i = 0; i < sq->npoints; i++) { | ||
1461 | if (bb->l > sq->points[i*2]) bb->l = sq->points[i*2]; | ||
1462 | if (bb->r < sq->points[i*2]) bb->r = sq->points[i*2]; | ||
1463 | if (bb->u > sq->points[i*2+1]) bb->u = sq->points[i*2+1]; | ||
1464 | if (bb->d < sq->points[i*2+1]) bb->d = sq->points[i*2+1]; | ||
1465 | } | ||
1466 | } | ||
1467 | |||
1468 | static struct bbox find_bbox(const game_params *params) | ||
1469 | { | ||
1470 | struct bbox bb; | ||
1471 | |||
1472 | /* | ||
1473 | * These should be hugely more than the real bounding box will | ||
1474 | * be. | ||
1475 | */ | ||
1476 | bb.l = 2.0F * (params->d1 + params->d2); | ||
1477 | bb.r = -2.0F * (params->d1 + params->d2); | ||
1478 | bb.u = 2.0F * (params->d1 + params->d2); | ||
1479 | bb.d = -2.0F * (params->d1 + params->d2); | ||
1480 | enum_grid_squares(params, find_bbox_callback, &bb); | ||
1481 | |||
1482 | return bb; | ||
1483 | } | ||
1484 | |||
1485 | #define XSIZE(gs, bb, solid) \ | ||
1486 | ((int)(((bb).r - (bb).l + 2*(solid)->border) * gs)) | ||
1487 | #define YSIZE(gs, bb, solid) \ | ||
1488 | ((int)(((bb).d - (bb).u + 2*(solid)->border) * gs)) | ||
1489 | |||
1490 | static void game_compute_size(const game_params *params, int tilesize, | ||
1491 | int *x, int *y) | ||
1492 | { | ||
1493 | struct bbox bb = find_bbox(params); | ||
1494 | |||
1495 | *x = XSIZE(tilesize, bb, solids[params->solid]); | ||
1496 | *y = YSIZE(tilesize, bb, solids[params->solid]); | ||
1497 | } | ||
1498 | |||
1499 | static void game_set_size(drawing *dr, game_drawstate *ds, | ||
1500 | const game_params *params, int tilesize) | ||
1501 | { | ||
1502 | struct bbox bb = find_bbox(params); | ||
1503 | |||
1504 | ds->gridscale = (float)tilesize; | ||
1505 | ds->ox = (int)(-(bb.l - solids[params->solid]->border) * ds->gridscale); | ||
1506 | ds->oy = (int)(-(bb.u - solids[params->solid]->border) * ds->gridscale); | ||
1507 | } | ||
1508 | |||
1509 | static float *game_colours(frontend *fe, int *ncolours) | ||
1510 | { | ||
1511 | float *ret = snewn(3 * NCOLOURS, float); | ||
1512 | |||
1513 | frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]); | ||
1514 | |||
1515 | ret[COL_BORDER * 3 + 0] = 0.0; | ||
1516 | ret[COL_BORDER * 3 + 1] = 0.0; | ||
1517 | ret[COL_BORDER * 3 + 2] = 0.0; | ||
1518 | |||
1519 | ret[COL_BLUE * 3 + 0] = 0.0; | ||
1520 | ret[COL_BLUE * 3 + 1] = 0.0; | ||
1521 | ret[COL_BLUE * 3 + 2] = 1.0; | ||
1522 | |||
1523 | *ncolours = NCOLOURS; | ||
1524 | return ret; | ||
1525 | } | ||
1526 | |||
1527 | static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) | ||
1528 | { | ||
1529 | struct game_drawstate *ds = snew(struct game_drawstate); | ||
1530 | |||
1531 | ds->ox = ds->oy = 0; | ||
1532 | ds->gridscale = 0.0F; /* not decided yet */ | ||
1533 | |||
1534 | return ds; | ||
1535 | } | ||
1536 | |||
1537 | static void game_free_drawstate(drawing *dr, game_drawstate *ds) | ||
1538 | { | ||
1539 | sfree(ds); | ||
1540 | } | ||
1541 | |||
1542 | static void game_redraw(drawing *dr, game_drawstate *ds, | ||
1543 | const game_state *oldstate, const game_state *state, | ||
1544 | int dir, const game_ui *ui, | ||
1545 | float animtime, float flashtime) | ||
1546 | { | ||
1547 | int i, j; | ||
1548 | struct bbox bb = find_bbox(&state->params); | ||
1549 | struct solid *poly; | ||
1550 | const int *pkey, *gkey; | ||
1551 | float t[3]; | ||
1552 | float angle; | ||
1553 | int square; | ||
1554 | |||
1555 | draw_rect(dr, 0, 0, XSIZE(GRID_SCALE, bb, state->solid), | ||
1556 | YSIZE(GRID_SCALE, bb, state->solid), COL_BACKGROUND); | ||
1557 | |||
1558 | if (dir < 0) { | ||
1559 | const game_state *t; | ||
1560 | |||
1561 | /* | ||
1562 | * This is an Undo. So reverse the order of the states, and | ||
1563 | * run the roll timer backwards. | ||
1564 | */ | ||
1565 | assert(oldstate); | ||
1566 | |||
1567 | t = oldstate; | ||
1568 | oldstate = state; | ||
1569 | state = t; | ||
1570 | |||
1571 | animtime = ROLLTIME - animtime; | ||
1572 | } | ||
1573 | |||
1574 | if (!oldstate) { | ||
1575 | oldstate = state; | ||
1576 | angle = 0.0; | ||
1577 | square = state->current; | ||
1578 | pkey = state->dpkey; | ||
1579 | gkey = state->dgkey; | ||
1580 | } else { | ||
1581 | angle = state->angle * animtime / ROLLTIME; | ||
1582 | square = state->previous; | ||
1583 | pkey = state->spkey; | ||
1584 | gkey = state->sgkey; | ||
1585 | } | ||
1586 | state = oldstate; | ||
1587 | |||
1588 | for (i = 0; i < state->grid->nsquares; i++) { | ||
1589 | int coords[8]; | ||
1590 | |||
1591 | for (j = 0; j < state->grid->squares[i].npoints; j++) { | ||
1592 | coords[2*j] = ((int)(state->grid->squares[i].points[2*j] * GRID_SCALE) | ||
1593 | + ds->ox); | ||
1594 | coords[2*j+1] = ((int)(state->grid->squares[i].points[2*j+1]*GRID_SCALE) | ||
1595 | + ds->oy); | ||
1596 | } | ||
1597 | |||
1598 | draw_polygon(dr, coords, state->grid->squares[i].npoints, | ||
1599 | GET_SQUARE(state, i) ? COL_BLUE : COL_BACKGROUND, | ||
1600 | COL_BORDER); | ||
1601 | } | ||
1602 | |||
1603 | /* | ||
1604 | * Now compute and draw the polyhedron. | ||
1605 | */ | ||
1606 | poly = transform_poly(state->solid, state->grid->squares[square].flip, | ||
1607 | pkey[0], pkey[1], angle); | ||
1608 | |||
1609 | /* | ||
1610 | * Compute the translation required to align the two key points | ||
1611 | * on the polyhedron with the same key points on the current | ||
1612 | * face. | ||
1613 | */ | ||
1614 | for (i = 0; i < 3; i++) { | ||
1615 | float tc = 0.0; | ||
1616 | |||
1617 | for (j = 0; j < 2; j++) { | ||
1618 | float grid_coord; | ||
1619 | |||
1620 | if (i < 2) { | ||
1621 | grid_coord = | ||
1622 | state->grid->squares[square].points[gkey[j]*2+i]; | ||
1623 | } else { | ||
1624 | grid_coord = 0.0; | ||
1625 | } | ||
1626 | |||
1627 | tc += (grid_coord - poly->vertices[pkey[j]*3+i]); | ||
1628 | } | ||
1629 | |||
1630 | t[i] = tc / 2; | ||
1631 | } | ||
1632 | for (i = 0; i < poly->nvertices; i++) | ||
1633 | for (j = 0; j < 3; j++) | ||
1634 | poly->vertices[i*3+j] += t[j]; | ||
1635 | |||
1636 | /* | ||
1637 | * Now actually draw each face. | ||
1638 | */ | ||
1639 | for (i = 0; i < poly->nfaces; i++) { | ||
1640 | float points[8]; | ||
1641 | int coords[8]; | ||
1642 | |||
1643 | for (j = 0; j < poly->order; j++) { | ||
1644 | int f = poly->faces[i*poly->order + j]; | ||
1645 | points[j*2] = (poly->vertices[f*3+0] - | ||
1646 | poly->vertices[f*3+2] * poly->shear); | ||
1647 | points[j*2+1] = (poly->vertices[f*3+1] - | ||
1648 | poly->vertices[f*3+2] * poly->shear); | ||
1649 | } | ||
1650 | |||
1651 | for (j = 0; j < poly->order; j++) { | ||
1652 | coords[j*2] = (int)floor(points[j*2] * GRID_SCALE) + ds->ox; | ||
1653 | coords[j*2+1] = (int)floor(points[j*2+1] * GRID_SCALE) + ds->oy; | ||
1654 | } | ||
1655 | |||
1656 | /* | ||
1657 | * Find out whether these points are in a clockwise or | ||
1658 | * anticlockwise arrangement. If the latter, discard the | ||
1659 | * face because it's facing away from the viewer. | ||
1660 | * | ||
1661 | * This would involve fiddly winding-number stuff for a | ||
1662 | * general polygon, but for the simple parallelograms we'll | ||
1663 | * be seeing here, all we have to do is check whether the | ||
1664 | * corners turn right or left. So we'll take the vector | ||
1665 | * from point 0 to point 1, turn it right 90 degrees, | ||
1666 | * and check the sign of the dot product with that and the | ||
1667 | * next vector (point 1 to point 2). | ||
1668 | */ | ||
1669 | { | ||
1670 | float v1x = points[2]-points[0]; | ||
1671 | float v1y = points[3]-points[1]; | ||
1672 | float v2x = points[4]-points[2]; | ||
1673 | float v2y = points[5]-points[3]; | ||
1674 | float dp = v1x * v2y - v1y * v2x; | ||
1675 | |||
1676 | if (dp <= 0) | ||
1677 | continue; | ||
1678 | } | ||
1679 | |||
1680 | draw_polygon(dr, coords, poly->order, | ||
1681 | state->facecolours[i] ? COL_BLUE : COL_BACKGROUND, | ||
1682 | COL_BORDER); | ||
1683 | } | ||
1684 | sfree(poly); | ||
1685 | |||
1686 | draw_update(dr, 0, 0, XSIZE(GRID_SCALE, bb, state->solid), | ||
1687 | YSIZE(GRID_SCALE, bb, state->solid)); | ||
1688 | |||
1689 | /* | ||
1690 | * Update the status bar. | ||
1691 | */ | ||
1692 | { | ||
1693 | char statusbuf[256]; | ||
1694 | |||
1695 | sprintf(statusbuf, "%sMoves: %d", | ||
1696 | (state->completed ? "COMPLETED! " : ""), | ||
1697 | (state->completed ? state->completed : state->movecount)); | ||
1698 | |||
1699 | status_bar(dr, statusbuf); | ||
1700 | } | ||
1701 | } | ||
1702 | |||
1703 | static float game_anim_length(const game_state *oldstate, | ||
1704 | const game_state *newstate, int dir, game_ui *ui) | ||
1705 | { | ||
1706 | return ROLLTIME; | ||
1707 | } | ||
1708 | |||
1709 | static float game_flash_length(const game_state *oldstate, | ||
1710 | const game_state *newstate, int dir, game_ui *ui) | ||
1711 | { | ||
1712 | return 0.0F; | ||
1713 | } | ||
1714 | |||
1715 | static int game_status(const game_state *state) | ||
1716 | { | ||
1717 | return state->completed ? +1 : 0; | ||
1718 | } | ||
1719 | |||
1720 | static int game_timing_state(const game_state *state, game_ui *ui) | ||
1721 | { | ||
1722 | return TRUE; | ||
1723 | } | ||
1724 | |||
1725 | static void game_print_size(const game_params *params, float *x, float *y) | ||
1726 | { | ||
1727 | } | ||
1728 | |||
1729 | static void game_print(drawing *dr, const game_state *state, int tilesize) | ||
1730 | { | ||
1731 | } | ||
1732 | |||
1733 | #ifdef COMBINED | ||
1734 | #define thegame cube | ||
1735 | #endif | ||
1736 | |||
1737 | const struct game thegame = { | ||
1738 | "Cube", "games.cube", "cube", | ||
1739 | default_params, | ||
1740 | game_fetch_preset, NULL, | ||
1741 | decode_params, | ||
1742 | encode_params, | ||
1743 | free_params, | ||
1744 | dup_params, | ||
1745 | TRUE, game_configure, custom_params, | ||
1746 | validate_params, | ||
1747 | new_game_desc, | ||
1748 | validate_desc, | ||
1749 | new_game, | ||
1750 | dup_game, | ||
1751 | free_game, | ||
1752 | FALSE, solve_game, | ||
1753 | FALSE, game_can_format_as_text_now, game_text_format, | ||
1754 | new_ui, | ||
1755 | free_ui, | ||
1756 | encode_ui, | ||
1757 | decode_ui, | ||
1758 | game_changed_state, | ||
1759 | interpret_move, | ||
1760 | execute_move, | ||
1761 | PREFERRED_GRID_SCALE, game_compute_size, game_set_size, | ||
1762 | game_colours, | ||
1763 | game_new_drawstate, | ||
1764 | game_free_drawstate, | ||
1765 | game_redraw, | ||
1766 | game_anim_length, | ||
1767 | game_flash_length, | ||
1768 | game_status, | ||
1769 | FALSE, FALSE, game_print_size, game_print, | ||
1770 | TRUE, /* wants_statusbar */ | ||
1771 | FALSE, game_timing_state, | ||
1772 | 0, /* flags */ | ||
1773 | }; | ||