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