28fef2e415
Change-Id: I34dd8673ebcbc7fcd55681175d0e18c8bbc5fe6c
2040 lines
52 KiB
C
2040 lines
52 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
* $Id$
|
|
*
|
|
* Copyright (C) 2017 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.
|
|
*
|
|
***************************************************************************/
|
|
|
|
/* rockbox frontend for puzzles */
|
|
|
|
#include "plugin.h"
|
|
|
|
#include "keymaps.h"
|
|
#include "src/puzzles.h"
|
|
|
|
#ifndef COMBINED
|
|
#include "lib/playback_control.h"
|
|
#endif
|
|
#include "lib/xlcd.h"
|
|
#include "fixedpoint.h"
|
|
|
|
/* how many ticks between timer callbacks */
|
|
#define TIMER_INTERVAL (HZ / 50)
|
|
#define BG_R .9f /* very light gray */
|
|
#define BG_G .9f
|
|
#define BG_B .9f
|
|
|
|
#ifdef COMBINED
|
|
#define SAVE_FILE PLUGIN_GAMES_DATA_DIR "/puzzles.sav"
|
|
#else
|
|
static char save_file_path[MAX_PATH];
|
|
#define SAVE_FILE ((const char*)save_file_path)
|
|
#endif
|
|
|
|
#define BG_COLOR LCD_RGBPACK((int)(255*BG_R), (int)(255*BG_G), (int)(255*BG_B))
|
|
|
|
#define MURICA
|
|
|
|
#ifdef MURICA
|
|
#define midend_serialize midend_serialise
|
|
#define midend_deserialize midend_deserialise
|
|
#define frontend_default_color frontend_default_colour
|
|
#define midend_colors midend_colours
|
|
#endif
|
|
|
|
static midend *me = NULL;
|
|
static unsigned *colors = NULL;
|
|
static int ncolors = 0;
|
|
static long last_keystate = 0;
|
|
|
|
#ifdef FOR_REAL
|
|
/* the "debug menu" is hidden by default in order to avoid the
|
|
* naturally ensuing complaints from users */
|
|
static bool debug_mode = false;
|
|
static int help_times = 0;
|
|
#endif
|
|
|
|
static void fix_size(void);
|
|
|
|
static struct viewport clip_rect;
|
|
static bool clipped = false;
|
|
extern bool audiobuf_available;
|
|
|
|
static struct settings_t {
|
|
int slowmo_factor;
|
|
bool bulk, timerflash, clipoff, shortcuts, no_aa;
|
|
} settings;
|
|
|
|
/* clipping is implemented through viewports and offsetting
|
|
* coordinates */
|
|
static void rb_clip(void *handle, int x, int y, int w, int h)
|
|
{
|
|
if(!settings.clipoff)
|
|
{
|
|
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(&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 color, char *text)
|
|
{
|
|
(void) fontsize;
|
|
LOGF("rb_draw_text(%d %d %s)", x, y, text);
|
|
|
|
offset_coords(&x, &y);
|
|
|
|
/* TODO: variable font size */
|
|
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(color);
|
|
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 color)
|
|
{
|
|
LOGF("rb_draw_rect(%d, %d, %d, %d, %d)", x, y, w, h, color);
|
|
rb_color(color);
|
|
offset_coords(&x, &y);
|
|
rb->lcd_fillrect(x, y, w, h);
|
|
}
|
|
|
|
#define SWAP(a, b, t) do { t = a; a = b; b = t; } while(0);
|
|
|
|
#define fp_fpart(f, bits) ((f) & ((1 << (bits)) - 1))
|
|
#define fp_rfpart(f, bits) ((1 << (bits)) - fp_fpart(f, bits))
|
|
|
|
#define FRACBITS 16
|
|
|
|
/* most of our time drawing lines is spent in this function! */
|
|
static inline void plot(unsigned x, unsigned y, unsigned long a,
|
|
unsigned long r1, unsigned long g1, unsigned long b1,
|
|
unsigned cl, unsigned cr, unsigned cu, unsigned cd)
|
|
{
|
|
/* This is really quite possibly the least efficient way of doing
|
|
this. A better way would be in draw_antialiased_line(), but the
|
|
problem with that is that the algorithms I investigated at
|
|
least were incorrect at least part of the time and didn't make
|
|
drawing much faster overall. */
|
|
if(!(cl <= x && x < cr && cu <= y && y < cd))
|
|
return;
|
|
|
|
fb_data *ptr = rb->lcd_framebuffer + y * LCD_WIDTH + x;
|
|
fb_data orig = *ptr;
|
|
unsigned long r2, g2, b2;
|
|
#if LCD_DEPTH != 24
|
|
r2 = RGB_UNPACK_RED(orig);
|
|
g2 = RGB_UNPACK_GREEN(orig);
|
|
b2 = RGB_UNPACK_BLUE(orig);
|
|
#else
|
|
r2 = orig.r;
|
|
g2 = orig.g;
|
|
b2 = orig.b;
|
|
#endif
|
|
|
|
unsigned long r, g, b;
|
|
r = ((r1 * a) + (r2 * (256 - a))) >> 8;
|
|
g = ((g1 * a) + (g2 * (256 - a))) >> 8;
|
|
b = ((b1 * a) + (b2 * (256 - a))) >> 8;
|
|
|
|
#if LCD_DEPTH != 24
|
|
*ptr = LCD_RGBPACK(r, g, b);
|
|
#else
|
|
*ptr = (fb_data) {b, g, r};
|
|
#endif
|
|
}
|
|
|
|
#undef ABS
|
|
#define ABS(a) ((a)<0?-(a):(a))
|
|
|
|
/* speed benchmark: 34392 lines/sec vs 112687 non-antialiased
|
|
* lines/sec at full optimization on ipod6g */
|
|
|
|
/* expects UN-OFFSET coordinates, directly access framebuffer */
|
|
static void draw_antialiased_line(int x0, int y0, int x1, int y1)
|
|
{
|
|
/* fixed-point Wu's algorithm, modified for integer-only endpoints */
|
|
|
|
/* passed to plot() to avoid re-calculation */
|
|
unsigned short l = 0, r = LCD_WIDTH, u = 0, d = LCD_HEIGHT;
|
|
if(clipped)
|
|
{
|
|
l = clip_rect.x;
|
|
r = clip_rect.x + clip_rect.width;
|
|
u = clip_rect.y;
|
|
d = clip_rect.y + clip_rect.height;
|
|
}
|
|
|
|
bool steep = ABS(y1 - y0) > ABS(x1 - x0);
|
|
int tmp;
|
|
if(steep)
|
|
{
|
|
SWAP(x0, y0, tmp);
|
|
SWAP(x1, y1, tmp);
|
|
}
|
|
if(x0 > x1)
|
|
{
|
|
SWAP(x0, x1, tmp);
|
|
SWAP(y0, y1, tmp);
|
|
}
|
|
|
|
int dx, dy;
|
|
dx = x1 - x0;
|
|
dy = y1 - y0;
|
|
|
|
if(!(dx << FRACBITS))
|
|
return; /* bail out */
|
|
|
|
long gradient = fp_div(dy << FRACBITS, dx << FRACBITS, FRACBITS);
|
|
long intery = (y0 << FRACBITS);
|
|
|
|
unsigned color = rb->lcd_get_foreground();
|
|
unsigned long r1, g1, b1;
|
|
r1 = RGB_UNPACK_RED(color);
|
|
g1 = RGB_UNPACK_GREEN(color);
|
|
b1 = RGB_UNPACK_BLUE(color);
|
|
|
|
/* main loop */
|
|
if(steep)
|
|
{
|
|
for(int x = x0; x <= x1; ++x, intery += gradient)
|
|
{
|
|
unsigned y = intery >> FRACBITS;
|
|
unsigned alpha = fp_fpart(intery, FRACBITS) >> (FRACBITS - 8);
|
|
|
|
plot(y, x, (1 << 8) - alpha, r1, g1, b1, l, r, u, d);
|
|
plot(y + 1, x, alpha, r1, g1, b1, l, r, u, d);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for(int x = x0; x <= x1; ++x, intery += gradient)
|
|
{
|
|
unsigned y = intery >> FRACBITS;
|
|
unsigned alpha = fp_fpart(intery, FRACBITS) >> (FRACBITS - 8);
|
|
|
|
plot(x, y, (1 << 8) - alpha, r1, g1, b1, l, r, u, d);
|
|
plot(x, y + 1, alpha, r1, g1, b1, l, r, u, d);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void rb_draw_line(void *handle, int x1, int y1, int x2, int y2,
|
|
int color)
|
|
{
|
|
LOGF("rb_draw_line(%d, %d, %d, %d, %d)", x1, y1, x2, y2, color);
|
|
rb_color(color);
|
|
if(settings.no_aa)
|
|
{
|
|
offset_coords(&x1, &y1);
|
|
offset_coords(&x2, &y2);
|
|
rb->lcd_drawline(x1, y1, x2, y2);
|
|
}
|
|
else
|
|
draw_antialiased_line(x1, y1, x2, y2);
|
|
}
|
|
|
|
#if 0
|
|
/*
|
|
* draw filled polygon
|
|
* originally by Sebastian Leonhardt (ulmutul)
|
|
* 'count' : number of coordinate pairs
|
|
* 'pxy': array of coordinates. pxy[0]=x0,pxy[1]=y0,...
|
|
* note: provide space for one extra coordinate, because the starting point
|
|
* will automatically be inserted as end point.
|
|
*/
|
|
|
|
/*
|
|
* helper function:
|
|
* find points of intersection between polygon and scanline
|
|
*/
|
|
|
|
#define MAX_INTERSECTION 32
|
|
|
|
static void fill_poly_line(int scanline, int count, int *pxy)
|
|
{
|
|
int i;
|
|
int j;
|
|
int num_of_intersects;
|
|
int direct, old_direct;
|
|
//intersections of every line with scanline (y-coord)
|
|
int intersection[MAX_INTERSECTION];
|
|
/* add starting point as ending point */
|
|
pxy[count*2] = pxy[0];
|
|
pxy[count*2+1] = pxy[1];
|
|
|
|
old_direct=0;
|
|
num_of_intersects=0;
|
|
for (i=0; i<count*2; i+=2) {
|
|
int x1=pxy[i];
|
|
int y1=pxy[i+1];
|
|
int x2=pxy[i+2];
|
|
int y2=pxy[i+3];
|
|
// skip if line is outside of scanline
|
|
if (y1 < y2) {
|
|
if (scanline < y1 || scanline > y2)
|
|
continue;
|
|
}
|
|
else {
|
|
if (scanline < y2 || scanline > y1)
|
|
continue;
|
|
}
|
|
// calculate x-coord of intersection
|
|
if (y1==y2) {
|
|
direct=0;
|
|
}
|
|
else {
|
|
direct = y1>y2 ? 1 : -1;
|
|
// omit double intersections, if both lines lead in the same direction
|
|
intersection[num_of_intersects] =
|
|
x1+((scanline-y1)*(x2-x1))/(y2-y1);
|
|
if ( (direct!=old_direct)
|
|
|| (intersection[num_of_intersects] != intersection[num_of_intersects-1])
|
|
)
|
|
++num_of_intersects;
|
|
}
|
|
old_direct = direct;
|
|
}
|
|
|
|
// sort points of intersection
|
|
for (i=0; i<num_of_intersects-1; ++i) {
|
|
for (j=i+1; j<num_of_intersects; ++j) {
|
|
if (intersection[j]<intersection[i]) {
|
|
int temp=intersection[i];
|
|
intersection[i]=intersection[j];
|
|
intersection[j]=temp;
|
|
}
|
|
}
|
|
}
|
|
// draw
|
|
for (i=0; i<num_of_intersects; i+=2) {
|
|
rb->lcd_hline(intersection[i], intersection[i+1], scanline);
|
|
}
|
|
}
|
|
|
|
/* two extra elements at end of pxy needed */
|
|
static void v_fillarea(int count, int *pxy)
|
|
{
|
|
int i;
|
|
int y1, y2;
|
|
|
|
// find min and max y coords
|
|
y1=y2=pxy[1];
|
|
for (i=3; i<count*2; i+=2) {
|
|
if (pxy[i] < y1) y1 = pxy[i];
|
|
else if (pxy[i] > y2) y2 = pxy[i];
|
|
}
|
|
|
|
for (i=y1; i<=y2; ++i) {
|
|
fill_poly_line(i, count, pxy);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static void rb_draw_poly(void *handle, int *coords, int npoints,
|
|
int fillcolor, int outlinecolor)
|
|
{
|
|
LOGF("rb_draw_poly");
|
|
|
|
if(fillcolor >= 0)
|
|
{
|
|
rb_color(fillcolor);
|
|
#if 1
|
|
/* serious hack: draw a bunch of triangles between adjacent points */
|
|
/* this generally works, even with some concave polygons */
|
|
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);
|
|
|
|
#if 0
|
|
/* debug code */
|
|
rb->lcd_set_foreground(LCD_RGBPACK(255,0,0));
|
|
rb->lcd_drawpixel(x1, y1);
|
|
rb->lcd_drawpixel(x2, y2);
|
|
rb->lcd_drawpixel(x3, y3);
|
|
rb->lcd_update();
|
|
rb->sleep(HZ);
|
|
rb_color(fillcolor);
|
|
rb->lcd_drawpixel(x1, y1);
|
|
rb->lcd_drawpixel(x2, y2);
|
|
rb->lcd_drawpixel(x3, y3);
|
|
rb->lcd_update();
|
|
#endif
|
|
}
|
|
#else
|
|
int *pxy = smalloc(sizeof(int) * 2 * npoints + 2);
|
|
/* copy points, offsetted */
|
|
for(int i = 0; i < npoints; ++i)
|
|
{
|
|
pxy[2 * i + 0] = coords[2 * i + 0];
|
|
pxy[2 * i + 1] = coords[2 * i + 1];
|
|
offset_coords(&pxy[2*i+0], &pxy[2*i+1]);
|
|
}
|
|
v_fillarea(npoints, pxy);
|
|
sfree(pxy);
|
|
#endif
|
|
}
|
|
|
|
/* draw outlines last so they're not covered by the fill */
|
|
assert(outlinecolor >= 0);
|
|
rb_color(outlinecolor);
|
|
|
|
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];
|
|
if(settings.no_aa)
|
|
{
|
|
offset_coords(&x1, &y1);
|
|
offset_coords(&x2, &y2);
|
|
rb->lcd_drawline(x1, y1,
|
|
x2, y2);
|
|
}
|
|
else
|
|
draw_antialiased_line(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];
|
|
if(settings.no_aa)
|
|
{
|
|
offset_coords(&x1, &y1);
|
|
offset_coords(&x2, &y2);
|
|
|
|
rb->lcd_drawline(x1, y1,
|
|
x2, y2);
|
|
}
|
|
else
|
|
draw_antialiased_line(x1, y1, x2, y2);
|
|
}
|
|
|
|
static void rb_draw_circle(void *handle, int cx, int cy, int radius,
|
|
int fillcolor, int outlinecolor)
|
|
{
|
|
LOGF("rb_draw_circle(%d, %d, %d)", cx, cy, radius);
|
|
offset_coords(&cx, &cy);
|
|
|
|
if(fillcolor >= 0)
|
|
{
|
|
rb_color(fillcolor);
|
|
xlcd_fillcircle(cx, cy, radius - 1);
|
|
}
|
|
|
|
assert(outlinecolor >= 0);
|
|
rb_color(outlinecolor);
|
|
xlcd_drawcircle(cx, cy, radius - 1);
|
|
}
|
|
|
|
struct blitter {
|
|
bool have_data;
|
|
int x, y;
|
|
struct bitmap bmp;
|
|
};
|
|
|
|
/* originally from emcc.c */
|
|
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 - 1 ? LCD_WIDTH - 1: x0);
|
|
x1 = (x1 < 0 ? 0 : x1 > LCD_WIDTH - 1 ? LCD_WIDTH - 1: x1);
|
|
y0 = (y0 < 0 ? 0 : y0 > LCD_HEIGHT - 1 ? LCD_HEIGHT - 1: y0);
|
|
y1 = (y1 < 0 ? 0 : y1 > LCD_HEIGHT - 1 ? LCD_HEIGHT - 1: y1);
|
|
|
|
/* Transform back into x,y,w,h to return */
|
|
*x = x0;
|
|
*y = y0;
|
|
*w = x1 - x0;
|
|
*h = y1 - y0;
|
|
}
|
|
|
|
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);
|
|
return;
|
|
}
|
|
|
|
/* copy a section of the framebuffer */
|
|
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) * 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;
|
|
|
|
offset_coords(&x, &y);
|
|
trim_rect(&x, &y, &w, &h);
|
|
rb->lcd_bitmap((fb_data*)bl->bmp.data, x, y, w, h);
|
|
}
|
|
|
|
static bool need_draw_update = false;
|
|
|
|
static int ud_l = 0, ud_u = 0, ud_r = LCD_WIDTH, ud_d = LCD_HEIGHT;
|
|
|
|
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);
|
|
|
|
/* It seems that the puzzles use a different definition of
|
|
* "updating" the display than Rockbox does; by calling this
|
|
* function, it tells us that it has either already drawn to the
|
|
* updated area (as rockbox assumes), or that it WILL draw to the
|
|
* said area. Thus we simply remember a rectangle that contains
|
|
* all the updated regions and update it at the very end. */
|
|
|
|
/* adapted from gtk.c */
|
|
if (!need_draw_update || ud_l > x ) ud_l = x;
|
|
if (!need_draw_update || ud_r < x+w) ud_r = x+w;
|
|
if (!need_draw_update || ud_u > y ) ud_u = y;
|
|
if (!need_draw_update || ud_d < y+h) ud_d = y+h;
|
|
|
|
need_draw_update = true;
|
|
}
|
|
|
|
static void rb_start_draw(void *handle)
|
|
{
|
|
(void) handle;
|
|
|
|
/* ... mumble mumble ... not ... reentrant ... mumble mumble ... */
|
|
|
|
need_draw_update = false;
|
|
ud_l = 0;
|
|
ud_r = LCD_WIDTH;
|
|
ud_u = 0;
|
|
ud_d = LCD_HEIGHT;
|
|
}
|
|
|
|
static void rb_end_draw(void *handle)
|
|
{
|
|
(void) handle;
|
|
|
|
LOGF("rb_end_draw");
|
|
|
|
if(need_draw_update)
|
|
rb->lcd_update_rect(ud_l, ud_u, ud_r - ud_l, ud_d - ud_u);
|
|
}
|
|
|
|
static char *titlebar = NULL;
|
|
|
|
static void rb_status_bar(void *handle, char *text)
|
|
{
|
|
if(titlebar)
|
|
sfree(titlebar);
|
|
titlebar = dupstr(text);
|
|
LOGF("game title is %s\n", text);
|
|
}
|
|
|
|
static void draw_title(void)
|
|
{
|
|
const char *str = NULL;
|
|
if(titlebar)
|
|
str = titlebar;
|
|
else
|
|
str = midend_which_game(me)->name;
|
|
|
|
/* quick hack */
|
|
bool orig_clipped = clipped;
|
|
if(orig_clipped)
|
|
rb_unclip(NULL);
|
|
|
|
int h;
|
|
rb->lcd_setfont(FONT_UI);
|
|
rb->lcd_getstringsize(str, NULL, &h);
|
|
|
|
rb->lcd_set_foreground(BG_COLOR);
|
|
rb->lcd_fillrect(0, LCD_HEIGHT - h, LCD_WIDTH, h);
|
|
|
|
rb->lcd_set_foreground(LCD_BLACK);
|
|
rb->lcd_putsxy(0, LCD_HEIGHT - h, str);
|
|
rb->lcd_update_rect(0, LCD_HEIGHT - h, LCD_WIDTH, h);
|
|
|
|
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)
|
|
{
|
|
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_color(frontend *fe, float *out)
|
|
{
|
|
*out++ = BG_R;
|
|
*out++ = BG_G;
|
|
*out++ = BG_B;
|
|
}
|
|
|
|
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, (void*)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, (char*)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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* return value is only meaningful when type == C_STRING */
|
|
static bool 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);
|
|
return false;
|
|
}
|
|
sfree(cfg->sval);
|
|
cfg->sval = newstr;
|
|
return true;
|
|
}
|
|
case C_BOOLEAN:
|
|
{
|
|
bool res = cfg->ival != 0;
|
|
rb->set_bool(cfg->name, &res);
|
|
|
|
/* seems to reset backdrop */
|
|
rb->lcd_set_backdrop(NULL);
|
|
|
|
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;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
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 bool config_menu(void)
|
|
{
|
|
char *title;
|
|
config_item *config = midend_get_config(me, CFG_SETTINGS, &title);
|
|
|
|
bool success = false;
|
|
|
|
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));
|
|
char *old_str = NULL;
|
|
if(old.type == C_STRING)
|
|
old_str = dupstr(old.sval);
|
|
bool freed_str = 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));
|
|
if(freed_str)
|
|
config[pos].sval = old_str;
|
|
}
|
|
else
|
|
{
|
|
if(old.type == C_STRING)
|
|
{
|
|
/* success, and we duplicated the old string, so free it */
|
|
sfree(old_str);
|
|
}
|
|
success = true;
|
|
}
|
|
break;
|
|
}
|
|
case ACTION_STD_PREV:
|
|
case ACTION_STD_CANCEL:
|
|
done = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
done:
|
|
sfree(title);
|
|
free_cfg(config);
|
|
return success;
|
|
}
|
|
|
|
const char *preset_formatter(int sel, void *data, char *buf, size_t len)
|
|
{
|
|
struct preset_menu *menu = data;
|
|
rb->snprintf(buf, len, "%s", menu->entries[sel].title);
|
|
return buf;
|
|
}
|
|
|
|
/* main worker function */
|
|
static bool do_preset_menu(struct preset_menu *menu, char *title)
|
|
{
|
|
if(!menu->n_entries)
|
|
return false;
|
|
|
|
/* display a list */
|
|
struct gui_synclist list;
|
|
|
|
rb->gui_synclist_init(&list, &preset_formatter, menu, false, 1, NULL);
|
|
rb->gui_synclist_set_icon_callback(&list, NULL);
|
|
rb->gui_synclist_set_nb_items(&list, menu->n_entries);
|
|
rb->gui_synclist_limit_scroll(&list, false);
|
|
|
|
rb->gui_synclist_select_item(&list, 0); /* we don't start with the current one selected */
|
|
|
|
char def[] = "Game Type";
|
|
rb->gui_synclist_set_title(&list, title ? title : def, 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:
|
|
{
|
|
int sel = rb->gui_synclist_get_sel_pos(&list);
|
|
struct preset_menu_entry *entry = menu->entries + sel;
|
|
if(entry->params)
|
|
{
|
|
midend_set_params(me, entry->params);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
/* recurse */
|
|
if(do_preset_menu(entry->submenu, entry->title))
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
case ACTION_STD_PREV:
|
|
case ACTION_STD_CANCEL:
|
|
return false;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool presets_menu(void)
|
|
{
|
|
return do_preset_menu(midend_get_presets(me, NULL), NULL);
|
|
}
|
|
|
|
static const struct {
|
|
const char *game, *help;
|
|
} quick_help_text[] = {
|
|
{ "Black Box", "Find the hidden balls in the box by bouncing laser beams off them." },
|
|
{ "Bridges", "Connect all the islands with a network of bridges." },
|
|
{ "Cube", "Pick up all the blue squares by rolling the cube over them." },
|
|
{ "Dominosa", "Tile the rectangle with a full set of dominoes." },
|
|
{ "Fifteen", "Slide the tiles around to arrange them into order." },
|
|
{ "Filling", "Mark every square with the area of its containing region." },
|
|
{ "Flip", "Flip groups of squares to light them all up at once." },
|
|
{ "Flood", "Turn the grid the same colour in as few flood fills as possible." },
|
|
{ "Galaxies", "Divide the grid into rotationally symmetric regions each centred on a dot." },
|
|
{ "Guess", "Guess the hidden combination of colours." },
|
|
{ "Inertia", "Collect all the gems without running into any of the mines." },
|
|
{ "Keen", "Complete the latin square in accordance with the arithmetic clues." },
|
|
{ "Light Up", "Place bulbs to light up all the squares." },
|
|
{ "Loopy", "Draw a single closed loop, given clues about number of adjacent edges." },
|
|
{ "Magnets", "Place magnets to satisfy the clues and avoid like poles touching." },
|
|
{ "Map", "Colour the map so that adjacent regions are never the same colour." },
|
|
{ "Mines", "Find all the mines without treading on any of them." },
|
|
{ "Net", "Rotate each tile to reassemble the network." },
|
|
{ "Netslide", "Slide a row at a time to reassemble the network." },
|
|
{ "Palisade", "Divide the grid into equal-sized areas in accordance with the clues." },
|
|
{ "Pattern", "Fill in the pattern in the grid, given only the lengths of runs of black squares." },
|
|
{ "Pearl", "Draw a single closed loop, given clues about corner and straight squares." },
|
|
{ "Pegs", "Jump pegs over each other to remove all but one." },
|
|
{ "Range", "Place black squares to limit the visible distance from each numbered cell." },
|
|
{ "Rectangles", "Divide the grid into rectangles with areas equal to the numbers." },
|
|
{ "Same Game", "Clear the grid by removing touching groups of the same colour squares." },
|
|
{ "Signpost", "Connect the squares into a path following the arrows." },
|
|
{ "Singles", "Black out the right set of duplicate numbers." },
|
|
{ "Sixteen", "Slide a row at a time to arrange the tiles into order." },
|
|
{ "Slant", "Draw a maze of slanting lines that matches the clues." },
|
|
{ "Solo", "Fill in the grid so that each row, column and square block contains one of every digit." },
|
|
{ "Tents", "Place a tent next to each tree." },
|
|
{ "Towers", "Complete the latin square of towers in accordance with the clues." },
|
|
{ "Tracks", "Fill in the railway track according to the clues." },
|
|
{ "Twiddle", "Rotate the tiles around themselves to arrange them into order." },
|
|
{ "Undead", "Place ghosts, vampires and zombies so that the right numbers of them can be seen in mirrors." },
|
|
{ "Unequal", "Complete the latin square in accordance with the > signs." },
|
|
{ "Unruly", "Fill in the black and white grid to avoid runs of three." },
|
|
{ "Untangle", "Reposition the points so that the lines do not cross." },
|
|
};
|
|
|
|
static void quick_help(void)
|
|
{
|
|
#ifdef FOR_REAL
|
|
if(++help_times >= 5)
|
|
{
|
|
rb->splash(HZ, "You are now a developer!");
|
|
debug_mode = true;
|
|
}
|
|
#endif
|
|
|
|
for(int i = 0; i < ARRAYLEN(quick_help_text); ++i)
|
|
{
|
|
if(!strcmp(midend_which_game(me)->name, quick_help_text[i].game))
|
|
{
|
|
rb->splash(0, quick_help_text[i].help);
|
|
rb->button_get(true);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void full_help(void)
|
|
{
|
|
/* TODO */
|
|
}
|
|
|
|
static void init_default_settings(void)
|
|
{
|
|
settings.slowmo_factor = 1;
|
|
settings.bulk = false;
|
|
settings.timerflash = false;
|
|
settings.clipoff = false;
|
|
settings.shortcuts = false;
|
|
settings.no_aa = false;
|
|
}
|
|
|
|
static void bench_aa(void)
|
|
{
|
|
rb->sleep(0);
|
|
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
|
|
rb->cpu_boost(true);
|
|
#endif
|
|
int next = *rb->current_tick + HZ;
|
|
int i = 0;
|
|
while(*rb->current_tick < next)
|
|
{
|
|
draw_antialiased_line(0, 0, 20, 31);
|
|
++i;
|
|
}
|
|
rb->splashf(HZ, "%d AA lines/sec", i);
|
|
next = *rb->current_tick + HZ;
|
|
int j = 0;
|
|
while(*rb->current_tick < next)
|
|
{
|
|
rb->lcd_drawline(0, 0, 20, 31);
|
|
++j;
|
|
}
|
|
rb->splashf(HZ, "%d normal lines/sec", j);
|
|
rb->splashf(HZ, "Efficiency: %d%%", 100 * i / j);
|
|
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
|
|
rb->cpu_boost(false);
|
|
#endif
|
|
}
|
|
|
|
static void debug_menu(void)
|
|
{
|
|
MENUITEM_STRINGLIST(menu, "Debug Menu", NULL,
|
|
"Slowmo factor",
|
|
"Randomize colors",
|
|
"Toggle bulk update",
|
|
"Toggle flash pixel on timer",
|
|
"Toggle clip",
|
|
"Toggle shortcuts",
|
|
"Toggle antialias",
|
|
"Benchmark antialias",
|
|
"Back");
|
|
bool quit = false;
|
|
int sel = 0;
|
|
while(!quit)
|
|
{
|
|
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);
|
|
break;
|
|
case 1:
|
|
{
|
|
unsigned *ptr = colors;
|
|
for(int i = 0; i < ncolors; ++i)
|
|
{
|
|
/* not seeded, who cares? */
|
|
*ptr++ = LCD_RGBPACK(rb->rand()%255, rb->rand()%255, rb->rand()%255);
|
|
}
|
|
break;
|
|
}
|
|
case 2:
|
|
settings.bulk = !settings.bulk;
|
|
break;
|
|
case 3:
|
|
settings.timerflash = !settings.timerflash;
|
|
break;
|
|
case 4:
|
|
settings.clipoff = !settings.clipoff;
|
|
break;
|
|
case 5:
|
|
settings.shortcuts = !settings.shortcuts;
|
|
break;
|
|
case 6:
|
|
settings.no_aa = !settings.no_aa;
|
|
break;
|
|
case 7:
|
|
bench_aa();
|
|
break;
|
|
case 8:
|
|
default:
|
|
quit = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int pausemenu_cb(int action, const struct menu_item_ex *this_item)
|
|
{
|
|
int i = (intptr_t) this_item;
|
|
if(action == ACTION_REQUEST_MENUITEM)
|
|
{
|
|
switch(i)
|
|
{
|
|
case 3:
|
|
if(!midend_can_undo(me))
|
|
return ACTION_EXIT_MENUITEM;
|
|
break;
|
|
case 4:
|
|
if(!midend_can_redo(me))
|
|
return ACTION_EXIT_MENUITEM;
|
|
break;
|
|
case 5:
|
|
if(!midend_which_game(me)->can_solve)
|
|
return ACTION_EXIT_MENUITEM;
|
|
break;
|
|
case 7:
|
|
#ifdef FOR_REAL
|
|
return ACTION_EXIT_MENUITEM;
|
|
#else
|
|
break;
|
|
#endif
|
|
case 8:
|
|
#ifdef COMBINED
|
|
/* audio buf is used, so no playback */
|
|
/* TODO: neglects app builds, but not many people will
|
|
* care, I bet */
|
|
return ACTION_EXIT_MENUITEM;
|
|
#else
|
|
if(audiobuf_available)
|
|
break;
|
|
else
|
|
return ACTION_EXIT_MENUITEM;
|
|
#endif
|
|
case 9:
|
|
if(!midend_get_presets(me, NULL)->n_entries)
|
|
return ACTION_EXIT_MENUITEM;
|
|
break;
|
|
case 10:
|
|
#ifdef FOR_REAL
|
|
if(debug_mode)
|
|
break;
|
|
return ACTION_EXIT_MENUITEM;
|
|
#else
|
|
break;
|
|
#endif
|
|
case 11:
|
|
if(!midend_which_game(me)->can_configure)
|
|
return ACTION_EXIT_MENUITEM;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
return action;
|
|
}
|
|
|
|
static void clear_and_draw(void)
|
|
{
|
|
rb->lcd_clear_display();
|
|
rb->lcd_update();
|
|
|
|
midend_force_redraw(me);
|
|
draw_title();
|
|
}
|
|
|
|
static void reset_drawing(void)
|
|
{
|
|
rb->lcd_set_viewport(NULL);
|
|
rb->lcd_set_backdrop(NULL);
|
|
rb->lcd_set_foreground(LCD_BLACK);
|
|
rb->lcd_set_background(BG_COLOR);
|
|
}
|
|
|
|
static int pause_menu(void)
|
|
{
|
|
#define static auto
|
|
#define const
|
|
MENUITEM_STRINGLIST(menu, NULL, pausemenu_cb,
|
|
"Resume Game",
|
|
"New Game",
|
|
"Restart Game",
|
|
"Undo",
|
|
"Redo",
|
|
"Solve",
|
|
"Quick Help",
|
|
"Extensive Help",
|
|
"Playback Control",
|
|
"Game Type",
|
|
"Debug Menu",
|
|
"Configure Game",
|
|
#ifdef COMBINED
|
|
"Select Another Game",
|
|
#endif
|
|
"Quit without Saving",
|
|
"Quit");
|
|
#undef static
|
|
#undef const
|
|
/* HACK ALERT */
|
|
char title[32] = { 0 };
|
|
rb->snprintf(title, sizeof(title), "%s Menu", midend_which_game(me)->name);
|
|
menu__.desc = title;
|
|
|
|
#ifdef FOR_REAL
|
|
help_times = 0;
|
|
#endif
|
|
|
|
bool quit = false;
|
|
int sel = 0;
|
|
while(!quit)
|
|
{
|
|
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:
|
|
midend_restart_game(me);
|
|
fix_size();
|
|
quit = true;
|
|
break;
|
|
case 3:
|
|
if(!midend_can_undo(me))
|
|
rb->splash(HZ, "Cannot undo.");
|
|
else
|
|
midend_process_key(me, 0, 0, 'u');
|
|
quit = true;
|
|
break;
|
|
case 4:
|
|
if(!midend_can_redo(me))
|
|
rb->splash(HZ, "Cannot redo.");
|
|
else
|
|
midend_process_key(me, 0, 0, 'r');
|
|
quit = true;
|
|
break;
|
|
case 5:
|
|
{
|
|
char *msg = midend_solve(me);
|
|
if(msg)
|
|
rb->splash(HZ, msg);
|
|
quit = true;
|
|
break;
|
|
}
|
|
case 6:
|
|
quick_help();
|
|
break;
|
|
case 7:
|
|
full_help();
|
|
break;
|
|
case 8:
|
|
playback_control(NULL);
|
|
break;
|
|
case 9:
|
|
if(presets_menu())
|
|
{
|
|
midend_new_game(me);
|
|
fix_size();
|
|
reset_drawing();
|
|
clear_and_draw();
|
|
quit = true;
|
|
}
|
|
break;
|
|
case 10:
|
|
debug_menu();
|
|
break;
|
|
case 11:
|
|
if(config_menu())
|
|
{
|
|
midend_new_game(me);
|
|
fix_size();
|
|
reset_drawing();
|
|
clear_and_draw();
|
|
quit = true;
|
|
}
|
|
break;
|
|
#ifdef COMBINED
|
|
case 12:
|
|
return -1;
|
|
case 13:
|
|
return -2;
|
|
case 14:
|
|
return -3;
|
|
#else
|
|
case 12:
|
|
return -2;
|
|
case 13:
|
|
return -3;
|
|
#endif
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
rb->lcd_set_background(BG_COLOR);
|
|
rb->lcd_clear_display();
|
|
rb->lcd_update();
|
|
midend_force_redraw(me);
|
|
return 0;
|
|
}
|
|
|
|
static bool want_redraw = true;
|
|
static bool accept_input = true;
|
|
|
|
/* ignore the excess of LOGFs below... */
|
|
#ifdef LOGF_ENABLE
|
|
#undef LOGF_ENABLE
|
|
#endif
|
|
static int process_input(int tmo)
|
|
{
|
|
LOGF("process_input start");
|
|
LOGF("------------------");
|
|
int state = 0;
|
|
|
|
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
|
|
rb->cpu_boost(false); /* about to block for button input */
|
|
#endif
|
|
|
|
int button = rb->button_get_w_tmo(tmo);
|
|
|
|
/* weird stuff */
|
|
exit_on_usb(button);
|
|
|
|
button = rb->button_status();
|
|
|
|
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
|
|
rb->cpu_boost(true);
|
|
#endif
|
|
|
|
if(button == BTN_PAUSE)
|
|
{
|
|
want_redraw = false;
|
|
/* quick hack to preserve the clipping state */
|
|
bool orig_clipped = clipped;
|
|
if(orig_clipped)
|
|
rb_unclip(NULL);
|
|
|
|
int rc = pause_menu();
|
|
|
|
if(orig_clipped)
|
|
rb_clip(NULL, clip_rect.x, clip_rect.y, clip_rect.width, clip_rect.height);
|
|
|
|
last_keystate = 0;
|
|
accept_input = true;
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* special case for inertia: moves occur after RELEASES */
|
|
if(!strcmp("Inertia", midend_which_game(me)->name))
|
|
{
|
|
LOGF("received button 0x%08x", button);
|
|
|
|
unsigned released = ~button & last_keystate;
|
|
last_keystate = button;
|
|
|
|
if(!button)
|
|
{
|
|
if(!accept_input)
|
|
{
|
|
LOGF("ignoring, all keys released but not accepting input before, can accept input later");
|
|
accept_input = true;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if(!released || !accept_input)
|
|
{
|
|
LOGF("released keys detected: 0x%08x", released);
|
|
LOGF("ignoring, either no keys released or not accepting input");
|
|
return 0;
|
|
}
|
|
|
|
button |= released;
|
|
if(last_keystate)
|
|
{
|
|
LOGF("ignoring input from now until all released");
|
|
accept_input = false;
|
|
}
|
|
LOGF("accepting event 0x%08x", button);
|
|
}
|
|
/* default is to ignore repeats except for untangle */
|
|
else if(strcmp("Untangle", midend_which_game(me)->name))
|
|
{
|
|
/* start accepting input again after a release */
|
|
if(!button)
|
|
{
|
|
accept_input = true;
|
|
return 0;
|
|
}
|
|
/* ignore repeats */
|
|
/* Untangle gets special treatment */
|
|
if(!accept_input)
|
|
return 0;
|
|
accept_input = false;
|
|
}
|
|
|
|
switch(button)
|
|
{
|
|
case BTN_UP:
|
|
state = CURSOR_UP;
|
|
break;
|
|
case BTN_DOWN:
|
|
state = CURSOR_DOWN;
|
|
break;
|
|
case BTN_LEFT:
|
|
state = CURSOR_LEFT;
|
|
break;
|
|
case BTN_RIGHT:
|
|
state = CURSOR_RIGHT;
|
|
break;
|
|
|
|
/* handle diagonals (mainly for Inertia) */
|
|
case BTN_DOWN | BTN_LEFT:
|
|
#ifdef BTN_DOWN_LEFT
|
|
case BTN_DOWN_LEFT:
|
|
#endif
|
|
state = '1' | MOD_NUM_KEYPAD;
|
|
break;
|
|
case BTN_DOWN | BTN_RIGHT:
|
|
#ifdef BTN_DOWN_RIGHT
|
|
case BTN_DOWN_RIGHT:
|
|
#endif
|
|
state = '3' | MOD_NUM_KEYPAD;
|
|
break;
|
|
case BTN_UP | BTN_LEFT:
|
|
#ifdef BTN_UP_LEFT
|
|
case BTN_UP_LEFT:
|
|
#endif
|
|
state = '7' | MOD_NUM_KEYPAD;
|
|
break;
|
|
case BTN_UP | BTN_RIGHT:
|
|
#ifdef BTN_UP_RIGHT
|
|
case BTN_UP_RIGHT:
|
|
#endif
|
|
state = '9' | MOD_NUM_KEYPAD;
|
|
break;
|
|
|
|
case BTN_FIRE:
|
|
state = CURSOR_SELECT;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if(settings.shortcuts)
|
|
{
|
|
static bool shortcuts_ok = true;
|
|
switch(button)
|
|
{
|
|
case BTN_LEFT | BTN_FIRE:
|
|
if(shortcuts_ok)
|
|
midend_process_key(me, 0, 0, 'u');
|
|
shortcuts_ok = false;
|
|
break;
|
|
case BTN_RIGHT | BTN_FIRE:
|
|
if(shortcuts_ok)
|
|
midend_process_key(me, 0, 0, 'r');
|
|
shortcuts_ok = false;
|
|
break;
|
|
case 0:
|
|
shortcuts_ok = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
LOGF("process_input done");
|
|
LOGF("------------------");
|
|
return state;
|
|
}
|
|
|
|
static long last_tstamp;
|
|
|
|
static void timer_cb(void)
|
|
{
|
|
#if LCD_DEPTH != 24
|
|
if(settings.timerflash)
|
|
{
|
|
static bool what = false;
|
|
what = !what;
|
|
if(what)
|
|
rb->lcd_framebuffer[0] = LCD_BLACK;
|
|
else
|
|
rb->lcd_framebuffer[0] = LCD_WHITE;
|
|
rb->lcd_update();
|
|
}
|
|
#endif
|
|
|
|
LOGF("timer callback");
|
|
midend_timer(me, ((float)(*rb->current_tick - last_tstamp) / (float)HZ) / settings.slowmo_factor);
|
|
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;
|
|
}
|
|
|
|
#ifdef COMBINED
|
|
/* can't use audio buffer */
|
|
char giant_buffer[1024*1024*4];
|
|
#else
|
|
/* points to pluginbuf */
|
|
char *giant_buffer = NULL;
|
|
#endif
|
|
static size_t giant_buffer_len = 0; /* set on start */
|
|
|
|
#ifdef COMBINED
|
|
const char *formatter(char *buf, size_t n, int i, const char *unit)
|
|
{
|
|
rb->snprintf(buf, n, "%s", gamelist[i]->name);
|
|
return buf;
|
|
}
|
|
#endif
|
|
|
|
static void fix_size(void)
|
|
{
|
|
int w = LCD_WIDTH, h = LCD_HEIGHT, h_x;
|
|
rb->lcd_setfont(FONT_UI);
|
|
rb->lcd_getstringsize("X", NULL, &h_x);
|
|
h -= h_x;
|
|
midend_size(me, &w, &h, TRUE);
|
|
}
|
|
|
|
static void reset_tlsf(void)
|
|
{
|
|
/* reset tlsf by nuking the signature */
|
|
/* will make any already-allocated memory point to garbage */
|
|
memset(giant_buffer, 0, 4);
|
|
init_memory_pool(giant_buffer_len, giant_buffer);
|
|
}
|
|
|
|
static int read_wrapper(void *ptr, void *buf, int len)
|
|
{
|
|
int fd = (int) ptr;
|
|
return rb->read(fd, buf, len);
|
|
}
|
|
|
|
static void write_wrapper(void *ptr, void *buf, int len)
|
|
{
|
|
int fd = (int) ptr;
|
|
rb->write(fd, buf, len);
|
|
}
|
|
|
|
static void init_colors(void)
|
|
{
|
|
float *floatcolors = midend_colors(me, &ncolors);
|
|
|
|
/* convert them to packed RGB */
|
|
colors = smalloc(ncolors * sizeof(unsigned));
|
|
unsigned *ptr = colors;
|
|
float *floatptr = floatcolors;
|
|
for(int i = 0; i < ncolors; ++i)
|
|
{
|
|
int r = 255 * *(floatptr++);
|
|
int g = 255 * *(floatptr++);
|
|
int b = 255 * *(floatptr++);
|
|
LOGF("color %d is %d %d %d", i, r, g, b);
|
|
*ptr++ = LCD_RGBPACK(r, g, b);
|
|
}
|
|
sfree(floatcolors);
|
|
}
|
|
|
|
static char *init_for_game(const game *gm, int load_fd, bool draw)
|
|
{
|
|
/* if we are loading a game tlsf has already been initialized */
|
|
if(load_fd < 0)
|
|
reset_tlsf();
|
|
|
|
me = midend_new(NULL, gm, &rb_drawing, NULL);
|
|
|
|
if(load_fd < 0)
|
|
midend_new_game(me);
|
|
else
|
|
{
|
|
char *ret = midend_deserialize(me, read_wrapper, (void*) load_fd);
|
|
if(ret)
|
|
return ret;
|
|
}
|
|
|
|
fix_size();
|
|
|
|
init_colors();
|
|
|
|
reset_drawing();
|
|
|
|
if(draw)
|
|
{
|
|
clear_and_draw();
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static void exit_handler(void)
|
|
{
|
|
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
|
|
rb->cpu_boost(false);
|
|
#endif
|
|
}
|
|
|
|
/* expects a totally free me* pointer */
|
|
static bool load_game(void)
|
|
{
|
|
reset_tlsf();
|
|
|
|
int fd = rb->open(SAVE_FILE, O_RDONLY);
|
|
if(fd < 0)
|
|
return false;
|
|
|
|
rb->splash(0, "Loading...");
|
|
|
|
LOGF("opening %s", SAVE_FILE);
|
|
|
|
char *game;
|
|
char *ret = identify_game(&game, read_wrapper, (void*)fd);
|
|
|
|
if(!*game && ret)
|
|
{
|
|
sfree(game);
|
|
rb->splash(HZ, ret);
|
|
rb->close(fd);
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
/* seek to beginning */
|
|
rb->lseek(fd, 0, SEEK_SET);
|
|
|
|
#ifdef COMBINED
|
|
/* search for the game and initialize the midend */
|
|
for(int i = 0; i < gamecount; ++i)
|
|
{
|
|
if(!strcmp(game, gamelist[i]->name))
|
|
{
|
|
sfree(ret);
|
|
ret = init_for_game(gamelist[i], fd, true);
|
|
if(ret)
|
|
{
|
|
rb->splash(HZ, ret);
|
|
sfree(ret);
|
|
rb->close(fd);
|
|
return false;
|
|
}
|
|
rb->close(fd);
|
|
/* success, we delete the save */
|
|
rb->remove(SAVE_FILE);
|
|
return true;
|
|
}
|
|
}
|
|
rb->splashf(HZ, "Incompatible game %s reported as compatible!?!? REEEPORT MEEEE!!!!", game);
|
|
rb->close(fd);
|
|
return false;
|
|
#else
|
|
if(!strcmp(game, thegame.name))
|
|
{
|
|
sfree(ret);
|
|
ret = init_for_game(&thegame, fd, false);
|
|
if(ret)
|
|
{
|
|
rb->splash(HZ, ret);
|
|
sfree(ret);
|
|
rb->close(fd);
|
|
return false;
|
|
}
|
|
rb->close(fd);
|
|
/* success, we delete the save */
|
|
rb->remove(SAVE_FILE);
|
|
return true;
|
|
}
|
|
rb->splashf(HZ, "Cannot load save game for %s!", game);
|
|
return false;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
static void save_game(void)
|
|
{
|
|
rb->splash(0, "Saving...");
|
|
int fd = rb->open(SAVE_FILE, O_WRONLY | O_CREAT | O_TRUNC, 0666);
|
|
midend_serialize(me, write_wrapper, (void*) fd);
|
|
rb->close(fd);
|
|
rb->lcd_update();
|
|
}
|
|
|
|
static bool load_success;
|
|
|
|
static int mainmenu_cb(int action, const struct menu_item_ex *this_item)
|
|
{
|
|
int i = (intptr_t) this_item;
|
|
if(action == ACTION_REQUEST_MENUITEM)
|
|
{
|
|
switch(i)
|
|
{
|
|
case 0:
|
|
case 7:
|
|
if(!load_success)
|
|
return ACTION_EXIT_MENUITEM;
|
|
break;
|
|
case 3:
|
|
#ifdef FOR_REAL
|
|
return ACTION_EXIT_MENUITEM;
|
|
#else
|
|
break;
|
|
#endif
|
|
case 4:
|
|
#ifdef COMBINED
|
|
/* audio buf is used, so no playback */
|
|
/* TODO: neglects app builds, but not many people will
|
|
* care, I bet */
|
|
return ACTION_EXIT_MENUITEM;
|
|
#else
|
|
if(audiobuf_available)
|
|
break;
|
|
else
|
|
return ACTION_EXIT_MENUITEM;
|
|
#endif
|
|
case 5:
|
|
if(!midend_get_presets(me, NULL)->n_entries)
|
|
return ACTION_EXIT_MENUITEM;
|
|
break;
|
|
case 6:
|
|
if(!midend_which_game(me)->can_configure)
|
|
return ACTION_EXIT_MENUITEM;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
return action;
|
|
}
|
|
|
|
enum plugin_status plugin_start(const void *param)
|
|
{
|
|
(void) param;
|
|
|
|
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
|
|
/* boost for init */
|
|
rb->cpu_boost(true);
|
|
#endif
|
|
|
|
#ifdef COMBINED
|
|
giant_buffer_len = sizeof(giant_buffer);
|
|
#else
|
|
giant_buffer = rb->plugin_get_buffer(&giant_buffer_len);
|
|
#endif
|
|
|
|
#ifndef COMBINED
|
|
rb->snprintf(save_file_path, sizeof(save_file_path), "%s/sgt-%s.sav", PLUGIN_GAMES_DATA_DIR, thegame.htmlhelp_topic);
|
|
#endif
|
|
|
|
rb_atexit(exit_handler);
|
|
|
|
if(fabs(sqrt(3)/2 - sin(PI/3)) > .01)
|
|
rb->splash(HZ, "WARNING: floating-point functions are being weird... report me!");
|
|
|
|
init_default_settings();
|
|
|
|
load_success = load_game();
|
|
|
|
#ifndef COMBINED
|
|
if(!load_success)
|
|
{
|
|
/* our main menu expects a ready-to-use midend */
|
|
init_for_game(&thegame, -1, false);
|
|
}
|
|
#endif
|
|
|
|
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
|
|
/* about to go to menu or button block */
|
|
rb->cpu_boost(false);
|
|
#endif
|
|
|
|
#ifdef FOR_REAL
|
|
help_times = 0;
|
|
#endif
|
|
|
|
#ifndef COMBINED
|
|
#define static auto
|
|
#define const
|
|
MENUITEM_STRINGLIST(menu, NULL, mainmenu_cb,
|
|
"Resume Game",
|
|
"New Game",
|
|
"Quick Help",
|
|
"Extensive Help",
|
|
"Playback Control",
|
|
"Game Type",
|
|
"Configure Game",
|
|
"Quit without Saving",
|
|
"Quit");
|
|
#undef static
|
|
#undef const
|
|
|
|
/* HACK ALERT */
|
|
char title[32] = { 0 };
|
|
rb->snprintf(title, sizeof(title), "%s Menu", midend_which_game(me)->name);
|
|
menu__.desc = title;
|
|
|
|
bool quit = false;
|
|
int sel = 0;
|
|
while(!quit)
|
|
{
|
|
switch(rb->do_menu(&menu, &sel, NULL, false))
|
|
{
|
|
case 0:
|
|
clear_and_draw();
|
|
goto game_loop;
|
|
case 1:
|
|
if(!load_success)
|
|
{
|
|
clear_and_draw();
|
|
goto game_loop;
|
|
}
|
|
quit = true;
|
|
break;
|
|
case 2:
|
|
quick_help();
|
|
break;
|
|
case 3:
|
|
full_help();
|
|
break;
|
|
case 4:
|
|
playback_control(NULL);
|
|
break;
|
|
case 5:
|
|
if(presets_menu())
|
|
{
|
|
midend_new_game(me);
|
|
fix_size();
|
|
init_colors();
|
|
reset_drawing();
|
|
clear_and_draw();
|
|
goto game_loop;
|
|
}
|
|
break;
|
|
case 6:
|
|
if(config_menu())
|
|
{
|
|
midend_new_game(me);
|
|
fix_size();
|
|
init_colors();
|
|
reset_drawing();
|
|
clear_and_draw();
|
|
goto game_loop;
|
|
}
|
|
break;
|
|
case 8:
|
|
if(load_success)
|
|
save_game();
|
|
/* fall through */
|
|
case 7:
|
|
/* we don't care about freeing anything because tlsf will
|
|
* be wiped out the next time around */
|
|
return PLUGIN_OK;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
#else
|
|
if(load_success)
|
|
goto game_loop;
|
|
#endif
|
|
|
|
#ifdef COMBINED
|
|
int gm = 0;
|
|
#endif
|
|
while(1)
|
|
{
|
|
#ifdef COMBINED
|
|
if(rb->set_int("Choose Game", "", UNIT_INT, &gm, NULL, 1, 0, gamecount - 1, formatter))
|
|
return PLUGIN_OK;
|
|
|
|
init_for_game(gamelist[gm], -1, true);
|
|
#else
|
|
init_for_game(&thegame, -1, true);
|
|
#endif
|
|
|
|
last_keystate = 0;
|
|
accept_input = true;
|
|
|
|
game_loop:
|
|
while(1)
|
|
{
|
|
want_redraw = true;
|
|
|
|
draw_title();
|
|
|
|
int button = process_input(timer_on ? TIMER_INTERVAL : -1);
|
|
|
|
if(button < 0)
|
|
{
|
|
rb_unclip(NULL);
|
|
deactivate_timer(NULL);
|
|
|
|
if(titlebar)
|
|
{
|
|
sfree(titlebar);
|
|
titlebar = NULL;
|
|
}
|
|
|
|
if(button == -1)
|
|
{
|
|
/* new game */
|
|
midend_free(me);
|
|
break;
|
|
}
|
|
else if(button == -2)
|
|
{
|
|
/* quit without saving */
|
|
midend_free(me);
|
|
sfree(colors);
|
|
exit(PLUGIN_OK);
|
|
}
|
|
else if(button == -3)
|
|
{
|
|
/* save and quit */
|
|
save_game();
|
|
midend_free(me);
|
|
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);
|
|
}
|
|
}
|