/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright (C) 2006 Eli Sherer
 *
 * All files in this archive are subject to the GNU General Public License.
 * See the file COPYING in the source tree root for full license agreement.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/

#include "plugin.h"

PLUGIN_HEADER

#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 SELECT BUTTON_SELECT

#define RC_QUIT BUTTON_RC_STOP

#elif (CONFIG_KEYPAD == IPOD_3G_PAD) || \
      (CONFIG_KEYPAD == IPOD_4G_PAD)

#define QUIT (BUTTON_SELECT | BUTTON_MENU)
#define LEFT BUTTON_LEFT
#define RIGHT BUTTON_RIGHT
#define PAUSE BUTTON_SELECT
#define SELECT BUTTON_SELECT
#define MENU_UP BUTTON_SCROLL_FWD
#define MENU_DOWN BUTTON_SCROLL_BACK
#define UP BUTTON_MENU
#define DOWN BUTTON_PLAY

#elif CONFIG_KEYPAD == IAUDIO_X5_PAD

#define QUIT BUTTON_POWER
#define LEFT BUTTON_LEFT
#define RIGHT BUTTON_RIGHT
#define SELECT BUTTON_SELECT
#define UP BUTTON_UP
#define DOWN BUTTON_DOWN
#define PAUSE BUTTON_PLAY

#elif (CONFIG_KEYPAD == GIGABEAT_PAD)

#define QUIT BUTTON_A
#define LEFT BUTTON_LEFT
#define RIGHT BUTTON_RIGHT
#define SELECT BUTTON_SELECT
#define UP BUTTON_UP
#define DOWN BUTTON_DOWN
#define PAUSE BUTTON_MENU

#elif CONFIG_KEYPAD == IRIVER_H10_PAD

#define QUIT BUTTON_POWER
#define LEFT BUTTON_LEFT
#define RIGHT BUTTON_RIGHT
#define SELECT BUTTON_REW
#define UP BUTTON_SCROLL_UP
#define DOWN BUTTON_SCROLL_DOWN
#define PAUSE BUTTON_PLAY

#else
#error Unsupported keypad
#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

#define MOVE_UUR 0              /* ball movement (12 ways) */
#define MOVE_UR  1
#define MOVE_URR 2
#define MOVE_DRR 3              /*    UUL110UUR    */
#define MOVE_DR  4              /*   UL10    1UR   */
#define MOVE_DDR 5              /* ULL9   .   2URR */
#define MOVE_DDL 6              /* DLL8       3DRR */
#define MOVE_DL  7              /*   DL7     4DR   */
#define MOVE_DLL 8              /*    DDL6 5DDR    */
#define MOVE_ULL 9
#define MOVE_UL  10
#define MOVE_UUL 11

#define CUBE_SIZE 8             /* 8x22=176 */
#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_BLUE LCD_RGBPACK(0,0,128)         /* used for menu selection */
#define CLR_LTBLUE LCD_RGBPACK(125, 145, 180) /* used for frame and filling */
#define PLR_COL  LCD_WHITE                    /* color used for the player */
#else
#define CLR_RED  LCD_DARKGRAY     /* used to imply danger */
#define CLR_BLUE LCD_BLACK        /* used for menu selection */
#define CLR_LTBLUE LCD_LIGHTGRAY  /* used for frame and filling */
#define PLR_COL  LCD_BLACK        /* color used for the player */
#endif

#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
#define UNCHECKED 0
#define CHECKED 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.
*/
#define CYCLETIME 50

static struct plugin_api *rb;
static bool quit = false;

static unsigned short board[BOARD_H][BOARD_W],
    testboard[BOARD_H][BOARD_W], boardcopy[BOARD_H][BOARD_W];

/*
   00001000 0x08 - 01110111 0x77
   00011100 0x1c - 01110111 0x77
   00111110 0x3e - 01100011 0x63
   01111111 0x7f - 00000000 0x00
   00111110 0x3e - 01100011 0x63
   00011100 0x1c - 01110111 0x77
   00001000 0x08 - 01110111 0x77
   00000000 0x00 - 00000000 0x00
 */
const unsigned char pics[2][8] = {
    {0x08, 0x1c, 0x3e, 0x7f, 0x3e, 0x1c, 0x08, 0x00},   /* Alien (QIX) */
    {0x77, 0x77, 0x63, 0x00, 0x63, 0x77, 0x77, 0x00}    /* Player (XONIX) */
};

static struct qix
{
    short velocity;             /* velocity */
    short x, y;                 /* position on screen */
    short angle;                /* angle */
} qixes[MAX_QIXES];             /* black_qix */

static struct splayer
{
    short i, j;                 /* position on board */
    short move, score, level, lives;
    bool drawing;
    bool gameover;
} player;

/*************************** STACK STUFF **********************/

/* the stack */
#define STACK_SIZE BOARD_W*BOARD_H
static struct pos
{
    int x, y;                   /* position on board */
} stack[STACK_SIZE];
static int stackPointer;

#define div(a,b) (((a)/(b)))

static 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 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 void emptyStack (void)
{
    stackPointer = 0;
    /* int x, y;
       while(pop(&x, &y)); */
}

/*********************** END OF STACK STUFF *********************/


/* calculate the new x coordinate of the ball according to angle and speed */
static int get_newx (int x, int len, int deg)
{
    int dx;
    if ((deg == MOVE_DRR) || (deg == MOVE_URR))
        dx = 2;
    else if ((deg == MOVE_DDR) || (deg == MOVE_UUR) || (deg == MOVE_DR)
             || (deg == MOVE_UR))
        dx = 1;
    else if ((deg == MOVE_DDL) || (deg == MOVE_UUL) || (deg == MOVE_DL)
             || (deg == MOVE_UL))
        dx = -1;
    else
        dx = -2;
    return x + dx * len;
}

/* calculate the new y coordinate of the ball according to angle and speed */
static int get_newy (int y, int len, int deg)
{
    int dy;
    if ((deg == MOVE_DRR) || (deg == MOVE_DLL) || (deg == MOVE_DR)
        || (deg == MOVE_DL))
        dy = 1;
    else if ((deg == MOVE_DDR) || (deg == MOVE_DDL))
        dy = 2;
    else if ((deg == MOVE_UUR) || (deg == MOVE_UUL))
        dy = -2;
    else
        dy = -1;
    return y + dy * len;
}

/* 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 i, j;                   /* testboard */
    for (j = 0; j < BOARD_H; j++)
        for (i = 0; i < BOARD_W; i++)
            testboard[j][i] = UNCHECKED;
}

/* 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) || (j == 0) || (i == BOARD_W - 1)
                || (j == BOARD_H - 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 =
            BOARD_X + t_rand (((BOARD_W - 4) * CUBE_SIZE) - 2 * CUBE_SIZE) +
            2 * CUBE_SIZE;
        qixes[j].y =
            BOARD_Y + t_rand (((BOARD_H - 6) * CUBE_SIZE) - 2 * CUBE_SIZE) +
            3 * CUBE_SIZE;

        qixes[j].angle = t_rand (12);
    }
    /*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;
}

/* 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;
    char str[25];

    rb->lcd_set_background (LCD_BLACK);
    rb->lcd_clear_display ();
    for (j = 0; j < BOARD_H; j++)
        for (i = 0; i < BOARD_W; i++) {
            rb->lcd_set_foreground (board[j][i]);
            rb->lcd_fillrect (BOARD_X + CUBE_SIZE * i, BOARD_Y + CUBE_SIZE * j,
                              CUBE_SIZE, CUBE_SIZE);
        }
    rb->lcd_set_foreground (LCD_BLACK);
    rb->lcd_set_background (CLR_LTBLUE);
    rb->snprintf (str, sizeof (str), "Level %d", player.level + 1);
    rb->lcd_putsxy (BOARD_X, BOARD_Y, str);
    rb->snprintf (str, sizeof (str), "%d%%", percentage ());
    rb->lcd_putsxy (BOARD_X + CUBE_SIZE * BOARD_W - 24, BOARD_Y, str);
    rb->snprintf (str, sizeof (str), "Score: %d", player.score);
    rb->lcd_putsxy (BOARD_X, BOARD_Y + CUBE_SIZE * BOARD_H - 8, str);
    rb->snprintf (str, sizeof (str), "%d Lives", player.lives);
    rb->lcd_putsxy (BOARD_X + CUBE_SIZE * BOARD_W - 60,
                    BOARD_Y + CUBE_SIZE * BOARD_H - 8, str);

    rb->lcd_set_foreground (PLR_COL);
    rb->lcd_set_background (board[player.j][player.i]);
    rb->lcd_mono_bitmap (pics[PIC_PLAYER], player.i * CUBE_SIZE + BOARD_X,
                         player.j * CUBE_SIZE + BOARD_Y, CUBE_SIZE, CUBE_SIZE);
    rb->lcd_set_background (EMPTIED);
    rb->lcd_set_foreground (LCD_WHITE);
    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);
    rb->lcd_set_foreground (LCD_BLACK);

    rb->lcd_update ();
}

static inline int infested_area (int i, int j)
{
    struct pos p, p1, p2, p3, p4;
    bool hit = false;
    p.x = i;
    p.y = j;
    emptyStack ();
    init_testboard ();
    if (!push (&p))
        return -1;
    while (pop (&p)) {
        hit = (boardcopy[p.y][p.x] == QIX);
        testboard[p.y][p.x] = CHECKED;
        if (hit)
            return true;        /*save some time and space */
        p1.x = p.x + 1;
        p1.y = p.y;
        p2.x = p.x - 1;
        p2.y = p.y;
        p3.x = p.x;
        p3.y = p.y + 1;
        p4.x = p.x;
        p4.y = p.y - 1;
        if ((p1.x < BOARD_W) && (p1.x >= 0) && (p1.y < BOARD_H) && (p1.y >= 0)
            && (testboard[p1.y][p1.x] == UNCHECKED))
            if (board[p1.y][p1.x] != FILLED)
                if (!push (&p1))
                    return -1;
        if ((p2.x < BOARD_W) && (p2.x >= 0) && (p2.y < BOARD_H) && (p2.y >= 0)
            && (testboard[p2.y][p2.x] == UNCHECKED))
            if (board[p2.y][p2.x] != FILLED)
                if (!push (&p2))
                    return -1;
        if ((p3.x < BOARD_W) && (p3.x >= 0) && (p3.y < BOARD_H) && (p3.y >= 0)
            && (testboard[p3.y][p3.x] == UNCHECKED))
            if (board[p3.y][p3.x] != FILLED)
                if (!push (&p3))
                    return -1;
        if ((p4.x < BOARD_W) && (p4.x >= 0) && (p4.y < BOARD_H) && (p4.y >= 0)
            && (testboard[p4.y][p4.x] == UNCHECKED))
            if (board[p4.y][p4.x] != FILLED)
                if (!push (&p4))
                    return -1;
    }
    return (hit ? 1 : 0);
}

static inline int fill_area (int i, int j)
{
    struct pos p, p1, p2, p3, p4;
    p.x = i;
    p.y = j;
    emptyStack ();
    init_testboard ();
    if (!push (&p))
        return -1;
    while (pop (&p)) {
        board[p.y][p.x] = FILLED;
        testboard[p.y][p.x] = CHECKED;
        p1.x = p.x + 1;
        p1.y = p.y;
        p2.x = p.x - 1;
        p2.y = p.y;
        p3.x = p.x;
        p3.y = p.y + 1;
        p4.x = p.x;
        p4.y = p.y - 1;
        if ((p1.x < BOARD_W) && (p1.x >= 0) && (p1.y < BOARD_H) && (p1.y >= 0)
            && (testboard[p1.y][p1.x] == UNCHECKED))
            if (board[p1.y][p1.x] == EMPTIED)
                if (!push (&p1))
                    return -1;
        if ((p2.x < BOARD_W) && (p2.x >= 0) && (p2.y < BOARD_H) && (p2.y >= 0)
            && (testboard[p2.y][p2.x] == UNCHECKED))
            if (board[p2.y][p2.x] == EMPTIED)
                if (!push (&p2))
                    return -1;
        if ((p3.x < BOARD_W) && (p3.x >= 0) && (p3.y < BOARD_H) && (p3.y >= 0)
            && (testboard[p3.y][p3.x] == UNCHECKED))
            if (board[p3.y][p3.x] == EMPTIED)
                if (!push (&p3))
                    return -1;
        if ((p4.x < BOARD_W) && (p4.x >= 0) && (p4.y < BOARD_H) && (p4.y >= 0)
            && (testboard[p4.y][p4.x] == UNCHECKED))
            if (board[p4.y][p4.x] == EMPTIED)
                if (!push (&p4))
                    return -1;
    }
    return 1;
}


/* 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;
            }
            boardcopy[j][i] = board[j][i];
        }

    if (fill) {
        for (i = 0; i < player.level + STARTING_QIXES; i++) /* add qixes to board */
            boardcopy[div (qixes[i].y - BOARD_Y, CUBE_SIZE)][div
                                                             (qixes[i].x - BOARD_X,
                                                              CUBE_SIZE)] = QIX;

        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);
                    if (ret < 0)
                        quit = true;
                    else if (ret == 0) {
                        ret = fill_area (i, j);
                        if (ret < 0)
                            quit = true;
                    }
                }
     }

     rb->button_clear_queue();
}

/* returns the color the real pixel(x,y) on the lcd is pointing at */
static unsigned short getpixel (int x, int y)
{
    int a = div (x - BOARD_X, CUBE_SIZE), b = div (y - BOARD_Y, CUBE_SIZE);
    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 short 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;
}

/* returns true if the (side) of the block          -***-
   starting from (newx,newy) has any filled pixels  *   *
   -***-
 */
static bool line_check (int newx, int newy, int side)
{
    int i = 0;
    bool filled = false;
    for (i = 3; ((i < CUBE_SIZE - 3) && (!filled)); i++) {
        switch (side) {
            case MOVE_LT:
                filled = getpixel (newx, newy + i) == FILLED;
                break;
            case MOVE_RT:
                filled = getpixel (newx + CUBE_SIZE - 1, newy + i) == FILLED;
                break;
            case MOVE_UP:
                filled = getpixel (newx + i, newy) == FILLED;
                break;
            case MOVE_DN:
                filled = getpixel (newx + i, newy + CUBE_SIZE - 1) == FILLED;
                break;
        }
    }
    return filled;
}

static void die (void)
{
    player.lives--;
    if (player.lives == 0)
        player.gameover = true;
    else {
        refresh_board ();
        rb->splash (HZ, true, "Crash!");
        complete_trail (false);
        player.move = MOVE_NO;
        player.drawing = false;
        player.i = BOARD_W / 2;
        player.j = 1;
    }
}

static void move_qix (struct qix *q)
{
    int newx, newy, dir;
    unsigned short nexthit;
    newx = get_newx (q->x, q->velocity, q->angle);
    newy = get_newy (q->y, q->velocity, q->angle);
    nexthit = next_hit (newx, newy);
    if (nexthit == EMPTIED) {
        q->x = newx;
        q->y = newy;
    } else if (nexthit == FILLED) {
        dir = q->angle;
        switch (dir) {
            case MOVE_URR:
            case MOVE_UUR:
            case MOVE_UR:      /* up-right (can hit ceiling or right wall) */
                if (line_check (newx, newy, MOVE_UP)
                    && line_check (newx, newy, MOVE_RT))
                    q->angle = q->angle + 6;
                else if (line_check (newx, newy, MOVE_UP))
                    q->angle = 5 - q->angle;    /* 5=180/(360/12)-1 */
                else
                    q->angle = 11 - q->angle;   /* 11=360/(360/12)-1 */
                break;
            case MOVE_ULL:
            case MOVE_UUL:
            case MOVE_UL:      /* up-left (can hit ceiling or left wall) */
                if (line_check (newx, newy, MOVE_UP)
                    && line_check (newx, newy, MOVE_LT))
                    q->angle = q->angle - 6;
                else if (line_check (newx, newy, MOVE_UP))
                    q->angle = 17 - q->angle;   /* 17=540/(360/12)-1 */
                else
                    q->angle = 11 - q->angle;   /* 11=360/(360/12)-1 */
                break;
            case MOVE_DLL:
            case MOVE_DDL:
            case MOVE_DL:      /* down-left (can hit floor or left wall) */
                if (line_check (newx, newy, MOVE_DN)
                    && line_check (newx, newy, MOVE_LT))
                    q->angle = q->angle - 6;
                else if (line_check (newx, newy, MOVE_DN))
                    q->angle = 17 - q->angle;   /* 17=540/(360/12)-1 */
                else
                    q->angle = 11 - q->angle;   /* 11=360/(360/12)-1 */
                break;
            case MOVE_DRR:
            case MOVE_DDR:
            case MOVE_DR:      /* down-right (can hit floor or right wall) */
                if (line_check (newx, newy, MOVE_DN)
                    && line_check (newx, newy, MOVE_RT))
                    q->angle = q->angle + 6;
                else if (line_check (newx, newy, MOVE_DN))
                    q->angle = 5 - q->angle;    /* 5=180/(360/12)-1 */
                else
                    q->angle = 11 - q->angle;   /* 11=360/(360/12)-1 */
                break;
        }
        q->x = newx;
        q->y = newy;
    } else if (nexthit == TRAIL) {
        die();
    }
}

/* 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;
    }
    j = percentage ();
    if (j > 75) {               /* finished level */
        rb->splash (HZ * 2, true, "Level %d finished", player.level+1);
        player.score += j;
        if (player.level < MAX_LEVEL)
            player.level++;
        init_board ();
        refresh_board ();
        rb->splash (HZ * 2, true, "Ready?");
    }
}

/* the main menu */
#define MAIN_MENU_SIZE 2
#define MENU_START 0
#define MENU_QUIT  1
static int game_menu (void)
{
    static char menu[MAIN_MENU_SIZE][15] = {
        "Start New Game",
        "Quit"
    };

    int button, selection = 0, sw, sh, i;
    bool quit = false;

    rb->lcd_setfont(FONT_UI);
    rb->lcd_getstringsize("A", &sw, &sh);
    if(sw*20 > LCD_WIDTH || sh*4 > LCD_HEIGHT)
        rb->lcd_setfont(FONT_SYSFIXED);

    rb->lcd_getstringsize ("XOBOX", &sw, &sh);
    sh++;
    rb->lcd_set_background (LCD_WHITE);
    rb->lcd_set_foreground (LCD_BLACK);
    rb->lcd_clear_display ();
    rb->lcd_putsxy (LCD_WIDTH / 2 - sw / 2, 2, "XOBOX");
    while (!quit) {
        for (i = 0; i < MAIN_MENU_SIZE; i++) {
            rb->lcd_set_foreground ((i == selection ? LCD_WHITE : LCD_BLACK));
            rb->lcd_set_background ((i == selection ? CLR_BLUE : LCD_WHITE));
            rb->lcd_putsxy (10, sh + 4 + i * sh, menu[i]);
        }
        rb->lcd_update ();
        button = rb->button_get (true);
        switch (button) {
#ifdef MENU_UP
            case MENU_UP:
#else
            case UP:
#endif
                selection = (selection + MAIN_MENU_SIZE - 1) % MAIN_MENU_SIZE;
                break;
#ifdef MENU_UP
            case MENU_DOWN:
#else
            case DOWN:
#endif
                selection = (selection + 1) % MAIN_MENU_SIZE;
                break;
            case SELECT:
            case RIGHT:
                quit = true;
                break;
#ifdef RC_QUIT
            case RC_QUIT:
#endif
            case QUIT:
                selection = MENU_QUIT;
                quit = true;
                break;
        }
    }
    return selection;
}

/* init game's variables */
static void init_game (void)
{
    player.level = 0;
    player.score = 0;
    player.lives = 3;
    player.gameover = false;
    player.drawing = false;
    rb->lcd_setfont(FONT_SYSFIXED);
    init_board ();
    refresh_board ();
    rb->splash (HZ * 2, true, "Ready?");
}

/* general keypad handler loop */
static int xobox_loop (void)
{
    int button = 0, ret;
    bool pause = false;
    int end;

    while (!quit) {
        end = *rb->current_tick + (CYCLETIME * HZ) / 1000;

#ifdef HAS_BUTTON_HOLD
        if (rb->button_hold()) {
        pause = true;
        rb->splash (HZ, true, "PAUSED");
        }
#endif

        button = rb->button_get_w_tmo (true);
        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, true, "Paused");
                break;
            case QUIT:
                ret = game_menu ();
                if (ret == MENU_START)
                    init_game ();
                else
                    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, true, "Game Over!");
            ret = game_menu ();
            if (ret == MENU_START)
                init_game ();
            else
                quit = true;
        }

        if (end > *rb->current_tick)
            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 (struct plugin_api *api, void *parameter)
{
    int ret = PLUGIN_OK;

    (void) parameter;
    rb = api;

    rb->lcd_setfont (FONT_SYSFIXED);

    /* Permanently enable the backlight (unless the user has turned it off) */
    if (rb->global_settings->backlight_timeout > 0)
        rb->backlight_set_timeout (1);

    randomize ();
    if (game_menu () == MENU_START) {
        init_game ();
        ret = xobox_loop ();
    }

    rb->backlight_set_timeout (rb->global_settings->backlight_timeout);
    rb->lcd_setfont (FONT_UI);

    return ret;
}
