me->nstates = me->statesize = me->statepos = 0;
me->states = NULL;
me->params = ourgame->default_params();
+ /*
+ * Allow environment-based changing of the default settings by
+ * defining a variable along the lines of `NET_DEFAULT=25x25w'
+ * in which the value is an encoded parameter string.
+ */
+ {
+ char buf[80], *e;
+ int j, k;
+ sprintf(buf, "%s_DEFAULT", 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)
+ me->ourgame->decode_params(me->params, e);
+ }
me->curparams = NULL;
me->desc = me->privdesc = NULL;
me->seedstr = NULL;
return me;
}
+const game *midend_which_game(midend *me)
+{
+ return me->ourgame;
+}
+
+static void midend_purge_states(midend *me)
+{
+ while (me->nstates > me->statepos) {
+ me->ourgame->free_game(me->states[--me->nstates].state);
+ if (me->states[me->nstates].movestr)
+ sfree(me->states[me->nstates].movestr);
+ }
+}
+
static void midend_free_game(midend *me)
{
while (me->nstates > 0) {
*y = me->winheight;
}
+int midend_tilesize(midend *me) { return me->tilesize; }
+
void midend_set_params(midend *me, game_params *params)
{
me->ourgame->free_params(me->params);
char newseed[16];
int i;
newseed[15] = '\0';
- newseed[0] = '1' + random_upto(me->random, 9);
+ newseed[0] = '1' + (char)random_upto(me->random, 9);
for (i = 1; i < 15; i++)
- newseed[i] = '0' + random_upto(me->random, 10);
+ newseed[i] = '0' + (char)random_upto(me->random, 10);
sfree(me->seedstr);
me->seedstr = dupstr(newseed);
me->states[me->nstates].state =
me->ourgame->new_game(me, me->params, me->desc);
+ /*
+ * As part of our commitment to self-testing, test the aux
+ * string to make sure nothing ghastly went wrong.
+ */
+ if (me->ourgame->can_solve && me->aux_info) {
+ game_state *s;
+ char *msg, *movestr;
+
+ msg = NULL;
+ movestr = me->ourgame->solve(me->states[0].state,
+ me->states[0].state,
+ me->aux_info, &msg);
+ assert(movestr && !msg);
+ s = me->ourgame->execute_move(me->states[0].state, movestr);
+ assert(s);
+ me->ourgame->free_game(s);
+ sfree(movestr);
+ }
+
+ /*
+ * Soak test, enabled by setting <gamename>_TESTSOLVE in the
+ * environment. This causes an immediate attempt to re-solve the
+ * game without benefit of aux_info. The effect is that (at least
+ * on Unix) you can run 'FOO_TESTSOLVE=1 foo --generate 10000
+ * <params>#12345' and it will generate a lot of game ids and
+ * instantly pass each one back to the solver.
+ *
+ * (It's worth putting in an explicit seed in any such test, so
+ * you can repeat it to diagnose a problem if one comes up!)
+ */
+ {
+ char buf[80];
+ int j, k;
+ static int doing_test_solve = -1;
+ if (doing_test_solve < 0) {
+ sprintf(buf, "%s_TESTSOLVE", 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 (getenv(buf)) {
+ /*
+ * Since this is used for correctness testing, it's
+ * helpful to have a visual acknowledgment that the
+ * user hasn't mistyped the environment variable name.
+ */
+ fprintf(stderr, "Running solver soak tests\n");
+ doing_test_solve = TRUE;
+ } else {
+ doing_test_solve = FALSE;
+ }
+ }
+ if (doing_test_solve) {
+ game_state *s;
+ char *msg, *movestr;
+
+ msg = NULL;
+ movestr = me->ourgame->solve(me->states[0].state,
+ me->states[0].state,
+ NULL, &msg);
+ assert(movestr && !msg);
+ s = me->ourgame->execute_move(me->states[0].state, movestr);
+ assert(s);
+ me->ourgame->free_game(s);
+ sfree(movestr);
+ }
+ }
+
me->states[me->nstates].movestr = NULL;
me->states[me->nstates].movetype = NEWGAME;
me->nstates++;
me->pressed_mouse_button = 0;
}
+int midend_can_undo(midend *me)
+{
+ return (me->statepos > 1);
+}
+
+int midend_can_redo(midend *me)
+{
+ return (me->statepos < me->nstates);
+}
+
static int midend_undo(midend *me)
{
if (me->statepos > 1) {
* Now enter the restarted state as the next move.
*/
midend_stop_anim(me);
- while (me->nstates > me->statepos)
- me->ourgame->free_game(me->states[--me->nstates].state);
+ midend_purge_states(me);
ensure(me);
me->states[me->nstates].state = s;
me->states[me->nstates].movestr = dupstr(me->desc);
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;
goto done;
} else if (s) {
midend_stop_anim(me);
- while (me->nstates > me->statepos)
- me->ourgame->free_game(me->states[--me->nstates].state);
+ midend_purge_states(me);
ensure(me);
assert(movestr != NULL);
me->states[me->nstates].state = s;
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);
buf[k] = '\0';
if ((e = getenv(buf)) != NULL &&
sscanf(e, "%2x%2x%2x", &r, &g, &b) == 3) {
- ret[i*3 + 0] = r / 255.0;
- ret[i*3 + 1] = g / 255.0;
- ret[i*3 + 2] = b / 255.0;
+ ret[i*3 + 0] = r / 255.0F;
+ ret[i*3 + 1] = g / 255.0F;
+ ret[i*3 + 2] = b / 255.0F;
}
}
}
me->ourgame->encode_params(preset, TRUE);
me->npresets++;
}
+ sfree(e);
}
}
* Now enter the solved state as the next move.
*/
midend_stop_anim(me);
- while (me->nstates > me->statepos) {
- me->ourgame->free_game(me->states[--me->nstates].state);
- if (me->states[me->nstates].movestr)
- sfree(me->states[me->nstates].movestr);
- }
+ midend_purge_states(me);
ensure(me);
me->states[me->nstates].state = s;
me->states[me->nstates].movestr = movestr;
me->anim_time = 0.0;
midend_finish_move(me);
}
- midend_redraw(me);
+ if (me->drawing)
+ midend_redraw(me);
midend_set_timer(me);
return NULL;
}
+int midend_status(midend *me)
+{
+ /*
+ * We should probably never be called when the state stack has no
+ * states on it at all - ideally, midends should never be left in
+ * that state for long enough to get put down and forgotten about.
+ * But if we are, I think we return _true_ - pedantically speaking
+ * a midend in that state is 'vacuously solved', and more
+ * practically, a user whose midend has been left in that state
+ * probably _does_ want the 'new game' option to be prominent.
+ */
+ if (me->statepos == 0)
+ return +1;
+
+ return me->ourgame->status(me->states[me->statepos-1].state);
+}
+
char *midend_rewrite_statusbar(midend *me, char *text)
{
/*
char timebuf[100], *ret;
int min, sec;
- sec = me->elapsed;
+ sec = (int)me->elapsed;
min = sec / 60;
sec %= 60;
sprintf(timebuf, "[%d:%02d] ", min, sec);
uistr = val;
val = NULL;
} else if (!strcmp(key, "TIME")) {
- elapsed = atof(val);
+ elapsed = (float)atof(val);
} else if (!strcmp(key, "NSTATES")) {
nstates = atoi(val);
if (nstates <= 0) {
return ret;
}
+/*
+ * This function examines a saved game file just far enough to
+ * determine which game type it contains. It returns NULL on success
+ * and the game name string in 'name' (which will be dynamically
+ * allocated and should be caller-freed), or an error message on
+ * failure.
+ */
+char *identify_game(char **name, int (*read)(void *ctx, void *buf, int len),
+ void *rctx)
+{
+ int nstates = 0, statepos = -1, gotstates = 0;
+ int started = FALSE;
+
+ char *val = NULL;
+ /* Initially all errors give the same report */
+ char *ret = "Data does not appear to be a saved game file";
+
+ *name = NULL;
+
+ /*
+ * Loop round and round reading one key/value pair at a time from
+ * the serialised stream, until we've found the game name.
+ */
+ while (nstates <= 0 || statepos < 0 || gotstates < nstates-1) {
+ char key[9], c;
+ int len;
+
+ do {
+ if (!read(rctx, key, 1)) {
+ /* unexpected EOF */
+ goto cleanup;
+ }
+ } while (key[0] == '\r' || key[0] == '\n');
+
+ if (!read(rctx, key+1, 8)) {
+ /* unexpected EOF */
+ goto cleanup;
+ }
+
+ if (key[8] != ':') {
+ if (started)
+ ret = "Data was incorrectly formatted for a saved game file";
+ goto cleanup;
+ }
+ len = strcspn(key, ": ");
+ assert(len <= 8);
+ key[len] = '\0';
+
+ len = 0;
+ while (1) {
+ if (!read(rctx, &c, 1)) {
+ /* unexpected EOF */
+ goto cleanup;
+ }
+
+ if (c == ':') {
+ break;
+ } else if (c >= '0' && c <= '9') {
+ len = (len * 10) + (c - '0');
+ } else {
+ if (started)
+ ret = "Data was incorrectly formatted for a"
+ " saved game file";
+ goto cleanup;
+ }
+ }
+
+ val = snewn(len+1, char);
+ if (!read(rctx, val, len)) {
+ if (started)
+ goto cleanup;
+ }
+ val[len] = '\0';
+
+ if (!started) {
+ if (strcmp(key, "SAVEFILE") || strcmp(val, SERIALISE_MAGIC)) {
+ /* ret already has the right message in it */
+ goto cleanup;
+ }
+ /* Now most errors are this one, unless otherwise specified */
+ ret = "Saved data ended unexpectedly";
+ started = TRUE;
+ } else {
+ if (!strcmp(key, "VERSION")) {
+ if (strcmp(val, SERIALISE_VERSION)) {
+ ret = "Cannot handle this version of the saved game"
+ " file format";
+ goto cleanup;
+ }
+ } else if (!strcmp(key, "GAME")) {
+ *name = dupstr(val);
+ ret = NULL;
+ goto cleanup;
+ }
+ }
+
+ sfree(val);
+ val = NULL;
+ }
+
+ cleanup:
+ sfree(val);
+ return ret;
+}
+
char *midend_print_puzzle(midend *me, document *doc, int with_soln)
{
game_state *soln = NULL;