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