Use wrap-safe TIME_BEFORE/TIME_AFTER macros to compare times with current_time, inste...
[kugel-rb.git] / apps / plugins / invadrox.c
blobb3e5d164c6650d5e3b1a03364d1f0ba0f1daffd0
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2006 Albert Veli
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
20 ****************************************************************************/
22 /* Improvised creds goes to:
24 * - Anders Clausen for ingeniously inventing the name Invadrox.
25 * - Linus Nielsen-Feltzing for patiently answering n00b questions.
28 #include "plugin.h"
29 #include "lib/highscore.h"
30 #include "lib/helper.h"
32 /* bitmaps */
33 #include "pluginbitmaps/invadrox_background.h"
35 /* get dimensions for later use from the bitmaps */
36 #include "pluginbitmaps/invadrox_aliens.h"
37 #include "pluginbitmaps/invadrox_ships.h"
38 #include "pluginbitmaps/invadrox_bombs.h"
39 #include "pluginbitmaps/invadrox_alien_explode.h"
40 #include "pluginbitmaps/invadrox_shield.h"
41 #include "pluginbitmaps/invadrox_ufo.h"
42 #include "pluginbitmaps/invadrox_ufo_explode.h"
43 #include "pluginbitmaps/invadrox_numbers.h"
44 #include "pluginbitmaps/invadrox_fire.h"
45 #define ALIEN_WIDTH (BMPWIDTH_invadrox_aliens/2)
46 #define ALIEN_HEIGHT (BMPHEIGHT_invadrox_aliens/3)
47 #define SHIP_WIDTH BMPWIDTH_invadrox_ships
48 #define SHIP_HEIGHT (BMPHEIGHT_invadrox_ships/3)
49 #define BOMB_WIDTH (BMPWIDTH_invadrox_bombs/3)
50 #define BOMB_HEIGHT (BMPHEIGHT_invadrox_bombs/6)
51 #define ALIEN_EXPLODE_WIDTH BMPWIDTH_invadrox_alien_explode
52 #define ALIEN_EXPLODE_HEIGHT BMPHEIGHT_invadrox_alien_explode
53 #define SHIELD_WIDTH BMPWIDTH_invadrox_shield
54 #define SHIELD_HEIGHT BMPHEIGHT_invadrox_shield
55 #define UFO_WIDTH BMPWIDTH_invadrox_ufo
56 #define UFO_HEIGHT BMPHEIGHT_invadrox_ufo
57 #define UFO_EXPLODE_WIDTH BMPWIDTH_invadrox_ufo_explode
58 #define UFO_EXPLODE_HEIGHT BMPHEIGHT_invadrox_ufo_explode
59 #define NUMBERS_WIDTH (BMPWIDTH_invadrox_numbers/10)
60 #define FONT_HEIGHT BMPHEIGHT_invadrox_numbers
61 #define FIRE_WIDTH BMPWIDTH_invadrox_fire
62 #define FIRE_HEIGHT BMPHEIGHT_invadrox_fire
64 PLUGIN_HEADER
66 /* Original graphics is only 1bpp so it should be portable
67 * to most targets. But for now, only support the simple ones.
69 #ifndef HAVE_LCD_BITMAP
70 #error INVADROX: Unsupported LCD
71 #endif
73 #if (LCD_DEPTH < 2)
74 #error INVADROX: Unsupported LCD
75 #endif
77 /* #define DEBUG */
78 #ifdef DEBUG
79 #define DBG(format, arg...) { DEBUGF("%s: " format, __FUNCTION__, ## arg); }
80 #else
81 #define DBG(format, arg...) {}
82 #endif
84 #if CONFIG_KEYPAD == IRIVER_H100_PAD
86 #define QUIT BUTTON_OFF
87 #define LEFT BUTTON_LEFT
88 #define RIGHT BUTTON_RIGHT
89 #define FIRE BUTTON_ON
91 #elif CONFIG_KEYPAD == IRIVER_H300_PAD
93 #define QUIT BUTTON_OFF
94 #define LEFT BUTTON_LEFT
95 #define RIGHT BUTTON_RIGHT
96 #define FIRE BUTTON_SELECT
98 #elif (CONFIG_KEYPAD == IRIVER_H10_PAD)
100 #define QUIT BUTTON_POWER
101 #define LEFT BUTTON_LEFT
102 #define RIGHT BUTTON_RIGHT
103 #define FIRE BUTTON_PLAY
105 #elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \
106 (CONFIG_KEYPAD == IPOD_3G_PAD) || \
107 (CONFIG_KEYPAD == IPOD_1G2G_PAD)
109 #define QUIT BUTTON_MENU
110 #define LEFT BUTTON_LEFT
111 #define RIGHT BUTTON_RIGHT
112 #define FIRE BUTTON_SELECT
114 #elif CONFIG_KEYPAD == IAUDIO_X5M5_PAD
116 #define QUIT BUTTON_POWER
117 #define LEFT BUTTON_LEFT
118 #define RIGHT BUTTON_RIGHT
119 #define FIRE BUTTON_SELECT
121 #elif CONFIG_KEYPAD == GIGABEAT_PAD
123 #define QUIT BUTTON_POWER
124 #define LEFT BUTTON_LEFT
125 #define RIGHT BUTTON_RIGHT
126 #define FIRE BUTTON_SELECT
128 #elif CONFIG_KEYPAD == SANSA_E200_PAD
130 #define QUIT BUTTON_POWER
131 #define LEFT BUTTON_LEFT
132 #define RIGHT BUTTON_RIGHT
133 #define FIRE BUTTON_SELECT
135 #elif CONFIG_KEYPAD == SANSA_FUZE_PAD
137 #define QUIT (BUTTON_HOME|BUTTON_REPEAT)
138 #define LEFT BUTTON_LEFT
139 #define RIGHT BUTTON_RIGHT
140 #define FIRE BUTTON_SELECT
142 #elif CONFIG_KEYPAD == ELIO_TPJ1022_PAD
144 /* TODO: Figure out which buttons to use for Tatung Elio TPJ-1022 */
145 #define QUIT BUTTON_AB
146 #define LEFT BUTTON_LEFT
147 #define RIGHT BUTTON_RIGHT
148 #define FIRE BUTTON_MENU
150 #elif CONFIG_KEYPAD == GIGABEAT_S_PAD
152 #define QUIT BUTTON_BACK
153 #define LEFT BUTTON_LEFT
154 #define RIGHT BUTTON_RIGHT
155 #define FIRE BUTTON_SELECT
157 #elif CONFIG_KEYPAD == COWOND2_PAD
159 #define QUIT BUTTON_POWER
161 #elif CONFIG_KEYPAD == IAUDIO67_PAD
163 #define QUIT BUTTON_POWER
164 #define LEFT BUTTON_LEFT
165 #define RIGHT BUTTON_RIGHT
166 #define FIRE BUTTON_PLAY
168 #elif CONFIG_KEYPAD == CREATIVEZVM_PAD
170 #define QUIT BUTTON_BACK
171 #define LEFT BUTTON_LEFT
172 #define RIGHT BUTTON_RIGHT
173 #define FIRE BUTTON_SELECT
175 #elif CONFIG_KEYPAD == ONDAVX747_PAD || \
176 CONFIG_KEYPAD == ONDAVX777_PAD || \
177 CONFIG_KEYPAD == MROBE500_PAD
179 #define QUIT BUTTON_POWER
181 #elif CONFIG_KEYPAD == SAMSUNG_YH_PAD
183 #define QUIT BUTTON_REC
184 #define LEFT BUTTON_LEFT
185 #define RIGHT BUTTON_RIGHT
186 #define FIRE BUTTON_PLAY
188 #else
189 #error INVADROX: Unsupported keypad
190 #endif
192 #ifdef HAVE_TOUCHSCREEN
193 #ifndef QUIT
194 #define QUIT BUTTON_TOPLEFT
195 #endif
196 #ifndef LEFT
197 #define LEFT BUTTON_MIDLEFT
198 #endif
199 #ifndef RIGHT
200 #define RIGHT BUTTON_MIDRIGHT
201 #endif
202 #ifndef FIRE
203 #define FIRE BUTTON_CENTER
204 #endif
205 #endif
207 #ifndef UNUSED
208 #define UNUSED __attribute__ ((unused))
209 #endif
211 /* Defines common to all models */
212 #define UFO_Y (SCORENUM_Y + FONT_HEIGHT + ALIEN_HEIGHT)
213 #define PLAYFIELD_Y (LCD_HEIGHT - SHIP_HEIGHT - 2)
214 #define PLAYFIELD_WIDTH (LCD_WIDTH - 2 * PLAYFIELD_X)
215 #define LEVEL_X (LCD_WIDTH - PLAYFIELD_X - LIVES_X - 2 * NUMBERS_WIDTH - 3 * NUM_SPACING)
216 #define SHIP_MIN_X (PLAYFIELD_X + PLAYFIELD_WIDTH / 5 - SHIELD_WIDTH / 2 - SHIP_WIDTH)
217 #define SHIP_MAX_X (PLAYFIELD_X + 4 * PLAYFIELD_WIDTH / 5 + SHIELD_WIDTH / 2)
218 /* SCORE_Y = 0 for most targets. Gigabeat redefines it later. */
219 #define SCORE_Y 0
220 #define MAX_LIVES 8
223 /* m:robe 500 defines */
224 #if ((LCD_WIDTH == 640) && (LCD_HEIGHT == 480)) || \
225 ((LCD_WIDTH == 480) && (LCD_HEIGHT == 640))
227 /* Original arcade game size 224x240, 1bpp with
228 * red overlay at top and green overlay at bottom.
230 * M:Robe 500: 640x480x16
231 * ======================
234 #define ARCADISH_GRAPHICS
235 #define PLAYFIELD_X 48
236 #define SHIP_Y (PLAYFIELD_Y - 2 * SHIP_HEIGHT)
237 #define ALIEN_START_Y (UFO_Y + ALIEN_HEIGHT)
238 #define SCORENUM_X (PLAYFIELD_X + NUMBERS_WIDTH)
239 #define SCORENUM_Y (SCORE_Y + FONT_HEIGHT + 2)
240 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 1 - 6 * NUMBERS_WIDTH - 5 * NUM_SPACING)
241 #define SHIELD_Y (PLAYFIELD_Y - 5 * SHIP_HEIGHT)
242 #define LIVES_X 10
243 #define MAX_Y 18
245 /* iPod Video defines */
246 #elif (LCD_WIDTH == 320) && (LCD_HEIGHT == 240)
248 /* Original arcade game size 224x240, 1bpp with
249 * red overlay at top and green overlay at bottom.
251 * iPod Video: 320x240x16
252 * ======================
253 * X: 48p padding at left/right gives 224p playfield in middle.
254 * 10p "border" gives 204p actual playfield. UFO use full 224p.
255 * Y: Use full 240p.
257 * MAX_X = (204 - 12) / 2 - 1 = 95
259 * Y: Score text 7 0
260 * Space 10 7
261 * Score 7 17
262 * Space 8 24
263 * 3 Ufo 7 32
264 * 2 Space Aliens start at 32 + 3 * 8 = 56
265 * 0 aliens 9*8 56 -
266 * space ~7*8 128 | 18.75 aliens space between
267 * shield 2*8 182 | first alien and ship.
268 * space 8 198 | MAX_Y = 18
269 * ship 8 206 -
270 * space 2*8 214
271 * hline 1 230 - PLAYFIELD_Y
272 * bottom border 10 240
273 * Lives and Level goes inside bottom border
276 #define ARCADISH_GRAPHICS
277 #define PLAYFIELD_X 48
278 #define SHIP_Y (PLAYFIELD_Y - 3 * SHIP_HEIGHT)
279 #define ALIEN_START_Y (UFO_Y + 3 * ALIEN_HEIGHT)
280 #define SCORENUM_X (PLAYFIELD_X + NUMBERS_WIDTH)
281 #define SCORENUM_Y SCORE_Y + (2 * (FONT_HEIGHT + 1) + 1)
282 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 1 - 6 * NUMBERS_WIDTH - 5 * NUM_SPACING)
283 #define SHIELD_Y (PLAYFIELD_Y - 6 * SHIP_HEIGHT)
284 #define LIVES_X 10
285 #define MAX_Y 18
287 #elif (LCD_WIDTH == 176) && (LCD_HEIGHT == 220)
289 /* Sandisk Sansa e200: 176x220x16
290 * ==============================
291 * X: No padding. 8p border -> 160p playfield.
293 * 8p Aliens with 3p spacing -> 88 + 30 = 118p aliens block.
294 * (160 - 118) / 2 = 21 rounds for whole block (more than original)
295 * MAX_X = (160 - 8) / 2 - 1 = 75 rounds for single alien (less than original)
297 * LOGO 70 0
298 * Score text 5 70
299 * Space 5 75
300 * Y Score 5 80
301 * Space 10 85
302 * 2 Ufo 5 95
303 * 2 Space 10 100
304 * 0 aliens 9*5 110 -
305 * space ~7*5 155 | 18.6 aliens space between
306 * shield 2*5 188 | first alien and ship.
307 * space 5 198 | MAX_Y = 18
308 * ship 5 203 -
309 * space 5 208
310 * hline 1 213 PLAYFIELD_Y
311 * bottom border 6
312 * LCD_HEIGHT 220
313 * Lives and Level goes inside bottom border
316 #define SMALL_GRAPHICS
317 #define PLAYFIELD_X 0
318 #define SHIP_Y (PLAYFIELD_Y - 2 * SHIP_HEIGHT)
319 #define SHIELD_Y (SHIP_Y - SHIP_HEIGHT - SHIELD_HEIGHT)
320 #define ALIEN_START_Y (UFO_Y + 3 * SHIP_HEIGHT)
321 /* Redefine SCORE_Y */
322 #undef SCORE_Y
323 #define SCORE_Y 70
324 #define SCORENUM_X (PLAYFIELD_X + NUMBERS_WIDTH)
325 #define SCORENUM_Y (SCORE_Y + 2 * FONT_HEIGHT)
326 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 1 - 6 * NUMBERS_WIDTH - 5 * NUM_SPACING)
327 #define LIVES_X 8
328 #define MAX_Y 18
331 #elif (LCD_WIDTH == 176) && (LCD_HEIGHT == 132)
333 /* iPod Nano: 176x132x16
334 * ======================
335 * X: No padding. 8p border -> 160p playfield.
337 * LIVES_X 8
338 * ALIEN_WIDTH 8
339 * ALIEN_HEIGHT 5
340 * ALIEN_SPACING 3
341 * SHIP_WIDTH 10
342 * SHIP_HEIGHT 5
343 * FONT_HEIGHT 5
344 * UFO_WIDTH 10
345 * UFO_HEIGHT 5
346 * SHIELD_WIDTH 15
347 * SHIELD_HEIGHT 10
348 * MAX_X 75
349 * MAX_Y = 18
350 * ALIEN_START_Y (UFO_Y + 12)
352 * 8p Aliens with 3p spacing -> 88 + 30 = 118p aliens block.
353 * (160 - 118) / 2 = 21 rounds for whole block (more than original)
354 * MAX_X = (160 - 8) / 2 - 1 = 75 rounds for single alien (less than original)
356 * Y: Scoreline 5 0 (combine scoretext and numbers on same line)
357 * Space 5 5
358 * 1 Ufo 5 10
359 * 3 Space 7 15
360 * 2 aliens 9*5 22 -
361 * space ~7*5 67 | Just above 18 aliens space between
362 * shield 2*5 100 | first alien and ship.
363 * space 5 110 | MAX_Y = 18
364 * ship 5 115 -
365 * space 5 120
366 * hline 1 125 PLAYFIELD_Y
367 * bottom border 6 126
368 * LCD_HEIGHT 131
369 * Lives and Level goes inside bottom border
372 #define SMALL_GRAPHICS
373 #define PLAYFIELD_X 0
374 #define SHIP_Y (PLAYFIELD_Y - 2 * SHIP_HEIGHT)
375 #define ALIEN_START_Y (UFO_Y + 12)
376 #define SCORENUM_X (PLAYFIELD_X + 6 * NUMBERS_WIDTH + 5 * NUM_SPACING)
377 #define SCORENUM_Y SCORE_Y
378 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 4 * NUMBERS_WIDTH - 3 * NUM_SPACING)
379 #define SHIELD_Y (SHIP_Y - SHIP_HEIGHT - SHIELD_HEIGHT)
380 #define LIVES_X 8
381 #define MAX_Y 18
383 #elif (LCD_WIDTH == 160) && (LCD_HEIGHT == 128)
385 /* iAudio X5, iRiver H10 20Gb, iPod 3g/4g, H100, M5: 160x128
386 * =========================================================
387 * X: No padding. No border -> 160p playfield.
389 * LIVES_X 0
390 * ALIEN_WIDTH 8
391 * ALIEN_HEIGHT 5
392 * ALIEN_SPACING 3
393 * SHIP_WIDTH 10
394 * SHIP_HEIGHT 5
395 * FONT_HEIGHT 5
396 * UFO_WIDTH 10
397 * UFO_HEIGHT 5
398 * SHIELD_WIDTH 15
399 * SHIELD_HEIGHT 10
400 * MAX_X 75
401 * MAX_Y = 18
402 * ALIEN_START_Y (UFO_Y + 10)
404 * 8p Aliens with 3p spacing -> 88 + 30 = 118p aliens block.
405 * (160 - 118) / 2 = 21 rounds for whole block (more than original)
406 * MAX_X = (160 - 8) / 2 - 1 = 75 rounds for single alien (less than original)
408 * Y: Scoreline 5 0 (combine scoretext and numbers on same line)
409 * Space 5 5
410 * 1 Ufo 5 10
411 * 2 Space 5 15
412 * 8 aliens 9*5 20 -
413 * space ~6*5 65 | Just above 18 aliens space between
414 * shield 2*5 96 | first alien and ship.
415 * space 5 106 | MAX_Y = 18
416 * ship 5 111 -
417 * space 5 116
418 * hline 1 121 PLAYFIELD_Y
419 * bottom border 6 122
420 * LCD_HEIGHT 128
421 * Lives and Level goes inside bottom border
424 #define SMALL_GRAPHICS
425 #define PLAYFIELD_X 0
426 #define SHIP_Y (PLAYFIELD_Y - 2 * SHIP_HEIGHT)
427 #define ALIEN_START_Y (UFO_Y + 10)
428 #define SCORENUM_X (PLAYFIELD_X + 6 * NUMBERS_WIDTH + 5 * NUM_SPACING)
429 #define SCORENUM_Y SCORE_Y
430 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 4 * NUMBERS_WIDTH - 3 * NUM_SPACING)
431 #define SHIELD_Y (SHIP_Y - SHIP_HEIGHT - SHIELD_HEIGHT)
432 #define LIVES_X 0
433 #define MAX_Y 18
436 #elif (LCD_WIDTH == 240) && ((LCD_HEIGHT == 320) || (LCD_HEIGHT == 400))
438 /* Gigabeat: 240x320x16
439 * ======================
440 * X: 8p padding at left/right gives 224p playfield in middle.
441 * 10p "border" gives 204p actual playfield. UFO use full 224p.
442 * Y: Use bottom 240p for playfield and top 80 pixels for logo.
444 * MAX_X = (204 - 12) / 2 - 1 = 95
446 * Y: Score text 7 0 + 80
447 * Space 10 7 + 80
448 * Score 7 17 + 80
449 * Space 8 24 + 80
450 * 3 Ufo 7 32 + 80
451 * 2 Space Aliens start at 32 + 3 * 8 = 56
452 * 0 aliens 9*8 56 -
453 * space ~7*8 128 | 18.75 aliens space between
454 * shield 2*8 182 | first alien and ship.
455 * space 8 198 | MAX_Y = 18
456 * ship 8 206 -
457 * space 2*8 214
458 * hline 1 230 310 - PLAYFIELD_Y
459 * bottom border 10 240 320
460 * Lives and Level goes inside bottom border
463 #define ARCADISH_GRAPHICS
464 #define PLAYFIELD_X 8
465 #define SHIP_Y (PLAYFIELD_Y - 3 * SHIP_HEIGHT)
466 #define ALIEN_START_Y (UFO_Y + 3 * ALIEN_HEIGHT)
467 /* Redefine SCORE_Y */
468 #undef SCORE_Y
469 #define SCORE_Y 80
470 #define SCORENUM_X (PLAYFIELD_X + NUMBERS_WIDTH)
471 #define SCORENUM_Y SCORE_Y + (2 * (FONT_HEIGHT + 1) + 1)
472 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 1 - 6 * NUMBERS_WIDTH - 5 * NUM_SPACING)
473 #define SHIELD_Y (PLAYFIELD_Y - 6 * SHIP_HEIGHT)
474 #define LIVES_X 10
475 #define MAX_Y 18
477 #elif (LCD_WIDTH == 220) && (LCD_HEIGHT == 176)
479 /* TPJ1022, H300, iPod Color: 220x176x16
480 * ============================
481 * X: 0p padding at left/right gives 220p playfield in middle.
482 * 8p "border" gives 204p actual playfield. UFO use full 220p.
483 * Y: Use full 176p for playfield.
485 * MAX_X = (204 - 12) / 2 - 1 = 95
487 * Y: Score text 7 0
488 * Space 8 7
489 * 1 Ufo 7 15
490 * 7 Space Aliens start at 15 + 3 * 8 = 56
491 * 6 aliens 9*8 25 -
492 * space ~7*8 103 | 15.6 aliens space between
493 * shield 2*8 126 | first alien and ship.
494 * space 8 142 | MAX_Y = 15
495 * ship 8 150 -
496 * space 8 158
497 * hline 1 166 - PLAYFIELD_Y
498 * bottom border 10 176
499 * Lives and Level goes inside bottom border
502 #define ARCADISH_GRAPHICS
503 #define PLAYFIELD_X 0
504 #define SHIP_Y (PLAYFIELD_Y - 2 * SHIP_HEIGHT)
505 #define ALIEN_START_Y (UFO_Y + 10)
506 #define SCORENUM_Y SCORE_Y
507 #define SCORENUM_X (PLAYFIELD_X + 6 * NUMBERS_WIDTH + 6 * NUM_SPACING)
508 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 4 * NUMBERS_WIDTH - 3 * NUM_SPACING)
509 #define SHIELD_Y (PLAYFIELD_Y - 5 * SHIP_HEIGHT)
510 #define LIVES_X 8
511 #define MAX_Y 15
514 #else
515 #error INVADROX: Unsupported LCD type
516 #endif
518 #define MAX_X ((LCD_WIDTH-LIVES_X*2-PLAYFIELD_X*2 - ALIEN_WIDTH)/2 - 1)
520 /* Defines common to each "graphic type" */
521 #ifdef ARCADISH_GRAPHICS
523 #define SHOT_HEIGHT 5
524 #define ALIEN_SPACING 4
525 #define ALIEN_SPEED 2
526 #define UFO_SPEED 1
527 #define NUM_SPACING 3
528 #define FIRE_SPEED 8
529 #define BOMB_SPEED 3
530 #define ALIENS 11
532 #elif defined SMALL_GRAPHICS
534 #define SHOT_HEIGHT 4
535 #define ALIEN_SPACING 3
536 #define ALIEN_SPEED 2
537 #define UFO_SPEED 1
538 #define NUM_SPACING 2
539 #define FIRE_SPEED 6
540 #define BOMB_SPEED 2
541 #define ALIENS 11
543 #else
544 #error Graphic type not defined
545 #endif
548 /* Colors */
549 #if (LCD_DEPTH >= 8)
550 #define SLIME_GREEN LCD_RGBPACK(31, 254, 31)
551 #define UFO_RED LCD_RGBPACK(254, 31, 31)
552 #elif (LCD_DEPTH == 2)
553 #define SLIME_GREEN LCD_LIGHTGRAY
554 #define UFO_RED LCD_LIGHTGRAY
555 #else
556 #error LCD type not implemented yet
557 #endif
559 /* Alien states */
560 #define DEAD 0
561 #define ALIVE 1
562 #define BOMBER 2
564 /* Fire/bomb/ufo states */
565 #define S_IDLE 0
566 #define S_ACTIVE 1
567 #define S_SHOWSCORE 2
568 #define S_EXPLODE -9
570 /* Fire/bomb targets */
571 #define TARGET_TOP 0
572 #define TARGET_SHIELD 1
573 #define TARGET_SHIP 2
574 #define TARGET_BOTTOM 3
575 #define TARGET_UFO 4
577 #define HISCOREFILE PLUGIN_GAMES_DIR "/invadrox.high"
580 /* The time (in ms) for one iteration through the game loop - decrease this
581 * to speed up the game - note that current_tick is (currently) only accurate
582 * to 10ms.
584 #define CYCLETIME 40
587 /* Physical x is at PLAYFIELD_X + LIVES_X + x * ALIEN_SPEED
588 * Physical y is at y * ALIEN_HEIGHT
590 struct alien {
591 int x; /* x-coordinate (0 - 95) */
592 int y; /* y-coordinate (0 - 18) */
593 unsigned char type; /* 0 (Kang), 1 (Kodos), 2 (Serak) */
594 unsigned char state; /* Dead, alive or bomber */
597 /* Aliens box 5 rows * ALIENS aliens in each row */
598 struct alien aliens[5 * ALIENS];
600 #define MAX_BOMBS 4
601 struct bomb {
602 int x, y;
603 unsigned char type;
604 unsigned char frame; /* Current animation frame */
605 unsigned char frames; /* Number of frames in animation */
606 unsigned char target; /* Remember target during explosion frames */
607 int state; /* 0 (IDLE) = inactive, 1 (FIRE) or negative, exploding */
609 struct bomb bombs[MAX_BOMBS];
610 /* Increase max_bombs at higher levels */
611 int max_bombs;
613 /* Raw framebuffer value of shield/ship green color */
614 fb_data screen_green, screen_white;
616 /* For optimization, precalculate startoffset of each scanline */
617 unsigned int ytab[LCD_HEIGHT];
619 int lives = 2;
620 int score = 0;
621 int scores[3] = { 30, 20, 10 };
622 int level = 0;
623 struct highscore hiscore;
624 bool game_over = false;
625 int ship_x, old_ship_x, ship_dir, ship_acc, max_ship_speed;
626 int ship_frame, ship_frame_counter;
627 bool ship_hit;
628 int fire, fire_target, fire_x, fire_y;
629 int curr_alien, aliens_paralyzed, gamespeed;
630 int ufo_state, ufo_x;
631 bool level_finished;
632 bool aliens_down, aliens_right, hit_left_border, hit_right_border;
635 /* No standard get_pixel function yet, use this hack instead */
636 #if (LCD_DEPTH >= 8)
638 #if defined(LCD_STRIDEFORMAT) && LCD_STRIDEFORMAT == VERTICAL_STRIDE
639 inline fb_data get_pixel(int x, int y)
641 return rb->lcd_framebuffer[x*LCD_HEIGHT+y];
643 #else
644 inline fb_data get_pixel(int x, int y)
646 return rb->lcd_framebuffer[ytab[y] + x];
648 #endif
650 #elif (LCD_DEPTH == 2)
652 #if (LCD_PIXELFORMAT == HORIZONTAL_PACKING)
653 static const unsigned char shifts[4] = {
654 6, 4, 2, 0
656 /* Horizontal packing */
657 inline fb_data get_pixel(int x, int y)
659 return (rb->lcd_framebuffer[ytab[y] + (x >> 2)] >> shifts[x & 3]) & 3;
661 #else
662 /* Vertical packing */
663 static const unsigned char shifts[4] = {
664 0, 2, 4, 6
666 inline fb_data get_pixel(int x, int y)
668 return (rb->lcd_framebuffer[ytab[y] + x] >> shifts[y & 3]) & 3;
670 #endif /* Horizontal/Vertical packing */
672 #else
673 #error get_pixel: pixelformat not implemented yet
674 #endif
677 /* Draw "digits" least significant digits of num at (x,y) */
678 void draw_number(int x, int y, int num, int digits)
680 int i;
681 int d;
683 for (i = digits - 1; i >= 0; i--) {
684 d = num % 10;
685 num = num / 10;
686 rb->lcd_bitmap_part(invadrox_numbers, d * NUMBERS_WIDTH, 0,
687 STRIDE( SCREEN_MAIN,
688 BMPWIDTH_invadrox_numbers,
689 BMPHEIGHT_invadrox_numbers),
690 x + i * (NUMBERS_WIDTH + NUM_SPACING), y,
691 NUMBERS_WIDTH, FONT_HEIGHT);
693 /* Update lcd */
694 rb->lcd_update_rect(x, y, 4 * NUMBERS_WIDTH + 3 * NUM_SPACING, FONT_HEIGHT);
698 inline void draw_score(void)
700 draw_number(SCORENUM_X, SCORENUM_Y, score, 4);
701 if (score > hiscore.score) {
702 /* Draw new hiscore (same as score) */
703 draw_number(HISCORENUM_X, SCORENUM_Y, score, 4);
708 void draw_level(void)
710 draw_number(LEVEL_X + 2 * NUM_SPACING, PLAYFIELD_Y + 2, level, 2);
714 void draw_lives(void)
716 int i;
717 /* Lives num */
718 rb->lcd_bitmap_part(invadrox_numbers, lives * NUMBERS_WIDTH, 0,
719 STRIDE( SCREEN_MAIN,
720 BMPWIDTH_invadrox_numbers,
721 BMPHEIGHT_invadrox_numbers),
722 PLAYFIELD_X + LIVES_X, PLAYFIELD_Y + 2,
723 NUMBERS_WIDTH, FONT_HEIGHT);
725 /* Ships */
726 for (i = 0; i < (lives - 1); i++) {
727 rb->lcd_bitmap_part(invadrox_ships, 0, 0,
728 STRIDE( SCREEN_MAIN,
729 BMPWIDTH_invadrox_ships,
730 BMPHEIGHT_invadrox_ships),
731 PLAYFIELD_X + LIVES_X + SHIP_WIDTH + i * (SHIP_WIDTH + NUM_SPACING),
732 PLAYFIELD_Y + 1, SHIP_WIDTH, SHIP_HEIGHT);
735 /* Erase ship to the right (if less than MAX_LIVES) */
736 if (lives < MAX_LIVES) {
737 rb->lcd_fillrect(PLAYFIELD_X + LIVES_X + SHIP_WIDTH + i * (SHIP_WIDTH + NUM_SPACING),
738 PLAYFIELD_Y + 1, SHIP_WIDTH, SHIP_HEIGHT);
740 /* Update lives (and level) part of screen */
741 rb->lcd_update_rect(PLAYFIELD_X + LIVES_X, PLAYFIELD_Y + 1,
742 PLAYFIELD_WIDTH - 2 * LIVES_X, MAX(FONT_HEIGHT + 1, SHIP_HEIGHT + 1));
746 inline void draw_aliens(void)
748 int i;
750 for (i = 0; i < 5 * ALIENS; i++) {
751 rb->lcd_bitmap_part(invadrox_aliens, aliens[i].x & 1 ? ALIEN_WIDTH : 0,
752 aliens[i].type * ALIEN_HEIGHT,
753 STRIDE( SCREEN_MAIN,
754 BMPWIDTH_invadrox_aliens,
755 BMPHEIGHT_invadrox_aliens),
756 PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED,
757 ALIEN_START_Y + aliens[i].y * ALIEN_HEIGHT,
758 ALIEN_WIDTH, ALIEN_HEIGHT);
763 /* Return false if there is no next alive alien (round is over) */
764 inline bool next_alien(void)
766 bool ret = true;
768 do {
769 curr_alien++;
770 if (curr_alien % ALIENS == 0) {
771 /* End of this row. Move up one row. */
772 curr_alien -= 2 * ALIENS;
773 if (curr_alien < 0) {
774 /* No more aliens in this round. */
775 curr_alien = 4 * ALIENS;
776 ret = false;
779 } while (aliens[curr_alien].state == DEAD && ret);
781 if (!ret) {
782 /* No more alive aliens. Round finished. */
783 if (hit_right_border) {
784 if (hit_left_border) {
785 DBG("ERROR: both left and right borders are set (%d)\n", curr_alien);
787 /* Move down-left next round */
788 aliens_right = false;
789 aliens_down = true;
790 hit_right_border = false;
791 } else if (hit_left_border) {
792 /* Move down-right next round */
793 aliens_right = true;
794 aliens_down = true;
795 hit_left_border = false;
796 } else {
797 /* Not left nor right. Set down to false. */
798 aliens_down = false;
802 return ret;
806 /* All aliens have been moved.
807 * Set curr_alien to first alive.
808 * Return false if no-one is left alive.
810 bool first_alien(void)
812 int i, y;
814 for (y = 4; y >= 0; y--) {
815 for (i = y * ALIENS; i < (y + 1) * ALIENS; i++) {
816 if (aliens[i].state != DEAD) {
817 curr_alien = i;
818 return true;
823 /* All aliens dead. */
824 level_finished = true;
826 return false;
830 bool move_aliens(void)
832 int x, y, old_x, old_y;
834 /* Move current alien (curr_alien is pointing to a living alien) */
836 old_x = aliens[curr_alien].x;
837 old_y = aliens[curr_alien].y;
839 if (aliens_down) {
840 aliens[curr_alien].y++;
841 if (aliens[curr_alien].y == MAX_Y) {
842 /* Alien is at bottom. Game Over. */
843 DBG("Alien %d is at bottom. Game Over.\n", curr_alien);
844 game_over = true;
845 return false;
849 if (aliens_right) {
850 /* Moving right */
851 if (aliens[curr_alien].x < MAX_X) {
852 aliens[curr_alien].x++;
855 /* Now, after move, check if we hit the right border. */
856 if (aliens[curr_alien].x == MAX_X) {
857 hit_right_border = true;
860 } else {
861 /* Moving left */
862 if (aliens[curr_alien].x > 0) {
863 aliens[curr_alien].x--;
866 /* Now, after move, check if we hit the left border. */
867 if (aliens[curr_alien].x == 0) {
868 hit_left_border = true;
872 /* Erase old position */
873 x = PLAYFIELD_X + LIVES_X + old_x * ALIEN_SPEED;
874 y = ALIEN_START_Y + old_y * ALIEN_HEIGHT;
875 if (aliens[curr_alien].y != old_y) {
876 /* Moved in y-dir. Erase whole alien. */
877 rb->lcd_fillrect(x, y, ALIEN_WIDTH, ALIEN_HEIGHT);
878 } else {
879 if (aliens_right) {
880 /* Erase left edge */
881 rb->lcd_fillrect(x, y, ALIEN_SPEED, ALIEN_HEIGHT);
882 } else {
883 /* Erase right edge */
884 x += ALIEN_WIDTH - ALIEN_SPEED;
885 rb->lcd_fillrect(x, y, ALIEN_SPEED, ALIEN_HEIGHT);
889 /* Draw alien at new pos */
890 x = PLAYFIELD_X + LIVES_X + aliens[curr_alien].x * ALIEN_SPEED;
891 y = ALIEN_START_Y + aliens[curr_alien].y * ALIEN_HEIGHT;
892 rb->lcd_bitmap_part(invadrox_aliens,
893 aliens[curr_alien].x & 1 ? ALIEN_WIDTH : 0,
894 aliens[curr_alien].type * ALIEN_HEIGHT,
895 STRIDE( SCREEN_MAIN,
896 BMPWIDTH_invadrox_aliens,
897 BMPHEIGHT_invadrox_aliens),
898 x, y, ALIEN_WIDTH, ALIEN_HEIGHT);
900 if (!next_alien()) {
901 /* Round finished. Set curr_alien to first alive from bottom. */
902 if (!first_alien()) {
903 /* Should never happen. Taken care of in move_fire(). */
904 return false;
906 /* TODO: Play next background sound */
909 return true;
913 inline void draw_ship(void)
915 /* Erase old ship */
916 if (old_ship_x < ship_x) {
917 /* Move right. Erase leftmost part of ship. */
918 rb->lcd_fillrect(old_ship_x, SHIP_Y, ship_x - old_ship_x, SHIP_HEIGHT);
919 } else if (old_ship_x > ship_x) {
920 /* Move left. Erase rightmost part of ship. */
921 rb->lcd_fillrect(ship_x + SHIP_WIDTH, SHIP_Y, old_ship_x - ship_x, SHIP_HEIGHT);
924 /* Draw ship */
925 rb->lcd_bitmap_part(invadrox_ships, 0, ship_frame * SHIP_HEIGHT,
926 STRIDE( SCREEN_MAIN,
927 BMPWIDTH_invadrox_ships,
928 BMPHEIGHT_invadrox_ships),
929 ship_x, SHIP_Y, SHIP_WIDTH, SHIP_HEIGHT);
930 if (ship_hit) {
931 /* Alternate between frame 1 and 2 during hit */
932 ship_frame_counter++;
933 if (ship_frame_counter > 2) {
934 ship_frame_counter = 0;
935 ship_frame++;
936 if (ship_frame > 2) {
937 ship_frame = 1;
942 /* Save ship_x for next time */
943 old_ship_x = ship_x;
947 inline void fire_alpha(int xc, int yc, fb_data color)
949 int oldmode = rb->lcd_get_drawmode();
951 rb->lcd_set_foreground(color);
952 rb->lcd_set_drawmode(DRMODE_FG);
954 rb->lcd_mono_bitmap(invadrox_fire, xc - (FIRE_WIDTH/2), yc, FIRE_WIDTH, FIRE_HEIGHT);
956 rb->lcd_set_foreground(LCD_BLACK);
957 rb->lcd_set_drawmode(oldmode);
961 void move_fire(void)
963 bool hit_green = false;
964 bool hit_white = false;
965 int i, j;
966 static int exploding_alien = -1;
967 fb_data pix;
969 if (fire == S_IDLE) {
970 return;
973 /* Alien hit. Wait until explosion is finished. */
974 if (aliens_paralyzed < 0) {
975 aliens_paralyzed++;
976 if (aliens_paralyzed == 0) {
977 /* Erase exploding_alien */
978 rb->lcd_fillrect(PLAYFIELD_X + LIVES_X + aliens[exploding_alien].x * ALIEN_SPEED,
979 ALIEN_START_Y + aliens[exploding_alien].y * ALIEN_HEIGHT,
980 ALIEN_EXPLODE_WIDTH, ALIEN_HEIGHT);
981 fire = S_IDLE;
982 /* Special case. We killed curr_alien. */
983 if (exploding_alien == curr_alien) {
984 if (!next_alien()) {
985 /* Round finished. Set curr_alien to first alive from bottom. */
986 first_alien();
990 return;
993 if (fire == S_ACTIVE) {
995 /* Erase */
996 rb->lcd_vline(fire_x, fire_y, fire_y + SHOT_HEIGHT);
998 /* Check top */
999 if (fire_y <= SCORENUM_Y + FONT_HEIGHT + 4) {
1001 /* TODO: Play explode sound */
1003 fire = S_EXPLODE;
1004 fire_target = TARGET_TOP;
1005 fire_alpha(fire_x, fire_y, UFO_RED);
1006 return;
1009 /* Move */
1010 fire_y -= FIRE_SPEED;
1012 /* Hit UFO? */
1013 if (ufo_state == S_ACTIVE) {
1014 if ((ABS(ufo_x + UFO_WIDTH / 2 - fire_x) <= UFO_WIDTH / 2) &&
1015 (fire_y <= UFO_Y + UFO_HEIGHT)) {
1016 ufo_state = S_EXPLODE;
1017 fire = S_EXPLODE;
1018 fire_target = TARGET_UFO;
1019 /* Center explosion */
1020 ufo_x -= (UFO_EXPLODE_WIDTH - UFO_WIDTH) / 2;
1021 rb->lcd_bitmap(invadrox_ufo_explode, ufo_x, UFO_Y - 1,
1022 UFO_EXPLODE_WIDTH, UFO_EXPLODE_HEIGHT);
1023 return;
1027 /* Hit bomb? (check position, not pixel value) */
1028 for (i = 0; i < max_bombs; i++) {
1029 if (bombs[i].state == S_ACTIVE) {
1030 /* Count as hit if within BOMB_WIDTH pixels */
1031 if ((ABS(bombs[i].x - fire_x) < BOMB_WIDTH) &&
1032 (fire_y - bombs[i].y < BOMB_HEIGHT)) {
1033 /* Erase bomb */
1034 rb->lcd_fillrect(bombs[i].x, bombs[i].y, BOMB_WIDTH, BOMB_HEIGHT);
1035 bombs[i].state = S_IDLE;
1036 /* Explode ship fire */
1037 fire = S_EXPLODE;
1038 fire_target = TARGET_SHIELD;
1039 fire_alpha(fire_x, fire_y, LCD_WHITE);
1040 return;
1045 /* Check for hit*/
1046 for (i = FIRE_SPEED; i >= 0; i--) {
1047 pix = get_pixel(fire_x, fire_y + i);
1048 if(pix == screen_white) {
1049 hit_white = true;
1050 fire_y += i;
1051 break;
1053 if(pix == screen_green) {
1054 hit_green = true;
1055 fire_y += i;
1056 break;
1060 if (hit_green) {
1061 /* Hit shield */
1063 /* TODO: Play explode sound */
1065 fire = S_EXPLODE;
1066 fire_target = TARGET_SHIELD;
1067 /* Center explosion around hit pixel */
1068 fire_y -= FIRE_HEIGHT / 2;
1069 fire_alpha(fire_x, fire_y, SLIME_GREEN);
1070 return;
1073 if (hit_white) {
1075 /* Hit alien? */
1076 for (i = 0; i < 5 * ALIENS; i++) {
1077 if (aliens[i].state != DEAD &&
1078 (ABS(fire_x - (PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED +
1079 ALIEN_WIDTH / 2)) <= ALIEN_WIDTH / 2) &&
1080 (ABS(fire_y - (ALIEN_START_Y + aliens[i].y * ALIEN_HEIGHT +
1081 ALIEN_HEIGHT / 2)) <= ALIEN_HEIGHT / 2)) {
1083 /* TODO: play alien hit sound */
1085 if (aliens[i].state == BOMBER) {
1086 /* Set (possible) alien above to bomber */
1087 for (j = i - ALIENS; j >= 0; j -= ALIENS) {
1088 if (aliens[j].state != DEAD) {
1089 /* printf("New bomber (%d, %d)\n", j % ALIENS, j / ALIENS); */
1090 aliens[j].state = BOMBER;
1091 break;
1095 aliens[i].state = DEAD;
1096 exploding_alien = i;
1097 score += scores[aliens[i].type];
1098 draw_score();
1099 /* Update score part of screen */
1100 rb->lcd_update_rect(SCORENUM_X, SCORENUM_Y,
1101 PLAYFIELD_WIDTH - 2 * NUMBERS_WIDTH, FONT_HEIGHT);
1103 /* Paralyze aliens S_EXPLODE frames */
1104 aliens_paralyzed = S_EXPLODE;
1105 rb->lcd_bitmap(invadrox_alien_explode,
1106 PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED,
1107 ALIEN_START_Y + aliens[i].y * ALIEN_HEIGHT,
1108 ALIEN_EXPLODE_WIDTH, ALIEN_EXPLODE_HEIGHT);
1109 /* Since alien is 1 pixel taller than explosion sprite, erase bottom line */
1110 rb->lcd_hline(PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED,
1111 PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED + ALIEN_WIDTH,
1112 ALIEN_START_Y + (aliens[i].y + 1) * ALIEN_HEIGHT - 1);
1113 return;
1118 /* Draw shot */
1119 rb->lcd_set_foreground(LCD_WHITE);
1120 rb->lcd_vline(fire_x, fire_y, fire_y + SHOT_HEIGHT);
1121 rb->lcd_set_foreground(LCD_BLACK);
1122 } else if (fire < S_IDLE) {
1123 /* Count up towards S_IDLE, then erase explosion */
1124 fire++;
1125 if (fire == S_IDLE) {
1126 /* Erase explosion */
1127 if (fire_target == TARGET_TOP) {
1128 rb->lcd_fillrect(fire_x - (FIRE_WIDTH / 2), fire_y, FIRE_WIDTH, FIRE_HEIGHT);
1129 } else if (fire_target == TARGET_SHIELD) {
1130 /* Draw explosion with black pixels */
1131 fire_alpha(fire_x, fire_y, LCD_BLACK);
1138 /* Return a BOMBER alien */
1139 inline int random_bomber(void)
1141 int i, col;
1143 /* TODO: Weigh higher probability near ship */
1144 col = rb->rand() % ALIENS;
1145 for (i = col + 4 * ALIENS; i >= 0; i -= ALIENS) {
1146 if (aliens[i].state == BOMBER) {
1147 return i;
1151 /* No BOMBER found in this col */
1153 for (i = 0; i < 5 * ALIENS; i++) {
1154 if (aliens[i].state == BOMBER) {
1155 return i;
1159 /* No BOMBER found at all (error?) */
1161 return -1;
1165 inline void draw_bomb(int i)
1167 rb->lcd_bitmap_part(invadrox_bombs, bombs[i].type * BOMB_WIDTH,
1168 bombs[i].frame * BOMB_HEIGHT,
1169 STRIDE( SCREEN_MAIN,
1170 BMPWIDTH_invadrox_bombs,
1171 BMPHEIGHT_invadrox_bombs),
1172 bombs[i].x, bombs[i].y,
1173 BOMB_WIDTH, BOMB_HEIGHT);
1174 /* Advance frame */
1175 bombs[i].frame++;
1176 if (bombs[i].frame == bombs[i].frames) {
1177 bombs[i].frame = 0;
1182 void move_bombs(void)
1184 int i, j, bomber;
1185 bool abort;
1187 for (i = 0; i < max_bombs; i++) {
1189 switch (bombs[i].state) {
1191 case S_IDLE:
1192 if (ship_hit) {
1193 continue;
1195 bomber = random_bomber();
1196 if (bomber < 0) {
1197 DBG("ERROR: No bomber available\n");
1198 continue;
1200 /* x, y */
1201 bombs[i].x = PLAYFIELD_X + LIVES_X + aliens[bomber].x * ALIEN_SPEED + ALIEN_WIDTH / 2;
1202 bombs[i].y = ALIEN_START_Y + (aliens[bomber].y + 1) * ALIEN_HEIGHT;
1204 /* Check for duplets in x and y direction */
1205 abort = false;
1206 for (j = i - 1; j >= 0; j--) {
1207 if ((bombs[j].state == S_ACTIVE) &&
1208 ((bombs[i].x == bombs[j].x) || (bombs[i].y == bombs[j].y))) {
1209 abort = true;
1210 break;
1213 if (abort) {
1214 /* Skip this one, continue with next bomb */
1215 /* printf("Bomb %d duplet of %d\n", i, j); */
1216 continue;
1219 /* Passed, set type */
1220 bombs[i].type = rb->rand() % 3;
1221 bombs[i].frame = 0;
1222 if (bombs[i].type == 0) {
1223 bombs[i].frames = 3;
1224 } else if (bombs[i].type == 1) {
1225 bombs[i].frames = 4;
1226 } else {
1227 bombs[i].frames = 6;
1230 /* Bombs away */
1231 bombs[i].state = S_ACTIVE;
1232 draw_bomb(i);
1233 continue;
1235 break;
1237 case S_ACTIVE:
1238 /* Erase old position */
1239 rb->lcd_fillrect(bombs[i].x, bombs[i].y, BOMB_WIDTH, BOMB_HEIGHT);
1241 /* Move */
1242 bombs[i].y += BOMB_SPEED;
1244 /* Check if bottom hit */
1245 if (bombs[i].y + BOMB_HEIGHT >= PLAYFIELD_Y) {
1246 bombs[i].y = PLAYFIELD_Y - FIRE_HEIGHT + 1;
1247 fire_alpha(bombs[i].x, bombs[i].y, LCD_WHITE);
1248 bombs[i].state = S_EXPLODE;
1249 bombs[i].target = TARGET_BOTTOM;
1250 break;
1253 /* Check for green (ship or shield) */
1254 for (j = BOMB_HEIGHT; j >= BOMB_HEIGHT - BOMB_SPEED; j--) {
1255 bombs[i].target = 0;
1256 if(get_pixel(bombs[i].x + BOMB_WIDTH / 2, bombs[i].y + j) == screen_green) {
1257 /* Move to hit pixel */
1258 bombs[i].x += BOMB_WIDTH / 2;
1259 bombs[i].y += j;
1261 /* Check if ship is hit */
1262 if (bombs[i].y > SHIELD_Y + SHIELD_HEIGHT && bombs[i].y < PLAYFIELD_Y) {
1264 /* TODO: play ship hit sound */
1266 ship_hit = true;
1267 ship_frame = 1;
1268 ship_frame_counter = 0;
1269 bombs[i].state = S_EXPLODE * 4;
1270 bombs[i].target = TARGET_SHIP;
1271 rb->lcd_bitmap_part(invadrox_ships, 0, 1 * SHIP_HEIGHT,
1272 STRIDE( SCREEN_MAIN,
1273 BMPWIDTH_invadrox_ships,
1274 BMPHEIGHT_invadrox_ships),
1275 ship_x, SHIP_Y,
1276 SHIP_WIDTH, SHIP_HEIGHT);
1277 break;
1279 /* Shield hit */
1280 bombs[i].state = S_EXPLODE;
1281 bombs[i].target = TARGET_SHIELD;
1282 /* Center explosion around hit pixel in shield */
1283 bombs[i].y -= FIRE_HEIGHT / 2;
1284 fire_alpha(bombs[i].x, bombs[i].y, SLIME_GREEN);
1285 break;
1289 if (bombs[i].target != 0) {
1290 /* Hit ship or shield, continue */
1291 continue;
1294 draw_bomb(i);
1295 break;
1297 default:
1298 /* If we get here state should be < 0, exploding */
1299 bombs[i].state++;
1300 if (bombs[i].state == S_IDLE) {
1301 if (ship_hit) {
1302 /* Erase explosion */
1303 rb->lcd_fillrect(ship_x, SHIP_Y, SHIP_WIDTH, SHIP_HEIGHT);
1304 rb->lcd_update_rect(ship_x, SHIP_Y, SHIP_WIDTH, SHIP_HEIGHT);
1305 ship_hit = false;
1306 ship_frame = 0;
1307 ship_x = PLAYFIELD_X + 2 * LIVES_X;
1308 lives--;
1309 if (lives == 0) {
1310 game_over = true;
1311 return;
1313 draw_lives();
1314 /* Sleep 1s to give player time to examine lives left */
1315 rb->sleep(HZ);
1317 /* Erase explosion (even if ship hit, might be another bomb) */
1318 fire_alpha(bombs[i].x, bombs[i].y, LCD_BLACK);
1320 break;
1326 inline void move_ship(void)
1328 ship_dir += ship_acc;
1329 if (ship_dir > max_ship_speed) {
1330 ship_dir = max_ship_speed;
1332 if (ship_dir < -max_ship_speed) {
1333 ship_dir = -max_ship_speed;
1335 ship_x += ship_dir;
1336 if (ship_x < SHIP_MIN_X) {
1337 ship_x = SHIP_MIN_X;
1339 if (ship_x > SHIP_MAX_X) {
1340 ship_x = SHIP_MAX_X;
1343 draw_ship();
1347 /* Unidentified Flying Object */
1348 void move_ufo(void)
1350 static int ufo_speed;
1351 static int counter;
1352 int mystery_score;
1354 switch (ufo_state) {
1356 case S_IDLE:
1358 if (rb->rand() % 500 == 0) {
1359 /* Uh-oh, it's time to launch a mystery UFO */
1361 /* TODO: Play UFO sound */
1363 if (rb->rand() % 2) {
1364 ufo_speed = UFO_SPEED;
1365 ufo_x = PLAYFIELD_X;
1366 } else {
1367 ufo_speed = -UFO_SPEED;
1368 ufo_x = LCD_WIDTH - PLAYFIELD_X - UFO_WIDTH;
1370 ufo_state = S_ACTIVE;
1371 /* UFO will be drawn next frame */
1373 break;
1375 case S_ACTIVE:
1376 /* Erase old pos */
1377 rb->lcd_fillrect(ufo_x, UFO_Y, UFO_WIDTH, UFO_HEIGHT);
1378 /* Move */
1379 ufo_x += ufo_speed;
1380 /* Check bounds */
1381 if (ufo_x < PLAYFIELD_X || ufo_x > LCD_WIDTH - PLAYFIELD_X - UFO_WIDTH) {
1382 ufo_state = S_IDLE;
1383 break;
1385 /* Draw new pos */
1386 rb->lcd_bitmap(invadrox_ufo, ufo_x, UFO_Y, UFO_WIDTH, UFO_HEIGHT);
1387 break;
1389 case S_SHOWSCORE:
1390 counter++;
1391 if (counter == S_IDLE) {
1392 /* Erase mystery number */
1393 rb->lcd_fillrect(ufo_x, UFO_Y, 3 * NUMBERS_WIDTH + 2 * NUM_SPACING, FONT_HEIGHT);
1394 ufo_state = S_IDLE;
1396 break;
1398 default:
1399 /* Exploding */
1400 ufo_state++;
1401 if (ufo_state == S_IDLE) {
1402 /* Erase explosion */
1403 rb->lcd_fillrect(ufo_x, UFO_Y - 1, UFO_EXPLODE_WIDTH, UFO_EXPLODE_HEIGHT);
1404 ufo_state = S_SHOWSCORE;
1405 counter = S_EXPLODE * 4;
1406 /* Draw mystery_score, sleep, increase score and continue */
1407 mystery_score = 50 + (rb->rand() % 6) * 50;
1408 if (mystery_score < 100) {
1409 draw_number(ufo_x, UFO_Y, mystery_score, 2);
1410 } else {
1411 draw_number(ufo_x, UFO_Y, mystery_score, 3);
1413 score += mystery_score;
1414 draw_score();
1416 break;
1421 void draw_background(void)
1424 rb->lcd_bitmap(invadrox_background, 0, 0, LCD_WIDTH, LCD_HEIGHT);
1425 rb->lcd_update();
1429 void new_level(void)
1431 int i;
1433 draw_background();
1434 /* Give an extra life for each new level */
1435 if (lives < MAX_LIVES) {
1436 lives++;
1438 draw_lives();
1440 /* Score */
1441 draw_score();
1442 draw_number(HISCORENUM_X, SCORENUM_Y, hiscore.score, 4);
1444 level++;
1445 draw_level();
1446 level_finished = false;
1448 ufo_state = S_IDLE;
1450 /* Init alien positions and states */
1451 for (i = 0; i < 4 * ALIENS; i++) {
1452 aliens[i].x = 0 + (i % ALIENS) * ((ALIEN_WIDTH + ALIEN_SPACING) / ALIEN_SPEED);
1453 aliens[i].y = 2 * (i / ALIENS);
1454 aliens[i].state = ALIVE;
1456 /* Last row, bombers */
1457 for (i = 4 * ALIENS; i < 5 * ALIENS; i++) {
1458 aliens[i].x = 0 + (i % ALIENS) * ((ALIEN_WIDTH + ALIEN_SPACING) / ALIEN_SPEED);
1459 aliens[i].y = 2 * (i / ALIENS);
1460 aliens[i].state = BOMBER;
1463 /* Init bombs to inactive (S_IDLE) */
1464 for (i = 0; i < MAX_BOMBS; i++) {
1465 bombs[i].state = S_IDLE;
1468 /* Start aliens closer to earth from level 2 */
1469 for (i = 0; i < 5 * ALIENS; i++) {
1470 if (level < 6) {
1471 aliens[i].y += level - 1;
1472 } else {
1473 aliens[i].y += 5;
1477 /* Max concurrent bombs */
1478 max_bombs = 1;
1480 gamespeed = 2;
1482 if (level > 1) {
1483 max_bombs++;
1486 /* Increase speed */
1487 if (level > 2) {
1488 gamespeed++;
1491 if (level > 3) {
1492 max_bombs++;
1495 /* Increase speed more */
1496 if (level > 4) {
1497 gamespeed++;
1500 if (level > 5) {
1501 max_bombs++;
1504 /* 4 shields */
1505 for (i = 1; i <= 4; i++) {
1506 rb->lcd_bitmap(invadrox_shield,
1507 PLAYFIELD_X + i * PLAYFIELD_WIDTH / 5 - SHIELD_WIDTH / 2,
1508 SHIELD_Y, SHIELD_WIDTH, SHIELD_HEIGHT);
1511 /* Bottom line */
1512 rb->lcd_set_foreground(SLIME_GREEN);
1513 rb->lcd_hline(PLAYFIELD_X, LCD_WIDTH - PLAYFIELD_X, PLAYFIELD_Y);
1514 /* Restore foreground to black (for fast erase later). */
1515 rb->lcd_set_foreground(LCD_BLACK);
1517 ship_x = PLAYFIELD_X + 2 * LIVES_X;
1518 if (level == 1) {
1519 old_ship_x = ship_x;
1521 ship_dir = 0;
1522 ship_acc = 0;
1523 ship_frame = 0;
1524 ship_hit = false;
1525 fire = S_IDLE;
1526 /* Start moving the bottom row left to right */
1527 curr_alien = 4 * ALIENS;
1528 aliens_paralyzed = 0;
1529 aliens_right = true;
1530 aliens_down = false;
1531 hit_left_border = false;
1532 hit_right_border = false;
1533 /* TODO: Change max_ship_speed to 3 at higher levels */
1534 max_ship_speed = 2;
1536 draw_aliens();
1538 rb->lcd_update();
1542 void init_invadrox(void)
1544 int i;
1546 /* Seed random number generator with a "random" number */
1547 rb->srand(rb->get_time()->tm_sec + rb->get_time()->tm_min * 60);
1549 /* Precalculate start of each scanline */
1550 for (i = 0; i < LCD_HEIGHT; i++) {
1551 #if (LCD_DEPTH >= 8)
1552 ytab[i] = i * LCD_WIDTH;
1553 #elif (LCD_DEPTH == 2) && (LCD_PIXELFORMAT == HORIZONTAL_PACKING)
1554 ytab[i] = i * (LCD_WIDTH / 4);
1555 #elif (LCD_DEPTH == 2) && (LCD_PIXELFORMAT == VERTICAL_PACKING)
1556 ytab[i] = (i / 4) * LCD_WIDTH;
1557 #else
1558 #error pixelformat not implemented yet
1559 #endif
1562 rb->lcd_set_background(LCD_BLACK);
1563 rb->lcd_set_foreground(LCD_BLACK);
1565 if (highscore_load(HISCOREFILE, &hiscore, 1) < 0) {
1566 /* Init hiscore to 0 */
1567 rb->strlcpy(hiscore.name, "Invader", sizeof(hiscore.name));
1568 hiscore.score = 0;
1569 hiscore.level = 1;
1572 /* Init alien types in aliens array */
1573 for (i = 0; i < 1 * ALIENS; i++) {
1574 aliens[i].type = 0; /* Kang */
1576 for (; i < 3 * ALIENS; i++) {
1577 aliens[i].type = 1; /* Kodos */
1579 for (; i < 5 * ALIENS; i++) {
1580 aliens[i].type = 2; /* Serak */
1584 /* Save screen white color */
1585 rb->lcd_set_foreground(LCD_WHITE);
1586 rb->lcd_drawpixel(0, 0);
1587 rb->lcd_update_rect(0, 0, 1, 1);
1588 screen_white = get_pixel(0, 0);
1590 /* Save screen green color */
1591 rb->lcd_set_foreground(SLIME_GREEN);
1592 rb->lcd_drawpixel(0, 0);
1593 rb->lcd_update_rect(0, 0, 1, 1);
1594 screen_green = get_pixel(0, 0);
1596 /* Restore black foreground */
1597 rb->lcd_set_foreground(LCD_BLACK);
1599 new_level();
1601 /* Flash score at start */
1602 for (i = 0; i < 5; i++) {
1603 rb->lcd_fillrect(SCORENUM_X, SCORENUM_Y,
1604 4 * NUMBERS_WIDTH + 3 * NUM_SPACING,
1605 FONT_HEIGHT);
1606 rb->lcd_update_rect(SCORENUM_X, SCORENUM_Y,
1607 4 * NUMBERS_WIDTH + 3 * NUM_SPACING,
1608 FONT_HEIGHT);
1609 rb->sleep(HZ / 10);
1610 draw_number(SCORENUM_X, SCORENUM_Y, score, 4);
1611 rb->sleep(HZ / 10);
1616 inline bool handle_buttons(void)
1618 static unsigned int oldbuttonstate = 0;
1620 unsigned int released, pressed, newbuttonstate;
1622 if (ship_hit) {
1623 /* Don't allow ship movement during explosion */
1624 newbuttonstate = 0;
1625 } else {
1626 newbuttonstate = rb->button_status();
1628 if(newbuttonstate == oldbuttonstate) {
1629 if (newbuttonstate == 0) {
1630 /* No button pressed. Stop ship. */
1631 ship_acc = 0;
1632 if (ship_dir > 0) {
1633 ship_dir--;
1635 if (ship_dir < 0) {
1636 ship_dir++;
1639 /* return false; */
1640 goto check_usb;
1642 released = ~newbuttonstate & oldbuttonstate;
1643 pressed = newbuttonstate & ~oldbuttonstate;
1644 oldbuttonstate = newbuttonstate;
1645 if (pressed) {
1646 if (pressed & LEFT) {
1647 if (ship_acc > -1) {
1648 ship_acc--;
1651 if (pressed & RIGHT) {
1652 if (ship_acc < 1) {
1653 ship_acc++;
1656 if (pressed & FIRE) {
1657 if (fire == S_IDLE) {
1658 /* Fire shot */
1659 fire_x = ship_x + SHIP_WIDTH / 2;
1660 fire_y = SHIP_Y - SHOT_HEIGHT;
1661 fire = S_ACTIVE;
1662 /* TODO: play fire sound */
1665 #ifdef RC_QUIT
1666 if (pressed & RC_QUIT) {
1667 rb->splash(HZ * 1, "Quit");
1668 return true;
1670 #endif
1671 if (pressed & QUIT) {
1672 rb->splash(HZ * 1, "Quit");
1673 return true;
1676 if (released) {
1677 if ((released & LEFT)) {
1678 if (ship_acc < 1) {
1679 ship_acc++;
1682 if ((released & RIGHT)) {
1683 if (ship_acc > -1) {
1684 ship_acc--;
1689 check_usb:
1691 /* Quit if USB is connected */
1692 if (rb->button_get(false) == SYS_USB_CONNECTED) {
1693 return true;
1696 return false;
1700 void game_loop(void)
1702 int i, end;
1704 /* Print dimensions (just for debugging) */
1705 DBG("%03dx%03dx%02d\n", LCD_WIDTH, LCD_HEIGHT, LCD_DEPTH);
1707 /* Init */
1708 init_invadrox();
1710 while (1) {
1711 /* Convert CYCLETIME (in ms) to HZ */
1712 end = *rb->current_tick + (CYCLETIME * HZ) / 1000;
1714 if (handle_buttons()) {
1715 return;
1718 /* Animate */
1719 move_ship();
1720 move_fire();
1722 /* Check if level is finished (marked by move_fire) */
1723 if (level_finished) {
1724 /* TODO: Play level finished sound */
1725 new_level();
1728 move_ufo();
1730 /* Move aliens */
1731 if (!aliens_paralyzed && !ship_hit) {
1732 for (i = 0; i < gamespeed; i++) {
1733 if (!move_aliens()) {
1734 if (game_over) {
1735 return;
1741 /* Move alien bombs */
1742 move_bombs();
1743 if (game_over) {
1744 return;
1747 /* Update "playfield" rect */
1748 rb->lcd_update_rect(PLAYFIELD_X, SCORENUM_Y + FONT_HEIGHT,
1749 PLAYFIELD_WIDTH,
1750 PLAYFIELD_Y + 1 - SCORENUM_Y - FONT_HEIGHT);
1752 /* Wait until next frame */
1753 DBG("%ld (%d)\n", end - *rb->current_tick, (CYCLETIME * HZ) / 1000);
1754 if (TIME_BEFORE(*rb->current_tick, end)) {
1755 rb->sleep(end - *rb->current_tick);
1756 } else {
1757 rb->yield();
1760 } /* end while */
1764 /* this is the plugin entry point */
1765 enum plugin_status plugin_start(UNUSED const void* parameter)
1767 rb->lcd_setfont(FONT_SYSFIXED);
1768 /* Turn off backlight timeout */
1769 backlight_force_on(); /* backlight control in lib/helper.c */
1771 /* now go ahead and have fun! */
1772 game_loop();
1774 /* Game Over. */
1775 /* TODO: Play game over sound */
1776 rb->splash(HZ * 2, "Game Over");
1777 if (score > hiscore.score) {
1778 /* Save new hiscore */
1779 highscore_update(score, level, "Invader", &hiscore, 1);
1780 highscore_save(HISCOREFILE, &hiscore, 1);
1783 /* Restore user's original backlight setting */
1784 rb->lcd_setfont(FONT_UI);
1785 /* Turn on backlight timeout (revert to settings) */
1786 backlight_use_settings(); /* backlight control in lib/helper.c */
1788 return PLUGIN_OK;
1794 * GNU Emacs settings: Kernighan & Richie coding style with
1795 * 4 spaces indent and no tabs.
1796 * Local Variables:
1797 * c-file-style: "k&r"
1798 * c-basic-offset: 4
1799 * indent-tabs-mode: nil
1800 * End: