Prepare new maemo release
[maemo-rb.git] / apps / plugins / invadrox.c
blobaf17554ecaf8de1c137a6b9734264b52bf9419cc
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
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 #ifndef ABS
85 #define ABS(a) (((a) < 0) ? -(a) : (a))
86 #endif
88 #if CONFIG_KEYPAD == IRIVER_H100_PAD
90 #define QUIT BUTTON_OFF
91 #define LEFT BUTTON_LEFT
92 #define RIGHT BUTTON_RIGHT
93 #define FIRE BUTTON_ON
95 #elif CONFIG_KEYPAD == IRIVER_H300_PAD
97 #define QUIT BUTTON_OFF
98 #define LEFT BUTTON_LEFT
99 #define RIGHT BUTTON_RIGHT
100 #define FIRE BUTTON_SELECT
102 #elif (CONFIG_KEYPAD == IRIVER_H10_PAD)
104 #define QUIT BUTTON_POWER
105 #define LEFT BUTTON_LEFT
106 #define RIGHT BUTTON_RIGHT
107 #define FIRE BUTTON_PLAY
109 #elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \
110 (CONFIG_KEYPAD == IPOD_3G_PAD) || \
111 (CONFIG_KEYPAD == IPOD_1G2G_PAD)
113 #define QUIT BUTTON_MENU
114 #define LEFT BUTTON_LEFT
115 #define RIGHT BUTTON_RIGHT
116 #define FIRE BUTTON_SELECT
118 #elif CONFIG_KEYPAD == IAUDIO_X5M5_PAD
120 #define QUIT BUTTON_POWER
121 #define LEFT BUTTON_LEFT
122 #define RIGHT BUTTON_RIGHT
123 #define FIRE BUTTON_SELECT
125 #elif CONFIG_KEYPAD == GIGABEAT_PAD \
126 || CONFIG_KEYPAD == SAMSUNG_YPR0_PAD
128 #define QUIT BUTTON_POWER
129 #define LEFT BUTTON_LEFT
130 #define RIGHT BUTTON_RIGHT
131 #define FIRE BUTTON_SELECT
133 #elif (CONFIG_KEYPAD == SANSA_E200_PAD) || \
134 (CONFIG_KEYPAD == SANSA_CONNECT_PAD)
136 #define QUIT BUTTON_POWER
137 #define LEFT BUTTON_LEFT
138 #define RIGHT BUTTON_RIGHT
139 #define FIRE BUTTON_SELECT
141 #elif CONFIG_KEYPAD == SANSA_FUZE_PAD
143 #define QUIT (BUTTON_HOME|BUTTON_REPEAT)
144 #define LEFT BUTTON_LEFT
145 #define RIGHT BUTTON_RIGHT
146 #define FIRE BUTTON_SELECT
148 #elif CONFIG_KEYPAD == TATUNG_TPJ1022_PAD
150 /* TODO: Figure out which buttons to use for Tatung Elio TPJ-1022 */
151 #define QUIT BUTTON_AB
152 #define LEFT BUTTON_LEFT
153 #define RIGHT BUTTON_RIGHT
154 #define FIRE BUTTON_MENU
156 #elif CONFIG_KEYPAD == GIGABEAT_S_PAD
158 #define QUIT BUTTON_BACK
159 #define LEFT BUTTON_LEFT
160 #define RIGHT BUTTON_RIGHT
161 #define FIRE BUTTON_SELECT
163 #elif CONFIG_KEYPAD == COWON_D2_PAD
165 #define QUIT BUTTON_POWER
166 #define LEFT BUTTON_MINUS
167 #define RIGHT BUTTON_PLUS
168 #define FIRE BUTTON_MENU
170 #elif CONFIG_KEYPAD == IAUDIO67_PAD
172 #define QUIT BUTTON_POWER
173 #define LEFT BUTTON_LEFT
174 #define RIGHT BUTTON_RIGHT
175 #define FIRE BUTTON_PLAY
177 #elif CONFIG_KEYPAD == CREATIVEZVM_PAD
179 #define QUIT BUTTON_BACK
180 #define LEFT BUTTON_LEFT
181 #define RIGHT BUTTON_RIGHT
182 #define FIRE BUTTON_SELECT
184 #elif CONFIG_KEYPAD == PHILIPS_HDD6330_PAD
186 #define QUIT BUTTON_POWER
187 #define LEFT BUTTON_LEFT
188 #define RIGHT BUTTON_RIGHT
189 #define FIRE BUTTON_PLAY
191 #elif CONFIG_KEYPAD == PHILIPS_SA9200_PAD
193 #define QUIT BUTTON_POWER
194 #define LEFT BUTTON_PREV
195 #define RIGHT BUTTON_NEXT
196 #define FIRE BUTTON_PLAY
198 #elif CONFIG_KEYPAD == ONDAVX747_PAD || \
199 CONFIG_KEYPAD == ONDAVX777_PAD || \
200 CONFIG_KEYPAD == MROBE500_PAD
202 #define QUIT BUTTON_POWER
204 #elif CONFIG_KEYPAD == SAMSUNG_YH_PAD
206 #define QUIT BUTTON_REC
207 #define LEFT BUTTON_LEFT
208 #define RIGHT BUTTON_RIGHT
209 #define FIRE BUTTON_PLAY
211 #elif CONFIG_KEYPAD == PBELL_VIBE500_PAD
213 #define QUIT BUTTON_REC
214 #define LEFT BUTTON_PREV
215 #define RIGHT BUTTON_NEXT
216 #define FIRE BUTTON_OK
218 #elif CONFIG_KEYPAD == MPIO_HD300_PAD
220 #define QUIT BUTTON_MENU
221 #define LEFT BUTTON_REW
222 #define RIGHT BUTTON_FF
223 #define FIRE BUTTON_ENTER
225 #elif CONFIG_KEYPAD == SANSA_FUZEPLUS_PAD
227 #define QUIT BUTTON_POWER
228 #define LEFT BUTTON_LEFT
229 #define RIGHT BUTTON_RIGHT
230 #define FIRE BUTTON_SELECT
232 #elif (CONFIG_KEYPAD == HM60X_PAD) || \
233 (CONFIG_KEYPAD == HM801_PAD)
235 #define QUIT BUTTON_POWER
236 #define LEFT BUTTON_LEFT
237 #define RIGHT BUTTON_RIGHT
238 #define FIRE BUTTON_SELECT
240 #else
241 #error INVADROX: Unsupported keypad
242 #endif
244 #ifndef RC_QUIT
245 #define RC_QUIT 0
246 #endif
248 #ifdef HAVE_TOUCHSCREEN
250 #ifndef QUIT
251 #define QUIT 0
252 #endif
253 #ifndef LEFT
254 #define LEFT 0
255 #endif
256 #ifndef RIGHT
257 #define RIGHT 0
258 #endif
259 #ifndef FIRE
260 #define FIRE 0
261 #endif
263 #define TOUCHSCREEN_QUIT BUTTON_TOPLEFT
264 #define TOUCHSCREEN_LEFT (BUTTON_MIDLEFT | BUTTON_BOTTOMLEFT)
265 #define TOUCHSCREEN_RIGHT (BUTTON_MIDRIGHT | BUTTON_BOTTOMRIGHT)
266 #define TOUCHSCREEN_FIRE (BUTTON_CENTER | BUTTON_BOTTOMMIDDLE)
268 #define ACTION_QUIT (QUIT | TOUCHSCREEN_QUIT | RC_QUIT)
269 #define ACTION_LEFT (LEFT | TOUCHSCREEN_LEFT)
270 #define ACTION_RIGHT (RIGHT | TOUCHSCREEN_RIGHT)
271 #define ACTION_FIRE (FIRE | TOUCHSCREEN_FIRE)
273 #else /* HAVE_TOUCHSCREEN */
275 #define ACTION_QUIT (QUIT | RC_QUIT)
276 #define ACTION_LEFT LEFT
277 #define ACTION_RIGHT RIGHT
278 #define ACTION_FIRE FIRE
280 #endif
282 #ifndef UNUSED
283 #define UNUSED __attribute__ ((unused))
284 #endif
286 /* Defines common to all models */
287 #define UFO_Y (SCORENUM_Y + FONT_HEIGHT + ALIEN_HEIGHT)
288 #define PLAYFIELD_Y (LCD_HEIGHT - SHIP_HEIGHT - 2)
289 #define PLAYFIELD_WIDTH (LCD_WIDTH - 2 * PLAYFIELD_X)
290 #define LEVEL_X (LCD_WIDTH - PLAYFIELD_X - LIVES_X - 2 * NUMBERS_WIDTH - 3 * NUM_SPACING)
291 #define SHIP_MIN_X (PLAYFIELD_X + PLAYFIELD_WIDTH / 5 - SHIELD_WIDTH / 2 - SHIP_WIDTH)
292 #define SHIP_MAX_X (PLAYFIELD_X + 4 * PLAYFIELD_WIDTH / 5 + SHIELD_WIDTH / 2)
293 /* SCORE_Y = 0 for most targets. Gigabeat redefines it later. */
294 #define SCORE_Y 0
295 #define MAX_LIVES 8
298 /* m:robe 500 defines */
299 #if ((LCD_WIDTH == 640) && (LCD_HEIGHT == 480)) || \
300 ((LCD_WIDTH == 480) && (LCD_HEIGHT == 640))
302 /* Original arcade game size 224x240, 1bpp with
303 * red overlay at top and green overlay at bottom.
305 * M:Robe 500: 640x480x16
306 * ======================
309 #define ARCADISH_GRAPHICS
310 #define PLAYFIELD_X 48
311 #define SHIP_Y (PLAYFIELD_Y - 2 * SHIP_HEIGHT)
312 #define ALIEN_START_Y (UFO_Y + ALIEN_HEIGHT)
313 #define SCORENUM_X (PLAYFIELD_X + NUMBERS_WIDTH)
314 #define SCORENUM_Y (SCORE_Y + FONT_HEIGHT + 2)
315 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 1 - 6 * NUMBERS_WIDTH - 5 * NUM_SPACING)
316 #define SHIELD_Y (PLAYFIELD_Y - 5 * SHIP_HEIGHT)
317 #define LIVES_X 10
318 #define MAX_Y 18
320 /* iPod Video defines */
321 #elif (LCD_WIDTH == 320) && (LCD_HEIGHT == 240)
323 /* Original arcade game size 224x240, 1bpp with
324 * red overlay at top and green overlay at bottom.
326 * iPod Video: 320x240x16
327 * ======================
328 * X: 48p padding at left/right gives 224p playfield in middle.
329 * 10p "border" gives 204p actual playfield. UFO use full 224p.
330 * Y: Use full 240p.
332 * MAX_X = (204 - 12) / 2 - 1 = 95
334 * Y: Score text 7 0
335 * Space 10 7
336 * Score 7 17
337 * Space 8 24
338 * 3 Ufo 7 32
339 * 2 Space Aliens start at 32 + 3 * 8 = 56
340 * 0 aliens 9*8 56 -
341 * space ~7*8 128 | 18.75 aliens space between
342 * shield 2*8 182 | first alien and ship.
343 * space 8 198 | MAX_Y = 18
344 * ship 8 206 -
345 * space 2*8 214
346 * hline 1 230 - PLAYFIELD_Y
347 * bottom border 10 240
348 * Lives and Level goes inside bottom border
351 #define ARCADISH_GRAPHICS
352 #define PLAYFIELD_X 48
353 #define SHIP_Y (PLAYFIELD_Y - 3 * SHIP_HEIGHT)
354 #define ALIEN_START_Y (UFO_Y + 3 * ALIEN_HEIGHT)
355 #define SCORENUM_X (PLAYFIELD_X + NUMBERS_WIDTH)
356 #define SCORENUM_Y SCORE_Y + (2 * (FONT_HEIGHT + 1) + 1)
357 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 1 - 6 * NUMBERS_WIDTH - 5 * NUM_SPACING)
358 #define SHIELD_Y (PLAYFIELD_Y - 6 * SHIP_HEIGHT)
359 #define LIVES_X 10
360 #define MAX_Y 18
362 #elif (LCD_WIDTH == 176) && (LCD_HEIGHT == 220)
364 /* Sandisk Sansa e200: 176x220x16
365 * ==============================
366 * X: No padding. 8p border -> 160p playfield.
368 * 8p Aliens with 3p spacing -> 88 + 30 = 118p aliens block.
369 * (160 - 118) / 2 = 21 rounds for whole block (more than original)
370 * MAX_X = (160 - 8) / 2 - 1 = 75 rounds for single alien (less than original)
372 * LOGO 70 0
373 * Score text 5 70
374 * Space 5 75
375 * Y Score 5 80
376 * Space 10 85
377 * 2 Ufo 5 95
378 * 2 Space 10 100
379 * 0 aliens 9*5 110 -
380 * space ~7*5 155 | 18.6 aliens space between
381 * shield 2*5 188 | first alien and ship.
382 * space 5 198 | MAX_Y = 18
383 * ship 5 203 -
384 * space 5 208
385 * hline 1 213 PLAYFIELD_Y
386 * bottom border 6
387 * LCD_HEIGHT 220
388 * Lives and Level goes inside bottom border
391 #define SMALL_GRAPHICS
392 #define PLAYFIELD_X 0
393 #define SHIP_Y (PLAYFIELD_Y - 2 * SHIP_HEIGHT)
394 #define SHIELD_Y (SHIP_Y - SHIP_HEIGHT - SHIELD_HEIGHT)
395 #define ALIEN_START_Y (UFO_Y + 3 * SHIP_HEIGHT)
396 /* Redefine SCORE_Y */
397 #undef SCORE_Y
398 #define SCORE_Y 70
399 #define SCORENUM_X (PLAYFIELD_X + NUMBERS_WIDTH)
400 #define SCORENUM_Y (SCORE_Y + 2 * FONT_HEIGHT)
401 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 1 - 6 * NUMBERS_WIDTH - 5 * NUM_SPACING)
402 #define LIVES_X 8
403 #define MAX_Y 18
406 #elif (LCD_WIDTH == 176) && (LCD_HEIGHT == 132)
408 /* iPod Nano: 176x132x16
409 * ======================
410 * X: No padding. 8p border -> 160p playfield.
412 * LIVES_X 8
413 * ALIEN_WIDTH 8
414 * ALIEN_HEIGHT 5
415 * ALIEN_SPACING 3
416 * SHIP_WIDTH 10
417 * SHIP_HEIGHT 5
418 * FONT_HEIGHT 5
419 * UFO_WIDTH 10
420 * UFO_HEIGHT 5
421 * SHIELD_WIDTH 15
422 * SHIELD_HEIGHT 10
423 * MAX_X 75
424 * MAX_Y = 18
425 * ALIEN_START_Y (UFO_Y + 12)
427 * 8p Aliens with 3p spacing -> 88 + 30 = 118p aliens block.
428 * (160 - 118) / 2 = 21 rounds for whole block (more than original)
429 * MAX_X = (160 - 8) / 2 - 1 = 75 rounds for single alien (less than original)
431 * Y: Scoreline 5 0 (combine scoretext and numbers on same line)
432 * Space 5 5
433 * 1 Ufo 5 10
434 * 3 Space 7 15
435 * 2 aliens 9*5 22 -
436 * space ~7*5 67 | Just above 18 aliens space between
437 * shield 2*5 100 | first alien and ship.
438 * space 5 110 | MAX_Y = 18
439 * ship 5 115 -
440 * space 5 120
441 * hline 1 125 PLAYFIELD_Y
442 * bottom border 6 126
443 * LCD_HEIGHT 131
444 * Lives and Level goes inside bottom border
447 #define SMALL_GRAPHICS
448 #define PLAYFIELD_X 0
449 #define SHIP_Y (PLAYFIELD_Y - 2 * SHIP_HEIGHT)
450 #define ALIEN_START_Y (UFO_Y + 12)
451 #define SCORENUM_X (PLAYFIELD_X + 6 * NUMBERS_WIDTH + 5 * NUM_SPACING)
452 #define SCORENUM_Y SCORE_Y
453 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 4 * NUMBERS_WIDTH - 3 * NUM_SPACING)
454 #define SHIELD_Y (SHIP_Y - SHIP_HEIGHT - SHIELD_HEIGHT)
455 #define LIVES_X 8
456 #define MAX_Y 18
458 #elif (LCD_WIDTH == 160) && (LCD_HEIGHT == 128)
460 /* iAudio X5, iRiver H10 20Gb, iPod 3g/4g, H100, M5: 160x128
461 * =========================================================
462 * X: No padding. No border -> 160p playfield.
464 * LIVES_X 0
465 * ALIEN_WIDTH 8
466 * ALIEN_HEIGHT 5
467 * ALIEN_SPACING 3
468 * SHIP_WIDTH 10
469 * SHIP_HEIGHT 5
470 * FONT_HEIGHT 5
471 * UFO_WIDTH 10
472 * UFO_HEIGHT 5
473 * SHIELD_WIDTH 15
474 * SHIELD_HEIGHT 10
475 * MAX_X 75
476 * MAX_Y = 18
477 * ALIEN_START_Y (UFO_Y + 10)
479 * 8p Aliens with 3p spacing -> 88 + 30 = 118p aliens block.
480 * (160 - 118) / 2 = 21 rounds for whole block (more than original)
481 * MAX_X = (160 - 8) / 2 - 1 = 75 rounds for single alien (less than original)
483 * Y: Scoreline 5 0 (combine scoretext and numbers on same line)
484 * Space 5 5
485 * 1 Ufo 5 10
486 * 2 Space 5 15
487 * 8 aliens 9*5 20 -
488 * space ~6*5 65 | Just above 18 aliens space between
489 * shield 2*5 96 | first alien and ship.
490 * space 5 106 | MAX_Y = 18
491 * ship 5 111 -
492 * space 5 116
493 * hline 1 121 PLAYFIELD_Y
494 * bottom border 6 122
495 * LCD_HEIGHT 128
496 * Lives and Level goes inside bottom border
499 #define SMALL_GRAPHICS
500 #define PLAYFIELD_X 0
501 #define SHIP_Y (PLAYFIELD_Y - 2 * SHIP_HEIGHT)
502 #define ALIEN_START_Y (UFO_Y + 10)
503 #define SCORENUM_X (PLAYFIELD_X + 6 * NUMBERS_WIDTH + 5 * NUM_SPACING)
504 #define SCORENUM_Y SCORE_Y
505 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 4 * NUMBERS_WIDTH - 3 * NUM_SPACING)
506 #define SHIELD_Y (SHIP_Y - SHIP_HEIGHT - SHIELD_HEIGHT)
507 #define LIVES_X 0
508 #define MAX_Y 18
511 #elif (LCD_WIDTH == 240) && ((LCD_HEIGHT == 320) || (LCD_HEIGHT == 400))
513 /* Gigabeat: 240x320x16
514 * ======================
515 * X: 8p padding at left/right gives 224p playfield in middle.
516 * 10p "border" gives 204p actual playfield. UFO use full 224p.
517 * Y: Use bottom 240p for playfield and top 80 pixels for logo.
519 * MAX_X = (204 - 12) / 2 - 1 = 95
521 * Y: Score text 7 0 + 80
522 * Space 10 7 + 80
523 * Score 7 17 + 80
524 * Space 8 24 + 80
525 * 3 Ufo 7 32 + 80
526 * 2 Space Aliens start at 32 + 3 * 8 = 56
527 * 0 aliens 9*8 56 -
528 * space ~7*8 128 | 18.75 aliens space between
529 * shield 2*8 182 | first alien and ship.
530 * space 8 198 | MAX_Y = 18
531 * ship 8 206 -
532 * space 2*8 214
533 * hline 1 230 310 - PLAYFIELD_Y
534 * bottom border 10 240 320
535 * Lives and Level goes inside bottom border
538 #define ARCADISH_GRAPHICS
539 #define PLAYFIELD_X 8
540 #define SHIP_Y (PLAYFIELD_Y - 3 * SHIP_HEIGHT)
541 #define ALIEN_START_Y (UFO_Y + 3 * ALIEN_HEIGHT)
542 /* Redefine SCORE_Y */
543 #undef SCORE_Y
544 #define SCORE_Y 80
545 #define SCORENUM_X (PLAYFIELD_X + NUMBERS_WIDTH)
546 #define SCORENUM_Y SCORE_Y + (2 * (FONT_HEIGHT + 1) + 1)
547 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 1 - 6 * NUMBERS_WIDTH - 5 * NUM_SPACING)
548 #define SHIELD_Y (PLAYFIELD_Y - 6 * SHIP_HEIGHT)
549 #define LIVES_X 10
550 #define MAX_Y 18
552 #elif (LCD_WIDTH == 220) && (LCD_HEIGHT == 176)
554 /* TPJ1022, H300, iPod Color: 220x176x16
555 * ============================
556 * X: 0p padding at left/right gives 220p playfield in middle.
557 * 8p "border" gives 204p actual playfield. UFO use full 220p.
558 * Y: Use full 176p for playfield.
560 * MAX_X = (204 - 12) / 2 - 1 = 95
562 * Y: Score text 7 0
563 * Space 8 7
564 * 1 Ufo 7 15
565 * 7 Space Aliens start at 15 + 3 * 8 = 56
566 * 6 aliens 9*8 25 -
567 * space ~7*8 103 | 15.6 aliens space between
568 * shield 2*8 126 | first alien and ship.
569 * space 8 142 | MAX_Y = 15
570 * ship 8 150 -
571 * space 8 158
572 * hline 1 166 - PLAYFIELD_Y
573 * bottom border 10 176
574 * Lives and Level goes inside bottom border
577 #define ARCADISH_GRAPHICS
578 #define PLAYFIELD_X 0
579 #define SHIP_Y (PLAYFIELD_Y - 2 * SHIP_HEIGHT)
580 #define ALIEN_START_Y (UFO_Y + 10)
581 #define SCORENUM_Y SCORE_Y
582 #define SCORENUM_X (PLAYFIELD_X + 6 * NUMBERS_WIDTH + 6 * NUM_SPACING)
583 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 4 * NUMBERS_WIDTH - 3 * NUM_SPACING)
584 #define SHIELD_Y (PLAYFIELD_Y - 5 * SHIP_HEIGHT)
585 #define LIVES_X 8
586 #define MAX_Y 15
589 #else
590 #error INVADROX: Unsupported LCD type
591 #endif
593 #define MAX_X ((LCD_WIDTH-LIVES_X*2-PLAYFIELD_X*2 - ALIEN_WIDTH)/2 - 1)
595 /* Defines common to each "graphic type" */
596 #ifdef ARCADISH_GRAPHICS
598 #define SHOT_HEIGHT 5
599 #define ALIEN_SPACING 4
600 #define ALIEN_SPEED 2
601 #define UFO_SPEED 1
602 #define NUM_SPACING 3
603 #define FIRE_SPEED 8
604 #define BOMB_SPEED 3
605 #define ALIENS 11
607 #elif defined SMALL_GRAPHICS
609 #define SHOT_HEIGHT 4
610 #define ALIEN_SPACING 3
611 #define ALIEN_SPEED 2
612 #define UFO_SPEED 1
613 #define NUM_SPACING 2
614 #define FIRE_SPEED 6
615 #define BOMB_SPEED 2
616 #define ALIENS 11
618 #else
619 #error Graphic type not defined
620 #endif
623 /* Colors */
624 #if (LCD_DEPTH >= 8)
625 #define SLIME_GREEN LCD_RGBPACK(31, 254, 31)
626 #define UFO_RED LCD_RGBPACK(254, 31, 31)
627 #elif (LCD_DEPTH == 2)
628 #define SLIME_GREEN LCD_LIGHTGRAY
629 #define UFO_RED LCD_LIGHTGRAY
630 #else
631 #error LCD type not implemented yet
632 #endif
634 /* Alien states */
635 #define DEAD 0
636 #define ALIVE 1
637 #define BOMBER 2
639 /* Fire/bomb/ufo states */
640 #define S_IDLE 0
641 #define S_ACTIVE 1
642 #define S_SHOWSCORE 2
643 #define S_EXPLODE -9
645 /* Fire/bomb targets */
646 #define TARGET_TOP 0
647 #define TARGET_SHIELD 1
648 #define TARGET_SHIP 2
649 #define TARGET_BOTTOM 3
650 #define TARGET_UFO 4
652 #define HISCOREFILE PLUGIN_GAMES_DATA_DIR "/invadrox.high"
655 /* The time (in ms) for one iteration through the game loop - decrease this
656 * to speed up the game - note that current_tick is (currently) only accurate
657 * to 10ms.
659 #define CYCLETIME 40
662 /* Physical x is at PLAYFIELD_X + LIVES_X + x * ALIEN_SPEED
663 * Physical y is at y * ALIEN_HEIGHT
665 struct alien {
666 int x; /* x-coordinate (0 - 95) */
667 int y; /* y-coordinate (0 - 18) */
668 unsigned char type; /* 0 (Kang), 1 (Kodos), 2 (Serak) */
669 unsigned char state; /* Dead, alive or bomber */
672 /* Aliens box 5 rows * ALIENS aliens in each row */
673 struct alien aliens[5 * ALIENS];
675 #define MAX_BOMBS 4
676 struct bomb {
677 int x, y;
678 unsigned char type;
679 unsigned char frame; /* Current animation frame */
680 unsigned char frames; /* Number of frames in animation */
681 unsigned char target; /* Remember target during explosion frames */
682 int state; /* 0 (IDLE) = inactive, 1 (FIRE) or negative, exploding */
684 struct bomb bombs[MAX_BOMBS];
685 /* Increase max_bombs at higher levels */
686 int max_bombs;
688 /* Raw framebuffer value of shield/ship green color */
689 fb_data screen_green, screen_white;
691 /* For optimization, precalculate startoffset of each scanline */
692 unsigned int ytab[LCD_HEIGHT];
694 int lives = 2;
695 int score = 0;
696 int scores[3] = { 30, 20, 10 };
697 int level = 0;
698 struct highscore hiscore;
699 bool game_over = false;
700 int ship_x, old_ship_x, ship_dir, ship_acc, max_ship_speed;
701 int ship_frame, ship_frame_counter;
702 bool ship_hit;
703 int fire, fire_target, fire_x, fire_y;
704 int curr_alien, aliens_paralyzed, gamespeed;
705 int ufo_state, ufo_x;
706 bool level_finished;
707 bool aliens_down, aliens_right, hit_left_border, hit_right_border;
710 /* No standard get_pixel function yet, use this hack instead */
711 #if (LCD_DEPTH >= 8)
713 #if defined(LCD_STRIDEFORMAT) && LCD_STRIDEFORMAT == VERTICAL_STRIDE
714 static inline fb_data get_pixel(int x, int y)
716 return rb->lcd_framebuffer[x*LCD_HEIGHT+y];
718 #else
719 static inline fb_data get_pixel(int x, int y)
721 return rb->lcd_framebuffer[ytab[y] + x];
723 #endif
725 #elif (LCD_DEPTH == 2)
727 #if (LCD_PIXELFORMAT == HORIZONTAL_PACKING)
728 static const unsigned char shifts[4] = {
729 6, 4, 2, 0
731 /* Horizontal packing */
732 static inline fb_data get_pixel(int x, int y)
734 return (rb->lcd_framebuffer[ytab[y] + (x >> 2)] >> shifts[x & 3]) & 3;
736 #else
737 /* Vertical packing */
738 static const unsigned char shifts[4] = {
739 0, 2, 4, 6
741 static inline fb_data get_pixel(int x, int y)
743 return (rb->lcd_framebuffer[ytab[y] + x] >> shifts[y & 3]) & 3;
745 #endif /* Horizontal/Vertical packing */
747 #else
748 #error get_pixel: pixelformat not implemented yet
749 #endif
752 /* Draw "digits" least significant digits of num at (x,y) */
753 static void draw_number(int x, int y, int num, int digits)
755 int i;
756 int d;
758 for (i = digits - 1; i >= 0; i--) {
759 d = num % 10;
760 num = num / 10;
761 rb->lcd_bitmap_part(invadrox_numbers, d * NUMBERS_WIDTH, 0,
762 STRIDE( SCREEN_MAIN,
763 BMPWIDTH_invadrox_numbers,
764 BMPHEIGHT_invadrox_numbers),
765 x + i * (NUMBERS_WIDTH + NUM_SPACING), y,
766 NUMBERS_WIDTH, FONT_HEIGHT);
768 /* Update lcd */
769 rb->lcd_update_rect(x, y, 4 * NUMBERS_WIDTH + 3 * NUM_SPACING, FONT_HEIGHT);
773 static inline void draw_score(void)
775 draw_number(SCORENUM_X, SCORENUM_Y, score, 4);
776 if (score > hiscore.score) {
777 /* Draw new hiscore (same as score) */
778 draw_number(HISCORENUM_X, SCORENUM_Y, score, 4);
783 static void draw_level(void)
785 draw_number(LEVEL_X + 2 * NUM_SPACING, PLAYFIELD_Y + 2, level, 2);
789 static void draw_lives(void)
791 int i;
792 /* Lives num */
793 rb->lcd_bitmap_part(invadrox_numbers, lives * NUMBERS_WIDTH, 0,
794 STRIDE( SCREEN_MAIN,
795 BMPWIDTH_invadrox_numbers,
796 BMPHEIGHT_invadrox_numbers),
797 PLAYFIELD_X + LIVES_X, PLAYFIELD_Y + 2,
798 NUMBERS_WIDTH, FONT_HEIGHT);
800 /* Ships */
801 for (i = 0; i < (lives - 1); i++) {
802 rb->lcd_bitmap_part(invadrox_ships, 0, 0,
803 STRIDE( SCREEN_MAIN,
804 BMPWIDTH_invadrox_ships,
805 BMPHEIGHT_invadrox_ships),
806 PLAYFIELD_X + LIVES_X + SHIP_WIDTH + i * (SHIP_WIDTH + NUM_SPACING),
807 PLAYFIELD_Y + 1, SHIP_WIDTH, SHIP_HEIGHT);
810 /* Erase ship to the right (if less than MAX_LIVES) */
811 if (lives < MAX_LIVES) {
812 rb->lcd_fillrect(PLAYFIELD_X + LIVES_X + SHIP_WIDTH + i * (SHIP_WIDTH + NUM_SPACING),
813 PLAYFIELD_Y + 1, SHIP_WIDTH, SHIP_HEIGHT);
815 /* Update lives (and level) part of screen */
816 rb->lcd_update_rect(PLAYFIELD_X + LIVES_X, PLAYFIELD_Y + 1,
817 PLAYFIELD_WIDTH - 2 * LIVES_X, MAX(FONT_HEIGHT + 1, SHIP_HEIGHT + 1));
821 static inline void draw_aliens(void)
823 int i;
825 for (i = 0; i < 5 * ALIENS; i++) {
826 rb->lcd_bitmap_part(invadrox_aliens, aliens[i].x & 1 ? ALIEN_WIDTH : 0,
827 aliens[i].type * ALIEN_HEIGHT,
828 STRIDE( SCREEN_MAIN,
829 BMPWIDTH_invadrox_aliens,
830 BMPHEIGHT_invadrox_aliens),
831 PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED,
832 ALIEN_START_Y + aliens[i].y * ALIEN_HEIGHT,
833 ALIEN_WIDTH, ALIEN_HEIGHT);
838 /* Return false if there is no next alive alien (round is over) */
839 static inline bool next_alien(void)
841 bool ret = true;
843 do {
844 curr_alien++;
845 if (curr_alien % ALIENS == 0) {
846 /* End of this row. Move up one row. */
847 curr_alien -= 2 * ALIENS;
848 if (curr_alien < 0) {
849 /* No more aliens in this round. */
850 curr_alien = 4 * ALIENS;
851 ret = false;
854 } while (aliens[curr_alien].state == DEAD && ret);
856 if (!ret) {
857 /* No more alive aliens. Round finished. */
858 if (hit_right_border) {
859 if (hit_left_border) {
860 DBG("ERROR: both left and right borders are set (%d)\n", curr_alien);
862 /* Move down-left next round */
863 aliens_right = false;
864 aliens_down = true;
865 hit_right_border = false;
866 } else if (hit_left_border) {
867 /* Move down-right next round */
868 aliens_right = true;
869 aliens_down = true;
870 hit_left_border = false;
871 } else {
872 /* Not left nor right. Set down to false. */
873 aliens_down = false;
877 return ret;
881 /* All aliens have been moved.
882 * Set curr_alien to first alive.
883 * Return false if no-one is left alive.
885 static bool first_alien(void)
887 int i, y;
889 for (y = 4; y >= 0; y--) {
890 for (i = y * ALIENS; i < (y + 1) * ALIENS; i++) {
891 if (aliens[i].state != DEAD) {
892 curr_alien = i;
893 return true;
898 /* All aliens dead. */
899 level_finished = true;
901 return false;
905 static bool move_aliens(void)
907 int x, y, old_x, old_y;
909 /* Move current alien (curr_alien is pointing to a living alien) */
911 old_x = aliens[curr_alien].x;
912 old_y = aliens[curr_alien].y;
914 if (aliens_down) {
915 aliens[curr_alien].y++;
916 if (aliens[curr_alien].y == MAX_Y) {
917 /* Alien is at bottom. Game Over. */
918 DBG("Alien %d is at bottom. Game Over.\n", curr_alien);
919 game_over = true;
920 return false;
924 if (aliens_right) {
925 /* Moving right */
926 if (aliens[curr_alien].x < MAX_X) {
927 aliens[curr_alien].x++;
930 /* Now, after move, check if we hit the right border. */
931 if (aliens[curr_alien].x == MAX_X) {
932 hit_right_border = true;
935 } else {
936 /* Moving left */
937 if (aliens[curr_alien].x > 0) {
938 aliens[curr_alien].x--;
941 /* Now, after move, check if we hit the left border. */
942 if (aliens[curr_alien].x == 0) {
943 hit_left_border = true;
947 /* Erase old position */
948 x = PLAYFIELD_X + LIVES_X + old_x * ALIEN_SPEED;
949 y = ALIEN_START_Y + old_y * ALIEN_HEIGHT;
950 if (aliens[curr_alien].y != old_y) {
951 /* Moved in y-dir. Erase whole alien. */
952 rb->lcd_fillrect(x, y, ALIEN_WIDTH, ALIEN_HEIGHT);
953 } else {
954 if (aliens_right) {
955 /* Erase left edge */
956 rb->lcd_fillrect(x, y, ALIEN_SPEED, ALIEN_HEIGHT);
957 } else {
958 /* Erase right edge */
959 x += ALIEN_WIDTH - ALIEN_SPEED;
960 rb->lcd_fillrect(x, y, ALIEN_SPEED, ALIEN_HEIGHT);
964 /* Draw alien at new pos */
965 x = PLAYFIELD_X + LIVES_X + aliens[curr_alien].x * ALIEN_SPEED;
966 y = ALIEN_START_Y + aliens[curr_alien].y * ALIEN_HEIGHT;
967 rb->lcd_bitmap_part(invadrox_aliens,
968 aliens[curr_alien].x & 1 ? ALIEN_WIDTH : 0,
969 aliens[curr_alien].type * ALIEN_HEIGHT,
970 STRIDE( SCREEN_MAIN,
971 BMPWIDTH_invadrox_aliens,
972 BMPHEIGHT_invadrox_aliens),
973 x, y, ALIEN_WIDTH, ALIEN_HEIGHT);
975 if (!next_alien()) {
976 /* Round finished. Set curr_alien to first alive from bottom. */
977 if (!first_alien()) {
978 /* Should never happen. Taken care of in move_fire(). */
979 return false;
981 /* TODO: Play next background sound */
984 return true;
988 static inline void draw_ship(void)
990 /* Erase old ship */
991 if (old_ship_x < ship_x) {
992 /* Move right. Erase leftmost part of ship. */
993 rb->lcd_fillrect(old_ship_x, SHIP_Y, ship_x - old_ship_x, SHIP_HEIGHT);
994 } else if (old_ship_x > ship_x) {
995 /* Move left. Erase rightmost part of ship. */
996 rb->lcd_fillrect(ship_x + SHIP_WIDTH, SHIP_Y, old_ship_x - ship_x, SHIP_HEIGHT);
999 /* Draw ship */
1000 rb->lcd_bitmap_part(invadrox_ships, 0, ship_frame * SHIP_HEIGHT,
1001 STRIDE( SCREEN_MAIN,
1002 BMPWIDTH_invadrox_ships,
1003 BMPHEIGHT_invadrox_ships),
1004 ship_x, SHIP_Y, SHIP_WIDTH, SHIP_HEIGHT);
1005 if (ship_hit) {
1006 /* Alternate between frame 1 and 2 during hit */
1007 ship_frame_counter++;
1008 if (ship_frame_counter > 2) {
1009 ship_frame_counter = 0;
1010 ship_frame++;
1011 if (ship_frame > 2) {
1012 ship_frame = 1;
1017 /* Save ship_x for next time */
1018 old_ship_x = ship_x;
1022 static inline void fire_alpha(int xc, int yc, fb_data color)
1024 int oldmode = rb->lcd_get_drawmode();
1026 rb->lcd_set_foreground(color);
1027 rb->lcd_set_drawmode(DRMODE_FG);
1029 rb->lcd_mono_bitmap(invadrox_fire, xc - (FIRE_WIDTH/2), yc, FIRE_WIDTH, FIRE_HEIGHT);
1031 rb->lcd_set_foreground(LCD_BLACK);
1032 rb->lcd_set_drawmode(oldmode);
1036 static void move_fire(void)
1038 bool hit_green = false;
1039 bool hit_white = false;
1040 int i, j;
1041 static int exploding_alien = -1;
1042 fb_data pix;
1044 if (fire == S_IDLE) {
1045 return;
1048 /* Alien hit. Wait until explosion is finished. */
1049 if (aliens_paralyzed < 0) {
1050 aliens_paralyzed++;
1051 if (aliens_paralyzed == 0) {
1052 /* Erase exploding_alien */
1053 rb->lcd_fillrect(PLAYFIELD_X + LIVES_X + aliens[exploding_alien].x * ALIEN_SPEED,
1054 ALIEN_START_Y + aliens[exploding_alien].y * ALIEN_HEIGHT,
1055 ALIEN_EXPLODE_WIDTH, ALIEN_HEIGHT);
1056 fire = S_IDLE;
1057 /* Special case. We killed curr_alien. */
1058 if (exploding_alien == curr_alien) {
1059 if (!next_alien()) {
1060 /* Round finished. Set curr_alien to first alive from bottom. */
1061 first_alien();
1065 return;
1068 if (fire == S_ACTIVE) {
1070 /* Erase */
1071 rb->lcd_vline(fire_x, fire_y, fire_y + SHOT_HEIGHT);
1073 /* Check top */
1074 if (fire_y <= SCORENUM_Y + FONT_HEIGHT + 4) {
1076 /* TODO: Play explode sound */
1078 fire = S_EXPLODE;
1079 fire_target = TARGET_TOP;
1080 fire_alpha(fire_x, fire_y, UFO_RED);
1081 return;
1084 /* Move */
1085 fire_y -= FIRE_SPEED;
1087 /* Hit UFO? */
1088 if (ufo_state == S_ACTIVE) {
1089 if ((ABS(ufo_x + UFO_WIDTH / 2 - fire_x) <= UFO_WIDTH / 2) &&
1090 (fire_y <= UFO_Y + UFO_HEIGHT)) {
1091 ufo_state = S_EXPLODE;
1092 fire = S_EXPLODE;
1093 fire_target = TARGET_UFO;
1094 /* Center explosion */
1095 ufo_x -= (UFO_EXPLODE_WIDTH - UFO_WIDTH) / 2;
1096 rb->lcd_bitmap(invadrox_ufo_explode, ufo_x, UFO_Y - 1,
1097 UFO_EXPLODE_WIDTH, UFO_EXPLODE_HEIGHT);
1098 return;
1102 /* Hit bomb? (check position, not pixel value) */
1103 for (i = 0; i < max_bombs; i++) {
1104 if (bombs[i].state == S_ACTIVE) {
1105 /* Count as hit if within BOMB_WIDTH pixels */
1106 if ((ABS(bombs[i].x - fire_x) < BOMB_WIDTH) &&
1107 (fire_y - bombs[i].y < BOMB_HEIGHT)) {
1108 /* Erase bomb */
1109 rb->lcd_fillrect(bombs[i].x, bombs[i].y, BOMB_WIDTH, BOMB_HEIGHT);
1110 bombs[i].state = S_IDLE;
1111 /* Explode ship fire */
1112 fire = S_EXPLODE;
1113 fire_target = TARGET_SHIELD;
1114 fire_alpha(fire_x, fire_y, LCD_WHITE);
1115 return;
1120 /* Check for hit*/
1121 for (i = FIRE_SPEED; i >= 0; i--) {
1122 pix = get_pixel(fire_x, fire_y + i);
1123 if(pix == screen_white) {
1124 hit_white = true;
1125 fire_y += i;
1126 break;
1128 if(pix == screen_green) {
1129 hit_green = true;
1130 fire_y += i;
1131 break;
1135 if (hit_green) {
1136 /* Hit shield */
1138 /* TODO: Play explode sound */
1140 fire = S_EXPLODE;
1141 fire_target = TARGET_SHIELD;
1142 /* Center explosion around hit pixel */
1143 fire_y -= FIRE_HEIGHT / 2;
1144 fire_alpha(fire_x, fire_y, SLIME_GREEN);
1145 return;
1148 if (hit_white) {
1150 /* Hit alien? */
1151 for (i = 0; i < 5 * ALIENS; i++) {
1152 if (aliens[i].state != DEAD &&
1153 (ABS(fire_x - (PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED +
1154 ALIEN_WIDTH / 2)) <= ALIEN_WIDTH / 2) &&
1155 (ABS(fire_y - (ALIEN_START_Y + aliens[i].y * ALIEN_HEIGHT +
1156 ALIEN_HEIGHT / 2)) <= ALIEN_HEIGHT / 2)) {
1158 /* TODO: play alien hit sound */
1160 if (aliens[i].state == BOMBER) {
1161 /* Set (possible) alien above to bomber */
1162 for (j = i - ALIENS; j >= 0; j -= ALIENS) {
1163 if (aliens[j].state != DEAD) {
1164 /* printf("New bomber (%d, %d)\n", j % ALIENS, j / ALIENS); */
1165 aliens[j].state = BOMBER;
1166 break;
1170 aliens[i].state = DEAD;
1171 exploding_alien = i;
1172 score += scores[aliens[i].type];
1173 draw_score();
1174 /* Update score part of screen */
1175 rb->lcd_update_rect(SCORENUM_X, SCORENUM_Y,
1176 PLAYFIELD_WIDTH - 2 * NUMBERS_WIDTH, FONT_HEIGHT);
1178 /* Paralyze aliens S_EXPLODE frames */
1179 aliens_paralyzed = S_EXPLODE;
1180 rb->lcd_bitmap(invadrox_alien_explode,
1181 PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED,
1182 ALIEN_START_Y + aliens[i].y * ALIEN_HEIGHT,
1183 ALIEN_EXPLODE_WIDTH, ALIEN_EXPLODE_HEIGHT);
1184 /* Since alien is 1 pixel taller than explosion sprite, erase bottom line */
1185 rb->lcd_hline(PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED,
1186 PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED + ALIEN_WIDTH,
1187 ALIEN_START_Y + (aliens[i].y + 1) * ALIEN_HEIGHT - 1);
1188 return;
1193 /* Draw shot */
1194 rb->lcd_set_foreground(LCD_WHITE);
1195 rb->lcd_vline(fire_x, fire_y, fire_y + SHOT_HEIGHT);
1196 rb->lcd_set_foreground(LCD_BLACK);
1197 } else if (fire < S_IDLE) {
1198 /* Count up towards S_IDLE, then erase explosion */
1199 fire++;
1200 if (fire == S_IDLE) {
1201 /* Erase explosion */
1202 if (fire_target == TARGET_TOP) {
1203 rb->lcd_fillrect(fire_x - (FIRE_WIDTH / 2), fire_y, FIRE_WIDTH, FIRE_HEIGHT);
1204 } else if (fire_target == TARGET_SHIELD) {
1205 /* Draw explosion with black pixels */
1206 fire_alpha(fire_x, fire_y, LCD_BLACK);
1213 /* Return a BOMBER alien */
1214 static inline int random_bomber(void)
1216 int i, col;
1218 /* TODO: Weigh higher probability near ship */
1219 col = rb->rand() % ALIENS;
1220 for (i = col + 4 * ALIENS; i >= 0; i -= ALIENS) {
1221 if (aliens[i].state == BOMBER) {
1222 return i;
1226 /* No BOMBER found in this col */
1228 for (i = 0; i < 5 * ALIENS; i++) {
1229 if (aliens[i].state == BOMBER) {
1230 return i;
1234 /* No BOMBER found at all (error?) */
1236 return -1;
1240 static inline void draw_bomb(int i)
1242 rb->lcd_bitmap_part(invadrox_bombs, bombs[i].type * BOMB_WIDTH,
1243 bombs[i].frame * BOMB_HEIGHT,
1244 STRIDE( SCREEN_MAIN,
1245 BMPWIDTH_invadrox_bombs,
1246 BMPHEIGHT_invadrox_bombs),
1247 bombs[i].x, bombs[i].y,
1248 BOMB_WIDTH, BOMB_HEIGHT);
1249 /* Advance frame */
1250 bombs[i].frame++;
1251 if (bombs[i].frame == bombs[i].frames) {
1252 bombs[i].frame = 0;
1257 static void move_bombs(void)
1259 int i, j, bomber;
1260 bool abort;
1262 for (i = 0; i < max_bombs; i++) {
1264 switch (bombs[i].state) {
1266 case S_IDLE:
1267 if (ship_hit) {
1268 continue;
1270 bomber = random_bomber();
1271 if (bomber < 0) {
1272 DBG("ERROR: No bomber available\n");
1273 continue;
1275 /* x, y */
1276 bombs[i].x = PLAYFIELD_X + LIVES_X + aliens[bomber].x * ALIEN_SPEED + ALIEN_WIDTH / 2;
1277 bombs[i].y = ALIEN_START_Y + (aliens[bomber].y + 1) * ALIEN_HEIGHT;
1279 /* Check for duplets in x and y direction */
1280 abort = false;
1281 for (j = i - 1; j >= 0; j--) {
1282 if ((bombs[j].state == S_ACTIVE) &&
1283 ((bombs[i].x == bombs[j].x) || (bombs[i].y == bombs[j].y))) {
1284 abort = true;
1285 break;
1288 if (abort) {
1289 /* Skip this one, continue with next bomb */
1290 /* printf("Bomb %d duplet of %d\n", i, j); */
1291 continue;
1294 /* Passed, set type */
1295 bombs[i].type = rb->rand() % 3;
1296 bombs[i].frame = 0;
1297 if (bombs[i].type == 0) {
1298 bombs[i].frames = 3;
1299 } else if (bombs[i].type == 1) {
1300 bombs[i].frames = 4;
1301 } else {
1302 bombs[i].frames = 6;
1305 /* Bombs away */
1306 bombs[i].state = S_ACTIVE;
1307 draw_bomb(i);
1308 continue;
1310 break;
1312 case S_ACTIVE:
1313 /* Erase old position */
1314 rb->lcd_fillrect(bombs[i].x, bombs[i].y, BOMB_WIDTH, BOMB_HEIGHT);
1316 /* Move */
1317 bombs[i].y += BOMB_SPEED;
1319 /* Check if bottom hit */
1320 if (bombs[i].y + BOMB_HEIGHT >= PLAYFIELD_Y) {
1321 bombs[i].y = PLAYFIELD_Y - FIRE_HEIGHT + 1;
1322 fire_alpha(bombs[i].x, bombs[i].y, LCD_WHITE);
1323 bombs[i].state = S_EXPLODE;
1324 bombs[i].target = TARGET_BOTTOM;
1325 break;
1328 /* Check for green (ship or shield) */
1329 for (j = BOMB_HEIGHT; j >= BOMB_HEIGHT - BOMB_SPEED; j--) {
1330 bombs[i].target = 0;
1331 if(get_pixel(bombs[i].x + BOMB_WIDTH / 2, bombs[i].y + j) == screen_green) {
1332 /* Move to hit pixel */
1333 bombs[i].x += BOMB_WIDTH / 2;
1334 bombs[i].y += j;
1336 /* Check if ship is hit */
1337 if (bombs[i].y > SHIELD_Y + SHIELD_HEIGHT && bombs[i].y < PLAYFIELD_Y) {
1339 /* TODO: play ship hit sound */
1341 ship_hit = true;
1342 ship_frame = 1;
1343 ship_frame_counter = 0;
1344 bombs[i].state = S_EXPLODE * 4;
1345 bombs[i].target = TARGET_SHIP;
1346 rb->lcd_bitmap_part(invadrox_ships, 0, 1 * SHIP_HEIGHT,
1347 STRIDE( SCREEN_MAIN,
1348 BMPWIDTH_invadrox_ships,
1349 BMPHEIGHT_invadrox_ships),
1350 ship_x, SHIP_Y,
1351 SHIP_WIDTH, SHIP_HEIGHT);
1352 break;
1354 /* Shield hit */
1355 bombs[i].state = S_EXPLODE;
1356 bombs[i].target = TARGET_SHIELD;
1357 /* Center explosion around hit pixel in shield */
1358 bombs[i].y -= FIRE_HEIGHT / 2;
1359 fire_alpha(bombs[i].x, bombs[i].y, SLIME_GREEN);
1360 break;
1364 if (bombs[i].target != 0) {
1365 /* Hit ship or shield, continue */
1366 continue;
1369 draw_bomb(i);
1370 break;
1372 default:
1373 /* If we get here state should be < 0, exploding */
1374 bombs[i].state++;
1375 if (bombs[i].state == S_IDLE) {
1376 if (ship_hit) {
1377 /* Erase explosion */
1378 rb->lcd_fillrect(ship_x, SHIP_Y, SHIP_WIDTH, SHIP_HEIGHT);
1379 rb->lcd_update_rect(ship_x, SHIP_Y, SHIP_WIDTH, SHIP_HEIGHT);
1380 ship_hit = false;
1381 ship_frame = 0;
1382 ship_x = PLAYFIELD_X + 2 * LIVES_X;
1383 lives--;
1384 if (lives == 0) {
1385 game_over = true;
1386 return;
1388 draw_lives();
1389 /* Sleep 1s to give player time to examine lives left */
1390 rb->sleep(HZ);
1392 /* Erase explosion (even if ship hit, might be another bomb) */
1393 fire_alpha(bombs[i].x, bombs[i].y, LCD_BLACK);
1395 break;
1401 static inline void move_ship(void)
1403 ship_dir += ship_acc;
1404 if (ship_dir > max_ship_speed) {
1405 ship_dir = max_ship_speed;
1407 if (ship_dir < -max_ship_speed) {
1408 ship_dir = -max_ship_speed;
1410 ship_x += ship_dir;
1411 if (ship_x < SHIP_MIN_X) {
1412 ship_x = SHIP_MIN_X;
1414 if (ship_x > SHIP_MAX_X) {
1415 ship_x = SHIP_MAX_X;
1418 draw_ship();
1422 /* Unidentified Flying Object */
1423 static void move_ufo(void)
1425 static int ufo_speed;
1426 static int counter;
1427 int mystery_score;
1429 switch (ufo_state) {
1431 case S_IDLE:
1433 if (rb->rand() % 500 == 0) {
1434 /* Uh-oh, it's time to launch a mystery UFO */
1436 /* TODO: Play UFO sound */
1438 if (rb->rand() % 2) {
1439 ufo_speed = UFO_SPEED;
1440 ufo_x = PLAYFIELD_X;
1441 } else {
1442 ufo_speed = -UFO_SPEED;
1443 ufo_x = LCD_WIDTH - PLAYFIELD_X - UFO_WIDTH;
1445 ufo_state = S_ACTIVE;
1446 /* UFO will be drawn next frame */
1448 break;
1450 case S_ACTIVE:
1451 /* Erase old pos */
1452 rb->lcd_fillrect(ufo_x, UFO_Y, UFO_WIDTH, UFO_HEIGHT);
1453 /* Move */
1454 ufo_x += ufo_speed;
1455 /* Check bounds */
1456 if (ufo_x < PLAYFIELD_X || ufo_x > LCD_WIDTH - PLAYFIELD_X - UFO_WIDTH) {
1457 ufo_state = S_IDLE;
1458 break;
1460 /* Draw new pos */
1461 rb->lcd_bitmap(invadrox_ufo, ufo_x, UFO_Y, UFO_WIDTH, UFO_HEIGHT);
1462 break;
1464 case S_SHOWSCORE:
1465 counter++;
1466 if (counter == S_IDLE) {
1467 /* Erase mystery number */
1468 rb->lcd_fillrect(ufo_x, UFO_Y, 3 * NUMBERS_WIDTH + 2 * NUM_SPACING, FONT_HEIGHT);
1469 ufo_state = S_IDLE;
1471 break;
1473 default:
1474 /* Exploding */
1475 ufo_state++;
1476 if (ufo_state == S_IDLE) {
1477 /* Erase explosion */
1478 rb->lcd_fillrect(ufo_x, UFO_Y - 1, UFO_EXPLODE_WIDTH, UFO_EXPLODE_HEIGHT);
1479 ufo_state = S_SHOWSCORE;
1480 counter = S_EXPLODE * 4;
1481 /* Draw mystery_score, sleep, increase score and continue */
1482 mystery_score = 50 + (rb->rand() % 6) * 50;
1483 if (mystery_score < 100) {
1484 draw_number(ufo_x, UFO_Y, mystery_score, 2);
1485 } else {
1486 draw_number(ufo_x, UFO_Y, mystery_score, 3);
1488 score += mystery_score;
1489 draw_score();
1491 break;
1496 static void draw_background(void)
1499 rb->lcd_bitmap(invadrox_background, 0, 0, LCD_WIDTH, LCD_HEIGHT);
1500 rb->lcd_update();
1504 static void new_level(void)
1506 int i;
1508 draw_background();
1509 /* Give an extra life for each new level */
1510 if (lives < MAX_LIVES) {
1511 lives++;
1513 draw_lives();
1515 /* Score */
1516 draw_score();
1517 draw_number(HISCORENUM_X, SCORENUM_Y, hiscore.score, 4);
1519 level++;
1520 draw_level();
1521 level_finished = false;
1523 ufo_state = S_IDLE;
1525 /* Init alien positions and states */
1526 for (i = 0; i < 4 * ALIENS; i++) {
1527 aliens[i].x = 0 + (i % ALIENS) * ((ALIEN_WIDTH + ALIEN_SPACING) / ALIEN_SPEED);
1528 aliens[i].y = 2 * (i / ALIENS);
1529 aliens[i].state = ALIVE;
1531 /* Last row, bombers */
1532 for (i = 4 * ALIENS; i < 5 * ALIENS; i++) {
1533 aliens[i].x = 0 + (i % ALIENS) * ((ALIEN_WIDTH + ALIEN_SPACING) / ALIEN_SPEED);
1534 aliens[i].y = 2 * (i / ALIENS);
1535 aliens[i].state = BOMBER;
1538 /* Init bombs to inactive (S_IDLE) */
1539 for (i = 0; i < MAX_BOMBS; i++) {
1540 bombs[i].state = S_IDLE;
1543 /* Start aliens closer to earth from level 2 */
1544 for (i = 0; i < 5 * ALIENS; i++) {
1545 if (level < 6) {
1546 aliens[i].y += level - 1;
1547 } else {
1548 aliens[i].y += 5;
1552 /* Max concurrent bombs */
1553 max_bombs = 1;
1555 gamespeed = 2;
1557 if (level > 1) {
1558 max_bombs++;
1561 /* Increase speed */
1562 if (level > 2) {
1563 gamespeed++;
1566 if (level > 3) {
1567 max_bombs++;
1570 /* Increase speed more */
1571 if (level > 4) {
1572 gamespeed++;
1575 if (level > 5) {
1576 max_bombs++;
1579 /* 4 shields */
1580 for (i = 1; i <= 4; i++) {
1581 rb->lcd_bitmap(invadrox_shield,
1582 PLAYFIELD_X + i * PLAYFIELD_WIDTH / 5 - SHIELD_WIDTH / 2,
1583 SHIELD_Y, SHIELD_WIDTH, SHIELD_HEIGHT);
1586 /* Bottom line */
1587 rb->lcd_set_foreground(SLIME_GREEN);
1588 rb->lcd_hline(PLAYFIELD_X, LCD_WIDTH - PLAYFIELD_X, PLAYFIELD_Y);
1589 /* Restore foreground to black (for fast erase later). */
1590 rb->lcd_set_foreground(LCD_BLACK);
1592 ship_x = PLAYFIELD_X + 2 * LIVES_X;
1593 if (level == 1) {
1594 old_ship_x = ship_x;
1596 ship_dir = 0;
1597 ship_acc = 0;
1598 ship_frame = 0;
1599 ship_hit = false;
1600 fire = S_IDLE;
1601 /* Start moving the bottom row left to right */
1602 curr_alien = 4 * ALIENS;
1603 aliens_paralyzed = 0;
1604 aliens_right = true;
1605 aliens_down = false;
1606 hit_left_border = false;
1607 hit_right_border = false;
1608 /* TODO: Change max_ship_speed to 3 at higher levels */
1609 max_ship_speed = 2;
1611 draw_aliens();
1613 rb->lcd_update();
1617 static void init_invadrox(void)
1619 int i;
1621 /* Seed random number generator with a "random" number */
1622 rb->srand(rb->get_time()->tm_sec + rb->get_time()->tm_min * 60);
1624 /* Precalculate start of each scanline */
1625 for (i = 0; i < LCD_HEIGHT; i++) {
1626 #if (LCD_DEPTH >= 8)
1627 ytab[i] = i * LCD_WIDTH;
1628 #elif (LCD_DEPTH == 2) && (LCD_PIXELFORMAT == HORIZONTAL_PACKING)
1629 ytab[i] = i * (LCD_WIDTH / 4);
1630 #elif (LCD_DEPTH == 2) && (LCD_PIXELFORMAT == VERTICAL_PACKING)
1631 ytab[i] = (i / 4) * LCD_WIDTH;
1632 #else
1633 #error pixelformat not implemented yet
1634 #endif
1637 rb->lcd_set_background(LCD_BLACK);
1638 rb->lcd_set_foreground(LCD_BLACK);
1640 if (highscore_load(HISCOREFILE, &hiscore, 1) < 0) {
1641 /* Init hiscore to 0 */
1642 rb->strlcpy(hiscore.name, "Invader", sizeof(hiscore.name));
1643 hiscore.score = 0;
1644 hiscore.level = 1;
1647 /* Init alien types in aliens array */
1648 for (i = 0; i < 1 * ALIENS; i++) {
1649 aliens[i].type = 0; /* Kang */
1651 for (; i < 3 * ALIENS; i++) {
1652 aliens[i].type = 1; /* Kodos */
1654 for (; i < 5 * ALIENS; i++) {
1655 aliens[i].type = 2; /* Serak */
1659 /* Save screen white color */
1660 rb->lcd_set_foreground(LCD_WHITE);
1661 rb->lcd_drawpixel(0, 0);
1662 rb->lcd_update_rect(0, 0, 1, 1);
1663 screen_white = get_pixel(0, 0);
1665 /* Save screen green color */
1666 rb->lcd_set_foreground(SLIME_GREEN);
1667 rb->lcd_drawpixel(0, 0);
1668 rb->lcd_update_rect(0, 0, 1, 1);
1669 screen_green = get_pixel(0, 0);
1671 /* Restore black foreground */
1672 rb->lcd_set_foreground(LCD_BLACK);
1674 new_level();
1676 /* Flash score at start */
1677 for (i = 0; i < 5; i++) {
1678 rb->lcd_fillrect(SCORENUM_X, SCORENUM_Y,
1679 4 * NUMBERS_WIDTH + 3 * NUM_SPACING,
1680 FONT_HEIGHT);
1681 rb->lcd_update_rect(SCORENUM_X, SCORENUM_Y,
1682 4 * NUMBERS_WIDTH + 3 * NUM_SPACING,
1683 FONT_HEIGHT);
1684 rb->sleep(HZ / 10);
1685 draw_number(SCORENUM_X, SCORENUM_Y, score, 4);
1686 rb->sleep(HZ / 10);
1691 static inline bool handle_buttons(void)
1693 static unsigned int oldbuttonstate = 0;
1695 unsigned int released, pressed, newbuttonstate;
1697 if (ship_hit) {
1698 /* Don't allow ship movement during explosion */
1699 newbuttonstate = 0;
1700 } else {
1701 newbuttonstate = rb->button_status();
1703 if(newbuttonstate == oldbuttonstate) {
1704 if (newbuttonstate == 0) {
1705 /* No button pressed. Stop ship. */
1706 ship_acc = 0;
1707 if (ship_dir > 0) {
1708 ship_dir--;
1710 if (ship_dir < 0) {
1711 ship_dir++;
1714 /* return false; */
1715 goto check_usb;
1717 released = ~newbuttonstate & oldbuttonstate;
1718 pressed = newbuttonstate & ~oldbuttonstate;
1719 oldbuttonstate = newbuttonstate;
1720 if (pressed) {
1721 if (pressed & ACTION_LEFT) {
1722 if (ship_acc > -1) {
1723 ship_acc--;
1726 if (pressed & ACTION_RIGHT) {
1727 if (ship_acc < 1) {
1728 ship_acc++;
1731 if (pressed & ACTION_FIRE) {
1732 if (fire == S_IDLE) {
1733 /* Fire shot */
1734 fire_x = ship_x + SHIP_WIDTH / 2;
1735 fire_y = SHIP_Y - SHOT_HEIGHT;
1736 fire = S_ACTIVE;
1737 /* TODO: play fire sound */
1740 if (pressed & ACTION_QUIT) {
1741 rb->splash(HZ * 1, "Quit");
1742 return true;
1745 if (released) {
1746 if ((released & ACTION_LEFT)) {
1747 if (ship_acc < 1) {
1748 ship_acc++;
1751 if ((released & ACTION_RIGHT)) {
1752 if (ship_acc > -1) {
1753 ship_acc--;
1758 check_usb:
1760 /* Quit if USB is connected */
1761 if (rb->button_get(false) == SYS_USB_CONNECTED) {
1762 return true;
1765 return false;
1769 static void game_loop(void)
1771 int i, end;
1773 /* Print dimensions (just for debugging) */
1774 DBG("%03dx%03dx%02d\n", LCD_WIDTH, LCD_HEIGHT, LCD_DEPTH);
1776 /* Init */
1777 init_invadrox();
1779 while (1) {
1780 /* Convert CYCLETIME (in ms) to HZ */
1781 end = *rb->current_tick + (CYCLETIME * HZ) / 1000;
1783 if (handle_buttons()) {
1784 return;
1787 /* Animate */
1788 move_ship();
1789 move_fire();
1791 /* Check if level is finished (marked by move_fire) */
1792 if (level_finished) {
1793 /* TODO: Play level finished sound */
1794 new_level();
1797 move_ufo();
1799 /* Move aliens */
1800 if (!aliens_paralyzed && !ship_hit) {
1801 for (i = 0; i < gamespeed; i++) {
1802 if (!move_aliens()) {
1803 if (game_over) {
1804 return;
1810 /* Move alien bombs */
1811 move_bombs();
1812 if (game_over) {
1813 return;
1816 /* Update "playfield" rect */
1817 rb->lcd_update_rect(PLAYFIELD_X, SCORENUM_Y + FONT_HEIGHT,
1818 PLAYFIELD_WIDTH,
1819 PLAYFIELD_Y + 1 - SCORENUM_Y - FONT_HEIGHT);
1821 /* Wait until next frame */
1822 DBG("%ld (%d)\n", end - *rb->current_tick, (CYCLETIME * HZ) / 1000);
1823 if (TIME_BEFORE(*rb->current_tick, end)) {
1824 rb->sleep(end - *rb->current_tick);
1825 } else {
1826 rb->yield();
1829 } /* end while */
1833 /* this is the plugin entry point */
1834 enum plugin_status plugin_start(UNUSED const void* parameter)
1836 rb->lcd_setfont(FONT_SYSFIXED);
1837 /* Turn off backlight timeout */
1838 backlight_ignore_timeout();
1840 /* now go ahead and have fun! */
1841 game_loop();
1843 /* Game Over. */
1844 /* TODO: Play game over sound */
1845 rb->splash(HZ * 2, "Game Over");
1846 if (score > hiscore.score) {
1847 /* Save new hiscore */
1848 highscore_update(score, level, "Invader", &hiscore, 1);
1849 highscore_save(HISCOREFILE, &hiscore, 1);
1852 /* Restore user's original backlight setting */
1853 rb->lcd_setfont(FONT_UI);
1854 /* Turn on backlight timeout (revert to settings) */
1855 backlight_use_settings();
1857 return PLUGIN_OK;
1863 * GNU Emacs settings: Kernighan & Richie coding style with
1864 * 4 spaces indent and no tabs.
1865 * Local Variables:
1866 * c-file-style: "k&r"
1867 * c-basic-offset: 4
1868 * indent-tabs-mode: nil
1869 * End: