Asteroids game using a Gameduino
A version of the classic asteroids game for the mbed using gameduino. The game currently runs far too fast and is unplayable.
Please see http://mbed.org/users/TheChrisyd/code/Gameduino/ for more information
Diff: main.cpp
- Revision:
- 0:2175bf475f30
- Child:
- 1:3907d67048f1
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/main.cpp Thu Dec 20 20:49:21 2012 +0000 @@ -0,0 +1,919 @@ +#include "mbed.h" +#include "GD.h" +#include "shield.h" + +GDClass GD(ARD_MOSI, ARD_MISO, ARD_SCK, ARD_D9, USBTX, USBRX) ; +SPI spimain(ARD_MOSI, ARD_MISO, ARD_SCK); // mosi, miso, sclk +DigitalIn down(ARD_A2);//0 +DigitalIn up(ARD_A3);//1 +DigitalIn left(ARD_A1);//2 +DigitalIn right(ARD_A0);//3 + +// ---------------------------------------------------------------------- +// qrand: quick random numbers +// ---------------------------------------------------------------------- + +static uint16_t lfsr = 1; + +static void qrandSeed(int seed) { + if (seed) { + lfsr = seed; + } else { + lfsr = 0x947; + } +} + +static byte qrand1() { // a random bit + lfsr = (lfsr >> 1) ^ (-(lfsr & 1) & 0xb400); + return lfsr & 1; +} + +static byte qrand(byte n) { // n random bits + byte r = 0; + while (n--) + r = (r << 1) | qrand1(); + return r; +} + +// ---------------------------------------------------------------------- +// controller: buttons on Arduino pins 3,4,5,6 +// ---------------------------------------------------------------------- + +static void controller_init() { + // Configure input pins with internal pullups + + down.mode(PullDown); + up.mode(PullDown); + left.mode(PullDown); + right.mode(PullDown); + +} + +#define CONTROL_LEFT 1 +#define CONTROL_RIGHT 2 +#define CONTROL_UP 4 +#define CONTROL_DOWN 8 + + +static byte controller_sense(uint16_t clock) { + byte r = 0; + + + if (down) + r |= CONTROL_DOWN; + if (up) + r |= CONTROL_UP; + if (left) + r |= CONTROL_LEFT; + if (right) + r |= CONTROL_RIGHT; + + return r; +} + +// Swap color's red and blue channels +uint16_t swapRB(uint16_t color) { + byte r = 31 & (color >> 10); + byte g = 31 & (color >> 5); + byte b = 31 & color; + return (color & 0x8000) | (b << 10) | (g << 5) | r; +} + +// Swap color's red and green channels +uint16_t swapRG(uint16_t color) { + byte r = 31 & (color >> 10); + byte g = 31 & (color >> 5); + byte b = 31 & color; + return (color & 0x8000) | (g << 10) | (r << 5) | b; +} + +#include "asteroidgraphics.h" +#include "splitscreen.h" + +static void update_score(); + +// [int(127 * math.sin(math.pi * 2 * i / 16)) for i in range(16)] +static PROGMEM signed char charsin[16] = {0, 48, 89, 117, 127, 117, 89, 48, 0, -48, -89, -117, -127, -117, -89, -48}; +char qsin(int a) { +//har)pgm_read_byte_near(charsin + ((a) & 15)) + return charsin[(a & 15)]; +} +#define qcos(a) qsin((a) + 4) + +static char spr2obj[256]; // Maps sprites to owning objects + +/* + +The general idea is that an object table ``objects`` has an entry for +each drawn thing on screen (e.g. player, missile, rock, explosion). +Each class of object has a ``handler`` function that does the necessary +housekeeping and draws the actual sprites. + +As part of the behavior, some classes need to know if they have collided +with anything. In particular the rocks need to know if they have collided +with the player or a missile. The `collide` member points to the +colliding sprite. + +*/ + +struct object { + int x, y; + byte handler, state; + byte collide; +} objects[128]; +#define COORD(c) ((c) << 4) + +static signed char explosions = -1; +static signed char enemies = -1; +static signed char missiles = -1; + +static void push(signed char *stk, byte i) { + objects[i].state = *stk; + *stk = i; +} + +static char pop(signed char *stk) { + signed char r = *stk; + if (0 <= r) { + *stk = objects[r].state; + } + return r; +} + +byte rr[4] = { 0,3,6,5 }; + +static struct { + byte boing, boom, pop; + byte thrust; + byte bass; +} sounds; + +static int player_vx, player_vy; // Player velocity +static int player_invincible, player_dying; +static byte lives; +static long score; +static byte level; + +// Move object po by velocity (vx, vy), optionally keeping in +// player's frame. +// Returns true if the object wrapped screen edge +static bool move(struct object *po, char vx, char vy, byte playerframe = 1) { + bool r = 0; + if (playerframe) { + po->x += (vx - player_vx); + po->y += (vy - player_vy); + } else { + po->x += vx; + po->y += vy; + } + + if (po->x > COORD(416)) + r = 1, po->x -= COORD(432); + else if (po->x < COORD(-16)) + r = 1, po->x += COORD(432); + + if (po->y > COORD(316)) + r = 1, po->y -= COORD(332); + else if (po->y < COORD(-16)) + r = 1, po->y += COORD(332); + return r; +} + + +#define HANDLE_NULL 0 +#define HANDLE_ROCK0 1 +#define HANDLE_ROCK1 2 +#define HANDLE_BANG0 3 +#define HANDLE_BANG1 4 +#define HANDLE_PLAYER 5 +#define HANDLE_MISSILE 6 + +// Expire object i, and return it to the free stk +static void expire(signed char *stk, byte i) { + objects[i].handler = HANDLE_NULL; + push(stk, i); +} + +static void handle_null(byte i, byte state, uint16_t clock) { +} + +static void handle_player(byte i, byte state, uint16_t clock) { + struct object *po = &objects[i]; + byte angle = (po->state & 15); + byte rot1 = (angle & 3); + byte rot2 = rr[3 & (angle >> 2)]; + if (!player_dying && (player_invincible & 1) == 0) + draw_player(200, 150, rot1, rot2); + + static byte prev_control; + byte control = controller_sense(clock); + + char thrust_x, thrust_y; + if (!player_dying && control & CONTROL_DOWN) { // thrust + byte flame_angle = angle ^ 8; + byte d; + for (d = 9; d > 5; d--) { + int flamex = 201 - (((d + (clock&3)) * qsin(flame_angle)) >> 5); + int flamey = 150 - (((d + (clock&3)) * qcos(flame_angle)) >> 5); + if ((player_invincible & 1) == 0) + draw_sparkr(flamex, flamey, rot1, rot2, 1); // collision class K + } + thrust_x = -qsin(angle); + thrust_y = -qcos(angle); + sounds.thrust = 1; + } else { + thrust_x = thrust_y = 0; + sounds.thrust = 0; + } + + player_vx = ((31 * player_vx) + thrust_x) / 32; + player_vy = ((31 * player_vy) + thrust_y) / 32; + + po->x += player_vx; + po->y += player_vy; + + if (clock & 1) { + char rotate = (512) / 400; + if (control & CONTROL_LEFT) + rotate++; + if (control & CONTROL_RIGHT) + rotate--; + po->state = ((angle + rotate) & 15); + } + + if (!player_dying && + !(prev_control & CONTROL_UP) && + (control & CONTROL_UP)) { // shoot! + signed char e = pop(&missiles); + if (0 <= e) { + objects[e].x = COORD(200); + objects[e].y = COORD(150); + objects[e].state = po->state; + objects[e].handler = HANDLE_MISSILE; + } + sounds.boing = 1; + } + prev_control = control; + if (player_invincible) + --player_invincible; + if (player_dying) { + if (--player_dying == 0) { + --lives; + update_score(); + if (lives != 0) { + player_invincible = 48; + } + } + } +} + +static void handle_missile(byte i, byte state, uint16_t clock) { + struct object *po = &objects[i]; + byte angle = (po->state & 15); + byte rot1 = (angle & 3); + byte rot2 = rr[3 & (angle >> 2)]; + draw_sparkr(po->x >> 4, po->y >> 4, rot1, rot2); + char vx = -qsin(po->state), vy = -qcos(po->state); + if (move(po, vx, vy, 0)) { + expire(&missiles, i); + } +} + +static void explodehere(struct object *po, byte handler, uint16_t clock) { + signed char e = pop(&explosions); + if (0 <= e) { + objects[e].x = po->x; + objects[e].y = po->y; + objects[e].handler = handler; + objects[e].state = clock; + } +} + +static void killplayer(uint16_t clock) { + if (!player_invincible && !player_dying) { + signed char e = pop(&explosions); + if (0 <= e) { + objects[e].x = COORD(200); + objects[e].y = COORD(150); + objects[e].handler = HANDLE_BANG1; + objects[e].state = clock; + } + player_dying = 2 * 36; + sounds.boom = 1; + sounds.pop = 1; + } +} + +static byte commonrock(uint16_t clock, byte i, byte speed, void df(int x, int y, byte anim, byte rot, byte jk)) { + struct object *po = &objects[i]; + + byte move_angle = po->state >> 4; + char vx = (speed * -qsin(move_angle)) >> 6, vy = (speed * -qcos(move_angle)) >> 6; + move(po, vx, vy); + + byte angle = (clock * speed) >> 4; + if (po->state & 1) + angle = ~angle; + byte rot1 = (angle & 3); + byte rot2 = rr[3 & (angle >> 2)]; + df(po->x >> 4, po->y >> 4, rot1, rot2, 1); + if (po->collide != 0xff) { + struct object *other = &objects[po->collide]; + switch (other->handler) { + case HANDLE_PLAYER: + killplayer(clock); + break; + case HANDLE_MISSILE: + expire(&missiles, po->collide); // missile is dead + expire(&enemies, i); + return 1; + } + } + return 0; +} + +static void handle_rock0(byte i, byte state, uint16_t clock) { + struct object *po = &objects[i]; + byte speed = 12 + (po->state & 7); + if (commonrock(clock, i, speed, draw_rock0r)) { + explodehere(po, HANDLE_BANG0, clock); + score += 10; + sounds.pop = 1; + } +} + +static void handle_rock1(byte i, byte state, uint16_t clock) { + struct object *po = &objects[i]; + byte speed = 6 + (po->state & 3); + if (commonrock(clock, i, speed, draw_rock1r)) { + int j; + for (j = 0; j < 4; j++) { + char e = pop(&enemies); + if (0 < e) { + objects[e].x = po->x; + objects[e].y = po->y; + objects[e].handler = HANDLE_ROCK0; + objects[e].state = (j << 6) + qrand(6); // spread fragments across 4 quadrants + } + } + explodehere(po, HANDLE_BANG1, clock); + score += 30; + sounds.boom = 1; + } +} + +static void handle_bang0(byte i, byte state, uint16_t clock) { + struct object *po = &objects[i]; + move(po, 0, 0); + byte anim = ((0xff & clock) - state) >> 1; + if (anim < EXPLODE16_FRAMES) + draw_explode16(po->x >> 4, po->y >> 4, anim, 0); + else + expire(&explosions, i); +} + +static void handle_bang1(byte i, byte state, uint16_t clock) { + struct object *po = &objects[i]; + move(po, 0, 0); + byte anim = ((0xff & clock) - state) >> 1; + byte rot = 7 & i; + if (anim < EXPLODE32_FRAMES) + draw_explode32(po->x >> 4, po->y >> 4, anim, rot); + else + expire(&explosions, i); +} + +typedef void (*handler)(byte, byte, uint16_t); +static handler handlers[] = { + handle_null, + handle_rock0, + handle_rock1, + handle_bang0, + handle_bang1, + handle_player, + handle_missile +}; + + +// uncompress one line of the title banner into buffer dst +// title banner lines are run-length encoded +static void titlepaint(char *dst, byte src, byte mask) { + if (src != 0xff) { + PROGMEM prog_uchar *psrc = title_runs + 2 * src; + byte a, b; + do { + a = *psrc++; + b = *psrc++; + while (a < (b & 0x7f)) + dst[a++] |= mask; + } while (!(b & 0x80)); + } +} + +// draw a title banner column src (0-511) to screen column dst (0-63). +static void column(byte dst, byte src) { + static char scratch[76]; + memset(scratch, 0, sizeof(scratch)); + //PROGMEM prog_uchar* titleadd = title + 2 * src; + prog_uchar line = title[2 * src];//*titleadd++;//pgm_read_byte_near(title + 2 * src); + titlepaint(scratch, line, 1); + line = title[2 * src+1];//*titleadd;//pgm_read_byte_near(title + 2 * src + 1); + titlepaint(scratch, line, 2); + + byte j; + for (j = 0; j < 38; j++) { + GD.wr(dst + (j << 6), + (((dst + j) & 15) << 4) + + scratch[2 * j] + + (scratch[2 * j + 1] << 2)); + } +} + +static void setup_sprites() { + GD.copy(PALETTE16A, palette16a, sizeof(palette16a)); + GD.copy(PALETTE4A, palette4a, sizeof(palette4a)); + GD.copy(PALETTE16B, palette16b, sizeof(palette16b)); + + // Use the first two 256-color palettes as pseudo-16 color palettes + int i; + for (i = 0; i < 256; i++) { + + // palette 0 decodes low nibble, hence (i & 15) + //PROGMEM prog_uchar* pallow = palette256a + ((i & 15) << 1); + uint16_t rgb = palette256a[((i & 15) << 1)]; + GD.wr16(RAM_SPRPAL + (i << 1), rgb); + + // palette 1 decodes nigh nibble, hence (i >> 4) + //PROGMEM prog_uchar* palhigh = palette256a + ((i >> 4) << 1); + rgb = palette256a[((i >> 4) << 1)]; + GD.wr16(RAM_SPRPAL + 512 + (i << 1), rgb); + } + + GD.uncompress(RAM_SPRIMG, asteroid_images_compressed); + GD.wr(JK_MODE, 1); +} + +// Run the object handlers, keeping track of sprite ownership in spr2obj +static void runobjects(uint16_t r) { + int i; + GD.__wstartspr(((r & 1) ? 256 : 0)); // write sprites to other frame + for (i = 0; i < 128; i++) { + struct object *po = &objects[i]; + handler h = (handler)handlers[po->handler]; + byte loSpr = GD.spr; + (*h)(i, po->state, r); + while (loSpr < GD.spr) { + spr2obj[loSpr++] = i; + } + } + // Hide all the remaining sprites + do + GD.xhide(); + while (GD.spr); + GD.__end(); +} + +// ---------------------------------------------------------------------- +// map +// ---------------------------------------------------------------------- + +static byte loaded[8]; +static byte scrap; + +// copy a (w,h) rectangle from the source image (x,y) into picture RAM +static void rect(unsigned int dst, byte x, byte y, byte w, byte h) { + PROGMEM prog_uchar *src = bg_pic + (16 * y) + x; + while (h--) { + GD.copy(dst, src, w); + dst += 64; + src += 16; + } +} + +static void map_draw(byte strip) { + strip &= 63; // Universe is finite but unbounded: 64 strips + byte s8 = (strip & 7); // Destination slot for this strip (0-7) + if (loaded[s8] != strip) { + qrandSeed(level ^ (strip * 77)); // strip number is the hash... + + // Random star pattern is made from characters 1-15 + GD.__wstart(s8 * (8 * 64)); + int i; + for (i = 0; i < (8 * 64); i++) { + byte r; + if (qrand(3) == 0) + r = qrand(4); + else + r = 0; + spimain.write(r); + } + GD.__end(); + + // Occasional planet, copied from the background char map + if (qrand(2) == 0) { + uint16_t dst = (qrand(3) * 8) + (s8 * (8 * 64)); + switch (qrand(2)) { + case 0: + rect(dst, 0, 1, 6, 6); + break; + case 1: + rect(dst, 7, 1, 6, 6); + break; + case 2: + rect(dst, 0, 7, 8, 4); + break; + case 3: + rect(dst, 8, 7, 5, 5); + break; + } + } + loaded[s8] = strip; + } +} + +static void map_coldstart() { + memset(loaded, 0xff, sizeof(loaded)); + scrap = 0xff; + byte i; + for (i = 0; i < 8; i++) + map_draw(i); +} + +static int atxy(int x, int y) { + return (y << 6) + x; +} + +static void update_score() { + unsigned long s = score; + uint16_t a = atxy(49, scrap << 3); + byte i; + //void* code; + for (i = 0; i < 6; i++) { + PROGMEM prog_uchar* digitcodes = (bg_pic + (16 * 30))+(s % 10); + + //code = &digitcodes + (s % 10); + GD.wr(a--, *digitcodes);//pgm_read_byte_near(digitcodes + (s % 10)) ; + s /= 10; + } + PROGMEM prog_uchar* digitcodes = bg_pic + (16 * 30) + lives; + //code = &digitcodes + lives; + GD.wr(atxy(0, scrap << 3), *digitcodes);// pgm_read_byte_near(digitcodes + lives)); +} + +static void map_refresh(byte strip) { + byte i; + byte newscrap = 7 & (strip + 7); + if (scrap != newscrap) { + scrap = newscrap; + + uint16_t scrapline = scrap << 6; + GD.wr16(COMM+2, 0x8000 | scrapline); // show scrapline at line 0 + GD.wr16(COMM+14, 0x8000 | (0x1ff & ((scrapline + 8) - 291))); // show scrapline+8 at line 291 + + GD.fill(atxy(0, scrap << 3), 0, 50); + update_score(); + + GD.fill(atxy(0, 1 + (scrap << 3)), 0, 64); + rect(atxy(0, 1 + (scrap << 3)), 0, 31, 16, 1); + rect(atxy(32, 1 + (scrap << 3)), 0, 31, 16, 1); + + loaded[scrap] = 0xff; + } + wait_ms(1); // wait for raster to pass the top line before overwriting it + for (i = 0; i < 6; i++) + map_draw(strip + i); +} + +static void start_level() { + int i; + + for (i = 0; i < 128; i++) + objects[i].handler = 0; + + player_vx = 0; + player_vy = 0; + player_invincible = 0; + player_dying = 0; + + objects[0].x = 0; + objects[0].y = 0; + objects[0].state = 0; + objects[0].handler = HANDLE_PLAYER; + + // Set up the pools of objects for missiles, enemies, explosions + missiles = 0; + enemies = 0; + explosions = 0; + for (i = 1; i < 16; i++) + push(&missiles, i); + for (i = 16; i < 80; i++) + push(&enemies, i); + for (i = 80; i < 128; i++) + push(&explosions, i); + + // Place asteroids in a ring around the edges of the screen + int x; + if ((level + 3) < 32) { + x = level + 3; + } else { + x = 32; + } + for (i = 0; i < x; i++) { + char e = pop(&enemies); + if (rand()%2 == 0) { + objects[e].x = rand()%2 ? COORD(32) : COORD(400-32); + objects[e].y = rand()%COORD(300); + } else { + objects[e].x = rand()%COORD(400); + objects[e].y = rand()%2 ? COORD(32) : COORD(300-32); + } + objects[e].handler = HANDLE_ROCK1; + objects[e].state = qrand(8); + } + + GD.copy(PALETTE16B, palette16b, sizeof(palette16b)); + for (i = 0; i < 16; i++) { + uint16_t a = PALETTE16B + 2 * i; + uint16_t c = GD.rd16(a); + if (level & 1) + c = swapRB(c); + if (level & 2) + c = swapRG(c); + if (level & 4) + c = swapRB(c); + GD.wr16(a, c); + } + + map_coldstart(); +} + +void setup() { + + GD.begin(); + controller_init(); + +} + +static void title_banner() { + GD.fill(VOICES, 0, 64 * 4); + GD.wr(J1_RESET, 1); + GD.wr(SPR_DISABLE, 1); + GD.wr16(SCROLL_X, 0); + GD.wr16(SCROLL_Y, 0); + GD.fill(RAM_PIC, 0, 4096); + setup_sprites(); + + uint16_t i; + uint16_t j; + + GD.__wstart(RAM_CHR); + for (i = 0; i < 256; i++) { + // bits control lit segments like this: + // 0 1 + // 2 3 + byte a = (i & 1) ? 0x3f : 0; + byte b = (i & 2) ? 0x3f : 0; + byte c = (i & 4) ? 0x3f : 0; + byte d = (i & 8) ? 0x3f : 0; + for (j = 0; j < 3; j++) { + spimain.write(a); + spimain.write(b); + } + spimain.write(0); + spimain.write(0); + for (j = 0; j < 3; j++) { + spimain.write(c); + spimain.write(d); + } + spimain.write(0); + spimain.write(0); + } + + GD.__end(); + + for (i = 0; i < 256; i++) { + GD.setpal(4 * i + 0, RGB(0,0,0)); + //prog_uchar* colptr = title_ramp + 2 * (i >> 4); + prog_uchar color = title_ramp[(i >> 4)];//pgm_read_word_near(title_ramp + 2 * (i >> 4)); + GD.setpal(4 * i + 3, color); + } + + for (i = 0; i < 64; i++) { + column(i, i); + } + + for (i = 0; i < 128; i++) { + objects[i].handler = 0; + objects[i].collide = 0xff; + } + + for (i = 0; i < 128; i++) + push(&enemies, i); + + for (i = 0; i < 40; i++) { + char e = pop(&enemies); + objects[e].x = COORD(rand()%400); + objects[e].y = COORD(rand()%300); + objects[e].handler = qrand1() ? HANDLE_ROCK1 : HANDLE_ROCK0; + objects[e].state = qrand(8); + } + + byte startgame = 0; + for (i = 0; startgame < 50; i++) { + for (j = 0; j < 256; j++) { + byte index = 15 & ((-i >> 2) + (j >> 4)); + prog_uchar* colour = title_ramp + 2 * index; + prog_uchar color = *colour;// pgm_read_word_near(title_ramp + 2 * index); + GD.setpal(4 * j + 3, color); + } + + if (!startgame && + (controller_sense(i) || (i == (2048 - 400)))) { + // explode all rocks! + for (j = 0; j < 128; j++) { + byte h = objects[j].handler; + if ((h == HANDLE_ROCK0) || (h == HANDLE_ROCK1)) + objects[j].handler = HANDLE_BANG1; + objects[j].state = i; + } + startgame = 1; + } + + if (startgame){ + startgame++; + } + + //runobjects(i); + + GD.waitvblank(); + GD.wr(SPR_PAGE, (i & 1)); + GD.wr(SPR_DISABLE, 0); + GD.wr16(SCROLL_X, i); + if ((i & 7) == 0) { + byte x = ((i >> 3) + 56); + column(63 & x, 255 & x); + } + } + + for (i = 0; i < 32; i++) { + for (j = 0; j < 256; j++) { + uint16_t a = RAM_PAL + (8 * j) + 6; + uint16_t pal = GD.rd16(a); + byte r = 31 & (pal >> 10); + byte g = 31 & (pal >> 5); + byte b = 31 & pal; + if (r) r--; + if (g) g--; + if (b) b--; + pal = (r << 10) | (g << 5) | b; + GD.wr16(a, pal); + } + GD.waitvblank(); + GD.waitvblank(); + + } + + GD.fill(RAM_PIC, 0, 4096); + +} + +#define SOUNDCYCLE(state) ((state) = v ? ((state) + 1) : 0) + +int main() { + + setup(); + while (1) { + + title_banner(); + + GD.uncompress(RAM_CHR, bg_chr_compressed); + GD.uncompress(RAM_PAL, bg_pal_compressed); + + GD.wr16(COMM+0, 0); + GD.wr16(COMM+2, 0x8000); + GD.wr16(COMM+4, 8); // split at line 8 + GD.wr16(COMM+6, 177); + GD.wr16(COMM+8, 166); + GD.wr16(COMM+10, 291); // split at line 291 + GD.wr16(COMM+12, 0); + GD.wr16(COMM+14, 0x8000 | (0x1ff & (8 - 291))); // show line 8 at line 292 + GD.microcode(splitscreen_code, sizeof(splitscreen_code)); + + setup_sprites(); + + + memset(&sounds, 0, sizeof(sounds)); + level = 0; + score = 0; + lives = 3; + unsigned int r = 0; + start_level(); + + while (lives) { + int i;//, j; + + runobjects(r); + + for (i = 0; i < 128; i++) + objects[i].collide = 0xff; + + GD.waitvblank(); + // swap frames + GD.wr(SPR_PAGE, (r & 1)); + int scrollx = objects[0].x >> 4; + int scrolly = objects[0].y >> 4; + GD.wr16(COMM+6, scrollx & 0x1ff); + GD.wr16(COMM+8, scrolly & 0x1ff); + map_refresh(scrolly >> 6); + update_score(); + GD.wr16(COMM+12, r); // horizontal scroll the bottom banner + + GD.waitvblank(); + + GD.__start(COLLISION); + for (i = 0; i < 256; i++) { + byte c = spimain.write(0); // c is the colliding sprite number + if (c != 0xff) { + objects[spr2obj[i]].collide = spr2obj[c]; + } + } + GD.__end(); + + if (sounds.boing) { + byte x; + if ((16 - (sounds.boing - 1) * 2) <0) { + x = 0; + } else { + x = 16 - (sounds.boing - 1) * 2; + } + byte v = x; //max(0, 16 - (sounds.boing - 1) * 2); + GD.voice(0, 0, 4 * 4000 - 700 * sounds.boing, v/2, v/2); + GD.voice(1, 1, 1000 - 100 * sounds.boing, v, v); + SOUNDCYCLE(sounds.boing); + } + if (sounds.boom) { + byte x; + if ((96 - (sounds.boom - 1) * 6) <0) { + x = 0; + } else { + x = 96 - (sounds.boom - 1) * 6; + } + byte v = x;//max(0, 96 - (sounds.boom - 1) * 6); + GD.voice(2, 0, 220, v, v); + GD.voice(3, 1, 220/8, v/2, v/2); + SOUNDCYCLE(sounds.boom); + } + if (sounds.pop) { + byte x; + if ((32 - (sounds.pop - 1) * 3) <0) { + x = 0; + } else { + x = 32 - (sounds.pop - 1) * 3; + } + byte v = x;//max(0, 32 - (sounds.pop - 1) * 3); + GD.voice(4, 0, 440, v, v); + GD.voice(5, 1, 440/8, v/2, v/2); + SOUNDCYCLE(sounds.pop); + } + GD.voice(6, 1, 40, sounds.thrust ? 10 : 0, sounds.thrust ? 10 : 0); + + static byte tune; + if (sounds.bass) { + byte v = sounds.bass < 9 ? 63 : 0; + int f0 = tune ? 130: 163 ; + byte partials[] = { 71, 32, 14, 75, 20, 40 }; + byte i; + for (i = 0; i < 6; i++) { + byte a = (v * partials[i]) >> 8; + GD.voice(7 + i, 0, f0 * (i + 1), a, a); + } + SOUNDCYCLE(sounds.bass); + } + static byte rhythm; + byte x; + if ((24 - level) <10) { + x = 10; + } else { + x = 24 - level; + } + if (++rhythm >= x) { + sounds.bass = 1; + rhythm = 0; + tune = !tune; + } + + byte nenemies = 64; + byte pe = enemies; + while (pe) { + pe = objects[pe].state; + nenemies--; + } + if (nenemies == 0) { + level++; + start_level(); + } + + r++; + } + } +} \ No newline at end of file