// 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;
}