James Harvey's extensions to Guess: a couple of extra game settings

plus a manual chapter.

[originally from svn r5999]
This commit is contained in:
Simon Tatham
2005-06-23 18:50:58 +00:00
parent 2ce863a76d
commit af052f2620
2 changed files with 197 additions and 49 deletions

172
guess.c
View File

@ -15,7 +15,7 @@
enum { enum {
COL_BACKGROUND, COL_BACKGROUND,
COL_HIGHLIGHT, COL_LOWLIGHT, COL_FRAME, COL_FLASH, COL_HOLD, COL_FRAME, COL_CURSOR, COL_FLASH, COL_HOLD,
COL_EMPTY, /* must be COL_1 - 1 */ COL_EMPTY, /* must be COL_1 - 1 */
COL_1, COL_2, COL_3, COL_4, COL_5, COL_6, COL_7, COL_8, COL_9, COL_10, COL_1, COL_2, COL_3, COL_4, COL_5, COL_6, COL_7, COL_8, COL_9, COL_10,
COL_CORRECTPLACE, COL_CORRECTCOLOUR, COL_CORRECTPLACE, COL_CORRECTCOLOUR,
@ -24,6 +24,7 @@ enum {
struct game_params { struct game_params {
int ncolours, npegs, nguesses; int ncolours, npegs, nguesses;
int allow_blank, allow_multiple;
}; };
#define FEEDBACK_CORRECTPLACE 1 #define FEEDBACK_CORRECTPLACE 1
@ -53,6 +54,9 @@ static game_params *default_params(void)
ret->npegs = 4; ret->npegs = 4;
ret->nguesses = 10; ret->nguesses = 10;
ret->allow_blank = 0;
ret->allow_multiple = 1;
return ret; return ret;
} }
@ -97,6 +101,22 @@ static void decode_params(game_params *params, char const *string)
while (*p && isdigit((unsigned char)*p)) p++; while (*p && isdigit((unsigned char)*p)) p++;
break; break;
case 'b':
params->allow_blank = 1;
break;
case 'B':
params->allow_blank = 0;
break;
case 'm':
params->allow_multiple = 1;
break;
case 'M':
params->allow_multiple = 0;
break;
default: default:
; ;
} }
@ -107,7 +127,9 @@ static char *encode_params(game_params *params, int full)
{ {
char data[256]; char data[256];
sprintf(data, "c%dp%dg%d", params->ncolours, params->npegs, params->nguesses); sprintf(data, "c%dp%dg%d%s%s",
params->ncolours, params->npegs, params->nguesses,
params->allow_blank ? "b" : "B", params->allow_multiple ? "m" : "M");
return dupstr(data); return dupstr(data);
} }
@ -117,30 +139,40 @@ static config_item *game_configure(game_params *params)
config_item *ret; config_item *ret;
char buf[80]; char buf[80];
ret = snewn(4, config_item); ret = snewn(6, config_item);
ret[0].name = "No. of colours"; ret[0].name = "Colours";
ret[0].type = C_STRING; ret[0].type = C_STRING;
sprintf(buf, "%d", params->ncolours); sprintf(buf, "%d", params->ncolours);
ret[0].sval = dupstr(buf); ret[0].sval = dupstr(buf);
ret[0].ival = 0; ret[0].ival = 0;
ret[1].name = "No. of pegs per row"; ret[1].name = "Pegs per guess";
ret[1].type = C_STRING; ret[1].type = C_STRING;
sprintf(buf, "%d", params->npegs); sprintf(buf, "%d", params->npegs);
ret[1].sval = dupstr(buf); ret[1].sval = dupstr(buf);
ret[1].ival = 0; ret[1].ival = 0;
ret[2].name = "No. of guesses"; ret[2].name = "Guesses";
ret[2].type = C_STRING; ret[2].type = C_STRING;
sprintf(buf, "%d", params->nguesses); sprintf(buf, "%d", params->nguesses);
ret[2].sval = dupstr(buf); ret[2].sval = dupstr(buf);
ret[2].ival = 0; ret[2].ival = 0;
ret[3].name = NULL; ret[3].name = "Allow blanks";
ret[3].type = C_END; ret[3].type = C_BOOLEAN;
ret[3].sval = NULL; ret[3].sval = NULL;
ret[3].ival = 0; ret[3].ival = params->allow_blank;
ret[4].name = "Allow duplicates";
ret[4].type = C_BOOLEAN;
ret[4].sval = NULL;
ret[4].ival = params->allow_multiple;
ret[5].name = NULL;
ret[5].type = C_END;
ret[5].sval = NULL;
ret[5].ival = 0;
return ret; return ret;
} }
@ -153,6 +185,9 @@ static game_params *custom_params(config_item *cfg)
ret->npegs = atoi(cfg[1].sval); ret->npegs = atoi(cfg[1].sval);
ret->nguesses = atoi(cfg[2].sval); ret->nguesses = atoi(cfg[2].sval);
ret->allow_blank = cfg[3].ival;
ret->allow_multiple = cfg[4].ival;
return ret; return ret;
} }
@ -166,6 +201,8 @@ static char *validate_params(game_params *params)
return "Too many colours"; return "Too many colours";
if (params->nguesses < 1) if (params->nguesses < 1)
return "Must have at least one guess"; return "Must have at least one guess";
if (!params->allow_multiple && params->ncolours < params->npegs)
return "Disallowing multiple colours requires at least as many colours as pegs";
return NULL; return NULL;
} }
@ -213,14 +250,21 @@ static char *new_game_desc(game_params *params, random_state *rs,
{ {
unsigned char *bmp = snewn(params->npegs, unsigned char); unsigned char *bmp = snewn(params->npegs, unsigned char);
char *ret; char *ret;
int i; int i, c;
pegrow colcount = new_pegrow(params->ncolours);
for (i = 0; i < params->npegs; i++) for (i = 0; i < params->npegs; i++) {
bmp[i] = (unsigned char)(random_upto(rs, params->ncolours)+1); newcol:
c = random_upto(rs, params->ncolours);
if (!params->allow_multiple && colcount->pegs[c]) goto newcol;
colcount->pegs[c]++;
bmp[i] = (unsigned char)(c+1);
}
obfuscate_bitmap(bmp, params->npegs*8, FALSE); obfuscate_bitmap(bmp, params->npegs*8, FALSE);
ret = bin2hex(bmp, params->npegs); ret = bin2hex(bmp, params->npegs);
sfree(bmp); sfree(bmp);
free_pegrow(colcount);
return ret; return ret;
} }
@ -231,10 +275,24 @@ static void game_free_aux_info(game_aux_info *aux)
static char *validate_desc(game_params *params, char *desc) static char *validate_desc(game_params *params, char *desc)
{ {
/* desc is just an (obfuscated) bitmap of the solution; all we unsigned char *bmp;
* care is that it's the correct length. */ int i;
/* desc is just an (obfuscated) bitmap of the solution; check that
* it's the correct length and (when unobfuscated) contains only
* sensible colours. */
if (strlen(desc) != params->npegs * 2) if (strlen(desc) != params->npegs * 2)
return "Game description is wrong length"; return "Game description is wrong length";
bmp = hex2bin(desc, params->npegs);
obfuscate_bitmap(bmp, params->npegs*8, TRUE);
for (i = 0; i < params->npegs; i++) {
if (bmp[i] < 1 || bmp[i] > params->ncolours) {
sfree(bmp);
return "Game description is corrupted";
}
}
sfree(bmp);
return NULL; return NULL;
} }
@ -308,6 +366,7 @@ struct game_ui {
int display_cur, markable; int display_cur, markable;
int drag_col, drag_x, drag_y; /* x and y are *center* of peg! */ int drag_col, drag_x, drag_y; /* x and y are *center* of peg! */
int drag_opeg; /* peg index, if dragged from a peg (from current guess), otherwise -1 */
}; };
static game_ui *new_ui(game_state *state) static game_ui *new_ui(game_state *state)
@ -317,6 +376,7 @@ static game_ui *new_ui(game_state *state)
ui->curr_pegs = new_pegrow(state->params.npegs); ui->curr_pegs = new_pegrow(state->params.npegs);
ui->holds = snewn(state->params.npegs, int); ui->holds = snewn(state->params.npegs, int);
memset(ui->holds, 0, sizeof(int)*state->params.npegs); memset(ui->holds, 0, sizeof(int)*state->params.npegs);
ui->drag_opeg = -1;
return ui; return ui;
} }
@ -395,18 +455,36 @@ struct game_drawstate {
int drag_col, blit_ox, blit_oy; int drag_col, blit_ox, blit_oy;
}; };
static void set_peg(game_ui *ui, int peg, int col) static int is_markable(game_params *params, pegrow pegs)
{ {
int i; int i, nset = 0, nrequired, ret = 0;
pegrow colcount = new_pegrow(params->ncolours);
ui->curr_pegs->pegs[peg] = col; nrequired = params->allow_blank ? 1 : params->npegs;
/* set to 'markable' if all of our pegs are filled. */ for (i = 0; i < params->npegs; i++) {
for (i = 0; i < ui->curr_pegs->npegs; i++) { if (pegs->pegs[i] > 0) {
if (ui->curr_pegs->pegs[i] == 0) return; colcount->pegs[pegs->pegs[i]]++;
nset++;
}
} }
debug(("UI is markable.")); if (nset < nrequired) goto done;
ui->markable = 1;
if (!params->allow_multiple) {
for (i = 0; i < params->ncolours; i++) {
if (colcount->pegs[i] > 1) goto done;
}
}
ret = 1;
done:
free_pegrow(colcount);
return ret;
}
static void set_peg(game_params *params, game_ui *ui, int peg, int col)
{
ui->curr_pegs->pegs[peg] = col;
ui->markable = is_markable(params, ui->curr_pegs);
} }
static int mark_pegs(pegrow guess, pegrow solution, int ncols) static int mark_pegs(pegrow guess, pegrow solution, int ncols)
@ -474,11 +552,9 @@ static game_state *mark_move(game_state *from, game_ui *ui)
} }
if (to->solved) ui->holds[i] = 0; if (to->solved) ui->holds[i] = 0;
} }
if (ncleared) { ui->markable = is_markable(&from->params, ui->curr_pegs);
ui->markable = 0; if (!ui->markable && ui->peg_cur == to->solution->npegs)
if (ui->peg_cur == to->solution->npegs) ui->peg_cur--;
ui->peg_cur--;
}
return to; return to;
} }
@ -514,8 +590,8 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
over_past_guess_x = (x - guess_ox) / PEGOFF; over_past_guess_x = (x - guess_ox) / PEGOFF;
} }
debug(("make_move: over_col %d, over_guess %d, over_hint %d," debug(("make_move: over_col %d, over_guess %d, over_hint %d,"
" over_past_guess %d", over_col, over_guess, over_hint, " over_past_guess (%d,%d)", over_col, over_guess, over_hint,
over_past_guess)); over_past_guess_x, over_past_guess_y));
assert(ds->blit_peg); assert(ds->blit_peg);
@ -523,11 +599,13 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
if (button == LEFT_BUTTON) { if (button == LEFT_BUTTON) {
if (over_col > 0) { if (over_col > 0) {
ui->drag_col = over_col; ui->drag_col = over_col;
ui->drag_opeg = -1;
debug(("Start dragging from colours")); debug(("Start dragging from colours"));
} else if (over_guess > -1) { } else if (over_guess > -1) {
int col = ui->curr_pegs->pegs[over_guess]; int col = ui->curr_pegs->pegs[over_guess];
if (col) { if (col) {
ui->drag_col = col; ui->drag_col = col;
ui->drag_opeg = over_guess;
debug(("Start dragging from a guess")); debug(("Start dragging from a guess"));
} }
} else if (over_past_guess_y > -1) { } else if (over_past_guess_y > -1) {
@ -535,6 +613,7 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
from->guesses[over_past_guess_y]->pegs[over_past_guess_x]; from->guesses[over_past_guess_y]->pegs[over_past_guess_x];
if (col) { if (col) {
ui->drag_col = col; ui->drag_col = col;
ui->drag_opeg = -1;
debug(("Start dragging from a past guess")); debug(("Start dragging from a past guess"));
} }
} }
@ -554,9 +633,16 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
if (over_guess > -1) { if (over_guess > -1) {
debug(("Dropping colour %d onto guess peg %d", debug(("Dropping colour %d onto guess peg %d",
ui->drag_col, over_guess)); ui->drag_col, over_guess));
set_peg(ui, over_guess, ui->drag_col); set_peg(&from->params, ui, over_guess, ui->drag_col);
} else {
if (ui->drag_opeg > -1) {
debug(("Removing colour %d from peg %d",
ui->drag_col, ui->drag_opeg));
set_peg(&from->params, ui, ui->drag_opeg, 0);
}
} }
ui->drag_col = 0; ui->drag_col = 0;
ui->drag_opeg = -1;
debug(("Stop dragging.")); debug(("Stop dragging."));
ret = from; ret = from;
} else if (button == RIGHT_BUTTON) { } else if (button == RIGHT_BUTTON) {
@ -595,7 +681,7 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
if (ui->peg_cur == from->params.npegs) { if (ui->peg_cur == from->params.npegs) {
ret = mark_move(from, ui); ret = mark_move(from, ui);
} else { } else {
set_peg(ui, ui->peg_cur, ui->colour_cur+1); set_peg(&from->params, ui, ui->peg_cur, ui->colour_cur+1);
ret = from; ret = from;
} }
} }
@ -734,13 +820,9 @@ static float *game_colours(frontend *fe, game_state *state, int *ncolours)
ret[COL_FRAME * 3 + 1] = 0.0F; ret[COL_FRAME * 3 + 1] = 0.0F;
ret[COL_FRAME * 3 + 2] = 0.0F; ret[COL_FRAME * 3 + 2] = 0.0F;
ret[COL_HIGHLIGHT * 3 + 0] = 1.0F; ret[COL_CURSOR * 3 + 0] = 0.0F;
ret[COL_HIGHLIGHT * 3 + 1] = 1.0F; ret[COL_CURSOR * 3 + 1] = 0.0F;
ret[COL_HIGHLIGHT * 3 + 2] = 1.0F; ret[COL_CURSOR * 3 + 2] = 0.0F;
ret[COL_LOWLIGHT * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] * 2.0 / 3.0;
ret[COL_LOWLIGHT * 3 + 1] = ret[COL_BACKGROUND * 3 + 1] * 2.0 / 3.0;
ret[COL_LOWLIGHT * 3 + 2] = ret[COL_BACKGROUND * 3 + 2] * 2.0 / 3.0;
ret[COL_FLASH * 3 + 0] = 0.5F; ret[COL_FLASH * 3 + 0] = 0.5F;
ret[COL_FLASH * 3 + 1] = 1.0F; ret[COL_FLASH * 3 + 1] = 1.0F;
@ -924,19 +1006,11 @@ static void cur_redraw(frontend *fe, game_drawstate *ds,
int x, int y, int erase) int x, int y, int erase)
{ {
int cgap = ds->gapsz / 2; int cgap = ds->gapsz / 2;
int x1, y1, x2, y2, hi, lo;
x1 = x-cgap; x2 = x+PEGSZ+cgap; draw_circle(fe, x+PEGRAD, y+PEGRAD, PEGRAD+cgap, 0,
y1 = y-cgap; y2 = y+PEGSZ+cgap; erase ? COL_BACKGROUND : COL_CURSOR);
hi = erase ? COL_BACKGROUND : COL_HIGHLIGHT;
lo = erase ? COL_BACKGROUND : COL_LOWLIGHT;
draw_line(fe, x1, y1, x2, y1, hi); draw_update(fe, x-cgap, y-cgap, x+PEGSZ+cgap, y+PEGSZ+cgap);
draw_line(fe, x2, y1, x2, y2, lo);
draw_line(fe, x2, y2, x1, y2, lo);
draw_line(fe, x1, y2, x1, y1, hi);
draw_update(fe, x1, y1, x2, y2);
} }
static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate, static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,

View File

@ -1038,6 +1038,80 @@ causes every square to flip itself and its four immediate neighbours
the game is different every time. the game is different every time.
\C{guess} \i{Guess}
\cfg{winhelp-topic}{games.guess}
You have a set of coloured pegs, and have to reproduce a
predetermined sequence of them (chosen by the computer) within a
certain number of guesses.
Each guess gets marked with the number of correctly-coloured pegs
in the correct places (in red), and also the number of
correctly-coloured pegs in the wrong places.
This game is also known (and marketed, by Hasbro, mainly) as
a board game `Mastermind', with 6 colours, 4 pegs per row, and 10 guesses.
However, this version allows custom settings of number of colours
(up to 10), number of pegs per row, and number of guesses.
\H{guess-controls} \i{Guess controls}
\IM{Guess controls} controls, for Guess
\IM{Guess controls} keys, for Guess
\IM{Guess controls} shortcuts (keyboard), for Guess
Drag a peg from the tray on the left-hand side to its required
position in the current guess; pegs may also be dragged from the
current guess to copy them elsewhere.
Right-clicking in the current guess adds a 'hold' marker; pegs
that have hold markers will be automatically added to the next guess
after marking.
When the guess is complete, the feedback pegs will be highlighted;
clicking on these will mark the current guess, copy any held pegs
to the next guess, and move the 'current guess' marker.
If you correctly position all the pegs the solution will be displayed
below; if you run out of guesses (or select 'Solve...') the solution
will also be revealed.
\H{guess-parameters} \I{parameters, for guess}Guess parameters
These parameters are available from the \q{Custom...} option on the
\q{Type} menu. The default game matches the parameters for the
board game 'Mastermind'.
\dt \e{Colours}
\dd Number of colours the solution is chosen from; from 2 to 10
(more is harder).
\dt \e{Pegs per guess}
\dd Number of pegs per guess (more is harder).
\dt \e{Guesses}
\dd Number of guesses you have to find the solution in (fewer is harder).
\dt \e{Allow blanks}
\dd Allows blank pegs to be given as part of a guess (makes it easier, because
you know that those will never be counted as part of the solution). This
is turned off by default.
Note that this doesn't allow blank pegs in the solution; if you really wanted
that, use one extra colour.
\dt \e{Allow duplicates}
\dd Allows the solution (and the guesses) to contain colours more than once;
this increases the search space (making things harder), and is turned on by
default.
\A{licence} \I{MIT licence}\ii{Licence} \A{licence} \I{MIT licence}\ii{Licence}
This software is \i{copyright} 2004-2005 Simon Tatham. This software is \i{copyright} 2004-2005 Simon Tatham.