Fix yellow.
[kugel-rb.git] / apps / plugins / invadrox.c
blob1bbca81a77a4c5dabfaf73486ab51e4735299f73
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
127 #define QUIT BUTTON_POWER
128 #define LEFT BUTTON_LEFT
129 #define RIGHT BUTTON_RIGHT
130 #define FIRE BUTTON_SELECT
132 #elif CONFIG_KEYPAD == SANSA_E200_PAD
134 #define QUIT BUTTON_POWER
135 #define LEFT BUTTON_LEFT
136 #define RIGHT BUTTON_RIGHT
137 #define FIRE BUTTON_SELECT
139 #elif CONFIG_KEYPAD == SANSA_FUZE_PAD
141 #define QUIT (BUTTON_HOME|BUTTON_REPEAT)
142 #define LEFT BUTTON_LEFT
143 #define RIGHT BUTTON_RIGHT
144 #define FIRE BUTTON_SELECT
146 #elif CONFIG_KEYPAD == TATUNG_TPJ1022_PAD
148 /* TODO: Figure out which buttons to use for Tatung Elio TPJ-1022 */
149 #define QUIT BUTTON_AB
150 #define LEFT BUTTON_LEFT
151 #define RIGHT BUTTON_RIGHT
152 #define FIRE BUTTON_MENU
154 #elif CONFIG_KEYPAD == GIGABEAT_S_PAD
156 #define QUIT BUTTON_BACK
157 #define LEFT BUTTON_LEFT
158 #define RIGHT BUTTON_RIGHT
159 #define FIRE BUTTON_SELECT
161 #elif CONFIG_KEYPAD == COWON_D2_PAD
163 #define QUIT BUTTON_POWER
164 #define LEFT BUTTON_MINUS
165 #define RIGHT BUTTON_PLUS
166 #define FIRE BUTTON_MENU
168 #elif CONFIG_KEYPAD == IAUDIO67_PAD
170 #define QUIT BUTTON_POWER
171 #define LEFT BUTTON_LEFT
172 #define RIGHT BUTTON_RIGHT
173 #define FIRE BUTTON_PLAY
175 #elif CONFIG_KEYPAD == CREATIVEZVM_PAD
177 #define QUIT BUTTON_BACK
178 #define LEFT BUTTON_LEFT
179 #define RIGHT BUTTON_RIGHT
180 #define FIRE BUTTON_SELECT
182 #elif CONFIG_KEYPAD == PHILIPS_HDD6330_PAD
184 #define QUIT BUTTON_POWER
185 #define LEFT BUTTON_LEFT
186 #define RIGHT BUTTON_RIGHT
187 #define FIRE BUTTON_PLAY
189 #elif CONFIG_KEYPAD == PHILIPS_SA9200_PAD
191 #define QUIT BUTTON_POWER
192 #define LEFT BUTTON_PREV
193 #define RIGHT BUTTON_NEXT
194 #define FIRE BUTTON_PLAY
196 #elif CONFIG_KEYPAD == ONDAVX747_PAD || \
197 CONFIG_KEYPAD == ONDAVX777_PAD || \
198 CONFIG_KEYPAD == MROBE500_PAD
200 #define QUIT BUTTON_POWER
202 #elif CONFIG_KEYPAD == SAMSUNG_YH_PAD
204 #define QUIT BUTTON_REC
205 #define LEFT BUTTON_LEFT
206 #define RIGHT BUTTON_RIGHT
207 #define FIRE BUTTON_PLAY
209 #elif CONFIG_KEYPAD == PBELL_VIBE500_PAD
211 #define QUIT BUTTON_REC
212 #define LEFT BUTTON_PREV
213 #define RIGHT BUTTON_NEXT
214 #define FIRE BUTTON_OK
216 #elif CONFIG_KEYPAD == MPIO_HD300_PAD
218 #define QUIT BUTTON_REC
219 #define LEFT BUTTON_MENU
220 #define RIGHT BUTTON_ENTER
221 #define FIRE BUTTON_PLAY
223 #else
224 #error INVADROX: Unsupported keypad
225 #endif
227 #ifndef RC_QUIT
228 #define RC_QUIT 0
229 #endif
231 #ifdef HAVE_TOUCHSCREEN
233 #ifndef QUIT
234 #define QUIT 0
235 #endif
236 #ifndef LEFT
237 #define LEFT 0
238 #endif
239 #ifndef RIGHT
240 #define RIGHT 0
241 #endif
242 #ifndef FIRE
243 #define FIRE 0
244 #endif
246 #define TOUCHSCREEN_QUIT BUTTON_TOPLEFT
247 #define TOUCHSCREEN_LEFT (BUTTON_MIDLEFT | BUTTON_BOTTOMLEFT)
248 #define TOUCHSCREEN_RIGHT (BUTTON_MIDRIGHT | BUTTON_BOTTOMRIGHT)
249 #define TOUCHSCREEN_FIRE (BUTTON_CENTER | BUTTON_BOTTOMMIDDLE)
251 #define ACTION_QUIT (QUIT | TOUCHSCREEN_QUIT | RC_QUIT)
252 #define ACTION_LEFT (LEFT | TOUCHSCREEN_LEFT)
253 #define ACTION_RIGHT (RIGHT | TOUCHSCREEN_RIGHT)
254 #define ACTION_FIRE (FIRE | TOUCHSCREEN_FIRE)
256 #else /* HAVE_TOUCHSCREEN */
258 #define ACTION_QUIT (QUIT | RC_QUIT)
259 #define ACTION_LEFT LEFT
260 #define ACTION_RIGHT RIGHT
261 #define ACTION_FIRE FIRE
263 #endif
265 #ifndef UNUSED
266 #define UNUSED __attribute__ ((unused))
267 #endif
269 /* Defines common to all models */
270 #define UFO_Y (SCORENUM_Y + FONT_HEIGHT + ALIEN_HEIGHT)
271 #define PLAYFIELD_Y (LCD_HEIGHT - SHIP_HEIGHT - 2)
272 #define PLAYFIELD_WIDTH (LCD_WIDTH - 2 * PLAYFIELD_X)
273 #define LEVEL_X (LCD_WIDTH - PLAYFIELD_X - LIVES_X - 2 * NUMBERS_WIDTH - 3 * NUM_SPACING)
274 #define SHIP_MIN_X (PLAYFIELD_X + PLAYFIELD_WIDTH / 5 - SHIELD_WIDTH / 2 - SHIP_WIDTH)
275 #define SHIP_MAX_X (PLAYFIELD_X + 4 * PLAYFIELD_WIDTH / 5 + SHIELD_WIDTH / 2)
276 /* SCORE_Y = 0 for most targets. Gigabeat redefines it later. */
277 #define SCORE_Y 0
278 #define MAX_LIVES 8
281 /* m:robe 500 defines */
282 #if ((LCD_WIDTH == 640) && (LCD_HEIGHT == 480)) || \
283 ((LCD_WIDTH == 480) && (LCD_HEIGHT == 640))
285 /* Original arcade game size 224x240, 1bpp with
286 * red overlay at top and green overlay at bottom.
288 * M:Robe 500: 640x480x16
289 * ======================
292 #define ARCADISH_GRAPHICS
293 #define PLAYFIELD_X 48
294 #define SHIP_Y (PLAYFIELD_Y - 2 * SHIP_HEIGHT)
295 #define ALIEN_START_Y (UFO_Y + ALIEN_HEIGHT)
296 #define SCORENUM_X (PLAYFIELD_X + NUMBERS_WIDTH)
297 #define SCORENUM_Y (SCORE_Y + FONT_HEIGHT + 2)
298 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 1 - 6 * NUMBERS_WIDTH - 5 * NUM_SPACING)
299 #define SHIELD_Y (PLAYFIELD_Y - 5 * SHIP_HEIGHT)
300 #define LIVES_X 10
301 #define MAX_Y 18
303 /* iPod Video defines */
304 #elif (LCD_WIDTH == 320) && (LCD_HEIGHT == 240)
306 /* Original arcade game size 224x240, 1bpp with
307 * red overlay at top and green overlay at bottom.
309 * iPod Video: 320x240x16
310 * ======================
311 * X: 48p padding at left/right gives 224p playfield in middle.
312 * 10p "border" gives 204p actual playfield. UFO use full 224p.
313 * Y: Use full 240p.
315 * MAX_X = (204 - 12) / 2 - 1 = 95
317 * Y: Score text 7 0
318 * Space 10 7
319 * Score 7 17
320 * Space 8 24
321 * 3 Ufo 7 32
322 * 2 Space Aliens start at 32 + 3 * 8 = 56
323 * 0 aliens 9*8 56 -
324 * space ~7*8 128 | 18.75 aliens space between
325 * shield 2*8 182 | first alien and ship.
326 * space 8 198 | MAX_Y = 18
327 * ship 8 206 -
328 * space 2*8 214
329 * hline 1 230 - PLAYFIELD_Y
330 * bottom border 10 240
331 * Lives and Level goes inside bottom border
334 #define ARCADISH_GRAPHICS
335 #define PLAYFIELD_X 48
336 #define SHIP_Y (PLAYFIELD_Y - 3 * SHIP_HEIGHT)
337 #define ALIEN_START_Y (UFO_Y + 3 * ALIEN_HEIGHT)
338 #define SCORENUM_X (PLAYFIELD_X + NUMBERS_WIDTH)
339 #define SCORENUM_Y SCORE_Y + (2 * (FONT_HEIGHT + 1) + 1)
340 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 1 - 6 * NUMBERS_WIDTH - 5 * NUM_SPACING)
341 #define SHIELD_Y (PLAYFIELD_Y - 6 * SHIP_HEIGHT)
342 #define LIVES_X 10
343 #define MAX_Y 18
345 #elif (LCD_WIDTH == 176) && (LCD_HEIGHT == 220)
347 /* Sandisk Sansa e200: 176x220x16
348 * ==============================
349 * X: No padding. 8p border -> 160p playfield.
351 * 8p Aliens with 3p spacing -> 88 + 30 = 118p aliens block.
352 * (160 - 118) / 2 = 21 rounds for whole block (more than original)
353 * MAX_X = (160 - 8) / 2 - 1 = 75 rounds for single alien (less than original)
355 * LOGO 70 0
356 * Score text 5 70
357 * Space 5 75
358 * Y Score 5 80
359 * Space 10 85
360 * 2 Ufo 5 95
361 * 2 Space 10 100
362 * 0 aliens 9*5 110 -
363 * space ~7*5 155 | 18.6 aliens space between
364 * shield 2*5 188 | first alien and ship.
365 * space 5 198 | MAX_Y = 18
366 * ship 5 203 -
367 * space 5 208
368 * hline 1 213 PLAYFIELD_Y
369 * bottom border 6
370 * LCD_HEIGHT 220
371 * Lives and Level goes inside bottom border
374 #define SMALL_GRAPHICS
375 #define PLAYFIELD_X 0
376 #define SHIP_Y (PLAYFIELD_Y - 2 * SHIP_HEIGHT)
377 #define SHIELD_Y (SHIP_Y - SHIP_HEIGHT - SHIELD_HEIGHT)
378 #define ALIEN_START_Y (UFO_Y + 3 * SHIP_HEIGHT)
379 /* Redefine SCORE_Y */
380 #undef SCORE_Y
381 #define SCORE_Y 70
382 #define SCORENUM_X (PLAYFIELD_X + NUMBERS_WIDTH)
383 #define SCORENUM_Y (SCORE_Y + 2 * FONT_HEIGHT)
384 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 1 - 6 * NUMBERS_WIDTH - 5 * NUM_SPACING)
385 #define LIVES_X 8
386 #define MAX_Y 18
389 #elif (LCD_WIDTH == 176) && (LCD_HEIGHT == 132)
391 /* iPod Nano: 176x132x16
392 * ======================
393 * X: No padding. 8p border -> 160p playfield.
395 * LIVES_X 8
396 * ALIEN_WIDTH 8
397 * ALIEN_HEIGHT 5
398 * ALIEN_SPACING 3
399 * SHIP_WIDTH 10
400 * SHIP_HEIGHT 5
401 * FONT_HEIGHT 5
402 * UFO_WIDTH 10
403 * UFO_HEIGHT 5
404 * SHIELD_WIDTH 15
405 * SHIELD_HEIGHT 10
406 * MAX_X 75
407 * MAX_Y = 18
408 * ALIEN_START_Y (UFO_Y + 12)
410 * 8p Aliens with 3p spacing -> 88 + 30 = 118p aliens block.
411 * (160 - 118) / 2 = 21 rounds for whole block (more than original)
412 * MAX_X = (160 - 8) / 2 - 1 = 75 rounds for single alien (less than original)
414 * Y: Scoreline 5 0 (combine scoretext and numbers on same line)
415 * Space 5 5
416 * 1 Ufo 5 10
417 * 3 Space 7 15
418 * 2 aliens 9*5 22 -
419 * space ~7*5 67 | Just above 18 aliens space between
420 * shield 2*5 100 | first alien and ship.
421 * space 5 110 | MAX_Y = 18
422 * ship 5 115 -
423 * space 5 120
424 * hline 1 125 PLAYFIELD_Y
425 * bottom border 6 126
426 * LCD_HEIGHT 131
427 * Lives and Level goes inside bottom border
430 #define SMALL_GRAPHICS
431 #define PLAYFIELD_X 0
432 #define SHIP_Y (PLAYFIELD_Y - 2 * SHIP_HEIGHT)
433 #define ALIEN_START_Y (UFO_Y + 12)
434 #define SCORENUM_X (PLAYFIELD_X + 6 * NUMBERS_WIDTH + 5 * NUM_SPACING)
435 #define SCORENUM_Y SCORE_Y
436 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 4 * NUMBERS_WIDTH - 3 * NUM_SPACING)
437 #define SHIELD_Y (SHIP_Y - SHIP_HEIGHT - SHIELD_HEIGHT)
438 #define LIVES_X 8
439 #define MAX_Y 18
441 #elif (LCD_WIDTH == 160) && (LCD_HEIGHT == 128)
443 /* iAudio X5, iRiver H10 20Gb, iPod 3g/4g, H100, M5: 160x128
444 * =========================================================
445 * X: No padding. No border -> 160p playfield.
447 * LIVES_X 0
448 * ALIEN_WIDTH 8
449 * ALIEN_HEIGHT 5
450 * ALIEN_SPACING 3
451 * SHIP_WIDTH 10
452 * SHIP_HEIGHT 5
453 * FONT_HEIGHT 5
454 * UFO_WIDTH 10
455 * UFO_HEIGHT 5
456 * SHIELD_WIDTH 15
457 * SHIELD_HEIGHT 10
458 * MAX_X 75
459 * MAX_Y = 18
460 * ALIEN_START_Y (UFO_Y + 10)
462 * 8p Aliens with 3p spacing -> 88 + 30 = 118p aliens block.
463 * (160 - 118) / 2 = 21 rounds for whole block (more than original)
464 * MAX_X = (160 - 8) / 2 - 1 = 75 rounds for single alien (less than original)
466 * Y: Scoreline 5 0 (combine scoretext and numbers on same line)
467 * Space 5 5
468 * 1 Ufo 5 10
469 * 2 Space 5 15
470 * 8 aliens 9*5 20 -
471 * space ~6*5 65 | Just above 18 aliens space between
472 * shield 2*5 96 | first alien and ship.
473 * space 5 106 | MAX_Y = 18
474 * ship 5 111 -
475 * space 5 116
476 * hline 1 121 PLAYFIELD_Y
477 * bottom border 6 122
478 * LCD_HEIGHT 128
479 * Lives and Level goes inside bottom border
482 #define SMALL_GRAPHICS
483 #define PLAYFIELD_X 0
484 #define SHIP_Y (PLAYFIELD_Y - 2 * SHIP_HEIGHT)
485 #define ALIEN_START_Y (UFO_Y + 10)
486 #define SCORENUM_X (PLAYFIELD_X + 6 * NUMBERS_WIDTH + 5 * NUM_SPACING)
487 #define SCORENUM_Y SCORE_Y
488 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 4 * NUMBERS_WIDTH - 3 * NUM_SPACING)
489 #define SHIELD_Y (SHIP_Y - SHIP_HEIGHT - SHIELD_HEIGHT)
490 #define LIVES_X 0
491 #define MAX_Y 18
494 #elif (LCD_WIDTH == 240) && ((LCD_HEIGHT == 320) || (LCD_HEIGHT == 400))
496 /* Gigabeat: 240x320x16
497 * ======================
498 * X: 8p padding at left/right gives 224p playfield in middle.
499 * 10p "border" gives 204p actual playfield. UFO use full 224p.
500 * Y: Use bottom 240p for playfield and top 80 pixels for logo.
502 * MAX_X = (204 - 12) / 2 - 1 = 95
504 * Y: Score text 7 0 + 80
505 * Space 10 7 + 80
506 * Score 7 17 + 80
507 * Space 8 24 + 80
508 * 3 Ufo 7 32 + 80
509 * 2 Space Aliens start at 32 + 3 * 8 = 56
510 * 0 aliens 9*8 56 -
511 * space ~7*8 128 | 18.75 aliens space between
512 * shield 2*8 182 | first alien and ship.
513 * space 8 198 | MAX_Y = 18
514 * ship 8 206 -
515 * space 2*8 214
516 * hline 1 230 310 - PLAYFIELD_Y
517 * bottom border 10 240 320
518 * Lives and Level goes inside bottom border
521 #define ARCADISH_GRAPHICS
522 #define PLAYFIELD_X 8
523 #define SHIP_Y (PLAYFIELD_Y - 3 * SHIP_HEIGHT)
524 #define ALIEN_START_Y (UFO_Y + 3 * ALIEN_HEIGHT)
525 /* Redefine SCORE_Y */
526 #undef SCORE_Y
527 #define SCORE_Y 80
528 #define SCORENUM_X (PLAYFIELD_X + NUMBERS_WIDTH)
529 #define SCORENUM_Y SCORE_Y + (2 * (FONT_HEIGHT + 1) + 1)
530 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 1 - 6 * NUMBERS_WIDTH - 5 * NUM_SPACING)
531 #define SHIELD_Y (PLAYFIELD_Y - 6 * SHIP_HEIGHT)
532 #define LIVES_X 10
533 #define MAX_Y 18
535 #elif (LCD_WIDTH == 220) && (LCD_HEIGHT == 176)
537 /* TPJ1022, H300, iPod Color: 220x176x16
538 * ============================
539 * X: 0p padding at left/right gives 220p playfield in middle.
540 * 8p "border" gives 204p actual playfield. UFO use full 220p.
541 * Y: Use full 176p for playfield.
543 * MAX_X = (204 - 12) / 2 - 1 = 95
545 * Y: Score text 7 0
546 * Space 8 7
547 * 1 Ufo 7 15
548 * 7 Space Aliens start at 15 + 3 * 8 = 56
549 * 6 aliens 9*8 25 -
550 * space ~7*8 103 | 15.6 aliens space between
551 * shield 2*8 126 | first alien and ship.
552 * space 8 142 | MAX_Y = 15
553 * ship 8 150 -
554 * space 8 158
555 * hline 1 166 - PLAYFIELD_Y
556 * bottom border 10 176
557 * Lives and Level goes inside bottom border
560 #define ARCADISH_GRAPHICS
561 #define PLAYFIELD_X 0
562 #define SHIP_Y (PLAYFIELD_Y - 2 * SHIP_HEIGHT)
563 #define ALIEN_START_Y (UFO_Y + 10)
564 #define SCORENUM_Y SCORE_Y
565 #define SCORENUM_X (PLAYFIELD_X + 6 * NUMBERS_WIDTH + 6 * NUM_SPACING)
566 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 4 * NUMBERS_WIDTH - 3 * NUM_SPACING)
567 #define SHIELD_Y (PLAYFIELD_Y - 5 * SHIP_HEIGHT)
568 #define LIVES_X 8
569 #define MAX_Y 15
572 #else
573 #error INVADROX: Unsupported LCD type
574 #endif
576 #define MAX_X ((LCD_WIDTH-LIVES_X*2-PLAYFIELD_X*2 - ALIEN_WIDTH)/2 - 1)
578 /* Defines common to each "graphic type" */
579 #ifdef ARCADISH_GRAPHICS
581 #define SHOT_HEIGHT 5
582 #define ALIEN_SPACING 4
583 #define ALIEN_SPEED 2
584 #define UFO_SPEED 1
585 #define NUM_SPACING 3
586 #define FIRE_SPEED 8
587 #define BOMB_SPEED 3
588 #define ALIENS 11
590 #elif defined SMALL_GRAPHICS
592 #define SHOT_HEIGHT 4
593 #define ALIEN_SPACING 3
594 #define ALIEN_SPEED 2
595 #define UFO_SPEED 1
596 #define NUM_SPACING 2
597 #define FIRE_SPEED 6
598 #define BOMB_SPEED 2
599 #define ALIENS 11
601 #else
602 #error Graphic type not defined
603 #endif
606 /* Colors */
607 #if (LCD_DEPTH >= 8)
608 #define SLIME_GREEN LCD_RGBPACK(31, 254, 31)
609 #define UFO_RED LCD_RGBPACK(254, 31, 31)
610 #elif (LCD_DEPTH == 2)
611 #define SLIME_GREEN LCD_LIGHTGRAY
612 #define UFO_RED LCD_LIGHTGRAY
613 #else
614 #error LCD type not implemented yet
615 #endif
617 /* Alien states */
618 #define DEAD 0
619 #define ALIVE 1
620 #define BOMBER 2
622 /* Fire/bomb/ufo states */
623 #define S_IDLE 0
624 #define S_ACTIVE 1
625 #define S_SHOWSCORE 2
626 #define S_EXPLODE -9
628 /* Fire/bomb targets */
629 #define TARGET_TOP 0
630 #define TARGET_SHIELD 1
631 #define TARGET_SHIP 2
632 #define TARGET_BOTTOM 3
633 #define TARGET_UFO 4
635 #define HISCOREFILE PLUGIN_GAMES_DATA_DIR "/invadrox.high"
638 /* The time (in ms) for one iteration through the game loop - decrease this
639 * to speed up the game - note that current_tick is (currently) only accurate
640 * to 10ms.
642 #define CYCLETIME 40
645 /* Physical x is at PLAYFIELD_X + LIVES_X + x * ALIEN_SPEED
646 * Physical y is at y * ALIEN_HEIGHT
648 struct alien {
649 int x; /* x-coordinate (0 - 95) */
650 int y; /* y-coordinate (0 - 18) */
651 unsigned char type; /* 0 (Kang), 1 (Kodos), 2 (Serak) */
652 unsigned char state; /* Dead, alive or bomber */
655 /* Aliens box 5 rows * ALIENS aliens in each row */
656 struct alien aliens[5 * ALIENS];
658 #define MAX_BOMBS 4
659 struct bomb {
660 int x, y;
661 unsigned char type;
662 unsigned char frame; /* Current animation frame */
663 unsigned char frames; /* Number of frames in animation */
664 unsigned char target; /* Remember target during explosion frames */
665 int state; /* 0 (IDLE) = inactive, 1 (FIRE) or negative, exploding */
667 struct bomb bombs[MAX_BOMBS];
668 /* Increase max_bombs at higher levels */
669 int max_bombs;
671 /* Raw framebuffer value of shield/ship green color */
672 fb_data screen_green, screen_white;
674 /* For optimization, precalculate startoffset of each scanline */
675 unsigned int ytab[LCD_HEIGHT];
677 int lives = 2;
678 int score = 0;
679 int scores[3] = { 30, 20, 10 };
680 int level = 0;
681 struct highscore hiscore;
682 bool game_over = false;
683 int ship_x, old_ship_x, ship_dir, ship_acc, max_ship_speed;
684 int ship_frame, ship_frame_counter;
685 bool ship_hit;
686 int fire, fire_target, fire_x, fire_y;
687 int curr_alien, aliens_paralyzed, gamespeed;
688 int ufo_state, ufo_x;
689 bool level_finished;
690 bool aliens_down, aliens_right, hit_left_border, hit_right_border;
693 /* No standard get_pixel function yet, use this hack instead */
694 #if (LCD_DEPTH >= 8)
696 #if defined(LCD_STRIDEFORMAT) && LCD_STRIDEFORMAT == VERTICAL_STRIDE
697 static inline fb_data get_pixel(int x, int y)
699 return rb->lcd_framebuffer[x*LCD_HEIGHT+y];
701 #else
702 static inline fb_data get_pixel(int x, int y)
704 return rb->lcd_framebuffer[ytab[y] + x];
706 #endif
708 #elif (LCD_DEPTH == 2)
710 #if (LCD_PIXELFORMAT == HORIZONTAL_PACKING)
711 static const unsigned char shifts[4] = {
712 6, 4, 2, 0
714 /* Horizontal packing */
715 static inline fb_data get_pixel(int x, int y)
717 return (rb->lcd_framebuffer[ytab[y] + (x >> 2)] >> shifts[x & 3]) & 3;
719 #else
720 /* Vertical packing */
721 static const unsigned char shifts[4] = {
722 0, 2, 4, 6
724 static inline fb_data get_pixel(int x, int y)
726 return (rb->lcd_framebuffer[ytab[y] + x] >> shifts[y & 3]) & 3;
728 #endif /* Horizontal/Vertical packing */
730 #else
731 #error get_pixel: pixelformat not implemented yet
732 #endif
735 /* Draw "digits" least significant digits of num at (x,y) */
736 void draw_number(int x, int y, int num, int digits)
738 int i;
739 int d;
741 for (i = digits - 1; i >= 0; i--) {
742 d = num % 10;
743 num = num / 10;
744 rb->lcd_bitmap_part(invadrox_numbers, d * NUMBERS_WIDTH, 0,
745 STRIDE( SCREEN_MAIN,
746 BMPWIDTH_invadrox_numbers,
747 BMPHEIGHT_invadrox_numbers),
748 x + i * (NUMBERS_WIDTH + NUM_SPACING), y,
749 NUMBERS_WIDTH, FONT_HEIGHT);
751 /* Update lcd */
752 rb->lcd_update_rect(x, y, 4 * NUMBERS_WIDTH + 3 * NUM_SPACING, FONT_HEIGHT);
756 static inline void draw_score(void)
758 draw_number(SCORENUM_X, SCORENUM_Y, score, 4);
759 if (score > hiscore.score) {
760 /* Draw new hiscore (same as score) */
761 draw_number(HISCORENUM_X, SCORENUM_Y, score, 4);
766 void draw_level(void)
768 draw_number(LEVEL_X + 2 * NUM_SPACING, PLAYFIELD_Y + 2, level, 2);
772 void draw_lives(void)
774 int i;
775 /* Lives num */
776 rb->lcd_bitmap_part(invadrox_numbers, lives * NUMBERS_WIDTH, 0,
777 STRIDE( SCREEN_MAIN,
778 BMPWIDTH_invadrox_numbers,
779 BMPHEIGHT_invadrox_numbers),
780 PLAYFIELD_X + LIVES_X, PLAYFIELD_Y + 2,
781 NUMBERS_WIDTH, FONT_HEIGHT);
783 /* Ships */
784 for (i = 0; i < (lives - 1); i++) {
785 rb->lcd_bitmap_part(invadrox_ships, 0, 0,
786 STRIDE( SCREEN_MAIN,
787 BMPWIDTH_invadrox_ships,
788 BMPHEIGHT_invadrox_ships),
789 PLAYFIELD_X + LIVES_X + SHIP_WIDTH + i * (SHIP_WIDTH + NUM_SPACING),
790 PLAYFIELD_Y + 1, SHIP_WIDTH, SHIP_HEIGHT);
793 /* Erase ship to the right (if less than MAX_LIVES) */
794 if (lives < MAX_LIVES) {
795 rb->lcd_fillrect(PLAYFIELD_X + LIVES_X + SHIP_WIDTH + i * (SHIP_WIDTH + NUM_SPACING),
796 PLAYFIELD_Y + 1, SHIP_WIDTH, SHIP_HEIGHT);
798 /* Update lives (and level) part of screen */
799 rb->lcd_update_rect(PLAYFIELD_X + LIVES_X, PLAYFIELD_Y + 1,
800 PLAYFIELD_WIDTH - 2 * LIVES_X, MAX(FONT_HEIGHT + 1, SHIP_HEIGHT + 1));
804 static inline void draw_aliens(void)
806 int i;
808 for (i = 0; i < 5 * ALIENS; i++) {
809 rb->lcd_bitmap_part(invadrox_aliens, aliens[i].x & 1 ? ALIEN_WIDTH : 0,
810 aliens[i].type * ALIEN_HEIGHT,
811 STRIDE( SCREEN_MAIN,
812 BMPWIDTH_invadrox_aliens,
813 BMPHEIGHT_invadrox_aliens),
814 PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED,
815 ALIEN_START_Y + aliens[i].y * ALIEN_HEIGHT,
816 ALIEN_WIDTH, ALIEN_HEIGHT);
821 /* Return false if there is no next alive alien (round is over) */
822 static inline bool next_alien(void)
824 bool ret = true;
826 do {
827 curr_alien++;
828 if (curr_alien % ALIENS == 0) {
829 /* End of this row. Move up one row. */
830 curr_alien -= 2 * ALIENS;
831 if (curr_alien < 0) {
832 /* No more aliens in this round. */
833 curr_alien = 4 * ALIENS;
834 ret = false;
837 } while (aliens[curr_alien].state == DEAD && ret);
839 if (!ret) {
840 /* No more alive aliens. Round finished. */
841 if (hit_right_border) {
842 if (hit_left_border) {
843 DBG("ERROR: both left and right borders are set (%d)\n", curr_alien);
845 /* Move down-left next round */
846 aliens_right = false;
847 aliens_down = true;
848 hit_right_border = false;
849 } else if (hit_left_border) {
850 /* Move down-right next round */
851 aliens_right = true;
852 aliens_down = true;
853 hit_left_border = false;
854 } else {
855 /* Not left nor right. Set down to false. */
856 aliens_down = false;
860 return ret;
864 /* All aliens have been moved.
865 * Set curr_alien to first alive.
866 * Return false if no-one is left alive.
868 bool first_alien(void)
870 int i, y;
872 for (y = 4; y >= 0; y--) {
873 for (i = y * ALIENS; i < (y + 1) * ALIENS; i++) {
874 if (aliens[i].state != DEAD) {
875 curr_alien = i;
876 return true;
881 /* All aliens dead. */
882 level_finished = true;
884 return false;
888 bool move_aliens(void)
890 int x, y, old_x, old_y;
892 /* Move current alien (curr_alien is pointing to a living alien) */
894 old_x = aliens[curr_alien].x;
895 old_y = aliens[curr_alien].y;
897 if (aliens_down) {
898 aliens[curr_alien].y++;
899 if (aliens[curr_alien].y == MAX_Y) {
900 /* Alien is at bottom. Game Over. */
901 DBG("Alien %d is at bottom. Game Over.\n", curr_alien);
902 game_over = true;
903 return false;
907 if (aliens_right) {
908 /* Moving right */
909 if (aliens[curr_alien].x < MAX_X) {
910 aliens[curr_alien].x++;
913 /* Now, after move, check if we hit the right border. */
914 if (aliens[curr_alien].x == MAX_X) {
915 hit_right_border = true;
918 } else {
919 /* Moving left */
920 if (aliens[curr_alien].x > 0) {
921 aliens[curr_alien].x--;
924 /* Now, after move, check if we hit the left border. */
925 if (aliens[curr_alien].x == 0) {
926 hit_left_border = true;
930 /* Erase old position */
931 x = PLAYFIELD_X + LIVES_X + old_x * ALIEN_SPEED;
932 y = ALIEN_START_Y + old_y * ALIEN_HEIGHT;
933 if (aliens[curr_alien].y != old_y) {
934 /* Moved in y-dir. Erase whole alien. */
935 rb->lcd_fillrect(x, y, ALIEN_WIDTH, ALIEN_HEIGHT);
936 } else {
937 if (aliens_right) {
938 /* Erase left edge */
939 rb->lcd_fillrect(x, y, ALIEN_SPEED, ALIEN_HEIGHT);
940 } else {
941 /* Erase right edge */
942 x += ALIEN_WIDTH - ALIEN_SPEED;
943 rb->lcd_fillrect(x, y, ALIEN_SPEED, ALIEN_HEIGHT);
947 /* Draw alien at new pos */
948 x = PLAYFIELD_X + LIVES_X + aliens[curr_alien].x * ALIEN_SPEED;
949 y = ALIEN_START_Y + aliens[curr_alien].y * ALIEN_HEIGHT;
950 rb->lcd_bitmap_part(invadrox_aliens,
951 aliens[curr_alien].x & 1 ? ALIEN_WIDTH : 0,
952 aliens[curr_alien].type * ALIEN_HEIGHT,
953 STRIDE( SCREEN_MAIN,
954 BMPWIDTH_invadrox_aliens,
955 BMPHEIGHT_invadrox_aliens),
956 x, y, ALIEN_WIDTH, ALIEN_HEIGHT);
958 if (!next_alien()) {
959 /* Round finished. Set curr_alien to first alive from bottom. */
960 if (!first_alien()) {
961 /* Should never happen. Taken care of in move_fire(). */
962 return false;
964 /* TODO: Play next background sound */
967 return true;
971 static inline void draw_ship(void)
973 /* Erase old ship */
974 if (old_ship_x < ship_x) {
975 /* Move right. Erase leftmost part of ship. */
976 rb->lcd_fillrect(old_ship_x, SHIP_Y, ship_x - old_ship_x, SHIP_HEIGHT);
977 } else if (old_ship_x > ship_x) {
978 /* Move left. Erase rightmost part of ship. */
979 rb->lcd_fillrect(ship_x + SHIP_WIDTH, SHIP_Y, old_ship_x - ship_x, SHIP_HEIGHT);
982 /* Draw ship */
983 rb->lcd_bitmap_part(invadrox_ships, 0, ship_frame * SHIP_HEIGHT,
984 STRIDE( SCREEN_MAIN,
985 BMPWIDTH_invadrox_ships,
986 BMPHEIGHT_invadrox_ships),
987 ship_x, SHIP_Y, SHIP_WIDTH, SHIP_HEIGHT);
988 if (ship_hit) {
989 /* Alternate between frame 1 and 2 during hit */
990 ship_frame_counter++;
991 if (ship_frame_counter > 2) {
992 ship_frame_counter = 0;
993 ship_frame++;
994 if (ship_frame > 2) {
995 ship_frame = 1;
1000 /* Save ship_x for next time */
1001 old_ship_x = ship_x;
1005 static inline void fire_alpha(int xc, int yc, fb_data color)
1007 int oldmode = rb->lcd_get_drawmode();
1009 rb->lcd_set_foreground(color);
1010 rb->lcd_set_drawmode(DRMODE_FG);
1012 rb->lcd_mono_bitmap(invadrox_fire, xc - (FIRE_WIDTH/2), yc, FIRE_WIDTH, FIRE_HEIGHT);
1014 rb->lcd_set_foreground(LCD_BLACK);
1015 rb->lcd_set_drawmode(oldmode);
1019 void move_fire(void)
1021 bool hit_green = false;
1022 bool hit_white = false;
1023 int i, j;
1024 static int exploding_alien = -1;
1025 fb_data pix;
1027 if (fire == S_IDLE) {
1028 return;
1031 /* Alien hit. Wait until explosion is finished. */
1032 if (aliens_paralyzed < 0) {
1033 aliens_paralyzed++;
1034 if (aliens_paralyzed == 0) {
1035 /* Erase exploding_alien */
1036 rb->lcd_fillrect(PLAYFIELD_X + LIVES_X + aliens[exploding_alien].x * ALIEN_SPEED,
1037 ALIEN_START_Y + aliens[exploding_alien].y * ALIEN_HEIGHT,
1038 ALIEN_EXPLODE_WIDTH, ALIEN_HEIGHT);
1039 fire = S_IDLE;
1040 /* Special case. We killed curr_alien. */
1041 if (exploding_alien == curr_alien) {
1042 if (!next_alien()) {
1043 /* Round finished. Set curr_alien to first alive from bottom. */
1044 first_alien();
1048 return;
1051 if (fire == S_ACTIVE) {
1053 /* Erase */
1054 rb->lcd_vline(fire_x, fire_y, fire_y + SHOT_HEIGHT);
1056 /* Check top */
1057 if (fire_y <= SCORENUM_Y + FONT_HEIGHT + 4) {
1059 /* TODO: Play explode sound */
1061 fire = S_EXPLODE;
1062 fire_target = TARGET_TOP;
1063 fire_alpha(fire_x, fire_y, UFO_RED);
1064 return;
1067 /* Move */
1068 fire_y -= FIRE_SPEED;
1070 /* Hit UFO? */
1071 if (ufo_state == S_ACTIVE) {
1072 if ((ABS(ufo_x + UFO_WIDTH / 2 - fire_x) <= UFO_WIDTH / 2) &&
1073 (fire_y <= UFO_Y + UFO_HEIGHT)) {
1074 ufo_state = S_EXPLODE;
1075 fire = S_EXPLODE;
1076 fire_target = TARGET_UFO;
1077 /* Center explosion */
1078 ufo_x -= (UFO_EXPLODE_WIDTH - UFO_WIDTH) / 2;
1079 rb->lcd_bitmap(invadrox_ufo_explode, ufo_x, UFO_Y - 1,
1080 UFO_EXPLODE_WIDTH, UFO_EXPLODE_HEIGHT);
1081 return;
1085 /* Hit bomb? (check position, not pixel value) */
1086 for (i = 0; i < max_bombs; i++) {
1087 if (bombs[i].state == S_ACTIVE) {
1088 /* Count as hit if within BOMB_WIDTH pixels */
1089 if ((ABS(bombs[i].x - fire_x) < BOMB_WIDTH) &&
1090 (fire_y - bombs[i].y < BOMB_HEIGHT)) {
1091 /* Erase bomb */
1092 rb->lcd_fillrect(bombs[i].x, bombs[i].y, BOMB_WIDTH, BOMB_HEIGHT);
1093 bombs[i].state = S_IDLE;
1094 /* Explode ship fire */
1095 fire = S_EXPLODE;
1096 fire_target = TARGET_SHIELD;
1097 fire_alpha(fire_x, fire_y, LCD_WHITE);
1098 return;
1103 /* Check for hit*/
1104 for (i = FIRE_SPEED; i >= 0; i--) {
1105 pix = get_pixel(fire_x, fire_y + i);
1106 if(pix == screen_white) {
1107 hit_white = true;
1108 fire_y += i;
1109 break;
1111 if(pix == screen_green) {
1112 hit_green = true;
1113 fire_y += i;
1114 break;
1118 if (hit_green) {
1119 /* Hit shield */
1121 /* TODO: Play explode sound */
1123 fire = S_EXPLODE;
1124 fire_target = TARGET_SHIELD;
1125 /* Center explosion around hit pixel */
1126 fire_y -= FIRE_HEIGHT / 2;
1127 fire_alpha(fire_x, fire_y, SLIME_GREEN);
1128 return;
1131 if (hit_white) {
1133 /* Hit alien? */
1134 for (i = 0; i < 5 * ALIENS; i++) {
1135 if (aliens[i].state != DEAD &&
1136 (ABS(fire_x - (PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED +
1137 ALIEN_WIDTH / 2)) <= ALIEN_WIDTH / 2) &&
1138 (ABS(fire_y - (ALIEN_START_Y + aliens[i].y * ALIEN_HEIGHT +
1139 ALIEN_HEIGHT / 2)) <= ALIEN_HEIGHT / 2)) {
1141 /* TODO: play alien hit sound */
1143 if (aliens[i].state == BOMBER) {
1144 /* Set (possible) alien above to bomber */
1145 for (j = i - ALIENS; j >= 0; j -= ALIENS) {
1146 if (aliens[j].state != DEAD) {
1147 /* printf("New bomber (%d, %d)\n", j % ALIENS, j / ALIENS); */
1148 aliens[j].state = BOMBER;
1149 break;
1153 aliens[i].state = DEAD;
1154 exploding_alien = i;
1155 score += scores[aliens[i].type];
1156 draw_score();
1157 /* Update score part of screen */
1158 rb->lcd_update_rect(SCORENUM_X, SCORENUM_Y,
1159 PLAYFIELD_WIDTH - 2 * NUMBERS_WIDTH, FONT_HEIGHT);
1161 /* Paralyze aliens S_EXPLODE frames */
1162 aliens_paralyzed = S_EXPLODE;
1163 rb->lcd_bitmap(invadrox_alien_explode,
1164 PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED,
1165 ALIEN_START_Y + aliens[i].y * ALIEN_HEIGHT,
1166 ALIEN_EXPLODE_WIDTH, ALIEN_EXPLODE_HEIGHT);
1167 /* Since alien is 1 pixel taller than explosion sprite, erase bottom line */
1168 rb->lcd_hline(PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED,
1169 PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED + ALIEN_WIDTH,
1170 ALIEN_START_Y + (aliens[i].y + 1) * ALIEN_HEIGHT - 1);
1171 return;
1176 /* Draw shot */
1177 rb->lcd_set_foreground(LCD_WHITE);
1178 rb->lcd_vline(fire_x, fire_y, fire_y + SHOT_HEIGHT);
1179 rb->lcd_set_foreground(LCD_BLACK);
1180 } else if (fire < S_IDLE) {
1181 /* Count up towards S_IDLE, then erase explosion */
1182 fire++;
1183 if (fire == S_IDLE) {
1184 /* Erase explosion */
1185 if (fire_target == TARGET_TOP) {
1186 rb->lcd_fillrect(fire_x - (FIRE_WIDTH / 2), fire_y, FIRE_WIDTH, FIRE_HEIGHT);
1187 } else if (fire_target == TARGET_SHIELD) {
1188 /* Draw explosion with black pixels */
1189 fire_alpha(fire_x, fire_y, LCD_BLACK);
1196 /* Return a BOMBER alien */
1197 static inline int random_bomber(void)
1199 int i, col;
1201 /* TODO: Weigh higher probability near ship */
1202 col = rb->rand() % ALIENS;
1203 for (i = col + 4 * ALIENS; i >= 0; i -= ALIENS) {
1204 if (aliens[i].state == BOMBER) {
1205 return i;
1209 /* No BOMBER found in this col */
1211 for (i = 0; i < 5 * ALIENS; i++) {
1212 if (aliens[i].state == BOMBER) {
1213 return i;
1217 /* No BOMBER found at all (error?) */
1219 return -1;
1223 static inline void draw_bomb(int i)
1225 rb->lcd_bitmap_part(invadrox_bombs, bombs[i].type * BOMB_WIDTH,
1226 bombs[i].frame * BOMB_HEIGHT,
1227 STRIDE( SCREEN_MAIN,
1228 BMPWIDTH_invadrox_bombs,
1229 BMPHEIGHT_invadrox_bombs),
1230 bombs[i].x, bombs[i].y,
1231 BOMB_WIDTH, BOMB_HEIGHT);
1232 /* Advance frame */
1233 bombs[i].frame++;
1234 if (bombs[i].frame == bombs[i].frames) {
1235 bombs[i].frame = 0;
1240 void move_bombs(void)
1242 int i, j, bomber;
1243 bool abort;
1245 for (i = 0; i < max_bombs; i++) {
1247 switch (bombs[i].state) {
1249 case S_IDLE:
1250 if (ship_hit) {
1251 continue;
1253 bomber = random_bomber();
1254 if (bomber < 0) {
1255 DBG("ERROR: No bomber available\n");
1256 continue;
1258 /* x, y */
1259 bombs[i].x = PLAYFIELD_X + LIVES_X + aliens[bomber].x * ALIEN_SPEED + ALIEN_WIDTH / 2;
1260 bombs[i].y = ALIEN_START_Y + (aliens[bomber].y + 1) * ALIEN_HEIGHT;
1262 /* Check for duplets in x and y direction */
1263 abort = false;
1264 for (j = i - 1; j >= 0; j--) {
1265 if ((bombs[j].state == S_ACTIVE) &&
1266 ((bombs[i].x == bombs[j].x) || (bombs[i].y == bombs[j].y))) {
1267 abort = true;
1268 break;
1271 if (abort) {
1272 /* Skip this one, continue with next bomb */
1273 /* printf("Bomb %d duplet of %d\n", i, j); */
1274 continue;
1277 /* Passed, set type */
1278 bombs[i].type = rb->rand() % 3;
1279 bombs[i].frame = 0;
1280 if (bombs[i].type == 0) {
1281 bombs[i].frames = 3;
1282 } else if (bombs[i].type == 1) {
1283 bombs[i].frames = 4;
1284 } else {
1285 bombs[i].frames = 6;
1288 /* Bombs away */
1289 bombs[i].state = S_ACTIVE;
1290 draw_bomb(i);
1291 continue;
1293 break;
1295 case S_ACTIVE:
1296 /* Erase old position */
1297 rb->lcd_fillrect(bombs[i].x, bombs[i].y, BOMB_WIDTH, BOMB_HEIGHT);
1299 /* Move */
1300 bombs[i].y += BOMB_SPEED;
1302 /* Check if bottom hit */
1303 if (bombs[i].y + BOMB_HEIGHT >= PLAYFIELD_Y) {
1304 bombs[i].y = PLAYFIELD_Y - FIRE_HEIGHT + 1;
1305 fire_alpha(bombs[i].x, bombs[i].y, LCD_WHITE);
1306 bombs[i].state = S_EXPLODE;
1307 bombs[i].target = TARGET_BOTTOM;
1308 break;
1311 /* Check for green (ship or shield) */
1312 for (j = BOMB_HEIGHT; j >= BOMB_HEIGHT - BOMB_SPEED; j--) {
1313 bombs[i].target = 0;
1314 if(get_pixel(bombs[i].x + BOMB_WIDTH / 2, bombs[i].y + j) == screen_green) {
1315 /* Move to hit pixel */
1316 bombs[i].x += BOMB_WIDTH / 2;
1317 bombs[i].y += j;
1319 /* Check if ship is hit */
1320 if (bombs[i].y > SHIELD_Y + SHIELD_HEIGHT && bombs[i].y < PLAYFIELD_Y) {
1322 /* TODO: play ship hit sound */
1324 ship_hit = true;
1325 ship_frame = 1;
1326 ship_frame_counter = 0;
1327 bombs[i].state = S_EXPLODE * 4;
1328 bombs[i].target = TARGET_SHIP;
1329 rb->lcd_bitmap_part(invadrox_ships, 0, 1 * SHIP_HEIGHT,
1330 STRIDE( SCREEN_MAIN,
1331 BMPWIDTH_invadrox_ships,
1332 BMPHEIGHT_invadrox_ships),
1333 ship_x, SHIP_Y,
1334 SHIP_WIDTH, SHIP_HEIGHT);
1335 break;
1337 /* Shield hit */
1338 bombs[i].state = S_EXPLODE;
1339 bombs[i].target = TARGET_SHIELD;
1340 /* Center explosion around hit pixel in shield */
1341 bombs[i].y -= FIRE_HEIGHT / 2;
1342 fire_alpha(bombs[i].x, bombs[i].y, SLIME_GREEN);
1343 break;
1347 if (bombs[i].target != 0) {
1348 /* Hit ship or shield, continue */
1349 continue;
1352 draw_bomb(i);
1353 break;
1355 default:
1356 /* If we get here state should be < 0, exploding */
1357 bombs[i].state++;
1358 if (bombs[i].state == S_IDLE) {
1359 if (ship_hit) {
1360 /* Erase explosion */
1361 rb->lcd_fillrect(ship_x, SHIP_Y, SHIP_WIDTH, SHIP_HEIGHT);
1362 rb->lcd_update_rect(ship_x, SHIP_Y, SHIP_WIDTH, SHIP_HEIGHT);
1363 ship_hit = false;
1364 ship_frame = 0;
1365 ship_x = PLAYFIELD_X + 2 * LIVES_X;
1366 lives--;
1367 if (lives == 0) {
1368 game_over = true;
1369 return;
1371 draw_lives();
1372 /* Sleep 1s to give player time to examine lives left */
1373 rb->sleep(HZ);
1375 /* Erase explosion (even if ship hit, might be another bomb) */
1376 fire_alpha(bombs[i].x, bombs[i].y, LCD_BLACK);
1378 break;
1384 static inline void move_ship(void)
1386 ship_dir += ship_acc;
1387 if (ship_dir > max_ship_speed) {
1388 ship_dir = max_ship_speed;
1390 if (ship_dir < -max_ship_speed) {
1391 ship_dir = -max_ship_speed;
1393 ship_x += ship_dir;
1394 if (ship_x < SHIP_MIN_X) {
1395 ship_x = SHIP_MIN_X;
1397 if (ship_x > SHIP_MAX_X) {
1398 ship_x = SHIP_MAX_X;
1401 draw_ship();
1405 /* Unidentified Flying Object */
1406 void move_ufo(void)
1408 static int ufo_speed;
1409 static int counter;
1410 int mystery_score;
1412 switch (ufo_state) {
1414 case S_IDLE:
1416 if (rb->rand() % 500 == 0) {
1417 /* Uh-oh, it's time to launch a mystery UFO */
1419 /* TODO: Play UFO sound */
1421 if (rb->rand() % 2) {
1422 ufo_speed = UFO_SPEED;
1423 ufo_x = PLAYFIELD_X;
1424 } else {
1425 ufo_speed = -UFO_SPEED;
1426 ufo_x = LCD_WIDTH - PLAYFIELD_X - UFO_WIDTH;
1428 ufo_state = S_ACTIVE;
1429 /* UFO will be drawn next frame */
1431 break;
1433 case S_ACTIVE:
1434 /* Erase old pos */
1435 rb->lcd_fillrect(ufo_x, UFO_Y, UFO_WIDTH, UFO_HEIGHT);
1436 /* Move */
1437 ufo_x += ufo_speed;
1438 /* Check bounds */
1439 if (ufo_x < PLAYFIELD_X || ufo_x > LCD_WIDTH - PLAYFIELD_X - UFO_WIDTH) {
1440 ufo_state = S_IDLE;
1441 break;
1443 /* Draw new pos */
1444 rb->lcd_bitmap(invadrox_ufo, ufo_x, UFO_Y, UFO_WIDTH, UFO_HEIGHT);
1445 break;
1447 case S_SHOWSCORE:
1448 counter++;
1449 if (counter == S_IDLE) {
1450 /* Erase mystery number */
1451 rb->lcd_fillrect(ufo_x, UFO_Y, 3 * NUMBERS_WIDTH + 2 * NUM_SPACING, FONT_HEIGHT);
1452 ufo_state = S_IDLE;
1454 break;
1456 default:
1457 /* Exploding */
1458 ufo_state++;
1459 if (ufo_state == S_IDLE) {
1460 /* Erase explosion */
1461 rb->lcd_fillrect(ufo_x, UFO_Y - 1, UFO_EXPLODE_WIDTH, UFO_EXPLODE_HEIGHT);
1462 ufo_state = S_SHOWSCORE;
1463 counter = S_EXPLODE * 4;
1464 /* Draw mystery_score, sleep, increase score and continue */
1465 mystery_score = 50 + (rb->rand() % 6) * 50;
1466 if (mystery_score < 100) {
1467 draw_number(ufo_x, UFO_Y, mystery_score, 2);
1468 } else {
1469 draw_number(ufo_x, UFO_Y, mystery_score, 3);
1471 score += mystery_score;
1472 draw_score();
1474 break;
1479 void draw_background(void)
1482 rb->lcd_bitmap(invadrox_background, 0, 0, LCD_WIDTH, LCD_HEIGHT);
1483 rb->lcd_update();
1487 void new_level(void)
1489 int i;
1491 draw_background();
1492 /* Give an extra life for each new level */
1493 if (lives < MAX_LIVES) {
1494 lives++;
1496 draw_lives();
1498 /* Score */
1499 draw_score();
1500 draw_number(HISCORENUM_X, SCORENUM_Y, hiscore.score, 4);
1502 level++;
1503 draw_level();
1504 level_finished = false;
1506 ufo_state = S_IDLE;
1508 /* Init alien positions and states */
1509 for (i = 0; i < 4 * ALIENS; i++) {
1510 aliens[i].x = 0 + (i % ALIENS) * ((ALIEN_WIDTH + ALIEN_SPACING) / ALIEN_SPEED);
1511 aliens[i].y = 2 * (i / ALIENS);
1512 aliens[i].state = ALIVE;
1514 /* Last row, bombers */
1515 for (i = 4 * ALIENS; i < 5 * ALIENS; i++) {
1516 aliens[i].x = 0 + (i % ALIENS) * ((ALIEN_WIDTH + ALIEN_SPACING) / ALIEN_SPEED);
1517 aliens[i].y = 2 * (i / ALIENS);
1518 aliens[i].state = BOMBER;
1521 /* Init bombs to inactive (S_IDLE) */
1522 for (i = 0; i < MAX_BOMBS; i++) {
1523 bombs[i].state = S_IDLE;
1526 /* Start aliens closer to earth from level 2 */
1527 for (i = 0; i < 5 * ALIENS; i++) {
1528 if (level < 6) {
1529 aliens[i].y += level - 1;
1530 } else {
1531 aliens[i].y += 5;
1535 /* Max concurrent bombs */
1536 max_bombs = 1;
1538 gamespeed = 2;
1540 if (level > 1) {
1541 max_bombs++;
1544 /* Increase speed */
1545 if (level > 2) {
1546 gamespeed++;
1549 if (level > 3) {
1550 max_bombs++;
1553 /* Increase speed more */
1554 if (level > 4) {
1555 gamespeed++;
1558 if (level > 5) {
1559 max_bombs++;
1562 /* 4 shields */
1563 for (i = 1; i <= 4; i++) {
1564 rb->lcd_bitmap(invadrox_shield,
1565 PLAYFIELD_X + i * PLAYFIELD_WIDTH / 5 - SHIELD_WIDTH / 2,
1566 SHIELD_Y, SHIELD_WIDTH, SHIELD_HEIGHT);
1569 /* Bottom line */
1570 rb->lcd_set_foreground(SLIME_GREEN);
1571 rb->lcd_hline(PLAYFIELD_X, LCD_WIDTH - PLAYFIELD_X, PLAYFIELD_Y);
1572 /* Restore foreground to black (for fast erase later). */
1573 rb->lcd_set_foreground(LCD_BLACK);
1575 ship_x = PLAYFIELD_X + 2 * LIVES_X;
1576 if (level == 1) {
1577 old_ship_x = ship_x;
1579 ship_dir = 0;
1580 ship_acc = 0;
1581 ship_frame = 0;
1582 ship_hit = false;
1583 fire = S_IDLE;
1584 /* Start moving the bottom row left to right */
1585 curr_alien = 4 * ALIENS;
1586 aliens_paralyzed = 0;
1587 aliens_right = true;
1588 aliens_down = false;
1589 hit_left_border = false;
1590 hit_right_border = false;
1591 /* TODO: Change max_ship_speed to 3 at higher levels */
1592 max_ship_speed = 2;
1594 draw_aliens();
1596 rb->lcd_update();
1600 void init_invadrox(void)
1602 int i;
1604 /* Seed random number generator with a "random" number */
1605 rb->srand(rb->get_time()->tm_sec + rb->get_time()->tm_min * 60);
1607 /* Precalculate start of each scanline */
1608 for (i = 0; i < LCD_HEIGHT; i++) {
1609 #if (LCD_DEPTH >= 8)
1610 ytab[i] = i * LCD_WIDTH;
1611 #elif (LCD_DEPTH == 2) && (LCD_PIXELFORMAT == HORIZONTAL_PACKING)
1612 ytab[i] = i * (LCD_WIDTH / 4);
1613 #elif (LCD_DEPTH == 2) && (LCD_PIXELFORMAT == VERTICAL_PACKING)
1614 ytab[i] = (i / 4) * LCD_WIDTH;
1615 #else
1616 #error pixelformat not implemented yet
1617 #endif
1620 rb->lcd_set_background(LCD_BLACK);
1621 rb->lcd_set_foreground(LCD_BLACK);
1623 if (highscore_load(HISCOREFILE, &hiscore, 1) < 0) {
1624 /* Init hiscore to 0 */
1625 rb->strlcpy(hiscore.name, "Invader", sizeof(hiscore.name));
1626 hiscore.score = 0;
1627 hiscore.level = 1;
1630 /* Init alien types in aliens array */
1631 for (i = 0; i < 1 * ALIENS; i++) {
1632 aliens[i].type = 0; /* Kang */
1634 for (; i < 3 * ALIENS; i++) {
1635 aliens[i].type = 1; /* Kodos */
1637 for (; i < 5 * ALIENS; i++) {
1638 aliens[i].type = 2; /* Serak */
1642 /* Save screen white color */
1643 rb->lcd_set_foreground(LCD_WHITE);
1644 rb->lcd_drawpixel(0, 0);
1645 rb->lcd_update_rect(0, 0, 1, 1);
1646 screen_white = get_pixel(0, 0);
1648 /* Save screen green color */
1649 rb->lcd_set_foreground(SLIME_GREEN);
1650 rb->lcd_drawpixel(0, 0);
1651 rb->lcd_update_rect(0, 0, 1, 1);
1652 screen_green = get_pixel(0, 0);
1654 /* Restore black foreground */
1655 rb->lcd_set_foreground(LCD_BLACK);
1657 new_level();
1659 /* Flash score at start */
1660 for (i = 0; i < 5; i++) {
1661 rb->lcd_fillrect(SCORENUM_X, SCORENUM_Y,
1662 4 * NUMBERS_WIDTH + 3 * NUM_SPACING,
1663 FONT_HEIGHT);
1664 rb->lcd_update_rect(SCORENUM_X, SCORENUM_Y,
1665 4 * NUMBERS_WIDTH + 3 * NUM_SPACING,
1666 FONT_HEIGHT);
1667 rb->sleep(HZ / 10);
1668 draw_number(SCORENUM_X, SCORENUM_Y, score, 4);
1669 rb->sleep(HZ / 10);
1674 static inline bool handle_buttons(void)
1676 static unsigned int oldbuttonstate = 0;
1678 unsigned int released, pressed, newbuttonstate;
1680 if (ship_hit) {
1681 /* Don't allow ship movement during explosion */
1682 newbuttonstate = 0;
1683 } else {
1684 newbuttonstate = rb->button_status();
1686 if(newbuttonstate == oldbuttonstate) {
1687 if (newbuttonstate == 0) {
1688 /* No button pressed. Stop ship. */
1689 ship_acc = 0;
1690 if (ship_dir > 0) {
1691 ship_dir--;
1693 if (ship_dir < 0) {
1694 ship_dir++;
1697 /* return false; */
1698 goto check_usb;
1700 released = ~newbuttonstate & oldbuttonstate;
1701 pressed = newbuttonstate & ~oldbuttonstate;
1702 oldbuttonstate = newbuttonstate;
1703 if (pressed) {
1704 if (pressed & ACTION_LEFT) {
1705 if (ship_acc > -1) {
1706 ship_acc--;
1709 if (pressed & ACTION_RIGHT) {
1710 if (ship_acc < 1) {
1711 ship_acc++;
1714 if (pressed & ACTION_FIRE) {
1715 if (fire == S_IDLE) {
1716 /* Fire shot */
1717 fire_x = ship_x + SHIP_WIDTH / 2;
1718 fire_y = SHIP_Y - SHOT_HEIGHT;
1719 fire = S_ACTIVE;
1720 /* TODO: play fire sound */
1723 if (pressed & ACTION_QUIT) {
1724 rb->splash(HZ * 1, "Quit");
1725 return true;
1728 if (released) {
1729 if ((released & ACTION_LEFT)) {
1730 if (ship_acc < 1) {
1731 ship_acc++;
1734 if ((released & ACTION_RIGHT)) {
1735 if (ship_acc > -1) {
1736 ship_acc--;
1741 check_usb:
1743 /* Quit if USB is connected */
1744 if (rb->button_get(false) == SYS_USB_CONNECTED) {
1745 return true;
1748 return false;
1752 void game_loop(void)
1754 int i, end;
1756 /* Print dimensions (just for debugging) */
1757 DBG("%03dx%03dx%02d\n", LCD_WIDTH, LCD_HEIGHT, LCD_DEPTH);
1759 /* Init */
1760 init_invadrox();
1762 while (1) {
1763 /* Convert CYCLETIME (in ms) to HZ */
1764 end = *rb->current_tick + (CYCLETIME * HZ) / 1000;
1766 if (handle_buttons()) {
1767 return;
1770 /* Animate */
1771 move_ship();
1772 move_fire();
1774 /* Check if level is finished (marked by move_fire) */
1775 if (level_finished) {
1776 /* TODO: Play level finished sound */
1777 new_level();
1780 move_ufo();
1782 /* Move aliens */
1783 if (!aliens_paralyzed && !ship_hit) {
1784 for (i = 0; i < gamespeed; i++) {
1785 if (!move_aliens()) {
1786 if (game_over) {
1787 return;
1793 /* Move alien bombs */
1794 move_bombs();
1795 if (game_over) {
1796 return;
1799 /* Update "playfield" rect */
1800 rb->lcd_update_rect(PLAYFIELD_X, SCORENUM_Y + FONT_HEIGHT,
1801 PLAYFIELD_WIDTH,
1802 PLAYFIELD_Y + 1 - SCORENUM_Y - FONT_HEIGHT);
1804 /* Wait until next frame */
1805 DBG("%ld (%d)\n", end - *rb->current_tick, (CYCLETIME * HZ) / 1000);
1806 if (TIME_BEFORE(*rb->current_tick, end)) {
1807 rb->sleep(end - *rb->current_tick);
1808 } else {
1809 rb->yield();
1812 } /* end while */
1816 /* this is the plugin entry point */
1817 enum plugin_status plugin_start(UNUSED const void* parameter)
1819 rb->lcd_setfont(FONT_SYSFIXED);
1820 /* Turn off backlight timeout */
1821 backlight_ignore_timeout();
1823 /* now go ahead and have fun! */
1824 game_loop();
1826 /* Game Over. */
1827 /* TODO: Play game over sound */
1828 rb->splash(HZ * 2, "Game Over");
1829 if (score > hiscore.score) {
1830 /* Save new hiscore */
1831 highscore_update(score, level, "Invader", &hiscore, 1);
1832 highscore_save(HISCOREFILE, &hiscore, 1);
1835 /* Restore user's original backlight setting */
1836 rb->lcd_setfont(FONT_UI);
1837 /* Turn on backlight timeout (revert to settings) */
1838 backlight_use_settings();
1840 return PLUGIN_OK;
1846 * GNU Emacs settings: Kernighan & Richie coding style with
1847 * 4 spaces indent and no tabs.
1848 * Local Variables:
1849 * c-file-style: "k&r"
1850 * c-basic-offset: 4
1851 * indent-tabs-mode: nil
1852 * End: