Oops, forgot to initialise changed_ascii on all paths in r9657.
[sgt/puzzles] / midend.c
CommitLineData
720a8fb7 1/*
2 * midend.c: general middle fragment sitting between the
3 * platform-specific front end and game-specific back end.
4 * Maintains a move list, takes care of Undo and Redo commands, and
7f89707c 5 * processes standard keystrokes for undo/redo/new/quit.
720a8fb7 6 */
7f77ea24 7
8#include <stdio.h>
5928817c 9#include <string.h>
7f77ea24 10#include <assert.h>
813593cc 11#include <stdlib.h>
12#include <ctype.h>
7f77ea24 13
14#include "puzzles.h"
15
1185e3c5 16enum { DEF_PARAMS, DEF_SEED, DEF_DESC }; /* for midend_game_id_int */
17
a4393230 18enum { NEWGAME, MOVE, SOLVE, RESTART };/* for midend_state_entry.movetype */
19
20#define special(type) ( (type) != MOVE )
21
28d0118c 22struct midend_state_entry {
23 game_state *state;
a4393230 24 char *movestr;
25 int movetype;
28d0118c 26};
27
dafd6cf6 28struct midend {
2ef96bd6 29 frontend *frontend;
48d70ca9 30 random_state *random;
be8d5aa1 31 const game *ourgame;
48d70ca9 32
a4393230 33 game_params **presets;
f92acd1a 34 char **preset_names, **preset_encodings;
a4393230 35 int npresets, presetsize;
36
0a6892db 37 /*
38 * `desc' and `privdesc' deserve a comment.
39 *
40 * `desc' is the game description as presented to the user when
41 * they ask for Game -> Specific. `privdesc', if non-NULL, is a
42 * different game description used to reconstruct the initial
43 * game_state when de-serialising. If privdesc is NULL, `desc'
44 * is used for both.
45 *
46 * For almost all games, `privdesc' is NULL and never used. The
47 * exception (as usual) is Mines: the initial game state has no
48 * squares open at all, but after the first click `desc' is
49 * rewritten to describe a game state with an initial click and
50 * thus a bunch of squares open. If we used that desc to
51 * serialise and deserialise, then the initial game state after
52 * deserialisation would look unlike the initial game state
53 * beforehand, and worse still execute_move() might fail on the
54 * attempted first click. So `privdesc' is also used in this
55 * case, to provide a game description describing the same
56 * fixed mine layout _but_ no initial click. (These game IDs
57 * may also be typed directly into Mines if you like.)
58 */
59 char *desc, *privdesc, *seedstr;
c566778e 60 char *aux_info;
1185e3c5 61 enum { GOT_SEED, GOT_DESC, GOT_NOTHING } genmode;
eb2ad6f1 62
a4393230 63 int nstates, statesize, statepos;
64 struct midend_state_entry *states;
eb2ad6f1 65
f60f7e7c 66 game_params *params, *curparams;
2ef96bd6 67 game_drawstate *drawstate;
74a4e547 68 game_ui *ui;
a4393230 69
70 game_state *oldstate;
2ef96bd6 71 float anim_time, anim_pos;
87ed82be 72 float flash_time, flash_pos;
c822de4a 73 int dir;
6776a950 74
48dcdd62 75 int timing;
76 float elapsed;
77 char *laststatus;
78
dafd6cf6 79 drawing *drawing;
80
6776a950 81 int pressed_mouse_button;
1e3e152d 82
83134cff 83 int preferred_tilesize, tilesize, winwidth, winheight;
7f77ea24 84};
85
86#define ensure(me) do { \
87 if ((me)->nstates >= (me)->statesize) { \
88 (me)->statesize = (me)->nstates + 128; \
28d0118c 89 (me)->states = sresize((me)->states, (me)->statesize, \
90 struct midend_state_entry); \
7f77ea24 91 } \
92} while (0)
93
dafd6cf6 94midend *midend_new(frontend *fe, const game *ourgame,
95 const drawing_api *drapi, void *drhandle)
7f77ea24 96{
dafd6cf6 97 midend *me = snew(midend);
cbb5549e 98 void *randseed;
99 int randseedsize;
100
101 get_random_seed(&randseed, &randseedsize);
7f77ea24 102
48d70ca9 103 me->frontend = fe;
be8d5aa1 104 me->ourgame = ourgame;
1fbb0680 105 me->random = random_new(randseed, randseedsize);
7f77ea24 106 me->nstates = me->statesize = me->statepos = 0;
107 me->states = NULL;
be8d5aa1 108 me->params = ourgame->default_params();
829c02d6 109 /*
110 * Allow environment-based changing of the default settings by
111 * defining a variable along the lines of `NET_DEFAULT=25x25w'
112 * in which the value is an encoded parameter string.
113 */
114 {
115 char buf[80], *e;
116 int j, k;
117 sprintf(buf, "%s_DEFAULT", me->ourgame->name);
118 for (j = k = 0; buf[j]; j++)
119 if (!isspace((unsigned char)buf[j]))
120 buf[k++] = toupper((unsigned char)buf[j]);
121 buf[k] = '\0';
122 if ((e = getenv(buf)) != NULL)
123 me->ourgame->decode_params(me->params, e);
124 }
f60f7e7c 125 me->curparams = NULL;
0a6892db 126 me->desc = me->privdesc = NULL;
1185e3c5 127 me->seedstr = NULL;
6f2d8d7c 128 me->aux_info = NULL;
1185e3c5 129 me->genmode = GOT_NOTHING;
2ef96bd6 130 me->drawstate = NULL;
131 me->oldstate = NULL;
eb2ad6f1 132 me->presets = NULL;
133 me->preset_names = NULL;
f92acd1a 134 me->preset_encodings = NULL;
eb2ad6f1 135 me->npresets = me->presetsize = 0;
87ed82be 136 me->anim_time = me->anim_pos = 0.0F;
137 me->flash_time = me->flash_pos = 0.0F;
c822de4a 138 me->dir = 0;
74a4e547 139 me->ui = NULL;
6776a950 140 me->pressed_mouse_button = 0;
48dcdd62 141 me->laststatus = NULL;
142 me->timing = FALSE;
143 me->elapsed = 0.0F;
1f3ee4ee 144 me->tilesize = me->winwidth = me->winheight = 0;
dafd6cf6 145 if (drapi)
83c0438f 146 me->drawing = drawing_new(drapi, me, drhandle);
dafd6cf6 147 else
148 me->drawing = NULL;
7f77ea24 149
83134cff 150 me->preferred_tilesize = ourgame->preferred_tilesize;
151 {
152 /*
153 * Allow an environment-based override for the default tile
154 * size by defining a variable along the lines of
155 * `NET_TILESIZE=15'.
156 */
157
158 char buf[80], *e;
159 int j, k, ts;
160
161 sprintf(buf, "%s_TILESIZE", me->ourgame->name);
162 for (j = k = 0; buf[j]; j++)
163 if (!isspace((unsigned char)buf[j]))
164 buf[k++] = toupper((unsigned char)buf[j]);
165 buf[k] = '\0';
166 if ((e = getenv(buf)) != NULL && sscanf(e, "%d", &ts) == 1 && ts > 0)
167 me->preferred_tilesize = ts;
168 }
169
cbb5549e 170 sfree(randseed);
171
7f77ea24 172 return me;
173}
174
0c5f1313 175static void midend_purge_states(midend *me)
176{
177 while (me->nstates > me->statepos) {
178 me->ourgame->free_game(me->states[--me->nstates].state);
179 if (me->states[me->nstates].movestr)
180 sfree(me->states[me->nstates].movestr);
181 }
182}
183
dafd6cf6 184static void midend_free_game(midend *me)
ab53eb64 185{
a4393230 186 while (me->nstates > 0) {
187 me->nstates--;
188 me->ourgame->free_game(me->states[me->nstates].state);
189 sfree(me->states[me->nstates].movestr);
190 }
ab53eb64 191
192 if (me->drawstate)
dafd6cf6 193 me->ourgame->free_drawstate(me->drawing, me->drawstate);
ab53eb64 194}
195
dafd6cf6 196void midend_free(midend *me)
7f77ea24 197{
ab53eb64 198 int i;
199
200 midend_free_game(me);
201
dafd6cf6 202 if (me->drawing)
203 drawing_free(me->drawing);
ab53eb64 204 random_free(me->random);
7f77ea24 205 sfree(me->states);
1185e3c5 206 sfree(me->desc);
871bf294 207 sfree(me->privdesc);
1185e3c5 208 sfree(me->seedstr);
c566778e 209 sfree(me->aux_info);
be8d5aa1 210 me->ourgame->free_params(me->params);
ab53eb64 211 if (me->npresets) {
212 for (i = 0; i < me->npresets; i++) {
213 sfree(me->presets[i]);
214 sfree(me->preset_names[i]);
f92acd1a 215 sfree(me->preset_encodings[i]);
ab53eb64 216 }
217 sfree(me->presets);
218 sfree(me->preset_names);
f92acd1a 219 sfree(me->preset_encodings);
ab53eb64 220 }
221 if (me->ui)
222 me->ourgame->free_ui(me->ui);
f60f7e7c 223 if (me->curparams)
224 me->ourgame->free_params(me->curparams);
48dcdd62 225 sfree(me->laststatus);
7f77ea24 226 sfree(me);
227}
228
dafd6cf6 229static void midend_size_new_drawstate(midend *me)
1f3ee4ee 230{
231 /*
232 * Don't even bother, if we haven't worked out our tile size
233 * anyway yet.
234 */
235 if (me->tilesize > 0) {
236 me->ourgame->compute_size(me->params, me->tilesize,
237 &me->winwidth, &me->winheight);
dafd6cf6 238 me->ourgame->set_size(me->drawing, me->drawstate,
239 me->params, me->tilesize);
1f3ee4ee 240 }
241}
242
8c4ea6f0 243void midend_size(midend *me, int *x, int *y, int user_size)
7f77ea24 244{
1f3ee4ee 245 int min, max;
246 int rx, ry;
247
248 /*
05e50a96 249 * We can't set the size on the same drawstate twice. So if
250 * we've already sized one drawstate, we must throw it away and
251 * create a new one.
252 */
253 if (me->drawstate && me->tilesize > 0) {
254 me->ourgame->free_drawstate(me->drawing, me->drawstate);
255 me->drawstate = me->ourgame->new_drawstate(me->drawing,
256 me->states[0].state);
257 }
258
259 /*
1f3ee4ee 260 * Find the tile size that best fits within the given space. If
8c4ea6f0 261 * `user_size' is TRUE, we must actually find the _largest_ such
262 * tile size, in order to get as close to the user's explicit
263 * request as possible; otherwise, we bound above at the game's
264 * preferred tile size, so that the game gets what it wants
265 * provided that this doesn't break the constraint from the
266 * front-end (which is likely to be a screen size or similar).
1f3ee4ee 267 */
8c4ea6f0 268 if (user_size) {
1f3ee4ee 269 max = 1;
270 do {
271 max *= 2;
272 me->ourgame->compute_size(me->params, max, &rx, &ry);
273 } while (rx <= *x && ry <= *y);
274 } else
83134cff 275 max = me->preferred_tilesize + 1;
1f3ee4ee 276 min = 1;
277
278 /*
279 * Now binary-search between min and max. We're looking for a
280 * boundary rather than a value: the point at which tile sizes
281 * stop fitting within the given dimensions. Thus, we stop when
282 * max and min differ by exactly 1.
283 */
284 while (max - min > 1) {
285 int mid = (max + min) / 2;
286 me->ourgame->compute_size(me->params, mid, &rx, &ry);
287 if (rx <= *x && ry <= *y)
288 min = mid;
289 else
290 max = mid;
291 }
292
293 /*
294 * Now `min' is a valid size, and `max' isn't. So use `min'.
295 */
296
297 me->tilesize = min;
8c4ea6f0 298 if (user_size)
299 /* If the user requested a change in size, make it permanent. */
888050f2 300 me->preferred_tilesize = me->tilesize;
1f3ee4ee 301 midend_size_new_drawstate(me);
302 *x = me->winwidth;
303 *y = me->winheight;
7f77ea24 304}
305
dc3de726 306int midend_tilesize(midend *me) { return me->tilesize; }
307
dafd6cf6 308void midend_set_params(midend *me, game_params *params)
7f77ea24 309{
be8d5aa1 310 me->ourgame->free_params(me->params);
311 me->params = me->ourgame->dup_params(params);
7f77ea24 312}
313
821ab2c6 314game_params *midend_get_params(midend *me)
315{
316 return me->ourgame->dup_params(me->params);
317}
318
dafd6cf6 319static void midend_set_timer(midend *me)
48dcdd62 320{
321 me->timing = (me->ourgame->is_timed &&
4d08de49 322 me->ourgame->timing_state(me->states[me->statepos-1].state,
323 me->ui));
48dcdd62 324 if (me->timing || me->flash_time || me->anim_time)
325 activate_timer(me->frontend);
326 else
327 deactivate_timer(me->frontend);
328}
329
dafd6cf6 330void midend_force_redraw(midend *me)
008b4378 331{
332 if (me->drawstate)
dafd6cf6 333 me->ourgame->free_drawstate(me->drawing, me->drawstate);
334 me->drawstate = me->ourgame->new_drawstate(me->drawing,
335 me->states[0].state);
1e3e152d 336 midend_size_new_drawstate(me);
008b4378 337 midend_redraw(me);
338}
339
dafd6cf6 340void midend_new_game(midend *me)
7f77ea24 341{
ab53eb64 342 midend_free_game(me);
2ef96bd6 343
7f77ea24 344 assert(me->nstates == 0);
345
1185e3c5 346 if (me->genmode == GOT_DESC) {
347 me->genmode = GOT_NOTHING;
348 } else {
349 random_state *rs;
350
351 if (me->genmode == GOT_SEED) {
352 me->genmode = GOT_NOTHING;
353 } else {
354 /*
355 * Generate a new random seed. 15 digits comes to about
356 * 48 bits, which should be more than enough.
97c44af3 357 *
358 * I'll avoid putting a leading zero on the number,
359 * just in case it confuses anybody who thinks it's
360 * processed as an integer rather than a string.
1185e3c5 361 */
362 char newseed[16];
363 int i;
364 newseed[15] = '\0';
5b502ae8 365 newseed[0] = '1' + (char)random_upto(me->random, 9);
97c44af3 366 for (i = 1; i < 15; i++)
5b502ae8 367 newseed[i] = '0' + (char)random_upto(me->random, 10);
1185e3c5 368 sfree(me->seedstr);
369 me->seedstr = dupstr(newseed);
f60f7e7c 370
371 if (me->curparams)
372 me->ourgame->free_params(me->curparams);
373 me->curparams = me->ourgame->dup_params(me->params);
1185e3c5 374 }
375
376 sfree(me->desc);
0a6892db 377 sfree(me->privdesc);
c566778e 378 sfree(me->aux_info);
6f2d8d7c 379 me->aux_info = NULL;
1185e3c5 380
1fbb0680 381 rs = random_new(me->seedstr, strlen(me->seedstr));
dafd6cf6 382 /*
383 * If this midend has been instantiated without providing a
384 * drawing API, it is non-interactive. This means that it's
385 * being used for bulk game generation, and hence we should
386 * pass the non-interactive flag to new_desc.
387 */
6aa6af4c 388 me->desc = me->ourgame->new_desc(me->curparams, rs,
dafd6cf6 389 &me->aux_info, (me->drawing != NULL));
0a6892db 390 me->privdesc = NULL;
1185e3c5 391 random_free(rs);
1185e3c5 392 }
7f77ea24 393
394 ensure(me);
0b2bbd55 395
396 /*
397 * It might seem a bit odd that we're using me->params to
398 * create the initial game state, rather than me->curparams
399 * which is better tailored to this specific game and which we
400 * always know.
401 *
402 * It's supposed to be an invariant in the midend that
403 * me->params and me->curparams differ in no aspect that is
404 * important after generation (i.e. after new_desc()). By
405 * deliberately passing the _less_ specific of these two
406 * parameter sets, we provoke play-time misbehaviour in the
407 * case where a game has failed to encode a play-time parameter
408 * in the non-full version of encode_params().
409 */
c380832d 410 me->states[me->nstates].state =
411 me->ourgame->new_game(me, me->params, me->desc);
0b2bbd55 412
640a235e 413 /*
414 * As part of our commitment to self-testing, test the aux
415 * string to make sure nothing ghastly went wrong.
416 */
417 if (me->ourgame->can_solve && me->aux_info) {
418 game_state *s;
419 char *msg, *movestr;
420
421 msg = NULL;
422 movestr = me->ourgame->solve(me->states[0].state,
423 me->states[0].state,
424 me->aux_info, &msg);
425 assert(movestr && !msg);
426 s = me->ourgame->execute_move(me->states[0].state, movestr);
427 assert(s);
428 me->ourgame->free_game(s);
429 sfree(movestr);
430 }
431
75bed5b2 432 /*
433 * Soak test, enabled by setting <gamename>_TESTSOLVE in the
434 * environment. This causes an immediate attempt to re-solve the
435 * game without benefit of aux_info. The effect is that (at least
436 * on Unix) you can run 'FOO_TESTSOLVE=1 foo --generate 10000
437 * <params>#12345' and it will generate a lot of game ids and
438 * instantly pass each one back to the solver.
439 *
440 * (It's worth putting in an explicit seed in any such test, so
441 * you can repeat it to diagnose a problem if one comes up!)
442 */
443 {
444 char buf[80];
445 int j, k;
446 static int doing_test_solve = -1;
447 if (doing_test_solve < 0) {
448 sprintf(buf, "%s_TESTSOLVE", me->ourgame->name);
449 for (j = k = 0; buf[j]; j++)
450 if (!isspace((unsigned char)buf[j]))
451 buf[k++] = toupper((unsigned char)buf[j]);
452 buf[k] = '\0';
453 if (getenv(buf)) {
454 /*
455 * Since this is used for correctness testing, it's
456 * helpful to have a visual acknowledgment that the
457 * user hasn't mistyped the environment variable name.
458 */
459 fprintf(stderr, "Running solver soak tests\n");
460 doing_test_solve = TRUE;
461 } else {
462 doing_test_solve = FALSE;
463 }
464 }
465 if (doing_test_solve) {
466 game_state *s;
467 char *msg, *movestr;
468
469 msg = NULL;
470 movestr = me->ourgame->solve(me->states[0].state,
471 me->states[0].state,
472 NULL, &msg);
473 assert(movestr && !msg);
474 s = me->ourgame->execute_move(me->states[0].state, movestr);
475 assert(s);
476 me->ourgame->free_game(s);
477 sfree(movestr);
478 }
479 }
480
a4393230 481 me->states[me->nstates].movestr = NULL;
482 me->states[me->nstates].movetype = NEWGAME;
28d0118c 483 me->nstates++;
7f77ea24 484 me->statepos = 1;
dafd6cf6 485 me->drawstate = me->ourgame->new_drawstate(me->drawing,
486 me->states[0].state);
1e3e152d 487 midend_size_new_drawstate(me);
48dcdd62 488 me->elapsed = 0.0F;
74a4e547 489 if (me->ui)
be8d5aa1 490 me->ourgame->free_ui(me->ui);
28d0118c 491 me->ui = me->ourgame->new_ui(me->states[0].state);
4d08de49 492 midend_set_timer(me);
6776a950 493 me->pressed_mouse_button = 0;
7f77ea24 494}
495
ea6ffc86 496int midend_can_undo(midend *me)
497{
498 return (me->statepos > 1);
499}
500
501int midend_can_redo(midend *me)
502{
503 return (me->statepos < me->nstates);
504}
505
dafd6cf6 506static int midend_undo(midend *me)
7f77ea24 507{
1482ee76 508 if (me->statepos > 1) {
07dfb697 509 if (me->ui)
510 me->ourgame->changed_state(me->ui,
511 me->states[me->statepos-1].state,
512 me->states[me->statepos-2].state);
7f77ea24 513 me->statepos--;
c822de4a 514 me->dir = -1;
1482ee76 515 return 1;
516 } else
517 return 0;
7f77ea24 518}
519
dafd6cf6 520static int midend_redo(midend *me)
7f77ea24 521{
1482ee76 522 if (me->statepos < me->nstates) {
07dfb697 523 if (me->ui)
524 me->ourgame->changed_state(me->ui,
525 me->states[me->statepos-1].state,
526 me->states[me->statepos].state);
7f77ea24 527 me->statepos++;
c822de4a 528 me->dir = +1;
1482ee76 529 return 1;
530 } else
531 return 0;
7f77ea24 532}
533
dafd6cf6 534static void midend_finish_move(midend *me)
87ed82be 535{
536 float flashtime;
537
28d0118c 538 /*
539 * We do not flash if the later of the two states is special.
540 * This covers both forward Solve moves and backward (undone)
541 * Restart moves.
542 */
543 if ((me->oldstate || me->statepos > 1) &&
a4393230 544 ((me->dir > 0 && !special(me->states[me->statepos-1].movetype)) ||
28d0118c 545 (me->dir < 0 && me->statepos < me->nstates &&
a4393230 546 !special(me->states[me->statepos].movetype)))) {
be8d5aa1 547 flashtime = me->ourgame->flash_length(me->oldstate ? me->oldstate :
28d0118c 548 me->states[me->statepos-2].state,
549 me->states[me->statepos-1].state,
e3f21163 550 me->oldstate ? me->dir : +1,
551 me->ui);
87ed82be 552 if (flashtime > 0) {
553 me->flash_pos = 0.0F;
554 me->flash_time = flashtime;
555 }
556 }
557
558 if (me->oldstate)
be8d5aa1 559 me->ourgame->free_game(me->oldstate);
87ed82be 560 me->oldstate = NULL;
561 me->anim_pos = me->anim_time = 0;
c822de4a 562 me->dir = 0;
87ed82be 563
48dcdd62 564 midend_set_timer(me);
87ed82be 565}
566
dafd6cf6 567void midend_stop_anim(midend *me)
7f77ea24 568{
8317499a 569 if (me->oldstate || me->anim_time != 0) {
87ed82be 570 midend_finish_move(me);
2ef96bd6 571 midend_redraw(me);
572 }
dd216087 573}
574
dafd6cf6 575void midend_restart_game(midend *me)
28d0118c 576{
577 game_state *s;
578
7f89707c 579 midend_stop_anim(me);
580
28d0118c 581 assert(me->statepos >= 1);
582 if (me->statepos == 1)
583 return; /* no point doing anything at all! */
584
0a6892db 585 /*
586 * During restart, we reconstruct the game from the (public)
587 * game description rather than from states[0], because that
588 * way Mines gets slightly more sensible behaviour (restart
589 * goes to _after_ the first click so you don't have to
590 * remember where you clicked).
591 */
592 s = me->ourgame->new_game(me, me->params, me->desc);
28d0118c 593
594 /*
595 * Now enter the restarted state as the next move.
596 */
597 midend_stop_anim(me);
0c5f1313 598 midend_purge_states(me);
28d0118c 599 ensure(me);
600 me->states[me->nstates].state = s;
a4393230 601 me->states[me->nstates].movestr = dupstr(me->desc);
602 me->states[me->nstates].movetype = RESTART;
28d0118c 603 me->statepos = ++me->nstates;
07dfb697 604 if (me->ui)
605 me->ourgame->changed_state(me->ui,
606 me->states[me->statepos-2].state,
607 me->states[me->statepos-1].state);
28d0118c 608 me->anim_time = 0.0;
609 midend_finish_move(me);
610 midend_redraw(me);
48dcdd62 611 midend_set_timer(me);
28d0118c 612}
613
dafd6cf6 614static int midend_really_process_key(midend *me, int x, int y, int button)
dd216087 615{
28d0118c 616 game_state *oldstate =
617 me->ourgame->dup_game(me->states[me->statepos - 1].state);
9d6c3859 618 int type = MOVE, gottype = FALSE, ret = 1;
dd216087 619 float anim_time;
4c692867 620 game_state *s;
621 char *movestr;
622
623 movestr =
624 me->ourgame->interpret_move(me->states[me->statepos-1].state,
625 me->ui, me->drawstate, x, y, button);
7f77ea24 626
4c692867 627 if (!movestr) {
628 if (button == 'n' || button == 'N' || button == '\x0E') {
629 midend_stop_anim(me);
630 midend_new_game(me);
631 midend_redraw(me);
632 goto done; /* never animate */
633 } else if (button == 'u' || button == 'u' ||
634 button == '\x1A' || button == '\x1F') {
635 midend_stop_anim(me);
9d6c3859 636 type = me->states[me->statepos-1].movetype;
637 gottype = TRUE;
4c692867 638 if (!midend_undo(me))
639 goto done;
640 } else if (button == 'r' || button == 'R' ||
641 button == '\x12' || button == '\x19') {
642 midend_stop_anim(me);
643 if (!midend_redo(me))
644 goto done;
640a235e 645 } else if (button == '\x13' && me->ourgame->can_solve) {
646 if (midend_solve(me))
647 goto done;
4c692867 648 } else if (button == 'q' || button == 'Q' || button == '\x11') {
649 ret = 0;
650 goto done;
651 } else
ab53eb64 652 goto done;
2ef96bd6 653 } else {
4c692867 654 if (!*movestr)
df11cd4e 655 s = me->states[me->statepos-1].state;
656 else {
657 s = me->ourgame->execute_move(me->states[me->statepos-1].state,
658 movestr);
659 assert(s != NULL);
df11cd4e 660 }
74a4e547 661
28d0118c 662 if (s == me->states[me->statepos-1].state) {
74a4e547 663 /*
664 * make_move() is allowed to return its input state to
665 * indicate that although no move has been made, the UI
666 * state has been updated and a redraw is called for.
667 */
668 midend_redraw(me);
0eb3b899 669 midend_set_timer(me);
ab53eb64 670 goto done;
74a4e547 671 } else if (s) {
dd216087 672 midend_stop_anim(me);
0c5f1313 673 midend_purge_states(me);
2ef96bd6 674 ensure(me);
a4393230 675 assert(movestr != NULL);
28d0118c 676 me->states[me->nstates].state = s;
a4393230 677 me->states[me->nstates].movestr = movestr;
678 me->states[me->nstates].movetype = MOVE;
2ef96bd6 679 me->statepos = ++me->nstates;
c822de4a 680 me->dir = +1;
faff1e07 681 if (me->ui)
682 me->ourgame->changed_state(me->ui,
683 me->states[me->statepos-2].state,
684 me->states[me->statepos-1].state);
2ef96bd6 685 } else {
a4393230 686 goto done;
2ef96bd6 687 }
7f77ea24 688 }
689
9d6c3859 690 if (!gottype)
691 type = me->states[me->statepos-1].movetype;
28d0118c 692
2ef96bd6 693 /*
694 * See if this move requires an animation.
695 */
9d6c3859 696 if (special(type) && !(type == SOLVE &&
2705d374 697 (me->ourgame->flags & SOLVE_ANIMATES))) {
28d0118c 698 anim_time = 0;
699 } else {
700 anim_time = me->ourgame->anim_length(oldstate,
701 me->states[me->statepos-1].state,
e3f21163 702 me->dir, me->ui);
28d0118c 703 }
2ef96bd6 704
ab53eb64 705 me->oldstate = oldstate; oldstate = NULL;
2ef96bd6 706 if (anim_time > 0) {
2ef96bd6 707 me->anim_time = anim_time;
708 } else {
2ef96bd6 709 me->anim_time = 0.0;
87ed82be 710 midend_finish_move(me);
7f77ea24 711 }
2ef96bd6 712 me->anim_pos = 0.0;
713
714 midend_redraw(me);
715
48dcdd62 716 midend_set_timer(me);
7f77ea24 717
ab53eb64 718 done:
719 if (oldstate) me->ourgame->free_game(oldstate);
720 return ret;
7f77ea24 721}
2ef96bd6 722
dafd6cf6 723int midend_process_key(midend *me, int x, int y, int button)
6776a950 724{
725 int ret = 1;
726
727 /*
728 * Harmonise mouse drag and release messages.
729 *
730 * Some front ends might accidentally switch from sending, say,
731 * RIGHT_DRAG messages to sending LEFT_DRAG, half way through a
732 * drag. (This can happen on the Mac, for example, since
733 * RIGHT_DRAG is usually done using Command+drag, and if the
734 * user accidentally releases Command half way through the drag
735 * then there will be trouble.)
736 *
737 * It would be an O(number of front ends) annoyance to fix this
738 * in the front ends, but an O(number of back ends) annoyance
739 * to have each game capable of dealing with it. Therefore, we
740 * fix it _here_ in the common midend code so that it only has
741 * to be done once.
742 *
eeba2afe 743 * The possible ways in which things can go screwy in the front
744 * end are:
6776a950 745 *
eeba2afe 746 * - in a system containing multiple physical buttons button
747 * presses can inadvertently overlap. We can see ABab (caps
748 * meaning button-down and lowercase meaning button-up) when
749 * the user had semantically intended AaBb.
6776a950 750 *
eeba2afe 751 * - in a system where one button is simulated by means of a
752 * modifier key and another button, buttons can mutate
753 * between press and release (possibly during drag). So we
754 * can see Ab instead of Aa.
6776a950 755 *
eeba2afe 756 * Definite requirements are:
757 *
758 * - button _presses_ must never be invented or destroyed. If
759 * the user presses two buttons in succession, the button
760 * presses must be transferred to the backend unchanged. So
761 * if we see AaBb , that's fine; if we see ABab (the button
762 * presses inadvertently overlapped) we must somehow
763 * `correct' it to AaBb.
764 *
765 * - every mouse action must end up looking like a press, zero
766 * or more drags, then a release. This allows back ends to
767 * make the _assumption_ that incoming mouse data will be
768 * sane in this regard, and not worry about the details.
769 *
770 * So my policy will be:
771 *
772 * - treat any button-up as a button-up for the currently
773 * pressed button, or ignore it if there is no currently
774 * pressed button.
775 *
776 * - treat any drag as a drag for the currently pressed
777 * button, or ignore it if there is no currently pressed
778 * button.
779 *
780 * - if we see a button-down while another button is currently
781 * pressed, invent a button-up for the first one and then
782 * pass the button-down through as before.
6776a950 783 *
93b1da3d 784 * 2005-05-31: An addendum to the above. Some games might want
785 * a `priority order' among buttons, such that if one button is
786 * pressed while another is down then a fixed one of the
787 * buttons takes priority no matter what order they're pressed
788 * in. Mines, in particular, wants to treat a left+right click
789 * like a left click for the benefit of users of other
790 * implementations. So the last of the above points is modified
791 * in the presence of an (optional) button priority order.
4a9957b6 792 *
793 * A further addition: we translate certain keyboard presses to
794 * cursor key 'select' buttons, so that a) frontends don't have
795 * to translate these themselves (like they do for CURSOR_UP etc),
796 * and b) individual games don't have to hard-code button presses
797 * of '\n' etc for keyboard-based cursors. The choice of buttons
798 * here could eventually be controlled by a runtime configuration
799 * option.
6776a950 800 */
801 if (IS_MOUSE_DRAG(button) || IS_MOUSE_RELEASE(button)) {
eeba2afe 802 if (me->pressed_mouse_button) {
803 if (IS_MOUSE_DRAG(button)) {
804 button = me->pressed_mouse_button +
805 (LEFT_DRAG - LEFT_BUTTON);
806 } else {
807 button = me->pressed_mouse_button +
808 (LEFT_RELEASE - LEFT_BUTTON);
6776a950 809 }
eeba2afe 810 } else
811 return ret; /* ignore it */
6776a950 812 } else if (IS_MOUSE_DOWN(button) && me->pressed_mouse_button) {
93b1da3d 813 /*
814 * If the new button has lower priority than the old one,
815 * don't bother doing this.
816 */
2705d374 817 if (me->ourgame->flags &
93b1da3d 818 BUTTON_BEATS(me->pressed_mouse_button, button))
819 return ret; /* just ignore it */
820
6776a950 821 /*
822 * Fabricate a button-up for the previously pressed button.
823 */
824 ret = ret && midend_really_process_key
825 (me, x, y, (me->pressed_mouse_button +
826 (LEFT_RELEASE - LEFT_BUTTON)));
827 }
828
829 /*
4a9957b6 830 * Translate keyboard presses to cursor selection.
831 */
832 if (button == '\n' || button == '\r')
833 button = CURSOR_SELECT;
834 if (button == ' ')
835 button = CURSOR_SELECT2;
836
837 /*
7b97f218 838 * Normalise both backspace characters (8 and 127) to \b. Easier
839 * to do this once, here, than to require all front ends to
840 * carefully generate the same one - now each front end can
841 * generate whichever is easiest.
842 */
843 if (button == '\177')
844 button = '\b';
845
846 /*
6776a950 847 * Now send on the event we originally received.
848 */
849 ret = ret && midend_really_process_key(me, x, y, button);
850
851 /*
852 * And update the currently pressed button.
853 */
854 if (IS_MOUSE_RELEASE(button))
855 me->pressed_mouse_button = 0;
856 else if (IS_MOUSE_DOWN(button))
857 me->pressed_mouse_button = button;
858
859 return ret;
860}
861
dafd6cf6 862void midend_redraw(midend *me)
2ef96bd6 863{
dafd6cf6 864 assert(me->drawing);
865
2ef96bd6 866 if (me->statepos > 0 && me->drawstate) {
dafd6cf6 867 start_draw(me->drawing);
2ef96bd6 868 if (me->oldstate && me->anim_time > 0 &&
869 me->anim_pos < me->anim_time) {
c822de4a 870 assert(me->dir != 0);
dafd6cf6 871 me->ourgame->redraw(me->drawing, me->drawstate, me->oldstate,
28d0118c 872 me->states[me->statepos-1].state, me->dir,
be8d5aa1 873 me->ui, me->anim_pos, me->flash_pos);
2ef96bd6 874 } else {
dafd6cf6 875 me->ourgame->redraw(me->drawing, me->drawstate, NULL,
28d0118c 876 me->states[me->statepos-1].state, +1 /*shrug*/,
be8d5aa1 877 me->ui, 0.0, me->flash_pos);
2ef96bd6 878 }
dafd6cf6 879 end_draw(me->drawing);
2ef96bd6 880 }
881}
882
afc306fc 883/*
884 * Nasty hacky function used to implement the --redo option in
885 * gtk.c. Only used for generating the puzzles' icons.
886 */
887void midend_freeze_timer(midend *me, float tprop)
888{
889 me->anim_pos = me->anim_time * tprop;
890 midend_redraw(me);
891 deactivate_timer(me->frontend);
892}
893
dafd6cf6 894void midend_timer(midend *me, float tplus)
2ef96bd6 895{
0eb3b899 896 int need_redraw = (me->anim_time > 0 || me->flash_time > 0);
897
2ef96bd6 898 me->anim_pos += tplus;
899 if (me->anim_pos >= me->anim_time ||
900 me->anim_time == 0 || !me->oldstate) {
87ed82be 901 if (me->anim_time > 0)
902 midend_finish_move(me);
903 }
48dcdd62 904
87ed82be 905 me->flash_pos += tplus;
906 if (me->flash_pos >= me->flash_time || me->flash_time == 0) {
907 me->flash_pos = me->flash_time = 0;
2ef96bd6 908 }
48dcdd62 909
0eb3b899 910 if (need_redraw)
911 midend_redraw(me);
48dcdd62 912
913 if (me->timing) {
914 float oldelapsed = me->elapsed;
915 me->elapsed += tplus;
916 if ((int)oldelapsed != (int)me->elapsed)
dafd6cf6 917 status_bar(me->drawing, me->laststatus ? me->laststatus : "");
48dcdd62 918 }
919
920 midend_set_timer(me);
2ef96bd6 921}
922
dafd6cf6 923float *midend_colours(midend *me, int *ncolours)
2ef96bd6 924{
2ef96bd6 925 float *ret;
926
8266f3fc 927 ret = me->ourgame->colours(me->frontend, ncolours);
2ef96bd6 928
813593cc 929 {
930 int i;
931
932 /*
933 * Allow environment-based overrides for the standard
934 * colours by defining variables along the lines of
935 * `NET_COLOUR_4=6000c0'.
936 */
937
938 for (i = 0; i < *ncolours; i++) {
939 char buf[80], *e;
940 unsigned int r, g, b;
056405ec 941 int j, k;
813593cc 942
943 sprintf(buf, "%s_COLOUR_%d", me->ourgame->name, i);
056405ec 944 for (j = k = 0; buf[j]; j++)
945 if (!isspace((unsigned char)buf[j]))
946 buf[k++] = toupper((unsigned char)buf[j]);
947 buf[k] = '\0';
813593cc 948 if ((e = getenv(buf)) != NULL &&
949 sscanf(e, "%2x%2x%2x", &r, &g, &b) == 3) {
5b502ae8 950 ret[i*3 + 0] = r / 255.0F;
951 ret[i*3 + 1] = g / 255.0F;
952 ret[i*3 + 2] = b / 255.0F;
813593cc 953 }
954 }
955 }
956
2ef96bd6 957 return ret;
958}
eb2ad6f1 959
dafd6cf6 960int midend_num_presets(midend *me)
eb2ad6f1 961{
962 if (!me->npresets) {
963 char *name;
964 game_params *preset;
965
be8d5aa1 966 while (me->ourgame->fetch_preset(me->npresets, &name, &preset)) {
eb2ad6f1 967 if (me->presetsize <= me->npresets) {
968 me->presetsize = me->npresets + 10;
969 me->presets = sresize(me->presets, me->presetsize,
970 game_params *);
971 me->preset_names = sresize(me->preset_names, me->presetsize,
972 char *);
f92acd1a 973 me->preset_encodings = sresize(me->preset_encodings,
974 me->presetsize, char *);
eb2ad6f1 975 }
976
977 me->presets[me->npresets] = preset;
978 me->preset_names[me->npresets] = name;
f92acd1a 979 me->preset_encodings[me->npresets] =
980 me->ourgame->encode_params(preset, TRUE);;
eb2ad6f1 981 me->npresets++;
982 }
983 }
984
813593cc 985 {
986 /*
987 * Allow environment-based extensions to the preset list by
988 * defining a variable along the lines of `SOLO_PRESETS=2x3
989 * Advanced:2x3da'. Colon-separated list of items,
990 * alternating between textual titles in the menu and
991 * encoded parameter strings.
992 */
993 char buf[80], *e, *p;
056405ec 994 int j, k;
813593cc 995
996 sprintf(buf, "%s_PRESETS", me->ourgame->name);
056405ec 997 for (j = k = 0; buf[j]; j++)
998 if (!isspace((unsigned char)buf[j]))
999 buf[k++] = toupper((unsigned char)buf[j]);
1000 buf[k] = '\0';
813593cc 1001
1002 if ((e = getenv(buf)) != NULL) {
1003 p = e = dupstr(e);
1004
1005 while (*p) {
1006 char *name, *val;
1007 game_params *preset;
1008
1009 name = p;
1010 while (*p && *p != ':') p++;
1011 if (*p) *p++ = '\0';
1012 val = p;
1013 while (*p && *p != ':') p++;
1014 if (*p) *p++ = '\0';
1015
1016 preset = me->ourgame->default_params();
1017 me->ourgame->decode_params(preset, val);
1018
3ff276f2 1019 if (me->ourgame->validate_params(preset, TRUE)) {
239ba6e6 1020 /* Drop this one from the list. */
1021 me->ourgame->free_params(preset);
1022 continue;
1023 }
1024
813593cc 1025 if (me->presetsize <= me->npresets) {
1026 me->presetsize = me->npresets + 10;
1027 me->presets = sresize(me->presets, me->presetsize,
1028 game_params *);
1029 me->preset_names = sresize(me->preset_names,
1030 me->presetsize, char *);
f92acd1a 1031 me->preset_encodings = sresize(me->preset_encodings,
1032 me->presetsize, char *);
813593cc 1033 }
1034
1035 me->presets[me->npresets] = preset;
cb64d2dd 1036 me->preset_names[me->npresets] = dupstr(name);
f92acd1a 1037 me->preset_encodings[me->npresets] =
1038 me->ourgame->encode_params(preset, TRUE);
813593cc 1039 me->npresets++;
1040 }
9a6d429a 1041 sfree(e);
813593cc 1042 }
1043 }
1044
eb2ad6f1 1045 return me->npresets;
1046}
1047
dafd6cf6 1048void midend_fetch_preset(midend *me, int n,
eb2ad6f1 1049 char **name, game_params **params)
1050{
1051 assert(n >= 0 && n < me->npresets);
1052 *name = me->preset_names[n];
1053 *params = me->presets[n];
1054}
fd1a1a2b 1055
f92acd1a 1056int midend_which_preset(midend *me)
1057{
1058 char *encoding = me->ourgame->encode_params(me->params, TRUE);
1059 int i, ret;
1060
1061 ret = -1;
1062 for (i = 0; i < me->npresets; i++)
1063 if (!strcmp(encoding, me->preset_encodings[i])) {
1064 ret = i;
1065 break;
1066 }
1067
1068 sfree(encoding);
1069 return ret;
1070}
1071
dafd6cf6 1072int midend_wants_statusbar(midend *me)
fd1a1a2b 1073{
ac9f41c4 1074 return me->ourgame->wants_statusbar;
fd1a1a2b 1075}
c8230524 1076
dafd6cf6 1077void midend_supersede_game_desc(midend *me, char *desc, char *privdesc)
c380832d 1078{
1079 sfree(me->desc);
0a6892db 1080 sfree(me->privdesc);
c380832d 1081 me->desc = dupstr(desc);
0a6892db 1082 me->privdesc = privdesc ? dupstr(privdesc) : NULL;
c380832d 1083}
1084
dafd6cf6 1085config_item *midend_get_config(midend *me, int which, char **wintitle)
c8230524 1086{
ab53eb64 1087 char *titlebuf, *parstr, *rest;
5928817c 1088 config_item *ret;
ab53eb64 1089 char sep;
5928817c 1090
ab53eb64 1091 assert(wintitle);
be8d5aa1 1092 titlebuf = snewn(40 + strlen(me->ourgame->name), char);
5928817c 1093
1094 switch (which) {
1095 case CFG_SETTINGS:
be8d5aa1 1096 sprintf(titlebuf, "%s configuration", me->ourgame->name);
ab53eb64 1097 *wintitle = titlebuf;
be8d5aa1 1098 return me->ourgame->configure(me->params);
5928817c 1099 case CFG_SEED:
1185e3c5 1100 case CFG_DESC:
ab53eb64 1101 if (!me->curparams) {
1102 sfree(titlebuf);
1103 return NULL;
1104 }
1105 sprintf(titlebuf, "%s %s selection", me->ourgame->name,
1185e3c5 1106 which == CFG_SEED ? "random" : "game");
ab53eb64 1107 *wintitle = titlebuf;
5928817c 1108
1109 ret = snewn(2, config_item);
1110
1111 ret[0].type = C_STRING;
1185e3c5 1112 if (which == CFG_SEED)
1113 ret[0].name = "Game random seed";
1114 else
1115 ret[0].name = "Game ID";
5928817c 1116 ret[0].ival = 0;
b0e26073 1117 /*
1185e3c5 1118 * For CFG_DESC the text going in here will be a string
1119 * encoding of the restricted parameters, plus a colon,
1120 * plus the game description. For CFG_SEED it will be the
1121 * full parameters, plus a hash, plus the random seed data.
1122 * Either of these is a valid full game ID (although only
1123 * the former is likely to persist across many code
1124 * changes).
b0e26073 1125 */
f60f7e7c 1126 parstr = me->ourgame->encode_params(me->curparams, which == CFG_SEED);
ab53eb64 1127 assert(parstr);
1185e3c5 1128 if (which == CFG_DESC) {
ab53eb64 1129 rest = me->desc ? me->desc : "";
1130 sep = ':';
1185e3c5 1131 } else {
ab53eb64 1132 rest = me->seedstr ? me->seedstr : "";
1133 sep = '#';
1185e3c5 1134 }
ab53eb64 1135 ret[0].sval = snewn(strlen(parstr) + strlen(rest) + 2, char);
1136 sprintf(ret[0].sval, "%s%c%s", parstr, sep, rest);
b0e26073 1137 sfree(parstr);
5928817c 1138
1139 ret[1].type = C_END;
1140 ret[1].name = ret[1].sval = NULL;
1141 ret[1].ival = 0;
1142
1143 return ret;
1144 }
1145
1146 assert(!"We shouldn't be here");
1147 return NULL;
c8230524 1148}
1149
dafd6cf6 1150static char *midend_game_id_int(midend *me, char *id, int defmode)
8b7938e7 1151{
1185e3c5 1152 char *error, *par, *desc, *seed;
5c9f61fd 1153 game_params *newcurparams, *newparams, *oldparams1, *oldparams2;
1154 int free_params;
8b7938e7 1155
1185e3c5 1156 seed = strchr(id, '#');
1157 desc = strchr(id, ':');
8b7938e7 1158
1185e3c5 1159 if (desc && (!seed || desc < seed)) {
1160 /*
1161 * We have a colon separating parameters from game
1162 * description. So `par' now points to the parameters
1163 * string, and `desc' to the description string.
1164 */
1165 *desc++ = '\0';
1166 par = id;
1167 seed = NULL;
1168 } else if (seed && (!desc || seed < desc)) {
8b7938e7 1169 /*
50ff5730 1170 * We have a hash separating parameters from random seed.
1185e3c5 1171 * So `par' now points to the parameters string, and `seed'
1172 * to the seed string.
8b7938e7 1173 */
1174 *seed++ = '\0';
1175 par = id;
1185e3c5 1176 desc = NULL;
8b7938e7 1177 } else {
1178 /*
1185e3c5 1179 * We only have one string. Depending on `defmode', we take
1180 * it to be either parameters, seed or description.
8b7938e7 1181 */
1185e3c5 1182 if (defmode == DEF_SEED) {
8b7938e7 1183 seed = id;
1185e3c5 1184 par = desc = NULL;
1185 } else if (defmode == DEF_DESC) {
1186 desc = id;
1187 par = seed = NULL;
8b7938e7 1188 } else {
8b7938e7 1189 par = id;
1185e3c5 1190 seed = desc = NULL;
8b7938e7 1191 }
1192 }
1193
5c9f61fd 1194 /*
1195 * We must be reasonably careful here not to modify anything in
1196 * `me' until we have finished validating things. This function
1197 * must either return an error and do nothing to the midend, or
1198 * return success and do everything; nothing in between is
1199 * acceptable.
1200 */
1201 newcurparams = newparams = oldparams1 = oldparams2 = NULL;
1202
8b7938e7 1203 if (par) {
5c9f61fd 1204 newcurparams = me->ourgame->dup_params(me->params);
1205 me->ourgame->decode_params(newcurparams, par);
3ff276f2 1206 error = me->ourgame->validate_params(newcurparams, desc == NULL);
8b7938e7 1207 if (error) {
5c9f61fd 1208 me->ourgame->free_params(newcurparams);
8b7938e7 1209 return error;
1210 }
5c9f61fd 1211 oldparams1 = me->curparams;
1185e3c5 1212
1213 /*
1214 * Now filter only the persistent parts of this state into
1215 * the long-term params structure, unless we've _only_
1216 * received a params string in which case the whole lot is
1217 * persistent.
1218 */
5c9f61fd 1219 oldparams2 = me->params;
1185e3c5 1220 if (seed || desc) {
5c9f61fd 1221 char *tmpstr;
1222
1223 newparams = me->ourgame->dup_params(me->params);
1224
1225 tmpstr = me->ourgame->encode_params(newcurparams, FALSE);
1226 me->ourgame->decode_params(newparams, tmpstr);
1227
3af70dd4 1228 sfree(tmpstr);
1185e3c5 1229 } else {
5c9f61fd 1230 newparams = me->ourgame->dup_params(newcurparams);
1185e3c5 1231 }
5c9f61fd 1232 free_params = TRUE;
1233 } else {
1234 newcurparams = me->curparams;
1235 newparams = me->params;
1236 free_params = FALSE;
8b7938e7 1237 }
1238
5c9f61fd 1239 if (desc) {
1240 error = me->ourgame->validate_desc(newparams, desc);
1241 if (error) {
1242 if (free_params) {
1243 if (newcurparams)
1244 me->ourgame->free_params(newcurparams);
1245 if (newparams)
1246 me->ourgame->free_params(newparams);
1247 }
1248 return error;
1249 }
1250 }
1251
1252 /*
1253 * Now we've got past all possible error points. Update the
1254 * midend itself.
1255 */
1256 me->params = newparams;
1257 me->curparams = newcurparams;
1258 if (oldparams1)
1259 me->ourgame->free_params(oldparams1);
1260 if (oldparams2)
1261 me->ourgame->free_params(oldparams2);
1262
3af70dd4 1263 sfree(me->desc);
0a6892db 1264 sfree(me->privdesc);
1265 me->desc = me->privdesc = NULL;
3af70dd4 1266 sfree(me->seedstr);
1267 me->seedstr = NULL;
1268
1185e3c5 1269 if (desc) {
1185e3c5 1270 me->desc = dupstr(desc);
1271 me->genmode = GOT_DESC;
c566778e 1272 sfree(me->aux_info);
6f2d8d7c 1273 me->aux_info = NULL;
8b7938e7 1274 }
1275
1185e3c5 1276 if (seed) {
1185e3c5 1277 me->seedstr = dupstr(seed);
1278 me->genmode = GOT_SEED;
1279 }
1280
8b7938e7 1281 return NULL;
1282}
1283
dafd6cf6 1284char *midend_game_id(midend *me, char *id)
1185e3c5 1285{
1286 return midend_game_id_int(me, id, DEF_PARAMS);
1287}
1288
dafd6cf6 1289char *midend_get_game_id(midend *me)
1290{
1291 char *parstr, *ret;
1292
1293 parstr = me->ourgame->encode_params(me->curparams, FALSE);
1294 assert(parstr);
1295 assert(me->desc);
1296 ret = snewn(strlen(parstr) + strlen(me->desc) + 2, char);
1297 sprintf(ret, "%s:%s", parstr, me->desc);
1298 sfree(parstr);
1299 return ret;
1300}
1301
1302char *midend_set_config(midend *me, int which, config_item *cfg)
c8230524 1303{
8b7938e7 1304 char *error;
c8230524 1305 game_params *params;
1306
5928817c 1307 switch (which) {
1308 case CFG_SETTINGS:
be8d5aa1 1309 params = me->ourgame->custom_params(cfg);
3ff276f2 1310 error = me->ourgame->validate_params(params, TRUE);
c8230524 1311
5928817c 1312 if (error) {
be8d5aa1 1313 me->ourgame->free_params(params);
5928817c 1314 return error;
1315 }
c8230524 1316
be8d5aa1 1317 me->ourgame->free_params(me->params);
5928817c 1318 me->params = params;
1319 break;
1320
1321 case CFG_SEED:
1185e3c5 1322 case CFG_DESC:
1323 error = midend_game_id_int(me, cfg[0].sval,
1324 (which == CFG_SEED ? DEF_SEED : DEF_DESC));
5928817c 1325 if (error)
1326 return error;
5928817c 1327 break;
1328 }
c8230524 1329
1330 return NULL;
1331}
9b4b03d3 1332
fa3abef5 1333int midend_can_format_as_text_now(midend *me)
1334{
1335 if (me->ourgame->can_format_as_text_ever)
1336 return me->ourgame->can_format_as_text_now(me->params);
1337 else
1338 return FALSE;
1339}
1340
dafd6cf6 1341char *midend_text_format(midend *me)
9b4b03d3 1342{
fa3abef5 1343 if (me->ourgame->can_format_as_text_ever && me->statepos > 0 &&
1344 me->ourgame->can_format_as_text_now(me->params))
28d0118c 1345 return me->ourgame->text_format(me->states[me->statepos-1].state);
9b4b03d3 1346 else
1347 return NULL;
1348}
2ac6d24e 1349
dafd6cf6 1350char *midend_solve(midend *me)
2ac6d24e 1351{
1352 game_state *s;
df11cd4e 1353 char *msg, *movestr;
2ac6d24e 1354
1355 if (!me->ourgame->can_solve)
1356 return "This game does not support the Solve operation";
1357
1358 if (me->statepos < 1)
1359 return "No game set up to solve"; /* _shouldn't_ happen! */
1360
821f9f32 1361 msg = NULL;
df11cd4e 1362 movestr = me->ourgame->solve(me->states[0].state,
1363 me->states[me->statepos-1].state,
1364 me->aux_info, &msg);
821f9f32 1365 if (!movestr) {
1366 if (!msg)
1367 msg = "Solve operation failed"; /* _shouldn't_ happen, but can */
2ac6d24e 1368 return msg;
821f9f32 1369 }
df11cd4e 1370 s = me->ourgame->execute_move(me->states[me->statepos-1].state, movestr);
1371 assert(s);
2ac6d24e 1372
1373 /*
28d0118c 1374 * Now enter the solved state as the next move.
2ac6d24e 1375 */
1376 midend_stop_anim(me);
0c5f1313 1377 midend_purge_states(me);
2ac6d24e 1378 ensure(me);
28d0118c 1379 me->states[me->nstates].state = s;
a4393230 1380 me->states[me->nstates].movestr = movestr;
1381 me->states[me->nstates].movetype = SOLVE;
2ac6d24e 1382 me->statepos = ++me->nstates;
07dfb697 1383 if (me->ui)
1384 me->ourgame->changed_state(me->ui,
1385 me->states[me->statepos-2].state,
1386 me->states[me->statepos-1].state);
9d6c3859 1387 me->dir = +1;
2705d374 1388 if (me->ourgame->flags & SOLVE_ANIMATES) {
9d6c3859 1389 me->oldstate = me->ourgame->dup_game(me->states[me->statepos-2].state);
1390 me->anim_time =
1391 me->ourgame->anim_length(me->states[me->statepos-2].state,
1392 me->states[me->statepos-1].state,
1393 +1, me->ui);
e80e7b71 1394 me->anim_pos = 0.0;
9d6c3859 1395 } else {
1396 me->anim_time = 0.0;
1397 midend_finish_move(me);
1398 }
91d3791c 1399 if (me->drawing)
1400 midend_redraw(me);
48dcdd62 1401 midend_set_timer(me);
2ac6d24e 1402 return NULL;
1403}
48dcdd62 1404
1cea529f 1405int midend_status(midend *me)
4496362f 1406{
1407 /*
1408 * We should probably never be called when the state stack has no
1409 * states on it at all - ideally, midends should never be left in
1410 * that state for long enough to get put down and forgotten about.
1411 * But if we are, I think we return _true_ - pedantically speaking
1412 * a midend in that state is 'vacuously solved', and more
1413 * practically, a user whose midend has been left in that state
1414 * probably _does_ want the 'new game' option to be prominent.
1415 */
1cea529f 1416 if (me->statepos == 0)
1417 return +1;
1418
1419 return me->ourgame->status(me->states[me->statepos-1].state);
4496362f 1420}
1421
dafd6cf6 1422char *midend_rewrite_statusbar(midend *me, char *text)
48dcdd62 1423{
1424 /*
1425 * An important special case is that we are occasionally called
1426 * with our own laststatus, to update the timer.
1427 */
1428 if (me->laststatus != text) {
1429 sfree(me->laststatus);
1430 me->laststatus = dupstr(text);
1431 }
1432
1433 if (me->ourgame->is_timed) {
1434 char timebuf[100], *ret;
1435 int min, sec;
1436
5b502ae8 1437 sec = (int)me->elapsed;
48dcdd62 1438 min = sec / 60;
1439 sec %= 60;
1440 sprintf(timebuf, "[%d:%02d] ", min, sec);
1441
1442 ret = snewn(strlen(timebuf) + strlen(text) + 1, char);
1443 strcpy(ret, timebuf);
1444 strcat(ret, text);
1445 return ret;
1446
1447 } else {
1448 return dupstr(text);
1449 }
1450}
a4393230 1451
1452#define SERIALISE_MAGIC "Simon Tatham's Portable Puzzle Collection"
1453#define SERIALISE_VERSION "1"
1454
dafd6cf6 1455void midend_serialise(midend *me,
a4393230 1456 void (*write)(void *ctx, void *buf, int len),
1457 void *wctx)
1458{
1459 int i;
1460
1461 /*
1462 * Each line of the save file contains three components. First
1463 * exactly 8 characters of header word indicating what type of
1464 * data is contained on the line; then a colon followed by a
1465 * decimal integer giving the length of the main string on the
1466 * line; then a colon followed by the string itself (exactly as
1467 * many bytes as previously specified, no matter what they
1468 * contain). Then a newline (of reasonably flexible form).
1469 */
1470#define wr(h,s) do { \
1471 char hbuf[80]; \
1472 char *str = (s); \
48d4740c 1473 sprintf(hbuf, "%-8.8s:%d:", (h), (int)strlen(str)); \
a4393230 1474 write(wctx, hbuf, strlen(hbuf)); \
1475 write(wctx, str, strlen(str)); \
1476 write(wctx, "\n", 1); \
1477} while (0)
1478
1479 /*
1480 * Magic string identifying the file, and version number of the
1481 * file format.
1482 */
1483 wr("SAVEFILE", SERIALISE_MAGIC);
1484 wr("VERSION", SERIALISE_VERSION);
1485
1486 /*
1487 * The game name. (Copied locally to avoid const annoyance.)
1488 */
1489 {
1490 char *s = dupstr(me->ourgame->name);
1491 wr("GAME", s);
1492 sfree(s);
1493 }
1494
1495 /*
1496 * The current long-term parameters structure, in full.
1497 */
1498 if (me->params) {
1499 char *s = me->ourgame->encode_params(me->params, TRUE);
1500 wr("PARAMS", s);
1501 sfree(s);
1502 }
1503
1504 /*
1505 * The current short-term parameters structure, in full.
1506 */
1507 if (me->curparams) {
1508 char *s = me->ourgame->encode_params(me->curparams, TRUE);
1509 wr("CPARAMS", s);
1510 sfree(s);
1511 }
1512
1513 /*
1514 * The current game description, the privdesc, and the random seed.
1515 */
1516 if (me->seedstr)
1517 wr("SEED", me->seedstr);
1518 if (me->desc)
1519 wr("DESC", me->desc);
1520 if (me->privdesc)
1521 wr("PRIVDESC", me->privdesc);
1522
1523 /*
1524 * The game's aux_info. We obfuscate this to prevent spoilers
1525 * (people are likely to run `head' or similar on a saved game
1526 * file simply to find out what it is, and don't necessarily
1527 * want to be told the answer to the puzzle!)
1528 */
1529 if (me->aux_info) {
1530 unsigned char *s1;
1531 char *s2;
1532 int len;
1533
1534 len = strlen(me->aux_info);
1535 s1 = snewn(len, unsigned char);
1536 memcpy(s1, me->aux_info, len);
1537 obfuscate_bitmap(s1, len*8, FALSE);
1538 s2 = bin2hex(s1, len);
1539
1540 wr("AUXINFO", s2);
1541
1542 sfree(s2);
1543 sfree(s1);
1544 }
1545
1546 /*
1547 * Any required serialisation of the game_ui.
1548 */
1549 if (me->ui) {
1550 char *s = me->ourgame->encode_ui(me->ui);
1551 if (s) {
1552 wr("UI", s);
1553 sfree(s);
1554 }
1555 }
1556
1557 /*
1558 * The game time, if it's a timed game.
1559 */
1560 if (me->ourgame->is_timed) {
1561 char buf[80];
1562 sprintf(buf, "%g", me->elapsed);
1563 wr("TIME", buf);
1564 }
1565
1566 /*
1567 * The length of, and position in, the states list.
1568 */
1569 {
1570 char buf[80];
1571 sprintf(buf, "%d", me->nstates);
1572 wr("NSTATES", buf);
1573 sprintf(buf, "%d", me->statepos);
1574 wr("STATEPOS", buf);
1575 }
1576
1577 /*
1578 * For each state after the initial one (which we know is
1579 * constructed from either privdesc or desc), enough
1580 * information for execute_move() to reconstruct it from the
1581 * previous one.
1582 */
1583 for (i = 1; i < me->nstates; i++) {
1584 assert(me->states[i].movetype != NEWGAME); /* only state 0 */
1585 switch (me->states[i].movetype) {
1586 case MOVE:
1587 wr("MOVE", me->states[i].movestr);
1588 break;
1589 case SOLVE:
1590 wr("SOLVE", me->states[i].movestr);
1591 break;
1592 case RESTART:
1593 wr("RESTART", me->states[i].movestr);
1594 break;
1595 }
1596 }
1597
1598#undef wr
1599}
1600
1601/*
1602 * This function returns NULL on success, or an error message.
1603 */
dafd6cf6 1604char *midend_deserialise(midend *me,
a4393230 1605 int (*read)(void *ctx, void *buf, int len),
1606 void *rctx)
1607{
1608 int nstates = 0, statepos = -1, gotstates = 0;
1609 int started = FALSE;
1610 int i;
1611
1612 char *val = NULL;
1613 /* Initially all errors give the same report */
1614 char *ret = "Data does not appear to be a saved game file";
1615
1616 /*
1617 * We construct all the new state in local variables while we
1618 * check its sanity. Only once we have finished reading the
1619 * serialised data and detected no errors at all do we start
dafd6cf6 1620 * modifying stuff in the midend passed in.
a4393230 1621 */
1622 char *seed = NULL, *parstr = NULL, *desc = NULL, *privdesc = NULL;
1623 char *auxinfo = NULL, *uistr = NULL, *cparstr = NULL;
1624 float elapsed = 0.0F;
1625 game_params *params = NULL, *cparams = NULL;
1626 game_ui *ui = NULL;
1627 struct midend_state_entry *states = NULL;
1628
1629 /*
1630 * Loop round and round reading one key/value pair at a time
1631 * from the serialised stream, until we have enough game states
1632 * to finish.
1633 */
1634 while (nstates <= 0 || statepos < 0 || gotstates < nstates-1) {
1635 char key[9], c;
1636 int len;
1637
1638 do {
1639 if (!read(rctx, key, 1)) {
1640 /* unexpected EOF */
1641 goto cleanup;
1642 }
1643 } while (key[0] == '\r' || key[0] == '\n');
1644
1645 if (!read(rctx, key+1, 8)) {
1646 /* unexpected EOF */
1647 goto cleanup;
1648 }
1649
1650 if (key[8] != ':') {
1651 if (started)
1652 ret = "Data was incorrectly formatted for a saved game file";
44f0599f 1653 goto cleanup;
a4393230 1654 }
1655 len = strcspn(key, ": ");
1656 assert(len <= 8);
1657 key[len] = '\0';
1658
1659 len = 0;
1660 while (1) {
1661 if (!read(rctx, &c, 1)) {
1662 /* unexpected EOF */
1663 goto cleanup;
1664 }
1665
1666 if (c == ':') {
1667 break;
1668 } else if (c >= '0' && c <= '9') {
1669 len = (len * 10) + (c - '0');
1670 } else {
1671 if (started)
1672 ret = "Data was incorrectly formatted for a"
1673 " saved game file";
1674 goto cleanup;
1675 }
1676 }
1677
1678 val = snewn(len+1, char);
1679 if (!read(rctx, val, len)) {
1680 if (started)
1681 goto cleanup;
1682 }
1683 val[len] = '\0';
1684
1685 if (!started) {
1686 if (strcmp(key, "SAVEFILE") || strcmp(val, SERIALISE_MAGIC)) {
1687 /* ret already has the right message in it */
1688 goto cleanup;
1689 }
1690 /* Now most errors are this one, unless otherwise specified */
1691 ret = "Saved data ended unexpectedly";
1692 started = TRUE;
1693 } else {
1694 if (!strcmp(key, "VERSION")) {
1695 if (strcmp(val, SERIALISE_VERSION)) {
1696 ret = "Cannot handle this version of the saved game"
1697 " file format";
1698 goto cleanup;
1699 }
1700 } else if (!strcmp(key, "GAME")) {
1701 if (strcmp(val, me->ourgame->name)) {
1702 ret = "Save file is from a different game";
1703 goto cleanup;
1704 }
1705 } else if (!strcmp(key, "PARAMS")) {
1706 sfree(parstr);
1707 parstr = val;
1708 val = NULL;
1709 } else if (!strcmp(key, "CPARAMS")) {
1710 sfree(cparstr);
1711 cparstr = val;
1712 val = NULL;
1713 } else if (!strcmp(key, "SEED")) {
1714 sfree(seed);
1715 seed = val;
1716 val = NULL;
1717 } else if (!strcmp(key, "DESC")) {
1718 sfree(desc);
1719 desc = val;
1720 val = NULL;
1721 } else if (!strcmp(key, "PRIVDESC")) {
1722 sfree(privdesc);
1723 privdesc = val;
1724 val = NULL;
1725 } else if (!strcmp(key, "AUXINFO")) {
1726 unsigned char *tmp;
1727 int len = strlen(val) / 2; /* length in bytes */
1728 tmp = hex2bin(val, len);
1729 obfuscate_bitmap(tmp, len*8, TRUE);
1730
1731 sfree(auxinfo);
1732 auxinfo = snewn(len + 1, char);
1733 memcpy(auxinfo, tmp, len);
1734 auxinfo[len] = '\0';
1735 sfree(tmp);
1736 } else if (!strcmp(key, "UI")) {
1737 sfree(uistr);
1738 uistr = val;
1739 val = NULL;
1740 } else if (!strcmp(key, "TIME")) {
5b502ae8 1741 elapsed = (float)atof(val);
a4393230 1742 } else if (!strcmp(key, "NSTATES")) {
1743 nstates = atoi(val);
1744 if (nstates <= 0) {
1745 ret = "Number of states in save file was negative";
1746 goto cleanup;
1747 }
1748 if (states) {
1749 ret = "Two state counts provided in save file";
1750 goto cleanup;
1751 }
1752 states = snewn(nstates, struct midend_state_entry);
1753 for (i = 0; i < nstates; i++) {
1754 states[i].state = NULL;
1755 states[i].movestr = NULL;
1756 states[i].movetype = NEWGAME;
1757 }
1758 } else if (!strcmp(key, "STATEPOS")) {
1759 statepos = atoi(val);
1760 } else if (!strcmp(key, "MOVE")) {
1761 gotstates++;
1762 states[gotstates].movetype = MOVE;
1763 states[gotstates].movestr = val;
1764 val = NULL;
1765 } else if (!strcmp(key, "SOLVE")) {
1766 gotstates++;
1767 states[gotstates].movetype = SOLVE;
1768 states[gotstates].movestr = val;
1769 val = NULL;
1770 } else if (!strcmp(key, "RESTART")) {
1771 gotstates++;
1772 states[gotstates].movetype = RESTART;
1773 states[gotstates].movestr = val;
1774 val = NULL;
1775 }
1776 }
1777
1778 sfree(val);
1779 val = NULL;
1780 }
1781
1782 params = me->ourgame->default_params();
1783 me->ourgame->decode_params(params, parstr);
3ff276f2 1784 if (me->ourgame->validate_params(params, TRUE)) {
a4393230 1785 ret = "Long-term parameters in save file are invalid";
1786 goto cleanup;
1787 }
1788 cparams = me->ourgame->default_params();
1789 me->ourgame->decode_params(cparams, cparstr);
3ff276f2 1790 if (me->ourgame->validate_params(cparams, FALSE)) {
a4393230 1791 ret = "Short-term parameters in save file are invalid";
1792 goto cleanup;
1793 }
3ff276f2 1794 if (seed && me->ourgame->validate_params(cparams, TRUE)) {
1795 /*
1796 * The seed's no use with this version, but we can perfectly
1797 * well use the rest of the data.
1798 */
1799 sfree(seed);
1800 seed = NULL;
1801 }
a4393230 1802 if (!desc) {
1803 ret = "Game description in save file is missing";
1804 goto cleanup;
1805 } else if (me->ourgame->validate_desc(params, desc)) {
1806 ret = "Game description in save file is invalid";
1807 goto cleanup;
1808 }
1809 if (privdesc && me->ourgame->validate_desc(params, privdesc)) {
1810 ret = "Game private description in save file is invalid";
1811 goto cleanup;
1812 }
1813 if (statepos < 0 || statepos >= nstates) {
1814 ret = "Game position in save file is out of range";
1815 }
1816
1817 states[0].state = me->ourgame->new_game(me, params,
1818 privdesc ? privdesc : desc);
1819 for (i = 1; i < nstates; i++) {
1820 assert(states[i].movetype != NEWGAME);
1821 switch (states[i].movetype) {
1822 case MOVE:
1823 case SOLVE:
1824 states[i].state = me->ourgame->execute_move(states[i-1].state,
1825 states[i].movestr);
1826 if (states[i].state == NULL) {
1827 ret = "Save file contained an invalid move";
1828 goto cleanup;
1829 }
1830 break;
1831 case RESTART:
1832 if (me->ourgame->validate_desc(params, states[i].movestr)) {
1833 ret = "Save file contained an invalid restart move";
1834 goto cleanup;
1835 }
1836 states[i].state = me->ourgame->new_game(me, params,
1837 states[i].movestr);
1838 break;
1839 }
1840 }
1841
1842 ui = me->ourgame->new_ui(states[0].state);
1843 me->ourgame->decode_ui(ui, uistr);
1844
1845 /*
1846 * Now we've run out of possible error conditions, so we're
1847 * ready to start overwriting the real data in the current
1848 * midend. We'll do this by swapping things with the local
1849 * variables, so that the same cleanup code will free the old
1850 * stuff.
1851 */
1852 {
1853 char *tmp;
1854
1855 tmp = me->desc;
1856 me->desc = desc;
1857 desc = tmp;
1858
1859 tmp = me->privdesc;
1860 me->privdesc = privdesc;
1861 privdesc = tmp;
1862
1863 tmp = me->seedstr;
1864 me->seedstr = seed;
1865 seed = tmp;
1866
1867 tmp = me->aux_info;
1868 me->aux_info = auxinfo;
1869 auxinfo = tmp;
1870 }
1871
1872 me->genmode = GOT_NOTHING;
1873
1874 me->statesize = nstates;
1875 nstates = me->nstates;
1876 me->nstates = me->statesize;
1877 {
1878 struct midend_state_entry *tmp;
1879 tmp = me->states;
1880 me->states = states;
1881 states = tmp;
1882 }
1883 me->statepos = statepos;
1884
1885 {
1886 game_params *tmp;
1887
1888 tmp = me->params;
1889 me->params = params;
1890 params = tmp;
1891
1892 tmp = me->curparams;
1893 me->curparams = cparams;
1894 cparams = tmp;
1895 }
1896
1897 me->oldstate = NULL;
1898 me->anim_time = me->anim_pos = me->flash_time = me->flash_pos = 0.0F;
1899 me->dir = 0;
1900
1901 {
1902 game_ui *tmp;
1903
1904 tmp = me->ui;
1905 me->ui = ui;
1906 ui = tmp;
1907 }
1908
1909 me->elapsed = elapsed;
1910 me->pressed_mouse_button = 0;
1911
1912 midend_set_timer(me);
1913
871bf294 1914 if (me->drawstate)
dafd6cf6 1915 me->ourgame->free_drawstate(me->drawing, me->drawstate);
871bf294 1916 me->drawstate =
dafd6cf6 1917 me->ourgame->new_drawstate(me->drawing,
1918 me->states[me->statepos-1].state);
871bf294 1919 midend_size_new_drawstate(me);
1920
a4393230 1921 ret = NULL; /* success! */
1922
1923 cleanup:
1924 sfree(val);
1925 sfree(seed);
1926 sfree(parstr);
1927 sfree(cparstr);
1928 sfree(desc);
1929 sfree(privdesc);
1930 sfree(auxinfo);
1931 sfree(uistr);
1932 if (params)
1933 me->ourgame->free_params(params);
1934 if (cparams)
1935 me->ourgame->free_params(cparams);
1936 if (ui)
1937 me->ourgame->free_ui(ui);
1938 if (states) {
1939 int i;
1940
1941 for (i = 0; i < nstates; i++) {
1942 if (states[i].state)
1943 me->ourgame->free_game(states[i].state);
1944 sfree(states[i].movestr);
1945 }
1946 sfree(states);
1947 }
1948
1949 return ret;
1950}
dafd6cf6 1951
1952char *midend_print_puzzle(midend *me, document *doc, int with_soln)
1953{
1954 game_state *soln = NULL;
1955
1956 if (me->statepos < 1)
1957 return "No game set up to print";/* _shouldn't_ happen! */
1958
1959 if (with_soln) {
1960 char *msg, *movestr;
1961
1962 if (!me->ourgame->can_solve)
1963 return "This game does not support the Solve operation";
1964
1965 msg = "Solve operation failed";/* game _should_ overwrite on error */
1966 movestr = me->ourgame->solve(me->states[0].state,
1967 me->states[me->statepos-1].state,
1968 me->aux_info, &msg);
1969 if (!movestr)
1970 return msg;
1971 soln = me->ourgame->execute_move(me->states[me->statepos-1].state,
1972 movestr);
1973 assert(soln);
1974
1975 sfree(movestr);
1976 } else
1977 soln = NULL;
1978
1979 /*
1980 * This call passes over ownership of the two game_states and
1981 * the game_params. Hence we duplicate the ones we want to
1982 * keep, and we don't have to bother freeing soln if it was
1983 * non-NULL.
1984 */
1985 document_add_puzzle(doc, me->ourgame,
1986 me->ourgame->dup_params(me->curparams),
1987 me->ourgame->dup_game(me->states[0].state), soln);
1988
1989 return NULL;
1990}