fix wrong score recording.
[kugel-rb.git] / apps / plugins / invadrox.c
blobfa7cb81e02134a5eab27d6830f1f0ddf67274125
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 PLUGIN_HEADER
34 /* Original graphics is only 1bpp so it should be portable
35 * to most targets. But for now, only support the simple ones.
37 #ifndef HAVE_LCD_BITMAP
38 #error INVADROX: Unsupported LCD
39 #endif
41 #if (LCD_DEPTH < 2)
42 #error INVADROX: Unsupported LCD
43 #endif
45 /* #define DEBUG */
46 #ifdef DEBUG
47 #define DBG(format, arg...) { DEBUGF("%s: " format, __FUNCTION__, ## arg); }
48 #else
49 #define DBG(format, arg...) {}
50 #endif
52 #if CONFIG_KEYPAD == IRIVER_H100_PAD
54 #define QUIT BUTTON_OFF
55 #define LEFT BUTTON_LEFT
56 #define RIGHT BUTTON_RIGHT
57 #define FIRE BUTTON_ON
59 #elif CONFIG_KEYPAD == IRIVER_H300_PAD
61 #define QUIT BUTTON_OFF
62 #define LEFT BUTTON_LEFT
63 #define RIGHT BUTTON_RIGHT
64 #define FIRE BUTTON_SELECT
66 #elif (CONFIG_KEYPAD == IRIVER_H10_PAD)
68 #define QUIT BUTTON_POWER
69 #define LEFT BUTTON_LEFT
70 #define RIGHT BUTTON_RIGHT
71 #define FIRE BUTTON_PLAY
73 #elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \
74 (CONFIG_KEYPAD == IPOD_3G_PAD) || \
75 (CONFIG_KEYPAD == IPOD_1G2G_PAD)
77 #define QUIT BUTTON_MENU
78 #define LEFT BUTTON_LEFT
79 #define RIGHT BUTTON_RIGHT
80 #define FIRE BUTTON_SELECT
82 #elif CONFIG_KEYPAD == IAUDIO_X5M5_PAD
84 #define QUIT BUTTON_POWER
85 #define LEFT BUTTON_LEFT
86 #define RIGHT BUTTON_RIGHT
87 #define FIRE BUTTON_SELECT
89 #elif CONFIG_KEYPAD == GIGABEAT_PAD
91 #define QUIT BUTTON_POWER
92 #define LEFT BUTTON_LEFT
93 #define RIGHT BUTTON_RIGHT
94 #define FIRE BUTTON_SELECT
96 #elif CONFIG_KEYPAD == SANSA_E200_PAD
98 #define QUIT BUTTON_POWER
99 #define LEFT BUTTON_LEFT
100 #define RIGHT BUTTON_RIGHT
101 #define FIRE BUTTON_SELECT
103 #elif CONFIG_KEYPAD == SANSA_FUZE_PAD
105 #define QUIT (BUTTON_HOME|BUTTON_REPEAT)
106 #define LEFT BUTTON_LEFT
107 #define RIGHT BUTTON_RIGHT
108 #define FIRE BUTTON_SELECT
110 #elif CONFIG_KEYPAD == ELIO_TPJ1022_PAD
112 /* TODO: Figure out which buttons to use for Tatung Elio TPJ-1022 */
113 #define QUIT BUTTON_AB
114 #define LEFT BUTTON_LEFT
115 #define RIGHT BUTTON_RIGHT
116 #define FIRE BUTTON_MENU
118 #elif CONFIG_KEYPAD == GIGABEAT_S_PAD
120 #define QUIT BUTTON_BACK
121 #define LEFT BUTTON_LEFT
122 #define RIGHT BUTTON_RIGHT
123 #define FIRE BUTTON_SELECT
125 #elif CONFIG_KEYPAD == COWOND2_PAD
127 #define QUIT BUTTON_POWER
129 #elif CONFIG_KEYPAD == IAUDIO67_PAD
131 #define QUIT BUTTON_POWER
132 #define LEFT BUTTON_LEFT
133 #define RIGHT BUTTON_RIGHT
134 #define FIRE BUTTON_PLAY
136 #elif CONFIG_KEYPAD == CREATIVEZVM_PAD
138 #define QUIT BUTTON_BACK
139 #define LEFT BUTTON_LEFT
140 #define RIGHT BUTTON_RIGHT
141 #define FIRE BUTTON_SELECT
143 #elif CONFIG_KEYPAD == ONDAVX747_PAD || CONFIG_KEYPAD == MROBE500_PAD
145 #define QUIT BUTTON_POWER
147 #else
148 #error INVADROX: Unsupported keypad
149 #endif
151 #ifdef HAVE_TOUCHSCREEN
152 #ifndef QUIT
153 #define QUIT BUTTON_TOPLEFT
154 #endif
155 #ifndef LEFT
156 #define LEFT BUTTON_MIDLEFT
157 #endif
158 #ifndef RIGHT
159 #define RIGHT BUTTON_MIDRIGHT
160 #endif
161 #ifndef FIRE
162 #define FIRE BUTTON_CENTER
163 #endif
164 #endif
166 #ifndef UNUSED
167 #define UNUSED __attribute__ ((unused))
168 #endif
170 #ifndef ABS
171 #define ABS(x) (((x) < 0) ? (-(x)) : (x))
172 #endif
175 /* Defines common to all models */
176 #define UFO_Y (SCORENUM_Y + FONT_HEIGHT + ALIEN_HEIGHT)
177 #define PLAYFIELD_Y (LCD_HEIGHT - SHIP_HEIGHT - 2)
178 #define PLAYFIELD_WIDTH (LCD_WIDTH - 2 * PLAYFIELD_X)
179 #define LEVEL_X (LCD_WIDTH - PLAYFIELD_X - LIVES_X - 2 * NUMBERS_WIDTH - 3 * NUM_SPACING)
180 #define SHIP_MIN_X (PLAYFIELD_X + PLAYFIELD_WIDTH / 5 - SHIELD_WIDTH / 2 - SHIP_WIDTH)
181 #define SHIP_MAX_X (PLAYFIELD_X + 4 * PLAYFIELD_WIDTH / 5 + SHIELD_WIDTH / 2)
182 /* SCORE_Y = 0 for most targets. Gigabeat redefines it later. */
183 #define SCORE_Y 0
184 #define MAX_LIVES 8
187 /* iPod Video defines */
188 #if (LCD_WIDTH == 320) && (LCD_HEIGHT == 240)
190 /* Original arcade game size 224x240, 1bpp with
191 * red overlay at top and green overlay at bottom.
193 * iPod Video: 320x240x16
194 * ======================
195 * X: 48p padding at left/right gives 224p playfield in middle.
196 * 10p "border" gives 204p actual playfield. UFO use full 224p.
197 * Y: Use full 240p.
199 * MAX_X = (204 - 12) / 2 - 1 = 95
201 * Y: Score text 7 0
202 * Space 10 7
203 * Score 7 17
204 * Space 8 24
205 * 3 Ufo 7 32
206 * 2 Space Aliens start at 32 + 3 * 8 = 56
207 * 0 aliens 9*8 56 -
208 * space ~7*8 128 | 18.75 aliens space between
209 * shield 2*8 182 | first alien and ship.
210 * space 8 198 | MAX_Y = 18
211 * ship 8 206 -
212 * space 2*8 214
213 * hline 1 230 - PLAYFIELD_Y
214 * bottom border 10 240
215 * Lives and Level goes inside bottom border
218 #define ARCADISH_GRAPHICS
219 #define PLAYFIELD_X 48
220 #define SHIP_Y (PLAYFIELD_Y - 3 * SHIP_HEIGHT)
221 #define ALIEN_START_Y (UFO_Y + 3 * ALIEN_HEIGHT)
222 #define SCORENUM_X (PLAYFIELD_X + NUMBERS_WIDTH)
223 #define SCORENUM_Y SCORE_Y + (2 * (FONT_HEIGHT + 1) + 1)
224 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 1 - 6 * NUMBERS_WIDTH - 5 * NUM_SPACING)
225 #define SHIELD_Y (PLAYFIELD_Y - 6 * SHIP_HEIGHT)
226 #define LIVES_X 10
227 #define MAX_X 95
228 #define MAX_Y 18
231 #elif (LCD_WIDTH == 176) && (LCD_HEIGHT == 220)
233 /* Sandisk Sansa e200: 176x220x16
234 * ==============================
235 * X: No padding. 8p border -> 160p playfield.
237 * 8p Aliens with 3p spacing -> 88 + 30 = 118p aliens block.
238 * (160 - 118) / 2 = 21 rounds for whole block (more than original)
239 * MAX_X = (160 - 8) / 2 - 1 = 75 rounds for single alien (less than original)
241 * LOGO 70 0
242 * Score text 5 70
243 * Space 5 75
244 * Y Score 5 80
245 * Space 10 85
246 * 2 Ufo 5 95
247 * 2 Space 10 100
248 * 0 aliens 9*5 110 -
249 * space ~7*5 155 | 18.6 aliens space between
250 * shield 2*5 188 | first alien and ship.
251 * space 5 198 | MAX_Y = 18
252 * ship 5 203 -
253 * space 5 208
254 * hline 1 213 PLAYFIELD_Y
255 * bottom border 6
256 * LCD_HEIGHT 220
257 * Lives and Level goes inside bottom border
260 #define SMALL_GRAPHICS
261 #define PLAYFIELD_X 0
262 #define SHIP_Y (PLAYFIELD_Y - 2 * SHIP_HEIGHT)
263 #define SHIELD_Y (SHIP_Y - SHIP_HEIGHT - SHIELD_HEIGHT)
264 #define ALIEN_START_Y (UFO_Y + 3 * SHIP_HEIGHT)
265 /* Redefine SCORE_Y */
266 #undef SCORE_Y
267 #define SCORE_Y 70
268 #define SCORENUM_X (PLAYFIELD_X + NUMBERS_WIDTH)
269 #define SCORENUM_Y (SCORE_Y + 2 * FONT_HEIGHT)
270 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 1 - 6 * NUMBERS_WIDTH - 5 * NUM_SPACING)
271 #define LIVES_X 8
272 #define MAX_X 75
273 #define MAX_Y 18
276 #elif (LCD_WIDTH == 176) && (LCD_HEIGHT == 132)
278 /* iPod Nano: 176x132x16
279 * ======================
280 * X: No padding. 8p border -> 160p playfield.
282 * LIVES_X 8
283 * ALIEN_WIDTH 8
284 * ALIEN_HEIGHT 5
285 * ALIEN_SPACING 3
286 * SHIP_WIDTH 10
287 * SHIP_HEIGHT 5
288 * FONT_HEIGHT 5
289 * UFO_WIDTH 10
290 * UFO_HEIGHT 5
291 * SHIELD_WIDTH 15
292 * SHIELD_HEIGHT 10
293 * MAX_X 75
294 * MAX_Y = 18
295 * ALIEN_START_Y (UFO_Y + 12)
297 * 8p Aliens with 3p spacing -> 88 + 30 = 118p aliens block.
298 * (160 - 118) / 2 = 21 rounds for whole block (more than original)
299 * MAX_X = (160 - 8) / 2 - 1 = 75 rounds for single alien (less than original)
301 * Y: Scoreline 5 0 (combine scoretext and numbers on same line)
302 * Space 5 5
303 * 1 Ufo 5 10
304 * 3 Space 7 15
305 * 2 aliens 9*5 22 -
306 * space ~7*5 67 | Just above 18 aliens space between
307 * shield 2*5 100 | first alien and ship.
308 * space 5 110 | MAX_Y = 18
309 * ship 5 115 -
310 * space 5 120
311 * hline 1 125 PLAYFIELD_Y
312 * bottom border 6 126
313 * LCD_HEIGHT 131
314 * Lives and Level goes inside bottom border
317 #define SMALL_GRAPHICS
318 #define PLAYFIELD_X 0
319 #define SHIP_Y (PLAYFIELD_Y - 2 * SHIP_HEIGHT)
320 #define ALIEN_START_Y (UFO_Y + 12)
321 #define SCORENUM_X (PLAYFIELD_X + 6 * NUMBERS_WIDTH + 5 * NUM_SPACING)
322 #define SCORENUM_Y SCORE_Y
323 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 4 * NUMBERS_WIDTH - 3 * NUM_SPACING)
324 #define SHIELD_Y (SHIP_Y - SHIP_HEIGHT - SHIELD_HEIGHT)
325 #define LIVES_X 8
326 #define MAX_X 75
327 #define MAX_Y 18
329 #elif (LCD_WIDTH == 160) && (LCD_HEIGHT == 128)
331 /* iAudio X5, iRiver H10 20Gb, iPod 3g/4g, H100, M5: 160x128
332 * =========================================================
333 * X: No padding. No border -> 160p playfield.
335 * LIVES_X 0
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 + 10)
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 * 2 Space 5 15
358 * 8 aliens 9*5 20 -
359 * space ~6*5 65 | Just above 18 aliens space between
360 * shield 2*5 96 | first alien and ship.
361 * space 5 106 | MAX_Y = 18
362 * ship 5 111 -
363 * space 5 116
364 * hline 1 121 PLAYFIELD_Y
365 * bottom border 6 122
366 * LCD_HEIGHT 128
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 + 10)
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 0
379 #define MAX_X 75
380 #define MAX_Y 18
383 #elif (LCD_WIDTH == 240) && ((LCD_HEIGHT == 320) || (LCD_HEIGHT == 400))
385 /* Gigabeat: 240x320x16
386 * ======================
387 * X: 8p padding at left/right gives 224p playfield in middle.
388 * 10p "border" gives 204p actual playfield. UFO use full 224p.
389 * Y: Use bottom 240p for playfield and top 80 pixels for logo.
391 * MAX_X = (204 - 12) / 2 - 1 = 95
393 * Y: Score text 7 0 + 80
394 * Space 10 7 + 80
395 * Score 7 17 + 80
396 * Space 8 24 + 80
397 * 3 Ufo 7 32 + 80
398 * 2 Space Aliens start at 32 + 3 * 8 = 56
399 * 0 aliens 9*8 56 -
400 * space ~7*8 128 | 18.75 aliens space between
401 * shield 2*8 182 | first alien and ship.
402 * space 8 198 | MAX_Y = 18
403 * ship 8 206 -
404 * space 2*8 214
405 * hline 1 230 310 - PLAYFIELD_Y
406 * bottom border 10 240 320
407 * Lives and Level goes inside bottom border
410 #define ARCADISH_GRAPHICS
411 #define PLAYFIELD_X 8
412 #define SHIP_Y (PLAYFIELD_Y - 3 * SHIP_HEIGHT)
413 #define ALIEN_START_Y (UFO_Y + 3 * ALIEN_HEIGHT)
414 /* Redefine SCORE_Y */
415 #undef SCORE_Y
416 #define SCORE_Y 80
417 #define SCORENUM_X (PLAYFIELD_X + NUMBERS_WIDTH)
418 #define SCORENUM_Y SCORE_Y + (2 * (FONT_HEIGHT + 1) + 1)
419 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 1 - 6 * NUMBERS_WIDTH - 5 * NUM_SPACING)
420 #define SHIELD_Y (PLAYFIELD_Y - 6 * SHIP_HEIGHT)
421 #define LIVES_X 10
422 #define MAX_X 95
423 #define MAX_Y 18
425 #elif (LCD_WIDTH == 220) && (LCD_HEIGHT == 176)
427 /* TPJ1022, H300, iPod Color: 220x176x16
428 * ============================
429 * X: 0p padding at left/right gives 220p playfield in middle.
430 * 8p "border" gives 204p actual playfield. UFO use full 220p.
431 * Y: Use full 176p for playfield.
433 * MAX_X = (204 - 12) / 2 - 1 = 95
435 * Y: Score text 7 0
436 * Space 8 7
437 * 1 Ufo 7 15
438 * 7 Space Aliens start at 15 + 3 * 8 = 56
439 * 6 aliens 9*8 25 -
440 * space ~7*8 103 | 15.6 aliens space between
441 * shield 2*8 126 | first alien and ship.
442 * space 8 142 | MAX_Y = 15
443 * ship 8 150 -
444 * space 8 158
445 * hline 1 166 - PLAYFIELD_Y
446 * bottom border 10 176
447 * Lives and Level goes inside bottom border
450 #define ARCADISH_GRAPHICS
451 #define PLAYFIELD_X 0
452 #define SHIP_Y (PLAYFIELD_Y - 2 * SHIP_HEIGHT)
453 #define ALIEN_START_Y (UFO_Y + 10)
454 #define SCORENUM_Y SCORE_Y
455 #define SCORENUM_X (PLAYFIELD_X + 6 * NUMBERS_WIDTH + 6 * NUM_SPACING)
456 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 4 * NUMBERS_WIDTH - 3 * NUM_SPACING)
457 #define SHIELD_Y (PLAYFIELD_Y - 5 * SHIP_HEIGHT)
458 #define LIVES_X 8
459 #define MAX_X 95
460 #define MAX_Y 15
463 #else
464 #error INVADROX: Unsupported LCD type
465 #endif
467 /* bitmaps */
468 #include "pluginbitmaps/invadrox_background.h"
470 /* get dimensions for later use from the bitmaps */
471 #include "pluginbitmaps/invadrox_aliens.h"
472 #include "pluginbitmaps/invadrox_ships.h"
473 #include "pluginbitmaps/invadrox_bombs.h"
474 #include "pluginbitmaps/invadrox_alien_explode.h"
475 #include "pluginbitmaps/invadrox_shield.h"
476 #include "pluginbitmaps/invadrox_ufo.h"
477 #include "pluginbitmaps/invadrox_ufo_explode.h"
478 #include "pluginbitmaps/invadrox_numbers.h"
479 #include "pluginbitmaps/invadrox_fire.h"
480 #define ALIEN_WIDTH (BMPWIDTH_invadrox_aliens/2)
481 #define ALIEN_HEIGHT (BMPHEIGHT_invadrox_aliens/3)
482 #define SHIP_WIDTH BMPWIDTH_invadrox_ships
483 #define SHIP_HEIGHT (BMPHEIGHT_invadrox_ships/3)
484 #define BOMB_WIDTH (BMPWIDTH_invadrox_bombs/3)
485 #define BOMB_HEIGHT (BMPHEIGHT_invadrox_bombs/6)
486 #define ALIEN_EXPLODE_WIDTH BMPWIDTH_invadrox_alien_explode
487 #define ALIEN_EXPLODE_HEIGHT BMPHEIGHT_invadrox_alien_explode
488 #define SHIELD_WIDTH BMPWIDTH_invadrox_shield
489 #define SHIELD_HEIGHT BMPHEIGHT_invadrox_shield
490 #define UFO_WIDTH BMPWIDTH_invadrox_ufo
491 #define UFO_HEIGHT BMPHEIGHT_invadrox_ufo
492 #define UFO_EXPLODE_WIDTH BMPWIDTH_invadrox_ufo_explode
493 #define UFO_EXPLODE_HEIGHT BMPHEIGHT_invadrox_ufo_explode
494 #define NUMBERS_WIDTH (BMPWIDTH_invadrox_numbers/10)
495 #define FONT_HEIGHT BMPHEIGHT_invadrox_numbers
496 #define FIRE_WIDTH BMPWIDTH_invadrox_fire
497 #define FIRE_HEIGHT BMPHEIGHT_invadrox_fire
499 /* Defines common to each "graphic type" */
500 #ifdef ARCADISH_GRAPHICS
502 #define SHOT_HEIGHT 5
503 #define ALIEN_SPACING 4
504 #define ALIEN_SPEED 2
505 #define UFO_SPEED 1
506 #define NUM_SPACING 3
507 #define FIRE_SPEED 8
508 #define BOMB_SPEED 3
509 #define ALIENS 11
511 #elif defined SMALL_GRAPHICS
513 #define SHOT_HEIGHT 4
514 #define ALIEN_SPACING 3
515 #define ALIEN_SPEED 2
516 #define UFO_SPEED 1
517 #define NUM_SPACING 2
518 #define FIRE_SPEED 6
519 #define BOMB_SPEED 2
520 #define ALIENS 11
522 #else
523 #error Graphic type not defined
524 #endif
527 /* Colors */
528 #if (LCD_DEPTH >= 8)
529 #define SLIME_GREEN LCD_RGBPACK(31, 254, 31)
530 #define UFO_RED LCD_RGBPACK(254, 31, 31)
531 #elif (LCD_DEPTH == 2)
532 #define SLIME_GREEN LCD_LIGHTGRAY
533 #define UFO_RED LCD_LIGHTGRAY
534 #else
535 #error LCD type not implemented yet
536 #endif
538 /* Alien states */
539 #define DEAD 0
540 #define ALIVE 1
541 #define BOMBER 2
543 /* Fire/bomb/ufo states */
544 #define S_IDLE 0
545 #define S_ACTIVE 1
546 #define S_SHOWSCORE 2
547 #define S_EXPLODE -9
549 /* Fire/bomb targets */
550 #define TARGET_TOP 0
551 #define TARGET_SHIELD 1
552 #define TARGET_SHIP 2
553 #define TARGET_BOTTOM 3
554 #define TARGET_UFO 4
556 #define HISCOREFILE PLUGIN_GAMES_DIR "/invadrox.high"
559 /* The time (in ms) for one iteration through the game loop - decrease this
560 * to speed up the game - note that current_tick is (currently) only accurate
561 * to 10ms.
563 #define CYCLETIME 40
566 /* Physical x is at PLAYFIELD_X + LIVES_X + x * ALIEN_SPEED
567 * Physical y is at y * ALIEN_HEIGHT
569 struct alien {
570 unsigned char x; /* x-coordinate (0 - 95) */
571 unsigned char y; /* y-coordinate (0 - 18) */
572 unsigned char type; /* 0 (Kang), 1 (Kodos), 2 (Serak) */
573 unsigned char state; /* Dead, alive or bomber */
576 /* Aliens box 5 rows * ALIENS aliens in each row */
577 struct alien aliens[5 * ALIENS];
579 #define MAX_BOMBS 4
580 struct bomb {
581 int x, y;
582 unsigned char type;
583 unsigned char frame; /* Current animation frame */
584 unsigned char frames; /* Number of frames in animation */
585 unsigned char target; /* Remember target during explosion frames */
586 int state; /* 0 (IDLE) = inactive, 1 (FIRE) or negative, exploding */
588 struct bomb bombs[MAX_BOMBS];
589 /* Increase max_bombs at higher levels */
590 int max_bombs;
592 /* Raw framebuffer value of shield/ship green color */
593 fb_data screen_green, screen_white;
595 /* For optimization, precalculate startoffset of each scanline */
596 unsigned int ytab[LCD_HEIGHT];
598 int lives = 2;
599 int score = 0;
600 int scores[3] = { 30, 20, 10 };
601 int level = 0;
602 struct highscore hiscore;
603 bool game_over = false;
604 int ship_x, old_ship_x, ship_dir, ship_acc, max_ship_speed;
605 int ship_frame, ship_frame_counter;
606 bool ship_hit;
607 int fire, fire_target, fire_x, fire_y;
608 int curr_alien, aliens_paralyzed, gamespeed;
609 int ufo_state, ufo_x;
610 bool level_finished;
611 bool aliens_down, aliens_right, hit_left_border, hit_right_border;
614 /* No standard get_pixel function yet, use this hack instead */
615 #if (LCD_DEPTH >= 8)
617 inline fb_data get_pixel(int x, int y)
619 return rb->lcd_framebuffer[ytab[y] + x];
622 #elif (LCD_DEPTH == 2)
624 #if (LCD_PIXELFORMAT == HORIZONTAL_PACKING)
625 static const unsigned char shifts[4] = {
626 6, 4, 2, 0
628 /* Horizontal packing */
629 inline fb_data get_pixel(int x, int y)
631 return (rb->lcd_framebuffer[ytab[y] + (x >> 2)] >> shifts[x & 3]) & 3;
633 #else
634 /* Vertical packing */
635 static const unsigned char shifts[4] = {
636 0, 2, 4, 6
638 inline fb_data get_pixel(int x, int y)
640 return (rb->lcd_framebuffer[ytab[y] + x] >> shifts[y & 3]) & 3;
642 #endif /* Horizontal/Vertical packing */
644 #else
645 #error get_pixel: pixelformat not implemented yet
646 #endif
649 /* Draw "digits" least significant digits of num at (x,y) */
650 void draw_number(int x, int y, int num, int digits)
652 int i;
653 int d;
655 for (i = digits - 1; i >= 0; i--) {
656 d = num % 10;
657 num = num / 10;
658 rb->lcd_bitmap_part(invadrox_numbers, d * NUMBERS_WIDTH, 0,
659 BMPWIDTH_invadrox_numbers,
660 x + i * (NUMBERS_WIDTH + NUM_SPACING), y,
661 NUMBERS_WIDTH, FONT_HEIGHT);
663 /* Update lcd */
664 rb->lcd_update_rect(x, y, 4 * NUMBERS_WIDTH + 3 * NUM_SPACING, FONT_HEIGHT);
668 inline void draw_score(void)
670 draw_number(SCORENUM_X, SCORENUM_Y, score, 4);
671 if (score > hiscore.score) {
672 /* Draw new hiscore (same as score) */
673 draw_number(HISCORENUM_X, SCORENUM_Y, score, 4);
678 void draw_level(void)
680 draw_number(LEVEL_X + 2 * NUM_SPACING, PLAYFIELD_Y + 2, level, 2);
684 void draw_lives(void)
686 int i;
687 /* Lives num */
688 rb->lcd_bitmap_part(invadrox_numbers, lives * NUMBERS_WIDTH, 0,
689 BMPWIDTH_invadrox_numbers, PLAYFIELD_X + LIVES_X, PLAYFIELD_Y + 2,
690 NUMBERS_WIDTH, FONT_HEIGHT);
692 /* Ships */
693 for (i = 0; i < (lives - 1); i++) {
694 rb->lcd_bitmap_part(invadrox_ships, 0, 0, BMPWIDTH_invadrox_ships,
695 PLAYFIELD_X + LIVES_X + SHIP_WIDTH + i * (SHIP_WIDTH + NUM_SPACING),
696 PLAYFIELD_Y + 1, SHIP_WIDTH, SHIP_HEIGHT);
699 /* Erase ship to the right (if less than MAX_LIVES) */
700 if (lives < MAX_LIVES) {
701 rb->lcd_fillrect(PLAYFIELD_X + LIVES_X + SHIP_WIDTH + i * (SHIP_WIDTH + NUM_SPACING),
702 PLAYFIELD_Y + 1, SHIP_WIDTH, SHIP_HEIGHT);
704 /* Update lives (and level) part of screen */
705 rb->lcd_update_rect(PLAYFIELD_X + LIVES_X, PLAYFIELD_Y + 1,
706 PLAYFIELD_WIDTH - 2 * LIVES_X, MAX(FONT_HEIGHT + 1, SHIP_HEIGHT + 1));
710 inline void draw_aliens(void)
712 int i;
714 for (i = 0; i < 5 * ALIENS; i++) {
715 rb->lcd_bitmap_part(invadrox_aliens, aliens[i].x & 1 ? ALIEN_WIDTH : 0, aliens[i].type * ALIEN_HEIGHT,
716 BMPWIDTH_invadrox_aliens, PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED,
717 ALIEN_START_Y + aliens[i].y * ALIEN_HEIGHT,
718 ALIEN_WIDTH, ALIEN_HEIGHT);
723 /* Return false if there is no next alive alien (round is over) */
724 inline bool next_alien(void)
726 bool ret = true;
728 do {
729 curr_alien++;
730 if (curr_alien % ALIENS == 0) {
731 /* End of this row. Move up one row. */
732 curr_alien -= 2 * ALIENS;
733 if (curr_alien < 0) {
734 /* No more aliens in this round. */
735 curr_alien = 4 * ALIENS;
736 ret = false;
739 } while (aliens[curr_alien].state == DEAD && ret);
741 if (!ret) {
742 /* No more alive aliens. Round finished. */
743 if (hit_right_border) {
744 if (hit_left_border) {
745 DBG("ERROR: both left and right borders are set (%d)\n", curr_alien);
747 /* Move down-left next round */
748 aliens_right = false;
749 aliens_down = true;
750 hit_right_border = false;
751 } else if (hit_left_border) {
752 /* Move down-right next round */
753 aliens_right = true;
754 aliens_down = true;
755 hit_left_border = false;
756 } else {
757 /* Not left nor right. Set down to false. */
758 aliens_down = false;
762 return ret;
766 /* All aliens have been moved.
767 * Set curr_alien to first alive.
768 * Return false if no-one is left alive.
770 bool first_alien(void)
772 int i, y;
774 for (y = 4; y >= 0; y--) {
775 for (i = y * ALIENS; i < (y + 1) * ALIENS; i++) {
776 if (aliens[i].state != DEAD) {
777 curr_alien = i;
778 return true;
783 /* All aliens dead. */
784 level_finished = true;
786 return false;
790 bool move_aliens(void)
792 int x, y, old_x, old_y;
794 /* Move current alien (curr_alien is pointing to a living alien) */
796 old_x = aliens[curr_alien].x;
797 old_y = aliens[curr_alien].y;
799 if (aliens_down) {
800 aliens[curr_alien].y++;
801 if (aliens[curr_alien].y == MAX_Y) {
802 /* Alien is at bottom. Game Over. */
803 DBG("Alien %d is at bottom. Game Over.\n", curr_alien);
804 game_over = true;
805 return false;
809 if (aliens_right) {
810 /* Moving right */
811 if (aliens[curr_alien].x < MAX_X) {
812 aliens[curr_alien].x++;
815 /* Now, after move, check if we hit the right border. */
816 if (aliens[curr_alien].x == MAX_X) {
817 hit_right_border = true;
820 } else {
821 /* Moving left */
822 if (aliens[curr_alien].x > 0) {
823 aliens[curr_alien].x--;
826 /* Now, after move, check if we hit the left border. */
827 if (aliens[curr_alien].x == 0) {
828 hit_left_border = true;
832 /* Erase old position */
833 x = PLAYFIELD_X + LIVES_X + old_x * ALIEN_SPEED;
834 y = ALIEN_START_Y + old_y * ALIEN_HEIGHT;
835 if (aliens[curr_alien].y != old_y) {
836 /* Moved in y-dir. Erase whole alien. */
837 rb->lcd_fillrect(x, y, ALIEN_WIDTH, ALIEN_HEIGHT);
838 } else {
839 if (aliens_right) {
840 /* Erase left edge */
841 rb->lcd_fillrect(x, y, ALIEN_SPEED, ALIEN_HEIGHT);
842 } else {
843 /* Erase right edge */
844 x += ALIEN_WIDTH - ALIEN_SPEED;
845 rb->lcd_fillrect(x, y, ALIEN_SPEED, ALIEN_HEIGHT);
849 /* Draw alien at new pos */
850 x = PLAYFIELD_X + LIVES_X + aliens[curr_alien].x * ALIEN_SPEED;
851 y = ALIEN_START_Y + aliens[curr_alien].y * ALIEN_HEIGHT;
852 rb->lcd_bitmap_part(invadrox_aliens,
853 aliens[curr_alien].x & 1 ? ALIEN_WIDTH : 0, aliens[curr_alien].type * ALIEN_HEIGHT,
854 BMPWIDTH_invadrox_aliens, x, y, ALIEN_WIDTH, ALIEN_HEIGHT);
856 if (!next_alien()) {
857 /* Round finished. Set curr_alien to first alive from bottom. */
858 if (!first_alien()) {
859 /* Should never happen. Taken care of in move_fire(). */
860 return false;
862 /* TODO: Play next background sound */
865 return true;
869 inline void draw_ship(void)
871 /* Erase old ship */
872 if (old_ship_x < ship_x) {
873 /* Move right. Erase leftmost part of ship. */
874 rb->lcd_fillrect(old_ship_x, SHIP_Y, ship_x - old_ship_x, SHIP_HEIGHT);
875 } else if (old_ship_x > ship_x) {
876 /* Move left. Erase rightmost part of ship. */
877 rb->lcd_fillrect(ship_x + SHIP_WIDTH, SHIP_Y, old_ship_x - ship_x, SHIP_HEIGHT);
880 /* Draw ship */
881 rb->lcd_bitmap_part(invadrox_ships, 0, ship_frame * SHIP_HEIGHT,
882 BMPWIDTH_invadrox_ships, ship_x, SHIP_Y, SHIP_WIDTH, SHIP_HEIGHT);
883 if (ship_hit) {
884 /* Alternate between frame 1 and 2 during hit */
885 ship_frame_counter++;
886 if (ship_frame_counter > 2) {
887 ship_frame_counter = 0;
888 ship_frame++;
889 if (ship_frame > 2) {
890 ship_frame = 1;
895 /* Save ship_x for next time */
896 old_ship_x = ship_x;
900 inline void fire_alpha(int xc, int yc, fb_data color)
902 int oldmode = rb->lcd_get_drawmode();
904 rb->lcd_set_foreground(color);
905 rb->lcd_set_drawmode(DRMODE_FG);
907 rb->lcd_mono_bitmap(invadrox_fire, xc - (FIRE_WIDTH/2), yc, FIRE_WIDTH, FIRE_HEIGHT);
909 rb->lcd_set_foreground(LCD_BLACK);
910 rb->lcd_set_drawmode(oldmode);
914 void move_fire(void)
916 bool hit_green = false;
917 bool hit_white = false;
918 int i, j;
919 static int exploding_alien = -1;
920 fb_data pix;
922 if (fire == S_IDLE) {
923 return;
926 /* Alien hit. Wait until explosion is finished. */
927 if (aliens_paralyzed < 0) {
928 aliens_paralyzed++;
929 if (aliens_paralyzed == 0) {
930 /* Erase exploding_alien */
931 rb->lcd_fillrect(PLAYFIELD_X + LIVES_X + aliens[exploding_alien].x * ALIEN_SPEED,
932 ALIEN_START_Y + aliens[exploding_alien].y * ALIEN_HEIGHT,
933 ALIEN_EXPLODE_WIDTH, ALIEN_HEIGHT);
934 fire = S_IDLE;
935 /* Special case. We killed curr_alien. */
936 if (exploding_alien == curr_alien) {
937 if (!next_alien()) {
938 /* Round finished. Set curr_alien to first alive from bottom. */
939 first_alien();
943 return;
946 if (fire == S_ACTIVE) {
948 /* Erase */
949 rb->lcd_vline(fire_x, fire_y, fire_y + SHOT_HEIGHT);
951 /* Check top */
952 if (fire_y <= SCORENUM_Y + FONT_HEIGHT + 4) {
954 /* TODO: Play explode sound */
956 fire = S_EXPLODE;
957 fire_target = TARGET_TOP;
958 fire_alpha(fire_x, fire_y, UFO_RED);
959 return;
962 /* Move */
963 fire_y -= FIRE_SPEED;
965 /* Hit UFO? */
966 if (ufo_state == S_ACTIVE) {
967 if ((ABS(ufo_x + UFO_WIDTH / 2 - fire_x) <= UFO_WIDTH / 2) &&
968 (fire_y <= UFO_Y + UFO_HEIGHT)) {
969 ufo_state = S_EXPLODE;
970 fire = S_EXPLODE;
971 fire_target = TARGET_UFO;
972 /* Center explosion */
973 ufo_x -= (UFO_EXPLODE_WIDTH - UFO_WIDTH) / 2;
974 rb->lcd_bitmap(invadrox_ufo_explode, ufo_x, UFO_Y - 1,
975 UFO_EXPLODE_WIDTH, UFO_EXPLODE_HEIGHT);
976 return;
980 /* Hit bomb? (check position, not pixel value) */
981 for (i = 0; i < max_bombs; i++) {
982 if (bombs[i].state == S_ACTIVE) {
983 /* Count as hit if within BOMB_WIDTH pixels */
984 if ((ABS(bombs[i].x - fire_x) < BOMB_WIDTH) &&
985 (fire_y - bombs[i].y < BOMB_HEIGHT)) {
986 /* Erase bomb */
987 rb->lcd_fillrect(bombs[i].x, bombs[i].y, BOMB_WIDTH, BOMB_HEIGHT);
988 bombs[i].state = S_IDLE;
989 /* Explode ship fire */
990 fire = S_EXPLODE;
991 fire_target = TARGET_SHIELD;
992 fire_alpha(fire_x, fire_y, LCD_WHITE);
993 return;
998 /* Check for hit*/
999 for (i = FIRE_SPEED; i >= 0; i--) {
1000 pix = get_pixel(fire_x, fire_y + i);
1001 if(pix == screen_white) {
1002 hit_white = true;
1003 fire_y += i;
1004 break;
1006 if(pix == screen_green) {
1007 hit_green = true;
1008 fire_y += i;
1009 break;
1013 if (hit_green) {
1014 /* Hit shield */
1016 /* TODO: Play explode sound */
1018 fire = S_EXPLODE;
1019 fire_target = TARGET_SHIELD;
1020 /* Center explosion around hit pixel */
1021 fire_y -= FIRE_HEIGHT / 2;
1022 fire_alpha(fire_x, fire_y, SLIME_GREEN);
1023 return;
1026 if (hit_white) {
1028 /* Hit alien? */
1029 for (i = 0; i < 5 * ALIENS; i++) {
1030 if (aliens[i].state != DEAD &&
1031 (ABS(fire_x - (PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED +
1032 ALIEN_WIDTH / 2)) <= ALIEN_WIDTH / 2) &&
1033 (ABS(fire_y - (ALIEN_START_Y + aliens[i].y * ALIEN_HEIGHT +
1034 ALIEN_HEIGHT / 2)) <= ALIEN_HEIGHT / 2)) {
1036 /* TODO: play alien hit sound */
1038 if (aliens[i].state == BOMBER) {
1039 /* Set (possible) alien above to bomber */
1040 for (j = i - ALIENS; j >= 0; j -= ALIENS) {
1041 if (aliens[j].state != DEAD) {
1042 /* printf("New bomber (%d, %d)\n", j % ALIENS, j / ALIENS); */
1043 aliens[j].state = BOMBER;
1044 break;
1048 aliens[i].state = DEAD;
1049 exploding_alien = i;
1050 score += scores[aliens[i].type];
1051 draw_score();
1052 /* Update score part of screen */
1053 rb->lcd_update_rect(SCORENUM_X, SCORENUM_Y,
1054 PLAYFIELD_WIDTH - 2 * NUMBERS_WIDTH, FONT_HEIGHT);
1056 /* Paralyze aliens S_EXPLODE frames */
1057 aliens_paralyzed = S_EXPLODE;
1058 rb->lcd_bitmap(invadrox_alien_explode,
1059 PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED,
1060 ALIEN_START_Y + aliens[i].y * ALIEN_HEIGHT,
1061 ALIEN_EXPLODE_WIDTH, ALIEN_EXPLODE_HEIGHT);
1062 /* Since alien is 1 pixel taller than explosion sprite, erase bottom line */
1063 rb->lcd_hline(PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED,
1064 PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED + ALIEN_WIDTH,
1065 ALIEN_START_Y + (aliens[i].y + 1) * ALIEN_HEIGHT - 1);
1066 return;
1071 /* Draw shot */
1072 rb->lcd_set_foreground(LCD_WHITE);
1073 rb->lcd_vline(fire_x, fire_y, fire_y + SHOT_HEIGHT);
1074 rb->lcd_set_foreground(LCD_BLACK);
1075 } else if (fire < S_IDLE) {
1076 /* Count up towards S_IDLE, then erase explosion */
1077 fire++;
1078 if (fire == S_IDLE) {
1079 /* Erase explosion */
1080 if (fire_target == TARGET_TOP) {
1081 rb->lcd_fillrect(fire_x - (FIRE_WIDTH / 2), fire_y, FIRE_WIDTH, FIRE_HEIGHT);
1082 } else if (fire_target == TARGET_SHIELD) {
1083 /* Draw explosion with black pixels */
1084 fire_alpha(fire_x, fire_y, LCD_BLACK);
1091 /* Return a BOMBER alien */
1092 inline int random_bomber(void)
1094 int i, col;
1096 /* TODO: Weigh higher probability near ship */
1097 col = rb->rand() % ALIENS;
1098 for (i = col + 4 * ALIENS; i >= 0; i -= ALIENS) {
1099 if (aliens[i].state == BOMBER) {
1100 return i;
1104 /* No BOMBER found in this col */
1106 for (i = 0; i < 5 * ALIENS; i++) {
1107 if (aliens[i].state == BOMBER) {
1108 return i;
1112 /* No BOMBER found at all (error?) */
1114 return -1;
1118 inline void draw_bomb(int i)
1120 rb->lcd_bitmap_part(invadrox_bombs, bombs[i].type * BOMB_WIDTH,
1121 bombs[i].frame * BOMB_HEIGHT,
1122 BMPWIDTH_invadrox_bombs, bombs[i].x, bombs[i].y,
1123 BOMB_WIDTH, BOMB_HEIGHT);
1124 /* Advance frame */
1125 bombs[i].frame++;
1126 if (bombs[i].frame == bombs[i].frames) {
1127 bombs[i].frame = 0;
1132 void move_bombs(void)
1134 int i, j, bomber;
1135 bool abort;
1137 for (i = 0; i < max_bombs; i++) {
1139 switch (bombs[i].state) {
1141 case S_IDLE:
1142 if (ship_hit) {
1143 continue;
1145 bomber = random_bomber();
1146 if (bomber < 0) {
1147 DBG("ERROR: No bomber available\n");
1148 continue;
1150 /* x, y */
1151 bombs[i].x = PLAYFIELD_X + LIVES_X + aliens[bomber].x * ALIEN_SPEED + ALIEN_WIDTH / 2;
1152 bombs[i].y = ALIEN_START_Y + (aliens[bomber].y + 1) * ALIEN_HEIGHT;
1154 /* Check for duplets in x and y direction */
1155 abort = false;
1156 for (j = i - 1; j >= 0; j--) {
1157 if ((bombs[j].state == S_ACTIVE) &&
1158 ((bombs[i].x == bombs[j].x) || (bombs[i].y == bombs[j].y))) {
1159 abort = true;
1160 break;
1163 if (abort) {
1164 /* Skip this one, continue with next bomb */
1165 /* printf("Bomb %d duplet of %d\n", i, j); */
1166 continue;
1169 /* Passed, set type */
1170 bombs[i].type = rb->rand() % 3;
1171 bombs[i].frame = 0;
1172 if (bombs[i].type == 0) {
1173 bombs[i].frames = 3;
1174 } else if (bombs[i].type == 1) {
1175 bombs[i].frames = 4;
1176 } else {
1177 bombs[i].frames = 6;
1180 /* Bombs away */
1181 bombs[i].state = S_ACTIVE;
1182 draw_bomb(i);
1183 continue;
1185 break;
1187 case S_ACTIVE:
1188 /* Erase old position */
1189 rb->lcd_fillrect(bombs[i].x, bombs[i].y, BOMB_WIDTH, BOMB_HEIGHT);
1191 /* Move */
1192 bombs[i].y += BOMB_SPEED;
1194 /* Check if bottom hit */
1195 if (bombs[i].y + BOMB_HEIGHT >= PLAYFIELD_Y) {
1196 bombs[i].y = PLAYFIELD_Y - FIRE_HEIGHT + 1;
1197 fire_alpha(bombs[i].x, bombs[i].y, LCD_WHITE);
1198 bombs[i].state = S_EXPLODE;
1199 bombs[i].target = TARGET_BOTTOM;
1200 break;
1203 /* Check for green (ship or shield) */
1204 for (j = BOMB_HEIGHT; j >= BOMB_HEIGHT - BOMB_SPEED; j--) {
1205 bombs[i].target = 0;
1206 if(get_pixel(bombs[i].x + BOMB_WIDTH / 2, bombs[i].y + j) == screen_green) {
1207 /* Move to hit pixel */
1208 bombs[i].x += BOMB_WIDTH / 2;
1209 bombs[i].y += j;
1211 /* Check if ship is hit */
1212 if (bombs[i].y > SHIELD_Y + SHIELD_HEIGHT && bombs[i].y < PLAYFIELD_Y) {
1214 /* TODO: play ship hit sound */
1216 ship_hit = true;
1217 ship_frame = 1;
1218 ship_frame_counter = 0;
1219 bombs[i].state = S_EXPLODE * 4;
1220 bombs[i].target = TARGET_SHIP;
1221 rb->lcd_bitmap_part(invadrox_ships, 0, 1 * SHIP_HEIGHT,
1222 BMPWIDTH_invadrox_ships, ship_x, SHIP_Y,
1223 SHIP_WIDTH, SHIP_HEIGHT);
1224 break;
1226 /* Shield hit */
1227 bombs[i].state = S_EXPLODE;
1228 bombs[i].target = TARGET_SHIELD;
1229 /* Center explosion around hit pixel in shield */
1230 bombs[i].y -= FIRE_HEIGHT / 2;
1231 fire_alpha(bombs[i].x, bombs[i].y, SLIME_GREEN);
1232 break;
1236 if (bombs[i].target != 0) {
1237 /* Hit ship or shield, continue */
1238 continue;
1241 draw_bomb(i);
1242 break;
1244 default:
1245 /* If we get here state should be < 0, exploding */
1246 bombs[i].state++;
1247 if (bombs[i].state == S_IDLE) {
1248 if (ship_hit) {
1249 /* Erase explosion */
1250 rb->lcd_fillrect(ship_x, SHIP_Y, SHIP_WIDTH, SHIP_HEIGHT);
1251 rb->lcd_update_rect(ship_x, SHIP_Y, SHIP_WIDTH, SHIP_HEIGHT);
1252 ship_hit = false;
1253 ship_frame = 0;
1254 ship_x = PLAYFIELD_X + 2 * LIVES_X;
1255 lives--;
1256 if (lives == 0) {
1257 game_over = true;
1258 return;
1260 draw_lives();
1261 /* Sleep 1s to give player time to examine lives left */
1262 rb->sleep(HZ);
1264 /* Erase explosion (even if ship hit, might be another bomb) */
1265 fire_alpha(bombs[i].x, bombs[i].y, LCD_BLACK);
1267 break;
1273 inline void move_ship(void)
1275 ship_dir += ship_acc;
1276 if (ship_dir > max_ship_speed) {
1277 ship_dir = max_ship_speed;
1279 if (ship_dir < -max_ship_speed) {
1280 ship_dir = -max_ship_speed;
1282 ship_x += ship_dir;
1283 if (ship_x < SHIP_MIN_X) {
1284 ship_x = SHIP_MIN_X;
1286 if (ship_x > SHIP_MAX_X) {
1287 ship_x = SHIP_MAX_X;
1290 draw_ship();
1294 /* Unidentified Flying Object */
1295 void move_ufo(void)
1297 static int ufo_speed;
1298 static int counter;
1299 int mystery_score;
1301 switch (ufo_state) {
1303 case S_IDLE:
1305 if (rb->rand() % 500 == 0) {
1306 /* Uh-oh, it's time to launch a mystery UFO */
1308 /* TODO: Play UFO sound */
1310 if (rb->rand() % 2) {
1311 ufo_speed = UFO_SPEED;
1312 ufo_x = PLAYFIELD_X;
1313 } else {
1314 ufo_speed = -UFO_SPEED;
1315 ufo_x = LCD_WIDTH - PLAYFIELD_X - UFO_WIDTH;
1317 ufo_state = S_ACTIVE;
1318 /* UFO will be drawn next frame */
1320 break;
1322 case S_ACTIVE:
1323 /* Erase old pos */
1324 rb->lcd_fillrect(ufo_x, UFO_Y, UFO_WIDTH, UFO_HEIGHT);
1325 /* Move */
1326 ufo_x += ufo_speed;
1327 /* Check bounds */
1328 if (ufo_x < PLAYFIELD_X || ufo_x > LCD_WIDTH - PLAYFIELD_X - UFO_WIDTH) {
1329 ufo_state = S_IDLE;
1330 break;
1332 /* Draw new pos */
1333 rb->lcd_bitmap(invadrox_ufo, ufo_x, UFO_Y, UFO_WIDTH, UFO_HEIGHT);
1334 break;
1336 case S_SHOWSCORE:
1337 counter++;
1338 if (counter == S_IDLE) {
1339 /* Erase mystery number */
1340 rb->lcd_fillrect(ufo_x, UFO_Y, 3 * NUMBERS_WIDTH + 2 * NUM_SPACING, FONT_HEIGHT);
1341 ufo_state = S_IDLE;
1343 break;
1345 default:
1346 /* Exploding */
1347 ufo_state++;
1348 if (ufo_state == S_IDLE) {
1349 /* Erase explosion */
1350 rb->lcd_fillrect(ufo_x, UFO_Y - 1, UFO_EXPLODE_WIDTH, UFO_EXPLODE_HEIGHT);
1351 ufo_state = S_SHOWSCORE;
1352 counter = S_EXPLODE * 4;
1353 /* Draw mystery_score, sleep, increase score and continue */
1354 mystery_score = 50 + (rb->rand() % 6) * 50;
1355 if (mystery_score < 100) {
1356 draw_number(ufo_x, UFO_Y, mystery_score, 2);
1357 } else {
1358 draw_number(ufo_x, UFO_Y, mystery_score, 3);
1360 score += mystery_score;
1361 draw_score();
1363 break;
1368 void draw_background(void)
1371 rb->lcd_bitmap(invadrox_background, 0, 0, LCD_WIDTH, LCD_HEIGHT);
1372 rb->lcd_update();
1376 void new_level(void)
1378 int i;
1380 draw_background();
1381 /* Give an extra life for each new level */
1382 if (lives < MAX_LIVES) {
1383 lives++;
1385 draw_lives();
1387 /* Score */
1388 draw_score();
1389 draw_number(HISCORENUM_X, SCORENUM_Y, hiscore.score, 4);
1391 level++;
1392 draw_level();
1393 level_finished = false;
1395 ufo_state = S_IDLE;
1397 /* Init alien positions and states */
1398 for (i = 0; i < 4 * ALIENS; i++) {
1399 aliens[i].x = 0 + (i % ALIENS) * ((ALIEN_WIDTH + ALIEN_SPACING) / ALIEN_SPEED);
1400 aliens[i].y = 2 * (i / ALIENS);
1401 aliens[i].state = ALIVE;
1403 /* Last row, bombers */
1404 for (i = 4 * ALIENS; i < 5 * ALIENS; i++) {
1405 aliens[i].x = 0 + (i % ALIENS) * ((ALIEN_WIDTH + ALIEN_SPACING) / ALIEN_SPEED);
1406 aliens[i].y = 2 * (i / ALIENS);
1407 aliens[i].state = BOMBER;
1410 /* Init bombs to inactive (S_IDLE) */
1411 for (i = 0; i < MAX_BOMBS; i++) {
1412 bombs[i].state = S_IDLE;
1415 /* Start aliens closer to earth from level 2 */
1416 for (i = 0; i < 5 * ALIENS; i++) {
1417 if (level < 6) {
1418 aliens[i].y += level - 1;
1419 } else {
1420 aliens[i].y += 5;
1424 /* Max concurrent bombs */
1425 max_bombs = 1;
1427 gamespeed = 2;
1429 if (level > 1) {
1430 max_bombs++;
1433 /* Increase speed */
1434 if (level > 2) {
1435 gamespeed++;
1438 if (level > 3) {
1439 max_bombs++;
1442 /* Increase speed more */
1443 if (level > 4) {
1444 gamespeed++;
1447 if (level > 5) {
1448 max_bombs++;
1451 /* 4 shields */
1452 for (i = 1; i <= 4; i++) {
1453 rb->lcd_bitmap(invadrox_shield,
1454 PLAYFIELD_X + i * PLAYFIELD_WIDTH / 5 - SHIELD_WIDTH / 2,
1455 SHIELD_Y, SHIELD_WIDTH, SHIELD_HEIGHT);
1458 /* Bottom line */
1459 rb->lcd_set_foreground(SLIME_GREEN);
1460 rb->lcd_hline(PLAYFIELD_X, LCD_WIDTH - PLAYFIELD_X, PLAYFIELD_Y);
1461 /* Restore foreground to black (for fast erase later). */
1462 rb->lcd_set_foreground(LCD_BLACK);
1464 ship_x = PLAYFIELD_X + 2 * LIVES_X;
1465 if (level == 1) {
1466 old_ship_x = ship_x;
1468 ship_dir = 0;
1469 ship_acc = 0;
1470 ship_frame = 0;
1471 ship_hit = false;
1472 fire = S_IDLE;
1473 /* Start moving the bottom row left to right */
1474 curr_alien = 4 * ALIENS;
1475 aliens_paralyzed = 0;
1476 aliens_right = true;
1477 aliens_down = false;
1478 hit_left_border = false;
1479 hit_right_border = false;
1480 /* TODO: Change max_ship_speed to 3 at higher levels */
1481 max_ship_speed = 2;
1483 draw_aliens();
1485 rb->lcd_update();
1489 void init_invadrox(void)
1491 int i;
1493 /* Seed random number generator with a "random" number */
1494 rb->srand(rb->get_time()->tm_sec + rb->get_time()->tm_min * 60);
1496 /* Precalculate start of each scanline */
1497 for (i = 0; i < LCD_HEIGHT; i++) {
1498 #if (LCD_DEPTH >= 8)
1499 ytab[i] = i * LCD_WIDTH;
1500 #elif (LCD_DEPTH == 2) && (LCD_PIXELFORMAT == HORIZONTAL_PACKING)
1501 ytab[i] = i * (LCD_WIDTH / 4);
1502 #elif (LCD_DEPTH == 2) && (LCD_PIXELFORMAT == VERTICAL_PACKING)
1503 ytab[i] = (i / 4) * LCD_WIDTH;
1504 #else
1505 #error pixelformat not implemented yet
1506 #endif
1509 rb->lcd_set_background(LCD_BLACK);
1510 rb->lcd_set_foreground(LCD_BLACK);
1512 if (highscore_load(HISCOREFILE, &hiscore, 1) < 0) {
1513 /* Init hiscore to 0 */
1514 rb->strlcpy(hiscore.name, "Invader", sizeof(hiscore.name));
1515 hiscore.score = 0;
1516 hiscore.level = 1;
1519 /* Init alien types in aliens array */
1520 for (i = 0; i < 1 * ALIENS; i++) {
1521 aliens[i].type = 0; /* Kang */
1523 for (; i < 3 * ALIENS; i++) {
1524 aliens[i].type = 1; /* Kodos */
1526 for (; i < 5 * ALIENS; i++) {
1527 aliens[i].type = 2; /* Serak */
1531 /* Save screen white color */
1532 rb->lcd_set_foreground(LCD_WHITE);
1533 rb->lcd_drawpixel(0, 0);
1534 rb->lcd_update_rect(0, 0, 1, 1);
1535 screen_white = get_pixel(0, 0);
1537 /* Save screen green color */
1538 rb->lcd_set_foreground(SLIME_GREEN);
1539 rb->lcd_drawpixel(0, 0);
1540 rb->lcd_update_rect(0, 0, 1, 1);
1541 screen_green = get_pixel(0, 0);
1543 /* Restore black foreground */
1544 rb->lcd_set_foreground(LCD_BLACK);
1546 new_level();
1548 /* Flash score at start */
1549 for (i = 0; i < 5; i++) {
1550 rb->lcd_fillrect(SCORENUM_X, SCORENUM_Y,
1551 4 * NUMBERS_WIDTH + 3 * NUM_SPACING,
1552 FONT_HEIGHT);
1553 rb->lcd_update_rect(SCORENUM_X, SCORENUM_Y,
1554 4 * NUMBERS_WIDTH + 3 * NUM_SPACING,
1555 FONT_HEIGHT);
1556 rb->sleep(HZ / 10);
1557 draw_number(SCORENUM_X, SCORENUM_Y, score, 4);
1558 rb->sleep(HZ / 10);
1563 inline bool handle_buttons(void)
1565 static unsigned int oldbuttonstate = 0;
1567 unsigned int released, pressed, newbuttonstate;
1569 if (ship_hit) {
1570 /* Don't allow ship movement during explosion */
1571 newbuttonstate = 0;
1572 } else {
1573 newbuttonstate = rb->button_status();
1575 if(newbuttonstate == oldbuttonstate) {
1576 if (newbuttonstate == 0) {
1577 /* No button pressed. Stop ship. */
1578 ship_acc = 0;
1579 if (ship_dir > 0) {
1580 ship_dir--;
1582 if (ship_dir < 0) {
1583 ship_dir++;
1586 /* return false; */
1587 goto check_usb;
1589 released = ~newbuttonstate & oldbuttonstate;
1590 pressed = newbuttonstate & ~oldbuttonstate;
1591 oldbuttonstate = newbuttonstate;
1592 if (pressed) {
1593 if (pressed & LEFT) {
1594 if (ship_acc > -1) {
1595 ship_acc--;
1598 if (pressed & RIGHT) {
1599 if (ship_acc < 1) {
1600 ship_acc++;
1603 if (pressed & FIRE) {
1604 if (fire == S_IDLE) {
1605 /* Fire shot */
1606 fire_x = ship_x + SHIP_WIDTH / 2;
1607 fire_y = SHIP_Y - SHOT_HEIGHT;
1608 fire = S_ACTIVE;
1609 /* TODO: play fire sound */
1612 #ifdef RC_QUIT
1613 if (pressed & RC_QUIT) {
1614 rb->splash(HZ * 1, "Quit");
1615 return true;
1617 #endif
1618 if (pressed & QUIT) {
1619 rb->splash(HZ * 1, "Quit");
1620 return true;
1623 if (released) {
1624 if ((released & LEFT)) {
1625 if (ship_acc < 1) {
1626 ship_acc++;
1629 if ((released & RIGHT)) {
1630 if (ship_acc > -1) {
1631 ship_acc--;
1636 check_usb:
1638 /* Quit if USB is connected */
1639 if (rb->button_get(false) == SYS_USB_CONNECTED) {
1640 return true;
1643 return false;
1647 void game_loop(void)
1649 int i, end;
1651 /* Print dimensions (just for debugging) */
1652 DBG("%03dx%03dx%02d\n", LCD_WIDTH, LCD_HEIGHT, LCD_DEPTH);
1654 /* Init */
1655 init_invadrox();
1657 while (1) {
1658 /* Convert CYCLETIME (in ms) to HZ */
1659 end = *rb->current_tick + (CYCLETIME * HZ) / 1000;
1661 if (handle_buttons()) {
1662 return;
1665 /* Animate */
1666 move_ship();
1667 move_fire();
1669 /* Check if level is finished (marked by move_fire) */
1670 if (level_finished) {
1671 /* TODO: Play level finished sound */
1672 new_level();
1675 move_ufo();
1677 /* Move aliens */
1678 if (!aliens_paralyzed && !ship_hit) {
1679 for (i = 0; i < gamespeed; i++) {
1680 if (!move_aliens()) {
1681 if (game_over) {
1682 return;
1688 /* Move alien bombs */
1689 move_bombs();
1690 if (game_over) {
1691 return;
1694 /* Update "playfield" rect */
1695 rb->lcd_update_rect(PLAYFIELD_X, SCORENUM_Y + FONT_HEIGHT,
1696 PLAYFIELD_WIDTH,
1697 PLAYFIELD_Y + 1 - SCORENUM_Y - FONT_HEIGHT);
1699 /* Wait until next frame */
1700 DBG("%ld (%d)\n", end - *rb->current_tick, (CYCLETIME * HZ) / 1000);
1701 if (end > *rb->current_tick) {
1702 rb->sleep(end - *rb->current_tick);
1703 } else {
1704 rb->yield();
1707 } /* end while */
1711 /* this is the plugin entry point */
1712 enum plugin_status plugin_start(UNUSED const void* parameter)
1714 rb->lcd_setfont(FONT_SYSFIXED);
1715 /* Turn off backlight timeout */
1716 backlight_force_on(); /* backlight control in lib/helper.c */
1718 /* now go ahead and have fun! */
1719 game_loop();
1721 /* Game Over. */
1722 /* TODO: Play game over sound */
1723 rb->splash(HZ * 2, "Game Over");
1724 if (score > hiscore.score) {
1725 /* Save new hiscore */
1726 hiscore.score = score;
1727 hiscore.level = level;
1728 highscore_save(HISCOREFILE, &hiscore, 1);
1731 /* Restore user's original backlight setting */
1732 rb->lcd_setfont(FONT_UI);
1733 /* Turn on backlight timeout (revert to settings) */
1734 backlight_use_settings(); /* backlight control in lib/helper.c */
1736 return PLUGIN_OK;
1742 * GNU Emacs settings: Kernighan & Richie coding style with
1743 * 4 spaces indent and no tabs.
1744 * Local Variables:
1745 * c-file-style: "k&r"
1746 * c-basic-offset: 4
1747 * indent-tabs-mode: nil
1748 * End: