Fix some quotation marks. Thanks to Alexander Levin for pointing it out.
[Rockbox.git] / apps / plugins / invadrox.c
blobd7439ee19ec4dd62f96fed9bef28823bbdc1f838
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2006 Albert Veli
12 * All files in this archive are subject to the GNU General Public License.
13 * See the file COPYING in the source tree root for full license agreement.
15 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
16 * KIND, either express or implied.
18 ****************************************************************************/
20 /* Improvised creds goes to:
22 * - Anders Clausen for ingeniously inventing the name Invadrox.
23 * - Linus Nielsen-Feltzing for patiently answering n00b questions.
26 #include "plugin.h"
27 #include "highscore.h"
28 #include "helper.h"
30 PLUGIN_HEADER
32 /* Original graphics is only 1bpp so it should be portable
33 * to most targets. But for now, only support the simple ones.
35 #ifndef HAVE_LCD_BITMAP
36 #error INVADROX: Unsupported LCD
37 #endif
39 #if (LCD_DEPTH < 2)
40 #error INVADROX: Unsupported LCD
41 #endif
43 /* #define DEBUG */
44 #ifdef DEBUG
45 #include <stdio.h>
46 #define DBG(format, arg...) { printf("%s: " format, __FUNCTION__, ## arg); }
47 #else
48 #define DBG(format, arg...) {}
49 #endif
51 #if CONFIG_KEYPAD == IRIVER_H100_PAD
53 #define QUIT BUTTON_OFF
54 #define LEFT BUTTON_LEFT
55 #define RIGHT BUTTON_RIGHT
56 #define FIRE BUTTON_ON
58 #elif CONFIG_KEYPAD == IRIVER_H300_PAD
60 #define QUIT BUTTON_OFF
61 #define LEFT BUTTON_LEFT
62 #define RIGHT BUTTON_RIGHT
63 #define FIRE BUTTON_SELECT
65 #elif (CONFIG_KEYPAD == IRIVER_H10_PAD)
67 #define QUIT BUTTON_POWER
68 #define LEFT BUTTON_LEFT
69 #define RIGHT BUTTON_RIGHT
70 #define FIRE BUTTON_PLAY
72 #elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \
73 (CONFIG_KEYPAD == IPOD_3G_PAD) || \
74 (CONFIG_KEYPAD == IPOD_1G2G_PAD)
76 #define QUIT BUTTON_MENU
77 #define LEFT BUTTON_LEFT
78 #define RIGHT BUTTON_RIGHT
79 #define FIRE BUTTON_SELECT
81 #elif CONFIG_KEYPAD == IAUDIO_X5M5_PAD
83 #define QUIT BUTTON_POWER
84 #define LEFT BUTTON_LEFT
85 #define RIGHT BUTTON_RIGHT
86 #define FIRE BUTTON_SELECT
88 #elif CONFIG_KEYPAD == GIGABEAT_PAD
90 #define QUIT BUTTON_POWER
91 #define LEFT BUTTON_LEFT
92 #define RIGHT BUTTON_RIGHT
93 #define FIRE BUTTON_SELECT
95 #elif CONFIG_KEYPAD == SANSA_E200_PAD
97 #define QUIT BUTTON_POWER
98 #define LEFT BUTTON_LEFT
99 #define RIGHT BUTTON_RIGHT
100 #define FIRE BUTTON_SELECT
102 #elif CONFIG_KEYPAD == ELIO_TPJ1022_PAD
104 /* TODO: Figure out which buttons to use for Tatung Elio TPJ-1022 */
105 #define QUIT BUTTON_AB
106 #define LEFT BUTTON_LEFT
107 #define RIGHT BUTTON_RIGHT
108 #define FIRE BUTTON_MENU
110 #elif CONFIG_KEYPAD == GIGABEAT_S_PAD
112 #define QUIT BUTTON_BACK
113 #define LEFT BUTTON_LEFT
114 #define RIGHT BUTTON_RIGHT
115 #define FIRE BUTTON_SELECT
117 #elif CONFIG_KEYPAD == COWOND2_PAD
119 #define QUIT BUTTON_POWER
120 #define LEFT BUTTON_MINUS
121 #define RIGHT BUTTON_PLUS
122 #define FIRE BUTTON_MENU
124 #else
125 #error INVADROX: Unsupported keypad
126 #endif
129 #ifndef UNUSED
130 #define UNUSED __attribute__ ((unused))
131 #endif
133 #ifndef ABS
134 #define ABS(x) (((x) < 0) ? (-(x)) : (x))
135 #endif
138 /* Defines common to all models */
139 #define UFO_Y (SCORENUM_Y + FONT_HEIGHT + ALIEN_HEIGHT)
140 #define PLAYFIELD_Y (LCD_HEIGHT - SHIP_HEIGHT - 2)
141 #define PLAYFIELD_WIDTH (LCD_WIDTH - 2 * PLAYFIELD_X)
142 #define LEVEL_X (LCD_WIDTH - PLAYFIELD_X - LIVES_X - LEVEL_WIDTH - 2 * NUMBERS_WIDTH - 3 * NUM_SPACING)
143 #define SHIP_MIN_X (PLAYFIELD_X + PLAYFIELD_WIDTH / 5 - SHIELD_WIDTH / 2 - SHIP_WIDTH)
144 #define SHIP_MAX_X (PLAYFIELD_X + 4 * PLAYFIELD_WIDTH / 5 + SHIELD_WIDTH / 2)
145 /* SCORE_Y = 0 for most targets. Gigabeat redefines it later. */
146 #define SCORE_Y 0
147 #define MAX_LIVES 8
150 /* iPod Video defines */
151 #if (LCD_WIDTH == 320) && (LCD_HEIGHT == 240)
153 /* Original arcade game size 224x240, 1bpp with
154 * red overlay at top and green overlay at bottom.
156 * iPod Video: 320x240x16
157 * ======================
158 * X: 48p padding at left/right gives 224p playfield in middle.
159 * 10p "border" gives 204p actual playfield. UFO use full 224p.
160 * Y: Use full 240p.
162 * MAX_X = (204 - 12) / 2 - 1 = 95
164 * Y: Score text 7 0
165 * Space 10 7
166 * Score 7 17
167 * Space 8 24
168 * 3 Ufo 7 32
169 * 2 Space Aliens start at 32 + 3 * 8 = 56
170 * 0 aliens 9*8 56 -
171 * space ~7*8 128 | 18.75 aliens space between
172 * shield 2*8 182 | first alien and ship.
173 * space 8 198 | MAX_Y = 18
174 * ship 8 206 -
175 * space 2*8 214
176 * hline 1 230 - PLAYFIELD_Y
177 * bottom border 10 240
178 * Lives and Level goes inside bottom border
181 #define ARCADISH_GRAPHICS
182 #define PLAYFIELD_X 48
183 #define SHIP_Y (PLAYFIELD_Y - 3 * SHIP_HEIGHT)
184 #define ALIEN_START_Y (UFO_Y + 3 * ALIEN_HEIGHT)
185 #define SCORENUM_X (PLAYFIELD_X + NUMBERS_WIDTH)
186 #define SCORENUM_Y SCORE_Y + (2 * (FONT_HEIGHT + 1) + 1)
187 #define HISCORE_X (LCD_WIDTH - PLAYFIELD_X - HISCORE_WIDTH)
188 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 1 - 6 * NUMBERS_WIDTH - 5 * NUM_SPACING)
189 #define SHIELD_Y (PLAYFIELD_Y - 6 * SHIP_HEIGHT)
190 #define LIVES_X 10
191 #define MAX_X 95
192 #define MAX_Y 18
195 #elif (LCD_WIDTH == 176) && (LCD_HEIGHT == 220)
197 /* Sandisk Sansa e200: 176x220x16
198 * ==============================
199 * X: No padding. 8p border -> 160p playfield.
201 * 8p Aliens with 3p spacing -> 88 + 30 = 118p aliens block.
202 * (160 - 118) / 2 = 21 rounds for whole block (more than original)
203 * MAX_X = (160 - 8) / 2 - 1 = 75 rounds for single alien (less than original)
205 * LOGO 70 0
206 * Score text 5 70
207 * Space 5 75
208 * Y Score 5 80
209 * Space 10 85
210 * 2 Ufo 5 95
211 * 2 Space 10 100
212 * 0 aliens 9*5 110 -
213 * space ~7*5 155 | 18.6 aliens space between
214 * shield 2*5 188 | first alien and ship.
215 * space 5 198 | MAX_Y = 18
216 * ship 5 203 -
217 * space 5 208
218 * hline 1 213 PLAYFIELD_Y
219 * bottom border 6
220 * LCD_HEIGHT 220
221 * Lives and Level goes inside bottom border
224 #define TINY_GRAPHICS
225 #define PLAYFIELD_X 0
226 #define SHIP_Y (PLAYFIELD_Y - 2 * SHIP_HEIGHT)
227 #define SHIELD_Y (SHIP_Y - SHIP_HEIGHT - SHIELD_HEIGHT)
228 #define ALIEN_START_Y (UFO_Y + 3 * SHIP_HEIGHT)
229 /* Redefine SCORE_Y */
230 #undef SCORE_Y
231 #define SCORE_Y 70
232 #define SCORENUM_X (PLAYFIELD_X + NUMBERS_WIDTH)
233 #define SCORENUM_Y (SCORE_Y + 2 * FONT_HEIGHT)
234 #define HISCORE_X (LCD_WIDTH - PLAYFIELD_X - HISCORE_WIDTH)
235 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 1 - 6 * NUMBERS_WIDTH - 5 * NUM_SPACING)
236 #define LIVES_X 8
237 #define MAX_X 75
238 #define MAX_Y 18
241 #elif (LCD_WIDTH == 176) && (LCD_HEIGHT == 132)
243 /* iPod Nano: 176x132x16
244 * ======================
245 * X: No padding. 8p border -> 160p playfield.
247 * LIVES_X 8
248 * ALIEN_WIDTH 8
249 * ALIEN_HEIGHT 5
250 * ALIEN_SPACING 3
251 * SHIP_WIDTH 10
252 * SHIP_HEIGHT 5
253 * FONT_HEIGHT 5
254 * UFO_WIDTH 10
255 * UFO_HEIGHT 5
256 * SHIELD_WIDTH 15
257 * SHIELD_HEIGHT 10
258 * MAX_X 75
259 * MAX_Y = 18
260 * ALIEN_START_Y (UFO_Y + 12)
262 * 8p Aliens with 3p spacing -> 88 + 30 = 118p aliens block.
263 * (160 - 118) / 2 = 21 rounds for whole block (more than original)
264 * MAX_X = (160 - 8) / 2 - 1 = 75 rounds for single alien (less than original)
266 * Y: Scoreline 5 0 (combine scoretext and numbers on same line)
267 * Space 5 5
268 * 1 Ufo 5 10
269 * 3 Space 7 15
270 * 2 aliens 9*5 22 -
271 * space ~7*5 67 | Just above 18 aliens space between
272 * shield 2*5 100 | first alien and ship.
273 * space 5 110 | MAX_Y = 18
274 * ship 5 115 -
275 * space 5 120
276 * hline 1 125 PLAYFIELD_Y
277 * bottom border 6 126
278 * LCD_HEIGHT 131
279 * Lives and Level goes inside bottom border
282 #define TINY_GRAPHICS
283 #define PLAYFIELD_X 0
284 #define SHIP_Y (PLAYFIELD_Y - 2 * SHIP_HEIGHT)
285 #define ALIEN_START_Y (UFO_Y + 12)
286 #define SCORENUM_X (PLAYFIELD_X + SCORE_WIDTH + NUMBERS_WIDTH + NUM_SPACING)
287 #define SCORENUM_Y SCORE_Y
288 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 4 * NUMBERS_WIDTH - 3 * NUM_SPACING)
289 #define HISCORE_X (HISCORENUM_X - NUMBERS_WIDTH - NUM_SPACING - HISCORE_WIDTH)
290 #define SHIELD_Y (SHIP_Y - SHIP_HEIGHT - SHIELD_HEIGHT)
291 #define LIVES_X 8
292 #define MAX_X 75
293 #define MAX_Y 18
295 #elif (LCD_WIDTH == 160) && (LCD_HEIGHT == 128)
297 /* iAudio X5, iRiver H10 20Gb, iPod 3g/4g: 160x128x16
298 * ======================================
299 * X: No padding. No border -> 160p playfield.
301 * LIVES_X 0
302 * ALIEN_WIDTH 8
303 * ALIEN_HEIGHT 5
304 * ALIEN_SPACING 3
305 * SHIP_WIDTH 10
306 * SHIP_HEIGHT 5
307 * FONT_HEIGHT 5
308 * UFO_WIDTH 10
309 * UFO_HEIGHT 5
310 * SHIELD_WIDTH 15
311 * SHIELD_HEIGHT 10
312 * MAX_X 75
313 * MAX_Y = 18
314 * ALIEN_START_Y (UFO_Y + 10)
316 * 8p Aliens with 3p spacing -> 88 + 30 = 118p aliens block.
317 * (160 - 118) / 2 = 21 rounds for whole block (more than original)
318 * MAX_X = (160 - 8) / 2 - 1 = 75 rounds for single alien (less than original)
320 * Y: Scoreline 5 0 (combine scoretext and numbers on same line)
321 * Space 5 5
322 * 1 Ufo 5 10
323 * 2 Space 5 15
324 * 8 aliens 9*5 20 -
325 * space ~6*5 65 | Just above 18 aliens space between
326 * shield 2*5 96 | first alien and ship.
327 * space 5 106 | MAX_Y = 18
328 * ship 5 111 -
329 * space 5 116
330 * hline 1 121 PLAYFIELD_Y
331 * bottom border 6 122
332 * LCD_HEIGHT 128
333 * Lives and Level goes inside bottom border
336 #define TINY_GRAPHICS
337 #define PLAYFIELD_X 0
338 #define SHIP_Y (PLAYFIELD_Y - 2 * SHIP_HEIGHT)
339 #define ALIEN_START_Y (UFO_Y + 10)
340 #define SCORENUM_X (PLAYFIELD_X + SCORE_WIDTH + NUMBERS_WIDTH + NUM_SPACING)
341 #define SCORENUM_Y SCORE_Y
342 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 4 * NUMBERS_WIDTH - 3 * NUM_SPACING)
343 #define HISCORE_X (HISCORENUM_X - NUMBERS_WIDTH - NUM_SPACING - HISCORE_WIDTH)
344 #define SHIELD_Y (SHIP_Y - SHIP_HEIGHT - SHIELD_HEIGHT)
345 #define LIVES_X 0
346 #define MAX_X 75
347 #define MAX_Y 18
350 #elif (LCD_WIDTH == 240) && (LCD_HEIGHT == 320)
352 /* Gigabeat: 240x320x16
353 * ======================
354 * X: 8p padding at left/right gives 224p playfield in middle.
355 * 10p "border" gives 204p actual playfield. UFO use full 224p.
356 * Y: Use bottom 240p for playfield and top 80 pixels for logo.
358 * MAX_X = (204 - 12) / 2 - 1 = 95
360 * Y: Score text 7 0 + 80
361 * Space 10 7 + 80
362 * Score 7 17 + 80
363 * Space 8 24 + 80
364 * 3 Ufo 7 32 + 80
365 * 2 Space Aliens start at 32 + 3 * 8 = 56
366 * 0 aliens 9*8 56 -
367 * space ~7*8 128 | 18.75 aliens space between
368 * shield 2*8 182 | first alien and ship.
369 * space 8 198 | MAX_Y = 18
370 * ship 8 206 -
371 * space 2*8 214
372 * hline 1 230 310 - PLAYFIELD_Y
373 * bottom border 10 240 320
374 * Lives and Level goes inside bottom border
377 #define ARCADISH_GRAPHICS
378 #define PLAYFIELD_X 8
379 #define SHIP_Y (PLAYFIELD_Y - 3 * SHIP_HEIGHT)
380 #define ALIEN_START_Y (UFO_Y + 3 * ALIEN_HEIGHT)
381 /* Redefine SCORE_Y */
382 #undef SCORE_Y
383 #define SCORE_Y 80
384 #define SCORENUM_X (PLAYFIELD_X + NUMBERS_WIDTH)
385 #define SCORENUM_Y SCORE_Y + (2 * (FONT_HEIGHT + 1) + 1)
386 #define HISCORE_X (LCD_WIDTH - PLAYFIELD_X - HISCORE_WIDTH)
387 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 1 - 6 * NUMBERS_WIDTH - 5 * NUM_SPACING)
388 #define SHIELD_Y (PLAYFIELD_Y - 6 * SHIP_HEIGHT)
389 #define LIVES_X 10
390 #define MAX_X 95
391 #define MAX_Y 18
393 #elif (LCD_WIDTH == 220) && (LCD_HEIGHT == 176)
395 /* TPJ1022, H300, iPod Color: 220x176x16
396 * ============================
397 * X: 0p padding at left/right gives 220p playfield in middle.
398 * 8p "border" gives 204p actual playfield. UFO use full 220p.
399 * Y: Use full 176p for playfield.
401 * MAX_X = (204 - 12) / 2 - 1 = 95
403 * Y: Score text 7 0
404 * Space 8 7
405 * 1 Ufo 7 15
406 * 7 Space Aliens start at 15 + 3 * 8 = 56
407 * 6 aliens 9*8 25 -
408 * space ~7*8 103 | 15.6 aliens space between
409 * shield 2*8 126 | first alien and ship.
410 * space 8 142 | MAX_Y = 15
411 * ship 8 150 -
412 * space 8 158
413 * hline 1 166 - PLAYFIELD_Y
414 * bottom border 10 176
415 * Lives and Level goes inside bottom border
418 #define ARCADISH_GRAPHICS
419 #define PLAYFIELD_X 0
420 #define SHIP_Y (PLAYFIELD_Y - 2 * SHIP_HEIGHT)
421 #define ALIEN_START_Y (UFO_Y + 10)
422 #define SCORENUM_Y SCORE_Y
423 #define SCORENUM_X (PLAYFIELD_X + SCORE_WIDTH + NUMBERS_WIDTH + NUM_SPACING)
424 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 4 * NUMBERS_WIDTH - 3 * NUM_SPACING)
425 #define HISCORE_X (HISCORENUM_X - NUMBERS_WIDTH - NUM_SPACING - HISCORE_WIDTH)
426 #define SHIELD_Y (PLAYFIELD_Y - 5 * SHIP_HEIGHT)
427 #define LIVES_X 8
428 #define MAX_X 95
429 #define MAX_Y 15
431 #else
432 #error INVADROX: Unsupported LCD type
433 #endif
436 /* Defines common to each "graphic type" */
437 #ifdef ARCADISH_GRAPHICS
439 #define STRIDE 71
440 #define SHIP_SRC_X 24
441 #define SHIP_WIDTH 16
442 #define SHIP_HEIGHT 8
443 #define SHOT_HEIGHT 5
444 #define ALIEN_WIDTH 12
445 #define ALIEN_EXPLODE_SRC_X 52
446 #define ALIEN_EXPLODE_SRC_Y 39
447 #define ALIEN_EXPLODE_WIDTH 13
448 #define ALIEN_EXPLODE_HEIGHT 7
449 #define ALIEN_HEIGHT 8
450 #define ALIEN_SPACING 4
451 #define ALIEN_SPEED 2
452 #define UFO_SRC_X 40
453 #define UFO_WIDTH 16
454 #define UFO_HEIGHT 7
455 #define UFO_EXPLODE_WIDTH 21
456 #define UFO_EXPLODE_HEIGHT 8
457 #define UFO_SPEED 1
458 #define FONT_HEIGHT 7
459 #define LEVEL_SRC_Y 24
460 #define LEVEL_WIDTH 37
461 #define SCORE_SRC_X 24
462 #define SCORE_SRC_Y 31
463 #define SCORE_WIDTH 37
464 #define HISCORE_WIDTH 61
465 #define NUM_SPACING 3
466 #define NUMBERS_SRC_Y 38
467 #define NUMBERS_WIDTH 5
468 #define SHIELD_SRC_X 40
469 #define SHIELD_SRC_Y 15
470 #define SHIELD_WIDTH 22
471 #define SHIELD_HEIGHT 16
472 #define FIRE_WIDTH 8
473 #define FIRE_HEIGHT 8
474 #define FIRE_SPEED 8
475 #define BOMB_SRC_X 62
476 #define BOMB_WIDTH 3
477 #define BOMB_HEIGHT 7
478 #define BOMB_SPEED 3
479 #define ALIENS 11
480 unsigned char fire_sprite[FIRE_HEIGHT] = {
481 (1 << 7) | (0 << 6) | (0 << 5) | (0 << 4) | (1 << 3) | (0 << 2) | (0 << 1) | 1,
482 (0 << 7) | (0 << 6) | (1 << 5) | (0 << 4) | (0 << 3) | (0 << 2) | (1 << 1) | 0,
483 (0 << 7) | (1 << 6) | (1 << 5) | (1 << 4) | (1 << 3) | (1 << 2) | (1 << 1) | 0,
484 (1 << 7) | (1 << 6) | (1 << 5) | (1 << 4) | (1 << 3) | (1 << 2) | (1 << 1) | 1,
485 (0 << 7) | (1 << 6) | (1 << 5) | (1 << 4) | (1 << 3) | (1 << 2) | (1 << 1) | 1,
486 (0 << 7) | (1 << 6) | (1 << 5) | (1 << 4) | (1 << 3) | (1 << 2) | (1 << 1) | 0,
487 (0 << 7) | (0 << 6) | (1 << 5) | (0 << 4) | (0 << 3) | (1 << 2) | (0 << 1) | 0,
488 (1 << 7) | (0 << 6) | (0 << 5) | (1 << 4) | (0 << 3) | (0 << 2) | (0 << 1) | 1
491 #elif defined TINY_GRAPHICS
493 #define STRIDE 53
494 #define SHIP_SRC_X 16
495 #define SHIP_WIDTH 10
496 #define SHIP_HEIGHT 5
497 #define SHOT_HEIGHT 4
498 #define ALIEN_WIDTH 8
499 #define ALIEN_HEIGHT 5
500 #define ALIEN_EXPLODE_SRC_X 40
501 #define ALIEN_EXPLODE_SRC_Y 26
502 #define ALIEN_EXPLODE_WIDTH 10
503 #define ALIEN_EXPLODE_HEIGHT 5
504 #define ALIEN_SPACING 3
505 #define ALIEN_SPEED 2
506 #define UFO_SRC_X 26
507 #define UFO_WIDTH 11
508 #define UFO_HEIGHT 5
509 #define UFO_EXPLODE_WIDTH 14
510 #define UFO_EXPLODE_HEIGHT 5
511 #define UFO_SPEED 1
512 #define FONT_HEIGHT 5
513 #define LEVEL_SRC_Y 15
514 #define LEVEL_WIDTH 29
515 #define NUMBERS_WIDTH 4
516 #define NUM_SPACING 2
517 #define SCORE_SRC_X 17
518 #define SCORE_SRC_Y 20
519 #define SCORE_WIDTH 28
520 #define HISCORE_WIDTH 45
521 #define NUMBERS_SRC_Y 25
522 #define SHIELD_SRC_X 29
523 #define SHIELD_SRC_Y 10
524 #define SHIELD_WIDTH 15
525 #define SHIELD_HEIGHT 10
526 #define FIRE_WIDTH 6
527 #define FIRE_HEIGHT 6
528 #define FIRE_SPEED 6
529 #define BOMB_SRC_X 44
530 #define BOMB_WIDTH 3
531 #define BOMB_HEIGHT 5
532 #define BOMB_SPEED 2
533 #define ALIENS 11
534 unsigned char fire_sprite[FIRE_HEIGHT] = {
535 (1 << 5) | (0 << 4) | (0 << 3) | (1 << 2) | (0 << 1) | 1,
536 (0 << 5) | (1 << 4) | (0 << 3) | (0 << 2) | (0 << 1) | 0,
537 (0 << 5) | (1 << 4) | (1 << 3) | (1 << 2) | (1 << 1) | 0,
538 (0 << 5) | (1 << 4) | (1 << 3) | (1 << 2) | (1 << 1) | 1,
539 (0 << 5) | (1 << 4) | (0 << 3) | (0 << 2) | (1 << 1) | 0,
540 (1 << 5) | (0 << 4) | (1 << 3) | (0 << 2) | (0 << 1) | 1
543 #else
544 #error Graphic type not defined
545 #endif
548 /* Colors */
549 #if (LCD_DEPTH >= 8)
550 #define SLIME_GREEN LCD_RGBPACK(31, 254, 31)
551 #define UFO_RED LCD_RGBPACK(254, 31, 31)
552 #elif (LCD_DEPTH == 2)
553 #define SLIME_GREEN LCD_LIGHTGRAY
554 #define UFO_RED LCD_LIGHTGRAY
555 #else
556 #error LCD type not implemented yet
557 #endif
559 /* Alien states */
560 #define DEAD 0
561 #define ALIVE 1
562 #define BOMBER 2
564 /* Fire/bomb/ufo states */
565 #define S_IDLE 0
566 #define S_ACTIVE 1
567 #define S_SHOWSCORE 2
568 #define S_EXPLODE -9
570 /* Fire/bomb targets */
571 #define TARGET_TOP 0
572 #define TARGET_SHIELD 1
573 #define TARGET_SHIP 2
574 #define TARGET_BOTTOM 3
575 #define TARGET_UFO 4
577 #define HISCOREFILE PLUGIN_GAMES_DIR "/invadrox.high"
580 /* The time (in ms) for one iteration through the game loop - decrease this
581 * to speed up the game - note that current_tick is (currently) only accurate
582 * to 10ms.
584 #define CYCLETIME 40
587 static struct plugin_api* rb;
589 /* Physical x is at PLAYFIELD_X + LIVES_X + x * ALIEN_SPEED
590 * Physical y is at y * ALIEN_HEIGHT
592 struct alien {
593 unsigned char x; /* x-coordinate (0 - 95) */
594 unsigned char y; /* y-coordinate (0 - 18) */
595 unsigned char type; /* 0 (Kang), 1 (Kodos), 2 (Serak) */
596 unsigned char state; /* Dead, alive or bomber */
599 /* Aliens box 5 rows * ALIENS aliens in each row */
600 struct alien aliens[5 * ALIENS];
602 #define MAX_BOMBS 4
603 struct bomb {
604 int x, y;
605 unsigned char type;
606 unsigned char frame; /* Current animation frame */
607 unsigned char frames; /* Number of frames in animation */
608 unsigned char target; /* Remember target during explosion frames */
609 int state; /* 0 (IDLE) = inactive, 1 (FIRE) or negative, exploding */
611 struct bomb bombs[MAX_BOMBS];
612 /* Increase max_bombs at higher levels */
613 int max_bombs;
615 /* Raw framebuffer value of shield/ship green color */
616 fb_data screen_green, screen_white;
618 /* For optimization, precalculate startoffset of each scanline */
619 unsigned int ytab[LCD_HEIGHT];
621 /* external bitmaps */
622 extern const fb_data invadrox[];
623 #if (LCD_WIDTH == 320) && (LCD_HEIGHT == 240)
624 /* iPod Video only */
625 extern const fb_data invadrox_left[];
626 extern const fb_data invadrox_right[];
627 #endif
628 #if ((LCD_WIDTH == 240) && (LCD_HEIGHT == 320)) || ((LCD_WIDTH == 176) && (LCD_HEIGHT == 220))
629 /* Gigabeat F, Sansa e200 */
630 extern const fb_data invadrox_logo[];
631 #endif
634 int lives = 2;
635 int score = 0;
636 int scores[3] = { 30, 20, 10 };
637 int level = 0;
638 struct highscore hiscore;
639 bool game_over = false;
640 int ship_x, old_ship_x, ship_dir, ship_acc, max_ship_speed;
641 int ship_frame, ship_frame_counter;
642 bool ship_hit;
643 int fire, fire_target, fire_x, fire_y;
644 int curr_alien, aliens_paralyzed, gamespeed;
645 int ufo_state, ufo_x;
646 bool level_finished;
647 bool aliens_down, aliens_right, hit_left_border, hit_right_border;
650 /* No standard get_pixel function yet, use this hack instead */
651 #if (LCD_DEPTH >= 8)
653 inline fb_data get_pixel(int x, int y)
655 return rb->lcd_framebuffer[ytab[y] + x];
658 #elif (LCD_DEPTH == 2)
660 #if (LCD_PIXELFORMAT == HORIZONTAL_PACKING)
661 static const unsigned char shifts[4] = {
662 6, 4, 2, 0
664 /* Horizontal packing */
665 inline fb_data get_pixel(int x, int y)
667 return (rb->lcd_framebuffer[ytab[y] + (x >> 2)] >> shifts[x & 3]) & 3;
669 #else
670 /* Vertical packing */
671 static const unsigned char shifts[4] = {
672 0, 2, 4, 6
674 inline fb_data get_pixel(int x, int y)
676 return (rb->lcd_framebuffer[ytab[y] + x] >> shifts[y & 3]) & 3;
678 #endif /* Horizontal/Vertical packing */
680 #else
681 #error get_pixel: pixelformat not implemented yet
682 #endif
685 /* Draw "digits" least significant digits of num at (x,y) */
686 void draw_number(int x, int y, int num, int digits)
688 int i;
689 int d;
691 for (i = digits - 1; i >= 0; i--) {
692 d = num % 10;
693 num = num / 10;
694 rb->lcd_bitmap_part(invadrox, d * NUMBERS_WIDTH, NUMBERS_SRC_Y,
695 STRIDE, x + i * (NUMBERS_WIDTH + NUM_SPACING), y,
696 NUMBERS_WIDTH, FONT_HEIGHT);
698 /* Update lcd */
699 rb->lcd_update_rect(x, y, 4 * NUMBERS_WIDTH + 3 * NUM_SPACING, FONT_HEIGHT);
703 inline void draw_score(void)
705 draw_number(SCORENUM_X, SCORENUM_Y, score, 4);
706 if (score > hiscore.score) {
707 /* Draw new hiscore (same as score) */
708 draw_number(HISCORENUM_X, SCORENUM_Y, score, 4);
713 void draw_level(void)
715 rb->lcd_bitmap_part(invadrox, 0, LEVEL_SRC_Y,
716 STRIDE, LEVEL_X, PLAYFIELD_Y + 2,
717 LEVEL_WIDTH, FONT_HEIGHT);
718 draw_number(LEVEL_X + LEVEL_WIDTH + 2 * NUM_SPACING, PLAYFIELD_Y + 2, level, 2);
722 void draw_lives(void)
724 int i;
725 /* Lives num */
726 rb->lcd_bitmap_part(invadrox, lives * NUMBERS_WIDTH, NUMBERS_SRC_Y,
727 STRIDE, PLAYFIELD_X + LIVES_X, PLAYFIELD_Y + 2,
728 NUMBERS_WIDTH, FONT_HEIGHT);
730 /* Ships */
731 for (i = 0; i < (lives - 1); i++) {
732 rb->lcd_bitmap_part(invadrox, SHIP_SRC_X, 0, STRIDE,
733 PLAYFIELD_X + LIVES_X + SHIP_WIDTH + i * (SHIP_WIDTH + NUM_SPACING),
734 PLAYFIELD_Y + 1, SHIP_WIDTH, SHIP_HEIGHT);
737 /* Erase ship to the righ (if less than MAX_LIVES) */
738 if (lives < MAX_LIVES) {
739 rb->lcd_fillrect(PLAYFIELD_X + LIVES_X + SHIP_WIDTH + i * (SHIP_WIDTH + NUM_SPACING),
740 PLAYFIELD_Y + 1, SHIP_WIDTH, SHIP_HEIGHT);
742 /* Update lives (and level) part of screen */
743 rb->lcd_update_rect(PLAYFIELD_X + LIVES_X, PLAYFIELD_Y + 1,
744 PLAYFIELD_WIDTH - 2 * LIVES_X, MAX(FONT_HEIGHT + 1, SHIP_HEIGHT + 1));
748 inline void draw_aliens(void)
750 int i;
752 for (i = 0; i < 5 * ALIENS; i++) {
753 rb->lcd_bitmap_part(invadrox, aliens[i].x & 1 ? ALIEN_WIDTH : 0, aliens[i].type * ALIEN_HEIGHT,
754 STRIDE, PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED,
755 ALIEN_START_Y + aliens[i].y * ALIEN_HEIGHT,
756 ALIEN_WIDTH, ALIEN_HEIGHT);
761 /* Return false if there is no next alive alien (round is over) */
762 inline bool next_alien(void)
764 bool ret = true;
766 do {
767 curr_alien++;
768 if (curr_alien % ALIENS == 0) {
769 /* End of this row. Move up one row. */
770 curr_alien -= 2 * ALIENS;
771 if (curr_alien < 0) {
772 /* No more aliens in this round. */
773 curr_alien = 4 * ALIENS;
774 ret = false;
777 } while (aliens[curr_alien].state == DEAD && ret);
779 if (!ret) {
780 /* No more alive aliens. Round finished. */
781 if (hit_right_border) {
782 if (hit_left_border) {
783 DBG("ERROR: both left and right borders are set (%d)\n", curr_alien);
785 /* Move down-left next round */
786 aliens_right = false;
787 aliens_down = true;
788 hit_right_border = false;
789 } else if (hit_left_border) {
790 /* Move down-right next round */
791 aliens_right = true;
792 aliens_down = true;
793 hit_left_border = false;
794 } else {
795 /* Not left nor right. Set down to false. */
796 aliens_down = false;
800 return ret;
804 /* All aliens have been moved.
805 * Set curr_alien to first alive.
806 * Return false if no-one is left alive.
808 bool first_alien(void)
810 int i, y;
812 for (y = 4; y >= 0; y--) {
813 for (i = y * ALIENS; i < (y + 1) * ALIENS; i++) {
814 if (aliens[i].state != DEAD) {
815 curr_alien = i;
816 return true;
821 /* All aliens dead. */
822 level_finished = true;
824 return false;
828 bool move_aliens(void)
830 int x, y, old_x, old_y;
832 /* Move current alien (curr_alien is pointing to a living alien) */
834 old_x = aliens[curr_alien].x;
835 old_y = aliens[curr_alien].y;
837 if (aliens_down) {
838 aliens[curr_alien].y++;
839 if (aliens[curr_alien].y == MAX_Y) {
840 /* Alien is at bottom. Game Over. */
841 DBG("Alien %d is at bottom. Game Over.\n", curr_alien);
842 game_over = true;
843 return false;
847 if (aliens_right) {
848 /* Moving right */
849 if (aliens[curr_alien].x < MAX_X) {
850 aliens[curr_alien].x++;
853 /* Now, after move, check if we hit the right border. */
854 if (aliens[curr_alien].x == MAX_X) {
855 hit_right_border = true;
858 } else {
859 /* Moving left */
860 if (aliens[curr_alien].x > 0) {
861 aliens[curr_alien].x--;
864 /* Now, after move, check if we hit the left border. */
865 if (aliens[curr_alien].x == 0) {
866 hit_left_border = true;
870 /* Erase old position */
871 x = PLAYFIELD_X + LIVES_X + old_x * ALIEN_SPEED;
872 y = ALIEN_START_Y + old_y * ALIEN_HEIGHT;
873 if (aliens[curr_alien].y != old_y) {
874 /* Moved in y-dir. Erase whole alien. */
875 rb->lcd_fillrect(x, y, ALIEN_WIDTH, ALIEN_HEIGHT);
876 } else {
877 if (aliens_right) {
878 /* Erase left edge */
879 rb->lcd_fillrect(x, y, ALIEN_SPEED, ALIEN_HEIGHT);
880 } else {
881 /* Erase right edge */
882 x += ALIEN_WIDTH - ALIEN_SPEED;
883 rb->lcd_fillrect(x, y, ALIEN_SPEED, ALIEN_HEIGHT);
887 /* Draw alien at new pos */
888 x = PLAYFIELD_X + LIVES_X + aliens[curr_alien].x * ALIEN_SPEED;
889 y = ALIEN_START_Y + aliens[curr_alien].y * ALIEN_HEIGHT;
890 rb->lcd_bitmap_part(invadrox,
891 aliens[curr_alien].x & 1 ? ALIEN_WIDTH : 0, aliens[curr_alien].type * ALIEN_HEIGHT,
892 STRIDE, x, y, ALIEN_WIDTH, ALIEN_HEIGHT);
894 if (!next_alien()) {
895 /* Round finished. Set curr_alien to first alive from bottom. */
896 if (!first_alien()) {
897 /* Should never happen. Taken care of in move_fire(). */
898 return false;
900 /* TODO: Play next background sound */
903 return true;
907 inline void draw_ship(void)
909 /* Erase old ship */
910 if (old_ship_x < ship_x) {
911 /* Move right. Erase leftmost part of ship. */
912 rb->lcd_fillrect(old_ship_x, SHIP_Y, ship_x - old_ship_x, SHIP_HEIGHT);
913 } else if (old_ship_x > ship_x) {
914 /* Move left. Erase rightmost part of ship. */
915 rb->lcd_fillrect(ship_x + SHIP_WIDTH, SHIP_Y, old_ship_x - ship_x, SHIP_HEIGHT);
918 /* Draw ship */
919 rb->lcd_bitmap_part(invadrox, SHIP_SRC_X, ship_frame * SHIP_HEIGHT,
920 STRIDE, ship_x, SHIP_Y, SHIP_WIDTH, SHIP_HEIGHT);
921 if (ship_hit) {
922 /* Alternate between frame 1 and 2 during hit */
923 ship_frame_counter++;
924 if (ship_frame_counter > 2) {
925 ship_frame_counter = 0;
926 ship_frame++;
927 if (ship_frame > 2) {
928 ship_frame = 1;
933 /* Save ship_x for next time */
934 old_ship_x = ship_x;
938 inline void fire_alpha(int xc, int yc, fb_data color)
940 int x, y;
941 unsigned char mask;
943 rb->lcd_set_foreground(color);
945 for (y = 0; y < FIRE_HEIGHT; y++) {
946 mask = 1 << (FIRE_WIDTH - 1);
947 for (x = -(FIRE_WIDTH / 2); x < (FIRE_WIDTH / 2); x++) {
948 if (fire_sprite[y] & mask) {
949 rb->lcd_drawpixel(xc + x, yc + y);
951 mask >>= 1;
955 rb->lcd_set_foreground(LCD_BLACK);
959 void move_fire(void)
961 bool hit_green = false;
962 bool hit_white = false;
963 int i, j;
964 static int exploding_alien = -1;
965 fb_data pix;
967 if (fire == S_IDLE) {
968 return;
971 /* Alien hit. Wait until explosion is finished. */
972 if (aliens_paralyzed < 0) {
973 aliens_paralyzed++;
974 if (aliens_paralyzed == 0) {
975 /* Erase exploding_alien */
976 rb->lcd_fillrect(PLAYFIELD_X + LIVES_X + aliens[exploding_alien].x * ALIEN_SPEED,
977 ALIEN_START_Y + aliens[exploding_alien].y * ALIEN_HEIGHT,
978 ALIEN_EXPLODE_WIDTH, ALIEN_HEIGHT);
979 fire = S_IDLE;
980 /* Special case. We killed curr_alien. */
981 if (exploding_alien == curr_alien) {
982 if (!next_alien()) {
983 /* Round finished. Set curr_alien to first alive from bottom. */
984 first_alien();
988 return;
991 if (fire == S_ACTIVE) {
993 /* Erase */
994 rb->lcd_vline(fire_x, fire_y, fire_y + SHOT_HEIGHT);
996 /* Check top */
997 if (fire_y <= SCORENUM_Y + FONT_HEIGHT + 4) {
999 /* TODO: Play explode sound */
1001 fire = S_EXPLODE;
1002 fire_target = TARGET_TOP;
1003 fire_alpha(fire_x, fire_y, UFO_RED);
1004 return;
1007 /* Move */
1008 fire_y -= FIRE_SPEED;
1010 /* Hit UFO? */
1011 if (ufo_state == S_ACTIVE) {
1012 if ((ABS(ufo_x + UFO_WIDTH / 2 - fire_x) <= UFO_WIDTH / 2) &&
1013 (fire_y <= UFO_Y + UFO_HEIGHT)) {
1014 ufo_state = S_EXPLODE;
1015 fire = S_EXPLODE;
1016 fire_target = TARGET_UFO;
1017 /* Center explosion */
1018 ufo_x -= (UFO_EXPLODE_WIDTH - UFO_WIDTH) / 2;
1019 rb->lcd_bitmap_part(invadrox, UFO_SRC_X, UFO_HEIGHT,
1020 STRIDE, ufo_x, UFO_Y - 1, UFO_EXPLODE_WIDTH, UFO_EXPLODE_HEIGHT);
1021 return;
1025 /* Hit bomb? (check position, not pixel value) */
1026 for (i = 0; i < max_bombs; i++) {
1027 if (bombs[i].state == S_ACTIVE) {
1028 /* Count as hit if within BOMB_WIDTH pixels */
1029 if ((ABS(bombs[i].x - fire_x) < BOMB_WIDTH) &&
1030 (fire_y - bombs[i].y < BOMB_HEIGHT)) {
1031 /* Erase bomb */
1032 rb->lcd_fillrect(bombs[i].x, bombs[i].y, BOMB_WIDTH, BOMB_HEIGHT);
1033 bombs[i].state = S_IDLE;
1034 /* Explode ship fire */
1035 fire = S_EXPLODE;
1036 fire_target = TARGET_SHIELD;
1037 fire_alpha(fire_x, fire_y, LCD_WHITE);
1038 return;
1043 /* Check for hit*/
1044 for (i = FIRE_SPEED; i >= 0; i--) {
1045 pix = get_pixel(fire_x, fire_y + i);
1046 if(pix == screen_white) {
1047 hit_white = true;
1048 fire_y += i;
1049 break;
1051 if(pix == screen_green) {
1052 hit_green = true;
1053 fire_y += i;
1054 break;
1058 if (hit_green) {
1059 /* Hit shield */
1061 /* TODO: Play explode sound */
1063 fire = S_EXPLODE;
1064 fire_target = TARGET_SHIELD;
1065 /* Center explosion around hit pixel */
1066 fire_y -= FIRE_HEIGHT / 2;
1067 fire_alpha(fire_x, fire_y, SLIME_GREEN);
1068 return;
1071 if (hit_white) {
1073 /* Hit alien? */
1074 for (i = 0; i < 5 * ALIENS; i++) {
1075 if (aliens[i].state != DEAD &&
1076 (ABS(fire_x - (PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED +
1077 ALIEN_WIDTH / 2)) <= ALIEN_WIDTH / 2) &&
1078 (ABS(fire_y - (ALIEN_START_Y + aliens[i].y * ALIEN_HEIGHT +
1079 ALIEN_HEIGHT / 2)) <= ALIEN_HEIGHT / 2)) {
1081 /* TODO: play alien hit sound */
1083 if (aliens[i].state == BOMBER) {
1084 /* Set (possible) alien above to bomber */
1085 for (j = i - ALIENS; j >= 0; j -= ALIENS) {
1086 if (aliens[j].state != DEAD) {
1087 /* printf("New bomber (%d, %d)\n", j % ALIENS, j / ALIENS); */
1088 aliens[j].state = BOMBER;
1089 break;
1093 aliens[i].state = DEAD;
1094 exploding_alien = i;
1095 score += scores[aliens[i].type];
1096 draw_score();
1097 /* Update score part of screen */
1098 rb->lcd_update_rect(SCORENUM_X, SCORENUM_Y,
1099 PLAYFIELD_WIDTH - 2 * NUMBERS_WIDTH, FONT_HEIGHT);
1101 /* Paralyze aliens S_EXPLODE frames */
1102 aliens_paralyzed = S_EXPLODE;
1103 rb->lcd_bitmap_part(invadrox, ALIEN_EXPLODE_SRC_X, ALIEN_EXPLODE_SRC_Y,
1104 STRIDE, PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED,
1105 ALIEN_START_Y + aliens[i].y * ALIEN_HEIGHT,
1106 ALIEN_EXPLODE_WIDTH, ALIEN_EXPLODE_HEIGHT);
1107 /* Since alien is 1 pixel taller than explosion sprite, erase bottom line */
1108 rb->lcd_hline(PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED,
1109 PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED + ALIEN_WIDTH,
1110 ALIEN_START_Y + (aliens[i].y + 1) * ALIEN_HEIGHT - 1);
1111 return;
1116 /* Draw shot */
1117 rb->lcd_set_foreground(LCD_WHITE);
1118 rb->lcd_vline(fire_x, fire_y, fire_y + SHOT_HEIGHT);
1119 rb->lcd_set_foreground(LCD_BLACK);
1120 } else if (fire < S_IDLE) {
1121 /* Count up towards S_IDLE, then erase explosion */
1122 fire++;
1123 if (fire == S_IDLE) {
1124 /* Erase explosion */
1125 if (fire_target == TARGET_TOP) {
1126 rb->lcd_fillrect(fire_x - (FIRE_WIDTH / 2), fire_y, FIRE_WIDTH, FIRE_HEIGHT);
1127 } else if (fire_target == TARGET_SHIELD) {
1128 /* Draw explosion with black pixels */
1129 fire_alpha(fire_x, fire_y, LCD_BLACK);
1136 /* Return a BOMBER alien */
1137 inline int random_bomber(void)
1139 int i, col;
1141 /* TODO: Weigh higher probability near ship */
1142 col = rb->rand() % ALIENS;
1143 for (i = col + 4 * ALIENS; i >= 0; i -= ALIENS) {
1144 if (aliens[i].state == BOMBER) {
1145 return i;
1149 /* No BOMBER found in this col */
1151 for (i = 0; i < 5 * ALIENS; i++) {
1152 if (aliens[i].state == BOMBER) {
1153 return i;
1157 /* No BOMBER found at all (error?) */
1159 return -1;
1163 inline void draw_bomb(int i)
1165 rb->lcd_bitmap_part(invadrox, BOMB_SRC_X + bombs[i].type * BOMB_WIDTH,
1166 bombs[i].frame * (BOMB_HEIGHT + 1),
1167 STRIDE, bombs[i].x, bombs[i].y,
1168 BOMB_WIDTH, BOMB_HEIGHT);
1169 /* Advance frame */
1170 bombs[i].frame++;
1171 if (bombs[i].frame == bombs[i].frames) {
1172 bombs[i].frame = 0;
1177 void move_bombs(void)
1179 int i, j, bomber;
1180 bool abort;
1182 for (i = 0; i < max_bombs; i++) {
1184 switch (bombs[i].state) {
1186 case S_IDLE:
1187 if (ship_hit) {
1188 continue;
1190 bomber = random_bomber();
1191 if (bomber < 0) {
1192 DBG("ERROR: No bomber available\n");
1193 continue;
1195 /* x, y */
1196 bombs[i].x = PLAYFIELD_X + LIVES_X + aliens[bomber].x * ALIEN_SPEED + ALIEN_WIDTH / 2;
1197 bombs[i].y = ALIEN_START_Y + (aliens[bomber].y + 1) * ALIEN_HEIGHT;
1199 /* Check for duplets in x and y direction */
1200 abort = false;
1201 for (j = i - 1; j >= 0; j--) {
1202 if ((bombs[j].state == S_ACTIVE) &&
1203 ((bombs[i].x == bombs[j].x) || (bombs[i].y == bombs[j].y))) {
1204 abort = true;
1205 break;
1208 if (abort) {
1209 /* Skip this one, continue with next bomb */
1210 /* printf("Bomb %d duplet of %d\n", i, j); */
1211 continue;
1214 /* Passed, set type */
1215 bombs[i].type = rb->rand() % 3;
1216 bombs[i].frame = 0;
1217 if (bombs[i].type == 0) {
1218 bombs[i].frames = 3;
1219 } else if (bombs[i].type == 1) {
1220 bombs[i].frames = 4;
1221 } else {
1222 bombs[i].frames = 6;
1225 /* Bombs away */
1226 bombs[i].state = S_ACTIVE;
1227 draw_bomb(i);
1228 continue;
1230 break;
1232 case S_ACTIVE:
1233 /* Erase old position */
1234 rb->lcd_fillrect(bombs[i].x, bombs[i].y, BOMB_WIDTH, BOMB_HEIGHT);
1236 /* Move */
1237 bombs[i].y += BOMB_SPEED;
1239 /* Check if bottom hit */
1240 if (bombs[i].y + BOMB_HEIGHT >= PLAYFIELD_Y) {
1241 bombs[i].y = PLAYFIELD_Y - FIRE_HEIGHT + 1;
1242 fire_alpha(bombs[i].x, bombs[i].y, LCD_WHITE);
1243 bombs[i].state = S_EXPLODE;
1244 bombs[i].target = TARGET_BOTTOM;
1245 break;
1248 /* Check for green (ship or shield) */
1249 for (j = BOMB_HEIGHT; j >= BOMB_HEIGHT - BOMB_SPEED; j--) {
1250 bombs[i].target = 0;
1251 if(get_pixel(bombs[i].x + BOMB_WIDTH / 2, bombs[i].y + j) == screen_green) {
1252 /* Move to hit pixel */
1253 bombs[i].x += BOMB_WIDTH / 2;
1254 bombs[i].y += j;
1256 /* Check if ship is hit */
1257 if (bombs[i].y > SHIELD_Y + SHIELD_HEIGHT && bombs[i].y < PLAYFIELD_Y) {
1259 /* TODO: play ship hit sound */
1261 ship_hit = true;
1262 ship_frame = 1;
1263 ship_frame_counter = 0;
1264 bombs[i].state = S_EXPLODE * 4;
1265 bombs[i].target = TARGET_SHIP;
1266 rb->lcd_bitmap_part(invadrox, SHIP_SRC_X, 1 * SHIP_HEIGHT, STRIDE,
1267 ship_x, SHIP_Y, SHIP_WIDTH, SHIP_HEIGHT);
1268 break;
1270 /* Shield hit */
1271 bombs[i].state = S_EXPLODE;
1272 bombs[i].target = TARGET_SHIELD;
1273 /* Center explosion around hit pixel in shield */
1274 bombs[i].y -= FIRE_HEIGHT / 2;
1275 fire_alpha(bombs[i].x, bombs[i].y, SLIME_GREEN);
1276 break;
1280 if (bombs[i].target != 0) {
1281 /* Hit ship or shield, continue */
1282 continue;
1285 draw_bomb(i);
1286 break;
1288 default:
1289 /* If we get here state should be < 0, exploding */
1290 bombs[i].state++;
1291 if (bombs[i].state == S_IDLE) {
1292 if (ship_hit) {
1293 /* Erase explosion */
1294 rb->lcd_fillrect(ship_x, SHIP_Y, SHIP_WIDTH, SHIP_HEIGHT);
1295 rb->lcd_update_rect(ship_x, SHIP_Y, SHIP_WIDTH, SHIP_HEIGHT);
1296 ship_hit = false;
1297 ship_frame = 0;
1298 ship_x = PLAYFIELD_X + 2 * LIVES_X;
1299 lives--;
1300 if (lives == 0) {
1301 game_over = true;
1302 return;
1304 draw_lives();
1305 /* Sleep 1s to give player time to examine lives left */
1306 rb->sleep(HZ);
1308 /* Erase explosion (even if ship hit, might be another bomb) */
1309 fire_alpha(bombs[i].x, bombs[i].y, LCD_BLACK);
1311 break;
1317 inline void move_ship(void)
1319 ship_dir += ship_acc;
1320 if (ship_dir > max_ship_speed) {
1321 ship_dir = max_ship_speed;
1323 if (ship_dir < -max_ship_speed) {
1324 ship_dir = -max_ship_speed;
1326 ship_x += ship_dir;
1327 if (ship_x < SHIP_MIN_X) {
1328 ship_x = SHIP_MIN_X;
1330 if (ship_x > SHIP_MAX_X) {
1331 ship_x = SHIP_MAX_X;
1334 draw_ship();
1338 /* Unidentified Flying Object */
1339 void move_ufo(void)
1341 static int ufo_speed;
1342 static int counter;
1343 int mystery_score;
1345 switch (ufo_state) {
1347 case S_IDLE:
1349 if (rb->rand() % 500 == 0) {
1350 /* Uh-oh, it's time to launch a mystery UFO */
1352 /* TODO: Play UFO sound */
1354 if (rb->rand() % 2) {
1355 ufo_speed = UFO_SPEED;
1356 ufo_x = PLAYFIELD_X;
1357 } else {
1358 ufo_speed = -UFO_SPEED;
1359 ufo_x = LCD_WIDTH - PLAYFIELD_X - UFO_WIDTH;
1361 ufo_state = S_ACTIVE;
1362 /* UFO will be drawn next frame */
1364 break;
1366 case S_ACTIVE:
1367 /* Erase old pos */
1368 rb->lcd_fillrect(ufo_x, UFO_Y, UFO_WIDTH, UFO_HEIGHT);
1369 /* Move */
1370 ufo_x += ufo_speed;
1371 /* Check bounds */
1372 if (ufo_x < PLAYFIELD_X || ufo_x > LCD_WIDTH - PLAYFIELD_X - UFO_WIDTH) {
1373 ufo_state = S_IDLE;
1374 break;
1376 /* Draw new pos */
1377 rb->lcd_bitmap_part(invadrox, UFO_SRC_X, 0,
1378 STRIDE, ufo_x, UFO_Y, UFO_WIDTH, UFO_HEIGHT);
1379 break;
1381 case S_SHOWSCORE:
1382 counter++;
1383 if (counter == S_IDLE) {
1384 /* Erase mystery number */
1385 rb->lcd_fillrect(ufo_x, UFO_Y, 3 * NUMBERS_WIDTH + 2 * NUM_SPACING, FONT_HEIGHT);
1386 ufo_state = S_IDLE;
1388 break;
1390 default:
1391 /* Exploding */
1392 ufo_state++;
1393 if (ufo_state == S_IDLE) {
1394 /* Erase explosion */
1395 rb->lcd_fillrect(ufo_x, UFO_Y - 1, UFO_EXPLODE_WIDTH, UFO_EXPLODE_HEIGHT);
1396 ufo_state = S_SHOWSCORE;
1397 counter = S_EXPLODE * 4;
1398 /* Draw mystery_score, sleep, increase score and continue */
1399 mystery_score = 50 + (rb->rand() % 6) * 50;
1400 if (mystery_score < 100) {
1401 draw_number(ufo_x, UFO_Y, mystery_score, 2);
1402 } else {
1403 draw_number(ufo_x, UFO_Y, mystery_score, 3);
1405 score += mystery_score;
1406 draw_score();
1408 break;
1413 void draw_background(void)
1416 #if (LCD_WIDTH == 320) && (LCD_HEIGHT == 240)
1417 /* Erase background to black */
1418 rb->lcd_fillrect(PLAYFIELD_X, 0, PLAYFIELD_WIDTH, LCD_HEIGHT);
1419 /* Left and right bitmaps */
1420 rb->lcd_bitmap(invadrox_left, 0, 0, PLAYFIELD_X, LCD_HEIGHT);
1421 rb->lcd_bitmap(invadrox_right, LCD_WIDTH - PLAYFIELD_X, 0, PLAYFIELD_X, LCD_HEIGHT);
1422 #else
1423 rb->lcd_fillrect(0, 0, LCD_WIDTH, LCD_HEIGHT);
1424 #endif
1426 #if ((LCD_WIDTH == 240) && (LCD_HEIGHT == 320)) || ((LCD_WIDTH == 176) && (LCD_HEIGHT == 220))
1427 rb->lcd_bitmap(invadrox_logo, 0, 0, LCD_WIDTH, SCORE_Y);
1428 #endif
1430 rb->lcd_update();
1434 void new_level(void)
1436 int i;
1438 draw_background();
1439 /* Give an extra life for each new level */
1440 if (lives < MAX_LIVES) {
1441 lives++;
1443 draw_lives();
1445 /* Score */
1446 rb->lcd_bitmap_part(invadrox, SCORE_SRC_X, SCORE_SRC_Y,
1447 STRIDE, PLAYFIELD_X, SCORE_Y, SCORE_WIDTH, FONT_HEIGHT);
1448 /* Hi-score */
1449 rb->lcd_bitmap_part(invadrox, 0, SCORE_SRC_Y,
1450 STRIDE, HISCORE_X, SCORE_Y,
1451 HISCORE_WIDTH, FONT_HEIGHT);
1452 draw_score();
1453 draw_number(HISCORENUM_X, SCORENUM_Y, hiscore.score, 4);
1455 level++;
1456 draw_level();
1457 level_finished = false;
1459 ufo_state = S_IDLE;
1461 /* Init alien positions and states */
1462 for (i = 0; i < 4 * ALIENS; i++) {
1463 aliens[i].x = 0 + (i % ALIENS) * ((ALIEN_WIDTH + ALIEN_SPACING) / ALIEN_SPEED);
1464 aliens[i].y = 2 * (i / ALIENS);
1465 aliens[i].state = ALIVE;
1467 /* Last row, bombers */
1468 for (i = 4 * ALIENS; i < 5 * ALIENS; i++) {
1469 aliens[i].x = 0 + (i % ALIENS) * ((ALIEN_WIDTH + ALIEN_SPACING) / ALIEN_SPEED);
1470 aliens[i].y = 2 * (i / ALIENS);
1471 aliens[i].state = BOMBER;
1474 /* Init bombs to inactive (S_IDLE) */
1475 for (i = 0; i < MAX_BOMBS; i++) {
1476 bombs[i].state = S_IDLE;
1479 /* Start aliens closer to earth from level 2 */
1480 for (i = 0; i < 5 * ALIENS; i++) {
1481 if (level < 6) {
1482 aliens[i].y += level - 1;
1483 } else {
1484 aliens[i].y += 5;
1488 /* Max concurrent bombs */
1489 max_bombs = 1;
1491 gamespeed = 2;
1493 if (level > 1) {
1494 max_bombs++;
1497 /* Increase speed */
1498 if (level > 2) {
1499 gamespeed++;
1502 if (level > 3) {
1503 max_bombs++;
1506 /* Increase speed more */
1507 if (level > 4) {
1508 gamespeed++;
1511 if (level > 5) {
1512 max_bombs++;
1515 /* 4 shields */
1516 for (i = 1; i <= 4; i++) {
1517 rb->lcd_bitmap_part(invadrox, SHIELD_SRC_X, SHIELD_SRC_Y, STRIDE,
1518 PLAYFIELD_X + i * PLAYFIELD_WIDTH / 5 - SHIELD_WIDTH / 2,
1519 SHIELD_Y, SHIELD_WIDTH, SHIELD_HEIGHT);
1522 /* Bottom line */
1523 rb->lcd_set_foreground(SLIME_GREEN);
1524 rb->lcd_hline(PLAYFIELD_X, LCD_WIDTH - PLAYFIELD_X, PLAYFIELD_Y);
1525 /* Restore foreground to black (for fast erase later). */
1526 rb->lcd_set_foreground(LCD_BLACK);
1528 ship_x = PLAYFIELD_X + 2 * LIVES_X;
1529 if (level == 1) {
1530 old_ship_x = ship_x;
1532 ship_dir = 0;
1533 ship_acc = 0;
1534 ship_frame = 0;
1535 ship_hit = false;
1536 fire = S_IDLE;
1537 /* Start moving the bottom row left to right */
1538 curr_alien = 4 * ALIENS;
1539 aliens_paralyzed = 0;
1540 aliens_right = true;
1541 aliens_down = false;
1542 hit_left_border = false;
1543 hit_right_border = false;
1544 /* TODO: Change max_ship_speed to 3 at higher levels */
1545 max_ship_speed = 2;
1547 draw_aliens();
1549 rb->lcd_update();
1553 void init_invadrox(void)
1555 int i;
1557 /* Seed random number generator with a "random" number */
1558 rb->srand(rb->get_time()->tm_sec + rb->get_time()->tm_min * 60);
1560 /* Precalculate start of each scanline */
1561 for (i = 0; i < LCD_HEIGHT; i++) {
1562 #if (LCD_DEPTH >= 8)
1563 ytab[i] = i * LCD_WIDTH;
1564 #elif (LCD_DEPTH == 2) && (LCD_PIXELFORMAT == HORIZONTAL_PACKING)
1565 ytab[i] = i * (LCD_WIDTH / 4);
1566 #elif (LCD_DEPTH == 2) && (LCD_PIXELFORMAT == VERTICAL_PACKING)
1567 ytab[i] = (i / 4) * LCD_WIDTH;
1568 #else
1569 #error pixelformat not implemented yet
1570 #endif
1573 rb->lcd_set_background(LCD_BLACK);
1574 rb->lcd_set_foreground(LCD_BLACK);
1576 highscore_init(rb);
1577 if (highscore_load(HISCOREFILE, &hiscore, 1) < 0) {
1578 /* Init hiscore to 0 */
1579 rb->strncpy(hiscore.name, "Invader", sizeof(hiscore.name));
1580 hiscore.score = 0;
1581 hiscore.level = 1;
1584 /* Init alien types in aliens array */
1585 for (i = 0; i < 1 * ALIENS; i++) {
1586 aliens[i].type = 0; /* Kang */
1588 for (; i < 3 * ALIENS; i++) {
1589 aliens[i].type = 1; /* Kodos */
1591 for (; i < 5 * ALIENS; i++) {
1592 aliens[i].type = 2; /* Serak */
1596 /* Save screen white color */
1597 rb->lcd_set_foreground(LCD_WHITE);
1598 rb->lcd_drawpixel(0, 0);
1599 rb->lcd_update_rect(0, 0, 1, 1);
1600 screen_white = get_pixel(0, 0);
1602 /* Save screen green color */
1603 rb->lcd_set_foreground(SLIME_GREEN);
1604 rb->lcd_drawpixel(0, 0);
1605 rb->lcd_update_rect(0, 0, 1, 1);
1606 screen_green = get_pixel(0, 0);
1608 /* Restore black foreground */
1609 rb->lcd_set_foreground(LCD_BLACK);
1611 new_level();
1613 /* Flash score at start */
1614 for (i = 0; i < 5; i++) {
1615 rb->lcd_fillrect(SCORENUM_X, SCORENUM_Y,
1616 4 * NUMBERS_WIDTH + 3 * NUM_SPACING,
1617 FONT_HEIGHT);
1618 rb->lcd_update_rect(SCORENUM_X, SCORENUM_Y,
1619 4 * NUMBERS_WIDTH + 3 * NUM_SPACING,
1620 FONT_HEIGHT);
1621 rb->sleep(HZ / 10);
1622 draw_number(SCORENUM_X, SCORENUM_Y, score, 4);
1623 rb->sleep(HZ / 10);
1628 inline bool handle_buttons(void)
1630 static unsigned int oldbuttonstate = 0;
1632 unsigned int released, pressed, newbuttonstate;
1634 if (ship_hit) {
1635 /* Don't allow ship movement during explosion */
1636 newbuttonstate = 0;
1637 } else {
1638 newbuttonstate = rb->button_status();
1640 if(newbuttonstate == oldbuttonstate) {
1641 if (newbuttonstate == 0) {
1642 /* No button pressed. Stop ship. */
1643 ship_acc = 0;
1644 if (ship_dir > 0) {
1645 ship_dir--;
1647 if (ship_dir < 0) {
1648 ship_dir++;
1651 /* return false; */
1652 goto check_usb;
1654 released = ~newbuttonstate & oldbuttonstate;
1655 pressed = newbuttonstate & ~oldbuttonstate;
1656 oldbuttonstate = newbuttonstate;
1657 if (pressed) {
1658 if (pressed & LEFT) {
1659 if (ship_acc > -1) {
1660 ship_acc--;
1663 if (pressed & RIGHT) {
1664 if (ship_acc < 1) {
1665 ship_acc++;
1668 if (pressed & FIRE) {
1669 if (fire == S_IDLE) {
1670 /* Fire shot */
1671 fire_x = ship_x + SHIP_WIDTH / 2;
1672 fire_y = SHIP_Y - SHOT_HEIGHT;
1673 fire = S_ACTIVE;
1674 /* TODO: play fire sound */
1677 #ifdef RC_QUIT
1678 if (pressed & RC_QUIT) {
1679 rb->splash(HZ * 1, "Quit");
1680 return true;
1682 #endif
1683 if (pressed & QUIT) {
1684 rb->splash(HZ * 1, "Quit");
1685 return true;
1688 if (released) {
1689 if ((released & LEFT)) {
1690 if (ship_acc < 1) {
1691 ship_acc++;
1694 if ((released & RIGHT)) {
1695 if (ship_acc > -1) {
1696 ship_acc--;
1701 check_usb:
1703 /* Quit if USB is connected */
1704 if (rb->button_get(false) == SYS_USB_CONNECTED) {
1705 return true;
1708 return false;
1712 void game_loop(void)
1714 int i, end;
1716 /* Print dimensions (just for debugging) */
1717 DBG("%03dx%03dx%02d\n", LCD_WIDTH, LCD_HEIGHT, LCD_DEPTH);
1719 /* Init */
1720 init_invadrox();
1722 while (1) {
1723 /* Convert CYCLETIME (in ms) to HZ */
1724 end = *rb->current_tick + (CYCLETIME * HZ) / 1000;
1726 if (handle_buttons()) {
1727 return;
1730 /* Animate */
1731 move_ship();
1732 move_fire();
1734 /* Check if level is finished (marked by move_fire) */
1735 if (level_finished) {
1736 /* TODO: Play level finished sound */
1737 new_level();
1740 move_ufo();
1742 /* Move aliens */
1743 if (!aliens_paralyzed && !ship_hit) {
1744 for (i = 0; i < gamespeed; i++) {
1745 if (!move_aliens()) {
1746 if (game_over) {
1747 return;
1753 /* Move alien bombs */
1754 move_bombs();
1755 if (game_over) {
1756 return;
1759 /* Update "playfield" rect */
1760 rb->lcd_update_rect(PLAYFIELD_X, SCORENUM_Y + FONT_HEIGHT,
1761 PLAYFIELD_WIDTH,
1762 PLAYFIELD_Y + 1 - SCORENUM_Y - FONT_HEIGHT);
1764 /* Wait until next frame */
1765 DBG("%d (%d)\n", end - *rb->current_tick, (CYCLETIME * HZ) / 1000);
1766 if (end > *rb->current_tick) {
1767 rb->sleep(end - *rb->current_tick);
1768 } else {
1769 rb->yield();
1772 } /* end while */
1776 /* this is the plugin entry point */
1777 enum plugin_status plugin_start(struct plugin_api* api, UNUSED void* parameter)
1779 rb = api;
1781 rb->lcd_setfont(FONT_SYSFIXED);
1782 /* Turn off backlight timeout */
1783 backlight_force_on(rb); /* backlight control in lib/helper.c */
1785 /* now go ahead and have fun! */
1786 game_loop();
1788 /* Game Over. */
1789 /* TODO: Play game over sound */
1790 rb->splash(HZ * 2, "Game Over");
1791 if (score > hiscore.score) {
1792 /* Save new hiscore */
1793 hiscore.score = score;
1794 hiscore.level = level;
1795 highscore_save(HISCOREFILE, &hiscore, 1);
1798 /* Restore user's original backlight setting */
1799 rb->lcd_setfont(FONT_UI);
1800 /* Turn on backlight timeout (revert to settings) */
1801 backlight_use_settings(rb); /* backlight control in lib/helper.c */
1803 return PLUGIN_OK;
1809 * GNU Emacs settings: Kernighan & Richie coding style with
1810 * 4 spaces indent and no tabs.
1811 * Local Variables:
1812 * c-file-style: "k&r"
1813 * c-basic-offset: 4
1814 * indent-tabs-mode: nil
1815 * End: