Actually implemented the serialise/deserialise functions in

midend.c. Also I've added an experimental front end in gtk.c only:
`Save' and `Load' options on the Game menu, which don't even show up
unless you define the magic environment variable
PUZZLES_EXPERIMENTAL_SAVE. Once I'm reasonably confident that the
whole edifice is plausibly stable, I'll take that out and turn it
into a supported feature (and also implement it in OS X and Windows
and write documentation).

[originally from svn r6030]
This commit is contained in:
Simon Tatham
2005-06-28 17:05:05 +00:00
parent 89fdc09c29
commit 7011028b17
3 changed files with 655 additions and 20 deletions

132
gtk.c
View File

@ -118,6 +118,7 @@ struct frontend {
char *laststatus; char *laststatus;
int pw, ph; /* pixmap size (w, h are area size) */ int pw, ph; /* pixmap size (w, h are area size) */
int ox, oy; /* offset of pixmap in drawing area */ int ox, oy; /* offset of pixmap in drawing area */
char *filesel_name;
}; };
void get_random_seed(void **randseed, int *randseedsize) void get_random_seed(void **randseed, int *randseedsize)
@ -450,6 +451,7 @@ static void destroy(GtkWidget *widget, gpointer data)
{ {
frontend *fe = (frontend *)data; frontend *fe = (frontend *)data;
deactivate_timer(fe); deactivate_timer(fe);
midend_free(fe->me);
gtk_main_quit(); gtk_main_quit();
} }
@ -1114,6 +1116,123 @@ static void menu_copy_event(GtkMenuItem *menuitem, gpointer data)
} }
} }
static void filesel_ok(GtkButton *button, gpointer data)
{
frontend *fe = (frontend *)data;
gpointer filesel = gtk_object_get_data(GTK_OBJECT(button), "user-data");
const char *name =
gtk_file_selection_get_filename(GTK_FILE_SELECTION(filesel));
fe->filesel_name = dupstr(name);
}
static char *file_selector(frontend *fe, char *title, int save)
{
GtkWidget *filesel =
gtk_file_selection_new(title);
fe->filesel_name = NULL;
gtk_window_set_modal(GTK_WINDOW(filesel), TRUE);
gtk_object_set_data
(GTK_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "user-data",
(gpointer)filesel);
gtk_signal_connect
(GTK_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "clicked",
GTK_SIGNAL_FUNC(filesel_ok), fe);
gtk_signal_connect_object
(GTK_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "clicked",
GTK_SIGNAL_FUNC(gtk_widget_destroy), (gpointer)filesel);
gtk_signal_connect_object
(GTK_OBJECT(GTK_FILE_SELECTION(filesel)->cancel_button), "clicked",
GTK_SIGNAL_FUNC(gtk_widget_destroy), (gpointer)filesel);
gtk_signal_connect(GTK_OBJECT(filesel), "destroy",
GTK_SIGNAL_FUNC(window_destroy), NULL);
gtk_widget_show(filesel);
gtk_window_set_transient_for(GTK_WINDOW(filesel), GTK_WINDOW(fe->window));
gtk_main();
return fe->filesel_name;
}
static void savefile_write(void *wctx, void *buf, int len)
{
FILE *fp = (FILE *)wctx;
fwrite(buf, 1, len, fp);
}
static int savefile_read(void *wctx, void *buf, int len)
{
FILE *fp = (FILE *)wctx;
int ret;
ret = fread(buf, 1, len, fp);
return (ret == len);
}
static void menu_save_event(GtkMenuItem *menuitem, gpointer data)
{
frontend *fe = (frontend *)data;
char *name;
name = file_selector(fe, "Enter name of game file to save", TRUE);
if (name) {
FILE *fp = fopen(name, "w");
sfree(name);
if (!fp) {
error_box(fe->window, "Unable to open save file");
return;
}
midend_serialise(fe->me, savefile_write, fp);
fclose(fp);
}
}
static void menu_load_event(GtkMenuItem *menuitem, gpointer data)
{
frontend *fe = (frontend *)data;
char *name, *err;
int x, y;
name = file_selector(fe, "Enter name of saved game file to load", FALSE);
if (name) {
FILE *fp = fopen(name, "r");
sfree(name);
if (!fp) {
error_box(fe->window, "Unable to open saved game file");
return;
}
err = midend_deserialise(fe->me, savefile_read, fp);
fclose(fp);
if (err) {
error_box(fe->window, err);
return;
}
get_size(fe, &x, &y);
fe->w = x;
fe->h = y;
gtk_drawing_area_size(GTK_DRAWING_AREA(fe->area), x, y);
{
GtkRequisition req;
gtk_widget_size_request(GTK_WIDGET(fe->window), &req);
gtk_window_resize(GTK_WINDOW(fe->window), req.width, req.height);
}
}
}
static void menu_solve_event(GtkMenuItem *menuitem, gpointer data) static void menu_solve_event(GtkMenuItem *menuitem, gpointer data)
{ {
frontend *fe = (frontend *)data; frontend *fe = (frontend *)data;
@ -1295,6 +1414,19 @@ static frontend *new_window(char *game_id, char **error)
} }
} }
if (getenv("PUZZLES_EXPERIMENTAL_SAVE") != NULL) {
add_menu_separator(GTK_CONTAINER(menu));
menuitem = gtk_menu_item_new_with_label("Load");
gtk_container_add(GTK_CONTAINER(menu), menuitem);
gtk_signal_connect(GTK_OBJECT(menuitem), "activate",
GTK_SIGNAL_FUNC(menu_load_event), fe);
gtk_widget_show(menuitem);
menuitem = gtk_menu_item_new_with_label("Save");
gtk_container_add(GTK_CONTAINER(menu), menuitem);
gtk_signal_connect(GTK_OBJECT(menuitem), "activate",
GTK_SIGNAL_FUNC(menu_save_event), fe);
gtk_widget_show(menuitem);
}
add_menu_separator(GTK_CONTAINER(menu)); add_menu_separator(GTK_CONTAINER(menu));
add_menu_item_with_key(fe, GTK_CONTAINER(menu), "Undo", 'u'); add_menu_item_with_key(fe, GTK_CONTAINER(menu), "Undo", 'u');
add_menu_item_with_key(fe, GTK_CONTAINER(menu), "Redo", '\x12'); add_menu_item_with_key(fe, GTK_CONTAINER(menu), "Redo", '\x12');

537
midend.c
View File

@ -15,9 +15,14 @@
enum { DEF_PARAMS, DEF_SEED, DEF_DESC }; /* for midend_game_id_int */ enum { DEF_PARAMS, DEF_SEED, DEF_DESC }; /* for midend_game_id_int */
enum { NEWGAME, MOVE, SOLVE, RESTART };/* for midend_state_entry.movetype */
#define special(type) ( (type) != MOVE )
struct midend_state_entry { struct midend_state_entry {
game_state *state; game_state *state;
int special; /* created by solve or restart */ char *movestr;
int movetype;
}; };
struct midend_data { struct midend_data {
@ -25,6 +30,10 @@ struct midend_data {
random_state *random; random_state *random;
const game *ourgame; const game *ourgame;
game_params **presets;
char **preset_names;
int npresets, presetsize;
/* /*
* `desc' and `privdesc' deserve a comment. * `desc' and `privdesc' deserve a comment.
* *
@ -50,17 +59,15 @@ struct midend_data {
char *desc, *privdesc, *seedstr; char *desc, *privdesc, *seedstr;
char *aux_info; char *aux_info;
enum { GOT_SEED, GOT_DESC, GOT_NOTHING } genmode; enum { GOT_SEED, GOT_DESC, GOT_NOTHING } genmode;
int nstates, statesize, statepos;
game_params **presets; int nstates, statesize, statepos;
char **preset_names; struct midend_state_entry *states;
int npresets, presetsize;
game_params *params, *curparams; game_params *params, *curparams;
struct midend_state_entry *states;
game_drawstate *drawstate; game_drawstate *drawstate;
game_state *oldstate;
game_ui *ui; game_ui *ui;
game_state *oldstate;
float anim_time, anim_pos; float anim_time, anim_pos;
float flash_time, flash_pos; float flash_time, flash_pos;
int dir; int dir;
@ -123,8 +130,11 @@ midend_data *midend_new(frontend *fe, const game *ourgame)
static void midend_free_game(midend_data *me) static void midend_free_game(midend_data *me)
{ {
while (me->nstates > 0) while (me->nstates > 0) {
me->ourgame->free_game(me->states[--me->nstates].state); me->nstates--;
me->ourgame->free_game(me->states[me->nstates].state);
sfree(me->states[me->nstates].movestr);
}
if (me->drawstate) if (me->drawstate)
me->ourgame->free_drawstate(me->drawstate); me->ourgame->free_drawstate(me->drawstate);
@ -247,7 +257,8 @@ void midend_new_game(midend_data *me)
ensure(me); ensure(me);
me->states[me->nstates].state = me->states[me->nstates].state =
me->ourgame->new_game(me, me->params, me->desc); me->ourgame->new_game(me, me->params, me->desc);
me->states[me->nstates].special = TRUE; me->states[me->nstates].movestr = NULL;
me->states[me->nstates].movetype = NEWGAME;
me->nstates++; me->nstates++;
me->statepos = 1; me->statepos = 1;
me->drawstate = me->ourgame->new_drawstate(me->states[0].state); me->drawstate = me->ourgame->new_drawstate(me->states[0].state);
@ -298,9 +309,9 @@ static void midend_finish_move(midend_data *me)
* Restart moves. * Restart moves.
*/ */
if ((me->oldstate || me->statepos > 1) && if ((me->oldstate || me->statepos > 1) &&
((me->dir > 0 && !me->states[me->statepos-1].special) || ((me->dir > 0 && !special(me->states[me->statepos-1].movetype)) ||
(me->dir < 0 && me->statepos < me->nstates && (me->dir < 0 && me->statepos < me->nstates &&
!me->states[me->statepos].special))) { !special(me->states[me->statepos].movetype)))) {
flashtime = me->ourgame->flash_length(me->oldstate ? me->oldstate : flashtime = me->ourgame->flash_length(me->oldstate ? me->oldstate :
me->states[me->statepos-2].state, me->states[me->statepos-2].state,
me->states[me->statepos-1].state, me->states[me->statepos-1].state,
@ -356,7 +367,8 @@ void midend_restart_game(midend_data *me)
me->ourgame->free_game(me->states[--me->nstates].state); me->ourgame->free_game(me->states[--me->nstates].state);
ensure(me); ensure(me);
me->states[me->nstates].state = s; me->states[me->nstates].state = s;
me->states[me->nstates].special = TRUE; /* we just restarted */ me->states[me->nstates].movestr = dupstr(me->desc);
me->states[me->nstates].movetype = RESTART;
me->statepos = ++me->nstates; me->statepos = ++me->nstates;
if (me->ui) if (me->ui)
me->ourgame->changed_state(me->ui, me->ourgame->changed_state(me->ui,
@ -383,7 +395,7 @@ static int midend_really_process_key(midend_data *me, int x, int y, int button)
} else if (button == 'u' || button == 'u' || } else if (button == 'u' || button == 'u' ||
button == '\x1A' || button == '\x1F') { button == '\x1A' || button == '\x1F') {
midend_stop_anim(me); midend_stop_anim(me);
special = me->states[me->statepos-1].special; special = special(me->states[me->statepos-1].movetype);
gotspecial = TRUE; gotspecial = TRUE;
if (!midend_undo(me)) if (!midend_undo(me))
goto done; goto done;
@ -410,7 +422,6 @@ static int midend_really_process_key(midend_data *me, int x, int y, int button)
s = me->ourgame->execute_move(me->states[me->statepos-1].state, s = me->ourgame->execute_move(me->states[me->statepos-1].state,
movestr); movestr);
assert(s != NULL); assert(s != NULL);
sfree(movestr);
} }
if (s == me->states[me->statepos-1].state) { if (s == me->states[me->statepos-1].state) {
@ -426,17 +437,19 @@ static int midend_really_process_key(midend_data *me, int x, int y, int button)
while (me->nstates > me->statepos) while (me->nstates > me->statepos)
me->ourgame->free_game(me->states[--me->nstates].state); me->ourgame->free_game(me->states[--me->nstates].state);
ensure(me); ensure(me);
assert(movestr != NULL);
me->states[me->nstates].state = s; me->states[me->nstates].state = s;
me->states[me->nstates].special = FALSE; /* normal move */ me->states[me->nstates].movestr = movestr;
me->states[me->nstates].movetype = MOVE;
me->statepos = ++me->nstates; me->statepos = ++me->nstates;
me->dir = +1; me->dir = +1;
} else { } else {
goto done; goto done;
} }
} }
if (!gotspecial) if (!gotspecial)
special = me->states[me->statepos-1].special; special = special(me->states[me->statepos-1].movetype);
/* /*
* See if this move requires an animation. * See if this move requires an animation.
@ -997,7 +1010,6 @@ char *midend_solve(midend_data *me)
return msg; return msg;
s = me->ourgame->execute_move(me->states[me->statepos-1].state, movestr); s = me->ourgame->execute_move(me->states[me->statepos-1].state, movestr);
assert(s); assert(s);
sfree(movestr);
/* /*
* Now enter the solved state as the next move. * Now enter the solved state as the next move.
@ -1007,7 +1019,8 @@ char *midend_solve(midend_data *me)
me->ourgame->free_game(me->states[--me->nstates].state); me->ourgame->free_game(me->states[--me->nstates].state);
ensure(me); ensure(me);
me->states[me->nstates].state = s; me->states[me->nstates].state = s;
me->states[me->nstates].special = TRUE; /* created using solve */ me->states[me->nstates].movestr = movestr;
me->states[me->nstates].movetype = SOLVE;
me->statepos = ++me->nstates; me->statepos = ++me->nstates;
if (me->ui) if (me->ui)
me->ourgame->changed_state(me->ui, me->ourgame->changed_state(me->ui,
@ -1049,3 +1062,487 @@ char *midend_rewrite_statusbar(midend_data *me, char *text)
return dupstr(text); return dupstr(text);
} }
} }
#define SERIALISE_MAGIC "Simon Tatham's Portable Puzzle Collection"
#define SERIALISE_VERSION "1"
void midend_serialise(midend_data *me,
void (*write)(void *ctx, void *buf, int len),
void *wctx)
{
int i;
/*
* Each line of the save file contains three components. First
* exactly 8 characters of header word indicating what type of
* data is contained on the line; then a colon followed by a
* decimal integer giving the length of the main string on the
* line; then a colon followed by the string itself (exactly as
* many bytes as previously specified, no matter what they
* contain). Then a newline (of reasonably flexible form).
*/
#define wr(h,s) do { \
char hbuf[80]; \
char *str = (s); \
sprintf(hbuf, "%-8.8s:%d:", (h), strlen(str)); \
write(wctx, hbuf, strlen(hbuf)); \
write(wctx, str, strlen(str)); \
write(wctx, "\n", 1); \
} while (0)
/*
* Magic string identifying the file, and version number of the
* file format.
*/
wr("SAVEFILE", SERIALISE_MAGIC);
wr("VERSION", SERIALISE_VERSION);
/*
* The game name. (Copied locally to avoid const annoyance.)
*/
{
char *s = dupstr(me->ourgame->name);
wr("GAME", s);
sfree(s);
}
/*
* The current long-term parameters structure, in full.
*/
if (me->params) {
char *s = me->ourgame->encode_params(me->params, TRUE);
wr("PARAMS", s);
sfree(s);
}
/*
* The current short-term parameters structure, in full.
*/
if (me->curparams) {
char *s = me->ourgame->encode_params(me->curparams, TRUE);
wr("CPARAMS", s);
sfree(s);
}
/*
* The current game description, the privdesc, and the random seed.
*/
if (me->seedstr)
wr("SEED", me->seedstr);
if (me->desc)
wr("DESC", me->desc);
if (me->privdesc)
wr("PRIVDESC", me->privdesc);
/*
* The game's aux_info. We obfuscate this to prevent spoilers
* (people are likely to run `head' or similar on a saved game
* file simply to find out what it is, and don't necessarily
* want to be told the answer to the puzzle!)
*/
if (me->aux_info) {
unsigned char *s1;
char *s2;
int len;
len = strlen(me->aux_info);
s1 = snewn(len, unsigned char);
memcpy(s1, me->aux_info, len);
obfuscate_bitmap(s1, len*8, FALSE);
s2 = bin2hex(s1, len);
wr("AUXINFO", s2);
sfree(s2);
sfree(s1);
}
/*
* Any required serialisation of the game_ui.
*/
if (me->ui) {
char *s = me->ourgame->encode_ui(me->ui);
if (s) {
wr("UI", s);
sfree(s);
}
}
/*
* The game time, if it's a timed game.
*/
if (me->ourgame->is_timed) {
char buf[80];
sprintf(buf, "%g", me->elapsed);
wr("TIME", buf);
}
/*
* The length of, and position in, the states list.
*/
{
char buf[80];
sprintf(buf, "%d", me->nstates);
wr("NSTATES", buf);
sprintf(buf, "%d", me->statepos);
wr("STATEPOS", buf);
}
/*
* For each state after the initial one (which we know is
* constructed from either privdesc or desc), enough
* information for execute_move() to reconstruct it from the
* previous one.
*/
for (i = 1; i < me->nstates; i++) {
assert(me->states[i].movetype != NEWGAME); /* only state 0 */
switch (me->states[i].movetype) {
case MOVE:
wr("MOVE", me->states[i].movestr);
break;
case SOLVE:
wr("SOLVE", me->states[i].movestr);
break;
case RESTART:
wr("RESTART", me->states[i].movestr);
break;
}
}
#undef wr
}
/*
* This function returns NULL on success, or an error message.
*/
char *midend_deserialise(midend_data *me,
int (*read)(void *ctx, void *buf, int len),
void *rctx)
{
int nstates = 0, statepos = -1, gotstates = 0;
int started = FALSE;
int i;
char *val = NULL;
/* Initially all errors give the same report */
char *ret = "Data does not appear to be a saved game file";
/*
* We construct all the new state in local variables while we
* check its sanity. Only once we have finished reading the
* serialised data and detected no errors at all do we start
* modifying stuff in the midend_data passed in.
*/
char *seed = NULL, *parstr = NULL, *desc = NULL, *privdesc = NULL;
char *auxinfo = NULL, *uistr = NULL, *cparstr = NULL;
float elapsed = 0.0F;
game_params *params = NULL, *cparams = NULL;
game_ui *ui = NULL;
struct midend_state_entry *states = NULL;
/*
* Loop round and round reading one key/value pair at a time
* from the serialised stream, until we have enough game states
* to finish.
*/
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";
}
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")) {
if (strcmp(val, me->ourgame->name)) {
ret = "Save file is from a different game";
goto cleanup;
}
} else if (!strcmp(key, "PARAMS")) {
sfree(parstr);
parstr = val;
val = NULL;
} else if (!strcmp(key, "CPARAMS")) {
sfree(cparstr);
cparstr = val;
val = NULL;
} else if (!strcmp(key, "SEED")) {
sfree(seed);
seed = val;
val = NULL;
} else if (!strcmp(key, "DESC")) {
sfree(desc);
desc = val;
val = NULL;
} else if (!strcmp(key, "PRIVDESC")) {
sfree(privdesc);
privdesc = val;
val = NULL;
} else if (!strcmp(key, "AUXINFO")) {
unsigned char *tmp;
int len = strlen(val) / 2; /* length in bytes */
tmp = hex2bin(val, len);
obfuscate_bitmap(tmp, len*8, TRUE);
sfree(auxinfo);
auxinfo = snewn(len + 1, char);
memcpy(auxinfo, tmp, len);
auxinfo[len] = '\0';
sfree(tmp);
} else if (!strcmp(key, "UI")) {
sfree(uistr);
uistr = val;
val = NULL;
} else if (!strcmp(key, "TIME")) {
elapsed = strtod(val, NULL);
} else if (!strcmp(key, "NSTATES")) {
nstates = atoi(val);
if (nstates <= 0) {
ret = "Number of states in save file was negative";
goto cleanup;
}
if (states) {
ret = "Two state counts provided in save file";
goto cleanup;
}
states = snewn(nstates, struct midend_state_entry);
for (i = 0; i < nstates; i++) {
states[i].state = NULL;
states[i].movestr = NULL;
states[i].movetype = NEWGAME;
}
} else if (!strcmp(key, "STATEPOS")) {
statepos = atoi(val);
} else if (!strcmp(key, "MOVE")) {
gotstates++;
states[gotstates].movetype = MOVE;
states[gotstates].movestr = val;
val = NULL;
} else if (!strcmp(key, "SOLVE")) {
gotstates++;
states[gotstates].movetype = SOLVE;
states[gotstates].movestr = val;
val = NULL;
} else if (!strcmp(key, "RESTART")) {
gotstates++;
states[gotstates].movetype = RESTART;
states[gotstates].movestr = val;
val = NULL;
}
}
sfree(val);
val = NULL;
}
params = me->ourgame->default_params();
me->ourgame->decode_params(params, parstr);
if (me->ourgame->validate_params(params)) {
ret = "Long-term parameters in save file are invalid";
goto cleanup;
}
cparams = me->ourgame->default_params();
me->ourgame->decode_params(cparams, cparstr);
if (me->ourgame->validate_params(cparams)) {
ret = "Short-term parameters in save file are invalid";
goto cleanup;
}
if (!desc) {
ret = "Game description in save file is missing";
goto cleanup;
} else if (me->ourgame->validate_desc(params, desc)) {
ret = "Game description in save file is invalid";
goto cleanup;
}
if (privdesc && me->ourgame->validate_desc(params, privdesc)) {
ret = "Game private description in save file is invalid";
goto cleanup;
}
if (statepos < 0 || statepos >= nstates) {
ret = "Game position in save file is out of range";
}
states[0].state = me->ourgame->new_game(me, params,
privdesc ? privdesc : desc);
for (i = 1; i < nstates; i++) {
assert(states[i].movetype != NEWGAME);
switch (states[i].movetype) {
case MOVE:
case SOLVE:
states[i].state = me->ourgame->execute_move(states[i-1].state,
states[i].movestr);
if (states[i].state == NULL) {
ret = "Save file contained an invalid move";
goto cleanup;
}
break;
case RESTART:
if (me->ourgame->validate_desc(params, states[i].movestr)) {
ret = "Save file contained an invalid restart move";
goto cleanup;
}
states[i].state = me->ourgame->new_game(me, params,
states[i].movestr);
break;
}
}
ui = me->ourgame->new_ui(states[0].state);
me->ourgame->decode_ui(ui, uistr);
/*
* Now we've run out of possible error conditions, so we're
* ready to start overwriting the real data in the current
* midend. We'll do this by swapping things with the local
* variables, so that the same cleanup code will free the old
* stuff.
*/
{
char *tmp;
tmp = me->desc;
me->desc = desc;
desc = tmp;
tmp = me->privdesc;
me->privdesc = privdesc;
privdesc = tmp;
tmp = me->seedstr;
me->seedstr = seed;
seed = tmp;
tmp = me->aux_info;
me->aux_info = auxinfo;
auxinfo = tmp;
}
me->genmode = GOT_NOTHING;
me->statesize = nstates;
nstates = me->nstates;
me->nstates = me->statesize;
{
struct midend_state_entry *tmp;
tmp = me->states;
me->states = states;
states = tmp;
}
me->statepos = statepos;
{
game_params *tmp;
tmp = me->params;
me->params = params;
params = tmp;
tmp = me->curparams;
me->curparams = cparams;
cparams = tmp;
}
me->oldstate = NULL;
me->anim_time = me->anim_pos = me->flash_time = me->flash_pos = 0.0F;
me->dir = 0;
{
game_ui *tmp;
tmp = me->ui;
me->ui = ui;
ui = tmp;
}
me->elapsed = elapsed;
me->pressed_mouse_button = 0;
midend_set_timer(me);
ret = NULL; /* success! */
cleanup:
sfree(val);
sfree(seed);
sfree(parstr);
sfree(cparstr);
sfree(desc);
sfree(privdesc);
sfree(auxinfo);
sfree(uistr);
if (params)
me->ourgame->free_params(params);
if (cparams)
me->ourgame->free_params(cparams);
if (ui)
me->ourgame->free_ui(ui);
if (states) {
int i;
for (i = 0; i < nstates; i++) {
if (states[i].state)
me->ourgame->free_game(states[i].state);
sfree(states[i].movestr);
}
sfree(states);
}
return ret;
}

View File

@ -186,6 +186,12 @@ char *midend_text_format(midend_data *me);
char *midend_solve(midend_data *me); char *midend_solve(midend_data *me);
void midend_supersede_game_desc(midend_data *me, char *desc, char *privdesc); void midend_supersede_game_desc(midend_data *me, char *desc, char *privdesc);
char *midend_rewrite_statusbar(midend_data *me, char *text); char *midend_rewrite_statusbar(midend_data *me, char *text);
void midend_serialise(midend_data *me,
void (*write)(void *ctx, void *buf, int len),
void *wctx);
char *midend_deserialise(midend_data *me,
int (*read)(void *ctx, void *buf, int len),
void *rctx);
/* /*
* malloc.c * malloc.c