mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2024-11-27 01:51:35 +00:00
611 lines
21 KiB
C
611 lines
21 KiB
C
|
|
/*
|
|
* An Apple ][ port of the Cosmic Impalas game
|
|
* described in the book
|
|
* "Making 8-bit Arcade Games in C"
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include <conio.h>
|
|
#include <apple2.h>
|
|
#include <peekpoke.h>
|
|
|
|
// type aliases for byte/signed byte/unsigned 16-bit
|
|
typedef unsigned char byte;
|
|
typedef signed char sbyte;
|
|
typedef unsigned short word;
|
|
|
|
// peeks, pokes, and strobes
|
|
#define STROBE(addr) __asm__ ("sta %w", addr)
|
|
|
|
// speaker click
|
|
#define CLICK STROBE(0xc030)
|
|
|
|
/// HIRES LOOKUP TABLE
|
|
|
|
#define VHEIGHT 192 // number of scanlines
|
|
#define VBWIDTH 40 // number of bytes per scanline
|
|
#define PIXWIDTH 280 // 7 pixels per byte
|
|
|
|
#define LUT(x) (byte*)(0x2000|x)
|
|
|
|
// starting address of each scanline
|
|
static byte* const vidmem[VHEIGHT] = {
|
|
LUT(0x0000), LUT(0x0400), LUT(0x0800), LUT(0x0c00), LUT(0x1000), LUT(0x1400), LUT(0x1800), LUT(0x1c00),
|
|
LUT(0x0080), LUT(0x0480), LUT(0x0880), LUT(0x0c80), LUT(0x1080), LUT(0x1480), LUT(0x1880), LUT(0x1c80),
|
|
LUT(0x0100), LUT(0x0500), LUT(0x0900), LUT(0x0d00), LUT(0x1100), LUT(0x1500), LUT(0x1900), LUT(0x1d00),
|
|
LUT(0x0180), LUT(0x0580), LUT(0x0980), LUT(0x0d80), LUT(0x1180), LUT(0x1580), LUT(0x1980), LUT(0x1d80),
|
|
LUT(0x0200), LUT(0x0600), LUT(0x0a00), LUT(0x0e00), LUT(0x1200), LUT(0x1600), LUT(0x1a00), LUT(0x1e00),
|
|
LUT(0x0280), LUT(0x0680), LUT(0x0a80), LUT(0x0e80), LUT(0x1280), LUT(0x1680), LUT(0x1a80), LUT(0x1e80),
|
|
LUT(0x0300), LUT(0x0700), LUT(0x0b00), LUT(0x0f00), LUT(0x1300), LUT(0x1700), LUT(0x1b00), LUT(0x1f00),
|
|
LUT(0x0380), LUT(0x0780), LUT(0x0b80), LUT(0x0f80), LUT(0x1380), LUT(0x1780), LUT(0x1b80), LUT(0x1f80),
|
|
LUT(0x0028), LUT(0x0428), LUT(0x0828), LUT(0x0c28), LUT(0x1028), LUT(0x1428), LUT(0x1828), LUT(0x1c28),
|
|
LUT(0x00a8), LUT(0x04a8), LUT(0x08a8), LUT(0x0ca8), LUT(0x10a8), LUT(0x14a8), LUT(0x18a8), LUT(0x1ca8),
|
|
LUT(0x0128), LUT(0x0528), LUT(0x0928), LUT(0x0d28), LUT(0x1128), LUT(0x1528), LUT(0x1928), LUT(0x1d28),
|
|
LUT(0x01a8), LUT(0x05a8), LUT(0x09a8), LUT(0x0da8), LUT(0x11a8), LUT(0x15a8), LUT(0x19a8), LUT(0x1da8),
|
|
LUT(0x0228), LUT(0x0628), LUT(0x0a28), LUT(0x0e28), LUT(0x1228), LUT(0x1628), LUT(0x1a28), LUT(0x1e28),
|
|
LUT(0x02a8), LUT(0x06a8), LUT(0x0aa8), LUT(0x0ea8), LUT(0x12a8), LUT(0x16a8), LUT(0x1aa8), LUT(0x1ea8),
|
|
LUT(0x0328), LUT(0x0728), LUT(0x0b28), LUT(0x0f28), LUT(0x1328), LUT(0x1728), LUT(0x1b28), LUT(0x1f28),
|
|
LUT(0x03a8), LUT(0x07a8), LUT(0x0ba8), LUT(0x0fa8), LUT(0x13a8), LUT(0x17a8), LUT(0x1ba8), LUT(0x1fa8),
|
|
LUT(0x0050), LUT(0x0450), LUT(0x0850), LUT(0x0c50), LUT(0x1050), LUT(0x1450), LUT(0x1850), LUT(0x1c50),
|
|
LUT(0x00d0), LUT(0x04d0), LUT(0x08d0), LUT(0x0cd0), LUT(0x10d0), LUT(0x14d0), LUT(0x18d0), LUT(0x1cd0),
|
|
LUT(0x0150), LUT(0x0550), LUT(0x0950), LUT(0x0d50), LUT(0x1150), LUT(0x1550), LUT(0x1950), LUT(0x1d50),
|
|
LUT(0x01d0), LUT(0x05d0), LUT(0x09d0), LUT(0x0dd0), LUT(0x11d0), LUT(0x15d0), LUT(0x19d0), LUT(0x1dd0),
|
|
LUT(0x0250), LUT(0x0650), LUT(0x0a50), LUT(0x0e50), LUT(0x1250), LUT(0x1650), LUT(0x1a50), LUT(0x1e50),
|
|
LUT(0x02d0), LUT(0x06d0), LUT(0x0ad0), LUT(0x0ed0), LUT(0x12d0), LUT(0x16d0), LUT(0x1ad0), LUT(0x1ed0),
|
|
LUT(0x0350), LUT(0x0750), LUT(0x0b50), LUT(0x0f50), LUT(0x1350), LUT(0x1750), LUT(0x1b50), LUT(0x1f50),
|
|
LUT(0x03d0), LUT(0x07d0), LUT(0x0bd0), LUT(0x0fd0), LUT(0x13d0), LUT(0x17d0), LUT(0x1bd0), LUT(0x1fd0)
|
|
};
|
|
|
|
// divide-by 7 table
|
|
const byte DIV7[256] = {
|
|
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4,
|
|
4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 9,
|
|
9, 9, 9, 9, 9, 9,10,10,10,10,10,10,10,11,11,11,11,11,11,11,12,12,12,12,12,12,12,13,13,13,13,13,
|
|
13,13,14,14,14,14,14,14,14,15,15,15,15,15,15,15,16,16,16,16,16,16,16,17,17,17,17,17,17,17,18,18,
|
|
18,18,18,18,18,19,19,19,19,19,19,19,20,20,20,20,20,20,20,21,21,21,21,21,21,21,22,22,22,22,22,22,
|
|
22,23,23,23,23,23,23,23,24,24,24,24,24,24,24,25,25,25,25,25,25,25,26,26,26,26,26,26,26,27,27,27,
|
|
27,27,27,27,28,28,28,28,28,28,28,29,29,29,29,29,29,29,30,30,30,30,30,30,30,31,31,31,31,31,31,31,
|
|
32,32,32,32,32,32,32,33,33,33,33,33,33,33,34,34,34,34,34,34,34,35,35,35,35,35,35,35,36,36,36,36};
|
|
|
|
// modulo-by-7 table
|
|
const byte MOD7[256] = {
|
|
0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3,
|
|
4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0,
|
|
1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4,
|
|
5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1,
|
|
2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5,
|
|
6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2,
|
|
3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6,
|
|
0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3};
|
|
|
|
// bitmask table
|
|
const byte BIT7[7] = { 1, 2, 4, 8, 16, 32, 64 };
|
|
|
|
/// SOUND FUNCTIONS
|
|
|
|
void tone(byte period, byte dur, sbyte mod) {
|
|
word i;
|
|
while (dur--) {
|
|
for (i=0; i<period; i++) ;
|
|
CLICK;
|
|
period += mod;
|
|
}
|
|
}
|
|
|
|
/// GRAPHICS FUNCTIONS
|
|
|
|
// clear screen and set graphics mode
|
|
void clrscr() {
|
|
STROBE(0xc052); // turn off mixed-mode
|
|
STROBE(0xc054); // page 1
|
|
STROBE(0xc057); // hi-res
|
|
STROBE(0xc050); // set graphics mode
|
|
memset((byte*)0x2000, 0, 0x2000); // clear page 1
|
|
}
|
|
|
|
// draw (xor) vertical line
|
|
void xor_vline(byte x, byte y1, byte y2) {
|
|
byte xb = DIV7[x]; // divide x by 7
|
|
byte mask = BIT7[MOD7[x]]; // lookup bitmask for remainder
|
|
byte y;
|
|
for (y=y1; y<=y2; y++) {
|
|
byte* dest = &vidmem[y][xb]; // lookup dest. address
|
|
*dest ^= mask; // XOR mask with destination
|
|
}
|
|
}
|
|
|
|
// draw (xor) a pixel
|
|
void xor_pixel(byte x, byte y) {
|
|
xor_vline(x, y, y); // draw line with 1-pixel height
|
|
}
|
|
|
|
typedef enum {
|
|
OP_DRAW, OP_XOR, OP_ERASE
|
|
} GraphicsOperation;
|
|
|
|
// render a sprite with the given graphics operation
|
|
byte render_sprite(const byte* src, byte x, byte y, byte op) {
|
|
byte i,j;
|
|
byte w = *src++; // get width from 1st byte of sprite
|
|
byte h = *src++; // get height from 2nd byte of sprite
|
|
byte xb = DIV7[x]; // xb = x DIV 7
|
|
byte xs = MOD7[x]; // xs = x MOD 7
|
|
byte result = 0; // result (used only with XOR)
|
|
for (j=0; j<h; j++) {
|
|
byte* dest = &vidmem[y++][xb]; // lookup video address
|
|
byte rest = 0; // rest = leftover bits
|
|
for (i=0; i<w; i++) {
|
|
byte data = *src++; // get next sprite byte
|
|
byte next = (data << xs) | rest; // shift and OR with leftover
|
|
// compute graphics operation, write to dest
|
|
switch (op) {
|
|
case OP_DRAW: *dest++ = next; break;
|
|
case OP_XOR: result |= (*dest++ ^= next) & next; break;
|
|
case OP_ERASE: *dest++ &= ~next; break;
|
|
}
|
|
rest = data >> (7-xs); // save leftover bits
|
|
}
|
|
// compute final byte operation
|
|
switch (op) {
|
|
case OP_DRAW: *dest = rest; break;
|
|
case OP_XOR: result |= (*dest ^= rest) & rest; break;
|
|
case OP_ERASE: *dest &= ~rest; break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void draw_sprite(const byte* src, byte x, byte y) {
|
|
render_sprite(src, x, y, OP_DRAW);
|
|
}
|
|
|
|
// XOR returns non-zero if any pixels were overlapped
|
|
byte xor_sprite(const byte* src, byte x, byte y) {
|
|
return render_sprite(src, x, y, OP_XOR);
|
|
}
|
|
|
|
void erase_sprite(const byte* src, byte x, byte y) {
|
|
render_sprite(src, x, y, OP_ERASE);
|
|
}
|
|
|
|
// clear just sets all bytes to 0, and is fast
|
|
void clear_sprite(const byte* src, byte x, byte y) {
|
|
byte i,j;
|
|
byte w = *src++;
|
|
byte h = *src++;
|
|
byte xb = DIV7[x];
|
|
for (j=0; j<h; j++) {
|
|
byte* dest = &vidmem[y++][xb];
|
|
for (i=0; i<=w; i++) {
|
|
dest[i] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// FONT FUNCTIONS
|
|
|
|
#define LOCHAR 0x20 // lowest character value in font
|
|
#define HICHAR 0x7f // highest character value in font
|
|
|
|
const byte font8x8[HICHAR-LOCHAR+1][8] = {/*{w:8,h:8,bpp:1,count:96}*/
|
|
{ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }, { 0x18,0x18,0x18,0x18,0x00,0x00,0x18,0x00 }, { 0x66,0x66,0x66,0x00,0x00,0x00,0x00,0x00 }, { 0x66,0x66,0xff,0x66,0xff,0x66,0x66,0x00 }, { 0x18,0x7c,0x06,0x3c,0x60,0x3e,0x18,0x00 }, { 0x46,0x66,0x30,0x18,0x0c,0x66,0x62,0x00 }, { 0x3c,0x66,0x3c,0x1c,0xe6,0x66,0xfc,0x00 }, { 0x60,0x30,0x18,0x00,0x00,0x00,0x00,0x00 }, { 0x30,0x18,0x0c,0x0c,0x0c,0x18,0x30,0x00 }, { 0x0c,0x18,0x30,0x30,0x30,0x18,0x0c,0x00 }, { 0x00,0x66,0x3c,0xff,0x3c,0x66,0x00,0x00 }, { 0x00,0x18,0x18,0x7e,0x18,0x18,0x00,0x00 }, { 0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x0c }, { 0x00,0x00,0x00,0x7e,0x00,0x00,0x00,0x00 }, { 0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x00 }, { 0x00,0xc0,0x60,0x30,0x18,0x0c,0x06,0x00 }, { 0x3c,0x66,0x76,0x6e,0x66,0x66,0x3c,0x00 }, { 0x18,0x18,0x1c,0x18,0x18,0x18,0x7e,0x00 }, { 0x3c,0x66,0x60,0x30,0x0c,0x06,0x7e,0x00 }, { 0x3c,0x66,0x60,0x38,0x60,0x66,0x3c,0x00 }, { 0x60,0x70,0x78,0x66,0xfe,0x60,0x60,0x00 }, { 0x7e,0x06,0x3e,0x60,0x60,0x66,0x3c,0x00 }, { 0x3c,0x66,0x06,0x3e,0x66,0x66,0x3c,0x00 }, { 0x7e,0x66,0x30,0x18,0x18,0x18,0x18,0x00 }, { 0x3c,0x66,0x66,0x3c,0x66,0x66,0x3c,0x00 }, { 0x3c,0x66,0x66,0x7c,0x60,0x66,0x3c,0x00 }, { 0x00,0x00,0x18,0x00,0x00,0x18,0x00,0x00 }, { 0x00,0x00,0x18,0x00,0x00,0x18,0x18,0x0c }, { 0x70,0x18,0x0c,0x06,0x0c,0x18,0x70,0x00 }, { 0x00,0x00,0x7e,0x00,0x7e,0x00,0x00,0x00 }, { 0x0e,0x18,0x30,0x60,0x30,0x18,0x0e,0x00 }, { 0x3c,0x66,0x60,0x30,0x18,0x00,0x18,0x00 }, { 0x3c,0x66,0x76,0x76,0x06,0x46,0x3c,0x00 }, { 0x18,0x3c,0x66,0x7e,0x66,0x66,0x66,0x00 }, { 0x3e,0x66,0x66,0x3e,0x66,0x66,0x3e,0x00 }, { 0x3c,0x66,0x06,0x06,0x06,0x66,0x3c,0x00 }, { 0x1e,0x36,0x66,0x66,0x66,0x36,0x1e,0x00 }, { 0x7e,0x06,0x06,0x1e,0x06,0x06,0x7e,0x00 }, { 0x7e,0x06,0x06,0x1e,0x06,0x06,0x06,0x00 }, { 0x3c,0x66,0x06,0x76,0x66,0x66,0x3c,0x00 }, { 0x66,0x66,0x66,0x7e,0x66,0x66,0x66,0x00 }, { 0x3c,0x18,0x18,0x18,0x18,0x18,0x3c,0x00 }, { 0x78,0x30,0x30,0x30,0x30,0x36,0x1c,0x00 }, { 0x66,0x36,0x1e,0x0e,0x1e,0x36,0x66,0x00 }, { 0x06,0x06,0x06,0x06,0x06,0x06,0x7e,0x00 }, { 0xc6,0xee,0xfe,0xd6,0xc6,0xc6,0xc6,0x00 }, { 0x66,0x6e,0x7e,0x7e,0x76,0x66,0x66,0x00 }, { 0x3c,0x66,0x66,0x66,0x66,0x66,0x3c,0x00 }, { 0x3e,0x66,0x66,0x3e,0x06,0x06,0x06,0x00 }, { 0x3c,0x66,0x66,0x66,0x66,0x3c,0x70,0x00 }, { 0x3e,0x66,0x66,0x3e,0x1e,0x36,0x66,0x00 }, { 0x3c,0x66,0x06,0x3c,0x60,0x66,0x3c,0x00 }, { 0x7e,0x18,0x18,0x18,0x18,0x18,0x18,0x00 }, { 0x66,0x66,0x66,0x66,0x66,0x66,0x3c,0x00 }, { 0x66,0x66,0x66,0x66,0x66,0x3c,0x18,0x00 }, { 0xc6,0xc6,0xc6,0xd6,0xfe,0xee,0xc6,0x00 }, { 0x66,0x66,0x3c,0x18,0x3c,0x66,0x66,0x00 }, { 0x66,0x66,0x66,0x3c,0x18,0x18,0x18,0x00 }, { 0x7e,0x60,0x30,0x18,0x0c,0x06,0x7e,0x00 }, { 0x3c,0x0c,0x0c,0x0c,0x0c,0x0c,0x3c,0x00 }, { 0x00,0x06,0x0c,0x18,0x30,0x60,0xc0,0x00 }, { 0x3c,0x30,0x30,0x30,0x30,0x30,0x3c,0x00 }, { 0x00,0x18,0x3c,0x7e,0x18,0x18,0x18,0x18 }, { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff }, { 0x06,0x0c,0x18,0x00,0x00,0x00,0x00,0x00 }, { 0x00,0x00,0x3c,0x60,0x7c,0x66,0x7c,0x00 }, { 0x00,0x06,0x06,0x3e,0x66,0x66,0x3e,0x00 }, { 0x00,0x00,0x3c,0x06,0x06,0x06,0x3c,0x00 }, { 0x00,0x60,0x60,0x7c,0x66,0x66,0x7c,0x00 }, { 0x00,0x00,0x3c,0x66,0x7e,0x06,0x3c,0x00 }, { 0x00,0x70,0x18,0x7c,0x18,0x18,0x18,0x00 }, { 0x00,0x00,0x7c,0x66,0x66,0x7c,0x60,0x3e }, { 0x00,0x06,0x06,0x3e,0x66,0x66,0x66,0x00 }, { 0x00,0x18,0x00,0x1c,0x18,0x18,0x3c,0x00 }, { 0x00,0x60,0x00,0x60,0x60,0x60,0x60,0x3c }, { 0x00,0x06,0x06,0x36,0x1e,0x36,0x66,0x00 }, { 0x00,0x1c,0x18,0x18,0x18,0x18,0x3c,0x00 }, { 0x00,0x00,0x66,0xfe,0xfe,0xd6,0xc6,0x00 }, { 0x00,0x00,0x3e,0x66,0x66,0x66,0x66,0x00 }, { 0x00,0x00,0x3c,0x66,0x66,0x66,0x3c,0x00 }, { 0x00,0x00,0x3e,0x66,0x66,0x3e,0x06,0x06 }, { 0x00,0x00,0x7c,0x66,0x66,0x7c,0x60,0x60 }, { 0x00,0x00,0x3e,0x66,0x06,0x06,0x06,0x00 }, { 0x00,0x00,0x7c,0x06,0x3c,0x60,0x3e,0x00 }, { 0x00,0x18,0x7e,0x18,0x18,0x18,0x70,0x00 }, { 0x00,0x00,0x66,0x66,0x66,0x66,0x7c,0x00 }, { 0x00,0x00,0x66,0x66,0x66,0x3c,0x18,0x00 }, { 0x00,0x00,0xc6,0xd6,0xfe,0x7c,0x6c,0x00 }, { 0x00,0x00,0x66,0x3c,0x18,0x3c,0x66,0x00 }, { 0x00,0x00,0x66,0x66,0x66,0x7c,0x30,0x1e }, { 0x00,0x00,0x7e,0x30,0x18,0x0c,0x7e,0x00 }, { 0x38,0x0c,0x0c,0x06,0x0c,0x0c,0x38,0x00 }, { 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18 }, { 0x1c,0x30,0x30,0x60,0x30,0x30,0x1c,0x00 }, { 0x00,0x4c,0x32,0x00,0x00,0x00,0x00,0x00 }, { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 },
|
|
};
|
|
|
|
// draw character from column 0..39, row 0..23
|
|
void draw_char(char ch, byte col, byte row) {
|
|
byte i;
|
|
const byte* src = &font8x8[(ch-LOCHAR)][0];
|
|
byte y = row*8;
|
|
for (i=0; i<8; i++) {
|
|
byte* dest = &vidmem[y++][col];
|
|
*dest = *src;
|
|
src += 1;
|
|
}
|
|
}
|
|
|
|
// draw string starting at row/col (vert 1 = draw vertical)
|
|
void draw_string(const char* str, byte col, byte row, byte vert) {
|
|
do {
|
|
byte ch = *str++;
|
|
if (!ch) break;
|
|
draw_char(ch, col, row);
|
|
if (vert) row++; else col++;
|
|
} while (1);
|
|
}
|
|
|
|
// draw 4-digit BCD word
|
|
void draw_bcd_word(word bcd, byte col, byte row, byte vert) {
|
|
byte j;
|
|
if (vert) row+=3; else col+=3; // move to rightmost digit
|
|
for (j=0; j<4; j++) {
|
|
draw_char('0'+(bcd&0xf), col, row);
|
|
if (vert) row--; else col--;
|
|
bcd >>= 4;
|
|
}
|
|
}
|
|
|
|
// add two 4-digit BCD words
|
|
word bcd_add(word a, word b) {
|
|
word result;
|
|
__asm__ ("sed"); // set decimal (BCD) mode
|
|
result = a+b;
|
|
__asm__ ("cld"); // clear BCD mode
|
|
return result;
|
|
}
|
|
|
|
//
|
|
// GAME GRAPHICS
|
|
//
|
|
|
|
const byte player_bitmap[] =
|
|
{3,16,/*{w:24,h:16,bpp:1}*/0x00,0x00,0x00,0x00,0x18,0x00,0x00,0x3C,0x00,0x04,0x18,0x20,0x04,0x18,0x20,0x0C,0x3C,0x30,0x3C,0x3C,0x3C,0xCC,0xE7,0x33,0x0C,0x66,0x30,0xC4,0xE7,0x23,0x34,0xE7,0x2C,0x0C,0x7E,0x30,0xCC,0xFF,0x33,0x3C,0x18,0x3C,0x0C,0x18,0x30,0x04,0x18,0x20};
|
|
const byte bomb_bitmap[] =
|
|
{1,5,/*{w:8,h:5,bpp:1}*/0x88,0x55,0x77,0x55,0x88};
|
|
const byte bullet_bitmap[] =
|
|
{1,5,/*{w:8,h:5,bpp:1}*/0x14,0x28,0x14,0x14,0x28};
|
|
const byte enemy1_bitmap[] =
|
|
{2,16,/*{w:16,h:16,bpp:1}*/0x00,0x00,0x70,0x38,0xF8,0x7C,0xFC,0xFC,0xFE,0xFC,0xFE,0xFF,0xFC,0xFF,0xF8,0x7F,0xF0,0x3F,0x88,0x47,0xF0,0x3F,0xF0,0x3F,0xD0,0x2F,0x8C,0xC7,0x48,0x48,0x80,0x04};
|
|
const byte enemy2_bitmap[] =
|
|
{2,16,/*{w:16,h:16,bpp:1}*/0x00,0x00,0x30,0x0C,0x10,0x08,0x3C,0x3D,0x2C,0x35,0x34,0x2D,0x24,0x25,0x50,0x15,0x28,0x0A,0x30,0x0D,0x28,0x15,0x4A,0x50,0x2A,0x55,0x22,0x05,0x0A,0x14,0x0A,0x50};
|
|
const byte enemy3_bitmap[] =
|
|
{2,16,/*{w:16,h:16,bpp:1}*/0x00,0x00,0x00,0x00,0x04,0x20,0x05,0xA0,0x05,0xA0,0x25,0xA4,0xA7,0xE5,0xF7,0xEF,0xF7,0xEF,0xFE,0x7F,0xFC,0x3F,0xBC,0x3D,0xE4,0x27,0x20,0x00,0x00,0x00,0x00,0x00};
|
|
const byte enemy4_bitmap[] =
|
|
{2,16,/*{w:16,h:16,bpp:1}*/0x00,0x00,0x00,0x00,0xF0,0x0F,0xF8,0x1F,0xD8,0x1B,0xF8,0x1F,0xF8,0x1F,0xF8,0x1F,0xF0,0x0F,0xA8,0x15,0xCC,0x33,0xE8,0x17,0x66,0x66,0x33,0xCC,0x61,0x86,0x40,0x02};
|
|
const byte explosion_bitmap[] =
|
|
{2,16,/*{w:16,h:16,bpp:1}*/0x40,0x00,0x02,0x44,0x08,0x20,0x40,0x02,0x28,0x14,0x54,0x2A,0x2A,0x54,0x55,0xAA,0x2A,0x54,0x55,0xAA,0x2A,0x54,0x54,0x2A,0x28,0x14,0x40,0x22,0x04,0x40,0x42,0x04};
|
|
|
|
const byte* const enemy_bitmaps[4] = {
|
|
enemy1_bitmap,
|
|
enemy2_bitmap,
|
|
enemy3_bitmap,
|
|
enemy4_bitmap
|
|
};
|
|
|
|
//
|
|
// GAME CODE
|
|
//
|
|
|
|
byte attract;
|
|
word score;
|
|
byte lives;
|
|
|
|
#define MAXLIVES 5
|
|
|
|
byte player_x;
|
|
byte bullet_x;
|
|
byte bullet_y;
|
|
byte bomb_x;
|
|
byte bomb_y;
|
|
|
|
typedef struct {
|
|
byte x,y;
|
|
const byte* shape; // need const here
|
|
} Enemy;
|
|
|
|
#define MAX_ENEMIES 28
|
|
|
|
Enemy enemies[MAX_ENEMIES];
|
|
byte enemy_index;
|
|
byte num_enemies;
|
|
|
|
typedef struct {
|
|
int right:1;
|
|
int down:1;
|
|
} MarchMode;
|
|
|
|
MarchMode this_mode, next_mode;
|
|
|
|
void draw_lives() {
|
|
byte i;
|
|
byte n = lives;
|
|
byte y = 18;
|
|
byte x = VBWIDTH-1;
|
|
for (i=0; i<MAXLIVES; i++) {
|
|
draw_char(i<n?'*':' ', x, y++);
|
|
}
|
|
}
|
|
|
|
void draw_score() {
|
|
byte x = VBWIDTH-1;
|
|
byte y = 10;
|
|
draw_bcd_word(score, x, y, 1);
|
|
}
|
|
|
|
void draw_bunker(byte x, byte y, byte y2, byte h, byte w) {
|
|
byte i,a,b;
|
|
for (i=0; i<h; i++) {
|
|
a = y-y2-i*2;
|
|
b = y-i;
|
|
xor_vline(x+i, a, b);
|
|
xor_vline(x+h*2+w-i-1, a, b);
|
|
}
|
|
for (i=0; i<w; i++) {
|
|
xor_vline(x+h+i, a, b);
|
|
}
|
|
}
|
|
|
|
void draw_playfield() {
|
|
byte i;
|
|
clrscr();
|
|
draw_string("PLAYER 1", VBWIDTH-1, 0, 1);
|
|
draw_score();
|
|
draw_lives();
|
|
for (i=0; i<VBWIDTH-4; i++)
|
|
vidmem[191][i] = (i&1) ? 0x55 : 0x2a;
|
|
draw_bunker(20, 165, 15, 15, 20);
|
|
draw_bunker(160, 165, 15, 15, 20);
|
|
}
|
|
|
|
void add_score(word pts) {
|
|
if (attract) return;
|
|
score = bcd_add(score, pts);
|
|
draw_score();
|
|
}
|
|
|
|
void xor_player_derez() {
|
|
byte i,j;
|
|
byte x = player_x+13;
|
|
byte y = 190;
|
|
byte* rand = (byte*) &clrscr; // use code as random #'s
|
|
for (j=1; j<=0x1f; j++) {
|
|
for (i=0; i<50; i++) {
|
|
signed char xx = x + (*rand++ & 0x1f) - 15;
|
|
signed char yy = y - (*rand++ & j);
|
|
xor_pixel(xx, yy);
|
|
if ((xx & 0x1f) > j) { CLICK; }
|
|
}
|
|
}
|
|
}
|
|
|
|
void destroy_player() {
|
|
xor_player_derez(); // xor derez pattern
|
|
xor_sprite(player_bitmap, player_x, 192-17); // erase ship via xor
|
|
xor_player_derez(); // xor 2x to erase derez pattern
|
|
player_x = 0xff;
|
|
lives--;
|
|
}
|
|
|
|
void init_enemies() {
|
|
byte i,x,y,bm;
|
|
x=0;
|
|
y=10;
|
|
bm=0;
|
|
for (i=0; i<MAX_ENEMIES; i++) {
|
|
Enemy* e = &enemies[i];
|
|
e->x = x;
|
|
e->y = y;
|
|
e->shape = enemy_bitmaps[bm];
|
|
x += 29;
|
|
if (x >= 200) {
|
|
x = 0;
|
|
y += 20;
|
|
bm++;
|
|
}
|
|
}
|
|
enemy_index = 0;
|
|
num_enemies = MAX_ENEMIES;
|
|
this_mode.right = 1;
|
|
this_mode.down = 0;
|
|
next_mode.right = 1;
|
|
next_mode.down = 0;
|
|
}
|
|
|
|
void delete_enemy(Enemy* e) {
|
|
byte x = e->x;
|
|
byte y = e->y;
|
|
clear_sprite(e->shape, x, y);
|
|
xor_sprite(explosion_bitmap, x, y);
|
|
memmove(e, e+1, sizeof(Enemy)*(enemies-e+MAX_ENEMIES-1));
|
|
num_enemies--; // update_next_enemy() will check enemy_index
|
|
tone(1,20,10);
|
|
xor_sprite(explosion_bitmap, x, y);
|
|
}
|
|
|
|
void update_next_enemy() {
|
|
Enemy* e;
|
|
if (enemy_index >= num_enemies) {
|
|
enemy_index = 0;
|
|
memcpy(&this_mode, &next_mode, sizeof(this_mode));
|
|
tone(220+num_enemies,3,1);
|
|
}
|
|
e = &enemies[enemy_index];
|
|
clear_sprite(e->shape, e->x, e->y);
|
|
if (this_mode.down) {
|
|
e->y += 4;
|
|
// if too close to ground, end game
|
|
if (e->y > 170) {
|
|
destroy_player();
|
|
lives = 0;
|
|
}
|
|
next_mode.down = 0;
|
|
} else {
|
|
if (this_mode.right) {
|
|
e->x += 2;
|
|
if (e->x >= 255-16) {
|
|
next_mode.down = 1;
|
|
next_mode.right = 0;
|
|
}
|
|
} else {
|
|
e->x -= 2;
|
|
if (e->x == 0) {
|
|
next_mode.down = 1;
|
|
next_mode.right = 1;
|
|
}
|
|
}
|
|
}
|
|
draw_sprite(e->shape, e->x, e->y);
|
|
enemy_index++;
|
|
}
|
|
|
|
char in_rect(Enemy* e, byte x, byte y, byte w, byte h) {
|
|
byte ew = e->shape[0]*8;
|
|
byte eh = e->shape[1];
|
|
return (x >= e->x-w && x <= e->x+ew && y >= e->y-h && y <= e->y+eh);
|
|
}
|
|
|
|
Enemy* find_enemy_at(byte x, byte y) {
|
|
byte i;
|
|
for (i=0; i<num_enemies; i++) {
|
|
Enemy* e = &enemies[i];
|
|
if (in_rect(e, x, y, 6, 2)) {
|
|
return e;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void check_bullet_hit(byte x, byte y) {
|
|
Enemy* e = find_enemy_at(x,y);
|
|
if (e) {
|
|
delete_enemy(e);
|
|
add_score(0x25);
|
|
}
|
|
}
|
|
|
|
void fire_bullet() {
|
|
bullet_x = player_x + 8;
|
|
bullet_y = 192-22;
|
|
xor_sprite(bullet_bitmap, bullet_x, bullet_y); // draw
|
|
}
|
|
|
|
void move_bullet() {
|
|
byte leftover = xor_sprite(bullet_bitmap, bullet_x, bullet_y); // erase
|
|
if (leftover || bullet_y < 10) {
|
|
clear_sprite(bullet_bitmap, bullet_x, bullet_y);
|
|
check_bullet_hit(bullet_x, bullet_y+2);
|
|
bullet_y = 0;
|
|
} else {
|
|
bullet_y -= 4;
|
|
tone(bullet_y,3,0);
|
|
xor_sprite(bullet_bitmap, bullet_x, bullet_y); // draw
|
|
}
|
|
}
|
|
|
|
void drop_bomb() {
|
|
Enemy* e = &enemies[enemy_index];
|
|
bomb_x = e->x + 7;
|
|
bomb_y = e->y + 16;
|
|
xor_sprite(bomb_bitmap, bomb_x, bomb_y);
|
|
}
|
|
|
|
void move_bomb() {
|
|
byte leftover = xor_sprite(bomb_bitmap, bomb_x, bomb_y); // erase
|
|
if (bomb_y > 192-12) {
|
|
bomb_y = 0;
|
|
} else if (leftover & 0x7f) { // don't count hi bit
|
|
erase_sprite(bomb_bitmap, bomb_x, bomb_y); // erase bunker
|
|
if (bomb_y > 192-23) {
|
|
// player was hit (probably)
|
|
destroy_player();
|
|
}
|
|
bomb_y = 0;
|
|
} else {
|
|
bomb_y += 3;
|
|
xor_sprite(bomb_bitmap, bomb_x, bomb_y);
|
|
}
|
|
}
|
|
|
|
byte frame;
|
|
signed char player_dir = 0;
|
|
|
|
void move_player() {
|
|
if (attract) {
|
|
if (bullet_y == 0) fire_bullet();
|
|
player_dir = 0;
|
|
} else {
|
|
char key;
|
|
// handle keyboard
|
|
if (kbhit()) {
|
|
key = cgetc();
|
|
switch (key) {
|
|
case 8: // left arrow
|
|
player_dir = player_dir < 0 ? 0 : -2;
|
|
break;
|
|
case 21: // right arrow
|
|
player_dir = player_dir > 0 ? 0 : 2;
|
|
break;
|
|
case ' ': // space
|
|
if (bullet_y == 0) {
|
|
fire_bullet();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// move player
|
|
if (player_dir < 0 && player_x > 0)
|
|
player_x += player_dir;
|
|
else if (player_dir > 0 && player_x < 255-28)
|
|
player_x += player_dir;
|
|
draw_sprite(player_bitmap, player_x, 192-17);
|
|
}
|
|
|
|
void play_round() {
|
|
draw_playfield();
|
|
player_x = VHEIGHT/2-8;
|
|
bullet_y = 0;
|
|
bomb_y = 0;
|
|
frame = 0;
|
|
while (player_x != 0xff && num_enemies) {
|
|
move_player();
|
|
if (bullet_y) {
|
|
move_bullet();
|
|
}
|
|
update_next_enemy();
|
|
if (frame & 1) {
|
|
if (bomb_y == 0) {
|
|
drop_bomb();
|
|
} else {
|
|
move_bomb();
|
|
}
|
|
}
|
|
frame++;
|
|
}
|
|
}
|
|
|
|
void init_game() {
|
|
score = 0;
|
|
lives = 5;
|
|
}
|
|
|
|
void game_over_msg() {
|
|
byte i;
|
|
byte x=11;
|
|
byte y=10;
|
|
for (i=0; i<50; i++) {
|
|
draw_string(" *************** ", x, y+0, 0);
|
|
draw_string("*** ***", x, y+1, 0);
|
|
draw_string("** GAME OVER **", x, y+2, 0);
|
|
draw_string("*** ***", x, y+3, 0);
|
|
draw_string(" *************** ", x, y+4, 0);
|
|
}
|
|
}
|
|
|
|
void play_game() {
|
|
attract = 0;
|
|
init_game();
|
|
init_enemies();
|
|
while (lives) {
|
|
play_round();
|
|
if (num_enemies == 0) {
|
|
init_enemies();
|
|
}
|
|
}
|
|
game_over_msg();
|
|
}
|
|
|
|
void attract_mode() {
|
|
attract = 1;
|
|
while (1) {
|
|
init_enemies();
|
|
play_round();
|
|
}
|
|
}
|
|
|
|
void main() {
|
|
while (1) {
|
|
play_game();
|
|
}
|
|
}
|