/*
* TODO:
*
- * - Keyboard-control cursor. (Would probably have to address both
- * square centres, for laying multiple edges at a time in a
- * drag-like style, and grid edges for marking particular line
- * segments as no-go.)
+ * - The current keyboard cursor mechanism works well on ordinary PC
+ * keyboards, but for platforms with only arrow keys and a select
+ * button or two, we may at some point need a simpler one which can
+ * handle 'x' markings without needing shift keys. For instance, a
+ * cursor with twice the grid resolution, so that it can range
+ * across face centres, edge centres and vertices; 'clicks' on face
+ * centres begin a drag as currently, clicks on edges toggle
+ * markings, and clicks on vertices are ignored (but it would be
+ * too confusing not to let the cursor rest on them). But I'm
+ * pretty sure that would be less pleasant to play on a full
+ * keyboard, so probably a #ifdef would be the thing.
*
* - Generation is still pretty slow, due to difficulty coming up in
* the first place with a loop that makes a soluble puzzle even
enum {
COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT,
+ COL_CURSOR_BACKGROUND = COL_LOWLIGHT,
COL_BLACK, COL_WHITE,
COL_ERROR, COL_GRID, COL_FLASH,
COL_DRAGON, COL_DRAGOFF,
char *errors; /* size w*h: errors detected */
char *marks; /* size w*h: 'no line here' marks placed. */
int completed, used_solve;
- int loop_length; /* filled in by check_completion when complete. */
};
#define DEFAULT_PRESET 3
sfree(params);
}
-static game_params *dup_params(game_params *params)
+static game_params *dup_params(const game_params *params)
{
game_params *ret = snew(game_params);
*ret = *params; /* structure copy */
}
}
-static char *encode_params(game_params *params, int full)
+static char *encode_params(const game_params *params, int full)
{
char buf[256];
sprintf(buf, "%dx%d", params->w, params->h);
return dupstr(buf);
}
-static config_item *game_configure(game_params *params)
+static config_item *game_configure(const game_params *params)
{
config_item *ret;
char buf[64];
return ret;
}
-static game_params *custom_params(config_item *cfg)
+static game_params *custom_params(const config_item *cfg)
{
game_params *ret = snew(game_params);
return ret;
}
-static char *validate_params(game_params *params, int full)
+static char *validate_params(const game_params *params, int full)
{
if (params->w < 5) return "Width must be at least five";
if (params->h < 5) return "Height must be at least five";
#endif
}
-static int new_clues(game_params *params, random_state *rs,
+static int new_clues(const game_params *params, random_state *rs,
char *clues, char *grid)
{
- int w = params->w, h = params->h;
+ int w = params->w, h = params->h, diff = params->difficulty;
int ngen = 0, x, y, d, ret, i;
+
+ /*
+ * Difficulty exception: 5x5 Tricky is not generable (the
+ * generator will spin forever trying) and so we fudge it to Easy.
+ */
+ if (w == 5 && h == 5 && diff > DIFF_EASY)
+ diff = DIFF_EASY;
+
while (1) {
ngen++;
pearl_loopgen(w, h, grid, rs);
/*
* See if we can solve the puzzle just like this.
*/
- ret = pearl_solve(w, h, clues, grid, params->difficulty, FALSE);
+ ret = pearl_solve(w, h, clues, grid, diff, FALSE);
assert(ret > 0); /* shouldn't be inconsistent! */
if (ret != 1)
continue; /* go round and try again */
/*
* Check this puzzle isn't too easy.
*/
- if (params->difficulty > DIFF_EASY) {
- ret = pearl_solve(w, h, clues, grid, params->difficulty-1, FALSE);
+ if (diff > DIFF_EASY) {
+ ret = pearl_solve(w, h, clues, grid, diff-1, FALSE);
assert(ret > 0);
if (ret == 1)
continue; /* too easy: try again */
clue = clues[y*w+x];
clues[y*w+x] = 0; /* try removing this clue */
- ret = pearl_solve(w, h, clues, grid, params->difficulty, FALSE);
+ ret = pearl_solve(w, h, clues, grid, diff, FALSE);
assert(ret > 0);
if (ret != 1)
clues[y*w+x] = clue; /* oops, put it back again */
break; /* got it */
}
+ debug(("%d %dx%d loops before finished puzzle.\n", ngen, w, h));
+
return ngen;
}
-static char *new_game_desc(game_params *params, random_state *rs,
+static char *new_game_desc(const game_params *params, random_state *rs,
char **aux, int interactive)
{
char *grid, *clues;
char *desc;
- int ngen, w = params->w, h = params->h, i, j;
+ int w = params->w, h = params->h, i, j;
grid = snewn(w*h, char);
clues = snewn(w*h, char);
- ngen = new_clues(params, rs, clues, grid);
-
- debug(("%d %dx%d loops before finished puzzle.\n", ngen, w, h));
+ new_clues(params, rs, clues, grid);
desc = snewn(w * h + 1, char);
for (i = j = 0; i < w*h; i++) {
return desc;
}
-static char *validate_desc(game_params *params, char *desc)
+static char *validate_desc(const game_params *params, const char *desc)
{
int i, sizesofar;
const int totalsize = params->w * params->h;
return NULL;
}
-static game_state *new_game(midend *me, game_params *params, char *desc)
+static game_state *new_game(midend *me, const game_params *params,
+ const char *desc)
{
game_state *state = snew(game_state);
int i, j, sz = params->w*params->h;
return state;
}
-static game_state *dup_game(game_state *state)
+static game_state *dup_game(const game_state *state)
{
game_state *ret = snew(game_state);
int sz = state->shared->sz, i;
#define ERROR_CLUE 16
-static void dsf_update_completion(game_state *state, int *loopclass,
- int ax, int ay, char dir,
- int *dsf, int *dsfsize)
+static void dsf_update_completion(game_state *state, int ax, int ay, char dir,
+ int *dsf)
{
int w = state->shared->w /*, h = state->shared->h */;
- int ac = ay*w+ax, ae, bx, by, bc, be;
+ int ac = ay*w+ax, bx, by, bc;
if (!(state->lines[ac] & dir)) return; /* no link */
bx = ax + DX(dir); by = ay + DY(dir);
assert(INGRID(state, bx, by)); /* should not have a link off grid */
bc = by*w+bx;
-#if 0
assert(state->lines[bc] & F(dir)); /* should have reciprocal link */
-#endif
- /* TODO put above assertion back in once we stop generating partially
- * soluble puzzles. */
if (!(state->lines[bc] & F(dir))) return;
- ae = dsf_canonify(dsf, ac);
- be = dsf_canonify(dsf, bc);
-
- if (ae == be) { /* detected a loop! */
- if (*loopclass != -1) /* this is the second loop, doom. */
- return;
- *loopclass = ae;
- } else {
- int size = dsfsize[ae] + dsfsize[be];
- dsf_merge(dsf, ac, bc);
- ae = dsf_canonify(dsf, ac);
- dsfsize[ae] = size;
- }
- return;
+ dsf_merge(dsf, ac, bc);
}
static void check_completion(game_state *state, int mark)
{
int w = state->shared->w, h = state->shared->h, x, y, i, d;
- int had_error = FALSE /*, is_complete = FALSE */, loopclass;
- int *dsf, *dsfsize;
+ int had_error = FALSE;
+ int *dsf, *component_state;
+ int nsilly, nloop, npath, largest_comp, largest_size, total_pathsize;
+ enum { COMP_NONE, COMP_LOOP, COMP_PATH, COMP_SILLY, COMP_EMPTY };
if (mark) {
for (i = 0; i < w*h; i++) {
#define ERROR(x,y,e) do { had_error = TRUE; if (mark) state->errors[(y)*w+(x)] |= (e); } while(0)
/*
- * First of all: we should have one single closed loop, passing through all clues.
+ * Analyse the solution into loops, paths and stranger things.
+ * Basic strategy here is all the same as in Loopy - see the big
+ * comment in loopy.c's check_completion() - and for exactly the
+ * same reasons, since Loopy and Pearl have basically the same
+ * form of expected solution.
*/
- dsf = snewn(w*h, int);
- dsfsize = snewn(w*h, int);
- dsf_init(dsf, w*h);
- for (i = 0; i < w*h; i++) dsfsize[i] = 1;
- loopclass = -1;
+ dsf = snew_dsf(w*h);
+ /* Build the dsf. */
for (x = 0; x < w; x++) {
for (y = 0; y < h; y++) {
- dsf_update_completion(state, &loopclass, x, y, R, dsf, dsfsize);
- dsf_update_completion(state, &loopclass, x, y, D, dsf, dsfsize);
+ dsf_update_completion(state, x, y, R, dsf);
+ dsf_update_completion(state, x, y, D, dsf);
}
}
- if (loopclass != -1) {
- /* We have a loop. Check all squares with lines on. */
- for (x = 0; x < w; x++) {
- for (y = 0; y < h; y++) {
- if (state->lines[y*w+x] == BLANK) {
- if (state->shared->clues[y*w+x] != NOCLUE) {
- /* the loop doesn't include this clue square! */
- ERROR(x, y, ERROR_CLUE);
- }
- } else {
- if (dsf_canonify(dsf, y*w+x) != loopclass) {
- /* these lines are not on the loop: mark them as error. */
- ERROR(x, y, state->lines[y*w+x]);
- }
- }
- }
- }
+
+ /* Initialise a state variable for each connected component. */
+ component_state = snewn(w*h, int);
+ for (i = 0; i < w*h; i++) {
+ if (dsf_canonify(dsf, i) == i)
+ component_state[i] = COMP_LOOP;
+ else
+ component_state[i] = COMP_NONE;
}
/*
- * Second: check no clues are contradicted.
+ * Classify components, and mark errors where a square has more
+ * than two line segments.
*/
-
for (x = 0; x < w; x++) {
for (y = 0; y < h; y++) {
int type = state->lines[y*w+x];
- /*
- * Check that no square has more than two line segments.
- */
- if (NBITS(type) > 2) {
+ int degree = NBITS(type);
+ int comp = dsf_canonify(dsf, y*w+x);
+ if (degree > 2) {
ERROR(x,y,type);
+ component_state[comp] = COMP_SILLY;
+ } else if (degree == 0) {
+ component_state[comp] = COMP_EMPTY;
+ } else if (degree == 1) {
+ if (component_state[comp] != COMP_SILLY)
+ component_state[comp] = COMP_PATH;
}
- /*
- * Check that no clues are contradicted. This code is similar to
- * the code that sets up the maximal clue array for any given
- * loop.
- */
+ }
+ }
+
+ /* Count the components, and find the largest sensible one. */
+ nsilly = nloop = npath = 0;
+ total_pathsize = 0;
+ largest_comp = largest_size = -1;
+ for (i = 0; i < w*h; i++) {
+ if (component_state[i] == COMP_SILLY) {
+ nsilly++;
+ } else if (component_state[i] == COMP_PATH) {
+ total_pathsize += dsf_size(dsf, i);
+ npath = 1;
+ } else if (component_state[i] == COMP_LOOP) {
+ int this_size;
+
+ nloop++;
+
+ if ((this_size = dsf_size(dsf, i)) > largest_size) {
+ largest_comp = i;
+ largest_size = this_size;
+ }
+ }
+ }
+ if (largest_size < total_pathsize) {
+ largest_comp = -1; /* means the paths */
+ largest_size = total_pathsize;
+ }
+
+ if (nloop > 0 && nloop + npath > 1) {
+ /*
+ * If there are at least two sensible components including at
+ * least one loop, highlight every sensible component that is
+ * not the largest one.
+ */
+ for (i = 0; i < w*h; i++) {
+ int comp = dsf_canonify(dsf, i);
+ if ((component_state[comp] == COMP_PATH &&
+ -1 != largest_comp) ||
+ (component_state[comp] == COMP_LOOP &&
+ comp != largest_comp))
+ ERROR(i%w, i/w, state->lines[i]);
+ }
+ }
+
+ /* Now we've finished with the dsf and component states. The only
+ * thing we'll need to remember later on is whether all edges were
+ * part of a single loop, for which our counter variables
+ * nsilly,nloop,npath are enough. */
+ sfree(component_state);
+ sfree(dsf);
+
+ /*
+ * Check that no clues are contradicted. This code is similar to
+ * the code that sets up the maximal clue array for any given
+ * loop.
+ */
+ for (x = 0; x < w; x++) {
+ for (y = 0; y < h; y++) {
+ int type = state->lines[y*w+x];
if (state->shared->clues[y*w+x] == CORNER) {
/* Supposed to be a corner: will find a contradiction if
* it actually contains a straight line, or if it touches any
}
}
}
- if (!had_error && loopclass != -1) {
- state->completed = TRUE;
- state->loop_length = dsfsize[loopclass];
- } else {
- state->completed = FALSE;
- }
- sfree(dsf);
- sfree(dsfsize);
+ if (nloop == 1 && nsilly == 0 && npath == 0) {
+ /*
+ * If there's exactly one loop (so that the puzzle is at least
+ * potentially complete), we need to ensure it hasn't left any
+ * clue out completely.
+ */
+ for (x = 0; x < w; x++) {
+ for (y = 0; y < h; y++) {
+ if (state->lines[y*w+x] == BLANK) {
+ if (state->shared->clues[y*w+x] != NOCLUE) {
+ /* the loop doesn't include this clue square! */
+ ERROR(x, y, ERROR_CLUE);
+ }
+ }
+ }
+ }
- return;
+ /*
+ * But if not, then we're done!
+ */
+ if (!had_error)
+ state->completed = TRUE;
+ }
}
/* completion check:
* - no clues must be contradicted (highlight clue itself in error if so)
* - if there is a closed loop it must include every line segment laid
* - if there's a smaller closed loop then highlight whole loop as error
- * - no square must have more than 3 lines radiating from centre point
+ * - no square must have more than 2 lines radiating from centre point
* (highlight all lines in that square as error if so)
*/
return move;
}
-static char *solve_game(game_state *state, game_state *currstate,
- char *aux, char **error)
+static char *solve_game(const game_state *state, const game_state *currstate,
+ const char *aux, char **error)
{
game_state *solved = dup_game(state);
int i, ret, sz = state->shared->sz;
return move;
}
-static int game_can_format_as_text_now(game_params *params)
+static int game_can_format_as_text_now(const game_params *params)
{
- return FALSE;
+ return TRUE;
}
-static char *game_text_format(game_state *state)
+static char *game_text_format(const game_state *state)
{
- return NULL;
+ int w = state->shared->w, h = state->shared->h, cw = 4, ch = 2;
+ int gw = cw*(w-1) + 2, gh = ch*(h-1) + 1, len = gw * gh, r, c, j;
+ char *board = snewn(len + 1, char);
+
+ assert(board);
+ memset(board, ' ', len);
+
+ for (r = 0; r < h; ++r) {
+ for (c = 0; c < w; ++c) {
+ int i = r*w + c, cell = r*ch*gw + c*cw;
+ board[cell] = "+BW"[(unsigned char)state->shared->clues[i]];
+ if (c < w - 1 && (state->lines[i] & R || state->lines[i+1] & L))
+ memset(board + cell + 1, '-', cw - 1);
+ if (r < h - 1 && (state->lines[i] & D || state->lines[i+w] & U))
+ for (j = 1; j < ch; ++j) board[cell + j*gw] = '|';
+ if (c < w - 1 && (state->marks[i] & R || state->marks[i+1] & L))
+ board[cell + cw/2] = 'x';
+ if (r < h - 1 && (state->marks[i] & D || state->marks[i+w] & U))
+ board[cell + (ch/2 * gw)] = 'x';
+ }
+
+ for (j = 0; j < (r == h - 1 ? 1 : ch); ++j)
+ board[r*ch*gw + (gw - 1) + j*gw] = '\n';
+ }
+
+ board[len] = '\0';
+ return board;
}
struct game_ui {
int *dragcoords; /* list of (y*w+x) coords in drag so far */
- int ndragcoords; /* number of entries in dragcoords. 0 = no drag. */
+ int ndragcoords; /* number of entries in dragcoords.
+ * 0 = click but no drag yet. -1 = no drag at all */
int clickx, clicky; /* pixel position of initial click */
+
+ int curx, cury; /* grid position of keyboard cursor */
+ int cursor_active; /* TRUE iff cursor is shown */
};
-static game_ui *new_ui(game_state *state)
+static game_ui *new_ui(const game_state *state)
{
game_ui *ui = snew(game_ui);
int sz = state->shared->sz;
- ui->ndragcoords = 0;
+ ui->ndragcoords = -1;
ui->dragcoords = snewn(sz, int);
+ ui->cursor_active = FALSE;
+ ui->curx = ui->cury = 0;
return ui;
}
sfree(ui);
}
-static char *encode_ui(game_ui *ui)
+static char *encode_ui(const game_ui *ui)
{
return NULL;
}
-static void decode_ui(game_ui *ui, char *encoding)
+static void decode_ui(game_ui *ui, const char *encoding)
{
}
-static void game_changed_state(game_ui *ui, game_state *oldstate,
- game_state *newstate)
+static void game_changed_state(game_ui *ui, const game_state *oldstate,
+ const game_state *newstate)
{
}
#define BORDER_WIDTH (max(TILE_SIZE / 32, 1))
#define COORD(x) ( (x) * TILE_SIZE + BORDER )
+#define CENTERED_COORD(x) ( COORD(x) + TILE_SIZE/2 )
#define FROMCOORD(x) ( ((x) < BORDER) ? -1 : ( ((x) - BORDER) / TILE_SIZE) )
#define DS_ESHIFT 4 /* R/U/L/D shift, for error flags */
#define DS_ERROR_CLUE (1 << 20)
#define DS_FLASH (1 << 21)
+#define DS_CURSOR (1 << 22)
enum { GUI_MASYU, GUI_LOOPY };
char *draglines; /* size w*h; lines flipped by current drag */
};
-static void update_ui_drag(game_state *state, game_ui *ui, int gx, int gy)
+static void update_ui_drag(const game_state *state, game_ui *ui,
+ int gx, int gy)
{
int /* sz = state->shared->sz, */ w = state->shared->w;
int i, ox, oy, pos;
if (!INGRID(state, gx, gy))
return; /* square is outside grid */
+ if (ui->ndragcoords < 0)
+ return; /* drag not in progress anyway */
+
pos = gy * w + gx;
lastpos = ui->dragcoords[ui->ndragcoords > 0 ? ui->ndragcoords-1 : 0];
if (ox == gx || oy == gy) {
int dx = (gx < ox ? -1 : gx > ox ? +1 : 0);
int dy = (gy < oy ? -1 : gy > oy ? +1 : 0);
+ int dir = (dy>0 ? D : dy<0 ? U : dx>0 ? R : L);
while (ox != gx || oy != gy) {
+ /*
+ * If the drag attempts to cross a 'no line here' mark,
+ * stop there. We physically don't allow the user to drag
+ * over those marks.
+ */
+ if (state->marks[oy*w+ox] & dir)
+ break;
ox += dx;
oy += dy;
ui->dragcoords[ui->ndragcoords++] = oy * w + ox;
* to state newstate, each of which equals either 0 or dir]
* }
*/
-static void interpret_ui_drag(game_state *state, game_ui *ui, int *clearing,
- int i, int *sx, int *sy, int *dx, int *dy,
- int *dir, int *oldstate, int *newstate)
+static void interpret_ui_drag(const game_state *state, const game_ui *ui,
+ int *clearing, int i, int *sx, int *sy,
+ int *dx, int *dy, int *dir,
+ int *oldstate, int *newstate)
{
int w = state->shared->w;
int sp = ui->dragcoords[i], dp = ui->dragcoords[i+1];
}
}
-static char *interpret_move(game_state *state, game_ui *ui, game_drawstate *ds,
- int x, int y, int button)
+static char *mark_in_direction(const game_state *state, int x, int y, int dir,
+ int primary, char *buf)
{
int w = state->shared->w /*, h = state->shared->h, sz = state->shared->sz */;
+ int x2 = x + DX(dir);
+ int y2 = y + DY(dir);
+ int dir2 = F(dir);
+
+ char ch = primary ? 'F' : 'M', *other;
+
+ if (!INGRID(state, x, y) || !INGRID(state, x2, y2)) return "";
+
+ /* disallow laying a mark over a line, or vice versa. */
+ other = primary ? state->marks : state->lines;
+ if (other[y*w+x] & dir || other[y2*w+x2] & dir2) return "";
+
+ sprintf(buf, "%c%d,%d,%d;%c%d,%d,%d", ch, dir, x, y, ch, dir2, x2, y2);
+ return dupstr(buf);
+}
+
+#define KEY_DIRECTION(btn) (\
+ (btn) == CURSOR_DOWN ? D : (btn) == CURSOR_UP ? U :\
+ (btn) == CURSOR_LEFT ? L : R)
+
+static char *interpret_move(const game_state *state, game_ui *ui,
+ const game_drawstate *ds,
+ int x, int y, int button)
+{
+ int w = state->shared->w, h = state->shared->h /*, sz = state->shared->sz */;
int gx = FROMCOORD(x), gy = FROMCOORD(y), i;
+ int release = FALSE;
char tmpbuf[80];
+ int shift = button & MOD_SHFT, control = button & MOD_CTRL;
+ button &= ~MOD_MASK;
+
if (IS_MOUSE_DOWN(button)) {
- if (!INGRID(state, gx, gy)) return NULL;
+ ui->cursor_active = FALSE;
+
+ if (!INGRID(state, gx, gy)) {
+ ui->ndragcoords = -1;
+ return NULL;
+ }
ui->clickx = x; ui->clicky = y;
ui->dragcoords[0] = gy * w + gx;
return "";
}
- if (button == LEFT_DRAG) {
+ if (button == LEFT_DRAG && ui->ndragcoords >= 0) {
update_ui_drag(state, ui, gx, gy);
return "";
}
- if (IS_MOUSE_RELEASE(button)) {
- if (ui->ndragcoords) {
+ if (IS_MOUSE_RELEASE(button)) release = TRUE;
+
+ if (IS_CURSOR_MOVE(button)) {
+ if (!ui->cursor_active) {
+ ui->cursor_active = TRUE;
+ } else if (control | shift) {
+ char *move;
+ if (ui->ndragcoords > 0) return NULL;
+ ui->ndragcoords = -1;
+ move = mark_in_direction(state, ui->curx, ui->cury,
+ KEY_DIRECTION(button), control, tmpbuf);
+ if (control && !shift && *move)
+ move_cursor(button, &ui->curx, &ui->cury, w, h, FALSE);
+ return move;
+ } else {
+ move_cursor(button, &ui->curx, &ui->cury, w, h, FALSE);
+ if (ui->ndragcoords >= 0)
+ update_ui_drag(state, ui, ui->curx, ui->cury);
+ }
+ return "";
+ }
+
+ if (IS_CURSOR_SELECT(button)) {
+ if (!ui->cursor_active) {
+ ui->cursor_active = TRUE;
+ return "";
+ } else if (button == CURSOR_SELECT) {
+ if (ui->ndragcoords == -1) {
+ ui->ndragcoords = 0;
+ ui->dragcoords[0] = ui->cury * w + ui->curx;
+ ui->clickx = CENTERED_COORD(ui->curx);
+ ui->clicky = CENTERED_COORD(ui->cury);
+ return "";
+ } else release = TRUE;
+ } else if (button == CURSOR_SELECT2 && ui->ndragcoords >= 0) {
+ ui->ndragcoords = -1;
+ return "";
+ }
+ }
+
+ if (button == 27 || button == '\b') {
+ ui->ndragcoords = -1;
+ return "";
+ }
+
+ if (release) {
+ if (ui->ndragcoords > 0) {
/* End of a drag: process the cached line data. */
int buflen = 0, bufsize = 256, tmplen;
char *buf = NULL;
}
}
- ui->ndragcoords = 0;
+ ui->ndragcoords = -1;
return buf ? buf : "";
- } else {
+ } else if (ui->ndragcoords == 0) {
/* Click (or tiny drag). Work out which edge we were
* closest to. */
int cx, cy;
- int gx2, gy2, l1, l2, ismark = (button == RIGHT_RELEASE);
- char movec = ismark ? 'M' : 'F';
+
+ ui->ndragcoords = -1;
/*
* We process clicks based on the mouse-down location,
gx = FROMCOORD(x);
gy = FROMCOORD(y);
- cx = COORD(gx) + TILE_SIZE/2;
- cy = COORD(gy) + TILE_SIZE/2;
+ cx = CENTERED_COORD(gx);
+ cy = CENTERED_COORD(gy);
if (!INGRID(state, gx, gy)) return "";
return "";
} else {
+ int direction;
if (abs(x-cx) < abs(y-cy)) {
/* Closest to top/bottom edge. */
- l1 = (y < cy) ? U : D;
+ direction = (y < cy) ? U : D;
} else {
/* Closest to left/right edge. */
- l1 = (x < cx) ? L : R;
- }
- gx2 = gx + DX(l1); gy2 = gy + DY(l1);
- l2 = F(l1);
-
- if (!INGRID(state, gx, gy) || !INGRID(state, gx2, gy2)) return "";
-
- /* disallow laying a mark over a line, or vice versa. */
- if (ismark) {
- if ((state->lines[gy*w+gx] & l1) || (state->lines[gy2*w+gx2] & l2))
- return "";
- } else {
- if ((state->marks[gy*w+gx] & l1) || (state->marks[gy2*w+gx2] & l2))
- return "";
+ direction = (x < cx) ? L : R;
}
-
- sprintf(tmpbuf, "%c%d,%d,%d;%c%d,%d,%d",
- movec, l1, gx, gy, movec, l2, gx2, gy2);
- return dupstr(tmpbuf);
+ return mark_in_direction(state, gx, gy, direction,
+ (button == LEFT_RELEASE), tmpbuf);
}
}
}
if (button == 'H' || button == 'h')
return dupstr("H");
- /* TODO cursor */
-
return NULL;
}
-static game_state *execute_move(game_state *state, char *move)
+static game_state *execute_move(const game_state *state, const char *move)
{
int w = state->shared->w, h = state->shared->h;
char c;
if (!INGRID(state, x, y)) goto badmove;
if (l < 0 || l > 15) goto badmove;
- /* TODO trying to set a line over a no-line mark should be
- * a failed move? */
-
if (c == 'L')
ret->lines[y*w + x] |= (char)l;
else if (c == 'N')
else if (c == 'M')
ret->marks[y*w + x] ^= (char)l;
+ /*
+ * If we ended up trying to lay a line _over_ a mark,
+ * that's a failed move: interpret_move() should have
+ * ensured we never received a move string like that in
+ * the first place.
+ */
+ if ((ret->lines[y*w + x] & (char)l) &&
+ (ret->marks[y*w + x] & (char)l))
+ goto badmove;
+
move += n;
} else if (strcmp(move, "H") == 0) {
pearl_solve(ret->shared->w, ret->shared->h,
#define FLASH_TIME 0.5F
-static void game_compute_size(game_params *params, int tilesize,
- int *x, int *y)
+static void game_compute_size(const game_params *params, int tilesize,
+ int *x, int *y)
{
/* Ick: fake up `ds->tilesize' for macro expansion purposes */
struct { int halfsz; } ads, *ds = &ads;
}
static void game_set_size(drawing *dr, game_drawstate *ds,
- game_params *params, int tilesize)
+ const game_params *params, int tilesize)
{
ds->halfsz = (tilesize-1)/2;
}
return ret;
}
-static game_drawstate *game_new_drawstate(drawing *dr, game_state *state)
+static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
{
struct game_drawstate *ds = snew(struct game_drawstate);
int i;
}
}
-static void draw_square(drawing *dr, game_drawstate *ds, game_ui *ui,
+static void draw_square(drawing *dr, game_drawstate *ds, const game_ui *ui,
int x, int y, unsigned int lflags, char clue)
{
int ox = COORD(x), oy = COORD(y);
clip(dr, ox, oy, TILE_SIZE, TILE_SIZE);
/* Clear the square. */
- draw_rect(dr, ox, oy, TILE_SIZE, TILE_SIZE, COL_BACKGROUND);
+ draw_rect(dr, ox, oy, TILE_SIZE, TILE_SIZE,
+ (lflags & DS_CURSOR) ?
+ COL_CURSOR_BACKGROUND : COL_BACKGROUND);
+
if (get_gui_style() == GUI_LOOPY) {
/* Draw small dot, underneath any lines. */
/* Draw a clue, if present */
if (clue != NOCLUE) {
int c = (lflags & DS_FLASH) ? COL_FLASH :
- (clue == CORNER) ? COL_BLACK : COL_WHITE;
+ (clue == STRAIGHT) ? COL_WHITE : COL_BLACK;
if (lflags & DS_ERROR_CLUE) /* draw a bigger 'error' clue circle. */
draw_circle(dr, cx, cy, TILE_SIZE*3/8, COL_ERROR, COL_ERROR);
draw_update(dr, ox, oy, TILE_SIZE, TILE_SIZE);
}
-static void game_redraw(drawing *dr, game_drawstate *ds, game_state *oldstate,
- game_state *state, int dir, game_ui *ui,
- float animtime, float flashtime)
+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 w = state->shared->w, h = state->shared->h, sz = state->shared->sz;
int x, y, force = 0, flashing = 0;
flashing = DS_FLASH;
memset(ds->draglines, 0, sz);
- if (ui->dragcoords) {
+ if (ui->ndragcoords > 0) {
int i, clearing = TRUE;
for (i = 0; i < ui->ndragcoords - 1; i++) {
int sx, sy, dx, dy, dir, oldstate, newstate;
f |= flashing;
+ if (ui->cursor_active && x == ui->curx && y == ui->cury)
+ f |= DS_CURSOR;
+
if (f != ds->lflags[y*w+x] || force) {
ds->lflags[y*w+x] = f;
draw_square(dr, ds, ui, x, y, f, state->shared->clues[y*w+x]);
}
}
-static float game_anim_length(game_state *oldstate, game_state *newstate,
- int dir, game_ui *ui)
+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(game_state *oldstate, game_state *newstate,
- int dir, game_ui *ui)
+static float game_flash_length(const game_state *oldstate,
+ const game_state *newstate, int dir, game_ui *ui)
{
- if (!oldstate->completed &&
- newstate->completed && !newstate->used_solve)
+ if (!oldstate->completed && newstate->completed &&
+ !oldstate->used_solve && !newstate->used_solve)
return FLASH_TIME;
else
return 0.0F;
}
-static int game_status(game_state *state)
+static int game_status(const game_state *state)
{
return state->completed ? +1 : 0;
}
-static int game_timing_state(game_state *state, game_ui *ui)
+static int game_timing_state(const game_state *state, game_ui *ui)
{
return TRUE;
}
-static void game_print_size(game_params *params, float *x, float *y)
+static void game_print_size(const game_params *params, float *x, float *y)
{
int pw, ph;
*y = ph / 100.0F;
}
-static void game_print(drawing *dr, game_state *state, int tilesize)
+static void game_print(drawing *dr, const game_state *state, int tilesize)
{
int w = state->shared->w, h = state->shared->h, x, y;
int black = print_mono_colour(dr, 0);
const struct game thegame = {
"Pearl", "games.pearl", "pearl",
default_params,
- game_fetch_preset,
+ game_fetch_preset, NULL,
decode_params,
encode_params,
free_params,
dup_game,
free_game,
TRUE, solve_game,
- FALSE, game_can_format_as_text_now, game_text_format,
+ TRUE, game_can_format_as_text_now, game_text_format,
new_ui,
free_ui,
encode_ui,