chipKIT® Development Platform

Inspired by Arduino™

GameDuino

Created Wed, 14 Sep 2011 10:31:46 +0000 by wvholst


wvholst

Wed, 14 Sep 2011 10:31:46 +0000

Hi all, a few weeks ago I got a GameDuino shield (to which the filtergods do not allow me to link). I soon figured out that my ambitions to make a Mario-like platform on it are probably exceeding the storage capabilities of the regular Arduinos, so I got the Chipkip UNO32.

However, the GameDuino libraries seem to look rather deep under the hood and cause compiler errors, such as:

/include/avr/pgmspace.h:4:2: error: #error ******** This sketch or library uses AVR-specific code that may not work with the chipKIT platform. See this forum for more information on porting code to chipKIT [xxxxx] ******** /Users/wvholst/Documents/Arduino/libraries/Gameduino/GD.cpp: In static member function 'static void GDClass::begin()': /Users/wvholst/Documents/Arduino/libraries/Gameduino/GD.cpp:33:3: error: 'SPSR' was not declared in this scope /Users/wvholst/Documents/Arduino/libraries/Gameduino/GD.cpp:33:16: error: 'SPI2X' was not declared in this scope

The designer of the GameDuino is willing to support the UNO32 and needs examples of the SPI init sequence for this. Where can I find those?


GeneApperson

Wed, 14 Sep 2011 17:14:19 +0000

The necessary code is in the SPI library. Look in [install dir]\hardware\pic32\libraries\SPI.cpp. The init code is in the function SPIClass::begin();

The SPI code is very simple.

Gene Apperson Digilent


hairymnstr

Thu, 15 Sep 2011 14:46:10 +0000

Just a comment on your original problem of memory, it might be worth trying to write a level interpreter for the Arduino/ChipKit and use an SD card for storing the actual game, if you try and save much level info you'll fill 128K pretty quick!


lloyddean

Sun, 18 Sep 2011 06:01:42 +0000

EDIT: Additional changes added in getting to work with Manic Minor

CD.h

/*
 * Copyright (C) 2011 by James Bowman <jamesb@excamera.com>
 * Gameduino library for arduino.
 *
 */

#ifndef _GD_H_INCLUDED
#define _GD_H_INCLUDED

// define SS_PIN before including "GD.h" to override this
#ifndef SS_PIN
#define SS_PIN 9
#endif

#if defined(BOARD_maple) || defined(__PIC32MX__)

    #if defined(BOARD_maple)
    
        #include "wirish.h"
        
        typedef const unsigned char         prog_uchar;

        #define Serial                      SerialUSB
        #define PROGMEM                     const
        
        extern HardwareSPI                  SPI;
        
        #include <stdio.h>
        #include <stdint.h>
        #include <string.h>

    #endif
    
    #define pgm_read_byte_near(adr)         (*(prog_uint8_t *)(adr))
    #define pgm_read_byte(adr)              (*(prog_uint8_t *)(adr))
    #define pgm_read_word_near(adr)         (*(prog_uint16_t*)(adr))
    #define pgm_read_word(adr)              (*(prog_uint16_t*)(adr))
    #define pgm_read_dword_near(adr)        (*(prog_uint32_t*)(adr))
    #define pgm_read_dword(adr)             (*(prog_uint32_t*)(adr))

#endif

class GDClass {
public:
  static void begin();
  static void end();
  static void __start(uint16_t addr);
  static void __wstart(uint16_t addr);
  static void __end(void);
  static byte rd(uint16_t addr);
  static void wr(uint16_t addr, byte v);
  static uint16_t rd16(uint16_t addr);
  static void wr16(uint16_t addr, uint16_t v);
  static void fill(int16_t addr, byte v, uint16_t count);
  static void copy(uint16_t addr, prog_uchar *src, int16_t count);
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
  static void copy(uint16_t addr, uint_farptr_t src, int16_t count);
  static void microcode(uint_farptr_t src, int16_t count);
  static void uncompress(uint16_t addr, uint_farptr_t src);
#endif

  static void setpal(int16_t pal, uint16_t rgb);
  static void sprite(int16_t spr, int16_t x, int16_t y, byte image, byte palette, byte rot = 0, byte jk = 0);
  static void sprite2x2(int16_t spr, int16_t x, int16_t y, byte image, byte palette, byte rot = 0, byte jk = 0);
  static void waitvblank();
  static void microcode(prog_uchar *src, int16_t count);
  static void uncompress(uint16_t addr, prog_uchar *src);

  static void voice(int16_t v, byte wave, uint16_t freq, byte lamp, byte ramp);
  static void ascii();
  static void putstr(int16_t x, int16_t y, const char *s);

  static void screenshot(uint16_t frame);

  void __wstartspr(uint16_t spr = 0);
  void xsprite(int16_t ox, int16_t oy, char x, char y, byte image, byte palette, byte rot = 0, byte jk = 0);
  void xhide();

  byte spr;   // Current sprite, incremented by xsprite/xhide above
};

extern GDClass GD;

#define RGB(r,g,b) ((((r) >> 3) << 10) | (((g) >> 3) << 5) | ((b) >> 3))
#define TRANSPARENT (1 << 15) // transparent for chars and sprites

#define RAM_PIC     0x0000    // Screen Picture, 64 x 64 = 4096 bytes
#define RAM_CHR     0x1000    // Screen Characters, 256 x 16 = 4096 bytes
#define RAM_PAL     0x2000    // Screen Character Palette, 256 x 8 = 2048 bytes

#define IDENT         0x2800
#define REV           0x2801
#define FRAME         0x2802
#define VBLANK        0x2803
#define SCROLL_X      0x2804
#define SCROLL_Y      0x2806
#define JK_MODE       0x2808
#define J1_RESET      0x2809
#define SPR_DISABLE   0x280a
#define SPR_PAGE      0x280b
#define IOMODE        0x280c

#define BG_COLOR      0x280e
#define SAMPLE_L      0x2810
#define SAMPLE_R      0x2812

#define MODULATOR     0x2814

#define SCREENSHOT_Y  0x281e

#define PALETTE16A 0x2840   // 16-color palette RAM A, 32 bytes
#define PALETTE16B 0x2860   // 16-color palette RAM B, 32 bytes
#define PALETTE4A  0x2880   // 4-color palette RAM A, 8 bytes
#define PALETTE4B  0x2888   // 4-color palette RAM A, 8 bytes
#define COMM       0x2890   // Communication buffer
#define COLLISION  0x2900   // Collision detection RAM, 256 bytes
#define VOICES     0x2a00   // Voice controls
#define J1_CODE    0x2b00   // J1 coprocessor microcode RAM
#define SCREENSHOT 0x2c00   // screenshot line RAM

#define RAM_SPR     0x3000    // Sprite Control, 512 x 4 = 2048 bytes
#define RAM_SPRPAL  0x3800    // Sprite Palettes, 4 x 256 = 2048 bytes
#define RAM_SPRIMG  0x4000    // Sprite Image, 64 x 256 = 16384 bytes

#ifndef GET_FAR_ADDRESS // at some point this will become official... https://savannah.nongnu.org/patch/?6352
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
#define GET_FAR_ADDRESS(var)                          \
({                                                    \
    uint_farptr_t tmp;                                \
                                                      \
    __asm__ __volatile__(                             \
                                                      \
            "ldi    %A0, lo8(%1)"           "\n\t"    \
            "ldi    %B0, hi8(%1)"           "\n\t"    \
            "ldi    %C0, hh8(%1)"           "\n\t"    \
            "clr    %D0"                    "\n\t"    \
        :                                             \
            "=d" (tmp)                                \
        :                                             \
            "p"  (&(var))                             \
    );                                                \
    tmp;                                              \
}) 
#else
#define GET_FAR_ADDRESS(var) (var)
#endif
#endif

#endif

CD.cpp

/*
 * Copyright (c) 2011 by James Bowman <jamesb@excamera.com>
 * Gameduino library for arduino.
 *
 */

#ifdef BOARD_maple

    #include "wirish.h"
    HardwareSPI SPI(1);
    #include "GD.h"
#else
    #include "WProgram.h"

    #if !defined(__PIC32MX__)
        #include <avr/pgmspace.h>
    #endif

    #include <GD.h>

    #if defined(__PIC32MX__)
        #ifdef BYTE
        #undef BYTE
        #endif
    #endif
    #include <SPI.h>
#endif

GDClass GD;

void GDClass::begin()
{
  delay(250); // give Gameduino time to boot
  pinMode(SS_PIN, OUTPUT);
#ifdef BOARD_maple
  SPI.begin(SPI_4_5MHZ, MSBFIRST, 0);
#else
  SPI.begin();
#if !defined(__PIC32MX__)
  SPI.setClockDivider(SPI_CLOCK_DIV2);
#else
  SPI.setClockDivider(SPI_CLOCK_DIV8);
#endif
  SPI.setBitOrder(MSBFIRST);
  SPI.setDataMode(SPI_MODE0);
#if !defined(__PIC32MX__)
  SPSR = (1 << SPI2X);
#endif
#endif
  digitalWrite(SS_PIN, HIGH);

  GD.wr(J1_RESET, 1);           // HALT coprocessor
  fill(RAM_PIC, 0, 1024 * 10);  // Zero all character RAM
  fill(RAM_SPRPAL, 0, 2048);    // Sprite palletes black
  fill(RAM_SPR, 0, 64 * 256);   // Clear all sprite data
  fill(VOICES, 0, 256);         // Silence
  fill(PALETTE16A, 0, 128);     // Black 16-, 4-palletes and COMM

  GD.wr16(SCROLL_X, 0);
  GD.wr16(SCROLL_Y, 0);
  GD.wr(JK_MODE, 0);
  GD.wr(SPR_DISABLE, 0);
  GD.wr(SPR_PAGE, 0);
  GD.wr(IOMODE, 0);
  GD.wr16(BG_COLOR, 0);
  GD.wr16(SAMPLE_L, 0);
  GD.wr16(SAMPLE_R, 0);
  GD.wr16(SCREENSHOT_Y, 0);
  GD.wr(MODULATOR, 64);
  int16_t i;
  __wstart(RAM_SPR);
  for (i = 0; i < 512; i++)
    GD.xhide();
  __end();
}

void GDClass::end() {
}

void GDClass::__start(uint16_t addr) // start an SPI transaction to addr
{
  digitalWrite(SS_PIN, LOW);
  SPI.transfer(highByte(addr));
  SPI.transfer(lowByte(addr));  
}

void GDClass::__wstart(uint16_t addr) // start an SPI write transaction to addr
{
  __start(0x8000|addr);
}

void GDClass::__wstartspr(uint16_t sprnum)
{
  __start((0x8000 | RAM_SPR) + (sprnum << 2));
  spr = 0;
}

void GDClass::__end() // end the SPI transaction
{
  digitalWrite(SS_PIN, HIGH);
}

byte GDClass::rd(uint16_t addr)
{
  __start(addr);
  byte r = SPI.transfer(0);
  __end();
  return r;
}

void GDClass::wr(uint16_t addr, byte v)
{
  __wstart(addr);
  SPI.transfer(v);
  __end();
}

uint16_t GDClass::rd16(uint16_t addr)
{
  uint16_t r;

  __start(addr);
  r = SPI.transfer(0);
  r |= (SPI.transfer(0) << 8);
  __end();
  return r;
}

void GDClass::wr16(uint16_t addr, uint16_t v)
{
  __wstart(addr);
  SPI.transfer(lowByte(v));
  SPI.transfer(highByte(v));
  __end();
}

void GDClass::fill(int16_t addr, byte v, uint16_t count)
{
  __wstart(addr);
  while (count--)
    SPI.transfer(v);
  __end();
}

void GDClass::copy(uint16_t addr, PROGMEM prog_uchar *src, int16_t count)
{
  __wstart(addr);
  while (count--) {
    SPI.transfer(pgm_read_byte_near(src));
    src++;
  }
  __end();
}

#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
void GDClass::copy(uint16_t addr, uint_farptr_t src, int16_t count)
{
  __wstart(addr);
  while (count--) {
    SPI.transfer(pgm_read_byte_far(src));
    src++;
  }
  __end();
}
#endif

void GDClass::microcode(PROGMEM prog_uchar *src, int16_t count)
{
  wr(J1_RESET, 1);
  copy(J1_CODE, src, count);
  wr(J1_RESET, 0);
}

#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
void GDClass::microcode(uint_farptr_t src, int16_t count)
{
  wr(J1_RESET, 1);
  copy(J1_CODE, src, count);
  wr(J1_RESET, 0);
}
#endif

void GDClass::setpal(int16_t pal, uint16_t rgb)
{
  wr16(RAM_PAL + (pal << 1), rgb);
}

void GDClass::sprite(int16_t spr, int16_t x, int16_t y, byte image, byte palette, byte rot, byte jk)
{
  __wstart(RAM_SPR + (spr << 2));
  SPI.transfer(lowByte(x));
  SPI.transfer((palette << 4) | (rot << 1) | (highByte(x) & 1));
  SPI.transfer(lowByte(y));
  SPI.transfer((jk << 7) | (image << 1) | (highByte(y) & 1));
  __end();
}

void GDClass::xsprite(int16_t ox, int16_t oy, char x, char y, byte image, byte palette, byte rot, byte jk)
{
  if (rot & 2)
    x = -16-x;
  if (rot & 4)
    y = -16-y;
  if (rot & 1) {
      int16_t s;
      s = x; x = y; y = s;
  }
  ox += x;
  oy += y;
  SPI.transfer(lowByte(ox));
  SPI.transfer((palette << 4) | (rot << 1) | (highByte(ox) & 1));
  SPI.transfer(lowByte(oy));
  SPI.transfer((jk << 7) | (image << 1) | (highByte(oy) & 1));
  spr++;
}

void GDClass::xhide()
{
  SPI.transfer(lowByte(400));
  SPI.transfer(highByte(400));
  SPI.transfer(lowByte(400));
  SPI.transfer(highByte(400));
  spr++;
}

void GDClass::sprite2x2(int16_t spr, int16_t x, int16_t y, byte image, byte palette, byte rot, byte jk)
{
  __wstart(0x3000 + (spr << 2));
  GD.xsprite(x, y, -16, -16, image + 0, palette, rot, jk);
  GD.xsprite(x, y,   0, -16, image + 1, palette, rot, jk);
  GD.xsprite(x, y, -16,   0, image + 2, palette, rot, jk);
  GD.xsprite(x, y,   0,   0, image + 3, palette, rot, jk);
  __end();
}

void GDClass::waitvblank()
{
  // Wait for the VLANK to go from 0 to 1: this is the start
  // of the vertical blanking interval.

  while (rd(VBLANK) == 1)
    ;
  while (rd(VBLANK) == 0)
    ;
}

/* Fixed ascii font, useful for debug */

#include "font8x8.h"
static byte stretch[16] = {
  0x00, 0x03, 0x0c, 0x0f,
  0x30, 0x33, 0x3c, 0x3f,
  0xc0, 0xc3, 0xcc, 0xcf,
  0xf0, 0xf3, 0xfc, 0xff
};


void GDClass::ascii()
{
  long i;
  for (i = 0; i < 768; i++) {
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
    byte b = pgm_read_byte_far(GET_FAR_ADDRESS(font8x8) + i);
#else
    byte b = pgm_read_byte(font8x8 + i);
#endif
    byte h = stretch[b >> 4];
    byte l = stretch[b & 0xf];
    GD.wr(0x1000 + (16 * ' ') + (2 * i), h);
    GD.wr(0x1000 + (16 * ' ') + (2 * i) + 1, l);
  }
  for (i = 0x20; i < 0x80; i++) {
    GD.setpal(4 * i + 0, TRANSPARENT);
    GD.setpal(4 * i + 3, RGB(255,255,255));
  }
  GD.fill(RAM_PIC, ' ', 4096);
}

void GDClass::putstr(int16_t x, int16_t y, const char *s)
{
  GD.__wstart((y << 6) + x);
  while (*s)
    SPI.transfer(*s++);
  GD.__end();
}

void GDClass::voice(int16_t v, byte wave, uint16_t freq, byte lamp, byte ramp)
{
  __wstart(VOICES + (v << 2));
  SPI.transfer(lowByte(freq));
  SPI.transfer(highByte(freq) | (wave << 7));
  SPI.transfer(lamp);
  SPI.transfer(ramp);
  __end();
}

void GDClass::screenshot(uint16_t frame)
{
  int16_t yy, xx;
  byte undone[38];  // 300-long bitmap of lines pending

  // initialize to 300 ones
  memset(undone, 0xff, 37);
  undone[37] = 0xf;
  int16_t nundone = 300;

  Serial.write(0xa5);   // sync byte
  Serial.write(lowByte(frame));
  Serial.write(highByte(frame));

  while (nundone) {
    // find a pending line a short distance ahead of the raster
    int16_t hwline = GD.rd16(SCREENSHOT_Y) & 0x1ff;
    for (yy = (hwline + 7) % 300; ((undone[yy>>3] >> (yy&7)) & 1) == 0; yy = (yy + 1) % 300)
      ;
    GD.wr16(SCREENSHOT_Y, 0x8000 | yy);   // ask for it

    // housekeeping while waiting: mark line done and send yy
    undone[yy>>3] ^= (1 << (yy&7));
    nundone--;
    Serial.write(lowByte(yy));
    Serial.write(highByte(yy));
    while ((GD.rd(SCREENSHOT_Y + 1) & 0x80) == 0)
      ;

    // Now send the line, compressing zero pixels
    uint16_t zeroes = 0;
    for (xx = 0; xx < 800; xx += 2) {
      uint16_t v = GD.rd16(SCREENSHOT + xx);
      if (v == 0) {
        zeroes++;
      } else {
        if (zeroes) {
          Serial.write(lowByte(zeroes));
          Serial.write(0x80 | highByte(zeroes));
          zeroes = 0;
        }
        Serial.write(lowByte(v));
        Serial.write(highByte(v));
      }
    }
    if (zeroes) {
      Serial.write(lowByte(zeroes));
      Serial.write(0x80 | highByte(zeroes));
    }
  }
  GD.wr16(SCREENSHOT_Y, 0);   // restore screen to normal
}

// near ptr version
class GDflashbits {
public:
  void begin(prog_uchar *s) {
    src = s;
    mask = 0x01;
  }
  byte get1(void) {
    byte r = (pgm_read_byte_near(src) & mask) != 0;
    mask <<= 1;
    if (!mask) {
      mask = 1;
      src++;
    }
    return r;
  }
  unsigned short getn(byte n) {
    unsigned short r = 0;
    while (n--) {
      r <<= 1;
      r |= get1();
    }
    return r;
  }
private:
  prog_uchar *src;
  byte mask;
};

static GDflashbits GDFB;

void GDClass::uncompress(uint16_t addr, prog_uchar *src)
{
  GDFB.begin(src);
  byte b_off = GDFB.getn(4);
  byte b_len = GDFB.getn(4);
  byte minlen = GDFB.getn(2);
  unsigned short items = GDFB.getn(16);
  while (items--) {
    if (GDFB.get1() == 0) {
      GD.wr(addr++, GDFB.getn(8));
    } else {
      int16_t offset = -GDFB.getn(b_off) - 1;
      int16_t l = GDFB.getn(b_len) + minlen;
      while (l--) {
        GD.wr(addr, GD.rd(addr + offset));
        addr++;
      }
    }
  }
}

#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
// far ptr version
class GDflashbitsF {
public:
  void begin(uint_farptr_t s) {
    src = s;
    mask = 0x01;
  }
  byte get1(void) {
    byte r = (pgm_read_byte_far(src) & mask) != 0;
    mask <<= 1;
    if (!mask) {
      mask = 1;
      src++;
    }
    return r;
  }
  unsigned short getn(byte n) {
    unsigned short r = 0;
    while (n--) {
      r <<= 1;
      r |= get1();
    }
    return r;
  }
private:
  uint_farptr_t src;
  byte mask;
};
static GDflashbitsF GDFBF;

void GDClass::uncompress(uint16_t addr, uint_farptr_t src)
{
  GDFBF.begin(src);
  byte b_off = GDFBF.getn(4);
  byte b_len = GDFBF.getn(4);
  byte minlen = GDFBF.getn(2);
  unsigned short items = GDFBF.getn(16);
  while (items--) {
    if (GDFBF.get1() == 0) {
      GD.wr(addr++, GDFBF.getn(8));
    } else {
      int16_t offset = -GDFBF.getn(b_off) - 1;
      int16_t l = GDFBF.getn(b_len) + minlen;
      while (l--) {
        GD.wr(addr, GD.rd(addr + offset));
        addr++;
      }
    }
  }
}
#endif

wvholst

Sun, 02 Oct 2011 19:15:43 +0000

Cool, have you passed on this code to the GameDuino guys already?


lloyddean

Mon, 03 Oct 2011 16:24:03 +0000

I thought I had back in July! If not feel free to pass it around.


Ddall

Mon, 10 Oct 2011 13:08:03 +0000

Just to be sure... Does this mean that Gameduino is FULLY functionnal on UNO32?!


lloyddean

Mon, 10 Oct 2011 16:18:03 +0000

Well, other than my own projects, which seem to work just fine, I've had Asteroids up and running, with a couple of changes, since mid July and everything - joystick, buttons and sound - seem to be working just fine.

Why do you ask?


lloyddean

Mon, 10 Oct 2011 16:21:31 +0000

Here's Asteroids - tested and working:

#include <WProgram.h>

#if defined(__PIC32MX__)
#ifdef BYTE
#undef BYTE
#endif
#endif

#include <SPI.h>
#include <GD.h>


// ----------------------------------------------------------------------
//     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
  byte i;
  for (i = 3; i < 7; i++) {
    pinMode(i, INPUT);
    digitalWrite(i, HIGH);
  }
}

#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 (!digitalRead(5))
    r |= CONTROL_DOWN;
  if (!digitalRead(4))
    r |= CONTROL_UP;
  if (!digitalRead(6))
    r |= CONTROL_LEFT;
  if (!digitalRead(3))
    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 prog_uchar charsin[16] = {0, 48, 89, 117, 127, 117, 89, 48, 0, -48, -89, -117, -127, -117, -89, -48};
#define qsin(a) (char)pgm_read_byte_near(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 char explosions = -1;
static char enemies = -1;
static char missiles = -1;

static void push(char *stk, byte i)
{
  objects[i].state = *stk;
  *stk = i;
}

static char pop(char *stk)
{
  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(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 - analogRead(0)) / 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!
    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)
{
  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) {
    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
};

class GDflashbits {
public:
  void begin(prog_uchar *s) {
    src = s;
    mask = 0x01;
  }
  byte get1(void) {
    byte r = (pgm_read_byte_near(src) & mask) != 0;
    mask <<= 1;
    if (!mask) {
      mask = 1;
      src++;
    }
    return r;
  }
  unsigned short getn(byte n) {
    unsigned short r = 0;
    while (n--) {
      r <<= 1;
      r |= get1();
    }
    return r;
  }
private:
  prog_uchar *src;
  byte mask;
};

static GDflashbits GDFB;

static void GD_uncompress(unsigned int addr, PROGMEM prog_uchar *src)
{
  GDFB.begin(src);
  byte b_off = GDFB.getn(4);
  byte b_len = GDFB.getn(4);
  byte minlen = GDFB.getn(2);
  unsigned short items = GDFB.getn(16);
  while (items--) {
    if (GDFB.get1() == 0) {
      GD.wr(addr++, GDFB.getn(8));
    } else {
      int offset = -GDFB.getn(b_off) - 1;
      int l = GDFB.getn(b_len) + minlen;
      while (l--) {
        GD.wr(addr, GD.rd(addr + offset));
        addr++;
      }
    }
  }
}

// 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) {
    prog_uchar *psrc = title_runs + 2 * src;
    byte a, b;
    do {
      a = pgm_read_byte_near(psrc++);
      b = pgm_read_byte_near(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));
  prog_uchar line1 = pgm_read_byte_near(title + 2 * src);
  titlepaint(scratch, line1, 1);
  prog_uchar line2 = pgm_read_byte_near(title + 2 * src + 1);
  titlepaint(scratch, line2, 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)
    uint16_t rgb = pgm_read_word_near(palette256a + ((i & 15) << 1));
    GD.wr16(RAM_SPRPAL + (i << 1), rgb);

    // palette 1 decodes nigh nibble, hence (i >> 4)
    rgb = pgm_read_word_near(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)
{
  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;
      SPI.transfer(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()
{
  prog_uchar* digitcodes = bg_pic + (16 * 30);
  unsigned long s = score;
  uint16_t a = atxy(49, scrap << 3);
  byte i;
  for (i = 0; i < 6; i++) {
    GD.wr(a--, pgm_read_byte_near(digitcodes + (s % 10)));
    s /= 10;
  }
  GD.wr(atxy(0, scrap << 3), 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;
  }
  delay(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
  for (i = 0; i < min(32, 3 + level); i++) {
    char e = pop(&enemies);
    if (random(2) == 0) {
      objects[e].x = random(2) ? COORD(32) : COORD(400-32);
      objects[e].y = random(COORD(300));
    } else {
      objects[e].x = random(COORD(400));
      objects[e].y = random(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++) {
      SPI.transfer(a);
      SPI.transfer(b);
    }
    SPI.transfer(0);
    SPI.transfer(0);
    for (j = 0; j < 3; j++) {
      SPI.transfer(c);
      SPI.transfer(d);
    }
    SPI.transfer(0);
    SPI.transfer(0);
  }
  GD.__end();
  for (i = 0; i < 256; i++) {
    GD.setpal(4 * i + 0, RGB(0,0,0));
    uint16_t color = 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(random(400));
    objects[e].y = COORD(random(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));
      uint16_t color = 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)

void loop()
{
  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 = SPI.transfer(0);   // c is the colliding sprite number
      if (c != 0xff) {
        objects[spr2obj[i]].collide = spr2obj[c];
      }
    }
    GD.__end();

    if (sounds.boing) {
      byte v = 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 v = 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 v = 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;
    if (++rhythm >= max(24 - level, 10)) {
      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++;
  }
}

Ddall

Tue, 11 Oct 2011 10:57:57 +0000

Why do you ask?

Because I'm thinking about buying one of those for my next project :roll:


lloyddean

Tue, 11 Oct 2011 15:55:47 +0000

Sorry - without visual cues I thought you were expressing doubt that I had it working.

It's an interesting little device. I hope you enjoy it!