mirror of
git://git.tartarus.org/simon/puzzles.git
synced 2025-04-22 00:15:46 -07:00
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:
132
gtk.c
132
gtk.c
@ -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
537
midend.c
@ -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;
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
Reference in New Issue
Block a user