diff options
Diffstat (limited to 'apps/plugins/puzzles/src/midend.c')
-rw-r--r-- | apps/plugins/puzzles/src/midend.c | 507 |
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 | */ | ||
106 | struct 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 | */ | ||
119 | static 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 | |||
97 | void midend_reset_tilesize(midend *me) | 124 | void 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 | ||
387 | static 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 | |||
357 | void midend_new_game(midend *me) | 403 | void 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 | ||
470 | int midend_can_undo(midend *me) | 533 | int 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 | ||
475 | int midend_can_redo(midend *me) | 538 | int 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 | ||
543 | struct newgame_undo_deserialise_read_ctx { | ||
544 | midend *me; | ||
545 | int len, pos; | ||
546 | }; | ||
547 | |||
548 | static 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 | |||
559 | struct newgame_undo_deserialise_check_ctx { | ||
560 | int refused; | ||
561 | }; | ||
562 | |||
563 | static 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 | |||
480 | static int midend_undo(midend *me) | 615 | static 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 | ||
1169 | void midend_supersede_game_desc(midend *me, char *desc, char *privdesc) | 1336 | void 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 | ||
1179 | config_item *midend_get_config(midend *me, int which, char **wintitle) | 1347 | config_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 | ||
1244 | static char *midend_game_id_int(midend *me, char *id, int defmode) | 1411 | static 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 | ||
1416 | char *midend_game_id(midend *me, char *id) | 1593 | const 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 | ||
1449 | char *midend_set_config(midend *me, int which, config_item *cfg) | 1626 | const 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 | ||
1497 | char *midend_solve(midend *me) | 1674 | const 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 | ||
1569 | char *midend_rewrite_statusbar(midend *me, char *text) | 1748 | char *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 | ||
1602 | void midend_serialise(midend *me, | 1781 | void 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 | */ |
1753 | char *midend_deserialise(midend *me, | 1937 | static 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 | ||
2304 | const 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 | */ |
2110 | char *identify_game(char **name, int (*read)(void *ctx, void *buf, int len), | 2317 | const 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 | ||
2208 | char *midend_print_puzzle(midend *me, document *doc, int with_soln) | 2416 | const 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"; |