diff options
author | Franklin Wei <git@fwei.tk> | 2017-04-29 18:21:56 -0400 |
---|---|---|
committer | Franklin Wei <git@fwei.tk> | 2017-04-29 18:24:42 -0400 |
commit | 881746789a489fad85aae8317555f73dbe261556 (patch) | |
tree | cec2946362c4698c8db3c10f3242ef546c2c22dd /apps/plugins/puzzles/src/blackbox.c | |
parent | 03dd4b92be7dcd5c8ab06da3810887060e06abd5 (diff) | |
download | rockbox-881746789a489fad85aae8317555f73dbe261556.tar.gz rockbox-881746789a489fad85aae8317555f73dbe261556.zip |
puzzles: refactor and resync with upstream
This brings puzzles up-to-date with upstream revision
2d333750272c3967cfd5cd3677572cddeaad5932, though certain changes made
by me, including cursor-only Untangle and some compilation fixes
remain. Upstream code has been moved to its separate subdirectory and
future syncs can be done by simply copying over the new sources.
Change-Id: Ia6506ca5f78c3627165ea6791d38db414ace0804
Diffstat (limited to 'apps/plugins/puzzles/src/blackbox.c')
-rw-r--r-- | apps/plugins/puzzles/src/blackbox.c | 1543 |
1 files changed, 1543 insertions, 0 deletions
diff --git a/apps/plugins/puzzles/src/blackbox.c b/apps/plugins/puzzles/src/blackbox.c new file mode 100644 index 0000000000..b334cf7117 --- /dev/null +++ b/apps/plugins/puzzles/src/blackbox.c | |||
@@ -0,0 +1,1543 @@ | |||
1 | /* | ||
2 | * blackbox.c: implementation of 'Black Box'. | ||
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 PREFERRED_TILE_SIZE 32 | ||
15 | #define FLASH_FRAME 0.2F | ||
16 | |||
17 | /* Terminology, for ease of reading various macros scattered about the place. | ||
18 | * | ||
19 | * The 'arena' is the inner area where the balls are placed. This is | ||
20 | * indexed from (0,0) to (w-1,h-1) but its offset in the grid is (1,1). | ||
21 | * | ||
22 | * The 'range' (firing range) is the bit around the edge where | ||
23 | * the lasers are fired from. This is indexed from 0 --> (2*(w+h) - 1), | ||
24 | * starting at the top left ((1,0) on the grid) and moving clockwise. | ||
25 | * | ||
26 | * The 'grid' is just the big array containing arena and range; | ||
27 | * locations (0,0), (0,w+1), (h+1,w+1) and (h+1,0) are unused. | ||
28 | */ | ||
29 | |||
30 | enum { | ||
31 | COL_BACKGROUND, COL_COVER, COL_LOCK, | ||
32 | COL_TEXT, COL_FLASHTEXT, | ||
33 | COL_HIGHLIGHT, COL_LOWLIGHT, COL_GRID, | ||
34 | COL_BALL, COL_WRONG, COL_BUTTON, | ||
35 | COL_CURSOR, | ||
36 | NCOLOURS | ||
37 | }; | ||
38 | |||
39 | struct game_params { | ||
40 | int w, h; | ||
41 | int minballs, maxballs; | ||
42 | }; | ||
43 | |||
44 | static game_params *default_params(void) | ||
45 | { | ||
46 | game_params *ret = snew(game_params); | ||
47 | |||
48 | ret->w = ret->h = 8; | ||
49 | ret->minballs = ret->maxballs = 5; | ||
50 | |||
51 | return ret; | ||
52 | } | ||
53 | |||
54 | static const game_params blackbox_presets[] = { | ||
55 | { 5, 5, 3, 3 }, | ||
56 | { 8, 8, 5, 5 }, | ||
57 | { 8, 8, 3, 6 }, | ||
58 | { 10, 10, 5, 5 }, | ||
59 | { 10, 10, 4, 10 } | ||
60 | }; | ||
61 | |||
62 | static int game_fetch_preset(int i, char **name, game_params **params) | ||
63 | { | ||
64 | char str[80]; | ||
65 | game_params *ret; | ||
66 | |||
67 | if (i < 0 || i >= lenof(blackbox_presets)) | ||
68 | return FALSE; | ||
69 | |||
70 | ret = snew(game_params); | ||
71 | *ret = blackbox_presets[i]; | ||
72 | |||
73 | if (ret->minballs == ret->maxballs) | ||
74 | sprintf(str, "%dx%d, %d balls", | ||
75 | ret->w, ret->h, ret->minballs); | ||
76 | else | ||
77 | sprintf(str, "%dx%d, %d-%d balls", | ||
78 | ret->w, ret->h, ret->minballs, ret->maxballs); | ||
79 | |||
80 | *name = dupstr(str); | ||
81 | *params = ret; | ||
82 | return TRUE; | ||
83 | } | ||
84 | |||
85 | static void free_params(game_params *params) | ||
86 | { | ||
87 | sfree(params); | ||
88 | } | ||
89 | |||
90 | static game_params *dup_params(const game_params *params) | ||
91 | { | ||
92 | game_params *ret = snew(game_params); | ||
93 | *ret = *params; /* structure copy */ | ||
94 | return ret; | ||
95 | } | ||
96 | |||
97 | static void decode_params(game_params *params, char const *string) | ||
98 | { | ||
99 | char const *p = string; | ||
100 | game_params *defs = default_params(); | ||
101 | |||
102 | *params = *defs; free_params(defs); | ||
103 | |||
104 | while (*p) { | ||
105 | switch (*p++) { | ||
106 | case 'w': | ||
107 | params->w = atoi(p); | ||
108 | while (*p && isdigit((unsigned char)*p)) p++; | ||
109 | break; | ||
110 | |||
111 | case 'h': | ||
112 | params->h = atoi(p); | ||
113 | while (*p && isdigit((unsigned char)*p)) p++; | ||
114 | break; | ||
115 | |||
116 | case 'm': | ||
117 | params->minballs = atoi(p); | ||
118 | while (*p && isdigit((unsigned char)*p)) p++; | ||
119 | break; | ||
120 | |||
121 | case 'M': | ||
122 | params->maxballs = atoi(p); | ||
123 | while (*p && isdigit((unsigned char)*p)) p++; | ||
124 | break; | ||
125 | |||
126 | default: | ||
127 | ; | ||
128 | } | ||
129 | } | ||
130 | } | ||
131 | |||
132 | static char *encode_params(const game_params *params, int full) | ||
133 | { | ||
134 | char str[256]; | ||
135 | |||
136 | sprintf(str, "w%dh%dm%dM%d", | ||
137 | params->w, params->h, params->minballs, params->maxballs); | ||
138 | return dupstr(str); | ||
139 | } | ||
140 | |||
141 | static config_item *game_configure(const game_params *params) | ||
142 | { | ||
143 | config_item *ret; | ||
144 | char buf[80]; | ||
145 | |||
146 | ret = snewn(4, config_item); | ||
147 | |||
148 | ret[0].name = "Width"; | ||
149 | ret[0].type = C_STRING; | ||
150 | sprintf(buf, "%d", params->w); | ||
151 | ret[0].sval = dupstr(buf); | ||
152 | ret[0].ival = 0; | ||
153 | |||
154 | ret[1].name = "Height"; | ||
155 | ret[1].type = C_STRING; | ||
156 | sprintf(buf, "%d", params->h); | ||
157 | ret[1].sval = dupstr(buf); | ||
158 | ret[1].ival = 0; | ||
159 | |||
160 | ret[2].name = "No. of balls"; | ||
161 | ret[2].type = C_STRING; | ||
162 | if (params->minballs == params->maxballs) | ||
163 | sprintf(buf, "%d", params->minballs); | ||
164 | else | ||
165 | sprintf(buf, "%d-%d", params->minballs, params->maxballs); | ||
166 | ret[2].sval = dupstr(buf); | ||
167 | ret[2].ival = 0; | ||
168 | |||
169 | ret[3].name = NULL; | ||
170 | ret[3].type = C_END; | ||
171 | ret[3].sval = NULL; | ||
172 | ret[3].ival = 0; | ||
173 | |||
174 | return ret; | ||
175 | } | ||
176 | |||
177 | static game_params *custom_params(const config_item *cfg) | ||
178 | { | ||
179 | game_params *ret = snew(game_params); | ||
180 | |||
181 | ret->w = atoi(cfg[0].sval); | ||
182 | ret->h = atoi(cfg[1].sval); | ||
183 | |||
184 | /* Allow 'a-b' for a range, otherwise assume a single number. */ | ||
185 | if (sscanf(cfg[2].sval, "%d-%d", &ret->minballs, &ret->maxballs) < 2) | ||
186 | ret->minballs = ret->maxballs = atoi(cfg[2].sval); | ||
187 | |||
188 | return ret; | ||
189 | } | ||
190 | |||
191 | static char *validate_params(const game_params *params, int full) | ||
192 | { | ||
193 | if (params->w < 2 || params->h < 2) | ||
194 | return "Width and height must both be at least two"; | ||
195 | /* next one is just for ease of coding stuff into 'char' | ||
196 | * types, and could be worked around if required. */ | ||
197 | if (params->w > 255 || params->h > 255) | ||
198 | return "Widths and heights greater than 255 are not supported"; | ||
199 | if (params->minballs > params->maxballs) | ||
200 | return "Minimum number of balls may not be greater than maximum"; | ||
201 | if (params->minballs >= params->w * params->h) | ||
202 | return "Too many balls to fit in grid"; | ||
203 | return NULL; | ||
204 | } | ||
205 | |||
206 | /* | ||
207 | * We store: width | height | ball1x | ball1y | [ ball2x | ball2y | [...] ] | ||
208 | * all stored as unsigned chars; validate_params has already | ||
209 | * checked this won't overflow an 8-bit char. | ||
210 | * Then we obfuscate it. | ||
211 | */ | ||
212 | |||
213 | static char *new_game_desc(const game_params *params, random_state *rs, | ||
214 | char **aux, int interactive) | ||
215 | { | ||
216 | int nballs = params->minballs, i; | ||
217 | char *grid, *ret; | ||
218 | unsigned char *bmp; | ||
219 | |||
220 | if (params->maxballs > params->minballs) | ||
221 | nballs += random_upto(rs, params->maxballs - params->minballs + 1); | ||
222 | |||
223 | grid = snewn(params->w*params->h, char); | ||
224 | memset(grid, 0, params->w * params->h * sizeof(char)); | ||
225 | |||
226 | bmp = snewn(nballs*2 + 2, unsigned char); | ||
227 | memset(bmp, 0, (nballs*2 + 2) * sizeof(unsigned char)); | ||
228 | |||
229 | bmp[0] = params->w; | ||
230 | bmp[1] = params->h; | ||
231 | |||
232 | for (i = 0; i < nballs; i++) { | ||
233 | int x, y; | ||
234 | |||
235 | do { | ||
236 | x = random_upto(rs, params->w); | ||
237 | y = random_upto(rs, params->h); | ||
238 | } while (grid[y*params->w + x]); | ||
239 | |||
240 | grid[y*params->w + x] = 1; | ||
241 | |||
242 | bmp[(i+1)*2 + 0] = x; | ||
243 | bmp[(i+1)*2 + 1] = y; | ||
244 | } | ||
245 | sfree(grid); | ||
246 | |||
247 | obfuscate_bitmap(bmp, (nballs*2 + 2) * 8, FALSE); | ||
248 | ret = bin2hex(bmp, nballs*2 + 2); | ||
249 | sfree(bmp); | ||
250 | |||
251 | return ret; | ||
252 | } | ||
253 | |||
254 | static char *validate_desc(const game_params *params, const char *desc) | ||
255 | { | ||
256 | int nballs, dlen = strlen(desc), i; | ||
257 | unsigned char *bmp; | ||
258 | char *ret; | ||
259 | |||
260 | /* the bitmap is 2+(nballs*2) long; the hex version is double that. */ | ||
261 | nballs = ((dlen/2)-2)/2; | ||
262 | |||
263 | if (dlen < 4 || dlen % 4 || | ||
264 | nballs < params->minballs || nballs > params->maxballs) | ||
265 | return "Game description is wrong length"; | ||
266 | |||
267 | bmp = hex2bin(desc, nballs*2 + 2); | ||
268 | obfuscate_bitmap(bmp, (nballs*2 + 2) * 8, TRUE); | ||
269 | ret = "Game description is corrupted"; | ||
270 | /* check general grid size */ | ||
271 | if (bmp[0] != params->w || bmp[1] != params->h) | ||
272 | goto done; | ||
273 | /* check each ball will fit on that grid */ | ||
274 | for (i = 0; i < nballs; i++) { | ||
275 | int x = bmp[(i+1)*2 + 0], y = bmp[(i+1)*2 + 1]; | ||
276 | if (x < 0 || y < 0 || x >= params->w || y >= params->h) | ||
277 | goto done; | ||
278 | } | ||
279 | ret = NULL; | ||
280 | |||
281 | done: | ||
282 | sfree(bmp); | ||
283 | return ret; | ||
284 | } | ||
285 | |||
286 | #define BALL_CORRECT 0x01 | ||
287 | #define BALL_GUESS 0x02 | ||
288 | #define BALL_LOCK 0x04 | ||
289 | |||
290 | #define LASER_FLAGMASK 0x1f800 | ||
291 | #define LASER_OMITTED 0x0800 | ||
292 | #define LASER_REFLECT 0x1000 | ||
293 | #define LASER_HIT 0x2000 | ||
294 | #define LASER_WRONG 0x4000 | ||
295 | #define LASER_FLASHED 0x8000 | ||
296 | #define LASER_EMPTY (~0) | ||
297 | |||
298 | #define FLAG_CURSOR 0x10000 /* needs to be disjoint from both sets */ | ||
299 | |||
300 | struct game_state { | ||
301 | int w, h, minballs, maxballs, nballs, nlasers; | ||
302 | unsigned int *grid; /* (w+2)x(h+2), to allow for laser firing range */ | ||
303 | unsigned int *exits; /* one per laser */ | ||
304 | int done; /* user has finished placing his own balls. */ | ||
305 | int laserno; /* number of next laser to be fired. */ | ||
306 | int nguesses, reveal, justwrong, nright, nwrong, nmissed; | ||
307 | }; | ||
308 | |||
309 | #define GRID(s,x,y) ((s)->grid[(y)*((s)->w+2) + (x)]) | ||
310 | |||
311 | #define RANGECHECK(s,x) ((x) >= 0 && (x) <= (s)->nlasers) | ||
312 | |||
313 | /* specify numbers because they must match array indexes. */ | ||
314 | enum { DIR_UP = 0, DIR_RIGHT = 1, DIR_DOWN = 2, DIR_LEFT = 3 }; | ||
315 | |||
316 | struct offset { int x, y; }; | ||
317 | |||
318 | static const struct offset offsets[] = { | ||
319 | { 0, -1 }, /* up */ | ||
320 | { 1, 0 }, /* right */ | ||
321 | { 0, 1 }, /* down */ | ||
322 | { -1, 0 } /* left */ | ||
323 | }; | ||
324 | |||
325 | #ifdef DEBUGGING | ||
326 | static const char *dirstrs[] = { | ||
327 | "UP", "RIGHT", "DOWN", "LEFT" | ||
328 | }; | ||
329 | #endif | ||
330 | |||
331 | static int range2grid(const game_state *state, int rangeno, int *x, int *y, | ||
332 | int *direction) | ||
333 | { | ||
334 | if (rangeno < 0) | ||
335 | return 0; | ||
336 | |||
337 | if (rangeno < state->w) { | ||
338 | /* top row; from (1,0) to (w,0) */ | ||
339 | *x = rangeno + 1; | ||
340 | *y = 0; | ||
341 | *direction = DIR_DOWN; | ||
342 | return 1; | ||
343 | } | ||
344 | rangeno -= state->w; | ||
345 | if (rangeno < state->h) { | ||
346 | /* RHS; from (w+1, 1) to (w+1, h) */ | ||
347 | *x = state->w+1; | ||
348 | *y = rangeno + 1; | ||
349 | *direction = DIR_LEFT; | ||
350 | return 1; | ||
351 | } | ||
352 | rangeno -= state->h; | ||
353 | if (rangeno < state->w) { | ||
354 | /* bottom row; from (1, h+1) to (w, h+1); counts backwards */ | ||
355 | *x = (state->w - rangeno); | ||
356 | *y = state->h+1; | ||
357 | *direction = DIR_UP; | ||
358 | return 1; | ||
359 | } | ||
360 | rangeno -= state->w; | ||
361 | if (rangeno < state->h) { | ||
362 | /* LHS; from (0, 1) to (0, h); counts backwards */ | ||
363 | *x = 0; | ||
364 | *y = (state->h - rangeno); | ||
365 | *direction = DIR_RIGHT; | ||
366 | return 1; | ||
367 | } | ||
368 | return 0; | ||
369 | } | ||
370 | |||
371 | static int grid2range(const game_state *state, int x, int y, int *rangeno) | ||
372 | { | ||
373 | int ret, x1 = state->w+1, y1 = state->h+1; | ||
374 | |||
375 | if (x > 0 && x < x1 && y > 0 && y < y1) return 0; /* in arena */ | ||
376 | if (x < 0 || x > x1 || y < 0 || y > y1) return 0; /* outside grid */ | ||
377 | |||
378 | if ((x == 0 || x == x1) && (y == 0 || y == y1)) | ||
379 | return 0; /* one of 4 corners */ | ||
380 | |||
381 | if (y == 0) { /* top line */ | ||
382 | ret = x - 1; | ||
383 | } else if (x == x1) { /* RHS */ | ||
384 | ret = y - 1 + state->w; | ||
385 | } else if (y == y1) { /* Bottom [and counts backwards] */ | ||
386 | ret = (state->w - x) + state->w + state->h; | ||
387 | } else { /* LHS [and counts backwards ] */ | ||
388 | ret = (state->h-y) + state->w + state->w + state->h; | ||
389 | } | ||
390 | *rangeno = ret; | ||
391 | debug(("grid2range: (%d,%d) rangeno = %d\n", x, y, ret)); | ||
392 | return 1; | ||
393 | } | ||
394 | |||
395 | static game_state *new_game(midend *me, const game_params *params, | ||
396 | const char *desc) | ||
397 | { | ||
398 | game_state *state = snew(game_state); | ||
399 | int dlen = strlen(desc), i; | ||
400 | unsigned char *bmp; | ||
401 | |||
402 | state->minballs = params->minballs; | ||
403 | state->maxballs = params->maxballs; | ||
404 | state->nballs = ((dlen/2)-2)/2; | ||
405 | |||
406 | bmp = hex2bin(desc, state->nballs*2 + 2); | ||
407 | obfuscate_bitmap(bmp, (state->nballs*2 + 2) * 8, TRUE); | ||
408 | |||
409 | state->w = bmp[0]; state->h = bmp[1]; | ||
410 | state->nlasers = 2 * (state->w + state->h); | ||
411 | |||
412 | state->grid = snewn((state->w+2)*(state->h+2), unsigned int); | ||
413 | memset(state->grid, 0, (state->w+2)*(state->h+2) * sizeof(unsigned int)); | ||
414 | |||
415 | state->exits = snewn(state->nlasers, unsigned int); | ||
416 | memset(state->exits, LASER_EMPTY, state->nlasers * sizeof(unsigned int)); | ||
417 | |||
418 | for (i = 0; i < state->nballs; i++) { | ||
419 | GRID(state, bmp[(i+1)*2 + 0]+1, bmp[(i+1)*2 + 1]+1) = BALL_CORRECT; | ||
420 | } | ||
421 | sfree(bmp); | ||
422 | |||
423 | state->done = state->nguesses = state->reveal = state->justwrong = | ||
424 | state->nright = state->nwrong = state->nmissed = 0; | ||
425 | state->laserno = 1; | ||
426 | |||
427 | return state; | ||
428 | } | ||
429 | |||
430 | #define XFER(x) ret->x = state->x | ||
431 | |||
432 | static game_state *dup_game(const game_state *state) | ||
433 | { | ||
434 | game_state *ret = snew(game_state); | ||
435 | |||
436 | XFER(w); XFER(h); | ||
437 | XFER(minballs); XFER(maxballs); | ||
438 | XFER(nballs); XFER(nlasers); | ||
439 | |||
440 | ret->grid = snewn((ret->w+2)*(ret->h+2), unsigned int); | ||
441 | memcpy(ret->grid, state->grid, (ret->w+2)*(ret->h+2) * sizeof(unsigned int)); | ||
442 | ret->exits = snewn(ret->nlasers, unsigned int); | ||
443 | memcpy(ret->exits, state->exits, ret->nlasers * sizeof(unsigned int)); | ||
444 | |||
445 | XFER(done); | ||
446 | XFER(laserno); | ||
447 | XFER(nguesses); | ||
448 | XFER(reveal); | ||
449 | XFER(justwrong); | ||
450 | XFER(nright); XFER(nwrong); XFER(nmissed); | ||
451 | |||
452 | return ret; | ||
453 | } | ||
454 | |||
455 | #undef XFER | ||
456 | |||
457 | static void free_game(game_state *state) | ||
458 | { | ||
459 | sfree(state->exits); | ||
460 | sfree(state->grid); | ||
461 | sfree(state); | ||
462 | } | ||
463 | |||
464 | static char *solve_game(const game_state *state, const game_state *currstate, | ||
465 | const char *aux, char **error) | ||
466 | { | ||
467 | return dupstr("S"); | ||
468 | } | ||
469 | |||
470 | static int game_can_format_as_text_now(const game_params *params) | ||
471 | { | ||
472 | return TRUE; | ||
473 | } | ||
474 | |||
475 | static char *game_text_format(const game_state *state) | ||
476 | { | ||
477 | return NULL; | ||
478 | } | ||
479 | |||
480 | struct game_ui { | ||
481 | int flash_laserno; | ||
482 | int errors, newmove; | ||
483 | int cur_x, cur_y, cur_visible; | ||
484 | int flash_laser; /* 0 = never, 1 = always, 2 = if anim. */ | ||
485 | }; | ||
486 | |||
487 | static game_ui *new_ui(const game_state *state) | ||
488 | { | ||
489 | game_ui *ui = snew(game_ui); | ||
490 | ui->flash_laserno = LASER_EMPTY; | ||
491 | ui->errors = 0; | ||
492 | ui->newmove = FALSE; | ||
493 | |||
494 | ui->cur_x = ui->cur_y = 1; | ||
495 | ui->cur_visible = 0; | ||
496 | |||
497 | ui->flash_laser = 0; | ||
498 | |||
499 | return ui; | ||
500 | } | ||
501 | |||
502 | static void free_ui(game_ui *ui) | ||
503 | { | ||
504 | sfree(ui); | ||
505 | } | ||
506 | |||
507 | static char *encode_ui(const game_ui *ui) | ||
508 | { | ||
509 | char buf[80]; | ||
510 | /* | ||
511 | * The error counter needs preserving across a serialisation. | ||
512 | */ | ||
513 | sprintf(buf, "E%d", ui->errors); | ||
514 | return dupstr(buf); | ||
515 | } | ||
516 | |||
517 | static void decode_ui(game_ui *ui, const char *encoding) | ||
518 | { | ||
519 | sscanf(encoding, "E%d", &ui->errors); | ||
520 | } | ||
521 | |||
522 | static void game_changed_state(game_ui *ui, const game_state *oldstate, | ||
523 | const game_state *newstate) | ||
524 | { | ||
525 | /* | ||
526 | * If we've encountered a `justwrong' state as a result of | ||
527 | * actually making a move, increment the ui error counter. | ||
528 | */ | ||
529 | if (newstate->justwrong && ui->newmove) | ||
530 | ui->errors++; | ||
531 | ui->newmove = FALSE; | ||
532 | } | ||
533 | |||
534 | #define OFFSET(gx,gy,o) do { \ | ||
535 | int off = (4 + (o) % 4) % 4; \ | ||
536 | (gx) += offsets[off].x; \ | ||
537 | (gy) += offsets[off].y; \ | ||
538 | } while(0) | ||
539 | |||
540 | enum { LOOK_LEFT, LOOK_FORWARD, LOOK_RIGHT }; | ||
541 | |||
542 | /* Given a position and a direction, check whether we can see a ball in front | ||
543 | * of us, or to our front-left or front-right. */ | ||
544 | static int isball(game_state *state, int gx, int gy, int direction, int lookwhere) | ||
545 | { | ||
546 | debug(("isball, (%d, %d), dir %s, lookwhere %s\n", gx, gy, dirstrs[direction], | ||
547 | lookwhere == LOOK_LEFT ? "LEFT" : | ||
548 | lookwhere == LOOK_FORWARD ? "FORWARD" : "RIGHT")); | ||
549 | OFFSET(gx,gy,direction); | ||
550 | if (lookwhere == LOOK_LEFT) | ||
551 | OFFSET(gx,gy,direction-1); | ||
552 | else if (lookwhere == LOOK_RIGHT) | ||
553 | OFFSET(gx,gy,direction+1); | ||
554 | else if (lookwhere != LOOK_FORWARD) | ||
555 | assert(!"unknown lookwhere"); | ||
556 | |||
557 | debug(("isball, new (%d, %d)\n", gx, gy)); | ||
558 | |||
559 | /* if we're off the grid (into the firing range) there's never a ball. */ | ||
560 | if (gx < 1 || gy < 1 || gx > state->w || gy > state->h) | ||
561 | return 0; | ||
562 | |||
563 | if (GRID(state, gx,gy) & BALL_CORRECT) | ||
564 | return 1; | ||
565 | |||
566 | return 0; | ||
567 | } | ||
568 | |||
569 | static int fire_laser_internal(game_state *state, int x, int y, int direction) | ||
570 | { | ||
571 | int unused, lno, tmp; | ||
572 | |||
573 | tmp = grid2range(state, x, y, &lno); | ||
574 | assert(tmp); | ||
575 | |||
576 | /* deal with strange initial reflection rules (that stop | ||
577 | * you turning down the laser range) */ | ||
578 | |||
579 | /* I've just chosen to prioritise instant-hit over instant-reflection; | ||
580 | * I can't find anywhere that gives me a definite algorithm for this. */ | ||
581 | if (isball(state, x, y, direction, LOOK_FORWARD)) { | ||
582 | debug(("Instant hit at (%d, %d)\n", x, y)); | ||
583 | return LASER_HIT; /* hit */ | ||
584 | } | ||
585 | |||
586 | if (isball(state, x, y, direction, LOOK_LEFT) || | ||
587 | isball(state, x, y, direction, LOOK_RIGHT)) { | ||
588 | debug(("Instant reflection at (%d, %d)\n", x, y)); | ||
589 | return LASER_REFLECT; /* reflection */ | ||
590 | } | ||
591 | /* move us onto the grid. */ | ||
592 | OFFSET(x, y, direction); | ||
593 | |||
594 | while (1) { | ||
595 | debug(("fire_laser: looping at (%d, %d) pointing %s\n", | ||
596 | x, y, dirstrs[direction])); | ||
597 | if (grid2range(state, x, y, &unused)) { | ||
598 | int exitno; | ||
599 | |||
600 | tmp = grid2range(state, x, y, &exitno); | ||
601 | assert(tmp); | ||
602 | |||
603 | return (lno == exitno ? LASER_REFLECT : exitno); | ||
604 | } | ||
605 | /* paranoia. This obviously should never happen */ | ||
606 | assert(!(GRID(state, x, y) & BALL_CORRECT)); | ||
607 | |||
608 | if (isball(state, x, y, direction, LOOK_FORWARD)) { | ||
609 | /* we're facing a ball; send back a reflection. */ | ||
610 | debug(("Ball ahead of (%d, %d)", x, y)); | ||
611 | return LASER_HIT; /* hit */ | ||
612 | } | ||
613 | |||
614 | if (isball(state, x, y, direction, LOOK_LEFT)) { | ||
615 | /* ball to our left; rotate clockwise and look again. */ | ||
616 | debug(("Ball to left; turning clockwise.\n")); | ||
617 | direction += 1; direction %= 4; | ||
618 | continue; | ||
619 | } | ||
620 | if (isball(state, x, y, direction, LOOK_RIGHT)) { | ||
621 | /* ball to our right; rotate anti-clockwise and look again. */ | ||
622 | debug(("Ball to rightl turning anti-clockwise.\n")); | ||
623 | direction += 3; direction %= 4; | ||
624 | continue; | ||
625 | } | ||
626 | /* ... otherwise, we have no balls ahead of us so just move one step. */ | ||
627 | debug(("No balls; moving forwards.\n")); | ||
628 | OFFSET(x, y, direction); | ||
629 | } | ||
630 | } | ||
631 | |||
632 | static int laser_exit(game_state *state, int entryno) | ||
633 | { | ||
634 | int tmp, x, y, direction; | ||
635 | |||
636 | tmp = range2grid(state, entryno, &x, &y, &direction); | ||
637 | assert(tmp); | ||
638 | |||
639 | return fire_laser_internal(state, x, y, direction); | ||
640 | } | ||
641 | |||
642 | static void fire_laser(game_state *state, int entryno) | ||
643 | { | ||
644 | int tmp, exitno, x, y, direction; | ||
645 | |||
646 | tmp = range2grid(state, entryno, &x, &y, &direction); | ||
647 | assert(tmp); | ||
648 | |||
649 | exitno = fire_laser_internal(state, x, y, direction); | ||
650 | |||
651 | if (exitno == LASER_HIT || exitno == LASER_REFLECT) { | ||
652 | GRID(state, x, y) = state->exits[entryno] = exitno; | ||
653 | } else { | ||
654 | int newno = state->laserno++; | ||
655 | int xend, yend, unused; | ||
656 | tmp = range2grid(state, exitno, &xend, ¥d, &unused); | ||
657 | assert(tmp); | ||
658 | GRID(state, x, y) = GRID(state, xend, yend) = newno; | ||
659 | state->exits[entryno] = exitno; | ||
660 | state->exits[exitno] = entryno; | ||
661 | } | ||
662 | } | ||
663 | |||
664 | /* Checks that the guessed balls in the state match up with the real balls | ||
665 | * for all possible lasers (i.e. not just the ones that the player might | ||
666 | * have already guessed). This is required because any layout with >4 balls | ||
667 | * might have multiple valid solutions. Returns non-zero for a 'correct' | ||
668 | * (i.e. consistent) layout. */ | ||
669 | static int check_guesses(game_state *state, int cagey) | ||
670 | { | ||
671 | game_state *solution, *guesses; | ||
672 | int i, x, y, n, unused, tmp; | ||
673 | int ret = 0; | ||
674 | |||
675 | if (cagey) { | ||
676 | /* | ||
677 | * First, check that each laser the player has already | ||
678 | * fired is consistent with the layout. If not, show them | ||
679 | * one error they've made and reveal no further | ||
680 | * information. | ||
681 | * | ||
682 | * Failing that, check to see whether the player would have | ||
683 | * been able to fire any laser which distinguished the real | ||
684 | * solution from their guess. If so, show them one such | ||
685 | * laser and reveal no further information. | ||
686 | */ | ||
687 | guesses = dup_game(state); | ||
688 | /* clear out BALL_CORRECT on guess, make BALL_GUESS BALL_CORRECT. */ | ||
689 | for (x = 1; x <= state->w; x++) { | ||
690 | for (y = 1; y <= state->h; y++) { | ||
691 | GRID(guesses, x, y) &= ~BALL_CORRECT; | ||
692 | if (GRID(guesses, x, y) & BALL_GUESS) | ||
693 | GRID(guesses, x, y) |= BALL_CORRECT; | ||
694 | } | ||
695 | } | ||
696 | n = 0; | ||
697 | for (i = 0; i < guesses->nlasers; i++) { | ||
698 | if (guesses->exits[i] != LASER_EMPTY && | ||
699 | guesses->exits[i] != laser_exit(guesses, i)) | ||
700 | n++; | ||
701 | } | ||
702 | if (n) { | ||
703 | /* | ||
704 | * At least one of the player's existing lasers | ||
705 | * contradicts their ball placement. Pick a random one, | ||
706 | * highlight it, and return. | ||
707 | * | ||
708 | * A temporary random state is created from the current | ||
709 | * grid, so that repeating the same marking will give | ||
710 | * the same answer instead of a different one. | ||
711 | */ | ||
712 | random_state *rs = random_new((char *)guesses->grid, | ||
713 | (state->w+2)*(state->h+2) * | ||
714 | sizeof(unsigned int)); | ||
715 | n = random_upto(rs, n); | ||
716 | random_free(rs); | ||
717 | for (i = 0; i < guesses->nlasers; i++) { | ||
718 | if (guesses->exits[i] != LASER_EMPTY && | ||
719 | guesses->exits[i] != laser_exit(guesses, i) && | ||
720 | n-- == 0) { | ||
721 | state->exits[i] |= LASER_WRONG; | ||
722 | tmp = laser_exit(state, i); | ||
723 | if (RANGECHECK(state, tmp)) | ||
724 | state->exits[tmp] |= LASER_WRONG; | ||
725 | state->justwrong = TRUE; | ||
726 | free_game(guesses); | ||
727 | return 0; | ||
728 | } | ||
729 | } | ||
730 | } | ||
731 | n = 0; | ||
732 | for (i = 0; i < guesses->nlasers; i++) { | ||
733 | if (guesses->exits[i] == LASER_EMPTY && | ||
734 | laser_exit(state, i) != laser_exit(guesses, i)) | ||
735 | n++; | ||
736 | } | ||
737 | if (n) { | ||
738 | /* | ||
739 | * At least one of the player's unfired lasers would | ||
740 | * demonstrate their ball placement to be wrong. Pick a | ||
741 | * random one, highlight it, and return. | ||
742 | * | ||
743 | * A temporary random state is created from the current | ||
744 | * grid, so that repeating the same marking will give | ||
745 | * the same answer instead of a different one. | ||
746 | */ | ||
747 | random_state *rs = random_new((char *)guesses->grid, | ||
748 | (state->w+2)*(state->h+2) * | ||
749 | sizeof(unsigned int)); | ||
750 | n = random_upto(rs, n); | ||
751 | random_free(rs); | ||
752 | for (i = 0; i < guesses->nlasers; i++) { | ||
753 | if (guesses->exits[i] == LASER_EMPTY && | ||
754 | laser_exit(state, i) != laser_exit(guesses, i) && | ||
755 | n-- == 0) { | ||
756 | fire_laser(state, i); | ||
757 | state->exits[i] |= LASER_OMITTED; | ||
758 | tmp = laser_exit(state, i); | ||
759 | if (RANGECHECK(state, tmp)) | ||
760 | state->exits[tmp] |= LASER_OMITTED; | ||
761 | state->justwrong = TRUE; | ||
762 | free_game(guesses); | ||
763 | return 0; | ||
764 | } | ||
765 | } | ||
766 | } | ||
767 | free_game(guesses); | ||
768 | } | ||
769 | |||
770 | /* duplicate the state (to solution) */ | ||
771 | solution = dup_game(state); | ||
772 | |||
773 | /* clear out the lasers of solution */ | ||
774 | for (i = 0; i < solution->nlasers; i++) { | ||
775 | tmp = range2grid(solution, i, &x, &y, &unused); | ||
776 | assert(tmp); | ||
777 | GRID(solution, x, y) = 0; | ||
778 | solution->exits[i] = LASER_EMPTY; | ||
779 | } | ||
780 | |||
781 | /* duplicate solution to guess. */ | ||
782 | guesses = dup_game(solution); | ||
783 | |||
784 | /* clear out BALL_CORRECT on guess, make BALL_GUESS BALL_CORRECT. */ | ||
785 | for (x = 1; x <= state->w; x++) { | ||
786 | for (y = 1; y <= state->h; y++) { | ||
787 | GRID(guesses, x, y) &= ~BALL_CORRECT; | ||
788 | if (GRID(guesses, x, y) & BALL_GUESS) | ||
789 | GRID(guesses, x, y) |= BALL_CORRECT; | ||
790 | } | ||
791 | } | ||
792 | |||
793 | /* for each laser (on both game_states), fire it if it hasn't been fired. | ||
794 | * If one has been fired (or received a hit) and another hasn't, we know | ||
795 | * the ball layouts didn't match and can short-circuit return. */ | ||
796 | for (i = 0; i < solution->nlasers; i++) { | ||
797 | if (solution->exits[i] == LASER_EMPTY) | ||
798 | fire_laser(solution, i); | ||
799 | if (guesses->exits[i] == LASER_EMPTY) | ||
800 | fire_laser(guesses, i); | ||
801 | } | ||
802 | |||
803 | /* check each game_state's laser against the other; if any differ, return 0 */ | ||
804 | ret = 1; | ||
805 | for (i = 0; i < solution->nlasers; i++) { | ||
806 | tmp = range2grid(solution, i, &x, &y, &unused); | ||
807 | assert(tmp); | ||
808 | |||
809 | if (solution->exits[i] != guesses->exits[i]) { | ||
810 | /* If the original state didn't have this shot fired, | ||
811 | * and it would be wrong between the guess and the solution, | ||
812 | * add it. */ | ||
813 | if (state->exits[i] == LASER_EMPTY) { | ||
814 | state->exits[i] = solution->exits[i]; | ||
815 | if (state->exits[i] == LASER_REFLECT || | ||
816 | state->exits[i] == LASER_HIT) | ||
817 | GRID(state, x, y) = state->exits[i]; | ||
818 | else { | ||
819 | /* add a new shot, incrementing state's laser count. */ | ||
820 | int ex, ey, newno = state->laserno++; | ||
821 | tmp = range2grid(state, state->exits[i], &ex, &ey, &unused); | ||
822 | assert(tmp); | ||
823 | GRID(state, x, y) = newno; | ||
824 | GRID(state, ex, ey) = newno; | ||
825 | } | ||
826 | state->exits[i] |= LASER_OMITTED; | ||
827 | } else { | ||
828 | state->exits[i] |= LASER_WRONG; | ||
829 | } | ||
830 | ret = 0; | ||
831 | } | ||
832 | } | ||
833 | if (ret == 0 || | ||
834 | state->nguesses < state->minballs || | ||
835 | state->nguesses > state->maxballs) goto done; | ||
836 | |||
837 | /* fix up original state so the 'correct' balls end up matching the guesses, | ||
838 | * as we've just proved that they were equivalent. */ | ||
839 | for (x = 1; x <= state->w; x++) { | ||
840 | for (y = 1; y <= state->h; y++) { | ||
841 | if (GRID(state, x, y) & BALL_GUESS) | ||
842 | GRID(state, x, y) |= BALL_CORRECT; | ||
843 | else | ||
844 | GRID(state, x, y) &= ~BALL_CORRECT; | ||
845 | } | ||
846 | } | ||
847 | |||
848 | done: | ||
849 | /* fill in nright and nwrong. */ | ||
850 | state->nright = state->nwrong = state->nmissed = 0; | ||
851 | for (x = 1; x <= state->w; x++) { | ||
852 | for (y = 1; y <= state->h; y++) { | ||
853 | int bs = GRID(state, x, y) & (BALL_GUESS | BALL_CORRECT); | ||
854 | if (bs == (BALL_GUESS | BALL_CORRECT)) | ||
855 | state->nright++; | ||
856 | else if (bs == BALL_GUESS) | ||
857 | state->nwrong++; | ||
858 | else if (bs == BALL_CORRECT) | ||
859 | state->nmissed++; | ||
860 | } | ||
861 | } | ||
862 | free_game(solution); | ||
863 | free_game(guesses); | ||
864 | state->reveal = 1; | ||
865 | return ret; | ||
866 | } | ||
867 | |||
868 | #define TILE_SIZE (ds->tilesize) | ||
869 | |||
870 | #define TODRAW(x) ((TILE_SIZE * (x)) + (TILE_SIZE / 2)) | ||
871 | #define FROMDRAW(x) (((x) - (TILE_SIZE / 2)) / TILE_SIZE) | ||
872 | |||
873 | #define CAN_REVEAL(state) ((state)->nguesses >= (state)->minballs && \ | ||
874 | (state)->nguesses <= (state)->maxballs && \ | ||
875 | !(state)->reveal && !(state)->justwrong) | ||
876 | |||
877 | struct game_drawstate { | ||
878 | int tilesize, crad, rrad, w, h; /* w and h to make macros work... */ | ||
879 | unsigned int *grid; /* as the game_state grid */ | ||
880 | int started, reveal; | ||
881 | int flash_laserno, isflash; | ||
882 | }; | ||
883 | |||
884 | static char *interpret_move(const game_state *state, game_ui *ui, | ||
885 | const game_drawstate *ds, | ||
886 | int x, int y, int button) | ||
887 | { | ||
888 | int gx = -1, gy = -1, rangeno = -1, wouldflash = 0; | ||
889 | enum { NONE, TOGGLE_BALL, TOGGLE_LOCK, FIRE, REVEAL, | ||
890 | TOGGLE_COLUMN_LOCK, TOGGLE_ROW_LOCK} action = NONE; | ||
891 | char buf[80], *nullret = NULL; | ||
892 | |||
893 | if (IS_CURSOR_MOVE(button)) { | ||
894 | int cx = ui->cur_x, cy = ui->cur_y; | ||
895 | |||
896 | move_cursor(button, &cx, &cy, state->w+2, state->h+2, 0); | ||
897 | if ((cx == 0 && cy == 0 && !CAN_REVEAL(state)) || | ||
898 | (cx == 0 && cy == state->h+1) || | ||
899 | (cx == state->w+1 && cy == 0) || | ||
900 | (cx == state->w+1 && cy == state->h+1)) | ||
901 | return NULL; /* disallow moving cursor to corners. */ | ||
902 | ui->cur_x = cx; | ||
903 | ui->cur_y = cy; | ||
904 | ui->cur_visible = 1; | ||
905 | return ""; | ||
906 | } | ||
907 | |||
908 | if (button == LEFT_BUTTON || button == RIGHT_BUTTON) { | ||
909 | gx = FROMDRAW(x); | ||
910 | gy = FROMDRAW(y); | ||
911 | ui->cur_visible = 0; | ||
912 | wouldflash = 1; | ||
913 | } else if (button == LEFT_RELEASE) { | ||
914 | ui->flash_laser = 0; | ||
915 | return ""; | ||
916 | } else if (IS_CURSOR_SELECT(button)) { | ||
917 | if (ui->cur_visible) { | ||
918 | gx = ui->cur_x; | ||
919 | gy = ui->cur_y; | ||
920 | ui->flash_laser = 0; | ||
921 | wouldflash = 2; | ||
922 | } else { | ||
923 | ui->cur_visible = 1; | ||
924 | return ""; | ||
925 | } | ||
926 | /* Fix up 'button' for the below logic. */ | ||
927 | if (button == CURSOR_SELECT2) button = RIGHT_BUTTON; | ||
928 | else button = LEFT_BUTTON; | ||
929 | } | ||
930 | |||
931 | if (gx != -1 && gy != -1) { | ||
932 | if (gx == 0 && gy == 0 && button == LEFT_BUTTON) | ||
933 | action = REVEAL; | ||
934 | if (gx >= 1 && gx <= state->w && gy >= 1 && gy <= state->h) { | ||
935 | if (button == LEFT_BUTTON) { | ||
936 | if (!(GRID(state, gx,gy) & BALL_LOCK)) | ||
937 | action = TOGGLE_BALL; | ||
938 | } else | ||
939 | action = TOGGLE_LOCK; | ||
940 | } | ||
941 | if (grid2range(state, gx, gy, &rangeno)) { | ||
942 | if (button == LEFT_BUTTON) | ||
943 | action = FIRE; | ||
944 | else if (gy == 0 || gy > state->h) | ||
945 | action = TOGGLE_COLUMN_LOCK; /* and use gx */ | ||
946 | else | ||
947 | action = TOGGLE_ROW_LOCK; /* and use gy */ | ||
948 | } | ||
949 | } | ||
950 | |||
951 | switch (action) { | ||
952 | case TOGGLE_BALL: | ||
953 | sprintf(buf, "T%d,%d", gx, gy); | ||
954 | break; | ||
955 | |||
956 | case TOGGLE_LOCK: | ||
957 | sprintf(buf, "LB%d,%d", gx, gy); | ||
958 | break; | ||
959 | |||
960 | case TOGGLE_COLUMN_LOCK: | ||
961 | sprintf(buf, "LC%d", gx); | ||
962 | break; | ||
963 | |||
964 | case TOGGLE_ROW_LOCK: | ||
965 | sprintf(buf, "LR%d", gy); | ||
966 | break; | ||
967 | |||
968 | case FIRE: | ||
969 | if (state->reveal && state->exits[rangeno] == LASER_EMPTY) | ||
970 | return nullret; | ||
971 | ui->flash_laserno = rangeno; | ||
972 | ui->flash_laser = wouldflash; | ||
973 | nullret = ""; | ||
974 | if (state->exits[rangeno] != LASER_EMPTY) | ||
975 | return ""; | ||
976 | sprintf(buf, "F%d", rangeno); | ||
977 | break; | ||
978 | |||
979 | case REVEAL: | ||
980 | if (!CAN_REVEAL(state)) return nullret; | ||
981 | if (ui->cur_visible == 1) ui->cur_x = ui->cur_y = 1; | ||
982 | sprintf(buf, "R"); | ||
983 | break; | ||
984 | |||
985 | default: | ||
986 | return nullret; | ||
987 | } | ||
988 | if (state->reveal) return nullret; | ||
989 | ui->newmove = TRUE; | ||
990 | return dupstr(buf); | ||
991 | } | ||
992 | |||
993 | static game_state *execute_move(const game_state *from, const char *move) | ||
994 | { | ||
995 | game_state *ret = dup_game(from); | ||
996 | int gx = -1, gy = -1, rangeno = -1; | ||
997 | |||
998 | if (ret->justwrong) { | ||
999 | int i; | ||
1000 | ret->justwrong = FALSE; | ||
1001 | for (i = 0; i < ret->nlasers; i++) | ||
1002 | if (ret->exits[i] != LASER_EMPTY) | ||
1003 | ret->exits[i] &= ~(LASER_OMITTED | LASER_WRONG); | ||
1004 | } | ||
1005 | |||
1006 | if (!strcmp(move, "S")) { | ||
1007 | check_guesses(ret, FALSE); | ||
1008 | return ret; | ||
1009 | } | ||
1010 | |||
1011 | if (from->reveal) goto badmove; | ||
1012 | if (!*move) goto badmove; | ||
1013 | |||
1014 | switch (move[0]) { | ||
1015 | case 'T': | ||
1016 | sscanf(move+1, "%d,%d", &gx, &gy); | ||
1017 | if (gx < 1 || gy < 1 || gx > ret->w || gy > ret->h) | ||
1018 | goto badmove; | ||
1019 | if (GRID(ret, gx, gy) & BALL_GUESS) { | ||
1020 | ret->nguesses--; | ||
1021 | GRID(ret, gx, gy) &= ~BALL_GUESS; | ||
1022 | } else { | ||
1023 | ret->nguesses++; | ||
1024 | GRID(ret, gx, gy) |= BALL_GUESS; | ||
1025 | } | ||
1026 | break; | ||
1027 | |||
1028 | case 'F': | ||
1029 | sscanf(move+1, "%d", &rangeno); | ||
1030 | if (ret->exits[rangeno] != LASER_EMPTY) | ||
1031 | goto badmove; | ||
1032 | if (!RANGECHECK(ret, rangeno)) | ||
1033 | goto badmove; | ||
1034 | fire_laser(ret, rangeno); | ||
1035 | break; | ||
1036 | |||
1037 | case 'R': | ||
1038 | if (ret->nguesses < ret->minballs || | ||
1039 | ret->nguesses > ret->maxballs) | ||
1040 | goto badmove; | ||
1041 | check_guesses(ret, TRUE); | ||
1042 | break; | ||
1043 | |||
1044 | case 'L': | ||
1045 | { | ||
1046 | int lcount = 0; | ||
1047 | if (strlen(move) < 2) goto badmove; | ||
1048 | switch (move[1]) { | ||
1049 | case 'B': | ||
1050 | sscanf(move+2, "%d,%d", &gx, &gy); | ||
1051 | if (gx < 1 || gy < 1 || gx > ret->w || gy > ret->h) | ||
1052 | goto badmove; | ||
1053 | GRID(ret, gx, gy) ^= BALL_LOCK; | ||
1054 | break; | ||
1055 | |||
1056 | #define COUNTLOCK do { if (GRID(ret, gx, gy) & BALL_LOCK) lcount++; } while (0) | ||
1057 | #define SETLOCKIF(c) do { \ | ||
1058 | if (lcount > (c)) GRID(ret, gx, gy) &= ~BALL_LOCK; \ | ||
1059 | else GRID(ret, gx, gy) |= BALL_LOCK; \ | ||
1060 | } while(0) | ||
1061 | |||
1062 | case 'C': | ||
1063 | sscanf(move+2, "%d", &gx); | ||
1064 | if (gx < 1 || gx > ret->w) goto badmove; | ||
1065 | for (gy = 1; gy <= ret->h; gy++) { COUNTLOCK; } | ||
1066 | for (gy = 1; gy <= ret->h; gy++) { SETLOCKIF(ret->h/2); } | ||
1067 | break; | ||
1068 | |||
1069 | case 'R': | ||
1070 | sscanf(move+2, "%d", &gy); | ||
1071 | if (gy < 1 || gy > ret->h) goto badmove; | ||
1072 | for (gx = 1; gx <= ret->w; gx++) { COUNTLOCK; } | ||
1073 | for (gx = 1; gx <= ret->w; gx++) { SETLOCKIF(ret->w/2); } | ||
1074 | break; | ||
1075 | |||
1076 | #undef COUNTLOCK | ||
1077 | #undef SETLOCKIF | ||
1078 | |||
1079 | default: | ||
1080 | goto badmove; | ||
1081 | } | ||
1082 | } | ||
1083 | break; | ||
1084 | |||
1085 | default: | ||
1086 | goto badmove; | ||
1087 | } | ||
1088 | |||
1089 | return ret; | ||
1090 | |||
1091 | badmove: | ||
1092 | free_game(ret); | ||
1093 | return NULL; | ||
1094 | } | ||
1095 | |||
1096 | /* ---------------------------------------------------------------------- | ||
1097 | * Drawing routines. | ||
1098 | */ | ||
1099 | |||
1100 | static void game_compute_size(const game_params *params, int tilesize, | ||
1101 | int *x, int *y) | ||
1102 | { | ||
1103 | /* Border is ts/2, to make things easier. | ||
1104 | * Thus we have (width) + 2 (firing range*2) + 1 (border*2) tiles | ||
1105 | * across, and similarly height + 2 + 1 tiles down. */ | ||
1106 | *x = (params->w + 3) * tilesize; | ||
1107 | *y = (params->h + 3) * tilesize; | ||
1108 | } | ||
1109 | |||
1110 | static void game_set_size(drawing *dr, game_drawstate *ds, | ||
1111 | const game_params *params, int tilesize) | ||
1112 | { | ||
1113 | ds->tilesize = tilesize; | ||
1114 | ds->crad = (tilesize-1)/2; | ||
1115 | ds->rrad = (3*tilesize)/8; | ||
1116 | } | ||
1117 | |||
1118 | static float *game_colours(frontend *fe, int *ncolours) | ||
1119 | { | ||
1120 | float *ret = snewn(3 * NCOLOURS, float); | ||
1121 | int i; | ||
1122 | |||
1123 | game_mkhighlight(fe, ret, COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT); | ||
1124 | |||
1125 | ret[COL_BALL * 3 + 0] = 0.0F; | ||
1126 | ret[COL_BALL * 3 + 1] = 0.0F; | ||
1127 | ret[COL_BALL * 3 + 2] = 0.0F; | ||
1128 | |||
1129 | ret[COL_WRONG * 3 + 0] = 1.0F; | ||
1130 | ret[COL_WRONG * 3 + 1] = 0.0F; | ||
1131 | ret[COL_WRONG * 3 + 2] = 0.0F; | ||
1132 | |||
1133 | ret[COL_BUTTON * 3 + 0] = 0.0F; | ||
1134 | ret[COL_BUTTON * 3 + 1] = 1.0F; | ||
1135 | ret[COL_BUTTON * 3 + 2] = 0.0F; | ||
1136 | |||
1137 | ret[COL_CURSOR * 3 + 0] = 1.0F; | ||
1138 | ret[COL_CURSOR * 3 + 1] = 0.0F; | ||
1139 | ret[COL_CURSOR * 3 + 2] = 0.0F; | ||
1140 | |||
1141 | for (i = 0; i < 3; i++) { | ||
1142 | ret[COL_GRID * 3 + i] = ret[COL_BACKGROUND * 3 + i] * 0.9F; | ||
1143 | ret[COL_LOCK * 3 + i] = ret[COL_BACKGROUND * 3 + i] * 0.7F; | ||
1144 | ret[COL_COVER * 3 + i] = ret[COL_BACKGROUND * 3 + i] * 0.5F; | ||
1145 | ret[COL_TEXT * 3 + i] = 0.0F; | ||
1146 | } | ||
1147 | |||
1148 | ret[COL_FLASHTEXT * 3 + 0] = 0.0F; | ||
1149 | ret[COL_FLASHTEXT * 3 + 1] = 1.0F; | ||
1150 | ret[COL_FLASHTEXT * 3 + 2] = 0.0F; | ||
1151 | |||
1152 | *ncolours = NCOLOURS; | ||
1153 | return ret; | ||
1154 | } | ||
1155 | |||
1156 | static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) | ||
1157 | { | ||
1158 | struct game_drawstate *ds = snew(struct game_drawstate); | ||
1159 | |||
1160 | ds->tilesize = 0; | ||
1161 | ds->w = state->w; ds->h = state->h; | ||
1162 | ds->grid = snewn((state->w+2)*(state->h+2), unsigned int); | ||
1163 | memset(ds->grid, 0, (state->w+2)*(state->h+2)*sizeof(unsigned int)); | ||
1164 | ds->started = ds->reveal = 0; | ||
1165 | ds->flash_laserno = LASER_EMPTY; | ||
1166 | ds->isflash = 0; | ||
1167 | |||
1168 | return ds; | ||
1169 | } | ||
1170 | |||
1171 | static void game_free_drawstate(drawing *dr, game_drawstate *ds) | ||
1172 | { | ||
1173 | sfree(ds->grid); | ||
1174 | sfree(ds); | ||
1175 | } | ||
1176 | |||
1177 | static void draw_square_cursor(drawing *dr, game_drawstate *ds, int dx, int dy) | ||
1178 | { | ||
1179 | int coff = TILE_SIZE/8; | ||
1180 | draw_rect_outline(dr, dx + coff, dy + coff, | ||
1181 | TILE_SIZE - coff*2, | ||
1182 | TILE_SIZE - coff*2, | ||
1183 | COL_CURSOR); | ||
1184 | } | ||
1185 | |||
1186 | |||
1187 | static void draw_arena_tile(drawing *dr, const game_state *gs, | ||
1188 | game_drawstate *ds, const game_ui *ui, | ||
1189 | int ax, int ay, int force, int isflash) | ||
1190 | { | ||
1191 | int gx = ax+1, gy = ay+1; | ||
1192 | int gs_tile = GRID(gs, gx, gy), ds_tile = GRID(ds, gx, gy); | ||
1193 | int dx = TODRAW(gx), dy = TODRAW(gy); | ||
1194 | |||
1195 | if (ui->cur_visible && ui->cur_x == gx && ui->cur_y == gy) | ||
1196 | gs_tile |= FLAG_CURSOR; | ||
1197 | |||
1198 | if (gs_tile != ds_tile || gs->reveal != ds->reveal || force) { | ||
1199 | int bcol, ocol, bg; | ||
1200 | |||
1201 | bg = (gs->reveal ? COL_BACKGROUND : | ||
1202 | (gs_tile & BALL_LOCK) ? COL_LOCK : COL_COVER); | ||
1203 | |||
1204 | draw_rect(dr, dx, dy, TILE_SIZE, TILE_SIZE, bg); | ||
1205 | draw_rect_outline(dr, dx, dy, TILE_SIZE, TILE_SIZE, COL_GRID); | ||
1206 | |||
1207 | if (gs->reveal) { | ||
1208 | /* Guessed balls are always black; if they're incorrect they'll | ||
1209 | * have a red cross added later. | ||
1210 | * Missing balls are red. */ | ||
1211 | if (gs_tile & BALL_GUESS) { | ||
1212 | bcol = isflash ? bg : COL_BALL; | ||
1213 | } else if (gs_tile & BALL_CORRECT) { | ||
1214 | bcol = isflash ? bg : COL_WRONG; | ||
1215 | } else { | ||
1216 | bcol = bg; | ||
1217 | } | ||
1218 | } else { | ||
1219 | /* guesses are black/black, all else background. */ | ||
1220 | if (gs_tile & BALL_GUESS) { | ||
1221 | bcol = COL_BALL; | ||
1222 | } else { | ||
1223 | bcol = bg; | ||
1224 | } | ||
1225 | } | ||
1226 | ocol = (gs_tile & FLAG_CURSOR && bcol != bg) ? COL_CURSOR : bcol; | ||
1227 | |||
1228 | draw_circle(dr, dx + TILE_SIZE/2, dy + TILE_SIZE/2, ds->crad-1, | ||
1229 | ocol, ocol); | ||
1230 | draw_circle(dr, dx + TILE_SIZE/2, dy + TILE_SIZE/2, ds->crad-3, | ||
1231 | bcol, bcol); | ||
1232 | |||
1233 | |||
1234 | if (gs_tile & FLAG_CURSOR && bcol == bg) | ||
1235 | draw_square_cursor(dr, ds, dx, dy); | ||
1236 | |||
1237 | if (gs->reveal && | ||
1238 | (gs_tile & BALL_GUESS) && | ||
1239 | !(gs_tile & BALL_CORRECT)) { | ||
1240 | int x1 = dx + 3, y1 = dy + 3; | ||
1241 | int x2 = dx + TILE_SIZE - 3, y2 = dy + TILE_SIZE-3; | ||
1242 | int coords[8]; | ||
1243 | |||
1244 | /* Incorrect guess; draw a red cross over the ball. */ | ||
1245 | coords[0] = x1-1; | ||
1246 | coords[1] = y1+1; | ||
1247 | coords[2] = x1+1; | ||
1248 | coords[3] = y1-1; | ||
1249 | coords[4] = x2+1; | ||
1250 | coords[5] = y2-1; | ||
1251 | coords[6] = x2-1; | ||
1252 | coords[7] = y2+1; | ||
1253 | draw_polygon(dr, coords, 4, COL_WRONG, COL_WRONG); | ||
1254 | coords[0] = x2+1; | ||
1255 | coords[1] = y1+1; | ||
1256 | coords[2] = x2-1; | ||
1257 | coords[3] = y1-1; | ||
1258 | coords[4] = x1-1; | ||
1259 | coords[5] = y2-1; | ||
1260 | coords[6] = x1+1; | ||
1261 | coords[7] = y2+1; | ||
1262 | draw_polygon(dr, coords, 4, COL_WRONG, COL_WRONG); | ||
1263 | } | ||
1264 | draw_update(dr, dx, dy, TILE_SIZE, TILE_SIZE); | ||
1265 | } | ||
1266 | GRID(ds,gx,gy) = gs_tile; | ||
1267 | } | ||
1268 | |||
1269 | static void draw_laser_tile(drawing *dr, const game_state *gs, | ||
1270 | game_drawstate *ds, const game_ui *ui, | ||
1271 | int lno, int force) | ||
1272 | { | ||
1273 | int gx, gy, dx, dy, unused; | ||
1274 | int wrong, omitted, reflect, hit, laserval, flash = 0, tmp; | ||
1275 | unsigned int gs_tile, ds_tile, exitno; | ||
1276 | |||
1277 | tmp = range2grid(gs, lno, &gx, &gy, &unused); | ||
1278 | assert(tmp); | ||
1279 | gs_tile = GRID(gs, gx, gy); | ||
1280 | ds_tile = GRID(ds, gx, gy); | ||
1281 | dx = TODRAW(gx); | ||
1282 | dy = TODRAW(gy); | ||
1283 | |||
1284 | wrong = gs->exits[lno] & LASER_WRONG; | ||
1285 | omitted = gs->exits[lno] & LASER_OMITTED; | ||
1286 | exitno = gs->exits[lno] & ~LASER_FLAGMASK; | ||
1287 | |||
1288 | reflect = gs_tile & LASER_REFLECT; | ||
1289 | hit = gs_tile & LASER_HIT; | ||
1290 | laserval = gs_tile & ~LASER_FLAGMASK; | ||
1291 | |||
1292 | if (lno == ds->flash_laserno) | ||
1293 | gs_tile |= LASER_FLASHED; | ||
1294 | else if (!(gs->exits[lno] & (LASER_HIT | LASER_REFLECT))) { | ||
1295 | if (exitno == ds->flash_laserno) | ||
1296 | gs_tile |= LASER_FLASHED; | ||
1297 | } | ||
1298 | if (gs_tile & LASER_FLASHED) flash = 1; | ||
1299 | |||
1300 | gs_tile |= wrong | omitted; | ||
1301 | |||
1302 | if (ui->cur_visible && ui->cur_x == gx && ui->cur_y == gy) | ||
1303 | gs_tile |= FLAG_CURSOR; | ||
1304 | |||
1305 | if (gs_tile != ds_tile || force) { | ||
1306 | draw_rect(dr, dx, dy, TILE_SIZE, TILE_SIZE, COL_BACKGROUND); | ||
1307 | draw_rect_outline(dr, dx, dy, TILE_SIZE, TILE_SIZE, COL_GRID); | ||
1308 | |||
1309 | if (gs_tile &~ (LASER_WRONG | LASER_OMITTED | FLAG_CURSOR)) { | ||
1310 | char str[32]; | ||
1311 | int tcol = flash ? COL_FLASHTEXT : omitted ? COL_WRONG : COL_TEXT; | ||
1312 | |||
1313 | if (reflect || hit) | ||
1314 | sprintf(str, "%s", reflect ? "R" : "H"); | ||
1315 | else | ||
1316 | sprintf(str, "%d", laserval); | ||
1317 | |||
1318 | if (wrong) { | ||
1319 | draw_circle(dr, dx + TILE_SIZE/2, dy + TILE_SIZE/2, | ||
1320 | ds->rrad, | ||
1321 | COL_WRONG, COL_WRONG); | ||
1322 | draw_circle(dr, dx + TILE_SIZE/2, dy + TILE_SIZE/2, | ||
1323 | ds->rrad - TILE_SIZE/16, | ||
1324 | COL_BACKGROUND, COL_WRONG); | ||
1325 | } | ||
1326 | |||
1327 | draw_text(dr, dx + TILE_SIZE/2, dy + TILE_SIZE/2, | ||
1328 | FONT_VARIABLE, TILE_SIZE/2, ALIGN_VCENTRE | ALIGN_HCENTRE, | ||
1329 | tcol, str); | ||
1330 | } | ||
1331 | if (gs_tile & FLAG_CURSOR) | ||
1332 | draw_square_cursor(dr, ds, dx, dy); | ||
1333 | |||
1334 | draw_update(dr, dx, dy, TILE_SIZE, TILE_SIZE); | ||
1335 | } | ||
1336 | GRID(ds, gx, gy) = gs_tile; | ||
1337 | } | ||
1338 | |||
1339 | #define CUR_ANIM 0.2F | ||
1340 | |||
1341 | static void game_redraw(drawing *dr, game_drawstate *ds, | ||
1342 | const game_state *oldstate, const game_state *state, | ||
1343 | int dir, const game_ui *ui, | ||
1344 | float animtime, float flashtime) | ||
1345 | { | ||
1346 | int i, x, y, ts = TILE_SIZE, isflash = 0, force = 0; | ||
1347 | |||
1348 | if (flashtime > 0) { | ||
1349 | int frame = (int)(flashtime / FLASH_FRAME); | ||
1350 | isflash = (frame % 2) == 0; | ||
1351 | debug(("game_redraw: flashtime = %f", flashtime)); | ||
1352 | } | ||
1353 | |||
1354 | if (!ds->started) { | ||
1355 | int x0 = TODRAW(0)-1, y0 = TODRAW(0)-1; | ||
1356 | int x1 = TODRAW(state->w+2), y1 = TODRAW(state->h+2); | ||
1357 | |||
1358 | draw_rect(dr, 0, 0, | ||
1359 | TILE_SIZE * (state->w+3), TILE_SIZE * (state->h+3), | ||
1360 | COL_BACKGROUND); | ||
1361 | |||
1362 | /* clockwise around the outline starting at pt behind (1,1). */ | ||
1363 | draw_line(dr, x0+ts, y0+ts, x0+ts, y0, COL_HIGHLIGHT); | ||
1364 | draw_line(dr, x0+ts, y0, x1-ts, y0, COL_HIGHLIGHT); | ||
1365 | draw_line(dr, x1-ts, y0, x1-ts, y0+ts, COL_LOWLIGHT); | ||
1366 | draw_line(dr, x1-ts, y0+ts, x1, y0+ts, COL_HIGHLIGHT); | ||
1367 | draw_line(dr, x1, y0+ts, x1, y1-ts, COL_LOWLIGHT); | ||
1368 | draw_line(dr, x1, y1-ts, x1-ts, y1-ts, COL_LOWLIGHT); | ||
1369 | draw_line(dr, x1-ts, y1-ts, x1-ts, y1, COL_LOWLIGHT); | ||
1370 | draw_line(dr, x1-ts, y1, x0+ts, y1, COL_LOWLIGHT); | ||
1371 | draw_line(dr, x0+ts, y1, x0+ts, y1-ts, COL_HIGHLIGHT); | ||
1372 | draw_line(dr, x0+ts, y1-ts, x0, y1-ts, COL_LOWLIGHT); | ||
1373 | draw_line(dr, x0, y1-ts, x0, y0+ts, COL_HIGHLIGHT); | ||
1374 | draw_line(dr, x0, y0+ts, x0+ts, y0+ts, COL_HIGHLIGHT); | ||
1375 | /* phew... */ | ||
1376 | |||
1377 | draw_update(dr, 0, 0, | ||
1378 | TILE_SIZE * (state->w+3), TILE_SIZE * (state->h+3)); | ||
1379 | force = 1; | ||
1380 | ds->started = 1; | ||
1381 | } | ||
1382 | |||
1383 | if (isflash != ds->isflash) force = 1; | ||
1384 | |||
1385 | /* draw the arena */ | ||
1386 | for (x = 0; x < state->w; x++) { | ||
1387 | for (y = 0; y < state->h; y++) { | ||
1388 | draw_arena_tile(dr, state, ds, ui, x, y, force, isflash); | ||
1389 | } | ||
1390 | } | ||
1391 | |||
1392 | /* draw the lasers */ | ||
1393 | ds->flash_laserno = LASER_EMPTY; | ||
1394 | if (ui->flash_laser == 1) | ||
1395 | ds->flash_laserno = ui->flash_laserno; | ||
1396 | else if (ui->flash_laser == 2 && animtime > 0) | ||
1397 | ds->flash_laserno = ui->flash_laserno; | ||
1398 | |||
1399 | for (i = 0; i < 2*(state->w+state->h); i++) { | ||
1400 | draw_laser_tile(dr, state, ds, ui, i, force); | ||
1401 | } | ||
1402 | |||
1403 | /* draw the 'finish' button */ | ||
1404 | if (CAN_REVEAL(state)) { | ||
1405 | int outline = (ui->cur_visible && ui->cur_x == 0 && ui->cur_y == 0) | ||
1406 | ? COL_CURSOR : COL_BALL; | ||
1407 | clip(dr, TODRAW(0)-1, TODRAW(0)-1, TILE_SIZE+1, TILE_SIZE+1); | ||
1408 | draw_circle(dr, TODRAW(0) + ds->crad, TODRAW(0) + ds->crad, ds->crad, | ||
1409 | outline, outline); | ||
1410 | draw_circle(dr, TODRAW(0) + ds->crad, TODRAW(0) + ds->crad, ds->crad-2, | ||
1411 | COL_BUTTON, COL_BUTTON); | ||
1412 | unclip(dr); | ||
1413 | } else { | ||
1414 | draw_rect(dr, TODRAW(0)-1, TODRAW(0)-1, | ||
1415 | TILE_SIZE+1, TILE_SIZE+1, COL_BACKGROUND); | ||
1416 | } | ||
1417 | draw_update(dr, TODRAW(0), TODRAW(0), TILE_SIZE, TILE_SIZE); | ||
1418 | ds->reveal = state->reveal; | ||
1419 | ds->isflash = isflash; | ||
1420 | |||
1421 | { | ||
1422 | char buf[256]; | ||
1423 | |||
1424 | if (ds->reveal) { | ||
1425 | if (state->nwrong == 0 && | ||
1426 | state->nmissed == 0 && | ||
1427 | state->nright >= state->minballs) | ||
1428 | sprintf(buf, "CORRECT!"); | ||
1429 | else | ||
1430 | sprintf(buf, "%d wrong and %d missed balls.", | ||
1431 | state->nwrong, state->nmissed); | ||
1432 | } else if (state->justwrong) { | ||
1433 | sprintf(buf, "Wrong! Guess again."); | ||
1434 | } else { | ||
1435 | if (state->nguesses > state->maxballs) | ||
1436 | sprintf(buf, "%d too many balls marked.", | ||
1437 | state->nguesses - state->maxballs); | ||
1438 | else if (state->nguesses <= state->maxballs && | ||
1439 | state->nguesses >= state->minballs) | ||
1440 | sprintf(buf, "Click button to verify guesses."); | ||
1441 | else if (state->maxballs == state->minballs) | ||
1442 | sprintf(buf, "Balls marked: %d / %d", | ||
1443 | state->nguesses, state->minballs); | ||
1444 | else | ||
1445 | sprintf(buf, "Balls marked: %d / %d-%d.", | ||
1446 | state->nguesses, state->minballs, state->maxballs); | ||
1447 | } | ||
1448 | if (ui->errors) { | ||
1449 | sprintf(buf + strlen(buf), " (%d error%s)", | ||
1450 | ui->errors, ui->errors > 1 ? "s" : ""); | ||
1451 | } | ||
1452 | status_bar(dr, buf); | ||
1453 | } | ||
1454 | } | ||
1455 | |||
1456 | static float game_anim_length(const game_state *oldstate, | ||
1457 | const game_state *newstate, int dir, game_ui *ui) | ||
1458 | { | ||
1459 | return (ui->flash_laser == 2) ? CUR_ANIM : 0.0F; | ||
1460 | } | ||
1461 | |||
1462 | static float game_flash_length(const game_state *oldstate, | ||
1463 | const game_state *newstate, int dir, game_ui *ui) | ||
1464 | { | ||
1465 | if (!oldstate->reveal && newstate->reveal) | ||
1466 | return 4.0F * FLASH_FRAME; | ||
1467 | else | ||
1468 | return 0.0F; | ||
1469 | } | ||
1470 | |||
1471 | static int game_status(const game_state *state) | ||
1472 | { | ||
1473 | if (state->reveal) { | ||
1474 | /* | ||
1475 | * We return nonzero whenever the solution has been revealed, | ||
1476 | * even (on spoiler grounds) if it wasn't guessed correctly. | ||
1477 | */ | ||
1478 | if (state->nwrong == 0 && | ||
1479 | state->nmissed == 0 && | ||
1480 | state->nright >= state->minballs) | ||
1481 | return +1; | ||
1482 | else | ||
1483 | return -1; | ||
1484 | } | ||
1485 | return 0; | ||
1486 | } | ||
1487 | |||
1488 | static int game_timing_state(const game_state *state, game_ui *ui) | ||
1489 | { | ||
1490 | return TRUE; | ||
1491 | } | ||
1492 | |||
1493 | static void game_print_size(const game_params *params, float *x, float *y) | ||
1494 | { | ||
1495 | } | ||
1496 | |||
1497 | static void game_print(drawing *dr, const game_state *state, int tilesize) | ||
1498 | { | ||
1499 | } | ||
1500 | |||
1501 | #ifdef COMBINED | ||
1502 | #define thegame blackbox | ||
1503 | #endif | ||
1504 | |||
1505 | const struct game thegame = { | ||
1506 | "Black Box", "games.blackbox", "blackbox", | ||
1507 | default_params, | ||
1508 | game_fetch_preset, NULL, | ||
1509 | decode_params, | ||
1510 | encode_params, | ||
1511 | free_params, | ||
1512 | dup_params, | ||
1513 | TRUE, game_configure, custom_params, | ||
1514 | validate_params, | ||
1515 | new_game_desc, | ||
1516 | validate_desc, | ||
1517 | new_game, | ||
1518 | dup_game, | ||
1519 | free_game, | ||
1520 | TRUE, solve_game, | ||
1521 | FALSE, game_can_format_as_text_now, game_text_format, | ||
1522 | new_ui, | ||
1523 | free_ui, | ||
1524 | encode_ui, | ||
1525 | decode_ui, | ||
1526 | game_changed_state, | ||
1527 | interpret_move, | ||
1528 | execute_move, | ||
1529 | PREFERRED_TILE_SIZE, game_compute_size, game_set_size, | ||
1530 | game_colours, | ||
1531 | game_new_drawstate, | ||
1532 | game_free_drawstate, | ||
1533 | game_redraw, | ||
1534 | game_anim_length, | ||
1535 | game_flash_length, | ||
1536 | game_status, | ||
1537 | FALSE, FALSE, game_print_size, game_print, | ||
1538 | TRUE, /* wants_statusbar */ | ||
1539 | FALSE, game_timing_state, | ||
1540 | REQUIRE_RBUTTON, /* flags */ | ||
1541 | }; | ||
1542 | |||
1543 | /* vim: set shiftwidth=4 tabstop=8: */ | ||