summaryrefslogtreecommitdiff
path: root/apps/plugins/puzzles/src/blackbox.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/plugins/puzzles/src/blackbox.c')
-rw-r--r--apps/plugins/puzzles/src/blackbox.c115
1 files changed, 66 insertions, 49 deletions
diff --git a/apps/plugins/puzzles/src/blackbox.c b/apps/plugins/puzzles/src/blackbox.c
index a9c1f88261..ab8e382d0a 100644
--- a/apps/plugins/puzzles/src/blackbox.c
+++ b/apps/plugins/puzzles/src/blackbox.c
@@ -7,7 +7,11 @@
7#include <string.h> 7#include <string.h>
8#include <assert.h> 8#include <assert.h>
9#include <ctype.h> 9#include <ctype.h>
10#include <math.h> 10#ifdef NO_TGMATH_H
11# include <math.h>
12#else
13# include <tgmath.h>
14#endif
11 15
12#include "puzzles.h" 16#include "puzzles.h"
13 17
@@ -192,12 +196,14 @@ static const char *validate_params(const game_params *params, bool full)
192 * types, and could be worked around if required. */ 196 * types, and could be worked around if required. */
193 if (params->w > 255 || params->h > 255) 197 if (params->w > 255 || params->h > 255)
194 return "Widths and heights greater than 255 are not supported"; 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";
195 if (params->minballs > params->maxballs) 203 if (params->minballs > params->maxballs)
196 return "Minimum number of balls may not be greater than maximum"; 204 return "Minimum number of balls may not be greater than maximum";
197 if (params->minballs >= params->w * params->h) 205 if (params->minballs >= params->w * params->h)
198 return "Too many balls to fit in grid"; 206 return "Too many balls to fit in grid";
199 if (params->minballs < 1)
200 return "Number of balls must be at least one";
201 return NULL; 207 return NULL;
202} 208}
203 209
@@ -307,7 +313,7 @@ struct game_state {
307 313
308#define GRID(s,x,y) ((s)->grid[(y)*((s)->w+2) + (x)]) 314#define GRID(s,x,y) ((s)->grid[(y)*((s)->w+2) + (x)])
309 315
310#define RANGECHECK(s,x) ((x) >= 0 && (x) <= (s)->nlasers) 316#define RANGECHECK(s,x) ((x) >= 0 && (x) < (s)->nlasers)
311 317
312/* specify numbers because they must match array indexes. */ 318/* specify numbers because they must match array indexes. */
313enum { DIR_UP = 0, DIR_RIGHT = 1, DIR_DOWN = 2, DIR_LEFT = 3 }; 319enum { DIR_UP = 0, DIR_RIGHT = 1, DIR_DOWN = 2, DIR_LEFT = 3 };
@@ -468,16 +474,6 @@ static char *solve_game(const game_state *state, const game_state *currstate,
468 return dupstr("S"); 474 return dupstr("S");
469} 475}
470 476
471static bool game_can_format_as_text_now(const game_params *params)
472{
473 return true;
474}
475
476static char *game_text_format(const game_state *state)
477{
478 return NULL;
479}
480
481struct game_ui { 477struct game_ui {
482 int flash_laserno; 478 int flash_laserno;
483 int errors; 479 int errors;
@@ -495,7 +491,7 @@ static game_ui *new_ui(const game_state *state)
495 ui->newmove = false; 491 ui->newmove = false;
496 492
497 ui->cur_x = ui->cur_y = 1; 493 ui->cur_x = ui->cur_y = 1;
498 ui->cur_visible = false; 494 ui->cur_visible = getenv_bool("PUZZLES_SHOW_CURSOR", false);
499 495
500 ui->flash_laser = 0; 496 ui->flash_laser = 0;
501 497
@@ -517,7 +513,8 @@ static char *encode_ui(const game_ui *ui)
517 return dupstr(buf); 513 return dupstr(buf);
518} 514}
519 515
520static void decode_ui(game_ui *ui, const char *encoding) 516static void decode_ui(game_ui *ui, const char *encoding,
517 const game_state *state)
521{ 518{
522 sscanf(encoding, "E%d", &ui->errors); 519 sscanf(encoding, "E%d", &ui->errors);
523} 520}
@@ -534,6 +531,41 @@ static void game_changed_state(game_ui *ui, const game_state *oldstate,
534 ui->newmove = false; 531 ui->newmove = false;
535} 532}
536 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
537#define OFFSET(gx,gy,o) do { \ 569#define OFFSET(gx,gy,o) do { \
538 int off = (4 + (o) % 4) % 4; \ 570 int off = (4 + (o) % 4) % 4; \
539 (gx) += offsets[off].x; \ 571 (gx) += offsets[off].x; \
@@ -875,7 +907,7 @@ done:
875#define TILE_SIZE (ds->tilesize) 907#define TILE_SIZE (ds->tilesize)
876 908
877#define TODRAW(x) ((TILE_SIZE * (x)) + (TILE_SIZE / 2)) 909#define TODRAW(x) ((TILE_SIZE * (x)) + (TILE_SIZE / 2))
878#define FROMDRAW(x) (((x) - (TILE_SIZE / 2)) / TILE_SIZE) 910#define FROMDRAW(x) (((x) + (TILE_SIZE / 2)) / TILE_SIZE - 1)
879 911
880#define CAN_REVEAL(state) ((state)->nguesses >= (state)->minballs && \ 912#define CAN_REVEAL(state) ((state)->nguesses >= (state)->minballs && \
881 (state)->nguesses <= (state)->maxballs && \ 913 (state)->nguesses <= (state)->maxballs && \
@@ -900,7 +932,7 @@ static char *interpret_move(const game_state *state, game_ui *ui,
900 if (IS_CURSOR_MOVE(button)) { 932 if (IS_CURSOR_MOVE(button)) {
901 int cx = ui->cur_x, cy = ui->cur_y; 933 int cx = ui->cur_x, cy = ui->cur_y;
902 934
903 move_cursor(button, &cx, &cy, state->w+2, state->h+2, false); 935 move_cursor(button, &cx, &cy, state->w+2, state->h+2, false, NULL);
904 if ((cx == 0 && cy == 0 && !CAN_REVEAL(state)) || 936 if ((cx == 0 && cy == 0 && !CAN_REVEAL(state)) ||
905 (cx == 0 && cy == state->h+1) || 937 (cx == 0 && cy == state->h+1) ||
906 (cx == state->w+1 && cy == 0) || 938 (cx == state->w+1 && cy == 0) ||
@@ -909,7 +941,7 @@ static char *interpret_move(const game_state *state, game_ui *ui,
909 ui->cur_x = cx; 941 ui->cur_x = cx;
910 ui->cur_y = cy; 942 ui->cur_y = cy;
911 ui->cur_visible = true; 943 ui->cur_visible = true;
912 return UI_UPDATE; 944 return MOVE_UI_UPDATE;
913 } 945 }
914 946
915 if (button == LEFT_BUTTON || button == RIGHT_BUTTON) { 947 if (button == LEFT_BUTTON || button == RIGHT_BUTTON) {
@@ -919,7 +951,7 @@ static char *interpret_move(const game_state *state, game_ui *ui,
919 wouldflash = 1; 951 wouldflash = 1;
920 } else if (button == LEFT_RELEASE) { 952 } else if (button == LEFT_RELEASE) {
921 ui->flash_laser = 0; 953 ui->flash_laser = 0;
922 return UI_UPDATE; 954 return MOVE_UI_UPDATE;
923 } else if (IS_CURSOR_SELECT(button)) { 955 } else if (IS_CURSOR_SELECT(button)) {
924 if (ui->cur_visible) { 956 if (ui->cur_visible) {
925 gx = ui->cur_x; 957 gx = ui->cur_x;
@@ -928,7 +960,7 @@ static char *interpret_move(const game_state *state, game_ui *ui,
928 wouldflash = 2; 960 wouldflash = 2;
929 } else { 961 } else {
930 ui->cur_visible = true; 962 ui->cur_visible = true;
931 return UI_UPDATE; 963 return MOVE_UI_UPDATE;
932 } 964 }
933 /* Fix up 'button' for the below logic. */ 965 /* Fix up 'button' for the below logic. */
934 if (button == CURSOR_SELECT2) button = RIGHT_BUTTON; 966 if (button == CURSOR_SELECT2) button = RIGHT_BUTTON;
@@ -977,9 +1009,9 @@ static char *interpret_move(const game_state *state, game_ui *ui,
977 return nullret; 1009 return nullret;
978 ui->flash_laserno = rangeno; 1010 ui->flash_laserno = rangeno;
979 ui->flash_laser = wouldflash; 1011 ui->flash_laser = wouldflash;
980 nullret = UI_UPDATE; 1012 nullret = MOVE_UI_UPDATE;
981 if (state->exits[rangeno] != LASER_EMPTY) 1013 if (state->exits[rangeno] != LASER_EMPTY)
982 return UI_UPDATE; 1014 return MOVE_UI_UPDATE;
983 sprintf(buf, "F%d", rangeno); 1015 sprintf(buf, "F%d", rangeno);
984 break; 1016 break;
985 1017
@@ -1034,10 +1066,10 @@ static game_state *execute_move(const game_state *from, const char *move)
1034 1066
1035 case 'F': 1067 case 'F':
1036 sscanf(move+1, "%d", &rangeno); 1068 sscanf(move+1, "%d", &rangeno);
1037 if (ret->exits[rangeno] != LASER_EMPTY)
1038 goto badmove;
1039 if (!RANGECHECK(ret, rangeno)) 1069 if (!RANGECHECK(ret, rangeno))
1040 goto badmove; 1070 goto badmove;
1071 if (ret->exits[rangeno] != LASER_EMPTY)
1072 goto badmove;
1041 fire_laser(ret, rangeno); 1073 fire_laser(ret, rangeno);
1042 break; 1074 break;
1043 1075
@@ -1119,7 +1151,7 @@ static void game_get_cursor_location(const game_ui *ui,
1119 */ 1151 */
1120 1152
1121static void game_compute_size(const game_params *params, int tilesize, 1153static void game_compute_size(const game_params *params, int tilesize,
1122 int *x, int *y) 1154 const game_ui *ui, int *x, int *y)
1123{ 1155{
1124 /* Border is ts/2, to make things easier. 1156 /* Border is ts/2, to make things easier.
1125 * Thus we have (width) + 2 (firing range*2) + 1 (border*2) tiles 1157 * Thus we have (width) + 2 (firing range*2) + 1 (border*2) tiles
@@ -1379,10 +1411,6 @@ static void game_redraw(drawing *dr, game_drawstate *ds,
1379 int x0 = TODRAW(0)-1, y0 = TODRAW(0)-1; 1411 int x0 = TODRAW(0)-1, y0 = TODRAW(0)-1;
1380 int x1 = TODRAW(state->w+2), y1 = TODRAW(state->h+2); 1412 int x1 = TODRAW(state->w+2), y1 = TODRAW(state->h+2);
1381 1413
1382 draw_rect(dr, 0, 0,
1383 TILE_SIZE * (state->w+3), TILE_SIZE * (state->h+3),
1384 COL_BACKGROUND);
1385
1386 /* clockwise around the outline starting at pt behind (1,1). */ 1414 /* clockwise around the outline starting at pt behind (1,1). */
1387 draw_line(dr, x0+ts, y0+ts, x0+ts, y0, COL_HIGHLIGHT); 1415 draw_line(dr, x0+ts, y0+ts, x0+ts, y0, COL_HIGHLIGHT);
1388 draw_line(dr, x0+ts, y0, x1-ts, y0, COL_HIGHLIGHT); 1416 draw_line(dr, x0+ts, y0, x1-ts, y0, COL_HIGHLIGHT);
@@ -1429,14 +1457,14 @@ static void game_redraw(drawing *dr, game_drawstate *ds,
1429 int outline = (ui->cur_visible && ui->cur_x == 0 && ui->cur_y == 0) 1457 int outline = (ui->cur_visible && ui->cur_x == 0 && ui->cur_y == 0)
1430 ? COL_CURSOR : COL_BALL; 1458 ? COL_CURSOR : COL_BALL;
1431 clip(dr, TODRAW(0)-1, TODRAW(0)-1, TILE_SIZE+1, TILE_SIZE+1); 1459 clip(dr, TODRAW(0)-1, TODRAW(0)-1, TILE_SIZE+1, TILE_SIZE+1);
1432 draw_circle(dr, TODRAW(0) + ds->crad, TODRAW(0) + ds->crad, ds->crad, 1460 draw_circle(dr, TODRAW(0) + ds->crad-1, TODRAW(0) + ds->crad-1, ds->crad-1,
1433 outline, outline); 1461 outline, outline);
1434 draw_circle(dr, TODRAW(0) + ds->crad, TODRAW(0) + ds->crad, ds->crad-2, 1462 draw_circle(dr, TODRAW(0) + ds->crad-1, TODRAW(0) + ds->crad-1, ds->crad-3,
1435 COL_BUTTON, COL_BUTTON); 1463 COL_BUTTON, COL_BUTTON);
1436 unclip(dr); 1464 unclip(dr);
1437 } else { 1465 } else {
1438 draw_rect(dr, TODRAW(0)-1, TODRAW(0)-1, 1466 draw_rect(dr, TODRAW(0)-1, TODRAW(0)-1,
1439 TILE_SIZE+1, TILE_SIZE+1, COL_BACKGROUND); 1467 TILE_SIZE, TILE_SIZE, COL_BACKGROUND);
1440 } 1468 }
1441 draw_update(dr, TODRAW(0), TODRAW(0), TILE_SIZE, TILE_SIZE); 1469 draw_update(dr, TODRAW(0), TODRAW(0), TILE_SIZE, TILE_SIZE);
1442 ds->reveal = state->reveal; 1470 ds->reveal = state->reveal;
@@ -1509,19 +1537,6 @@ static int game_status(const game_state *state)
1509 return 0; 1537 return 0;
1510} 1538}
1511 1539
1512static bool game_timing_state(const game_state *state, game_ui *ui)
1513{
1514 return true;
1515}
1516
1517static void game_print_size(const game_params *params, float *x, float *y)
1518{
1519}
1520
1521static void game_print(drawing *dr, const game_state *state, int tilesize)
1522{
1523}
1524
1525#ifdef COMBINED 1540#ifdef COMBINED
1526#define thegame blackbox 1541#define thegame blackbox
1527#endif 1542#endif
@@ -1542,13 +1557,15 @@ const struct game thegame = {
1542 dup_game, 1557 dup_game,
1543 free_game, 1558 free_game,
1544 true, solve_game, 1559 true, solve_game,
1545 false, game_can_format_as_text_now, game_text_format, 1560 false, NULL, NULL, /* can_format_as_text_now, text_format */
1561 NULL, NULL, /* get_prefs, set_prefs */
1546 new_ui, 1562 new_ui,
1547 free_ui, 1563 free_ui,
1548 encode_ui, 1564 encode_ui,
1549 decode_ui, 1565 decode_ui,
1550 NULL, /* game_request_keys */ 1566 NULL, /* game_request_keys */
1551 game_changed_state, 1567 game_changed_state,
1568 current_key_label,
1552 interpret_move, 1569 interpret_move,
1553 execute_move, 1570 execute_move,
1554 PREFERRED_TILE_SIZE, game_compute_size, game_set_size, 1571 PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
@@ -1560,9 +1577,9 @@ const struct game thegame = {
1560 game_flash_length, 1577 game_flash_length,
1561 game_get_cursor_location, 1578 game_get_cursor_location,
1562 game_status, 1579 game_status,
1563 false, false, game_print_size, game_print, 1580 false, false, NULL, NULL, /* print_size, print */
1564 true, /* wants_statusbar */ 1581 true, /* wants_statusbar */
1565 false, game_timing_state, 1582 false, NULL, /* timing_state */
1566 REQUIRE_RBUTTON, /* flags */ 1583 REQUIRE_RBUTTON, /* flags */
1567}; 1584};
1568 1585