chiark / gitweb /
Framework alteration: we now support a `game_ui' structure in
[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 int midend_process_key(midend_data *me, int x, int y, int button)
162 {
163     game_state *oldstate = dup_game(me->states[me->statepos - 1]);
164     float anim_time;
165
166     if (me->oldstate || me->anim_time) {
167         midend_finish_move(me);
168         midend_redraw(me);
169     }
170
171     if (button == 'n' || button == 'N' || button == '\x0E') {
172         midend_new_game(me);
173         midend_redraw(me);
174         return 1;                      /* never animate */
175     } else if (button == 'r' || button == 'R') {
176         midend_restart_game(me);
177         midend_redraw(me);
178         return 1;                      /* never animate */
179     } else if (button == 'u' || button == 'u' ||
180                button == '\x1A' || button == '\x1F') {
181         if (!midend_undo(me))
182             return 1;
183     } else if (button == '\x12') {
184         if (!midend_redo(me))
185             return 1;
186     } else if (button == 'q' || button == 'Q' || button == '\x11') {
187         free_game(oldstate);
188         return 0;
189     } else {
190         game_state *s = make_move(me->states[me->statepos-1], me->ui,
191                                   x, y, button);
192
193         if (s == me->states[me->statepos-1]) {
194             /*
195              * make_move() is allowed to return its input state to
196              * indicate that although no move has been made, the UI
197              * state has been updated and a redraw is called for.
198              */
199             midend_redraw(me);
200             return 1;
201         } else if (s) {
202             while (me->nstates > me->statepos)
203                 free_game(me->states[--me->nstates]);
204             ensure(me);
205             me->states[me->nstates] = s;
206             me->statepos = ++me->nstates;
207         } else {
208             free_game(oldstate);
209             return 1;
210         }
211     }
212
213     /*
214      * See if this move requires an animation.
215      */
216     anim_time = game_anim_length(oldstate, me->states[me->statepos-1]);
217
218     me->oldstate = oldstate;
219     if (anim_time > 0) {
220         me->anim_time = anim_time;
221     } else {
222         me->anim_time = 0.0;
223         midend_finish_move(me);
224     }
225     me->anim_pos = 0.0;
226
227     midend_redraw(me);
228
229     activate_timer(me->frontend);
230
231     return 1;
232 }
233
234 void midend_redraw(midend_data *me)
235 {
236     if (me->statepos > 0 && me->drawstate) {
237         start_draw(me->frontend);
238         if (me->oldstate && me->anim_time > 0 &&
239             me->anim_pos < me->anim_time) {
240             game_redraw(me->frontend, me->drawstate, me->oldstate,
241                         me->states[me->statepos-1], me->ui, me->anim_pos,
242                         me->flash_pos);
243         } else {
244             game_redraw(me->frontend, me->drawstate, NULL,
245                         me->states[me->statepos-1], me->ui, 0.0,
246                         me->flash_pos);
247         }
248         end_draw(me->frontend);
249     }
250 }
251
252 void midend_timer(midend_data *me, float tplus)
253 {
254     me->anim_pos += tplus;
255     if (me->anim_pos >= me->anim_time ||
256         me->anim_time == 0 || !me->oldstate) {
257         if (me->anim_time > 0)
258             midend_finish_move(me);
259     }
260     me->flash_pos += tplus;
261     if (me->flash_pos >= me->flash_time || me->flash_time == 0) {
262         me->flash_pos = me->flash_time = 0;
263     }
264     if (me->flash_time == 0 && me->anim_time == 0)
265         deactivate_timer(me->frontend);
266     midend_redraw(me);
267 }
268
269 float *midend_colours(midend_data *me, int *ncolours)
270 {
271     game_state *state = NULL;
272     float *ret;
273
274     if (me->nstates == 0) {
275         char *seed = new_game_seed(me->params, me->random);
276         state = new_game(me->params, seed);
277         sfree(seed);
278     } else
279         state = me->states[0];
280
281     ret = game_colours(me->frontend, state, ncolours);
282
283     if (me->nstates == 0)
284         free_game(state);
285
286     return ret;
287 }
288
289 int midend_num_presets(midend_data *me)
290 {
291     if (!me->npresets) {
292         char *name;
293         game_params *preset;
294
295         while (game_fetch_preset(me->npresets, &name, &preset)) {
296             if (me->presetsize <= me->npresets) {
297                 me->presetsize = me->npresets + 10;
298                 me->presets = sresize(me->presets, me->presetsize,
299                                       game_params *);
300                 me->preset_names = sresize(me->preset_names, me->presetsize,
301                                            char *);
302             }
303
304             me->presets[me->npresets] = preset;
305             me->preset_names[me->npresets] = name;
306             me->npresets++;
307         }
308     }
309
310     return me->npresets;
311 }
312
313 void midend_fetch_preset(midend_data *me, int n,
314                          char **name, game_params **params)
315 {
316     assert(n >= 0 && n < me->npresets);
317     *name = me->preset_names[n];
318     *params = me->presets[n];
319 }
320
321 int midend_wants_statusbar(midend_data *me)
322 {
323     return game_wants_statusbar();
324 }
325
326 config_item *midend_get_config(midend_data *me, int which, char **wintitle)
327 {
328     char *titlebuf;
329     config_item *ret;
330
331     titlebuf = snewn(40 + strlen(game_name), char);
332
333     switch (which) {
334       case CFG_SETTINGS:
335         sprintf(titlebuf, "%s configuration", game_name);
336         *wintitle = dupstr(titlebuf);
337         return game_configure(me->params);
338       case CFG_SEED:
339         sprintf(titlebuf, "%s game selection", game_name);
340         *wintitle = dupstr(titlebuf);
341
342         ret = snewn(2, config_item);
343
344         ret[0].type = C_STRING;
345         ret[0].name = "Game ID";
346         ret[0].ival = 0;
347         ret[0].sval = dupstr(me->seed);
348
349         ret[1].type = C_END;
350         ret[1].name = ret[1].sval = NULL;
351         ret[1].ival = 0;
352
353         return ret;
354     }
355
356     assert(!"We shouldn't be here");
357     return NULL;
358 }
359
360 char *midend_set_config(midend_data *me, int which, config_item *cfg)
361 {
362     char *error;
363     game_params *params;
364
365     switch (which) {
366       case CFG_SETTINGS:
367         params = custom_params(cfg);
368         error = validate_params(params);
369
370         if (error) {
371             free_params(params);
372             return error;
373         }
374
375         free_params(me->params);
376         me->params = params;
377         break;
378
379       case CFG_SEED:
380         error = validate_seed(me->params, cfg[0].sval);
381         if (error)
382             return error;
383
384         sfree(me->seed);
385         me->seed = dupstr(cfg[0].sval);
386         me->fresh_seed = TRUE;
387
388         break;
389     }
390
391     return NULL;
392 }