diff --git a/.gitignore b/.gitignore index 6fa0960..8434efa 100644 --- a/.gitignore +++ b/.gitignore @@ -46,6 +46,7 @@ include/ /matching /mines /mineobfusc +/mosaic /net /netslide /nullgame diff --git a/CMakeLists.txt b/CMakeLists.txt index 058107a..aeabad3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -116,6 +116,12 @@ puzzle(mines OBJECTIVE "Find all the mines without treading on any of them.") cliprogram(mineobfusc mines.c COMPILE_DEFINITIONS STANDALONE_OBFUSCATOR) +puzzle(mosaic + DISPLAYNAME "Mosaic" + DESCRIPTION "Grid-filling puzzle" + OBJECTIVE "Fill in the grid given clues about number of \ +nearby black squares.") + puzzle(net # The Windows Net shouldn't be called 'net.exe', since Windows # already has a reasonably important utility program by that name! diff --git a/LICENCE b/LICENCE index 85f67ba..9fcd781 100644 --- a/LICENCE +++ b/LICENCE @@ -3,7 +3,7 @@ This software is copyright (c) 2004-2014 Simon Tatham. Portions copyright Richard Boulton, James Harvey, Mike Pinna, Jonas Kölker, Dariusz Olszewski, Michael Schierl, Lambros Lambrou, Bernd Schmidt, Steffen Bauer, Lennard Sprong, Rogier Goossens, Michael -Quevillon and Asher Gordon. +Quevillon, Asher Gordon and Didi Kohen. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files diff --git a/html/mosaic.html b/html/mosaic.html new file mode 100644 index 0000000..555354b --- /dev/null +++ b/html/mosaic.html @@ -0,0 +1,11 @@ +Mosaic +

+Colour every square either black or white. +Each number indicates how many black squares are in the 3×3 square +surrounding the number – including the clue square +itself. +

+Left-click in an empty square to turn it black, or right-click to turn +it white. Click again in an already-filled square to cycle it between +black and white and empty. You can left- or right-drag to set multiple +squares at once. diff --git a/icons/icons.cmake b/icons/icons.cmake index 0834166..8edbad7 100644 --- a/icons/icons.cmake +++ b/icons/icons.cmake @@ -55,6 +55,7 @@ set(lightup_crop 256x256 112x112+144+0) set(loopy_crop 257x257 113x113+0+0) set(magnets_crop 264x232 96x96+36+100) set(mines_crop 240x240 110x110+130+130) +set(mosaic_crop 288x288 97x97+142+78) set(net_crop 193x193 113x113+0+80) set(netslide_crop 289x289 144x144+0+0) set(palisade_crop 288x288 192x192+0+0) diff --git a/icons/mosaic.sav b/icons/mosaic.sav new file mode 100644 index 0000000..1dfd65f --- /dev/null +++ b/icons/mosaic.sav @@ -0,0 +1,54 @@ +SAVEFILE:41:Simon Tatham's Portable Puzzle Collection +VERSION :1:1 +GAME :6:Mosaic +PARAMS :7:8x8a0h1 +CPARAMS :7:8x8a0h1 +SEED :15:393271218291393 +DESC :41:b2c3b4a2a5c6e3a55c6a5a4244e0c3a64d4b4232b +TIME :7:214.795 +NSTATES :2:45 +STATEPOS:2:45 +MOVE :4:T2,5 +MOVE :10:d3,5,2,5,2 +MOVE :10:d4,5,3,5,2 +MOVE :10:d4,4,4,5,2 +MOVE :10:d3,4,4,4,2 +MOVE :10:d2,4,3,4,2 +MOVE :4:T2,6 +MOVE :10:d3,6,2,6,2 +MOVE :10:d4,6,3,6,2 +MOVE :4:t1,6 +MOVE :4:t1,7 +MOVE :10:d2,7,1,7,1 +MOVE :10:d3,7,2,7,1 +MOVE :4:T4,7 +MOVE :4:t5,6 +MOVE :10:d5,7,5,6,1 +MOVE :4:T6,6 +MOVE :10:d6,7,6,6,2 +MOVE :4:t7,7 +MOVE :10:d7,6,7,7,1 +MOVE :10:d7,5,7,6,1 +MOVE :10:d6,5,7,5,1 +MOVE :4:T6,4 +MOVE :10:d7,4,6,4,2 +MOVE :4:T1,5 +MOVE :4:t1,4 +MOVE :10:d0,4,1,4,1 +MOVE :10:d0,3,0,4,1 +MOVE :10:d1,3,0,3,1 +MOVE :4:t0,5 +MOVE :4:t0,6 +MOVE :10:d0,7,0,6,1 +MOVE :4:t2,3 +MOVE :4:t3,3 +MOVE :4:T4,3 +MOVE :4:t5,3 +MOVE :10:d5,4,5,3,1 +MOVE :10:d5,5,5,4,1 +MOVE :4:T6,3 +MOVE :4:t7,3 +MOVE :10:d7,2,7,3,1 +MOVE :10:d6,2,7,2,1 +MOVE :4:T6,1 +MOVE :10:d7,1,6,1,2 diff --git a/mosaic.c b/mosaic.c new file mode 100644 index 0000000..bceafd9 --- /dev/null +++ b/mosaic.c @@ -0,0 +1,1600 @@ +/* + * mosaic.c: A puzzle based on a square grid, with some of the tiles + * having clues as to how many black squares are around them. + * the purpose of the game is to find what should be on all tiles (black or + * unmarked) + * + * The game is also known as: ArtMosaico, Count and Darken, Cuenta Y Sombrea, + * Fill-a-Pix, Fill-In, Komsu Karala, Magipic, Majipiku, Mosaico, Mosaik, + * Mozaiek, Nampre Puzzle, Nurie-Puzzle, Oekaki-Pix, Voisimage. + * + * Implementation is loosely based on https://github.com/mordechaim/Mosaic, UI + * interaction is based on the range puzzle in the collection. + */ + +#include +#include +#include +#include +#include +#include + +#include "puzzles.h" + +#define DEFAULT_SIZE 10 +#define MAX_TILES 10000 +#define MAX_TILES_ERROR "Maximum size is 10000 tiles" +#define DEFAULT_TILE_SIZE 32 +#define DEBUG_IMAGE 1 +#undef DEBUG_IMAGE +#define FLASH_TIME 0.5F +/* To enable debug prints define DEBUG_PRINTS */ + +/* Getting the coordinates and returning NULL when out of scope + * The parentheses are needed to avoid order of operations issues + */ +#define get_coords(params, array, x, y) \ + (((x) >= 0 && (y) >= 0) && ((x) < params->width && (y) < params->height)) \ + ? array + ((y)*params->width) + x \ + : NULL + +#define COORD_FROM_CELL(d) ((d * ds->tilesize) + ds->tilesize / 2) - 1 + +enum { + COL_BACKGROUND = 0, + COL_UNMARKED, + COL_GRID, + COL_MARKED, + COL_BLANK, + COL_TEXT_SOLVED, + COL_ERROR, + COL_CURSOR, + NCOLOURS, + COL_TEXT_DARK = COL_MARKED, + COL_TEXT_LIGHT = COL_BLANK +}; + +enum cell_state { + STATE_UNMARKED = 0, + STATE_MARKED = 1, + STATE_BLANK = 2, + STATE_SOLVED = 4, + STATE_ERROR = 8, + STATE_UNMARKED_ERROR = STATE_ERROR | STATE_UNMARKED, + STATE_MARKED_ERROR = STATE_ERROR | STATE_MARKED, + STATE_BLANK_ERROR = STATE_ERROR | STATE_BLANK, + STATE_BLANK_SOLVED = STATE_SOLVED | STATE_BLANK, + STATE_MARKED_SOLVED = STATE_MARKED | STATE_SOLVED, + STATE_OK_NUM = STATE_BLANK | STATE_MARKED +}; + +struct game_params { + int width; + int height; + bool aggressive; +}; + +typedef struct board_state board_state; + +typedef struct needed_list_item needed_list_item; + +struct needed_list_item { + int x, y; + needed_list_item *next; +}; + +struct game_state { + bool cheating; + int not_completed_clues; + int width; + int height; + char *cells_contents; + board_state *board; +}; + +struct board_state { + unsigned int references; + struct board_cell *actual_board; +}; + +struct board_cell { + char clue; + bool shown; +}; + +struct solution_cell { + char cell; + bool solved; + bool needed; +}; + +struct desc_cell { + char clue; + bool shown; + bool value; + bool full; + bool empty; +}; + +struct game_ui { + bool solved; + bool in_progress; + int last_x, last_y, last_state; + int cur_x, cur_y; + int prev_cur_x, prev_cur_y; + bool cur_visible; +}; + +struct game_drawstate { + int tilesize; + bool started; + int *state; + int cur_x, cur_y; /* -1, -1 for no cursor displayed. */ + int prev_cur_x, prev_cur_y; +}; + +static game_params *default_params(void) +{ + game_params *ret = snew(game_params); + + ret->width = DEFAULT_SIZE; + ret->height = DEFAULT_SIZE; + ret->aggressive = true; + + return ret; +} + +static bool game_fetch_preset(int i, char **name, game_params **params) +{ + const int sizes[6] = { 3, 5, 10, 15, 25, 50 }; + const bool aggressiveness[6] = { true, true, true, true, true, false }; + if (i < 0 || i > 5) { + return false; + } + game_params *res = snew(game_params); + res->height = sizes[i]; + res->width = sizes[i]; + res->aggressive = aggressiveness[i]; + *params = res; + + char value[80]; + sprintf(value, "Size: %dx%d", sizes[i], sizes[i]); + *name = dupstr(value); + return true; +} + +static void free_params(game_params *params) +{ + sfree(params); +} + +static game_params *dup_params(const game_params *params) +{ + game_params *ret = snew(game_params); + *ret = *params; /* structure copy */ + return ret; +} + +static void decode_params(game_params *params, char const *string) +{ + params->width = params->height = atoi(string); + while (*string && isdigit((unsigned char)*string)) string++; + if (*string == 'x') { + string++; + params->height = atoi(string); + while (*string && isdigit((unsigned char)*string)) string++; + } + if (*string == 'h') { + string++; + params->aggressive = atoi(string); + while (*string && isdigit((unsigned char)*string)) string++; + } +} + +static char *encode_params(const game_params *params, bool full) +{ + char encoded[128]; + int pos = 0; + pos += sprintf(encoded + pos, "%dx%d", params->width, params->height); + if (full) { + if (params->aggressive) + pos += sprintf(encoded + pos, "h%d", params->aggressive); + } + return dupstr(encoded); +} + +static config_item *game_configure(const game_params *params) +{ + config_item *config = snewn(4, config_item); + char value[80]; + + config[0].type = C_STRING; + config[0].name = "Height"; + sprintf(value, "%d", params->height); + config[0].u.string.sval = dupstr(value); + + config[1].type = C_STRING; + config[1].name = "Width"; + sprintf(value, "%d", params->width); + config[1].u.string.sval = dupstr(value); + + config[2].name = "Aggressive generation (longer)"; + config[2].type = C_BOOLEAN; + config[2].u.boolean.bval = params->aggressive; + + config[3].type = C_END; + + return config; +} + +static game_params *custom_params(const config_item *cfg) +{ + game_params *res = snew(game_params); + res->height = atol(cfg[0].u.string.sval); + res->width = atol(cfg[1].u.string.sval); + res->aggressive = cfg[2].u.boolean.bval; + return res; +} + +static const char *validate_params(const game_params *params, bool full) +{ + if (params->height < 3 || params->width < 3) { + return "Minimal size is 3x3"; + } + if (params->height * params->width > MAX_TILES) { + return MAX_TILES_ERROR; + } + return NULL; +} + +static bool get_pixel(const game_params *params, const bool *image, + const int x, const int y) +{ + const bool *pixel; + pixel = get_coords(params, image, x, y); + if (pixel) { + return *pixel; + } + return 0; +} + +static void populate_cell(const game_params *params, const bool *image, + const int x, const int y, bool edge, + struct desc_cell *desc) +{ + int clue = 0; + bool xEdge = false; + bool yEdge = false; + if (edge) { + if (x > 0) { + clue += get_pixel(params, image, x - 1, y); + if (y > 0) { + clue += get_pixel(params, image, x - 1, y - 1); + } + if (y < params->height - 1) { + clue += get_pixel(params, image, x - 1, y + 1); + } + } else { + xEdge = true; + } + + if (y > 0) { + clue += get_pixel(params, image, x, y - 1); + } else { + yEdge = true; + } + if (x < params->width - 1) { + clue += get_pixel(params, image, x + 1, y); + if (y > 0) { + clue += get_pixel(params, image, x + 1, y - 1); + } + if (y < params->height - 1) { + clue += get_pixel(params, image, x + 1, y + 1); + } + } else { + xEdge = true; + } + if (y < params->height - 1) { + clue += get_pixel(params, image, x, y + 1); + } else { + yEdge = true; + } + } else { + clue += get_pixel(params, image, x - 1, y - 1); + clue += get_pixel(params, image, x - 1, y); + clue += get_pixel(params, image, x - 1, y + 1); + clue += get_pixel(params, image, x, y - 1); + clue += get_pixel(params, image, x, y + 1); + clue += get_pixel(params, image, x + 1, y - 1); + clue += get_pixel(params, image, x + 1, y); + clue += get_pixel(params, image, x + 1, y + 1); + } + + desc->value = get_pixel(params, image, x, y); + clue += desc->value; + if (clue == 0) { + desc->empty = true; + desc->full = false; + } else { + desc->empty = false; + /* setting the default */ + desc->full = false; + if (clue == 9) { + desc->full = true; + } else if (edge && ((xEdge && yEdge && clue == 4) || + ((xEdge || yEdge) && clue == 6))) { + + desc->full = true; + } + } + desc->shown = true; + desc->clue = clue; +} + +static void count_around(const game_params *params, + struct solution_cell *sol, int x, int y, + int *marked, int *blank, int *total) +{ + int i, j; + struct solution_cell *curr = NULL; + (*total) = 0; + (*blank) = 0; + (*marked) = 0; + + for (i = -1; i < 2; i++) { + for (j = -1; j < 2; j++) { + curr = get_coords(params, sol, x + i, y + j); + if (curr) { + (*total)++; + if ((curr->cell & STATE_BLANK) != 0) { + (*blank)++; + } else if ((curr->cell & STATE_MARKED) != 0) { + (*marked)++; + } + } + } + } +} + +static void count_around_state(const game_state *state, int x, int y, + int *marked, int *blank, int *total) +{ + int i, j; + char *curr = NULL; + (*total) = 0; + (*blank) = 0; + (*marked) = 0; + + for (i = -1; i < 2; i++) { + for (j = -1; j < 2; j++) { + curr = get_coords(state, state->cells_contents, x + i, y + j); + if (curr) { + (*total)++; + if ((*curr & STATE_BLANK) != 0) { + (*blank)++; + } else if ((*curr & STATE_MARKED) != 0) { + (*marked)++; + } + } + } + } +} + +static void count_clues_around(const game_params *params, + struct desc_cell *desc, int x, int y, + int *clues, int *total) +{ + int i, j; + struct desc_cell *curr = NULL; + (*total) = 0; + (*clues) = 0; + + for (i = -1; i < 2; i++) { + for (j = -1; j < 2; j++) { + curr = get_coords(params, desc, x + i, y + j); + if (curr) { + (*total)++; + if (curr->shown) { + (*clues)++; + } + } + } + } +} + +static void mark_around(const game_params *params, + struct solution_cell *sol, int x, int y, int mark) +{ + int i, j, marked = 0; + struct solution_cell *curr; + + for (i = -1; i < 2; i++) { + for (j = -1; j < 2; j++) { + curr = get_coords(params, sol, x + i, y + j); + if (curr) { + if (curr->cell == STATE_UNMARKED) { + curr->cell = mark; + marked++; + } + } + } + } +} + +static char solve_cell(const game_params *params, struct desc_cell *desc, + struct board_cell *board, struct solution_cell *sol, + int x, int y) +{ + struct desc_cell curr; + + if (desc) { + curr.shown = desc[(y * params->width) + x].shown; + curr.clue = desc[(y * params->width) + x].clue; + curr.full = desc[(y * params->width) + x].full; + curr.empty = desc[(y * params->width) + x].empty; + } else { + curr.shown = board[(y * params->width) + x].shown; + curr.clue = board[(y * params->width) + x].clue; + curr.full = false; + curr.empty = false; + } + int marked = 0, total = 0, blank = 0; + + if (sol[(y * params->width) + x].solved) { + return 0; + } + count_around(params, sol, x, y, &marked, &blank, &total); + if (curr.full && curr.shown) { + sol[(y * params->width) + x].solved = true; + if (marked + blank < total) { + sol[(y * params->width) + x].needed = true; + } + mark_around(params, sol, x, y, STATE_MARKED); + return 1; + } + if (curr.empty && curr.shown) { + sol[(y * params->width) + x].solved = true; + if (marked + blank < total) { + sol[(y * params->width) + x].needed = true; + } + mark_around(params, sol, x, y, STATE_BLANK); + return 1; + } + if (curr.shown) { + if (!sol[(y * params->width) + x].solved) { + if (marked == curr.clue) { + sol[(y * params->width) + x].solved = true; + if (total != marked + blank) { + sol[(y * params->width) + x].needed = true; + } + mark_around(params, sol, x, y, STATE_BLANK); + } else if (curr.clue == (total - blank)) { + sol[(y * params->width) + x].solved = true; + if (total != marked + blank) { + sol[(y * params->width) + x].needed = true; + } + mark_around(params, sol, x, y, STATE_MARKED); + } else if (total == marked + blank) { + return -1; + } else { + return 0; + } + return 1; + } + return 0; + } else if (total == marked + blank) { + sol[(y * params->width) + x].solved = true; + return 1; + } else { + return 0; + } +} + +static bool solve_check(const game_params *params, struct desc_cell *desc, + random_state *rs, struct solution_cell **sol_return) +{ + int x, y, i; + int board_size = params->height * params->width; + struct solution_cell *sol = snewn(board_size, struct solution_cell), + *curr_sol; + bool made_progress = true, error = false; + int solved = 0, curr = 0, shown = 0; + needed_list_item *head = NULL, *curr_needed, **needed_array; + struct desc_cell *curr_desc; + + memset(sol, 0, board_size * sizeof(*sol)); + for (y = 0; y < params->height; y++) { + for (x = 0; x < params->width; x++) { + curr_desc = get_coords(params, desc, x, y); + if (curr_desc->shown) { + curr_needed = snew(needed_list_item); + curr_needed->next = head; + head = curr_needed; + curr_needed->x = x; + curr_needed->y = y; + shown++; + } + } + } + needed_array = snewn(shown, needed_list_item *); + curr_needed = head; + i = 0; + while (curr_needed) { + needed_array[i] = curr_needed; + curr_needed = curr_needed->next; + i++; + } + if (rs) { + shuffle(needed_array, shown, sizeof(*needed_array), rs); + } + solved = 0; + while (solved < shown && made_progress && !error) { + made_progress = false; + for (i = 0; i < shown; i++) { + curr = solve_cell(params, desc, NULL, sol, needed_array[i]->x, + needed_array[i]->y); + if (curr < 0) { + error = true; +#ifdef DEBUG_PRINTS + printf("error in cell x=%d, y=%d\n", needed_array[i]->x, + needed_array[i]->y); +#endif + break; + } + if (curr > 0) { + solved++; + made_progress = true; + } + } + } + while (head) { + curr_needed = head; + head = curr_needed->next; + sfree(curr_needed); + } + sfree(needed_array); + solved = 0; + /* verifying all the board is solved */ + if (made_progress) { + for (y = 0; y < params->height; y++) { + for (x = 0; x < params->width; x++) { + curr_sol = get_coords(params, sol, x, y); + if ((curr_sol->cell & (STATE_MARKED | STATE_BLANK)) > 0) { + solved++; + } + } + } + } + if (sol_return) { + *sol_return = sol; + } else { + sfree(sol); + } + return solved == board_size; +} + +static bool solve_game_actual(const game_params *params, + struct board_cell *desc, + struct solution_cell **sol_return) +{ + int x, y; + int board_size = params->height * params->width; + struct solution_cell *sol = snewn(board_size, struct solution_cell); + bool made_progress = true, error = false; + int solved = 0, iter = 0, curr = 0; + + memset(sol, 0, params->height * params->width * sizeof(*sol)); + solved = 0; + while (solved < params->height * params->width && made_progress + && !error) { + for (y = 0; y < params->height; y++) { + for (x = 0; x < params->width; x++) { + curr = solve_cell(params, NULL, desc, sol, x, y); + if (curr < 0) { + error = true; +#ifdef DEBUG_PRINTS + printf("error in cell x=%d, y=%d\n", x, y); +#endif + break; + } + if (curr > 0) { + made_progress = true; + } + solved += curr; + } + } + iter++; + } + if (sol_return) { + *sol_return = sol; + } else { + sfree(sol); + } + return solved == params->height * params->width; +} + +static void hide_clues(const game_params *params, struct desc_cell *desc, + random_state *rs) +{ + int shown, total, x, y, i; + int needed = 0; + struct desc_cell *curr; + struct solution_cell *sol = NULL, *curr_sol = NULL; + needed_list_item *head = NULL, *curr_needed, **needed_array; + +#ifdef DEBUG_PRINTS + printf("Hiding clues\n"); +#endif + solve_check(params, desc, rs, &sol); + for (y = 0; y < params->height; y++) { + for (x = 0; x < params->width; x++) { + count_clues_around(params, desc, x, y, &shown, &total); + curr = get_coords(params, desc, x, y); + curr_sol = get_coords(params, sol, x, y); + if (curr_sol->needed && params->aggressive) { + curr_needed = snew(needed_list_item); + curr_needed->x = x; + curr_needed->y = y; + curr_needed->next = head; + head = curr_needed; + needed++; + } else if (!curr_sol->needed) { + curr->shown = false; + } + } + } + if (params->aggressive) { + curr_needed = head; + needed_array = snewn(needed, needed_list_item *); + memset(needed_array, 0, needed * sizeof(*needed_array)); + i = 0; + while (curr_needed) { + needed_array[i] = curr_needed; + curr_needed = curr_needed->next; + i++; + } + shuffle(needed_array, needed, sizeof(*needed_array), rs); + for (i = 0; i < needed; i++) { + curr_needed = needed_array[i]; + curr = + get_coords(params, desc, curr_needed->x, curr_needed->y); + if (curr) { + curr->shown = false; + if (!solve_check(params, desc, NULL, NULL)) { +#ifdef DEBUG_PRINTS + printf("Hiding cell %d, %d not possible.\n", + curr_needed->x, curr_needed->y); +#endif + curr->shown = true; + } + sfree(curr_needed); + needed_array[i] = NULL; + } + curr_needed = NULL; + } + sfree(needed_array); + } +#ifdef DEBUG_PRINTS + printf("needed %d\n", needed); +#endif + sfree(sol); +} + +static bool start_point_check(size_t size, struct desc_cell *desc) +{ + int i; + for (i = 0; i < size; i++) { + if (desc[i].empty || desc[i].full) { + return true; + } + } + return false; +} + +static void game_get_cursor_location(const game_ui *ui, + const game_drawstate *ds, + const game_state *state, + const game_params *params, int *x, + int *y, int *w, int *h) +{ + if (ui->cur_visible) { + *x = COORD_FROM_CELL(ui->cur_x); + *y = COORD_FROM_CELL(ui->cur_y); + *w = *h = ds->tilesize; + } +} + +static void generate_image(const game_params *params, random_state *rs, + bool *image) +{ + int x, y; + for (y = 0; y < params->height; y++) { + for (x = 0; x < params->width; x++) { + image[(y * params->width) + x] = random_bits(rs, 1); + } + } +} + +static char *new_game_desc(const game_params *params, random_state *rs, + char **aux, bool interactive) +{ + bool *image = snewn(params->height * params->width, bool); + bool valid = false; + char *desc_string = snewn((params->height * params->width) + 1, char); + char *compressed_desc = + snewn((params->height * params->width) + 1, char); + char space_count; + + struct desc_cell *desc = + snewn(params->height * params->width, struct desc_cell); + int x, y, location_in_str; + + while (!valid) { + generate_image(params, rs, image); +#ifdef DEBUG_IMAGE + image[0] = 1; + image[1] = 1; + image[2] = 0; + image[3] = 1; + image[4] = 1; + image[5] = 0; + image[6] = 0; + image[7] = 0; + image[8] = 0; +#endif + + for (y = 0; y < params->height; y++) { + for (x = 0; x < params->width; x++) { + populate_cell(params, image, x, y, + x * y == 0 || y == params->height - 1 || + x == params->width - 1, + &desc[(y * params->width) + x]); + } + } + valid = + start_point_check((params->height - 1) * (params->width - 1), + desc); + if (!valid) { +#ifdef DEBUG_PRINTS + printf("Not valid, regenerating.\n"); +#endif + } else { + valid = solve_check(params, desc, rs, NULL); + if (!valid) { +#ifdef DEBUG_PRINTS + printf("Couldn't solve, regenerating."); +#endif + } else { + hide_clues(params, desc, rs); + } + } + } + location_in_str = 0; + for (y = 0; y < params->height; y++) { + for (x = 0; x < params->width; x++) { + if (desc[(y * params->width) + x].shown) { +#ifdef DEBUG_PRINTS + printf("%d(%d)", desc[(y * params->width) + x].value, + desc[(y * params->width) + x].clue); +#endif + sprintf(desc_string + location_in_str, "%d", + desc[(y * params->width) + x].clue); + } else { +#ifdef DEBUG_PRINTS + printf("%d( )", desc[(y * params->width) + x].value); +#endif + sprintf(desc_string + location_in_str, " "); + } + location_in_str += 1; + } +#ifdef DEBUG_PRINTS + printf("\n"); +#endif + } + location_in_str = 0; + space_count = 'a' - 1; + for (y = 0; y < params->height; y++) { + for (x = 0; x < params->width; x++) { + if (desc[(y * params->width) + x].shown) { + if (space_count >= 'a') { + sprintf(compressed_desc + location_in_str, "%c", + space_count); + location_in_str++; + space_count = 'a' - 1; + } + sprintf(compressed_desc + location_in_str, "%d", + desc[(y * params->width) + x].clue); + location_in_str++; + } else { + if (space_count <= 'z') { + space_count++; + } else { + sprintf(compressed_desc + location_in_str, "%c", + space_count); + location_in_str++; + space_count = 'a' - 1; + } + } + } + } + if (space_count >= 'a') { + sprintf(compressed_desc + location_in_str, "%c", space_count); + location_in_str++; + } + compressed_desc[location_in_str] = '\0'; +#ifdef DEBUG_PRINTS + printf("compressed_desc: %s\n", compressed_desc); +#endif + return compressed_desc; +} + +static const char *validate_desc(const game_params *params, + const char *desc) +{ + int size_dest = params->height * params->width; + char *curr_desc = dupstr(desc); + char *desc_base = curr_desc; + int length; + length = 0; + + while (*curr_desc != '\0') { + if (*curr_desc >= 'a' && *curr_desc <= 'z') { + length += *curr_desc - 'a'; + } + length++; + curr_desc++; + } + + sfree(desc_base); + if (length != size_dest) { + return "Desc size mismatch"; + } + return NULL; +} + +static game_state *new_game(midend *me, const game_params *params, + const char *desc) +{ + game_state *state = snew(game_state); + char *curr_desc = dupstr(desc); + char *desc_base = curr_desc; + int dest_loc; + int spaces, total_spaces; + + state->cheating = false; + state->not_completed_clues = 0; + dest_loc = 0; + state->height = params->height; + state->width = params->width; + state->cells_contents = snewn(params->height * params->width, char); + memset(state->cells_contents, 0, params->height * params->width); + state->board = snew(board_state); + state->board->references = 1; + state->board->actual_board = + snewn(params->height * params->width, struct board_cell); + + while (*curr_desc != '\0') { + if (*curr_desc >= '0' && *curr_desc <= '9') { + state->board->actual_board[dest_loc].shown = true; + state->not_completed_clues++; + state->board->actual_board[dest_loc].clue = *curr_desc - '0'; + } else { + if (*curr_desc != ' ') { + total_spaces = *curr_desc - 'a' + 1; + } else { + total_spaces = 1; + } + spaces = 0; + while (spaces < total_spaces) { + state->board->actual_board[dest_loc].shown = false; + state->board->actual_board[dest_loc].clue = -1; + spaces++; + if (spaces < total_spaces) { + dest_loc++; + } + } + } + curr_desc++; + dest_loc++; + } + + sfree(desc_base); + return state; +} + +static game_state *dup_game(const game_state *state) +{ + game_state *ret = snew(game_state); + + ret->cheating = state->cheating; + ret->width = state->width; + ret->height = state->height; + ret->cells_contents = snewn(state->height * state->width, char); + memcpy(ret->cells_contents, state->cells_contents, + state->height * state->width); + ret->board = state->board; + ret->board->references++; + + return ret; +} + +static void free_game(game_state *state) +{ + sfree(state->cells_contents); + state->cells_contents = NULL; + if (state->board->references <= 1) { + sfree(state->board); + state->board = NULL; + } else { + state->board->references--; + } + sfree(state); +} + +static char *solve_game(const game_state *state, + const game_state *currstate, const char *aux, + const char **error) +{ + struct solution_cell *sol = NULL; + game_params param; + bool solved; + char *ret = NULL; + unsigned int curr_ret; + int i, bits, ret_loc = 1; + int size = state->width * state->height; + + param.width = state->width; + param.height = state->height; + solved = solve_game_actual(¶m, state->board->actual_board, &sol); + if (!solved) { + *error = dupstr("Could not solve this board"); + sfree(sol); + return NULL; + } + + ret = snewn((size / 4) + 3, char); + + ret[0] = 's'; + i = 0; + while (i < size) { + curr_ret = 0; + bits = 0; + while (bits < 8 && i < size) { + curr_ret <<= 1; + curr_ret |= sol[i].cell == STATE_MARKED; + i++; + bits++; + } + curr_ret <<= 8 - bits; + sprintf(ret + ret_loc, "%02x", curr_ret); + ret_loc += 2; + } + + sfree(sol); + return ret; +} + +static bool game_can_format_as_text_now(const game_params *params) +{ + return true; +} + +static char *game_text_format(const game_state *state) +{ + char *desc_string = + snewn((state->height * state->width) * 3 + 1, char); + int location_in_str = 0, x, y; + for (y = 0; y < state->height; y++) { + for (x = 0; x < state->width; x++) { + if (state->board->actual_board[(y * state->width) + x].shown) { + sprintf(desc_string + location_in_str, "|%d|", + state->board->actual_board[(y * state->width) + + x].clue); + } else { + sprintf(desc_string + location_in_str, "| |"); + } + location_in_str += 3; + } + sprintf(desc_string + location_in_str, "\n"); + location_in_str += 1; + } + return desc_string; +} + +static game_ui *new_ui(const game_state *state) +{ + game_ui *ui = snew(game_ui); + ui->last_x = -1; + ui->last_y = -1; + ui->last_state = 0; + ui->solved = false; + ui->cur_x = ui->cur_y = 0; + ui->cur_visible = false; + return ui; +} + +static void free_ui(game_ui *ui) +{ + sfree(ui); +} + +static char *encode_ui(const game_ui *ui) +{ + return NULL; +} + +static void decode_ui(game_ui *ui, const char *encoding) +{ + ui->last_x = -1; + ui->last_y = -1; + ui->last_state = 0; + ui->solved = false; + ui->cur_x = ui->cur_y = 0; +} + +static void game_changed_state(game_ui *ui, const game_state *oldstate, + const game_state *newstate) +{ +} + +static char *interpret_move(const game_state *state, game_ui *ui, + const game_drawstate *ds, int x, int y, + int button) +{ + int gameX, gameY, i, srcX = ui->last_x, srcY = + ui->last_y, dirX, dirY, diff; + char move_type; + char move_desc[80]; + char *ret = NULL; + const char *cell_state; + bool changed = false; + if (state->not_completed_clues == 0 && !IS_CURSOR_MOVE(button)) { + return NULL; + } + gameX = (x - (ds->tilesize / 2)) / ds->tilesize; + gameY = (y - (ds->tilesize / 2)) / ds->tilesize; + if (button == LEFT_BUTTON || button == RIGHT_BUTTON) { + cell_state = + get_coords(state, state->cells_contents, gameX, gameY); + if (cell_state) { + ui->last_state = *cell_state & (STATE_BLANK | STATE_MARKED); + ui->last_state = + (ui->last_state + + ((button == + RIGHT_BUTTON) ? 2 : 1)) % (STATE_BLANK | STATE_MARKED); + } + if (button == RIGHT_BUTTON) { + /* Right button toggles twice */ + move_type = 'T'; + } else { + move_type = 't'; + } + if (gameX >= 0 && gameY >= 0 && gameX < state->width && + gameY < state->height) { + sprintf(move_desc, "%c%d,%d", move_type, gameX, gameY); + ui->last_x = gameX; + ui->last_y = gameY; + ret = dupstr(move_desc); + } else { + ui->last_x = -1; + ui->last_y = -1; + } + changed = true; + ui->cur_visible = false; + } else if (button == LEFT_DRAG || button == RIGHT_DRAG) { + move_type = 'd'; + /* allowing only drags in straight lines */ + if (gameX >= 0 && gameY >= 0 && gameX < state->width && + gameY < state->height && ui->last_x >= 0 && ui->last_y >= 0 && + (gameY == ui->last_y || gameX == ui->last_x)) { + sprintf(move_desc, "%c%d,%d,%d,%d,%d", move_type, gameX, gameY, + ui->last_x, ui->last_y, ui->last_state); + if (srcX == gameX && srcY != gameY) { + dirX = 0; + diff = srcY - gameY; + if (diff < 0) { + dirY = -1; + diff *= -1; + } else { + dirY = 1; + } + } else { + diff = srcX - gameX; + dirY = 0; + if (diff < 0) { + dirX = -1; + diff *= -1; + } else { + dirX = 1; + } + } + for (i = 0; i < diff; i++) { + cell_state = get_coords(state, state->cells_contents, + gameX + (dirX * i), + gameY + (dirY * i)); + if (cell_state && (*cell_state & STATE_OK_NUM) == 0 + && ui->last_state > 0) { + changed = true; + break; + } + } + ui->last_x = gameX; + ui->last_y = gameY; + if (changed) { + ret = dupstr(move_desc); + } + } else { + ui->last_x = -1; + ui->last_y = -1; + } + ui->cur_visible = false; + } else if (button == LEFT_RELEASE || button == RIGHT_RELEASE) { + move_type = 'e'; + if (gameX >= 0 && gameY >= 0 && gameX < state->width && + gameY < state->height && ui->last_x >= 0 && ui->last_y >= 0 && + (gameY == ui->last_y || gameX == ui->last_x)) { + sprintf(move_desc, "%c%d,%d,%d,%d,%d", move_type, gameX, gameY, + ui->last_x, ui->last_y, ui->last_state); + if (srcX == gameX && srcY != gameY) { + dirX = 0; + diff = srcY - gameY; + if (diff < 0) { + dirY = -1; + diff *= -1; + } else { + dirY = 1; + } + } else { + diff = srcX - gameX; + dirY = 0; + if (diff < 0) { + dirX = -1; + diff *= -1; + } else { + dirX = 1; + } + } + for (i = 0; i < diff; i++) { + cell_state = get_coords(state, state->cells_contents, + gameX + (dirX * i), + gameY + (dirY * i)); + if (cell_state && (*cell_state & STATE_OK_NUM) == 0 + && ui->last_state > 0) { + changed = true; + break; + } + } + if (changed) { + ret = dupstr(move_desc); + } + } else { + ui->last_x = -1; + ui->last_y = -1; + } + ui->cur_visible = false; + } else if (IS_CURSOR_MOVE(button)) { + ui->prev_cur_x = ui->cur_x; + ui->prev_cur_y = ui->cur_y; + move_cursor(button, &ui->cur_x, &ui->cur_y, state->width, + state->height, false); + ui->cur_visible = true; + return UI_UPDATE; + } else if (IS_CURSOR_SELECT(button)) { + if (!ui->cur_visible) { + ui->cur_x = 0; + ui->cur_y = 0; + ui->cur_visible = true; + return UI_UPDATE; + } + + if (button == CURSOR_SELECT2) { + sprintf(move_desc, "T%d,%d", ui->cur_x, ui->cur_y); + ret = dupstr(move_desc); + } else { + /* Otherwise, treat as LEFT_BUTTON, for a single square. */ + sprintf(move_desc, "t%d,%d", ui->cur_x, ui->cur_y); + ret = dupstr(move_desc); + } + } + return ret; +} + +static void update_board_state_around(game_state *state, int x, int y) +{ + int i, j; + struct board_cell *curr; + char *curr_state; + int total; + int blank; + int marked; + + for (i = -1; i < 2; i++) { + for (j = -1; j < 2; j++) { + curr = + get_coords(state, state->board->actual_board, x + i, + y + j); + if (curr && curr->shown) { + curr_state = + get_coords(state, state->cells_contents, x + i, y + j); + count_around_state(state, x + i, y + j, &marked, &blank, + &total); + if (curr->clue == marked && (total - marked - blank) == 0) { + *curr_state &= STATE_MARKED | STATE_BLANK; + *curr_state |= STATE_SOLVED; + } else if (curr->clue < marked + || curr->clue > (total - blank)) { + *curr_state &= STATE_MARKED | STATE_BLANK; + *curr_state |= STATE_ERROR; + } else { + *curr_state &= STATE_MARKED | STATE_BLANK; + } + } + } + } +} + +static game_state *execute_move(const game_state *state, const char *move) +{ + game_state *new_state = dup_game(state); + int i = 0, x = -1, y = -1, clues_left = 0; + int srcX = -1, srcY = -1, size = state->height * state->width; + const char *p; + char *cell, sol_char; + int steps = 1, bits, sol_location, dirX, dirY, diff, + last_state = STATE_UNMARKED; + unsigned int sol_value; + struct board_cell *curr_cell; + char move_type; + int nparams = 0, move_params[5]; + + p = move; + move_type = *p++; + switch (move_type) { + case 't': + case 'T': + nparams = 2; + break; + case 'd': + case 'e': + nparams = 5; + break; + } + + for (i = 0; i < nparams; i++) { + move_params[i] = atoi(p); + while (*p && isdigit((unsigned char)*p)) p++; + if (i+1 < nparams) { + if (*p != ',') + return NULL; + p++; + } + } + + if (move_type == 't' || move_type == 'T') { + if (move_type == 'T') { + steps++; + } + x = move_params[0]; + y = move_params[1]; + if (x == -1 || y == -1) { + return new_state; + } + cell = get_coords(new_state, new_state->cells_contents, x, y); + if (*cell >= STATE_OK_NUM) { + *cell &= STATE_OK_NUM; + } + *cell = (*cell + steps) % STATE_OK_NUM; + update_board_state_around(new_state, x, y); + } else if (move_type == 's') { + new_state->not_completed_clues = 0; + new_state->cheating = true; + sol_location = 0; + bits = 0; + i = 1; + while (i < strlen(move)) { + sol_value = 0; + while (bits < 8) { + sol_value <<= 4; + sol_char = move[i]; + if (sol_char >= '0' && sol_char <= '9') { + sol_value |= sol_char - '0'; + } else { + sol_value |= (sol_char - 'a') + 10; + } + bits += 4; + i++; + } + while (bits > 0 && sol_location < size) { + if (sol_value & 0x80) { + new_state->cells_contents[sol_location] = + STATE_MARKED_SOLVED; + } else { + new_state->cells_contents[sol_location] = + STATE_BLANK_SOLVED; + } + sol_value <<= 1; + bits--; + sol_location++; + } + } + return new_state; + } else if (move_type == 'd' || move_type == 'e') { + x = move_params[0]; + y = move_params[1]; + srcX = move_params[2]; + srcY = move_params[3]; + last_state = move_params[4]; + if (srcX == x && srcY != y) { + dirX = 0; + diff = srcY - y; + if (diff < 0) { + dirY = -1; + diff *= -1; + } else { + dirY = 1; + } + } else { + diff = srcX - x; + dirY = 0; + if (diff < 0) { + dirX = -1; + diff *= -1; + } else { + dirX = 1; + } + } + for (i = 0; i < diff; i++) { + cell = get_coords(new_state, new_state->cells_contents, + x + (dirX * i), y + (dirY * i)); + if ((*cell & STATE_OK_NUM) == 0) { + *cell = last_state; + update_board_state_around(new_state, x + (dirX * i), + y + (dirY * i)); + } + } + } + for (y = 0; y < state->height; y++) { + for (x = 0; x < state->width; x++) { + cell = get_coords(new_state, new_state->cells_contents, x, y); + curr_cell = get_coords(new_state, new_state->board->actual_board, + x, y); + if (curr_cell->shown && ((*cell & STATE_SOLVED) == 0)) { + clues_left++; + } + } + } + new_state->not_completed_clues = clues_left; + return new_state; +} + +/* ---------------------------------------------------------------------- + * Drawing routines. + */ + +static void game_compute_size(const game_params *params, int tilesize, + int *x, int *y) +{ + *x = (params->width + 1) * tilesize; + *y = (params->height + 1) * tilesize; +} + +static void game_set_size(drawing *dr, game_drawstate *ds, + const game_params *params, int tilesize) +{ + ds->tilesize = tilesize; +} + +#define COLOUR(ret, i, r, g, b) \ + ((ret[3 * (i) + 0] = (r)), (ret[3 * (i) + 1] = (g)), (ret[3 * (i) + 2] = (b))) + +static float *game_colours(frontend *fe, int *ncolours) +{ + float *ret = snewn(3 * NCOLOURS, float); + + frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]); + COLOUR(ret, COL_GRID, 0.0F, 102 / 255.0F, 99 / 255.0F); + COLOUR(ret, COL_ERROR, 1.0F, 0.0F, 0.0F); + COLOUR(ret, COL_BLANK, 236 / 255.0F, 236 / 255.0F, 236 / 255.0F); + COLOUR(ret, COL_MARKED, 20 / 255.0F, 20 / 255.0F, 20 / 255.0F); + COLOUR(ret, COL_UNMARKED, 148 / 255.0F, 196 / 255.0F, 190 / 255.0F); + COLOUR(ret, COL_TEXT_SOLVED, 100 / 255.0F, 100 / 255.0F, 100 / 255.0F); + COLOUR(ret, COL_CURSOR, 255 / 255.0F, 200 / 255.0F, 200 / 255.0F); + + *ncolours = NCOLOURS; + return ret; +} + +/* Extra flags in game_drawstate entries, not in main game state */ +#define DRAWFLAG_CURSOR 0x100 + +static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) +{ + struct game_drawstate *ds = snew(game_drawstate); + int i; + + ds->tilesize = 0; + ds->started = false; + ds->state = NULL; + ds->state = snewn(state->width * state->height, int); + for (i = 0; i < state->width * state->height; i++) + ds->state[i] = -1; + + return ds; +} + +static void game_free_drawstate(drawing *dr, game_drawstate *ds) +{ + sfree(ds->state); + sfree(ds); +} + +static void draw_cell(drawing *dr, int cell, int ts, char clue_val, + int x, int y) +{ + int startX = ((x * ts) + ts / 2) - 1, startY = ((y * ts) + ts / 2) - 1; + int color, text_color = COL_TEXT_DARK; + + draw_rect_outline(dr, startX - 1, startY - 1, ts + 1, ts + 1, + (cell & DRAWFLAG_CURSOR) ? COL_CURSOR : COL_GRID); + + if (cell & STATE_MARKED) { + color = COL_MARKED; + text_color = COL_TEXT_LIGHT; + } else if (cell & STATE_BLANK) { + text_color = COL_TEXT_DARK; + color = COL_BLANK; + } else { + text_color = COL_TEXT_DARK; + color = COL_UNMARKED; + } + if (cell & STATE_ERROR) { + text_color = COL_ERROR; + } else if (cell & STATE_SOLVED) { + text_color = COL_TEXT_SOLVED; + } + + draw_rect(dr, startX, startY, ts - 1, ts - 1, color); + if (clue_val >= 0) { + char clue[80]; + sprintf(clue, "%d", clue_val); + draw_text(dr, startX + ts / 2, startY + ts / 2, 1, ts * 3 / 5, + ALIGN_VCENTRE | ALIGN_HCENTRE, text_color, clue); + } + draw_update(dr, startX, startY, ts - 1, ts - 1); +} + +static void game_redraw(drawing *dr, game_drawstate *ds, + const game_state *oldstate, + const game_state *state, int dir, + const game_ui *ui, float animtime, + float flashtime) +{ + int x, y; + char status[80], clue_val; + bool flashing = (flashtime > 0 && (flashtime <= FLASH_TIME / 3 || + flashtime > 2*FLASH_TIME / 3)); + + if (!ds->started) { + /* + * The initial contents of the window are not guaranteed and + * can vary with front ends. To be on the safe side, all games + * should start by drawing a big background-colour rectangle + * covering the whole window. + */ + draw_rect(dr, 0, 0, (state->width + 1) * ds->tilesize, + (state->height + 1) * ds->tilesize, COL_BACKGROUND); + draw_update(dr, 0, 0, (state->width + 1) * ds->tilesize, + (state->height + 1) * ds->tilesize); + ds->started = true; + } + for (y = 0; y < state->height; y++) { + for (x = 0; x < state->width; x++) { + int cell = state->cells_contents[(y * state->width) + x]; + if (flashing) + cell ^= (STATE_BLANK | STATE_MARKED); + if (ui->cur_visible && ui->cur_x == x && ui->cur_y == y) + cell |= DRAWFLAG_CURSOR; + + if (state->board->actual_board[(y * state->width) + x].shown) { + clue_val = state->board->actual_board[ + (y * state->width) + x].clue; + } else { + clue_val = -1; + } + + if (ds->state[(y * state->width) + x] != cell) { + draw_cell(dr, cell, ds->tilesize, clue_val, x, y); + ds->state[(y * state->width) + x] = cell; + } + } + } + sprintf(status, "Clues left: %d", state->not_completed_clues); + if (state->not_completed_clues == 0 && !state->cheating) { + sprintf(status, "COMPLETED!"); + } else if (state->not_completed_clues == 0 && state->cheating) { + sprintf(status, "Auto solved"); + } + status_bar(dr, status); +} + +static float game_anim_length(const game_state *oldstate, + const game_state *newstate, int dir, + game_ui *ui) +{ + return 0.0F; +} + +static float game_flash_length(const game_state *oldstate, + const game_state *newstate, int dir, + game_ui *ui) +{ + if (!oldstate->cheating && oldstate->not_completed_clues > 0 && + newstate->not_completed_clues == 0) { + return FLASH_TIME; + } + return 0.0F; +} + +static int game_status(const game_state *state) +{ + return 0; +} + +static bool game_timing_state(const game_state *state, game_ui *ui) +{ + return state->not_completed_clues > 0; +} + +static void game_print_size(const game_params *params, float *x, float *y) +{ +} + +static void game_print(drawing *dr, const game_state *state, int tilesize) +{ +} + +#ifdef COMBINED +#define thegame mosaic +#endif + +const struct game thegame = { + "Mosaic", "games.mosaic", "mosaic", + default_params, + game_fetch_preset, NULL, + decode_params, + encode_params, + free_params, + dup_params, + true, game_configure, custom_params, + validate_params, + new_game_desc, + validate_desc, + new_game, + dup_game, + free_game, + true, solve_game, + true, game_can_format_as_text_now, game_text_format, + new_ui, + free_ui, + encode_ui, + decode_ui, + NULL, /* game_request_keys */ + game_changed_state, + interpret_move, + execute_move, + DEFAULT_TILE_SIZE, game_compute_size, game_set_size, + game_colours, + game_new_drawstate, + game_free_drawstate, + game_redraw, + game_anim_length, + game_flash_length, + game_get_cursor_location, + game_status, +#ifndef NO_PRINTING + false, false, game_print_size, game_print, +#endif + true, /* wants_statusbar */ + false, game_timing_state, + 0, /* flags */ +}; diff --git a/puzzles.but b/puzzles.but index ee519b8..5180907 100644 --- a/puzzles.but +++ b/puzzles.but @@ -3354,6 +3354,56 @@ These parameters are available from the \q{Custom...} option on the \dd The size of the regions into which the grid must be subdivided. +\C{mosaic} \i{Mosaic} + +\cfg{winhelp-topic}{games.mosaic} + +You are given a grid of squares, which you must colour either black or +white. + +Some squares contain clue numbers. Each clue tells you the number of +black squares in the 3\times\.3 region surrounding the clue \dash +\e{including} the clue square itself. + +This game is variously known in other locations as: ArtMosaico, Count +and Darken, Cuenta Y Sombrea, Fill-a-Pix, Fill-In, Komsu Karala, +Magipic, Majipiku, Mosaico, Mosaik, Mozaiek, Nampre Puzzle, +Nurie-Puzzle, Oekaki-Pix, Voisimage. + +Mosaic was contributed to this collection by Didi Kohen. Colour design +by Michal Shomer. The implementation is loosely based on +\W{https://github.com/mordechaim/Mosaic}\cw{github.com/mordechaim/Mosaic}. + +\H{mosaic-controls} \I{controls, for Mosaic}Mosaic controls + +To play Unruly, click the mouse in a square to change its colour. +Left-clicking an empty square will turn it black, and right-clicking +will turn it white. Keep clicking the same button to cycle through the +three possible states for the square. + +If you hold down the mouse button and drag, you can colour multiple +cells in a single action. + +You can also use the cursor keys to move around the grid. Pressing the +return or space keys will turn an empty square black or white +respectively (and then cycle the colours in the same way as the mouse +buttons), and pressing Backspace will reset a square to empty. + +\H{Mosaic-parameters} \I{parameters, for Mosaic}Mosaic parameters + +These parameters are available from the \q{Custom...} option on the +\q{Type} menu. + +\dt \e{Width}, \e{Height} + +\dd Size of grid in squares. + +\dt \e{Aggressive generation} + +\dd With this option set, the game generator will try harder to +eliminate unnecessary clues on the board. This slows down generation, +so it's not recommended for boards larger than, say, 30\times\.30. + \A{licence} \I{MIT licence}\ii{Licence} This software is \i{copyright} 2004-2014 Simon Tatham. @@ -3361,7 +3411,7 @@ This software is \i{copyright} 2004-2014 Simon Tatham. Portions copyright Richard Boulton, James Harvey, Mike Pinna, Jonas K\u00F6{oe}lker, Dariusz Olszewski, Michael Schierl, Lambros Lambrou, Bernd Schmidt, Steffen Bauer, Lennard Sprong, Rogier Goossens, Michael -Quevillon and Asher Gordon. +Quevillon, Asher Gordon and Didi Kohen. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files