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