summaryrefslogtreecommitdiff
path: root/apps/plugins/puzzles/src/midend.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/plugins/puzzles/src/midend.c')
-rw-r--r--apps/plugins/puzzles/src/midend.c507
1 files changed, 358 insertions, 149 deletions
diff --git a/apps/plugins/puzzles/src/midend.c b/apps/plugins/puzzles/src/midend.c
index 09b59b25e2..4ccb1f94a0 100644
--- a/apps/plugins/puzzles/src/midend.c
+++ b/apps/plugins/puzzles/src/midend.c
@@ -63,6 +63,9 @@ struct midend {
63 int nstates, statesize, statepos; 63 int nstates, statesize, statepos;
64 struct midend_state_entry *states; 64 struct midend_state_entry *states;
65 65
66 char *newgame_undo_buf;
67 int newgame_undo_len, newgame_undo_size;
68
66 game_params *params, *curparams; 69 game_params *params, *curparams;
67 game_drawstate *drawstate; 70 game_drawstate *drawstate;
68 game_ui *ui; 71 game_ui *ui;
@@ -94,6 +97,30 @@ struct midend {
94 } \ 97 } \
95} while (0) 98} while (0)
96 99
100/*
101 * Structure storing all the decoded data from reading a serialised
102 * game. We keep it in one of these while we check its sanity, and
103 * only once we're completely satisfied do we install it all in the
104 * midend structure proper.
105 */
106struct deserialise_data {
107 char *seed, *parstr, *desc, *privdesc;
108 char *auxinfo, *uistr, *cparstr;
109 float elapsed;
110 game_params *params, *cparams;
111 game_ui *ui;
112 struct midend_state_entry *states;
113 int nstates, statepos;
114};
115
116/*
117 * Forward reference.
118 */
119static const char *midend_deserialise_internal(
120 midend *me, int (*read)(void *ctx, void *buf, int len), void *rctx,
121 const char *(*check)(void *ctx, midend *, const struct deserialise_data *),
122 void *cctx);
123
97void midend_reset_tilesize(midend *me) 124void midend_reset_tilesize(midend *me)
98{ 125{
99 me->preferred_tilesize = me->ourgame->preferred_tilesize; 126 me->preferred_tilesize = me->ourgame->preferred_tilesize;
@@ -131,6 +158,8 @@ midend *midend_new(frontend *fe, const game *ourgame,
131 me->random = random_new(randseed, randseedsize); 158 me->random = random_new(randseed, randseedsize);
132 me->nstates = me->statesize = me->statepos = 0; 159 me->nstates = me->statesize = me->statepos = 0;
133 me->states = NULL; 160 me->states = NULL;
161 me->newgame_undo_buf = NULL;
162 me->newgame_undo_size = me->newgame_undo_len = 0;
134 me->params = ourgame->default_params(); 163 me->params = ourgame->default_params();
135 me->game_id_change_notify_function = NULL; 164 me->game_id_change_notify_function = NULL;
136 me->game_id_change_notify_ctx = NULL; 165 me->game_id_change_notify_ctx = NULL;
@@ -228,6 +257,7 @@ void midend_free(midend *me)
228 if (me->drawing) 257 if (me->drawing)
229 drawing_free(me->drawing); 258 drawing_free(me->drawing);
230 random_free(me->random); 259 random_free(me->random);
260 sfree(me->newgame_undo_buf);
231 sfree(me->states); 261 sfree(me->states);
232 sfree(me->desc); 262 sfree(me->desc);
233 sfree(me->privdesc); 263 sfree(me->privdesc);
@@ -354,8 +384,40 @@ void midend_force_redraw(midend *me)
354 midend_redraw(me); 384 midend_redraw(me);
355} 385}
356 386
387static void newgame_serialise_write(void *ctx, const void *buf, int len)
388{
389 midend *const me = ctx;
390 int new_len;
391
392 assert(len < INT_MAX - me->newgame_undo_len);
393 new_len = me->newgame_undo_len + len;
394 if (new_len > me->newgame_undo_size) {
395 me->newgame_undo_size = new_len + new_len / 4 + 1024;
396 me->newgame_undo_buf = sresize(me->newgame_undo_buf,
397 me->newgame_undo_size, char);
398 }
399 memcpy(me->newgame_undo_buf + me->newgame_undo_len, buf, len);
400 me->newgame_undo_len = new_len;
401}
402
357void midend_new_game(midend *me) 403void midend_new_game(midend *me)
358{ 404{
405 me->newgame_undo_len = 0;
406 if (me->nstates != 0) {
407 /*
408 * Serialise the whole of the game that we're about to
409 * supersede, so that the 'New Game' action can be undone
410 * later. But if nstates == 0, that means there _isn't_ a
411 * current game (not even a starting position), because this
412 * is the initial call to midend_new_game when the midend is
413 * first set up; in that situation, we want to avoid writing
414 * out any serialisation, because it would be useless anyway
415 * and just confuse us into thinking we had something to undo
416 * to.
417 */
418 midend_serialise(me, newgame_serialise_write, me);
419 }
420
359 midend_stop_anim(me); 421 midend_stop_anim(me);
360 midend_free_game(me); 422 midend_free_game(me);
361 423
@@ -434,7 +496,8 @@ void midend_new_game(midend *me)
434 */ 496 */
435 if (me->ourgame->can_solve && me->aux_info) { 497 if (me->ourgame->can_solve && me->aux_info) {
436 game_state *s; 498 game_state *s;
437 char *msg, *movestr; 499 const char *msg;
500 char *movestr;
438 501
439 msg = NULL; 502 msg = NULL;
440 movestr = me->ourgame->solve(me->states[0].state, 503 movestr = me->ourgame->solve(me->states[0].state,
@@ -469,7 +532,7 @@ void midend_new_game(midend *me)
469 532
470int midend_can_undo(midend *me) 533int midend_can_undo(midend *me)
471{ 534{
472 return (me->statepos > 1); 535 return (me->statepos > 1 || me->newgame_undo_len);
473} 536}
474 537
475int midend_can_redo(midend *me) 538int midend_can_redo(midend *me)
@@ -477,8 +540,82 @@ int midend_can_redo(midend *me)
477 return (me->statepos < me->nstates); 540 return (me->statepos < me->nstates);
478} 541}
479 542
543struct newgame_undo_deserialise_read_ctx {
544 midend *me;
545 int len, pos;
546};
547
548static int newgame_undo_deserialise_read(void *ctx, void *buf, int len)
549{
550 struct newgame_undo_deserialise_read_ctx *const rctx = ctx;
551 midend *const me = rctx->me;
552
553 int use = min(len, rctx->len - rctx->pos);
554 memcpy(buf, me->newgame_undo_buf + rctx->pos, use);
555 rctx->pos += use;
556 return use;
557}
558
559struct newgame_undo_deserialise_check_ctx {
560 int refused;
561};
562
563static const char *newgame_undo_deserialise_check(
564 void *vctx, midend *me, const struct deserialise_data *data)
565{
566 struct newgame_undo_deserialise_check_ctx *ctx =
567 (struct newgame_undo_deserialise_check_ctx *)vctx;
568 char *old, *new;
569
570 /*
571 * Undoing a New Game operation is only permitted if it doesn't
572 * change the game parameters. The point of having the ability at
573 * all is to recover from the momentary finger error of having hit
574 * the 'n' key (perhaps in place of some other nearby key), or hit
575 * the New Game menu item by mistake when aiming for the adjacent
576 * Restart; in both those situations, the game params are the same
577 * before and after the new-game operation.
578 *
579 * In principle, we could generalise this so that _any_ call to
580 * midend_new_game could be undone, but that would need all front
581 * ends to be alert to the possibility that any keystroke passed
582 * to midend_process_key might (if it turns out to have been one
583 * of the synonyms for undo, which the frontend doesn't
584 * necessarily check for) have various knock-on effects like
585 * needing to select a different preset in the game type menu, or
586 * even resizing the window. At least for the moment, it's easier
587 * not to do that, and to simply disallow any newgame-undo that is
588 * disruptive in either of those ways.
589 *
590 * We check both params and cparams, to be as safe as possible.
591 */
592
593 old = me->ourgame->encode_params(me->params, TRUE);
594 new = me->ourgame->encode_params(data->params, TRUE);
595 if (strcmp(old, new)) {
596 /* Set a flag to distinguish this deserialise failure
597 * from one due to faulty decoding */
598 ctx->refused = TRUE;
599 return "Undoing this new-game operation would change params";
600 }
601
602 old = me->ourgame->encode_params(me->curparams, TRUE);
603 new = me->ourgame->encode_params(data->cparams, TRUE);
604 if (strcmp(old, new)) {
605 ctx->refused = TRUE;
606 return "Undoing this new-game operation would change params";
607 }
608
609 /*
610 * Otherwise, fine, go ahead.
611 */
612 return NULL;
613}
614
480static int midend_undo(midend *me) 615static int midend_undo(midend *me)
481{ 616{
617 const char *deserialise_error;
618
482 if (me->statepos > 1) { 619 if (me->statepos > 1) {
483 if (me->ui) 620 if (me->ui)
484 me->ourgame->changed_state(me->ui, 621 me->ourgame->changed_state(me->ui,
@@ -487,6 +624,36 @@ static int midend_undo(midend *me)
487 me->statepos--; 624 me->statepos--;
488 me->dir = -1; 625 me->dir = -1;
489 return 1; 626 return 1;
627 } else if (me->newgame_undo_len) {
628 /* This undo cannot be undone with redo */
629 struct newgame_undo_deserialise_read_ctx rctx;
630 struct newgame_undo_deserialise_check_ctx cctx;
631 rctx.me = me;
632 rctx.len = me->newgame_undo_len; /* copy for reentrancy safety */
633 rctx.pos = 0;
634 cctx.refused = FALSE;
635 deserialise_error = midend_deserialise_internal(
636 me, newgame_undo_deserialise_read, &rctx,
637 newgame_undo_deserialise_check, &cctx);
638 if (cctx.refused) {
639 /*
640 * Our post-deserialisation check shows that we can't use
641 * this saved game after all. (deserialise_error will
642 * contain the dummy error message generated by our check
643 * function, which we ignore.)
644 */
645 return 0;
646 } else {
647 /*
648 * There should never be any _other_ deserialisation
649 * error, because this serialised data has been held in
650 * our memory since it was created, and hasn't had any
651 * opportunity to be corrupted on disk, accidentally
652 * replaced by the wrong file, etc., by user error.
653 */
654 assert(!deserialise_error);
655 return 1;
656 }
490 } else 657 } else
491 return 0; 658 return 0;
492} 659}
@@ -629,7 +796,7 @@ static int midend_really_process_key(midend *me, int x, int y, int button)
629 } else 796 } else
630 goto done; 797 goto done;
631 } else { 798 } else {
632 if (!*movestr) 799 if (movestr == UI_UPDATE)
633 s = me->states[me->statepos-1].state; 800 s = me->states[me->statepos-1].state;
634 else { 801 else {
635 s = me->ourgame->execute_move(me->states[me->statepos-1].state, 802 s = me->ourgame->execute_move(me->states[me->statepos-1].state,
@@ -1166,7 +1333,8 @@ void midend_request_id_changes(midend *me, void (*notify)(void *), void *ctx)
1166 me->game_id_change_notify_ctx = ctx; 1333 me->game_id_change_notify_ctx = ctx;
1167} 1334}
1168 1335
1169void midend_supersede_game_desc(midend *me, char *desc, char *privdesc) 1336void midend_supersede_game_desc(midend *me, const char *desc,
1337 const char *privdesc)
1170{ 1338{
1171 sfree(me->desc); 1339 sfree(me->desc);
1172 sfree(me->privdesc); 1340 sfree(me->privdesc);
@@ -1178,7 +1346,8 @@ void midend_supersede_game_desc(midend *me, char *desc, char *privdesc)
1178 1346
1179config_item *midend_get_config(midend *me, int which, char **wintitle) 1347config_item *midend_get_config(midend *me, int which, char **wintitle)
1180{ 1348{
1181 char *titlebuf, *parstr, *rest; 1349 char *titlebuf, *parstr;
1350 const char *rest;
1182 config_item *ret; 1351 config_item *ret;
1183 char sep; 1352 char sep;
1184 1353
@@ -1207,7 +1376,6 @@ config_item *midend_get_config(midend *me, int which, char **wintitle)
1207 ret[0].name = "Game random seed"; 1376 ret[0].name = "Game random seed";
1208 else 1377 else
1209 ret[0].name = "Game ID"; 1378 ret[0].name = "Game ID";
1210 ret[0].ival = 0;
1211 /* 1379 /*
1212 * For CFG_DESC the text going in here will be a string 1380 * For CFG_DESC the text going in here will be a string
1213 * encoding of the restricted parameters, plus a colon, 1381 * encoding of the restricted parameters, plus a colon,
@@ -1226,13 +1394,12 @@ config_item *midend_get_config(midend *me, int which, char **wintitle)
1226 rest = me->seedstr ? me->seedstr : ""; 1394 rest = me->seedstr ? me->seedstr : "";
1227 sep = '#'; 1395 sep = '#';
1228 } 1396 }
1229 ret[0].sval = snewn(strlen(parstr) + strlen(rest) + 2, char); 1397 ret[0].u.string.sval = snewn(strlen(parstr) + strlen(rest) + 2, char);
1230 sprintf(ret[0].sval, "%s%c%s", parstr, sep, rest); 1398 sprintf(ret[0].u.string.sval, "%s%c%s", parstr, sep, rest);
1231 sfree(parstr); 1399 sfree(parstr);
1232 1400
1233 ret[1].type = C_END; 1401 ret[1].type = C_END;
1234 ret[1].name = ret[1].sval = NULL; 1402 ret[1].name = NULL;
1235 ret[1].ival = 0;
1236 1403
1237 return ret; 1404 return ret;
1238 } 1405 }
@@ -1241,9 +1408,11 @@ config_item *midend_get_config(midend *me, int which, char **wintitle)
1241 return NULL; 1408 return NULL;
1242} 1409}
1243 1410
1244static char *midend_game_id_int(midend *me, char *id, int defmode) 1411static const char *midend_game_id_int(midend *me, const char *id, int defmode)
1245{ 1412{
1246 char *error, *par, *desc, *seed; 1413 const char *error;
1414 char *par = NULL;
1415 const char *desc, *seed;
1247 game_params *newcurparams, *newparams, *oldparams1, *oldparams2; 1416 game_params *newcurparams, *newparams, *oldparams1, *oldparams2;
1248 int free_params; 1417 int free_params;
1249 1418
@@ -1256,8 +1425,10 @@ static char *midend_game_id_int(midend *me, char *id, int defmode)
1256 * description. So `par' now points to the parameters 1425 * description. So `par' now points to the parameters
1257 * string, and `desc' to the description string. 1426 * string, and `desc' to the description string.
1258 */ 1427 */
1259 *desc++ = '\0'; 1428 par = snewn(desc-id + 1, char);
1260 par = id; 1429 strncpy(par, id, desc-id);
1430 par[desc-id] = '\0';
1431 desc++;
1261 seed = NULL; 1432 seed = NULL;
1262 } else if (seed && (!desc || seed < desc)) { 1433 } else if (seed && (!desc || seed < desc)) {
1263 /* 1434 /*
@@ -1265,8 +1436,10 @@ static char *midend_game_id_int(midend *me, char *id, int defmode)
1265 * So `par' now points to the parameters string, and `seed' 1436 * So `par' now points to the parameters string, and `seed'
1266 * to the seed string. 1437 * to the seed string.
1267 */ 1438 */
1268 *seed++ = '\0'; 1439 par = snewn(seed-id + 1, char);
1269 par = id; 1440 strncpy(par, id, seed-id);
1441 par[seed-id] = '\0';
1442 seed++;
1270 desc = NULL; 1443 desc = NULL;
1271 } else { 1444 } else {
1272 /* 1445 /*
@@ -1275,12 +1448,14 @@ static char *midend_game_id_int(midend *me, char *id, int defmode)
1275 */ 1448 */
1276 if (defmode == DEF_SEED) { 1449 if (defmode == DEF_SEED) {
1277 seed = id; 1450 seed = id;
1278 par = desc = NULL; 1451 par = NULL;
1452 desc = NULL;
1279 } else if (defmode == DEF_DESC) { 1453 } else if (defmode == DEF_DESC) {
1280 desc = id; 1454 desc = id;
1281 par = seed = NULL; 1455 par = NULL;
1456 seed = NULL;
1282 } else { 1457 } else {
1283 par = id; 1458 par = dupstr(id);
1284 seed = desc = NULL; 1459 seed = desc = NULL;
1285 } 1460 }
1286 } 1461 }
@@ -1410,10 +1585,12 @@ static char *midend_game_id_int(midend *me, char *id, int defmode)
1410 me->genmode = GOT_SEED; 1585 me->genmode = GOT_SEED;
1411 } 1586 }
1412 1587
1588 sfree(par);
1589
1413 return NULL; 1590 return NULL;
1414} 1591}
1415 1592
1416char *midend_game_id(midend *me, char *id) 1593const char *midend_game_id(midend *me, const char *id)
1417{ 1594{
1418 return midend_game_id_int(me, id, DEF_PARAMS); 1595 return midend_game_id_int(me, id, DEF_PARAMS);
1419} 1596}
@@ -1446,9 +1623,9 @@ char *midend_get_random_seed(midend *me)
1446 return ret; 1623 return ret;
1447} 1624}
1448 1625
1449char *midend_set_config(midend *me, int which, config_item *cfg) 1626const char *midend_set_config(midend *me, int which, config_item *cfg)
1450{ 1627{
1451 char *error; 1628 const char *error;
1452 game_params *params; 1629 game_params *params;
1453 1630
1454 switch (which) { 1631 switch (which) {
@@ -1467,7 +1644,7 @@ char *midend_set_config(midend *me, int which, config_item *cfg)
1467 1644
1468 case CFG_SEED: 1645 case CFG_SEED:
1469 case CFG_DESC: 1646 case CFG_DESC:
1470 error = midend_game_id_int(me, cfg[0].sval, 1647 error = midend_game_id_int(me, cfg[0].u.string.sval,
1471 (which == CFG_SEED ? DEF_SEED : DEF_DESC)); 1648 (which == CFG_SEED ? DEF_SEED : DEF_DESC));
1472 if (error) 1649 if (error)
1473 return error; 1650 return error;
@@ -1494,10 +1671,11 @@ char *midend_text_format(midend *me)
1494 return NULL; 1671 return NULL;
1495} 1672}
1496 1673
1497char *midend_solve(midend *me) 1674const char *midend_solve(midend *me)
1498{ 1675{
1499 game_state *s; 1676 game_state *s;
1500 char *msg, *movestr; 1677 const char *msg;
1678 char *movestr;
1501 1679
1502 if (!me->ourgame->can_solve) 1680 if (!me->ourgame->can_solve)
1503 return "This game does not support the Solve operation"; 1681 return "This game does not support the Solve operation";
@@ -1509,6 +1687,7 @@ char *midend_solve(midend *me)
1509 movestr = me->ourgame->solve(me->states[0].state, 1687 movestr = me->ourgame->solve(me->states[0].state,
1510 me->states[me->statepos-1].state, 1688 me->states[me->statepos-1].state,
1511 me->aux_info, &msg); 1689 me->aux_info, &msg);
1690 assert(movestr != UI_UPDATE);
1512 if (!movestr) { 1691 if (!movestr) {
1513 if (!msg) 1692 if (!msg)
1514 msg = "Solve operation failed"; /* _shouldn't_ happen, but can */ 1693 msg = "Solve operation failed"; /* _shouldn't_ happen, but can */
@@ -1566,7 +1745,7 @@ int midend_status(midend *me)
1566 return me->ourgame->status(me->states[me->statepos-1].state); 1745 return me->ourgame->status(me->states[me->statepos-1].state);
1567} 1746}
1568 1747
1569char *midend_rewrite_statusbar(midend *me, char *text) 1748char *midend_rewrite_statusbar(midend *me, const char *text)
1570{ 1749{
1571 /* 1750 /*
1572 * An important special case is that we are occasionally called 1751 * An important special case is that we are occasionally called
@@ -1600,7 +1779,7 @@ char *midend_rewrite_statusbar(midend *me, char *text)
1600#define SERIALISE_VERSION "1" 1779#define SERIALISE_VERSION "1"
1601 1780
1602void midend_serialise(midend *me, 1781void midend_serialise(midend *me,
1603 void (*write)(void *ctx, void *buf, int len), 1782 void (*write)(void *ctx, const void *buf, int len),
1604 void *wctx) 1783 void *wctx)
1605{ 1784{
1606 int i; 1785 int i;
@@ -1616,7 +1795,7 @@ void midend_serialise(midend *me,
1616 */ 1795 */
1617#define wr(h,s) do { \ 1796#define wr(h,s) do { \
1618 char hbuf[80]; \ 1797 char hbuf[80]; \
1619 char *str = (s); \ 1798 const char *str = (s); \
1620 char lbuf[9]; \ 1799 char lbuf[9]; \
1621 copy_left_justified(lbuf, sizeof(lbuf), h); \ 1800 copy_left_justified(lbuf, sizeof(lbuf), h); \
1622 sprintf(hbuf, "%s:%d:", lbuf, (int)strlen(str)); \ 1801 sprintf(hbuf, "%s:%d:", lbuf, (int)strlen(str)); \
@@ -1748,39 +1927,43 @@ void midend_serialise(midend *me,
1748} 1927}
1749 1928
1750/* 1929/*
1751 * This function returns NULL on success, or an error message. 1930 * Internal version of midend_deserialise, taking an extra check
1931 * function to be called just before beginning to install things in
1932 * the midend.
1933 *
1934 * Like midend_deserialise proper, this function returns NULL on
1935 * success, or an error message.
1752 */ 1936 */
1753char *midend_deserialise(midend *me, 1937static const char *midend_deserialise_internal(
1754 int (*read)(void *ctx, void *buf, int len), 1938 midend *me, int (*read)(void *ctx, void *buf, int len), void *rctx,
1755 void *rctx) 1939 const char *(*check)(void *ctx, midend *, const struct deserialise_data *),
1940 void *cctx)
1756{ 1941{
1757 int nstates = 0, statepos = -1, gotstates = 0; 1942 struct deserialise_data data;
1943 int gotstates = 0;
1758 int started = FALSE; 1944 int started = FALSE;
1759 int i; 1945 int i;
1760 1946
1761 char *val = NULL; 1947 char *val = NULL;
1762 /* Initially all errors give the same report */ 1948 /* Initially all errors give the same report */
1763 char *ret = "Data does not appear to be a saved game file"; 1949 const char *ret = "Data does not appear to be a saved game file";
1764 1950
1765 /* 1951 data.seed = data.parstr = data.desc = data.privdesc = NULL;
1766 * We construct all the new state in local variables while we 1952 data.auxinfo = data.uistr = data.cparstr = NULL;
1767 * check its sanity. Only once we have finished reading the 1953 data.elapsed = 0.0F;
1768 * serialised data and detected no errors at all do we start 1954 data.params = data.cparams = NULL;
1769 * modifying stuff in the midend passed in. 1955 data.ui = NULL;
1770 */ 1956 data.states = NULL;
1771 char *seed = NULL, *parstr = NULL, *desc = NULL, *privdesc = NULL; 1957 data.nstates = 0;
1772 char *auxinfo = NULL, *uistr = NULL, *cparstr = NULL; 1958 data.statepos = -1;
1773 float elapsed = 0.0F;
1774 game_params *params = NULL, *cparams = NULL;
1775 game_ui *ui = NULL;
1776 struct midend_state_entry *states = NULL;
1777 1959
1778 /* 1960 /*
1779 * Loop round and round reading one key/value pair at a time 1961 * Loop round and round reading one key/value pair at a time
1780 * from the serialised stream, until we have enough game states 1962 * from the serialised stream, until we have enough game states
1781 * to finish. 1963 * to finish.
1782 */ 1964 */
1783 while (nstates <= 0 || statepos < 0 || gotstates < nstates-1) { 1965 while (data.nstates <= 0 || data.statepos < 0 ||
1966 gotstates < data.nstates-1) {
1784 char key[9], c; 1967 char key[9], c;
1785 int len; 1968 int len;
1786 1969
@@ -1852,24 +2035,24 @@ char *midend_deserialise(midend *me,
1852 goto cleanup; 2035 goto cleanup;
1853 } 2036 }
1854 } else if (!strcmp(key, "PARAMS")) { 2037 } else if (!strcmp(key, "PARAMS")) {
1855 sfree(parstr); 2038 sfree(data.parstr);
1856 parstr = val; 2039 data.parstr = val;
1857 val = NULL; 2040 val = NULL;
1858 } else if (!strcmp(key, "CPARAMS")) { 2041 } else if (!strcmp(key, "CPARAMS")) {
1859 sfree(cparstr); 2042 sfree(data.cparstr);
1860 cparstr = val; 2043 data.cparstr = val;
1861 val = NULL; 2044 val = NULL;
1862 } else if (!strcmp(key, "SEED")) { 2045 } else if (!strcmp(key, "SEED")) {
1863 sfree(seed); 2046 sfree(data.seed);
1864 seed = val; 2047 data.seed = val;
1865 val = NULL; 2048 val = NULL;
1866 } else if (!strcmp(key, "DESC")) { 2049 } else if (!strcmp(key, "DESC")) {
1867 sfree(desc); 2050 sfree(data.desc);
1868 desc = val; 2051 data.desc = val;
1869 val = NULL; 2052 val = NULL;
1870 } else if (!strcmp(key, "PRIVDESC")) { 2053 } else if (!strcmp(key, "PRIVDESC")) {
1871 sfree(privdesc); 2054 sfree(data.privdesc);
1872 privdesc = val; 2055 data.privdesc = val;
1873 val = NULL; 2056 val = NULL;
1874 } else if (!strcmp(key, "AUXINFO")) { 2057 } else if (!strcmp(key, "AUXINFO")) {
1875 unsigned char *tmp; 2058 unsigned char *tmp;
@@ -1877,49 +2060,49 @@ char *midend_deserialise(midend *me,
1877 tmp = hex2bin(val, len); 2060 tmp = hex2bin(val, len);
1878 obfuscate_bitmap(tmp, len*8, TRUE); 2061 obfuscate_bitmap(tmp, len*8, TRUE);
1879 2062
1880 sfree(auxinfo); 2063 sfree(data.auxinfo);
1881 auxinfo = snewn(len + 1, char); 2064 data.auxinfo = snewn(len + 1, char);
1882 memcpy(auxinfo, tmp, len); 2065 memcpy(data.auxinfo, tmp, len);
1883 auxinfo[len] = '\0'; 2066 data.auxinfo[len] = '\0';
1884 sfree(tmp); 2067 sfree(tmp);
1885 } else if (!strcmp(key, "UI")) { 2068 } else if (!strcmp(key, "UI")) {
1886 sfree(uistr); 2069 sfree(data.uistr);
1887 uistr = val; 2070 data.uistr = val;
1888 val = NULL; 2071 val = NULL;
1889 } else if (!strcmp(key, "TIME")) { 2072 } else if (!strcmp(key, "TIME")) {
1890 elapsed = (float)atof(val); 2073 data.elapsed = (float)atof(val);
1891 } else if (!strcmp(key, "NSTATES")) { 2074 } else if (!strcmp(key, "NSTATES")) {
1892 nstates = atoi(val); 2075 data.nstates = atoi(val);
1893 if (nstates <= 0) { 2076 if (data.nstates <= 0) {
1894 ret = "Number of states in save file was negative"; 2077 ret = "Number of states in save file was negative";
1895 goto cleanup; 2078 goto cleanup;
1896 } 2079 }
1897 if (states) { 2080 if (data.states) {
1898 ret = "Two state counts provided in save file"; 2081 ret = "Two state counts provided in save file";
1899 goto cleanup; 2082 goto cleanup;
1900 } 2083 }
1901 states = snewn(nstates, struct midend_state_entry); 2084 data.states = snewn(data.nstates, struct midend_state_entry);
1902 for (i = 0; i < nstates; i++) { 2085 for (i = 0; i < data.nstates; i++) {
1903 states[i].state = NULL; 2086 data.states[i].state = NULL;
1904 states[i].movestr = NULL; 2087 data.states[i].movestr = NULL;
1905 states[i].movetype = NEWGAME; 2088 data.states[i].movetype = NEWGAME;
1906 } 2089 }
1907 } else if (!strcmp(key, "STATEPOS")) { 2090 } else if (!strcmp(key, "STATEPOS")) {
1908 statepos = atoi(val); 2091 data.statepos = atoi(val);
1909 } else if (!strcmp(key, "MOVE")) { 2092 } else if (!strcmp(key, "MOVE")) {
1910 gotstates++; 2093 gotstates++;
1911 states[gotstates].movetype = MOVE; 2094 data.states[gotstates].movetype = MOVE;
1912 states[gotstates].movestr = val; 2095 data.states[gotstates].movestr = val;
1913 val = NULL; 2096 val = NULL;
1914 } else if (!strcmp(key, "SOLVE")) { 2097 } else if (!strcmp(key, "SOLVE")) {
1915 gotstates++; 2098 gotstates++;
1916 states[gotstates].movetype = SOLVE; 2099 data.states[gotstates].movetype = SOLVE;
1917 states[gotstates].movestr = val; 2100 data.states[gotstates].movestr = val;
1918 val = NULL; 2101 val = NULL;
1919 } else if (!strcmp(key, "RESTART")) { 2102 } else if (!strcmp(key, "RESTART")) {
1920 gotstates++; 2103 gotstates++;
1921 states[gotstates].movetype = RESTART; 2104 data.states[gotstates].movetype = RESTART;
1922 states[gotstates].movestr = val; 2105 data.states[gotstates].movestr = val;
1923 val = NULL; 2106 val = NULL;
1924 } 2107 }
1925 } 2108 }
@@ -1928,68 +2111,77 @@ char *midend_deserialise(midend *me,
1928 val = NULL; 2111 val = NULL;
1929 } 2112 }
1930 2113
1931 params = me->ourgame->default_params(); 2114 data.params = me->ourgame->default_params();
1932 me->ourgame->decode_params(params, parstr); 2115 me->ourgame->decode_params(data.params, data.parstr);
1933 if (me->ourgame->validate_params(params, TRUE)) { 2116 if (me->ourgame->validate_params(data.params, TRUE)) {
1934 ret = "Long-term parameters in save file are invalid"; 2117 ret = "Long-term parameters in save file are invalid";
1935 goto cleanup; 2118 goto cleanup;
1936 } 2119 }
1937 cparams = me->ourgame->default_params(); 2120 data.cparams = me->ourgame->default_params();
1938 me->ourgame->decode_params(cparams, cparstr); 2121 me->ourgame->decode_params(data.cparams, data.cparstr);
1939 if (me->ourgame->validate_params(cparams, FALSE)) { 2122 if (me->ourgame->validate_params(data.cparams, FALSE)) {
1940 ret = "Short-term parameters in save file are invalid"; 2123 ret = "Short-term parameters in save file are invalid";
1941 goto cleanup; 2124 goto cleanup;
1942 } 2125 }
1943 if (seed && me->ourgame->validate_params(cparams, TRUE)) { 2126 if (data.seed && me->ourgame->validate_params(data.cparams, TRUE)) {
1944 /* 2127 /*
1945 * The seed's no use with this version, but we can perfectly 2128 * The seed's no use with this version, but we can perfectly
1946 * well use the rest of the data. 2129 * well use the rest of the data.
1947 */ 2130 */
1948 sfree(seed); 2131 sfree(data.seed);
1949 seed = NULL; 2132 data.seed = NULL;
1950 } 2133 }
1951 if (!desc) { 2134 if (!data.desc) {
1952 ret = "Game description in save file is missing"; 2135 ret = "Game description in save file is missing";
1953 goto cleanup; 2136 goto cleanup;
1954 } else if (me->ourgame->validate_desc(params, desc)) { 2137 } else if (me->ourgame->validate_desc(data.cparams, data.desc)) {
1955 ret = "Game description in save file is invalid"; 2138 ret = "Game description in save file is invalid";
1956 goto cleanup; 2139 goto cleanup;
1957 } 2140 }
1958 if (privdesc && me->ourgame->validate_desc(params, privdesc)) { 2141 if (data.privdesc &&
2142 me->ourgame->validate_desc(data.cparams, data.privdesc)) {
1959 ret = "Game private description in save file is invalid"; 2143 ret = "Game private description in save file is invalid";
1960 goto cleanup; 2144 goto cleanup;
1961 } 2145 }
1962 if (statepos < 0 || statepos >= nstates) { 2146 if (data.statepos < 0 || data.statepos >= data.nstates) {
1963 ret = "Game position in save file is out of range"; 2147 ret = "Game position in save file is out of range";
1964 } 2148 }
1965 2149
1966 states[0].state = me->ourgame->new_game(me, params, 2150 data.states[0].state = me->ourgame->new_game(
1967 privdesc ? privdesc : desc); 2151 me, data.cparams, data.privdesc ? data.privdesc : data.desc);
1968 for (i = 1; i < nstates; i++) { 2152 for (i = 1; i < data.nstates; i++) {
1969 assert(states[i].movetype != NEWGAME); 2153 assert(data.states[i].movetype != NEWGAME);
1970 switch (states[i].movetype) { 2154 switch (data.states[i].movetype) {
1971 case MOVE: 2155 case MOVE:
1972 case SOLVE: 2156 case SOLVE:
1973 states[i].state = me->ourgame->execute_move(states[i-1].state, 2157 data.states[i].state = me->ourgame->execute_move(
1974 states[i].movestr); 2158 data.states[i-1].state, data.states[i].movestr);
1975 if (states[i].state == NULL) { 2159 if (data.states[i].state == NULL) {
1976 ret = "Save file contained an invalid move"; 2160 ret = "Save file contained an invalid move";
1977 goto cleanup; 2161 goto cleanup;
1978 } 2162 }
1979 break; 2163 break;
1980 case RESTART: 2164 case RESTART:
1981 if (me->ourgame->validate_desc(params, states[i].movestr)) { 2165 if (me->ourgame->validate_desc(
2166 data.cparams, data.states[i].movestr)) {
1982 ret = "Save file contained an invalid restart move"; 2167 ret = "Save file contained an invalid restart move";
1983 goto cleanup; 2168 goto cleanup;
1984 } 2169 }
1985 states[i].state = me->ourgame->new_game(me, params, 2170 data.states[i].state = me->ourgame->new_game(
1986 states[i].movestr); 2171 me, data.cparams, data.states[i].movestr);
1987 break; 2172 break;
1988 } 2173 }
1989 } 2174 }
1990 2175
1991 ui = me->ourgame->new_ui(states[0].state); 2176 data.ui = me->ourgame->new_ui(data.states[0].state);
1992 me->ourgame->decode_ui(ui, uistr); 2177 me->ourgame->decode_ui(data.ui, data.uistr);
2178
2179 /*
2180 * Run the externally provided check function, and abort if it
2181 * returns an error message.
2182 */
2183 if (check && (ret = check(cctx, me, &data)) != NULL)
2184 goto cleanup; /* error message is already in ret */
1993 2185
1994 /* 2186 /*
1995 * Now we've run out of possible error conditions, so we're 2187 * Now we've run out of possible error conditions, so we're
@@ -2002,45 +2194,54 @@ char *midend_deserialise(midend *me,
2002 char *tmp; 2194 char *tmp;
2003 2195
2004 tmp = me->desc; 2196 tmp = me->desc;
2005 me->desc = desc; 2197 me->desc = data.desc;
2006 desc = tmp; 2198 data.desc = tmp;
2007 2199
2008 tmp = me->privdesc; 2200 tmp = me->privdesc;
2009 me->privdesc = privdesc; 2201 me->privdesc = data.privdesc;
2010 privdesc = tmp; 2202 data.privdesc = tmp;
2011 2203
2012 tmp = me->seedstr; 2204 tmp = me->seedstr;
2013 me->seedstr = seed; 2205 me->seedstr = data.seed;
2014 seed = tmp; 2206 data.seed = tmp;
2015 2207
2016 tmp = me->aux_info; 2208 tmp = me->aux_info;
2017 me->aux_info = auxinfo; 2209 me->aux_info = data.auxinfo;
2018 auxinfo = tmp; 2210 data.auxinfo = tmp;
2019 } 2211 }
2020 2212
2021 me->genmode = GOT_NOTHING; 2213 me->genmode = GOT_NOTHING;
2022 2214
2023 me->statesize = nstates; 2215 me->statesize = data.nstates;
2024 nstates = me->nstates; 2216 data.nstates = me->nstates;
2025 me->nstates = me->statesize; 2217 me->nstates = me->statesize;
2026 { 2218 {
2027 struct midend_state_entry *tmp; 2219 struct midend_state_entry *tmp;
2028 tmp = me->states; 2220 tmp = me->states;
2029 me->states = states; 2221 me->states = data.states;
2030 states = tmp; 2222 data.states = tmp;
2031 } 2223 }
2032 me->statepos = statepos; 2224 me->statepos = data.statepos;
2225
2226 /*
2227 * Don't save the "new game undo" state. So "new game" twice or
2228 * (in some environments) switching away and back, will make a
2229 * "new game" irreversible. Maybe in the future we will have a
2230 * more sophisticated way to decide when to discard the previous
2231 * game state.
2232 */
2233 me->newgame_undo_len = 0;
2033 2234
2034 { 2235 {
2035 game_params *tmp; 2236 game_params *tmp;
2036 2237
2037 tmp = me->params; 2238 tmp = me->params;
2038 me->params = params; 2239 me->params = data.params;
2039 params = tmp; 2240 data.params = tmp;
2040 2241
2041 tmp = me->curparams; 2242 tmp = me->curparams;
2042 me->curparams = cparams; 2243 me->curparams = data.cparams;
2043 cparams = tmp; 2244 data.cparams = tmp;
2044 } 2245 }
2045 2246
2046 me->oldstate = NULL; 2247 me->oldstate = NULL;
@@ -2051,11 +2252,11 @@ char *midend_deserialise(midend *me,
2051 game_ui *tmp; 2252 game_ui *tmp;
2052 2253
2053 tmp = me->ui; 2254 tmp = me->ui;
2054 me->ui = ui; 2255 me->ui = data.ui;
2055 ui = tmp; 2256 data.ui = tmp;
2056 } 2257 }
2057 2258
2058 me->elapsed = elapsed; 2259 me->elapsed = data.elapsed;
2059 me->pressed_mouse_button = 0; 2260 me->pressed_mouse_button = 0;
2060 2261
2061 midend_set_timer(me); 2262 midend_set_timer(me);
@@ -2073,33 +2274,39 @@ char *midend_deserialise(midend *me,
2073 2274
2074 cleanup: 2275 cleanup:
2075 sfree(val); 2276 sfree(val);
2076 sfree(seed); 2277 sfree(data.seed);
2077 sfree(parstr); 2278 sfree(data.parstr);
2078 sfree(cparstr); 2279 sfree(data.cparstr);
2079 sfree(desc); 2280 sfree(data.desc);
2080 sfree(privdesc); 2281 sfree(data.privdesc);
2081 sfree(auxinfo); 2282 sfree(data.auxinfo);
2082 sfree(uistr); 2283 sfree(data.uistr);
2083 if (params) 2284 if (data.params)
2084 me->ourgame->free_params(params); 2285 me->ourgame->free_params(data.params);
2085 if (cparams) 2286 if (data.cparams)
2086 me->ourgame->free_params(cparams); 2287 me->ourgame->free_params(data.cparams);
2087 if (ui) 2288 if (data.ui)
2088 me->ourgame->free_ui(ui); 2289 me->ourgame->free_ui(data.ui);
2089 if (states) { 2290 if (data.states) {
2090 int i; 2291 int i;
2091 2292
2092 for (i = 0; i < nstates; i++) { 2293 for (i = 0; i < data.nstates; i++) {
2093 if (states[i].state) 2294 if (data.states[i].state)
2094 me->ourgame->free_game(states[i].state); 2295 me->ourgame->free_game(data.states[i].state);
2095 sfree(states[i].movestr); 2296 sfree(data.states[i].movestr);
2096 } 2297 }
2097 sfree(states); 2298 sfree(data.states);
2098 } 2299 }
2099 2300
2100 return ret; 2301 return ret;
2101} 2302}
2102 2303
2304const char *midend_deserialise(
2305 midend *me, int (*read)(void *ctx, void *buf, int len), void *rctx)
2306{
2307 return midend_deserialise_internal(me, read, rctx, NULL, NULL);
2308}
2309
2103/* 2310/*
2104 * This function examines a saved game file just far enough to 2311 * This function examines a saved game file just far enough to
2105 * determine which game type it contains. It returns NULL on success 2312 * determine which game type it contains. It returns NULL on success
@@ -2107,15 +2314,16 @@ char *midend_deserialise(midend *me,
2107 * allocated and should be caller-freed), or an error message on 2314 * allocated and should be caller-freed), or an error message on
2108 * failure. 2315 * failure.
2109 */ 2316 */
2110char *identify_game(char **name, int (*read)(void *ctx, void *buf, int len), 2317const char *identify_game(char **name,
2111 void *rctx) 2318 int (*read)(void *ctx, void *buf, int len),
2319 void *rctx)
2112{ 2320{
2113 int nstates = 0, statepos = -1, gotstates = 0; 2321 int nstates = 0, statepos = -1, gotstates = 0;
2114 int started = FALSE; 2322 int started = FALSE;
2115 2323
2116 char *val = NULL; 2324 char *val = NULL;
2117 /* Initially all errors give the same report */ 2325 /* Initially all errors give the same report */
2118 char *ret = "Data does not appear to be a saved game file"; 2326 const char *ret = "Data does not appear to be a saved game file";
2119 2327
2120 *name = NULL; 2328 *name = NULL;
2121 2329
@@ -2205,7 +2413,7 @@ char *identify_game(char **name, int (*read)(void *ctx, void *buf, int len),
2205 return ret; 2413 return ret;
2206} 2414}
2207 2415
2208char *midend_print_puzzle(midend *me, document *doc, int with_soln) 2416const char *midend_print_puzzle(midend *me, document *doc, int with_soln)
2209{ 2417{
2210 game_state *soln = NULL; 2418 game_state *soln = NULL;
2211 2419
@@ -2213,7 +2421,8 @@ char *midend_print_puzzle(midend *me, document *doc, int with_soln)
2213 return "No game set up to print";/* _shouldn't_ happen! */ 2421 return "No game set up to print";/* _shouldn't_ happen! */
2214 2422
2215 if (with_soln) { 2423 if (with_soln) {
2216 char *msg, *movestr; 2424 const char *msg;
2425 char *movestr;
2217 2426
2218 if (!me->ourgame->can_solve) 2427 if (!me->ourgame->can_solve)
2219 return "This game does not support the Solve operation"; 2428 return "This game does not support the Solve operation";