chiark / gitweb /
Add a menu bar, in both Windows and GTK. In particular, game modules
[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 <assert.h>
10
11 #include "puzzles.h"
12
13 struct midend_data {
14     frontend *frontend;
15     char *seed;
16     int nstates, statesize, statepos;
17
18     game_params **presets;
19     char **preset_names;
20     int npresets, presetsize;
21
22     game_params *params;
23     game_state **states;
24     game_drawstate *drawstate;
25     game_state *oldstate;
26     float anim_time, anim_pos;
27 };
28
29 #define ensure(me) do { \
30     if ((me)->nstates >= (me)->statesize) { \
31         (me)->statesize = (me)->nstates + 128; \
32         (me)->states = sresize((me)->states, (me)->statesize, game_state *); \
33     } \
34 } while (0)
35
36 midend_data *midend_new(frontend *frontend)
37 {
38     midend_data *me = snew(midend_data);
39
40     me->frontend = frontend;
41     me->nstates = me->statesize = me->statepos = 0;
42     me->states = NULL;
43     me->params = default_params();
44     me->seed = NULL;
45     me->drawstate = NULL;
46     me->oldstate = NULL;
47     me->presets = NULL;
48     me->preset_names = NULL;
49     me->npresets = me->presetsize = 0;
50
51     return me;
52 }
53
54 void midend_free(midend_data *me)
55 {
56     sfree(me->states);
57     sfree(me->seed);
58     free_params(me->params);
59     sfree(me);
60 }
61
62 void midend_size(midend_data *me, int *x, int *y)
63 {
64     game_size(me->params, x, y);
65 }
66
67 void midend_set_params(midend_data *me, game_params *params)
68 {
69     free_params(me->params);
70     me->params = dup_params(params);
71 }
72
73 void midend_new_game(midend_data *me, char *seed)
74 {
75     while (me->nstates > 0)
76         free_game(me->states[--me->nstates]);
77
78     if (me->drawstate)
79         game_free_drawstate(me->drawstate);
80
81     assert(me->nstates == 0);
82
83     sfree(me->seed);
84     if (seed)
85         me->seed = dupstr(seed);
86     else
87         me->seed = new_game_seed(me->params);
88
89     ensure(me);
90     me->states[me->nstates++] = new_game(me->params, me->seed);
91     me->statepos = 1;
92     me->drawstate = game_new_drawstate(me->states[0]);
93 }
94
95 void midend_restart_game(midend_data *me)
96 {
97     while (me->nstates > 1)
98         free_game(me->states[--me->nstates]);
99     me->statepos = me->nstates;
100 }
101
102 static int midend_undo(midend_data *me)
103 {
104     if (me->statepos > 1) {
105         me->statepos--;
106         return 1;
107     } else
108         return 0;
109 }
110
111 static int midend_redo(midend_data *me)
112 {
113     if (me->statepos < me->nstates) {
114         me->statepos++;
115         return 1;
116     } else
117         return 0;
118 }
119
120 int midend_process_key(midend_data *me, int x, int y, int button)
121 {
122     game_state *oldstate = dup_game(me->states[me->statepos - 1]);
123     float anim_time;
124
125     if (me->oldstate || me->anim_time) {
126         if (me->oldstate)
127             free_game(me->oldstate);
128         me->oldstate = NULL;
129         me->anim_pos = me->anim_time = 0;
130         deactivate_timer(me->frontend);
131         midend_redraw(me);
132     }
133
134     if (button == 'n' || button == 'N' || button == '\x0E') {
135         midend_new_game(me, NULL);
136         midend_redraw(me);
137         return 1;                      /* never animate */
138     } else if (button == 'r' || button == 'R') {
139         midend_restart_game(me);
140         midend_redraw(me);
141         return 1;                      /* never animate */
142     } else if (button == 'u' || button == 'u' ||
143                button == '\x1A' || button == '\x1F') {
144         if (!midend_undo(me))
145             return 1;
146     } else if (button == '\x12') {
147         if (!midend_redo(me))
148             return 1;
149     } else if (button == 'q' || button == 'Q' || button == '\x11') {
150         free_game(oldstate);
151         return 0;
152     } else {
153         game_state *s = make_move(me->states[me->statepos-1], x, y, button);
154
155         if (s) {
156             while (me->nstates > me->statepos)
157                 free_game(me->states[--me->nstates]);
158             ensure(me);
159             me->states[me->nstates] = s;
160             me->statepos = ++me->nstates;
161         } else {
162             free_game(oldstate);
163             return 1;
164         }
165     }
166
167     /*
168      * See if this move requires an animation.
169      */
170     anim_time = game_anim_length(oldstate, me->states[me->statepos-1]);
171
172     if (anim_time > 0) {
173         me->oldstate = oldstate;
174         me->anim_time = anim_time;
175     } else {
176         free_game(oldstate);
177         me->oldstate = NULL;
178         me->anim_time = 0.0;
179     }
180     me->anim_pos = 0.0;
181
182     midend_redraw(me);
183
184     activate_timer(me->frontend);
185
186     return 1;
187 }
188
189 void midend_redraw(midend_data *me)
190 {
191     if (me->statepos > 0 && me->drawstate) {
192         start_draw(me->frontend);
193         if (me->oldstate && me->anim_time > 0 &&
194             me->anim_pos < me->anim_time) {
195             game_redraw(me->frontend, me->drawstate, me->oldstate,
196                         me->states[me->statepos-1], me->anim_pos);
197         } else {
198             game_redraw(me->frontend, me->drawstate, NULL,
199                         me->states[me->statepos-1], 0.0);
200         }
201         end_draw(me->frontend);
202     }
203 }
204
205 void midend_timer(midend_data *me, float tplus)
206 {
207     me->anim_pos += tplus;
208     if (me->anim_pos >= me->anim_time ||
209         me->anim_time == 0 || !me->oldstate) {
210         if (me->oldstate)
211             free_game(me->oldstate);
212         me->oldstate = NULL;
213         me->anim_pos = me->anim_time = 0;
214         deactivate_timer(me->frontend);
215     }
216     midend_redraw(me);
217 }
218
219 float *midend_colours(midend_data *me, int *ncolours)
220 {
221     game_state *state = NULL;
222     float *ret;
223
224     if (me->nstates == 0) {
225         char *seed = new_game_seed(me->params);
226         state = new_game(me->params, seed);
227         sfree(seed);
228     } else
229         state = me->states[0];
230
231     ret = game_colours(me->frontend, state, ncolours);
232
233     if (me->nstates == 0)
234         free_game(state);
235
236     return ret;
237 }
238
239 int midend_num_presets(midend_data *me)
240 {
241     if (!me->npresets) {
242         char *name;
243         game_params *preset;
244
245         while (game_fetch_preset(me->npresets, &name, &preset)) {
246             if (me->presetsize <= me->npresets) {
247                 me->presetsize = me->npresets + 10;
248                 me->presets = sresize(me->presets, me->presetsize,
249                                       game_params *);
250                 me->preset_names = sresize(me->preset_names, me->presetsize,
251                                            char *);
252             }
253
254             me->presets[me->npresets] = preset;
255             me->preset_names[me->npresets] = name;
256             me->npresets++;
257         }
258     }
259
260     return me->npresets;
261 }
262
263 void midend_fetch_preset(midend_data *me, int n,
264                          char **name, game_params **params)
265 {
266     assert(n >= 0 && n < me->npresets);
267     *name = me->preset_names[n];
268     *params = me->presets[n];
269 }