Fix FS#10486 - "fuze screen not drawn correctly on backlight turn-on if playlist...
[kugel-rb/myfork.git] / apps / plugins / invadrox.c
blob19b2b670a4481e29a3c74e1be233daa6830a6535
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 || CONFIG_KEYPAD == MROBE500_PAD
177 #define QUIT BUTTON_POWER
179 #elif CONFIG_KEYPAD == SAMSUNG_YH_PAD
181 #define QUIT BUTTON_REC
182 #define LEFT BUTTON_LEFT
183 #define RIGHT BUTTON_RIGHT
184 #define FIRE BUTTON_PLAY
186 #else
187 #error INVADROX: Unsupported keypad
188 #endif
190 #ifdef HAVE_TOUCHSCREEN
191 #ifndef QUIT
192 #define QUIT BUTTON_TOPLEFT
193 #endif
194 #ifndef LEFT
195 #define LEFT BUTTON_MIDLEFT
196 #endif
197 #ifndef RIGHT
198 #define RIGHT BUTTON_MIDRIGHT
199 #endif
200 #ifndef FIRE
201 #define FIRE BUTTON_CENTER
202 #endif
203 #endif
205 #ifndef UNUSED
206 #define UNUSED __attribute__ ((unused))
207 #endif
209 /* Defines common to all models */
210 #define UFO_Y (SCORENUM_Y + FONT_HEIGHT + ALIEN_HEIGHT)
211 #define PLAYFIELD_Y (LCD_HEIGHT - SHIP_HEIGHT - 2)
212 #define PLAYFIELD_WIDTH (LCD_WIDTH - 2 * PLAYFIELD_X)
213 #define LEVEL_X (LCD_WIDTH - PLAYFIELD_X - LIVES_X - 2 * NUMBERS_WIDTH - 3 * NUM_SPACING)
214 #define SHIP_MIN_X (PLAYFIELD_X + PLAYFIELD_WIDTH / 5 - SHIELD_WIDTH / 2 - SHIP_WIDTH)
215 #define SHIP_MAX_X (PLAYFIELD_X + 4 * PLAYFIELD_WIDTH / 5 + SHIELD_WIDTH / 2)
216 /* SCORE_Y = 0 for most targets. Gigabeat redefines it later. */
217 #define SCORE_Y 0
218 #define MAX_LIVES 8
221 /* m:robe 500 defines */
222 #if ((LCD_WIDTH == 640) && (LCD_HEIGHT == 480)) || \
223 ((LCD_WIDTH == 480) && (LCD_HEIGHT == 640))
225 /* Original arcade game size 224x240, 1bpp with
226 * red overlay at top and green overlay at bottom.
228 * M:Robe 500: 640x480x16
229 * ======================
232 #define ARCADISH_GRAPHICS
233 #define PLAYFIELD_X 48
234 #define SHIP_Y (PLAYFIELD_Y - 2 * SHIP_HEIGHT)
235 #define ALIEN_START_Y (UFO_Y + ALIEN_HEIGHT)
236 #define SCORENUM_X (PLAYFIELD_X + NUMBERS_WIDTH)
237 #define SCORENUM_Y (SCORE_Y + FONT_HEIGHT + 2)
238 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 1 - 6 * NUMBERS_WIDTH - 5 * NUM_SPACING)
239 #define SHIELD_Y (PLAYFIELD_Y - 5 * SHIP_HEIGHT)
240 #define LIVES_X 10
241 #define MAX_Y 18
243 /* iPod Video defines */
244 #elif (LCD_WIDTH == 320) && (LCD_HEIGHT == 240)
246 /* Original arcade game size 224x240, 1bpp with
247 * red overlay at top and green overlay at bottom.
249 * iPod Video: 320x240x16
250 * ======================
251 * X: 48p padding at left/right gives 224p playfield in middle.
252 * 10p "border" gives 204p actual playfield. UFO use full 224p.
253 * Y: Use full 240p.
255 * MAX_X = (204 - 12) / 2 - 1 = 95
257 * Y: Score text 7 0
258 * Space 10 7
259 * Score 7 17
260 * Space 8 24
261 * 3 Ufo 7 32
262 * 2 Space Aliens start at 32 + 3 * 8 = 56
263 * 0 aliens 9*8 56 -
264 * space ~7*8 128 | 18.75 aliens space between
265 * shield 2*8 182 | first alien and ship.
266 * space 8 198 | MAX_Y = 18
267 * ship 8 206 -
268 * space 2*8 214
269 * hline 1 230 - PLAYFIELD_Y
270 * bottom border 10 240
271 * Lives and Level goes inside bottom border
274 #define ARCADISH_GRAPHICS
275 #define PLAYFIELD_X 48
276 #define SHIP_Y (PLAYFIELD_Y - 3 * SHIP_HEIGHT)
277 #define ALIEN_START_Y (UFO_Y + 3 * ALIEN_HEIGHT)
278 #define SCORENUM_X (PLAYFIELD_X + NUMBERS_WIDTH)
279 #define SCORENUM_Y SCORE_Y + (2 * (FONT_HEIGHT + 1) + 1)
280 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 1 - 6 * NUMBERS_WIDTH - 5 * NUM_SPACING)
281 #define SHIELD_Y (PLAYFIELD_Y - 6 * SHIP_HEIGHT)
282 #define LIVES_X 10
283 #define MAX_Y 18
285 #elif (LCD_WIDTH == 176) && (LCD_HEIGHT == 220)
287 /* Sandisk Sansa e200: 176x220x16
288 * ==============================
289 * X: No padding. 8p border -> 160p playfield.
291 * 8p Aliens with 3p spacing -> 88 + 30 = 118p aliens block.
292 * (160 - 118) / 2 = 21 rounds for whole block (more than original)
293 * MAX_X = (160 - 8) / 2 - 1 = 75 rounds for single alien (less than original)
295 * LOGO 70 0
296 * Score text 5 70
297 * Space 5 75
298 * Y Score 5 80
299 * Space 10 85
300 * 2 Ufo 5 95
301 * 2 Space 10 100
302 * 0 aliens 9*5 110 -
303 * space ~7*5 155 | 18.6 aliens space between
304 * shield 2*5 188 | first alien and ship.
305 * space 5 198 | MAX_Y = 18
306 * ship 5 203 -
307 * space 5 208
308 * hline 1 213 PLAYFIELD_Y
309 * bottom border 6
310 * LCD_HEIGHT 220
311 * Lives and Level goes inside bottom border
314 #define SMALL_GRAPHICS
315 #define PLAYFIELD_X 0
316 #define SHIP_Y (PLAYFIELD_Y - 2 * SHIP_HEIGHT)
317 #define SHIELD_Y (SHIP_Y - SHIP_HEIGHT - SHIELD_HEIGHT)
318 #define ALIEN_START_Y (UFO_Y + 3 * SHIP_HEIGHT)
319 /* Redefine SCORE_Y */
320 #undef SCORE_Y
321 #define SCORE_Y 70
322 #define SCORENUM_X (PLAYFIELD_X + NUMBERS_WIDTH)
323 #define SCORENUM_Y (SCORE_Y + 2 * FONT_HEIGHT)
324 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 1 - 6 * NUMBERS_WIDTH - 5 * NUM_SPACING)
325 #define LIVES_X 8
326 #define MAX_Y 18
329 #elif (LCD_WIDTH == 176) && (LCD_HEIGHT == 132)
331 /* iPod Nano: 176x132x16
332 * ======================
333 * X: No padding. 8p border -> 160p playfield.
335 * LIVES_X 8
336 * ALIEN_WIDTH 8
337 * ALIEN_HEIGHT 5
338 * ALIEN_SPACING 3
339 * SHIP_WIDTH 10
340 * SHIP_HEIGHT 5
341 * FONT_HEIGHT 5
342 * UFO_WIDTH 10
343 * UFO_HEIGHT 5
344 * SHIELD_WIDTH 15
345 * SHIELD_HEIGHT 10
346 * MAX_X 75
347 * MAX_Y = 18
348 * ALIEN_START_Y (UFO_Y + 12)
350 * 8p Aliens with 3p spacing -> 88 + 30 = 118p aliens block.
351 * (160 - 118) / 2 = 21 rounds for whole block (more than original)
352 * MAX_X = (160 - 8) / 2 - 1 = 75 rounds for single alien (less than original)
354 * Y: Scoreline 5 0 (combine scoretext and numbers on same line)
355 * Space 5 5
356 * 1 Ufo 5 10
357 * 3 Space 7 15
358 * 2 aliens 9*5 22 -
359 * space ~7*5 67 | Just above 18 aliens space between
360 * shield 2*5 100 | first alien and ship.
361 * space 5 110 | MAX_Y = 18
362 * ship 5 115 -
363 * space 5 120
364 * hline 1 125 PLAYFIELD_Y
365 * bottom border 6 126
366 * LCD_HEIGHT 131
367 * Lives and Level goes inside bottom border
370 #define SMALL_GRAPHICS
371 #define PLAYFIELD_X 0
372 #define SHIP_Y (PLAYFIELD_Y - 2 * SHIP_HEIGHT)
373 #define ALIEN_START_Y (UFO_Y + 12)
374 #define SCORENUM_X (PLAYFIELD_X + 6 * NUMBERS_WIDTH + 5 * NUM_SPACING)
375 #define SCORENUM_Y SCORE_Y
376 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 4 * NUMBERS_WIDTH - 3 * NUM_SPACING)
377 #define SHIELD_Y (SHIP_Y - SHIP_HEIGHT - SHIELD_HEIGHT)
378 #define LIVES_X 8
379 #define MAX_Y 18
381 #elif (LCD_WIDTH == 160) && (LCD_HEIGHT == 128)
383 /* iAudio X5, iRiver H10 20Gb, iPod 3g/4g, H100, M5: 160x128
384 * =========================================================
385 * X: No padding. No border -> 160p playfield.
387 * LIVES_X 0
388 * ALIEN_WIDTH 8
389 * ALIEN_HEIGHT 5
390 * ALIEN_SPACING 3
391 * SHIP_WIDTH 10
392 * SHIP_HEIGHT 5
393 * FONT_HEIGHT 5
394 * UFO_WIDTH 10
395 * UFO_HEIGHT 5
396 * SHIELD_WIDTH 15
397 * SHIELD_HEIGHT 10
398 * MAX_X 75
399 * MAX_Y = 18
400 * ALIEN_START_Y (UFO_Y + 10)
402 * 8p Aliens with 3p spacing -> 88 + 30 = 118p aliens block.
403 * (160 - 118) / 2 = 21 rounds for whole block (more than original)
404 * MAX_X = (160 - 8) / 2 - 1 = 75 rounds for single alien (less than original)
406 * Y: Scoreline 5 0 (combine scoretext and numbers on same line)
407 * Space 5 5
408 * 1 Ufo 5 10
409 * 2 Space 5 15
410 * 8 aliens 9*5 20 -
411 * space ~6*5 65 | Just above 18 aliens space between
412 * shield 2*5 96 | first alien and ship.
413 * space 5 106 | MAX_Y = 18
414 * ship 5 111 -
415 * space 5 116
416 * hline 1 121 PLAYFIELD_Y
417 * bottom border 6 122
418 * LCD_HEIGHT 128
419 * Lives and Level goes inside bottom border
422 #define SMALL_GRAPHICS
423 #define PLAYFIELD_X 0
424 #define SHIP_Y (PLAYFIELD_Y - 2 * SHIP_HEIGHT)
425 #define ALIEN_START_Y (UFO_Y + 10)
426 #define SCORENUM_X (PLAYFIELD_X + 6 * NUMBERS_WIDTH + 5 * NUM_SPACING)
427 #define SCORENUM_Y SCORE_Y
428 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 4 * NUMBERS_WIDTH - 3 * NUM_SPACING)
429 #define SHIELD_Y (SHIP_Y - SHIP_HEIGHT - SHIELD_HEIGHT)
430 #define LIVES_X 0
431 #define MAX_Y 18
434 #elif (LCD_WIDTH == 240) && ((LCD_HEIGHT == 320) || (LCD_HEIGHT == 400))
436 /* Gigabeat: 240x320x16
437 * ======================
438 * X: 8p padding at left/right gives 224p playfield in middle.
439 * 10p "border" gives 204p actual playfield. UFO use full 224p.
440 * Y: Use bottom 240p for playfield and top 80 pixels for logo.
442 * MAX_X = (204 - 12) / 2 - 1 = 95
444 * Y: Score text 7 0 + 80
445 * Space 10 7 + 80
446 * Score 7 17 + 80
447 * Space 8 24 + 80
448 * 3 Ufo 7 32 + 80
449 * 2 Space Aliens start at 32 + 3 * 8 = 56
450 * 0 aliens 9*8 56 -
451 * space ~7*8 128 | 18.75 aliens space between
452 * shield 2*8 182 | first alien and ship.
453 * space 8 198 | MAX_Y = 18
454 * ship 8 206 -
455 * space 2*8 214
456 * hline 1 230 310 - PLAYFIELD_Y
457 * bottom border 10 240 320
458 * Lives and Level goes inside bottom border
461 #define ARCADISH_GRAPHICS
462 #define PLAYFIELD_X 8
463 #define SHIP_Y (PLAYFIELD_Y - 3 * SHIP_HEIGHT)
464 #define ALIEN_START_Y (UFO_Y + 3 * ALIEN_HEIGHT)
465 /* Redefine SCORE_Y */
466 #undef SCORE_Y
467 #define SCORE_Y 80
468 #define SCORENUM_X (PLAYFIELD_X + NUMBERS_WIDTH)
469 #define SCORENUM_Y SCORE_Y + (2 * (FONT_HEIGHT + 1) + 1)
470 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 1 - 6 * NUMBERS_WIDTH - 5 * NUM_SPACING)
471 #define SHIELD_Y (PLAYFIELD_Y - 6 * SHIP_HEIGHT)
472 #define LIVES_X 10
473 #define MAX_Y 18
475 #elif (LCD_WIDTH == 220) && (LCD_HEIGHT == 176)
477 /* TPJ1022, H300, iPod Color: 220x176x16
478 * ============================
479 * X: 0p padding at left/right gives 220p playfield in middle.
480 * 8p "border" gives 204p actual playfield. UFO use full 220p.
481 * Y: Use full 176p for playfield.
483 * MAX_X = (204 - 12) / 2 - 1 = 95
485 * Y: Score text 7 0
486 * Space 8 7
487 * 1 Ufo 7 15
488 * 7 Space Aliens start at 15 + 3 * 8 = 56
489 * 6 aliens 9*8 25 -
490 * space ~7*8 103 | 15.6 aliens space between
491 * shield 2*8 126 | first alien and ship.
492 * space 8 142 | MAX_Y = 15
493 * ship 8 150 -
494 * space 8 158
495 * hline 1 166 - PLAYFIELD_Y
496 * bottom border 10 176
497 * Lives and Level goes inside bottom border
500 #define ARCADISH_GRAPHICS
501 #define PLAYFIELD_X 0
502 #define SHIP_Y (PLAYFIELD_Y - 2 * SHIP_HEIGHT)
503 #define ALIEN_START_Y (UFO_Y + 10)
504 #define SCORENUM_Y SCORE_Y
505 #define SCORENUM_X (PLAYFIELD_X + 6 * NUMBERS_WIDTH + 6 * NUM_SPACING)
506 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 4 * NUMBERS_WIDTH - 3 * NUM_SPACING)
507 #define SHIELD_Y (PLAYFIELD_Y - 5 * SHIP_HEIGHT)
508 #define LIVES_X 8
509 #define MAX_Y 15
512 #else
513 #error INVADROX: Unsupported LCD type
514 #endif
516 #define MAX_X ((LCD_WIDTH-LIVES_X*2-PLAYFIELD_X*2 - ALIEN_WIDTH)/2 - 1)
518 /* Defines common to each "graphic type" */
519 #ifdef ARCADISH_GRAPHICS
521 #define SHOT_HEIGHT 5
522 #define ALIEN_SPACING 4
523 #define ALIEN_SPEED 2
524 #define UFO_SPEED 1
525 #define NUM_SPACING 3
526 #define FIRE_SPEED 8
527 #define BOMB_SPEED 3
528 #define ALIENS 11
530 #elif defined SMALL_GRAPHICS
532 #define SHOT_HEIGHT 4
533 #define ALIEN_SPACING 3
534 #define ALIEN_SPEED 2
535 #define UFO_SPEED 1
536 #define NUM_SPACING 2
537 #define FIRE_SPEED 6
538 #define BOMB_SPEED 2
539 #define ALIENS 11
541 #else
542 #error Graphic type not defined
543 #endif
546 /* Colors */
547 #if (LCD_DEPTH >= 8)
548 #define SLIME_GREEN LCD_RGBPACK(31, 254, 31)
549 #define UFO_RED LCD_RGBPACK(254, 31, 31)
550 #elif (LCD_DEPTH == 2)
551 #define SLIME_GREEN LCD_LIGHTGRAY
552 #define UFO_RED LCD_LIGHTGRAY
553 #else
554 #error LCD type not implemented yet
555 #endif
557 /* Alien states */
558 #define DEAD 0
559 #define ALIVE 1
560 #define BOMBER 2
562 /* Fire/bomb/ufo states */
563 #define S_IDLE 0
564 #define S_ACTIVE 1
565 #define S_SHOWSCORE 2
566 #define S_EXPLODE -9
568 /* Fire/bomb targets */
569 #define TARGET_TOP 0
570 #define TARGET_SHIELD 1
571 #define TARGET_SHIP 2
572 #define TARGET_BOTTOM 3
573 #define TARGET_UFO 4
575 #define HISCOREFILE PLUGIN_GAMES_DIR "/invadrox.high"
578 /* The time (in ms) for one iteration through the game loop - decrease this
579 * to speed up the game - note that current_tick is (currently) only accurate
580 * to 10ms.
582 #define CYCLETIME 40
585 /* Physical x is at PLAYFIELD_X + LIVES_X + x * ALIEN_SPEED
586 * Physical y is at y * ALIEN_HEIGHT
588 struct alien {
589 int x; /* x-coordinate (0 - 95) */
590 int y; /* y-coordinate (0 - 18) */
591 unsigned char type; /* 0 (Kang), 1 (Kodos), 2 (Serak) */
592 unsigned char state; /* Dead, alive or bomber */
595 /* Aliens box 5 rows * ALIENS aliens in each row */
596 struct alien aliens[5 * ALIENS];
598 #define MAX_BOMBS 4
599 struct bomb {
600 int x, y;
601 unsigned char type;
602 unsigned char frame; /* Current animation frame */
603 unsigned char frames; /* Number of frames in animation */
604 unsigned char target; /* Remember target during explosion frames */
605 int state; /* 0 (IDLE) = inactive, 1 (FIRE) or negative, exploding */
607 struct bomb bombs[MAX_BOMBS];
608 /* Increase max_bombs at higher levels */
609 int max_bombs;
611 /* Raw framebuffer value of shield/ship green color */
612 fb_data screen_green, screen_white;
614 /* For optimization, precalculate startoffset of each scanline */
615 unsigned int ytab[LCD_HEIGHT];
617 int lives = 2;
618 int score = 0;
619 int scores[3] = { 30, 20, 10 };
620 int level = 0;
621 struct highscore hiscore;
622 bool game_over = false;
623 int ship_x, old_ship_x, ship_dir, ship_acc, max_ship_speed;
624 int ship_frame, ship_frame_counter;
625 bool ship_hit;
626 int fire, fire_target, fire_x, fire_y;
627 int curr_alien, aliens_paralyzed, gamespeed;
628 int ufo_state, ufo_x;
629 bool level_finished;
630 bool aliens_down, aliens_right, hit_left_border, hit_right_border;
633 /* No standard get_pixel function yet, use this hack instead */
634 #if (LCD_DEPTH >= 8)
636 inline fb_data get_pixel(int x, int y)
638 return rb->lcd_framebuffer[ytab[y] + x];
641 #elif (LCD_DEPTH == 2)
643 #if (LCD_PIXELFORMAT == HORIZONTAL_PACKING)
644 static const unsigned char shifts[4] = {
645 6, 4, 2, 0
647 /* Horizontal packing */
648 inline fb_data get_pixel(int x, int y)
650 return (rb->lcd_framebuffer[ytab[y] + (x >> 2)] >> shifts[x & 3]) & 3;
652 #else
653 /* Vertical packing */
654 static const unsigned char shifts[4] = {
655 0, 2, 4, 6
657 inline fb_data get_pixel(int x, int y)
659 return (rb->lcd_framebuffer[ytab[y] + x] >> shifts[y & 3]) & 3;
661 #endif /* Horizontal/Vertical packing */
663 #else
664 #error get_pixel: pixelformat not implemented yet
665 #endif
668 /* Draw "digits" least significant digits of num at (x,y) */
669 void draw_number(int x, int y, int num, int digits)
671 int i;
672 int d;
674 for (i = digits - 1; i >= 0; i--) {
675 d = num % 10;
676 num = num / 10;
677 rb->lcd_bitmap_part(invadrox_numbers, d * NUMBERS_WIDTH, 0,
678 BMPWIDTH_invadrox_numbers,
679 x + i * (NUMBERS_WIDTH + NUM_SPACING), y,
680 NUMBERS_WIDTH, FONT_HEIGHT);
682 /* Update lcd */
683 rb->lcd_update_rect(x, y, 4 * NUMBERS_WIDTH + 3 * NUM_SPACING, FONT_HEIGHT);
687 inline void draw_score(void)
689 draw_number(SCORENUM_X, SCORENUM_Y, score, 4);
690 if (score > hiscore.score) {
691 /* Draw new hiscore (same as score) */
692 draw_number(HISCORENUM_X, SCORENUM_Y, score, 4);
697 void draw_level(void)
699 draw_number(LEVEL_X + 2 * NUM_SPACING, PLAYFIELD_Y + 2, level, 2);
703 void draw_lives(void)
705 int i;
706 /* Lives num */
707 rb->lcd_bitmap_part(invadrox_numbers, lives * NUMBERS_WIDTH, 0,
708 BMPWIDTH_invadrox_numbers, PLAYFIELD_X + LIVES_X, PLAYFIELD_Y + 2,
709 NUMBERS_WIDTH, FONT_HEIGHT);
711 /* Ships */
712 for (i = 0; i < (lives - 1); i++) {
713 rb->lcd_bitmap_part(invadrox_ships, 0, 0, BMPWIDTH_invadrox_ships,
714 PLAYFIELD_X + LIVES_X + SHIP_WIDTH + i * (SHIP_WIDTH + NUM_SPACING),
715 PLAYFIELD_Y + 1, SHIP_WIDTH, SHIP_HEIGHT);
718 /* Erase ship to the right (if less than MAX_LIVES) */
719 if (lives < MAX_LIVES) {
720 rb->lcd_fillrect(PLAYFIELD_X + LIVES_X + SHIP_WIDTH + i * (SHIP_WIDTH + NUM_SPACING),
721 PLAYFIELD_Y + 1, SHIP_WIDTH, SHIP_HEIGHT);
723 /* Update lives (and level) part of screen */
724 rb->lcd_update_rect(PLAYFIELD_X + LIVES_X, PLAYFIELD_Y + 1,
725 PLAYFIELD_WIDTH - 2 * LIVES_X, MAX(FONT_HEIGHT + 1, SHIP_HEIGHT + 1));
729 inline void draw_aliens(void)
731 int i;
733 for (i = 0; i < 5 * ALIENS; i++) {
734 rb->lcd_bitmap_part(invadrox_aliens, aliens[i].x & 1 ? ALIEN_WIDTH : 0, aliens[i].type * ALIEN_HEIGHT,
735 BMPWIDTH_invadrox_aliens, PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED,
736 ALIEN_START_Y + aliens[i].y * ALIEN_HEIGHT,
737 ALIEN_WIDTH, ALIEN_HEIGHT);
742 /* Return false if there is no next alive alien (round is over) */
743 inline bool next_alien(void)
745 bool ret = true;
747 do {
748 curr_alien++;
749 if (curr_alien % ALIENS == 0) {
750 /* End of this row. Move up one row. */
751 curr_alien -= 2 * ALIENS;
752 if (curr_alien < 0) {
753 /* No more aliens in this round. */
754 curr_alien = 4 * ALIENS;
755 ret = false;
758 } while (aliens[curr_alien].state == DEAD && ret);
760 if (!ret) {
761 /* No more alive aliens. Round finished. */
762 if (hit_right_border) {
763 if (hit_left_border) {
764 DBG("ERROR: both left and right borders are set (%d)\n", curr_alien);
766 /* Move down-left next round */
767 aliens_right = false;
768 aliens_down = true;
769 hit_right_border = false;
770 } else if (hit_left_border) {
771 /* Move down-right next round */
772 aliens_right = true;
773 aliens_down = true;
774 hit_left_border = false;
775 } else {
776 /* Not left nor right. Set down to false. */
777 aliens_down = false;
781 return ret;
785 /* All aliens have been moved.
786 * Set curr_alien to first alive.
787 * Return false if no-one is left alive.
789 bool first_alien(void)
791 int i, y;
793 for (y = 4; y >= 0; y--) {
794 for (i = y * ALIENS; i < (y + 1) * ALIENS; i++) {
795 if (aliens[i].state != DEAD) {
796 curr_alien = i;
797 return true;
802 /* All aliens dead. */
803 level_finished = true;
805 return false;
809 bool move_aliens(void)
811 int x, y, old_x, old_y;
813 /* Move current alien (curr_alien is pointing to a living alien) */
815 old_x = aliens[curr_alien].x;
816 old_y = aliens[curr_alien].y;
818 if (aliens_down) {
819 aliens[curr_alien].y++;
820 if (aliens[curr_alien].y == MAX_Y) {
821 /* Alien is at bottom. Game Over. */
822 DBG("Alien %d is at bottom. Game Over.\n", curr_alien);
823 game_over = true;
824 return false;
828 if (aliens_right) {
829 /* Moving right */
830 if (aliens[curr_alien].x < MAX_X) {
831 aliens[curr_alien].x++;
834 /* Now, after move, check if we hit the right border. */
835 if (aliens[curr_alien].x == MAX_X) {
836 hit_right_border = true;
839 } else {
840 /* Moving left */
841 if (aliens[curr_alien].x > 0) {
842 aliens[curr_alien].x--;
845 /* Now, after move, check if we hit the left border. */
846 if (aliens[curr_alien].x == 0) {
847 hit_left_border = true;
851 /* Erase old position */
852 x = PLAYFIELD_X + LIVES_X + old_x * ALIEN_SPEED;
853 y = ALIEN_START_Y + old_y * ALIEN_HEIGHT;
854 if (aliens[curr_alien].y != old_y) {
855 /* Moved in y-dir. Erase whole alien. */
856 rb->lcd_fillrect(x, y, ALIEN_WIDTH, ALIEN_HEIGHT);
857 } else {
858 if (aliens_right) {
859 /* Erase left edge */
860 rb->lcd_fillrect(x, y, ALIEN_SPEED, ALIEN_HEIGHT);
861 } else {
862 /* Erase right edge */
863 x += ALIEN_WIDTH - ALIEN_SPEED;
864 rb->lcd_fillrect(x, y, ALIEN_SPEED, ALIEN_HEIGHT);
868 /* Draw alien at new pos */
869 x = PLAYFIELD_X + LIVES_X + aliens[curr_alien].x * ALIEN_SPEED;
870 y = ALIEN_START_Y + aliens[curr_alien].y * ALIEN_HEIGHT;
871 rb->lcd_bitmap_part(invadrox_aliens,
872 aliens[curr_alien].x & 1 ? ALIEN_WIDTH : 0, aliens[curr_alien].type * ALIEN_HEIGHT,
873 BMPWIDTH_invadrox_aliens, x, y, ALIEN_WIDTH, ALIEN_HEIGHT);
875 if (!next_alien()) {
876 /* Round finished. Set curr_alien to first alive from bottom. */
877 if (!first_alien()) {
878 /* Should never happen. Taken care of in move_fire(). */
879 return false;
881 /* TODO: Play next background sound */
884 return true;
888 inline void draw_ship(void)
890 /* Erase old ship */
891 if (old_ship_x < ship_x) {
892 /* Move right. Erase leftmost part of ship. */
893 rb->lcd_fillrect(old_ship_x, SHIP_Y, ship_x - old_ship_x, SHIP_HEIGHT);
894 } else if (old_ship_x > ship_x) {
895 /* Move left. Erase rightmost part of ship. */
896 rb->lcd_fillrect(ship_x + SHIP_WIDTH, SHIP_Y, old_ship_x - ship_x, SHIP_HEIGHT);
899 /* Draw ship */
900 rb->lcd_bitmap_part(invadrox_ships, 0, ship_frame * SHIP_HEIGHT,
901 BMPWIDTH_invadrox_ships, ship_x, SHIP_Y, SHIP_WIDTH, SHIP_HEIGHT);
902 if (ship_hit) {
903 /* Alternate between frame 1 and 2 during hit */
904 ship_frame_counter++;
905 if (ship_frame_counter > 2) {
906 ship_frame_counter = 0;
907 ship_frame++;
908 if (ship_frame > 2) {
909 ship_frame = 1;
914 /* Save ship_x for next time */
915 old_ship_x = ship_x;
919 inline void fire_alpha(int xc, int yc, fb_data color)
921 int oldmode = rb->lcd_get_drawmode();
923 rb->lcd_set_foreground(color);
924 rb->lcd_set_drawmode(DRMODE_FG);
926 rb->lcd_mono_bitmap(invadrox_fire, xc - (FIRE_WIDTH/2), yc, FIRE_WIDTH, FIRE_HEIGHT);
928 rb->lcd_set_foreground(LCD_BLACK);
929 rb->lcd_set_drawmode(oldmode);
933 void move_fire(void)
935 bool hit_green = false;
936 bool hit_white = false;
937 int i, j;
938 static int exploding_alien = -1;
939 fb_data pix;
941 if (fire == S_IDLE) {
942 return;
945 /* Alien hit. Wait until explosion is finished. */
946 if (aliens_paralyzed < 0) {
947 aliens_paralyzed++;
948 if (aliens_paralyzed == 0) {
949 /* Erase exploding_alien */
950 rb->lcd_fillrect(PLAYFIELD_X + LIVES_X + aliens[exploding_alien].x * ALIEN_SPEED,
951 ALIEN_START_Y + aliens[exploding_alien].y * ALIEN_HEIGHT,
952 ALIEN_EXPLODE_WIDTH, ALIEN_HEIGHT);
953 fire = S_IDLE;
954 /* Special case. We killed curr_alien. */
955 if (exploding_alien == curr_alien) {
956 if (!next_alien()) {
957 /* Round finished. Set curr_alien to first alive from bottom. */
958 first_alien();
962 return;
965 if (fire == S_ACTIVE) {
967 /* Erase */
968 rb->lcd_vline(fire_x, fire_y, fire_y + SHOT_HEIGHT);
970 /* Check top */
971 if (fire_y <= SCORENUM_Y + FONT_HEIGHT + 4) {
973 /* TODO: Play explode sound */
975 fire = S_EXPLODE;
976 fire_target = TARGET_TOP;
977 fire_alpha(fire_x, fire_y, UFO_RED);
978 return;
981 /* Move */
982 fire_y -= FIRE_SPEED;
984 /* Hit UFO? */
985 if (ufo_state == S_ACTIVE) {
986 if ((ABS(ufo_x + UFO_WIDTH / 2 - fire_x) <= UFO_WIDTH / 2) &&
987 (fire_y <= UFO_Y + UFO_HEIGHT)) {
988 ufo_state = S_EXPLODE;
989 fire = S_EXPLODE;
990 fire_target = TARGET_UFO;
991 /* Center explosion */
992 ufo_x -= (UFO_EXPLODE_WIDTH - UFO_WIDTH) / 2;
993 rb->lcd_bitmap(invadrox_ufo_explode, ufo_x, UFO_Y - 1,
994 UFO_EXPLODE_WIDTH, UFO_EXPLODE_HEIGHT);
995 return;
999 /* Hit bomb? (check position, not pixel value) */
1000 for (i = 0; i < max_bombs; i++) {
1001 if (bombs[i].state == S_ACTIVE) {
1002 /* Count as hit if within BOMB_WIDTH pixels */
1003 if ((ABS(bombs[i].x - fire_x) < BOMB_WIDTH) &&
1004 (fire_y - bombs[i].y < BOMB_HEIGHT)) {
1005 /* Erase bomb */
1006 rb->lcd_fillrect(bombs[i].x, bombs[i].y, BOMB_WIDTH, BOMB_HEIGHT);
1007 bombs[i].state = S_IDLE;
1008 /* Explode ship fire */
1009 fire = S_EXPLODE;
1010 fire_target = TARGET_SHIELD;
1011 fire_alpha(fire_x, fire_y, LCD_WHITE);
1012 return;
1017 /* Check for hit*/
1018 for (i = FIRE_SPEED; i >= 0; i--) {
1019 pix = get_pixel(fire_x, fire_y + i);
1020 if(pix == screen_white) {
1021 hit_white = true;
1022 fire_y += i;
1023 break;
1025 if(pix == screen_green) {
1026 hit_green = true;
1027 fire_y += i;
1028 break;
1032 if (hit_green) {
1033 /* Hit shield */
1035 /* TODO: Play explode sound */
1037 fire = S_EXPLODE;
1038 fire_target = TARGET_SHIELD;
1039 /* Center explosion around hit pixel */
1040 fire_y -= FIRE_HEIGHT / 2;
1041 fire_alpha(fire_x, fire_y, SLIME_GREEN);
1042 return;
1045 if (hit_white) {
1047 /* Hit alien? */
1048 for (i = 0; i < 5 * ALIENS; i++) {
1049 if (aliens[i].state != DEAD &&
1050 (ABS(fire_x - (PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED +
1051 ALIEN_WIDTH / 2)) <= ALIEN_WIDTH / 2) &&
1052 (ABS(fire_y - (ALIEN_START_Y + aliens[i].y * ALIEN_HEIGHT +
1053 ALIEN_HEIGHT / 2)) <= ALIEN_HEIGHT / 2)) {
1055 /* TODO: play alien hit sound */
1057 if (aliens[i].state == BOMBER) {
1058 /* Set (possible) alien above to bomber */
1059 for (j = i - ALIENS; j >= 0; j -= ALIENS) {
1060 if (aliens[j].state != DEAD) {
1061 /* printf("New bomber (%d, %d)\n", j % ALIENS, j / ALIENS); */
1062 aliens[j].state = BOMBER;
1063 break;
1067 aliens[i].state = DEAD;
1068 exploding_alien = i;
1069 score += scores[aliens[i].type];
1070 draw_score();
1071 /* Update score part of screen */
1072 rb->lcd_update_rect(SCORENUM_X, SCORENUM_Y,
1073 PLAYFIELD_WIDTH - 2 * NUMBERS_WIDTH, FONT_HEIGHT);
1075 /* Paralyze aliens S_EXPLODE frames */
1076 aliens_paralyzed = S_EXPLODE;
1077 rb->lcd_bitmap(invadrox_alien_explode,
1078 PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED,
1079 ALIEN_START_Y + aliens[i].y * ALIEN_HEIGHT,
1080 ALIEN_EXPLODE_WIDTH, ALIEN_EXPLODE_HEIGHT);
1081 /* Since alien is 1 pixel taller than explosion sprite, erase bottom line */
1082 rb->lcd_hline(PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED,
1083 PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED + ALIEN_WIDTH,
1084 ALIEN_START_Y + (aliens[i].y + 1) * ALIEN_HEIGHT - 1);
1085 return;
1090 /* Draw shot */
1091 rb->lcd_set_foreground(LCD_WHITE);
1092 rb->lcd_vline(fire_x, fire_y, fire_y + SHOT_HEIGHT);
1093 rb->lcd_set_foreground(LCD_BLACK);
1094 } else if (fire < S_IDLE) {
1095 /* Count up towards S_IDLE, then erase explosion */
1096 fire++;
1097 if (fire == S_IDLE) {
1098 /* Erase explosion */
1099 if (fire_target == TARGET_TOP) {
1100 rb->lcd_fillrect(fire_x - (FIRE_WIDTH / 2), fire_y, FIRE_WIDTH, FIRE_HEIGHT);
1101 } else if (fire_target == TARGET_SHIELD) {
1102 /* Draw explosion with black pixels */
1103 fire_alpha(fire_x, fire_y, LCD_BLACK);
1110 /* Return a BOMBER alien */
1111 inline int random_bomber(void)
1113 int i, col;
1115 /* TODO: Weigh higher probability near ship */
1116 col = rb->rand() % ALIENS;
1117 for (i = col + 4 * ALIENS; i >= 0; i -= ALIENS) {
1118 if (aliens[i].state == BOMBER) {
1119 return i;
1123 /* No BOMBER found in this col */
1125 for (i = 0; i < 5 * ALIENS; i++) {
1126 if (aliens[i].state == BOMBER) {
1127 return i;
1131 /* No BOMBER found at all (error?) */
1133 return -1;
1137 inline void draw_bomb(int i)
1139 rb->lcd_bitmap_part(invadrox_bombs, bombs[i].type * BOMB_WIDTH,
1140 bombs[i].frame * BOMB_HEIGHT,
1141 BMPWIDTH_invadrox_bombs, bombs[i].x, bombs[i].y,
1142 BOMB_WIDTH, BOMB_HEIGHT);
1143 /* Advance frame */
1144 bombs[i].frame++;
1145 if (bombs[i].frame == bombs[i].frames) {
1146 bombs[i].frame = 0;
1151 void move_bombs(void)
1153 int i, j, bomber;
1154 bool abort;
1156 for (i = 0; i < max_bombs; i++) {
1158 switch (bombs[i].state) {
1160 case S_IDLE:
1161 if (ship_hit) {
1162 continue;
1164 bomber = random_bomber();
1165 if (bomber < 0) {
1166 DBG("ERROR: No bomber available\n");
1167 continue;
1169 /* x, y */
1170 bombs[i].x = PLAYFIELD_X + LIVES_X + aliens[bomber].x * ALIEN_SPEED + ALIEN_WIDTH / 2;
1171 bombs[i].y = ALIEN_START_Y + (aliens[bomber].y + 1) * ALIEN_HEIGHT;
1173 /* Check for duplets in x and y direction */
1174 abort = false;
1175 for (j = i - 1; j >= 0; j--) {
1176 if ((bombs[j].state == S_ACTIVE) &&
1177 ((bombs[i].x == bombs[j].x) || (bombs[i].y == bombs[j].y))) {
1178 abort = true;
1179 break;
1182 if (abort) {
1183 /* Skip this one, continue with next bomb */
1184 /* printf("Bomb %d duplet of %d\n", i, j); */
1185 continue;
1188 /* Passed, set type */
1189 bombs[i].type = rb->rand() % 3;
1190 bombs[i].frame = 0;
1191 if (bombs[i].type == 0) {
1192 bombs[i].frames = 3;
1193 } else if (bombs[i].type == 1) {
1194 bombs[i].frames = 4;
1195 } else {
1196 bombs[i].frames = 6;
1199 /* Bombs away */
1200 bombs[i].state = S_ACTIVE;
1201 draw_bomb(i);
1202 continue;
1204 break;
1206 case S_ACTIVE:
1207 /* Erase old position */
1208 rb->lcd_fillrect(bombs[i].x, bombs[i].y, BOMB_WIDTH, BOMB_HEIGHT);
1210 /* Move */
1211 bombs[i].y += BOMB_SPEED;
1213 /* Check if bottom hit */
1214 if (bombs[i].y + BOMB_HEIGHT >= PLAYFIELD_Y) {
1215 bombs[i].y = PLAYFIELD_Y - FIRE_HEIGHT + 1;
1216 fire_alpha(bombs[i].x, bombs[i].y, LCD_WHITE);
1217 bombs[i].state = S_EXPLODE;
1218 bombs[i].target = TARGET_BOTTOM;
1219 break;
1222 /* Check for green (ship or shield) */
1223 for (j = BOMB_HEIGHT; j >= BOMB_HEIGHT - BOMB_SPEED; j--) {
1224 bombs[i].target = 0;
1225 if(get_pixel(bombs[i].x + BOMB_WIDTH / 2, bombs[i].y + j) == screen_green) {
1226 /* Move to hit pixel */
1227 bombs[i].x += BOMB_WIDTH / 2;
1228 bombs[i].y += j;
1230 /* Check if ship is hit */
1231 if (bombs[i].y > SHIELD_Y + SHIELD_HEIGHT && bombs[i].y < PLAYFIELD_Y) {
1233 /* TODO: play ship hit sound */
1235 ship_hit = true;
1236 ship_frame = 1;
1237 ship_frame_counter = 0;
1238 bombs[i].state = S_EXPLODE * 4;
1239 bombs[i].target = TARGET_SHIP;
1240 rb->lcd_bitmap_part(invadrox_ships, 0, 1 * SHIP_HEIGHT,
1241 BMPWIDTH_invadrox_ships, ship_x, SHIP_Y,
1242 SHIP_WIDTH, SHIP_HEIGHT);
1243 break;
1245 /* Shield hit */
1246 bombs[i].state = S_EXPLODE;
1247 bombs[i].target = TARGET_SHIELD;
1248 /* Center explosion around hit pixel in shield */
1249 bombs[i].y -= FIRE_HEIGHT / 2;
1250 fire_alpha(bombs[i].x, bombs[i].y, SLIME_GREEN);
1251 break;
1255 if (bombs[i].target != 0) {
1256 /* Hit ship or shield, continue */
1257 continue;
1260 draw_bomb(i);
1261 break;
1263 default:
1264 /* If we get here state should be < 0, exploding */
1265 bombs[i].state++;
1266 if (bombs[i].state == S_IDLE) {
1267 if (ship_hit) {
1268 /* Erase explosion */
1269 rb->lcd_fillrect(ship_x, SHIP_Y, SHIP_WIDTH, SHIP_HEIGHT);
1270 rb->lcd_update_rect(ship_x, SHIP_Y, SHIP_WIDTH, SHIP_HEIGHT);
1271 ship_hit = false;
1272 ship_frame = 0;
1273 ship_x = PLAYFIELD_X + 2 * LIVES_X;
1274 lives--;
1275 if (lives == 0) {
1276 game_over = true;
1277 return;
1279 draw_lives();
1280 /* Sleep 1s to give player time to examine lives left */
1281 rb->sleep(HZ);
1283 /* Erase explosion (even if ship hit, might be another bomb) */
1284 fire_alpha(bombs[i].x, bombs[i].y, LCD_BLACK);
1286 break;
1292 inline void move_ship(void)
1294 ship_dir += ship_acc;
1295 if (ship_dir > max_ship_speed) {
1296 ship_dir = max_ship_speed;
1298 if (ship_dir < -max_ship_speed) {
1299 ship_dir = -max_ship_speed;
1301 ship_x += ship_dir;
1302 if (ship_x < SHIP_MIN_X) {
1303 ship_x = SHIP_MIN_X;
1305 if (ship_x > SHIP_MAX_X) {
1306 ship_x = SHIP_MAX_X;
1309 draw_ship();
1313 /* Unidentified Flying Object */
1314 void move_ufo(void)
1316 static int ufo_speed;
1317 static int counter;
1318 int mystery_score;
1320 switch (ufo_state) {
1322 case S_IDLE:
1324 if (rb->rand() % 500 == 0) {
1325 /* Uh-oh, it's time to launch a mystery UFO */
1327 /* TODO: Play UFO sound */
1329 if (rb->rand() % 2) {
1330 ufo_speed = UFO_SPEED;
1331 ufo_x = PLAYFIELD_X;
1332 } else {
1333 ufo_speed = -UFO_SPEED;
1334 ufo_x = LCD_WIDTH - PLAYFIELD_X - UFO_WIDTH;
1336 ufo_state = S_ACTIVE;
1337 /* UFO will be drawn next frame */
1339 break;
1341 case S_ACTIVE:
1342 /* Erase old pos */
1343 rb->lcd_fillrect(ufo_x, UFO_Y, UFO_WIDTH, UFO_HEIGHT);
1344 /* Move */
1345 ufo_x += ufo_speed;
1346 /* Check bounds */
1347 if (ufo_x < PLAYFIELD_X || ufo_x > LCD_WIDTH - PLAYFIELD_X - UFO_WIDTH) {
1348 ufo_state = S_IDLE;
1349 break;
1351 /* Draw new pos */
1352 rb->lcd_bitmap(invadrox_ufo, ufo_x, UFO_Y, UFO_WIDTH, UFO_HEIGHT);
1353 break;
1355 case S_SHOWSCORE:
1356 counter++;
1357 if (counter == S_IDLE) {
1358 /* Erase mystery number */
1359 rb->lcd_fillrect(ufo_x, UFO_Y, 3 * NUMBERS_WIDTH + 2 * NUM_SPACING, FONT_HEIGHT);
1360 ufo_state = S_IDLE;
1362 break;
1364 default:
1365 /* Exploding */
1366 ufo_state++;
1367 if (ufo_state == S_IDLE) {
1368 /* Erase explosion */
1369 rb->lcd_fillrect(ufo_x, UFO_Y - 1, UFO_EXPLODE_WIDTH, UFO_EXPLODE_HEIGHT);
1370 ufo_state = S_SHOWSCORE;
1371 counter = S_EXPLODE * 4;
1372 /* Draw mystery_score, sleep, increase score and continue */
1373 mystery_score = 50 + (rb->rand() % 6) * 50;
1374 if (mystery_score < 100) {
1375 draw_number(ufo_x, UFO_Y, mystery_score, 2);
1376 } else {
1377 draw_number(ufo_x, UFO_Y, mystery_score, 3);
1379 score += mystery_score;
1380 draw_score();
1382 break;
1387 void draw_background(void)
1390 rb->lcd_bitmap(invadrox_background, 0, 0, LCD_WIDTH, LCD_HEIGHT);
1391 rb->lcd_update();
1395 void new_level(void)
1397 int i;
1399 draw_background();
1400 /* Give an extra life for each new level */
1401 if (lives < MAX_LIVES) {
1402 lives++;
1404 draw_lives();
1406 /* Score */
1407 draw_score();
1408 draw_number(HISCORENUM_X, SCORENUM_Y, hiscore.score, 4);
1410 level++;
1411 draw_level();
1412 level_finished = false;
1414 ufo_state = S_IDLE;
1416 /* Init alien positions and states */
1417 for (i = 0; i < 4 * ALIENS; i++) {
1418 aliens[i].x = 0 + (i % ALIENS) * ((ALIEN_WIDTH + ALIEN_SPACING) / ALIEN_SPEED);
1419 aliens[i].y = 2 * (i / ALIENS);
1420 aliens[i].state = ALIVE;
1422 /* Last row, bombers */
1423 for (i = 4 * ALIENS; i < 5 * ALIENS; i++) {
1424 aliens[i].x = 0 + (i % ALIENS) * ((ALIEN_WIDTH + ALIEN_SPACING) / ALIEN_SPEED);
1425 aliens[i].y = 2 * (i / ALIENS);
1426 aliens[i].state = BOMBER;
1429 /* Init bombs to inactive (S_IDLE) */
1430 for (i = 0; i < MAX_BOMBS; i++) {
1431 bombs[i].state = S_IDLE;
1434 /* Start aliens closer to earth from level 2 */
1435 for (i = 0; i < 5 * ALIENS; i++) {
1436 if (level < 6) {
1437 aliens[i].y += level - 1;
1438 } else {
1439 aliens[i].y += 5;
1443 /* Max concurrent bombs */
1444 max_bombs = 1;
1446 gamespeed = 2;
1448 if (level > 1) {
1449 max_bombs++;
1452 /* Increase speed */
1453 if (level > 2) {
1454 gamespeed++;
1457 if (level > 3) {
1458 max_bombs++;
1461 /* Increase speed more */
1462 if (level > 4) {
1463 gamespeed++;
1466 if (level > 5) {
1467 max_bombs++;
1470 /* 4 shields */
1471 for (i = 1; i <= 4; i++) {
1472 rb->lcd_bitmap(invadrox_shield,
1473 PLAYFIELD_X + i * PLAYFIELD_WIDTH / 5 - SHIELD_WIDTH / 2,
1474 SHIELD_Y, SHIELD_WIDTH, SHIELD_HEIGHT);
1477 /* Bottom line */
1478 rb->lcd_set_foreground(SLIME_GREEN);
1479 rb->lcd_hline(PLAYFIELD_X, LCD_WIDTH - PLAYFIELD_X, PLAYFIELD_Y);
1480 /* Restore foreground to black (for fast erase later). */
1481 rb->lcd_set_foreground(LCD_BLACK);
1483 ship_x = PLAYFIELD_X + 2 * LIVES_X;
1484 if (level == 1) {
1485 old_ship_x = ship_x;
1487 ship_dir = 0;
1488 ship_acc = 0;
1489 ship_frame = 0;
1490 ship_hit = false;
1491 fire = S_IDLE;
1492 /* Start moving the bottom row left to right */
1493 curr_alien = 4 * ALIENS;
1494 aliens_paralyzed = 0;
1495 aliens_right = true;
1496 aliens_down = false;
1497 hit_left_border = false;
1498 hit_right_border = false;
1499 /* TODO: Change max_ship_speed to 3 at higher levels */
1500 max_ship_speed = 2;
1502 draw_aliens();
1504 rb->lcd_update();
1508 void init_invadrox(void)
1510 int i;
1512 /* Seed random number generator with a "random" number */
1513 rb->srand(rb->get_time()->tm_sec + rb->get_time()->tm_min * 60);
1515 /* Precalculate start of each scanline */
1516 for (i = 0; i < LCD_HEIGHT; i++) {
1517 #if (LCD_DEPTH >= 8)
1518 ytab[i] = i * LCD_WIDTH;
1519 #elif (LCD_DEPTH == 2) && (LCD_PIXELFORMAT == HORIZONTAL_PACKING)
1520 ytab[i] = i * (LCD_WIDTH / 4);
1521 #elif (LCD_DEPTH == 2) && (LCD_PIXELFORMAT == VERTICAL_PACKING)
1522 ytab[i] = (i / 4) * LCD_WIDTH;
1523 #else
1524 #error pixelformat not implemented yet
1525 #endif
1528 rb->lcd_set_background(LCD_BLACK);
1529 rb->lcd_set_foreground(LCD_BLACK);
1531 if (highscore_load(HISCOREFILE, &hiscore, 1) < 0) {
1532 /* Init hiscore to 0 */
1533 rb->strlcpy(hiscore.name, "Invader", sizeof(hiscore.name));
1534 hiscore.score = 0;
1535 hiscore.level = 1;
1538 /* Init alien types in aliens array */
1539 for (i = 0; i < 1 * ALIENS; i++) {
1540 aliens[i].type = 0; /* Kang */
1542 for (; i < 3 * ALIENS; i++) {
1543 aliens[i].type = 1; /* Kodos */
1545 for (; i < 5 * ALIENS; i++) {
1546 aliens[i].type = 2; /* Serak */
1550 /* Save screen white color */
1551 rb->lcd_set_foreground(LCD_WHITE);
1552 rb->lcd_drawpixel(0, 0);
1553 rb->lcd_update_rect(0, 0, 1, 1);
1554 screen_white = get_pixel(0, 0);
1556 /* Save screen green color */
1557 rb->lcd_set_foreground(SLIME_GREEN);
1558 rb->lcd_drawpixel(0, 0);
1559 rb->lcd_update_rect(0, 0, 1, 1);
1560 screen_green = get_pixel(0, 0);
1562 /* Restore black foreground */
1563 rb->lcd_set_foreground(LCD_BLACK);
1565 new_level();
1567 /* Flash score at start */
1568 for (i = 0; i < 5; i++) {
1569 rb->lcd_fillrect(SCORENUM_X, SCORENUM_Y,
1570 4 * NUMBERS_WIDTH + 3 * NUM_SPACING,
1571 FONT_HEIGHT);
1572 rb->lcd_update_rect(SCORENUM_X, SCORENUM_Y,
1573 4 * NUMBERS_WIDTH + 3 * NUM_SPACING,
1574 FONT_HEIGHT);
1575 rb->sleep(HZ / 10);
1576 draw_number(SCORENUM_X, SCORENUM_Y, score, 4);
1577 rb->sleep(HZ / 10);
1582 inline bool handle_buttons(void)
1584 static unsigned int oldbuttonstate = 0;
1586 unsigned int released, pressed, newbuttonstate;
1588 if (ship_hit) {
1589 /* Don't allow ship movement during explosion */
1590 newbuttonstate = 0;
1591 } else {
1592 newbuttonstate = rb->button_status();
1594 if(newbuttonstate == oldbuttonstate) {
1595 if (newbuttonstate == 0) {
1596 /* No button pressed. Stop ship. */
1597 ship_acc = 0;
1598 if (ship_dir > 0) {
1599 ship_dir--;
1601 if (ship_dir < 0) {
1602 ship_dir++;
1605 /* return false; */
1606 goto check_usb;
1608 released = ~newbuttonstate & oldbuttonstate;
1609 pressed = newbuttonstate & ~oldbuttonstate;
1610 oldbuttonstate = newbuttonstate;
1611 if (pressed) {
1612 if (pressed & LEFT) {
1613 if (ship_acc > -1) {
1614 ship_acc--;
1617 if (pressed & RIGHT) {
1618 if (ship_acc < 1) {
1619 ship_acc++;
1622 if (pressed & FIRE) {
1623 if (fire == S_IDLE) {
1624 /* Fire shot */
1625 fire_x = ship_x + SHIP_WIDTH / 2;
1626 fire_y = SHIP_Y - SHOT_HEIGHT;
1627 fire = S_ACTIVE;
1628 /* TODO: play fire sound */
1631 #ifdef RC_QUIT
1632 if (pressed & RC_QUIT) {
1633 rb->splash(HZ * 1, "Quit");
1634 return true;
1636 #endif
1637 if (pressed & QUIT) {
1638 rb->splash(HZ * 1, "Quit");
1639 return true;
1642 if (released) {
1643 if ((released & LEFT)) {
1644 if (ship_acc < 1) {
1645 ship_acc++;
1648 if ((released & RIGHT)) {
1649 if (ship_acc > -1) {
1650 ship_acc--;
1655 check_usb:
1657 /* Quit if USB is connected */
1658 if (rb->button_get(false) == SYS_USB_CONNECTED) {
1659 return true;
1662 return false;
1666 void game_loop(void)
1668 int i, end;
1670 /* Print dimensions (just for debugging) */
1671 DBG("%03dx%03dx%02d\n", LCD_WIDTH, LCD_HEIGHT, LCD_DEPTH);
1673 /* Init */
1674 init_invadrox();
1676 while (1) {
1677 /* Convert CYCLETIME (in ms) to HZ */
1678 end = *rb->current_tick + (CYCLETIME * HZ) / 1000;
1680 if (handle_buttons()) {
1681 return;
1684 /* Animate */
1685 move_ship();
1686 move_fire();
1688 /* Check if level is finished (marked by move_fire) */
1689 if (level_finished) {
1690 /* TODO: Play level finished sound */
1691 new_level();
1694 move_ufo();
1696 /* Move aliens */
1697 if (!aliens_paralyzed && !ship_hit) {
1698 for (i = 0; i < gamespeed; i++) {
1699 if (!move_aliens()) {
1700 if (game_over) {
1701 return;
1707 /* Move alien bombs */
1708 move_bombs();
1709 if (game_over) {
1710 return;
1713 /* Update "playfield" rect */
1714 rb->lcd_update_rect(PLAYFIELD_X, SCORENUM_Y + FONT_HEIGHT,
1715 PLAYFIELD_WIDTH,
1716 PLAYFIELD_Y + 1 - SCORENUM_Y - FONT_HEIGHT);
1718 /* Wait until next frame */
1719 DBG("%ld (%d)\n", end - *rb->current_tick, (CYCLETIME * HZ) / 1000);
1720 if (end > *rb->current_tick) {
1721 rb->sleep(end - *rb->current_tick);
1722 } else {
1723 rb->yield();
1726 } /* end while */
1730 /* this is the plugin entry point */
1731 enum plugin_status plugin_start(UNUSED const void* parameter)
1733 rb->lcd_setfont(FONT_SYSFIXED);
1734 /* Turn off backlight timeout */
1735 backlight_force_on(); /* backlight control in lib/helper.c */
1737 /* now go ahead and have fun! */
1738 game_loop();
1740 /* Game Over. */
1741 /* TODO: Play game over sound */
1742 rb->splash(HZ * 2, "Game Over");
1743 if (score > hiscore.score) {
1744 /* Save new hiscore */
1745 highscore_update(score, level, "Invader", &hiscore, 1);
1746 highscore_save(HISCOREFILE, &hiscore, 1);
1749 /* Restore user's original backlight setting */
1750 rb->lcd_setfont(FONT_UI);
1751 /* Turn on backlight timeout (revert to settings) */
1752 backlight_use_settings(); /* backlight control in lib/helper.c */
1754 return PLUGIN_OK;
1760 * GNU Emacs settings: Kernighan & Richie coding style with
1761 * 4 spaces indent and no tabs.
1762 * Local Variables:
1763 * c-file-style: "k&r"
1764 * c-basic-offset: 4
1765 * indent-tabs-mode: nil
1766 * End: