From c8266e0330789dceef7ece0cd4fdcc927d0ac5fc Mon Sep 17 00:00:00 2001 From: simon Date: Wed, 25 May 2005 11:09:43 +0000 Subject: [PATCH] Support for `pencil marks' in Solo, by right-clicking and typing a number. Many thanks to Chris Thomas, for helping with the detailed UI design by means of testing an endless series of prototypes. git-svn-id: svn://svn.tartarus.org/sgt/puzzles@5842 cda61777-01e9-0310-a592-d414129be87e --- puzzles.but | 17 +++++++ solo.c | 150 +++++++++++++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 146 insertions(+), 21 deletions(-) diff --git a/puzzles.but b/puzzles.but index 1faa9e8..865aad7 100644 --- a/puzzles.but +++ b/puzzles.but @@ -760,6 +760,23 @@ type a digit or letter on the keyboard to fill that square. If you make a mistake, click the mouse in the incorrect square and press Space to clear it again (or use the Undo feature). +If you \e{right}-click in a square and then type a number, that +number will be entered in the square as a \q{pencil mark}. You can +have pencil marks for multiple numbers in the same square. + +The game pays no attention to pencil marks, so exactly what you use +them for is up to you: you can use them as reminders that a +particular square needs to be re-examined once you know more about a +particular number, or you can use them as lists of the possible +numbers in a given square, or anything else you feel like. + +To erase a single pencil mark, right-click in the square and type +the same number again. + +All pencil marks in a square are erased when you left-click and type +a number, or when you left-click and press space. Right-clicking and +pressing space will also erase pencil marks. + (All the actions described in \k{common-actions} are also available.) \H{solo-parameters} \I{parameters, for Solo}Solo parameters diff --git a/solo.c b/solo.c index f074a9c..ad6c2f4 100644 --- a/solo.c +++ b/solo.c @@ -3,6 +3,30 @@ * * TODO: * + * - reports from users are that `Trivial'-mode puzzles are still + * rather hard compared to newspapers' easy ones, so some better + * low-end difficulty grading would be nice + * + it's possible that really easy puzzles always have + * _several_ things you can do, so don't make you hunt too + * hard for the one deduction you can currently make + * + it's also possible that easy puzzles require fewer + * cross-eliminations: perhaps there's a higher incidence of + * things you can deduce by looking only at (say) rows, + * rather than things you have to check both rows and columns + * for + * + but really, what I need to do is find some really easy + * puzzles and _play_ them, to see what's actually easy about + * them + * + while I'm revamping this area, filling in the _last_ + * number in a nearly-full row or column should certainly be + * permitted even at the lowest difficulty level. + * + also Owen noticed that `Basic' grids requiring numeric + * elimination are actually very hard, so I wonder if a + * difficulty gradation between that and positional- + * elimination-only might be in order + * + but it's not good to have _too_ many difficulty levels, or + * it'll take too long to randomly generate a given level. + * * - it might still be nice to do some prioritisation on the * removal of numbers from the grid * + one possibility is to try to minimise the maximum number @@ -20,8 +44,13 @@ * click, _or_ you highlight a square and then type. At most * one thing is ever highlighted at a time, so there's no way * to confuse the two. - * + `pencil marks' might be useful for more subtle forms of - * deduction, now we can create puzzles that require them. + * + then again, I don't actually like sudoku.com's interface; + * it's too much like a paint package whereas I prefer to + * think of Solo as a text editor. + * + another PDA-friendly possibility is a drag interface: + * _drag_ numbers from the palette into the grid squares. + * Thought experiments suggest I'd prefer that to the + * sudoku.com approach, but I haven't actually tried it. */ /* @@ -96,6 +125,7 @@ enum { COL_CLUE, COL_USER, COL_HIGHLIGHT, + COL_PENCIL, NCOLOURS }; @@ -106,6 +136,7 @@ struct game_params { struct game_state { int c, r; digit *grid; + unsigned char *pencil; /* c*r*c*r elements */ unsigned char *immutable; /* marks which digits are clues */ int completed, cheated; }; @@ -1599,6 +1630,8 @@ static game_state *new_game(game_params *params, char *desc) state->r = params->r; state->grid = snewn(area, digit); + state->pencil = snewn(area * cr, unsigned char); + memset(state->pencil, 0, area * cr); state->immutable = snewn(area, unsigned char); memset(state->immutable, FALSE, area); @@ -1640,6 +1673,9 @@ static game_state *dup_game(game_state *state) ret->grid = snewn(area, digit); memcpy(ret->grid, state->grid, area); + ret->pencil = snewn(area * cr, unsigned char); + memcpy(ret->pencil, state->pencil, area * cr); + ret->immutable = snewn(area, unsigned char); memcpy(ret->immutable, state->immutable, area); @@ -1652,6 +1688,7 @@ static game_state *dup_game(game_state *state) static void free_game(game_state *state) { sfree(state->immutable); + sfree(state->pencil); sfree(state->grid); sfree(state); } @@ -1762,6 +1799,11 @@ struct game_ui { * enter that number or letter in the grid. */ int hx, hy; + /* + * This indicates whether the current highlight is a + * pencil-mark one or a real one. + */ + int hpencil; }; static game_ui *new_ui(game_state *state) @@ -1769,6 +1811,7 @@ static game_ui *new_ui(game_state *state) game_ui *ui = snew(game_ui); ui->hx = ui->hy = -1; + ui->hpencil = 0; return ui; } @@ -1790,13 +1833,21 @@ static game_state *make_move(game_state *from, game_ui *ui, int x, int y, tx = (x + TILE_SIZE - BORDER) / TILE_SIZE - 1; ty = (y + TILE_SIZE - BORDER) / TILE_SIZE - 1; - if (tx >= 0 && tx < cr && ty >= 0 && ty < cr && button == LEFT_BUTTON) { + if (tx >= 0 && tx < cr && ty >= 0 && ty < cr && + (button == LEFT_BUTTON || button == RIGHT_BUTTON)) { + /* + * Prevent pencil-mode highlighting of a filled square. + */ + if (button == RIGHT_BUTTON && from->grid[ty*cr+tx]) + return NULL; + if (tx == ui->hx && ty == ui->hy) { ui->hx = ui->hy = -1; } else { ui->hx = tx; ui->hy = ty; } + ui->hpencil = (button == RIGHT_BUTTON); return from; /* UI activity occurred */ } @@ -1816,17 +1867,32 @@ static game_state *make_move(game_state *from, game_ui *ui, int x, int y, if (from->immutable[ui->hy*cr+ui->hx]) return NULL; /* can't overwrite this square */ + /* + * Can't make pencil marks in a filled square. In principle + * this shouldn't happen anyway because we should never + * have even been able to pencil-highlight the square, but + * it never hurts to be careful. + */ + if (ui->hpencil && from->grid[ui->hy*cr+ui->hx]) + return NULL; + ret = dup_game(from); - ret->grid[ui->hy*cr+ui->hx] = n; - ui->hx = ui->hy = -1; + if (ui->hpencil && n > 0) { + int index = (ui->hy*cr+ui->hx) * cr + (n-1); + ret->pencil[index] = !ret->pencil[index]; + } else { + ret->grid[ui->hy*cr+ui->hx] = n; + memset(ret->pencil + (ui->hy*cr+ui->hx)*cr, 0, cr); - /* - * We've made a real change to the grid. Check to see - * if the game has been completed. - */ - if (!ret->completed && check_valid(c, r, ret->grid)) { - ret->completed = TRUE; - } + /* + * We've made a real change to the grid. Check to see + * if the game has been completed. + */ + if (!ret->completed && check_valid(c, r, ret->grid)) { + ret->completed = TRUE; + } + } + ui->hx = ui->hy = -1; return ret; /* made a valid move */ } @@ -1842,6 +1908,7 @@ struct game_drawstate { int started; int c, r, cr; digit *grid; + unsigned char *pencil; unsigned char *hl; }; @@ -1878,6 +1945,10 @@ static float *game_colours(frontend *fe, game_state *state, int *ncolours) ret[COL_HIGHLIGHT * 3 + 1] = 0.85F * ret[COL_BACKGROUND * 3 + 1]; ret[COL_HIGHLIGHT * 3 + 2] = 0.85F * ret[COL_BACKGROUND * 3 + 2]; + ret[COL_PENCIL * 3 + 0] = 0.5F * ret[COL_BACKGROUND * 3 + 0]; + ret[COL_PENCIL * 3 + 1] = 0.5F * ret[COL_BACKGROUND * 3 + 1]; + ret[COL_PENCIL * 3 + 2] = ret[COL_BACKGROUND * 3 + 2]; + *ncolours = NCOLOURS; return ret; } @@ -1893,6 +1964,8 @@ static game_drawstate *game_new_drawstate(game_state *state) ds->cr = cr; ds->grid = snewn(cr*cr, digit); memset(ds->grid, 0, cr*cr); + ds->pencil = snewn(cr*cr*cr, digit); + memset(ds->pencil, 0, cr*cr*cr); ds->hl = snewn(cr*cr, unsigned char); memset(ds->hl, 0, cr*cr); @@ -1902,6 +1975,7 @@ static game_drawstate *game_new_drawstate(game_state *state) static void game_free_drawstate(game_drawstate *ds) { sfree(ds->hl); + sfree(ds->pencil); sfree(ds->grid); sfree(ds); } @@ -1914,7 +1988,9 @@ static void draw_number(frontend *fe, game_drawstate *ds, game_state *state, int cx, cy, cw, ch; char str[2]; - if (ds->grid[y*cr+x] == state->grid[y*cr+x] && ds->hl[y*cr+x] == hl) + if (ds->grid[y*cr+x] == state->grid[y*cr+x] && + ds->hl[y*cr+x] == hl && + !memcmp(ds->pencil+(y*cr+x)*cr, state->pencil+(y*cr+x)*cr, cr)) return; /* no change required */ tx = BORDER + x * TILE_SIZE + 2; @@ -1936,9 +2012,20 @@ static void draw_number(frontend *fe, game_drawstate *ds, game_state *state, clip(fe, cx, cy, cw, ch); - /* background needs erasing? */ - if (ds->grid[y*cr+x] || ds->hl[y*cr+x] != hl) - draw_rect(fe, cx, cy, cw, ch, hl ? COL_HIGHLIGHT : COL_BACKGROUND); + /* background needs erasing */ + draw_rect(fe, cx, cy, cw, ch, hl == 1 ? COL_HIGHLIGHT : COL_BACKGROUND); + + /* pencil-mode highlight */ + if (hl == 2) { + int coords[6]; + coords[0] = cx; + coords[1] = cy; + coords[2] = cx+cw/2; + coords[3] = cy; + coords[4] = cx; + coords[5] = cy+ch/2; + draw_polygon(fe, coords, 3, TRUE, COL_HIGHLIGHT); + } /* new number needs drawing? */ if (state->grid[y*cr+x]) { @@ -1949,6 +2036,23 @@ static void draw_number(frontend *fe, game_drawstate *ds, game_state *state, draw_text(fe, tx + TILE_SIZE/2, ty + TILE_SIZE/2, FONT_VARIABLE, TILE_SIZE/2, ALIGN_VCENTRE | ALIGN_HCENTRE, state->immutable[y*cr+x] ? COL_CLUE : COL_USER, str); + } else { + /* pencil marks required? */ + int i, j; + + for (i = j = 0; i < cr; i++) + if (state->pencil[(y*cr+x)*cr+i]) { + int dx = j % r, dy = j / r, crm = max(c, r); + str[1] = '\0'; + str[0] = i + '1'; + if (str[0] > '9') + str[0] += 'a' - ('9'+1); + draw_text(fe, tx + (4*dx+3) * TILE_SIZE / (4*r+2), + ty + (4*dy+3) * TILE_SIZE / (4*c+2), + FONT_VARIABLE, TILE_SIZE/(crm*5/4), + ALIGN_VCENTRE | ALIGN_HCENTRE, COL_PENCIL, str); + j++; + } } unclip(fe); @@ -1956,6 +2060,7 @@ static void draw_number(frontend *fe, game_drawstate *ds, game_state *state, draw_update(fe, cx, cy, cw, ch); ds->grid[y*cr+x] = state->grid[y*cr+x]; + memcpy(ds->pencil+(y*cr+x)*cr, state->pencil+(y*cr+x)*cr, cr); ds->hl[y*cr+x] = hl; } @@ -1995,11 +2100,14 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate, */ for (x = 0; x < cr; x++) { for (y = 0; y < cr; y++) { - draw_number(fe, ds, state, x, y, - (x == ui->hx && y == ui->hy) || - (flashtime > 0 && - (flashtime <= FLASH_TIME/3 || - flashtime >= FLASH_TIME*2/3))); + int highlight = 0; + if (flashtime > 0 && + (flashtime <= FLASH_TIME/3 || + flashtime >= FLASH_TIME*2/3)) + highlight = 1; + if (x == ui->hx && y == ui->hy) + highlight = ui->hpencil ? 2 : 1; + draw_number(fe, ds, state, x, y, highlight); } } -- 2.11.0