Add a `jumble' key (`J') to Net, which scrambles the positions of all unlocked
[sgt/puzzles] / midend.c
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 */
7
8 #include <stdio.h>
9 #include <string.h>
10 #include <assert.h>
11
12 #include "puzzles.h"
13
14 struct midend_data {
15 frontend *frontend;
16 random_state *random;
17
18 char *seed;
19 int fresh_seed;
20 int nstates, statesize, statepos;
21
22 game_params **presets;
23 char **preset_names;
24 int npresets, presetsize;
25
26 game_params *params;
27 game_state **states;
28 game_drawstate *drawstate;
29 game_state *oldstate;
30 game_ui *ui;
31 float anim_time, anim_pos;
32 float flash_time, flash_pos;
33 int dir;
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
43 midend_data *midend_new(frontend *fe)
44 {
45 midend_data *me = snew(midend_data);
46 void *randseed;
47 int randseedsize;
48
49 get_random_seed(&randseed, &randseedsize);
50
51 me->frontend = fe;
52 me->random = random_init(randseed, randseedsize);
53 me->nstates = me->statesize = me->statepos = 0;
54 me->states = NULL;
55 me->params = default_params();
56 me->seed = NULL;
57 me->fresh_seed = FALSE;
58 me->drawstate = NULL;
59 me->oldstate = NULL;
60 me->presets = NULL;
61 me->preset_names = NULL;
62 me->npresets = me->presetsize = 0;
63 me->anim_time = me->anim_pos = 0.0F;
64 me->flash_time = me->flash_pos = 0.0F;
65 me->dir = 0;
66 me->ui = NULL;
67
68 sfree(randseed);
69
70 return me;
71 }
72
73 void midend_free(midend_data *me)
74 {
75 sfree(me->states);
76 sfree(me->seed);
77 free_params(me->params);
78 sfree(me);
79 }
80
81 void midend_size(midend_data *me, int *x, int *y)
82 {
83 game_size(me->params, x, y);
84 }
85
86 void midend_set_params(midend_data *me, game_params *params)
87 {
88 free_params(me->params);
89 me->params = dup_params(params);
90 }
91
92 void midend_new_game(midend_data *me)
93 {
94 while (me->nstates > 0)
95 free_game(me->states[--me->nstates]);
96
97 if (me->drawstate)
98 game_free_drawstate(me->drawstate);
99
100 assert(me->nstates == 0);
101
102 if (!me->fresh_seed) {
103 sfree(me->seed);
104 me->seed = new_game_seed(me->params, me->random);
105 } else
106 me->fresh_seed = FALSE;
107
108 ensure(me);
109 me->states[me->nstates++] = new_game(me->params, me->seed);
110 me->statepos = 1;
111 me->drawstate = game_new_drawstate(me->states[0]);
112 if (me->ui)
113 free_ui(me->ui);
114 me->ui = new_ui(me->states[0]);
115 }
116
117 void midend_restart_game(midend_data *me)
118 {
119 while (me->nstates > 1)
120 free_game(me->states[--me->nstates]);
121 me->statepos = me->nstates;
122 free_ui(me->ui);
123 me->ui = new_ui(me->states[0]);
124 }
125
126 static int midend_undo(midend_data *me)
127 {
128 if (me->statepos > 1) {
129 me->statepos--;
130 me->dir = -1;
131 return 1;
132 } else
133 return 0;
134 }
135
136 static int midend_redo(midend_data *me)
137 {
138 if (me->statepos < me->nstates) {
139 me->statepos++;
140 me->dir = +1;
141 return 1;
142 } else
143 return 0;
144 }
145
146 static 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],
153 me->states[me->statepos-1],
154 me->oldstate ? me->dir : +1);
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;
165 me->dir = 0;
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
173 static void midend_stop_anim(midend_data *me)
174 {
175 if (me->oldstate || me->anim_time) {
176 midend_finish_move(me);
177 midend_redraw(me);
178 }
179 }
180
181 int 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;
185
186 if (button == 'n' || button == 'N' || button == '\x0E') {
187 midend_stop_anim(me);
188 midend_new_game(me);
189 midend_redraw(me);
190 return 1; /* never animate */
191 } else if (button == 'r' || button == 'R') {
192 midend_stop_anim(me);
193 midend_restart_game(me);
194 midend_redraw(me);
195 return 1; /* never animate */
196 } else if (button == 'u' || button == 'u' ||
197 button == '\x1A' || button == '\x1F') {
198 midend_stop_anim(me);
199 if (!midend_undo(me))
200 return 1;
201 } else if (button == '\x12') {
202 midend_stop_anim(me);
203 if (!midend_redo(me))
204 return 1;
205 } else if (button == 'q' || button == 'Q' || button == '\x11') {
206 free_game(oldstate);
207 return 0;
208 } else {
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) {
221 midend_stop_anim(me);
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;
227 me->dir = +1;
228 } else {
229 free_game(oldstate);
230 return 1;
231 }
232 }
233
234 /*
235 * See if this move requires an animation.
236 */
237 anim_time = game_anim_length(oldstate, me->states[me->statepos-1], me->dir);
238
239 me->oldstate = oldstate;
240 if (anim_time > 0) {
241 me->anim_time = anim_time;
242 } else {
243 me->anim_time = 0.0;
244 midend_finish_move(me);
245 }
246 me->anim_pos = 0.0;
247
248 midend_redraw(me);
249
250 activate_timer(me->frontend);
251
252 return 1;
253 }
254
255 void 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) {
261 assert(me->dir != 0);
262 game_redraw(me->frontend, me->drawstate, me->oldstate,
263 me->states[me->statepos-1], me->dir,
264 me->ui, me->anim_pos, me->flash_pos);
265 } else {
266 game_redraw(me->frontend, me->drawstate, NULL,
267 me->states[me->statepos-1], +1 /*shrug*/,
268 me->ui, 0.0, me->flash_pos);
269 }
270 end_draw(me->frontend);
271 }
272 }
273
274 void 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) {
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;
285 }
286 if (me->flash_time == 0 && me->anim_time == 0)
287 deactivate_timer(me->frontend);
288 midend_redraw(me);
289 }
290
291 float *midend_colours(midend_data *me, int *ncolours)
292 {
293 game_state *state = NULL;
294 float *ret;
295
296 if (me->nstates == 0) {
297 char *seed = new_game_seed(me->params, me->random);
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 }
310
311 int 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
335 void 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 }
342
343 int midend_wants_statusbar(midend_data *me)
344 {
345 return game_wants_statusbar();
346 }
347
348 config_item *midend_get_config(midend_data *me, int which, char **wintitle)
349 {
350 char *titlebuf, *parstr;
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;
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);
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;
388 }
389
390 char *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
443 char *midend_set_config(midend_data *me, int which, config_item *cfg)
444 {
445 char *error;
446 game_params *params;
447
448 switch (which) {
449 case CFG_SETTINGS:
450 params = custom_params(cfg);
451 error = validate_params(params);
452
453 if (error) {
454 free_params(params);
455 return error;
456 }
457
458 free_params(me->params);
459 me->params = params;
460 break;
461
462 case CFG_SEED:
463 error = midend_game_id(me, cfg[0].sval, TRUE);
464 if (error)
465 return error;
466 break;
467 }
468
469 return NULL;
470 }