#include <vectrex.h> // I think this is Chris's version. Planning to switch to Peer's C library
#define debugstr(s) /* ignore */
#define signed16 long
#define unsigned16 unsigned long
#define signed8 signed char
#define unsigned8 unsigned char
#define boolean int
// Under GCC, using default header file/library.
// some assembly required...
#define jsrx(v, func) asm("ldx %0\n\t" \
"jsr " #func "\n\t" : : "g" (v) : "d", "x")
#define Draw_VLp(ra) jsrx(ra, 0xF410) // (pattern y x)* 0x01
static inline void intensity(unsigned8 brightness) {Intensity_a(brightness);}
static inline void dot(signed8 y, signed8 x) {Dot_d((int)y, (int)x);}
static inline void move(signed8 y, signed8 x) {Moveto_d((int)y, (int)x);}
static inline void reset_beam(void) {Reset0Ref();}
static inline void set_scale(unsigned8 scale) {/*VIA_t1_cnt_lo*/ *(volatile unsigned char *)0xD004 = scale;}
static inline void line(signed8 y, signed8 x) {*(volatile unsigned char *)0xC823 = 0; Draw_Line_d((int)y, (int)x);}
static inline void dots(unsigned8 size, const signed8 *array) { *(volatile char *)0xC823 = size-1; Dot_List((void *)array); }
static inline void displaylist(const signed8 *list) { Draw_VLp(list); }
static inline void wait_retrace(void) {Wait_Recal();}
static inline void rotate_vl(unsigned8 angle, unsigned8 points, const void *original, void *rotated)
{
*(volatile unsigned int *)0xC836 = angle;
*(volatile unsigned int *)0xC823 = points-1;
Rot_VL((int *)original, rotated);
}
#define joybit Joy_Digital
#define tstat (*((volatile unsigned8 *) 0xc856))
#define pot0 (*((volatile signed8 *) 0xc81b))
#define pot1 (*((volatile signed8 *) 0xc81c))
#define pot2 (*((volatile signed8 *) 0xc81d))
#define pot3 (*((volatile signed8 *) 0xc81e))
#define epot0 (*((volatile unsigned8 *) 0xc81f))
#define epot1 (*((volatile unsigned8 *) 0xc820))
#define epot2 (*((volatile unsigned8 *) 0xc821))
#define epot3 (*((volatile unsigned8 *) 0xc822))
#define t1lolc (*((volatile unsigned8 *) 0xd004))
#define read_ram(x) (*((volatile signed8 *) x))
#define joystick1_button1 (signed8)(read_ram(0xC80F) & 1U) /* A */
#define joystick1_button2 (signed8)(read_ram(0xC80F) & 2U) /* S */
#define joystick1_button3 (signed8)(read_ram(0xC80F) & 4U) /* D */
#define joystick1_button4 (signed8)(read_ram(0xC80F) & 8U) /* F */
static signed8 XJoy, YJoy, button1, button2, button3, button4;
static void poll_joystick(void)
{
joybit();
if (pot0 < -10) {
// Left
XJoy = -1;
} else if (pot0 > 10) {
// Right
XJoy = 1;
} else {
XJoy = 0;
}
if (pot1 < -10) {
// Down
YJoy = -1;
} else if (pot1 > 10) {
// Up
YJoy = 1;
} else {
YJoy = 0;
}
asm("pshs x,y,u,dp");
asm("lda #0");
asm("jsr 0HF1B4 ; read_btns_mask":::"a","b","d");
asm("puls dp,u,y,x");
button1 = joystick1_button1;
button2 = joystick1_button2;
button3 = joystick1_button3;
button4 = joystick1_button4;
}
static void init_hardware(void)
{
// setup joystick read function to read only joystick 1
epot0 = 1;
epot1 = 3;
epot2 = 0;
epot3 = 0;
}
#define signed16 long
#define unsigned16 unsigned long
#define signed8 signed char
#define unsigned8 unsigned char
#define boolean int
#define MAX_BRIGHTNESS (0x7f)
static signed16 Debug;
#define IN_ROM __attribute__ ((section(".text")))
/* Shields get a special proc because they're one continuous line */
static const signed8 shieldvec[] IN_ROM = {
0, -72,24,
-1, 24,24,
-1, 96,-96,
-1, 24,24,
-1, -72,72,
-1, -72,-72,
-1, 24,-24,
-1, 96,96,
-1, 24,-24,
-1, -72,-72,
-1, -72,72,
1
};
static const signed8 crosshair[] IN_ROM = {
0, 17, 40, // trying to move it to the center
-1, -30, 0,
0, 15, 20,
-1, 0, -115,
0, -15, 20,
-1, 30, 0,
0, -10, 20,
-1, -10, 0,
0, -10, 10,
-1, 0, 10,
0, -15, 10,
-1, 0, -30,
0, -20, 15,
-1, 105, 0,
0, -20, 15,
-1, 0, -30,
0, -20, 10,
-1, 0, 10,
0, -10, 10,
-1, -10, 0,
1
};
#define DEFINE(name) static const signed8 name[] IN_ROM = {
#define move_rel_xy(x,y) 0 /*MOVE*/, ((signed8)y), ((signed8)x)
#define line_rel_xy(x,y) (signed8)-1 /*LINE*/, ((signed8)y), ((signed8)x)
#define ENDDEF(name) 1 /*STOP*/ }; static void SHOW_##name(void) { displaylist(name); }
#include "font.h"
#include "recording.h"
static const signed8 *digitlist[10] = {
N0, N1, N2, N3, N4, N5, N6, N7, N8, N9
};
#include "placard.h"
// 16 bit maths, no floats.
static signed16 rseed;
static inline signed16 urandom16(void)
{
// VECTREX HAS A BUILT-IN RANDOM THAT WE CAN USE!
// (though it doesn't seem very good - this may be better and faster)
return (rseed = (rseed * 2053) + 13849);
}
static inline unsigned16 irand(signed16 max) {
// returns int 0..max-1
return (unsigned16)(urandom16() % max); // probably not very random, but this is a video game, not crypto
}
static inline signed16 pickrandom(signed16 low, signed16 high)
{
// We'll just assume that high > low...
return (signed16)irand(high-low+1)+low; // pick a random integer between low and high inclusive.
}
static signed8 stardots[8]; // fill with randoms ONCE - could move this into ROM...
static signed8 rotated3[8]; // rotate stardots into place
static signed8 rotated2[8];
static signed8 rotated1[8];
static signed8 rotated0[8];
static signed8 *rotated[4] = {
rotated0,
rotated1,
rotated2,
rotated3
};
static unsigned8 starfield_scale;
static inline void init_stars(void)
{
starfield_scale = (unsigned8)0xFF;
// origin at 0,0
stardots[0] = -60; stardots[1] = (signed8)pickrandom(-45,45); // LEFT
stardots[2] = 60; stardots[3] = (signed8)pickrandom(-45,45); // RIGHT
stardots[4] = (signed8)pickrandom(-45,45); stardots[5] = 60; // TOP
stardots[6] = (signed8)pickrandom(-45,45); stardots[7] = -60; // BOT
stardots[6] -= stardots[4]; stardots[7] -= stardots[5]; // abs to rel
stardots[4] -= stardots[2]; stardots[5] -= stardots[3];
stardots[2] -= stardots[0]; stardots[3] -= stardots[1];
rotate_vl((unsigned int)(pickrandom(0,15)), 4U, stardots, rotated0);
rotate_vl((unsigned int)(pickrandom(16,31)), 4U, stardots, rotated1);
rotate_vl((unsigned int)(pickrandom(32,47)), 4U, stardots, rotated2);
rotate_vl((unsigned int)(pickrandom(48,63)), 4U, stardots, rotated3);
}
static void stars(unsigned8 speed) // speed must be a power of 2
{
unsigned8 shifted_scale;
signed8 i;
debugstr("stars");
// New fast algorithm, does approximate fading with distance.
shifted_scale = starfield_scale-(unsigned8)0xBF;
for (i = 0; i < 4; i++) {
if ((unsigned8)shifted_scale > (unsigned8)0x20) { // disappear here
reset_beam();
intensity((shifted_scale>>1)-0x10);
set_scale(shifted_scale);
dots(4, rotated[i]);
} else {
if (((unsigned8)shifted_scale & (unsigned8)0x3F) == 0U) {
// pull in new ring of stars
rotate_vl((unsigned int)(pickrandom(0,63)), 4U, stardots, rotated[i]);
}
}
shifted_scale += 0x40;
}
starfield_scale -= speed; // must be power of 2
}
static void position_and_scale(signed8 absx, signed8 absy, signed16 scale)
{
reset_beam();
set_scale(0x7f);
move(absy, absx);
set_scale((unsigned8)scale);
}
static inline void position_and_scale_and_intensity(signed8 absx, signed8 absy, signed16 scale, unsigned8 bright)
{
position_and_scale(absx, absy, scale);
intensity(bright);
}
/* Shields on the vectrex are sideways wrt the 'real' tailgunner */
static void draw_shields(unsigned8 scale)
{
debugstr("draw_shields");
position_and_scale(0, 0, scale);
displaylist(shieldvec);
}
static inline void Draw_crosshair(signed8 absx, signed8 absy)
{
debugstr("Draw_crosshair");
position_and_scale(absx, absy, 0x18);
displaylist(crosshair);
}
/* 3d rendering */
static unsigned8 Credits;
static unsigned8 intro_rot;
static signed16 frame_number;
static unsigned8 global_flashing_intensity;
static signed16 HighScore;
static signed16 LastScore;
static signed16 Score;
#ifdef NOTYET
static signed16 UsingShields;
static signed16 Shields;
static signed16 shieldsegment;
static signed16 shieldsubticksleft;
#endif
// The revolving intro placard...
static void drawtgintro(unsigned8 scale, unsigned8 bright) {
position_and_scale_and_intensity(0,0, (unsigned8)((signed16)scale*(signed16)2/(signed16)5), bright);
displaylist((signed8 *)placard[((unsigned8)intro_rot+128)&255]);
}
static inline void show_digit(signed8 digit)
{
displaylist(digitlist[(int)digit]);
}
static void SHOW_NUM(signed16 num, signed8 absx, signed8 absy, unsigned16 scale) { // left-aligned
signed8 digit, zeroes;
// If scale is 0, this draws a number starting at the current position, which is at the origin
// of the previous number offset by the number width.
if (scale) position_and_scale(absx, absy, 0x20);
// This replaces code that used divide by 10 and modulo 10. Much faster.
// handles full 16 bit range of -32768:32767 - Uses negative numbers to avoid the issue of negating -32768
if (num >= 0) num = -num; else SHOW_MINUS(); // TO DO: add a font character for '-'.
digit = 0;
zeroes = 1; // CLRing is shorter
// max 11 add/subtracts...
if (num <= -20000) { num += 20000; digit += 2; zeroes = 0; }
if (num <= -10000) { num += 10000; digit += 1; zeroes = 0; }
if (!zeroes) show_digit(digit);
digit = 0;
if (num <= -8000) { num += 8000; digit += 8; zeroes = 0; } else if (num <= -4000) { num += 4000; digit += 4; zeroes = 0; }
if (num <= -2000) { num += 2000; digit += 2; zeroes = 0; }
if (num <= -1000) { num += 1000; digit += 1; zeroes = 0; }
if (!zeroes) show_digit(digit);
digit = 0;
if (num <= -800) { num += 800; digit += 8; zeroes = 0; } else if (num <= -400) { num += 400; digit += 4; zeroes = 0; }
if (num <= -200) { num += 200; digit += 2; zeroes = 0; }
if (num <= -100) { num += 100; digit += 1; zeroes = 0; }
if (!zeroes) show_digit(digit);
digit = 0;
if (num <= -80) { num += 80; digit += 8; zeroes = 0; } else if (num <= -40) { num += 40; digit += 4; zeroes = 0; }
if (num <= -20) { num += 20; digit += 2; zeroes = 0; }
if (num <= -10) { num += 10; digit += 1; zeroes = 0; }
if (!zeroes) show_digit(digit);
show_digit((signed8)-num);
}
static void Draw_lastscore(void)
{
debugstr("Draw_lastscore");
position_and_scale(-100,127,32);
SHOW_SCORE();
SHOW_NUM(LastScore, -98,113, 256);
}
static void Draw_highscore(void)
{
debugstr("Draw_highscore");
position_and_scale(40,127,32);
SHOW_HIGH_SCORE();
SHOW_NUM(HighScore, 52,113, 256);
}
static void Draw_insertcoin(void) // Not yet used
{
debugstr("Draw_insertcoin");
position_and_scale_and_intensity(-36,-80,32, global_flashing_intensity<<1);
SHOW_INSERT_COIN();
}
static void Draw_start(void)
{
debugstr("Draw_start");
position_and_scale_and_intensity(-34,-80,32, global_flashing_intensity<<1);
SHOW_PRESS_START();
}
static void Draw_credits(void)
{
debugstr("Draw_credits");
position_and_scale_and_intensity(-39,-30,48,0x5F);
SHOW_CREDITS();
if (Credits > 9U) {
SHOW_NUM(9, 0,0,0); // display any more than 9 as 9. (This is what many arcade machines did in real life)
} else {
SHOW_NUM(Credits, 0,0,0);
}
}
static void Draw_ships_remaining(void)
{
SHOW_NUM(10, 90,120, 64);
}
static void Draw_shields_remaining(void)
{
SHOW_NUM(80, -5,127, 128);
}
static void Draw_score(void)
{
SHOW_NUM(Score, -100,120, 64);
}
static signed8 crosshair_x, crosshair_y;
void follow_crosshair()
{
XJoy+=XJoy;XJoy+=XJoy;YJoy+=YJoy;YJoy+=YJoy;
// constrain joysticks but carefully avoid integer overflow
if ((crosshair_x >= 0) && (XJoy > 0) && (crosshair_x >= 127-XJoy)) /* ignore */;
else if ((crosshair_x < 0) && (XJoy < 0) && (crosshair_x < -127-XJoy)) /* ignore */;
else crosshair_x += XJoy;
if ((crosshair_y >= 0) && (YJoy > 0) && (crosshair_y >= 127-YJoy)) /* ignore */;
else if ((crosshair_y < 0) && (YJoy < 0) && (crosshair_y < -127-YJoy)) /* ignore */;
else crosshair_y += YJoy;
Draw_crosshair(crosshair_x, crosshair_y);
}
static signed8 last_button1;
static void next_frame(void)
{
// do things here that must be done on every frame, eg polling buttons/joystick
debugstr("next_frame");
wait_retrace();
poll_joystick();
if (button1 && !last_button1) { // 'A' for A Coin ;-)
Credits += 1;
}
last_button1 = button1;
if ((frame_number & 16) == 0) {
global_flashing_intensity += 4; // 16 frames of getting brighter
} else {
global_flashing_intensity -= 4; // then 16 frames of getting darker
}
frame_number++; // wraps.
//if (Debug) SHOW_NUM(Debug, -100,-100, 128);
}
static boolean firing;
static unsigned16 missile_scale;
static signed8 missile_x, missile_y;
static const signed8 paired_missiles[] IN_ROM = {
0, 0,-127,
-1, -5,-5, // left to right pointing
-1, 5,30, -1, 5,-30,
-1, -5,5,
0, 0,127, 0, 0,127, // (large move in two parts)
-1, 5,5,
-1, -5,-30, -1, -5,30, // right to left pointing
-1, 5,-5,
1
};
void Draw_missiles(void)
{
position_and_scale_and_intensity(missile_x,missile_y, (unsigned8)missile_scale, MAX_BRIGHTNESS);
displaylist(paired_missiles);
// This hack to draw both left and right missiles in one call doesn't
// look as nice as I hoped it would on the screen. I think I am going
// to have to go back to individual left and right missiles, and
// also orient them toward the crosshair using trig, then rotate
// them using the built-in bios procedure.
}
static int *waveptr;
static int ship_scale = 1;
static int speed_toggle = 0;
void SHIPWAVE(void)
{
int x,y;
if (ship_scale == 1) {
waveptr = (int *)Ship0Wave0; // ( scale x y (drawing commands)* 1 )* 1
}
ship_scale = *waveptr++;
x = *waveptr++; y = (int)((*waveptr++ + 128) * 2 - 128); // deliberately exaggerate ship motion in Y
position_and_scale_and_intensity(x, y-60, (unsigned char)((long)ship_scale*3L/8L), MAX_BRIGHTNESS); // should a y offset be stored in the structure?
displaylist((signed8 *)waveptr);
if (speed_toggle == 0) {
waveptr -= 3;
} else {
do { waveptr += 3; } while (*waveptr != 1); // step over display list
waveptr += 1; // step over 'end of list'
// scale, or 1 for end of wave
if (*waveptr == 1) ship_scale = 1; // end of a sequence of frames
}
speed_toggle = 1-speed_toggle;
}
int main(void) { // cmoc doesn't allow argc, argv
#define STAR_SPEED 4
signed16 frames;
unsigned8 placard_scale, placard_brightness;
signed8 Shots_fired;
// I think all the static data could safely be initialised to 0 in a block
// by the startup code. (Possibly excepting the two arrays containing pointers
// to const arrays). I don't believe I have any statics that require initialisation
// to any non-0 values.
missile_scale =
(unsigned16)(Debug = Score = HighScore = LastScore = rseed = frame_number =
(signed16)(last_button1 = crosshair_x = crosshair_y = XJoy = YJoy =
(signed8)(Credits = global_flashing_intensity = 0)));
init_hardware();
init_stars();
Score = 72; HighScore = 2047; LastScore = 132; // only for demo.
ship_scale = 1;
speed_toggle = 0;
Debug = sizeof(Ship0Wave0);
// Attract mode
for (;;) {
Shots_fired = button1 = button2 = button3 = button4 = firing = 0;
//Debug=Credits;
debugstr("main loop");
// approaching banner:
if (Credits == 0) {
placard_scale = (unsigned8)((signed16)(0xFF-160));
placard_brightness = (unsigned8)((signed16)(((signed16)MAX_BRIGHTNESS+(signed16)MAX_BRIGHTNESS-160)));
intro_rot = 128;
// start at 128 step by -2 end at 64 after one full revolution (128 + 32 steps)
for (frames = 0; frames < 160; frames++) {
next_frame();
drawtgintro(placard_scale, placard_brightness>>1);
placard_scale++; placard_brightness++;
stars(STAR_SPEED);
intro_rot -= 2;
if (button1) break;
}
if (!button1) {signed16 i;
// pulse the placard a few times...
for (i = 0; i < 2; i++) {
placard_brightness = MAX_BRIGHTNESS&0xF8;
while (placard_brightness != 0) {
next_frame();
drawtgintro(placard_scale, placard_brightness);
stars(STAR_SPEED);
placard_brightness -= 8;
if (button1) break;
}
if (!button1) while (placard_brightness != (MAX_BRIGHTNESS&0xF8)) {
placard_brightness += 8;
next_frame();
drawtgintro(placard_scale, placard_brightness);
stars(STAR_SPEED);
if (button1) break;
}
}
}
// now fade it out
if (!button1) {
placard_brightness = MAX_BRIGHTNESS&0xF8;
while (placard_brightness != 0) {
next_frame();
drawtgintro(placard_scale, placard_brightness);
stars(STAR_SPEED);
placard_brightness -= 8;
if (button1) break;
}
}
}
debugstr("wait for coins...");
global_flashing_intensity = 0; frame_number = 0; // start flashing from off position
for (frames = 0; ; frames++) {
next_frame();
stars(STAR_SPEED);
if (Credits == 0) Draw_insertcoin(); else {Draw_start(); Draw_credits(); /* -- TOO SLOW*/}
intensity(MAX_BRIGHTNESS);
Draw_lastscore();
Draw_highscore();
if (button2 && (Credits > 0)) break; // 'S' for Start
}
Credits -= 1;
// shields and crosshair - simulated play...
// for (frames = 0; Shots_fired < 10 || firing; frames++) {
for (frames = 0; (long)frames < 126L*2L; frames++) {
next_frame();
stars(STAR_SPEED);
intensity(MAX_BRIGHTNESS);
Draw_ships_remaining();
Draw_shields_remaining();
Draw_score();
SHIPWAVE(); // need to implement passing now
/*if (!firing) */follow_crosshair();
if (button3) { // 'D' for Defend
if (pickrandom(0,31)) draw_shields(0xF0);
}
if (button4 && !firing && !button3) { // 'F' for Fire. One at a time and not when shields up.
firing = 1; // firing position is higher than on original game for a reason :-)
Shots_fired += 1;
missile_x = 0; missile_y = 0; // we treat the missile as being in the center of the ship
// but *draw* missiles to the left and right of the center...
missile_scale = 0x7f; // start from left and right edges
}
if (firing) {
missile_scale -= 1U;
if (missile_scale <= 4U) {
firing = 0;
} else {
signed16 m, mx,my, delta_y;
mx = (signed16)crosshair_x - (signed16)missile_x ; // re-center on the missile.
// not sure why it converges on Y so fast. Ought to start at foot of screem
my = (signed16)crosshair_y - (signed16)missile_y ; // offsets may now be outside -128:127 range
delta_y = (((my) << 5L) - my)>>5L;
// if (delta_y > 8L) delta_y = 8L; else if (delta_y < -8L) delta_y = -8L;
missile_x = (signed8)(((((mx) << 5L) - mx)>>5L) + (signed16)missile_x); // move 15/16ths of the distance to crosshair
missile_y = (signed8)((signed16)delta_y + (signed16)missile_y); // add original offset back in
m = (signed16)missile_scale<<3L; // 7/8ths
m -= (signed16)missile_scale;
missile_scale = (unsigned8)((signed16)m>>3L);
}
// stop RHS from going off-screen - these would not be needed if we used individual missiles
if (((signed16)missile_x + (signed16)missile_scale) > 127L) missile_x = (signed8)(127L-(signed16)missile_scale);
// stop LHS from going off-screen
if (((signed16)missile_x - (signed16)missile_scale) < -127L) missile_x = (signed8)(-127L+(signed16)missile_scale);
Draw_missiles();
}
}
// 'whoosh' when a ship got by us.
debugstr("whoosh...");
for (frames = 0; frames < 64; frames++) {
next_frame();
stars(STAR_SPEED<<1);
intensity((unsigned8)(64-frames)*2); // fade out
Draw_ships_remaining();
Draw_shields_remaining();
Draw_score();
}
}
return 0;
SHOW_N0();
SHOW_N1();
SHOW_N2();
SHOW_N3();
SHOW_N4();
SHOW_N5();
SHOW_N6();
SHOW_N7();
SHOW_N8();
SHOW_N9();
}