From e8e85c5762da65ef7fa6e49ee8cc61f132be6d34 Mon Sep 17 00:00:00 2001 From: Franklin Wei Date: Tue, 21 Nov 2017 19:28:16 -0500 Subject: [PATCH] puzzles: resync with upstream; add Loopy and Palisade, mouse mode This brings a various small changes to the drawing and input code, as well as a brand new "mouse mode", where input goes to a virtual mouse cursor. Only Loopy has this mouse mode enabled by default, while other games have it hidden away under the debug menu. Some changes by me to Palisade were required to make it playable; those are included here as well. Right now, sgt-net is pushing the c200v2's upper limit on size and may have to be dropped in a future commit. Change-Id: I495d2a2125462c2985aec1ffbc54bbe3fe5133bd --- apps/plugins/bitmaps/native/SOURCES | 5 + apps/plugins/puzzles/SOURCES.games | 20 +- apps/plugins/puzzles/rbmalloc.c | 6 +- apps/plugins/puzzles/rockbox.c | 449 ++++++++++++++++++++++------ apps/plugins/puzzles/src/LICENCE | 5 +- apps/plugins/puzzles/src/filling.c | 2 +- apps/plugins/puzzles/src/grid.c | 96 ++++++ apps/plugins/puzzles/src/grid.h | 1 + apps/plugins/puzzles/src/loopy.c | 3 + apps/plugins/puzzles/src/palisade.c | 21 +- 10 files changed, 500 insertions(+), 108 deletions(-) diff --git a/apps/plugins/bitmaps/native/SOURCES b/apps/plugins/bitmaps/native/SOURCES index b6a563d2b0..99297306e7 100644 --- a/apps/plugins/bitmaps/native/SOURCES +++ b/apps/plugins/bitmaps/native/SOURCES @@ -538,6 +538,11 @@ pegbox_pieces.9x7x1.bmp #endif #endif +/* Puzzles */ +#if defined(HAVE_LCD_COLOR) && \ + (!defined(LCD_STRIDEFORMAT) || (LCD_STRIDEFORMAT != VERTICAL_STRIDE)) +puzzles_cursor.11x16x24.bmp +#endif /* Rockblox */ #if LCD_DEPTH >= 16 /* colour versions*/ diff --git a/apps/plugins/puzzles/SOURCES.games b/apps/plugins/puzzles/SOURCES.games index 8893c3e9a4..2fbedae073 100644 --- a/apps/plugins/puzzles/SOURCES.games +++ b/apps/plugins/puzzles/SOURCES.games @@ -5,21 +5,20 @@ src/bridges.c src/cube.c src/dominosa.c src/fifteen.c -/*src/filling.c*/ +//src/filling.c /* requires numeric input */ src/flip.c src/flood.c src/galaxies.c src/guess.c src/inertia.c -/*src/keen.c*/ +//src/keen.c /* numeric input */ src/lightup.c -/*src/loopy.c*/ src/magnets.c src/map.c src/mines.c src/net.c src/netslide.c -/*src/palisade.c*/ +src/palisade.c src/pattern.c src/pegs.c src/range.c @@ -29,17 +28,16 @@ src/signpost.c src/singles.c src/sixteen.c src/slant.c -/*src/solo.c*/ src/tents.c -/*src/towers.c*/ +//src/towers.c /* numeric input */ src/tracks.c src/twiddle.c -/*src/undead.c*/ -/*src/unequal.c*/ +//src/undead.c /* keyboard input */ +//src/unequal.c /* numeric input */ src/unruly.c src/untangle.c -/* disabled for now */ +/* disabled for now (fix puzzles.make and CATEGORIES to accomodate these) */ /*src/unfinished/group.c*/ /*src/unfinished/separate.c*/ /*src/unfinished/slide.c*/ @@ -48,6 +46,6 @@ src/untangle.c /* no c200v2 */ #if PLUGIN_BUFFER_SIZE > 0x14000 src/pearl.c -/*src/loopy.c*/ -/*src/solo.c*/ +src/loopy.c /* mouse input */ +//src/solo.c /* numeric input */ #endif diff --git a/apps/plugins/puzzles/rbmalloc.c b/apps/plugins/puzzles/rbmalloc.c index 1cb903ef4f..4baca79c3d 100644 --- a/apps/plugins/puzzles/rbmalloc.c +++ b/apps/plugins/puzzles/rbmalloc.c @@ -51,7 +51,7 @@ static bool grab_audiobuf(void) void *smalloc(size_t size) { void *p; p = malloc(size); - LOGF("allocs: %d", ++allocs); + //LOGF("allocs: %d", ++allocs); if (!p) { if(grab_audiobuf()) @@ -67,7 +67,7 @@ void *smalloc(size_t size) { void sfree(void *p) { if (p) { ++frees; - LOGF("frees: %d, total outstanding: %d", frees, allocs - frees); + //LOGF("frees: %d, total outstanding: %d", frees, allocs - frees); free(p); } } @@ -80,7 +80,7 @@ void *srealloc(void *p, size_t size) { if (p) { q = realloc(p, size); } else { - LOGF("allocs: %d", ++allocs); + //LOGF("allocs: %d", ++allocs); q = malloc(size); } if (!q) diff --git a/apps/plugins/puzzles/rockbox.c b/apps/plugins/puzzles/rockbox.c index 44df35027a..f77b538019 100644 --- a/apps/plugins/puzzles/rockbox.c +++ b/apps/plugins/puzzles/rockbox.c @@ -44,6 +44,8 @@ #include "fixedpoint.h" +#include "pluginbitmaps/puzzles_cursor.h" + /* how many ticks between timer callbacks */ #define TIMER_INTERVAL (HZ / 50) @@ -122,12 +124,14 @@ static int help_times = 0; /* clipping stuff */ static struct viewport clip_rect; -static bool clipped = false, zoom_enabled = false, view_mode = true; +static bool clipped = false, zoom_enabled = false, view_mode = true, mouse_mode = false; + +static int mouse_x, mouse_y; extern bool audiobuf_available; /* defined in rbmalloc.c */ static fb_data *zoom_fb; /* dynamically allocated */ -static int zoom_w, zoom_h, zoom_clipu, zoom_clipd, zoom_clipl, zoom_clipr; +static int zoom_x, zoom_y, zoom_w, zoom_h, zoom_clipu, zoom_clipd, zoom_clipl, zoom_clipr; static int cur_font = FONT_UI; static bool need_draw_update = false; @@ -135,7 +139,12 @@ static int ud_l = 0, ud_u = 0, ud_r = LCD_WIDTH, ud_d = LCD_HEIGHT; static char *titlebar = NULL; -static bool want_redraw = true, accept_input = true; +/* how to process the input (custom, per-game) */ +static struct { + bool want_spacebar, falling_edge, ignore_repeats, rclick_on_hold; +} input_settings; + +static bool accept_input = true; /* last timer call */ static long last_tstamp; @@ -145,10 +154,10 @@ static bool load_success; /* debug settings */ /* did I mention there's a secret debug menu? */ -static struct settings_t { +static struct { int slowmo_factor; bool timerflash, clipoff, shortcuts, no_aa, polyanim; -} settings; +} debug_settings; /* re-implementations of many rockbox primitives, adapted to draw into * a custom framebuffer. */ @@ -469,7 +478,7 @@ static void rb_clip(void *handle, int x, int y, int w, int h) { if(!zoom_enabled) { - if(!settings.clipoff) + if(!debug_settings.clipoff) { LOGF("rb_clip(%d %d %d %d)", x, y, w, h); clip_rect.x = MAX(0, x); @@ -736,7 +745,7 @@ static void rb_draw_line(void *handle, int x1, int y1, int x2, int y2, { LOGF("rb_draw_line(%d, %d, %d, %d, %d)", x1, y1, x2, y2, color); rb_color(color); - if(settings.no_aa) + if(debug_settings.no_aa) { offset_coords(&x1, &y1); offset_coords(&x2, &y2); @@ -968,7 +977,7 @@ static void rb_draw_poly(void *handle, int *coords, int npoints, x3, y3); #ifdef DEBUG_MENU - if(settings.polyanim) + if(debug_settings.polyanim) { rb->lcd_update(); rb->sleep(HZ/4); @@ -1014,7 +1023,7 @@ static void rb_draw_poly(void *handle, int *coords, int npoints, y1 = coords[2 * (i - 1) + 1]; x2 = coords[2 * i]; y2 = coords[2 * i + 1]; - if(settings.no_aa) + if(debug_settings.no_aa) { offset_coords(&x1, &y1); offset_coords(&x2, &y2); @@ -1025,7 +1034,7 @@ static void rb_draw_poly(void *handle, int *coords, int npoints, draw_antialiased_line(rb->lcd_framebuffer, LCD_WIDTH, LCD_HEIGHT, x1, y1, x2, y2); #ifdef DEBUG_MENU - if(settings.polyanim) + if(debug_settings.polyanim) { rb->lcd_update(); rb->sleep(HZ/4); @@ -1038,7 +1047,7 @@ static void rb_draw_poly(void *handle, int *coords, int npoints, y1 = coords[1]; x2 = coords[2 * (npoints - 1)]; y2 = coords[2 * (npoints - 1) + 1]; - if(settings.no_aa) + if(debug_settings.no_aa) { offset_coords(&x1, &y1); offset_coords(&x2, &y2); @@ -1193,7 +1202,7 @@ static void rb_blitter_save(void *handle, blitter *bl, int x, int y) #if defined(LCD_STRIDEFORMAT) && (LCD_STRIDEFORMAT == VERTICAL_STRIDE) #error no vertical stride #else - if(bl->bmp.data) + if(bl && bl->bmp.data) { int w = bl->bmp.width, h = bl->bmp.height; int screen_w = zoom_enabled ? zoom_w : LCD_WIDTH; @@ -1282,6 +1291,8 @@ static void rb_start_draw(void *handle) static void rb_end_draw(void *handle) { (void) handle; + /* we ignore the backend's redraw requests and just unconditionally update everything */ +#if 0 if(!zoom_enabled) { LOGF("rb_end_draw"); @@ -1293,6 +1304,7 @@ static void rb_end_draw(void *handle) { /* stubbed */ } +#endif } static void rb_status_bar(void *handle, const char *text) @@ -1347,6 +1359,54 @@ static void draw_title(bool clear_first) } } +#define MOUSE_W BMPWIDTH_puzzles_cursor +#define MOUSE_H BMPHEIGHT_puzzles_cursor + +static blitter *mouse_bl = NULL; + +static void clear_mouse(void) +{ + bool orig_clipped = clipped; + if(!zoom_enabled) + { + if(orig_clipped) + rb_unclip(NULL); + } + + if(mouse_bl) + rb_blitter_load(NULL, mouse_bl, BLITTER_FROMSAVED, BLITTER_FROMSAVED); + + if(!zoom_enabled) + { + if(orig_clipped) + rb_clip(NULL, clip_rect.x, clip_rect.y, clip_rect.width, clip_rect.height); + } +} + +static void draw_mouse(void) +{ + bool orig_clipped = clipped; + if(!zoom_enabled) + { + if(orig_clipped) + rb_unclip(NULL); + } + + if(!mouse_bl) + mouse_bl = rb_blitter_new(NULL, MOUSE_W, MOUSE_H); + + /* save area being covered (will be restored elsewhere) */ + rb_blitter_save(NULL, mouse_bl, mouse_x, mouse_y); + + rb->lcd_bitmap_transparent(puzzles_cursor, mouse_x, mouse_y, BMPWIDTH_puzzles_cursor, BMPHEIGHT_puzzles_cursor); + + if(!zoom_enabled) + { + if(orig_clipped) + rb_clip(NULL, clip_rect.x, clip_rect.y, clip_rect.width, clip_rect.height); + } +} + static char *rb_text_fallback(void *handle, const char *const *strings, int nstrings) { @@ -1403,7 +1463,7 @@ void get_random_seed(void **randseed, int *randseedsize) static void timer_cb(void) { #if LCD_DEPTH != 24 - if(settings.timerflash) + if(debug_settings.timerflash) { static bool what = false; what = !what; @@ -1416,7 +1476,7 @@ static void timer_cb(void) #endif LOGF("timer callback"); - midend_timer(me, ((float)(*rb->current_tick - last_tstamp) / (float)HZ) / settings.slowmo_factor); + midend_timer(me, ((float)(*rb->current_tick - last_tstamp) / (float)HZ) / debug_settings.slowmo_factor); last_tstamp = *rb->current_tick; } @@ -1440,12 +1500,27 @@ void frontend_default_color(frontend *fe, float *out) /** frontend code -- mostly UI stuff **/ -/* set do_pausemenu to false to just return -1 on BTN_PAUSE and do - * nothing else. */ +static void send_click(int button, bool release) +{ + int x = (zoom_enabled ? zoom_x : 0) + mouse_x, + y = (zoom_enabled ? zoom_y : 0) + mouse_y; + assert(LEFT_BUTTON + 6 == LEFT_RELEASE); + + midend_process_key(me, x, y, button); + + if(release) + midend_process_key(me, x, y, button + 6); +} + +/* This function handles most user input. It has specific workarounds + * and fixes for certain games to allow them to work well on + * Rockbox. It will either return a positive value that can be passed + * to the midend, or a negative flag value. Set do_pausemenu to false + * to just return -1 on BTN_PAUSE and do nothing else. */ static int process_input(int tmo, bool do_pausemenu) { LOGF("process_input start"); - LOGF("------------------"); + LOGF("-------------------"); int state = 0; #ifdef HAVE_ADJUSTABLE_CPU_FREQ @@ -1457,13 +1532,24 @@ static int process_input(int tmo, bool do_pausemenu) /* weird stuff */ exit_on_usb(button); - /* these games require a second input on long-press */ - if(accept_input && (button == (BTN_FIRE | BUTTON_REPEAT)) && - (strcmp("Mines", midend_which_game(me)->name) != 0 || - strcmp("Magnets", midend_which_game(me)->name) != 0)) + /* See if the button is a long-press. */ + if(accept_input && (button == (BTN_FIRE | BUTTON_REPEAT))) { + LOGF("button is long-press, ignoring subsequent input until release"); + /* Ignore repeated long presses. */ accept_input = false; - return ' '; + + if(mouse_mode && input_settings.rclick_on_hold) + { + /* simulate right-click */ + LOGF("sending right click"); + send_click(RIGHT_BUTTON, true); + return 0; + } + + /* These games want a spacebar in this event. */ + if(!mouse_mode && input_settings.want_spacebar) + return ' '; } button = rb->button_status(); @@ -1476,7 +1562,6 @@ static int process_input(int tmo, bool do_pausemenu) { if(do_pausemenu) { - want_redraw = false; /* quick hack to preserve the clipping state */ bool orig_clipped = clipped; if(orig_clipped) @@ -1496,13 +1581,99 @@ static int process_input(int tmo, bool do_pausemenu) return -1; } - /* these games require, for one reason or another, that events + /* Mouse movement (if enabled). This goes here since none of the + * following code is needed for mouse mode. */ + if(mouse_mode) + { + if(button & BTN_UP) + state = CURSOR_UP; + else if(button & BTN_DOWN) + state = CURSOR_DOWN; + else if(button & BTN_LEFT) + state = CURSOR_LEFT; + else if(button & BTN_RIGHT) + state = CURSOR_RIGHT; + + unsigned released = ~button & last_keystate, + pressed = button & ~last_keystate; + + last_keystate = button; + + /* rclick on hold requires that we fire left-click on a + * release, otherwise it's impossible to distinguish the + * two. */ + if(input_settings.rclick_on_hold) + { + if(accept_input && released == BTN_FIRE) + { + LOGF("sending left click"); + send_click(LEFT_BUTTON, true); /* right-click is handled earlier */ + } + } + else + { + if(pressed & BTN_FIRE) + send_click(LEFT_BUTTON, false); + else if(released & BTN_FIRE) + send_click(LEFT_RELEASE, false); + else if(button & BTN_FIRE) + send_click(LEFT_DRAG, false); + } + + static int last_mousedir = 0, held_count = 0, v = 0; + + /* acceleration */ + if(state && state == last_mousedir) + { + if(++held_count % 5 == 0 && v < 15) + v++; + } + else + { + if(!button) + { + LOGF("all keys released, accepting further input"); + accept_input = true; + } + last_mousedir = state; + v = 1; + held_count = 0; + } + + /* get the direction vector the cursor is moving in. */ + int new_x = mouse_x, new_y = mouse_y; + + /* in src/misc.c */ + move_cursor(state, &new_x, &new_y, LCD_WIDTH, LCD_HEIGHT, FALSE); + + int dx = new_x - mouse_x, dy = new_y - mouse_y; + + mouse_x += dx * v; + mouse_y += dy * v; + + /* The % operator with negative operands is messy; this is much + * simpler. */ + if(mouse_x < 0) + mouse_x = 0; + if(mouse_y < 0) + mouse_y = 0; + + if(mouse_x >= LCD_WIDTH) + mouse_x = LCD_WIDTH - 1; + if(mouse_y >= LCD_HEIGHT) + mouse_y = LCD_HEIGHT - 1; + + /* no buttons are sent to the midend in mouse mode */ + return 0; + } + + /* These games require, for one reason or another, that events * fire upon buttons being released rather than when they are - * pressed */ - if(strcmp("Inertia", midend_which_game(me)->name) == 0 || - strcmp("Mines", midend_which_game(me)->name) == 0 || - strcmp("Magnets", midend_which_game(me)->name) == 0 || - strcmp("Map", midend_which_game(me)->name) == 0) + * pressed. For Inertia, it is because it needs to be able to + * sense multiple simultaneous keypresses (to move diagonally), + * and the others require a long press to map to a secondary + * "action" key. */ + if(input_settings.falling_edge) { LOGF("received button 0x%08x", button); @@ -1536,8 +1707,8 @@ static int process_input(int tmo, bool do_pausemenu) button |= released; LOGF("accepting event 0x%08x", button); } - /* default is to ignore repeats except for untangle */ - else if(strcmp("Untangle", midend_which_game(me)->name) != 0) + /* Ignore repeats in all games which are not Untangle. */ + else if(input_settings.ignore_repeats) { /* start accepting input again after a release */ if(!button) @@ -1545,10 +1716,11 @@ static int process_input(int tmo, bool do_pausemenu) accept_input = true; return 0; } - /* ignore repeats */ - /* Untangle gets special treatment */ + + /* ignore repeats (in mouse mode, only ignore repeats of BTN_FIRE) */ if(!accept_input) return 0; + accept_input = false; } @@ -1604,7 +1776,7 @@ static int process_input(int tmo, bool do_pausemenu) break; } - if(settings.shortcuts) + if(debug_settings.shortcuts) { static bool shortcuts_ok = true; switch(button) @@ -1665,9 +1837,9 @@ static void zoom(void) /* draws go to the zoom framebuffer */ midend_force_redraw(me); - int x = 0, y = 0; + zoom_x = zoom_y = 0; - rb->lcd_bitmap_part(zoom_fb, x, y, STRIDE(SCREEN_MAIN, zoom_w, zoom_h), + rb->lcd_bitmap_part(zoom_fb, zoom_x, zoom_y, STRIDE(SCREEN_MAIN, zoom_w, zoom_h), 0, 0, LCD_WIDTH, LCD_HEIGHT); draw_title(false); /* false since we don't want to use more screen space than we need. */ @@ -1693,16 +1865,16 @@ static void zoom(void) switch(button) { case BTN_UP: - y -= PAN_Y; /* clamped later */ + zoom_y -= PAN_Y; /* clamped later */ break; case BTN_DOWN: - y += PAN_Y; /* clamped later */ + zoom_y += PAN_Y; /* clamped later */ break; case BTN_LEFT: - x -= PAN_X; /* clamped later */ + zoom_x -= PAN_X; /* clamped later */ break; case BTN_RIGHT: - x += PAN_X; /* clamped later */ + zoom_x += PAN_X; /* clamped later */ break; case BTN_PAUSE: zoom_enabled = false; @@ -1716,15 +1888,15 @@ static void zoom(void) break; } - if(y < 0) - y = 0; - if(x < 0) - x = 0; + if(zoom_y < 0) + zoom_y = 0; + if(zoom_x < 0) + zoom_x = 0; - if(y + LCD_HEIGHT >= zoom_h) - y = zoom_h - LCD_HEIGHT; - if(x + LCD_WIDTH >= zoom_w) - x = zoom_w - LCD_WIDTH; + if(zoom_y + LCD_HEIGHT >= zoom_h) + zoom_y = zoom_h - LCD_HEIGHT; + if(zoom_x + LCD_WIDTH >= zoom_w) + zoom_x = zoom_w - LCD_WIDTH; if(timer_on) timer_cb(); @@ -1732,7 +1904,7 @@ static void zoom(void) /* goes to zoom_fb */ midend_redraw(me); - rb->lcd_bitmap_part(zoom_fb, x, y, STRIDE(SCREEN_MAIN, zoom_w, zoom_h), + rb->lcd_bitmap_part(zoom_fb, zoom_x, zoom_y, STRIDE(SCREEN_MAIN, zoom_w, zoom_h), 0, 0, LCD_WIDTH, LCD_HEIGHT); draw_title(false); rb->lcd_update(); @@ -1755,13 +1927,26 @@ static void zoom(void) if(timer_on) timer_cb(); - if(want_redraw) - midend_redraw(me); + midend_redraw(me); - rb->lcd_bitmap_part(zoom_fb, x, y, STRIDE(SCREEN_MAIN, zoom_w, zoom_h), + /* blit */ + rb->lcd_bitmap_part(zoom_fb, zoom_x, zoom_y, STRIDE(SCREEN_MAIN, zoom_w, zoom_h), 0, 0, LCD_WIDTH, LCD_HEIGHT); + draw_title(false); + + /* The cursor is always in screenspace coordinates; when + * zoomed, this means the mouse is always restricted to + * the bounds of the physical display, not the virtual + * zoom framebuffer. */ + if(mouse_mode) + draw_mouse(); + rb->lcd_update(); + + if(mouse_mode) + clear_mouse(); + rb->yield(); } } @@ -2202,12 +2387,12 @@ static void full_help(const char *name) static void init_default_settings(void) { - settings.slowmo_factor = 1; - settings.timerflash = false; - settings.clipoff = false; - settings.shortcuts = false; - settings.no_aa = false; - settings.polyanim = false; + debug_settings.slowmo_factor = 1; + debug_settings.timerflash = false; + debug_settings.clipoff = false; + debug_settings.shortcuts = false; + debug_settings.no_aa = false; + debug_settings.polyanim = false; } #ifdef DEBUG_MENU @@ -2250,6 +2435,11 @@ static void debug_menu(void) "Toggle antialias", "Benchmark antialias", "Toggle show poly steps", + "Toggle mouse mode", + "Toggle spacebar on long click", + "Toggle send keys on release", + "Toggle ignore repeats", + "Toggle right-click on hold vs. dragging", "Back"); bool quit = false; int sel = 0; @@ -2258,7 +2448,7 @@ static void debug_menu(void) switch(rb->do_menu(&menu, &sel, NULL, false)) { case 0: - rb->set_int("Slowmo factor", "", UNIT_INT, &settings.slowmo_factor, NULL, 1, 1, 15, NULL); + rb->set_int("Slowmo factor", "", UNIT_INT, &debug_settings.slowmo_factor, NULL, 1, 1, 15, NULL); break; case 1: { @@ -2271,24 +2461,38 @@ static void debug_menu(void) break; } case 2: - settings.timerflash = !settings.timerflash; + debug_settings.timerflash = !debug_settings.timerflash; break; case 3: - settings.clipoff = !settings.clipoff; + debug_settings.clipoff = !debug_settings.clipoff; break; case 4: - settings.shortcuts = !settings.shortcuts; + debug_settings.shortcuts = !debug_settings.shortcuts; break; case 5: - settings.no_aa = !settings.no_aa; + debug_settings.no_aa = !debug_settings.no_aa; break; case 6: bench_aa(); break; case 7: - settings.polyanim = !settings.polyanim; + debug_settings.polyanim = !debug_settings.polyanim; break; case 8: + mouse_mode = !mouse_mode; + break; + case 9: + input_settings.want_spacebar = !input_settings.want_spacebar; + break; + case 10: + input_settings.falling_edge = !input_settings.falling_edge; + break; + case 11: + input_settings.ignore_repeats = !input_settings.ignore_repeats; + break; + case 12: + input_settings.rclick_on_hold = !input_settings.rclick_on_hold; + break; default: quit = true; break; @@ -2316,8 +2520,6 @@ static int pausemenu_cb(int action, const struct menu_item_ex *this_item) if(!midend_which_game(me)->can_solve) return ACTION_EXIT_MENUITEM; break; - case 7: - break; case 9: if(audiobuf_available) break; @@ -2349,10 +2551,18 @@ static int pausemenu_cb(int action, const struct menu_item_ex *this_item) static void clear_and_draw(void) { rb->lcd_clear_display(); - rb->lcd_update(); midend_force_redraw(me); + draw_title(true); + + if(mouse_mode) + draw_mouse(); + + rb->lcd_update(); + + if(mouse_mode) + clear_mouse(); } static void reset_drawing(void) @@ -2482,8 +2692,8 @@ static int pause_menu(void) } rb->lcd_set_background(BG_COLOR); rb->lcd_clear_display(); - rb->lcd_update(); midend_force_redraw(me); + rb->lcd_update(); return 0; } @@ -2542,6 +2752,72 @@ static void init_colors(void) sfree(floatcolors); } +static bool string_in_list(const char *target, const char **list) +{ + /* list is terminated with NULL */ + const char *i; + + while((i = *list++)) + { + if(!strcmp(target, i)) + return true; + } + + return false; +} + +static void tune_input(const char *name) +{ + /* game-specific stuff */ + + static const char *want_spacebar[] = { + "Magnets", + "Mines", + "Palisade", + NULL + }; + + /* these get a spacebar on long click */ + input_settings.want_spacebar = string_in_list(name, want_spacebar); + + static const char *falling_edge[] = { + "Inertia", + "Magnets", + "Map", + "Mines", + "Palisade", + NULL + }; + + /* wait until a key is released to send an action */ + input_settings.falling_edge = string_in_list(name, falling_edge); + + /* in all games but untangle (mouse mode overrides this) */ + static const char *ignore_repeats[] = { + "Untangle", + NULL + }; + + input_settings.ignore_repeats = !string_in_list(name, falling_edge); + + /* set to false if you want dragging to be possible */ + static const char *rclick_on_hold[] = { + "Map", + "Signpost", + "Untangle", + NULL + }; + + input_settings.rclick_on_hold = !string_in_list(name, falling_edge); + + static const char *mouse_games[] = { + "Loopy", + NULL + }; + + mouse_mode = string_in_list(name, mouse_games); +} + static const char *init_for_game(const game *gm, int load_fd, bool draw) { me = midend_new(NULL, gm, &rb_drawing, NULL); @@ -2555,6 +2831,11 @@ static const char *init_for_game(const game *gm, int load_fd, bool draw) return ret; } + tune_input(gm->name); + + mouse_x = LCD_WIDTH / 2; + mouse_y = LCD_HEIGHT / 2; + fix_size(); init_colors(); @@ -2845,12 +3126,6 @@ enum plugin_status plugin_start(const void *param) init_tlsf(); - /* sanity check */ - if(fabs(sqrt(3)/2 - sin(PI/3)) > .01) - { - return PLUGIN_ERROR; - } - init_default_settings(); init_fonttab(); @@ -2964,12 +3239,6 @@ enum plugin_status plugin_start(const void *param) game_loop: while(1) { - want_redraw = true; - - int theight = get_titleheight(); - draw_title(true); - rb->lcd_update_rect(0, LCD_HEIGHT - theight, LCD_WIDTH, theight); - int button = process_input(timer_on ? TIMER_INTERVAL : -1, true); if(button < 0) @@ -3008,17 +3277,21 @@ enum plugin_status plugin_start(const void *param) if(button) midend_process_key(me, 0, 0, button); - draw_title(true); /* will draw to fb */ - - if(want_redraw) - midend_redraw(me); - - /* push title to screen as well */ - rb->lcd_update_rect(0, LCD_HEIGHT - theight, LCD_WIDTH, theight); - if(timer_on) timer_cb(); + midend_redraw(me); + + draw_title(true); /* will draw to fb */ + + if(mouse_mode) + draw_mouse(); + + rb->lcd_update(); + + if(mouse_mode) + clear_mouse(); + rb->yield(); } sfree(colors); diff --git a/apps/plugins/puzzles/src/LICENCE b/apps/plugins/puzzles/src/LICENCE index 4235005ea7..ce0418e6cc 100644 --- a/apps/plugins/puzzles/src/LICENCE +++ b/apps/plugins/puzzles/src/LICENCE @@ -1,8 +1,9 @@ This software is copyright (c) 2004-2014 Simon Tatham. Portions copyright Richard Boulton, James Harvey, Mike Pinna, Jonas -Kölker, Dariusz Olszewski, Michael Schierl, Lambros Lambrou, Bernd -Schmidt, Steffen Bauer, Lennard Sprong and Rogier Goossens. +Kölker, Dariusz Olszewski, Michael Schierl, Lambros Lambrou, Bernd +Schmidt, Steffen Bauer, Lennard Sprong, Rogier Goossens and Michael +Quevillon. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files diff --git a/apps/plugins/puzzles/src/filling.c b/apps/plugins/puzzles/src/filling.c index 7e71eb25b4..231de4c079 100644 --- a/apps/plugins/puzzles/src/filling.c +++ b/apps/plugins/puzzles/src/filling.c @@ -1,6 +1,6 @@ /* -*- tab-width: 8; indent-tabs-mode: t -*- * filling.c: An implementation of the Nikoli game fillomino. - * Copyright (C) 2007 Jonas Kölker. See LICENSE for the license. + * Copyright (C) 2007 Jonas Kölker. See LICENSE for the license. */ /* TODO: diff --git a/apps/plugins/puzzles/src/grid.c b/apps/plugins/puzzles/src/grid.c index 52648e5a92..b5e6bb0937 100644 --- a/apps/plugins/puzzles/src/grid.c +++ b/apps/plugins/puzzles/src/grid.c @@ -2060,6 +2060,102 @@ static grid *grid_new_greathexagonal(int width, int height, const char *desc) return g; } +#define KAGOME_TILESIZE 18 +/* Vector for side of triangle - ratio is close to sqrt(3) */ +#define KAGOME_A 15 +#define KAGOME_B 26 + +static void grid_size_kagome(int width, int height, + int *tilesize, int *xextent, int *yextent) +{ + int a = KAGOME_A; + int b = KAGOME_B; + + *tilesize = KAGOME_TILESIZE; + *xextent = (4*a) * (width-1) + 6*a; + *yextent = (2*b) * (height-1) + 2*b; +} + +static grid *grid_new_kagome(int width, int height, const char *desc) +{ + int x, y; + int a = KAGOME_A; + int b = KAGOME_B; + + /* Upper bounds - don't have to be exact */ + int max_faces = 6 * (width + 1) * (height + 1); + int max_dots = 6 * width * height; + + tree234 *points; + + grid *g = grid_empty(); + g->tilesize = KAGOME_TILESIZE; + g->faces = snewn(max_faces, grid_face); + g->dots = snewn(max_dots, grid_dot); + + points = newtree234(grid_point_cmp_fn); + + for (y = 0; y < height; y++) { + for (x = 0; x < width; x++) { + grid_dot *d; + /* centre of hexagon */ + int px = (4*a) * x; + int py = (2*b) * y; + if (y % 2) + px += 2*a; + + /* hexagon */ + grid_face_add_new(g, 6); + d = grid_get_dot(g, points, px + a, py - b); grid_face_set_dot(g, d, 0); + d = grid_get_dot(g, points, px + 2*a, py ); grid_face_set_dot(g, d, 1); + d = grid_get_dot(g, points, px + a, py + b); grid_face_set_dot(g, d, 2); + d = grid_get_dot(g, points, px - a, py + b); grid_face_set_dot(g, d, 3); + d = grid_get_dot(g, points, px - 2*a, py ); grid_face_set_dot(g, d, 4); + d = grid_get_dot(g, points, px - a, py - b); grid_face_set_dot(g, d, 5); + + /* Triangle above right */ + if ((x < width - 1) || (!(y % 2) && y)) { + grid_face_add_new(g, 3); + d = grid_get_dot(g, points, px + 3*a, py - b); grid_face_set_dot(g, d, 0); + d = grid_get_dot(g, points, px + 2*a, py ); grid_face_set_dot(g, d, 1); + d = grid_get_dot(g, points, px + a, py - b); grid_face_set_dot(g, d, 2); + } + + /* Triangle below right */ + if ((x < width - 1) || (!(y % 2) && (y < height - 1))) { + grid_face_add_new(g, 3); + d = grid_get_dot(g, points, px + 3*a, py + b); grid_face_set_dot(g, d, 0); + d = grid_get_dot(g, points, px + a, py + b); grid_face_set_dot(g, d, 1); + d = grid_get_dot(g, points, px + 2*a, py ); grid_face_set_dot(g, d, 2); + } + + /* Left triangles */ + if (!x && (y % 2)) { + /* Triangle above left */ + grid_face_add_new(g, 3); + d = grid_get_dot(g, points, px - a, py - b); grid_face_set_dot(g, d, 0); + d = grid_get_dot(g, points, px - 2*a, py ); grid_face_set_dot(g, d, 1); + d = grid_get_dot(g, points, px - 3*a, py - b); grid_face_set_dot(g, d, 2); + + /* Triangle below left */ + if (y < height - 1) { + grid_face_add_new(g, 3); + d = grid_get_dot(g, points, px - a, py + b); grid_face_set_dot(g, d, 0); + d = grid_get_dot(g, points, px - 3*a, py + b); grid_face_set_dot(g, d, 1); + d = grid_get_dot(g, points, px - 2*a, py ); grid_face_set_dot(g, d, 2); + } + } + } + } + + freetree234(points); + assert(g->num_faces <= max_faces); + assert(g->num_dots <= max_dots); + + grid_make_consistent(g); + return g; +} + #define OCTAGONAL_TILESIZE 40 /* b/a approx sqrt(2) */ #define OCTAGONAL_A 29 diff --git a/apps/plugins/puzzles/src/grid.h b/apps/plugins/puzzles/src/grid.h index 19079a44b8..26d0b16633 100644 --- a/apps/plugins/puzzles/src/grid.h +++ b/apps/plugins/puzzles/src/grid.h @@ -100,6 +100,7 @@ typedef struct grid { A(SNUBSQUARE,snubsquare) \ A(CAIRO,cairo) \ A(GREATHEXAGONAL,greathexagonal) \ + A(KAGOME,kagome) \ A(OCTAGONAL,octagonal) \ A(KITE,kites) \ A(FLORET,floret) \ diff --git a/apps/plugins/puzzles/src/loopy.c b/apps/plugins/puzzles/src/loopy.c index 5f1940e945..c14412d6be 100644 --- a/apps/plugins/puzzles/src/loopy.c +++ b/apps/plugins/puzzles/src/loopy.c @@ -278,6 +278,7 @@ static void check_caches(const solver_state* sstate); A("Penrose (kite/dart)",PENROSE_P2,3,3) \ A("Penrose (rhombs)",PENROSE_P3,3,3) \ A("Great-Great-Dodecagonal",GREATGREATDODECAGONAL,2,2) \ + A("Kagome",KAGOME,3,3) \ /* end of list */ #define GRID_NAME(title,type,amin,omin) title, @@ -544,6 +545,7 @@ static const game_params loopy_presets_more[] = { #ifdef SMALL_SCREEN { 7, 7, DIFF_HARD, LOOPY_GRID_HONEYCOMB }, { 5, 4, DIFF_HARD, LOOPY_GRID_GREATHEXAGONAL }, + { 5, 4, DIFF_HARD, LOOPY_GRID_KAGOME }, { 5, 5, DIFF_HARD, LOOPY_GRID_OCTAGONAL }, { 3, 3, DIFF_HARD, LOOPY_GRID_FLORET }, { 3, 3, DIFF_HARD, LOOPY_GRID_DODECAGONAL }, @@ -552,6 +554,7 @@ static const game_params loopy_presets_more[] = { #else { 10, 10, DIFF_HARD, LOOPY_GRID_HONEYCOMB }, { 5, 4, DIFF_HARD, LOOPY_GRID_GREATHEXAGONAL }, + { 5, 4, DIFF_HARD, LOOPY_GRID_KAGOME }, { 7, 7, DIFF_HARD, LOOPY_GRID_OCTAGONAL }, { 5, 5, DIFF_HARD, LOOPY_GRID_FLORET }, { 5, 4, DIFF_HARD, LOOPY_GRID_DODECAGONAL }, diff --git a/apps/plugins/puzzles/src/palisade.c b/apps/plugins/puzzles/src/palisade.c index 5227a1d56c..e495bbed2c 100644 --- a/apps/plugins/puzzles/src/palisade.c +++ b/apps/plugins/puzzles/src/palisade.c @@ -865,14 +865,16 @@ static char *game_text_format(const game_state *state) struct game_ui { int x, y; - unsigned int show: 1; + unsigned int show: 1; + unsigned int fake_ctrl: 1; + unsigned int fake_shift: 1; }; static game_ui *new_ui(const game_state *state) { game_ui *ui = snew(game_ui); ui->x = ui->y = 0; - ui->show = FALSE; + ui->show = ui->fake_ctrl = ui->fake_shift = FALSE; return ui; } @@ -916,7 +918,10 @@ static char *interpret_move(const game_state *state, game_ui *ui, const game_drawstate *ds, int x, int y, int button) { int w = state->shared->params.w, h = state->shared->params.h; - int control = button & MOD_CTRL, shift = button & MOD_SHFT; + int control = (button & MOD_CTRL) | ui->fake_ctrl, shift = (button & MOD_SHFT) | ui->fake_shift; + + /* reset */ + ui->fake_ctrl = ui->fake_shift = FALSE; button &= ~MOD_MASK; @@ -999,6 +1004,16 @@ static char *interpret_move(const game_state *state, game_ui *ui, return UI_UPDATE; } } + else if(IS_CURSOR_SELECT(button)) { + /* CURSOR_SELECT or CURSOR_SELECT2 tells us to toggle whether + * the button press should be interpreted as having CTRL or + * shift pressed along with it, respectively. */ + ui->show = TRUE; + if(button == CURSOR_SELECT2) + ui->fake_shift = !ui->fake_shift; + else + ui->fake_ctrl = !ui->fake_ctrl; + } return NULL; }