chiark / gitweb /
Add a `jumble' key (`J') to Net, which scrambles the positions of all unlocked
[sgt-puzzles.git] / 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 }