MPEGPlayer: Add a second layer of caching to help speed up byte-wise scanning and...
[kugel-rb.git] / apps / plugins / invadrox.c
blob851487cf9a4fc14d3d1d0271b2fa2a8f903bf12b
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2006 Albert Veli
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
20 ****************************************************************************/
22 /* Improvised creds goes to:
24 * - Anders Clausen for ingeniously inventing the name Invadrox.
25 * - Linus Nielsen-Feltzing for patiently answering n00b questions.
28 #include "plugin.h"
29 #include "lib/highscore.h"
30 #include "lib/helper.h"
32 /* bitmaps */
33 #include "pluginbitmaps/invadrox_background.h"
35 /* get dimensions for later use from the bitmaps */
36 #include "pluginbitmaps/invadrox_aliens.h"
37 #include "pluginbitmaps/invadrox_ships.h"
38 #include "pluginbitmaps/invadrox_bombs.h"
39 #include "pluginbitmaps/invadrox_alien_explode.h"
40 #include "pluginbitmaps/invadrox_shield.h"
41 #include "pluginbitmaps/invadrox_ufo.h"
42 #include "pluginbitmaps/invadrox_ufo_explode.h"
43 #include "pluginbitmaps/invadrox_numbers.h"
44 #include "pluginbitmaps/invadrox_fire.h"
45 #define ALIEN_WIDTH (BMPWIDTH_invadrox_aliens/2)
46 #define ALIEN_HEIGHT (BMPHEIGHT_invadrox_aliens/3)
47 #define SHIP_WIDTH BMPWIDTH_invadrox_ships
48 #define SHIP_HEIGHT (BMPHEIGHT_invadrox_ships/3)
49 #define BOMB_WIDTH (BMPWIDTH_invadrox_bombs/3)
50 #define BOMB_HEIGHT (BMPHEIGHT_invadrox_bombs/6)
51 #define ALIEN_EXPLODE_WIDTH BMPWIDTH_invadrox_alien_explode
52 #define ALIEN_EXPLODE_HEIGHT BMPHEIGHT_invadrox_alien_explode
53 #define SHIELD_WIDTH BMPWIDTH_invadrox_shield
54 #define SHIELD_HEIGHT BMPHEIGHT_invadrox_shield
55 #define UFO_WIDTH BMPWIDTH_invadrox_ufo
56 #define UFO_HEIGHT BMPHEIGHT_invadrox_ufo
57 #define UFO_EXPLODE_WIDTH BMPWIDTH_invadrox_ufo_explode
58 #define UFO_EXPLODE_HEIGHT BMPHEIGHT_invadrox_ufo_explode
59 #define NUMBERS_WIDTH (BMPWIDTH_invadrox_numbers/10)
60 #define FONT_HEIGHT BMPHEIGHT_invadrox_numbers
61 #define FIRE_WIDTH BMPWIDTH_invadrox_fire
62 #define FIRE_HEIGHT BMPHEIGHT_invadrox_fire
64 PLUGIN_HEADER
66 /* Original graphics is only 1bpp so it should be portable
67 * to most targets. But for now, only support the simple ones.
69 #ifndef HAVE_LCD_BITMAP
70 #error INVADROX: Unsupported LCD
71 #endif
73 #if (LCD_DEPTH < 2)
74 #error INVADROX: Unsupported LCD
75 #endif
77 /* #define DEBUG */
78 #ifdef DEBUG
79 #define DBG(format, arg...) { DEBUGF("%s: " format, __FUNCTION__, ## arg); }
80 #else
81 #define DBG(format, arg...) {}
82 #endif
84 #ifndef ABS
85 #define ABS(a) (((a) < 0) ? -(a) : (a))
86 #endif
88 #if CONFIG_KEYPAD == IRIVER_H100_PAD
90 #define QUIT BUTTON_OFF
91 #define LEFT BUTTON_LEFT
92 #define RIGHT BUTTON_RIGHT
93 #define FIRE BUTTON_ON
95 #elif CONFIG_KEYPAD == IRIVER_H300_PAD
97 #define QUIT BUTTON_OFF
98 #define LEFT BUTTON_LEFT
99 #define RIGHT BUTTON_RIGHT
100 #define FIRE BUTTON_SELECT
102 #elif (CONFIG_KEYPAD == IRIVER_H10_PAD)
104 #define QUIT BUTTON_POWER
105 #define LEFT BUTTON_LEFT
106 #define RIGHT BUTTON_RIGHT
107 #define FIRE BUTTON_PLAY
109 #elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \
110 (CONFIG_KEYPAD == IPOD_3G_PAD) || \
111 (CONFIG_KEYPAD == IPOD_1G2G_PAD)
113 #define QUIT BUTTON_MENU
114 #define LEFT BUTTON_LEFT
115 #define RIGHT BUTTON_RIGHT
116 #define FIRE BUTTON_SELECT
118 #elif CONFIG_KEYPAD == IAUDIO_X5M5_PAD
120 #define QUIT BUTTON_POWER
121 #define LEFT BUTTON_LEFT
122 #define RIGHT BUTTON_RIGHT
123 #define FIRE BUTTON_SELECT
125 #elif CONFIG_KEYPAD == GIGABEAT_PAD
127 #define QUIT BUTTON_POWER
128 #define LEFT BUTTON_LEFT
129 #define RIGHT BUTTON_RIGHT
130 #define FIRE BUTTON_SELECT
132 #elif CONFIG_KEYPAD == SANSA_E200_PAD
134 #define QUIT BUTTON_POWER
135 #define LEFT BUTTON_LEFT
136 #define RIGHT BUTTON_RIGHT
137 #define FIRE BUTTON_SELECT
139 #elif CONFIG_KEYPAD == SANSA_FUZE_PAD
141 #define QUIT (BUTTON_HOME|BUTTON_REPEAT)
142 #define LEFT BUTTON_LEFT
143 #define RIGHT BUTTON_RIGHT
144 #define FIRE BUTTON_SELECT
146 #elif CONFIG_KEYPAD == TATUNG_TPJ1022_PAD
148 /* TODO: Figure out which buttons to use for Tatung Elio TPJ-1022 */
149 #define QUIT BUTTON_AB
150 #define LEFT BUTTON_LEFT
151 #define RIGHT BUTTON_RIGHT
152 #define FIRE BUTTON_MENU
154 #elif CONFIG_KEYPAD == GIGABEAT_S_PAD
156 #define QUIT BUTTON_BACK
157 #define LEFT BUTTON_LEFT
158 #define RIGHT BUTTON_RIGHT
159 #define FIRE BUTTON_SELECT
161 #elif CONFIG_KEYPAD == COWON_D2_PAD
163 #define QUIT BUTTON_POWER
164 #define LEFT BUTTON_MINUS
165 #define RIGHT BUTTON_PLUS
166 #define FIRE BUTTON_MENU
168 #elif CONFIG_KEYPAD == IAUDIO67_PAD
170 #define QUIT BUTTON_POWER
171 #define LEFT BUTTON_LEFT
172 #define RIGHT BUTTON_RIGHT
173 #define FIRE BUTTON_PLAY
175 #elif CONFIG_KEYPAD == CREATIVEZVM_PAD
177 #define QUIT BUTTON_BACK
178 #define LEFT BUTTON_LEFT
179 #define RIGHT BUTTON_RIGHT
180 #define FIRE BUTTON_SELECT
182 #elif CONFIG_KEYPAD == PHILIPS_SA9200_PAD
184 #define QUIT BUTTON_POWER
185 #define LEFT BUTTON_PREV
186 #define RIGHT BUTTON_NEXT
187 #define FIRE BUTTON_PLAY
189 #elif CONFIG_KEYPAD == ONDAVX747_PAD || \
190 CONFIG_KEYPAD == ONDAVX777_PAD || \
191 CONFIG_KEYPAD == MROBE500_PAD
193 #define QUIT BUTTON_POWER
195 #elif CONFIG_KEYPAD == SAMSUNG_YH_PAD
197 #define QUIT BUTTON_REC
198 #define LEFT BUTTON_LEFT
199 #define RIGHT BUTTON_RIGHT
200 #define FIRE BUTTON_PLAY
202 #elif CONFIG_KEYPAD == PBELL_VIBE500_PAD
204 #define QUIT BUTTON_REC
205 #define LEFT BUTTON_PREV
206 #define RIGHT BUTTON_NEXT
207 #define FIRE BUTTON_OK
209 #else
210 #error INVADROX: Unsupported keypad
211 #endif
213 #ifndef RC_QUIT
214 #define RC_QUIT 0
215 #endif
217 #ifdef HAVE_TOUCHSCREEN
219 #ifndef QUIT
220 #define QUIT 0
221 #endif
222 #ifndef LEFT
223 #define LEFT 0
224 #endif
225 #ifndef RIGHT
226 #define RIGHT 0
227 #endif
228 #ifndef FIRE
229 #define FIRE 0
230 #endif
232 #define TOUCHSCREEN_QUIT BUTTON_TOPLEFT
233 #define TOUCHSCREEN_LEFT (BUTTON_MIDLEFT | BUTTON_BOTTOMLEFT)
234 #define TOUCHSCREEN_RIGHT (BUTTON_MIDRIGHT | BUTTON_BOTTOMRIGHT)
235 #define TOUCHSCREEN_FIRE (BUTTON_CENTER | BUTTON_BOTTOMMIDDLE)
237 #define ACTION_QUIT (QUIT | TOUCHSCREEN_QUIT | RC_QUIT)
238 #define ACTION_LEFT (LEFT | TOUCHSCREEN_LEFT)
239 #define ACTION_RIGHT (RIGHT | TOUCHSCREEN_RIGHT)
240 #define ACTION_FIRE (FIRE | TOUCHSCREEN_FIRE)
242 #else /* HAVE_TOUCHSCREEN */
244 #define ACTION_QUIT (QUIT | RC_QUIT)
245 #define ACTION_LEFT LEFT
246 #define ACTION_RIGHT RIGHT
247 #define ACTION_FIRE FIRE
249 #endif
251 #ifndef UNUSED
252 #define UNUSED __attribute__ ((unused))
253 #endif
255 /* Defines common to all models */
256 #define UFO_Y (SCORENUM_Y + FONT_HEIGHT + ALIEN_HEIGHT)
257 #define PLAYFIELD_Y (LCD_HEIGHT - SHIP_HEIGHT - 2)
258 #define PLAYFIELD_WIDTH (LCD_WIDTH - 2 * PLAYFIELD_X)
259 #define LEVEL_X (LCD_WIDTH - PLAYFIELD_X - LIVES_X - 2 * NUMBERS_WIDTH - 3 * NUM_SPACING)
260 #define SHIP_MIN_X (PLAYFIELD_X + PLAYFIELD_WIDTH / 5 - SHIELD_WIDTH / 2 - SHIP_WIDTH)
261 #define SHIP_MAX_X (PLAYFIELD_X + 4 * PLAYFIELD_WIDTH / 5 + SHIELD_WIDTH / 2)
262 /* SCORE_Y = 0 for most targets. Gigabeat redefines it later. */
263 #define SCORE_Y 0
264 #define MAX_LIVES 8
267 /* m:robe 500 defines */
268 #if ((LCD_WIDTH == 640) && (LCD_HEIGHT == 480)) || \
269 ((LCD_WIDTH == 480) && (LCD_HEIGHT == 640))
271 /* Original arcade game size 224x240, 1bpp with
272 * red overlay at top and green overlay at bottom.
274 * M:Robe 500: 640x480x16
275 * ======================
278 #define ARCADISH_GRAPHICS
279 #define PLAYFIELD_X 48
280 #define SHIP_Y (PLAYFIELD_Y - 2 * SHIP_HEIGHT)
281 #define ALIEN_START_Y (UFO_Y + ALIEN_HEIGHT)
282 #define SCORENUM_X (PLAYFIELD_X + NUMBERS_WIDTH)
283 #define SCORENUM_Y (SCORE_Y + FONT_HEIGHT + 2)
284 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 1 - 6 * NUMBERS_WIDTH - 5 * NUM_SPACING)
285 #define SHIELD_Y (PLAYFIELD_Y - 5 * SHIP_HEIGHT)
286 #define LIVES_X 10
287 #define MAX_Y 18
289 /* iPod Video defines */
290 #elif (LCD_WIDTH == 320) && (LCD_HEIGHT == 240)
292 /* Original arcade game size 224x240, 1bpp with
293 * red overlay at top and green overlay at bottom.
295 * iPod Video: 320x240x16
296 * ======================
297 * X: 48p padding at left/right gives 224p playfield in middle.
298 * 10p "border" gives 204p actual playfield. UFO use full 224p.
299 * Y: Use full 240p.
301 * MAX_X = (204 - 12) / 2 - 1 = 95
303 * Y: Score text 7 0
304 * Space 10 7
305 * Score 7 17
306 * Space 8 24
307 * 3 Ufo 7 32
308 * 2 Space Aliens start at 32 + 3 * 8 = 56
309 * 0 aliens 9*8 56 -
310 * space ~7*8 128 | 18.75 aliens space between
311 * shield 2*8 182 | first alien and ship.
312 * space 8 198 | MAX_Y = 18
313 * ship 8 206 -
314 * space 2*8 214
315 * hline 1 230 - PLAYFIELD_Y
316 * bottom border 10 240
317 * Lives and Level goes inside bottom border
320 #define ARCADISH_GRAPHICS
321 #define PLAYFIELD_X 48
322 #define SHIP_Y (PLAYFIELD_Y - 3 * SHIP_HEIGHT)
323 #define ALIEN_START_Y (UFO_Y + 3 * ALIEN_HEIGHT)
324 #define SCORENUM_X (PLAYFIELD_X + NUMBERS_WIDTH)
325 #define SCORENUM_Y SCORE_Y + (2 * (FONT_HEIGHT + 1) + 1)
326 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 1 - 6 * NUMBERS_WIDTH - 5 * NUM_SPACING)
327 #define SHIELD_Y (PLAYFIELD_Y - 6 * SHIP_HEIGHT)
328 #define LIVES_X 10
329 #define MAX_Y 18
331 #elif (LCD_WIDTH == 176) && (LCD_HEIGHT == 220)
333 /* Sandisk Sansa e200: 176x220x16
334 * ==============================
335 * X: No padding. 8p border -> 160p playfield.
337 * 8p Aliens with 3p spacing -> 88 + 30 = 118p aliens block.
338 * (160 - 118) / 2 = 21 rounds for whole block (more than original)
339 * MAX_X = (160 - 8) / 2 - 1 = 75 rounds for single alien (less than original)
341 * LOGO 70 0
342 * Score text 5 70
343 * Space 5 75
344 * Y Score 5 80
345 * Space 10 85
346 * 2 Ufo 5 95
347 * 2 Space 10 100
348 * 0 aliens 9*5 110 -
349 * space ~7*5 155 | 18.6 aliens space between
350 * shield 2*5 188 | first alien and ship.
351 * space 5 198 | MAX_Y = 18
352 * ship 5 203 -
353 * space 5 208
354 * hline 1 213 PLAYFIELD_Y
355 * bottom border 6
356 * LCD_HEIGHT 220
357 * Lives and Level goes inside bottom border
360 #define SMALL_GRAPHICS
361 #define PLAYFIELD_X 0
362 #define SHIP_Y (PLAYFIELD_Y - 2 * SHIP_HEIGHT)
363 #define SHIELD_Y (SHIP_Y - SHIP_HEIGHT - SHIELD_HEIGHT)
364 #define ALIEN_START_Y (UFO_Y + 3 * SHIP_HEIGHT)
365 /* Redefine SCORE_Y */
366 #undef SCORE_Y
367 #define SCORE_Y 70
368 #define SCORENUM_X (PLAYFIELD_X + NUMBERS_WIDTH)
369 #define SCORENUM_Y (SCORE_Y + 2 * FONT_HEIGHT)
370 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 1 - 6 * NUMBERS_WIDTH - 5 * NUM_SPACING)
371 #define LIVES_X 8
372 #define MAX_Y 18
375 #elif (LCD_WIDTH == 176) && (LCD_HEIGHT == 132)
377 /* iPod Nano: 176x132x16
378 * ======================
379 * X: No padding. 8p border -> 160p playfield.
381 * LIVES_X 8
382 * ALIEN_WIDTH 8
383 * ALIEN_HEIGHT 5
384 * ALIEN_SPACING 3
385 * SHIP_WIDTH 10
386 * SHIP_HEIGHT 5
387 * FONT_HEIGHT 5
388 * UFO_WIDTH 10
389 * UFO_HEIGHT 5
390 * SHIELD_WIDTH 15
391 * SHIELD_HEIGHT 10
392 * MAX_X 75
393 * MAX_Y = 18
394 * ALIEN_START_Y (UFO_Y + 12)
396 * 8p Aliens with 3p spacing -> 88 + 30 = 118p aliens block.
397 * (160 - 118) / 2 = 21 rounds for whole block (more than original)
398 * MAX_X = (160 - 8) / 2 - 1 = 75 rounds for single alien (less than original)
400 * Y: Scoreline 5 0 (combine scoretext and numbers on same line)
401 * Space 5 5
402 * 1 Ufo 5 10
403 * 3 Space 7 15
404 * 2 aliens 9*5 22 -
405 * space ~7*5 67 | Just above 18 aliens space between
406 * shield 2*5 100 | first alien and ship.
407 * space 5 110 | MAX_Y = 18
408 * ship 5 115 -
409 * space 5 120
410 * hline 1 125 PLAYFIELD_Y
411 * bottom border 6 126
412 * LCD_HEIGHT 131
413 * Lives and Level goes inside bottom border
416 #define SMALL_GRAPHICS
417 #define PLAYFIELD_X 0
418 #define SHIP_Y (PLAYFIELD_Y - 2 * SHIP_HEIGHT)
419 #define ALIEN_START_Y (UFO_Y + 12)
420 #define SCORENUM_X (PLAYFIELD_X + 6 * NUMBERS_WIDTH + 5 * NUM_SPACING)
421 #define SCORENUM_Y SCORE_Y
422 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 4 * NUMBERS_WIDTH - 3 * NUM_SPACING)
423 #define SHIELD_Y (SHIP_Y - SHIP_HEIGHT - SHIELD_HEIGHT)
424 #define LIVES_X 8
425 #define MAX_Y 18
427 #elif (LCD_WIDTH == 160) && (LCD_HEIGHT == 128)
429 /* iAudio X5, iRiver H10 20Gb, iPod 3g/4g, H100, M5: 160x128
430 * =========================================================
431 * X: No padding. No border -> 160p playfield.
433 * LIVES_X 0
434 * ALIEN_WIDTH 8
435 * ALIEN_HEIGHT 5
436 * ALIEN_SPACING 3
437 * SHIP_WIDTH 10
438 * SHIP_HEIGHT 5
439 * FONT_HEIGHT 5
440 * UFO_WIDTH 10
441 * UFO_HEIGHT 5
442 * SHIELD_WIDTH 15
443 * SHIELD_HEIGHT 10
444 * MAX_X 75
445 * MAX_Y = 18
446 * ALIEN_START_Y (UFO_Y + 10)
448 * 8p Aliens with 3p spacing -> 88 + 30 = 118p aliens block.
449 * (160 - 118) / 2 = 21 rounds for whole block (more than original)
450 * MAX_X = (160 - 8) / 2 - 1 = 75 rounds for single alien (less than original)
452 * Y: Scoreline 5 0 (combine scoretext and numbers on same line)
453 * Space 5 5
454 * 1 Ufo 5 10
455 * 2 Space 5 15
456 * 8 aliens 9*5 20 -
457 * space ~6*5 65 | Just above 18 aliens space between
458 * shield 2*5 96 | first alien and ship.
459 * space 5 106 | MAX_Y = 18
460 * ship 5 111 -
461 * space 5 116
462 * hline 1 121 PLAYFIELD_Y
463 * bottom border 6 122
464 * LCD_HEIGHT 128
465 * Lives and Level goes inside bottom border
468 #define SMALL_GRAPHICS
469 #define PLAYFIELD_X 0
470 #define SHIP_Y (PLAYFIELD_Y - 2 * SHIP_HEIGHT)
471 #define ALIEN_START_Y (UFO_Y + 10)
472 #define SCORENUM_X (PLAYFIELD_X + 6 * NUMBERS_WIDTH + 5 * NUM_SPACING)
473 #define SCORENUM_Y SCORE_Y
474 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 4 * NUMBERS_WIDTH - 3 * NUM_SPACING)
475 #define SHIELD_Y (SHIP_Y - SHIP_HEIGHT - SHIELD_HEIGHT)
476 #define LIVES_X 0
477 #define MAX_Y 18
480 #elif (LCD_WIDTH == 240) && ((LCD_HEIGHT == 320) || (LCD_HEIGHT == 400))
482 /* Gigabeat: 240x320x16
483 * ======================
484 * X: 8p padding at left/right gives 224p playfield in middle.
485 * 10p "border" gives 204p actual playfield. UFO use full 224p.
486 * Y: Use bottom 240p for playfield and top 80 pixels for logo.
488 * MAX_X = (204 - 12) / 2 - 1 = 95
490 * Y: Score text 7 0 + 80
491 * Space 10 7 + 80
492 * Score 7 17 + 80
493 * Space 8 24 + 80
494 * 3 Ufo 7 32 + 80
495 * 2 Space Aliens start at 32 + 3 * 8 = 56
496 * 0 aliens 9*8 56 -
497 * space ~7*8 128 | 18.75 aliens space between
498 * shield 2*8 182 | first alien and ship.
499 * space 8 198 | MAX_Y = 18
500 * ship 8 206 -
501 * space 2*8 214
502 * hline 1 230 310 - PLAYFIELD_Y
503 * bottom border 10 240 320
504 * Lives and Level goes inside bottom border
507 #define ARCADISH_GRAPHICS
508 #define PLAYFIELD_X 8
509 #define SHIP_Y (PLAYFIELD_Y - 3 * SHIP_HEIGHT)
510 #define ALIEN_START_Y (UFO_Y + 3 * ALIEN_HEIGHT)
511 /* Redefine SCORE_Y */
512 #undef SCORE_Y
513 #define SCORE_Y 80
514 #define SCORENUM_X (PLAYFIELD_X + NUMBERS_WIDTH)
515 #define SCORENUM_Y SCORE_Y + (2 * (FONT_HEIGHT + 1) + 1)
516 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 1 - 6 * NUMBERS_WIDTH - 5 * NUM_SPACING)
517 #define SHIELD_Y (PLAYFIELD_Y - 6 * SHIP_HEIGHT)
518 #define LIVES_X 10
519 #define MAX_Y 18
521 #elif (LCD_WIDTH == 220) && (LCD_HEIGHT == 176)
523 /* TPJ1022, H300, iPod Color: 220x176x16
524 * ============================
525 * X: 0p padding at left/right gives 220p playfield in middle.
526 * 8p "border" gives 204p actual playfield. UFO use full 220p.
527 * Y: Use full 176p for playfield.
529 * MAX_X = (204 - 12) / 2 - 1 = 95
531 * Y: Score text 7 0
532 * Space 8 7
533 * 1 Ufo 7 15
534 * 7 Space Aliens start at 15 + 3 * 8 = 56
535 * 6 aliens 9*8 25 -
536 * space ~7*8 103 | 15.6 aliens space between
537 * shield 2*8 126 | first alien and ship.
538 * space 8 142 | MAX_Y = 15
539 * ship 8 150 -
540 * space 8 158
541 * hline 1 166 - PLAYFIELD_Y
542 * bottom border 10 176
543 * Lives and Level goes inside bottom border
546 #define ARCADISH_GRAPHICS
547 #define PLAYFIELD_X 0
548 #define SHIP_Y (PLAYFIELD_Y - 2 * SHIP_HEIGHT)
549 #define ALIEN_START_Y (UFO_Y + 10)
550 #define SCORENUM_Y SCORE_Y
551 #define SCORENUM_X (PLAYFIELD_X + 6 * NUMBERS_WIDTH + 6 * NUM_SPACING)
552 #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 4 * NUMBERS_WIDTH - 3 * NUM_SPACING)
553 #define SHIELD_Y (PLAYFIELD_Y - 5 * SHIP_HEIGHT)
554 #define LIVES_X 8
555 #define MAX_Y 15
558 #else
559 #error INVADROX: Unsupported LCD type
560 #endif
562 #define MAX_X ((LCD_WIDTH-LIVES_X*2-PLAYFIELD_X*2 - ALIEN_WIDTH)/2 - 1)
564 /* Defines common to each "graphic type" */
565 #ifdef ARCADISH_GRAPHICS
567 #define SHOT_HEIGHT 5
568 #define ALIEN_SPACING 4
569 #define ALIEN_SPEED 2
570 #define UFO_SPEED 1
571 #define NUM_SPACING 3
572 #define FIRE_SPEED 8
573 #define BOMB_SPEED 3
574 #define ALIENS 11
576 #elif defined SMALL_GRAPHICS
578 #define SHOT_HEIGHT 4
579 #define ALIEN_SPACING 3
580 #define ALIEN_SPEED 2
581 #define UFO_SPEED 1
582 #define NUM_SPACING 2
583 #define FIRE_SPEED 6
584 #define BOMB_SPEED 2
585 #define ALIENS 11
587 #else
588 #error Graphic type not defined
589 #endif
592 /* Colors */
593 #if (LCD_DEPTH >= 8)
594 #define SLIME_GREEN LCD_RGBPACK(31, 254, 31)
595 #define UFO_RED LCD_RGBPACK(254, 31, 31)
596 #elif (LCD_DEPTH == 2)
597 #define SLIME_GREEN LCD_LIGHTGRAY
598 #define UFO_RED LCD_LIGHTGRAY
599 #else
600 #error LCD type not implemented yet
601 #endif
603 /* Alien states */
604 #define DEAD 0
605 #define ALIVE 1
606 #define BOMBER 2
608 /* Fire/bomb/ufo states */
609 #define S_IDLE 0
610 #define S_ACTIVE 1
611 #define S_SHOWSCORE 2
612 #define S_EXPLODE -9
614 /* Fire/bomb targets */
615 #define TARGET_TOP 0
616 #define TARGET_SHIELD 1
617 #define TARGET_SHIP 2
618 #define TARGET_BOTTOM 3
619 #define TARGET_UFO 4
621 #define HISCOREFILE PLUGIN_GAMES_DIR "/invadrox.high"
624 /* The time (in ms) for one iteration through the game loop - decrease this
625 * to speed up the game - note that current_tick is (currently) only accurate
626 * to 10ms.
628 #define CYCLETIME 40
631 /* Physical x is at PLAYFIELD_X + LIVES_X + x * ALIEN_SPEED
632 * Physical y is at y * ALIEN_HEIGHT
634 struct alien {
635 int x; /* x-coordinate (0 - 95) */
636 int y; /* y-coordinate (0 - 18) */
637 unsigned char type; /* 0 (Kang), 1 (Kodos), 2 (Serak) */
638 unsigned char state; /* Dead, alive or bomber */
641 /* Aliens box 5 rows * ALIENS aliens in each row */
642 struct alien aliens[5 * ALIENS];
644 #define MAX_BOMBS 4
645 struct bomb {
646 int x, y;
647 unsigned char type;
648 unsigned char frame; /* Current animation frame */
649 unsigned char frames; /* Number of frames in animation */
650 unsigned char target; /* Remember target during explosion frames */
651 int state; /* 0 (IDLE) = inactive, 1 (FIRE) or negative, exploding */
653 struct bomb bombs[MAX_BOMBS];
654 /* Increase max_bombs at higher levels */
655 int max_bombs;
657 /* Raw framebuffer value of shield/ship green color */
658 fb_data screen_green, screen_white;
660 /* For optimization, precalculate startoffset of each scanline */
661 unsigned int ytab[LCD_HEIGHT];
663 int lives = 2;
664 int score = 0;
665 int scores[3] = { 30, 20, 10 };
666 int level = 0;
667 struct highscore hiscore;
668 bool game_over = false;
669 int ship_x, old_ship_x, ship_dir, ship_acc, max_ship_speed;
670 int ship_frame, ship_frame_counter;
671 bool ship_hit;
672 int fire, fire_target, fire_x, fire_y;
673 int curr_alien, aliens_paralyzed, gamespeed;
674 int ufo_state, ufo_x;
675 bool level_finished;
676 bool aliens_down, aliens_right, hit_left_border, hit_right_border;
679 /* No standard get_pixel function yet, use this hack instead */
680 #if (LCD_DEPTH >= 8)
682 #if defined(LCD_STRIDEFORMAT) && LCD_STRIDEFORMAT == VERTICAL_STRIDE
683 inline fb_data get_pixel(int x, int y)
685 return rb->lcd_framebuffer[x*LCD_HEIGHT+y];
687 #else
688 inline fb_data get_pixel(int x, int y)
690 return rb->lcd_framebuffer[ytab[y] + x];
692 #endif
694 #elif (LCD_DEPTH == 2)
696 #if (LCD_PIXELFORMAT == HORIZONTAL_PACKING)
697 static const unsigned char shifts[4] = {
698 6, 4, 2, 0
700 /* Horizontal packing */
701 inline fb_data get_pixel(int x, int y)
703 return (rb->lcd_framebuffer[ytab[y] + (x >> 2)] >> shifts[x & 3]) & 3;
705 #else
706 /* Vertical packing */
707 static const unsigned char shifts[4] = {
708 0, 2, 4, 6
710 inline fb_data get_pixel(int x, int y)
712 return (rb->lcd_framebuffer[ytab[y] + x] >> shifts[y & 3]) & 3;
714 #endif /* Horizontal/Vertical packing */
716 #else
717 #error get_pixel: pixelformat not implemented yet
718 #endif
721 /* Draw "digits" least significant digits of num at (x,y) */
722 void draw_number(int x, int y, int num, int digits)
724 int i;
725 int d;
727 for (i = digits - 1; i >= 0; i--) {
728 d = num % 10;
729 num = num / 10;
730 rb->lcd_bitmap_part(invadrox_numbers, d * NUMBERS_WIDTH, 0,
731 STRIDE( SCREEN_MAIN,
732 BMPWIDTH_invadrox_numbers,
733 BMPHEIGHT_invadrox_numbers),
734 x + i * (NUMBERS_WIDTH + NUM_SPACING), y,
735 NUMBERS_WIDTH, FONT_HEIGHT);
737 /* Update lcd */
738 rb->lcd_update_rect(x, y, 4 * NUMBERS_WIDTH + 3 * NUM_SPACING, FONT_HEIGHT);
742 inline void draw_score(void)
744 draw_number(SCORENUM_X, SCORENUM_Y, score, 4);
745 if (score > hiscore.score) {
746 /* Draw new hiscore (same as score) */
747 draw_number(HISCORENUM_X, SCORENUM_Y, score, 4);
752 void draw_level(void)
754 draw_number(LEVEL_X + 2 * NUM_SPACING, PLAYFIELD_Y + 2, level, 2);
758 void draw_lives(void)
760 int i;
761 /* Lives num */
762 rb->lcd_bitmap_part(invadrox_numbers, lives * NUMBERS_WIDTH, 0,
763 STRIDE( SCREEN_MAIN,
764 BMPWIDTH_invadrox_numbers,
765 BMPHEIGHT_invadrox_numbers),
766 PLAYFIELD_X + LIVES_X, PLAYFIELD_Y + 2,
767 NUMBERS_WIDTH, FONT_HEIGHT);
769 /* Ships */
770 for (i = 0; i < (lives - 1); i++) {
771 rb->lcd_bitmap_part(invadrox_ships, 0, 0,
772 STRIDE( SCREEN_MAIN,
773 BMPWIDTH_invadrox_ships,
774 BMPHEIGHT_invadrox_ships),
775 PLAYFIELD_X + LIVES_X + SHIP_WIDTH + i * (SHIP_WIDTH + NUM_SPACING),
776 PLAYFIELD_Y + 1, SHIP_WIDTH, SHIP_HEIGHT);
779 /* Erase ship to the right (if less than MAX_LIVES) */
780 if (lives < MAX_LIVES) {
781 rb->lcd_fillrect(PLAYFIELD_X + LIVES_X + SHIP_WIDTH + i * (SHIP_WIDTH + NUM_SPACING),
782 PLAYFIELD_Y + 1, SHIP_WIDTH, SHIP_HEIGHT);
784 /* Update lives (and level) part of screen */
785 rb->lcd_update_rect(PLAYFIELD_X + LIVES_X, PLAYFIELD_Y + 1,
786 PLAYFIELD_WIDTH - 2 * LIVES_X, MAX(FONT_HEIGHT + 1, SHIP_HEIGHT + 1));
790 inline void draw_aliens(void)
792 int i;
794 for (i = 0; i < 5 * ALIENS; i++) {
795 rb->lcd_bitmap_part(invadrox_aliens, aliens[i].x & 1 ? ALIEN_WIDTH : 0,
796 aliens[i].type * ALIEN_HEIGHT,
797 STRIDE( SCREEN_MAIN,
798 BMPWIDTH_invadrox_aliens,
799 BMPHEIGHT_invadrox_aliens),
800 PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED,
801 ALIEN_START_Y + aliens[i].y * ALIEN_HEIGHT,
802 ALIEN_WIDTH, ALIEN_HEIGHT);
807 /* Return false if there is no next alive alien (round is over) */
808 inline bool next_alien(void)
810 bool ret = true;
812 do {
813 curr_alien++;
814 if (curr_alien % ALIENS == 0) {
815 /* End of this row. Move up one row. */
816 curr_alien -= 2 * ALIENS;
817 if (curr_alien < 0) {
818 /* No more aliens in this round. */
819 curr_alien = 4 * ALIENS;
820 ret = false;
823 } while (aliens[curr_alien].state == DEAD && ret);
825 if (!ret) {
826 /* No more alive aliens. Round finished. */
827 if (hit_right_border) {
828 if (hit_left_border) {
829 DBG("ERROR: both left and right borders are set (%d)\n", curr_alien);
831 /* Move down-left next round */
832 aliens_right = false;
833 aliens_down = true;
834 hit_right_border = false;
835 } else if (hit_left_border) {
836 /* Move down-right next round */
837 aliens_right = true;
838 aliens_down = true;
839 hit_left_border = false;
840 } else {
841 /* Not left nor right. Set down to false. */
842 aliens_down = false;
846 return ret;
850 /* All aliens have been moved.
851 * Set curr_alien to first alive.
852 * Return false if no-one is left alive.
854 bool first_alien(void)
856 int i, y;
858 for (y = 4; y >= 0; y--) {
859 for (i = y * ALIENS; i < (y + 1) * ALIENS; i++) {
860 if (aliens[i].state != DEAD) {
861 curr_alien = i;
862 return true;
867 /* All aliens dead. */
868 level_finished = true;
870 return false;
874 bool move_aliens(void)
876 int x, y, old_x, old_y;
878 /* Move current alien (curr_alien is pointing to a living alien) */
880 old_x = aliens[curr_alien].x;
881 old_y = aliens[curr_alien].y;
883 if (aliens_down) {
884 aliens[curr_alien].y++;
885 if (aliens[curr_alien].y == MAX_Y) {
886 /* Alien is at bottom. Game Over. */
887 DBG("Alien %d is at bottom. Game Over.\n", curr_alien);
888 game_over = true;
889 return false;
893 if (aliens_right) {
894 /* Moving right */
895 if (aliens[curr_alien].x < MAX_X) {
896 aliens[curr_alien].x++;
899 /* Now, after move, check if we hit the right border. */
900 if (aliens[curr_alien].x == MAX_X) {
901 hit_right_border = true;
904 } else {
905 /* Moving left */
906 if (aliens[curr_alien].x > 0) {
907 aliens[curr_alien].x--;
910 /* Now, after move, check if we hit the left border. */
911 if (aliens[curr_alien].x == 0) {
912 hit_left_border = true;
916 /* Erase old position */
917 x = PLAYFIELD_X + LIVES_X + old_x * ALIEN_SPEED;
918 y = ALIEN_START_Y + old_y * ALIEN_HEIGHT;
919 if (aliens[curr_alien].y != old_y) {
920 /* Moved in y-dir. Erase whole alien. */
921 rb->lcd_fillrect(x, y, ALIEN_WIDTH, ALIEN_HEIGHT);
922 } else {
923 if (aliens_right) {
924 /* Erase left edge */
925 rb->lcd_fillrect(x, y, ALIEN_SPEED, ALIEN_HEIGHT);
926 } else {
927 /* Erase right edge */
928 x += ALIEN_WIDTH - ALIEN_SPEED;
929 rb->lcd_fillrect(x, y, ALIEN_SPEED, ALIEN_HEIGHT);
933 /* Draw alien at new pos */
934 x = PLAYFIELD_X + LIVES_X + aliens[curr_alien].x * ALIEN_SPEED;
935 y = ALIEN_START_Y + aliens[curr_alien].y * ALIEN_HEIGHT;
936 rb->lcd_bitmap_part(invadrox_aliens,
937 aliens[curr_alien].x & 1 ? ALIEN_WIDTH : 0,
938 aliens[curr_alien].type * ALIEN_HEIGHT,
939 STRIDE( SCREEN_MAIN,
940 BMPWIDTH_invadrox_aliens,
941 BMPHEIGHT_invadrox_aliens),
942 x, y, ALIEN_WIDTH, ALIEN_HEIGHT);
944 if (!next_alien()) {
945 /* Round finished. Set curr_alien to first alive from bottom. */
946 if (!first_alien()) {
947 /* Should never happen. Taken care of in move_fire(). */
948 return false;
950 /* TODO: Play next background sound */
953 return true;
957 inline void draw_ship(void)
959 /* Erase old ship */
960 if (old_ship_x < ship_x) {
961 /* Move right. Erase leftmost part of ship. */
962 rb->lcd_fillrect(old_ship_x, SHIP_Y, ship_x - old_ship_x, SHIP_HEIGHT);
963 } else if (old_ship_x > ship_x) {
964 /* Move left. Erase rightmost part of ship. */
965 rb->lcd_fillrect(ship_x + SHIP_WIDTH, SHIP_Y, old_ship_x - ship_x, SHIP_HEIGHT);
968 /* Draw ship */
969 rb->lcd_bitmap_part(invadrox_ships, 0, ship_frame * SHIP_HEIGHT,
970 STRIDE( SCREEN_MAIN,
971 BMPWIDTH_invadrox_ships,
972 BMPHEIGHT_invadrox_ships),
973 ship_x, SHIP_Y, SHIP_WIDTH, SHIP_HEIGHT);
974 if (ship_hit) {
975 /* Alternate between frame 1 and 2 during hit */
976 ship_frame_counter++;
977 if (ship_frame_counter > 2) {
978 ship_frame_counter = 0;
979 ship_frame++;
980 if (ship_frame > 2) {
981 ship_frame = 1;
986 /* Save ship_x for next time */
987 old_ship_x = ship_x;
991 inline void fire_alpha(int xc, int yc, fb_data color)
993 int oldmode = rb->lcd_get_drawmode();
995 rb->lcd_set_foreground(color);
996 rb->lcd_set_drawmode(DRMODE_FG);
998 rb->lcd_mono_bitmap(invadrox_fire, xc - (FIRE_WIDTH/2), yc, FIRE_WIDTH, FIRE_HEIGHT);
1000 rb->lcd_set_foreground(LCD_BLACK);
1001 rb->lcd_set_drawmode(oldmode);
1005 void move_fire(void)
1007 bool hit_green = false;
1008 bool hit_white = false;
1009 int i, j;
1010 static int exploding_alien = -1;
1011 fb_data pix;
1013 if (fire == S_IDLE) {
1014 return;
1017 /* Alien hit. Wait until explosion is finished. */
1018 if (aliens_paralyzed < 0) {
1019 aliens_paralyzed++;
1020 if (aliens_paralyzed == 0) {
1021 /* Erase exploding_alien */
1022 rb->lcd_fillrect(PLAYFIELD_X + LIVES_X + aliens[exploding_alien].x * ALIEN_SPEED,
1023 ALIEN_START_Y + aliens[exploding_alien].y * ALIEN_HEIGHT,
1024 ALIEN_EXPLODE_WIDTH, ALIEN_HEIGHT);
1025 fire = S_IDLE;
1026 /* Special case. We killed curr_alien. */
1027 if (exploding_alien == curr_alien) {
1028 if (!next_alien()) {
1029 /* Round finished. Set curr_alien to first alive from bottom. */
1030 first_alien();
1034 return;
1037 if (fire == S_ACTIVE) {
1039 /* Erase */
1040 rb->lcd_vline(fire_x, fire_y, fire_y + SHOT_HEIGHT);
1042 /* Check top */
1043 if (fire_y <= SCORENUM_Y + FONT_HEIGHT + 4) {
1045 /* TODO: Play explode sound */
1047 fire = S_EXPLODE;
1048 fire_target = TARGET_TOP;
1049 fire_alpha(fire_x, fire_y, UFO_RED);
1050 return;
1053 /* Move */
1054 fire_y -= FIRE_SPEED;
1056 /* Hit UFO? */
1057 if (ufo_state == S_ACTIVE) {
1058 if ((ABS(ufo_x + UFO_WIDTH / 2 - fire_x) <= UFO_WIDTH / 2) &&
1059 (fire_y <= UFO_Y + UFO_HEIGHT)) {
1060 ufo_state = S_EXPLODE;
1061 fire = S_EXPLODE;
1062 fire_target = TARGET_UFO;
1063 /* Center explosion */
1064 ufo_x -= (UFO_EXPLODE_WIDTH - UFO_WIDTH) / 2;
1065 rb->lcd_bitmap(invadrox_ufo_explode, ufo_x, UFO_Y - 1,
1066 UFO_EXPLODE_WIDTH, UFO_EXPLODE_HEIGHT);
1067 return;
1071 /* Hit bomb? (check position, not pixel value) */
1072 for (i = 0; i < max_bombs; i++) {
1073 if (bombs[i].state == S_ACTIVE) {
1074 /* Count as hit if within BOMB_WIDTH pixels */
1075 if ((ABS(bombs[i].x - fire_x) < BOMB_WIDTH) &&
1076 (fire_y - bombs[i].y < BOMB_HEIGHT)) {
1077 /* Erase bomb */
1078 rb->lcd_fillrect(bombs[i].x, bombs[i].y, BOMB_WIDTH, BOMB_HEIGHT);
1079 bombs[i].state = S_IDLE;
1080 /* Explode ship fire */
1081 fire = S_EXPLODE;
1082 fire_target = TARGET_SHIELD;
1083 fire_alpha(fire_x, fire_y, LCD_WHITE);
1084 return;
1089 /* Check for hit*/
1090 for (i = FIRE_SPEED; i >= 0; i--) {
1091 pix = get_pixel(fire_x, fire_y + i);
1092 if(pix == screen_white) {
1093 hit_white = true;
1094 fire_y += i;
1095 break;
1097 if(pix == screen_green) {
1098 hit_green = true;
1099 fire_y += i;
1100 break;
1104 if (hit_green) {
1105 /* Hit shield */
1107 /* TODO: Play explode sound */
1109 fire = S_EXPLODE;
1110 fire_target = TARGET_SHIELD;
1111 /* Center explosion around hit pixel */
1112 fire_y -= FIRE_HEIGHT / 2;
1113 fire_alpha(fire_x, fire_y, SLIME_GREEN);
1114 return;
1117 if (hit_white) {
1119 /* Hit alien? */
1120 for (i = 0; i < 5 * ALIENS; i++) {
1121 if (aliens[i].state != DEAD &&
1122 (ABS(fire_x - (PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED +
1123 ALIEN_WIDTH / 2)) <= ALIEN_WIDTH / 2) &&
1124 (ABS(fire_y - (ALIEN_START_Y + aliens[i].y * ALIEN_HEIGHT +
1125 ALIEN_HEIGHT / 2)) <= ALIEN_HEIGHT / 2)) {
1127 /* TODO: play alien hit sound */
1129 if (aliens[i].state == BOMBER) {
1130 /* Set (possible) alien above to bomber */
1131 for (j = i - ALIENS; j >= 0; j -= ALIENS) {
1132 if (aliens[j].state != DEAD) {
1133 /* printf("New bomber (%d, %d)\n", j % ALIENS, j / ALIENS); */
1134 aliens[j].state = BOMBER;
1135 break;
1139 aliens[i].state = DEAD;
1140 exploding_alien = i;
1141 score += scores[aliens[i].type];
1142 draw_score();
1143 /* Update score part of screen */
1144 rb->lcd_update_rect(SCORENUM_X, SCORENUM_Y,
1145 PLAYFIELD_WIDTH - 2 * NUMBERS_WIDTH, FONT_HEIGHT);
1147 /* Paralyze aliens S_EXPLODE frames */
1148 aliens_paralyzed = S_EXPLODE;
1149 rb->lcd_bitmap(invadrox_alien_explode,
1150 PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED,
1151 ALIEN_START_Y + aliens[i].y * ALIEN_HEIGHT,
1152 ALIEN_EXPLODE_WIDTH, ALIEN_EXPLODE_HEIGHT);
1153 /* Since alien is 1 pixel taller than explosion sprite, erase bottom line */
1154 rb->lcd_hline(PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED,
1155 PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED + ALIEN_WIDTH,
1156 ALIEN_START_Y + (aliens[i].y + 1) * ALIEN_HEIGHT - 1);
1157 return;
1162 /* Draw shot */
1163 rb->lcd_set_foreground(LCD_WHITE);
1164 rb->lcd_vline(fire_x, fire_y, fire_y + SHOT_HEIGHT);
1165 rb->lcd_set_foreground(LCD_BLACK);
1166 } else if (fire < S_IDLE) {
1167 /* Count up towards S_IDLE, then erase explosion */
1168 fire++;
1169 if (fire == S_IDLE) {
1170 /* Erase explosion */
1171 if (fire_target == TARGET_TOP) {
1172 rb->lcd_fillrect(fire_x - (FIRE_WIDTH / 2), fire_y, FIRE_WIDTH, FIRE_HEIGHT);
1173 } else if (fire_target == TARGET_SHIELD) {
1174 /* Draw explosion with black pixels */
1175 fire_alpha(fire_x, fire_y, LCD_BLACK);
1182 /* Return a BOMBER alien */
1183 inline int random_bomber(void)
1185 int i, col;
1187 /* TODO: Weigh higher probability near ship */
1188 col = rb->rand() % ALIENS;
1189 for (i = col + 4 * ALIENS; i >= 0; i -= ALIENS) {
1190 if (aliens[i].state == BOMBER) {
1191 return i;
1195 /* No BOMBER found in this col */
1197 for (i = 0; i < 5 * ALIENS; i++) {
1198 if (aliens[i].state == BOMBER) {
1199 return i;
1203 /* No BOMBER found at all (error?) */
1205 return -1;
1209 inline void draw_bomb(int i)
1211 rb->lcd_bitmap_part(invadrox_bombs, bombs[i].type * BOMB_WIDTH,
1212 bombs[i].frame * BOMB_HEIGHT,
1213 STRIDE( SCREEN_MAIN,
1214 BMPWIDTH_invadrox_bombs,
1215 BMPHEIGHT_invadrox_bombs),
1216 bombs[i].x, bombs[i].y,
1217 BOMB_WIDTH, BOMB_HEIGHT);
1218 /* Advance frame */
1219 bombs[i].frame++;
1220 if (bombs[i].frame == bombs[i].frames) {
1221 bombs[i].frame = 0;
1226 void move_bombs(void)
1228 int i, j, bomber;
1229 bool abort;
1231 for (i = 0; i < max_bombs; i++) {
1233 switch (bombs[i].state) {
1235 case S_IDLE:
1236 if (ship_hit) {
1237 continue;
1239 bomber = random_bomber();
1240 if (bomber < 0) {
1241 DBG("ERROR: No bomber available\n");
1242 continue;
1244 /* x, y */
1245 bombs[i].x = PLAYFIELD_X + LIVES_X + aliens[bomber].x * ALIEN_SPEED + ALIEN_WIDTH / 2;
1246 bombs[i].y = ALIEN_START_Y + (aliens[bomber].y + 1) * ALIEN_HEIGHT;
1248 /* Check for duplets in x and y direction */
1249 abort = false;
1250 for (j = i - 1; j >= 0; j--) {
1251 if ((bombs[j].state == S_ACTIVE) &&
1252 ((bombs[i].x == bombs[j].x) || (bombs[i].y == bombs[j].y))) {
1253 abort = true;
1254 break;
1257 if (abort) {
1258 /* Skip this one, continue with next bomb */
1259 /* printf("Bomb %d duplet of %d\n", i, j); */
1260 continue;
1263 /* Passed, set type */
1264 bombs[i].type = rb->rand() % 3;
1265 bombs[i].frame = 0;
1266 if (bombs[i].type == 0) {
1267 bombs[i].frames = 3;
1268 } else if (bombs[i].type == 1) {
1269 bombs[i].frames = 4;
1270 } else {
1271 bombs[i].frames = 6;
1274 /* Bombs away */
1275 bombs[i].state = S_ACTIVE;
1276 draw_bomb(i);
1277 continue;
1279 break;
1281 case S_ACTIVE:
1282 /* Erase old position */
1283 rb->lcd_fillrect(bombs[i].x, bombs[i].y, BOMB_WIDTH, BOMB_HEIGHT);
1285 /* Move */
1286 bombs[i].y += BOMB_SPEED;
1288 /* Check if bottom hit */
1289 if (bombs[i].y + BOMB_HEIGHT >= PLAYFIELD_Y) {
1290 bombs[i].y = PLAYFIELD_Y - FIRE_HEIGHT + 1;
1291 fire_alpha(bombs[i].x, bombs[i].y, LCD_WHITE);
1292 bombs[i].state = S_EXPLODE;
1293 bombs[i].target = TARGET_BOTTOM;
1294 break;
1297 /* Check for green (ship or shield) */
1298 for (j = BOMB_HEIGHT; j >= BOMB_HEIGHT - BOMB_SPEED; j--) {
1299 bombs[i].target = 0;
1300 if(get_pixel(bombs[i].x + BOMB_WIDTH / 2, bombs[i].y + j) == screen_green) {
1301 /* Move to hit pixel */
1302 bombs[i].x += BOMB_WIDTH / 2;
1303 bombs[i].y += j;
1305 /* Check if ship is hit */
1306 if (bombs[i].y > SHIELD_Y + SHIELD_HEIGHT && bombs[i].y < PLAYFIELD_Y) {
1308 /* TODO: play ship hit sound */
1310 ship_hit = true;
1311 ship_frame = 1;
1312 ship_frame_counter = 0;
1313 bombs[i].state = S_EXPLODE * 4;
1314 bombs[i].target = TARGET_SHIP;
1315 rb->lcd_bitmap_part(invadrox_ships, 0, 1 * SHIP_HEIGHT,
1316 STRIDE( SCREEN_MAIN,
1317 BMPWIDTH_invadrox_ships,
1318 BMPHEIGHT_invadrox_ships),
1319 ship_x, SHIP_Y,
1320 SHIP_WIDTH, SHIP_HEIGHT);
1321 break;
1323 /* Shield hit */
1324 bombs[i].state = S_EXPLODE;
1325 bombs[i].target = TARGET_SHIELD;
1326 /* Center explosion around hit pixel in shield */
1327 bombs[i].y -= FIRE_HEIGHT / 2;
1328 fire_alpha(bombs[i].x, bombs[i].y, SLIME_GREEN);
1329 break;
1333 if (bombs[i].target != 0) {
1334 /* Hit ship or shield, continue */
1335 continue;
1338 draw_bomb(i);
1339 break;
1341 default:
1342 /* If we get here state should be < 0, exploding */
1343 bombs[i].state++;
1344 if (bombs[i].state == S_IDLE) {
1345 if (ship_hit) {
1346 /* Erase explosion */
1347 rb->lcd_fillrect(ship_x, SHIP_Y, SHIP_WIDTH, SHIP_HEIGHT);
1348 rb->lcd_update_rect(ship_x, SHIP_Y, SHIP_WIDTH, SHIP_HEIGHT);
1349 ship_hit = false;
1350 ship_frame = 0;
1351 ship_x = PLAYFIELD_X + 2 * LIVES_X;
1352 lives--;
1353 if (lives == 0) {
1354 game_over = true;
1355 return;
1357 draw_lives();
1358 /* Sleep 1s to give player time to examine lives left */
1359 rb->sleep(HZ);
1361 /* Erase explosion (even if ship hit, might be another bomb) */
1362 fire_alpha(bombs[i].x, bombs[i].y, LCD_BLACK);
1364 break;
1370 inline void move_ship(void)
1372 ship_dir += ship_acc;
1373 if (ship_dir > max_ship_speed) {
1374 ship_dir = max_ship_speed;
1376 if (ship_dir < -max_ship_speed) {
1377 ship_dir = -max_ship_speed;
1379 ship_x += ship_dir;
1380 if (ship_x < SHIP_MIN_X) {
1381 ship_x = SHIP_MIN_X;
1383 if (ship_x > SHIP_MAX_X) {
1384 ship_x = SHIP_MAX_X;
1387 draw_ship();
1391 /* Unidentified Flying Object */
1392 void move_ufo(void)
1394 static int ufo_speed;
1395 static int counter;
1396 int mystery_score;
1398 switch (ufo_state) {
1400 case S_IDLE:
1402 if (rb->rand() % 500 == 0) {
1403 /* Uh-oh, it's time to launch a mystery UFO */
1405 /* TODO: Play UFO sound */
1407 if (rb->rand() % 2) {
1408 ufo_speed = UFO_SPEED;
1409 ufo_x = PLAYFIELD_X;
1410 } else {
1411 ufo_speed = -UFO_SPEED;
1412 ufo_x = LCD_WIDTH - PLAYFIELD_X - UFO_WIDTH;
1414 ufo_state = S_ACTIVE;
1415 /* UFO will be drawn next frame */
1417 break;
1419 case S_ACTIVE:
1420 /* Erase old pos */
1421 rb->lcd_fillrect(ufo_x, UFO_Y, UFO_WIDTH, UFO_HEIGHT);
1422 /* Move */
1423 ufo_x += ufo_speed;
1424 /* Check bounds */
1425 if (ufo_x < PLAYFIELD_X || ufo_x > LCD_WIDTH - PLAYFIELD_X - UFO_WIDTH) {
1426 ufo_state = S_IDLE;
1427 break;
1429 /* Draw new pos */
1430 rb->lcd_bitmap(invadrox_ufo, ufo_x, UFO_Y, UFO_WIDTH, UFO_HEIGHT);
1431 break;
1433 case S_SHOWSCORE:
1434 counter++;
1435 if (counter == S_IDLE) {
1436 /* Erase mystery number */
1437 rb->lcd_fillrect(ufo_x, UFO_Y, 3 * NUMBERS_WIDTH + 2 * NUM_SPACING, FONT_HEIGHT);
1438 ufo_state = S_IDLE;
1440 break;
1442 default:
1443 /* Exploding */
1444 ufo_state++;
1445 if (ufo_state == S_IDLE) {
1446 /* Erase explosion */
1447 rb->lcd_fillrect(ufo_x, UFO_Y - 1, UFO_EXPLODE_WIDTH, UFO_EXPLODE_HEIGHT);
1448 ufo_state = S_SHOWSCORE;
1449 counter = S_EXPLODE * 4;
1450 /* Draw mystery_score, sleep, increase score and continue */
1451 mystery_score = 50 + (rb->rand() % 6) * 50;
1452 if (mystery_score < 100) {
1453 draw_number(ufo_x, UFO_Y, mystery_score, 2);
1454 } else {
1455 draw_number(ufo_x, UFO_Y, mystery_score, 3);
1457 score += mystery_score;
1458 draw_score();
1460 break;
1465 void draw_background(void)
1468 rb->lcd_bitmap(invadrox_background, 0, 0, LCD_WIDTH, LCD_HEIGHT);
1469 rb->lcd_update();
1473 void new_level(void)
1475 int i;
1477 draw_background();
1478 /* Give an extra life for each new level */
1479 if (lives < MAX_LIVES) {
1480 lives++;
1482 draw_lives();
1484 /* Score */
1485 draw_score();
1486 draw_number(HISCORENUM_X, SCORENUM_Y, hiscore.score, 4);
1488 level++;
1489 draw_level();
1490 level_finished = false;
1492 ufo_state = S_IDLE;
1494 /* Init alien positions and states */
1495 for (i = 0; i < 4 * ALIENS; i++) {
1496 aliens[i].x = 0 + (i % ALIENS) * ((ALIEN_WIDTH + ALIEN_SPACING) / ALIEN_SPEED);
1497 aliens[i].y = 2 * (i / ALIENS);
1498 aliens[i].state = ALIVE;
1500 /* Last row, bombers */
1501 for (i = 4 * ALIENS; i < 5 * ALIENS; i++) {
1502 aliens[i].x = 0 + (i % ALIENS) * ((ALIEN_WIDTH + ALIEN_SPACING) / ALIEN_SPEED);
1503 aliens[i].y = 2 * (i / ALIENS);
1504 aliens[i].state = BOMBER;
1507 /* Init bombs to inactive (S_IDLE) */
1508 for (i = 0; i < MAX_BOMBS; i++) {
1509 bombs[i].state = S_IDLE;
1512 /* Start aliens closer to earth from level 2 */
1513 for (i = 0; i < 5 * ALIENS; i++) {
1514 if (level < 6) {
1515 aliens[i].y += level - 1;
1516 } else {
1517 aliens[i].y += 5;
1521 /* Max concurrent bombs */
1522 max_bombs = 1;
1524 gamespeed = 2;
1526 if (level > 1) {
1527 max_bombs++;
1530 /* Increase speed */
1531 if (level > 2) {
1532 gamespeed++;
1535 if (level > 3) {
1536 max_bombs++;
1539 /* Increase speed more */
1540 if (level > 4) {
1541 gamespeed++;
1544 if (level > 5) {
1545 max_bombs++;
1548 /* 4 shields */
1549 for (i = 1; i <= 4; i++) {
1550 rb->lcd_bitmap(invadrox_shield,
1551 PLAYFIELD_X + i * PLAYFIELD_WIDTH / 5 - SHIELD_WIDTH / 2,
1552 SHIELD_Y, SHIELD_WIDTH, SHIELD_HEIGHT);
1555 /* Bottom line */
1556 rb->lcd_set_foreground(SLIME_GREEN);
1557 rb->lcd_hline(PLAYFIELD_X, LCD_WIDTH - PLAYFIELD_X, PLAYFIELD_Y);
1558 /* Restore foreground to black (for fast erase later). */
1559 rb->lcd_set_foreground(LCD_BLACK);
1561 ship_x = PLAYFIELD_X + 2 * LIVES_X;
1562 if (level == 1) {
1563 old_ship_x = ship_x;
1565 ship_dir = 0;
1566 ship_acc = 0;
1567 ship_frame = 0;
1568 ship_hit = false;
1569 fire = S_IDLE;
1570 /* Start moving the bottom row left to right */
1571 curr_alien = 4 * ALIENS;
1572 aliens_paralyzed = 0;
1573 aliens_right = true;
1574 aliens_down = false;
1575 hit_left_border = false;
1576 hit_right_border = false;
1577 /* TODO: Change max_ship_speed to 3 at higher levels */
1578 max_ship_speed = 2;
1580 draw_aliens();
1582 rb->lcd_update();
1586 void init_invadrox(void)
1588 int i;
1590 /* Seed random number generator with a "random" number */
1591 rb->srand(rb->get_time()->tm_sec + rb->get_time()->tm_min * 60);
1593 /* Precalculate start of each scanline */
1594 for (i = 0; i < LCD_HEIGHT; i++) {
1595 #if (LCD_DEPTH >= 8)
1596 ytab[i] = i * LCD_WIDTH;
1597 #elif (LCD_DEPTH == 2) && (LCD_PIXELFORMAT == HORIZONTAL_PACKING)
1598 ytab[i] = i * (LCD_WIDTH / 4);
1599 #elif (LCD_DEPTH == 2) && (LCD_PIXELFORMAT == VERTICAL_PACKING)
1600 ytab[i] = (i / 4) * LCD_WIDTH;
1601 #else
1602 #error pixelformat not implemented yet
1603 #endif
1606 rb->lcd_set_background(LCD_BLACK);
1607 rb->lcd_set_foreground(LCD_BLACK);
1609 if (highscore_load(HISCOREFILE, &hiscore, 1) < 0) {
1610 /* Init hiscore to 0 */
1611 rb->strlcpy(hiscore.name, "Invader", sizeof(hiscore.name));
1612 hiscore.score = 0;
1613 hiscore.level = 1;
1616 /* Init alien types in aliens array */
1617 for (i = 0; i < 1 * ALIENS; i++) {
1618 aliens[i].type = 0; /* Kang */
1620 for (; i < 3 * ALIENS; i++) {
1621 aliens[i].type = 1; /* Kodos */
1623 for (; i < 5 * ALIENS; i++) {
1624 aliens[i].type = 2; /* Serak */
1628 /* Save screen white color */
1629 rb->lcd_set_foreground(LCD_WHITE);
1630 rb->lcd_drawpixel(0, 0);
1631 rb->lcd_update_rect(0, 0, 1, 1);
1632 screen_white = get_pixel(0, 0);
1634 /* Save screen green color */
1635 rb->lcd_set_foreground(SLIME_GREEN);
1636 rb->lcd_drawpixel(0, 0);
1637 rb->lcd_update_rect(0, 0, 1, 1);
1638 screen_green = get_pixel(0, 0);
1640 /* Restore black foreground */
1641 rb->lcd_set_foreground(LCD_BLACK);
1643 new_level();
1645 /* Flash score at start */
1646 for (i = 0; i < 5; i++) {
1647 rb->lcd_fillrect(SCORENUM_X, SCORENUM_Y,
1648 4 * NUMBERS_WIDTH + 3 * NUM_SPACING,
1649 FONT_HEIGHT);
1650 rb->lcd_update_rect(SCORENUM_X, SCORENUM_Y,
1651 4 * NUMBERS_WIDTH + 3 * NUM_SPACING,
1652 FONT_HEIGHT);
1653 rb->sleep(HZ / 10);
1654 draw_number(SCORENUM_X, SCORENUM_Y, score, 4);
1655 rb->sleep(HZ / 10);
1660 inline bool handle_buttons(void)
1662 static unsigned int oldbuttonstate = 0;
1664 unsigned int released, pressed, newbuttonstate;
1666 if (ship_hit) {
1667 /* Don't allow ship movement during explosion */
1668 newbuttonstate = 0;
1669 } else {
1670 newbuttonstate = rb->button_status();
1672 if(newbuttonstate == oldbuttonstate) {
1673 if (newbuttonstate == 0) {
1674 /* No button pressed. Stop ship. */
1675 ship_acc = 0;
1676 if (ship_dir > 0) {
1677 ship_dir--;
1679 if (ship_dir < 0) {
1680 ship_dir++;
1683 /* return false; */
1684 goto check_usb;
1686 released = ~newbuttonstate & oldbuttonstate;
1687 pressed = newbuttonstate & ~oldbuttonstate;
1688 oldbuttonstate = newbuttonstate;
1689 if (pressed) {
1690 if (pressed & ACTION_LEFT) {
1691 if (ship_acc > -1) {
1692 ship_acc--;
1695 if (pressed & ACTION_RIGHT) {
1696 if (ship_acc < 1) {
1697 ship_acc++;
1700 if (pressed & ACTION_FIRE) {
1701 if (fire == S_IDLE) {
1702 /* Fire shot */
1703 fire_x = ship_x + SHIP_WIDTH / 2;
1704 fire_y = SHIP_Y - SHOT_HEIGHT;
1705 fire = S_ACTIVE;
1706 /* TODO: play fire sound */
1709 if (pressed & ACTION_QUIT) {
1710 rb->splash(HZ * 1, "Quit");
1711 return true;
1714 if (released) {
1715 if ((released & ACTION_LEFT)) {
1716 if (ship_acc < 1) {
1717 ship_acc++;
1720 if ((released & ACTION_RIGHT)) {
1721 if (ship_acc > -1) {
1722 ship_acc--;
1727 check_usb:
1729 /* Quit if USB is connected */
1730 if (rb->button_get(false) == SYS_USB_CONNECTED) {
1731 return true;
1734 return false;
1738 void game_loop(void)
1740 int i, end;
1742 /* Print dimensions (just for debugging) */
1743 DBG("%03dx%03dx%02d\n", LCD_WIDTH, LCD_HEIGHT, LCD_DEPTH);
1745 /* Init */
1746 init_invadrox();
1748 while (1) {
1749 /* Convert CYCLETIME (in ms) to HZ */
1750 end = *rb->current_tick + (CYCLETIME * HZ) / 1000;
1752 if (handle_buttons()) {
1753 return;
1756 /* Animate */
1757 move_ship();
1758 move_fire();
1760 /* Check if level is finished (marked by move_fire) */
1761 if (level_finished) {
1762 /* TODO: Play level finished sound */
1763 new_level();
1766 move_ufo();
1768 /* Move aliens */
1769 if (!aliens_paralyzed && !ship_hit) {
1770 for (i = 0; i < gamespeed; i++) {
1771 if (!move_aliens()) {
1772 if (game_over) {
1773 return;
1779 /* Move alien bombs */
1780 move_bombs();
1781 if (game_over) {
1782 return;
1785 /* Update "playfield" rect */
1786 rb->lcd_update_rect(PLAYFIELD_X, SCORENUM_Y + FONT_HEIGHT,
1787 PLAYFIELD_WIDTH,
1788 PLAYFIELD_Y + 1 - SCORENUM_Y - FONT_HEIGHT);
1790 /* Wait until next frame */
1791 DBG("%ld (%d)\n", end - *rb->current_tick, (CYCLETIME * HZ) / 1000);
1792 if (TIME_BEFORE(*rb->current_tick, end)) {
1793 rb->sleep(end - *rb->current_tick);
1794 } else {
1795 rb->yield();
1798 } /* end while */
1802 /* this is the plugin entry point */
1803 enum plugin_status plugin_start(UNUSED const void* parameter)
1805 rb->lcd_setfont(FONT_SYSFIXED);
1806 /* Turn off backlight timeout */
1807 backlight_force_on(); /* backlight control in lib/helper.c */
1809 /* now go ahead and have fun! */
1810 game_loop();
1812 /* Game Over. */
1813 /* TODO: Play game over sound */
1814 rb->splash(HZ * 2, "Game Over");
1815 if (score > hiscore.score) {
1816 /* Save new hiscore */
1817 highscore_update(score, level, "Invader", &hiscore, 1);
1818 highscore_save(HISCOREFILE, &hiscore, 1);
1821 /* Restore user's original backlight setting */
1822 rb->lcd_setfont(FONT_UI);
1823 /* Turn on backlight timeout (revert to settings) */
1824 backlight_use_settings(); /* backlight control in lib/helper.c */
1826 return PLUGIN_OK;
1832 * GNU Emacs settings: Kernighan & Richie coding style with
1833 * 4 spaces indent and no tabs.
1834 * Local Variables:
1835 * c-file-style: "k&r"
1836 * c-basic-offset: 4
1837 * indent-tabs-mode: nil
1838 * End: