2 * blackbox.c: implementation of 'Black Box'.
14 #define PREFERRED_TILE_SIZE 32
15 #define FLASH_FRAME 0.2F
17 /* Terminology, for ease of reading various macros scattered about the place.
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).
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.
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.
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
,
41 int minballs
, maxballs
;
44 static game_params
*default_params(void)
46 game_params
*ret
= snew(game_params
);
49 ret
->minballs
= ret
->maxballs
= 5;
54 static const game_params blackbox_presets
[] = {
62 static int game_fetch_preset(int i
, char **name
, game_params
**params
)
67 if (i
< 0 || i
>= lenof(blackbox_presets
))
70 ret
= snew(game_params
);
71 *ret
= blackbox_presets
[i
];
73 if (ret
->minballs
== ret
->maxballs
)
74 sprintf(str
, "%dx%d, %d balls",
75 ret
->w
, ret
->h
, ret
->minballs
);
77 sprintf(str
, "%dx%d, %d-%d balls",
78 ret
->w
, ret
->h
, ret
->minballs
, ret
->maxballs
);
85 static void free_params(game_params
*params
)
90 static game_params
*dup_params(const game_params
*params
)
92 game_params
*ret
= snew(game_params
);
93 *ret
= *params
; /* structure copy */
97 static void decode_params(game_params
*params
, char const *string
)
99 char const *p
= string
;
100 game_params
*defs
= default_params();
102 *params
= *defs
; free_params(defs
);
108 while (*p
&& isdigit((unsigned char)*p
)) p
++;
113 while (*p
&& isdigit((unsigned char)*p
)) p
++;
117 params
->minballs
= atoi(p
);
118 while (*p
&& isdigit((unsigned char)*p
)) p
++;
122 params
->maxballs
= atoi(p
);
123 while (*p
&& isdigit((unsigned char)*p
)) p
++;
132 static char *encode_params(const game_params
*params
, int full
)
136 sprintf(str
, "w%dh%dm%dM%d",
137 params
->w
, params
->h
, params
->minballs
, params
->maxballs
);
141 static config_item
*game_configure(const game_params
*params
)
146 ret
= snewn(4, config_item
);
148 ret
[0].name
= "Width";
149 ret
[0].type
= C_STRING
;
150 sprintf(buf
, "%d", params
->w
);
151 ret
[0].sval
= dupstr(buf
);
154 ret
[1].name
= "Height";
155 ret
[1].type
= C_STRING
;
156 sprintf(buf
, "%d", params
->h
);
157 ret
[1].sval
= dupstr(buf
);
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
);
165 sprintf(buf
, "%d-%d", params
->minballs
, params
->maxballs
);
166 ret
[2].sval
= dupstr(buf
);
177 static game_params
*custom_params(const config_item
*cfg
)
179 game_params
*ret
= snew(game_params
);
181 ret
->w
= atoi(cfg
[0].sval
);
182 ret
->h
= atoi(cfg
[1].sval
);
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
);
191 static char *validate_params(const game_params
*params
, int full
)
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";
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.
213 static char *new_game_desc(const game_params
*params
, random_state
*rs
,
214 char **aux
, int interactive
)
216 int nballs
= params
->minballs
, i
;
220 if (params
->maxballs
> params
->minballs
)
221 nballs
+= random_upto(rs
, params
->maxballs
- params
->minballs
+ 1);
223 grid
= snewn(params
->w
*params
->h
, char);
224 memset(grid
, 0, params
->w
* params
->h
* sizeof(char));
226 bmp
= snewn(nballs
*2 + 2, unsigned char);
227 memset(bmp
, 0, (nballs
*2 + 2) * sizeof(unsigned char));
232 for (i
= 0; i
< nballs
; i
++) {
236 x
= random_upto(rs
, params
->w
);
237 y
= random_upto(rs
, params
->h
);
238 } while (grid
[y
*params
->w
+ x
]);
240 grid
[y
*params
->w
+ x
] = 1;
242 bmp
[(i
+1)*2 + 0] = x
;
243 bmp
[(i
+1)*2 + 1] = y
;
247 obfuscate_bitmap(bmp
, (nballs
*2 + 2) * 8, FALSE
);
248 ret
= bin2hex(bmp
, nballs
*2 + 2);
254 static char *validate_desc(const game_params
*params
, const char *desc
)
256 int nballs
, dlen
= strlen(desc
), i
;
260 /* the bitmap is 2+(nballs*2) long; the hex version is double that. */
261 nballs
= ((dlen
/2)-2)/2;
263 if (dlen
< 4 || dlen
% 4 ||
264 nballs
< params
->minballs
|| nballs
> params
->maxballs
)
265 return "Game description is wrong length";
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
)
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
)
286 #define BALL_CORRECT 0x01
287 #define BALL_GUESS 0x02
288 #define BALL_LOCK 0x04
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)
298 #define FLAG_CURSOR 0x10000 /* needs to be disjoint from both sets */
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
;
309 #define GRID(s,x,y) ((s)->grid[(y)*((s)->w+2) + (x)])
311 #define RANGECHECK(s,x) ((x) >= 0 && (x) <= (s)->nlasers)
313 /* specify numbers because they must match array indexes. */
314 enum { DIR_UP
= 0, DIR_RIGHT
= 1, DIR_DOWN
= 2, DIR_LEFT
= 3 };
316 struct offset
{ int x
, y
; };
318 static const struct offset offsets
[] = {
320 { 1, 0 }, /* right */
326 static const char *dirstrs
[] = {
327 "UP", "RIGHT", "DOWN", "LEFT"
331 static int range2grid(const game_state
*state
, int rangeno
, int *x
, int *y
,
337 if (rangeno
< state
->w
) {
338 /* top row; from (1,0) to (w,0) */
341 *direction
= DIR_DOWN
;
345 if (rangeno
< state
->h
) {
346 /* RHS; from (w+1, 1) to (w+1, h) */
349 *direction
= DIR_LEFT
;
353 if (rangeno
< state
->w
) {
354 /* bottom row; from (1, h+1) to (w, h+1); counts backwards */
355 *x
= (state
->w
- rangeno
);
361 if (rangeno
< state
->h
) {
362 /* LHS; from (0, 1) to (0, h); counts backwards */
364 *y
= (state
->h
- rangeno
);
365 *direction
= DIR_RIGHT
;
371 static int grid2range(const game_state
*state
, int x
, int y
, int *rangeno
)
373 int ret
, x1
= state
->w
+1, y1
= state
->h
+1;
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 */
378 if ((x
== 0 || x
== x1
) && (y
== 0 || y
== y1
))
379 return 0; /* one of 4 corners */
381 if (y
== 0) { /* top line */
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
;
391 debug(("grid2range: (%d,%d) rangeno = %d\n", x
, y
, ret
));
395 static game_state
*new_game(midend
*me
, const game_params
*params
,
398 game_state
*state
= snew(game_state
);
399 int dlen
= strlen(desc
), i
;
402 state
->minballs
= params
->minballs
;
403 state
->maxballs
= params
->maxballs
;
404 state
->nballs
= ((dlen
/2)-2)/2;
406 bmp
= hex2bin(desc
, state
->nballs
*2 + 2);
407 obfuscate_bitmap(bmp
, (state
->nballs
*2 + 2) * 8, TRUE
);
409 state
->w
= bmp
[0]; state
->h
= bmp
[1];
410 state
->nlasers
= 2 * (state
->w
+ state
->h
);
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));
415 state
->exits
= snewn(state
->nlasers
, unsigned int);
416 memset(state
->exits
, LASER_EMPTY
, state
->nlasers
* sizeof(unsigned int));
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
;
423 state
->done
= state
->nguesses
= state
->reveal
= state
->justwrong
=
424 state
->nright
= state
->nwrong
= state
->nmissed
= 0;
430 #define XFER(x) ret->x = state->x
432 static game_state
*dup_game(const game_state
*state
)
434 game_state
*ret
= snew(game_state
);
437 XFER(minballs
); XFER(maxballs
);
438 XFER(nballs
); XFER(nlasers
);
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));
450 XFER(nright
); XFER(nwrong
); XFER(nmissed
);
457 static void free_game(game_state
*state
)
464 static char *solve_game(const game_state
*state
, const game_state
*currstate
,
465 const char *aux
, char **error
)
470 static int game_can_format_as_text_now(const game_params
*params
)
475 static char *game_text_format(const game_state
*state
)
483 int cur_x
, cur_y
, cur_visible
;
484 int flash_laser
; /* 0 = never, 1 = always, 2 = if anim. */
487 static game_ui
*new_ui(const game_state
*state
)
489 game_ui
*ui
= snew(game_ui
);
490 ui
->flash_laserno
= LASER_EMPTY
;
494 ui
->cur_x
= ui
->cur_y
= 1;
502 static void free_ui(game_ui
*ui
)
507 static char *encode_ui(const game_ui
*ui
)
511 * The error counter needs preserving across a serialisation.
513 sprintf(buf
, "E%d", ui
->errors
);
517 static void decode_ui(game_ui
*ui
, const char *encoding
)
519 sscanf(encoding
, "E%d", &ui
->errors
);
522 static void game_changed_state(game_ui
*ui
, const game_state
*oldstate
,
523 const game_state
*newstate
)
526 * If we've encountered a `justwrong' state as a result of
527 * actually making a move, increment the ui error counter.
529 if (newstate
->justwrong
&& ui
->newmove
)
534 #define OFFSET(gx,gy,o) do { \
535 int off = (4 + (o) % 4) % 4; \
536 (gx) += offsets[off].x; \
537 (gy) += offsets[off].y; \
540 enum { LOOK_LEFT
, LOOK_FORWARD
, LOOK_RIGHT
};
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
)
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");
557 debug(("isball, new (%d, %d)\n", gx
, gy
));
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
)
563 if (GRID(state
, gx
,gy
) & BALL_CORRECT
)
569 static int fire_laser_internal(game_state
*state
, int x
, int y
, int direction
)
571 int unused
, lno
, tmp
;
573 tmp
= grid2range(state
, x
, y
, &lno
);
576 /* deal with strange initial reflection rules (that stop
577 * you turning down the laser range) */
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 */
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 */
591 /* move us onto the grid. */
592 OFFSET(x
, y
, direction
);
595 debug(("fire_laser: looping at (%d, %d) pointing %s\n",
596 x
, y
, dirstrs
[direction
]));
597 if (grid2range(state
, x
, y
, &unused
)) {
600 tmp
= grid2range(state
, x
, y
, &exitno
);
603 return (lno
== exitno
? LASER_REFLECT
: exitno
);
605 /* paranoia. This obviously should never happen */
606 assert(!(GRID(state
, x
, y
) & BALL_CORRECT
));
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 */
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;
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;
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
);
632 static int laser_exit(game_state
*state
, int entryno
)
634 int tmp
, x
, y
, direction
;
636 tmp
= range2grid(state
, entryno
, &x
, &y
, &direction
);
639 return fire_laser_internal(state
, x
, y
, direction
);
642 static void fire_laser(game_state
*state
, int entryno
)
644 int tmp
, exitno
, x
, y
, direction
;
646 tmp
= range2grid(state
, entryno
, &x
, &y
, &direction
);
649 exitno
= fire_laser_internal(state
, x
, y
, direction
);
651 if (exitno
== LASER_HIT
|| exitno
== LASER_REFLECT
) {
652 GRID(state
, x
, y
) = state
->exits
[entryno
] = exitno
;
654 int newno
= state
->laserno
++;
655 int xend
, yend
, unused
;
656 tmp
= range2grid(state
, exitno
, &xend
, ¥d
, &unused
);
658 GRID(state
, x
, y
) = GRID(state
, xend
, yend
) = newno
;
659 state
->exits
[entryno
] = exitno
;
660 state
->exits
[exitno
] = entryno
;
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
)
671 game_state
*solution
, *guesses
;
672 int i
, x
, y
, n
, unused
, tmp
;
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
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.
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
;
697 for (i
= 0; i
< guesses
->nlasers
; i
++) {
698 if (guesses
->exits
[i
] != LASER_EMPTY
&&
699 guesses
->exits
[i
] != laser_exit(guesses
, i
))
704 * At least one of the player's existing lasers
705 * contradicts their ball placement. Pick a random one,
706 * highlight it, and return.
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.
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
);
717 for (i
= 0; i
< guesses
->nlasers
; i
++) {
718 if (guesses
->exits
[i
] != LASER_EMPTY
&&
719 guesses
->exits
[i
] != laser_exit(guesses
, i
) &&
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
;
732 for (i
= 0; i
< guesses
->nlasers
; i
++) {
733 if (guesses
->exits
[i
] == LASER_EMPTY
&&
734 laser_exit(state
, i
) != laser_exit(guesses
, i
))
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.
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.
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
);
752 for (i
= 0; i
< guesses
->nlasers
; i
++) {
753 if (guesses
->exits
[i
] == LASER_EMPTY
&&
754 laser_exit(state
, i
) != laser_exit(guesses
, i
) &&
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
;
770 /* duplicate the state (to solution) */
771 solution
= dup_game(state
);
773 /* clear out the lasers of solution */
774 for (i
= 0; i
< solution
->nlasers
; i
++) {
775 tmp
= range2grid(solution
, i
, &x
, &y
, &unused
);
777 GRID(solution
, x
, y
) = 0;
778 solution
->exits
[i
] = LASER_EMPTY
;
781 /* duplicate solution to guess. */
782 guesses
= dup_game(solution
);
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
;
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
);
803 /* check each game_state's laser against the other; if any differ, return 0 */
805 for (i
= 0; i
< solution
->nlasers
; i
++) {
806 tmp
= range2grid(solution
, i
, &x
, &y
, &unused
);
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,
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
];
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
);
823 GRID(state
, x
, y
) = newno
;
824 GRID(state
, ex
, ey
) = newno
;
826 state
->exits
[i
] |= LASER_OMITTED
;
828 state
->exits
[i
] |= LASER_WRONG
;
834 state
->nguesses
< state
->minballs
||
835 state
->nguesses
> state
->maxballs
) goto done
;
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
;
844 GRID(state
, x
, y
) &= ~BALL_CORRECT
;
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
))
856 else if (bs
== BALL_GUESS
)
858 else if (bs
== BALL_CORRECT
)
868 #define TILE_SIZE (ds->tilesize)
870 #define TODRAW(x) ((TILE_SIZE * (x)) + (TILE_SIZE / 2))
871 #define FROMDRAW(x) (((x) - (TILE_SIZE / 2)) / TILE_SIZE)
873 #define CAN_REVEAL(state) ((state)->nguesses >= (state)->minballs && \
874 (state)->nguesses <= (state)->maxballs && \
875 !(state)->reveal && !(state)->justwrong)
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 */
881 int flash_laserno
, isflash
;
884 static char *interpret_move(const game_state
*state
, game_ui
*ui
,
885 const game_drawstate
*ds
,
886 int x
, int y
, int button
)
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
;
893 if (IS_CURSOR_MOVE(button
)) {
894 int cx
= ui
->cur_x
, cy
= ui
->cur_y
;
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. */
908 if (button
== LEFT_BUTTON
|| button
== RIGHT_BUTTON
) {
913 } else if (button
== LEFT_RELEASE
) {
916 } else if (IS_CURSOR_SELECT(button
)) {
917 if (ui
->cur_visible
) {
926 /* Fix up 'button' for the below logic. */
927 if (button
== CURSOR_SELECT2
) button
= RIGHT_BUTTON
;
928 else button
= LEFT_BUTTON
;
931 if (gx
!= -1 && gy
!= -1) {
932 if (gx
== 0 && gy
== 0 && button
== LEFT_BUTTON
)
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
;
939 action
= TOGGLE_LOCK
;
941 if (grid2range(state
, gx
, gy
, &rangeno
)) {
942 if (button
== LEFT_BUTTON
)
944 else if (gy
== 0 || gy
> state
->h
)
945 action
= TOGGLE_COLUMN_LOCK
; /* and use gx */
947 action
= TOGGLE_ROW_LOCK
; /* and use gy */
953 sprintf(buf
, "T%d,%d", gx
, gy
);
957 sprintf(buf
, "LB%d,%d", gx
, gy
);
960 case TOGGLE_COLUMN_LOCK
:
961 sprintf(buf
, "LC%d", gx
);
964 case TOGGLE_ROW_LOCK
:
965 sprintf(buf
, "LR%d", gy
);
969 if (state
->reveal
&& state
->exits
[rangeno
] == LASER_EMPTY
)
971 ui
->flash_laserno
= rangeno
;
972 ui
->flash_laser
= wouldflash
;
974 if (state
->exits
[rangeno
] != LASER_EMPTY
)
976 sprintf(buf
, "F%d", rangeno
);
980 if (!CAN_REVEAL(state
)) return nullret
;
981 if (ui
->cur_visible
== 1) ui
->cur_x
= ui
->cur_y
= 1;
988 if (state
->reveal
) return nullret
;
993 static game_state
*execute_move(const game_state
*from
, const char *move
)
995 game_state
*ret
= dup_game(from
);
996 int gx
= -1, gy
= -1, rangeno
= -1;
998 if (ret
->justwrong
) {
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
);
1006 if (!strcmp(move
, "S")) {
1007 check_guesses(ret
, FALSE
);
1011 if (from
->reveal
) goto badmove
;
1012 if (!*move
) goto badmove
;
1016 sscanf(move
+1, "%d,%d", &gx
, &gy
);
1017 if (gx
< 1 || gy
< 1 || gx
> ret
->w
|| gy
> ret
->h
)
1019 if (GRID(ret
, gx
, gy
) & BALL_GUESS
) {
1021 GRID(ret
, gx
, gy
) &= ~BALL_GUESS
;
1024 GRID(ret
, gx
, gy
) |= BALL_GUESS
;
1029 sscanf(move
+1, "%d", &rangeno
);
1030 if (ret
->exits
[rangeno
] != LASER_EMPTY
)
1032 if (!RANGECHECK(ret
, rangeno
))
1034 fire_laser(ret
, rangeno
);
1038 if (ret
->nguesses
< ret
->minballs
||
1039 ret
->nguesses
> ret
->maxballs
)
1041 check_guesses(ret
, TRUE
);
1047 if (strlen(move
) < 2) goto badmove
;
1050 sscanf(move
+2, "%d,%d", &gx
, &gy
);
1051 if (gx
< 1 || gy
< 1 || gx
> ret
->w
|| gy
> ret
->h
)
1053 GRID(ret
, gx
, gy
) ^= BALL_LOCK
;
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; \
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); }
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); }
1096 /* ----------------------------------------------------------------------
1100 static void game_compute_size(const game_params
*params
, int tilesize
,
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
;
1110 static void game_set_size(drawing
*dr
, game_drawstate
*ds
,
1111 const game_params
*params
, int tilesize
)
1113 ds
->tilesize
= tilesize
;
1114 ds
->crad
= (tilesize
-1)/2;
1115 ds
->rrad
= (3*tilesize
)/8;
1118 static float *game_colours(frontend
*fe
, int *ncolours
)
1120 float *ret
= snewn(3 * NCOLOURS
, float);
1123 game_mkhighlight(fe
, ret
, COL_BACKGROUND
, COL_HIGHLIGHT
, COL_LOWLIGHT
);
1125 ret
[COL_BALL
* 3 + 0] = 0.0F
;
1126 ret
[COL_BALL
* 3 + 1] = 0.0F
;
1127 ret
[COL_BALL
* 3 + 2] = 0.0F
;
1129 ret
[COL_WRONG
* 3 + 0] = 1.0F
;
1130 ret
[COL_WRONG
* 3 + 1] = 0.0F
;
1131 ret
[COL_WRONG
* 3 + 2] = 0.0F
;
1133 ret
[COL_BUTTON
* 3 + 0] = 0.0F
;
1134 ret
[COL_BUTTON
* 3 + 1] = 1.0F
;
1135 ret
[COL_BUTTON
* 3 + 2] = 0.0F
;
1137 ret
[COL_CURSOR
* 3 + 0] = 1.0F
;
1138 ret
[COL_CURSOR
* 3 + 1] = 0.0F
;
1139 ret
[COL_CURSOR
* 3 + 2] = 0.0F
;
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
;
1148 ret
[COL_FLASHTEXT
* 3 + 0] = 0.0F
;
1149 ret
[COL_FLASHTEXT
* 3 + 1] = 1.0F
;
1150 ret
[COL_FLASHTEXT
* 3 + 2] = 0.0F
;
1152 *ncolours
= NCOLOURS
;
1156 static game_drawstate
*game_new_drawstate(drawing
*dr
, const game_state
*state
)
1158 struct game_drawstate
*ds
= snew(struct game_drawstate
);
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
;
1171 static void game_free_drawstate(drawing
*dr
, game_drawstate
*ds
)
1177 static void draw_square_cursor(drawing
*dr
, game_drawstate
*ds
, int dx
, int dy
)
1179 int coff
= TILE_SIZE
/8;
1180 draw_rect_outline(dr
, dx
+ coff
, dy
+ coff
,
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
)
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
);
1195 if (ui
->cur_visible
&& ui
->cur_x
== gx
&& ui
->cur_y
== gy
)
1196 gs_tile
|= FLAG_CURSOR
;
1198 if (gs_tile
!= ds_tile
|| gs
->reveal
!= ds
->reveal
|| force
) {
1201 bg
= (gs
->reveal
? COL_BACKGROUND
:
1202 (gs_tile
& BALL_LOCK
) ? COL_LOCK
: COL_COVER
);
1204 draw_rect(dr
, dx
, dy
, TILE_SIZE
, TILE_SIZE
, bg
);
1205 draw_rect_outline(dr
, dx
, dy
, TILE_SIZE
, TILE_SIZE
, COL_GRID
);
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
;
1219 /* guesses are black/black, all else background. */
1220 if (gs_tile
& BALL_GUESS
) {
1226 ocol
= (gs_tile
& FLAG_CURSOR
&& bcol
!= bg
) ? COL_CURSOR
: bcol
;
1228 draw_circle(dr
, dx
+ TILE_SIZE
/2, dy
+ TILE_SIZE
/2, ds
->crad
-1,
1230 draw_circle(dr
, dx
+ TILE_SIZE
/2, dy
+ TILE_SIZE
/2, ds
->crad
-3,
1234 if (gs_tile
& FLAG_CURSOR
&& bcol
== bg
)
1235 draw_square_cursor(dr
, ds
, dx
, dy
);
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;
1244 /* Incorrect guess; draw a red cross over the ball. */
1253 draw_polygon(dr
, coords
, 4, COL_WRONG
, COL_WRONG
);
1262 draw_polygon(dr
, coords
, 4, COL_WRONG
, COL_WRONG
);
1264 draw_update(dr
, dx
, dy
, TILE_SIZE
, TILE_SIZE
);
1266 GRID(ds
,gx
,gy
) = gs_tile
;
1269 static void draw_laser_tile(drawing
*dr
, const game_state
*gs
,
1270 game_drawstate
*ds
, const game_ui
*ui
,
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
;
1277 tmp
= range2grid(gs
, lno
, &gx
, &gy
, &unused
);
1279 gs_tile
= GRID(gs
, gx
, gy
);
1280 ds_tile
= GRID(ds
, gx
, gy
);
1284 wrong
= gs
->exits
[lno
] & LASER_WRONG
;
1285 omitted
= gs
->exits
[lno
] & LASER_OMITTED
;
1286 exitno
= gs
->exits
[lno
] & ~LASER_FLAGMASK
;
1288 reflect
= gs_tile
& LASER_REFLECT
;
1289 hit
= gs_tile
& LASER_HIT
;
1290 laserval
= gs_tile
& ~LASER_FLAGMASK
;
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
;
1298 if (gs_tile
& LASER_FLASHED
) flash
= 1;
1300 gs_tile
|= wrong
| omitted
;
1302 if (ui
->cur_visible
&& ui
->cur_x
== gx
&& ui
->cur_y
== gy
)
1303 gs_tile
|= FLAG_CURSOR
;
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
);
1309 if (gs_tile
&~ (LASER_WRONG
| LASER_OMITTED
| FLAG_CURSOR
)) {
1311 int tcol
= flash
? COL_FLASHTEXT
: omitted
? COL_WRONG
: COL_TEXT
;
1314 sprintf(str
, "%s", reflect
? "R" : "H");
1316 sprintf(str
, "%d", laserval
);
1319 draw_circle(dr
, dx
+ TILE_SIZE
/2, dy
+ TILE_SIZE
/2,
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
);
1327 draw_text(dr
, dx
+ TILE_SIZE
/2, dy
+ TILE_SIZE
/2,
1328 FONT_VARIABLE
, TILE_SIZE
/2, ALIGN_VCENTRE
| ALIGN_HCENTRE
,
1331 if (gs_tile
& FLAG_CURSOR
)
1332 draw_square_cursor(dr
, ds
, dx
, dy
);
1334 draw_update(dr
, dx
, dy
, TILE_SIZE
, TILE_SIZE
);
1336 GRID(ds
, gx
, gy
) = gs_tile
;
1339 #define CUR_ANIM 0.2F
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
)
1346 int i
, x
, y
, ts
= TILE_SIZE
, isflash
= 0, force
= 0;
1348 if (flashtime
> 0) {
1349 int frame
= (int)(flashtime
/ FLASH_FRAME
);
1350 isflash
= (frame
% 2) == 0;
1351 debug(("game_redraw: flashtime = %f", flashtime
));
1355 int x0
= TODRAW(0)-1, y0
= TODRAW(0)-1;
1356 int x1
= TODRAW(state
->w
+2), y1
= TODRAW(state
->h
+2);
1359 TILE_SIZE
* (state
->w
+3), TILE_SIZE
* (state
->h
+3),
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
);
1377 draw_update(dr
, 0, 0,
1378 TILE_SIZE
* (state
->w
+3), TILE_SIZE
* (state
->h
+3));
1383 if (isflash
!= ds
->isflash
) force
= 1;
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
);
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
;
1399 for (i
= 0; i
< 2*(state
->w
+state
->h
); i
++) {
1400 draw_laser_tile(dr
, state
, ds
, ui
, i
, force
);
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
,
1410 draw_circle(dr
, TODRAW(0) + ds
->crad
, TODRAW(0) + ds
->crad
, ds
->crad
-2,
1411 COL_BUTTON
, COL_BUTTON
);
1414 draw_rect(dr
, TODRAW(0)-1, TODRAW(0)-1,
1415 TILE_SIZE
+1, TILE_SIZE
+1, COL_BACKGROUND
);
1417 draw_update(dr
, TODRAW(0), TODRAW(0), TILE_SIZE
, TILE_SIZE
);
1418 ds
->reveal
= state
->reveal
;
1419 ds
->isflash
= isflash
;
1425 if (state
->nwrong
== 0 &&
1426 state
->nmissed
== 0 &&
1427 state
->nright
>= state
->minballs
)
1428 sprintf(buf
, "CORRECT!");
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.");
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
);
1445 sprintf(buf
, "Balls marked: %d / %d-%d.",
1446 state
->nguesses
, state
->minballs
, state
->maxballs
);
1449 sprintf(buf
+ strlen(buf
), " (%d error%s)",
1450 ui
->errors
, ui
->errors
> 1 ? "s" : "");
1452 status_bar(dr
, buf
);
1456 static float game_anim_length(const game_state
*oldstate
,
1457 const game_state
*newstate
, int dir
, game_ui
*ui
)
1459 return (ui
->flash_laser
== 2) ? CUR_ANIM
: 0.0F
;
1462 static float game_flash_length(const game_state
*oldstate
,
1463 const game_state
*newstate
, int dir
, game_ui
*ui
)
1465 if (!oldstate
->reveal
&& newstate
->reveal
)
1466 return 4.0F
* FLASH_FRAME
;
1471 static int game_status(const game_state
*state
)
1473 if (state
->reveal
) {
1475 * We return nonzero whenever the solution has been revealed,
1476 * even (on spoiler grounds) if it wasn't guessed correctly.
1478 if (state
->nwrong
== 0 &&
1479 state
->nmissed
== 0 &&
1480 state
->nright
>= state
->minballs
)
1488 static int game_timing_state(const game_state
*state
, game_ui
*ui
)
1493 static void game_print_size(const game_params
*params
, float *x
, float *y
)
1497 static void game_print(drawing
*dr
, const game_state
*state
, int tilesize
)
1502 #define thegame blackbox
1505 const struct game thegame
= {
1506 "Black Box", "games.blackbox", "blackbox",
1508 game_fetch_preset
, NULL
,
1513 TRUE
, game_configure
, custom_params
,
1521 FALSE
, game_can_format_as_text_now
, game_text_format
,
1529 PREFERRED_TILE_SIZE
, game_compute_size
, game_set_size
,
1532 game_free_drawstate
,
1537 FALSE
, FALSE
, game_print_size
, game_print
,
1538 TRUE
, /* wants_statusbar */
1539 FALSE
, game_timing_state
,
1540 REQUIRE_RBUTTON
, /* flags */
1543 /* vim: set shiftwidth=4 tabstop=8: */