rockbox/apps/plugins/invadrox.c
Daniel Stenberg 2acc0ac542 Updated our source code header to explicitly mention that we are GPL v2 or
later. We still need to hunt down snippets used that are not. 1324 modified
files...
http://www.rockbox.org/mail/archive/rockbox-dev-archive-2008-06/0060.shtml


git-svn-id: svn://svn.rockbox.org/rockbox/trunk@17847 a1c6a512-1295-4272-9138-f99709370657
2008-06-28 18:10:04 +00:00

1829 lines
52 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2006 Albert Veli
*
* 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.
*
****************************************************************************/
/* Improvised creds goes to:
*
* - Anders Clausen for ingeniously inventing the name Invadrox.
* - Linus Nielsen-Feltzing for patiently answering n00b questions.
*/
#include "plugin.h"
#include "highscore.h"
#include "helper.h"
PLUGIN_HEADER
/* Original graphics is only 1bpp so it should be portable
* to most targets. But for now, only support the simple ones.
*/
#ifndef HAVE_LCD_BITMAP
#error INVADROX: Unsupported LCD
#endif
#if (LCD_DEPTH < 2)
#error INVADROX: Unsupported LCD
#endif
/* #define DEBUG */
#ifdef DEBUG
#include <stdio.h>
#define DBG(format, arg...) { printf("%s: " format, __FUNCTION__, ## arg); }
#else
#define DBG(format, arg...) {}
#endif
#if CONFIG_KEYPAD == IRIVER_H100_PAD
#define QUIT BUTTON_OFF
#define LEFT BUTTON_LEFT
#define RIGHT BUTTON_RIGHT
#define FIRE BUTTON_ON
#elif CONFIG_KEYPAD == IRIVER_H300_PAD
#define QUIT BUTTON_OFF
#define LEFT BUTTON_LEFT
#define RIGHT BUTTON_RIGHT
#define FIRE BUTTON_SELECT
#elif (CONFIG_KEYPAD == IRIVER_H10_PAD)
#define QUIT BUTTON_POWER
#define LEFT BUTTON_LEFT
#define RIGHT BUTTON_RIGHT
#define FIRE BUTTON_PLAY
#elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \
(CONFIG_KEYPAD == IPOD_3G_PAD) || \
(CONFIG_KEYPAD == IPOD_1G2G_PAD)
#define QUIT BUTTON_MENU
#define LEFT BUTTON_LEFT
#define RIGHT BUTTON_RIGHT
#define FIRE BUTTON_SELECT
#elif CONFIG_KEYPAD == IAUDIO_X5M5_PAD
#define QUIT BUTTON_POWER
#define LEFT BUTTON_LEFT
#define RIGHT BUTTON_RIGHT
#define FIRE BUTTON_SELECT
#elif CONFIG_KEYPAD == GIGABEAT_PAD
#define QUIT BUTTON_POWER
#define LEFT BUTTON_LEFT
#define RIGHT BUTTON_RIGHT
#define FIRE BUTTON_SELECT
#elif CONFIG_KEYPAD == SANSA_E200_PAD
#define QUIT BUTTON_POWER
#define LEFT BUTTON_LEFT
#define RIGHT BUTTON_RIGHT
#define FIRE BUTTON_SELECT
#elif CONFIG_KEYPAD == ELIO_TPJ1022_PAD
/* TODO: Figure out which buttons to use for Tatung Elio TPJ-1022 */
#define QUIT BUTTON_AB
#define LEFT BUTTON_LEFT
#define RIGHT BUTTON_RIGHT
#define FIRE BUTTON_MENU
#elif CONFIG_KEYPAD == GIGABEAT_S_PAD
#define QUIT BUTTON_BACK
#define LEFT BUTTON_LEFT
#define RIGHT BUTTON_RIGHT
#define FIRE BUTTON_SELECT
#elif CONFIG_KEYPAD == COWOND2_PAD
#define QUIT BUTTON_POWER
#else
#error INVADROX: Unsupported keypad
#endif
#ifdef HAVE_TOUCHPAD
#ifndef QUIT
#define QUIT BUTTON_TOPLEFT
#endif
#ifndef LEFT
#define LEFT BUTTON_MIDLEFT
#endif
#ifndef RIGHT
#define RIGHT BUTTON_MIDRIGHT
#endif
#ifndef FIRE
#define FIRE BUTTON_CENTER
#endif
#endif
#ifndef UNUSED
#define UNUSED __attribute__ ((unused))
#endif
#ifndef ABS
#define ABS(x) (((x) < 0) ? (-(x)) : (x))
#endif
/* Defines common to all models */
#define UFO_Y (SCORENUM_Y + FONT_HEIGHT + ALIEN_HEIGHT)
#define PLAYFIELD_Y (LCD_HEIGHT - SHIP_HEIGHT - 2)
#define PLAYFIELD_WIDTH (LCD_WIDTH - 2 * PLAYFIELD_X)
#define LEVEL_X (LCD_WIDTH - PLAYFIELD_X - LIVES_X - LEVEL_WIDTH - 2 * NUMBERS_WIDTH - 3 * NUM_SPACING)
#define SHIP_MIN_X (PLAYFIELD_X + PLAYFIELD_WIDTH / 5 - SHIELD_WIDTH / 2 - SHIP_WIDTH)
#define SHIP_MAX_X (PLAYFIELD_X + 4 * PLAYFIELD_WIDTH / 5 + SHIELD_WIDTH / 2)
/* SCORE_Y = 0 for most targets. Gigabeat redefines it later. */
#define SCORE_Y 0
#define MAX_LIVES 8
/* iPod Video defines */
#if (LCD_WIDTH == 320) && (LCD_HEIGHT == 240)
/* Original arcade game size 224x240, 1bpp with
* red overlay at top and green overlay at bottom.
*
* iPod Video: 320x240x16
* ======================
* X: 48p padding at left/right gives 224p playfield in middle.
* 10p "border" gives 204p actual playfield. UFO use full 224p.
* Y: Use full 240p.
*
* MAX_X = (204 - 12) / 2 - 1 = 95
*
* Y: Score text 7 0
* Space 10 7
* Score 7 17
* Space 8 24
* 3 Ufo 7 32
* 2 Space Aliens start at 32 + 3 * 8 = 56
* 0 aliens 9*8 56 -
* space ~7*8 128 | 18.75 aliens space between
* shield 2*8 182 | first alien and ship.
* space 8 198 | MAX_Y = 18
* ship 8 206 -
* space 2*8 214
* hline 1 230 - PLAYFIELD_Y
* bottom border 10 240
* Lives and Level goes inside bottom border
*/
#define ARCADISH_GRAPHICS
#define PLAYFIELD_X 48
#define SHIP_Y (PLAYFIELD_Y - 3 * SHIP_HEIGHT)
#define ALIEN_START_Y (UFO_Y + 3 * ALIEN_HEIGHT)
#define SCORENUM_X (PLAYFIELD_X + NUMBERS_WIDTH)
#define SCORENUM_Y SCORE_Y + (2 * (FONT_HEIGHT + 1) + 1)
#define HISCORE_X (LCD_WIDTH - PLAYFIELD_X - HISCORE_WIDTH)
#define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 1 - 6 * NUMBERS_WIDTH - 5 * NUM_SPACING)
#define SHIELD_Y (PLAYFIELD_Y - 6 * SHIP_HEIGHT)
#define LIVES_X 10
#define MAX_X 95
#define MAX_Y 18
#elif (LCD_WIDTH == 176) && (LCD_HEIGHT == 220)
/* Sandisk Sansa e200: 176x220x16
* ==============================
* X: No padding. 8p border -> 160p playfield.
*
* 8p Aliens with 3p spacing -> 88 + 30 = 118p aliens block.
* (160 - 118) / 2 = 21 rounds for whole block (more than original)
* MAX_X = (160 - 8) / 2 - 1 = 75 rounds for single alien (less than original)
*
* LOGO 70 0
* Score text 5 70
* Space 5 75
* Y Score 5 80
* Space 10 85
* 2 Ufo 5 95
* 2 Space 10 100
* 0 aliens 9*5 110 -
* space ~7*5 155 | 18.6 aliens space between
* shield 2*5 188 | first alien and ship.
* space 5 198 | MAX_Y = 18
* ship 5 203 -
* space 5 208
* hline 1 213 PLAYFIELD_Y
* bottom border 6
* LCD_HEIGHT 220
* Lives and Level goes inside bottom border
*/
#define TINY_GRAPHICS
#define PLAYFIELD_X 0
#define SHIP_Y (PLAYFIELD_Y - 2 * SHIP_HEIGHT)
#define SHIELD_Y (SHIP_Y - SHIP_HEIGHT - SHIELD_HEIGHT)
#define ALIEN_START_Y (UFO_Y + 3 * SHIP_HEIGHT)
/* Redefine SCORE_Y */
#undef SCORE_Y
#define SCORE_Y 70
#define SCORENUM_X (PLAYFIELD_X + NUMBERS_WIDTH)
#define SCORENUM_Y (SCORE_Y + 2 * FONT_HEIGHT)
#define HISCORE_X (LCD_WIDTH - PLAYFIELD_X - HISCORE_WIDTH)
#define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 1 - 6 * NUMBERS_WIDTH - 5 * NUM_SPACING)
#define LIVES_X 8
#define MAX_X 75
#define MAX_Y 18
#elif (LCD_WIDTH == 176) && (LCD_HEIGHT == 132)
/* iPod Nano: 176x132x16
* ======================
* X: No padding. 8p border -> 160p playfield.
*
* LIVES_X 8
* ALIEN_WIDTH 8
* ALIEN_HEIGHT 5
* ALIEN_SPACING 3
* SHIP_WIDTH 10
* SHIP_HEIGHT 5
* FONT_HEIGHT 5
* UFO_WIDTH 10
* UFO_HEIGHT 5
* SHIELD_WIDTH 15
* SHIELD_HEIGHT 10
* MAX_X 75
* MAX_Y = 18
* ALIEN_START_Y (UFO_Y + 12)
*
* 8p Aliens with 3p spacing -> 88 + 30 = 118p aliens block.
* (160 - 118) / 2 = 21 rounds for whole block (more than original)
* MAX_X = (160 - 8) / 2 - 1 = 75 rounds for single alien (less than original)
*
* Y: Scoreline 5 0 (combine scoretext and numbers on same line)
* Space 5 5
* 1 Ufo 5 10
* 3 Space 7 15
* 2 aliens 9*5 22 -
* space ~7*5 67 | Just above 18 aliens space between
* shield 2*5 100 | first alien and ship.
* space 5 110 | MAX_Y = 18
* ship 5 115 -
* space 5 120
* hline 1 125 PLAYFIELD_Y
* bottom border 6 126
* LCD_HEIGHT 131
* Lives and Level goes inside bottom border
*/
#define TINY_GRAPHICS
#define PLAYFIELD_X 0
#define SHIP_Y (PLAYFIELD_Y - 2 * SHIP_HEIGHT)
#define ALIEN_START_Y (UFO_Y + 12)
#define SCORENUM_X (PLAYFIELD_X + SCORE_WIDTH + NUMBERS_WIDTH + NUM_SPACING)
#define SCORENUM_Y SCORE_Y
#define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 4 * NUMBERS_WIDTH - 3 * NUM_SPACING)
#define HISCORE_X (HISCORENUM_X - NUMBERS_WIDTH - NUM_SPACING - HISCORE_WIDTH)
#define SHIELD_Y (SHIP_Y - SHIP_HEIGHT - SHIELD_HEIGHT)
#define LIVES_X 8
#define MAX_X 75
#define MAX_Y 18
#elif (LCD_WIDTH == 160) && (LCD_HEIGHT == 128)
/* iAudio X5, iRiver H10 20Gb, iPod 3g/4g: 160x128x16
* ======================================
* X: No padding. No border -> 160p playfield.
*
* LIVES_X 0
* ALIEN_WIDTH 8
* ALIEN_HEIGHT 5
* ALIEN_SPACING 3
* SHIP_WIDTH 10
* SHIP_HEIGHT 5
* FONT_HEIGHT 5
* UFO_WIDTH 10
* UFO_HEIGHT 5
* SHIELD_WIDTH 15
* SHIELD_HEIGHT 10
* MAX_X 75
* MAX_Y = 18
* ALIEN_START_Y (UFO_Y + 10)
*
* 8p Aliens with 3p spacing -> 88 + 30 = 118p aliens block.
* (160 - 118) / 2 = 21 rounds for whole block (more than original)
* MAX_X = (160 - 8) / 2 - 1 = 75 rounds for single alien (less than original)
*
* Y: Scoreline 5 0 (combine scoretext and numbers on same line)
* Space 5 5
* 1 Ufo 5 10
* 2 Space 5 15
* 8 aliens 9*5 20 -
* space ~6*5 65 | Just above 18 aliens space between
* shield 2*5 96 | first alien and ship.
* space 5 106 | MAX_Y = 18
* ship 5 111 -
* space 5 116
* hline 1 121 PLAYFIELD_Y
* bottom border 6 122
* LCD_HEIGHT 128
* Lives and Level goes inside bottom border
*/
#define TINY_GRAPHICS
#define PLAYFIELD_X 0
#define SHIP_Y (PLAYFIELD_Y - 2 * SHIP_HEIGHT)
#define ALIEN_START_Y (UFO_Y + 10)
#define SCORENUM_X (PLAYFIELD_X + SCORE_WIDTH + NUMBERS_WIDTH + NUM_SPACING)
#define SCORENUM_Y SCORE_Y
#define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 4 * NUMBERS_WIDTH - 3 * NUM_SPACING)
#define HISCORE_X (HISCORENUM_X - NUMBERS_WIDTH - NUM_SPACING - HISCORE_WIDTH)
#define SHIELD_Y (SHIP_Y - SHIP_HEIGHT - SHIELD_HEIGHT)
#define LIVES_X 0
#define MAX_X 75
#define MAX_Y 18
#elif (LCD_WIDTH == 240) && (LCD_HEIGHT == 320)
/* Gigabeat: 240x320x16
* ======================
* X: 8p padding at left/right gives 224p playfield in middle.
* 10p "border" gives 204p actual playfield. UFO use full 224p.
* Y: Use bottom 240p for playfield and top 80 pixels for logo.
*
* MAX_X = (204 - 12) / 2 - 1 = 95
*
* Y: Score text 7 0 + 80
* Space 10 7 + 80
* Score 7 17 + 80
* Space 8 24 + 80
* 3 Ufo 7 32 + 80
* 2 Space Aliens start at 32 + 3 * 8 = 56
* 0 aliens 9*8 56 -
* space ~7*8 128 | 18.75 aliens space between
* shield 2*8 182 | first alien and ship.
* space 8 198 | MAX_Y = 18
* ship 8 206 -
* space 2*8 214
* hline 1 230 310 - PLAYFIELD_Y
* bottom border 10 240 320
* Lives and Level goes inside bottom border
*/
#define ARCADISH_GRAPHICS
#define PLAYFIELD_X 8
#define SHIP_Y (PLAYFIELD_Y - 3 * SHIP_HEIGHT)
#define ALIEN_START_Y (UFO_Y + 3 * ALIEN_HEIGHT)
/* Redefine SCORE_Y */
#undef SCORE_Y
#define SCORE_Y 80
#define SCORENUM_X (PLAYFIELD_X + NUMBERS_WIDTH)
#define SCORENUM_Y SCORE_Y + (2 * (FONT_HEIGHT + 1) + 1)
#define HISCORE_X (LCD_WIDTH - PLAYFIELD_X - HISCORE_WIDTH)
#define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 1 - 6 * NUMBERS_WIDTH - 5 * NUM_SPACING)
#define SHIELD_Y (PLAYFIELD_Y - 6 * SHIP_HEIGHT)
#define LIVES_X 10
#define MAX_X 95
#define MAX_Y 18
#elif (LCD_WIDTH == 220) && (LCD_HEIGHT == 176)
/* TPJ1022, H300, iPod Color: 220x176x16
* ============================
* X: 0p padding at left/right gives 220p playfield in middle.
* 8p "border" gives 204p actual playfield. UFO use full 220p.
* Y: Use full 176p for playfield.
*
* MAX_X = (204 - 12) / 2 - 1 = 95
*
* Y: Score text 7 0
* Space 8 7
* 1 Ufo 7 15
* 7 Space Aliens start at 15 + 3 * 8 = 56
* 6 aliens 9*8 25 -
* space ~7*8 103 | 15.6 aliens space between
* shield 2*8 126 | first alien and ship.
* space 8 142 | MAX_Y = 15
* ship 8 150 -
* space 8 158
* hline 1 166 - PLAYFIELD_Y
* bottom border 10 176
* Lives and Level goes inside bottom border
*/
#define ARCADISH_GRAPHICS
#define PLAYFIELD_X 0
#define SHIP_Y (PLAYFIELD_Y - 2 * SHIP_HEIGHT)
#define ALIEN_START_Y (UFO_Y + 10)
#define SCORENUM_Y SCORE_Y
#define SCORENUM_X (PLAYFIELD_X + SCORE_WIDTH + NUMBERS_WIDTH + NUM_SPACING)
#define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 4 * NUMBERS_WIDTH - 3 * NUM_SPACING)
#define HISCORE_X (HISCORENUM_X - NUMBERS_WIDTH - NUM_SPACING - HISCORE_WIDTH)
#define SHIELD_Y (PLAYFIELD_Y - 5 * SHIP_HEIGHT)
#define LIVES_X 8
#define MAX_X 95
#define MAX_Y 15
#else
#error INVADROX: Unsupported LCD type
#endif
/* Defines common to each "graphic type" */
#ifdef ARCADISH_GRAPHICS
#define STRIDE 71
#define SHIP_SRC_X 24
#define SHIP_WIDTH 16
#define SHIP_HEIGHT 8
#define SHOT_HEIGHT 5
#define ALIEN_WIDTH 12
#define ALIEN_EXPLODE_SRC_X 52
#define ALIEN_EXPLODE_SRC_Y 39
#define ALIEN_EXPLODE_WIDTH 13
#define ALIEN_EXPLODE_HEIGHT 7
#define ALIEN_HEIGHT 8
#define ALIEN_SPACING 4
#define ALIEN_SPEED 2
#define UFO_SRC_X 40
#define UFO_WIDTH 16
#define UFO_HEIGHT 7
#define UFO_EXPLODE_WIDTH 21
#define UFO_EXPLODE_HEIGHT 8
#define UFO_SPEED 1
#define FONT_HEIGHT 7
#define LEVEL_SRC_Y 24
#define LEVEL_WIDTH 37
#define SCORE_SRC_X 24
#define SCORE_SRC_Y 31
#define SCORE_WIDTH 37
#define HISCORE_WIDTH 61
#define NUM_SPACING 3
#define NUMBERS_SRC_Y 38
#define NUMBERS_WIDTH 5
#define SHIELD_SRC_X 40
#define SHIELD_SRC_Y 15
#define SHIELD_WIDTH 22
#define SHIELD_HEIGHT 16
#define FIRE_WIDTH 8
#define FIRE_HEIGHT 8
#define FIRE_SPEED 8
#define BOMB_SRC_X 62
#define BOMB_WIDTH 3
#define BOMB_HEIGHT 7
#define BOMB_SPEED 3
#define ALIENS 11
unsigned char fire_sprite[FIRE_HEIGHT] = {
(1 << 7) | (0 << 6) | (0 << 5) | (0 << 4) | (1 << 3) | (0 << 2) | (0 << 1) | 1,
(0 << 7) | (0 << 6) | (1 << 5) | (0 << 4) | (0 << 3) | (0 << 2) | (1 << 1) | 0,
(0 << 7) | (1 << 6) | (1 << 5) | (1 << 4) | (1 << 3) | (1 << 2) | (1 << 1) | 0,
(1 << 7) | (1 << 6) | (1 << 5) | (1 << 4) | (1 << 3) | (1 << 2) | (1 << 1) | 1,
(0 << 7) | (1 << 6) | (1 << 5) | (1 << 4) | (1 << 3) | (1 << 2) | (1 << 1) | 1,
(0 << 7) | (1 << 6) | (1 << 5) | (1 << 4) | (1 << 3) | (1 << 2) | (1 << 1) | 0,
(0 << 7) | (0 << 6) | (1 << 5) | (0 << 4) | (0 << 3) | (1 << 2) | (0 << 1) | 0,
(1 << 7) | (0 << 6) | (0 << 5) | (1 << 4) | (0 << 3) | (0 << 2) | (0 << 1) | 1
};
#elif defined TINY_GRAPHICS
#define STRIDE 53
#define SHIP_SRC_X 16
#define SHIP_WIDTH 10
#define SHIP_HEIGHT 5
#define SHOT_HEIGHT 4
#define ALIEN_WIDTH 8
#define ALIEN_HEIGHT 5
#define ALIEN_EXPLODE_SRC_X 40
#define ALIEN_EXPLODE_SRC_Y 26
#define ALIEN_EXPLODE_WIDTH 10
#define ALIEN_EXPLODE_HEIGHT 5
#define ALIEN_SPACING 3
#define ALIEN_SPEED 2
#define UFO_SRC_X 26
#define UFO_WIDTH 11
#define UFO_HEIGHT 5
#define UFO_EXPLODE_WIDTH 14
#define UFO_EXPLODE_HEIGHT 5
#define UFO_SPEED 1
#define FONT_HEIGHT 5
#define LEVEL_SRC_Y 15
#define LEVEL_WIDTH 29
#define NUMBERS_WIDTH 4
#define NUM_SPACING 2
#define SCORE_SRC_X 17
#define SCORE_SRC_Y 20
#define SCORE_WIDTH 28
#define HISCORE_WIDTH 45
#define NUMBERS_SRC_Y 25
#define SHIELD_SRC_X 29
#define SHIELD_SRC_Y 10
#define SHIELD_WIDTH 15
#define SHIELD_HEIGHT 10
#define FIRE_WIDTH 6
#define FIRE_HEIGHT 6
#define FIRE_SPEED 6
#define BOMB_SRC_X 44
#define BOMB_WIDTH 3
#define BOMB_HEIGHT 5
#define BOMB_SPEED 2
#define ALIENS 11
unsigned char fire_sprite[FIRE_HEIGHT] = {
(1 << 5) | (0 << 4) | (0 << 3) | (1 << 2) | (0 << 1) | 1,
(0 << 5) | (1 << 4) | (0 << 3) | (0 << 2) | (0 << 1) | 0,
(0 << 5) | (1 << 4) | (1 << 3) | (1 << 2) | (1 << 1) | 0,
(0 << 5) | (1 << 4) | (1 << 3) | (1 << 2) | (1 << 1) | 1,
(0 << 5) | (1 << 4) | (0 << 3) | (0 << 2) | (1 << 1) | 0,
(1 << 5) | (0 << 4) | (1 << 3) | (0 << 2) | (0 << 1) | 1
};
#else
#error Graphic type not defined
#endif
/* Colors */
#if (LCD_DEPTH >= 8)
#define SLIME_GREEN LCD_RGBPACK(31, 254, 31)
#define UFO_RED LCD_RGBPACK(254, 31, 31)
#elif (LCD_DEPTH == 2)
#define SLIME_GREEN LCD_LIGHTGRAY
#define UFO_RED LCD_LIGHTGRAY
#else
#error LCD type not implemented yet
#endif
/* Alien states */
#define DEAD 0
#define ALIVE 1
#define BOMBER 2
/* Fire/bomb/ufo states */
#define S_IDLE 0
#define S_ACTIVE 1
#define S_SHOWSCORE 2
#define S_EXPLODE -9
/* Fire/bomb targets */
#define TARGET_TOP 0
#define TARGET_SHIELD 1
#define TARGET_SHIP 2
#define TARGET_BOTTOM 3
#define TARGET_UFO 4
#define HISCOREFILE PLUGIN_GAMES_DIR "/invadrox.high"
/* 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 40
static const struct plugin_api* rb;
/* Physical x is at PLAYFIELD_X + LIVES_X + x * ALIEN_SPEED
* Physical y is at y * ALIEN_HEIGHT
*/
struct alien {
unsigned char x; /* x-coordinate (0 - 95) */
unsigned char y; /* y-coordinate (0 - 18) */
unsigned char type; /* 0 (Kang), 1 (Kodos), 2 (Serak) */
unsigned char state; /* Dead, alive or bomber */
};
/* Aliens box 5 rows * ALIENS aliens in each row */
struct alien aliens[5 * ALIENS];
#define MAX_BOMBS 4
struct bomb {
int x, y;
unsigned char type;
unsigned char frame; /* Current animation frame */
unsigned char frames; /* Number of frames in animation */
unsigned char target; /* Remember target during explosion frames */
int state; /* 0 (IDLE) = inactive, 1 (FIRE) or negative, exploding */
};
struct bomb bombs[MAX_BOMBS];
/* Increase max_bombs at higher levels */
int max_bombs;
/* Raw framebuffer value of shield/ship green color */
fb_data screen_green, screen_white;
/* For optimization, precalculate startoffset of each scanline */
unsigned int ytab[LCD_HEIGHT];
/* external bitmaps */
extern const fb_data invadrox[];
#if (LCD_WIDTH == 320) && (LCD_HEIGHT == 240)
/* iPod Video only */
extern const fb_data invadrox_left[];
extern const fb_data invadrox_right[];
#endif
#if ((LCD_WIDTH == 240) && (LCD_HEIGHT == 320)) || ((LCD_WIDTH == 176) && (LCD_HEIGHT == 220))
/* Gigabeat F, Sansa e200 */
extern const fb_data invadrox_logo[];
#endif
int lives = 2;
int score = 0;
int scores[3] = { 30, 20, 10 };
int level = 0;
struct highscore hiscore;
bool game_over = false;
int ship_x, old_ship_x, ship_dir, ship_acc, max_ship_speed;
int ship_frame, ship_frame_counter;
bool ship_hit;
int fire, fire_target, fire_x, fire_y;
int curr_alien, aliens_paralyzed, gamespeed;
int ufo_state, ufo_x;
bool level_finished;
bool aliens_down, aliens_right, hit_left_border, hit_right_border;
/* No standard get_pixel function yet, use this hack instead */
#if (LCD_DEPTH >= 8)
inline fb_data get_pixel(int x, int y)
{
return rb->lcd_framebuffer[ytab[y] + x];
}
#elif (LCD_DEPTH == 2)
#if (LCD_PIXELFORMAT == HORIZONTAL_PACKING)
static const unsigned char shifts[4] = {
6, 4, 2, 0
};
/* Horizontal packing */
inline fb_data get_pixel(int x, int y)
{
return (rb->lcd_framebuffer[ytab[y] + (x >> 2)] >> shifts[x & 3]) & 3;
}
#else
/* Vertical packing */
static const unsigned char shifts[4] = {
0, 2, 4, 6
};
inline fb_data get_pixel(int x, int y)
{
return (rb->lcd_framebuffer[ytab[y] + x] >> shifts[y & 3]) & 3;
}
#endif /* Horizontal/Vertical packing */
#else
#error get_pixel: pixelformat not implemented yet
#endif
/* Draw "digits" least significant digits of num at (x,y) */
void draw_number(int x, int y, int num, int digits)
{
int i;
int d;
for (i = digits - 1; i >= 0; i--) {
d = num % 10;
num = num / 10;
rb->lcd_bitmap_part(invadrox, d * NUMBERS_WIDTH, NUMBERS_SRC_Y,
STRIDE, x + i * (NUMBERS_WIDTH + NUM_SPACING), y,
NUMBERS_WIDTH, FONT_HEIGHT);
}
/* Update lcd */
rb->lcd_update_rect(x, y, 4 * NUMBERS_WIDTH + 3 * NUM_SPACING, FONT_HEIGHT);
}
inline void draw_score(void)
{
draw_number(SCORENUM_X, SCORENUM_Y, score, 4);
if (score > hiscore.score) {
/* Draw new hiscore (same as score) */
draw_number(HISCORENUM_X, SCORENUM_Y, score, 4);
}
}
void draw_level(void)
{
rb->lcd_bitmap_part(invadrox, 0, LEVEL_SRC_Y,
STRIDE, LEVEL_X, PLAYFIELD_Y + 2,
LEVEL_WIDTH, FONT_HEIGHT);
draw_number(LEVEL_X + LEVEL_WIDTH + 2 * NUM_SPACING, PLAYFIELD_Y + 2, level, 2);
}
void draw_lives(void)
{
int i;
/* Lives num */
rb->lcd_bitmap_part(invadrox, lives * NUMBERS_WIDTH, NUMBERS_SRC_Y,
STRIDE, PLAYFIELD_X + LIVES_X, PLAYFIELD_Y + 2,
NUMBERS_WIDTH, FONT_HEIGHT);
/* Ships */
for (i = 0; i < (lives - 1); i++) {
rb->lcd_bitmap_part(invadrox, SHIP_SRC_X, 0, STRIDE,
PLAYFIELD_X + LIVES_X + SHIP_WIDTH + i * (SHIP_WIDTH + NUM_SPACING),
PLAYFIELD_Y + 1, SHIP_WIDTH, SHIP_HEIGHT);
}
/* Erase ship to the righ (if less than MAX_LIVES) */
if (lives < MAX_LIVES) {
rb->lcd_fillrect(PLAYFIELD_X + LIVES_X + SHIP_WIDTH + i * (SHIP_WIDTH + NUM_SPACING),
PLAYFIELD_Y + 1, SHIP_WIDTH, SHIP_HEIGHT);
}
/* Update lives (and level) part of screen */
rb->lcd_update_rect(PLAYFIELD_X + LIVES_X, PLAYFIELD_Y + 1,
PLAYFIELD_WIDTH - 2 * LIVES_X, MAX(FONT_HEIGHT + 1, SHIP_HEIGHT + 1));
}
inline void draw_aliens(void)
{
int i;
for (i = 0; i < 5 * ALIENS; i++) {
rb->lcd_bitmap_part(invadrox, aliens[i].x & 1 ? ALIEN_WIDTH : 0, aliens[i].type * ALIEN_HEIGHT,
STRIDE, PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED,
ALIEN_START_Y + aliens[i].y * ALIEN_HEIGHT,
ALIEN_WIDTH, ALIEN_HEIGHT);
}
}
/* Return false if there is no next alive alien (round is over) */
inline bool next_alien(void)
{
bool ret = true;
do {
curr_alien++;
if (curr_alien % ALIENS == 0) {
/* End of this row. Move up one row. */
curr_alien -= 2 * ALIENS;
if (curr_alien < 0) {
/* No more aliens in this round. */
curr_alien = 4 * ALIENS;
ret = false;
}
}
} while (aliens[curr_alien].state == DEAD && ret);
if (!ret) {
/* No more alive aliens. Round finished. */
if (hit_right_border) {
if (hit_left_border) {
DBG("ERROR: both left and right borders are set (%d)\n", curr_alien);
}
/* Move down-left next round */
aliens_right = false;
aliens_down = true;
hit_right_border = false;
} else if (hit_left_border) {
/* Move down-right next round */
aliens_right = true;
aliens_down = true;
hit_left_border = false;
} else {
/* Not left nor right. Set down to false. */
aliens_down = false;
}
}
return ret;
}
/* All aliens have been moved.
* Set curr_alien to first alive.
* Return false if no-one is left alive.
*/
bool first_alien(void)
{
int i, y;
for (y = 4; y >= 0; y--) {
for (i = y * ALIENS; i < (y + 1) * ALIENS; i++) {
if (aliens[i].state != DEAD) {
curr_alien = i;
return true;
}
}
}
/* All aliens dead. */
level_finished = true;
return false;
}
bool move_aliens(void)
{
int x, y, old_x, old_y;
/* Move current alien (curr_alien is pointing to a living alien) */
old_x = aliens[curr_alien].x;
old_y = aliens[curr_alien].y;
if (aliens_down) {
aliens[curr_alien].y++;
if (aliens[curr_alien].y == MAX_Y) {
/* Alien is at bottom. Game Over. */
DBG("Alien %d is at bottom. Game Over.\n", curr_alien);
game_over = true;
return false;
}
}
if (aliens_right) {
/* Moving right */
if (aliens[curr_alien].x < MAX_X) {
aliens[curr_alien].x++;
}
/* Now, after move, check if we hit the right border. */
if (aliens[curr_alien].x == MAX_X) {
hit_right_border = true;
}
} else {
/* Moving left */
if (aliens[curr_alien].x > 0) {
aliens[curr_alien].x--;
}
/* Now, after move, check if we hit the left border. */
if (aliens[curr_alien].x == 0) {
hit_left_border = true;
}
}
/* Erase old position */
x = PLAYFIELD_X + LIVES_X + old_x * ALIEN_SPEED;
y = ALIEN_START_Y + old_y * ALIEN_HEIGHT;
if (aliens[curr_alien].y != old_y) {
/* Moved in y-dir. Erase whole alien. */
rb->lcd_fillrect(x, y, ALIEN_WIDTH, ALIEN_HEIGHT);
} else {
if (aliens_right) {
/* Erase left edge */
rb->lcd_fillrect(x, y, ALIEN_SPEED, ALIEN_HEIGHT);
} else {
/* Erase right edge */
x += ALIEN_WIDTH - ALIEN_SPEED;
rb->lcd_fillrect(x, y, ALIEN_SPEED, ALIEN_HEIGHT);
}
}
/* Draw alien at new pos */
x = PLAYFIELD_X + LIVES_X + aliens[curr_alien].x * ALIEN_SPEED;
y = ALIEN_START_Y + aliens[curr_alien].y * ALIEN_HEIGHT;
rb->lcd_bitmap_part(invadrox,
aliens[curr_alien].x & 1 ? ALIEN_WIDTH : 0, aliens[curr_alien].type * ALIEN_HEIGHT,
STRIDE, x, y, ALIEN_WIDTH, ALIEN_HEIGHT);
if (!next_alien()) {
/* Round finished. Set curr_alien to first alive from bottom. */
if (!first_alien()) {
/* Should never happen. Taken care of in move_fire(). */
return false;
}
/* TODO: Play next background sound */
}
return true;
}
inline void draw_ship(void)
{
/* Erase old ship */
if (old_ship_x < ship_x) {
/* Move right. Erase leftmost part of ship. */
rb->lcd_fillrect(old_ship_x, SHIP_Y, ship_x - old_ship_x, SHIP_HEIGHT);
} else if (old_ship_x > ship_x) {
/* Move left. Erase rightmost part of ship. */
rb->lcd_fillrect(ship_x + SHIP_WIDTH, SHIP_Y, old_ship_x - ship_x, SHIP_HEIGHT);
}
/* Draw ship */
rb->lcd_bitmap_part(invadrox, SHIP_SRC_X, ship_frame * SHIP_HEIGHT,
STRIDE, ship_x, SHIP_Y, SHIP_WIDTH, SHIP_HEIGHT);
if (ship_hit) {
/* Alternate between frame 1 and 2 during hit */
ship_frame_counter++;
if (ship_frame_counter > 2) {
ship_frame_counter = 0;
ship_frame++;
if (ship_frame > 2) {
ship_frame = 1;
}
}
}
/* Save ship_x for next time */
old_ship_x = ship_x;
}
inline void fire_alpha(int xc, int yc, fb_data color)
{
int x, y;
unsigned char mask;
rb->lcd_set_foreground(color);
for (y = 0; y < FIRE_HEIGHT; y++) {
mask = 1 << (FIRE_WIDTH - 1);
for (x = -(FIRE_WIDTH / 2); x < (FIRE_WIDTH / 2); x++) {
if (fire_sprite[y] & mask) {
rb->lcd_drawpixel(xc + x, yc + y);
}
mask >>= 1;
}
}
rb->lcd_set_foreground(LCD_BLACK);
}
void move_fire(void)
{
bool hit_green = false;
bool hit_white = false;
int i, j;
static int exploding_alien = -1;
fb_data pix;
if (fire == S_IDLE) {
return;
}
/* Alien hit. Wait until explosion is finished. */
if (aliens_paralyzed < 0) {
aliens_paralyzed++;
if (aliens_paralyzed == 0) {
/* Erase exploding_alien */
rb->lcd_fillrect(PLAYFIELD_X + LIVES_X + aliens[exploding_alien].x * ALIEN_SPEED,
ALIEN_START_Y + aliens[exploding_alien].y * ALIEN_HEIGHT,
ALIEN_EXPLODE_WIDTH, ALIEN_HEIGHT);
fire = S_IDLE;
/* Special case. We killed curr_alien. */
if (exploding_alien == curr_alien) {
if (!next_alien()) {
/* Round finished. Set curr_alien to first alive from bottom. */
first_alien();
}
}
}
return;
}
if (fire == S_ACTIVE) {
/* Erase */
rb->lcd_vline(fire_x, fire_y, fire_y + SHOT_HEIGHT);
/* Check top */
if (fire_y <= SCORENUM_Y + FONT_HEIGHT + 4) {
/* TODO: Play explode sound */
fire = S_EXPLODE;
fire_target = TARGET_TOP;
fire_alpha(fire_x, fire_y, UFO_RED);
return;
}
/* Move */
fire_y -= FIRE_SPEED;
/* Hit UFO? */
if (ufo_state == S_ACTIVE) {
if ((ABS(ufo_x + UFO_WIDTH / 2 - fire_x) <= UFO_WIDTH / 2) &&
(fire_y <= UFO_Y + UFO_HEIGHT)) {
ufo_state = S_EXPLODE;
fire = S_EXPLODE;
fire_target = TARGET_UFO;
/* Center explosion */
ufo_x -= (UFO_EXPLODE_WIDTH - UFO_WIDTH) / 2;
rb->lcd_bitmap_part(invadrox, UFO_SRC_X, UFO_HEIGHT,
STRIDE, ufo_x, UFO_Y - 1, UFO_EXPLODE_WIDTH, UFO_EXPLODE_HEIGHT);
return;
}
}
/* Hit bomb? (check position, not pixel value) */
for (i = 0; i < max_bombs; i++) {
if (bombs[i].state == S_ACTIVE) {
/* Count as hit if within BOMB_WIDTH pixels */
if ((ABS(bombs[i].x - fire_x) < BOMB_WIDTH) &&
(fire_y - bombs[i].y < BOMB_HEIGHT)) {
/* Erase bomb */
rb->lcd_fillrect(bombs[i].x, bombs[i].y, BOMB_WIDTH, BOMB_HEIGHT);
bombs[i].state = S_IDLE;
/* Explode ship fire */
fire = S_EXPLODE;
fire_target = TARGET_SHIELD;
fire_alpha(fire_x, fire_y, LCD_WHITE);
return;
}
}
}
/* Check for hit*/
for (i = FIRE_SPEED; i >= 0; i--) {
pix = get_pixel(fire_x, fire_y + i);
if(pix == screen_white) {
hit_white = true;
fire_y += i;
break;
}
if(pix == screen_green) {
hit_green = true;
fire_y += i;
break;
}
}
if (hit_green) {
/* Hit shield */
/* TODO: Play explode sound */
fire = S_EXPLODE;
fire_target = TARGET_SHIELD;
/* Center explosion around hit pixel */
fire_y -= FIRE_HEIGHT / 2;
fire_alpha(fire_x, fire_y, SLIME_GREEN);
return;
}
if (hit_white) {
/* Hit alien? */
for (i = 0; i < 5 * ALIENS; i++) {
if (aliens[i].state != DEAD &&
(ABS(fire_x - (PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED +
ALIEN_WIDTH / 2)) <= ALIEN_WIDTH / 2) &&
(ABS(fire_y - (ALIEN_START_Y + aliens[i].y * ALIEN_HEIGHT +
ALIEN_HEIGHT / 2)) <= ALIEN_HEIGHT / 2)) {
/* TODO: play alien hit sound */
if (aliens[i].state == BOMBER) {
/* Set (possible) alien above to bomber */
for (j = i - ALIENS; j >= 0; j -= ALIENS) {
if (aliens[j].state != DEAD) {
/* printf("New bomber (%d, %d)\n", j % ALIENS, j / ALIENS); */
aliens[j].state = BOMBER;
break;
}
}
}
aliens[i].state = DEAD;
exploding_alien = i;
score += scores[aliens[i].type];
draw_score();
/* Update score part of screen */
rb->lcd_update_rect(SCORENUM_X, SCORENUM_Y,
PLAYFIELD_WIDTH - 2 * NUMBERS_WIDTH, FONT_HEIGHT);
/* Paralyze aliens S_EXPLODE frames */
aliens_paralyzed = S_EXPLODE;
rb->lcd_bitmap_part(invadrox, ALIEN_EXPLODE_SRC_X, ALIEN_EXPLODE_SRC_Y,
STRIDE, PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED,
ALIEN_START_Y + aliens[i].y * ALIEN_HEIGHT,
ALIEN_EXPLODE_WIDTH, ALIEN_EXPLODE_HEIGHT);
/* Since alien is 1 pixel taller than explosion sprite, erase bottom line */
rb->lcd_hline(PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED,
PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED + ALIEN_WIDTH,
ALIEN_START_Y + (aliens[i].y + 1) * ALIEN_HEIGHT - 1);
return;
}
}
}
/* Draw shot */
rb->lcd_set_foreground(LCD_WHITE);
rb->lcd_vline(fire_x, fire_y, fire_y + SHOT_HEIGHT);
rb->lcd_set_foreground(LCD_BLACK);
} else if (fire < S_IDLE) {
/* Count up towards S_IDLE, then erase explosion */
fire++;
if (fire == S_IDLE) {
/* Erase explosion */
if (fire_target == TARGET_TOP) {
rb->lcd_fillrect(fire_x - (FIRE_WIDTH / 2), fire_y, FIRE_WIDTH, FIRE_HEIGHT);
} else if (fire_target == TARGET_SHIELD) {
/* Draw explosion with black pixels */
fire_alpha(fire_x, fire_y, LCD_BLACK);
}
}
}
}
/* Return a BOMBER alien */
inline int random_bomber(void)
{
int i, col;
/* TODO: Weigh higher probability near ship */
col = rb->rand() % ALIENS;
for (i = col + 4 * ALIENS; i >= 0; i -= ALIENS) {
if (aliens[i].state == BOMBER) {
return i;
}
}
/* No BOMBER found in this col */
for (i = 0; i < 5 * ALIENS; i++) {
if (aliens[i].state == BOMBER) {
return i;
}
}
/* No BOMBER found at all (error?) */
return -1;
}
inline void draw_bomb(int i)
{
rb->lcd_bitmap_part(invadrox, BOMB_SRC_X + bombs[i].type * BOMB_WIDTH,
bombs[i].frame * (BOMB_HEIGHT + 1),
STRIDE, bombs[i].x, bombs[i].y,
BOMB_WIDTH, BOMB_HEIGHT);
/* Advance frame */
bombs[i].frame++;
if (bombs[i].frame == bombs[i].frames) {
bombs[i].frame = 0;
}
}
void move_bombs(void)
{
int i, j, bomber;
bool abort;
for (i = 0; i < max_bombs; i++) {
switch (bombs[i].state) {
case S_IDLE:
if (ship_hit) {
continue;
}
bomber = random_bomber();
if (bomber < 0) {
DBG("ERROR: No bomber available\n");
continue;
}
/* x, y */
bombs[i].x = PLAYFIELD_X + LIVES_X + aliens[bomber].x * ALIEN_SPEED + ALIEN_WIDTH / 2;
bombs[i].y = ALIEN_START_Y + (aliens[bomber].y + 1) * ALIEN_HEIGHT;
/* Check for duplets in x and y direction */
abort = false;
for (j = i - 1; j >= 0; j--) {
if ((bombs[j].state == S_ACTIVE) &&
((bombs[i].x == bombs[j].x) || (bombs[i].y == bombs[j].y))) {
abort = true;
break;
}
}
if (abort) {
/* Skip this one, continue with next bomb */
/* printf("Bomb %d duplet of %d\n", i, j); */
continue;
}
/* Passed, set type */
bombs[i].type = rb->rand() % 3;
bombs[i].frame = 0;
if (bombs[i].type == 0) {
bombs[i].frames = 3;
} else if (bombs[i].type == 1) {
bombs[i].frames = 4;
} else {
bombs[i].frames = 6;
}
/* Bombs away */
bombs[i].state = S_ACTIVE;
draw_bomb(i);
continue;
break;
case S_ACTIVE:
/* Erase old position */
rb->lcd_fillrect(bombs[i].x, bombs[i].y, BOMB_WIDTH, BOMB_HEIGHT);
/* Move */
bombs[i].y += BOMB_SPEED;
/* Check if bottom hit */
if (bombs[i].y + BOMB_HEIGHT >= PLAYFIELD_Y) {
bombs[i].y = PLAYFIELD_Y - FIRE_HEIGHT + 1;
fire_alpha(bombs[i].x, bombs[i].y, LCD_WHITE);
bombs[i].state = S_EXPLODE;
bombs[i].target = TARGET_BOTTOM;
break;
}
/* Check for green (ship or shield) */
for (j = BOMB_HEIGHT; j >= BOMB_HEIGHT - BOMB_SPEED; j--) {
bombs[i].target = 0;
if(get_pixel(bombs[i].x + BOMB_WIDTH / 2, bombs[i].y + j) == screen_green) {
/* Move to hit pixel */
bombs[i].x += BOMB_WIDTH / 2;
bombs[i].y += j;
/* Check if ship is hit */
if (bombs[i].y > SHIELD_Y + SHIELD_HEIGHT && bombs[i].y < PLAYFIELD_Y) {
/* TODO: play ship hit sound */
ship_hit = true;
ship_frame = 1;
ship_frame_counter = 0;
bombs[i].state = S_EXPLODE * 4;
bombs[i].target = TARGET_SHIP;
rb->lcd_bitmap_part(invadrox, SHIP_SRC_X, 1 * SHIP_HEIGHT, STRIDE,
ship_x, SHIP_Y, SHIP_WIDTH, SHIP_HEIGHT);
break;
}
/* Shield hit */
bombs[i].state = S_EXPLODE;
bombs[i].target = TARGET_SHIELD;
/* Center explosion around hit pixel in shield */
bombs[i].y -= FIRE_HEIGHT / 2;
fire_alpha(bombs[i].x, bombs[i].y, SLIME_GREEN);
break;
}
}
if (bombs[i].target != 0) {
/* Hit ship or shield, continue */
continue;
}
draw_bomb(i);
break;
default:
/* If we get here state should be < 0, exploding */
bombs[i].state++;
if (bombs[i].state == S_IDLE) {
if (ship_hit) {
/* Erase explosion */
rb->lcd_fillrect(ship_x, SHIP_Y, SHIP_WIDTH, SHIP_HEIGHT);
rb->lcd_update_rect(ship_x, SHIP_Y, SHIP_WIDTH, SHIP_HEIGHT);
ship_hit = false;
ship_frame = 0;
ship_x = PLAYFIELD_X + 2 * LIVES_X;
lives--;
if (lives == 0) {
game_over = true;
return;
}
draw_lives();
/* Sleep 1s to give player time to examine lives left */
rb->sleep(HZ);
}
/* Erase explosion (even if ship hit, might be another bomb) */
fire_alpha(bombs[i].x, bombs[i].y, LCD_BLACK);
}
break;
}
}
}
inline void move_ship(void)
{
ship_dir += ship_acc;
if (ship_dir > max_ship_speed) {
ship_dir = max_ship_speed;
}
if (ship_dir < -max_ship_speed) {
ship_dir = -max_ship_speed;
}
ship_x += ship_dir;
if (ship_x < SHIP_MIN_X) {
ship_x = SHIP_MIN_X;
}
if (ship_x > SHIP_MAX_X) {
ship_x = SHIP_MAX_X;
}
draw_ship();
}
/* Unidentified Flying Object */
void move_ufo(void)
{
static int ufo_speed;
static int counter;
int mystery_score;
switch (ufo_state) {
case S_IDLE:
if (rb->rand() % 500 == 0) {
/* Uh-oh, it's time to launch a mystery UFO */
/* TODO: Play UFO sound */
if (rb->rand() % 2) {
ufo_speed = UFO_SPEED;
ufo_x = PLAYFIELD_X;
} else {
ufo_speed = -UFO_SPEED;
ufo_x = LCD_WIDTH - PLAYFIELD_X - UFO_WIDTH;
}
ufo_state = S_ACTIVE;
/* UFO will be drawn next frame */
}
break;
case S_ACTIVE:
/* Erase old pos */
rb->lcd_fillrect(ufo_x, UFO_Y, UFO_WIDTH, UFO_HEIGHT);
/* Move */
ufo_x += ufo_speed;
/* Check bounds */
if (ufo_x < PLAYFIELD_X || ufo_x > LCD_WIDTH - PLAYFIELD_X - UFO_WIDTH) {
ufo_state = S_IDLE;
break;
}
/* Draw new pos */
rb->lcd_bitmap_part(invadrox, UFO_SRC_X, 0,
STRIDE, ufo_x, UFO_Y, UFO_WIDTH, UFO_HEIGHT);
break;
case S_SHOWSCORE:
counter++;
if (counter == S_IDLE) {
/* Erase mystery number */
rb->lcd_fillrect(ufo_x, UFO_Y, 3 * NUMBERS_WIDTH + 2 * NUM_SPACING, FONT_HEIGHT);
ufo_state = S_IDLE;
}
break;
default:
/* Exploding */
ufo_state++;
if (ufo_state == S_IDLE) {
/* Erase explosion */
rb->lcd_fillrect(ufo_x, UFO_Y - 1, UFO_EXPLODE_WIDTH, UFO_EXPLODE_HEIGHT);
ufo_state = S_SHOWSCORE;
counter = S_EXPLODE * 4;
/* Draw mystery_score, sleep, increase score and continue */
mystery_score = 50 + (rb->rand() % 6) * 50;
if (mystery_score < 100) {
draw_number(ufo_x, UFO_Y, mystery_score, 2);
} else {
draw_number(ufo_x, UFO_Y, mystery_score, 3);
}
score += mystery_score;
draw_score();
}
break;
}
}
void draw_background(void)
{
#if (LCD_WIDTH == 320) && (LCD_HEIGHT == 240)
/* Erase background to black */
rb->lcd_fillrect(PLAYFIELD_X, 0, PLAYFIELD_WIDTH, LCD_HEIGHT);
/* Left and right bitmaps */
rb->lcd_bitmap(invadrox_left, 0, 0, PLAYFIELD_X, LCD_HEIGHT);
rb->lcd_bitmap(invadrox_right, LCD_WIDTH - PLAYFIELD_X, 0, PLAYFIELD_X, LCD_HEIGHT);
#else
rb->lcd_fillrect(0, 0, LCD_WIDTH, LCD_HEIGHT);
#endif
#if ((LCD_WIDTH == 240) && (LCD_HEIGHT == 320)) || ((LCD_WIDTH == 176) && (LCD_HEIGHT == 220))
rb->lcd_bitmap(invadrox_logo, 0, 0, LCD_WIDTH, SCORE_Y);
#endif
rb->lcd_update();
}
void new_level(void)
{
int i;
draw_background();
/* Give an extra life for each new level */
if (lives < MAX_LIVES) {
lives++;
}
draw_lives();
/* Score */
rb->lcd_bitmap_part(invadrox, SCORE_SRC_X, SCORE_SRC_Y,
STRIDE, PLAYFIELD_X, SCORE_Y, SCORE_WIDTH, FONT_HEIGHT);
/* Hi-score */
rb->lcd_bitmap_part(invadrox, 0, SCORE_SRC_Y,
STRIDE, HISCORE_X, SCORE_Y,
HISCORE_WIDTH, FONT_HEIGHT);
draw_score();
draw_number(HISCORENUM_X, SCORENUM_Y, hiscore.score, 4);
level++;
draw_level();
level_finished = false;
ufo_state = S_IDLE;
/* Init alien positions and states */
for (i = 0; i < 4 * ALIENS; i++) {
aliens[i].x = 0 + (i % ALIENS) * ((ALIEN_WIDTH + ALIEN_SPACING) / ALIEN_SPEED);
aliens[i].y = 2 * (i / ALIENS);
aliens[i].state = ALIVE;
}
/* Last row, bombers */
for (i = 4 * ALIENS; i < 5 * ALIENS; i++) {
aliens[i].x = 0 + (i % ALIENS) * ((ALIEN_WIDTH + ALIEN_SPACING) / ALIEN_SPEED);
aliens[i].y = 2 * (i / ALIENS);
aliens[i].state = BOMBER;
}
/* Init bombs to inactive (S_IDLE) */
for (i = 0; i < MAX_BOMBS; i++) {
bombs[i].state = S_IDLE;
}
/* Start aliens closer to earth from level 2 */
for (i = 0; i < 5 * ALIENS; i++) {
if (level < 6) {
aliens[i].y += level - 1;
} else {
aliens[i].y += 5;
}
}
/* Max concurrent bombs */
max_bombs = 1;
gamespeed = 2;
if (level > 1) {
max_bombs++;
}
/* Increase speed */
if (level > 2) {
gamespeed++;
}
if (level > 3) {
max_bombs++;
}
/* Increase speed more */
if (level > 4) {
gamespeed++;
}
if (level > 5) {
max_bombs++;
}
/* 4 shields */
for (i = 1; i <= 4; i++) {
rb->lcd_bitmap_part(invadrox, SHIELD_SRC_X, SHIELD_SRC_Y, STRIDE,
PLAYFIELD_X + i * PLAYFIELD_WIDTH / 5 - SHIELD_WIDTH / 2,
SHIELD_Y, SHIELD_WIDTH, SHIELD_HEIGHT);
}
/* Bottom line */
rb->lcd_set_foreground(SLIME_GREEN);
rb->lcd_hline(PLAYFIELD_X, LCD_WIDTH - PLAYFIELD_X, PLAYFIELD_Y);
/* Restore foreground to black (for fast erase later). */
rb->lcd_set_foreground(LCD_BLACK);
ship_x = PLAYFIELD_X + 2 * LIVES_X;
if (level == 1) {
old_ship_x = ship_x;
}
ship_dir = 0;
ship_acc = 0;
ship_frame = 0;
ship_hit = false;
fire = S_IDLE;
/* Start moving the bottom row left to right */
curr_alien = 4 * ALIENS;
aliens_paralyzed = 0;
aliens_right = true;
aliens_down = false;
hit_left_border = false;
hit_right_border = false;
/* TODO: Change max_ship_speed to 3 at higher levels */
max_ship_speed = 2;
draw_aliens();
rb->lcd_update();
}
void init_invadrox(void)
{
int i;
/* Seed random number generator with a "random" number */
rb->srand(rb->get_time()->tm_sec + rb->get_time()->tm_min * 60);
/* Precalculate start of each scanline */
for (i = 0; i < LCD_HEIGHT; i++) {
#if (LCD_DEPTH >= 8)
ytab[i] = i * LCD_WIDTH;
#elif (LCD_DEPTH == 2) && (LCD_PIXELFORMAT == HORIZONTAL_PACKING)
ytab[i] = i * (LCD_WIDTH / 4);
#elif (LCD_DEPTH == 2) && (LCD_PIXELFORMAT == VERTICAL_PACKING)
ytab[i] = (i / 4) * LCD_WIDTH;
#else
#error pixelformat not implemented yet
#endif
}
rb->lcd_set_background(LCD_BLACK);
rb->lcd_set_foreground(LCD_BLACK);
highscore_init(rb);
if (highscore_load(HISCOREFILE, &hiscore, 1) < 0) {
/* Init hiscore to 0 */
rb->strncpy(hiscore.name, "Invader", sizeof(hiscore.name));
hiscore.score = 0;
hiscore.level = 1;
}
/* Init alien types in aliens array */
for (i = 0; i < 1 * ALIENS; i++) {
aliens[i].type = 0; /* Kang */
}
for (; i < 3 * ALIENS; i++) {
aliens[i].type = 1; /* Kodos */
}
for (; i < 5 * ALIENS; i++) {
aliens[i].type = 2; /* Serak */
}
/* Save screen white color */
rb->lcd_set_foreground(LCD_WHITE);
rb->lcd_drawpixel(0, 0);
rb->lcd_update_rect(0, 0, 1, 1);
screen_white = get_pixel(0, 0);
/* Save screen green color */
rb->lcd_set_foreground(SLIME_GREEN);
rb->lcd_drawpixel(0, 0);
rb->lcd_update_rect(0, 0, 1, 1);
screen_green = get_pixel(0, 0);
/* Restore black foreground */
rb->lcd_set_foreground(LCD_BLACK);
new_level();
/* Flash score at start */
for (i = 0; i < 5; i++) {
rb->lcd_fillrect(SCORENUM_X, SCORENUM_Y,
4 * NUMBERS_WIDTH + 3 * NUM_SPACING,
FONT_HEIGHT);
rb->lcd_update_rect(SCORENUM_X, SCORENUM_Y,
4 * NUMBERS_WIDTH + 3 * NUM_SPACING,
FONT_HEIGHT);
rb->sleep(HZ / 10);
draw_number(SCORENUM_X, SCORENUM_Y, score, 4);
rb->sleep(HZ / 10);
}
}
inline bool handle_buttons(void)
{
static unsigned int oldbuttonstate = 0;
unsigned int released, pressed, newbuttonstate;
if (ship_hit) {
/* Don't allow ship movement during explosion */
newbuttonstate = 0;
} else {
newbuttonstate = rb->button_status();
}
if(newbuttonstate == oldbuttonstate) {
if (newbuttonstate == 0) {
/* No button pressed. Stop ship. */
ship_acc = 0;
if (ship_dir > 0) {
ship_dir--;
}
if (ship_dir < 0) {
ship_dir++;
}
}
/* return false; */
goto check_usb;
}
released = ~newbuttonstate & oldbuttonstate;
pressed = newbuttonstate & ~oldbuttonstate;
oldbuttonstate = newbuttonstate;
if (pressed) {
if (pressed & LEFT) {
if (ship_acc > -1) {
ship_acc--;
}
}
if (pressed & RIGHT) {
if (ship_acc < 1) {
ship_acc++;
}
}
if (pressed & FIRE) {
if (fire == S_IDLE) {
/* Fire shot */
fire_x = ship_x + SHIP_WIDTH / 2;
fire_y = SHIP_Y - SHOT_HEIGHT;
fire = S_ACTIVE;
/* TODO: play fire sound */
}
}
#ifdef RC_QUIT
if (pressed & RC_QUIT) {
rb->splash(HZ * 1, "Quit");
return true;
}
#endif
if (pressed & QUIT) {
rb->splash(HZ * 1, "Quit");
return true;
}
}
if (released) {
if ((released & LEFT)) {
if (ship_acc < 1) {
ship_acc++;
}
}
if ((released & RIGHT)) {
if (ship_acc > -1) {
ship_acc--;
}
}
}
check_usb:
/* Quit if USB is connected */
if (rb->button_get(false) == SYS_USB_CONNECTED) {
return true;
}
return false;
}
void game_loop(void)
{
int i, end;
/* Print dimensions (just for debugging) */
DBG("%03dx%03dx%02d\n", LCD_WIDTH, LCD_HEIGHT, LCD_DEPTH);
/* Init */
init_invadrox();
while (1) {
/* Convert CYCLETIME (in ms) to HZ */
end = *rb->current_tick + (CYCLETIME * HZ) / 1000;
if (handle_buttons()) {
return;
}
/* Animate */
move_ship();
move_fire();
/* Check if level is finished (marked by move_fire) */
if (level_finished) {
/* TODO: Play level finished sound */
new_level();
}
move_ufo();
/* Move aliens */
if (!aliens_paralyzed && !ship_hit) {
for (i = 0; i < gamespeed; i++) {
if (!move_aliens()) {
if (game_over) {
return;
}
}
}
}
/* Move alien bombs */
move_bombs();
if (game_over) {
return;
}
/* Update "playfield" rect */
rb->lcd_update_rect(PLAYFIELD_X, SCORENUM_Y + FONT_HEIGHT,
PLAYFIELD_WIDTH,
PLAYFIELD_Y + 1 - SCORENUM_Y - FONT_HEIGHT);
/* Wait until next frame */
DBG("%d (%d)\n", end - *rb->current_tick, (CYCLETIME * HZ) / 1000);
if (end > *rb->current_tick) {
rb->sleep(end - *rb->current_tick);
} else {
rb->yield();
}
} /* end while */
}
/* this is the plugin entry point */
enum plugin_status plugin_start(const struct plugin_api* api, UNUSED const void* parameter)
{
rb = api;
rb->lcd_setfont(FONT_SYSFIXED);
/* Turn off backlight timeout */
backlight_force_on(rb); /* backlight control in lib/helper.c */
/* now go ahead and have fun! */
game_loop();
/* Game Over. */
/* TODO: Play game over sound */
rb->splash(HZ * 2, "Game Over");
if (score > hiscore.score) {
/* Save new hiscore */
hiscore.score = score;
hiscore.level = level;
highscore_save(HISCOREFILE, &hiscore, 1);
}
/* Restore user's original backlight setting */
rb->lcd_setfont(FONT_UI);
/* Turn on backlight timeout (revert to settings) */
backlight_use_settings(rb); /* backlight control in lib/helper.c */
return PLUGIN_OK;
}
/**
* GNU Emacs settings: Kernighan & Richie coding style with
* 4 spaces indent and no tabs.
* Local Variables:
* c-file-style: "k&r"
* c-basic-offset: 4
* indent-tabs-mode: nil
* End:
*/