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