mirror of
git://git.tartarus.org/simon/puzzles.git
synced 2025-04-21 16:05:44 -07:00
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. [originally from svn r5842]
This commit is contained in:
17
puzzles.but
17
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
|
make a mistake, click the mouse in the incorrect square and press
|
||||||
Space to clear it again (or use the Undo feature).
|
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.)
|
(All the actions described in \k{common-actions} are also available.)
|
||||||
|
|
||||||
\H{solo-parameters} \I{parameters, for Solo}Solo parameters
|
\H{solo-parameters} \I{parameters, for Solo}Solo parameters
|
||||||
|
132
solo.c
132
solo.c
@ -3,6 +3,30 @@
|
|||||||
*
|
*
|
||||||
* TODO:
|
* 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
|
* - it might still be nice to do some prioritisation on the
|
||||||
* removal of numbers from the grid
|
* removal of numbers from the grid
|
||||||
* + one possibility is to try to minimise the maximum number
|
* + 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
|
* click, _or_ you highlight a square and then type. At most
|
||||||
* one thing is ever highlighted at a time, so there's no way
|
* one thing is ever highlighted at a time, so there's no way
|
||||||
* to confuse the two.
|
* to confuse the two.
|
||||||
* + `pencil marks' might be useful for more subtle forms of
|
* + then again, I don't actually like sudoku.com's interface;
|
||||||
* deduction, now we can create puzzles that require them.
|
* 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_CLUE,
|
||||||
COL_USER,
|
COL_USER,
|
||||||
COL_HIGHLIGHT,
|
COL_HIGHLIGHT,
|
||||||
|
COL_PENCIL,
|
||||||
NCOLOURS
|
NCOLOURS
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -106,6 +136,7 @@ struct game_params {
|
|||||||
struct game_state {
|
struct game_state {
|
||||||
int c, r;
|
int c, r;
|
||||||
digit *grid;
|
digit *grid;
|
||||||
|
unsigned char *pencil; /* c*r*c*r elements */
|
||||||
unsigned char *immutable; /* marks which digits are clues */
|
unsigned char *immutable; /* marks which digits are clues */
|
||||||
int completed, cheated;
|
int completed, cheated;
|
||||||
};
|
};
|
||||||
@ -1599,6 +1630,8 @@ static game_state *new_game(game_params *params, char *desc)
|
|||||||
state->r = params->r;
|
state->r = params->r;
|
||||||
|
|
||||||
state->grid = snewn(area, digit);
|
state->grid = snewn(area, digit);
|
||||||
|
state->pencil = snewn(area * cr, unsigned char);
|
||||||
|
memset(state->pencil, 0, area * cr);
|
||||||
state->immutable = snewn(area, unsigned char);
|
state->immutable = snewn(area, unsigned char);
|
||||||
memset(state->immutable, FALSE, area);
|
memset(state->immutable, FALSE, area);
|
||||||
|
|
||||||
@ -1640,6 +1673,9 @@ static game_state *dup_game(game_state *state)
|
|||||||
ret->grid = snewn(area, digit);
|
ret->grid = snewn(area, digit);
|
||||||
memcpy(ret->grid, state->grid, area);
|
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);
|
ret->immutable = snewn(area, unsigned char);
|
||||||
memcpy(ret->immutable, state->immutable, area);
|
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)
|
static void free_game(game_state *state)
|
||||||
{
|
{
|
||||||
sfree(state->immutable);
|
sfree(state->immutable);
|
||||||
|
sfree(state->pencil);
|
||||||
sfree(state->grid);
|
sfree(state->grid);
|
||||||
sfree(state);
|
sfree(state);
|
||||||
}
|
}
|
||||||
@ -1762,6 +1799,11 @@ struct game_ui {
|
|||||||
* enter that number or letter in the grid.
|
* enter that number or letter in the grid.
|
||||||
*/
|
*/
|
||||||
int hx, hy;
|
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)
|
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);
|
game_ui *ui = snew(game_ui);
|
||||||
|
|
||||||
ui->hx = ui->hy = -1;
|
ui->hx = ui->hy = -1;
|
||||||
|
ui->hpencil = 0;
|
||||||
|
|
||||||
return ui;
|
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;
|
tx = (x + TILE_SIZE - BORDER) / TILE_SIZE - 1;
|
||||||
ty = (y + 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) {
|
if (tx == ui->hx && ty == ui->hy) {
|
||||||
ui->hx = ui->hy = -1;
|
ui->hx = ui->hy = -1;
|
||||||
} else {
|
} else {
|
||||||
ui->hx = tx;
|
ui->hx = tx;
|
||||||
ui->hy = ty;
|
ui->hy = ty;
|
||||||
}
|
}
|
||||||
|
ui->hpencil = (button == RIGHT_BUTTON);
|
||||||
return from; /* UI activity occurred */
|
return from; /* UI activity occurred */
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1816,9 +1867,22 @@ static game_state *make_move(game_state *from, game_ui *ui, int x, int y,
|
|||||||
if (from->immutable[ui->hy*cr+ui->hx])
|
if (from->immutable[ui->hy*cr+ui->hx])
|
||||||
return NULL; /* can't overwrite this square */
|
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 = dup_game(from);
|
||||||
|
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;
|
ret->grid[ui->hy*cr+ui->hx] = n;
|
||||||
ui->hx = ui->hy = -1;
|
memset(ret->pencil + (ui->hy*cr+ui->hx)*cr, 0, cr);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We've made a real change to the grid. Check to see
|
* We've made a real change to the grid. Check to see
|
||||||
@ -1827,6 +1891,8 @@ static game_state *make_move(game_state *from, game_ui *ui, int x, int y,
|
|||||||
if (!ret->completed && check_valid(c, r, ret->grid)) {
|
if (!ret->completed && check_valid(c, r, ret->grid)) {
|
||||||
ret->completed = TRUE;
|
ret->completed = TRUE;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
ui->hx = ui->hy = -1;
|
||||||
|
|
||||||
return ret; /* made a valid move */
|
return ret; /* made a valid move */
|
||||||
}
|
}
|
||||||
@ -1842,6 +1908,7 @@ struct game_drawstate {
|
|||||||
int started;
|
int started;
|
||||||
int c, r, cr;
|
int c, r, cr;
|
||||||
digit *grid;
|
digit *grid;
|
||||||
|
unsigned char *pencil;
|
||||||
unsigned char *hl;
|
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 + 1] = 0.85F * ret[COL_BACKGROUND * 3 + 1];
|
||||||
ret[COL_HIGHLIGHT * 3 + 2] = 0.85F * ret[COL_BACKGROUND * 3 + 2];
|
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;
|
*ncolours = NCOLOURS;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@ -1893,6 +1964,8 @@ static game_drawstate *game_new_drawstate(game_state *state)
|
|||||||
ds->cr = cr;
|
ds->cr = cr;
|
||||||
ds->grid = snewn(cr*cr, digit);
|
ds->grid = snewn(cr*cr, digit);
|
||||||
memset(ds->grid, 0, cr*cr);
|
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);
|
ds->hl = snewn(cr*cr, unsigned char);
|
||||||
memset(ds->hl, 0, cr*cr);
|
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)
|
static void game_free_drawstate(game_drawstate *ds)
|
||||||
{
|
{
|
||||||
sfree(ds->hl);
|
sfree(ds->hl);
|
||||||
|
sfree(ds->pencil);
|
||||||
sfree(ds->grid);
|
sfree(ds->grid);
|
||||||
sfree(ds);
|
sfree(ds);
|
||||||
}
|
}
|
||||||
@ -1914,7 +1988,9 @@ static void draw_number(frontend *fe, game_drawstate *ds, game_state *state,
|
|||||||
int cx, cy, cw, ch;
|
int cx, cy, cw, ch;
|
||||||
char str[2];
|
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 */
|
return; /* no change required */
|
||||||
|
|
||||||
tx = BORDER + x * TILE_SIZE + 2;
|
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);
|
clip(fe, cx, cy, cw, ch);
|
||||||
|
|
||||||
/* background needs erasing? */
|
/* background needs erasing */
|
||||||
if (ds->grid[y*cr+x] || ds->hl[y*cr+x] != hl)
|
draw_rect(fe, cx, cy, cw, ch, hl == 1 ? COL_HIGHLIGHT : COL_BACKGROUND);
|
||||||
draw_rect(fe, cx, cy, cw, ch, hl ? 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? */
|
/* new number needs drawing? */
|
||||||
if (state->grid[y*cr+x]) {
|
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,
|
draw_text(fe, tx + TILE_SIZE/2, ty + TILE_SIZE/2,
|
||||||
FONT_VARIABLE, TILE_SIZE/2, ALIGN_VCENTRE | ALIGN_HCENTRE,
|
FONT_VARIABLE, TILE_SIZE/2, ALIGN_VCENTRE | ALIGN_HCENTRE,
|
||||||
state->immutable[y*cr+x] ? COL_CLUE : COL_USER, str);
|
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);
|
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);
|
draw_update(fe, cx, cy, cw, ch);
|
||||||
|
|
||||||
ds->grid[y*cr+x] = state->grid[y*cr+x];
|
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;
|
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 (x = 0; x < cr; x++) {
|
||||||
for (y = 0; y < cr; y++) {
|
for (y = 0; y < cr; y++) {
|
||||||
draw_number(fe, ds, state, x, y,
|
int highlight = 0;
|
||||||
(x == ui->hx && y == ui->hy) ||
|
if (flashtime > 0 &&
|
||||||
(flashtime > 0 &&
|
|
||||||
(flashtime <= FLASH_TIME/3 ||
|
(flashtime <= FLASH_TIME/3 ||
|
||||||
flashtime >= FLASH_TIME*2/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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user