Add a `jumble' key (`J') to Net, which scrambles the positions of all unlocked
[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
5 * processes standard keystrokes for undo/redo/new/restart/quit.
6 */
7f77ea24 7
8#include <stdio.h>
5928817c 9#include <string.h>
7f77ea24 10#include <assert.h>
11
12#include "puzzles.h"
13
14struct midend_data {
2ef96bd6 15 frontend *frontend;
48d70ca9 16 random_state *random;
17
7f77ea24 18 char *seed;
5928817c 19 int fresh_seed;
7f77ea24 20 int nstates, statesize, statepos;
eb2ad6f1 21
22 game_params **presets;
23 char **preset_names;
24 int npresets, presetsize;
25
7f77ea24 26 game_params *params;
27 game_state **states;
2ef96bd6 28 game_drawstate *drawstate;
29 game_state *oldstate;
74a4e547 30 game_ui *ui;
2ef96bd6 31 float anim_time, anim_pos;
87ed82be 32 float flash_time, flash_pos;
c822de4a 33 int dir;
7f77ea24 34};
35
36#define ensure(me) do { \
37 if ((me)->nstates >= (me)->statesize) { \
38 (me)->statesize = (me)->nstates + 128; \
39 (me)->states = sresize((me)->states, (me)->statesize, game_state *); \
40 } \
41} while (0)
42
cbb5549e 43midend_data *midend_new(frontend *fe)
7f77ea24 44{
45 midend_data *me = snew(midend_data);
cbb5549e 46 void *randseed;
47 int randseedsize;
48
49 get_random_seed(&randseed, &randseedsize);
7f77ea24 50
48d70ca9 51 me->frontend = fe;
52 me->random = random_init(randseed, randseedsize);
7f77ea24 53 me->nstates = me->statesize = me->statepos = 0;
54 me->states = NULL;
55 me->params = default_params();
56 me->seed = NULL;
5928817c 57 me->fresh_seed = FALSE;
2ef96bd6 58 me->drawstate = NULL;
59 me->oldstate = NULL;
eb2ad6f1 60 me->presets = NULL;
61 me->preset_names = NULL;
62 me->npresets = me->presetsize = 0;
87ed82be 63 me->anim_time = me->anim_pos = 0.0F;
64 me->flash_time = me->flash_pos = 0.0F;
c822de4a 65 me->dir = 0;
74a4e547 66 me->ui = NULL;
7f77ea24 67
cbb5549e 68 sfree(randseed);
69
7f77ea24 70 return me;
71}
72
73void midend_free(midend_data *me)
74{
75 sfree(me->states);
76 sfree(me->seed);
77 free_params(me->params);
78 sfree(me);
79}
80
81void midend_size(midend_data *me, int *x, int *y)
82{
83 game_size(me->params, x, y);
84}
85
86void midend_set_params(midend_data *me, game_params *params)
87{
88 free_params(me->params);
eb2ad6f1 89 me->params = dup_params(params);
7f77ea24 90}
91
5928817c 92void midend_new_game(midend_data *me)
7f77ea24 93{
94 while (me->nstates > 0)
95 free_game(me->states[--me->nstates]);
96
2ef96bd6 97 if (me->drawstate)
98 game_free_drawstate(me->drawstate);
99
7f77ea24 100 assert(me->nstates == 0);
101
5928817c 102 if (!me->fresh_seed) {
103 sfree(me->seed);
48d70ca9 104 me->seed = new_game_seed(me->params, me->random);
5928817c 105 } else
106 me->fresh_seed = FALSE;
7f77ea24 107
108 ensure(me);
109 me->states[me->nstates++] = new_game(me->params, me->seed);
110 me->statepos = 1;
2ef96bd6 111 me->drawstate = game_new_drawstate(me->states[0]);
74a4e547 112 if (me->ui)
113 free_ui(me->ui);
114 me->ui = new_ui(me->states[0]);
7f77ea24 115}
116
117void midend_restart_game(midend_data *me)
118{
119 while (me->nstates > 1)
120 free_game(me->states[--me->nstates]);
121 me->statepos = me->nstates;
74a4e547 122 free_ui(me->ui);
123 me->ui = new_ui(me->states[0]);
7f77ea24 124}
125
1482ee76 126static int midend_undo(midend_data *me)
7f77ea24 127{
1482ee76 128 if (me->statepos > 1) {
7f77ea24 129 me->statepos--;
c822de4a 130 me->dir = -1;
1482ee76 131 return 1;
132 } else
133 return 0;
7f77ea24 134}
135
1482ee76 136static int midend_redo(midend_data *me)
7f77ea24 137{
1482ee76 138 if (me->statepos < me->nstates) {
7f77ea24 139 me->statepos++;
c822de4a 140 me->dir = +1;
1482ee76 141 return 1;
142 } else
143 return 0;
7f77ea24 144}
145
87ed82be 146static void midend_finish_move(midend_data *me)
147{
148 float flashtime;
149
150 if (me->oldstate || me->statepos > 1) {
151 flashtime = game_flash_length(me->oldstate ? me->oldstate :
152 me->states[me->statepos-2],
c822de4a 153 me->states[me->statepos-1],
154 me->oldstate ? me->dir : +1);
87ed82be 155 if (flashtime > 0) {
156 me->flash_pos = 0.0F;
157 me->flash_time = flashtime;
158 }
159 }
160
161 if (me->oldstate)
162 free_game(me->oldstate);
163 me->oldstate = NULL;
164 me->anim_pos = me->anim_time = 0;
c822de4a 165 me->dir = 0;
87ed82be 166
167 if (me->flash_time == 0 && me->anim_time == 0)
168 deactivate_timer(me->frontend);
169 else
170 activate_timer(me->frontend);
171}
172
dd216087 173static void midend_stop_anim(midend_data *me)
7f77ea24 174{
2ef96bd6 175 if (me->oldstate || me->anim_time) {
87ed82be 176 midend_finish_move(me);
2ef96bd6 177 midend_redraw(me);
178 }
dd216087 179}
180
181int midend_process_key(midend_data *me, int x, int y, int button)
182{
183 game_state *oldstate = dup_game(me->states[me->statepos - 1]);
184 float anim_time;
7f77ea24 185
186 if (button == 'n' || button == 'N' || button == '\x0E') {
dd216087 187 midend_stop_anim(me);
5928817c 188 midend_new_game(me);
2ef96bd6 189 midend_redraw(me);
190 return 1; /* never animate */
7f77ea24 191 } else if (button == 'r' || button == 'R') {
dd216087 192 midend_stop_anim(me);
7f77ea24 193 midend_restart_game(me);
2ef96bd6 194 midend_redraw(me);
195 return 1; /* never animate */
7f77ea24 196 } else if (button == 'u' || button == 'u' ||
1482ee76 197 button == '\x1A' || button == '\x1F') {
dd216087 198 midend_stop_anim(me);
1482ee76 199 if (!midend_undo(me))
200 return 1;
7f77ea24 201 } else if (button == '\x12') {
dd216087 202 midend_stop_anim(me);
1482ee76 203 if (!midend_redo(me))
204 return 1;
7f77ea24 205 } else if (button == 'q' || button == 'Q' || button == '\x11') {
2ef96bd6 206 free_game(oldstate);
207 return 0;
208 } else {
74a4e547 209 game_state *s = make_move(me->states[me->statepos-1], me->ui,
210 x, y, button);
211
212 if (s == me->states[me->statepos-1]) {
213 /*
214 * make_move() is allowed to return its input state to
215 * indicate that although no move has been made, the UI
216 * state has been updated and a redraw is called for.
217 */
218 midend_redraw(me);
219 return 1;
220 } else if (s) {
dd216087 221 midend_stop_anim(me);
2ef96bd6 222 while (me->nstates > me->statepos)
223 free_game(me->states[--me->nstates]);
224 ensure(me);
225 me->states[me->nstates] = s;
226 me->statepos = ++me->nstates;
c822de4a 227 me->dir = +1;
2ef96bd6 228 } else {
229 free_game(oldstate);
230 return 1;
231 }
7f77ea24 232 }
233
2ef96bd6 234 /*
235 * See if this move requires an animation.
236 */
c822de4a 237 anim_time = game_anim_length(oldstate, me->states[me->statepos-1], me->dir);
2ef96bd6 238
87ed82be 239 me->oldstate = oldstate;
2ef96bd6 240 if (anim_time > 0) {
2ef96bd6 241 me->anim_time = anim_time;
242 } else {
2ef96bd6 243 me->anim_time = 0.0;
87ed82be 244 midend_finish_move(me);
7f77ea24 245 }
2ef96bd6 246 me->anim_pos = 0.0;
247
248 midend_redraw(me);
249
250 activate_timer(me->frontend);
7f77ea24 251
252 return 1;
253}
2ef96bd6 254
255void midend_redraw(midend_data *me)
256{
257 if (me->statepos > 0 && me->drawstate) {
258 start_draw(me->frontend);
259 if (me->oldstate && me->anim_time > 0 &&
260 me->anim_pos < me->anim_time) {
c822de4a 261 assert(me->dir != 0);
2ef96bd6 262 game_redraw(me->frontend, me->drawstate, me->oldstate,
c822de4a 263 me->states[me->statepos-1], me->dir,
264 me->ui, me->anim_pos, me->flash_pos);
2ef96bd6 265 } else {
266 game_redraw(me->frontend, me->drawstate, NULL,
c822de4a 267 me->states[me->statepos-1], +1 /*shrug*/,
268 me->ui, 0.0, me->flash_pos);
2ef96bd6 269 }
270 end_draw(me->frontend);
271 }
272}
273
274void midend_timer(midend_data *me, float tplus)
275{
276 me->anim_pos += tplus;
277 if (me->anim_pos >= me->anim_time ||
278 me->anim_time == 0 || !me->oldstate) {
87ed82be 279 if (me->anim_time > 0)
280 midend_finish_move(me);
281 }
282 me->flash_pos += tplus;
283 if (me->flash_pos >= me->flash_time || me->flash_time == 0) {
284 me->flash_pos = me->flash_time = 0;
2ef96bd6 285 }
87ed82be 286 if (me->flash_time == 0 && me->anim_time == 0)
287 deactivate_timer(me->frontend);
2ef96bd6 288 midend_redraw(me);
289}
290
291float *midend_colours(midend_data *me, int *ncolours)
292{
293 game_state *state = NULL;
294 float *ret;
295
296 if (me->nstates == 0) {
48d70ca9 297 char *seed = new_game_seed(me->params, me->random);
2ef96bd6 298 state = new_game(me->params, seed);
299 sfree(seed);
300 } else
301 state = me->states[0];
302
303 ret = game_colours(me->frontend, state, ncolours);
304
305 if (me->nstates == 0)
306 free_game(state);
307
308 return ret;
309}
eb2ad6f1 310
311int midend_num_presets(midend_data *me)
312{
313 if (!me->npresets) {
314 char *name;
315 game_params *preset;
316
317 while (game_fetch_preset(me->npresets, &name, &preset)) {
318 if (me->presetsize <= me->npresets) {
319 me->presetsize = me->npresets + 10;
320 me->presets = sresize(me->presets, me->presetsize,
321 game_params *);
322 me->preset_names = sresize(me->preset_names, me->presetsize,
323 char *);
324 }
325
326 me->presets[me->npresets] = preset;
327 me->preset_names[me->npresets] = name;
328 me->npresets++;
329 }
330 }
331
332 return me->npresets;
333}
334
335void midend_fetch_preset(midend_data *me, int n,
336 char **name, game_params **params)
337{
338 assert(n >= 0 && n < me->npresets);
339 *name = me->preset_names[n];
340 *params = me->presets[n];
341}
fd1a1a2b 342
343int midend_wants_statusbar(midend_data *me)
344{
345 return game_wants_statusbar();
346}
c8230524 347
5928817c 348config_item *midend_get_config(midend_data *me, int which, char **wintitle)
c8230524 349{
b0e26073 350 char *titlebuf, *parstr;
5928817c 351 config_item *ret;
352
353 titlebuf = snewn(40 + strlen(game_name), char);
354
355 switch (which) {
356 case CFG_SETTINGS:
357 sprintf(titlebuf, "%s configuration", game_name);
358 *wintitle = dupstr(titlebuf);
359 return game_configure(me->params);
360 case CFG_SEED:
361 sprintf(titlebuf, "%s game selection", game_name);
362 *wintitle = dupstr(titlebuf);
363
364 ret = snewn(2, config_item);
365
366 ret[0].type = C_STRING;
367 ret[0].name = "Game ID";
368 ret[0].ival = 0;
b0e26073 369 /*
370 * The text going in here will be a string encoding of the
371 * parameters, plus a colon, plus the game seed. This is a
372 * full game ID.
373 */
374 parstr = encode_params(me->params);
375 ret[0].sval = snewn(strlen(parstr) + strlen(me->seed) + 2, char);
376 sprintf(ret[0].sval, "%s:%s", parstr, me->seed);
377 sfree(parstr);
5928817c 378
379 ret[1].type = C_END;
380 ret[1].name = ret[1].sval = NULL;
381 ret[1].ival = 0;
382
383 return ret;
384 }
385
386 assert(!"We shouldn't be here");
387 return NULL;
c8230524 388}
389
8b7938e7 390char *midend_game_id(midend_data *me, char *id, int def_seed)
391{
392 char *error, *par, *seed;
393 game_params *params;
394
395 seed = strchr(id, ':');
396
397 if (seed) {
398 /*
399 * We have a colon separating parameters from game seed. So
400 * `par' now points to the parameters string, and `seed' to
401 * the seed string.
402 */
403 *seed++ = '\0';
404 par = id;
405 } else {
406 /*
407 * We only have one string. Depending on `def_seed', we
408 * take it to be either parameters or seed.
409 */
410 if (def_seed) {
411 seed = id;
412 par = NULL;
413 } else {
414 seed = NULL;
415 par = id;
416 }
417 }
418
419 if (par) {
420 params = decode_params(par);
421 error = validate_params(params);
422 if (error) {
423 free_params(params);
424 return error;
425 }
426 free_params(me->params);
427 me->params = params;
428 }
429
430 if (seed) {
431 error = validate_seed(me->params, seed);
432 if (error)
433 return error;
434
435 sfree(me->seed);
436 me->seed = dupstr(seed);
437 me->fresh_seed = TRUE;
438 }
439
440 return NULL;
441}
442
5928817c 443char *midend_set_config(midend_data *me, int which, config_item *cfg)
c8230524 444{
8b7938e7 445 char *error;
c8230524 446 game_params *params;
447
5928817c 448 switch (which) {
449 case CFG_SETTINGS:
450 params = custom_params(cfg);
451 error = validate_params(params);
c8230524 452
5928817c 453 if (error) {
454 free_params(params);
455 return error;
456 }
c8230524 457
5928817c 458 free_params(me->params);
459 me->params = params;
460 break;
461
462 case CFG_SEED:
8b7938e7 463 error = midend_game_id(me, cfg[0].sval, TRUE);
5928817c 464 if (error)
465 return error;
5928817c 466 break;
467 }
c8230524 468
469 return NULL;
470}