Use a more natural guard for the callback definition
[kugel-rb.git] / apps / plugins / invadrox.c
blob3e5c98e2a39b07add0b724e3b81e51965ca24290
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 == TATUNG_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 == COWON_D2_PAD
159 #define QUIT BUTTON_POWER
160 #define LEFT BUTTON_MINUS
161 #define RIGHT BUTTON_PLUS
162 #define FIRE BUTTON_MENU
164 #elif CONFIG_KEYPAD == IAUDIO67_PAD
166 #define QUIT BUTTON_POWER
167 #define LEFT BUTTON_LEFT
168 #define RIGHT BUTTON_RIGHT
169 #define FIRE BUTTON_PLAY
171 #elif CONFIG_KEYPAD == CREATIVEZVM_PAD
173 #define QUIT BUTTON_BACK
174 #define LEFT BUTTON_LEFT
175 #define RIGHT BUTTON_RIGHT
176 #define FIRE BUTTON_SELECT
178 #elif CONFIG_KEYPAD == PHILIPS_SA9200_PAD
180 #define QUIT BUTTON_POWER
181 #define LEFT BUTTON_PREV
182 #define RIGHT BUTTON_NEXT
183 #define FIRE BUTTON_PLAY
185 #elif CONFIG_KEYPAD == ONDAVX747_PAD || \
186 CONFIG_KEYPAD == ONDAVX777_PAD || \
187 CONFIG_KEYPAD == MROBE500_PAD
189 #define QUIT BUTTON_POWER
191 #elif CONFIG_KEYPAD == SAMSUNG_YH_PAD
193 #define QUIT BUTTON_REC
194 #define LEFT BUTTON_LEFT
195 #define RIGHT BUTTON_RIGHT
196 #define FIRE BUTTON_PLAY
198 #elif CONFIG_KEYPAD == PBELL_VIBE500_PAD
200 #define QUIT BUTTON_REC
201 #define LEFT BUTTON_PREV
202 #define RIGHT BUTTON_NEXT
203 #define FIRE BUTTON_OK
205 #else
206 #error INVADROX: Unsupported keypad
207 #endif
209 #ifndef RC_QUIT
210 #define RC_QUIT 0
211 #endif
213 #ifdef HAVE_TOUCHSCREEN
215 #ifndef QUIT
216 #define QUIT 0
217 #endif
218 #ifndef LEFT
219 #define LEFT 0
220 #endif
221 #ifndef RIGHT
222 #define RIGHT 0
223 #endif
224 #ifndef FIRE
225 #define FIRE 0
226 #endif
228 #define TOUCHSCREEN_QUIT BUTTON_TOPLEFT
229 #define TOUCHSCREEN_LEFT (BUTTON_MIDLEFT | BUTTON_BOTTOMLEFT)
230 #define TOUCHSCREEN_RIGHT (BUTTON_MIDRIGHT | BUTTON_BOTTOMRIGHT)
231 #define TOUCHSCREEN_FIRE (BUTTON_CENTER | BUTTON_BOTTOMMIDDLE)
233 #define ACTION_QUIT (QUIT | TOUCHSCREEN_QUIT | RC_QUIT)
234 #define ACTION_LEFT (LEFT | TOUCHSCREEN_LEFT)
235 #define ACTION_RIGHT (RIGHT | TOUCHSCREEN_RIGHT)
236 #define ACTION_FIRE (FIRE | TOUCHSCREEN_FIRE)
238 #else /* HAVE_TOUCHSCREEN */
240 #define ACTION_QUIT (QUIT | RC_QUIT)
241 #define ACTION_LEFT LEFT
242 #define ACTION_RIGHT RIGHT
243 #define ACTION_FIRE FIRE
245 #endif
247 #ifndef UNUSED
248 #define UNUSED __attribute__ ((unused))
249 #endif
251 /* Defines common to all models */
252 #define UFO_Y (SCORENUM_Y + FONT_HEIGHT + ALIEN_HEIGHT)
253 #define PLAYFIELD_Y (LCD_HEIGHT - SHIP_HEIGHT - 2)
254 #define PLAYFIELD_WIDTH (LCD_WIDTH - 2 * PLAYFIELD_X)
255 #define LEVEL_X (LCD_WIDTH - PLAYFIELD_X - LIVES_X - 2 * NUMBERS_WIDTH - 3 * NUM_SPACING)
256 #define SHIP_MIN_X (PLAYFIELD_X + PLAYFIELD_WIDTH / 5 - SHIELD_WIDTH / 2 - SHIP_WIDTH)
257 #define SHIP_MAX_X (PLAYFIELD_X + 4 * PLAYFIELD_WIDTH / 5 + SHIELD_WIDTH / 2)
258 /* SCORE_Y = 0 for most targets. Gigabeat redefines it later. */
259 #define SCORE_Y 0
260 #define MAX_LIVES 8
263 /* m:robe 500 defines */
264 #if ((LCD_WIDTH == 640) && (LCD_HEIGHT == 480)) || \
265 ((LCD_WIDTH == 480) && (LCD_HEIGHT == 640))
267 /* Original arcade game size 224x240, 1bpp with
268 * red overlay at top and green overlay at bottom.
270 * M:Robe 500: 640x480x16
271 * ======================
274 #define ARCADISH_GRAPHICS
275 #define PLAYFIELD_X 48
276 #define SHIP_Y (PLAYFIELD_Y - 2 * SHIP_HEIGHT)
277 #define ALIEN_START_Y (UFO_Y + ALIEN_HEIGHT)
278 #define SCORENUM_X (PLAYFIELD_X + NUMBERS_WIDTH)
279 #define SCORENUM_Y (SCORE_Y + FONT_HEIGHT + 2)
280 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 1 - 6 * NUMBERS_WIDTH - 5 * NUM_SPACING)
281 #define SHIELD_Y (PLAYFIELD_Y - 5 * SHIP_HEIGHT)
282 #define LIVES_X 10
283 #define MAX_Y 18
285 /* iPod Video defines */
286 #elif (LCD_WIDTH == 320) && (LCD_HEIGHT == 240)
288 /* Original arcade game size 224x240, 1bpp with
289 * red overlay at top and green overlay at bottom.
291 * iPod Video: 320x240x16
292 * ======================
293 * X: 48p padding at left/right gives 224p playfield in middle.
294 * 10p "border" gives 204p actual playfield. UFO use full 224p.
295 * Y: Use full 240p.
297 * MAX_X = (204 - 12) / 2 - 1 = 95
299 * Y: Score text 7 0
300 * Space 10 7
301 * Score 7 17
302 * Space 8 24
303 * 3 Ufo 7 32
304 * 2 Space Aliens start at 32 + 3 * 8 = 56
305 * 0 aliens 9*8 56 -
306 * space ~7*8 128 | 18.75 aliens space between
307 * shield 2*8 182 | first alien and ship.
308 * space 8 198 | MAX_Y = 18
309 * ship 8 206 -
310 * space 2*8 214
311 * hline 1 230 - PLAYFIELD_Y
312 * bottom border 10 240
313 * Lives and Level goes inside bottom border
316 #define ARCADISH_GRAPHICS
317 #define PLAYFIELD_X 48
318 #define SHIP_Y (PLAYFIELD_Y - 3 * SHIP_HEIGHT)
319 #define ALIEN_START_Y (UFO_Y + 3 * ALIEN_HEIGHT)
320 #define SCORENUM_X (PLAYFIELD_X + NUMBERS_WIDTH)
321 #define SCORENUM_Y SCORE_Y + (2 * (FONT_HEIGHT + 1) + 1)
322 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 1 - 6 * NUMBERS_WIDTH - 5 * NUM_SPACING)
323 #define SHIELD_Y (PLAYFIELD_Y - 6 * SHIP_HEIGHT)
324 #define LIVES_X 10
325 #define MAX_Y 18
327 #elif (LCD_WIDTH == 176) && (LCD_HEIGHT == 220)
329 /* Sandisk Sansa e200: 176x220x16
330 * ==============================
331 * X: No padding. 8p border -> 160p playfield.
333 * 8p Aliens with 3p spacing -> 88 + 30 = 118p aliens block.
334 * (160 - 118) / 2 = 21 rounds for whole block (more than original)
335 * MAX_X = (160 - 8) / 2 - 1 = 75 rounds for single alien (less than original)
337 * LOGO 70 0
338 * Score text 5 70
339 * Space 5 75
340 * Y Score 5 80
341 * Space 10 85
342 * 2 Ufo 5 95
343 * 2 Space 10 100
344 * 0 aliens 9*5 110 -
345 * space ~7*5 155 | 18.6 aliens space between
346 * shield 2*5 188 | first alien and ship.
347 * space 5 198 | MAX_Y = 18
348 * ship 5 203 -
349 * space 5 208
350 * hline 1 213 PLAYFIELD_Y
351 * bottom border 6
352 * LCD_HEIGHT 220
353 * Lives and Level goes inside bottom border
356 #define SMALL_GRAPHICS
357 #define PLAYFIELD_X 0
358 #define SHIP_Y (PLAYFIELD_Y - 2 * SHIP_HEIGHT)
359 #define SHIELD_Y (SHIP_Y - SHIP_HEIGHT - SHIELD_HEIGHT)
360 #define ALIEN_START_Y (UFO_Y + 3 * SHIP_HEIGHT)
361 /* Redefine SCORE_Y */
362 #undef SCORE_Y
363 #define SCORE_Y 70
364 #define SCORENUM_X (PLAYFIELD_X + NUMBERS_WIDTH)
365 #define SCORENUM_Y (SCORE_Y + 2 * FONT_HEIGHT)
366 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 1 - 6 * NUMBERS_WIDTH - 5 * NUM_SPACING)
367 #define LIVES_X 8
368 #define MAX_Y 18
371 #elif (LCD_WIDTH == 176) && (LCD_HEIGHT == 132)
373 /* iPod Nano: 176x132x16
374 * ======================
375 * X: No padding. 8p border -> 160p playfield.
377 * LIVES_X 8
378 * ALIEN_WIDTH 8
379 * ALIEN_HEIGHT 5
380 * ALIEN_SPACING 3
381 * SHIP_WIDTH 10
382 * SHIP_HEIGHT 5
383 * FONT_HEIGHT 5
384 * UFO_WIDTH 10
385 * UFO_HEIGHT 5
386 * SHIELD_WIDTH 15
387 * SHIELD_HEIGHT 10
388 * MAX_X 75
389 * MAX_Y = 18
390 * ALIEN_START_Y (UFO_Y + 12)
392 * 8p Aliens with 3p spacing -> 88 + 30 = 118p aliens block.
393 * (160 - 118) / 2 = 21 rounds for whole block (more than original)
394 * MAX_X = (160 - 8) / 2 - 1 = 75 rounds for single alien (less than original)
396 * Y: Scoreline 5 0 (combine scoretext and numbers on same line)
397 * Space 5 5
398 * 1 Ufo 5 10
399 * 3 Space 7 15
400 * 2 aliens 9*5 22 -
401 * space ~7*5 67 | Just above 18 aliens space between
402 * shield 2*5 100 | first alien and ship.
403 * space 5 110 | MAX_Y = 18
404 * ship 5 115 -
405 * space 5 120
406 * hline 1 125 PLAYFIELD_Y
407 * bottom border 6 126
408 * LCD_HEIGHT 131
409 * Lives and Level goes inside bottom border
412 #define SMALL_GRAPHICS
413 #define PLAYFIELD_X 0
414 #define SHIP_Y (PLAYFIELD_Y - 2 * SHIP_HEIGHT)
415 #define ALIEN_START_Y (UFO_Y + 12)
416 #define SCORENUM_X (PLAYFIELD_X + 6 * NUMBERS_WIDTH + 5 * NUM_SPACING)
417 #define SCORENUM_Y SCORE_Y
418 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 4 * NUMBERS_WIDTH - 3 * NUM_SPACING)
419 #define SHIELD_Y (SHIP_Y - SHIP_HEIGHT - SHIELD_HEIGHT)
420 #define LIVES_X 8
421 #define MAX_Y 18
423 #elif (LCD_WIDTH == 160) && (LCD_HEIGHT == 128)
425 /* iAudio X5, iRiver H10 20Gb, iPod 3g/4g, H100, M5: 160x128
426 * =========================================================
427 * X: No padding. No border -> 160p playfield.
429 * LIVES_X 0
430 * ALIEN_WIDTH 8
431 * ALIEN_HEIGHT 5
432 * ALIEN_SPACING 3
433 * SHIP_WIDTH 10
434 * SHIP_HEIGHT 5
435 * FONT_HEIGHT 5
436 * UFO_WIDTH 10
437 * UFO_HEIGHT 5
438 * SHIELD_WIDTH 15
439 * SHIELD_HEIGHT 10
440 * MAX_X 75
441 * MAX_Y = 18
442 * ALIEN_START_Y (UFO_Y + 10)
444 * 8p Aliens with 3p spacing -> 88 + 30 = 118p aliens block.
445 * (160 - 118) / 2 = 21 rounds for whole block (more than original)
446 * MAX_X = (160 - 8) / 2 - 1 = 75 rounds for single alien (less than original)
448 * Y: Scoreline 5 0 (combine scoretext and numbers on same line)
449 * Space 5 5
450 * 1 Ufo 5 10
451 * 2 Space 5 15
452 * 8 aliens 9*5 20 -
453 * space ~6*5 65 | Just above 18 aliens space between
454 * shield 2*5 96 | first alien and ship.
455 * space 5 106 | MAX_Y = 18
456 * ship 5 111 -
457 * space 5 116
458 * hline 1 121 PLAYFIELD_Y
459 * bottom border 6 122
460 * LCD_HEIGHT 128
461 * Lives and Level goes inside bottom border
464 #define SMALL_GRAPHICS
465 #define PLAYFIELD_X 0
466 #define SHIP_Y (PLAYFIELD_Y - 2 * SHIP_HEIGHT)
467 #define ALIEN_START_Y (UFO_Y + 10)
468 #define SCORENUM_X (PLAYFIELD_X + 6 * NUMBERS_WIDTH + 5 * NUM_SPACING)
469 #define SCORENUM_Y SCORE_Y
470 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 4 * NUMBERS_WIDTH - 3 * NUM_SPACING)
471 #define SHIELD_Y (SHIP_Y - SHIP_HEIGHT - SHIELD_HEIGHT)
472 #define LIVES_X 0
473 #define MAX_Y 18
476 #elif (LCD_WIDTH == 240) && ((LCD_HEIGHT == 320) || (LCD_HEIGHT == 400))
478 /* Gigabeat: 240x320x16
479 * ======================
480 * X: 8p padding at left/right gives 224p playfield in middle.
481 * 10p "border" gives 204p actual playfield. UFO use full 224p.
482 * Y: Use bottom 240p for playfield and top 80 pixels for logo.
484 * MAX_X = (204 - 12) / 2 - 1 = 95
486 * Y: Score text 7 0 + 80
487 * Space 10 7 + 80
488 * Score 7 17 + 80
489 * Space 8 24 + 80
490 * 3 Ufo 7 32 + 80
491 * 2 Space Aliens start at 32 + 3 * 8 = 56
492 * 0 aliens 9*8 56 -
493 * space ~7*8 128 | 18.75 aliens space between
494 * shield 2*8 182 | first alien and ship.
495 * space 8 198 | MAX_Y = 18
496 * ship 8 206 -
497 * space 2*8 214
498 * hline 1 230 310 - PLAYFIELD_Y
499 * bottom border 10 240 320
500 * Lives and Level goes inside bottom border
503 #define ARCADISH_GRAPHICS
504 #define PLAYFIELD_X 8
505 #define SHIP_Y (PLAYFIELD_Y - 3 * SHIP_HEIGHT)
506 #define ALIEN_START_Y (UFO_Y + 3 * ALIEN_HEIGHT)
507 /* Redefine SCORE_Y */
508 #undef SCORE_Y
509 #define SCORE_Y 80
510 #define SCORENUM_X (PLAYFIELD_X + NUMBERS_WIDTH)
511 #define SCORENUM_Y SCORE_Y + (2 * (FONT_HEIGHT + 1) + 1)
512 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 1 - 6 * NUMBERS_WIDTH - 5 * NUM_SPACING)
513 #define SHIELD_Y (PLAYFIELD_Y - 6 * SHIP_HEIGHT)
514 #define LIVES_X 10
515 #define MAX_Y 18
517 #elif (LCD_WIDTH == 220) && (LCD_HEIGHT == 176)
519 /* TPJ1022, H300, iPod Color: 220x176x16
520 * ============================
521 * X: 0p padding at left/right gives 220p playfield in middle.
522 * 8p "border" gives 204p actual playfield. UFO use full 220p.
523 * Y: Use full 176p for playfield.
525 * MAX_X = (204 - 12) / 2 - 1 = 95
527 * Y: Score text 7 0
528 * Space 8 7
529 * 1 Ufo 7 15
530 * 7 Space Aliens start at 15 + 3 * 8 = 56
531 * 6 aliens 9*8 25 -
532 * space ~7*8 103 | 15.6 aliens space between
533 * shield 2*8 126 | first alien and ship.
534 * space 8 142 | MAX_Y = 15
535 * ship 8 150 -
536 * space 8 158
537 * hline 1 166 - PLAYFIELD_Y
538 * bottom border 10 176
539 * Lives and Level goes inside bottom border
542 #define ARCADISH_GRAPHICS
543 #define PLAYFIELD_X 0
544 #define SHIP_Y (PLAYFIELD_Y - 2 * SHIP_HEIGHT)
545 #define ALIEN_START_Y (UFO_Y + 10)
546 #define SCORENUM_Y SCORE_Y
547 #define SCORENUM_X (PLAYFIELD_X + 6 * NUMBERS_WIDTH + 6 * NUM_SPACING)
548 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 4 * NUMBERS_WIDTH - 3 * NUM_SPACING)
549 #define SHIELD_Y (PLAYFIELD_Y - 5 * SHIP_HEIGHT)
550 #define LIVES_X 8
551 #define MAX_Y 15
554 #else
555 #error INVADROX: Unsupported LCD type
556 #endif
558 #define MAX_X ((LCD_WIDTH-LIVES_X*2-PLAYFIELD_X*2 - ALIEN_WIDTH)/2 - 1)
560 /* Defines common to each "graphic type" */
561 #ifdef ARCADISH_GRAPHICS
563 #define SHOT_HEIGHT 5
564 #define ALIEN_SPACING 4
565 #define ALIEN_SPEED 2
566 #define UFO_SPEED 1
567 #define NUM_SPACING 3
568 #define FIRE_SPEED 8
569 #define BOMB_SPEED 3
570 #define ALIENS 11
572 #elif defined SMALL_GRAPHICS
574 #define SHOT_HEIGHT 4
575 #define ALIEN_SPACING 3
576 #define ALIEN_SPEED 2
577 #define UFO_SPEED 1
578 #define NUM_SPACING 2
579 #define FIRE_SPEED 6
580 #define BOMB_SPEED 2
581 #define ALIENS 11
583 #else
584 #error Graphic type not defined
585 #endif
588 /* Colors */
589 #if (LCD_DEPTH >= 8)
590 #define SLIME_GREEN LCD_RGBPACK(31, 254, 31)
591 #define UFO_RED LCD_RGBPACK(254, 31, 31)
592 #elif (LCD_DEPTH == 2)
593 #define SLIME_GREEN LCD_LIGHTGRAY
594 #define UFO_RED LCD_LIGHTGRAY
595 #else
596 #error LCD type not implemented yet
597 #endif
599 /* Alien states */
600 #define DEAD 0
601 #define ALIVE 1
602 #define BOMBER 2
604 /* Fire/bomb/ufo states */
605 #define S_IDLE 0
606 #define S_ACTIVE 1
607 #define S_SHOWSCORE 2
608 #define S_EXPLODE -9
610 /* Fire/bomb targets */
611 #define TARGET_TOP 0
612 #define TARGET_SHIELD 1
613 #define TARGET_SHIP 2
614 #define TARGET_BOTTOM 3
615 #define TARGET_UFO 4
617 #define HISCOREFILE PLUGIN_GAMES_DIR "/invadrox.high"
620 /* The time (in ms) for one iteration through the game loop - decrease this
621 * to speed up the game - note that current_tick is (currently) only accurate
622 * to 10ms.
624 #define CYCLETIME 40
627 /* Physical x is at PLAYFIELD_X + LIVES_X + x * ALIEN_SPEED
628 * Physical y is at y * ALIEN_HEIGHT
630 struct alien {
631 int x; /* x-coordinate (0 - 95) */
632 int y; /* y-coordinate (0 - 18) */
633 unsigned char type; /* 0 (Kang), 1 (Kodos), 2 (Serak) */
634 unsigned char state; /* Dead, alive or bomber */
637 /* Aliens box 5 rows * ALIENS aliens in each row */
638 struct alien aliens[5 * ALIENS];
640 #define MAX_BOMBS 4
641 struct bomb {
642 int x, y;
643 unsigned char type;
644 unsigned char frame; /* Current animation frame */
645 unsigned char frames; /* Number of frames in animation */
646 unsigned char target; /* Remember target during explosion frames */
647 int state; /* 0 (IDLE) = inactive, 1 (FIRE) or negative, exploding */
649 struct bomb bombs[MAX_BOMBS];
650 /* Increase max_bombs at higher levels */
651 int max_bombs;
653 /* Raw framebuffer value of shield/ship green color */
654 fb_data screen_green, screen_white;
656 /* For optimization, precalculate startoffset of each scanline */
657 unsigned int ytab[LCD_HEIGHT];
659 int lives = 2;
660 int score = 0;
661 int scores[3] = { 30, 20, 10 };
662 int level = 0;
663 struct highscore hiscore;
664 bool game_over = false;
665 int ship_x, old_ship_x, ship_dir, ship_acc, max_ship_speed;
666 int ship_frame, ship_frame_counter;
667 bool ship_hit;
668 int fire, fire_target, fire_x, fire_y;
669 int curr_alien, aliens_paralyzed, gamespeed;
670 int ufo_state, ufo_x;
671 bool level_finished;
672 bool aliens_down, aliens_right, hit_left_border, hit_right_border;
675 /* No standard get_pixel function yet, use this hack instead */
676 #if (LCD_DEPTH >= 8)
678 #if defined(LCD_STRIDEFORMAT) && LCD_STRIDEFORMAT == VERTICAL_STRIDE
679 inline fb_data get_pixel(int x, int y)
681 return rb->lcd_framebuffer[x*LCD_HEIGHT+y];
683 #else
684 inline fb_data get_pixel(int x, int y)
686 return rb->lcd_framebuffer[ytab[y] + x];
688 #endif
690 #elif (LCD_DEPTH == 2)
692 #if (LCD_PIXELFORMAT == HORIZONTAL_PACKING)
693 static const unsigned char shifts[4] = {
694 6, 4, 2, 0
696 /* Horizontal packing */
697 inline fb_data get_pixel(int x, int y)
699 return (rb->lcd_framebuffer[ytab[y] + (x >> 2)] >> shifts[x & 3]) & 3;
701 #else
702 /* Vertical packing */
703 static const unsigned char shifts[4] = {
704 0, 2, 4, 6
706 inline fb_data get_pixel(int x, int y)
708 return (rb->lcd_framebuffer[ytab[y] + x] >> shifts[y & 3]) & 3;
710 #endif /* Horizontal/Vertical packing */
712 #else
713 #error get_pixel: pixelformat not implemented yet
714 #endif
717 /* Draw "digits" least significant digits of num at (x,y) */
718 void draw_number(int x, int y, int num, int digits)
720 int i;
721 int d;
723 for (i = digits - 1; i >= 0; i--) {
724 d = num % 10;
725 num = num / 10;
726 rb->lcd_bitmap_part(invadrox_numbers, d * NUMBERS_WIDTH, 0,
727 STRIDE( SCREEN_MAIN,
728 BMPWIDTH_invadrox_numbers,
729 BMPHEIGHT_invadrox_numbers),
730 x + i * (NUMBERS_WIDTH + NUM_SPACING), y,
731 NUMBERS_WIDTH, FONT_HEIGHT);
733 /* Update lcd */
734 rb->lcd_update_rect(x, y, 4 * NUMBERS_WIDTH + 3 * NUM_SPACING, FONT_HEIGHT);
738 inline void draw_score(void)
740 draw_number(SCORENUM_X, SCORENUM_Y, score, 4);
741 if (score > hiscore.score) {
742 /* Draw new hiscore (same as score) */
743 draw_number(HISCORENUM_X, SCORENUM_Y, score, 4);
748 void draw_level(void)
750 draw_number(LEVEL_X + 2 * NUM_SPACING, PLAYFIELD_Y + 2, level, 2);
754 void draw_lives(void)
756 int i;
757 /* Lives num */
758 rb->lcd_bitmap_part(invadrox_numbers, lives * NUMBERS_WIDTH, 0,
759 STRIDE( SCREEN_MAIN,
760 BMPWIDTH_invadrox_numbers,
761 BMPHEIGHT_invadrox_numbers),
762 PLAYFIELD_X + LIVES_X, PLAYFIELD_Y + 2,
763 NUMBERS_WIDTH, FONT_HEIGHT);
765 /* Ships */
766 for (i = 0; i < (lives - 1); i++) {
767 rb->lcd_bitmap_part(invadrox_ships, 0, 0,
768 STRIDE( SCREEN_MAIN,
769 BMPWIDTH_invadrox_ships,
770 BMPHEIGHT_invadrox_ships),
771 PLAYFIELD_X + LIVES_X + SHIP_WIDTH + i * (SHIP_WIDTH + NUM_SPACING),
772 PLAYFIELD_Y + 1, SHIP_WIDTH, SHIP_HEIGHT);
775 /* Erase ship to the right (if less than MAX_LIVES) */
776 if (lives < MAX_LIVES) {
777 rb->lcd_fillrect(PLAYFIELD_X + LIVES_X + SHIP_WIDTH + i * (SHIP_WIDTH + NUM_SPACING),
778 PLAYFIELD_Y + 1, SHIP_WIDTH, SHIP_HEIGHT);
780 /* Update lives (and level) part of screen */
781 rb->lcd_update_rect(PLAYFIELD_X + LIVES_X, PLAYFIELD_Y + 1,
782 PLAYFIELD_WIDTH - 2 * LIVES_X, MAX(FONT_HEIGHT + 1, SHIP_HEIGHT + 1));
786 inline void draw_aliens(void)
788 int i;
790 for (i = 0; i < 5 * ALIENS; i++) {
791 rb->lcd_bitmap_part(invadrox_aliens, aliens[i].x & 1 ? ALIEN_WIDTH : 0,
792 aliens[i].type * ALIEN_HEIGHT,
793 STRIDE( SCREEN_MAIN,
794 BMPWIDTH_invadrox_aliens,
795 BMPHEIGHT_invadrox_aliens),
796 PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED,
797 ALIEN_START_Y + aliens[i].y * ALIEN_HEIGHT,
798 ALIEN_WIDTH, ALIEN_HEIGHT);
803 /* Return false if there is no next alive alien (round is over) */
804 inline bool next_alien(void)
806 bool ret = true;
808 do {
809 curr_alien++;
810 if (curr_alien % ALIENS == 0) {
811 /* End of this row. Move up one row. */
812 curr_alien -= 2 * ALIENS;
813 if (curr_alien < 0) {
814 /* No more aliens in this round. */
815 curr_alien = 4 * ALIENS;
816 ret = false;
819 } while (aliens[curr_alien].state == DEAD && ret);
821 if (!ret) {
822 /* No more alive aliens. Round finished. */
823 if (hit_right_border) {
824 if (hit_left_border) {
825 DBG("ERROR: both left and right borders are set (%d)\n", curr_alien);
827 /* Move down-left next round */
828 aliens_right = false;
829 aliens_down = true;
830 hit_right_border = false;
831 } else if (hit_left_border) {
832 /* Move down-right next round */
833 aliens_right = true;
834 aliens_down = true;
835 hit_left_border = false;
836 } else {
837 /* Not left nor right. Set down to false. */
838 aliens_down = false;
842 return ret;
846 /* All aliens have been moved.
847 * Set curr_alien to first alive.
848 * Return false if no-one is left alive.
850 bool first_alien(void)
852 int i, y;
854 for (y = 4; y >= 0; y--) {
855 for (i = y * ALIENS; i < (y + 1) * ALIENS; i++) {
856 if (aliens[i].state != DEAD) {
857 curr_alien = i;
858 return true;
863 /* All aliens dead. */
864 level_finished = true;
866 return false;
870 bool move_aliens(void)
872 int x, y, old_x, old_y;
874 /* Move current alien (curr_alien is pointing to a living alien) */
876 old_x = aliens[curr_alien].x;
877 old_y = aliens[curr_alien].y;
879 if (aliens_down) {
880 aliens[curr_alien].y++;
881 if (aliens[curr_alien].y == MAX_Y) {
882 /* Alien is at bottom. Game Over. */
883 DBG("Alien %d is at bottom. Game Over.\n", curr_alien);
884 game_over = true;
885 return false;
889 if (aliens_right) {
890 /* Moving right */
891 if (aliens[curr_alien].x < MAX_X) {
892 aliens[curr_alien].x++;
895 /* Now, after move, check if we hit the right border. */
896 if (aliens[curr_alien].x == MAX_X) {
897 hit_right_border = true;
900 } else {
901 /* Moving left */
902 if (aliens[curr_alien].x > 0) {
903 aliens[curr_alien].x--;
906 /* Now, after move, check if we hit the left border. */
907 if (aliens[curr_alien].x == 0) {
908 hit_left_border = true;
912 /* Erase old position */
913 x = PLAYFIELD_X + LIVES_X + old_x * ALIEN_SPEED;
914 y = ALIEN_START_Y + old_y * ALIEN_HEIGHT;
915 if (aliens[curr_alien].y != old_y) {
916 /* Moved in y-dir. Erase whole alien. */
917 rb->lcd_fillrect(x, y, ALIEN_WIDTH, ALIEN_HEIGHT);
918 } else {
919 if (aliens_right) {
920 /* Erase left edge */
921 rb->lcd_fillrect(x, y, ALIEN_SPEED, ALIEN_HEIGHT);
922 } else {
923 /* Erase right edge */
924 x += ALIEN_WIDTH - ALIEN_SPEED;
925 rb->lcd_fillrect(x, y, ALIEN_SPEED, ALIEN_HEIGHT);
929 /* Draw alien at new pos */
930 x = PLAYFIELD_X + LIVES_X + aliens[curr_alien].x * ALIEN_SPEED;
931 y = ALIEN_START_Y + aliens[curr_alien].y * ALIEN_HEIGHT;
932 rb->lcd_bitmap_part(invadrox_aliens,
933 aliens[curr_alien].x & 1 ? ALIEN_WIDTH : 0,
934 aliens[curr_alien].type * ALIEN_HEIGHT,
935 STRIDE( SCREEN_MAIN,
936 BMPWIDTH_invadrox_aliens,
937 BMPHEIGHT_invadrox_aliens),
938 x, y, ALIEN_WIDTH, ALIEN_HEIGHT);
940 if (!next_alien()) {
941 /* Round finished. Set curr_alien to first alive from bottom. */
942 if (!first_alien()) {
943 /* Should never happen. Taken care of in move_fire(). */
944 return false;
946 /* TODO: Play next background sound */
949 return true;
953 inline void draw_ship(void)
955 /* Erase old ship */
956 if (old_ship_x < ship_x) {
957 /* Move right. Erase leftmost part of ship. */
958 rb->lcd_fillrect(old_ship_x, SHIP_Y, ship_x - old_ship_x, SHIP_HEIGHT);
959 } else if (old_ship_x > ship_x) {
960 /* Move left. Erase rightmost part of ship. */
961 rb->lcd_fillrect(ship_x + SHIP_WIDTH, SHIP_Y, old_ship_x - ship_x, SHIP_HEIGHT);
964 /* Draw ship */
965 rb->lcd_bitmap_part(invadrox_ships, 0, ship_frame * SHIP_HEIGHT,
966 STRIDE( SCREEN_MAIN,
967 BMPWIDTH_invadrox_ships,
968 BMPHEIGHT_invadrox_ships),
969 ship_x, SHIP_Y, SHIP_WIDTH, SHIP_HEIGHT);
970 if (ship_hit) {
971 /* Alternate between frame 1 and 2 during hit */
972 ship_frame_counter++;
973 if (ship_frame_counter > 2) {
974 ship_frame_counter = 0;
975 ship_frame++;
976 if (ship_frame > 2) {
977 ship_frame = 1;
982 /* Save ship_x for next time */
983 old_ship_x = ship_x;
987 inline void fire_alpha(int xc, int yc, fb_data color)
989 int oldmode = rb->lcd_get_drawmode();
991 rb->lcd_set_foreground(color);
992 rb->lcd_set_drawmode(DRMODE_FG);
994 rb->lcd_mono_bitmap(invadrox_fire, xc - (FIRE_WIDTH/2), yc, FIRE_WIDTH, FIRE_HEIGHT);
996 rb->lcd_set_foreground(LCD_BLACK);
997 rb->lcd_set_drawmode(oldmode);
1001 void move_fire(void)
1003 bool hit_green = false;
1004 bool hit_white = false;
1005 int i, j;
1006 static int exploding_alien = -1;
1007 fb_data pix;
1009 if (fire == S_IDLE) {
1010 return;
1013 /* Alien hit. Wait until explosion is finished. */
1014 if (aliens_paralyzed < 0) {
1015 aliens_paralyzed++;
1016 if (aliens_paralyzed == 0) {
1017 /* Erase exploding_alien */
1018 rb->lcd_fillrect(PLAYFIELD_X + LIVES_X + aliens[exploding_alien].x * ALIEN_SPEED,
1019 ALIEN_START_Y + aliens[exploding_alien].y * ALIEN_HEIGHT,
1020 ALIEN_EXPLODE_WIDTH, ALIEN_HEIGHT);
1021 fire = S_IDLE;
1022 /* Special case. We killed curr_alien. */
1023 if (exploding_alien == curr_alien) {
1024 if (!next_alien()) {
1025 /* Round finished. Set curr_alien to first alive from bottom. */
1026 first_alien();
1030 return;
1033 if (fire == S_ACTIVE) {
1035 /* Erase */
1036 rb->lcd_vline(fire_x, fire_y, fire_y + SHOT_HEIGHT);
1038 /* Check top */
1039 if (fire_y <= SCORENUM_Y + FONT_HEIGHT + 4) {
1041 /* TODO: Play explode sound */
1043 fire = S_EXPLODE;
1044 fire_target = TARGET_TOP;
1045 fire_alpha(fire_x, fire_y, UFO_RED);
1046 return;
1049 /* Move */
1050 fire_y -= FIRE_SPEED;
1052 /* Hit UFO? */
1053 if (ufo_state == S_ACTIVE) {
1054 if ((ABS(ufo_x + UFO_WIDTH / 2 - fire_x) <= UFO_WIDTH / 2) &&
1055 (fire_y <= UFO_Y + UFO_HEIGHT)) {
1056 ufo_state = S_EXPLODE;
1057 fire = S_EXPLODE;
1058 fire_target = TARGET_UFO;
1059 /* Center explosion */
1060 ufo_x -= (UFO_EXPLODE_WIDTH - UFO_WIDTH) / 2;
1061 rb->lcd_bitmap(invadrox_ufo_explode, ufo_x, UFO_Y - 1,
1062 UFO_EXPLODE_WIDTH, UFO_EXPLODE_HEIGHT);
1063 return;
1067 /* Hit bomb? (check position, not pixel value) */
1068 for (i = 0; i < max_bombs; i++) {
1069 if (bombs[i].state == S_ACTIVE) {
1070 /* Count as hit if within BOMB_WIDTH pixels */
1071 if ((ABS(bombs[i].x - fire_x) < BOMB_WIDTH) &&
1072 (fire_y - bombs[i].y < BOMB_HEIGHT)) {
1073 /* Erase bomb */
1074 rb->lcd_fillrect(bombs[i].x, bombs[i].y, BOMB_WIDTH, BOMB_HEIGHT);
1075 bombs[i].state = S_IDLE;
1076 /* Explode ship fire */
1077 fire = S_EXPLODE;
1078 fire_target = TARGET_SHIELD;
1079 fire_alpha(fire_x, fire_y, LCD_WHITE);
1080 return;
1085 /* Check for hit*/
1086 for (i = FIRE_SPEED; i >= 0; i--) {
1087 pix = get_pixel(fire_x, fire_y + i);
1088 if(pix == screen_white) {
1089 hit_white = true;
1090 fire_y += i;
1091 break;
1093 if(pix == screen_green) {
1094 hit_green = true;
1095 fire_y += i;
1096 break;
1100 if (hit_green) {
1101 /* Hit shield */
1103 /* TODO: Play explode sound */
1105 fire = S_EXPLODE;
1106 fire_target = TARGET_SHIELD;
1107 /* Center explosion around hit pixel */
1108 fire_y -= FIRE_HEIGHT / 2;
1109 fire_alpha(fire_x, fire_y, SLIME_GREEN);
1110 return;
1113 if (hit_white) {
1115 /* Hit alien? */
1116 for (i = 0; i < 5 * ALIENS; i++) {
1117 if (aliens[i].state != DEAD &&
1118 (ABS(fire_x - (PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED +
1119 ALIEN_WIDTH / 2)) <= ALIEN_WIDTH / 2) &&
1120 (ABS(fire_y - (ALIEN_START_Y + aliens[i].y * ALIEN_HEIGHT +
1121 ALIEN_HEIGHT / 2)) <= ALIEN_HEIGHT / 2)) {
1123 /* TODO: play alien hit sound */
1125 if (aliens[i].state == BOMBER) {
1126 /* Set (possible) alien above to bomber */
1127 for (j = i - ALIENS; j >= 0; j -= ALIENS) {
1128 if (aliens[j].state != DEAD) {
1129 /* printf("New bomber (%d, %d)\n", j % ALIENS, j / ALIENS); */
1130 aliens[j].state = BOMBER;
1131 break;
1135 aliens[i].state = DEAD;
1136 exploding_alien = i;
1137 score += scores[aliens[i].type];
1138 draw_score();
1139 /* Update score part of screen */
1140 rb->lcd_update_rect(SCORENUM_X, SCORENUM_Y,
1141 PLAYFIELD_WIDTH - 2 * NUMBERS_WIDTH, FONT_HEIGHT);
1143 /* Paralyze aliens S_EXPLODE frames */
1144 aliens_paralyzed = S_EXPLODE;
1145 rb->lcd_bitmap(invadrox_alien_explode,
1146 PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED,
1147 ALIEN_START_Y + aliens[i].y * ALIEN_HEIGHT,
1148 ALIEN_EXPLODE_WIDTH, ALIEN_EXPLODE_HEIGHT);
1149 /* Since alien is 1 pixel taller than explosion sprite, erase bottom line */
1150 rb->lcd_hline(PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED,
1151 PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED + ALIEN_WIDTH,
1152 ALIEN_START_Y + (aliens[i].y + 1) * ALIEN_HEIGHT - 1);
1153 return;
1158 /* Draw shot */
1159 rb->lcd_set_foreground(LCD_WHITE);
1160 rb->lcd_vline(fire_x, fire_y, fire_y + SHOT_HEIGHT);
1161 rb->lcd_set_foreground(LCD_BLACK);
1162 } else if (fire < S_IDLE) {
1163 /* Count up towards S_IDLE, then erase explosion */
1164 fire++;
1165 if (fire == S_IDLE) {
1166 /* Erase explosion */
1167 if (fire_target == TARGET_TOP) {
1168 rb->lcd_fillrect(fire_x - (FIRE_WIDTH / 2), fire_y, FIRE_WIDTH, FIRE_HEIGHT);
1169 } else if (fire_target == TARGET_SHIELD) {
1170 /* Draw explosion with black pixels */
1171 fire_alpha(fire_x, fire_y, LCD_BLACK);
1178 /* Return a BOMBER alien */
1179 inline int random_bomber(void)
1181 int i, col;
1183 /* TODO: Weigh higher probability near ship */
1184 col = rb->rand() % ALIENS;
1185 for (i = col + 4 * ALIENS; i >= 0; i -= ALIENS) {
1186 if (aliens[i].state == BOMBER) {
1187 return i;
1191 /* No BOMBER found in this col */
1193 for (i = 0; i < 5 * ALIENS; i++) {
1194 if (aliens[i].state == BOMBER) {
1195 return i;
1199 /* No BOMBER found at all (error?) */
1201 return -1;
1205 inline void draw_bomb(int i)
1207 rb->lcd_bitmap_part(invadrox_bombs, bombs[i].type * BOMB_WIDTH,
1208 bombs[i].frame * BOMB_HEIGHT,
1209 STRIDE( SCREEN_MAIN,
1210 BMPWIDTH_invadrox_bombs,
1211 BMPHEIGHT_invadrox_bombs),
1212 bombs[i].x, bombs[i].y,
1213 BOMB_WIDTH, BOMB_HEIGHT);
1214 /* Advance frame */
1215 bombs[i].frame++;
1216 if (bombs[i].frame == bombs[i].frames) {
1217 bombs[i].frame = 0;
1222 void move_bombs(void)
1224 int i, j, bomber;
1225 bool abort;
1227 for (i = 0; i < max_bombs; i++) {
1229 switch (bombs[i].state) {
1231 case S_IDLE:
1232 if (ship_hit) {
1233 continue;
1235 bomber = random_bomber();
1236 if (bomber < 0) {
1237 DBG("ERROR: No bomber available\n");
1238 continue;
1240 /* x, y */
1241 bombs[i].x = PLAYFIELD_X + LIVES_X + aliens[bomber].x * ALIEN_SPEED + ALIEN_WIDTH / 2;
1242 bombs[i].y = ALIEN_START_Y + (aliens[bomber].y + 1) * ALIEN_HEIGHT;
1244 /* Check for duplets in x and y direction */
1245 abort = false;
1246 for (j = i - 1; j >= 0; j--) {
1247 if ((bombs[j].state == S_ACTIVE) &&
1248 ((bombs[i].x == bombs[j].x) || (bombs[i].y == bombs[j].y))) {
1249 abort = true;
1250 break;
1253 if (abort) {
1254 /* Skip this one, continue with next bomb */
1255 /* printf("Bomb %d duplet of %d\n", i, j); */
1256 continue;
1259 /* Passed, set type */
1260 bombs[i].type = rb->rand() % 3;
1261 bombs[i].frame = 0;
1262 if (bombs[i].type == 0) {
1263 bombs[i].frames = 3;
1264 } else if (bombs[i].type == 1) {
1265 bombs[i].frames = 4;
1266 } else {
1267 bombs[i].frames = 6;
1270 /* Bombs away */
1271 bombs[i].state = S_ACTIVE;
1272 draw_bomb(i);
1273 continue;
1275 break;
1277 case S_ACTIVE:
1278 /* Erase old position */
1279 rb->lcd_fillrect(bombs[i].x, bombs[i].y, BOMB_WIDTH, BOMB_HEIGHT);
1281 /* Move */
1282 bombs[i].y += BOMB_SPEED;
1284 /* Check if bottom hit */
1285 if (bombs[i].y + BOMB_HEIGHT >= PLAYFIELD_Y) {
1286 bombs[i].y = PLAYFIELD_Y - FIRE_HEIGHT + 1;
1287 fire_alpha(bombs[i].x, bombs[i].y, LCD_WHITE);
1288 bombs[i].state = S_EXPLODE;
1289 bombs[i].target = TARGET_BOTTOM;
1290 break;
1293 /* Check for green (ship or shield) */
1294 for (j = BOMB_HEIGHT; j >= BOMB_HEIGHT - BOMB_SPEED; j--) {
1295 bombs[i].target = 0;
1296 if(get_pixel(bombs[i].x + BOMB_WIDTH / 2, bombs[i].y + j) == screen_green) {
1297 /* Move to hit pixel */
1298 bombs[i].x += BOMB_WIDTH / 2;
1299 bombs[i].y += j;
1301 /* Check if ship is hit */
1302 if (bombs[i].y > SHIELD_Y + SHIELD_HEIGHT && bombs[i].y < PLAYFIELD_Y) {
1304 /* TODO: play ship hit sound */
1306 ship_hit = true;
1307 ship_frame = 1;
1308 ship_frame_counter = 0;
1309 bombs[i].state = S_EXPLODE * 4;
1310 bombs[i].target = TARGET_SHIP;
1311 rb->lcd_bitmap_part(invadrox_ships, 0, 1 * SHIP_HEIGHT,
1312 STRIDE( SCREEN_MAIN,
1313 BMPWIDTH_invadrox_ships,
1314 BMPHEIGHT_invadrox_ships),
1315 ship_x, SHIP_Y,
1316 SHIP_WIDTH, SHIP_HEIGHT);
1317 break;
1319 /* Shield hit */
1320 bombs[i].state = S_EXPLODE;
1321 bombs[i].target = TARGET_SHIELD;
1322 /* Center explosion around hit pixel in shield */
1323 bombs[i].y -= FIRE_HEIGHT / 2;
1324 fire_alpha(bombs[i].x, bombs[i].y, SLIME_GREEN);
1325 break;
1329 if (bombs[i].target != 0) {
1330 /* Hit ship or shield, continue */
1331 continue;
1334 draw_bomb(i);
1335 break;
1337 default:
1338 /* If we get here state should be < 0, exploding */
1339 bombs[i].state++;
1340 if (bombs[i].state == S_IDLE) {
1341 if (ship_hit) {
1342 /* Erase explosion */
1343 rb->lcd_fillrect(ship_x, SHIP_Y, SHIP_WIDTH, SHIP_HEIGHT);
1344 rb->lcd_update_rect(ship_x, SHIP_Y, SHIP_WIDTH, SHIP_HEIGHT);
1345 ship_hit = false;
1346 ship_frame = 0;
1347 ship_x = PLAYFIELD_X + 2 * LIVES_X;
1348 lives--;
1349 if (lives == 0) {
1350 game_over = true;
1351 return;
1353 draw_lives();
1354 /* Sleep 1s to give player time to examine lives left */
1355 rb->sleep(HZ);
1357 /* Erase explosion (even if ship hit, might be another bomb) */
1358 fire_alpha(bombs[i].x, bombs[i].y, LCD_BLACK);
1360 break;
1366 inline void move_ship(void)
1368 ship_dir += ship_acc;
1369 if (ship_dir > max_ship_speed) {
1370 ship_dir = max_ship_speed;
1372 if (ship_dir < -max_ship_speed) {
1373 ship_dir = -max_ship_speed;
1375 ship_x += ship_dir;
1376 if (ship_x < SHIP_MIN_X) {
1377 ship_x = SHIP_MIN_X;
1379 if (ship_x > SHIP_MAX_X) {
1380 ship_x = SHIP_MAX_X;
1383 draw_ship();
1387 /* Unidentified Flying Object */
1388 void move_ufo(void)
1390 static int ufo_speed;
1391 static int counter;
1392 int mystery_score;
1394 switch (ufo_state) {
1396 case S_IDLE:
1398 if (rb->rand() % 500 == 0) {
1399 /* Uh-oh, it's time to launch a mystery UFO */
1401 /* TODO: Play UFO sound */
1403 if (rb->rand() % 2) {
1404 ufo_speed = UFO_SPEED;
1405 ufo_x = PLAYFIELD_X;
1406 } else {
1407 ufo_speed = -UFO_SPEED;
1408 ufo_x = LCD_WIDTH - PLAYFIELD_X - UFO_WIDTH;
1410 ufo_state = S_ACTIVE;
1411 /* UFO will be drawn next frame */
1413 break;
1415 case S_ACTIVE:
1416 /* Erase old pos */
1417 rb->lcd_fillrect(ufo_x, UFO_Y, UFO_WIDTH, UFO_HEIGHT);
1418 /* Move */
1419 ufo_x += ufo_speed;
1420 /* Check bounds */
1421 if (ufo_x < PLAYFIELD_X || ufo_x > LCD_WIDTH - PLAYFIELD_X - UFO_WIDTH) {
1422 ufo_state = S_IDLE;
1423 break;
1425 /* Draw new pos */
1426 rb->lcd_bitmap(invadrox_ufo, ufo_x, UFO_Y, UFO_WIDTH, UFO_HEIGHT);
1427 break;
1429 case S_SHOWSCORE:
1430 counter++;
1431 if (counter == S_IDLE) {
1432 /* Erase mystery number */
1433 rb->lcd_fillrect(ufo_x, UFO_Y, 3 * NUMBERS_WIDTH + 2 * NUM_SPACING, FONT_HEIGHT);
1434 ufo_state = S_IDLE;
1436 break;
1438 default:
1439 /* Exploding */
1440 ufo_state++;
1441 if (ufo_state == S_IDLE) {
1442 /* Erase explosion */
1443 rb->lcd_fillrect(ufo_x, UFO_Y - 1, UFO_EXPLODE_WIDTH, UFO_EXPLODE_HEIGHT);
1444 ufo_state = S_SHOWSCORE;
1445 counter = S_EXPLODE * 4;
1446 /* Draw mystery_score, sleep, increase score and continue */
1447 mystery_score = 50 + (rb->rand() % 6) * 50;
1448 if (mystery_score < 100) {
1449 draw_number(ufo_x, UFO_Y, mystery_score, 2);
1450 } else {
1451 draw_number(ufo_x, UFO_Y, mystery_score, 3);
1453 score += mystery_score;
1454 draw_score();
1456 break;
1461 void draw_background(void)
1464 rb->lcd_bitmap(invadrox_background, 0, 0, LCD_WIDTH, LCD_HEIGHT);
1465 rb->lcd_update();
1469 void new_level(void)
1471 int i;
1473 draw_background();
1474 /* Give an extra life for each new level */
1475 if (lives < MAX_LIVES) {
1476 lives++;
1478 draw_lives();
1480 /* Score */
1481 draw_score();
1482 draw_number(HISCORENUM_X, SCORENUM_Y, hiscore.score, 4);
1484 level++;
1485 draw_level();
1486 level_finished = false;
1488 ufo_state = S_IDLE;
1490 /* Init alien positions and states */
1491 for (i = 0; i < 4 * ALIENS; i++) {
1492 aliens[i].x = 0 + (i % ALIENS) * ((ALIEN_WIDTH + ALIEN_SPACING) / ALIEN_SPEED);
1493 aliens[i].y = 2 * (i / ALIENS);
1494 aliens[i].state = ALIVE;
1496 /* Last row, bombers */
1497 for (i = 4 * ALIENS; i < 5 * ALIENS; i++) {
1498 aliens[i].x = 0 + (i % ALIENS) * ((ALIEN_WIDTH + ALIEN_SPACING) / ALIEN_SPEED);
1499 aliens[i].y = 2 * (i / ALIENS);
1500 aliens[i].state = BOMBER;
1503 /* Init bombs to inactive (S_IDLE) */
1504 for (i = 0; i < MAX_BOMBS; i++) {
1505 bombs[i].state = S_IDLE;
1508 /* Start aliens closer to earth from level 2 */
1509 for (i = 0; i < 5 * ALIENS; i++) {
1510 if (level < 6) {
1511 aliens[i].y += level - 1;
1512 } else {
1513 aliens[i].y += 5;
1517 /* Max concurrent bombs */
1518 max_bombs = 1;
1520 gamespeed = 2;
1522 if (level > 1) {
1523 max_bombs++;
1526 /* Increase speed */
1527 if (level > 2) {
1528 gamespeed++;
1531 if (level > 3) {
1532 max_bombs++;
1535 /* Increase speed more */
1536 if (level > 4) {
1537 gamespeed++;
1540 if (level > 5) {
1541 max_bombs++;
1544 /* 4 shields */
1545 for (i = 1; i <= 4; i++) {
1546 rb->lcd_bitmap(invadrox_shield,
1547 PLAYFIELD_X + i * PLAYFIELD_WIDTH / 5 - SHIELD_WIDTH / 2,
1548 SHIELD_Y, SHIELD_WIDTH, SHIELD_HEIGHT);
1551 /* Bottom line */
1552 rb->lcd_set_foreground(SLIME_GREEN);
1553 rb->lcd_hline(PLAYFIELD_X, LCD_WIDTH - PLAYFIELD_X, PLAYFIELD_Y);
1554 /* Restore foreground to black (for fast erase later). */
1555 rb->lcd_set_foreground(LCD_BLACK);
1557 ship_x = PLAYFIELD_X + 2 * LIVES_X;
1558 if (level == 1) {
1559 old_ship_x = ship_x;
1561 ship_dir = 0;
1562 ship_acc = 0;
1563 ship_frame = 0;
1564 ship_hit = false;
1565 fire = S_IDLE;
1566 /* Start moving the bottom row left to right */
1567 curr_alien = 4 * ALIENS;
1568 aliens_paralyzed = 0;
1569 aliens_right = true;
1570 aliens_down = false;
1571 hit_left_border = false;
1572 hit_right_border = false;
1573 /* TODO: Change max_ship_speed to 3 at higher levels */
1574 max_ship_speed = 2;
1576 draw_aliens();
1578 rb->lcd_update();
1582 void init_invadrox(void)
1584 int i;
1586 /* Seed random number generator with a "random" number */
1587 rb->srand(rb->get_time()->tm_sec + rb->get_time()->tm_min * 60);
1589 /* Precalculate start of each scanline */
1590 for (i = 0; i < LCD_HEIGHT; i++) {
1591 #if (LCD_DEPTH >= 8)
1592 ytab[i] = i * LCD_WIDTH;
1593 #elif (LCD_DEPTH == 2) && (LCD_PIXELFORMAT == HORIZONTAL_PACKING)
1594 ytab[i] = i * (LCD_WIDTH / 4);
1595 #elif (LCD_DEPTH == 2) && (LCD_PIXELFORMAT == VERTICAL_PACKING)
1596 ytab[i] = (i / 4) * LCD_WIDTH;
1597 #else
1598 #error pixelformat not implemented yet
1599 #endif
1602 rb->lcd_set_background(LCD_BLACK);
1603 rb->lcd_set_foreground(LCD_BLACK);
1605 if (highscore_load(HISCOREFILE, &hiscore, 1) < 0) {
1606 /* Init hiscore to 0 */
1607 rb->strlcpy(hiscore.name, "Invader", sizeof(hiscore.name));
1608 hiscore.score = 0;
1609 hiscore.level = 1;
1612 /* Init alien types in aliens array */
1613 for (i = 0; i < 1 * ALIENS; i++) {
1614 aliens[i].type = 0; /* Kang */
1616 for (; i < 3 * ALIENS; i++) {
1617 aliens[i].type = 1; /* Kodos */
1619 for (; i < 5 * ALIENS; i++) {
1620 aliens[i].type = 2; /* Serak */
1624 /* Save screen white color */
1625 rb->lcd_set_foreground(LCD_WHITE);
1626 rb->lcd_drawpixel(0, 0);
1627 rb->lcd_update_rect(0, 0, 1, 1);
1628 screen_white = get_pixel(0, 0);
1630 /* Save screen green color */
1631 rb->lcd_set_foreground(SLIME_GREEN);
1632 rb->lcd_drawpixel(0, 0);
1633 rb->lcd_update_rect(0, 0, 1, 1);
1634 screen_green = get_pixel(0, 0);
1636 /* Restore black foreground */
1637 rb->lcd_set_foreground(LCD_BLACK);
1639 new_level();
1641 /* Flash score at start */
1642 for (i = 0; i < 5; i++) {
1643 rb->lcd_fillrect(SCORENUM_X, SCORENUM_Y,
1644 4 * NUMBERS_WIDTH + 3 * NUM_SPACING,
1645 FONT_HEIGHT);
1646 rb->lcd_update_rect(SCORENUM_X, SCORENUM_Y,
1647 4 * NUMBERS_WIDTH + 3 * NUM_SPACING,
1648 FONT_HEIGHT);
1649 rb->sleep(HZ / 10);
1650 draw_number(SCORENUM_X, SCORENUM_Y, score, 4);
1651 rb->sleep(HZ / 10);
1656 inline bool handle_buttons(void)
1658 static unsigned int oldbuttonstate = 0;
1660 unsigned int released, pressed, newbuttonstate;
1662 if (ship_hit) {
1663 /* Don't allow ship movement during explosion */
1664 newbuttonstate = 0;
1665 } else {
1666 newbuttonstate = rb->button_status();
1668 if(newbuttonstate == oldbuttonstate) {
1669 if (newbuttonstate == 0) {
1670 /* No button pressed. Stop ship. */
1671 ship_acc = 0;
1672 if (ship_dir > 0) {
1673 ship_dir--;
1675 if (ship_dir < 0) {
1676 ship_dir++;
1679 /* return false; */
1680 goto check_usb;
1682 released = ~newbuttonstate & oldbuttonstate;
1683 pressed = newbuttonstate & ~oldbuttonstate;
1684 oldbuttonstate = newbuttonstate;
1685 if (pressed) {
1686 if (pressed & ACTION_LEFT) {
1687 if (ship_acc > -1) {
1688 ship_acc--;
1691 if (pressed & ACTION_RIGHT) {
1692 if (ship_acc < 1) {
1693 ship_acc++;
1696 if (pressed & ACTION_FIRE) {
1697 if (fire == S_IDLE) {
1698 /* Fire shot */
1699 fire_x = ship_x + SHIP_WIDTH / 2;
1700 fire_y = SHIP_Y - SHOT_HEIGHT;
1701 fire = S_ACTIVE;
1702 /* TODO: play fire sound */
1705 if (pressed & ACTION_QUIT) {
1706 rb->splash(HZ * 1, "Quit");
1707 return true;
1710 if (released) {
1711 if ((released & ACTION_LEFT)) {
1712 if (ship_acc < 1) {
1713 ship_acc++;
1716 if ((released & ACTION_RIGHT)) {
1717 if (ship_acc > -1) {
1718 ship_acc--;
1723 check_usb:
1725 /* Quit if USB is connected */
1726 if (rb->button_get(false) == SYS_USB_CONNECTED) {
1727 return true;
1730 return false;
1734 void game_loop(void)
1736 int i, end;
1738 /* Print dimensions (just for debugging) */
1739 DBG("%03dx%03dx%02d\n", LCD_WIDTH, LCD_HEIGHT, LCD_DEPTH);
1741 /* Init */
1742 init_invadrox();
1744 while (1) {
1745 /* Convert CYCLETIME (in ms) to HZ */
1746 end = *rb->current_tick + (CYCLETIME * HZ) / 1000;
1748 if (handle_buttons()) {
1749 return;
1752 /* Animate */
1753 move_ship();
1754 move_fire();
1756 /* Check if level is finished (marked by move_fire) */
1757 if (level_finished) {
1758 /* TODO: Play level finished sound */
1759 new_level();
1762 move_ufo();
1764 /* Move aliens */
1765 if (!aliens_paralyzed && !ship_hit) {
1766 for (i = 0; i < gamespeed; i++) {
1767 if (!move_aliens()) {
1768 if (game_over) {
1769 return;
1775 /* Move alien bombs */
1776 move_bombs();
1777 if (game_over) {
1778 return;
1781 /* Update "playfield" rect */
1782 rb->lcd_update_rect(PLAYFIELD_X, SCORENUM_Y + FONT_HEIGHT,
1783 PLAYFIELD_WIDTH,
1784 PLAYFIELD_Y + 1 - SCORENUM_Y - FONT_HEIGHT);
1786 /* Wait until next frame */
1787 DBG("%ld (%d)\n", end - *rb->current_tick, (CYCLETIME * HZ) / 1000);
1788 if (TIME_BEFORE(*rb->current_tick, end)) {
1789 rb->sleep(end - *rb->current_tick);
1790 } else {
1791 rb->yield();
1794 } /* end while */
1798 /* this is the plugin entry point */
1799 enum plugin_status plugin_start(UNUSED const void* parameter)
1801 rb->lcd_setfont(FONT_SYSFIXED);
1802 /* Turn off backlight timeout */
1803 backlight_force_on(); /* backlight control in lib/helper.c */
1805 /* now go ahead and have fun! */
1806 game_loop();
1808 /* Game Over. */
1809 /* TODO: Play game over sound */
1810 rb->splash(HZ * 2, "Game Over");
1811 if (score > hiscore.score) {
1812 /* Save new hiscore */
1813 highscore_update(score, level, "Invader", &hiscore, 1);
1814 highscore_save(HISCOREFILE, &hiscore, 1);
1817 /* Restore user's original backlight setting */
1818 rb->lcd_setfont(FONT_UI);
1819 /* Turn on backlight timeout (revert to settings) */
1820 backlight_use_settings(); /* backlight control in lib/helper.c */
1822 return PLUGIN_OK;
1828 * GNU Emacs settings: Kernighan & Richie coding style with
1829 * 4 spaces indent and no tabs.
1830 * Local Variables:
1831 * c-file-style: "k&r"
1832 * c-basic-offset: 4
1833 * indent-tabs-mode: nil
1834 * End: