/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright (C) 2006 by Mat Holton
 *
 * 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/display_text.h"
#include "lib/helper.h"
#include "lib/highscore.h"
#include "lib/playback_control.h"



/* variable button definitions */
#if CONFIG_KEYPAD == RECORDER_PAD
#define AST_PAUSE BUTTON_ON
#define AST_QUIT BUTTON_OFF
#define AST_THRUST BUTTON_UP
#define AST_HYPERSPACE BUTTON_DOWN
#define AST_LEFT BUTTON_LEFT
#define AST_RIGHT BUTTON_RIGHT
#define AST_FIRE BUTTON_PLAY

#elif CONFIG_KEYPAD == ARCHOS_AV300_PAD
#define AST_PAUSE BUTTON_ON
#define AST_QUIT BUTTON_OFF
#define AST_THRUST BUTTON_UP
#define AST_HYPERSPACE BUTTON_DOWN
#define AST_LEFT BUTTON_LEFT
#define AST_RIGHT BUTTON_RIGHT
#define AST_FIRE BUTTON_SELECT

#elif CONFIG_KEYPAD == ONDIO_PAD
#define AST_PAUSE (BUTTON_MENU | BUTTON_OFF)
#define AST_QUIT BUTTON_OFF
#define AST_THRUST BUTTON_UP
#define AST_HYPERSPACE BUTTON_DOWN
#define AST_LEFT BUTTON_LEFT
#define AST_RIGHT BUTTON_RIGHT
#define AST_FIRE BUTTON_MENU

#elif (CONFIG_KEYPAD == IRIVER_H100_PAD) || \
      (CONFIG_KEYPAD == IRIVER_H300_PAD)
#define AST_PAUSE BUTTON_REC
#define AST_QUIT BUTTON_OFF
#define AST_THRUST BUTTON_UP
#define AST_HYPERSPACE BUTTON_DOWN
#define AST_LEFT BUTTON_LEFT
#define AST_RIGHT BUTTON_RIGHT
#define AST_FIRE BUTTON_SELECT

#define AST_RC_QUIT BUTTON_RC_STOP

#elif (CONFIG_KEYPAD == IAUDIO_X5M5_PAD)
#define AST_PAUSE BUTTON_PLAY
#define AST_QUIT BUTTON_POWER
#define AST_THRUST BUTTON_UP
#define AST_HYPERSPACE BUTTON_DOWN
#define AST_LEFT BUTTON_LEFT
#define AST_RIGHT BUTTON_RIGHT
#define AST_FIRE BUTTON_SELECT

#elif (CONFIG_KEYPAD == IPOD_4G_PAD) || (CONFIG_KEYPAD == IPOD_3G_PAD) || \
      (CONFIG_KEYPAD == IPOD_1G2G_PAD)
#define AST_PAUSE (BUTTON_SELECT | BUTTON_PLAY)
#define AST_QUIT (BUTTON_SELECT | BUTTON_MENU)
#define AST_THRUST BUTTON_MENU
#define AST_HYPERSPACE BUTTON_PLAY
#define AST_LEFT BUTTON_SCROLL_BACK
#define AST_RIGHT BUTTON_SCROLL_FWD
#define AST_FIRE BUTTON_SELECT

#elif (CONFIG_KEYPAD == GIGABEAT_PAD)
#define AST_PAUSE BUTTON_A
#define AST_QUIT BUTTON_POWER
#define AST_THRUST BUTTON_UP
#define AST_HYPERSPACE BUTTON_DOWN
#define AST_LEFT BUTTON_LEFT
#define AST_RIGHT BUTTON_RIGHT
#define AST_FIRE BUTTON_SELECT

#elif (CONFIG_KEYPAD == SANSA_E200_PAD)
#define AST_PAUSE BUTTON_REC
#define AST_QUIT BUTTON_POWER
#define AST_THRUST BUTTON_UP
#define AST_HYPERSPACE BUTTON_DOWN
#define AST_LEFT BUTTON_SCROLL_BACK
#define AST_RIGHT BUTTON_SCROLL_FWD
#define AST_FIRE BUTTON_SELECT

#elif (CONFIG_KEYPAD == SANSA_FUZE_PAD)
#define AST_PAUSE (BUTTON_SELECT | BUTTON_UP)
#define AST_QUIT (BUTTON_HOME|BUTTON_REPEAT)
#define AST_THRUST BUTTON_UP
#define AST_HYPERSPACE BUTTON_DOWN
#define AST_LEFT BUTTON_SCROLL_BACK
#define AST_RIGHT BUTTON_SCROLL_FWD
#define AST_FIRE BUTTON_SELECT

#elif (CONFIG_KEYPAD == SANSA_C200_PAD)
#define AST_PAUSE BUTTON_REC
#define AST_QUIT BUTTON_POWER
#define AST_THRUST BUTTON_UP
#define AST_HYPERSPACE BUTTON_DOWN
#define AST_LEFT BUTTON_LEFT
#define AST_RIGHT BUTTON_RIGHT
#define AST_FIRE BUTTON_SELECT

#elif (CONFIG_KEYPAD == SANSA_CLIP_PAD)
#define AST_PAUSE BUTTON_HOME
#define AST_QUIT BUTTON_POWER
#define AST_THRUST BUTTON_UP
#define AST_HYPERSPACE BUTTON_DOWN
#define AST_LEFT BUTTON_LEFT
#define AST_RIGHT BUTTON_RIGHT
#define AST_FIRE BUTTON_SELECT

#elif (CONFIG_KEYPAD == SANSA_M200_PAD)
#define AST_PAUSE (BUTTON_SELECT | BUTTON_UP)
#define AST_QUIT BUTTON_POWER
#define AST_THRUST BUTTON_UP
#define AST_HYPERSPACE BUTTON_DOWN
#define AST_LEFT BUTTON_LEFT
#define AST_RIGHT BUTTON_RIGHT
#define AST_FIRE (BUTTON_SELECT | BUTTON_REL)

#elif (CONFIG_KEYPAD == IRIVER_H10_PAD)
#define AST_PAUSE BUTTON_PLAY
#define AST_QUIT BUTTON_POWER
#define AST_THRUST BUTTON_SCROLL_UP
#define AST_HYPERSPACE BUTTON_SCROLL_DOWN
#define AST_LEFT BUTTON_LEFT
#define AST_RIGHT BUTTON_RIGHT
#define AST_FIRE BUTTON_REW

#elif (CONFIG_KEYPAD == GIGABEAT_S_PAD)
#define AST_PAUSE BUTTON_PLAY
#define AST_QUIT BUTTON_BACK
#define AST_THRUST BUTTON_UP
#define AST_HYPERSPACE BUTTON_DOWN
#define AST_LEFT BUTTON_LEFT
#define AST_RIGHT BUTTON_RIGHT
#define AST_FIRE BUTTON_SELECT

#elif (CONFIG_KEYPAD == MROBE100_PAD)
#define AST_PAUSE BUTTON_DISPLAY
#define AST_QUIT BUTTON_POWER
#define AST_THRUST BUTTON_UP
#define AST_HYPERSPACE BUTTON_DOWN
#define AST_LEFT BUTTON_LEFT
#define AST_RIGHT BUTTON_RIGHT
#define AST_FIRE BUTTON_SELECT

#elif CONFIG_KEYPAD == IAUDIO_M3_PAD
#define AST_PAUSE BUTTON_RC_PLAY
#define AST_QUIT BUTTON_RC_REC
#define AST_THRUST BUTTON_RC_VOL_UP
#define AST_HYPERSPACE BUTTON_RC_VOL_DOWN
#define AST_LEFT BUTTON_RC_REW
#define AST_RIGHT BUTTON_RC_FF
#define AST_FIRE BUTTON_RC_MODE

#elif (CONFIG_KEYPAD == COWON_D2_PAD)
#define AST_QUIT BUTTON_POWER

#elif CONFIG_KEYPAD == CREATIVEZVM_PAD
#define AST_PAUSE BUTTON_PLAY
#define AST_QUIT BUTTON_BACK
#define AST_THRUST BUTTON_UP
#define AST_HYPERSPACE BUTTON_DOWN
#define AST_LEFT BUTTON_LEFT
#define AST_RIGHT BUTTON_RIGHT
#define AST_FIRE BUTTON_SELECT

#elif CONFIG_KEYPAD == CREATIVE_ZENXFI3_PAD
#define AST_PAUSE     (BUTTON_PLAY | BUTTON_REL)
#define AST_QUIT       BUTTON_POWER
#define AST_THRUST     BUTTON_UP
#define AST_HYPERSPACE BUTTON_DOWN
#define AST_LEFT       BUTTON_BACK
#define AST_RIGHT      BUTTON_MENU
#define AST_FIRE       BUTTON_VOL_UP

#elif CONFIG_KEYPAD == PHILIPS_HDD1630_PAD
#define AST_PAUSE BUTTON_VIEW
#define AST_QUIT BUTTON_POWER
#define AST_THRUST BUTTON_UP
#define AST_HYPERSPACE BUTTON_DOWN
#define AST_LEFT BUTTON_LEFT
#define AST_RIGHT BUTTON_RIGHT
#define AST_FIRE BUTTON_PLAYLIST

#elif CONFIG_KEYPAD == PHILIPS_HDD6330_PAD
#define AST_PAUSE BUTTON_PLAY
#define AST_QUIT BUTTON_POWER
#define AST_THRUST BUTTON_UP
#define AST_HYPERSPACE BUTTON_DOWN
#define AST_LEFT BUTTON_LEFT
#define AST_RIGHT BUTTON_RIGHT
#define AST_FIRE BUTTON_VOL_DOWN

#elif CONFIG_KEYPAD == PHILIPS_SA9200_PAD
#define AST_PAUSE BUTTON_RIGHT
#define AST_QUIT BUTTON_POWER
#define AST_THRUST BUTTON_UP
#define AST_HYPERSPACE BUTTON_DOWN
#define AST_LEFT BUTTON_PREV
#define AST_RIGHT BUTTON_NEXT
#define AST_FIRE BUTTON_LEFT

#elif (CONFIG_KEYPAD == ONDAVX747_PAD) || \
      (CONFIG_KEYPAD == ONDAVX777_PAD) || \
      (CONFIG_KEYPAD == MROBE500_PAD)
#define AST_QUIT BUTTON_POWER

#elif (CONFIG_KEYPAD == SAMSUNG_YH820_PAD) || \
      (CONFIG_KEYPAD == SAMSUNG_YH92X_PAD)

#define AST_PAUSE      BUTTON_FFWD
#define AST_QUIT       BUTTON_REW
#define AST_THRUST     BUTTON_UP
#define AST_HYPERSPACE BUTTON_DOWN
#define AST_LEFT       BUTTON_LEFT
#define AST_RIGHT      BUTTON_RIGHT
#define AST_FIRE       BUTTON_PLAY

#elif (CONFIG_KEYPAD == PBELL_VIBE500_PAD)

#define AST_PAUSE      BUTTON_PLAY
#define AST_QUIT       BUTTON_REC
#define AST_THRUST     BUTTON_UP
#define AST_HYPERSPACE BUTTON_DOWN
#define AST_LEFT       BUTTON_PREV
#define AST_RIGHT      BUTTON_NEXT
#define AST_FIRE       BUTTON_OK

#elif (CONFIG_KEYPAD == MPIO_HD200_PAD)

#define AST_PAUSE (BUTTON_PLAY|BUTTON_FUNC)
#define AST_QUIT (BUTTON_REC|BUTTON_PLAY)
#define AST_THRUST BUTTON_REC
#define AST_HYPERSPACE BUTTON_PLAY
#define AST_LEFT BUTTON_REW
#define AST_RIGHT BUTTON_FF
#define AST_FIRE BUTTON_FUNC

#elif (CONFIG_KEYPAD == MPIO_HD300_PAD)

#define AST_PAUSE (BUTTON_PLAY|BUTTON_REL)
#define AST_QUIT (BUTTON_MENU|BUTTON_REPEAT)
#define AST_THRUST BUTTON_REC
#define AST_HYPERSPACE (BUTTON_PLAY|BUTTON_REPEAT)
#define AST_LEFT BUTTON_UP
#define AST_RIGHT BUTTON_DOWN
#define AST_FIRE BUTTON_ENTER

#elif (CONFIG_KEYPAD == SANSA_FUZEPLUS_PAD)

#define AST_PAUSE BUTTON_PLAYPAUSE
#define AST_QUIT BUTTON_POWER
#define AST_THRUST BUTTON_UP
#define AST_HYPERSPACE BUTTON_BACK
#define AST_LEFT BUTTON_LEFT
#define AST_RIGHT BUTTON_RIGHT
#define AST_FIRE BUTTON_SELECT

#elif (CONFIG_KEYPAD == SANSA_CONNECT_PAD)

#define ALT_PAUSE BUTTON_VOL_DOWN
#define AST_QUIT BUTTON_POWER
#define AST_THRUST BUTTON_UP
#define AST_HYPERSPACE BUTTON_DOWN
#define AST_LEFT BUTTON_LEFT
#define AST_RIGHT BUTTON_RIGHT
#define AST_FIRE BUTTON_SELECT

#elif (CONFIG_KEYPAD == SAMSUNG_YPR0_PAD)
#define AST_PAUSE       BUTTON_MENU
#define AST_QUIT        BUTTON_BACK
#define AST_THRUST      BUTTON_UP
#define AST_HYPERSPACE  BUTTON_DOWN
#define AST_LEFT        BUTTON_LEFT
#define AST_RIGHT       BUTTON_RIGHT
#define AST_FIRE        BUTTON_SELECT

#elif (CONFIG_KEYPAD == HM60X_PAD)
#define AST_PAUSE       (BUTTON_SELECT|BUTTON_POWER)
#define AST_QUIT        BUTTON_POWER
#define AST_THRUST      BUTTON_UP
#define AST_HYPERSPACE  BUTTON_DOWN
#define AST_LEFT        BUTTON_LEFT
#define AST_RIGHT       BUTTON_RIGHT
#define AST_FIRE        BUTTON_SELECT

#elif (CONFIG_KEYPAD == HM801_PAD)
#define AST_PAUSE       BUTTON_PLAY
#define AST_QUIT        BUTTON_POWER
#define AST_THRUST      BUTTON_UP
#define AST_HYPERSPACE  BUTTON_DOWN
#define AST_LEFT        BUTTON_LEFT
#define AST_RIGHT       BUTTON_RIGHT
#define AST_FIRE        BUTTON_SELECT

#elif CONFIG_KEYPAD == SONY_NWZ_PAD
#define AST_PAUSE       BUTTON_POWER
#define AST_QUIT        BUTTON_BACK
#define AST_THRUST      BUTTON_UP
#define AST_HYPERSPACE  BUTTON_DOWN
#define AST_LEFT        BUTTON_LEFT
#define AST_RIGHT       BUTTON_RIGHT
#define AST_FIRE        BUTTON_PLAY

#elif CONFIG_KEYPAD == CREATIVE_ZEN_PAD
#define AST_PAUSE       BUTTON_PLAYPAUSE
#define AST_QUIT        BUTTON_BACK
#define AST_THRUST      BUTTON_UP
#define AST_HYPERSPACE  BUTTON_DOWN
#define AST_LEFT        BUTTON_LEFT
#define AST_RIGHT       BUTTON_RIGHT
#define AST_FIRE        BUTTON_SELECT

#elif (CONFIG_KEYPAD == DX50_PAD)
#define AST_QUIT        BUTTON_POWER
#define AST_THRUST      BUTTON_VOL_UP
#define AST_HYPERSPACE  BUTTON_VOL_DOWN
#define AST_LEFT        BUTTON_LEFT
#define AST_RIGHT       BUTTON_PLAY
#define AST_FIRE        BUTTON_RIGHT

#else
#error No keymap defined!
#endif

#ifdef HAVE_TOUCHSCREEN
#ifndef AST_PAUSE
#define AST_PAUSE       BUTTON_CENTER
#endif
#ifndef AST_QUIT
#define AST_QUIT        BUTTON_TOPLEFT
#endif
#ifndef AST_THRUST
#define AST_THRUST      BUTTON_TOPMIDDLE
#endif
#ifndef AST_HYPERSPACE
#define AST_HYPERSPACE  BUTTON_TOPRIGHT
#endif
#ifndef AST_LEFT
#define AST_LEFT        BUTTON_MIDLEFT
#endif
#ifndef AST_RIGHT
#define AST_RIGHT       BUTTON_MIDRIGHT
#endif
#ifndef AST_FIRE
#define AST_FIRE        BUTTON_BOTTOMMIDDLE
#endif
#endif

#define RES                     MAX(LCD_WIDTH, LCD_HEIGHT)
#define LARGE_LCD               (RES >= 200)

#define CYCLETIME               30

#define SHOW_COL                0
#define SCALE                   5000
#define WRAP_GAP                (LARGE*SCALE*3)
#define POINT_SIZE              2
#define START_LEVEL             1
#define SHOW_LEVEL_TIME         50
#define EXPLOSION_LENGTH        20

#define MAX_NUM_ASTEROIDS       25
#define MAX_NUM_MISSILES        6
#define NUM_STARS               50
#define NUM_TRAIL_POINTS        70
#define MAX_LEVEL               MAX_NUM_ASTEROIDS

#define NUM_ASTEROID_VERTICES   10
#define NUM_SHIP_VERTICES       4
#define NUM_ENEMY_VERTICES      8

#define INVULNERABLE_TIME       30
#define BLINK_TIME              10
#define EXTRA_LIFE              250
#define START_LIVES             3
#define MISSILE_LIFE_LENGTH     40

#define ASTEROID_SPEED          (RES/20)
#define SPACE_CHECK_SIZE        30*SCALE

#if (LARGE_LCD)
#define SIZE_SHIP_COLLISION     8*SCALE
#else
#define SIZE_SHIP_COLLISION     6*SCALE
#endif

#define LITTLE_SHIP             1
#define BIG_SHIP                2
#define ENEMY_BIG_PROBABILITY_START     10
#define ENEMY_APPEAR_PROBABILITY_START  35
#define ENEMY_APPEAR_TIMING_START       600
#define ENEMY_SPEED             4
#define ENEMY_MISSILE_LIFE_LENGTH   (RES/2)
#if (LARGE_LCD)
#define SIZE_ENEMY_COLLISION    7*SCALE
#else
#define SIZE_ENEMY_COLLISION    5*SCALE
#endif

#define SIN_COS_SCALE           10000

#define FAST_ROT_CW_SIN         873
#define FAST_ROT_CW_COS         9963
#define FAST_ROT_ACW_SIN        -873
#define FAST_ROT_ACW_COS        9963

#define MEDIUM_ROT_CW_SIN       350
#define MEDIUM_ROT_CW_COS       9994
#define MEDIUM_ROT_ACW_SIN      -350
#define MEDIUM_ROT_ACW_COS      9994

#define SLOW_ROT_CW_SIN         350
#define SLOW_ROT_CW_COS         9994
#define SLOW_ROT_ACW_SIN        -350
#define SLOW_ROT_ACW_COS        9994

#ifdef HAVE_LCD_COLOR
#define SHIP_ROT_CW_SIN         2419
#define SHIP_ROT_CW_COS         9702
#define SHIP_ROT_ACW_SIN        -2419
#define SHIP_ROT_ACW_COS        9702
#else
#define SHIP_ROT_CW_SIN         3827
#define SHIP_ROT_CW_COS         9239
#define SHIP_ROT_ACW_SIN        -3827
#define SHIP_ROT_ACW_COS        9239
#endif


#define SCALED_WIDTH            (LCD_WIDTH*SCALE)
#define SCALED_HEIGHT           (LCD_HEIGHT*SCALE)
#define CENTER_LCD_X            (LCD_WIDTH/2)
#define CENTER_LCD_Y            (LCD_HEIGHT/2)

#ifdef HAVE_LCD_COLOR
#define ASTEROID_R  230
#define ASTEROID_G  200
#define ASTEROID_B  100
#define SHIP_R      255
#define SHIP_G      255
#define SHIP_B      255
#define ENEMY_R     50
#define ENEMY_G     220
#define ENEMY_B     50
#define THRUST_R    200
#define THRUST_G    200
#define THRUST_B    0

#define COL_MISSILE  LCD_RGBPACK(200,0,0)
#define COL_PLAYER   LCD_RGBPACK(200,200,200)
#define COL_INVULN   LCD_RGBPACK(100,100,200)
#define COL_STARS    LCD_WHITE
#define COL_ASTEROID LCD_RGBPACK(ASTEROID_R,ASTEROID_G,ASTEROID_B)
#define COL_TEXT     LCD_RGBPACK(200,200,255)
#define COL_ENEMY    LCD_RGBPACK(ENEMY_R,ENEMY_G,ENEMY_B)
#define SET_FG       rb->lcd_set_foreground
#define SET_BG       rb->lcd_set_background
#else
#define SET_FG(x)
#define SET_BG(x)
#endif

#define SCORE_FILE PLUGIN_GAMES_DATA_DIR "/spacerocks.score"
#define NUM_SCORES 5

static struct highscore highscores[NUM_SCORES];

/* The array of points that make up an asteroid */
static const short asteroid_one[NUM_ASTEROID_VERTICES*2] =
{
    -2, -12,
     4,  -8,
     8, -14,
    16,  -5,
    14,   0,
    20,   2,
    12,  14,
    -4,  14,
   -10,   6,
   -10,  -8,
};

/* The array of points that make up an asteroid */
static const short asteroid_two[NUM_ASTEROID_VERTICES*2] =
{
    -2, -12,
     4, -16,
     6, -14,
    16,  -8,
    14,   0,
    20,   2,
    12,  14,
    -4,  14,
   -10,   6,
   -10,  -8,
};

/* The array of points that make up an asteroid */
static const short asteroid_three[NUM_ASTEROID_VERTICES*2] =
{
    -2, -12,
     4, -16,
     6, -14,
     2,  -8,
    14,   0,
    20,   2,
    12,  14,
    -4,  14,
   -16,   6,
   -10,  -8,
};

/* The array of points the make up the ship */
static const short ship_vertices[NUM_SHIP_VERTICES*2] =
{
#if (LARGE_LCD)
    0, -6,
    4,  6,
    0,  2,
   -4,  6,
#else
    0, -4,
    3,  4,
    0,  1,
   -3,  4,
#endif
};

/* The array of points the make up the bad spaceship */
static const short enemy_vertices[NUM_ENEMY_VERTICES*2] =
{
#if (LARGE_LCD)
   -8,  0,
   -4,  4,
    4,  4,
    8,  0,
   -8,  0,
    8,  0,
    4, -4,
   -4, -4,
#else
   -5,  0,
   -2,  2,
    2,  2,
    5,  0,
   -5,  0,
    5,  0,
    2, -2,
   -2, -2,
#endif
};

enum asteroid_type
{
#if (LARGE_LCD)
    SMALL  = 2,
    MEDIUM = 4,
    LARGE  = 6,
#else
    SMALL  = 1,
    MEDIUM = 2,
    LARGE  = 3,
#endif
};

enum explosion_type
{
    EXPLOSION_SHIP,
    EXPLOSION_ASTEROID,
    EXPLOSION_ENEMY,
    EXPLOSION_THRUST,
};

enum game_state
{
    GAME_OVER,
    SHOW_LEVEL,
    PLAY_MODE,
    PAUSE_MODE,
};

struct Point
{
    int x;
    int y;
    int dx;
    int dy;
};

struct TrailPoint
{
    struct Point position;
    int alive;
#ifdef HAVE_LCD_COLOR
    short r;
    short g;
    short b;
    short dec;
#endif
};

/* Asteroid structure, contains an array of points */
struct Asteroid
{
    struct Point position;
    struct Point rotation;
    struct Point vertices[NUM_ASTEROID_VERTICES];
    bool exists;
    int explode_countdown;
    enum asteroid_type type;
    int radius;
    long speed_cos;
    long speed_sin;
};

struct Ship
{
    struct Point position;
    struct Point rotation;
    struct Point vertices[NUM_SHIP_VERTICES];
    bool exists;
    int explode_countdown;
    int invulnerable_time;
};

struct Enemy
{
    struct Point position;
    struct Point vertices[NUM_ENEMY_VERTICES];
    bool exists;
    int explode_countdown;
    int appear_countdown;
    short size_probability;
    short appear_probability;
    short appear_timing;
};

struct Missile
{
    struct Point position;
    struct Point oldpoint;
    int alive;
};

static enum game_state game_state;
static int asteroid_count;
static int next_missile_count;
static int next_thrust_count;
static int num_lives;
static int extra_life;
static int show_level_timeout;
static int current_level;
static int current_score;

static struct Ship ship;
static struct Point stars[NUM_STARS];
static struct Asteroid asteroids_array[MAX_NUM_ASTEROIDS];
static struct Missile missiles_array[MAX_NUM_MISSILES];
static struct Missile enemy_missile;
static struct Enemy enemy;
static struct Point lives_points[NUM_SHIP_VERTICES];
static struct TrailPoint trail_points[NUM_TRAIL_POINTS];

/*************************************************
** Handle polygon and point
*************************************************/

/* Check if point is in a polygon */
static bool is_point_in_polygon(struct Point* vertices, int num_vertices,
                                int x, int y)
{
    struct Point* pi;
    struct Point* pj;
    int n;
    bool c = false;

    if (x < -SCALED_WIDTH/2) x += SCALED_WIDTH;
    else if (x > SCALED_WIDTH/2) x -= SCALED_WIDTH;
    if (y < -SCALED_HEIGHT/2) y += SCALED_HEIGHT;
    else if (y > SCALED_HEIGHT/2) y -= SCALED_HEIGHT;

    pi = vertices;
    pj = vertices + num_vertices-1;

    n = num_vertices;
    while (n--)
    {
        if ((((pi->y <= y) && (y < pj->y)) || ((pj->y <= y) && (y < pi->y))) &&
            (x < (pj->x - pi->x) * (y - pi->y) / (pj->y - pi->y) + pi->x))
            c = !c;

        pj = pi;
        pi++;
    }

    return c;
}

/* Check if point is within a rectangle */
static bool is_point_within_rectangle(struct Point* rect, struct Point* p,
                                      int size)
{
    int dx = p->x - rect->x;
    int dy = p->y - rect->y;
#if SHOW_COL
    rb->lcd_drawrect((rect->x - size)/SCALE, (rect->y - size)/SCALE,
                     (size*2+1)/SCALE, (size*2+1)/SCALE);
#endif
    if (dx < -SCALED_WIDTH/2) dx += SCALED_WIDTH;
    else if (dx > SCALED_WIDTH/2) dx -= SCALED_WIDTH;
    if (dy < -SCALED_HEIGHT/2) dy += SCALED_HEIGHT;
    else if (dy > SCALED_HEIGHT/2) dy -= SCALED_HEIGHT;
    return (dx > -size && dx < size && dy > -size && dy < size);
}

/* Rotate polygon */
static void rotate_polygon(struct Point* vertices, int num_vertices,
                           struct Point* rotation, int cos, int sin)
{
    struct Point* point;
    int n;
    long temp_x, temp_y;

    temp_x = rotation->x;
    temp_y = rotation->y;
    rotation->x = (temp_x*cos - temp_y*sin)/SIN_COS_SCALE;
    rotation->y = (temp_y*cos + temp_x*sin)/SIN_COS_SCALE;
#define MIN_SCALE   (SIN_COS_SCALE-10)
#define MAX_SCALE   (SIN_COS_SCALE+10)
    /* normalize vector. this is not accurate but would be enough. */
    temp_x = rotation->x*rotation->x + rotation->y*rotation->y;
    if (temp_x <= MIN_SCALE*MIN_SCALE)
    {
        rotation->x = rotation->x*SIN_COS_SCALE/MIN_SCALE;
        rotation->y = rotation->y*SIN_COS_SCALE/MIN_SCALE;
    }
    else if (temp_x >= MAX_SCALE*MAX_SCALE)
    {
        rotation->x = rotation->x*SIN_COS_SCALE/MAX_SCALE;
        rotation->y = rotation->y*SIN_COS_SCALE/MAX_SCALE;
    }
#undef  MIN_SCALE
#undef  MAX_SCALE

    point = vertices;
    n = num_vertices;
    while (n--)
    {
        point->x = (point->dx*rotation->x - point->dy*rotation->y)/SIN_COS_SCALE;
        point->y = (point->dy*rotation->x + point->dx*rotation->y)/SIN_COS_SCALE;
        point++;
    }
}

/* Draw polygon */
static void draw_polygon(struct Point* vertices, int num_vertices,
                         int px, int py)
{
    int n, new_x, new_y, old_x, old_y;
    struct Point *p;
    bool draw_wrap;

    if (px > SCALED_WIDTH - WRAP_GAP)
        px -= SCALED_WIDTH;
    if (py > SCALED_HEIGHT - WRAP_GAP)
        py -= SCALED_HEIGHT;

    draw_wrap = (px < WRAP_GAP || py < WRAP_GAP);

    p = vertices + num_vertices - 1;
    old_x = (p->x + px)/SCALE;
    old_y = (p->y + py)/SCALE;
    p = vertices;
    n = num_vertices;
    while (n--)
    {
        new_x = (p->x + px)/SCALE;
        new_y = (p->y + py)/SCALE;

        rb->lcd_drawline(old_x, old_y, new_x, new_y);
        if (draw_wrap)
        {
            rb->lcd_drawline(old_x + LCD_WIDTH, old_y, new_x + LCD_WIDTH, new_y);
            rb->lcd_drawline(old_x, old_y + LCD_HEIGHT, new_x, new_y + LCD_HEIGHT);
            rb->lcd_drawline(old_x + LCD_WIDTH, old_y + LCD_HEIGHT,
                             new_x + LCD_WIDTH, new_y + LCD_HEIGHT);
        }
        old_x = new_x;
        old_y = new_y;
        p++;
    }
}

static void move_point(struct Point* point)
{
    point->x += point->dx;
    point->y += point->dy;

    /* Check bounds on the x-axis: */
    point->x %= SCALED_WIDTH;
    if (point->x < 0)
        point->x += SCALED_WIDTH;

    /* Check bounds on the y-axis: */
    point->y %= SCALED_HEIGHT;
    if (point->y < 0)
        point->y += SCALED_HEIGHT;
}

/*************************************************
** Handle trail blaiz.
*************************************************/

static void create_ship_trail(struct TrailPoint* tpoint)
{
    tpoint->position.x += ship.vertices[2].x;
    tpoint->position.y += ship.vertices[2].y;
    tpoint->position.dx = -( ship.vertices[0].x - ship.vertices[2].x )/10;
    tpoint->position.dy = -( ship.vertices[0].y - ship.vertices[2].y )/10;
}

static void create_explosion_trail(struct TrailPoint* tpoint)
{
    tpoint->position.dx = (rb->rand()%5001)-2500;
    tpoint->position.dy = (rb->rand()%5001)-2500;
}

static void create_trail_blaze(int colour, struct Point* position)
{
    int numtoadd;
    struct TrailPoint* tpoint;
    int n;

    if (colour != EXPLOSION_SHIP)
    {
        numtoadd = NUM_TRAIL_POINTS/5;
    }
    else
    {
        numtoadd = NUM_TRAIL_POINTS/8;
    }

    /* give the point a random countdown timer, so they dissapears at different
       times */
    tpoint = trail_points;
    n = NUM_TRAIL_POINTS;
    while (n--)
    {
        /* find a space in the array of trail_points that is NULL or DEAD or
           whatever and place this one here. */
        if (tpoint->alive <= 0)
        {
            /* take a random point near the position. */
            tpoint->position.x = (rb->rand()%18000)-9000 + position->x;
            tpoint->position.y = (rb->rand()%18000)-9000 + position->y;

            switch(colour)
            {
                case EXPLOSION_SHIP:
                    create_explosion_trail(tpoint);
                    tpoint->alive = 51;
#ifdef HAVE_LCD_COLOR
                    tpoint->r = SHIP_R;
                    tpoint->g = SHIP_G;
                    tpoint->b = SHIP_B;
                    tpoint->dec = 2;
#endif
                break;
                case EXPLOSION_ASTEROID:
                    create_explosion_trail(tpoint);
                    tpoint->alive = 51;
#ifdef HAVE_LCD_COLOR
                    tpoint->r = ASTEROID_R;
                    tpoint->g = ASTEROID_G;
                    tpoint->b = ASTEROID_B;
                    tpoint->dec = 2;
#endif
                break;
                case EXPLOSION_ENEMY:
                    create_explosion_trail(tpoint);
                    tpoint->alive = 51;
#ifdef HAVE_LCD_COLOR
                    tpoint->r = ENEMY_R;
                    tpoint->g = ENEMY_G;
                    tpoint->b = ENEMY_B;
                    tpoint->dec = 2;
#endif
                break;
                case EXPLOSION_THRUST:
                    create_ship_trail(tpoint);
                    tpoint->alive = 17;
#ifdef HAVE_LCD_COLOR
                    tpoint->r = THRUST_R;
                    tpoint->g = THRUST_G;
                    tpoint->b = THRUST_B;
                    tpoint->dec = 4;
#endif
                break;
            }

            /* give the points a speed based on direction of travel
               - i.e. opposite */
            tpoint->position.dx += position->dx;
            tpoint->position.dy += position->dy;

            numtoadd--;
            if (numtoadd <= 0)
                break;
        }
        tpoint++;
    }
}

static void draw_and_move_trail_blaze(void)
{
    struct TrailPoint* tpoint;
    int n;

    /* loop through, if alive then move and draw.
       when drawn, countdown it's timer.
       if zero kill it! */

    tpoint = trail_points;
    n = NUM_TRAIL_POINTS;
    while (n--)
    {
        if (tpoint->alive > 0)
        {
            if (game_state != PAUSE_MODE)
            {
                tpoint->alive--;
                move_point(&(tpoint->position));
#ifdef HAVE_LCD_COLOR
                /* intensity = tpoint->alive/2; */
                if (tpoint->r >= tpoint->dec) tpoint->r -= tpoint->dec;
                if (tpoint->g >= tpoint->dec) tpoint->g -= tpoint->dec;
                if (tpoint->b >= tpoint->dec) tpoint->b -= tpoint->dec;
#endif
            }
            SET_FG(LCD_RGBPACK(tpoint->r, tpoint->g, tpoint->b));
            rb->lcd_drawpixel(tpoint->position.x/SCALE, tpoint->position.y/SCALE);
        }
        tpoint++;
    }
}

/*************************************************
** Handle asteroid.
*************************************************/

static void rotate_asteroid(struct Asteroid* asteroid)
{
    rotate_polygon(asteroid->vertices, NUM_ASTEROID_VERTICES,
                   &asteroid->rotation,
                   asteroid->speed_cos, asteroid->speed_sin);
}

/* Initialise the passed Asteroid.
 * if position is NULL, place it at the random loacation
 * where ship doesn't exist
 */
static void initialise_asteroid(struct Asteroid* asteroid,
                                enum asteroid_type type, struct Point *position)
{
    const short *asteroid_vertices;
    struct Point* point;
    int n;

    asteroid->exists = true;
    asteroid->explode_countdown = 0;
    asteroid->type = type;

    /* Set the radius of the asteroid: */
    asteroid->radius = (int)type*SCALE*3;

    /* shall we move Clockwise and Fast */
    n = rb->rand()%100;
    if (n < 25)
    {
        asteroid->speed_cos = FAST_ROT_CW_COS;
        asteroid->speed_sin = FAST_ROT_CW_SIN;
    }
    else if (n < 50)
    {
        asteroid->speed_cos = FAST_ROT_ACW_COS;
        asteroid->speed_sin = FAST_ROT_ACW_SIN;
    }
    else if (n < 75)
    {
        asteroid->speed_cos = SLOW_ROT_ACW_COS;
        asteroid->speed_sin = SLOW_ROT_ACW_SIN;
    }
    else
    {
        asteroid->speed_cos = SLOW_ROT_CW_COS;
        asteroid->speed_sin = SLOW_ROT_CW_SIN;
    }

    n = rb->rand()%99;
    if (n < 33)
        asteroid_vertices = asteroid_one;
    else if (n < 66)
        asteroid_vertices = asteroid_two;
    else
        asteroid_vertices = asteroid_three;

    point = asteroid->vertices;
    for(n = 0; n < NUM_ASTEROID_VERTICES*2; n += 2)
    {
        point->x = asteroid_vertices[n];
        point->y = asteroid_vertices[n+1];
        point->x *= asteroid->radius/20;
        point->y *= asteroid->radius/20;
        /* dx and dy are used when rotate polygon */
        point->dx = point->x;
        point->dy = point->y;
        point++;
    }

    if (!position)
    {
        do {
            /* Set the position randomly: */
            asteroid->position.x = (rb->rand()%SCALED_WIDTH);
            asteroid->position.y = (rb->rand()%SCALED_HEIGHT);
        } while (is_point_within_rectangle(&ship.position, &asteroid->position,
                                           SPACE_CHECK_SIZE));
    }
    else
    {
        asteroid->position.x = position->x;
        asteroid->position.y = position->y;
    }

    do {
        asteroid->position.dx = (rb->rand()%ASTEROID_SPEED)-ASTEROID_SPEED/2;
    } while (asteroid->position.dx == 0);

    do {
        asteroid->position.dy = (rb->rand()%ASTEROID_SPEED)-ASTEROID_SPEED/2;
    } while (asteroid->position.dy == 0);

    asteroid->position.dx *= SCALE/10;
    asteroid->position.dy *= SCALE/10;

    asteroid->rotation.x = SIN_COS_SCALE;
    asteroid->rotation.y = 0;

    /* Now rotate the asteroid a bit, so they all look a bit different */
    for(n = (rb->rand()%30)+2; n--; )
        rotate_asteroid(asteroid);

    /* great, we've created an asteroid, don't forget to increment the total: */
    asteroid_count++;
}

/*
 * Creates a new asteroid of the given 4type (size) and at the given location.
 */
static void create_asteroid(enum asteroid_type type, struct Point *position)
{
    struct Asteroid* asteroid;
    int n;

    asteroid = asteroids_array;
    n = MAX_NUM_ASTEROIDS;
    while (n--)
    {
        if (!asteroid->exists && asteroid->explode_countdown <= 0)
        {
            initialise_asteroid(asteroid, type, position);
            break;
        }
        asteroid++;
    }
}

/* Draw and move all asteroids */
static void draw_and_move_asteroids(void)
{
    struct Asteroid* asteroid;
    int n;

    SET_FG(COL_ASTEROID);

    asteroid = asteroids_array;
    n = MAX_NUM_ASTEROIDS;
    while (n--)
    {
        if (asteroid->exists)
        {
            draw_polygon(asteroid->vertices, NUM_ASTEROID_VERTICES,
                         asteroid->position.x, asteroid->position.y);
        }
        if (game_state != PAUSE_MODE)
        {
            if (asteroid->exists)
            {
                move_point(&asteroid->position);
                rotate_asteroid(asteroid);
            }
            else if (asteroid->explode_countdown > 0)
            {
                asteroid->explode_countdown--;
            }
        }
        asteroid++;
    }
}

static void explode_asteroid(struct Asteroid* asteroid)
{
    struct Point p;
    p.dx = asteroid->position.dx;
    p.dy = asteroid->position.dy;
    p.x  = asteroid->position.x;
    p.y  = asteroid->position.y;

    asteroid_count--;
    asteroid->exists = false;

    switch(asteroid->type)
    {
        case SMALL:
            asteroid->explode_countdown = EXPLOSION_LENGTH;
            create_trail_blaze(EXPLOSION_ASTEROID, &p);
            break;

        case MEDIUM:
            create_asteroid(SMALL, &p);
            create_asteroid(SMALL, &p);
            break;

        case LARGE:
            create_asteroid(MEDIUM, &p);
            create_asteroid(MEDIUM, &p);
            break;
    }
}

/*************************************************
** Handle ship.
*************************************************/

/* Initialise the ship */
static void initialise_ship(void)
{
    struct Point* point;
    struct Point* lives_point;
    int n;

    ship.position.x = CENTER_LCD_X * SCALE;
    ship.position.y = CENTER_LCD_Y * SCALE;
    ship.position.dx = 0;
    ship.position.dy = 0;
    ship.rotation.x = SIN_COS_SCALE;
    ship.rotation.y = 0;
    ship.exists = true;
    ship.explode_countdown = 0;
    ship.invulnerable_time = INVULNERABLE_TIME;

    point = ship.vertices;
    lives_point = lives_points;
    for(n = 0; n < NUM_SHIP_VERTICES*2; n += 2)
    {
        point->x = ship_vertices[n];
        point->y = ship_vertices[n+1];
        point->x *= SCALE;
        point->y *= SCALE;
        /* dx and dy are used when rotate polygon */
        point->dx = point->x;
        point->dy = point->y;
        /* grab a copy of the ships points for the lives display: */
        lives_point->x = point->x;
        lives_point->y = point->y;

        point++;
        lives_point++;
    }
}

/*
 * Draws the ship, moves the ship and creates a new
 * one if it's finished exploding.
 */
static void draw_and_move_ship(void)
{
    if (ship.invulnerable_time > BLINK_TIME || ship.invulnerable_time % 2 != 0)
    {
        SET_FG(COL_INVULN);
    }
    else
    {
        SET_FG(COL_PLAYER);
    }

    if (ship.exists)
    {
        draw_polygon(ship.vertices, NUM_SHIP_VERTICES,
                     ship.position.x, ship.position.y);
    }

    if (game_state != PAUSE_MODE)
    {
        if (ship.exists)
        {
            if (ship.invulnerable_time > 0)
                ship.invulnerable_time--;
            move_point(&ship.position);
        }
        else if (ship.explode_countdown > 0)
        {
            ship.explode_countdown--;
            if (ship.explode_countdown <= 0)
            {
                num_lives--;
                if (num_lives <= 0)
                {
                    game_state = GAME_OVER;
                }
                else
                {
                    initialise_ship();
                }
            }
        }
    }
}

static void explode_ship(void)
{
    if (!ship.invulnerable_time)
    {
        /* if not invulnerable, blow up ship */
        ship.explode_countdown = EXPLOSION_LENGTH;
        ship.exists = false;
        create_trail_blaze(EXPLOSION_SHIP, &ship.position);
    }
}

/* Rotate the ship using the passed sin & cos values */
static void rotate_ship(int cos, int sin)
{
    if (ship.exists)
    {
        rotate_polygon(ship.vertices, NUM_SHIP_VERTICES,
                       &ship.rotation, cos, sin);
    }
}

static void thrust_ship(void)
{
    if (ship.exists)
    {
        ship.position.dx += ( ship.vertices[0].x - ship.vertices[2].x )/20;
        ship.position.dy += ( ship.vertices[0].y - ship.vertices[2].y )/20;

        /* if dx and dy are below a certain threshold, then set 'em to 0
           but to do this we need to ascertain if the spacehip as moved on
           screen for more than a certain amount. */

        create_trail_blaze(EXPLOSION_THRUST, &ship.position);
    }
}

/* stop movement of ship, 'cos that's what happens when you go into hyperspace. */
static void hyperspace(void)
{
    if (ship.exists)
    {
        ship.position.dx = ship.position.dy = 0;
        ship.position.x = (rb->rand()%SCALED_WIDTH);
        ship.position.y = (rb->rand()%SCALED_HEIGHT);
    }
}

static void draw_lives(void)
{
    int n;
#if (LARGE_LCD)
    int px = (LCD_WIDTH-1 - 4)*SCALE;
    int py = (LCD_HEIGHT-1 - 6)*SCALE;
#else
    int px = (LCD_WIDTH-1 - 3)*SCALE;
    int py = (LCD_HEIGHT-1 - 4)*SCALE;
#endif

    SET_FG(COL_PLAYER);

    n = num_lives-1;
    while (n--)
    {
        draw_polygon(lives_points, NUM_SHIP_VERTICES, px, py);
#if (LARGE_LCD)
        px -= 8*SCALE;
#else
        px -= 6*SCALE;
#endif
    }
}

/*
 * missile
 */

/* Initialise a missile */
static void initialise_missile(struct Missile* missile)
{
    missile->position.x = ship.position.x + ship.vertices[0].x;
    missile->position.y = ship.position.y + ship.vertices[0].y;
    missile->position.dx = (ship.vertices[0].x - ship.vertices[2].x)/2;
    missile->position.dy = (ship.vertices[0].y - ship.vertices[2].y)/2;
    missile->alive = MISSILE_LIFE_LENGTH;
    missile->oldpoint.x = missile->position.x;
    missile->oldpoint.y = missile->position.y;
}

/* Fire the next missile */
static void fire_missile(void)
{
    struct Missile* missile;
    int n;

    if (ship.exists)
    {
        missile = missiles_array;
        n = MAX_NUM_MISSILES;
        while (n--)
        {
            if (missile->alive <= 0)
            {
                initialise_missile(missile);
                break;
            }
            missile++;
        }
    }
}

/* Draw and Move all the missiles */
static void draw_and_move_missiles(void)
{
    struct Missile* missile;
    struct Point vertices[2];
    int n;

    SET_FG(COL_MISSILE);

    missile = missiles_array;
    n = MAX_NUM_MISSILES;
    while (n--)
    {
        if (missile->alive > 0)
        {
            vertices[0].x = 0;
            vertices[0].y = 0;
            vertices[1].x = -missile->position.dx;
            vertices[1].y = -missile->position.dy;
            draw_polygon(vertices, 2, missile->position.x, missile->position.y);

            if (game_state != PAUSE_MODE)
            {
                missile->oldpoint.x = missile->position.x;
                missile->oldpoint.y = missile->position.y;
                move_point(&missile->position);
                missile->alive--;
            }
        }
        missile++;
    }
}

/*************************************************
** Handle enemy.
*************************************************/

static void initialise_enemy(void)
{
    struct Point* point;
    int n;
    int size;

    if (rb->rand()%100 > enemy.size_probability)
    {
        size = BIG_SHIP;
        enemy.size_probability++;
        if (enemy.size_probability > 90)
        {
            enemy.size_probability = ENEMY_BIG_PROBABILITY_START;
        }
    }
    else
    {
        size = LITTLE_SHIP;
        enemy.size_probability = ENEMY_BIG_PROBABILITY_START;
    }

    enemy.exists = true;
    enemy.explode_countdown = 0;
    enemy.appear_countdown = enemy.appear_timing;

    point = enemy.vertices;
    for(n = 0; n < NUM_ENEMY_VERTICES*2; n += 2)
    {
        point->x  = enemy_vertices[n];
        point->y  = enemy_vertices[n+1];
        point->x *= size*SCALE/2;
        point->y *= size*SCALE/2;
        point++;
    }

    if (ship.position.x >= SCALED_WIDTH/2)
    {
        enemy.position.dx = ENEMY_SPEED;
        enemy.position.x  = 0;
    }
    else
    {
        enemy.position.dx = -ENEMY_SPEED;
        enemy.position.x  = SCALED_WIDTH;
    }

    if (ship.position.y >= SCALED_HEIGHT/2)
    {
        enemy.position.dy = ENEMY_SPEED;
        enemy.position.y  = 0;
    }
    else
    {
        enemy.position.dy = -ENEMY_SPEED;
        enemy.position.y  = SCALED_HEIGHT;
    }

    enemy.position.dx *= SCALE/10;
    enemy.position.dy *= SCALE/10;
}

static void draw_and_move_enemy(void)
{
    SET_FG(COL_ENEMY);

    if (enemy.exists)
    {
        draw_polygon(enemy.vertices, NUM_ENEMY_VERTICES,
                     enemy.position.x, enemy.position.y);
    }

    if (game_state != PAUSE_MODE)
    {
        if (enemy.exists)
        {
            enemy.position.x += enemy.position.dx;
            enemy.position.y += enemy.position.dy;

            if (enemy.position.x > SCALED_WIDTH || enemy.position.x < 0)
                enemy.exists = false;

            enemy.position.y %= SCALED_HEIGHT;
            if (enemy.position.y < 0)
                enemy.position.y += SCALED_HEIGHT;

            if ((rb->rand()%1000) < 10)
                enemy.position.dy = -enemy.position.dy;
        }
        else if (enemy.explode_countdown > 0)
        {
            enemy.explode_countdown--;
        }
        else
        {
            if (enemy.appear_countdown > 0)
                enemy.appear_countdown--;
            else if (rb->rand()%100 >= enemy.appear_probability)
                initialise_enemy();
        }
    }

    if (enemy_missile.alive <= 0)
    {
        /* if no missile and the enemy is here and not exploding..
           then shoot baby! */
        if (enemy.exists && ship.exists &&
            game_state == PLAY_MODE && (rb->rand()%10) >= 5 )
        {
            int dx = ship.position.x - enemy.position.x;
            int dy = ship.position.y - enemy.position.y;

            if (dx < -SCALED_WIDTH/2) dx += SCALED_WIDTH;
            else if (dx > SCALED_WIDTH/2) dx -= SCALED_WIDTH;
            if (dy < -SCALED_HEIGHT/2) dy += SCALED_HEIGHT;
            else if (dy > SCALED_HEIGHT/2) dy -= SCALED_HEIGHT;

            enemy_missile.position.x = enemy.position.x;
            enemy_missile.position.y = enemy.position.y;

            /* lame, needs to be sorted - it's trying to shoot at the ship */
            if (dx < -5*SCALE)
                enemy_missile.position.dx = -1;
            else if (dx > 5*SCALE)
                enemy_missile.position.dx = 1;
            else
                enemy_missile.position.dx = 0;

            if (dy < -5*SCALE)
                enemy_missile.position.dy = -1;
            else if (dy > 5*SCALE)
                enemy_missile.position.dy = 1;
            else
                enemy_missile.position.dy = 0;

            while (enemy_missile.position.dx == 0 &&
                   enemy_missile.position.dy == 0)
            {
                enemy_missile.position.dx = rb->rand()%2-1;
                enemy_missile.position.dy = rb->rand()%2-1;
            }

            enemy_missile.position.dx *= SCALE;
            enemy_missile.position.dy *= SCALE;
            enemy_missile.alive = ENEMY_MISSILE_LIFE_LENGTH;
        }
    }
    else
    {
        rb->lcd_fillrect( enemy_missile.position.x/SCALE,
                          enemy_missile.position.y/SCALE,
                          POINT_SIZE, POINT_SIZE );
        if (game_state != PAUSE_MODE)
        {
            move_point(&enemy_missile.position);
            enemy_missile.alive--;
        }
    }
}

/*************************************************
** Check collisions.
*************************************************/

/* Add score if missile hit asteroid or enemy */
static void add_score(int val)
{
    current_score += val;
    if (current_score >= extra_life)
    {
        num_lives++;
        extra_life += EXTRA_LIFE;
    }
}

static bool is_point_within_asteroid(struct Asteroid* asteroid,
                                     struct Point* point)
{
    if (is_point_within_rectangle(&asteroid->position, point, asteroid->radius)
     && is_point_in_polygon(asteroid->vertices, NUM_ASTEROID_VERTICES,
                            point->x - asteroid->position.x,
                            point->y - asteroid->position.y))
    {
        explode_asteroid(asteroid);
        return true;
    }
    else
        return false;
}

static bool is_point_within_ship(struct Point* point)
{
    if (is_point_within_rectangle(&ship.position, point, SIZE_SHIP_COLLISION)
     && is_point_in_polygon(ship.vertices, NUM_SHIP_VERTICES,
                            point->x - ship.position.x,
                            point->y - ship.position.y))
    {
        return true;
    }
    else
        return false;
}

static bool is_point_within_enemy(struct Point* point)
{
    if (is_point_within_rectangle(&enemy.position, point, SIZE_ENEMY_COLLISION))
    {
        add_score(5);
        enemy.explode_countdown = EXPLOSION_LENGTH;
        enemy.exists = false;
        create_trail_blaze(EXPLOSION_ENEMY, &enemy.position);
        return true;
    }
    else
        return false;
}

static bool is_ship_within_asteroid(struct Asteroid* asteroid)
{
    struct Point p;

    if (!is_point_within_rectangle(&asteroid->position, &ship.position,
                                   asteroid->radius+SIZE_SHIP_COLLISION))
        return false;

    p.x = ship.position.x + ship.vertices[0].x;
    p.y = ship.position.y + ship.vertices[0].y;
    if (is_point_within_asteroid(asteroid, &p))
        return true;

    p.x = ship.position.x + ship.vertices[1].x;
    p.y = ship.position.y + ship.vertices[1].y;
    if (is_point_within_asteroid(asteroid, &p))
        return true;

    p.x = ship.position.x + ship.vertices[3].x;
    p.y = ship.position.y + ship.vertices[3].y;
    if (is_point_within_asteroid(asteroid, &p))
        return true;

    return false;
}

/* Check for collsions between the missiles and the asteroids and the ship */
static void check_collisions(void)
{
    struct Missile* missile;
    struct Asteroid* asteroid;
    int m, n;
    bool asteroids_onscreen = false;

    asteroid = asteroids_array;
    m = MAX_NUM_ASTEROIDS;
    while (m--)
    {
        /* if the asteroids exists then test missile collision: */
        if (asteroid->exists)
        {
            missile = missiles_array;
            n = MAX_NUM_MISSILES;
            while (n--)
            {
                /* if the missiles exists: */
                if (missile->alive > 0)
                {
                    /* has the missile hit the asteroid? */
                    if (is_point_within_asteroid(asteroid, &missile->position) ||
                        is_point_within_asteroid(asteroid, &missile->oldpoint))
                    {
                        add_score(1);
                        missile->alive = 0;
                        break;
                    }
                }
                missile++;
            }

            /* now check collision with ship: */
            if (asteroid->exists && ship.exists)
            {
                if (is_ship_within_asteroid(asteroid))
                {
                    add_score(1);
                    explode_ship();
                }
            }

            /* has the enemy missile blown something up? */
            if (asteroid->exists && enemy_missile.alive > 0)
            {
                if (is_point_within_asteroid(asteroid, &enemy_missile.position))
                {
                    enemy_missile.alive = 0;
                }
            }
        }

        /* is an asteroid still exploding? */
        if (asteroid->explode_countdown > 0)
            asteroids_onscreen = true;

        asteroid++;
    }

    /* now check collision between ship and enemy */
    if (enemy.exists && ship.exists)
    {
        /* has the enemy collided with the ship? */
        if (is_point_within_enemy(&ship.position))
        {
            explode_ship();
            create_trail_blaze(EXPLOSION_ENEMY, &enemy.position);
        }

        if (enemy.exists)
        {
            /* Now see if the enemy has been shot at by the ships missiles: */
            missile = missiles_array;
            n = MAX_NUM_MISSILES;
            while (n--)
            {
                if (missile->alive > 0 &&
                    is_point_within_enemy(&missile->position))
                {
                    missile->alive = 0;
                    break;
                }
                missile++;
            }
        }
    }

    /* test collision with enemy missile and ship: */
    if (enemy_missile.alive > 0 && is_point_within_ship(&enemy_missile.position))
    {
        explode_ship();
        enemy_missile.alive = 0;
        enemy_missile.position.x = enemy_missile.position.y = 0;
    }

    /* if all asteroids cleared then start again: */
    if (asteroid_count == 0 && !asteroids_onscreen
        && !enemy.exists && enemy.explode_countdown <= 0)
    {
        current_level++;
        if (current_level > MAX_LEVEL)
            current_level = START_LEVEL;
        enemy.appear_probability += 5;
        if (enemy.appear_probability >= 100)
            enemy.appear_probability = ENEMY_APPEAR_PROBABILITY_START;
        enemy.appear_timing -= 30;
        if (enemy.appear_timing < 30)
            enemy.appear_timing = 30;
        game_state = SHOW_LEVEL;
        show_level_timeout = SHOW_LEVEL_TIME;
    }
}

/*
 * stars
 */

static void create_stars(void)
{
    struct Point* p;
    int n;

    p = stars;
    n = NUM_STARS;
    while (n--)
    {
        p->x = (rb->rand()%LCD_WIDTH);
        p->y = (rb->rand()%LCD_HEIGHT);
        p++;
    }
}

static void drawstars(void)
{
    struct Point* p;
    int n;

    SET_FG(COL_STARS);

    p = stars;
    n = NUM_STARS;
    while (n--)
    {
        rb->lcd_drawpixel(p->x , p->y);
        p++;
    }
}

/*************************************************
** Creates start_num number of new asteroids of
** full size.
**************************************************/
static void initialise_level(int start_num)
{
    struct Asteroid* asteroid;
    struct Missile* missile;
    struct TrailPoint* tpoint;
    int n;
    asteroid_count = next_missile_count = next_thrust_count = 0;

    /* no enemy */
    enemy.exists = 0;
    enemy.explode_countdown = 0;
    enemy_missile.alive = 0;

    /* clear asteroids */
    asteroid = asteroids_array;
    n = MAX_NUM_ASTEROIDS;
    while (n--)
    {
        asteroid->exists = false;
        asteroid++;
    }

    /* make some LARGE asteroids */
    for(n = 0; n < start_num; n++)
        initialise_asteroid(&asteroids_array[n], LARGE, NULL);

    /* ensure all missiles are out of action: */
    missile = missiles_array;
    n = MAX_NUM_MISSILES;
    while (n--)
    {
        missile->alive = 0;
        missile++;
    }

    tpoint = trail_points;
    n = NUM_TRAIL_POINTS;
    while (n--)
    {
        tpoint->alive = 0;
        tpoint++;
    }
}

static void initialise_game(void)
{
    enemy.appear_probability = ENEMY_APPEAR_PROBABILITY_START;
    enemy.appear_timing = ENEMY_APPEAR_TIMING_START;
    enemy.appear_countdown = enemy.appear_timing;
    enemy.size_probability = ENEMY_BIG_PROBABILITY_START;
    current_level = START_LEVEL;
    num_lives = START_LIVES;
    extra_life = EXTRA_LIFE;
    current_score = 0;
    initialise_ship();
    initialise_level(0);
    game_state = SHOW_LEVEL;
    show_level_timeout = SHOW_LEVEL_TIME;
}

/* menu stuff */
static bool spacerocks_help(void)
{
    static char *help_text[] = {
        "Spacerocks", "", "Aim", "",
        "The", "goal", "of", "the", "game", "is", "to", "blow", "up",
        "the", "asteroids", "and", "avoid", "being", "hit", "by", "them.",
        "Also", "you'd", "better", "watch", "out", "for", "the", "UFOs!"
    };
    static struct style_text formation[]={
        { 0, TEXT_CENTER|TEXT_UNDERLINE },
        { 2, C_RED },
        LAST_STYLE_ITEM
    };

    rb->lcd_setfont(FONT_UI);
    SET_BG(LCD_BLACK);
    SET_FG(LCD_WHITE);
    if (display_text(ARRAYLEN(help_text), help_text, formation, NULL, true))
        return true;
    rb->lcd_setfont(FONT_SYSFIXED);

    return false;
}

#define PLUGIN_OTHER 10
static bool ingame;
static int spacerocks_menu_cb(int action, const struct menu_item_ex *this_item)
{
    if (action == ACTION_REQUEST_MENUITEM
        && !ingame && ((intptr_t)this_item)==0)
        return ACTION_EXIT_MENUITEM;
    return action;
}

static int spacerocks_menu(void)
{
    int selection = 0;
    MENUITEM_STRINGLIST(main_menu, "Spacerocks Menu", spacerocks_menu_cb,
                        "Resume Game", "Start New Game",
                        "Help", "High Scores",
                        "Playback Control", "Quit");
    rb->button_clear_queue();

    while (1)
    {
        switch (rb->do_menu(&main_menu, &selection, NULL, false))
        {
            case 0:
                return PLUGIN_OTHER;
            case 1:
                initialise_game();
                return PLUGIN_OTHER;
            case 2:
                if (spacerocks_help())
                    return PLUGIN_USB_CONNECTED;
                break;
            case 3:
                highscore_show(-1, highscores, NUM_SCORES, true);
                break;
            case 4:
                playback_control(NULL);
                break;
            case 5:
                return PLUGIN_OK;
            case MENU_ATTACHED_USB:
                return PLUGIN_USB_CONNECTED;
            default:
                break;
        }
    }
}

static int spacerocks_game_loop(void)
{
    int button;
    int end;
    int position;
    int ret;

    if ((ret = spacerocks_menu()) != PLUGIN_OTHER)
        return ret;

    SET_BG(LCD_BLACK);

    ingame = true;
    while (true)
    {
        end = *rb->current_tick + (CYCLETIME * HZ) / 1000;
        rb->lcd_clear_display();
        SET_FG(COL_TEXT);
        switch(game_state)
        {
            case GAME_OVER:
                ingame = false;
                rb->splash (HZ * 2, "Game Over");
                rb->lcd_clear_display();
                position = highscore_update(current_score, current_level, "",
                                            highscores, NUM_SCORES);
                if (position != -1)
                {
                    if (position == 0)
                        rb->splash(HZ*2, "New High Score");
                    highscore_show(position, highscores, NUM_SCORES, true);
                }
                return PLUGIN_OTHER;
                break;

            case PAUSE_MODE:
                rb->lcd_putsxyf(1,LCD_HEIGHT-8, "score %d ", current_score);
                rb->lcd_putsxy(CENTER_LCD_X - 15,
                               CENTER_LCD_Y + CENTER_LCD_Y/2 - 4, "pause");
                draw_and_move_missiles();
                draw_lives();
                draw_and_move_ship();
                break;

            case PLAY_MODE:
                rb->lcd_putsxyf(1, LCD_HEIGHT-8, "score %d ", current_score);
                draw_and_move_missiles();
                draw_lives();
                check_collisions();
                draw_and_move_ship();
                break;

            case SHOW_LEVEL:
                rb->lcd_putsxyf(1, LCD_HEIGHT-8, "score %d ", current_score);
                rb->lcd_putsxyf(CENTER_LCD_X - 20,
                               CENTER_LCD_Y + CENTER_LCD_Y/2 - 4,
                               "stage %d ", current_level);
                draw_lives();
                draw_and_move_ship();
                show_level_timeout--;
                if (show_level_timeout <= 0)
                {
                    initialise_level(current_level);
                    game_state = PLAY_MODE;
                }
                break;
        }
        draw_and_move_trail_blaze();
        drawstars();
        draw_and_move_asteroids();
        draw_and_move_enemy();

        rb->lcd_update();

#ifdef HAS_BUTTON_HOLD
        if (rb->button_hold() && game_state == PLAY_MODE)
            game_state = PAUSE_MODE;
#endif
        button = rb->button_get(false);
        switch(button)
        {
            case(AST_QUIT):
                return PLUGIN_OTHER;
                break;
#ifdef AST_PAUSE
            case(AST_PAUSE):
                if (game_state == PAUSE_MODE)
                    game_state = PLAY_MODE;
                else if (game_state == PLAY_MODE)
                    game_state = PAUSE_MODE;
                break;
#endif
            case (AST_LEFT):
            case (AST_LEFT | BUTTON_REPEAT):
                if (game_state == PLAY_MODE || game_state == SHOW_LEVEL)
                    rotate_ship(SHIP_ROT_ACW_COS, SHIP_ROT_ACW_SIN);
                break;

            case (AST_RIGHT):
            case (AST_RIGHT | BUTTON_REPEAT):
                if (game_state == PLAY_MODE || game_state == SHOW_LEVEL)
                    rotate_ship(SHIP_ROT_CW_COS, SHIP_ROT_CW_SIN);
                break;

            case (AST_THRUST):
            case (AST_THRUST | BUTTON_REPEAT):
                if (game_state == PLAY_MODE || game_state == SHOW_LEVEL)
                {
                    if (next_thrust_count <= 0)
                    {
                        next_thrust_count = 5;
                        thrust_ship();
                    }
                }
                break;

            case (AST_HYPERSPACE):
                if (game_state == PLAY_MODE)
                    hyperspace();
                /* maybe shield if it gets too hard */
                break;

            case (AST_FIRE):
            case (AST_FIRE | BUTTON_REPEAT):
                if (game_state == PLAY_MODE)
                {
                    if (next_missile_count <= 0)
                    {
                        fire_missile();
                        next_missile_count = 10;
                    }
                }
                else if(game_state == PAUSE_MODE)
                    game_state = PLAY_MODE;
                break;

            default:
                if (rb->default_event_handler(button)==SYS_USB_CONNECTED)
                    return PLUGIN_USB_CONNECTED;
                break;
        }

        if (next_missile_count > 0)
            next_missile_count--;

        if (next_thrust_count > 0)
            next_thrust_count--;

        if (TIME_BEFORE(*rb->current_tick, end))
            rb->sleep(end-*rb->current_tick);
        else
            rb->yield();
    }
}

enum plugin_status plugin_start(const void* parameter)
{
    (void)parameter;
    int ret = PLUGIN_OTHER;

#if LCD_DEPTH > 1
    rb->lcd_set_backdrop(NULL);
#endif
    /* universal font */
    rb->lcd_setfont(FONT_SYSFIXED);
    /* Turn off backlight timeout */
    backlight_ignore_timeout();
    highscore_load(SCORE_FILE, highscores, NUM_SCORES);
    rb->srand(*rb->current_tick);

    /* create stars once, and once only: */
    create_stars();

    while (ret == PLUGIN_OTHER)
        ret = spacerocks_game_loop();

    rb->lcd_setfont(FONT_UI);
    highscore_save(SCORE_FILE, highscores, NUM_SCORES);
    /* Turn on backlight timeout (revert to settings) */
    backlight_use_settings();

    return ret;
}
