Created Wed, 14 Sep 2011 10:31:46 +0000 by 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?
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
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!
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
Sun, 02 Oct 2011 19:15:43 +0000
Cool, have you passed on this code to the GameDuino guys already?
Mon, 03 Oct 2011 16:24:03 +0000
I thought I had back in July! If not feel free to pass it around.
Mon, 10 Oct 2011 13:08:03 +0000
Just to be sure... Does this mean that Gameduino is FULLY functionnal on UNO32?!
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?
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++;
}
}
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:
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!