+ me->dir = +1;
+ return 1;
+ } else
+ return 0;
+}
+
+static void midend_finish_move(midend *me)
+{
+ float flashtime;
+
+ /*
+ * We do not flash if the later of the two states is special.
+ * This covers both forward Solve moves and backward (undone)
+ * Restart moves.
+ */
+ if ((me->oldstate || me->statepos > 1) &&
+ ((me->dir > 0 && !special(me->states[me->statepos-1].movetype)) ||
+ (me->dir < 0 && me->statepos < me->nstates &&
+ !special(me->states[me->statepos].movetype)))) {
+ flashtime = me->ourgame->flash_length(me->oldstate ? me->oldstate :
+ me->states[me->statepos-2].state,
+ me->states[me->statepos-1].state,
+ me->oldstate ? me->dir : +1,
+ me->ui);
+ if (flashtime > 0) {
+ me->flash_pos = 0.0F;
+ me->flash_time = flashtime;
+ }
+ }
+
+ if (me->oldstate)
+ me->ourgame->free_game(me->oldstate);
+ me->oldstate = NULL;
+ me->anim_pos = me->anim_time = 0;
+ me->dir = 0;
+
+ midend_set_timer(me);
+}
+
+void midend_stop_anim(midend *me)
+{
+ if (me->oldstate || me->anim_time != 0) {
+ midend_finish_move(me);
+ midend_redraw(me);
+ }
+}
+
+void midend_restart_game(midend *me)
+{
+ game_state *s;
+
+ midend_stop_anim(me);
+
+ assert(me->statepos >= 1);
+ if (me->statepos == 1)
+ return; /* no point doing anything at all! */
+
+ /*
+ * During restart, we reconstruct the game from the (public)
+ * game description rather than from states[0], because that
+ * way Mines gets slightly more sensible behaviour (restart
+ * goes to _after_ the first click so you don't have to
+ * remember where you clicked).
+ */
+ s = me->ourgame->new_game(me, me->params, me->desc);
+
+ /*
+ * Now enter the restarted state as the next move.
+ */
+ midend_stop_anim(me);
+ midend_purge_states(me);
+ ensure(me);
+ me->states[me->nstates].state = s;
+ me->states[me->nstates].movestr = dupstr(me->desc);
+ me->states[me->nstates].movetype = RESTART;
+ me->statepos = ++me->nstates;
+ if (me->ui)
+ me->ourgame->changed_state(me->ui,
+ me->states[me->statepos-2].state,
+ me->states[me->statepos-1].state);
+ me->anim_time = 0.0;
+ midend_finish_move(me);
+ midend_redraw(me);
+ midend_set_timer(me);
+}
+
+static int midend_really_process_key(midend *me, int x, int y, int button)
+{
+ game_state *oldstate =
+ me->ourgame->dup_game(me->states[me->statepos - 1].state);
+ int type = MOVE, gottype = FALSE, ret = 1;
+ float anim_time;
+ game_state *s;
+ char *movestr;
+
+ movestr =
+ me->ourgame->interpret_move(me->states[me->statepos-1].state,
+ me->ui, me->drawstate, x, y, button);
+
+ if (!movestr) {
+ if (button == 'n' || button == 'N' || button == '\x0E') {
+ midend_stop_anim(me);
+ midend_new_game(me);
+ midend_redraw(me);
+ goto done; /* never animate */
+ } else if (button == 'u' || button == 'u' ||
+ button == '\x1A' || button == '\x1F') {
+ midend_stop_anim(me);
+ type = me->states[me->statepos-1].movetype;
+ gottype = TRUE;
+ if (!midend_undo(me))
+ goto done;
+ } else if (button == 'r' || button == 'R' ||
+ button == '\x12' || button == '\x19') {
+ midend_stop_anim(me);
+ if (!midend_redo(me))
+ goto done;
+ } else if (button == '\x13' && me->ourgame->can_solve) {
+ if (midend_solve(me))
+ goto done;
+ } else if (button == 'q' || button == 'Q' || button == '\x11') {
+ ret = 0;
+ goto done;
+ } else
+ goto done;
+ } else {
+ if (!*movestr)
+ s = me->states[me->statepos-1].state;
+ else {
+ s = me->ourgame->execute_move(me->states[me->statepos-1].state,
+ movestr);
+ assert(s != NULL);
+ }
+
+ if (s == me->states[me->statepos-1].state) {
+ /*
+ * make_move() is allowed to return its input state to
+ * indicate that although no move has been made, the UI
+ * state has been updated and a redraw is called for.
+ */
+ midend_redraw(me);
+ midend_set_timer(me);
+ goto done;
+ } else if (s) {
+ midend_stop_anim(me);
+ midend_purge_states(me);
+ ensure(me);
+ assert(movestr != NULL);
+ me->states[me->nstates].state = s;
+ me->states[me->nstates].movestr = movestr;
+ me->states[me->nstates].movetype = MOVE;
+ me->statepos = ++me->nstates;
+ me->dir = +1;
+ if (me->ui)
+ me->ourgame->changed_state(me->ui,
+ me->states[me->statepos-2].state,
+ me->states[me->statepos-1].state);
+ } else {
+ goto done;
+ }
+ }
+
+ if (!gottype)
+ type = me->states[me->statepos-1].movetype;
+
+ /*
+ * See if this move requires an animation.
+ */
+ if (special(type) && !(type == SOLVE &&
+ (me->ourgame->flags & SOLVE_ANIMATES))) {
+ anim_time = 0;
+ } else {
+ anim_time = me->ourgame->anim_length(oldstate,
+ me->states[me->statepos-1].state,
+ me->dir, me->ui);
+ }
+
+ me->oldstate = oldstate; oldstate = NULL;
+ if (anim_time > 0) {
+ me->anim_time = anim_time;
+ } else {
+ me->anim_time = 0.0;
+ midend_finish_move(me);
+ }
+ me->anim_pos = 0.0;
+
+ midend_redraw(me);
+
+ midend_set_timer(me);
+
+ done:
+ if (oldstate) me->ourgame->free_game(oldstate);
+ return ret;
+}
+
+int midend_process_key(midend *me, int x, int y, int button)
+{
+ int ret = 1;
+
+ /*
+ * Harmonise mouse drag and release messages.
+ *
+ * Some front ends might accidentally switch from sending, say,
+ * RIGHT_DRAG messages to sending LEFT_DRAG, half way through a
+ * drag. (This can happen on the Mac, for example, since
+ * RIGHT_DRAG is usually done using Command+drag, and if the
+ * user accidentally releases Command half way through the drag
+ * then there will be trouble.)
+ *
+ * It would be an O(number of front ends) annoyance to fix this
+ * in the front ends, but an O(number of back ends) annoyance
+ * to have each game capable of dealing with it. Therefore, we
+ * fix it _here_ in the common midend code so that it only has
+ * to be done once.
+ *
+ * The possible ways in which things can go screwy in the front
+ * end are:
+ *
+ * - in a system containing multiple physical buttons button
+ * presses can inadvertently overlap. We can see ABab (caps
+ * meaning button-down and lowercase meaning button-up) when
+ * the user had semantically intended AaBb.
+ *
+ * - in a system where one button is simulated by means of a
+ * modifier key and another button, buttons can mutate
+ * between press and release (possibly during drag). So we
+ * can see Ab instead of Aa.
+ *
+ * Definite requirements are:
+ *
+ * - button _presses_ must never be invented or destroyed. If
+ * the user presses two buttons in succession, the button
+ * presses must be transferred to the backend unchanged. So
+ * if we see AaBb , that's fine; if we see ABab (the button
+ * presses inadvertently overlapped) we must somehow
+ * `correct' it to AaBb.
+ *
+ * - every mouse action must end up looking like a press, zero
+ * or more drags, then a release. This allows back ends to
+ * make the _assumption_ that incoming mouse data will be
+ * sane in this regard, and not worry about the details.
+ *
+ * So my policy will be:
+ *
+ * - treat any button-up as a button-up for the currently
+ * pressed button, or ignore it if there is no currently
+ * pressed button.
+ *
+ * - treat any drag as a drag for the currently pressed
+ * button, or ignore it if there is no currently pressed
+ * button.
+ *
+ * - if we see a button-down while another button is currently
+ * pressed, invent a button-up for the first one and then
+ * pass the button-down through as before.
+ *
+ * 2005-05-31: An addendum to the above. Some games might want
+ * a `priority order' among buttons, such that if one button is
+ * pressed while another is down then a fixed one of the
+ * buttons takes priority no matter what order they're pressed
+ * in. Mines, in particular, wants to treat a left+right click
+ * like a left click for the benefit of users of other
+ * implementations. So the last of the above points is modified
+ * in the presence of an (optional) button priority order.
+ *
+ * A further addition: we translate certain keyboard presses to
+ * cursor key 'select' buttons, so that a) frontends don't have
+ * to translate these themselves (like they do for CURSOR_UP etc),
+ * and b) individual games don't have to hard-code button presses
+ * of '\n' etc for keyboard-based cursors. The choice of buttons
+ * here could eventually be controlled by a runtime configuration
+ * option.
+ */
+ if (IS_MOUSE_DRAG(button) || IS_MOUSE_RELEASE(button)) {
+ if (me->pressed_mouse_button) {
+ if (IS_MOUSE_DRAG(button)) {
+ button = me->pressed_mouse_button +
+ (LEFT_DRAG - LEFT_BUTTON);
+ } else {
+ button = me->pressed_mouse_button +
+ (LEFT_RELEASE - LEFT_BUTTON);
+ }
+ } else
+ return ret; /* ignore it */
+ } else if (IS_MOUSE_DOWN(button) && me->pressed_mouse_button) {
+ /*
+ * If the new button has lower priority than the old one,
+ * don't bother doing this.
+ */
+ if (me->ourgame->flags &
+ BUTTON_BEATS(me->pressed_mouse_button, button))
+ return ret; /* just ignore it */
+
+ /*
+ * Fabricate a button-up for the previously pressed button.
+ */
+ ret = ret && midend_really_process_key
+ (me, x, y, (me->pressed_mouse_button +
+ (LEFT_RELEASE - LEFT_BUTTON)));
+ }
+
+ /*
+ * Translate keyboard presses to cursor selection.
+ */
+ if (button == '\n' || button == '\r')
+ button = CURSOR_SELECT;
+ if (button == ' ')
+ button = CURSOR_SELECT2;
+
+ /*
+ * Normalise both backspace characters (8 and 127) to \b. Easier
+ * to do this once, here, than to require all front ends to
+ * carefully generate the same one - now each front end can
+ * generate whichever is easiest.
+ */
+ if (button == '\177')
+ button = '\b';
+
+ /*
+ * Now send on the event we originally received.
+ */
+ ret = ret && midend_really_process_key(me, x, y, button);
+
+ /*
+ * And update the currently pressed button.
+ */
+ if (IS_MOUSE_RELEASE(button))
+ me->pressed_mouse_button = 0;
+ else if (IS_MOUSE_DOWN(button))
+ me->pressed_mouse_button = button;
+
+ return ret;
+}
+
+void midend_redraw(midend *me)
+{
+ assert(me->drawing);
+
+ if (me->statepos > 0 && me->drawstate) {
+ start_draw(me->drawing);
+ if (me->oldstate && me->anim_time > 0 &&
+ me->anim_pos < me->anim_time) {
+ assert(me->dir != 0);
+ me->ourgame->redraw(me->drawing, me->drawstate, me->oldstate,
+ me->states[me->statepos-1].state, me->dir,
+ me->ui, me->anim_pos, me->flash_pos);
+ } else {
+ me->ourgame->redraw(me->drawing, me->drawstate, NULL,
+ me->states[me->statepos-1].state, +1 /*shrug*/,
+ me->ui, 0.0, me->flash_pos);
+ }
+ end_draw(me->drawing);
+ }
+}
+
+/*
+ * Nasty hacky function used to implement the --redo option in
+ * gtk.c. Only used for generating the puzzles' icons.
+ */
+void midend_freeze_timer(midend *me, float tprop)
+{
+ me->anim_pos = me->anim_time * tprop;
+ midend_redraw(me);
+ deactivate_timer(me->frontend);
+}
+
+void midend_timer(midend *me, float tplus)
+{
+ int need_redraw = (me->anim_time > 0 || me->flash_time > 0);
+
+ me->anim_pos += tplus;
+ if (me->anim_pos >= me->anim_time ||
+ me->anim_time == 0 || !me->oldstate) {
+ if (me->anim_time > 0)
+ midend_finish_move(me);
+ }
+
+ me->flash_pos += tplus;
+ if (me->flash_pos >= me->flash_time || me->flash_time == 0) {
+ me->flash_pos = me->flash_time = 0;
+ }
+
+ if (need_redraw)
+ midend_redraw(me);
+
+ if (me->timing) {
+ float oldelapsed = me->elapsed;
+ me->elapsed += tplus;
+ if ((int)oldelapsed != (int)me->elapsed)
+ status_bar(me->drawing, me->laststatus ? me->laststatus : "");
+ }
+
+ midend_set_timer(me);
+}
+
+float *midend_colours(midend *me, int *ncolours)
+{
+ float *ret;
+
+ ret = me->ourgame->colours(me->frontend, ncolours);
+
+ {
+ int i;
+
+ /*
+ * Allow environment-based overrides for the standard
+ * colours by defining variables along the lines of
+ * `NET_COLOUR_4=6000c0'.
+ */
+
+ for (i = 0; i < *ncolours; i++) {
+ char buf[80], *e;
+ unsigned int r, g, b;
+ int j, k;
+
+ sprintf(buf, "%s_COLOUR_%d", me->ourgame->name, i);
+ for (j = k = 0; buf[j]; j++)
+ if (!isspace((unsigned char)buf[j]))
+ buf[k++] = toupper((unsigned char)buf[j]);
+ buf[k] = '\0';
+ if ((e = getenv(buf)) != NULL &&
+ sscanf(e, "%2x%2x%2x", &r, &g, &b) == 3) {
+ ret[i*3 + 0] = r / 255.0F;
+ ret[i*3 + 1] = g / 255.0F;
+ ret[i*3 + 2] = b / 255.0F;
+ }
+ }
+ }
+
+ return ret;
+}
+
+int midend_num_presets(midend *me)
+{
+ if (!me->npresets) {
+ char *name;
+ game_params *preset;
+
+ while (me->ourgame->fetch_preset(me->npresets, &name, &preset)) {
+ if (me->presetsize <= me->npresets) {
+ me->presetsize = me->npresets + 10;
+ me->presets = sresize(me->presets, me->presetsize,
+ game_params *);
+ me->preset_names = sresize(me->preset_names, me->presetsize,
+ char *);
+ me->preset_encodings = sresize(me->preset_encodings,
+ me->presetsize, char *);
+ }
+
+ me->presets[me->npresets] = preset;
+ me->preset_names[me->npresets] = name;
+ me->preset_encodings[me->npresets] =
+ me->ourgame->encode_params(preset, TRUE);;
+ me->npresets++;
+ }
+ }
+
+ {
+ /*
+ * Allow environment-based extensions to the preset list by
+ * defining a variable along the lines of `SOLO_PRESETS=2x3
+ * Advanced:2x3da'. Colon-separated list of items,
+ * alternating between textual titles in the menu and
+ * encoded parameter strings.
+ */
+ char buf[80], *e, *p;
+ int j, k;
+
+ sprintf(buf, "%s_PRESETS", me->ourgame->name);
+ for (j = k = 0; buf[j]; j++)
+ if (!isspace((unsigned char)buf[j]))
+ buf[k++] = toupper((unsigned char)buf[j]);
+ buf[k] = '\0';
+
+ if ((e = getenv(buf)) != NULL) {
+ p = e = dupstr(e);
+
+ while (*p) {
+ char *name, *val;
+ game_params *preset;
+
+ name = p;
+ while (*p && *p != ':') p++;
+ if (*p) *p++ = '\0';
+ val = p;
+ while (*p && *p != ':') p++;
+ if (*p) *p++ = '\0';
+
+ preset = me->ourgame->default_params();
+ me->ourgame->decode_params(preset, val);
+
+ if (me->ourgame->validate_params(preset, TRUE)) {
+ /* Drop this one from the list. */
+ me->ourgame->free_params(preset);
+ continue;
+ }
+
+ if (me->presetsize <= me->npresets) {
+ me->presetsize = me->npresets + 10;
+ me->presets = sresize(me->presets, me->presetsize,
+ game_params *);
+ me->preset_names = sresize(me->preset_names,
+ me->presetsize, char *);
+ me->preset_encodings = sresize(me->preset_encodings,
+ me->presetsize, char *);
+ }
+
+ me->presets[me->npresets] = preset;
+ me->preset_names[me->npresets] = dupstr(name);
+ me->preset_encodings[me->npresets] =
+ me->ourgame->encode_params(preset, TRUE);
+ me->npresets++;
+ }
+ sfree(e);
+ }
+ }
+
+ return me->npresets;
+}
+
+void midend_fetch_preset(midend *me, int n,
+ char **name, game_params **params)
+{
+ assert(n >= 0 && n < me->npresets);
+ *name = me->preset_names[n];
+ *params = me->presets[n];
+}
+
+int midend_which_preset(midend *me)
+{
+ char *encoding = me->ourgame->encode_params(me->params, TRUE);
+ int i, ret;
+
+ ret = -1;
+ for (i = 0; i < me->npresets; i++)
+ if (!strcmp(encoding, me->preset_encodings[i])) {
+ ret = i;
+ break;
+ }
+
+ sfree(encoding);
+ return ret;