summaryrefslogtreecommitdiff
path: root/apps/plugins/puzzles/src/range.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/plugins/puzzles/src/range.c')
-rw-r--r--apps/plugins/puzzles/src/range.c182
1 files changed, 111 insertions, 71 deletions
diff --git a/apps/plugins/puzzles/src/range.c b/apps/plugins/puzzles/src/range.c
index fc0a5405f1..bc29f7604b 100644
--- a/apps/plugins/puzzles/src/range.c
+++ b/apps/plugins/puzzles/src/range.c
@@ -58,7 +58,11 @@
58#include <string.h> 58#include <string.h>
59#include <assert.h> 59#include <assert.h>
60#include <ctype.h> 60#include <ctype.h>
61#include <math.h> 61#ifdef NO_TGMATH_H
62# include <math.h>
63#else
64# include <tgmath.h>
65#endif
62 66
63#include "puzzles.h" 67#include "puzzles.h"
64 68
@@ -911,8 +915,8 @@ static const char *validate_params(const game_params *params, bool full)
911 int const w = params->w, h = params->h; 915 int const w = params->w, h = params->h;
912 if (w < 1) return "Error: width is less than 1"; 916 if (w < 1) return "Error: width is less than 1";
913 if (h < 1) return "Error: height is less than 1"; 917 if (h < 1) return "Error: height is less than 1";
918 if (w > SCHAR_MAX - (h - 1)) return "Error: w + h is too big";
914 if (w * h < 1) return "Error: size is less than 1"; 919 if (w * h < 1) return "Error: size is less than 1";
915 if (w + h - 1 > SCHAR_MAX) return "Error: w + h is too big";
916 /* I might be unable to store clues in my puzzle_size *grid; */ 920 /* I might be unable to store clues in my puzzle_size *grid; */
917 if (full) { 921 if (full) {
918 if (w == 2 && h == 2) return "Error: can't create 2x2 puzzles"; 922 if (w == 2 && h == 2) return "Error: can't create 2x2 puzzles";
@@ -1221,28 +1225,101 @@ static char *game_text_format(const game_state *state)
1221struct game_ui { 1225struct game_ui {
1222 puzzle_size r, c; /* cursor position */ 1226 puzzle_size r, c; /* cursor position */
1223 bool cursor_show; 1227 bool cursor_show;
1228
1229 /*
1230 * User preference option to swap the left and right mouse
1231 * buttons.
1232 *
1233 * The original puzzle submitter thought it would be more useful
1234 * to have the left button turn an empty square into a dotted one,
1235 * on the grounds that that was what you did most often; I (SGT)
1236 * felt instinctively that the left button ought to place black
1237 * squares and the right button place dots, on the grounds that
1238 * that was consistent with many other puzzles in which the left
1239 * button fills in the data used by the solution checker while the
1240 * right button places pencil marks for the user's convenience.
1241 *
1242 * My first beta-player wasn't sure either, so I thought I'd
1243 * pre-emptively put in a 'configuration' mechanism just in case.
1244 */
1245 bool swap_buttons;
1224}; 1246};
1225 1247
1248static void legacy_prefs_override(struct game_ui *ui_out)
1249{
1250 static int initialised = false;
1251 static int swap_buttons = -1;
1252
1253 if (!initialised) {
1254 initialised = true;
1255 swap_buttons = getenv_bool("RANGE_SWAP_BUTTONS", -1);
1256 }
1257
1258 if (swap_buttons != -1)
1259 ui_out->swap_buttons = swap_buttons;
1260}
1261
1226static game_ui *new_ui(const game_state *state) 1262static game_ui *new_ui(const game_state *state)
1227{ 1263{
1228 struct game_ui *ui = snew(game_ui); 1264 struct game_ui *ui = snew(game_ui);
1229 ui->r = ui->c = 0; 1265 ui->r = ui->c = 0;
1230 ui->cursor_show = false; 1266 ui->cursor_show = getenv_bool("PUZZLES_SHOW_CURSOR", false);
1267
1268 ui->swap_buttons = false;
1269 legacy_prefs_override(ui);
1270
1231 return ui; 1271 return ui;
1232} 1272}
1233 1273
1234static void free_ui(game_ui *ui) 1274static config_item *get_prefs(game_ui *ui)
1235{ 1275{
1236 sfree(ui); 1276 config_item *ret;
1277
1278 ret = snewn(2, config_item);
1279
1280 ret[0].name = "Mouse button order";
1281 ret[0].kw = "left-mouse-button";
1282 ret[0].type = C_CHOICES;
1283 ret[0].u.choices.choicenames =
1284 ":Left to fill, right to dot:Left to dot, right to fill";
1285 ret[0].u.choices.choicekws = ":fill:dot";
1286 ret[0].u.choices.selected = ui->swap_buttons;
1287
1288 ret[1].name = NULL;
1289 ret[1].type = C_END;
1290
1291 return ret;
1237} 1292}
1238 1293
1239static char *encode_ui(const game_ui *ui) 1294static void set_prefs(game_ui *ui, const config_item *cfg)
1240{ 1295{
1241 return NULL; 1296 ui->swap_buttons = cfg[0].u.choices.selected;
1297}
1298
1299static void free_ui(game_ui *ui)
1300{
1301 sfree(ui);
1242} 1302}
1243 1303
1244static void decode_ui(game_ui *ui, const char *encoding) 1304static const char *current_key_label(const game_ui *ui,
1305 const game_state *state, int button)
1245{ 1306{
1307 int cell;
1308
1309 if (IS_CURSOR_SELECT(button)) {
1310 cell = state->grid[idx(ui->r, ui->c, state->params.w)];
1311 if (!ui->cursor_show || cell > 0) return "";
1312 switch (cell) {
1313 case EMPTY:
1314 return button == CURSOR_SELECT ? "Fill" : "Dot";
1315 case WHITE:
1316 return button == CURSOR_SELECT ? "Empty" : "Fill";
1317 case BLACK:
1318 return button == CURSOR_SELECT ? "Dot" : "Empty";
1319 }
1320 }
1321 return "";
1322
1246} 1323}
1247 1324
1248typedef struct drawcell { 1325typedef struct drawcell {
@@ -1253,7 +1330,6 @@ typedef struct drawcell {
1253struct game_drawstate { 1330struct game_drawstate {
1254 int tilesize; 1331 int tilesize;
1255 drawcell *grid; 1332 drawcell *grid;
1256 bool started;
1257}; 1333};
1258 1334
1259#define TILESIZE (ds->tilesize) 1335#define TILESIZE (ds->tilesize)
@@ -1283,38 +1359,12 @@ static char *interpret_move(const game_state *state, game_ui *ui,
1283 } 1359 }
1284 1360
1285 if (button == LEFT_BUTTON || button == RIGHT_BUTTON) { 1361 if (button == LEFT_BUTTON || button == RIGHT_BUTTON) {
1286 /* 1362 if (ui->swap_buttons) {
1287 * Utterly awful hack, exactly analogous to the one in Slant, 1363 if (button == LEFT_BUTTON)
1288 * to configure the left and right mouse buttons the opposite 1364 button = RIGHT_BUTTON;
1289 * way round. 1365 else
1290 * 1366 button = LEFT_BUTTON;
1291 * The original puzzle submitter thought it would be more 1367 }
1292 * useful to have the left button turn an empty square into a
1293 * dotted one, on the grounds that that was what you did most
1294 * often; I (SGT) felt instinctively that the left button
1295 * ought to place black squares and the right button place
1296 * dots, on the grounds that that was consistent with many
1297 * other puzzles in which the left button fills in the data
1298 * used by the solution checker while the right button places
1299 * pencil marks for the user's convenience.
1300 *
1301 * My first beta-player wasn't sure either, so I thought I'd
1302 * pre-emptively put in a 'configuration' mechanism just in
1303 * case.
1304 */
1305 {
1306 static int swap_buttons = -1;
1307 if (swap_buttons < 0) {
1308 char *env = getenv("RANGE_SWAP_BUTTONS");
1309 swap_buttons = (env && (env[0] == 'y' || env[0] == 'Y'));
1310 }
1311 if (swap_buttons) {
1312 if (button == LEFT_BUTTON)
1313 button = RIGHT_BUTTON;
1314 else
1315 button = LEFT_BUTTON;
1316 }
1317 }
1318 } 1368 }
1319 1369
1320 switch (button) { 1370 switch (button) {
@@ -1355,14 +1405,14 @@ static char *interpret_move(const game_state *state, game_ui *ui,
1355 else if (do_post) 1405 else if (do_post)
1356 return nfmtstr(40, "W,%d,%d", ui->r, ui->c); 1406 return nfmtstr(40, "W,%d,%d", ui->r, ui->c);
1357 else 1407 else
1358 return UI_UPDATE; 1408 return MOVE_UI_UPDATE;
1359 1409
1360 } else if (!out_of_bounds(ui->r + dr[i], ui->c + dc[i], w, h)) { 1410 } else if (!out_of_bounds(ui->r + dr[i], ui->c + dc[i], w, h)) {
1361 ui->r += dr[i]; 1411 ui->r += dr[i];
1362 ui->c += dc[i]; 1412 ui->c += dc[i];
1363 } 1413 }
1364 } else ui->cursor_show = true; 1414 } else ui->cursor_show = true;
1365 return UI_UPDATE; 1415 return MOVE_UI_UPDATE;
1366 } 1416 }
1367 1417
1368 if (action == hint) { 1418 if (action == hint) {
@@ -1408,7 +1458,7 @@ static char *interpret_move(const game_state *state, game_ui *ui,
1408static bool find_errors(const game_state *state, bool *report) 1458static bool find_errors(const game_state *state, bool *report)
1409{ 1459{
1410 int const w = state->params.w, h = state->params.h, n = w * h; 1460 int const w = state->params.w, h = state->params.h, n = w * h;
1411 int *dsf; 1461 DSF *dsf;
1412 1462
1413 int r, c, i; 1463 int r, c, i;
1414 1464
@@ -1463,7 +1513,7 @@ static bool find_errors(const game_state *state, bool *report)
1463 /* 1513 /*
1464 * Check that all the white cells form a single connected component. 1514 * Check that all the white cells form a single connected component.
1465 */ 1515 */
1466 dsf = snew_dsf(n); 1516 dsf = dsf_new(n);
1467 for (r = 0; r < h-1; ++r) 1517 for (r = 0; r < h-1; ++r)
1468 for (c = 0; c < w; ++c) 1518 for (c = 0; c < w; ++c)
1469 if (state->grid[r*w+c] != BLACK && 1519 if (state->grid[r*w+c] != BLACK &&
@@ -1474,11 +1524,12 @@ static bool find_errors(const game_state *state, bool *report)
1474 if (state->grid[r*w+c] != BLACK && 1524 if (state->grid[r*w+c] != BLACK &&
1475 state->grid[r*w+(c+1)] != BLACK) 1525 state->grid[r*w+(c+1)] != BLACK)
1476 dsf_merge(dsf, r*w+c, r*w+(c+1)); 1526 dsf_merge(dsf, r*w+c, r*w+(c+1));
1477 if (nblack + dsf_size(dsf, any_white_cell) < n) { 1527 if (any_white_cell != -1 &&
1528 nblack + dsf_size(dsf, any_white_cell) < n) {
1478 int biggest, canonical; 1529 int biggest, canonical;
1479 1530
1480 if (!report) { 1531 if (!report) {
1481 sfree(dsf); 1532 dsf_free(dsf);
1482 goto found_error; 1533 goto found_error;
1483 } 1534 }
1484 1535
@@ -1503,7 +1554,7 @@ static bool find_errors(const game_state *state, bool *report)
1503 if (state->grid[i] != BLACK && dsf_canonify(dsf, i) != canonical) 1554 if (state->grid[i] != BLACK && dsf_canonify(dsf, i) != canonical)
1504 report[i] = true; 1555 report[i] = true;
1505 } 1556 }
1506 sfree(dsf); 1557 dsf_free(dsf);
1507 1558
1508 free_game(dup); 1559 free_game(dup);
1509 return false; /* if report != NULL, this is ignored */ 1560 return false; /* if report != NULL, this is ignored */
@@ -1604,13 +1655,12 @@ enum {
1604 COL_USER = COL_GRID, 1655 COL_USER = COL_GRID,
1605 COL_ERROR, 1656 COL_ERROR,
1606 COL_LOWLIGHT, 1657 COL_LOWLIGHT,
1607 COL_HIGHLIGHT = COL_ERROR, /* mkhighlight needs it, I don't */
1608 COL_CURSOR = COL_LOWLIGHT, 1658 COL_CURSOR = COL_LOWLIGHT,
1609 NCOLOURS 1659 NCOLOURS
1610}; 1660};
1611 1661
1612static void game_compute_size(const game_params *params, int tilesize, 1662static void game_compute_size(const game_params *params, int tilesize,
1613 int *x, int *y) 1663 const game_ui *ui, int *x, int *y)
1614{ 1664{
1615 *x = (1 + params->w) * tilesize; 1665 *x = (1 + params->w) * tilesize;
1616 *y = (1 + params->h) * tilesize; 1666 *y = (1 + params->h) * tilesize;
@@ -1629,7 +1679,7 @@ static float *game_colours(frontend *fe, int *ncolours)
1629{ 1679{
1630 float *ret = snewn(3 * NCOLOURS, float); 1680 float *ret = snewn(3 * NCOLOURS, float);
1631 1681
1632 game_mkhighlight(fe, ret, COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT); 1682 game_mkhighlight(fe, ret, COL_BACKGROUND, -1, COL_LOWLIGHT);
1633 COLOUR(ret, COL_GRID, 0.0F, 0.0F, 0.0F); 1683 COLOUR(ret, COL_GRID, 0.0F, 0.0F, 0.0F);
1634 COLOUR(ret, COL_ERROR, 1.0F, 0.0F, 0.0F); 1684 COLOUR(ret, COL_ERROR, 1.0F, 0.0F, 0.0F);
1635 1685
@@ -1655,7 +1705,6 @@ static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
1655 int i; 1705 int i;
1656 1706
1657 ds->tilesize = 0; 1707 ds->tilesize = 0;
1658 ds->started = false;
1659 1708
1660 ds->grid = snewn(n, drawcell); 1709 ds->grid = snewn(n, drawcell);
1661 for (i = 0; i < n; ++i) 1710 for (i = 0; i < n; ++i)
@@ -1690,7 +1739,6 @@ static void game_redraw(drawing *dr, game_drawstate *ds,
1690 float animtime, float flashtime) 1739 float animtime, float flashtime)
1691{ 1740{
1692 int const w = state->params.w, h = state->params.h, n = w * h; 1741 int const w = state->params.w, h = state->params.h, n = w * h;
1693 int const wpx = (w+1) * ds->tilesize, hpx = (h+1) * ds->tilesize;
1694 int const flash = ((int) (flashtime * 5 / FLASH_TIME)) % 2; 1742 int const flash = ((int) (flashtime * 5 / FLASH_TIME)) % 2;
1695 1743
1696 int r, c, i; 1744 int r, c, i;
@@ -1701,12 +1749,6 @@ static void game_redraw(drawing *dr, game_drawstate *ds,
1701 1749
1702 assert (oldstate == NULL); /* only happens if animating moves */ 1750 assert (oldstate == NULL); /* only happens if animating moves */
1703 1751
1704 if (!ds->started) {
1705 ds->started = true;
1706 draw_rect(dr, 0, 0, wpx, hpx, COL_BACKGROUND);
1707 draw_update(dr, 0, 0, wpx, hpx);
1708 }
1709
1710 for (i = r = 0; r < h; ++r) { 1752 for (i = r = 0; r < h; ++r) {
1711 for (c = 0; c < w; ++c, ++i) { 1753 for (c = 0; c < w; ++c, ++i) {
1712 drawcell cell = makecell(state->grid[i], errors[i], false, flash); 1754 drawcell cell = makecell(state->grid[i], errors[i], false, flash);
@@ -1757,25 +1799,21 @@ static void draw_cell(drawing *draw, game_drawstate *ds, int r, int c,
1757 draw_update(draw, x, y, ts + 1, ts + 1); 1799 draw_update(draw, x, y, ts + 1, ts + 1);
1758} 1800}
1759 1801
1760static bool game_timing_state(const game_state *state, game_ui *ui)
1761{
1762 puts("warning: game_timing_state was called (this shouldn't happen)");
1763 return false; /* the (non-existing) timer should not be running */
1764}
1765
1766/* ---------------------------------------------------------------------- 1802/* ----------------------------------------------------------------------
1767 * User interface: print 1803 * User interface: print
1768 */ 1804 */
1769 1805
1770static void game_print_size(const game_params *params, float *x, float *y) 1806static void game_print_size(const game_params *params, const game_ui *ui,
1807 float *x, float *y)
1771{ 1808{
1772 int print_width, print_height; 1809 int print_width, print_height;
1773 game_compute_size(params, 800, &print_width, &print_height); 1810 game_compute_size(params, 800, ui, &print_width, &print_height);
1774 *x = print_width / 100.0F; 1811 *x = print_width / 100.0F;
1775 *y = print_height / 100.0F; 1812 *y = print_height / 100.0F;
1776} 1813}
1777 1814
1778static void game_print(drawing *dr, const game_state *state, int tilesize) 1815static void game_print(drawing *dr, const game_state *state, const game_ui *ui,
1816 int tilesize)
1779{ 1817{
1780 int const w = state->params.w, h = state->params.h; 1818 int const w = state->params.w, h = state->params.h;
1781 game_drawstate ds_obj, *ds = &ds_obj; 1819 game_drawstate ds_obj, *ds = &ds_obj;
@@ -1821,12 +1859,14 @@ struct game const thegame = {
1821 free_game, 1859 free_game,
1822 true, solve_game, 1860 true, solve_game,
1823 true, game_can_format_as_text_now, game_text_format, 1861 true, game_can_format_as_text_now, game_text_format,
1862 get_prefs, set_prefs,
1824 new_ui, 1863 new_ui,
1825 free_ui, 1864 free_ui,
1826 encode_ui, 1865 NULL, /* encode_ui */
1827 decode_ui, 1866 NULL, /* decode_ui */
1828 NULL, /* game_request_keys */ 1867 NULL, /* game_request_keys */
1829 game_changed_state, 1868 game_changed_state,
1869 current_key_label,
1830 interpret_move, 1870 interpret_move,
1831 execute_move, 1871 execute_move,
1832 PREFERRED_TILE_SIZE, game_compute_size, game_set_size, 1872 PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
@@ -1840,6 +1880,6 @@ struct game const thegame = {
1840 game_status, 1880 game_status,
1841 true, false, game_print_size, game_print, 1881 true, false, game_print_size, game_print,
1842 false, /* wants_statusbar */ 1882 false, /* wants_statusbar */
1843 false, game_timing_state, 1883 false, NULL, /* timing_state */
1844 0, /* flags */ 1884 0, /* flags */
1845}; 1885};