diff options
Diffstat (limited to 'apps/plugins/puzzles/rockbox.c')
-rw-r--r-- | apps/plugins/puzzles/rockbox.c | 1682 |
1 files changed, 1682 insertions, 0 deletions
diff --git a/apps/plugins/puzzles/rockbox.c b/apps/plugins/puzzles/rockbox.c new file mode 100644 index 0000000000..3a1c00e615 --- /dev/null +++ b/apps/plugins/puzzles/rockbox.c | |||
@@ -0,0 +1,1682 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2016 Franklin Wei | ||
11 | * | ||
12 | * This program is free software; you can redistribute it and/or | ||
13 | * modify it under the terms of the GNU General Public License | ||
14 | * as published by the Free Software Foundation; either version 2 | ||
15 | * of the License, or (at your option) any later version. | ||
16 | * | ||
17 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
18 | * KIND, either express or implied. | ||
19 | * | ||
20 | ***************************************************************************/ | ||
21 | |||
22 | /* rockbox frontend for puzzles */ | ||
23 | |||
24 | #include "plugin.h" | ||
25 | |||
26 | #include "puzzles.h" | ||
27 | #include "keymaps.h" | ||
28 | |||
29 | #ifndef COMBINED | ||
30 | #include "lib/playback_control.h" | ||
31 | #endif | ||
32 | #include "lib/xlcd.h" | ||
33 | |||
34 | /* how many ticks between timer callbacks */ | ||
35 | #define TIMER_INTERVAL (HZ / 50) | ||
36 | #define BG_R .9f /* very light gray */ | ||
37 | #define BG_G .9f | ||
38 | #define BG_B .9f | ||
39 | |||
40 | #ifdef COMBINED | ||
41 | #define SAVE_FILE PLUGIN_GAMES_DATA_DIR "/puzzles.sav" | ||
42 | #else | ||
43 | static char save_file_path[MAX_PATH]; | ||
44 | #define SAVE_FILE ((const char*)save_file_path) | ||
45 | #endif | ||
46 | |||
47 | #define BG_COLOR LCD_RGBPACK((int)(255*BG_R), (int)(255*BG_G), (int)(255*BG_B)) | ||
48 | |||
49 | #define MURICA | ||
50 | |||
51 | #ifdef MURICA | ||
52 | #define midend_serialize midend_serialise | ||
53 | #define midend_deserialize midend_deserialise | ||
54 | #define frontend_default_color frontend_default_colour | ||
55 | #define midend_colors midend_colours | ||
56 | #endif | ||
57 | |||
58 | static midend *me = NULL; | ||
59 | static unsigned *colors = NULL; | ||
60 | static int ncolors = 0; | ||
61 | static long last_keystate = 0; | ||
62 | |||
63 | static void fix_size(void); | ||
64 | |||
65 | static void rb_start_draw(void *handle) | ||
66 | { | ||
67 | (void) handle; | ||
68 | } | ||
69 | |||
70 | static struct viewport clip_rect; | ||
71 | static bool clipped = false; | ||
72 | |||
73 | static struct settings_t { | ||
74 | int slowmo_factor; | ||
75 | bool bulk, timerflash; | ||
76 | } settings; | ||
77 | |||
78 | /* clipping is implemented through viewports and offsetting | ||
79 | * coordinates */ | ||
80 | static void rb_clip(void *handle, int x, int y, int w, int h) | ||
81 | { | ||
82 | LOGF("rb_clip(%d %d %d %d)", x, y, w, h); | ||
83 | clip_rect.x = x; | ||
84 | clip_rect.y = y; | ||
85 | clip_rect.width = w; | ||
86 | clip_rect.height = h; | ||
87 | clip_rect.font = FONT_UI; | ||
88 | clip_rect.drawmode = DRMODE_SOLID; | ||
89 | #if LCD_DEPTH > 1 | ||
90 | clip_rect.fg_pattern = LCD_DEFAULT_FG; | ||
91 | clip_rect.bg_pattern = LCD_DEFAULT_BG; | ||
92 | #endif | ||
93 | rb->lcd_set_viewport(&clip_rect); | ||
94 | clipped = true; | ||
95 | } | ||
96 | |||
97 | static void rb_unclip(void *handle) | ||
98 | { | ||
99 | LOGF("rb_unclip"); | ||
100 | rb->lcd_set_viewport(NULL); | ||
101 | clipped = false; | ||
102 | } | ||
103 | |||
104 | static void offset_coords(int *x, int *y) | ||
105 | { | ||
106 | if(clipped) | ||
107 | { | ||
108 | *x -= clip_rect.x; | ||
109 | *y -= clip_rect.y; | ||
110 | } | ||
111 | } | ||
112 | |||
113 | static void rb_color(int n) | ||
114 | { | ||
115 | if(n < 0) | ||
116 | { | ||
117 | fatal("bad color %d", n); | ||
118 | return; | ||
119 | } | ||
120 | rb->lcd_set_foreground(colors[n]); | ||
121 | } | ||
122 | |||
123 | static void rb_draw_text(void *handle, int x, int y, int fonttype, | ||
124 | int fontsize, int align, int color, char *text) | ||
125 | { | ||
126 | (void) fontsize; | ||
127 | LOGF("rb_draw_text(%d %d %s)", x, y, text); | ||
128 | |||
129 | offset_coords(&x, &y); | ||
130 | |||
131 | /* TODO: variable font size */ | ||
132 | switch(fonttype) | ||
133 | { | ||
134 | case FONT_FIXED: | ||
135 | rb->lcd_setfont(FONT_SYSFIXED); | ||
136 | break; | ||
137 | case FONT_VARIABLE: | ||
138 | rb->lcd_setfont(FONT_UI); | ||
139 | break; | ||
140 | default: | ||
141 | fatal("bad font"); | ||
142 | break; | ||
143 | } | ||
144 | |||
145 | int w, h; | ||
146 | rb->lcd_getstringsize(text, &w, &h); | ||
147 | |||
148 | static int cap_h = -1; | ||
149 | if(cap_h < 0) | ||
150 | rb->lcd_getstringsize("X", NULL, &cap_h); | ||
151 | |||
152 | if(align & ALIGN_VNORMAL) | ||
153 | y -= h; | ||
154 | else if(align & ALIGN_VCENTRE) | ||
155 | y -= cap_h / 2; | ||
156 | |||
157 | if(align & ALIGN_HCENTRE) | ||
158 | x -= w / 2; | ||
159 | else if(align & ALIGN_HRIGHT) | ||
160 | x -= w; | ||
161 | |||
162 | rb_color(color); | ||
163 | rb->lcd_set_drawmode(DRMODE_COMPLEMENT); | ||
164 | rb->lcd_putsxy(x, y, text); | ||
165 | rb->lcd_set_drawmode(DRMODE_SOLID); | ||
166 | } | ||
167 | |||
168 | static void rb_draw_rect(void *handle, int x, int y, int w, int h, int color) | ||
169 | { | ||
170 | LOGF("rb_draw_rect(%d, %d, %d, %d, %d)", x, y, w, h, color); | ||
171 | rb_color(color); | ||
172 | offset_coords(&x, &y); | ||
173 | rb->lcd_fillrect(x, y, w, h); | ||
174 | } | ||
175 | |||
176 | static void rb_draw_line(void *handle, int x1, int y1, int x2, int y2, | ||
177 | int color) | ||
178 | { | ||
179 | LOGF("rb_draw_line(%d, %d, %d, %d, %d)", x1, y1, x2, y2, color); | ||
180 | offset_coords(&x1, &y1); | ||
181 | offset_coords(&x2, &y2); | ||
182 | rb_color(color); | ||
183 | rb->lcd_drawline(x1, y1, x2, y2); | ||
184 | } | ||
185 | |||
186 | /* | ||
187 | * draw filled polygon | ||
188 | * originally by Sebastian Leonhardt (ulmutul) | ||
189 | * 'count' : number of coordinate pairs | ||
190 | * 'pxy': array of coordinates. pxy[0]=x0,pxy[1]=y0,... | ||
191 | * note: provide space for one extra coordinate, because the starting point | ||
192 | * will automatically be inserted as end point. | ||
193 | */ | ||
194 | |||
195 | /* | ||
196 | * helper function: | ||
197 | * find points of intersection between polygon and scanline | ||
198 | */ | ||
199 | |||
200 | #define MAX_INTERSECTION 32 | ||
201 | |||
202 | static void fill_poly_line(int scanline, int count, int *pxy) | ||
203 | { | ||
204 | int i; | ||
205 | int j; | ||
206 | int num_of_intersects; | ||
207 | int direct, old_direct; | ||
208 | //intersections of every line with scanline (y-coord) | ||
209 | int intersection[MAX_INTERSECTION]; | ||
210 | /* add starting point as ending point */ | ||
211 | pxy[count*2] = pxy[0]; | ||
212 | pxy[count*2+1] = pxy[1]; | ||
213 | |||
214 | old_direct=0; | ||
215 | num_of_intersects=0; | ||
216 | for (i=0; i<count*2; i+=2) { | ||
217 | int x1=pxy[i]; | ||
218 | int y1=pxy[i+1]; | ||
219 | int x2=pxy[i+2]; | ||
220 | int y2=pxy[i+3]; | ||
221 | // skip if line is outside of scanline | ||
222 | if (y1 < y2) { | ||
223 | if (scanline < y1 || scanline > y2) | ||
224 | continue; | ||
225 | } | ||
226 | else { | ||
227 | if (scanline < y2 || scanline > y1) | ||
228 | continue; | ||
229 | } | ||
230 | // calculate x-coord of intersection | ||
231 | if (y1==y2) { | ||
232 | direct=0; | ||
233 | } | ||
234 | else { | ||
235 | direct = y1>y2 ? 1 : -1; | ||
236 | // omit double intersections, if both lines lead in the same direction | ||
237 | intersection[num_of_intersects] = | ||
238 | x1+((scanline-y1)*(x2-x1))/(y2-y1); | ||
239 | if ( (direct!=old_direct) | ||
240 | || (intersection[num_of_intersects] != intersection[num_of_intersects-1]) | ||
241 | ) | ||
242 | ++num_of_intersects; | ||
243 | } | ||
244 | old_direct = direct; | ||
245 | } | ||
246 | |||
247 | // sort points of intersection | ||
248 | for (i=0; i<num_of_intersects-1; ++i) { | ||
249 | for (j=i+1; j<num_of_intersects; ++j) { | ||
250 | if (intersection[j]<intersection[i]) { | ||
251 | int temp=intersection[i]; | ||
252 | intersection[i]=intersection[j]; | ||
253 | intersection[j]=temp; | ||
254 | } | ||
255 | } | ||
256 | } | ||
257 | // draw | ||
258 | for (i=0; i<num_of_intersects; i+=2) { | ||
259 | rb->lcd_hline(intersection[i], intersection[i+1], scanline); | ||
260 | } | ||
261 | } | ||
262 | |||
263 | /* two extra elements at end of pxy needed */ | ||
264 | static void v_fillarea(int count, int *pxy) | ||
265 | { | ||
266 | int i; | ||
267 | int y1, y2; | ||
268 | |||
269 | // find min and max y coords | ||
270 | y1=y2=pxy[1]; | ||
271 | for (i=3; i<count*2; i+=2) { | ||
272 | if (pxy[i] < y1) y1 = pxy[i]; | ||
273 | else if (pxy[i] > y2) y2 = pxy[i]; | ||
274 | } | ||
275 | |||
276 | for (i=y1; i<=y2; ++i) { | ||
277 | fill_poly_line(i, count, pxy); | ||
278 | } | ||
279 | } | ||
280 | |||
281 | static void rb_draw_poly(void *handle, int *coords, int npoints, | ||
282 | int fillcolor, int outlinecolor) | ||
283 | { | ||
284 | LOGF("rb_draw_poly"); | ||
285 | |||
286 | if(fillcolor >= 0) | ||
287 | { | ||
288 | rb_color(fillcolor); | ||
289 | #if 1 | ||
290 | /* serious hack: draw a bunch of triangles between adjacent points */ | ||
291 | /* this generally works, even with some concave polygons */ | ||
292 | for(int i = 2; i < npoints; ++i) | ||
293 | { | ||
294 | int x1, y1, x2, y2, x3, y3; | ||
295 | x1 = coords[0]; | ||
296 | y1 = coords[1]; | ||
297 | x2 = coords[(i - 1) * 2]; | ||
298 | y2 = coords[(i - 1) * 2 + 1]; | ||
299 | x3 = coords[i * 2]; | ||
300 | y3 = coords[i * 2 + 1]; | ||
301 | offset_coords(&x1, &y1); | ||
302 | offset_coords(&x2, &y2); | ||
303 | offset_coords(&x3, &y3); | ||
304 | xlcd_filltriangle(x1, y1, | ||
305 | x2, y2, | ||
306 | x3, y3); | ||
307 | |||
308 | #if 0 | ||
309 | /* debug code */ | ||
310 | rb->lcd_set_foreground(LCD_RGBPACK(255,0,0)); | ||
311 | rb->lcd_drawpixel(x1, y1); | ||
312 | rb->lcd_drawpixel(x2, y2); | ||
313 | rb->lcd_drawpixel(x3, y3); | ||
314 | rb->lcd_update(); | ||
315 | rb->sleep(HZ); | ||
316 | rb_color(fillcolor); | ||
317 | rb->lcd_drawpixel(x1, y1); | ||
318 | rb->lcd_drawpixel(x2, y2); | ||
319 | rb->lcd_drawpixel(x3, y3); | ||
320 | rb->lcd_update(); | ||
321 | #endif | ||
322 | } | ||
323 | #else | ||
324 | int *pxy = smalloc(sizeof(int) * 2 * npoints + 2); | ||
325 | /* copy points, offsetted */ | ||
326 | for(int i = 0; i < npoints; ++i) | ||
327 | { | ||
328 | pxy[2 * i + 0] = coords[2 * i + 0]; | ||
329 | pxy[2 * i + 1] = coords[2 * i + 1]; | ||
330 | offset_coords(&pxy[2*i+0], &pxy[2*i+1]); | ||
331 | } | ||
332 | v_fillarea(npoints, pxy); | ||
333 | sfree(pxy); | ||
334 | #endif | ||
335 | } | ||
336 | |||
337 | /* draw outlines last so they're not covered by the fill */ | ||
338 | assert(outlinecolor >= 0); | ||
339 | rb_color(outlinecolor); | ||
340 | |||
341 | for(int i = 1; i < npoints; ++i) | ||
342 | { | ||
343 | int x1, y1, x2, y2; | ||
344 | x1 = coords[2 * (i - 1)]; | ||
345 | y1 = coords[2 * (i - 1) + 1]; | ||
346 | x2 = coords[2 * i]; | ||
347 | y2 = coords[2 * i + 1]; | ||
348 | offset_coords(&x1, &y1); | ||
349 | offset_coords(&x2, &y2); | ||
350 | rb->lcd_drawline(x1, y1, | ||
351 | x2, y2); | ||
352 | //rb->lcd_update(); | ||
353 | //rb->sleep(HZ/2); | ||
354 | } | ||
355 | |||
356 | int x1, y1, x2, y2; | ||
357 | x1 = coords[0]; | ||
358 | y1 = coords[1]; | ||
359 | x2 = coords[2 * (npoints - 1)]; | ||
360 | y2 = coords[2 * (npoints - 1) + 1]; | ||
361 | offset_coords(&x1, &y1); | ||
362 | offset_coords(&x2, &y2); | ||
363 | |||
364 | rb->lcd_drawline(x1, y1, | ||
365 | x2, y2); | ||
366 | } | ||
367 | |||
368 | static void rb_draw_circle(void *handle, int cx, int cy, int radius, | ||
369 | int fillcolor, int outlinecolor) | ||
370 | { | ||
371 | LOGF("rb_draw_circle(%d, %d, %d)", cx, cy, radius); | ||
372 | offset_coords(&cx, &cy); | ||
373 | |||
374 | if(fillcolor >= 0) | ||
375 | { | ||
376 | rb_color(fillcolor); | ||
377 | xlcd_fillcircle(cx, cy, radius - 1); | ||
378 | } | ||
379 | |||
380 | assert(outlinecolor >= 0); | ||
381 | rb_color(outlinecolor); | ||
382 | xlcd_drawcircle(cx, cy, radius - 1); | ||
383 | } | ||
384 | |||
385 | struct blitter { | ||
386 | bool have_data; | ||
387 | int x, y; | ||
388 | struct bitmap bmp; | ||
389 | }; | ||
390 | |||
391 | static blitter *rb_blitter_new(void *handle, int w, int h) | ||
392 | { | ||
393 | LOGF("rb_blitter_new"); | ||
394 | blitter *b = snew(blitter); | ||
395 | b->bmp.width = w; | ||
396 | b->bmp.height = h; | ||
397 | b->bmp.data = smalloc(w * h * sizeof(fb_data)); | ||
398 | b->have_data = false; | ||
399 | return b; | ||
400 | } | ||
401 | |||
402 | static void rb_blitter_free(void *handle, blitter *bl) | ||
403 | { | ||
404 | LOGF("rb_blitter_free"); | ||
405 | sfree(bl->bmp.data); | ||
406 | sfree(bl); | ||
407 | return; | ||
408 | } | ||
409 | |||
410 | static void trim_rect(int *x, int *y, int *w, int *h) | ||
411 | { | ||
412 | int x0, x1, y0, y1; | ||
413 | |||
414 | /* | ||
415 | * Reduce the size of the copied rectangle to stop it going | ||
416 | * outside the bounds of the canvas. | ||
417 | */ | ||
418 | |||
419 | /* Transform from x,y,w,h form into coordinates of all edges */ | ||
420 | x0 = *x; | ||
421 | y0 = *y; | ||
422 | x1 = *x + *w; | ||
423 | y1 = *y + *h; | ||
424 | |||
425 | /* Clip each coordinate at both extremes of the canvas */ | ||
426 | x0 = (x0 < 0 ? 0 : x0 > LCD_WIDTH ? LCD_WIDTH : x0); | ||
427 | x1 = (x1 < 0 ? 0 : x1 > LCD_WIDTH ? LCD_WIDTH : x1); | ||
428 | y0 = (y0 < 0 ? 0 : y0 > LCD_HEIGHT ? LCD_HEIGHT : y0); | ||
429 | y1 = (y1 < 0 ? 0 : y1 > LCD_HEIGHT ? LCD_HEIGHT : y1); | ||
430 | |||
431 | /* Transform back into x,y,w,h to return */ | ||
432 | *x = x0; | ||
433 | *y = y0; | ||
434 | *w = x1 - x0; | ||
435 | *h = y1 - y0; | ||
436 | } | ||
437 | |||
438 | /* copy a section of the framebuffer */ | ||
439 | static void rb_blitter_save(void *handle, blitter *bl, int x, int y) | ||
440 | { | ||
441 | /* no viewport offset */ | ||
442 | #if defined(LCD_STRIDEFORMAT) && (LCD_STRIDEFORMAT == VERTICAL_STRIDE) | ||
443 | #error no vertical stride | ||
444 | #else | ||
445 | if(bl->bmp.data) | ||
446 | { | ||
447 | int w = bl->bmp.width, h = bl->bmp.height; | ||
448 | trim_rect(&x, &y, &w, &h); | ||
449 | LOGF("rb_blitter_save(%d, %d, %d, %d)", x, y, w, h); | ||
450 | for(int i = 0; i < h; ++i) | ||
451 | { | ||
452 | /* copy line-by-line */ | ||
453 | rb->memcpy(bl->bmp.data + sizeof(fb_data) * i * w, | ||
454 | rb->lcd_framebuffer + (y + i) * LCD_WIDTH + x, | ||
455 | w * sizeof(fb_data)); | ||
456 | } | ||
457 | bl->x = x; | ||
458 | bl->y = y; | ||
459 | bl->have_data = true; | ||
460 | } | ||
461 | #endif | ||
462 | } | ||
463 | |||
464 | static void rb_blitter_load(void *handle, blitter *bl, int x, int y) | ||
465 | { | ||
466 | LOGF("rb_blitter_load"); | ||
467 | if(!bl->have_data) | ||
468 | return; | ||
469 | int w = bl->bmp.width, h = bl->bmp.height; | ||
470 | |||
471 | if(x == BLITTER_FROMSAVED) x = bl->x; | ||
472 | if(y == BLITTER_FROMSAVED) y = bl->y; | ||
473 | |||
474 | offset_coords(&x, &y); | ||
475 | trim_rect(&x, &y, &w, &h); | ||
476 | rb->lcd_bitmap((fb_data*)bl->bmp.data, x, y, w, h); | ||
477 | } | ||
478 | |||
479 | static void rb_draw_update(void *handle, int x, int y, int w, int h) | ||
480 | { | ||
481 | LOGF("rb_draw_update(%d, %d, %d, %d)", x, y, w, h); | ||
482 | if(!settings.bulk) | ||
483 | rb->lcd_update_rect(x, y, w, h); | ||
484 | else | ||
485 | rb->lcd_update(); | ||
486 | } | ||
487 | |||
488 | static void rb_end_draw(void *handle) | ||
489 | { | ||
490 | LOGF("rb_end_draw"); | ||
491 | } | ||
492 | |||
493 | static char *titlebar = NULL; | ||
494 | |||
495 | static void rb_status_bar(void *handle, char *text) | ||
496 | { | ||
497 | if(titlebar) | ||
498 | sfree(titlebar); | ||
499 | titlebar = dupstr(text); | ||
500 | LOGF("game title is %s\n", text); | ||
501 | } | ||
502 | |||
503 | static void draw_title(void) | ||
504 | { | ||
505 | const char *str = NULL; | ||
506 | if(titlebar) | ||
507 | str = titlebar; | ||
508 | else | ||
509 | str = midend_which_game(me)->name; | ||
510 | |||
511 | /* quick hack */ | ||
512 | bool orig_clipped = clipped; | ||
513 | if(orig_clipped) | ||
514 | rb_unclip(NULL); | ||
515 | |||
516 | int h; | ||
517 | rb->lcd_setfont(FONT_UI); | ||
518 | rb->lcd_getstringsize(str, NULL, &h); | ||
519 | |||
520 | rb->lcd_set_foreground(BG_COLOR); | ||
521 | rb->lcd_fillrect(0, LCD_HEIGHT - h, LCD_WIDTH, h); | ||
522 | |||
523 | rb->lcd_set_foreground(LCD_BLACK); | ||
524 | rb->lcd_putsxy(0, LCD_HEIGHT - h, str); | ||
525 | rb->lcd_update_rect(0, LCD_HEIGHT - h, LCD_WIDTH, h); | ||
526 | |||
527 | if(orig_clipped) | ||
528 | rb_clip(NULL, clip_rect.x, clip_rect.y, clip_rect.width, clip_rect.height); | ||
529 | } | ||
530 | |||
531 | static char *rb_text_fallback(void *handle, const char *const *strings, | ||
532 | int nstrings) | ||
533 | { | ||
534 | return dupstr(strings[0]); | ||
535 | } | ||
536 | |||
537 | const drawing_api rb_drawing = { | ||
538 | rb_draw_text, | ||
539 | rb_draw_rect, | ||
540 | rb_draw_line, | ||
541 | rb_draw_poly, | ||
542 | rb_draw_circle, | ||
543 | rb_draw_update, | ||
544 | rb_clip, | ||
545 | rb_unclip, | ||
546 | rb_start_draw, | ||
547 | rb_end_draw, | ||
548 | rb_status_bar, | ||
549 | rb_blitter_new, | ||
550 | rb_blitter_free, | ||
551 | rb_blitter_save, | ||
552 | rb_blitter_load, | ||
553 | NULL, NULL, NULL, NULL, NULL, NULL, /* {begin,end}_{doc,page,puzzle} */ | ||
554 | NULL, NULL, /* line_width, line_dotted */ | ||
555 | rb_text_fallback, | ||
556 | NULL, | ||
557 | }; | ||
558 | |||
559 | void frontend_default_color(frontend *fe, float *out) | ||
560 | { | ||
561 | *out++ = BG_R; | ||
562 | *out++ = BG_G; | ||
563 | *out++ = BG_B; | ||
564 | } | ||
565 | |||
566 | void fatal(char *fmt, ...) | ||
567 | { | ||
568 | va_list ap; | ||
569 | |||
570 | rb->splash(HZ, "FATAL ERROR"); | ||
571 | |||
572 | va_start(ap, fmt); | ||
573 | char buf[80]; | ||
574 | rb->vsnprintf(buf, 80, fmt, ap); | ||
575 | rb->splash(HZ * 2, buf); | ||
576 | va_end(ap); | ||
577 | |||
578 | exit(1); | ||
579 | } | ||
580 | |||
581 | void get_random_seed(void **randseed, int *randseedsize) | ||
582 | { | ||
583 | *randseed = snew(long); | ||
584 | long seed = *rb->current_tick; | ||
585 | rb->memcpy(*randseed, &seed, sizeof(seed)); | ||
586 | //*(long*)*randseed = 42; // debug | ||
587 | //rb->splash(HZ, "DEBUG SEED ON"); | ||
588 | *randseedsize = sizeof(long); | ||
589 | } | ||
590 | |||
591 | const char *config_choices_formatter(int sel, void *data, char *buf, size_t len) | ||
592 | { | ||
593 | /* we can't rely on being called in any particular order */ | ||
594 | char *list = dupstr(data); | ||
595 | char delimbuf[2] = { *list, 0 }; | ||
596 | char *save = NULL; | ||
597 | char *str = rb->strtok_r(list, delimbuf, &save); | ||
598 | for(int i = 0; i < sel; ++i) | ||
599 | str = rb->strtok_r(NULL, delimbuf, &save); | ||
600 | rb->snprintf(buf, len, "%s", str); | ||
601 | sfree(list); | ||
602 | return buf; | ||
603 | } | ||
604 | |||
605 | static int list_choose(const char *list_str, const char *title) | ||
606 | { | ||
607 | char delim = *list_str; | ||
608 | |||
609 | const char *ptr = list_str + 1; | ||
610 | int n = 0; | ||
611 | while(ptr) | ||
612 | { | ||
613 | n++; | ||
614 | ptr = strchr(ptr + 1, delim); | ||
615 | } | ||
616 | |||
617 | struct gui_synclist list; | ||
618 | |||
619 | rb->gui_synclist_init(&list, &config_choices_formatter, (void*)list_str, false, 1, NULL); | ||
620 | rb->gui_synclist_set_icon_callback(&list, NULL); | ||
621 | rb->gui_synclist_set_nb_items(&list, n); | ||
622 | rb->gui_synclist_limit_scroll(&list, false); | ||
623 | |||
624 | rb->gui_synclist_select_item(&list, 0); | ||
625 | |||
626 | rb->gui_synclist_set_title(&list, (char*)title, NOICON); | ||
627 | while (1) | ||
628 | { | ||
629 | rb->gui_synclist_draw(&list); | ||
630 | int button = rb->get_action(CONTEXT_LIST, TIMEOUT_BLOCK); | ||
631 | if(rb->gui_synclist_do_button(&list, &button, LIST_WRAP_ON)) | ||
632 | continue; | ||
633 | switch(button) | ||
634 | { | ||
635 | case ACTION_STD_OK: | ||
636 | return rb->gui_synclist_get_sel_pos(&list); | ||
637 | case ACTION_STD_PREV: | ||
638 | case ACTION_STD_CANCEL: | ||
639 | return -1; | ||
640 | default: | ||
641 | break; | ||
642 | } | ||
643 | } | ||
644 | } | ||
645 | |||
646 | static void do_configure_item(config_item *cfg) | ||
647 | { | ||
648 | switch(cfg->type) | ||
649 | { | ||
650 | case C_STRING: | ||
651 | { | ||
652 | #define MAX_STRLEN 128 | ||
653 | char *newstr = smalloc(MAX_STRLEN); | ||
654 | rb->strlcpy(newstr, cfg->sval, MAX_STRLEN); | ||
655 | rb->lcd_set_foreground(LCD_WHITE); | ||
656 | rb->lcd_set_background(LCD_BLACK); | ||
657 | if(rb->kbd_input(newstr, MAX_STRLEN) < 0) | ||
658 | { | ||
659 | sfree(newstr); | ||
660 | break; | ||
661 | } | ||
662 | sfree(cfg->sval); | ||
663 | cfg->sval = newstr; | ||
664 | break; | ||
665 | } | ||
666 | case C_BOOLEAN: | ||
667 | { | ||
668 | bool res = cfg->ival != 0; | ||
669 | rb->set_bool(cfg->name, &res); | ||
670 | cfg->ival = res; | ||
671 | break; | ||
672 | } | ||
673 | case C_CHOICES: | ||
674 | { | ||
675 | int sel = list_choose(cfg->sval, cfg->name); | ||
676 | if(sel >= 0) | ||
677 | cfg->ival = sel; | ||
678 | break; | ||
679 | } | ||
680 | default: | ||
681 | fatal("bad type"); | ||
682 | break; | ||
683 | } | ||
684 | } | ||
685 | |||
686 | const char *config_formatter(int sel, void *data, char *buf, size_t len) | ||
687 | { | ||
688 | config_item *cfg = data; | ||
689 | cfg += sel; | ||
690 | rb->snprintf(buf, len, "%s", cfg->name); | ||
691 | return buf; | ||
692 | } | ||
693 | |||
694 | static void config_menu(void) | ||
695 | { | ||
696 | char *title; | ||
697 | config_item *config = midend_get_config(me, CFG_SETTINGS, &title); | ||
698 | |||
699 | if(!config) | ||
700 | { | ||
701 | rb->splash(HZ, "Nothing to configure."); | ||
702 | goto done; | ||
703 | } | ||
704 | |||
705 | /* count */ | ||
706 | int n = 0; | ||
707 | config_item *ptr = config; | ||
708 | while(ptr->type != C_END) | ||
709 | { | ||
710 | n++; | ||
711 | ptr++; | ||
712 | } | ||
713 | |||
714 | /* display a list */ | ||
715 | struct gui_synclist list; | ||
716 | |||
717 | rb->gui_synclist_init(&list, &config_formatter, config, false, 1, NULL); | ||
718 | rb->gui_synclist_set_icon_callback(&list, NULL); | ||
719 | rb->gui_synclist_set_nb_items(&list, n); | ||
720 | rb->gui_synclist_limit_scroll(&list, false); | ||
721 | |||
722 | rb->gui_synclist_select_item(&list, 0); | ||
723 | |||
724 | bool done = false; | ||
725 | rb->gui_synclist_set_title(&list, title, NOICON); | ||
726 | while (!done) | ||
727 | { | ||
728 | rb->gui_synclist_draw(&list); | ||
729 | int button = rb->get_action(CONTEXT_LIST, TIMEOUT_BLOCK); | ||
730 | if(rb->gui_synclist_do_button(&list, &button, LIST_WRAP_ON)) | ||
731 | continue; | ||
732 | switch(button) | ||
733 | { | ||
734 | case ACTION_STD_OK: | ||
735 | { | ||
736 | config_item old; | ||
737 | int pos = rb->gui_synclist_get_sel_pos(&list); | ||
738 | memcpy(&old, config + pos, sizeof(old)); | ||
739 | do_configure_item(config + pos); | ||
740 | char *err = midend_set_config(me, CFG_SETTINGS, config); | ||
741 | if(err) | ||
742 | { | ||
743 | rb->splash(HZ, err); | ||
744 | memcpy(config + pos, &old, sizeof(old)); | ||
745 | } | ||
746 | break; | ||
747 | } | ||
748 | case ACTION_STD_PREV: | ||
749 | case ACTION_STD_CANCEL: | ||
750 | done = true; | ||
751 | break; | ||
752 | default: | ||
753 | break; | ||
754 | } | ||
755 | } | ||
756 | |||
757 | done: | ||
758 | sfree(title); | ||
759 | free_cfg(config); | ||
760 | } | ||
761 | |||
762 | const char *preset_formatter(int sel, void *data, char *buf, size_t len) | ||
763 | { | ||
764 | char *name; | ||
765 | game_params *junk; | ||
766 | midend_fetch_preset(me, sel, &name, &junk); | ||
767 | rb->strlcpy(buf, name, len); | ||
768 | return buf; | ||
769 | } | ||
770 | |||
771 | static void presets_menu(void) | ||
772 | { | ||
773 | if(!midend_num_presets(me)) | ||
774 | { | ||
775 | rb->splash(HZ, "No presets!"); | ||
776 | return; | ||
777 | } | ||
778 | |||
779 | /* display a list */ | ||
780 | struct gui_synclist list; | ||
781 | |||
782 | rb->gui_synclist_init(&list, &preset_formatter, NULL, false, 1, NULL); | ||
783 | rb->gui_synclist_set_icon_callback(&list, NULL); | ||
784 | rb->gui_synclist_set_nb_items(&list, midend_num_presets(me)); | ||
785 | rb->gui_synclist_limit_scroll(&list, false); | ||
786 | |||
787 | int current = midend_which_preset(me); | ||
788 | rb->gui_synclist_select_item(&list, current >= 0 ? current : 0); | ||
789 | |||
790 | bool done = false; | ||
791 | rb->gui_synclist_set_title(&list, "Game Type", NOICON); | ||
792 | while (!done) | ||
793 | { | ||
794 | rb->gui_synclist_draw(&list); | ||
795 | int button = rb->get_action(CONTEXT_LIST, TIMEOUT_BLOCK); | ||
796 | if(rb->gui_synclist_do_button(&list, &button, LIST_WRAP_ON)) | ||
797 | continue; | ||
798 | switch(button) | ||
799 | { | ||
800 | case ACTION_STD_OK: | ||
801 | { | ||
802 | int sel = rb->gui_synclist_get_sel_pos(&list); | ||
803 | char *junk; | ||
804 | game_params *params; | ||
805 | midend_fetch_preset(me, sel, &junk, ¶ms); | ||
806 | midend_set_params(me, params); | ||
807 | done = true; | ||
808 | break; | ||
809 | } | ||
810 | case ACTION_STD_PREV: | ||
811 | case ACTION_STD_CANCEL: | ||
812 | done = true; | ||
813 | break; | ||
814 | default: | ||
815 | break; | ||
816 | } | ||
817 | } | ||
818 | } | ||
819 | |||
820 | static const struct { | ||
821 | const char *game, *help; | ||
822 | } quick_help_text[] = { | ||
823 | { "Black Box", "Find the hidden balls in the box by bouncing laser beams off them." }, | ||
824 | { "Bridges", "Connect all the islands with a network of bridges." }, | ||
825 | { "Cube", "Pick up all the blue squares by rolling the cube over them." }, | ||
826 | { "Dominosa", "Tile the rectangle with a full set of dominoes." }, | ||
827 | { "Fifteen", "Slide the tiles around to arrange them into order." }, | ||
828 | { "Filling", "Mark every square with the area of its containing region." }, | ||
829 | { "Flip", "Flip groups of squares to light them all up at once." }, | ||
830 | { "Flood", "Turn the grid the same colour in as few flood fills as possible." }, | ||
831 | { "Galaxies", "Divide the grid into rotationally symmetric regions each centred on a dot." }, | ||
832 | { "Guess", "Guess the hidden combination of colours." }, | ||
833 | { "Inertia", "Collect all the gems without running into any of the mines." }, | ||
834 | { "Keen", "Complete the latin square in accordance with the arithmetic clues." }, | ||
835 | { "Light Up", "Place bulbs to light up all the squares." }, | ||
836 | { "Loopy", "Draw a single closed loop, given clues about number of adjacent edges." }, | ||
837 | { "Magnets", "Place magnets to satisfy the clues and avoid like poles touching." }, | ||
838 | { "Map", "Colour the map so that adjacent regions are never the same colour." }, | ||
839 | { "Mines", "Find all the mines without treading on any of them." }, | ||
840 | { "Net", "Rotate each tile to reassemble the network." }, | ||
841 | { "Netslide", "Slide a row at a time to reassemble the network." }, | ||
842 | { "Palisade", "Divide the grid into equal-sized areas in accordance with the clues." }, | ||
843 | { "Pattern", "Fill in the pattern in the grid, given only the lengths of runs of black squares." }, | ||
844 | { "Pearl", "Draw a single closed loop, given clues about corner and straight squares." }, | ||
845 | { "Pegs", "Jump pegs over each other to remove all but one." }, | ||
846 | { "Range", "Place black squares to limit the visible distance from each numbered cell." }, | ||
847 | { "Rectangles", "Divide the grid into rectangles with areas equal to the numbers." }, | ||
848 | { "Same Game", "Clear the grid by removing touching groups of the same colour squares." }, | ||
849 | { "Signpost", "Connect the squares into a path following the arrows." }, | ||
850 | { "Singles", "Black out the right set of duplicate numbers." }, | ||
851 | { "Sixteen", "Slide a row at a time to arrange the tiles into order." }, | ||
852 | { "Slant", "Draw a maze of slanting lines that matches the clues." }, | ||
853 | { "Solo", "Fill in the grid so that each row, column and square block contains one of every digit." }, | ||
854 | { "Tents", "Place a tent next to each tree." }, | ||
855 | { "Towers", "Complete the latin square of towers in accordance with the clues." }, | ||
856 | { "Tracks", "Fill in the railway track according to the clues." }, | ||
857 | { "Twiddle", "Rotate the tiles around themselves to arrange them into order." }, | ||
858 | { "Undead", "Place ghosts, vampires and zombies so that the right numbers of them can be seen in mirrors." }, | ||
859 | { "Unequal", "Complete the latin square in accordance with the > signs." }, | ||
860 | { "Unruly", "Fill in the black and white grid to avoid runs of three." }, | ||
861 | { "Untangle", "Reposition the points so that the lines do not cross." }, | ||
862 | }; | ||
863 | |||
864 | static void quick_help(void) | ||
865 | { | ||
866 | for(int i = 0; i < ARRAYLEN(quick_help_text); ++i) | ||
867 | { | ||
868 | if(!strcmp(midend_which_game(me)->name, quick_help_text[i].game)) | ||
869 | { | ||
870 | rb->splash(0, quick_help_text[i].help); | ||
871 | rb->button_get(true); | ||
872 | return; | ||
873 | } | ||
874 | } | ||
875 | } | ||
876 | |||
877 | static void full_help(void) | ||
878 | { | ||
879 | /* TODO */ | ||
880 | } | ||
881 | |||
882 | static void init_default_settings(void) | ||
883 | { | ||
884 | settings.slowmo_factor = 1; | ||
885 | settings.bulk = false; | ||
886 | settings.timerflash = false; | ||
887 | } | ||
888 | |||
889 | static void debug_menu(void) | ||
890 | { | ||
891 | MENUITEM_STRINGLIST(menu, "Debug Menu", NULL, | ||
892 | "Slowmo factor", | ||
893 | "Randomize colors", | ||
894 | "Toggle bulk update", | ||
895 | "Toggle flash pixel on timer", | ||
896 | "Back"); | ||
897 | bool quit = false; | ||
898 | int sel = 0; | ||
899 | while(!quit) | ||
900 | { | ||
901 | switch(rb->do_menu(&menu, &sel, NULL, false)) | ||
902 | { | ||
903 | case 0: | ||
904 | rb->set_int("Slowmo factor", "", UNIT_INT, &settings.slowmo_factor, NULL, 1, 1, 10, NULL); | ||
905 | break; | ||
906 | case 1: | ||
907 | { | ||
908 | unsigned *ptr = colors; | ||
909 | for(int i = 0; i < ncolors; ++i) | ||
910 | { | ||
911 | /* not seeded, who cares? */ | ||
912 | *ptr++ = LCD_RGBPACK(rb->rand()%255, rb->rand()%255, rb->rand()%255); | ||
913 | } | ||
914 | break; | ||
915 | } | ||
916 | case 2: | ||
917 | settings.bulk = !settings.bulk; | ||
918 | break; | ||
919 | case 3: | ||
920 | settings.timerflash = !settings.timerflash; | ||
921 | break; | ||
922 | case 4: | ||
923 | default: | ||
924 | quit = true; | ||
925 | break; | ||
926 | } | ||
927 | } | ||
928 | } | ||
929 | |||
930 | static int pausemenu_cb(int action, const struct menu_item_ex *this_item) | ||
931 | { | ||
932 | int i = (intptr_t) this_item; | ||
933 | if(action == ACTION_REQUEST_MENUITEM) | ||
934 | { | ||
935 | switch(i) | ||
936 | { | ||
937 | case 3: | ||
938 | if(!midend_can_undo(me)) | ||
939 | return ACTION_EXIT_MENUITEM; | ||
940 | break; | ||
941 | case 4: | ||
942 | if(!midend_can_redo(me)) | ||
943 | return ACTION_EXIT_MENUITEM; | ||
944 | break; | ||
945 | case 5: | ||
946 | if(!midend_which_game(me)->can_solve) | ||
947 | return ACTION_EXIT_MENUITEM; | ||
948 | break; | ||
949 | case 7: | ||
950 | #ifdef FOR_REAL | ||
951 | return ACTION_EXIT_MENUITEM; | ||
952 | #else | ||
953 | break; | ||
954 | #endif | ||
955 | case 8: | ||
956 | if(!midend_num_presets(me)) | ||
957 | return ACTION_EXIT_MENUITEM; | ||
958 | break; | ||
959 | case 9: | ||
960 | #ifdef FOR_REAL | ||
961 | return ACTION_EXIT_MENUITEM; | ||
962 | #else | ||
963 | break; | ||
964 | #endif | ||
965 | case 10: | ||
966 | if(!midend_which_game(me)->can_configure) | ||
967 | return ACTION_EXIT_MENUITEM; | ||
968 | break; | ||
969 | default: | ||
970 | break; | ||
971 | } | ||
972 | } | ||
973 | return action; | ||
974 | } | ||
975 | |||
976 | static int pause_menu(void) | ||
977 | { | ||
978 | #define static auto | ||
979 | #define const | ||
980 | MENUITEM_STRINGLIST(menu, NULL, pausemenu_cb, | ||
981 | "Resume Game", | ||
982 | "New Game", | ||
983 | "Restart Game", | ||
984 | "Undo", | ||
985 | "Redo", | ||
986 | "Solve", | ||
987 | "Quick Help", | ||
988 | "Extensive Help", | ||
989 | "Game Type", | ||
990 | "Debug Menu", | ||
991 | "Configure Game", | ||
992 | #ifdef COMBINED | ||
993 | "Select Another Game", | ||
994 | #endif | ||
995 | "Quit without Saving", | ||
996 | "Quit"); | ||
997 | #undef static | ||
998 | #undef const | ||
999 | /* HACK ALERT */ | ||
1000 | char title[32] = { 0 }; | ||
1001 | rb->snprintf(title, sizeof(title), "%s Menu", midend_which_game(me)->name); | ||
1002 | menu__.desc = title; | ||
1003 | |||
1004 | bool quit = false; | ||
1005 | int sel = 0; | ||
1006 | while(!quit) | ||
1007 | { | ||
1008 | switch(rb->do_menu(&menu, &sel, NULL, false)) | ||
1009 | { | ||
1010 | case 0: | ||
1011 | quit = true; | ||
1012 | break; | ||
1013 | case 1: | ||
1014 | midend_new_game(me); | ||
1015 | fix_size(); | ||
1016 | quit = true; | ||
1017 | break; | ||
1018 | case 2: | ||
1019 | midend_restart_game(me); | ||
1020 | fix_size(); | ||
1021 | quit = true; | ||
1022 | break; | ||
1023 | case 3: | ||
1024 | if(!midend_can_undo(me)) | ||
1025 | rb->splash(HZ, "Cannot undo."); | ||
1026 | else | ||
1027 | midend_process_key(me, 0, 0, 'u'); | ||
1028 | quit = true; | ||
1029 | break; | ||
1030 | case 4: | ||
1031 | if(!midend_can_redo(me)) | ||
1032 | rb->splash(HZ, "Cannot redo."); | ||
1033 | else | ||
1034 | midend_process_key(me, 0, 0, 'r'); | ||
1035 | quit = true; | ||
1036 | break; | ||
1037 | case 5: | ||
1038 | { | ||
1039 | char *msg = midend_solve(me); | ||
1040 | if(msg) | ||
1041 | rb->splash(HZ, msg); | ||
1042 | quit = true; | ||
1043 | break; | ||
1044 | } | ||
1045 | case 6: | ||
1046 | quick_help(); | ||
1047 | break; | ||
1048 | case 7: | ||
1049 | full_help(); | ||
1050 | break; | ||
1051 | case 8: | ||
1052 | presets_menu(); | ||
1053 | midend_new_game(me); | ||
1054 | fix_size(); | ||
1055 | quit = true; | ||
1056 | break; | ||
1057 | case 9: | ||
1058 | debug_menu(); | ||
1059 | break; | ||
1060 | case 10: | ||
1061 | config_menu(); | ||
1062 | midend_new_game(me); | ||
1063 | fix_size(); | ||
1064 | quit = true; | ||
1065 | break; | ||
1066 | #ifdef COMBINED | ||
1067 | case 11: | ||
1068 | return -1; | ||
1069 | case 12: | ||
1070 | return -2; | ||
1071 | case 13: | ||
1072 | return -3; | ||
1073 | #else | ||
1074 | case 11: | ||
1075 | return -2; | ||
1076 | case 12: | ||
1077 | return -3; | ||
1078 | #endif | ||
1079 | default: | ||
1080 | break; | ||
1081 | } | ||
1082 | } | ||
1083 | rb->lcd_set_background(BG_COLOR); | ||
1084 | rb->lcd_clear_display(); | ||
1085 | rb->lcd_update(); | ||
1086 | midend_force_redraw(me); | ||
1087 | return 0; | ||
1088 | } | ||
1089 | |||
1090 | static bool want_redraw = true; | ||
1091 | static bool accept_input = true; | ||
1092 | |||
1093 | /* ignore the excess of LOGFs below... */ | ||
1094 | #ifdef LOGF_ENABLE | ||
1095 | #undef LOGF_ENABLE | ||
1096 | #endif | ||
1097 | static int process_input(int tmo) | ||
1098 | { | ||
1099 | LOGF("process_input start"); | ||
1100 | LOGF("------------------"); | ||
1101 | int state = 0; | ||
1102 | |||
1103 | #ifdef HAVE_ADJUSTABLE_CPU_FREQ | ||
1104 | rb->cpu_boost(false); /* about to block for button input */ | ||
1105 | #endif | ||
1106 | |||
1107 | int button = rb->button_get_w_tmo(tmo); | ||
1108 | |||
1109 | /* weird stuff */ | ||
1110 | exit_on_usb(button); | ||
1111 | |||
1112 | button = rb->button_status(); | ||
1113 | |||
1114 | #ifdef HAVE_ADJUSTABLE_CPU_FREQ | ||
1115 | rb->cpu_boost(true); | ||
1116 | #endif | ||
1117 | |||
1118 | if(button == BTN_PAUSE) | ||
1119 | { | ||
1120 | want_redraw = false; | ||
1121 | /* quick hack to preserve the clipping state */ | ||
1122 | bool orig_clipped = clipped; | ||
1123 | if(orig_clipped) | ||
1124 | rb_unclip(NULL); | ||
1125 | |||
1126 | int rc = pause_menu(); | ||
1127 | |||
1128 | if(orig_clipped) | ||
1129 | rb_clip(NULL, clip_rect.x, clip_rect.y, clip_rect.width, clip_rect.height); | ||
1130 | |||
1131 | last_keystate = 0; | ||
1132 | accept_input = true; | ||
1133 | |||
1134 | return rc; | ||
1135 | } | ||
1136 | |||
1137 | /* special case for inertia: moves occur after RELEASES */ | ||
1138 | if(!strcmp("Inertia", midend_which_game(me)->name)) | ||
1139 | { | ||
1140 | LOGF("received button 0x%08x", button); | ||
1141 | |||
1142 | unsigned released = ~button & last_keystate; | ||
1143 | last_keystate = button; | ||
1144 | |||
1145 | if(!button) | ||
1146 | { | ||
1147 | if(!accept_input) | ||
1148 | { | ||
1149 | LOGF("ignoring, all keys released but not accepting input before, can accept input later"); | ||
1150 | accept_input = true; | ||
1151 | return 0; | ||
1152 | } | ||
1153 | } | ||
1154 | |||
1155 | if(!released || !accept_input) | ||
1156 | { | ||
1157 | LOGF("released keys detected: 0x%08x", released); | ||
1158 | LOGF("ignoring, either no keys released or not accepting input"); | ||
1159 | return 0; | ||
1160 | } | ||
1161 | |||
1162 | button |= released; | ||
1163 | if(last_keystate) | ||
1164 | { | ||
1165 | LOGF("ignoring input from now until all released"); | ||
1166 | accept_input = false; | ||
1167 | } | ||
1168 | LOGF("accepting event 0x%08x", button); | ||
1169 | } | ||
1170 | /* not inertia: events fire on presses */ | ||
1171 | else | ||
1172 | { | ||
1173 | /* start accepting input again after a release */ | ||
1174 | if(!button) | ||
1175 | { | ||
1176 | accept_input = true; | ||
1177 | return 0; | ||
1178 | } | ||
1179 | /* ignore repeats */ | ||
1180 | if(!accept_input) | ||
1181 | return 0; | ||
1182 | accept_input = false; | ||
1183 | } | ||
1184 | |||
1185 | switch(button) | ||
1186 | { | ||
1187 | case BTN_UP: | ||
1188 | state = CURSOR_UP; | ||
1189 | break; | ||
1190 | case BTN_DOWN: | ||
1191 | state = CURSOR_DOWN; | ||
1192 | break; | ||
1193 | case BTN_LEFT: | ||
1194 | state = CURSOR_LEFT; | ||
1195 | break; | ||
1196 | case BTN_RIGHT: | ||
1197 | state = CURSOR_RIGHT; | ||
1198 | break; | ||
1199 | |||
1200 | /* handle diagonals (mainly for Inertia) */ | ||
1201 | case BTN_DOWN | BTN_LEFT: | ||
1202 | #ifdef BTN_DOWN_LEFT | ||
1203 | case BTN_DOWN_LEFT: | ||
1204 | #endif | ||
1205 | state = '1' | MOD_NUM_KEYPAD; | ||
1206 | break; | ||
1207 | case BTN_DOWN | BTN_RIGHT: | ||
1208 | #ifdef BTN_DOWN_RIGHT | ||
1209 | case BTN_DOWN_RIGHT: | ||
1210 | #endif | ||
1211 | state = '3' | MOD_NUM_KEYPAD; | ||
1212 | break; | ||
1213 | case BTN_UP | BTN_LEFT: | ||
1214 | #ifdef BTN_UP_LEFT | ||
1215 | case BTN_UP_LEFT: | ||
1216 | #endif | ||
1217 | state = '7' | MOD_NUM_KEYPAD; | ||
1218 | break; | ||
1219 | case BTN_UP | BTN_RIGHT: | ||
1220 | #ifdef BTN_UP_RIGHT | ||
1221 | case BTN_UP_RIGHT: | ||
1222 | #endif | ||
1223 | state = '9' | MOD_NUM_KEYPAD; | ||
1224 | break; | ||
1225 | |||
1226 | case BTN_FIRE: | ||
1227 | state = CURSOR_SELECT; | ||
1228 | break; | ||
1229 | } | ||
1230 | LOGF("process_input done"); | ||
1231 | LOGF("------------------"); | ||
1232 | return state; | ||
1233 | } | ||
1234 | |||
1235 | static long last_tstamp; | ||
1236 | |||
1237 | static void timer_cb(void) | ||
1238 | { | ||
1239 | #if LCD_DEPTH != 24 | ||
1240 | if(settings.timerflash) | ||
1241 | { | ||
1242 | static bool what = false; | ||
1243 | what = !what; | ||
1244 | if(what) | ||
1245 | rb->lcd_framebuffer[0] = LCD_BLACK; | ||
1246 | else | ||
1247 | rb->lcd_framebuffer[0] = LCD_WHITE; | ||
1248 | rb->lcd_update(); | ||
1249 | } | ||
1250 | #endif | ||
1251 | |||
1252 | LOGF("timer callback"); | ||
1253 | midend_timer(me, ((float)(*rb->current_tick - last_tstamp) / (float)HZ) / settings.slowmo_factor); | ||
1254 | last_tstamp = *rb->current_tick; | ||
1255 | } | ||
1256 | |||
1257 | static volatile bool timer_on = false; | ||
1258 | |||
1259 | void activate_timer(frontend *fe) | ||
1260 | { | ||
1261 | last_tstamp = *rb->current_tick; | ||
1262 | timer_on = true; | ||
1263 | } | ||
1264 | |||
1265 | void deactivate_timer(frontend *fe) | ||
1266 | { | ||
1267 | timer_on = false; | ||
1268 | } | ||
1269 | |||
1270 | #ifdef COMBINED | ||
1271 | /* can't use audio buffer */ | ||
1272 | static char giant_buffer[1024*1024*4]; | ||
1273 | #else | ||
1274 | /* points to audiobuf */ | ||
1275 | static char *giant_buffer = NULL; | ||
1276 | #endif | ||
1277 | static size_t giant_buffer_len = 0; /* set on start */ | ||
1278 | |||
1279 | #ifdef COMBINED | ||
1280 | const char *formatter(char *buf, size_t n, int i, const char *unit) | ||
1281 | { | ||
1282 | rb->snprintf(buf, n, "%s", gamelist[i]->name); | ||
1283 | return buf; | ||
1284 | } | ||
1285 | #endif | ||
1286 | |||
1287 | static void fix_size(void) | ||
1288 | { | ||
1289 | int w = LCD_WIDTH, h = LCD_HEIGHT, h_x; | ||
1290 | rb->lcd_setfont(FONT_UI); | ||
1291 | rb->lcd_getstringsize("X", NULL, &h_x); | ||
1292 | h -= h_x; | ||
1293 | midend_size(me, &w, &h, TRUE); | ||
1294 | } | ||
1295 | |||
1296 | static void reset_tlsf(void) | ||
1297 | { | ||
1298 | /* reset tlsf by nuking the signature */ | ||
1299 | /* will make any already-allocated memory point to garbage */ | ||
1300 | memset(giant_buffer, 0, 4); | ||
1301 | init_memory_pool(giant_buffer_len, giant_buffer); | ||
1302 | } | ||
1303 | |||
1304 | static int read_wrapper(void *ptr, void *buf, int len) | ||
1305 | { | ||
1306 | int fd = (int) ptr; | ||
1307 | return rb->read(fd, buf, len); | ||
1308 | } | ||
1309 | |||
1310 | static void write_wrapper(void *ptr, void *buf, int len) | ||
1311 | { | ||
1312 | int fd = (int) ptr; | ||
1313 | rb->write(fd, buf, len); | ||
1314 | } | ||
1315 | |||
1316 | static void clear_and_draw(void) | ||
1317 | { | ||
1318 | rb->lcd_clear_display(); | ||
1319 | rb->lcd_update(); | ||
1320 | |||
1321 | midend_force_redraw(me); | ||
1322 | draw_title(); | ||
1323 | } | ||
1324 | |||
1325 | static char *init_for_game(const game *gm, int load_fd, bool draw) | ||
1326 | { | ||
1327 | /* if we are loading a game tlsf has already been initialized */ | ||
1328 | if(load_fd < 0) | ||
1329 | reset_tlsf(); | ||
1330 | |||
1331 | me = midend_new(NULL, gm, &rb_drawing, NULL); | ||
1332 | |||
1333 | if(load_fd < 0) | ||
1334 | midend_new_game(me); | ||
1335 | else | ||
1336 | { | ||
1337 | char *ret = midend_deserialize(me, read_wrapper, (void*) load_fd); | ||
1338 | if(ret) | ||
1339 | return ret; | ||
1340 | } | ||
1341 | |||
1342 | fix_size(); | ||
1343 | |||
1344 | float *floatcolors = midend_colors(me, &ncolors); | ||
1345 | |||
1346 | /* convert them to packed RGB */ | ||
1347 | colors = smalloc(ncolors * sizeof(unsigned)); | ||
1348 | unsigned *ptr = colors; | ||
1349 | float *floatptr = floatcolors; | ||
1350 | for(int i = 0; i < ncolors; ++i) | ||
1351 | { | ||
1352 | int r = 255 * *(floatptr++); | ||
1353 | int g = 255 * *(floatptr++); | ||
1354 | int b = 255 * *(floatptr++); | ||
1355 | LOGF("color %d is %d %d %d", i, r, g, b); | ||
1356 | *ptr++ = LCD_RGBPACK(r, g, b); | ||
1357 | } | ||
1358 | sfree(floatcolors); | ||
1359 | |||
1360 | rb->lcd_set_viewport(NULL); | ||
1361 | rb->lcd_set_backdrop(NULL); | ||
1362 | rb->lcd_set_foreground(LCD_BLACK); | ||
1363 | rb->lcd_set_background(BG_COLOR); | ||
1364 | |||
1365 | if(draw) | ||
1366 | { | ||
1367 | clear_and_draw(); | ||
1368 | } | ||
1369 | return NULL; | ||
1370 | } | ||
1371 | |||
1372 | static void exit_handler(void) | ||
1373 | { | ||
1374 | #ifdef HAVE_ADJUSTABLE_CPU_FREQ | ||
1375 | rb->cpu_boost(false); | ||
1376 | #endif | ||
1377 | } | ||
1378 | |||
1379 | /* expects a totally free me* pointer */ | ||
1380 | static bool load_game(void) | ||
1381 | { | ||
1382 | reset_tlsf(); | ||
1383 | |||
1384 | int fd = rb->open(SAVE_FILE, O_RDONLY); | ||
1385 | if(fd < 0) | ||
1386 | return false; | ||
1387 | |||
1388 | rb->splash(0, "Loading..."); | ||
1389 | |||
1390 | LOGF("opening %s", SAVE_FILE); | ||
1391 | |||
1392 | char *game; | ||
1393 | char *ret = identify_game(&game, read_wrapper, (void*)fd); | ||
1394 | |||
1395 | if(!*game && ret) | ||
1396 | { | ||
1397 | sfree(game); | ||
1398 | rb->splash(HZ, ret); | ||
1399 | rb->close(fd); | ||
1400 | return false; | ||
1401 | } | ||
1402 | else | ||
1403 | { | ||
1404 | /* seek to beginning */ | ||
1405 | rb->lseek(fd, 0, SEEK_SET); | ||
1406 | |||
1407 | #ifdef COMBINED | ||
1408 | /* search for the game and initialize the midend */ | ||
1409 | for(int i = 0; i < gamecount; ++i) | ||
1410 | { | ||
1411 | if(!strcmp(game, gamelist[i]->name)) | ||
1412 | { | ||
1413 | sfree(ret); | ||
1414 | ret = init_for_game(gamelist[i], fd, true); | ||
1415 | if(ret) | ||
1416 | { | ||
1417 | rb->splash(HZ, ret); | ||
1418 | sfree(ret); | ||
1419 | rb->close(fd); | ||
1420 | return false; | ||
1421 | } | ||
1422 | rb->close(fd); | ||
1423 | /* success, we delete the save */ | ||
1424 | rb->remove(SAVE_FILE); | ||
1425 | return true; | ||
1426 | } | ||
1427 | } | ||
1428 | rb->splashf(HZ, "Incompatible game %s reported as compatible!?!? REEEPORT MEEEE!!!!", game); | ||
1429 | rb->close(fd); | ||
1430 | return false; | ||
1431 | #else | ||
1432 | if(!strcmp(game, thegame.name)) | ||
1433 | { | ||
1434 | sfree(ret); | ||
1435 | ret = init_for_game(&thegame, fd, false); | ||
1436 | if(ret) | ||
1437 | { | ||
1438 | rb->splash(HZ, ret); | ||
1439 | sfree(ret); | ||
1440 | rb->close(fd); | ||
1441 | return false; | ||
1442 | } | ||
1443 | rb->close(fd); | ||
1444 | /* success, we delete the save */ | ||
1445 | rb->remove(SAVE_FILE); | ||
1446 | return true; | ||
1447 | } | ||
1448 | rb->splashf(HZ, "Cannot load save game for %s!", game); | ||
1449 | return false; | ||
1450 | #endif | ||
1451 | } | ||
1452 | } | ||
1453 | |||
1454 | static void save_game(void) | ||
1455 | { | ||
1456 | rb->splash(0, "Saving..."); | ||
1457 | int fd = rb->open(SAVE_FILE, O_WRONLY | O_CREAT | O_TRUNC, 0666); | ||
1458 | midend_serialize(me, write_wrapper, (void*) fd); | ||
1459 | rb->close(fd); | ||
1460 | rb->lcd_update(); | ||
1461 | } | ||
1462 | |||
1463 | static bool load_success; | ||
1464 | |||
1465 | static int mainmenu_cb(int action, const struct menu_item_ex *this_item) | ||
1466 | { | ||
1467 | int i = (intptr_t) this_item; | ||
1468 | if(action == ACTION_REQUEST_MENUITEM) | ||
1469 | { | ||
1470 | switch(i) | ||
1471 | { | ||
1472 | case 0: | ||
1473 | case 7: | ||
1474 | if(!load_success) | ||
1475 | return ACTION_EXIT_MENUITEM; | ||
1476 | break; | ||
1477 | case 3: | ||
1478 | #ifdef FOR_REAL | ||
1479 | return ACTION_EXIT_MENUITEM; | ||
1480 | #else | ||
1481 | break; | ||
1482 | #endif | ||
1483 | case 5: | ||
1484 | if(!midend_num_presets(me)) | ||
1485 | return ACTION_EXIT_MENUITEM; | ||
1486 | break; | ||
1487 | case 6: | ||
1488 | if(!midend_which_game(me)->can_configure) | ||
1489 | return ACTION_EXIT_MENUITEM; | ||
1490 | break; | ||
1491 | default: | ||
1492 | break; | ||
1493 | } | ||
1494 | } | ||
1495 | return action; | ||
1496 | } | ||
1497 | |||
1498 | enum plugin_status plugin_start(const void *param) | ||
1499 | { | ||
1500 | (void) param; | ||
1501 | |||
1502 | #ifdef HAVE_ADJUSTABLE_CPU_FREQ | ||
1503 | /* boost for init */ | ||
1504 | rb->cpu_boost(true); | ||
1505 | #endif | ||
1506 | |||
1507 | #ifdef COMBINED | ||
1508 | giant_buffer_len = sizeof(giant_buffer); | ||
1509 | #else | ||
1510 | giant_buffer = rb->plugin_get_buffer(&giant_buffer_len); | ||
1511 | #endif | ||
1512 | |||
1513 | #ifndef COMBINED | ||
1514 | rb->snprintf(save_file_path, sizeof(save_file_path), "%s/sgt-%s.sav", PLUGIN_GAMES_DATA_DIR, thegame.htmlhelp_topic); | ||
1515 | #endif | ||
1516 | |||
1517 | rb_atexit(exit_handler); | ||
1518 | |||
1519 | if(fabs(sqrt(3)/2 - sin(PI/3)) > .01) | ||
1520 | rb->splash(HZ, "WARNING: floating-point functions are being weird... report me!"); | ||
1521 | |||
1522 | init_default_settings(); | ||
1523 | |||
1524 | load_success = load_game(); | ||
1525 | |||
1526 | #ifndef COMBINED | ||
1527 | if(!load_success) | ||
1528 | { | ||
1529 | /* our main menu expects a ready-to-use midend */ | ||
1530 | init_for_game(&thegame, -1, false); | ||
1531 | } | ||
1532 | #endif | ||
1533 | |||
1534 | #ifdef HAVE_ADJUSTABLE_CPU_FREQ | ||
1535 | /* about to go to menu or button block */ | ||
1536 | rb->cpu_boost(false); | ||
1537 | #endif | ||
1538 | |||
1539 | #ifndef COMBINED | ||
1540 | #define static auto | ||
1541 | #define const | ||
1542 | MENUITEM_STRINGLIST(menu, NULL, mainmenu_cb, | ||
1543 | "Resume Game", | ||
1544 | "New Game", | ||
1545 | "Quick Help", | ||
1546 | "Extensive Help", | ||
1547 | "Playback Control", | ||
1548 | "Game Type", | ||
1549 | "Configure Game", | ||
1550 | "Quit without Saving", | ||
1551 | "Quit"); | ||
1552 | #undef static | ||
1553 | #undef const | ||
1554 | |||
1555 | /* HACK ALERT */ | ||
1556 | char title[32] = { 0 }; | ||
1557 | rb->snprintf(title, sizeof(title), "%s Menu", midend_which_game(me)->name); | ||
1558 | menu__.desc = title; | ||
1559 | |||
1560 | bool quit = false; | ||
1561 | int sel = 0; | ||
1562 | while(!quit) | ||
1563 | { | ||
1564 | switch(rb->do_menu(&menu, &sel, NULL, false)) | ||
1565 | { | ||
1566 | case 0: | ||
1567 | clear_and_draw(); | ||
1568 | goto game_loop; | ||
1569 | case 1: | ||
1570 | if(!load_success) | ||
1571 | { | ||
1572 | clear_and_draw(); | ||
1573 | goto game_loop; | ||
1574 | } | ||
1575 | quit = true; | ||
1576 | break; | ||
1577 | case 2: | ||
1578 | quick_help(); | ||
1579 | break; | ||
1580 | case 3: | ||
1581 | full_help(); | ||
1582 | break; | ||
1583 | case 4: | ||
1584 | playback_control(NULL); | ||
1585 | break; | ||
1586 | case 5: | ||
1587 | presets_menu(); | ||
1588 | break; | ||
1589 | case 6: | ||
1590 | config_menu(); | ||
1591 | break; | ||
1592 | case 8: | ||
1593 | if(load_success) | ||
1594 | save_game(); | ||
1595 | /* fall through */ | ||
1596 | case 7: | ||
1597 | /* we don't care about freeing anything because tlsf will | ||
1598 | * be wiped out the next time around */ | ||
1599 | return PLUGIN_OK; | ||
1600 | default: | ||
1601 | break; | ||
1602 | } | ||
1603 | } | ||
1604 | #else | ||
1605 | if(load_success) | ||
1606 | goto game_loop; | ||
1607 | #endif | ||
1608 | |||
1609 | #ifdef COMBINED | ||
1610 | int gm = 0; | ||
1611 | #endif | ||
1612 | while(1) | ||
1613 | { | ||
1614 | #ifdef COMBINED | ||
1615 | if(rb->set_int("Choose Game", "", UNIT_INT, &gm, NULL, 1, 0, gamecount - 1, formatter)) | ||
1616 | return PLUGIN_OK; | ||
1617 | |||
1618 | init_for_game(gamelist[gm], -1, true); | ||
1619 | #else | ||
1620 | init_for_game(&thegame, -1, true); | ||
1621 | #endif | ||
1622 | |||
1623 | last_keystate = 0; | ||
1624 | accept_input = true; | ||
1625 | |||
1626 | game_loop: | ||
1627 | while(1) | ||
1628 | { | ||
1629 | want_redraw = true; | ||
1630 | |||
1631 | draw_title(); | ||
1632 | |||
1633 | int button = process_input(timer_on ? TIMER_INTERVAL : -1); | ||
1634 | |||
1635 | if(button < 0) | ||
1636 | { | ||
1637 | rb_unclip(NULL); | ||
1638 | deactivate_timer(NULL); | ||
1639 | |||
1640 | if(titlebar) | ||
1641 | { | ||
1642 | sfree(titlebar); | ||
1643 | titlebar = NULL; | ||
1644 | } | ||
1645 | |||
1646 | if(button == -1) | ||
1647 | { | ||
1648 | /* new game */ | ||
1649 | midend_free(me); | ||
1650 | break; | ||
1651 | } | ||
1652 | else if(button == -2) | ||
1653 | { | ||
1654 | /* quit without saving */ | ||
1655 | midend_free(me); | ||
1656 | sfree(colors); | ||
1657 | exit(PLUGIN_OK); | ||
1658 | } | ||
1659 | else if(button == -3) | ||
1660 | { | ||
1661 | /* save and quit */ | ||
1662 | save_game(); | ||
1663 | midend_free(me); | ||
1664 | sfree(colors); | ||
1665 | exit(PLUGIN_OK); | ||
1666 | } | ||
1667 | } | ||
1668 | |||
1669 | if(button) | ||
1670 | midend_process_key(me, 0, 0, button); | ||
1671 | |||
1672 | if(want_redraw) | ||
1673 | midend_redraw(me); | ||
1674 | |||
1675 | if(timer_on) | ||
1676 | timer_cb(); | ||
1677 | |||
1678 | rb->yield(); | ||
1679 | } | ||
1680 | sfree(colors); | ||
1681 | } | ||
1682 | } | ||