// 92%-written game in the style of VeCaves // Note that trivially turning the orientation sideways (swappedxy) // is pretty close to games such as http://www.la1n.ch/vecz/ #ifdef LINUX // althought I *might* produce a PiTrex version of this, currently the // only reason for having the Linux code here is to be able to use the // linux debugger, and ocassionally print some strings out to the console. // I guess I could add OpenGL calls and do it properly some time... #include <stdio.h> #include <stdlib.h> #define PRIVATE_RANDOM 1 // Use a local implementation of random to ensure repeatability #define int8_t char void Moveto_d (int8_t y, int8_t x) { } void Draw_Line_d (int8_t y, int8_t x) { } void Wait_Recal (void) { fprintf (stdout, "WaitRecal();\n"); } void enable_controller_1_y (void) { } void Joy_Digital (void) { } int8_t joystick_1_y (void) { return 0; } void Intensity_a (int8_t a) { } void Reset0Ref (void) { } #else #include <vectrex.h> #include "controller.h" #define int8_t int #endif // end of vectrex-only section (ie not linux) static int swappedxy = 1; void xyMoveto_d (int8_t y, int8_t x) { if (swappedxy) Moveto_d(x,y); else Moveto_d(y,x); } void xyDraw_Line_d (int8_t y, int8_t x) { if (swappedxy) Draw_Line_d(x,y); else Draw_Line_d(y,x); } #define Moveto_d(a,b) xyMoveto_d(a,b) #define Draw_Line_d(a,b) xyDraw_Line_d(a,b) static unsigned int8_t itoa(char *dest, long num) { // left-aligned int8_t digit, zeroes; char *initial = dest; // 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 >= 0L) num = -num; else *dest++ = '-'; digit = 0; zeroes = 1; // CLRing is shorter // max 11 add/subtracts... if (num <= -20000L) { num += 20000L; digit += 2; zeroes = 0; } if (num <= -10000L) { num += 10000L; digit += 1; zeroes = 0; } if (!zeroes) *dest++ = (char)(digit+'0'); digit = 0; if (num <= -8000L) { num += 8000L; digit += 8; zeroes = 0; } else if (num <= -4000L) { num += 4000L; digit += 4; zeroes = 0; } if (num <= -2000L) { num += 2000L; digit += 2; zeroes = 0; } if (num <= -1000L) { num += 1000L; digit += 1; zeroes = 0; } if (!zeroes) *dest++ = (char)(digit+'0'); digit = 0; if (num <= -800L) { num += 800L; digit += 8; zeroes = 0; } else if (num <= -400L) { num += 400L; digit += 4; zeroes = 0; } if (num <= -200L) { num += 200L; digit += 2; zeroes = 0; } if (num <= -100L) { num += 100L; digit += 1; zeroes = 0; } if (!zeroes) *dest++ = (char)(digit+'0'); digit = 0; if (num <= -80L) { num += 80L; digit += 8; zeroes = 0; } else if (num <= -40L) { num += 40L; digit += 4; zeroes = 0; } if (num <= -20L) { num += 20L; digit += 2; zeroes = 0; } if (num <= -10L) { num += 10L; digit += 1; zeroes = 0; } if (!zeroes) *dest++ = (char)(digit+'0'); *dest++ = (char)(((int8_t)-num)+'0'); *dest = '\0'; return (unsigned int8_t)(dest-initial); } unsigned int8_t _RANGE (unsigned int idx, unsigned int low, unsigned int high, long line, char *name) { if ((idx < low) || (idx > high)) { #ifdef LINUX fprintf (stderr, "Range error at line %ld: %s[%u] (%u to %u)\n", line, name, idx, low, high); exit (1); #else //for (;;) ; (void) line; (void) name; #endif } return idx; } static unsigned int8_t base; #ifdef DEBUG #define RANGE(i,low,high,line,name) _RANGE(i,low,high,line,name) #else #define RANGE(i,low,high,line,name) (i) #endif // WRAP only has to be twice as wide as DISPLAY_WIDTH, rounded up to a power of two minus one. // so a WRAP of 127 or 255 turns out to be overkill! #define WRAP 63U #define wrap(x) ((unsigned int)((base+(x))&WRAP)) #define basedARRAY(name, base, x) _##name[RANGE(((base+(unsigned int)(x)) & _upper_##name),0, _upper_##name ,__LINE__,#name)] #define ARRAY(name,x) basedARRAY(name,base,x) #define DECLARE(type, name, upper) static unsigned int8_t _upper_##name = ((unsigned int8_t)(upper)-1U); type _##name[upper] DECLARE(static int8_t, snake, 8); #define snake(x) ARRAY(snake,x) DECLARE(static int8_t, height, (WRAP+1U)); #define height(x) ARRAY(height,x) DECLARE(static int8_t, rockabshigh, (WRAP+1U)); #define rockabshigh(x) ARRAY(rockabshigh,x) DECLARE(static int8_t, rockabslow, (WRAP+1U)); #define rockabslow(x) ARRAY(rockabslow,x) #define DISPLAY_WIDTH 32 #define XSTEP ((unsigned int)(256L/(long)DISPLAY_WIDTH)) /* i.e. 8 */ #define ROCKS 2 // max rocks on screen at once. rock parameters are inserted dynamically // when a rock is created... unsigned int8_t halfcaveheight = 30; DECLARE(static unsigned int8_t, rocky, ROCKS); #define rocky(x) ARRAY(rocky, x) DECLARE(static int8_t, rockoffset, ROCKS); #define rockoffset(x) ARRAY(rockoffset, x) DECLARE(static unsigned int8_t, rockstart, ROCKS); #define rockstart(x) ARRAY(rockstart,x) #define ROCKWIDTH 4 #define ROCKSTYLES 2 // ROCKWIDTH*ROCKSTYLES MUST BE A POWER OF TWO! // To optimise hit detection, we only need to check the leftmost-facing // edges on the upper and lower halves of each rock. leftmost-facing is // trivially determined by testing each y against y+1 is increasing. // stop checking as soon as that is not true... #ifdef CMOC #pragma const_data start #else #define IN_ROM __attribute__ ((section(".text"))) #endif /* Each rock is a top half and a bottom half - the x coordinates are implicit the first and last y coordinate must be equal for them to join up. _ top: / \ bot: \_/ 0123 To optimise hit detection, only the left-facing faces are tested. */ #define MAXROCKHEIGHT 10 #define DRIFT 20 DECLARE(static const int8_t, rockyhigh, (ROCKWIDTH * ROCKSTYLES)) IN_ROM = { 0, 4, 10, 2, -4, 8, 2, -2, /* 0, 4, 10, 2, -4, 8, 2, -2, 0, 4, 10, 2, -4, 8, 2, -2, 0, 4, 10, 2, -4, 8, 2, -2, 0, 4, 10, 2, -4, 8, 2, -2, 0, 4, 10, 2, -4, 8, 2, -2, 0, 4, 10, 2, -4, 8, 2, -2, 0, 4, 10, 2, -4, 8, 2, -2, */ }; #define rockyhigh(x) basedARRAY(rockyhigh, stylebase, x) DECLARE(static const int8_t, rockylow, (ROCKWIDTH * ROCKSTYLES)) IN_ROM = { 0, -6, -2, 2, -4, -6, -4, -2, /* 0, -6, -2, 2, -4, -6, -4, -2, 0, -6, -2, 2, -4, -6, -4, -2, 0, -6, -2, 2, -4, -6, -4, -2, 0, -6, -2, 2, -4, -6, -4, -2, 0, -6, -2, 2, -4, -6, -4, -2, 0, -6, -2, 2, -4, -6, -4, -2, 0, -6, -2, 2, -4, -6, -4, -2, */ }; #define rockylow(x) basedARRAY(rockylow, stylebase, x) #ifdef CMOC #pragma const_data end #endif #ifdef PRIVATE_RANDOM static unsigned int8_t _x, _a, _b, _c; void initRandom (unsigned int8_t s1, unsigned int8_t s2, unsigned int8_t s3, unsigned int8_t x0) { _x = x0; _a = s1; _b = s2; _c = s3; _x++; _a = (_a ^ _c ^ _x); _b = (_b + _a); _c = ((_c + (_b >> 1)) ^ _a); } unsigned int8_t irand127 (void) { // assert returns unsigned value that fits in an int. _x++; _a = (_a ^ _c ^ _x); _b = (_b + _a); _c = ((_c + (_b >> 1)) ^ _a); return _c & 127; } #define Random() (((irand127() << 4) ^ (irand127()>>4))&255) #else #define irand127() ((unsigned int8_t)Random()&127U) #endif static int8_t rand8pow2 (unsigned int8_t max) { // BROKEN!! when max not 2^N return ((int) (Random () & (max - 1))) - ((int) max >> 1); } // <-ROCKWIDTH-> <-ROCKWIDTH-> #define AdjustOffsets(rock) { \ int8_t delta = ((int8_t)(irand127() % (halfcaveheight - MAXROCKHEIGHT - DRIFT))); \ if (Random()&1) rockoffset (rock) -= delta; else rockoffset (rock) += delta; \ } /* in the first iteration of this code, offset was the notional centerline of the cave Now we're using height which is in the range 0..127 above y=0 and the lower line is 128 below that. need to change that 128 to the shrinking value used before... More urgently, work out and document exactly what all the arrays are for, and rename them with more descriptive names. In places where we halt movement in order to debug a range error etc, put a helpful message on the screen, and allow resumption by some keypress eg 1+2 */ static inline void check_for_next_rock (unsigned int8_t base) { static unsigned int8_t rock, i; for (rock = 0; rock < ROCKS; rock++) { if (rockstart (rock) == wrap(0) /* base */ ) { // dropping off left for (i = 0; i < ROCKWIDTH; i++) { // wipe departing rock limits rockabshigh (i) = -128; rockabslow (i) = -128; } // now create new rock at RHS of screen rockstart (rock) = wrap(DISPLAY_WIDTH); rockoffset (rock) = height (DISPLAY_WIDTH)-64 /* -128/2 */ ; // start in center AdjustOffsets(rock); } } } static inline void set_scale (unsigned int8_t scale) { #ifndef LINUX VIA_t1_cnt_lo = scale; #endif } #define SEX5(i) ((((int8_t)(i))<<3)>>3) void do_explosion (int8_t explosion, int8_t y, int8_t x) { int8_t i; if (explosion == 4) { // not at all sure this will work... Print_Str_d(y, x, "YOU ARE A WINNER!\x80"); } else { Moveto_d (y, x); for (i = 0; i < 16; i++) { x = SEX5 ((Random () /* & 31 */)); y = SEX5 ((Random () /* & 31 */)); if ( ((y < 0) && (explosion&1)) || ((y >= 0) && (explosion&2)) ) { // coincidentally bright, and gets you back to where you want to be :-) Draw_Line_d (y, x); Draw_Line_d (-y, -x); } } } } void interpolate (unsigned int low, unsigned int high /* exclusive, eg 128 */ ) { int lower, final; // , gap; unsigned int halfway; int mid; halfway = ((high - low) / 2) + low; // avoid overflow you would get from (high+low)/2 lower = height (low); // eg 0 final = height (high); // 128 (or 0) mid = (int)(((long)lower+(long)final)/2L); // would ((lower>>1) + (final>>1)) work? Does GCC use divide? mid += (int) ((Random()&2)-1) /* plus 1 or -1 */ * ((final-mid)/2); height (halfway) = mid; #ifdef LINUX fprintf (stdout, "h[%d]=%d h[%d]=%d => h[%d]=%d\n", low, lower, high, final, halfway, mid); fprintf (stdout, "%d = %d\n", (base + halfway) & WRAP, height (halfway)); #endif if (high - low > 2) { interpolate (low, halfway); // eg 0 to 64 interpolate (halfway, high); // 64 to 128 } } int main (void) { static unsigned int8_t timeout, i, j, x, xstep, movement; static int8_t rock, delta, vertical; static int explosion; static int8_t last, YJoy; static unsigned int8_t line; static int wobble; static char msg[80]; // these ones, not reset on end of game: static long score; score = 0L; halfcaveheight = 64; msg[0] = ' '; msg[1] = ' '; msg[2] = '0'; msg[3] = '\x80'; SOFT_RESTART: // Make sure *everything* is reinitialised. This is overkill, // but it only happens on restarting a game, so the CPU overhead // isn't noticable. I could examine the source and remove a // few of these initialisations if I had to. #ifdef PRIVATE_RANDOM _x = 0; _a = 0; _b = 0; _c = 0; #endif timeout = 0; i = 0; j = 0; base = 0; xstep = 0; movement = 0; explosion = 0; wobble = 4; x = 0; delta = 0; rocky (0) = 0; rocky (1) = 4; // index into rockyhigh/low styles rockoffset (0) = rockoffset (1) = 0; // position between the walls rockstart (0) = 15; rockstart (1) = 31; // offset from left where rocks first appear for (i = 0; i < 8; i++) snake (i) = 0; for (i = 0; i <= WRAP; i++) { height (i) = 0; rockabshigh (i) = 0; rockabslow (i) = 0; } #ifdef LINUX fprintf (stdout, "Restarting:\n"); #endif #ifdef PRIVATE_RANDOM initRandom (0x48, 0x8B, 0xEF, 0x16); #endif timeout = 225; base = 0; xstep = 1; movement = 1; delta = 1; #define midpoint ((WRAP >> 1) + 1) // E.g. 127 -> 63, 63 -> 31 etc. for (i = 0; i <= WRAP; i++) height (i) = 0; // safety first reinitialise height (0) = 108; height (midpoint) = 20 /* +rand8pow2(16) */ ; (void)rand8pow2(16); interpolate (0, midpoint); // just the first half // cut & pasted :-/ rocky (0) = 0;// index into array of rock shapes. currently only 2, more allowed for. rockstart (0) = wrap((DISPLAY_WIDTH >> 1)+1); rockoffset (0) = height ((DISPLAY_WIDTH >> 1)+1)-64 /* -128/2 */ ; // start in center AdjustOffsets(0); rocky (1) = 4; rockstart (1) = wrap(DISPLAY_WIDTH); rockoffset (1) = height (DISPLAY_WIDTH)-64 /* -128/2 */ ; // start in center AdjustOffsets(1); for (i = 0; i < 8; i++) snake (i) = 0; for (i = 0; i <= WRAP; i++) { rockabshigh (i) = -128; rockabslow (i) = -128; } // MAIN GAME LOOP /* In essence, the array describing the cave is shifted left one unit after every frame. In fact, the array is not changed, rather a base offset is increased, so whenever we access "element(i)" we are really accessing "element[base+i]". By keeping the array size to a power of two, any offset past the end of the array is wrapped around to the no-longer-needed start of the array (which has dropped off the left hand side by now) by AND-ing with the array size minus one. However care has to be taken that anything dropping off the left is suitably uninitialised. In particular, the handling of rocks is currently needing close review. */ for (;;) { // move base to scroll // generate the back half of the river when it is empty. if (wrap(0) == 0) { interpolate (midpoint, (unsigned int) (WRAP + 1)); for (i = (WRAP>>1)+1; i <= WRAP; i++) { rockabshigh (i) = rockabslow (i) = -128; } } check_for_next_rock (base); Wait_Recal (); enable_controller_1_y (); Joy_Digital (); check_buttons(); YJoy = 0; if (joystick_1_y () < -10) YJoy = -2; else if (joystick_1_y () > 10) YJoy = 2; snake (7) = snake (6) + YJoy + wobble; wobble = -wobble; // add an animation effect to snake! Reset0Ref (); Intensity_a (0x7F); set_scale (127); if (button_1_4_held()) { msg[0]=' ';msg[1]=' '; i=itoa(msg+2, score);msg[i+2] = (char)0x80; Print_Str_d(120, -128, msg); // adds about 2300 cycles } // draw snake if (delta == 0) { do_explosion (explosion, snake (7), /* -128 + XSTEP*8 */ -72); if (++timeout == 0) goto SOFT_RESTART; // best not to have any stack variables declared between there and here... } else { Moveto_d (last = snake (0), -128); // tail (0) for (i = 1; i < 8; i++) { // to head... (7) Draw_Line_d (snake (i) - last, 8); last = snake (i); } } // draw rocks for (x = 0; x < DISPLAY_WIDTH; x++) { // scan across same area as is displayed for (rock = 0; rock < 2; rock++) { // check every rock // does a rock start at this screen position? if (wrap (x) == rockstart (rock)) { unsigned int8_t stylebase; // only 2 rocks onscreen at once, but which two slides across the list of choices stylebase = rocky (rock); // upper half of rock Reset0Ref (); // a rock should be accurately positioned Intensity_a (0x7F); Moveto_d (last = rockoffset (rock) + rockyhigh (0), (int) (-128 + x * XSTEP)); rockabshigh (x) = last; for (i = 1; i < ROCKWIDTH; i++) { Draw_Line_d (rockoffset (rock) + rockyhigh (i) - last, XSTEP); rockabshigh (x + i) = last = rockoffset (rock) + rockyhigh (i); } // lower half of rock Reset0Ref (); Intensity_a (0x4F); Moveto_d (last = rockoffset (rock) + rockylow (0), (int) (-128 + x * XSTEP)); rockabslow (x) = last; for (i = 1; i < ROCKWIDTH; i++) { Draw_Line_d (rockoffset (rock) + rockylow (i) - last, XSTEP); rockabslow (x + i) = last = rockoffset (rock) + rockylow (i); } } } } // draw cave: a sneaky design decision means we don't need to repeatedly // do 'Reset0Ref()' to correct for drift, because all the action happens // at the left where drawing starts. Drift may be visible on the right // of the screen, but as long as the rocks don't get too near to the top // or bottom walls, no-one can see any problem because the collisions happen // at the left, where everything has been recently realigned. // This may be the best place to look for things to optimise. We're currently // at 44K cycles and would prefer 30K cycles. Algorithmically this code is // now fairly good, so I think next thing to look at is graphics calls. for (line = 0; line < 2; line++) { // upper and lower walls vertical = (int)-128*(int)line;// 0 or -128 Reset0Ref (); Intensity_a ((line == 0 ? (unsigned int) 0x7F : (unsigned int) 0x3F)); Moveto_d (last = vertical + height (0), -128); // LHS of screen for (i = 0; i < DISPLAY_WIDTH; i++) { int this = vertical + height (i); Draw_Line_d (this - last, 8); last = this; } if ((line == 0) && (snake (7) >= vertical + height (7))) { explosion = 1; delta = 0; movement = 0; // end game when hit roof } else if ((line == 1) && (snake (7) <= vertical + height (7))) { explosion = 2; delta = 0; movement = 0; // end game when hit floor } } // test hitting rocks? if ((rockabshigh (7) != -128) && (snake (7) <= rockabshigh (7)) && (snake (7) >= rockabslow (7))) { explosion = 3; delta = 0; movement = 0; // CRASHED! } // SCROLL: // BEFORE MOVEMENT: base = wrap (movement); // Position 1 moves to position 0. (movement == 1) // AFTER MOVEMENT: if (base == 0) { score += 1L; // at the end of a successful segment, narrow the cave and increase the score... if (score < 34L) halfcaveheight -= (unsigned int8_t)delta; // when the cave height is too low the machine reboots - may be at rock spawning // when available space goes negative? (see line with -DRIFT in it...) if (halfcaveheight <= 25) { explosion = 4; delta = 0; movement = 0; // TO DO: post 'GAME OVER' and play some kind of fanfare when the cave is too narrow. } } } return 0; }