#include <vectrex.h> #include <assert.h> #include "controller.h" //#define TESTMODE 1 typedef int int8_t; typedef unsigned int uint8_t; typedef long int int16_t; #define TRUE 1 #define FALSE 0 const int8_t racetrack[11] = { 4, 127,0, 0,127, -127,0, 0,-127 }; const int8_t parked_car[13] = { 5, 8,4, 0,32, -16,0, 0,-32, 8,-4, 0,0 }; int8_t moving_car[13]; #define MAXV 15 // Originally 7. Now 15. static inline void rotate_vl(uint8_t angle, const int8_t *original, int8_t *rotated) { *(volatile unsigned int *)0xC836 = angle; *(volatile int *)0xC823 = original[0]; rotated[0] = original[0]; Rot_VL((int8_t *)original+1, rotated+1); } static inline void set_scale(uint8_t scale) {/*VIA_t1_cnt_lo*/ *(volatile unsigned char *)0xD004 = scale;} static void position_and_scale(int8_t y, int8_t x, uint8_t scale) { Reset0Ref(); set_scale(0x7f); // scale for moving Moveto_d(y, x); set_scale(scale); // scale for drawing } void cDraw_VLc(int8_t *vList) { register int count = *(vList++);// count in list do { VIA_port_a = *(vList++); // first y coordinate to dac VIA_port_b = 0; // mux enable, dac to -> integrator y (and x) VIA_port_b++; // mux disable, dac only to x VIA_port_a = *(vList++); // dac -> x VIA_shift_reg = (uint8_t)0xff; // full "unblank" output VIA_t1_cnt_hi = 0; // start timer while ((VIA_int_flags & 0x40) == 0); // wait till timer finishes VIA_shift_reg = 0; // output full blank } while (--count >=0); // loop thru all vectors } typedef enum gamestate { PRE_RACE, WAITING_FOR_CHEQUERED_FLAG, RACING, FINISHED, TIMED_OUT, DISQUALIFIED, CRASHED } gamestate; static gamestate state = PRE_RACE; uint8_t laps_run, laps_left; static inline void car(int8_t y, int8_t x, uint8_t angle) { rotate_vl(angle>>2, parked_car, moving_car); position_and_scale(y, x, 127); cDraw_VLc((int8_t *)moving_car); } static inline void track(void) { position_and_scale(-128, -128, 255); cDraw_VLc((int8_t *)racetrack); if (state == WAITING_FOR_CHEQUERED_FLAG || ((state == RACING) && (laps_left == 1)) || (state == FINISHED)) { Reset0Ref(); Moveto_d(-24,0); Draw_Line_d(-40,0); // This would be better as a 50% dashed line } position_and_scale(-48, -48, 96); cDraw_VLc((int8_t *)racetrack); } static inline int8_t inside(int16_t y, int16_t x) { #define yedge 48L #define xedge 48L if (y < 0L) y = -y; if (x < 0L) x = -x; return (y <= yedge && x <= xedge); } static inline int8_t outside(int16_t y, int16_t x) { return ((y <= -126L) || (y >= 126L) || (x <= -126L) || (x >= 126L)); } static int8_t BadY, BadX; // coordinates of where the crash happened. // can be used to draw the car bursting into flames :-) int crashed(int16_t Y, int16_t X) { int8_t x,y; int8_t each, *ptr = moving_car+1; position_and_scale((int8_t)(Y>>8L),(int8_t)(X>>8L), 127); BadY=(int8_t)(Y>>8L); BadX=(int8_t)(X>>8L); position_and_scale(BadY,BadX,127); if (outside(Y>>8L,X>>8L)) return 2; // front point if (inside(Y>>8L,X>>8L)) return 1; x = y = 0L; for (each = 0; each < 4; each++) { y += *ptr++; x += *ptr++; Reset0Ref(); Moveto_d((int8_t)((Y>>8L)+((int16_t)y)), (int8_t)((X>>8L)+((int16_t)x))); if (outside((Y>>8L)+((int16_t)y),(X>>8L)+((int16_t)x))) { BadY += y; BadX += x; return 2; // test x and y ranges } if (inside((Y>>8L)+((int16_t)y),(X>>8L)+((int16_t)x))) { BadY += y; BadX += x; return 1; } } return 0; } // don't need 16 bits for this game. This is overkill. // Actually could use BIOS code... The Vectrex rotation // handles 64 angles per circle - my own code has assumed // 256 angles per circle. const int16_t qsine[65] = { 0, 402, 803, 1205, 1605, 2005, 2404, 2801, 3196, 3589, 3980, 4369, 4756, 5139, 5519, 5896, 6269, 6639, 7005, 7366, 7723, 8075, 8423, 8765, 9102, 9434, 9759, 10079, 10393, 10701, 11002, 11297, 11585, 11866, 12139, 12406, 12665, 12916, 13159, 13395, 13622, 13842, 14053, 14255, 14449, 14634, 14810, 14978, 15136, 15286, 15426, 15557, 15678, 15790, 15892, 15985, 16069, 16142, 16206, 16260, 16305, 16339, 16364, 16379, 16384, }; static inline int16_t hsine(uint8_t x) { return x>=64 ? qsine[128-x] : qsine[x]; } static inline int16_t sine(uint8_t x) { return x&0x80 ? -hsine(x&0x7f) : hsine(x); } static inline int16_t SIN(uint8_t x) { return (sine(x&255U)); } static inline int16_t COS(uint8_t x) { return (sine((x+64U)&255U)); } void N1(int tick) { position_and_scale(0,0,(uint8_t)tick<<1); Moveto_d(-12,-8); // to 8,12 Moveto_d(0,8); // to 8,0 Draw_Line_d(24,0); // to 8,24 Moveto_d(-24,16); // to 24,0 } void N2(int tick) { position_and_scale(0,0,(uint8_t)tick<<1); Moveto_d(-12,-8); // to 8,12 Moveto_d(24,0); // to 0,24 Draw_Line_d(0,16); // to 16,24 Draw_Line_d(-12,0); // to 16,12 Draw_Line_d(0,-16); // to 0,12 Draw_Line_d(-12,0); // to 0,0 Draw_Line_d(0,16); // to 16,0 Moveto_d(0,8); // to 24,0 } void N3(int tick) { position_and_scale(0,0,(uint8_t)tick<<1); Moveto_d(-12,-8); // to 8,12 Draw_Line_d(0,16); // to 16,0 Moveto_d(12,-8); // to 8,12 Draw_Line_d(0,8); // to 16,12 Moveto_d(-12,0); // to 16,0 Draw_Line_d(24,0); // to 16,24 Draw_Line_d(0,-16); // to 0,24 Moveto_d(-24,24); // to 24,0 } void speedometer(uint8_t v) { position_and_scale(-48+4,0, 255); switch (v) { // Eyeballed! I really should use sin and cos to draw the speedo more accurately! // at some point I decided to have 15 speeds instead of 7. case 0: Draw_Line_d(32,-24+4); break; case 1: Draw_Line_d(33,-18); break; case 2: Draw_Line_d(35,-16); break; case 3: Draw_Line_d(36,-13); break; case 4: Draw_Line_d(37,-10); break; case 5: Draw_Line_d(38,-7); break; case 6: Draw_Line_d(39,-4); break; case 7: Draw_Line_d(39,-2); break; case 8: Draw_Line_d(39, 2); break; case 9: Draw_Line_d(39, 4); break; case 10: Draw_Line_d(38, 7); break; case 11: Draw_Line_d(37, 10); break; case 12: Draw_Line_d(36, 13); break; case 13: Draw_Line_d(35, 16); break; case 14: Draw_Line_d(33,-18); break; case 15: Draw_Line_d(32,24-4); break; } } char *pnum(char *p, int16_t num, int8_t decimal) { // does NOT add terminating \x80 or \x00 #define add_digit(x) *p++ = (x) char digit, zeroes; // handles full 16 bit range of -32768:32767 - Uses negative numbers to avoid the issue of negating -32768 if (num >= 0) num = -num; else add_digit('-'); digit = '0'; zeroes = 1; // Lets us use CLR which is faster // max 11 add/subtracts... if (num <= -20000) { num += 20000; digit += 2; zeroes = 0; } if (num <= -10000) { num += 10000; digit += 1; zeroes = 0; } if (!zeroes) add_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) add_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) add_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 (decimal) { add_digit('.'); zeroes = 0; } if (!zeroes) add_digit(digit); add_digit((char)(-num)+'0'); return p; } void heat_counter(uint8_t lap) { char HEAT[13] = {'H', 'E', 'A', 'T', ' ' }; char *endp = HEAT+5; endp = pnum(endp, lap, FALSE); *endp++ = 0x80; *endp = '\0'; Reset0Ref(); Print_Str_d(110, -110, HEAT); } void timeleft(int16_t centiseconds) { char TIME[14] = {'T', 'I', 'M', 'E', ' ' }; char *endp = TIME+5; endp = pnum(endp, centiseconds, TRUE); *endp++ = 0x80; *endp = '\0'; Reset0Ref(); Print_Str_d(110, -8, TIME); } void lapsleft(int16_t remaining) { char LAPS[14] = {'L', 'A', 'P', 'S', ' ' }; char *endp = LAPS+5; endp = pnum(endp, remaining, FALSE); *endp++ = 0x80; *endp = '\0'; Reset0Ref(); Print_Str_d(110, -124, LAPS); } void instructions(uint8_t laps, uint8_t secs) { char instructions[] = { (char)-8,(char)80, // WxH (char)-110,(char)-126, // y,x 'F','I','N','I','S','H',' ','?','?',' ', 'L','A','P','S',' ','I','N',' ',' ',' ',' ','S',(char)0x80,0 }; char *endp; Reset0Ref(); // secs MUST be between 10 and 99! endp = pnum((char *)instructions+11, (int16_t)secs, FALSE); endp = pnum((char *)instructions+22, (int16_t)laps, FALSE); *endp++ = 'S'; *endp++ = 0x80; *endp = '\0'; Print_Str_hwyx((char *)instructions); } typedef enum progression { STARTINGLINE, FIRST, SECOND, THIRD, FOURTH } PROGRESSION; PROGRESSION movement = STARTINGLINE; void lap_counter(int8_t y, int8_t x) { static int8_t q; if (x<=0 && y < 0) { q = FOURTH; } else if (x > 0 && y < 0) { q = FIRST; } else if (x > 0 && y >= 0) { q = SECOND; } else if (x <= 0 && y >= 0) { q = THIRD; } switch (movement) { case STARTINGLINE: // q's on FOURTH!(?) if (q == THIRD) {state = DISQUALIFIED;}; if (q == FOURTH) movement = STARTINGLINE; else if (q == FIRST) movement = FIRST; //else movement = q; break; case FIRST: if (q == FOURTH) {state = DISQUALIFIED;}; if (q == SECOND) movement = SECOND; //else movement = q; break; case SECOND: if (q == FIRST) {state = DISQUALIFIED;}; if (q == THIRD) movement = THIRD; //else movement = q; break; case THIRD: if (q == SECOND) {state = DISQUALIFIED;}; if (q == FOURTH) movement = FOURTH; //else movement = q; break; case FOURTH: if (q == THIRD) {state = DISQUALIFIED;}; if (q == FIRST) { laps_left -= 1; laps_run += 1; if (laps_left == 0) { state = FINISHED; } movement = FIRST; } //else movement = q; break; } //static char debug[4] = {'?', ' ', 0x80, 0}; //static char debug2[4] = {'?', ' ', 0x80, 0}; //(void)pnum(debug, q, FALSE); Reset0Ref(); Print_Str_d(-32, -48, debug); //(void)pnum(debug2, movement, FALSE); Reset0Ref(); Print_Str_d(-32, 40, debug2); } int main(void) { // This is *almost a 1-button game (turn left). But not quite. // There is a boatload of CPU power still free for all sorts of additions! static int countdown = 3; // We'll tune these once the features are added. uint8_t heat = 1; uint8_t subsec_timer = 0; int16_t secs_taken = 0L; int16_t ticks_left = 127; int16_t timer = 30000; // hundredths of a second left. 30 second max per heat. state = WAITING_FOR_CHEQUERED_FLAG; int16_t Y = (int16_t)-88<<(int16_t)8, X = 0; int8_t y, x; // Angle and Velocity uint8_t A = 128, V = 0; // A = 128 is horizontal, pointing right // A race is a fixed number of heats. In each heat you must run X laps within a certain time limit. // Lap and heat counting not yet fully implemented but getting there! // There *is* a speed at around V=8 or 9 where you can *just about* circle on automatic, but it // will eventually hit a wall. The number of laps needed must be in excess of the number that // you can get away with on automatic! for (;;) { Wait_Recal(); check_buttons(); Intensity_7F(); track(); y = (int8_t)(Y>>(int16_t)8); x = (int8_t)(X>>(int16_t)8); car (y, x, A); switch (state) { case PRE_RACE: // output message about #laps and #seconds here *only*. case WAITING_FOR_CHEQUERED_FLAG: // if the driver presses the accelerator while waiting for the flag to start, // it is a false start and he is penalised by losing this race. #ifdef TESTMODE laps_left = 2; instructions(laps_left*30 /* seconds*/, laps_left); // 5 second laps are probably optimal! timer = (laps_left-1)*3000; #else laps_left = 10; instructions(laps_left*6 /* seconds*/, laps_left); // 5 second laps are probably optimal! timer = (laps_left-1)*600; #endif heat_counter(heat); Reset0Ref(); set_scale(32); ticks_left -= 4; if (ticks_left <= 0) { countdown -= 1; ticks_left = 127; } Y = (int16_t)-88<<(int16_t)8; X = 0; y = (int8_t)(Y>>(int16_t)8); x = (int8_t)(X>>(int16_t)8); A = 128; switch (countdown) { case 3: N3((int8_t)ticks_left); continue; case 2: N2((int8_t)ticks_left); continue; case 1: N1((int8_t)ticks_left); continue; case 0: state = RACING; countdown = 3; heat += 1; movement = STARTINGLINE; laps_run = 0; secs_taken = 0L; continue; } case RACING: lap_counter(y,x); speedometer(V); timeleft(timer); lapsleft(laps_left); timer -= 2L; // 50 FPS is 100 centiseconds. subsec_timer += 1; if (subsec_timer == 50) { subsec_timer = 0; secs_taken += 1; } ticks_left -= 4; if (ticks_left > 0) { Reset0Ref(); Print_Str_d(0, -48, " START!\x80"); } else ticks_left = 0; if (timer <= 0L) { state = TIMED_OUT; break; } switch (crashed(Y, X)) { case 2: y = (int8_t)(Y>>(int16_t)8); x = (int8_t)(X>>(int16_t)8); case 1: V=0; state = CRASHED; case 0:; } if (state == CRASHED) break; if (button_1_1_held()) A+=1; // turn left if (button_1_2_held()) A-=1; // turn right if (button_1_3_pressed() && (--V > 127)) V = 0; // brake (reverse is not allowed) if (button_1_4_pressed() && (++V > MAXV)) V = MAXV; // accelerate // perhaps to make it a harder game, automatically accelerate as play progresses? // Speeds restricted to 0 through 7 (or 15) for now X -= (int16_t)V*(int16_t)(int8_t)(COS(A)>>(int16_t)8); Y -= (int16_t)V*(int16_t)(int8_t)(SIN(A)>>(int16_t)8); // test position on next frame once updated position is drawn. continue; case FINISHED: Reset0Ref(); Print_Str_d(33, -48, " YOU\x80"); Reset0Ref(); Print_Str_d(16, -48, " WIN!\x80"); { static char tmp[16], *ptr; Reset0Ref(); ptr = pnum(tmp, laps_run, FALSE); *ptr++ = ' '; *ptr++ = 'L'; *ptr++ = 'A'; *ptr++ = 'P'; *ptr++ = 'S'; *ptr++ = 0x80; *ptr = 0; Print_Str_d(-4, -48, tmp); Reset0Ref(); ptr = pnum(tmp, secs_taken, FALSE); *ptr++ = ' '; *ptr++ = 'S'; *ptr++ = 'E'; *ptr++ = 'C'; *ptr++ = 'S'; *ptr++ = 0x80; *ptr = 0; Print_Str_d(-20, -48, tmp); } ticks_left = 127; if (button_1_3_pressed()) state = WAITING_FOR_CHEQUERED_FLAG; continue; case DISQUALIFIED: Reset0Ref(); Print_Str_d(16, -42, "DONT GO\x80"); Reset0Ref(); Print_Str_d(-4, -49, "BACKWARD\x80"); ticks_left = 127; if (button_1_3_pressed()) state = WAITING_FOR_CHEQUERED_FLAG; continue; case TIMED_OUT: Reset0Ref(); Print_Str_d(12, -50, " TOO\x80"); Reset0Ref(); Print_Str_d(-4, -48, " SLOW!\x80"); ticks_left = 127; if (button_1_3_pressed()) state = WAITING_FOR_CHEQUERED_FLAG; continue; case CRASHED: Reset0Ref(); Print_Str_d(0, -48, "CRASHED!\x80"); Reset0Ref(); // I had a small correction in here for wraparound on the crash location, which // I removed thinking it was overkill (especially when I narrowed the outer wall a little), // but I still see crash marks on the wrong wall, so I need to remember what that correction // was and put it back in. Or remove the crash mark (which was only originally a debugging // aid, but then I thought it would be nice to draw an engine fire at that location! - which // I haven't done yet... ) #ifdef DRAW_ENGINE_FIRE position_and_scale(BadY,BadX, 127); // draw a fire here! maybe sparks like the battlezone volcano Moveto_d(-4,-4);Draw_Line_d(8,8); Moveto_d(-4,-4); // (this is just an X) Moveto_d( 4,-4);Draw_Line_d(-8,8); Moveto_d(4,-4); #endif ticks_left = 127; if (button_1_3_pressed()) state = WAITING_FOR_CHEQUERED_FLAG; } } return 0; }