rockbox/apps/plugins/xobox.c
Christian Soffke faa2cb9942 plugins: Improve usability of iPod keymaps
- Reduce need to press multiple buttons at
the same time to quit a plugin

- Have "Menu" be default way to quit plugins or
to access plugin menu

- Fall back to (Long) "Select" or Long "Menu"
in cases where Menu button isn't available
(e.g. in ImageViewer and many games)

out of scope:
boomshine, lua_scripts, Rockpaint,
Doom, Duke3D, Pacbox, Quake,
Sgt-Puzzles, Wolf3D, XWorld,
Minesweeper, Pixel Painter, Spacerocks

Change-Id: I6d4dc7174695fe4b8ee9cbaccb21bdbfe6af5c48
2022-12-29 04:39:22 +01:00

1321 lines
36 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2006 Eli Sherer
* 2007 Antoine Cellerier
*
* 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 "lib/helper.h"
#include "lib/playback_control.h"
#include "lib/highscore.h"
#if (CONFIG_KEYPAD == IRIVER_H100_PAD) || (CONFIG_KEYPAD == IRIVER_H300_PAD)
#define QUIT BUTTON_OFF
#define LEFT BUTTON_LEFT
#define RIGHT BUTTON_RIGHT
#define PAUSE BUTTON_MODE
#define UP BUTTON_UP
#define DOWN BUTTON_DOWN
#define RC_QUIT BUTTON_RC_STOP
#elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \
(CONFIG_KEYPAD == IPOD_3G_PAD) || \
(CONFIG_KEYPAD == IPOD_1G2G_PAD)
#define QUIT (BUTTON_SELECT | BUTTON_REPEAT)
#define LEFT BUTTON_LEFT
#define RIGHT BUTTON_RIGHT
#define PAUSE (BUTTON_SELECT | BUTTON_REL)
#define MENU_UP BUTTON_SCROLL_FWD
#define MENU_DOWN BUTTON_SCROLL_BACK
#define UP BUTTON_MENU
#define DOWN BUTTON_PLAY
#elif CONFIG_KEYPAD == IAUDIO_X5M5_PAD
#define QUIT BUTTON_POWER
#define LEFT BUTTON_LEFT
#define RIGHT BUTTON_RIGHT
#define UP BUTTON_UP
#define DOWN BUTTON_DOWN
#define PAUSE BUTTON_PLAY
#elif (CONFIG_KEYPAD == GIGABEAT_PAD)
#define QUIT BUTTON_POWER
#define LEFT BUTTON_LEFT
#define RIGHT BUTTON_RIGHT
#define UP BUTTON_UP
#define DOWN BUTTON_DOWN
#define PAUSE BUTTON_A
#elif (CONFIG_KEYPAD == SANSA_E200_PAD) || \
(CONFIG_KEYPAD == SANSA_C200_PAD)
#define QUIT BUTTON_POWER
#define LEFT BUTTON_LEFT
#define RIGHT BUTTON_RIGHT
#define UP BUTTON_UP
#define DOWN BUTTON_DOWN
#define PAUSE BUTTON_REC
#elif (CONFIG_KEYPAD == SANSA_CLIP_PAD)
#define QUIT BUTTON_POWER
#define LEFT BUTTON_LEFT
#define RIGHT BUTTON_RIGHT
#define UP BUTTON_UP
#define DOWN BUTTON_DOWN
#define PAUSE BUTTON_HOME
#elif (CONFIG_KEYPAD == SANSA_FUZE_PAD)
#define QUIT (BUTTON_HOME|BUTTON_REPEAT)
#define LEFT BUTTON_LEFT
#define RIGHT BUTTON_RIGHT
#define UP BUTTON_UP
#define DOWN BUTTON_DOWN
#define PAUSE BUTTON_SELECT
#elif (CONFIG_KEYPAD == SANSA_M200_PAD)
#define QUIT BUTTON_POWER
#define LEFT BUTTON_LEFT
#define RIGHT BUTTON_RIGHT
#define UP BUTTON_UP
#define DOWN BUTTON_DOWN
#define PAUSE BUTTON_SELECT
#elif CONFIG_KEYPAD == IRIVER_H10_PAD
#define QUIT BUTTON_POWER
#define LEFT BUTTON_LEFT
#define RIGHT BUTTON_RIGHT
#define UP BUTTON_SCROLL_UP
#define DOWN BUTTON_SCROLL_DOWN
#define PAUSE BUTTON_PLAY
#elif (CONFIG_KEYPAD == GIGABEAT_S_PAD)
#define QUIT BUTTON_BACK
#define LEFT BUTTON_LEFT
#define RIGHT BUTTON_RIGHT
#define UP BUTTON_UP
#define DOWN BUTTON_DOWN
#define PAUSE BUTTON_PLAY
#elif (CONFIG_KEYPAD == MROBE100_PAD)
#define QUIT BUTTON_POWER
#define LEFT BUTTON_LEFT
#define RIGHT BUTTON_RIGHT
#define UP BUTTON_UP
#define DOWN BUTTON_DOWN
#define PAUSE BUTTON_DISPLAY
#elif CONFIG_KEYPAD == IAUDIO_M3_PAD
#define QUIT BUTTON_RC_REC
#define LEFT BUTTON_RC_REW
#define RIGHT BUTTON_RC_FF
#define UP BUTTON_RC_VOL_UP
#define DOWN BUTTON_RC_VOL_DOWN
#define PAUSE BUTTON_RC_PLAY
#elif CONFIG_KEYPAD == COWON_D2_PAD
#define QUIT BUTTON_POWER
#elif CONFIG_KEYPAD == CREATIVEZVM_PAD
#define QUIT BUTTON_BACK
#define LEFT BUTTON_LEFT
#define RIGHT BUTTON_RIGHT
#define UP BUTTON_UP
#define DOWN BUTTON_DOWN
#define PAUSE BUTTON_PLAY
#elif CONFIG_KEYPAD == CREATIVE_ZENXFI3_PAD
#define QUIT BUTTON_POWER
#define LEFT BUTTON_BACK
#define RIGHT BUTTON_MENU
#define UP BUTTON_UP
#define DOWN BUTTON_DOWN
#define PAUSE BUTTON_PLAY
#elif CONFIG_KEYPAD == PHILIPS_HDD1630_PAD
#define QUIT BUTTON_POWER
#define LEFT BUTTON_LEFT
#define RIGHT BUTTON_RIGHT
#define UP BUTTON_UP
#define DOWN BUTTON_DOWN
#define PAUSE BUTTON_VIEW
#elif CONFIG_KEYPAD == PHILIPS_HDD6330_PAD
#define QUIT BUTTON_POWER
#define LEFT BUTTON_PREV
#define RIGHT BUTTON_NEXT
#define UP BUTTON_UP
#define DOWN BUTTON_DOWN
#define PAUSE BUTTON_MENU
#elif CONFIG_KEYPAD == PHILIPS_SA9200_PAD
#define QUIT BUTTON_POWER
#define LEFT BUTTON_PREV
#define RIGHT BUTTON_NEXT
#define UP BUTTON_UP
#define DOWN BUTTON_DOWN
#define PAUSE BUTTON_MENU
#elif CONFIG_KEYPAD == ONDAVX747_PAD || \
CONFIG_KEYPAD == ONDAVX777_PAD || \
CONFIG_KEYPAD == MROBE500_PAD
#define QUIT BUTTON_POWER
#elif CONFIG_KEYPAD == SAMSUNG_YH820_PAD || \
CONFIG_KEYPAD == SAMSUNG_YH92X_PAD
#define QUIT BUTTON_REW
#define LEFT BUTTON_LEFT
#define RIGHT BUTTON_RIGHT
#define UP BUTTON_UP
#define DOWN BUTTON_DOWN
#define PAUSE BUTTON_PLAY
#elif CONFIG_KEYPAD == PBELL_VIBE500_PAD
#define QUIT BUTTON_REC
#define LEFT BUTTON_PREV
#define RIGHT BUTTON_NEXT
#define UP BUTTON_UP
#define DOWN BUTTON_DOWN
#define PAUSE BUTTON_PLAY
#elif CONFIG_KEYPAD == MPIO_HD200_PAD
#define QUIT (BUTTON_REC|BUTTON_PLAY)
#define LEFT BUTTON_VOL_DOWN
#define RIGHT BUTTON_VOL_UP
#define UP BUTTON_REW
#define DOWN BUTTON_FF
#define PAUSE BUTTON_PLAY
#elif CONFIG_KEYPAD == MPIO_HD300_PAD
#define QUIT (BUTTON_MENU | BUTTON_REPEAT)
#define LEFT BUTTON_REW
#define RIGHT BUTTON_FF
#define UP BUTTON_UP
#define DOWN BUTTON_DOWN
#define PAUSE BUTTON_PLAY
#elif CONFIG_KEYPAD == SANSA_FUZEPLUS_PAD
#define QUIT BUTTON_POWER
#define LEFT BUTTON_LEFT
#define RIGHT BUTTON_RIGHT
#define UP BUTTON_UP
#define DOWN BUTTON_DOWN
#define PAUSE BUTTON_PLAYPAUSE
#elif CONFIG_KEYPAD == SANSA_CONNECT_PAD
#define QUIT BUTTON_POWER
#define LEFT BUTTON_LEFT
#define RIGHT BUTTON_RIGHT
#define UP BUTTON_UP
#define DOWN BUTTON_DOWN
#define PAUSE BUTTON_SELECT
#elif (CONFIG_KEYPAD == SAMSUNG_YPR0_PAD)
#define QUIT BUTTON_BACK
#define LEFT BUTTON_LEFT
#define RIGHT BUTTON_RIGHT
#define UP BUTTON_UP
#define DOWN BUTTON_DOWN
#define PAUSE BUTTON_SELECT
#elif (CONFIG_KEYPAD == HM60X_PAD) || \
(CONFIG_KEYPAD == HM801_PAD)
#define QUIT BUTTON_POWER
#define LEFT BUTTON_LEFT
#define RIGHT BUTTON_RIGHT
#define UP BUTTON_UP
#define DOWN BUTTON_DOWN
#define PAUSE BUTTON_SELECT
#elif CONFIG_KEYPAD == SONY_NWZ_PAD
#define QUIT BUTTON_BACK
#define LEFT BUTTON_LEFT
#define RIGHT BUTTON_RIGHT
#define UP BUTTON_UP
#define DOWN BUTTON_DOWN
#define PAUSE BUTTON_PLAY
#elif CONFIG_KEYPAD == CREATIVE_ZEN_PAD
#define QUIT BUTTON_BACK
#define LEFT BUTTON_LEFT
#define RIGHT BUTTON_RIGHT
#define UP BUTTON_UP
#define DOWN BUTTON_DOWN
#define PAUSE BUTTON_PLAYPAUSE
#elif CONFIG_KEYPAD == DX50_PAD
#define QUIT BUTTON_POWER
#define LEFT BUTTON_LEFT
#define RIGHT BUTTON_RIGHT
#define UP BUTTON_VOL_UP
#define DOWN BUTTON_VOL_DOWN
#define PAUSE BUTTON_PLAY
#elif CONFIG_KEYPAD == CREATIVE_ZENXFI2_PAD
#define QUIT BUTTON_POWER
#define PAUSE BUTTON_MENU
#elif CONFIG_KEYPAD == AGPTEK_ROCKER_PAD
#define QUIT BUTTON_POWER
#define LEFT BUTTON_LEFT
#define RIGHT BUTTON_RIGHT
#define UP BUTTON_UP
#define DOWN BUTTON_DOWN
#define PAUSE BUTTON_SELECT
#elif CONFIG_KEYPAD == XDUOO_X3_PAD || CONFIG_KEYPAD == XDUOO_X3II_PAD || CONFIG_KEYPAD == XDUOO_X20_PAD
#define QUIT BUTTON_POWER
#define LEFT BUTTON_PREV
#define RIGHT BUTTON_NEXT
#define UP BUTTON_HOME
#define DOWN BUTTON_OPTION
#define PAUSE BUTTON_PLAY
#elif CONFIG_KEYPAD == FIIO_M3K_LINUX_PAD
#define QUIT BUTTON_POWER
#define LEFT BUTTON_PREV
#define RIGHT BUTTON_NEXT
#define UP BUTTON_HOME
#define DOWN BUTTON_OPTION
#define PAUSE BUTTON_PLAY
#elif CONFIG_KEYPAD == IHIFI_770_PAD || CONFIG_KEYPAD == IHIFI_800_PAD
#define QUIT BUTTON_POWER
#define LEFT BUTTON_HOME
#define RIGHT BUTTON_VOL_DOWN
#define UP BUTTON_PREV
#define DOWN BUTTON_NEXT
#define PAUSE BUTTON_PLAY
#elif CONFIG_KEYPAD == EROSQ_PAD
#define QUIT BUTTON_POWER
#define LEFT BUTTON_SCROLL_BACK
#define RIGHT BUTTON_SCROLL_FWD
#define UP BUTTON_PREV
#define DOWN BUTTON_NEXT
#define PAUSE BUTTON_PLAY
#elif CONFIG_KEYPAD == FIIO_M3K_PAD
#define QUIT BUTTON_POWER
#define LEFT BUTTON_LEFT
#define RIGHT BUTTON_RIGHT
#define UP BUTTON_UP
#define DOWN BUTTON_DOWN
#define PAUSE BUTTON_PLAY
#elif CONFIG_KEYPAD == SHANLING_Q1_PAD
/* use touchscreen */
#else
#error "No keymap defined!"
#endif
#if defined(HAVE_TOUCHSCREEN)
#ifndef QUIT
#define QUIT BUTTON_TOPLEFT
#endif
#ifndef LEFT
#define LEFT BUTTON_MIDLEFT
#endif
#ifndef RIGHT
#define RIGHT BUTTON_MIDRIGHT
#endif
#ifndef UP
#define UP BUTTON_TOPMIDDLE
#endif
#ifndef DOWN
#define DOWN BUTTON_BOTTOMMIDDLE
#endif
#ifndef PAUSE
#define PAUSE BUTTON_CENTER
#endif
#endif
#define MOVE_NO 0 /* player movement */
#define MOVE_UP 1 /* 1 */
#define MOVE_DN 2 /* 3 0 4 */
#define MOVE_LT 3 /* 2 */
#define MOVE_RT 4
/* ball movement (12 ways) */
/* UUL UR */
/* UL UR */
/* ULL . URR */
/* DLL DRR */
/* DL DR */
/* DDL DDR */
#define DIR_UU (1<<7)
#define DIR_U (1<<6)
#define DIR_RR (1<<5)
#define DIR_R (1<<4)
#define DIR_DD (1<<3)
#define DIR_D (1<<2)
#define DIR_LL (1<<1)
#define DIR_L (1<<0)
#define MOVE_UUR ( DIR_UU | DIR_R )
#define MOVE_UR ( DIR_U | DIR_R )
#define MOVE_URR ( DIR_U | DIR_RR )
#define MOVE_DRR ( DIR_D | DIR_RR )
#define MOVE_DR ( DIR_D | DIR_R )
#define MOVE_DDR ( DIR_DD | DIR_R )
#define MOVE_DDL ( DIR_DD | DIR_L )
#define MOVE_DL ( DIR_D | DIR_L )
#define MOVE_DLL ( DIR_D | DIR_LL )
#define MOVE_ULL ( DIR_U | DIR_LL )
#define MOVE_UL ( DIR_U | DIR_L )
#define MOVE_UUL ( DIR_UU | DIR_L )
#if (LCD_WIDTH>112) && (LCD_HEIGHT>64)
# define CUBE_SIZE 8 /* 8x22=176 */
# define pos(a) ((a)>>3)
#else
# define CUBE_SIZE 4
# define pos(a) ((a)>>2)
#endif
#define STARTING_QIXES 2
#define MAX_LEVEL 10
#define MAX_QIXES MAX_LEVEL+STARTING_QIXES
#define BOARD_W ((int)(LCD_WIDTH/CUBE_SIZE))
#define BOARD_H ((int)(LCD_HEIGHT/CUBE_SIZE))
#define BOARD_X (LCD_WIDTH-BOARD_W*CUBE_SIZE)/2
#define BOARD_Y (LCD_HEIGHT-BOARD_H*CUBE_SIZE)/2
#ifdef HAVE_LCD_COLOR
#define CLR_RED LCD_RGBPACK(255,0,0) /* used to imply danger */
#define CLR_LTBLUE LCD_RGBPACK(125, 145, 180) /* used for frame and filling */
#define PLR_COL LCD_WHITE /* color used for the player */
#elif LCD_DEPTH>=2
#define CLR_RED LCD_DARKGRAY /* used to imply danger */
#define CLR_LTBLUE LCD_LIGHTGRAY /* used for frame and filling */
#define PLR_COL LCD_BLACK /* color used for the player */
#endif
#if LCD_DEPTH>=2
#define EMPTIED LCD_BLACK /* empty spot */
#define FILLED CLR_LTBLUE /* filled spot */
#define TRAIL CLR_RED /* the red trail of the player */
#define QIX LCD_WHITE
#else
#define EMPTIED 0
#define FILLED 1
#define TRAIL 2
#define QIX 3
#endif
#define UNCHECKED 0
#define CHECKED 1
#define PAINTED -1
#define PIC_QIX 0
#define PIC_PLAYER 1
/* The time (in ms) for one iteration through the game loop - decrease this
to speed up the game - note that current_tick is (currently) only accurate
to 10ms.
*/
static int speed = 6; /* CYCLETIME = (11-speed)*10 ms */
static int difficulty = 75; /* Percentage of screen that needs to be filled
* in order to win the game */
static bool quit = false;
static bool _ingame = false;
#define RESUME_FILE PLUGIN_GAMES_DATA_DIR "/xobox.resume"
#define SCORE_FILE PLUGIN_GAMES_DATA_DIR "/xobox.score"
#define NUM_SCORES 5
static struct highscore highscores[NUM_SCORES];
static unsigned int board[BOARD_H][BOARD_W];
static int testboard[BOARD_H][BOARD_W];
#if CUBE_SIZE == 8
/*
00011000 0x18 - 11100111 0xe7
00111100 0x3c - 11100111 0xe7
01111110 0x7e - 11000011 0xc3
11111111 0xff - 00000000 0x00
11111111 0xff - 00000000 0x00
01111110 0x7e - 11000011 0xc3
00111100 0x3c - 11100111 0xe7
00011000 0x18 - 11100111 0xe7
*/
const unsigned char pics[2][8] = {
{0x18, 0x3c, 0x7e, 0xff, 0xff, 0x7e, 0x3c, 0x18}, /* Alien (QIX) */
{0xe7, 0xe7, 0xc3, 0x00, 0x00, 0xc3, 0xe7, 0xe7} /* Player (XONIX) */
};
#elif CUBE_SIZE == 4
/*
0110 0x6 - 1001 0x9
1111 0xf - 0110 0x6
1111 0xf - 0110 0x6
0110 0x6 - 1001 0x9
*/
const unsigned char pics[2][4] = {
{0x6, 0xf, 0xf, 0x6}, /* Alien (QIX) */
{0x9, 0x6, 0x6, 0x9} /* Player (XONIX) */
};
#else
#error Incorrect CUBE_SIZE value.
#endif
static struct qix
{
int velocity; /* velocity */
int x, y; /* position on screen */
int angle; /* angle */
} qixes[MAX_QIXES]; /* black_qix */
static struct splayer
{
int i, j; /* position on board */
int move, score, level, lives;
bool drawing;
bool gameover;
} player;
static int percentage_cache;
/*************************** STACK STUFF **********************/
/* the stack */
#define STACK_SIZE (2*BOARD_W*BOARD_H)
static struct pos
{
int x, y; /* position on board */
} stack[STACK_SIZE];
static int stackPointer;
static inline bool pop (struct pos *p)
{
if (stackPointer > 0) {
p->x = stack[stackPointer].x;
p->y = stack[stackPointer].y;
stackPointer--;
return true;
} else
return false; /* SE */
}
static inline bool push (struct pos *p)
{
if (stackPointer < STACK_SIZE - 1) {
stackPointer++;
stack[stackPointer].x = p->x;
stack[stackPointer].y = p->y;
return true;
} else
return false; /* SOF */
}
static inline void emptyStack (void)
{
stackPointer = 0;
}
/*********************** END OF STACK STUFF *********************/
/* calculate the new x coordinate of the ball according to angle and speed */
static inline int get_newx (int x, int len, int deg)
{
if (deg & DIR_R)
return x + len;
else if (deg & DIR_L)
return x - len;
else if (deg & DIR_RR)
return x + len * 2;
else /* (def & DIR_LL) */
return x - len * 2;
}
/* calculate the new y coordinate of the ball according to angle and speed */
static inline int get_newy (int y, int len, int deg)
{
if (deg & DIR_D)
return y + len;
else if (deg & DIR_U)
return y - len;
else if (deg & DIR_DD)
return y + len * 2;
else /* (deg & DIR_UU) */
return y - len * 2;
}
/* make random function get it's value from the device ticker */
static inline void randomize (void)
{
rb->srand (*rb->current_tick);
}
/* get a random number between 0 and range-1 */
static int t_rand (int range)
{
return rb->rand () % range;
}
/* initializes the test help board */
static void init_testboard (void)
{
int j; /* testboard */
for (j = 0; j < BOARD_H; j++)
/* UNCHEKED == (int)0 */
rb->memset( testboard[j], 0, BOARD_W * sizeof( int ) );
}
/* initializes the game board on with the player,qix's and black qix */
static void init_board (void)
{
int i, j;
for (j = 0; j < BOARD_H; j++)
for (i = 0; i < BOARD_W; i++) { /* make a nice cyan frame */
if ((i == 0) || (j <= 1) || (i == BOARD_W - 1)
|| (j >= BOARD_H - 2))
board[j][i] = FILLED;
else
board[j][i] = EMPTIED;
}
/* (level+2) is the number of qixes */
for (j = 0; j < player.level + STARTING_QIXES; j++) {
qixes[j].velocity = t_rand (2) + 1; /* 1 or 2 pix-per-sec */
/* not on frame */
qixes[j].x = CUBE_SIZE*2 + 2*t_rand (((BOARD_W-4)*CUBE_SIZE)/2);
qixes[j].y = CUBE_SIZE*2 + 2*t_rand (((BOARD_H-4)*CUBE_SIZE)/2);
const int angle_table[] = {
MOVE_UUR, MOVE_UR, MOVE_URR, MOVE_DRR, MOVE_DR, MOVE_DDR,
MOVE_UUL, MOVE_UL, MOVE_ULL, MOVE_DLL, MOVE_DL, MOVE_DDL };
qixes[j].angle = angle_table[t_rand (12)];
#if CUBE_SIZE == 4
/* Work arround a nasty bug. FIXME */
if( qixes[j].angle & (DIR_LL|DIR_RR|DIR_UU|DIR_DD) )
qixes[j].velocity = 1;
#endif
}
/*black_qix.velocity=1;
black_qix.x=BOARD_X+(BOARD_W*CUBE_SIZE)/2-CUBE_SIZE/2;
black_qix.y=BOARD_Y+(BOARD_H*CUBE_SIZE)-CUBE_SIZE-CUBE_SIZE/2;
black_qix.angle=MOVE_UR; */
player.move = MOVE_NO;
player.drawing = false;
player.i = BOARD_W / 2;
player.j = 1;
percentage_cache = 0;
}
/* calculates the percentage of the screen filling */
static int percentage (void)
{
int i, j, filled = 0;
for (j = 2; j < BOARD_H - 2; j++)
for (i = 1; i < BOARD_W - 1; i++)
if (board[j][i] == FILLED)
filled++;
return (filled * 100) / ((BOARD_W - 2) * (BOARD_H - 4));
}
/* draw the board on with all the game figures */
static void refresh_board (void)
{
int i, j;
int x;
#if LCD_DEPTH>=2
rb->lcd_set_background (LCD_BLACK);
#endif
rb->lcd_clear_display ();
for (j = 0; j < BOARD_H; j++)
{
unsigned last_color = board[j][0];
int last_i = 0;
for (i = 1; i < BOARD_W; i++) {
if( last_color != board[j][i] )
{
#if LCD_DEPTH>=2
rb->lcd_set_foreground (last_color);
#else
if (last_color != EMPTIED)
#endif
rb->lcd_fillrect (BOARD_X + CUBE_SIZE * (last_i),
BOARD_Y + CUBE_SIZE * j,
CUBE_SIZE * (i - last_i), CUBE_SIZE );
last_color = board[j][i];
last_i = i;
}
}
#if LCD_DEPTH>=2
rb->lcd_set_foreground (last_color);
#else
if (last_color != EMPTIED)
#endif
rb->lcd_fillrect (BOARD_X + CUBE_SIZE * (last_i),
BOARD_Y + CUBE_SIZE * j,
CUBE_SIZE * (i - last_i), CUBE_SIZE);
}
#if LCD_DEPTH>=2
rb->lcd_set_foreground (LCD_BLACK);
rb->lcd_set_background (CLR_LTBLUE);
#else
rb->lcd_set_drawmode (DRMODE_COMPLEMENT);
#endif
rb->lcd_putsxyf (BOARD_X, BOARD_Y, "Level %d", player.level + 1);
rb->lcd_putsxyf (BOARD_X + CUBE_SIZE * BOARD_W - 24, BOARD_Y, "%d%%",
percentage_cache);
rb->lcd_putsxyf (BOARD_X, BOARD_Y + CUBE_SIZE * BOARD_H - 8, "Score: %d",
player.score);
#if LCD_DEPTH>=2
x = BOARD_X + CUBE_SIZE * BOARD_W - 60;
#else
x = BOARD_X + CUBE_SIZE * BOARD_W - 40;
#endif
rb->lcd_putsxyf (x, BOARD_Y + CUBE_SIZE * BOARD_H - 8,
(player.lives != 1) ? "%d Lives" : "%d Life", player.lives);
#if LCD_DEPTH>=2
rb->lcd_set_foreground (PLR_COL);
rb->lcd_set_background (board[player.j][player.i]);
#endif
rb->lcd_mono_bitmap (pics[PIC_PLAYER], player.i * CUBE_SIZE + BOARD_X,
player.j * CUBE_SIZE + BOARD_Y, CUBE_SIZE, CUBE_SIZE);
#if LCD_DEPTH>=2
rb->lcd_set_background (EMPTIED);
rb->lcd_set_foreground (LCD_WHITE);
rb->lcd_set_drawmode (DRMODE_FG);
#else
rb->lcd_set_drawmode (DRMODE_FG);
#endif
for (j = 0; j < player.level + STARTING_QIXES; j++)
rb->lcd_mono_bitmap (pics[PIC_QIX], qixes[j].x + BOARD_X,
qixes[j].y + BOARD_Y, CUBE_SIZE, CUBE_SIZE);
#if LCD_DEPTH>=2
rb->lcd_set_foreground (LCD_BLACK);
#endif
rb->lcd_set_drawmode (DRMODE_SOLID);
rb->lcd_update ();
}
static inline int infested_area (int i, int j, int v)
{
struct pos p;
p.x = i;
p.y = j;
emptyStack ();
if (!push (&p))
return -1;
while (pop (&p)) {
if (testboard[p.y][p.x] == v) continue;
if (testboard[p.y][p.x] > UNCHECKED)
return 1; /* This area was previously flagged as infested */
testboard[p.y][p.x] = v;
if (board[p.y][p.x] == QIX)
return 1; /* Infested area */
{
struct pos p1 = { p.x+1, p.y };
if ((p1.x < BOARD_W)
&& (board[p1.y][p1.x] != FILLED)
&& (!push (&p1)))
return -1;
}
{
struct pos p1 = { p.x-1, p.y };
if ((p1.x >= 0)
&& (board[p1.y][p1.x] != FILLED)
&& (!push (&p1)))
return -1;
}
{
struct pos p1 = { p.x, p.y+1 };
if ((p1.y < BOARD_H)
&& (board[p1.y][p1.x] != FILLED)
&& (!push (&p1)))
return -1;
}
{
struct pos p1 = { p.x, p.y-1 };
if ((p1.y >= 0)
&& (board[p1.y][p1.x] != FILLED)
&& (!push (&p1)))
return -1;
}
}
return 0;
}
static inline int fill_area (int i, int j)
{
struct pos p;
p.x = i;
p.y = j;
int v = testboard[p.y][p.x];
emptyStack ();
if (!push (&p))
return -1;
while (pop (&p)) {
board[p.y][p.x] = FILLED;
testboard[p.y][p.x] = PAINTED;
{
struct pos p1 = { p.x+1, p.y };
if ((p1.x < BOARD_W)
&& (testboard[p1.y][p1.x] == v)
&& (!push (&p1)))
return -1;
}
{
struct pos p1 = { p.x-1, p.y };
if ((p1.x >= 0)
&& (testboard[p1.y][p1.x] == v)
&& (!push (&p1)))
return -1;
}
{
struct pos p1 = { p.x, p.y+1 };
if ((p1.y < BOARD_H)
&& (testboard[p1.y][p1.x] == v)
&& (!push (&p1)))
return -1;
}
{
struct pos p1 = { p.x, p.y-1 };
if ((p1.y >= 0)
&& (testboard[p1.y][p1.x] == v)
&& (!push (&p1)))
return -1;
}
}
return 0;
}
/* take care of stuff after xonix has landed on a filled spot */
static void complete_trail (int fill)
{
int i, j, ret;
for (j = 0; j < BOARD_H; j++) {
for (i = 0; i < BOARD_W; i++) {
if (board[j][i] == TRAIL) {
if (fill)
board[j][i] = FILLED;
else
board[j][i] = EMPTIED;
}
}
}
if (fill) {
int v = CHECKED;
for (i = 0; i < player.level + STARTING_QIXES; i++) /* add qixes to board */
board[pos(qixes[i].y - BOARD_Y)]
[pos(qixes[i].x - BOARD_X)] = QIX;
init_testboard();
for (j = 1; j < BOARD_H - 1; j++) {
for (i = 0; i < BOARD_W - 0; i++) {
if (board[j][i] != FILLED) {
ret = infested_area (i, j, v);
if (ret < 0 || ( ret == 0 && fill_area (i, j) ) )
quit = true;
v++;
}
}
}
for (i = 0; i < player.level + STARTING_QIXES; i++) /* add qixes to board */
board[pos(qixes[i].y - BOARD_Y)]
[pos(qixes[i].x - BOARD_X)] = EMPTIED;
percentage_cache = percentage();
}
rb->button_clear_queue();
}
/* returns the color the real pixel(x,y) on the lcd is pointing at */
static inline unsigned int getpixel (int x, int y)
{
const int a = pos (x - BOARD_X), b = pos (y - BOARD_Y);
if ((a > 0) && (a < BOARD_W) && (b > 0) && (b < BOARD_H)) /* if inside board */
return board[b][a];
else
return FILLED;
}
/* returns the color the ball on (newx,newy) is heading at *----*
checks the four edge points of the square if 1st of all | |
are a trail (cause it's a lose life situation) and 2nd | |
if it's filled so it needs to bounce. *____*
*/
static inline unsigned int next_hit (int newx, int newy)
{
if ((getpixel (newx, newy) == TRAIL)
|| (getpixel (newx, newy + CUBE_SIZE - 1) == TRAIL)
|| (getpixel (newx + CUBE_SIZE - 1, newy) == TRAIL)
|| (getpixel (newx + CUBE_SIZE - 1, newy + CUBE_SIZE - 1) == TRAIL))
return TRAIL;
else if ((getpixel (newx, newy) == FILLED)
|| (getpixel (newx, newy + CUBE_SIZE - 1) == FILLED)
|| (getpixel (newx + CUBE_SIZE - 1, newy) == FILLED)
|| (getpixel (newx + CUBE_SIZE - 1, newy + CUBE_SIZE - 1) ==
FILLED))
return FILLED;
else
return EMPTIED;
}
static void die (void)
{
player.lives--;
if (player.lives == 0)
player.gameover = true;
else {
refresh_board ();
rb->splash (HZ, "Crash!");
complete_trail (false);
player.move = MOVE_NO;
player.drawing = false;
player.i = BOARD_W / 2;
player.j = 1;
}
}
/* returns true if the (side) of the block -***-
starting from (newx,newy) has any filled pixels * *
-***-
*/
static inline bool line_check_lt (int newx, int newy)
{
return getpixel (newx, newy + CUBE_SIZE/2-1) == FILLED
&& getpixel (newx, newy + CUBE_SIZE/2 ) == FILLED;
}
static inline bool line_check_rt (int newx, int newy)
{
return getpixel (newx + CUBE_SIZE-1, newy + CUBE_SIZE/2-1) == FILLED
&& getpixel (newx + CUBE_SIZE-1, newy + CUBE_SIZE/2 ) == FILLED;
}
static inline bool line_check_up (int newx, int newy)
{
return getpixel (newx + CUBE_SIZE/2-1, newy) == FILLED
&& getpixel (newx + CUBE_SIZE/2 , newy) == FILLED;
}
static inline bool line_check_dn (int newx, int newy)
{
return getpixel (newx + CUBE_SIZE/2-1, newy + CUBE_SIZE-1) == FILLED
&& getpixel (newx + CUBE_SIZE/2 , newy + CUBE_SIZE-1) == FILLED;
}
static inline void move_qix (struct qix *q)
{
int newx, newy;
newx = get_newx (q->x, q->velocity, q->angle);
newy = get_newy (q->y, q->velocity, q->angle);
switch (next_hit (newx, newy))
{
case EMPTIED:
q->x = newx;
q->y = newy;
break;
case FILLED:
{
const int a = q->angle;
q->angle =
((a&(DIR_UU|DIR_U))
? (line_check_up (newx, newy) ? ((a&(DIR_UU|DIR_U))>>4)
: (a&(DIR_UU|DIR_U)))
: 0)
|
((a&(DIR_RR|DIR_R))
? (line_check_rt (newx, newy) ? ((a&(DIR_RR|DIR_R))>>4)
: (a&(DIR_RR|DIR_R)))
: 0)
|
((a&(DIR_DD|DIR_D))
? (line_check_dn (newx, newy) ? ((a&(DIR_DD|DIR_D))<<4)
: (a&(DIR_DD|DIR_D)))
: 0)
|
((a&(DIR_LL|DIR_L))
? (line_check_lt (newx, newy) ? ((a&(DIR_LL|DIR_L))<<4)
: (a&(DIR_LL|DIR_L)))
: 0);
q->x = get_newx (q->x, q->velocity, q->angle);
q->y = get_newy (q->y, q->velocity, q->angle);
break;
}
case TRAIL:
die();
break;
}
}
/* move the board forward timewise */
static inline void move_board (void)
{
int j, newi, newj;
for (j = 0; j < player.level + STARTING_QIXES; j++)
move_qix (&qixes[j]);
/* move_qix(&black_qix,true); */
if (player.move) {
newi = player.i;
newj = player.j;
switch (player.move) {
case MOVE_UP:
if (player.j > 1)
newj--;
break;
case MOVE_DN:
if (player.j < BOARD_H - 2)
newj++;
break;
case MOVE_LT:
if (player.i > 0)
newi--;
break;
case MOVE_RT:
if (player.i < BOARD_W - 1)
newi++;
break;
default:
break;
}
if ((player.drawing) && (board[newj][newi] == EMPTIED)) /* continue drawing */
board[newj][newi] = TRAIL;
else if ((player.drawing) && (board[newj][newi] == FILLED)) { /* finish drawing */
player.move = MOVE_NO; /* stop moving */
player.drawing = false;
complete_trail (true);
} else if ((board[player.j][player.i] == FILLED)
&& (board[newj][newi] == EMPTIED)) {
/* start drawing */
player.drawing = true;
board[newj][newi] = TRAIL;
/* if the block after next is empty and we're moving onto filled, stop */
} else if ((board[newj][newi] == FILLED)
&& (board[newj + newj-player.j][newi + newi-player.i] == EMPTIED)) {
player.move = MOVE_NO;
}
player.i = newi;
player.j = newj;
}
if (percentage_cache >= difficulty) { /* finished level */
refresh_board ();
rb->splashf (HZ * 2, "Level %d finished", player.level+1);
player.score += percentage_cache;
if (player.level < MAX_LEVEL)
player.level++;
init_board ();
refresh_board ();
rb->button_clear_queue();
rb->splash (HZ * 2, "Ready?");
}
}
/* init game's variables */
static void init_game (void)
{
player.level = 0;
player.score = 0;
player.lives = 3;
player.gameover = false;
player.drawing = false;
init_board ();
refresh_board ();
rb->splash (HZ * 2, "Ready?");
}
static bool load_game(void)
{
int fd = rb->open(RESUME_FILE, O_RDONLY);
if (fd < 0) {
return true;
}
bool load_success =
rb->read(fd, &player, sizeof(player)) == sizeof(player) &&
rb->read(fd, &qixes, sizeof(qixes)) == sizeof(qixes) &&
rb->read(fd, &stack, sizeof(stack)) == sizeof(stack) &&
rb->read(fd, &board, sizeof(board)) == sizeof(board) &&
rb->read(fd, &testboard, sizeof(testboard)) == sizeof(testboard) &&
rb->read(fd, &speed, sizeof(speed)) == sizeof(speed) &&
rb->read(fd, &difficulty, sizeof(difficulty)) == sizeof(difficulty) &&
rb->read(fd, &stackPointer, sizeof(stackPointer)) == sizeof(stackPointer) &&
rb->read(fd, &percentage_cache,
sizeof(percentage_cache)) == sizeof(percentage_cache);
rb->close(fd);
_ingame = load_success;
return load_success;
}
static bool save_game(void)
{
int fd = rb->open(RESUME_FILE, O_WRONLY|O_CREAT, 0666);
if (fd < 0) {
return false;
}
bool save_success =
rb->write(fd, &player, sizeof(player)) > 0 &&
rb->write(fd, &qixes, sizeof(qixes)) > 0 &&
rb->write(fd, &stack, sizeof(stack)) > 0 &&
rb->write(fd, &board, sizeof(board)) > 0 &&
rb->write(fd, &testboard, sizeof(testboard)) > 0 &&
rb->write(fd, &speed, sizeof(speed)) > 0 &&
rb->write(fd, &difficulty, sizeof(difficulty)) > 0 &&
rb->write(fd, &stackPointer, sizeof(stackPointer)) > 0 &&
rb->write(fd, &percentage_cache, sizeof(percentage_cache)) > 0;
rb->close(fd);
if (!save_success) {
rb->remove(RESUME_FILE);
}
return save_success;
}
/* the main menu */
static int xobox_menu_cb(int action,
const struct menu_item_ex *this_item,
struct gui_synclist *this_list)
{
(void)this_list;
intptr_t item = (intptr_t)this_item;
if(action == ACTION_REQUEST_MENUITEM
&& !_ingame && (item == 0 || item == 6))
return ACTION_EXIT_MENUITEM;
return action;
}
static int xobox_menu(bool ingame)
{
rb->button_clear_queue();
int selection = 0;
MENUITEM_STRINGLIST(main_menu, "Xobox Menu", xobox_menu_cb,
"Resume Game", "Start New Game",
"Speed", "Difficulty",
"High Scores", "Playback Control",
"Quit Without Saving", "Quit");
_ingame = ingame;
while (true) {
switch (rb->do_menu(&main_menu, &selection, NULL, false)) {
case 0:
rb->remove(RESUME_FILE);
refresh_board();
rb->splash (HZ*2, "Ready?");
return 0;
case 1:
init_game ();
return 0;
case 2:
rb->set_int ("Speed", "", UNIT_INT, &speed, NULL, 1, 1, 10, NULL);
break;
case 3:
rb->set_int ("Difficulty", "", UNIT_INT, &difficulty, NULL,
5, 50, 95, NULL);
break;
case 4:
highscore_show(-1, highscores, NUM_SCORES, true);
break;
case 5:
playback_control(NULL);
break;
case 6:
return 1;
case 7:
if (_ingame) {
rb->splash(HZ, "Saving game...");
if (!save_game()) {
rb->splash(HZ, "Failed to save game");
}
}
return 1;
case MENU_ATTACHED_USB:
return 1;
default:
break;
}
}
}
/* general keypad handler loop */
static int xobox_loop (void)
{
int button = 0;
bool pause = false;
int end;
if (xobox_menu(_ingame)) {
return PLUGIN_OK;
}
while (!quit) {
end = *rb->current_tick + ((11-speed)*HZ)/100;
#ifdef HAS_BUTTON_HOLD
if (rb->button_hold()) {
pause = true;
rb->splash (HZ, "Paused");
}
#endif
button = rb->button_get_w_tmo (1);
switch (button) {
case UP:
case UP|BUTTON_REPEAT:
player.move = MOVE_UP;
break;
case DOWN:
case DOWN|BUTTON_REPEAT:
player.move = MOVE_DN;
break;
case LEFT:
case LEFT|BUTTON_REPEAT:
player.move = MOVE_LT;
break;
case RIGHT:
case RIGHT|BUTTON_REPEAT:
player.move = MOVE_RT;
break;
case PAUSE:
pause = !pause;
if (pause)
rb->splash (HZ, "Paused");
break;
case QUIT:
if (!pause) {
if (xobox_menu(true)) {
quit = true;
}
}
break;
default:
if (rb->default_event_handler (button) == SYS_USB_CONNECTED)
return PLUGIN_USB_CONNECTED;
break;
}
if (!pause) {
move_board ();
refresh_board ();
}
if (player.gameover) {
rb->splash (HZ, "Game Over!");
int pos = highscore_update(player.score, player.level, "",
highscores, NUM_SCORES);
if (pos != -1) {
if (pos == 0) {
rb->splashf(HZ, "New High Score: %d", player.score);
}
highscore_show(pos, highscores, NUM_SCORES, true);
}
if (xobox_menu(false)) {
quit = true;
}
}
if (TIME_BEFORE(*rb->current_tick, end))
rb->sleep (end - *rb->current_tick);
else
rb->yield ();
} /* end while */
return PLUGIN_OK; /* for no warnings on compiling */
}
/* plugin main procedure */
enum plugin_status plugin_start (const void *parameter)
{
int ret = PLUGIN_OK;
(void) parameter;
rb->lcd_setfont (FONT_SYSFIXED);
#if LCD_DEPTH>=2
rb->lcd_set_backdrop(NULL);
#endif
/* Turn off backlight timeout */
backlight_ignore_timeout();
highscore_load(SCORE_FILE, highscores, NUM_SCORES);
if (!load_game()) {
rb->splash(HZ, "Failed to load saved game");
}
randomize ();
ret = xobox_loop ();
/* Turn on backlight timeout (revert to settings) */
backlight_use_settings();
rb->lcd_setfont (FONT_UI);
highscore_save(SCORE_FILE, highscores, NUM_SCORES);
return ret;
}