New makefile solution: A single invocation of 'make' to build the entire tree. Fully...
[kugel-rb.git] / apps / plugins / invadrox.c
blob8b73221331e562e68c8e81bfc84fe790e3b5155f
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2006 Albert Veli
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
20 ****************************************************************************/
22 /* Improvised creds goes to:
24 * - Anders Clausen for ingeniously inventing the name Invadrox.
25 * - Linus Nielsen-Feltzing for patiently answering n00b questions.
28 #include "plugin.h"
29 #include "lib/highscore.h"
30 #include "lib/helper.h"
32 PLUGIN_HEADER
34 /* Original graphics is only 1bpp so it should be portable
35 * to most targets. But for now, only support the simple ones.
37 #ifndef HAVE_LCD_BITMAP
38 #error INVADROX: Unsupported LCD
39 #endif
41 #if (LCD_DEPTH < 2)
42 #error INVADROX: Unsupported LCD
43 #endif
45 /* #define DEBUG */
46 #ifdef DEBUG
47 #include <stdio.h>
48 #define DBG(format, arg...) { printf("%s: " format, __FUNCTION__, ## arg); }
49 #else
50 #define DBG(format, arg...) {}
51 #endif
53 #if CONFIG_KEYPAD == IRIVER_H100_PAD
55 #define QUIT BUTTON_OFF
56 #define LEFT BUTTON_LEFT
57 #define RIGHT BUTTON_RIGHT
58 #define FIRE BUTTON_ON
60 #elif CONFIG_KEYPAD == IRIVER_H300_PAD
62 #define QUIT BUTTON_OFF
63 #define LEFT BUTTON_LEFT
64 #define RIGHT BUTTON_RIGHT
65 #define FIRE BUTTON_SELECT
67 #elif (CONFIG_KEYPAD == IRIVER_H10_PAD)
69 #define QUIT BUTTON_POWER
70 #define LEFT BUTTON_LEFT
71 #define RIGHT BUTTON_RIGHT
72 #define FIRE BUTTON_PLAY
74 #elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \
75 (CONFIG_KEYPAD == IPOD_3G_PAD) || \
76 (CONFIG_KEYPAD == IPOD_1G2G_PAD)
78 #define QUIT BUTTON_MENU
79 #define LEFT BUTTON_LEFT
80 #define RIGHT BUTTON_RIGHT
81 #define FIRE BUTTON_SELECT
83 #elif CONFIG_KEYPAD == IAUDIO_X5M5_PAD
85 #define QUIT BUTTON_POWER
86 #define LEFT BUTTON_LEFT
87 #define RIGHT BUTTON_RIGHT
88 #define FIRE BUTTON_SELECT
90 #elif CONFIG_KEYPAD == GIGABEAT_PAD
92 #define QUIT BUTTON_POWER
93 #define LEFT BUTTON_LEFT
94 #define RIGHT BUTTON_RIGHT
95 #define FIRE BUTTON_SELECT
97 #elif CONFIG_KEYPAD == SANSA_E200_PAD
99 #define QUIT BUTTON_POWER
100 #define LEFT BUTTON_LEFT
101 #define RIGHT BUTTON_RIGHT
102 #define FIRE BUTTON_SELECT
104 #elif CONFIG_KEYPAD == ELIO_TPJ1022_PAD
106 /* TODO: Figure out which buttons to use for Tatung Elio TPJ-1022 */
107 #define QUIT BUTTON_AB
108 #define LEFT BUTTON_LEFT
109 #define RIGHT BUTTON_RIGHT
110 #define FIRE BUTTON_MENU
112 #elif CONFIG_KEYPAD == GIGABEAT_S_PAD
114 #define QUIT BUTTON_BACK
115 #define LEFT BUTTON_LEFT
116 #define RIGHT BUTTON_RIGHT
117 #define FIRE BUTTON_SELECT
119 #elif CONFIG_KEYPAD == COWOND2_PAD
121 #define QUIT BUTTON_POWER
123 #elif CONFIG_KEYPAD == IAUDIO67_PAD
125 #define QUIT BUTTON_POWER
126 #define LEFT BUTTON_LEFT
127 #define RIGHT BUTTON_RIGHT
128 #define FIRE BUTTON_PLAY
130 #else
131 #error INVADROX: Unsupported keypad
132 #endif
134 #ifdef HAVE_TOUCHSCREEN
135 #ifndef QUIT
136 #define QUIT BUTTON_TOPLEFT
137 #endif
138 #ifndef LEFT
139 #define LEFT BUTTON_MIDLEFT
140 #endif
141 #ifndef RIGHT
142 #define RIGHT BUTTON_MIDRIGHT
143 #endif
144 #ifndef FIRE
145 #define FIRE BUTTON_CENTER
146 #endif
147 #endif
149 #ifndef UNUSED
150 #define UNUSED __attribute__ ((unused))
151 #endif
153 #ifndef ABS
154 #define ABS(x) (((x) < 0) ? (-(x)) : (x))
155 #endif
158 /* Defines common to all models */
159 #define UFO_Y (SCORENUM_Y + FONT_HEIGHT + ALIEN_HEIGHT)
160 #define PLAYFIELD_Y (LCD_HEIGHT - SHIP_HEIGHT - 2)
161 #define PLAYFIELD_WIDTH (LCD_WIDTH - 2 * PLAYFIELD_X)
162 #define LEVEL_X (LCD_WIDTH - PLAYFIELD_X - LIVES_X - LEVEL_WIDTH - 2 * NUMBERS_WIDTH - 3 * NUM_SPACING)
163 #define SHIP_MIN_X (PLAYFIELD_X + PLAYFIELD_WIDTH / 5 - SHIELD_WIDTH / 2 - SHIP_WIDTH)
164 #define SHIP_MAX_X (PLAYFIELD_X + 4 * PLAYFIELD_WIDTH / 5 + SHIELD_WIDTH / 2)
165 /* SCORE_Y = 0 for most targets. Gigabeat redefines it later. */
166 #define SCORE_Y 0
167 #define MAX_LIVES 8
170 /* iPod Video defines */
171 #if (LCD_WIDTH == 320) && (LCD_HEIGHT == 240)
173 /* Original arcade game size 224x240, 1bpp with
174 * red overlay at top and green overlay at bottom.
176 * iPod Video: 320x240x16
177 * ======================
178 * X: 48p padding at left/right gives 224p playfield in middle.
179 * 10p "border" gives 204p actual playfield. UFO use full 224p.
180 * Y: Use full 240p.
182 * MAX_X = (204 - 12) / 2 - 1 = 95
184 * Y: Score text 7 0
185 * Space 10 7
186 * Score 7 17
187 * Space 8 24
188 * 3 Ufo 7 32
189 * 2 Space Aliens start at 32 + 3 * 8 = 56
190 * 0 aliens 9*8 56 -
191 * space ~7*8 128 | 18.75 aliens space between
192 * shield 2*8 182 | first alien and ship.
193 * space 8 198 | MAX_Y = 18
194 * ship 8 206 -
195 * space 2*8 214
196 * hline 1 230 - PLAYFIELD_Y
197 * bottom border 10 240
198 * Lives and Level goes inside bottom border
201 #define ARCADISH_GRAPHICS
202 #define PLAYFIELD_X 48
203 #define SHIP_Y (PLAYFIELD_Y - 3 * SHIP_HEIGHT)
204 #define ALIEN_START_Y (UFO_Y + 3 * ALIEN_HEIGHT)
205 #define SCORENUM_X (PLAYFIELD_X + NUMBERS_WIDTH)
206 #define SCORENUM_Y SCORE_Y + (2 * (FONT_HEIGHT + 1) + 1)
207 #define HISCORE_X (LCD_WIDTH - PLAYFIELD_X - HISCORE_WIDTH)
208 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 1 - 6 * NUMBERS_WIDTH - 5 * NUM_SPACING)
209 #define SHIELD_Y (PLAYFIELD_Y - 6 * SHIP_HEIGHT)
210 #define LIVES_X 10
211 #define MAX_X 95
212 #define MAX_Y 18
215 #elif (LCD_WIDTH == 176) && (LCD_HEIGHT == 220)
217 /* Sandisk Sansa e200: 176x220x16
218 * ==============================
219 * X: No padding. 8p border -> 160p playfield.
221 * 8p Aliens with 3p spacing -> 88 + 30 = 118p aliens block.
222 * (160 - 118) / 2 = 21 rounds for whole block (more than original)
223 * MAX_X = (160 - 8) / 2 - 1 = 75 rounds for single alien (less than original)
225 * LOGO 70 0
226 * Score text 5 70
227 * Space 5 75
228 * Y Score 5 80
229 * Space 10 85
230 * 2 Ufo 5 95
231 * 2 Space 10 100
232 * 0 aliens 9*5 110 -
233 * space ~7*5 155 | 18.6 aliens space between
234 * shield 2*5 188 | first alien and ship.
235 * space 5 198 | MAX_Y = 18
236 * ship 5 203 -
237 * space 5 208
238 * hline 1 213 PLAYFIELD_Y
239 * bottom border 6
240 * LCD_HEIGHT 220
241 * Lives and Level goes inside bottom border
244 #define TINY_GRAPHICS
245 #define PLAYFIELD_X 0
246 #define SHIP_Y (PLAYFIELD_Y - 2 * SHIP_HEIGHT)
247 #define SHIELD_Y (SHIP_Y - SHIP_HEIGHT - SHIELD_HEIGHT)
248 #define ALIEN_START_Y (UFO_Y + 3 * SHIP_HEIGHT)
249 /* Redefine SCORE_Y */
250 #undef SCORE_Y
251 #define SCORE_Y 70
252 #define SCORENUM_X (PLAYFIELD_X + NUMBERS_WIDTH)
253 #define SCORENUM_Y (SCORE_Y + 2 * FONT_HEIGHT)
254 #define HISCORE_X (LCD_WIDTH - PLAYFIELD_X - HISCORE_WIDTH)
255 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 1 - 6 * NUMBERS_WIDTH - 5 * NUM_SPACING)
256 #define LIVES_X 8
257 #define MAX_X 75
258 #define MAX_Y 18
261 #elif (LCD_WIDTH == 176) && (LCD_HEIGHT == 132)
263 /* iPod Nano: 176x132x16
264 * ======================
265 * X: No padding. 8p border -> 160p playfield.
267 * LIVES_X 8
268 * ALIEN_WIDTH 8
269 * ALIEN_HEIGHT 5
270 * ALIEN_SPACING 3
271 * SHIP_WIDTH 10
272 * SHIP_HEIGHT 5
273 * FONT_HEIGHT 5
274 * UFO_WIDTH 10
275 * UFO_HEIGHT 5
276 * SHIELD_WIDTH 15
277 * SHIELD_HEIGHT 10
278 * MAX_X 75
279 * MAX_Y = 18
280 * ALIEN_START_Y (UFO_Y + 12)
282 * 8p Aliens with 3p spacing -> 88 + 30 = 118p aliens block.
283 * (160 - 118) / 2 = 21 rounds for whole block (more than original)
284 * MAX_X = (160 - 8) / 2 - 1 = 75 rounds for single alien (less than original)
286 * Y: Scoreline 5 0 (combine scoretext and numbers on same line)
287 * Space 5 5
288 * 1 Ufo 5 10
289 * 3 Space 7 15
290 * 2 aliens 9*5 22 -
291 * space ~7*5 67 | Just above 18 aliens space between
292 * shield 2*5 100 | first alien and ship.
293 * space 5 110 | MAX_Y = 18
294 * ship 5 115 -
295 * space 5 120
296 * hline 1 125 PLAYFIELD_Y
297 * bottom border 6 126
298 * LCD_HEIGHT 131
299 * Lives and Level goes inside bottom border
302 #define TINY_GRAPHICS
303 #define PLAYFIELD_X 0
304 #define SHIP_Y (PLAYFIELD_Y - 2 * SHIP_HEIGHT)
305 #define ALIEN_START_Y (UFO_Y + 12)
306 #define SCORENUM_X (PLAYFIELD_X + SCORE_WIDTH + NUMBERS_WIDTH + NUM_SPACING)
307 #define SCORENUM_Y SCORE_Y
308 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 4 * NUMBERS_WIDTH - 3 * NUM_SPACING)
309 #define HISCORE_X (HISCORENUM_X - NUMBERS_WIDTH - NUM_SPACING - HISCORE_WIDTH)
310 #define SHIELD_Y (SHIP_Y - SHIP_HEIGHT - SHIELD_HEIGHT)
311 #define LIVES_X 8
312 #define MAX_X 75
313 #define MAX_Y 18
315 #elif (LCD_WIDTH == 160) && (LCD_HEIGHT == 128)
317 /* iAudio X5, iRiver H10 20Gb, iPod 3g/4g: 160x128x16
318 * ======================================
319 * X: No padding. No border -> 160p playfield.
321 * LIVES_X 0
322 * ALIEN_WIDTH 8
323 * ALIEN_HEIGHT 5
324 * ALIEN_SPACING 3
325 * SHIP_WIDTH 10
326 * SHIP_HEIGHT 5
327 * FONT_HEIGHT 5
328 * UFO_WIDTH 10
329 * UFO_HEIGHT 5
330 * SHIELD_WIDTH 15
331 * SHIELD_HEIGHT 10
332 * MAX_X 75
333 * MAX_Y = 18
334 * ALIEN_START_Y (UFO_Y + 10)
336 * 8p Aliens with 3p spacing -> 88 + 30 = 118p aliens block.
337 * (160 - 118) / 2 = 21 rounds for whole block (more than original)
338 * MAX_X = (160 - 8) / 2 - 1 = 75 rounds for single alien (less than original)
340 * Y: Scoreline 5 0 (combine scoretext and numbers on same line)
341 * Space 5 5
342 * 1 Ufo 5 10
343 * 2 Space 5 15
344 * 8 aliens 9*5 20 -
345 * space ~6*5 65 | Just above 18 aliens space between
346 * shield 2*5 96 | first alien and ship.
347 * space 5 106 | MAX_Y = 18
348 * ship 5 111 -
349 * space 5 116
350 * hline 1 121 PLAYFIELD_Y
351 * bottom border 6 122
352 * LCD_HEIGHT 128
353 * Lives and Level goes inside bottom border
356 #define TINY_GRAPHICS
357 #define PLAYFIELD_X 0
358 #define SHIP_Y (PLAYFIELD_Y - 2 * SHIP_HEIGHT)
359 #define ALIEN_START_Y (UFO_Y + 10)
360 #define SCORENUM_X (PLAYFIELD_X + SCORE_WIDTH + NUMBERS_WIDTH + NUM_SPACING)
361 #define SCORENUM_Y SCORE_Y
362 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 4 * NUMBERS_WIDTH - 3 * NUM_SPACING)
363 #define HISCORE_X (HISCORENUM_X - NUMBERS_WIDTH - NUM_SPACING - HISCORE_WIDTH)
364 #define SHIELD_Y (SHIP_Y - SHIP_HEIGHT - SHIELD_HEIGHT)
365 #define LIVES_X 0
366 #define MAX_X 75
367 #define MAX_Y 18
370 #elif (LCD_WIDTH == 240) && (LCD_HEIGHT == 320)
372 /* Gigabeat: 240x320x16
373 * ======================
374 * X: 8p padding at left/right gives 224p playfield in middle.
375 * 10p "border" gives 204p actual playfield. UFO use full 224p.
376 * Y: Use bottom 240p for playfield and top 80 pixels for logo.
378 * MAX_X = (204 - 12) / 2 - 1 = 95
380 * Y: Score text 7 0 + 80
381 * Space 10 7 + 80
382 * Score 7 17 + 80
383 * Space 8 24 + 80
384 * 3 Ufo 7 32 + 80
385 * 2 Space Aliens start at 32 + 3 * 8 = 56
386 * 0 aliens 9*8 56 -
387 * space ~7*8 128 | 18.75 aliens space between
388 * shield 2*8 182 | first alien and ship.
389 * space 8 198 | MAX_Y = 18
390 * ship 8 206 -
391 * space 2*8 214
392 * hline 1 230 310 - PLAYFIELD_Y
393 * bottom border 10 240 320
394 * Lives and Level goes inside bottom border
397 #define ARCADISH_GRAPHICS
398 #define PLAYFIELD_X 8
399 #define SHIP_Y (PLAYFIELD_Y - 3 * SHIP_HEIGHT)
400 #define ALIEN_START_Y (UFO_Y + 3 * ALIEN_HEIGHT)
401 /* Redefine SCORE_Y */
402 #undef SCORE_Y
403 #define SCORE_Y 80
404 #define SCORENUM_X (PLAYFIELD_X + NUMBERS_WIDTH)
405 #define SCORENUM_Y SCORE_Y + (2 * (FONT_HEIGHT + 1) + 1)
406 #define HISCORE_X (LCD_WIDTH - PLAYFIELD_X - HISCORE_WIDTH)
407 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 1 - 6 * NUMBERS_WIDTH - 5 * NUM_SPACING)
408 #define SHIELD_Y (PLAYFIELD_Y - 6 * SHIP_HEIGHT)
409 #define LIVES_X 10
410 #define MAX_X 95
411 #define MAX_Y 18
413 #elif (LCD_WIDTH == 220) && (LCD_HEIGHT == 176)
415 /* TPJ1022, H300, iPod Color: 220x176x16
416 * ============================
417 * X: 0p padding at left/right gives 220p playfield in middle.
418 * 8p "border" gives 204p actual playfield. UFO use full 220p.
419 * Y: Use full 176p for playfield.
421 * MAX_X = (204 - 12) / 2 - 1 = 95
423 * Y: Score text 7 0
424 * Space 8 7
425 * 1 Ufo 7 15
426 * 7 Space Aliens start at 15 + 3 * 8 = 56
427 * 6 aliens 9*8 25 -
428 * space ~7*8 103 | 15.6 aliens space between
429 * shield 2*8 126 | first alien and ship.
430 * space 8 142 | MAX_Y = 15
431 * ship 8 150 -
432 * space 8 158
433 * hline 1 166 - PLAYFIELD_Y
434 * bottom border 10 176
435 * Lives and Level goes inside bottom border
438 #define ARCADISH_GRAPHICS
439 #define PLAYFIELD_X 0
440 #define SHIP_Y (PLAYFIELD_Y - 2 * SHIP_HEIGHT)
441 #define ALIEN_START_Y (UFO_Y + 10)
442 #define SCORENUM_Y SCORE_Y
443 #define SCORENUM_X (PLAYFIELD_X + SCORE_WIDTH + NUMBERS_WIDTH + NUM_SPACING)
444 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 4 * NUMBERS_WIDTH - 3 * NUM_SPACING)
445 #define HISCORE_X (HISCORENUM_X - NUMBERS_WIDTH - NUM_SPACING - HISCORE_WIDTH)
446 #define SHIELD_Y (PLAYFIELD_Y - 5 * SHIP_HEIGHT)
447 #define LIVES_X 8
448 #define MAX_X 95
449 #define MAX_Y 15
451 #else
452 #error INVADROX: Unsupported LCD type
453 #endif
456 /* Defines common to each "graphic type" */
457 #ifdef ARCADISH_GRAPHICS
459 #define STRIDE 71
460 #define SHIP_SRC_X 24
461 #define SHIP_WIDTH 16
462 #define SHIP_HEIGHT 8
463 #define SHOT_HEIGHT 5
464 #define ALIEN_WIDTH 12
465 #define ALIEN_EXPLODE_SRC_X 52
466 #define ALIEN_EXPLODE_SRC_Y 39
467 #define ALIEN_EXPLODE_WIDTH 13
468 #define ALIEN_EXPLODE_HEIGHT 7
469 #define ALIEN_HEIGHT 8
470 #define ALIEN_SPACING 4
471 #define ALIEN_SPEED 2
472 #define UFO_SRC_X 40
473 #define UFO_WIDTH 16
474 #define UFO_HEIGHT 7
475 #define UFO_EXPLODE_WIDTH 21
476 #define UFO_EXPLODE_HEIGHT 8
477 #define UFO_SPEED 1
478 #define FONT_HEIGHT 7
479 #define LEVEL_SRC_Y 24
480 #define LEVEL_WIDTH 37
481 #define SCORE_SRC_X 24
482 #define SCORE_SRC_Y 31
483 #define SCORE_WIDTH 37
484 #define HISCORE_WIDTH 61
485 #define NUM_SPACING 3
486 #define NUMBERS_SRC_Y 38
487 #define NUMBERS_WIDTH 5
488 #define SHIELD_SRC_X 40
489 #define SHIELD_SRC_Y 15
490 #define SHIELD_WIDTH 22
491 #define SHIELD_HEIGHT 16
492 #define FIRE_WIDTH 8
493 #define FIRE_HEIGHT 8
494 #define FIRE_SPEED 8
495 #define BOMB_SRC_X 62
496 #define BOMB_WIDTH 3
497 #define BOMB_HEIGHT 7
498 #define BOMB_SPEED 3
499 #define ALIENS 11
500 unsigned char fire_sprite[FIRE_HEIGHT] = {
501 (1 << 7) | (0 << 6) | (0 << 5) | (0 << 4) | (1 << 3) | (0 << 2) | (0 << 1) | 1,
502 (0 << 7) | (0 << 6) | (1 << 5) | (0 << 4) | (0 << 3) | (0 << 2) | (1 << 1) | 0,
503 (0 << 7) | (1 << 6) | (1 << 5) | (1 << 4) | (1 << 3) | (1 << 2) | (1 << 1) | 0,
504 (1 << 7) | (1 << 6) | (1 << 5) | (1 << 4) | (1 << 3) | (1 << 2) | (1 << 1) | 1,
505 (0 << 7) | (1 << 6) | (1 << 5) | (1 << 4) | (1 << 3) | (1 << 2) | (1 << 1) | 1,
506 (0 << 7) | (1 << 6) | (1 << 5) | (1 << 4) | (1 << 3) | (1 << 2) | (1 << 1) | 0,
507 (0 << 7) | (0 << 6) | (1 << 5) | (0 << 4) | (0 << 3) | (1 << 2) | (0 << 1) | 0,
508 (1 << 7) | (0 << 6) | (0 << 5) | (1 << 4) | (0 << 3) | (0 << 2) | (0 << 1) | 1
511 #elif defined TINY_GRAPHICS
513 #define STRIDE 53
514 #define SHIP_SRC_X 16
515 #define SHIP_WIDTH 10
516 #define SHIP_HEIGHT 5
517 #define SHOT_HEIGHT 4
518 #define ALIEN_WIDTH 8
519 #define ALIEN_HEIGHT 5
520 #define ALIEN_EXPLODE_SRC_X 40
521 #define ALIEN_EXPLODE_SRC_Y 26
522 #define ALIEN_EXPLODE_WIDTH 10
523 #define ALIEN_EXPLODE_HEIGHT 5
524 #define ALIEN_SPACING 3
525 #define ALIEN_SPEED 2
526 #define UFO_SRC_X 26
527 #define UFO_WIDTH 11
528 #define UFO_HEIGHT 5
529 #define UFO_EXPLODE_WIDTH 14
530 #define UFO_EXPLODE_HEIGHT 5
531 #define UFO_SPEED 1
532 #define FONT_HEIGHT 5
533 #define LEVEL_SRC_Y 15
534 #define LEVEL_WIDTH 29
535 #define NUMBERS_WIDTH 4
536 #define NUM_SPACING 2
537 #define SCORE_SRC_X 17
538 #define SCORE_SRC_Y 20
539 #define SCORE_WIDTH 28
540 #define HISCORE_WIDTH 45
541 #define NUMBERS_SRC_Y 25
542 #define SHIELD_SRC_X 29
543 #define SHIELD_SRC_Y 10
544 #define SHIELD_WIDTH 15
545 #define SHIELD_HEIGHT 10
546 #define FIRE_WIDTH 6
547 #define FIRE_HEIGHT 6
548 #define FIRE_SPEED 6
549 #define BOMB_SRC_X 44
550 #define BOMB_WIDTH 3
551 #define BOMB_HEIGHT 5
552 #define BOMB_SPEED 2
553 #define ALIENS 11
554 unsigned char fire_sprite[FIRE_HEIGHT] = {
555 (1 << 5) | (0 << 4) | (0 << 3) | (1 << 2) | (0 << 1) | 1,
556 (0 << 5) | (1 << 4) | (0 << 3) | (0 << 2) | (0 << 1) | 0,
557 (0 << 5) | (1 << 4) | (1 << 3) | (1 << 2) | (1 << 1) | 0,
558 (0 << 5) | (1 << 4) | (1 << 3) | (1 << 2) | (1 << 1) | 1,
559 (0 << 5) | (1 << 4) | (0 << 3) | (0 << 2) | (1 << 1) | 0,
560 (1 << 5) | (0 << 4) | (1 << 3) | (0 << 2) | (0 << 1) | 1
563 #else
564 #error Graphic type not defined
565 #endif
568 /* Colors */
569 #if (LCD_DEPTH >= 8)
570 #define SLIME_GREEN LCD_RGBPACK(31, 254, 31)
571 #define UFO_RED LCD_RGBPACK(254, 31, 31)
572 #elif (LCD_DEPTH == 2)
573 #define SLIME_GREEN LCD_LIGHTGRAY
574 #define UFO_RED LCD_LIGHTGRAY
575 #else
576 #error LCD type not implemented yet
577 #endif
579 /* Alien states */
580 #define DEAD 0
581 #define ALIVE 1
582 #define BOMBER 2
584 /* Fire/bomb/ufo states */
585 #define S_IDLE 0
586 #define S_ACTIVE 1
587 #define S_SHOWSCORE 2
588 #define S_EXPLODE -9
590 /* Fire/bomb targets */
591 #define TARGET_TOP 0
592 #define TARGET_SHIELD 1
593 #define TARGET_SHIP 2
594 #define TARGET_BOTTOM 3
595 #define TARGET_UFO 4
597 #define HISCOREFILE PLUGIN_GAMES_DIR "/invadrox.high"
600 /* The time (in ms) for one iteration through the game loop - decrease this
601 * to speed up the game - note that current_tick is (currently) only accurate
602 * to 10ms.
604 #define CYCLETIME 40
607 static const struct plugin_api* rb;
609 /* Physical x is at PLAYFIELD_X + LIVES_X + x * ALIEN_SPEED
610 * Physical y is at y * ALIEN_HEIGHT
612 struct alien {
613 unsigned char x; /* x-coordinate (0 - 95) */
614 unsigned char y; /* y-coordinate (0 - 18) */
615 unsigned char type; /* 0 (Kang), 1 (Kodos), 2 (Serak) */
616 unsigned char state; /* Dead, alive or bomber */
619 /* Aliens box 5 rows * ALIENS aliens in each row */
620 struct alien aliens[5 * ALIENS];
622 #define MAX_BOMBS 4
623 struct bomb {
624 int x, y;
625 unsigned char type;
626 unsigned char frame; /* Current animation frame */
627 unsigned char frames; /* Number of frames in animation */
628 unsigned char target; /* Remember target during explosion frames */
629 int state; /* 0 (IDLE) = inactive, 1 (FIRE) or negative, exploding */
631 struct bomb bombs[MAX_BOMBS];
632 /* Increase max_bombs at higher levels */
633 int max_bombs;
635 /* Raw framebuffer value of shield/ship green color */
636 fb_data screen_green, screen_white;
638 /* For optimization, precalculate startoffset of each scanline */
639 unsigned int ytab[LCD_HEIGHT];
641 /* external bitmaps */
642 extern const fb_data invadrox[];
643 #if (LCD_WIDTH == 320) && (LCD_HEIGHT == 240)
644 /* iPod Video only */
645 extern const fb_data invadrox_left[];
646 extern const fb_data invadrox_right[];
647 #endif
648 #if ((LCD_WIDTH == 240) && (LCD_HEIGHT == 320)) || ((LCD_WIDTH == 176) && (LCD_HEIGHT == 220))
649 /* Gigabeat F, Sansa e200 */
650 extern const fb_data invadrox_logo[];
651 #endif
654 int lives = 2;
655 int score = 0;
656 int scores[3] = { 30, 20, 10 };
657 int level = 0;
658 struct highscore hiscore;
659 bool game_over = false;
660 int ship_x, old_ship_x, ship_dir, ship_acc, max_ship_speed;
661 int ship_frame, ship_frame_counter;
662 bool ship_hit;
663 int fire, fire_target, fire_x, fire_y;
664 int curr_alien, aliens_paralyzed, gamespeed;
665 int ufo_state, ufo_x;
666 bool level_finished;
667 bool aliens_down, aliens_right, hit_left_border, hit_right_border;
670 /* No standard get_pixel function yet, use this hack instead */
671 #if (LCD_DEPTH >= 8)
673 inline fb_data get_pixel(int x, int y)
675 return rb->lcd_framebuffer[ytab[y] + x];
678 #elif (LCD_DEPTH == 2)
680 #if (LCD_PIXELFORMAT == HORIZONTAL_PACKING)
681 static const unsigned char shifts[4] = {
682 6, 4, 2, 0
684 /* Horizontal packing */
685 inline fb_data get_pixel(int x, int y)
687 return (rb->lcd_framebuffer[ytab[y] + (x >> 2)] >> shifts[x & 3]) & 3;
689 #else
690 /* Vertical packing */
691 static const unsigned char shifts[4] = {
692 0, 2, 4, 6
694 inline fb_data get_pixel(int x, int y)
696 return (rb->lcd_framebuffer[ytab[y] + x] >> shifts[y & 3]) & 3;
698 #endif /* Horizontal/Vertical packing */
700 #else
701 #error get_pixel: pixelformat not implemented yet
702 #endif
705 /* Draw "digits" least significant digits of num at (x,y) */
706 void draw_number(int x, int y, int num, int digits)
708 int i;
709 int d;
711 for (i = digits - 1; i >= 0; i--) {
712 d = num % 10;
713 num = num / 10;
714 rb->lcd_bitmap_part(invadrox, d * NUMBERS_WIDTH, NUMBERS_SRC_Y,
715 STRIDE, x + i * (NUMBERS_WIDTH + NUM_SPACING), y,
716 NUMBERS_WIDTH, FONT_HEIGHT);
718 /* Update lcd */
719 rb->lcd_update_rect(x, y, 4 * NUMBERS_WIDTH + 3 * NUM_SPACING, FONT_HEIGHT);
723 inline void draw_score(void)
725 draw_number(SCORENUM_X, SCORENUM_Y, score, 4);
726 if (score > hiscore.score) {
727 /* Draw new hiscore (same as score) */
728 draw_number(HISCORENUM_X, SCORENUM_Y, score, 4);
733 void draw_level(void)
735 rb->lcd_bitmap_part(invadrox, 0, LEVEL_SRC_Y,
736 STRIDE, LEVEL_X, PLAYFIELD_Y + 2,
737 LEVEL_WIDTH, FONT_HEIGHT);
738 draw_number(LEVEL_X + LEVEL_WIDTH + 2 * NUM_SPACING, PLAYFIELD_Y + 2, level, 2);
742 void draw_lives(void)
744 int i;
745 /* Lives num */
746 rb->lcd_bitmap_part(invadrox, lives * NUMBERS_WIDTH, NUMBERS_SRC_Y,
747 STRIDE, PLAYFIELD_X + LIVES_X, PLAYFIELD_Y + 2,
748 NUMBERS_WIDTH, FONT_HEIGHT);
750 /* Ships */
751 for (i = 0; i < (lives - 1); i++) {
752 rb->lcd_bitmap_part(invadrox, SHIP_SRC_X, 0, STRIDE,
753 PLAYFIELD_X + LIVES_X + SHIP_WIDTH + i * (SHIP_WIDTH + NUM_SPACING),
754 PLAYFIELD_Y + 1, SHIP_WIDTH, SHIP_HEIGHT);
757 /* Erase ship to the righ (if less than MAX_LIVES) */
758 if (lives < MAX_LIVES) {
759 rb->lcd_fillrect(PLAYFIELD_X + LIVES_X + SHIP_WIDTH + i * (SHIP_WIDTH + NUM_SPACING),
760 PLAYFIELD_Y + 1, SHIP_WIDTH, SHIP_HEIGHT);
762 /* Update lives (and level) part of screen */
763 rb->lcd_update_rect(PLAYFIELD_X + LIVES_X, PLAYFIELD_Y + 1,
764 PLAYFIELD_WIDTH - 2 * LIVES_X, MAX(FONT_HEIGHT + 1, SHIP_HEIGHT + 1));
768 inline void draw_aliens(void)
770 int i;
772 for (i = 0; i < 5 * ALIENS; i++) {
773 rb->lcd_bitmap_part(invadrox, aliens[i].x & 1 ? ALIEN_WIDTH : 0, aliens[i].type * ALIEN_HEIGHT,
774 STRIDE, PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED,
775 ALIEN_START_Y + aliens[i].y * ALIEN_HEIGHT,
776 ALIEN_WIDTH, ALIEN_HEIGHT);
781 /* Return false if there is no next alive alien (round is over) */
782 inline bool next_alien(void)
784 bool ret = true;
786 do {
787 curr_alien++;
788 if (curr_alien % ALIENS == 0) {
789 /* End of this row. Move up one row. */
790 curr_alien -= 2 * ALIENS;
791 if (curr_alien < 0) {
792 /* No more aliens in this round. */
793 curr_alien = 4 * ALIENS;
794 ret = false;
797 } while (aliens[curr_alien].state == DEAD && ret);
799 if (!ret) {
800 /* No more alive aliens. Round finished. */
801 if (hit_right_border) {
802 if (hit_left_border) {
803 DBG("ERROR: both left and right borders are set (%d)\n", curr_alien);
805 /* Move down-left next round */
806 aliens_right = false;
807 aliens_down = true;
808 hit_right_border = false;
809 } else if (hit_left_border) {
810 /* Move down-right next round */
811 aliens_right = true;
812 aliens_down = true;
813 hit_left_border = false;
814 } else {
815 /* Not left nor right. Set down to false. */
816 aliens_down = false;
820 return ret;
824 /* All aliens have been moved.
825 * Set curr_alien to first alive.
826 * Return false if no-one is left alive.
828 bool first_alien(void)
830 int i, y;
832 for (y = 4; y >= 0; y--) {
833 for (i = y * ALIENS; i < (y + 1) * ALIENS; i++) {
834 if (aliens[i].state != DEAD) {
835 curr_alien = i;
836 return true;
841 /* All aliens dead. */
842 level_finished = true;
844 return false;
848 bool move_aliens(void)
850 int x, y, old_x, old_y;
852 /* Move current alien (curr_alien is pointing to a living alien) */
854 old_x = aliens[curr_alien].x;
855 old_y = aliens[curr_alien].y;
857 if (aliens_down) {
858 aliens[curr_alien].y++;
859 if (aliens[curr_alien].y == MAX_Y) {
860 /* Alien is at bottom. Game Over. */
861 DBG("Alien %d is at bottom. Game Over.\n", curr_alien);
862 game_over = true;
863 return false;
867 if (aliens_right) {
868 /* Moving right */
869 if (aliens[curr_alien].x < MAX_X) {
870 aliens[curr_alien].x++;
873 /* Now, after move, check if we hit the right border. */
874 if (aliens[curr_alien].x == MAX_X) {
875 hit_right_border = true;
878 } else {
879 /* Moving left */
880 if (aliens[curr_alien].x > 0) {
881 aliens[curr_alien].x--;
884 /* Now, after move, check if we hit the left border. */
885 if (aliens[curr_alien].x == 0) {
886 hit_left_border = true;
890 /* Erase old position */
891 x = PLAYFIELD_X + LIVES_X + old_x * ALIEN_SPEED;
892 y = ALIEN_START_Y + old_y * ALIEN_HEIGHT;
893 if (aliens[curr_alien].y != old_y) {
894 /* Moved in y-dir. Erase whole alien. */
895 rb->lcd_fillrect(x, y, ALIEN_WIDTH, ALIEN_HEIGHT);
896 } else {
897 if (aliens_right) {
898 /* Erase left edge */
899 rb->lcd_fillrect(x, y, ALIEN_SPEED, ALIEN_HEIGHT);
900 } else {
901 /* Erase right edge */
902 x += ALIEN_WIDTH - ALIEN_SPEED;
903 rb->lcd_fillrect(x, y, ALIEN_SPEED, ALIEN_HEIGHT);
907 /* Draw alien at new pos */
908 x = PLAYFIELD_X + LIVES_X + aliens[curr_alien].x * ALIEN_SPEED;
909 y = ALIEN_START_Y + aliens[curr_alien].y * ALIEN_HEIGHT;
910 rb->lcd_bitmap_part(invadrox,
911 aliens[curr_alien].x & 1 ? ALIEN_WIDTH : 0, aliens[curr_alien].type * ALIEN_HEIGHT,
912 STRIDE, x, y, ALIEN_WIDTH, ALIEN_HEIGHT);
914 if (!next_alien()) {
915 /* Round finished. Set curr_alien to first alive from bottom. */
916 if (!first_alien()) {
917 /* Should never happen. Taken care of in move_fire(). */
918 return false;
920 /* TODO: Play next background sound */
923 return true;
927 inline void draw_ship(void)
929 /* Erase old ship */
930 if (old_ship_x < ship_x) {
931 /* Move right. Erase leftmost part of ship. */
932 rb->lcd_fillrect(old_ship_x, SHIP_Y, ship_x - old_ship_x, SHIP_HEIGHT);
933 } else if (old_ship_x > ship_x) {
934 /* Move left. Erase rightmost part of ship. */
935 rb->lcd_fillrect(ship_x + SHIP_WIDTH, SHIP_Y, old_ship_x - ship_x, SHIP_HEIGHT);
938 /* Draw ship */
939 rb->lcd_bitmap_part(invadrox, SHIP_SRC_X, ship_frame * SHIP_HEIGHT,
940 STRIDE, ship_x, SHIP_Y, SHIP_WIDTH, SHIP_HEIGHT);
941 if (ship_hit) {
942 /* Alternate between frame 1 and 2 during hit */
943 ship_frame_counter++;
944 if (ship_frame_counter > 2) {
945 ship_frame_counter = 0;
946 ship_frame++;
947 if (ship_frame > 2) {
948 ship_frame = 1;
953 /* Save ship_x for next time */
954 old_ship_x = ship_x;
958 inline void fire_alpha(int xc, int yc, fb_data color)
960 int x, y;
961 unsigned char mask;
963 rb->lcd_set_foreground(color);
965 for (y = 0; y < FIRE_HEIGHT; y++) {
966 mask = 1 << (FIRE_WIDTH - 1);
967 for (x = -(FIRE_WIDTH / 2); x < (FIRE_WIDTH / 2); x++) {
968 if (fire_sprite[y] & mask) {
969 rb->lcd_drawpixel(xc + x, yc + y);
971 mask >>= 1;
975 rb->lcd_set_foreground(LCD_BLACK);
979 void move_fire(void)
981 bool hit_green = false;
982 bool hit_white = false;
983 int i, j;
984 static int exploding_alien = -1;
985 fb_data pix;
987 if (fire == S_IDLE) {
988 return;
991 /* Alien hit. Wait until explosion is finished. */
992 if (aliens_paralyzed < 0) {
993 aliens_paralyzed++;
994 if (aliens_paralyzed == 0) {
995 /* Erase exploding_alien */
996 rb->lcd_fillrect(PLAYFIELD_X + LIVES_X + aliens[exploding_alien].x * ALIEN_SPEED,
997 ALIEN_START_Y + aliens[exploding_alien].y * ALIEN_HEIGHT,
998 ALIEN_EXPLODE_WIDTH, ALIEN_HEIGHT);
999 fire = S_IDLE;
1000 /* Special case. We killed curr_alien. */
1001 if (exploding_alien == curr_alien) {
1002 if (!next_alien()) {
1003 /* Round finished. Set curr_alien to first alive from bottom. */
1004 first_alien();
1008 return;
1011 if (fire == S_ACTIVE) {
1013 /* Erase */
1014 rb->lcd_vline(fire_x, fire_y, fire_y + SHOT_HEIGHT);
1016 /* Check top */
1017 if (fire_y <= SCORENUM_Y + FONT_HEIGHT + 4) {
1019 /* TODO: Play explode sound */
1021 fire = S_EXPLODE;
1022 fire_target = TARGET_TOP;
1023 fire_alpha(fire_x, fire_y, UFO_RED);
1024 return;
1027 /* Move */
1028 fire_y -= FIRE_SPEED;
1030 /* Hit UFO? */
1031 if (ufo_state == S_ACTIVE) {
1032 if ((ABS(ufo_x + UFO_WIDTH / 2 - fire_x) <= UFO_WIDTH / 2) &&
1033 (fire_y <= UFO_Y + UFO_HEIGHT)) {
1034 ufo_state = S_EXPLODE;
1035 fire = S_EXPLODE;
1036 fire_target = TARGET_UFO;
1037 /* Center explosion */
1038 ufo_x -= (UFO_EXPLODE_WIDTH - UFO_WIDTH) / 2;
1039 rb->lcd_bitmap_part(invadrox, UFO_SRC_X, UFO_HEIGHT,
1040 STRIDE, ufo_x, UFO_Y - 1, UFO_EXPLODE_WIDTH, UFO_EXPLODE_HEIGHT);
1041 return;
1045 /* Hit bomb? (check position, not pixel value) */
1046 for (i = 0; i < max_bombs; i++) {
1047 if (bombs[i].state == S_ACTIVE) {
1048 /* Count as hit if within BOMB_WIDTH pixels */
1049 if ((ABS(bombs[i].x - fire_x) < BOMB_WIDTH) &&
1050 (fire_y - bombs[i].y < BOMB_HEIGHT)) {
1051 /* Erase bomb */
1052 rb->lcd_fillrect(bombs[i].x, bombs[i].y, BOMB_WIDTH, BOMB_HEIGHT);
1053 bombs[i].state = S_IDLE;
1054 /* Explode ship fire */
1055 fire = S_EXPLODE;
1056 fire_target = TARGET_SHIELD;
1057 fire_alpha(fire_x, fire_y, LCD_WHITE);
1058 return;
1063 /* Check for hit*/
1064 for (i = FIRE_SPEED; i >= 0; i--) {
1065 pix = get_pixel(fire_x, fire_y + i);
1066 if(pix == screen_white) {
1067 hit_white = true;
1068 fire_y += i;
1069 break;
1071 if(pix == screen_green) {
1072 hit_green = true;
1073 fire_y += i;
1074 break;
1078 if (hit_green) {
1079 /* Hit shield */
1081 /* TODO: Play explode sound */
1083 fire = S_EXPLODE;
1084 fire_target = TARGET_SHIELD;
1085 /* Center explosion around hit pixel */
1086 fire_y -= FIRE_HEIGHT / 2;
1087 fire_alpha(fire_x, fire_y, SLIME_GREEN);
1088 return;
1091 if (hit_white) {
1093 /* Hit alien? */
1094 for (i = 0; i < 5 * ALIENS; i++) {
1095 if (aliens[i].state != DEAD &&
1096 (ABS(fire_x - (PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED +
1097 ALIEN_WIDTH / 2)) <= ALIEN_WIDTH / 2) &&
1098 (ABS(fire_y - (ALIEN_START_Y + aliens[i].y * ALIEN_HEIGHT +
1099 ALIEN_HEIGHT / 2)) <= ALIEN_HEIGHT / 2)) {
1101 /* TODO: play alien hit sound */
1103 if (aliens[i].state == BOMBER) {
1104 /* Set (possible) alien above to bomber */
1105 for (j = i - ALIENS; j >= 0; j -= ALIENS) {
1106 if (aliens[j].state != DEAD) {
1107 /* printf("New bomber (%d, %d)\n", j % ALIENS, j / ALIENS); */
1108 aliens[j].state = BOMBER;
1109 break;
1113 aliens[i].state = DEAD;
1114 exploding_alien = i;
1115 score += scores[aliens[i].type];
1116 draw_score();
1117 /* Update score part of screen */
1118 rb->lcd_update_rect(SCORENUM_X, SCORENUM_Y,
1119 PLAYFIELD_WIDTH - 2 * NUMBERS_WIDTH, FONT_HEIGHT);
1121 /* Paralyze aliens S_EXPLODE frames */
1122 aliens_paralyzed = S_EXPLODE;
1123 rb->lcd_bitmap_part(invadrox, ALIEN_EXPLODE_SRC_X, ALIEN_EXPLODE_SRC_Y,
1124 STRIDE, PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED,
1125 ALIEN_START_Y + aliens[i].y * ALIEN_HEIGHT,
1126 ALIEN_EXPLODE_WIDTH, ALIEN_EXPLODE_HEIGHT);
1127 /* Since alien is 1 pixel taller than explosion sprite, erase bottom line */
1128 rb->lcd_hline(PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED,
1129 PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED + ALIEN_WIDTH,
1130 ALIEN_START_Y + (aliens[i].y + 1) * ALIEN_HEIGHT - 1);
1131 return;
1136 /* Draw shot */
1137 rb->lcd_set_foreground(LCD_WHITE);
1138 rb->lcd_vline(fire_x, fire_y, fire_y + SHOT_HEIGHT);
1139 rb->lcd_set_foreground(LCD_BLACK);
1140 } else if (fire < S_IDLE) {
1141 /* Count up towards S_IDLE, then erase explosion */
1142 fire++;
1143 if (fire == S_IDLE) {
1144 /* Erase explosion */
1145 if (fire_target == TARGET_TOP) {
1146 rb->lcd_fillrect(fire_x - (FIRE_WIDTH / 2), fire_y, FIRE_WIDTH, FIRE_HEIGHT);
1147 } else if (fire_target == TARGET_SHIELD) {
1148 /* Draw explosion with black pixels */
1149 fire_alpha(fire_x, fire_y, LCD_BLACK);
1156 /* Return a BOMBER alien */
1157 inline int random_bomber(void)
1159 int i, col;
1161 /* TODO: Weigh higher probability near ship */
1162 col = rb->rand() % ALIENS;
1163 for (i = col + 4 * ALIENS; i >= 0; i -= ALIENS) {
1164 if (aliens[i].state == BOMBER) {
1165 return i;
1169 /* No BOMBER found in this col */
1171 for (i = 0; i < 5 * ALIENS; i++) {
1172 if (aliens[i].state == BOMBER) {
1173 return i;
1177 /* No BOMBER found at all (error?) */
1179 return -1;
1183 inline void draw_bomb(int i)
1185 rb->lcd_bitmap_part(invadrox, BOMB_SRC_X + bombs[i].type * BOMB_WIDTH,
1186 bombs[i].frame * (BOMB_HEIGHT + 1),
1187 STRIDE, bombs[i].x, bombs[i].y,
1188 BOMB_WIDTH, BOMB_HEIGHT);
1189 /* Advance frame */
1190 bombs[i].frame++;
1191 if (bombs[i].frame == bombs[i].frames) {
1192 bombs[i].frame = 0;
1197 void move_bombs(void)
1199 int i, j, bomber;
1200 bool abort;
1202 for (i = 0; i < max_bombs; i++) {
1204 switch (bombs[i].state) {
1206 case S_IDLE:
1207 if (ship_hit) {
1208 continue;
1210 bomber = random_bomber();
1211 if (bomber < 0) {
1212 DBG("ERROR: No bomber available\n");
1213 continue;
1215 /* x, y */
1216 bombs[i].x = PLAYFIELD_X + LIVES_X + aliens[bomber].x * ALIEN_SPEED + ALIEN_WIDTH / 2;
1217 bombs[i].y = ALIEN_START_Y + (aliens[bomber].y + 1) * ALIEN_HEIGHT;
1219 /* Check for duplets in x and y direction */
1220 abort = false;
1221 for (j = i - 1; j >= 0; j--) {
1222 if ((bombs[j].state == S_ACTIVE) &&
1223 ((bombs[i].x == bombs[j].x) || (bombs[i].y == bombs[j].y))) {
1224 abort = true;
1225 break;
1228 if (abort) {
1229 /* Skip this one, continue with next bomb */
1230 /* printf("Bomb %d duplet of %d\n", i, j); */
1231 continue;
1234 /* Passed, set type */
1235 bombs[i].type = rb->rand() % 3;
1236 bombs[i].frame = 0;
1237 if (bombs[i].type == 0) {
1238 bombs[i].frames = 3;
1239 } else if (bombs[i].type == 1) {
1240 bombs[i].frames = 4;
1241 } else {
1242 bombs[i].frames = 6;
1245 /* Bombs away */
1246 bombs[i].state = S_ACTIVE;
1247 draw_bomb(i);
1248 continue;
1250 break;
1252 case S_ACTIVE:
1253 /* Erase old position */
1254 rb->lcd_fillrect(bombs[i].x, bombs[i].y, BOMB_WIDTH, BOMB_HEIGHT);
1256 /* Move */
1257 bombs[i].y += BOMB_SPEED;
1259 /* Check if bottom hit */
1260 if (bombs[i].y + BOMB_HEIGHT >= PLAYFIELD_Y) {
1261 bombs[i].y = PLAYFIELD_Y - FIRE_HEIGHT + 1;
1262 fire_alpha(bombs[i].x, bombs[i].y, LCD_WHITE);
1263 bombs[i].state = S_EXPLODE;
1264 bombs[i].target = TARGET_BOTTOM;
1265 break;
1268 /* Check for green (ship or shield) */
1269 for (j = BOMB_HEIGHT; j >= BOMB_HEIGHT - BOMB_SPEED; j--) {
1270 bombs[i].target = 0;
1271 if(get_pixel(bombs[i].x + BOMB_WIDTH / 2, bombs[i].y + j) == screen_green) {
1272 /* Move to hit pixel */
1273 bombs[i].x += BOMB_WIDTH / 2;
1274 bombs[i].y += j;
1276 /* Check if ship is hit */
1277 if (bombs[i].y > SHIELD_Y + SHIELD_HEIGHT && bombs[i].y < PLAYFIELD_Y) {
1279 /* TODO: play ship hit sound */
1281 ship_hit = true;
1282 ship_frame = 1;
1283 ship_frame_counter = 0;
1284 bombs[i].state = S_EXPLODE * 4;
1285 bombs[i].target = TARGET_SHIP;
1286 rb->lcd_bitmap_part(invadrox, SHIP_SRC_X, 1 * SHIP_HEIGHT, STRIDE,
1287 ship_x, SHIP_Y, SHIP_WIDTH, SHIP_HEIGHT);
1288 break;
1290 /* Shield hit */
1291 bombs[i].state = S_EXPLODE;
1292 bombs[i].target = TARGET_SHIELD;
1293 /* Center explosion around hit pixel in shield */
1294 bombs[i].y -= FIRE_HEIGHT / 2;
1295 fire_alpha(bombs[i].x, bombs[i].y, SLIME_GREEN);
1296 break;
1300 if (bombs[i].target != 0) {
1301 /* Hit ship or shield, continue */
1302 continue;
1305 draw_bomb(i);
1306 break;
1308 default:
1309 /* If we get here state should be < 0, exploding */
1310 bombs[i].state++;
1311 if (bombs[i].state == S_IDLE) {
1312 if (ship_hit) {
1313 /* Erase explosion */
1314 rb->lcd_fillrect(ship_x, SHIP_Y, SHIP_WIDTH, SHIP_HEIGHT);
1315 rb->lcd_update_rect(ship_x, SHIP_Y, SHIP_WIDTH, SHIP_HEIGHT);
1316 ship_hit = false;
1317 ship_frame = 0;
1318 ship_x = PLAYFIELD_X + 2 * LIVES_X;
1319 lives--;
1320 if (lives == 0) {
1321 game_over = true;
1322 return;
1324 draw_lives();
1325 /* Sleep 1s to give player time to examine lives left */
1326 rb->sleep(HZ);
1328 /* Erase explosion (even if ship hit, might be another bomb) */
1329 fire_alpha(bombs[i].x, bombs[i].y, LCD_BLACK);
1331 break;
1337 inline void move_ship(void)
1339 ship_dir += ship_acc;
1340 if (ship_dir > max_ship_speed) {
1341 ship_dir = max_ship_speed;
1343 if (ship_dir < -max_ship_speed) {
1344 ship_dir = -max_ship_speed;
1346 ship_x += ship_dir;
1347 if (ship_x < SHIP_MIN_X) {
1348 ship_x = SHIP_MIN_X;
1350 if (ship_x > SHIP_MAX_X) {
1351 ship_x = SHIP_MAX_X;
1354 draw_ship();
1358 /* Unidentified Flying Object */
1359 void move_ufo(void)
1361 static int ufo_speed;
1362 static int counter;
1363 int mystery_score;
1365 switch (ufo_state) {
1367 case S_IDLE:
1369 if (rb->rand() % 500 == 0) {
1370 /* Uh-oh, it's time to launch a mystery UFO */
1372 /* TODO: Play UFO sound */
1374 if (rb->rand() % 2) {
1375 ufo_speed = UFO_SPEED;
1376 ufo_x = PLAYFIELD_X;
1377 } else {
1378 ufo_speed = -UFO_SPEED;
1379 ufo_x = LCD_WIDTH - PLAYFIELD_X - UFO_WIDTH;
1381 ufo_state = S_ACTIVE;
1382 /* UFO will be drawn next frame */
1384 break;
1386 case S_ACTIVE:
1387 /* Erase old pos */
1388 rb->lcd_fillrect(ufo_x, UFO_Y, UFO_WIDTH, UFO_HEIGHT);
1389 /* Move */
1390 ufo_x += ufo_speed;
1391 /* Check bounds */
1392 if (ufo_x < PLAYFIELD_X || ufo_x > LCD_WIDTH - PLAYFIELD_X - UFO_WIDTH) {
1393 ufo_state = S_IDLE;
1394 break;
1396 /* Draw new pos */
1397 rb->lcd_bitmap_part(invadrox, UFO_SRC_X, 0,
1398 STRIDE, ufo_x, UFO_Y, UFO_WIDTH, UFO_HEIGHT);
1399 break;
1401 case S_SHOWSCORE:
1402 counter++;
1403 if (counter == S_IDLE) {
1404 /* Erase mystery number */
1405 rb->lcd_fillrect(ufo_x, UFO_Y, 3 * NUMBERS_WIDTH + 2 * NUM_SPACING, FONT_HEIGHT);
1406 ufo_state = S_IDLE;
1408 break;
1410 default:
1411 /* Exploding */
1412 ufo_state++;
1413 if (ufo_state == S_IDLE) {
1414 /* Erase explosion */
1415 rb->lcd_fillrect(ufo_x, UFO_Y - 1, UFO_EXPLODE_WIDTH, UFO_EXPLODE_HEIGHT);
1416 ufo_state = S_SHOWSCORE;
1417 counter = S_EXPLODE * 4;
1418 /* Draw mystery_score, sleep, increase score and continue */
1419 mystery_score = 50 + (rb->rand() % 6) * 50;
1420 if (mystery_score < 100) {
1421 draw_number(ufo_x, UFO_Y, mystery_score, 2);
1422 } else {
1423 draw_number(ufo_x, UFO_Y, mystery_score, 3);
1425 score += mystery_score;
1426 draw_score();
1428 break;
1433 void draw_background(void)
1436 #if (LCD_WIDTH == 320) && (LCD_HEIGHT == 240)
1437 /* Erase background to black */
1438 rb->lcd_fillrect(PLAYFIELD_X, 0, PLAYFIELD_WIDTH, LCD_HEIGHT);
1439 /* Left and right bitmaps */
1440 rb->lcd_bitmap(invadrox_left, 0, 0, PLAYFIELD_X, LCD_HEIGHT);
1441 rb->lcd_bitmap(invadrox_right, LCD_WIDTH - PLAYFIELD_X, 0, PLAYFIELD_X, LCD_HEIGHT);
1442 #else
1443 rb->lcd_fillrect(0, 0, LCD_WIDTH, LCD_HEIGHT);
1444 #endif
1446 #if ((LCD_WIDTH == 240) && (LCD_HEIGHT == 320)) || ((LCD_WIDTH == 176) && (LCD_HEIGHT == 220))
1447 rb->lcd_bitmap(invadrox_logo, 0, 0, LCD_WIDTH, SCORE_Y);
1448 #endif
1450 rb->lcd_update();
1454 void new_level(void)
1456 int i;
1458 draw_background();
1459 /* Give an extra life for each new level */
1460 if (lives < MAX_LIVES) {
1461 lives++;
1463 draw_lives();
1465 /* Score */
1466 rb->lcd_bitmap_part(invadrox, SCORE_SRC_X, SCORE_SRC_Y,
1467 STRIDE, PLAYFIELD_X, SCORE_Y, SCORE_WIDTH, FONT_HEIGHT);
1468 /* Hi-score */
1469 rb->lcd_bitmap_part(invadrox, 0, SCORE_SRC_Y,
1470 STRIDE, HISCORE_X, SCORE_Y,
1471 HISCORE_WIDTH, FONT_HEIGHT);
1472 draw_score();
1473 draw_number(HISCORENUM_X, SCORENUM_Y, hiscore.score, 4);
1475 level++;
1476 draw_level();
1477 level_finished = false;
1479 ufo_state = S_IDLE;
1481 /* Init alien positions and states */
1482 for (i = 0; i < 4 * ALIENS; i++) {
1483 aliens[i].x = 0 + (i % ALIENS) * ((ALIEN_WIDTH + ALIEN_SPACING) / ALIEN_SPEED);
1484 aliens[i].y = 2 * (i / ALIENS);
1485 aliens[i].state = ALIVE;
1487 /* Last row, bombers */
1488 for (i = 4 * ALIENS; i < 5 * ALIENS; i++) {
1489 aliens[i].x = 0 + (i % ALIENS) * ((ALIEN_WIDTH + ALIEN_SPACING) / ALIEN_SPEED);
1490 aliens[i].y = 2 * (i / ALIENS);
1491 aliens[i].state = BOMBER;
1494 /* Init bombs to inactive (S_IDLE) */
1495 for (i = 0; i < MAX_BOMBS; i++) {
1496 bombs[i].state = S_IDLE;
1499 /* Start aliens closer to earth from level 2 */
1500 for (i = 0; i < 5 * ALIENS; i++) {
1501 if (level < 6) {
1502 aliens[i].y += level - 1;
1503 } else {
1504 aliens[i].y += 5;
1508 /* Max concurrent bombs */
1509 max_bombs = 1;
1511 gamespeed = 2;
1513 if (level > 1) {
1514 max_bombs++;
1517 /* Increase speed */
1518 if (level > 2) {
1519 gamespeed++;
1522 if (level > 3) {
1523 max_bombs++;
1526 /* Increase speed more */
1527 if (level > 4) {
1528 gamespeed++;
1531 if (level > 5) {
1532 max_bombs++;
1535 /* 4 shields */
1536 for (i = 1; i <= 4; i++) {
1537 rb->lcd_bitmap_part(invadrox, SHIELD_SRC_X, SHIELD_SRC_Y, STRIDE,
1538 PLAYFIELD_X + i * PLAYFIELD_WIDTH / 5 - SHIELD_WIDTH / 2,
1539 SHIELD_Y, SHIELD_WIDTH, SHIELD_HEIGHT);
1542 /* Bottom line */
1543 rb->lcd_set_foreground(SLIME_GREEN);
1544 rb->lcd_hline(PLAYFIELD_X, LCD_WIDTH - PLAYFIELD_X, PLAYFIELD_Y);
1545 /* Restore foreground to black (for fast erase later). */
1546 rb->lcd_set_foreground(LCD_BLACK);
1548 ship_x = PLAYFIELD_X + 2 * LIVES_X;
1549 if (level == 1) {
1550 old_ship_x = ship_x;
1552 ship_dir = 0;
1553 ship_acc = 0;
1554 ship_frame = 0;
1555 ship_hit = false;
1556 fire = S_IDLE;
1557 /* Start moving the bottom row left to right */
1558 curr_alien = 4 * ALIENS;
1559 aliens_paralyzed = 0;
1560 aliens_right = true;
1561 aliens_down = false;
1562 hit_left_border = false;
1563 hit_right_border = false;
1564 /* TODO: Change max_ship_speed to 3 at higher levels */
1565 max_ship_speed = 2;
1567 draw_aliens();
1569 rb->lcd_update();
1573 void init_invadrox(void)
1575 int i;
1577 /* Seed random number generator with a "random" number */
1578 rb->srand(rb->get_time()->tm_sec + rb->get_time()->tm_min * 60);
1580 /* Precalculate start of each scanline */
1581 for (i = 0; i < LCD_HEIGHT; i++) {
1582 #if (LCD_DEPTH >= 8)
1583 ytab[i] = i * LCD_WIDTH;
1584 #elif (LCD_DEPTH == 2) && (LCD_PIXELFORMAT == HORIZONTAL_PACKING)
1585 ytab[i] = i * (LCD_WIDTH / 4);
1586 #elif (LCD_DEPTH == 2) && (LCD_PIXELFORMAT == VERTICAL_PACKING)
1587 ytab[i] = (i / 4) * LCD_WIDTH;
1588 #else
1589 #error pixelformat not implemented yet
1590 #endif
1593 rb->lcd_set_background(LCD_BLACK);
1594 rb->lcd_set_foreground(LCD_BLACK);
1596 highscore_init(rb);
1597 if (highscore_load(HISCOREFILE, &hiscore, 1) < 0) {
1598 /* Init hiscore to 0 */
1599 rb->strncpy(hiscore.name, "Invader", sizeof(hiscore.name));
1600 hiscore.score = 0;
1601 hiscore.level = 1;
1604 /* Init alien types in aliens array */
1605 for (i = 0; i < 1 * ALIENS; i++) {
1606 aliens[i].type = 0; /* Kang */
1608 for (; i < 3 * ALIENS; i++) {
1609 aliens[i].type = 1; /* Kodos */
1611 for (; i < 5 * ALIENS; i++) {
1612 aliens[i].type = 2; /* Serak */
1616 /* Save screen white color */
1617 rb->lcd_set_foreground(LCD_WHITE);
1618 rb->lcd_drawpixel(0, 0);
1619 rb->lcd_update_rect(0, 0, 1, 1);
1620 screen_white = get_pixel(0, 0);
1622 /* Save screen green color */
1623 rb->lcd_set_foreground(SLIME_GREEN);
1624 rb->lcd_drawpixel(0, 0);
1625 rb->lcd_update_rect(0, 0, 1, 1);
1626 screen_green = get_pixel(0, 0);
1628 /* Restore black foreground */
1629 rb->lcd_set_foreground(LCD_BLACK);
1631 new_level();
1633 /* Flash score at start */
1634 for (i = 0; i < 5; i++) {
1635 rb->lcd_fillrect(SCORENUM_X, SCORENUM_Y,
1636 4 * NUMBERS_WIDTH + 3 * NUM_SPACING,
1637 FONT_HEIGHT);
1638 rb->lcd_update_rect(SCORENUM_X, SCORENUM_Y,
1639 4 * NUMBERS_WIDTH + 3 * NUM_SPACING,
1640 FONT_HEIGHT);
1641 rb->sleep(HZ / 10);
1642 draw_number(SCORENUM_X, SCORENUM_Y, score, 4);
1643 rb->sleep(HZ / 10);
1648 inline bool handle_buttons(void)
1650 static unsigned int oldbuttonstate = 0;
1652 unsigned int released, pressed, newbuttonstate;
1654 if (ship_hit) {
1655 /* Don't allow ship movement during explosion */
1656 newbuttonstate = 0;
1657 } else {
1658 newbuttonstate = rb->button_status();
1660 if(newbuttonstate == oldbuttonstate) {
1661 if (newbuttonstate == 0) {
1662 /* No button pressed. Stop ship. */
1663 ship_acc = 0;
1664 if (ship_dir > 0) {
1665 ship_dir--;
1667 if (ship_dir < 0) {
1668 ship_dir++;
1671 /* return false; */
1672 goto check_usb;
1674 released = ~newbuttonstate & oldbuttonstate;
1675 pressed = newbuttonstate & ~oldbuttonstate;
1676 oldbuttonstate = newbuttonstate;
1677 if (pressed) {
1678 if (pressed & LEFT) {
1679 if (ship_acc > -1) {
1680 ship_acc--;
1683 if (pressed & RIGHT) {
1684 if (ship_acc < 1) {
1685 ship_acc++;
1688 if (pressed & FIRE) {
1689 if (fire == S_IDLE) {
1690 /* Fire shot */
1691 fire_x = ship_x + SHIP_WIDTH / 2;
1692 fire_y = SHIP_Y - SHOT_HEIGHT;
1693 fire = S_ACTIVE;
1694 /* TODO: play fire sound */
1697 #ifdef RC_QUIT
1698 if (pressed & RC_QUIT) {
1699 rb->splash(HZ * 1, "Quit");
1700 return true;
1702 #endif
1703 if (pressed & QUIT) {
1704 rb->splash(HZ * 1, "Quit");
1705 return true;
1708 if (released) {
1709 if ((released & LEFT)) {
1710 if (ship_acc < 1) {
1711 ship_acc++;
1714 if ((released & RIGHT)) {
1715 if (ship_acc > -1) {
1716 ship_acc--;
1721 check_usb:
1723 /* Quit if USB is connected */
1724 if (rb->button_get(false) == SYS_USB_CONNECTED) {
1725 return true;
1728 return false;
1732 void game_loop(void)
1734 int i, end;
1736 /* Print dimensions (just for debugging) */
1737 DBG("%03dx%03dx%02d\n", LCD_WIDTH, LCD_HEIGHT, LCD_DEPTH);
1739 /* Init */
1740 init_invadrox();
1742 while (1) {
1743 /* Convert CYCLETIME (in ms) to HZ */
1744 end = *rb->current_tick + (CYCLETIME * HZ) / 1000;
1746 if (handle_buttons()) {
1747 return;
1750 /* Animate */
1751 move_ship();
1752 move_fire();
1754 /* Check if level is finished (marked by move_fire) */
1755 if (level_finished) {
1756 /* TODO: Play level finished sound */
1757 new_level();
1760 move_ufo();
1762 /* Move aliens */
1763 if (!aliens_paralyzed && !ship_hit) {
1764 for (i = 0; i < gamespeed; i++) {
1765 if (!move_aliens()) {
1766 if (game_over) {
1767 return;
1773 /* Move alien bombs */
1774 move_bombs();
1775 if (game_over) {
1776 return;
1779 /* Update "playfield" rect */
1780 rb->lcd_update_rect(PLAYFIELD_X, SCORENUM_Y + FONT_HEIGHT,
1781 PLAYFIELD_WIDTH,
1782 PLAYFIELD_Y + 1 - SCORENUM_Y - FONT_HEIGHT);
1784 /* Wait until next frame */
1785 DBG("%d (%d)\n", end - *rb->current_tick, (CYCLETIME * HZ) / 1000);
1786 if (end > *rb->current_tick) {
1787 rb->sleep(end - *rb->current_tick);
1788 } else {
1789 rb->yield();
1792 } /* end while */
1796 /* this is the plugin entry point */
1797 enum plugin_status plugin_start(const struct plugin_api* api, UNUSED const void* parameter)
1799 rb = api;
1801 rb->lcd_setfont(FONT_SYSFIXED);
1802 /* Turn off backlight timeout */
1803 backlight_force_on(rb); /* backlight control in lib/helper.c */
1805 /* now go ahead and have fun! */
1806 game_loop();
1808 /* Game Over. */
1809 /* TODO: Play game over sound */
1810 rb->splash(HZ * 2, "Game Over");
1811 if (score > hiscore.score) {
1812 /* Save new hiscore */
1813 hiscore.score = score;
1814 hiscore.level = level;
1815 highscore_save(HISCOREFILE, &hiscore, 1);
1818 /* Restore user's original backlight setting */
1819 rb->lcd_setfont(FONT_UI);
1820 /* Turn on backlight timeout (revert to settings) */
1821 backlight_use_settings(rb); /* backlight control in lib/helper.c */
1823 return PLUGIN_OK;
1829 * GNU Emacs settings: Kernighan & Richie coding style with
1830 * 4 spaces indent and no tabs.
1831 * Local Variables:
1832 * c-file-style: "k&r"
1833 * c-basic-offset: 4
1834 * indent-tabs-mode: nil
1835 * End: