summaryrefslogtreecommitdiff
path: root/apps/plugins/puzzles/rockbox.c
diff options
context:
space:
mode:
authorFranklin Wei <frankhwei536@gmail.com>2016-11-20 15:16:41 -0500
committerFranklin Wei <frankhwei536@gmail.com>2016-11-24 16:23:09 -0500
commit56c9984511f016eab7e1278ba9e40d88bb59a162 (patch)
tree1bfa6d3aeb3bf2a6ffec71387ac073cd0b8b2a51 /apps/plugins/puzzles/rockbox.c
parent29648f817677b84c03c2bcfe89eb8cf53653e7db (diff)
downloadrockbox-puzzles.zip
rockbox-puzzles.tar.gz
rockbox-puzzles.tar.bz2
rockbox-puzzles.tar.xz
[WIP] Port of Simon Tatham's Puzzle Collectionpuzzles
Original revision: 5123b1bf68777ffa86e651f178046b26a87cf2d9 MIT Licensed. Some games still crash and others are unplayable due to issues with controls. Still need a "real" polygon filling algorithm. The following games are at least partially broken for various reasons: Cube: crash with certain settings Galaxies: crash Inertia: crash Keen: input issues Loopy: weird stuff happens Map: crash on input Mines: weird stuff happens on target Palisade: input issues Signpost: crash on input Solo: input issues Towers: input and drawing issues Train Tracks: drawing issues Twiddle: weird animation on target Undead: input and drawing issues Unequal: input and drawing issues Untangle: input issues All in all, about 40% of the games are at least partially broken. Change-Id: I7c69b6860ab115f973c8d76799502e9bb3d52368
Diffstat (limited to 'apps/plugins/puzzles/rockbox.c')
-rw-r--r--apps/plugins/puzzles/rockbox.c775
1 files changed, 775 insertions, 0 deletions
diff --git a/apps/plugins/puzzles/rockbox.c b/apps/plugins/puzzles/rockbox.c
new file mode 100644
index 0000000..32fc38e
--- /dev/null
+++ b/apps/plugins/puzzles/rockbox.c
@@ -0,0 +1,775 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2016 Franklin Wei
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+#include "plugin.h"
+
+#include "puzzles.h"
+
+#include "lib/pluginlib_actions.h"
+#include "lib/xlcd.h"
+
+/* how many ticks between timer callbacks */
+#define TIMER_INTERVAL (HZ / 33)
+
+static midend *me = NULL;
+static unsigned *colors = NULL;
+static int ncolors = 0;
+
+static void init_for_game(const game *gm);
+static void fix_size(void);
+
+static void rb_start_draw(void *handle)
+{
+}
+
+static struct viewport clip_rect;
+static bool clipped = false;
+
+static void rb_clip(void *handle, int x, int y, int w, int h)
+{
+ LOGF("rb_clip(%d %d %d %d)", x, y, w, h);
+ clip_rect.x = x;
+ clip_rect.y = y;
+ clip_rect.width = w;
+ clip_rect.height = h;
+ clip_rect.font = FONT_UI;
+ clip_rect.drawmode = DRMODE_SOLID;
+#if LCD_DEPTH > 1
+ clip_rect.fg_pattern = LCD_DEFAULT_FG;
+ clip_rect.bg_pattern = LCD_DEFAULT_BG;
+#endif
+ rb->lcd_set_viewport(NULL);
+ rb->lcd_set_viewport(&clip_rect);
+ clipped = true;
+}
+
+static void rb_unclip(void *handle)
+{
+ LOGF("rb_unclip");
+ rb->lcd_set_viewport(NULL);
+ clipped = false;
+}
+
+static void offset_coords(int *x, int *y)
+{
+ if(clipped)
+ {
+ *x -= clip_rect.x;
+ *y -= clip_rect.y;
+ }
+}
+
+static void rb_color(int n)
+{
+ if(n < 0)
+ {
+ fatal("bad color %d", n);
+ return;
+ }
+ rb->lcd_set_foreground(colors[n]);
+}
+
+static void rb_draw_text(void *handle, int x, int y, int fonttype,
+ int fontsize, int align, int colour, char *text)
+{
+ LOGF("rb_draw_text(%d %d %s)", x, y, text);
+
+ offset_coords(&x, &y);
+
+ switch(fonttype)
+ {
+ case FONT_FIXED:
+ rb->lcd_setfont(FONT_SYSFIXED);
+ break;
+ case FONT_VARIABLE:
+ rb->lcd_setfont(FONT_UI);
+ break;
+ default:
+ fatal("bad font");
+ break;
+ }
+
+ int w, h;
+ rb->lcd_getstringsize(text, &w, &h);
+
+ static int cap_h = -1;
+ if(cap_h < 0)
+ rb->lcd_getstringsize("X", NULL, &cap_h);
+
+ if(align & ALIGN_VNORMAL)
+ y -= h;
+ else if(align & ALIGN_VCENTRE)
+ y -= cap_h / 2;
+
+ if(align & ALIGN_HCENTRE)
+ x -= w / 2;
+ else if(align & ALIGN_HRIGHT)
+ x -= w;
+
+ rb_color(colour);
+ rb->lcd_set_drawmode(DRMODE_COMPLEMENT);
+ rb->lcd_putsxy(x, y, text);
+ rb->lcd_set_drawmode(DRMODE_SOLID);
+}
+
+static void rb_draw_rect(void *handle, int x, int y, int w, int h, int colour)
+{
+ LOGF("rb_draw_rect(%d, %d, %d, %d, %d)", x, y, w, h, colour);
+ rb_color(colour);
+ offset_coords(&x, &y);
+ rb->lcd_fillrect(x, y, w, h);
+}
+
+static void rb_draw_line(void *handle, int x1, int y1, int x2, int y2,
+ int colour)
+{
+ LOGF("rb_draw_line(%d, %d, %d, %d, %d)", x1, y1, x2, y2, colour);
+ offset_coords(&x1, &y1);
+ offset_coords(&x2, &y2);
+ rb_color(colour);
+ rb->lcd_drawline(x1, y1, x2, y2);
+}
+
+static void rb_draw_poly(void *handle, int *coords, int npoints,
+ int fillcolour, int outlinecolour)
+{
+ LOGF("rb_draw_poly");
+ if(fillcolour >= 0)
+ {
+ rb_color(fillcolour);
+ for(int i = 2; i < npoints; ++i)
+ {
+ int x1, y1, x2, y2, x3, y3;
+ x1 = coords[0];
+ y1 = coords[1];
+ x2 = coords[(i - 1) * 2];
+ y2 = coords[(i - 1) * 2 + 1];
+ x3 = coords[i * 2];
+ y3 = coords[i * 2 + 1];
+ offset_coords(&x1, &y1);
+ offset_coords(&x2, &y2);
+ offset_coords(&x3, &y3);
+ xlcd_filltriangle(x1, y1,
+ x2, y2,
+ x3, y3);
+ }
+ }
+
+ assert(outlinecolour >= 0);
+ rb_color(outlinecolour);
+
+ for(int i = 1; i < npoints; ++i)
+ {
+ int x1, y1, x2, y2;
+ x1 = coords[2 * (i - 1)];
+ y1 = coords[2 * (i - 1) + 1];
+ x2 = coords[2 * i];
+ y2 = coords[2 * i + 1];
+ offset_coords(&x1, &y1);
+ offset_coords(&x2, &y2);
+ rb->lcd_drawline(x1, y1,
+ x2, y2);
+ }
+
+ int x1, y1, x2, y2;
+ x1 = coords[0];
+ y1 = coords[1];
+ x2 = coords[2 * (npoints - 1)];
+ y2 = coords[2 * (npoints - 1) + 1];
+ offset_coords(&x1, &y1);
+ offset_coords(&x2, &y2);
+
+ rb->lcd_drawline(x1, y1,
+ x2, y2);
+}
+
+static void rb_draw_circle(void *handle, int cx, int cy, int radius,
+ int fillcolour, int outlinecolour)
+{
+ LOGF("rb_draw_circle(%d, %d, %d)", cx, cy, radius);
+ offset_coords(&cx, &cy);
+ if(fillcolour >= 0)
+ {
+ rb_color(fillcolour);
+ xlcd_fillcircle(cx, cy, radius);
+ }
+ assert(outlinecolour >= 0);
+ rb_color(outlinecolour);
+ xlcd_drawcircle(cx, cy, radius);
+}
+
+struct blitter {
+ bool have_data;
+ int x, y;
+ struct bitmap bmp;
+};
+
+static blitter *rb_blitter_new(void *handle, int w, int h)
+{
+ LOGF("rb_blitter_new");
+ blitter *b = snew(blitter);
+ b->bmp.width = w;
+ b->bmp.height = h;
+ b->bmp.data = smalloc(w * h * sizeof(fb_data));
+ b->have_data = false;
+ return b;
+}
+
+static void rb_blitter_free(void *handle, blitter *bl)
+{
+ LOGF("rb_blitter_free");
+ sfree(bl->bmp.data);
+ sfree(bl);
+ bl->bmp.data = NULL;
+ return;
+}
+
+static void trim_rect(int *x, int *y, int *w, int *h)
+{
+ int x0, x1, y0, y1;
+
+ /*
+ * Reduce the size of the copied rectangle to stop it going
+ * outside the bounds of the canvas.
+ */
+
+ /* Transform from x,y,w,h form into coordinates of all edges */
+ x0 = *x;
+ y0 = *y;
+ x1 = *x + *w;
+ y1 = *y + *h;
+
+ /* Clip each coordinate at both extremes of the canvas */
+ x0 = (x0 < 0 ? 0 : x0 > LCD_WIDTH ? LCD_WIDTH : x0);
+ x1 = (x1 < 0 ? 0 : x1 > LCD_WIDTH ? LCD_WIDTH : x1);
+ y0 = (y0 < 0 ? 0 : y0 > LCD_HEIGHT ? LCD_HEIGHT : y0);
+ y1 = (y1 < 0 ? 0 : y1 > LCD_HEIGHT ? LCD_HEIGHT : y1);
+
+ /* Transform back into x,y,w,h to return */
+ *x = x0;
+ *y = y0;
+ *w = x1 - x0;
+ *h = y1 - y0;
+}
+
+static void rb_blitter_save(void *handle, blitter *bl, int x, int y)
+{
+ /* no viewport offset */
+#if defined(LCD_STRIDEFORMAT) && (LCD_STRIDEFORMAT == VERTICAL_STRIDE)
+#error no vertical stride
+#else
+ if(bl->bmp.data)
+ {
+ int w = bl->bmp.width, h = bl->bmp.height;
+ trim_rect(&x, &y, &w, &h);
+ LOGF("rb_blitter_save(%d, %d, %d, %d)", x, y, w, h);
+ for(int i = 0; i < h; ++i)
+ {
+ /* copy line-by-line */
+ rb->memcpy(bl->bmp.data + sizeof(fb_data) * (y + i) * w,
+ rb->lcd_framebuffer + (y + i) * LCD_WIDTH + x,
+ w * sizeof(fb_data));
+ }
+ bl->x = x;
+ bl->y = y;
+ bl->have_data = true;
+ }
+#endif
+}
+
+static void rb_blitter_load(void *handle, blitter *bl, int x, int y)
+{
+ LOGF("rb_blitter_load");
+ if(!bl->have_data)
+ return;
+ int w = bl->bmp.width, h = bl->bmp.height;
+
+ if(x == BLITTER_FROMSAVED) x = bl->x;
+ if(y == BLITTER_FROMSAVED) y = bl->y;
+
+ trim_rect(&x, &y, &w, &h);
+ offset_coords(&x, &y);
+ rb->lcd_bitmap(bl->bmp.data, x, y, w, h);
+}
+
+static void rb_draw_update(void *handle, int x, int y, int w, int h)
+{
+ LOGF("rb_draw_update(%d, %d, %d, %d)", x, y, w, h);
+ rb->lcd_update_rect(x, y, w, h);
+}
+
+static void rb_end_draw(void *handle)
+{
+ LOGF("rb_end_draw");
+}
+
+static void rb_status_bar(void *handle, char *text)
+{
+ LOGF("game title is %s\n", text);
+}
+
+static char *rb_text_fallback(void *handle, const char *const *strings,
+ int nstrings)
+{
+ return dupstr(strings[0]);
+}
+
+const drawing_api rb_drawing = {
+ rb_draw_text,
+ rb_draw_rect,
+ rb_draw_line,
+ rb_draw_poly,
+ rb_draw_circle,
+ rb_draw_update,
+ rb_clip,
+ rb_unclip,
+ rb_start_draw,
+ rb_end_draw,
+ rb_status_bar,
+ rb_blitter_new,
+ rb_blitter_free,
+ rb_blitter_save,
+ rb_blitter_load,
+ NULL, NULL, NULL, NULL, NULL, NULL, /* {begin,end}_{doc,page,puzzle} */
+ NULL, NULL, /* line_width, line_dotted */
+ rb_text_fallback,
+ NULL,
+};
+
+void frontend_default_colour(frontend *fe, float *out)
+{
+ *out++ = 1.0;
+ *out++ = 1.0;
+ *out++ = 1.0;
+}
+
+void fatal(char *fmt, ...)
+{
+ va_list ap;
+
+ rb->splash(HZ, "FATAL ERROR");
+
+ va_start(ap, fmt);
+ char buf[80];
+ rb->vsnprintf(buf, 80, fmt, ap);
+ rb->splash(HZ * 2, buf);
+ va_end(ap);
+
+ exit(1);
+}
+
+void get_random_seed(void **randseed, int *randseedsize)
+{
+ *randseed = snew(long);
+ long seed = *rb->current_tick;
+ rb->memcpy(*randseed, &seed, sizeof(seed));
+ *randseedsize = sizeof(long);
+}
+
+const char *config_choices_formatter(int sel, void *data, char *buf, size_t len)
+{
+ /* we can't rely on being called in any particular order */
+ char *list = dupstr(data);
+ char delimbuf[2] = { *list, 0 };
+ char *save = NULL;
+ char *str = rb->strtok_r(list, delimbuf, &save);
+ for(int i = 0; i < sel; ++i)
+ str = rb->strtok_r(NULL, delimbuf, &save);
+ rb->snprintf(buf, len, "%s", str);
+ sfree(list);
+ return buf;
+}
+
+static int list_choose(const char *list_str, const char *title)
+{
+ char delim = *list_str;
+
+ const char *ptr = list_str + 1;
+ int n = 0;
+ while(ptr)
+ {
+ n++;
+ ptr = strchr(ptr + 1, delim);
+ }
+
+ struct gui_synclist list;
+
+ rb->gui_synclist_init(&list, &config_choices_formatter, list_str, false, 1, NULL);
+ rb->gui_synclist_set_icon_callback(&list, NULL);
+ rb->gui_synclist_set_nb_items(&list, n);
+ rb->gui_synclist_limit_scroll(&list, false);
+
+ rb->gui_synclist_select_item(&list, 0);
+
+ rb->gui_synclist_set_title(&list, title, NOICON);
+ while (1)
+ {
+ rb->gui_synclist_draw(&list);
+ int button = rb->get_action(CONTEXT_LIST, TIMEOUT_BLOCK);
+ if(rb->gui_synclist_do_button(&list, &button, LIST_WRAP_ON))
+ continue;
+ switch(button)
+ {
+ case ACTION_STD_OK:
+ return rb->gui_synclist_get_sel_pos(&list);
+ case ACTION_STD_PREV:
+ case ACTION_STD_CANCEL:
+ return -1;
+ default:
+ break;
+ }
+ }
+}
+
+static void do_configure_item(config_item *cfg)
+{
+ switch(cfg->type)
+ {
+ case C_STRING:
+ {
+#define MAX_STRLEN 128
+ char *newstr = smalloc(MAX_STRLEN);
+ rb->strlcpy(newstr, cfg->sval, MAX_STRLEN);
+ rb->lcd_set_foreground(LCD_WHITE);
+ rb->lcd_set_background(LCD_BLACK);
+ if(rb->kbd_input(newstr, MAX_STRLEN) < 0)
+ {
+ sfree(newstr);
+ break;
+ }
+ sfree(cfg->sval);
+ cfg->sval = newstr;
+ break;
+ }
+ case C_BOOLEAN:
+ {
+ bool res = cfg->ival != 0;
+ rb->set_bool(cfg->name, &res);
+ cfg->ival = res;
+ break;
+ }
+ case C_CHOICES:
+ {
+ int sel = list_choose(cfg->sval, cfg->name);
+ if(sel >= 0)
+ cfg->ival = sel;
+ break;
+ }
+ default:
+ fatal("bad type");
+ break;
+ }
+}
+
+const char *config_formatter(int sel, void *data, char *buf, size_t len)
+{
+ config_item *cfg = data;
+ cfg += sel;
+ rb->snprintf(buf, len, "%s", cfg->name);
+ return buf;
+}
+
+static void config_menu(void)
+{
+ char *title;
+ config_item *config = midend_get_config(me, CFG_SETTINGS, &title);
+
+ if(!config)
+ {
+ rb->splash(HZ, "Nothing to configure.");
+ goto done;
+ }
+
+ /* count */
+ int n = 0;
+ config_item *ptr = config;
+ while(ptr->type != C_END)
+ {
+ n++;
+ ptr++;
+ }
+
+ /* display a list */
+ struct gui_synclist list;
+
+ rb->gui_synclist_init(&list, &config_formatter, config, false, 1, NULL);
+ rb->gui_synclist_set_icon_callback(&list, NULL);
+ rb->gui_synclist_set_nb_items(&list, n);
+ rb->gui_synclist_limit_scroll(&list, false);
+
+ rb->gui_synclist_select_item(&list, 0);
+
+ bool done = false;
+ rb->gui_synclist_set_title(&list, title, NOICON);
+ while (!done)
+ {
+ rb->gui_synclist_draw(&list);
+ int button = rb->get_action(CONTEXT_LIST, TIMEOUT_BLOCK);
+ if(rb->gui_synclist_do_button(&list, &button, LIST_WRAP_ON))
+ continue;
+ switch(button)
+ {
+ case ACTION_STD_OK:
+ {
+ config_item old;
+ int pos = rb->gui_synclist_get_sel_pos(&list);
+ memcpy(&old, config + pos, sizeof(old));
+ do_configure_item(config + pos);
+ char *err = midend_set_config(me, CFG_SETTINGS, config);
+ if(err)
+ {
+ rb->splash(HZ, err);
+ memcpy(config + pos, &old, sizeof(old));
+ }
+ break;
+ }
+ case ACTION_STD_PREV:
+ case ACTION_STD_CANCEL:
+ done = true;
+ break;
+ default:
+ break;
+ }
+ }
+
+done:
+ sfree(title);
+ free_cfg(config);
+}
+
+static int pause_menu(void)
+{
+ MENUITEM_STRINGLIST(menu, "Paused", NULL,
+ "Resume Game",
+ "New Game",
+ "Undo",
+ "Redo",
+ "Solve",
+ "Configure Game",
+ "Select Another Game",
+ "Quit");
+ bool quit = false;
+ while(!quit)
+ {
+ int sel = 0;
+ switch(rb->do_menu(&menu, &sel, NULL, false))
+ {
+ case 0:
+ quit = true;
+ break;
+ case 1:
+ midend_new_game(me);
+ fix_size();
+ quit = true;
+ break;
+ case 2:
+ if(!midend_can_undo(me))
+ rb->splash(HZ, "Cannot undo.");
+ else
+ midend_process_key(me, 0, 0, 'u');
+ quit = true;
+ break;
+ case 3:
+ if(!midend_can_undo(me))
+ rb->splash(HZ, "Cannot redo.");
+ else
+ midend_process_key(me, 0, 0, 'r');
+ quit = true;
+ break;
+ case 4:
+ {
+ char *msg = midend_solve(me);
+ if(msg)
+ rb->splash(HZ, msg);
+ quit = true;
+ break;
+ }
+ case 5:
+ config_menu();
+ break;
+ case 6:
+ return -1;
+ case 7:
+ return -2;
+ default:
+ break;
+ }
+ }
+ rb->lcd_set_background(LCD_WHITE);
+ rb->lcd_clear_display();
+ rb->lcd_update();
+ midend_force_redraw(me);
+ return 0;
+}
+
+static bool want_redraw = true;
+
+static int process_input(int tmo)
+{
+ int state = 0;
+ static const struct button_mapping *plugin_contexts[] = { pla_main_ctx };
+ int button = pluginlib_getaction(tmo, plugin_contexts, ARRAYLEN(plugin_contexts));
+ switch(button)
+ {
+ case PLA_UP:
+ state |= CURSOR_UP;
+ break;
+ case PLA_DOWN:
+ state |= CURSOR_DOWN;
+ break;
+ case PLA_LEFT:
+ state |= CURSOR_LEFT;
+ break;
+ case PLA_RIGHT:
+ state |= CURSOR_RIGHT;
+ break;
+ case PLA_SELECT:
+ state |= CURSOR_SELECT;
+ break;
+ case PLA_CANCEL:
+ want_redraw = false;
+ return pause_menu();
+ default:
+ exit_on_usb(button);
+ break;
+ }
+ return state;
+}
+
+static long last_tstamp;
+
+static void timer_cb(void)
+{
+ LOGF("timer callback");
+ midend_timer(me, (float)(*rb->current_tick - last_tstamp) / (float)HZ);
+ last_tstamp = *rb->current_tick;
+}
+
+static volatile bool timer_on = false;
+
+void activate_timer(frontend *fe)
+{
+ last_tstamp = *rb->current_tick;
+ timer_on = true;
+}
+
+void deactivate_timer(frontend *fe)
+{
+ timer_on = false;
+}
+
+/* can't use audio buffer */
+char giant_buffer[1024*1024*4];
+
+const char *formatter(char *buf, size_t n, int i, const char *unit)
+{
+ rb->snprintf(buf, n, "%s", gamelist[i]->name);
+ return buf;
+}
+
+static void fix_size(void)
+{
+ int w = LCD_WIDTH, h = LCD_HEIGHT;
+ midend_size(me, &w, &h, TRUE);
+}
+
+static void init_for_game(const game *gm)
+{
+ me = midend_new(NULL, gm, &rb_drawing, NULL);
+ midend_new_game(me);
+
+ fix_size();
+
+ float *floatcolors = midend_colours(me, &ncolors);
+
+ /* convert them to packed RGB */
+ colors = smalloc(ncolors * sizeof(unsigned));
+ unsigned *ptr = colors;
+ for(int i = 0; i < ncolors; ++i)
+ {
+ int r = 255 * *(floatcolors++);
+ int g = 255 * *(floatcolors++);
+ int b = 255 * *(floatcolors++);
+ LOGF("color %d is %d %d %d", i, r, g, b);
+ *ptr++ = LCD_RGBPACK(r, g, b);
+ }
+
+ /* seems to crash */
+ //sfree(floatcolors);
+
+ rb->lcd_set_viewport(NULL);
+ rb->lcd_set_backdrop(NULL);
+ rb->lcd_set_foreground(LCD_BLACK);
+ rb->lcd_set_background(LCD_WHITE);
+ rb->lcd_clear_display();
+ rb->lcd_update();
+
+ midend_force_redraw(me);
+}
+
+enum plugin_status plugin_start(const void *param)
+{
+ (void) param;
+ /* reset tlsf by nuking the signature */
+ /* will make any already-allocated memory point to garbage */
+ memset(giant_buffer, 0, 4);
+ init_memory_pool(sizeof(giant_buffer), giant_buffer);
+
+ int gm = 0;
+ while(1)
+ {
+ if(rb->set_int("Choose Game", "", UNIT_INT, &gm, NULL, 1, 0, gamecount - 1, formatter))
+ return PLUGIN_OK;
+
+ init_for_game(gamelist[gm]);
+
+ while(1)
+ {
+ want_redraw = true;
+ int button = process_input(timer_on ? TIMER_INTERVAL : -1);
+ if(button < 0)
+ {
+ rb_unclip(NULL);
+ deactivate_timer(NULL);
+ midend_free(me);
+ /* new game */
+ if(button == -1)
+ break;
+ else if(button == -2)
+ {
+ sfree(colors);
+ exit(PLUGIN_OK);
+ }
+ }
+
+ if(button)
+ midend_process_key(me, 0, 0, button);
+ if(want_redraw)
+ midend_redraw(me);
+
+ if(timer_on)
+ timer_cb();
+
+ rb->yield();
+ }
+ sfree(colors);
+ }
+}