Asteroids game using a Gameduino

Dependencies:   Gameduino mbed

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

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