#include <vectrex/bios.h>

#pragma vx_copyright "2017"
#pragma vx_title_pos 0,-80
#pragma vx_title_size -8, 80
#pragma vx_title "g RIPSOFT"
#pragma vx_music vx_music_1

static int show_debug = 0;
static void DEBUG(void) {
  show_debug = 1;
}

#define line_rel_XY(x,y) line(y,x)
#define move_rel_XY(x,y) move(y,x)

#define MAX_BRIGHTNESS (0x7f)
#define fp2_14 /* SIGNED */ short /* we use a restricted range (2.14) fixed point for trig support */
#define ONE_POINT_ZERO ((fp2_14)(1<<14))
#define FAKE100 128 /* We use 128 instead of 100 in 'percentage' calculations to avoid dividing by 100 */
                    /* (we also use fake degrees consisting of 256 angles in a circle - keeps variables
                       within a single byte which would otherwise require two bytes.) */

static int TargetFPS, ObservedFPS; // Maybe these should be unsigned chars?
static int millis_time;
static unsigned char vectrex_beam_intensity;
static unsigned char Pressed_Key;
static short mouse_x, mouse_y;
static unsigned char mouse_fire, mouse_shields;
static int ScreenWidth, ScreenHeight;

#pragma const_data start

/* Shields get a special proc because they're one continuous line */
static signed char shieldvec[] = {
  24,24,  96,-96, 24,24, -72,72, -72,-72, 24,-24, 96,96, 24,-24, -72,-72, -72,72
};

static signed char crosshair_points[41] = { 
 0x03, 0x20, 0x3e, 0x20,
 0x34, 0x16, 0x34, 0x2a,
 0x16, 0x34, 0x2a, 0x34,
 0x20, 0x02, 0x20, 0x3e,
 0x16, 0x0c, 0x2a, 0x0c,
 0x0c, 0x16, 0x0c, 0x2a,
 0x1b, 0x2a, 0x25, 0x2a,
 0x2a, 0x1b, 0x2a, 0x25,
 0x1b, 0x16, 0x25, 0x16,
 0x16, 0x1b, 0x16, 0x25,
 0
};

static char tg_lookup[(char)7*8*2] = {
  0x17, 0x25, /* TAIL */
  0x1b, 0x25,
  0x19, 0x25,
  0x19, 0x20,
  0x1c, 0x20,
  0x1d, 0x25,
  0x1f, 0x20,
  0x1d, 0x23,
  0x1e, 0x23,
  0x22, 0x20,
  0x22, 0x25,
  0x25, 0x25,
  0x25, 0x20,
  0x28, 0x20,
  0x29, 0x1f,
  0x29, 0x1a,

  0x29, 0x1f, /* GUN */
  0x2c, 0x1f,
  0x2c, 0x1d,
  0x2c, 0x1e /* 1d */,
  0x2c, 0x1a,
  0x29, 0x1d,
  0x28, 0x1a,
  0x25, 0x1a,
  0x25, 0x1f,
  0x28, 0x1f,
  0x26, 0x1d,
  0x25, 0x1d,
  0x23, 0x1f,
  0x23, 0x1a,
  0x20, 0x1f,
  0x20, 0x1a,

  0x1f, 0x1f, /* NER */
  0x1f, 0x1a,
  0x1c, 0x1f,
  0x1c, 0x1a,
  0x1a, 0x1f,
  0x1a, 0x1a,
  0x17, 0x1a,
  0x17, 0x1f,
  0x17, 0x1c,
  0x15, 0x1c,
  0x16, 0x1c,
  0x16, 0x1a,
  0x13, 0x1a,
  0x13, 0x1f,
  0x16, 0x1f,
  0x00, 0x00,

  0x03, 0x2d, /* rectangles */
  0x3c, 0x2d,
  0x3c, 0x12,
  0x03, 0x12,
  0x00, 0x31,
  0x3f, 0x31,
  0x3f, 0x0e,
  0x00, 0x0e,
};

unsigned char tg[39*2] = {
  0x00, 0x01,  /* TAIL */
  0x02, 0x03,
  0x04, 0x05,
  0x05, 0x06,
  0x07, 0x08,
  0x09, 0x0a,
  0x0b, 0x0c,
  0x0c, 0x0d,
  0x0e, 0x0f,

  0x10, 0x11, /* GUN */
  0x11, 0x12,
  0x13, 0x14,
  0x12, 0x15,
  0x16, 0x17,
  0x17, 0x18,
  0x18, 0x19,
  0x1a, 0x1b,
  0x1c, 0x1d,
  0x1d, 0x1e,
  0x1e, 0x1f,

  0x20, 0x21, /* NER */
  0x21, 0x22,
  0x22, 0x23,
  0x24, 0x25,
  0x25, 0x26,
  0x26, 0x27,
  0x28, 0x29,
  0x2a, 0x2b,
  0x2b, 0x2c,
  0x2c, 0x2d,
  0x2d, 0x2e,

  0x30, 0x31, /* rectangles */
  0x31, 0x32,
  0x32, 0x33,
  0x33, 0x30,
  0x34, 0x35,
  0x35, 0x36,
  0x36, 0x37,
  0x37, 0x34,
};

static unsigned char tg_intro_placard[] = {
  // outer border
  163,63,   93,63,    93,63,    93,193,   93,193,   163,193,  163,193,  163,63,

  // iner border
  155,71,   101,71,   101,71,   101,185,  101,185,  155,185,  155,185,  155,71,

  // text
  149,109,  149,93,   131,101,  149,101,  149,119,  131,113,  131,125,  149,119,
  140,122,  140,116,  149,137,  131,137,  131,149,  149,149,  131,161,  131,149,
  107,167,  125,167,  125,179,  125,167,  116,179,  125,179,  107,179,  116,176,
  116,167,  116,179,  107,149,  107,161,  125,149,  107,149,  125,161,  125,149,
  116,149,  116,155,  107,143,  125,143,  125,131,  107,143,  107,131,  125,131,
  107,125,  125,125,  125,113,  107,125,  107,113,  125,113,  107,107,  125,107,
  107,95,   107,107,  125,95,   107,95,   114,86,   114,92,   107,89,   114,89, 
  107,77,   107,89,   125,77,   107,77,   125,89,   125,77,
};


// encoded strings for printing to screen:
static unsigned char SCORE_STR96[] = {5, 29, 13, 25, 28, 15};
static unsigned char HIGH_SCORE_STR96[] = {10, 18,19,17,18,0,29,13,25,28,15};
static unsigned char CREDITS_STR128[] = {8, 13,28,15,14,19,30,29,0};
static unsigned char INSERT_COIN_STR128[] = {11, 19,24,29,15,28,30,0,13,25,19,24};
static unsigned char PRESS_START_STR128[] = {11, 26,28,15,29,29,0,29,30,11,28,30}; // Hmmm... I think it actually said PUSH start...

// font data:

static unsigned char font_points[] = {
 /* 0 */ 0xae, 0xe4, 0x40, 0x0a,
 /* 1 */ 0x2c,
 /* 2 */ 0xae, 0xe9, 0x95, 0x50, 0x04,
 /* 3 */ 0x04, 0x79, 0x4e, 0xea,
 /* 4 */ 0xa5, 0x59, 0x72, // originally 0x5a, 0x59, 0x4e,
 /* 5 */ 0x04, 0x49, 0x95, 0x5a, 0xae,
 /* 6 */ 0x59, 0x94, 0x40, 0x0a,
 /* 7 */ 0x2e, 0xea,
 /* 8 */ 0x04, 0xae, 0x59, 0xe4, 0xa0,
 /* 9 */ 0xea, 0xa5, 0x59, 0xe4,
 /* A */ 0x4e, 0xae, 0x59, 0xa0,
 /* B */ 0x69, 0x04, 0x4e, 0xea, 0xb1,
 /* C */ 0xae, 0x40, 0x0a,
 /* D */ 0xad, 0xd9, 0x93, 0x30, 0x0a, // originally 0xae, 0xe4, 0x40, 0x1b, (note fewer segments - changes length and index tables)
 /* E */ 0x40, 0x0a, 0x57, 0xae,
 /* F */ 0x0a, 0x57, 0xae,
 /* G */ 0x79, 0x94, 0x40, 0x0a, 0xae,
 /* H */ 0x0a, 0x59, 0x4e,
 /* I */ 0x2c,
 /* J */ 0x50, 0x03, 0x3d, 0xbe,
 /* K */ 0x0a, 0xe5, 0x54,
 /* L */ 0x0a, 0x04,
 /* M */ 0x0a, 0xa7, 0x7e, 0xe4,
 /* N */ 0x0a, 0xa4, 0x4e,
 /* O */ 0xae, 0xe4, 0x40, 0x0a,
 /* P */ 0x0a, 0xae, 0xe9, 0x95,
 /* Q */ 0x0a, 0xae, 0xe4, 0x40, 0x48,
 /* R */ 0x48, 0x59, 0x9e, 0xea, 0xa0,
 /* S */ 0x04, 0x49, 0x95, 0x5a, 0xae,
 /* T */ 0xae, 0xc2,
 /* U */ 0x0a, 0x04, 0x4e,
 /* V */ 0xa2, 0x2e,
 /* W */ 0xa1, 0x17, 0x73, 0x3e,
 /* X */ 0x0e, 0xa4,
 /* Y */ 0x27, 0x7a, 0x7e,
 /* Z */ 0xae, 0xe0, 0x04,
};

static unsigned char font_length[] = {
 /* 0 */ 4, /* 1 */ 1, /* 2 */ 5, /* 3 */ 4, /* 4 */ 3, /* 5 */ 5,
 /* 6 */ 4, /* 7 */ 2, /* 8 */ 5, /* 9 */ 4, /* A */ 4, /* B */ 5,
 /* C */ 3, /* D */ 5, /* E */ 4, /* F */ 3, /* G */ 5, /* H */ 3,
 /* I */ 1, /* J */ 4, /* K */ 3, /* L */ 2, /* M */ 4, /* N */ 3,
 /* O */ 4, /* P */ 4, /* Q */ 5, /* R */ 5, /* S */ 5, /* T */ 2,
 /* U */ 3, /* V */ 2, /* W */ 4, /* X */ 2, /* Y */ 3, /* Z */ 3,
};

static unsigned char font_index[] = {
  /* 0 */ 0,    /* 1 */ 4,    /* 2 */ 5,    /* 3 */ 10,   /* 4 */ 14,
  /* 5 */ 17,   /* 6 */ 22,   /* 7 */ 26,   /* 8 */ 28,   /* 9 */ 33,
  /* A */ 37,   /* B */ 41,   /* C */ 46,   /* D */ 49,   /* E */ 54,
  /* F */ 58,   /* G */ 61,   /* H */ 66,   /* I */ 69,   /* J */ 70,
  /* K */ 74,   /* L */ 77,   /* M */ 79,   /* N */ 83,   /* O */ 86,
  /* P */ 90,   /* Q */ 94,   /* R */ 99,   /* S */ 104,  /* T */ 109,
  /* U */ 111,  /* V */ 114,  /* W */ 116,  /* X */ 120,  /* Y */ 122,
  /* Z */ 125,
};

static unsigned char ShipMeshPointers[] = {
  0,  20,  20+21,  20+21+17,  20+21+17+23
};

// static char ship_mesh_length[] = { 20,  21,  17,  23 };

// Ships hand-coded by Peter Hirschberger.  Great work, Peter!
// Original data was given to me as doubles (eg lines of 1.5 length) but I've scaled everything to ints
static char ShipMesh[(20+21+17+23)*2*3] = {  // tweaked values -129 => -128 and tweaked 150 => 127
        /* FROM */          /* TO */
       0, 0, -100,       40, 0, -60,
       40, 0, -60,        0, 0, -20,
        0, 0, -20,      -40, 0, -60,
      -40, 0, -60,       0, 0, -100,
       0, 0, -100,       0, 40, -60,
       0, 40, -60,        0, 0, -20,
        0, 0, -20,      0, -40, -60,
      0, -40, -60,       0, 0, -100,
        0, 0, -20,       30, 0, 120,
        0, 0, -20,      20, 20, 120,
        0, 0, -20,     -20, 20, 120,
        0, 0, -20,      -30, 0, 120,
       30, 0, 120,      20, 20, 120,
      20, 20, 120,     -20, 20, 120,
     -20, 20, 120,      -30, 0, 120,
      -30, 0, 120,       30, 0, 120,
       30, 0, 120,     100, -40, 80,
     100, -40, 80,        0, 0, -20,
      -30, 0, 120,    -100, -40, 80,
    -100, -40, 80,        0, 0, -20,
        0, 0, -90,       45, 0, -60,
       45, 0, -60,        45, 0, 60,
        45, 0, 60,         0, 0, 90,
         0, 0, 90,       -45, 0, 60,
       -45, 0, 60,      -45, 0, -60,
      -45, 0, -60,        0, 0, -90,
        45, 0, 60,       60, 24, 60,
       60, 24, 60,      60, 24, 127,
      60, 24, 127,       45, 0, 127,
       45, 0, 127,        45, 0, 60,
       -45, 0, 60,      -60, 24, 60,
      -60, 24, 60,     -60, 24, 127,
     -60, 24, 127,      -45, 0, 127,
      -45, 0, 127,       -45, 0, 60,
       -45, 0, 60,    -60, -27, -90,
    -60, -27, -90,     0, -34, -128,
     0, -34, -128,     60, -27, -90,
     60, -27, -90,        45, 0, 60,
        0, 0, -90,     0, -34, -128,
       45, 0, -60,     60, -27, -90,
      -45, 0, -60,    -60, -27, -90,
       0, 0, -100,       60, 0, -40,
       60, 0, -40,         0, 0, 20,
         0, 0, 20,      -60, 0, -40,
      -60, 0, -40,       0, 0, -100,
       0, 0, -100,       0, 40, -40,
       0, 40, -40,         0, 0, 20,
       60, 0, -40,       0, 40, -40,
       0, 40, -40,      -60, 0, -40,
         0, 0, 20,       0, 80, 100,
       0, 80, 100,       0, 40, 100,
       0, 40, 100,         0, 0, 20,
         0, 0, 20,    100, -40, 100,
    100, -40, 100,     40, -20, 100,
     40, -20, 100,         0, 0, 20,
         0, 0, 20,   -100, -40, 100,
   -100, -40, 100,    -40, -20, 100,
    -40, -20, 100,         0, 0, 20,
        0, 2, -92,       30, 2, -70,
       30, 2, -70,       0, 10, -20,
       0, 10, -20,      -30, 2, -70,
      -30, 2, -70,        0, 2, -92,
        0, 2, -92,      0, -25, -45,
      0, -25, -45,       0, 10, -20,
       30, 2, -70,      22, 15, -60,
      -30, 2, -70,     -22, 15, -60,
       30, 2, -70,      0, -25, -45,
      -30, 2, -70,      0, -25, -45,
      22, 15, -60,     -22, 15, -60,
      22, 15, -60,       0, 10, -20,
     -22, 15, -60,       0, 10, -20,
       30, 2, -70,      -30, 2, -70,
       0, 10, -20,    -20, -10, 110,
       0, 10, -20,     20, -10, 110,
    -20, -10, 110,     20, -10, 110,
      0, -25, -45,    -20, -10, 110,
      0, -25, -45,     20, -10, 110,
       0, 10, -20,      -90, 20, 90,
      -90, 20, 90,    -20, -10, 110,
       0, 10, -20,       90, 20, 90,
       90, 20, 90,     20, -10, 110,
};


//
// 18.14 fixed point sine and cosine tables when used with 32 bit ints.  However when used
// with 16 bit ints, these are 2.14 tables :-(  This may not matter as long as all we ever
// do is multiply shorts by fixed point values in the range -1.0 to 1.0 - which is the case
// when multiplying by sin() or cos() of an angle.

//
// These table assume that there are 256 angles in a circle.  We don't use 360 anywhere in this code.
//

static fp2_14 sine[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 // => 1.0
  };


// I've checked the results (see asin_test.c) and they're close enough for game work...

// Can halve the size of this table easily by using rotational symmetry around 0.
// (same as first part of sin table optimisation)

static fp2_14 asintab[129] = { // multiply by 360 for degrees, by 256 for angles
                                     // (float)asintab[x]*(360.0) / (1<<14) would be directly equivalent to scratch's asin
                                     // but we want asintab[x]>>6 for 256 angles
  -4096,  -3770,  -3634,  -3530,  -3442,  -3365,  -3294,  -3230,
  -3169,  -3112,  -3058,  -3007,  -2958,  -2911,  -2865,  -2821,
  -2778,  -2737,  -2696,  -2657,  -2619,  -2581,  -2544,  -2508,
  -2473,  -2438,  -2404,  -2371,  -2338,  -2306,  -2274,  -2242,
  -2211,  -2181,  -2151,  -2121,  -2091,  -2062,  -2033,  -2005,
  -1977,  -1949,  -1921,  -1894,  -1867,  -1840,  -1813,  -1787,
  -1760,  -1734,  -1709,  -1683,  -1658,  -1632,  -1607,  -1583,
  -1558,  -1533,  -1509,  -1485,  -1460,  -1436,  -1413,  -1389,
  -1365,  -1342,  -1319,  -1295,  -1272,  -1249,  -1226,  -1203,
  -1181,  -1158,  -1136,  -1113,  -1091,  -1069,  -1046,  -1024,
  -1002,   -980,   -959,   -937,   -915,   -893,   -872,   -850,
  -829,   -807,   -786,   -765,   -743,   -722,   -701,   -680,
  -659,   -638,   -617,   -596,   -575,   -554,   -533,   -513,
  -492,   -471,   -450,   -430,   -409,   -389,   -368,   -347,
  -327,   -306,   -286,   -265,   -245,   -224,   -204,   -183,
  -163,   -143,   -122,   -102,    -82,    -61,    -41,    -20,
  0,                                                             // X=-X: antisymmetric
};

#pragma const_data end

#define UP 0
#define DOWN 1
static unsigned char pen;
static void putPenDown(void)
{
  pen = DOWN;
}

static void putPenUp(void)
{
  pen = UP;
}

static void Display_Vectrex_vector(short from_x, short from_y, short to_x, short to_y)
{
  // Use hardware.
}

static short last_x, last_y;
static void gotoxy(short x, short y)
{
  // since 'down' is 1, we can just do "if (pen) ..." and when we do,
  // can make this a boolean 'pen_down'.  However I'd rather just remove
  // all traces of Scratch's pen up/pen down
  if (pen == DOWN) Display_Vectrex_vector(last_x, last_y, x, y);
  last_x = x; last_y = y;
}

static short rseed;
static short urandom16(void)
{
  return (rseed = (rseed * 2053) + 13849);
}

static unsigned short irand(short max) {
  // returns int 0..max-1
  return urandom16() % max; // probably not very random, but this is a video game, not crypto
}

static short pickrandom(short low, short high)
{
  // We'll just assume that high > low...
  return (short)irand(high-low+1)+low;  // pick a random integer between low and high inclusive.
}

//
// Square root is used in two places in this program.  One is in a distance test and can
// be avoided by squaring the other side of the comparison instead; unfortunately the
// other is in the code that computes the attackers' flightpaths and cannot be avoided.
//
// I haven't tried it on the vectrex yet and don't know if this will be fast enough,
// if not there are probably some simpler approximate-square-root algorithms I can
// use instead.  But lets see how well the obvious one works first...
//

static unsigned short isqrt(unsigned short op)
{
  unsigned short res = 0U;
  unsigned short one = 1U<<14U; // The second-to-top bit is set
  unsigned short limit;

  // "one" starts at the highest power of four <= than the argument.
  while (one > op) one >>= 2;

  while (one != 0) {
    if (op >= (limit = res + one)) {
      op -= limit;
      res += one;
      res += one;
    }
    res >>= 1;
    one >>= 2;
  }
#ifdef SQRT_ROUNDING
  /* Do arithmetic rounding to nearest integer */
  if (op > res) res++;
#endif
  return res;
}

// to whom it may concern ... :-)  entirely irrelevant to this implementation, but nevertheless
// some interesting discussion: https://stackoverflow.com/questions/3380628/fast-arc-cos-algorithm
// (also remember identities to allow the sin code to be used for cos, and asin code to be used for acos;
//  and depending on the speed of your divide, maybe also define tan in terms of sin & cos.)

// Could use asin(x) = atan2 (x, sqrt ((1.0 + x) * (1.0 - x)))
// except our atan2 is pretty slow and isqrt is worse, and then we have muldiv to worry about!

static short asin_of_fp2_14(fp2_14 angle) // parameter is consistent with the result of fp2_14_sin(angle)
{                                       // angle inherited from Scratch implementation is in range -1.0 to 1.0
                                        // so looks pretty standard.  Results appear to be in the -180 to 180
                                        // range (in the Scratch implementation) although since this is
                                        // used on ships facing the player, the actual range is more around
                                        // +/-10 and I don't think ever outside +/-90.  I mention all this
                                        // because, although a simple table-driven solution is obvious,
                                        // it is only an approximation and perhaps a larger table might
                                        // be used within the active range to get better precision?
                                        // Or get an initial value with a table and refine with
                                        // a couple of iterations of code?  We already have a high expense
                                        // for the atan implementation.
                                        //
                                        // Our reimplementation should used 256 angles rather than degrees
                                        // NOTE that I am non-standardly returning -128..128 rather than 0..255 -
                                        // this is to be compatible with the Scratch implementation

  if (angle > 128) return -(asintab[256-angle]>>6);  // or use abs instead of '-'.  first half of table is all negative or 0.
  return asintab[angle]>>6; // returns fp equivalent to -1.0 to 1.0 I think

  // ( Multiply fp result by 360 for degrees, or by 256 for our angles 
  //   - conflate the scaling with the shift to convert down from fp2_14 to int)

  // It would make a lot more sense to move the >>6 to the point of call, where it can cancel out things like the *256 above,
  // and avoid any loss of precision due to the shifting.  Not that accuracy is so important here.

  // This whole area is getting a bit messy, but at least I'm starting to get
  // the last of the missing components in place.  May end up doing a lot of side-by-side comparison with the Scratch code
  // to debug some of this... (after removing all the randoms and making it all repeatable...)
}


// Although lookup tables work pretty well in video games for sin/asin where
// the input domain is very restricted, the technique doesn't work so well
// for atan.  A better solution is to use an approximation, such as this one.

// (or maybe a cruder approximation as in https://dspguru.com/dsp/tricks/fixed-point-atan2-with-self-normalization/ ? )

// This code is taken from https://geekshavefeelings.com/posts/fixed-point-atan2
// I've taken that code and shoe-horned it in to an interface that returns values
// that are very close to the values that Scratch returns for the same parameters
// - at least when used in the context of this program!  I make no claims at all
// for it working in any other context.

// In scratch, the parameters are floats, whereas here they are short ints, so
// there's a small loss of precision there; and the results here are short ints
// too, so there's a second loss of precision.  Despite that, the results are
// pretty close (less than 2 degrees of absolute error) and definitely good enough
// for a video game.

// I leave most of the original comments intact.

/*
 * fxpt_atan2.c
 *
 * Copyright (C) 2012, Xo Wang
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is furnished to do
 * so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

/**
 * Negative absolute value. Used to avoid undefined behavior for most negative
 * integer (see C99 standard 7.20.6.1.2 and footnote 265 for the description of
 * abs/labs/llabs behavior).
 *
 * @param i 16-bit signed integer
 * @return negative absolute value of i; defined for all values of i
 */
static short s16_nabs(short j) {
//#if (((short)-1) >> 1) == ((short)-1)
  // signed right shift sign-extends (arithmetic)
//  const short negSign = ~(j >> 15); // splat sign bit into all 16 and complement
  // if j is positive (negSign is -1), xor will invert j and sub will add 1
  // otherwise j is unchanged
//  return (j ^ negSign) - negSign;
//#else
  return (j < 0 ? j : -j);
//#endif
}

// I haven't checked this yet for signed parameters, so the places I'm using it
// all assume the parameters are unsigned and handle the negation separately.
// Rather hacky.  I'll revisit this after the code is running well.
unsigned short muldiv(unsigned short a, unsigned short b, unsigned short c)
{
  // replace this with asm and the MUL instruction at some point!

  unsigned short q = 0, r = 0, qn, rn;
  // from https://stackoverflow.com/questions/4144232/how-to-calculate-a-times-b-divided-by-c-only-using-32-bit-integer-types-even-i
  // ... doing it the hard way, without double precision of any kind, either in C or assembler...
  if (a < b) {
    qn = b / c;
    rn = b % c;
    while (a) {
      if (a&1) { q += qn; r += rn; if (r >= c) { q++; r -= c; } }
      a >>= 1; qn <<= 1; rn <<= 1; if (rn >= c) { qn++; rn -= c; }
    }
  } else {
    // the while loop should be over the smaller variable, for speed
    qn = a / c;
    rn = a % c;
    while (b) {
      if (b&1) { q += qn; r += rn; if (r >= c) { q++; r -= c; } }
      b >>= 1; qn <<= 1; rn <<= 1; if (rn >= c) { qn++; rn -= c; }
    }
  }
 return q; // also, r = (a * b) % c   (q = quotient, r = remainder)
}

static short q15_mul(short j, short k) {
  // I'll fix this later.  Use a flag, and s16_nabs() to unnegate the negative params
  // check it works using the atan_test.c program - trivial mistakes here can really break things
  if (j<0 && k<0) return (short)muldiv(-j,-k,1<<15);
  if (j<0) return -(short)muldiv(-j,k,1<<15);
  if (k<0) return -(short)muldiv(j,-k,1<<15);
  return (short)muldiv(j, k, 1<<15);
}

static short q15_div(short numer, short denom) {
  // ditto
  if (numer<0 && denom<0) return (short)muldiv(-numer, 1<<15,  -denom);
  if (numer<0) return -(short)muldiv(-numer, 1<<15,  denom);
  if (denom<0) return -(short)muldiv(numer, 1<<15,  -denom);
  return (short)muldiv(numer, 1<<15,  denom);
}

/**
 * 16-bit fixed point four-quadrant arctangent. Given some Cartesian vector
 * (x, y), find the angle subtended by the vector and the positive x-axis.
 *
 * The value returned is in units of 1/65536ths of one turn. This allows the use
 * of the full 16-bit unsigned range to represent a turn. e.g. 0x0000 is 0
 * radians, 0x8000 is pi radians, and 0xFFFF is (65535 / 32768) * pi radians.
 *
 * Because the magnitude of the input vector does not change the angle it
 * represents, the inputs can be in any signed 16-bit fixed-point format.
 *
 * @param y y-coordinate in signed 16-bit
 * @param x x-coordinate in signed 16-bit
 * @return angle in (val / 32768) * pi radian increments from 0x0000 to 0xFFFF
 */
unsigned short fxpt_atan2(short y, short x) {
  short nabs_y, nabs_x;
  if (x == y) { // x/y or y/x would return -1 since 1 isn't representable
    if (y > 0) { // 1/8
      return 8192U;
    } else if (y < 0) { // 5/8
      return 40960U;
    } else { // x = y = 0
      return 0U;
    }
  }
  nabs_y = s16_nabs(y); nabs_x = s16_nabs(x);
  if (nabs_x < nabs_y) { // octants 1, 4, 5, 8
    short y_over_x = q15_div(y, x);
    short correction = q15_mul(/*q15_from_double(0.273 * M_1_PI)*/2847, s16_nabs(y_over_x));
    short unrotated = q15_mul(/*q15_from_double(0.25 + 0.273 * M_1_PI)*/11039 + correction, y_over_x);
    if (x > 0) { // octants 1, 8
      return unrotated;
    } else { // octants 4, 5
      return 32768U + unrotated;
    }
  } else { // octants 2, 3, 6, 7
    short x_over_y = q15_div(x, y);
    short correction = q15_mul(/*q15_from_double(0.273 * M_1_PI)*/2847, s16_nabs(x_over_y));
    short unrotated = q15_mul(/*q15_from_double(0.25 + 0.273 * M_1_PI)*/11039 + correction, x_over_y);
    if (y > 0) { // octants 2, 3
      return 16384U - unrotated;
    } else { // octants 6, 7
      return 49152U - unrotated;
    }
  }
}

#ifdef NEVER
static short Scratch_atan2(short x, short y)  // substitute for atan2(x/y), using real degrees.  Not needed here.
{
  // sign shenannigans needed to match Scratch's behaviour...
  short result = ((fxpt_atan2(x, y)>>8)-128)*360/256;

  if (result <= -90) {
    return result + 180;
  } else if (result >= 90) {
    return result - 180;
  }
  return result;

}
#endif

static short Scratch_atan2_fakedegrees(short x, short y)  // substitute for atan2(x/y) but using 256 angles in a circle rather than 360 degrees
{
  short result = ((fxpt_atan2(x, y)>>8)-128);

  if (result <= -64) {
    return result + 128;
  } else if (result >= 64) {
    return result - 128;
  }
  return result;

}


#ifdef NEVER // replaced by atan2 - we don't have Scratch's safety net of trapping /0 errors...
static short atan_of_fp2_14(fp2_14 angle) // angle in is arbitrary value
{
  // TO DO FP:
  return 0; // returns atan of angle - watch for range issues!
}
#endif

static unsigned char numdigits(short num)
{
  // a chain of if/then/elses may be more efficient than a software divide by 10 ?
  unsigned char result = 0U;
  do {
    num = num / 10;
    result++;
  } while (num > 0);
  return result;
}

static unsigned char /* boolean */ clipoff(short x, short y)
{
  /* Stars never reach center of screen */
  // size of rectangle still hard-coded
  return ( -0x20 <= x && x < 0x20 && -0x12 <= y && y < 0x12);
}

/* The screen is divided up into 8 sectors:

          \ | /
           \|/
          --+--
           /|\
          / | \

   We want 16 stars on screen at all times.

   At any time, each sector should have 2 stars active in it.

   At least, that was the original algorithm...  now it's less
   strict, and assigns the initial starting position to anywhere
   on one of the four sides with equal probability as to which
   side.  Not guaranteed to be as visually appealing but a little
   cheaper to implement.
*/

static short pxa[16], pya[16];

static short star_x1(unsigned char idx) {
  unsigned char xtype = (idx>>2)&3, ytype = idx&3;

  idx = xtype ^ ytype; // four types of star trail ...
  if (idx == 0) { // left edge
    return -127<<3;
  } else if (idx == 2) { // right edge
    return 127<<3;
  } else { // top or bottom edges
    return pickrandom(-127<<3, 127<<3);
  }
}

static short star_y1(unsigned char idx) {
  unsigned char xtype = (idx>>2)&3, ytype = idx&3;

  idx = xtype ^ ytype; // four types of star trail ...
  if (idx == 1) { // top edge
    return 127<<3; // right edge
  } else if (idx == 3) { // bottom edge
    return -127<<3;
  } else {
    return pickrandom(-127<<3, 127<<3);
  }
}

static void move_star(unsigned char starno)
{
  short x = pxa[starno], y=pya[starno];

  // coordinate space is 0,0 center!
  x = ((x<<5)-x)>>5;
  y = ((y<<5)-y)>>5;
  // move stars closer to the center, ie towards 0
  if (clipoff(x>>3,y>>3)) {
    x = star_x1(starno); y = star_y1(starno); // reset when they disappear
  }
  pxa[starno] = x; pya[starno] = y;

}

static void plot_star(unsigned int starno)
{
  short brightx, brighty;
  if (pxa[starno] < 0) brightx = -pxa[starno]; else brightx = pxa[starno];
  brightx = (brightx>>3)&127;
  if (pya[starno] < 0) brighty = -pya[starno]; else brighty = pya[starno];
  brighty = (brighty>>3)&127;
  // take this out if we don't want fading over distance...
  intensity((char)((brightx+brighty)>>1)); // good enough approximation for hypotenuse :-)
  dot((char)(pxa[starno]>>4),(char)(pya[starno]>>4));
  move((char)(-(pxa[starno]>>4)),(char)(-(pya[starno]>>4))); // correct back to 0,0 for next dot.
}


static void init_stars(void)
{
  unsigned char i, star;

  for (star = 0U; star < 16U; star++) {
    pxa[star] = star_x1(star); pya[star] = star_y1(star);
    for (i=0; i < star*13; i++) move_star(star); // skew the starting points (QUICK HACK)
  }
}

static void stars(unsigned char speed) // will always be centered on 0,0
{
  unsigned char i, star;
  for (star = 0U; star < 16U; star++) {
    for (i = 0U; i < speed; i++) move_star(star);
  }

  for (star = 0U; star < 16U; star++) {
    if ((star & 15) == 0) {
      // control the frequency that the beam is reset!
      // Use a smaller mask if there is jitter if only
      // doing this once every 16 stars
      reset_beam();
      set_scale(0xF0);
    }
    plot_star(star);
  }
  intensity(MAX_BRIGHTNESS); // restore default
}

/* Shields on the vectrex are sideways wrt the 'real' tailgunner */
static void draw_shields(char absx, char absy, unsigned char scale)
{
  signed char *ptr = shieldvec;
  int i;
  signed char x1, y1;

  reset_beam();
  set_scale(0x7f);
  move(absx, absy);
  set_scale(scale);

  move(-72, 24); /* Manually tweaked */

  for (i = 0; i < 10; i++) {
    /* DRAW THE VECTOR */
    y1 = (*ptr++);
    x1 = (*ptr++);
    line(y1, x1); // Swapped x for y because of portrait mode Vectrex display
  }
}

static void draw_crosshair(char absx, char absy, unsigned char scale)
{
  signed char  x1, y1, x2, y2, dx, dy;
  signed char *ptr;

  reset_beam();
  set_scale(0x7f);
  move(absx, absy);
  set_scale(scale);
  move(-0x20, -0x20);

  ptr = crosshair_points;
  for (;;) {
    /* DRAW THE VECTOR */
    x1 = *ptr++; if (x1 == 0) break; 
    y1 = *ptr++; 
    x2 = *ptr++; 
    y2 = *ptr++; 

    dx = x2-x1; 
    dy = y2-y1;

    move(y1, x1); 
    line(dy, dx );
    move(-dy,-dx);
    move(-y1, -x1);    
    /* back at relative 0,0 */
  }
}
// This is the machine-translated Scratch code after manual massaging.

// We don't yet have a sound system for the vectrex...
#define SOUND_BOUNCE 1
#define SOUND_EXPLODE 2
#define SOUND_LASER 3
#define SOUND_SHIELD 4
#define SOUND_STAR 5
#define SOUND_COIN 6

// Internal game state
#define PREGAME    0 /* Loops over Tumbling banner, demo wave, then INSERT COIN */
#define TUMBLING   1
#define DEMOWAVE   2
#define INSERTCOIN 3
#define COINED     4 /* loops demo wave while showing PRESS START */
#define PLAYING    5
#define PASSING    6

/* 3d rendering */

// Remember that changing FOCAL_LENGTH changes the scale it is drawn at as well!
// Maybe not the proper name for this parameter?

//#define FOCAL_LENGTH_SHIFT 7
//#define FOCAL_LENGTH_SHIFT 8
#define FOCAL_LENGTH_SHIFT 9  /* as long as compilers map /512 to >>9, we're golden... */
//#define FOCAL_LENGTH_SHIFT 10

#define FOCAL_LENGTH ((short)1<<(short)FOCAL_LENGTH_SHIFT)
/* ships */

// Accel is less than 1.0 but velocities are integers.  Currently I believe
// I am adding accel to a velocity but that is not going to work because the
// fixed point delta V will truncate to 0.  Need to look at the acceleration
// code carefully and restructure it in a way that will work, eg perhaps by
// multiplying by 1.1 instead of adding .1 to a value?

// We can't just change velocities to fixed point with the current implementation
// because there are not enough bits in fp2_14!

#define SHIP_ACCEL_FP div_int_by_int_returning_fp2_14(1, 10)
#define SHIP_MAXVEL 20

#define SHIP_SPAWN_Z 2000

#define SHIPSPERWAVE 3U

#define SHIP_TYPE 0U
#define SHIP_X 1U
#define SHIP_Y 2U
#define SHIP_Z 3U
#define SHIP_X_ROTATION 4U
#define SHIP_Y_ROTATION 5U
#define SHIP_Z_ROTATION 6U
#define SHIP_SCALE_FP 7U
#define SHIP_SPEED 8U
#define SHIP_TIME 9U
#define SHIP_TARGET_X 10U
#define SHIP_TARGET_Y 11U
#define SHIP_TARGET_Z 12U
#define SHIP_STATUS 13U  // Adding this so that when we destroy a ship, we can merely flag it as dead,
                         // unlike the current code from Scratch which deleted an entry from the middle
                         // of a list of ships - something that is rather expensive to do on this platform
                         // ... whereas checking a flag in a loop that is only over 3 ships at most is cheap

                         // Statuses may include ATTACKING, EXPLODING, DEAD ? 

#define SHIP_RECORD_LENGTH 14U /* number of fields in the ship struct.  If we made it 16 could use a shift when multiplying is needed */
                           // ship array currently taking up 14*2*3 bytes (84) 
#define SHIP_STATUS_ALIVE 0U     // attacking the player and on-screen
                                 // *OR* bounced off shields and falling into the distance...
#define SHIP_STATUS_EXPLODING 1U // on screen, but rendered with (to be written) explosion code, and no missile hit checks applied
#define SHIP_STATUS_DEAD 2U      // shot, exploded, and off-screen
#define SHIP_STATUS_PASSED 3U    // not on screen, but not dead.  But dead as far as the code is concerned.
#define SHIP_STATUS_LOST 4U      // FOR DEBUGGING ONLY.  ship has wandered outside the cube!

static unsigned char Credits; // already hacked to handle > 9 coins.  If anyone inserts 128 virtual coins they get all they deserve.

static short intro_z; // intro_x and intro_y are effectively constant (modulo changes to screen geometry on the fly...)
static unsigned char intro_rot; // 256 'degrees' in a circle...

//static short distance;
//static short edge;

// since these times are used in mutually exclusive sections, I could conflate them
// down to a single timer variable... (no need so far, ram usage is low...)
static short insertcoin_mode_timer; // a timer that counts down from 129 to 0 - a frame counter

static short passing_mode_timer; // a timer that counts down from 129 to 0 - a frame counter

static short flashing_intro_timer; // a timer that counts down from 129 to 0 - a frame counter
static unsigned char global_flashing_intensity;
static unsigned char flashingtexttimer;

static short hide_intro_timer; // I think hide was a timer I planned to use in attract mode, to switch from the
                   // tumbling intro placard to a demo attack wave.

static short Mode; // there's a small state machine to denote where in the game we are.
                   // See 'internal game state' above...  Note that Mode is *only* modified
                   // within the playTailgunner() procedure, to keep things easy to find

static short mouseDownPrev;

static unsigned char laserid;
static short lasert;
static short lastfiringtime;

static short HighScore;
static short LastScore;
static short Score;

static short UsingShields;
static short Shields;
static short shieldsegment;
static short shieldsubticksleft;

static short ShipsPassedThisWave;
static short ShipsPassedTotal;

static fp2_14 cosX;  // used in calculating ship rotations
static fp2_14 cosY;
static fp2_14 cosZ;
static fp2_14 sinX;
static fp2_14 sinY;
static fp2_14 sinZ;

static short stopintroflag; // Kill the attract screen when a coin is inserted.  0 allows, 2 stops.  1 - not sure why there are multi values!!???

//static short targettingx; // Used in 'renderlasers' - Scratch version.  May or may not end up in final code.
//static short targettingy;

static short textx;  // coordinates where text is displayed
static short texty;
static short textz;

static short tgi;
static short tgx;
static short tgy;
static short tgz;

static unsigned char nextwaveshiptype;


// 'this_*' variables are valid after the corresponding GET call gets the data from the Ships[] array and places them here
// - they're valid for the current ship, temporarily.  Writing to them changes nothing unless they're written back with a SET call...

static unsigned char this_status; // is ship alive or dead?
static fp2_14 this_scale_fp; // Surprisingly, this does not appear to be used at the moment!  Which is surprising considering how complex it has made the code!
                             // (when I say 'not used', I mean 'never altered' after the original assignment of 2.0 (which I tweaked to 1.999 for safety)
                             // so if I'm not changing it - why is it here????!
static short this_ship_time; // ship time, currently incremented once per frame, so not consistent with real time.  maybe recode later using millis?
static unsigned char this_model; // TO DO: !!! Careful checking needed!  Scratch used 1..4 - now in process of changing to 0..3 - NOT COMPLETED!!!! 
static short this_X;
static short this_Y;
static short this_Z;

static short this_vel;

static short this_X_destination;
static short this_Y_destination;
static short this_Z_destination;

static short this_X_rotation;
static short this_Y_rotation;
static short this_Z_rotation;

// used in steering:

// only used by Transform_point which is called by Draw_ship_at_rotated_scale
static short transformed_X;
static short transformed_Y;
static short transformed_Z;
static short XY;
static short XZ;
static short YX;
static short dirXZ;
static short dirY;

static short ships[SHIPSPERWAVE * SHIP_RECORD_LENGTH]; // max ships * size of ship record.
                                            // remember that hard coded offsets are based at 1
                                            // for Scratch arrays, so need to be replaced with
                                            // suitable-adjusted constants (which I think I've
                                            // done now)

static fp2_14 sinsymmetry(unsigned char angle128)
{
  // '>', not '>=', is a special case for sin(x)=1.0
  if (angle128 > 64U) return sine[128-angle128]; else return sine[angle128];
}

static fp2_14 fp2_14_sin(short angle256)
{
  if ((unsigned char)angle256 >= 128U) return -sinsymmetry((unsigned char)(angle256-128)&127); else return sinsymmetry((unsigned char)angle256&127);
}

static fp2_14 fp2_14_cos(short angle256)
{
  return fp2_14_sin(angle256+64); // implicit &255 in these unsigned char parameters
}

static short mul_short_by_fp2_14_returning_short(short num, fp2_14 fp)
{
  short sign = 1;
  if (num < 0) { num = -num; sign = -sign; }
  if (fp < 0) { fp = -fp; sign = -sign; }
  return sign * muldiv(num, fp, ONE_POINT_ZERO);  // clearly could be a lot more efficient, but this has to do for now while developing
}

static fp2_14 div_int_by_int_returning_fp2_14(short num, short den) // divides 2 ints and returns fp result
{
  // should we range-test the results of these? Even if only on the OpenGL version where I can output some diagnostic to a console?
  short sign = 1;
  if (num < 0) { num = -num; sign = -sign; }
  if (den < 0) { den = -den; sign = -sign; }
  return sign * muldiv(num, ONE_POINT_ZERO, den);
}

// The revolving intro placard...

static void draw_tg_intro_with_rolling_rotation(short from_x, short from_y, short to_x, short to_y, unsigned char rot)
{
  if ((from_x != to_x) || (from_y != to_y)) {
    short screen_fromx, screen_fromy;
    short PackX, PackY, PackZ;
    short screenx; // 3d coordinates are convered to screen coordinates when displaying vectors
    short screeny;

    PackX = to_x; PackY = to_y;
    PackZ = mul_short_by_fp2_14_returning_short(PackY, fp2_14_sin (rot));
    PackY = mul_short_by_fp2_14_returning_short(PackY, fp2_14_cos (rot));

    PackZ += intro_z;
    // should I change this to use Get_screenpos() ???
    screenx = ((PackX << FOCAL_LENGTH_SHIFT) / (FOCAL_LENGTH + PackZ));
    screeny = ((PackY << FOCAL_LENGTH_SHIFT) / (FOCAL_LENGTH + PackZ));
    //gotoxy (screenx, screeny);

if (((screenx >> 8)&255 != 0) && ((screenx >> 8)&255 != 0xff)) DEBUG();
if (((screeny >> 8)&255 != 0) && ((screeny >> 8)&255 != 0xff)) DEBUG();
    move_rel_XY((char)(screen_fromx=screenx), (char)(screen_fromy=screeny));

    tgz = mul_short_by_fp2_14_returning_short(from_y, fp2_14_sin (rot));
    tgy = mul_short_by_fp2_14_returning_short(tgy, fp2_14_cos (rot));

    tgz += intro_z;
    // ditto Get_screenpos() ???
    screenx = ((from_x << FOCAL_LENGTH_SHIFT) / (FOCAL_LENGTH + tgz));
    screeny = ((tgy << FOCAL_LENGTH_SHIFT) / (FOCAL_LENGTH + tgz));

    // ultimately would prefer to get rid of pen up/down/gotoxy and call vectrex code directly
    //putPenDown (); gotoxy (screenx, screeny); putPenUp ();
if (((screenx-screen_fromx >> 8)&255 != 0) && (((screenx-screen_fromx) >> 8)&255 != 0xff)) DEBUG();
if (((screeny-screen_fromy >> 8)&255 != 0) && (((screeny-screen_fromy) >> 8)&255 != 0xff)) DEBUG();
    line_rel_XY((char)(screenx-screen_fromx), (char)(screeny-screen_fromy));

if ((((-screenx) >> 8)&255 != 0) && (((-screenx) >> 8)&255 != 0xff)) DEBUG();
if ((((-screeny) >> 8)&255 != 0) && (((-screeny) >> 8)&255 != 0xff)) DEBUG();
    move_rel_XY((char)-(screenx), (char)-(screeny));
  }
}

static void drawtgintro(char absx, char absy, unsigned char scale, unsigned char bright) {
  unsigned char objects;

  reset_beam();
  set_scale(0x7f);
  move(absx, absy); // after positioning the placard, everything is treated as relative to 0,0
  set_scale(scale);
  intensity(bright);

  if (stopintroflag == 2) return;

  if (fp2_14_cos (intro_rot) > 0) { // are we looking at the front of the tumbling intro placard?
               /* (this should be a compile-time constant)                 */
    objects = ((sizeof (tg_intro_placard) / sizeof (tg_intro_placard[0])) / 4); // yes - draw rectangles and text together
  } else {
    objects = 8; // no - just draw the rectangles
  }

  {unsigned char i; char tgfrom_x, tgfrom_y, tgto_x, tgto_y;
    tgi = 0;
    for (i = 0; i < objects; i++) {
      tgfrom_x = (char)tg_intro_placard[tgi]-128; // make coords center on 0,0
      tgfrom_y = (char)tg_intro_placard[tgi+1]-128;
      tgto_x = (char)tg_intro_placard[tgi+2]-128;
      tgto_y = (char)tg_intro_placard[tgi+3]-128;
      tgi += 4;
      // somewhere the x,y,z coordinates got a bit messed up...
      draw_tg_intro_with_rolling_rotation (tgfrom_y, tgfrom_x, tgto_y, tgto_x, intro_rot);
    }
  }
  // restore intensity to 110 after drawing flashing intro placard
  intensity(110);
}
static void Playintro(void)
{
  intro_z = 206; intro_rot = 64; //90; // not sure about Z.  still to test the approach of the placard.
  flashing_intro_timer = 0; hide_intro_timer = 0;
  stopintroflag = 0;
}
//#endif

// and now the crucial ship stuff...

static void initialise_ship(unsigned char id, unsigned char model_type, short x, short y, short z, short Xrot, short Yrot, short Zrot, short scale_fp)
{
  unsigned char ship_next = id/*0..3*/ * SHIP_RECORD_LENGTH;

  /* Set up all the fields in the ship struct */
  ships[ship_next++] = model_type;  // (was model 1 to 4, now model 0 to 3)    SHIP_TYPE 0U
  ships[ship_next++] = x;     // SHIP_X 1U
  ships[ship_next++] = y;     // SHIP_Y 2U
  ships[ship_next++] = z;     // SHIP_Z 3U
  ships[ship_next++] = Xrot;  // SHIP_X_ROTATION 4U
  ships[ship_next++] = Yrot;  // SHIP_Y_ROTATION 5U
  ships[ship_next++] = Zrot;  // SHIP_Z_ROTATION 6U
  ships[ship_next++] = scale_fp; // (see comment by this_scale_fp declaration)    SHIP_SCALE_FP 7U
  ships[ship_next++] = ((SHIP_MAXVEL * pickrandom (80, 120)) / 100); // integer, range roughly 16:24?    SHIP_SPEED 8U
                                                             // change to a range that encourages /128 instead 
  ships[ship_next++] = 0;     // SHIP_TIME 9U
  ships[ship_next++] = pickrandom (-400, 400); // SHIP_TARGET_X 10U
  ships[ship_next++] = pickrandom (-300, 300); // SHIP_TARGET_Y 11U
  ships[ship_next++] = z;                      // SHIP_TARGET_Z 12U
    // Initial phase is with ships viewed from above, flying in 2D in the plane Z=max depth,
    // hence why target z is initially same as ship.z
    // Later, they turn and head towards the viewer at z=0.
  ships[ship_next++] = SHIP_STATUS_ALIVE;      // SHIP_STATUS 13U
}

static void debug_ship(unsigned char id)
{
  short model_type, x, y, z, Xrot, Yrot, Zrot, scale_fp, speed, shiptime, tgx_, tgy_, tgz_, status;
  short ship_next = id/*0..3*/ * SHIP_RECORD_LENGTH;

  /* Set up all the fields in the ship struct */
  model_type = ships[ship_next++];  // (was model 1 to 4, now model 0 to 3)    SHIP_TYPE 0U
  x = ships[ship_next++];     // SHIP_X 1U
  y = ships[ship_next++];     // SHIP_Y 2U
  z = ships[ship_next++];     // SHIP_Z 3U
  Xrot = ships[ship_next++];  // SHIP_X_ROTATION 4U
  Yrot = ships[ship_next++];  // SHIP_Y_ROTATION 5U
  Zrot = ships[ship_next++];  // SHIP_Z_ROTATION 6U
  scale_fp = ships[ship_next++]; // (see comment by this_scale_fp declaration)    SHIP_SCALE_FP 7U
  speed = ships[ship_next++];    // integer, range roughly 16:24?    SHIP_SPEED 8U
  shiptime = ships[ship_next++]; // SHIP_TIME 9U
  tgx_ = ships[ship_next++];   // SHIP_TARGET_X 10U
  tgy_ = ships[ship_next++];   // SHIP_TARGET_Y 11U
  tgz_ = ships[ship_next++];   // SHIP_TARGET_Z 12U
  status = ships[ship_next++];      // SHIP_STATUS 13U

#ifdef OPENGL_DEBUGGING
  fprintf(stdout, "ship[%d] = {", id);
  fprintf(stdout, "  STATUS %d\n", status);
  fprintf(stdout, "  type %d\n", model_type);
  fprintf(stdout, "  location %d %d %d\n",x,y,z);
  fprintf(stdout, "  rotation %d %d %d\n",Xrot,Yrot,Zrot);
  fprintf(stdout, "  scale %f\n", scale_fp / (float)(1<<14));
  fprintf(stdout, "  speed %d\n", speed);
  fprintf(stdout, "  target location %d %d %d\n", tgx_,tgy_,tgz_);
  fprintf(stdout, "  ships time %d\n", shiptime);
  fprintf(stdout, "}\n");
#endif
}

static void Set_rotation_vars(short Xrot, short Yrot, short Zrot)
{
  sinX = fp2_14_sin (Xrot);
  cosX = fp2_14_cos (Xrot);
  sinY = fp2_14_sin (Yrot);
  cosY = fp2_14_cos (Yrot);
  sinZ = fp2_14_sin (Zrot);
  cosZ = fp2_14_cos (Zrot);
}

static void Transform_point(short x, short y, short z)
{
  // mul_short_by_fp2_14_returning_short
  XY = (mul_short_by_fp2_14_returning_short(y, cosX) - mul_short_by_fp2_14_returning_short(z, sinX));
  XZ = (mul_short_by_fp2_14_returning_short(y, sinX) + mul_short_by_fp2_14_returning_short(z, cosX));
  transformed_Z = (mul_short_by_fp2_14_returning_short(XZ, cosY) - mul_short_by_fp2_14_returning_short(x, sinY));
  YX = (mul_short_by_fp2_14_returning_short(XZ, sinY) + mul_short_by_fp2_14_returning_short(x, cosY));
  transformed_X = (mul_short_by_fp2_14_returning_short(YX, cosZ) - mul_short_by_fp2_14_returning_short(XY, sinZ));
  transformed_Y = (mul_short_by_fp2_14_returning_short(YX, sinZ) + mul_short_by_fp2_14_returning_short(XY, cosZ));
}
static short screen_x; // 3d coordinates are convered to screen coordinates when displaying vectors
static short screen_y;

// inline this for speed rather than size?  (Since 6809 parameter passing
// has quite a high overhead...)
static void Get_screenpos_POSSIBLY_BUGGY(short x, short y, short z)
{
  screen_x = (x << FOCAL_LENGTH_SHIFT) / z; screen_y = (y << FOCAL_LENGTH_SHIFT) / z;

  // Wait - is this a bug?  Shouldn't it be:
  //   screen_x = (FOCAL_LENGTH * x) / (FOCAL_LENGTH + z);
  //   screen_y = (FOCAL_LENGTH * y) / (FOCAL_LENGTH + z);
  // If there is a problem with hit detection when the game is working,
  // this should be the first place to check...
}

// This is never called with a ship id.  The id has been determined by the caller and all the 'this_*' variables
// set up in place already.  I.e. this code does not need to check the ship status to see if it is dead or alive
// - we know it is alive.

static void Draw_ship_at_rotated_scale(unsigned char model, short x, short y, short z, short Xrot, short Yrot, short Zrot, short scale_fp)
{
  unsigned char meshIndex; // shipmeshpointers are in range 0..82 so using a byte for the index is OK

  reset_beam();
  set_scale(0x7F); // to be determined by inspection
  intensity(85);

  // ship mesh array (of shorts) contains from_x,y,z and to_x,y,z

  Set_rotation_vars (Xrot, Yrot, Zrot);
  for (meshIndex = ShipMeshPointers[model]; meshIndex < ShipMeshPointers[model+1]; meshIndex++) {
                                                                           // +1 is the start of the *next* set of data (or past the array end)
                                                                           // We could have used ship_mesh_length instead
    // Transform_point updates XY, XZ, YX and this_X,Y,Z
                                                                                     // (already tweaked offsets from +1:+6 to +0:+5
                                                                                     //  to sort Scratch array base issue)
    Transform_point (mul_short_by_fp2_14_returning_short(ShipMesh[((meshIndex * 6) + 0)], scale_fp),
                     mul_short_by_fp2_14_returning_short(ShipMesh[((meshIndex * 6) + 1)], scale_fp),
                     mul_short_by_fp2_14_returning_short(ShipMesh[((meshIndex * 6) + 2)], scale_fp)); // -> transformed_X,Y,Z
    Get_screenpos_POSSIBLY_BUGGY (x + transformed_X, y + transformed_Y, z + transformed_Z); // -> screen_x,y  (was previously -> this_X,Y)
    // original: gotoxy (screen_x, screen_y);
    // currently being hacked:
    gotoxy (screen_x+ScreenWidth / 2, screen_y+ScreenHeight / 2);
    putPenDown ();

    Transform_point (mul_short_by_fp2_14_returning_short(ShipMesh[((meshIndex * 6) + 3)], scale_fp),
                     mul_short_by_fp2_14_returning_short(ShipMesh[((meshIndex * 6) + 4)], scale_fp),
                     mul_short_by_fp2_14_returning_short(ShipMesh[((meshIndex * 6) + 5)], scale_fp)); // -> transformed_X,Y,Z
    Get_screenpos_POSSIBLY_BUGGY (x + transformed_X, y + transformed_Y, z + transformed_Z); // -> screen_x,y  (was previously -> this_X,Y)
    // original: gotoxy (screen_x, screen_y); // TO DO: clip the ends of these vectors! ONLY WHEN SHIPS ARE VERY CLOSE
                                              // http://malban.de/oldies/vectrex/vectrex-clipping
    // currently being hacked:
    gotoxy (screen_x+ScreenWidth / 2, screen_y+ScreenHeight / 2);
     // ultimately get rid of pen up/down/gotoxy and call vectrex code directly with from/to vectors?
    putPenUp ();
  }

  intensity(110);
}

// And finally the text displays

static void segment(unsigned char coords)
{
  short fromx;
  short fromy;
  short tox;
  short toy;
  short from, to;

  // remember to use shifts instead of divides (unless GCC does it for us?)

  // clever packing may be undone by overhead of div&modulo 5 ... reconsider using less dense tables?
  to = coords & 15;
  tox = ((to % 5) *  4);
  toy = ((to / 5) * 12);

  from = coords >> 4;
  fromx = ((from % 5) *  4);
  fromy = ((from / 5) * 12);

  move((char)fromy, (char)fromx);
  line((char)(toy-fromy), (char)(tox-fromx));
  move((char)-toy, (char)-tox); // back to origin
}

static void ch(unsigned char charno, short scale)
{
  if (charno /* > 0U */) { // 0 is blank space
    {unsigned char i, limit;
      // If the array indexes, and calculations, in the for loop test comparison are recalculated on every loop, perhaps
      // we should pre-calculate them before entering the loop, and use the cached values?
      i = font_index[charno-1];
      limit = font_index[charno-1]+font_length[charno-1];
      while (i < limit) {
        segment(font_points[i++]);
      }
    }
  }
  // add move back here once debugged
  move(0U, (char)(scale>>3));
}

// DO NOT INLINE OR MACROFY THESE TWO!
static void ch96(unsigned char charno)
{
  ch(charno, 96);
}

static void ch96str(unsigned char *text)
{unsigned char i;
  for (i = 1; i <= text[0]; i++) ch96(text[i]);
}

static void ch128(unsigned char charno)
{
  ch(charno, 128);
}

static void ch128str(unsigned char *text)
{unsigned char i;
  for (i = 1; i <= text[0]; i++) ch128(text[i]);
}

static void Drawnum(short num, unsigned char size) // Up to 32767, no negative numbers.  (Not needed)
{
  unsigned char i;
  short tens = 10000;

  i = 4U;
  for (;;) { // handle leading-zero suppression
    if ((num >= tens) || (i == 0)) {
      // ch(1+...) because Digits are encoded as 1..10, not 0..9
      ch((unsigned char)(1+(num / tens) % 10), size); // i==0 for single digit 0 case
    }
    tens = tens / 10;
    if (i == 0) return;
    i -= 1;
  }
}


// DO NOT inline or turn these into macros.  Keep the code size small.
static void Drawsmallnum(short num)
{
  Drawnum(num, 96);
}

static void Drawbignum(short num)
{
  Drawnum(num, 128);
}

static void Drawbignumat(short num, short x, short y)
{
  textx = x; texty = y;
  Drawbignum(num);
}

static void Drawsmallnumat(short num, short x, short y)
{
  textx = x; texty = y;
  Drawsmallnum(num);
}

static void Draw_shields_left(void)
{
  Drawbignumat (Shields, -10, 140);
}

static void Draw_score(void)
{
  Drawbignumat (Score, -220, 130);
}

static void Draw_highscore(void)
{
  textx = 42;
  texty = 130;
  // HIGH_SCORE
  ch96str(HIGH_SCORE_STR96);
                                                  // remember, char width is (24 * textscale) / 128
  Drawsmallnumat (HighScore, (/* WAS: 208*/204 - ((24 * 96 * (numdigits (HighScore)-1)) >> 7 /* / 128 */)), 105);
}

static void InsertCoin(void) // Not yet used
{
  textx = -135;
  texty = -120;
  intensity(global_flashing_intensity);
  ch128str(INSERT_COIN_STR128);
  intensity(110);
}

static void Draw_ShipsPassedTotal(void) {
  Drawbignumat (ShipsPassedTotal, 200, 130);
}

#ifdef NEVER
  InsertCoin ();
#endif

static void Press_start(void)
{
  textx = -120;
  texty = -10;
  intensity(global_flashing_intensity);
  ch128str(PRESS_START_STR128);
  intensity(110);
}

static void Draw_credits(void)
{
  textx = -100;
  texty = -120;
  ch128str(CREDITS_STR128);
  if (Credits > 9U) {
    ch128 (10);  // display any more than 9 as 9. (This is what many arcade machines did in real life)
  } else {
    ch128 (Credits + 1);
  }
}

static void Draw_last_game_score(void)
{
  textx = -220;
  texty = 130;
  ch96str(SCORE_STR96);
  Drawsmallnumat (LastScore, -220, 105);
}

void position_and_scale(char absx, char absy, short scale, char bright)
{
  reset_beam();
  set_scale(0x7f);
  move(absy, absx);
  set_scale((char)(scale>>3));
  intensity(bright);
  textx = texty = 0; // relative now...
}

static void alphabet_test(void)
{
  unsigned char ptr = 1;

  position_and_scale(-120, 100, 256, MAX_BRIGHTNESS);
  {unsigned char i;
    for (i = 0U; i < 10U; i++) ch (ptr++, 256);
  }
#ifdef NEVER
  position_and_scale(40, 100, 128, MAX_BRIGHTNESS);
  {unsigned char i;
    for (i = 0U; i < 8U; i++) ch (ptr++, 128);
  }

  position_and_scale(20, -50, 96, MAX_BRIGHTNESS);
  {unsigned char i;
    for (i = 0U; i < 18U; i++) ch (ptr++, 96);
  }
#endif
}

#include "strings.c"

int main(void) {
  unsigned char frames;
  unsigned char boxscale = 0x20;
  init_stars();
  reset_beam();
  set_scale(0x7f);
  intensity(MAX_BRIGHTNESS);
  move(0,0);
  for (;;) {
    //ship:
#ifdef NEVER
    for (frames = 0; frames < 200U; frames++) {
      wait_retrace();
      stars(1);
      Draw_ship_at_rotated_scale(0, 0, 0, 1024, 0, 0, 0, ONE_POINT_ZERO);
    }
#endif
    // approach banner:
    Playintro(); // horizontal
    intro_rot += 64; // facing backwards (no text)
    intro_rot += 128; // frontal with text correct
    intro_rot -= 32; // tilted back at 45 degrees - frame is rotated but text is not??? and the words appear to be "GUNNER TAIL"
    for (boxscale = 0x20; boxscale < 0xE0U; boxscale++) {
      wait_retrace();
      //intensity(0x1F+(boxscale>>2));
      //print_tg(0, 0, boxscale, 0x1F+(boxscale>>2), intro_rot);
      drawtgintro(0,0, 0xff/*scale*/, 0x7F/*intensity*/);
      //stars(1);
      //intro_rot = (intro_rot+1)&255;
      if (show_debug) SHOW_INSERT_COIN(-100,100,32);
    }
#ifdef NEVER
    // fade banner:
    for (; boxscale != 0U; boxscale -= 4U) {
      wait_retrace();
      print_tg(0, 0, 0xE0, 0x1F+(boxscale>>2), intro_rot);
      stars(1);
    }
    //stars:
    for (frames = 0; frames < 200U; frames++) {
      wait_retrace();
      stars(1);
      SHOW_SCORE(0,100-(frames>>2),32);
    }
    for (frames = 0; frames < 200U; frames++) {
      wait_retrace();
      stars(1);
      SHOW_HIGH_SCORE(0,50-(frames>>2),32);
    }
    for (frames = 0; frames < 200U; frames++) {
      wait_retrace();
      stars(1);
      SHOW_CREDITS(0,0-(frames>>2),32);
    }
    for (frames = 0; frames < 200U; frames++) {
      wait_retrace();
      stars(1);
      SHOW_INSERT_COIN(0,-50-(frames>>2),32);
    }
    for (frames = 0; frames < 100U; frames++) {
      wait_retrace();
      stars(1);
      SHOW_PRESS_START(0,-100-(frames>>2),32);
    }
#ifdef NEVER
    for (frames = 0; frames < 200U; frames++) {
      wait_retrace();
      stars(1);
      //alphabet_test();
    }
#endif
    //stars:
    for (frames = 0; frames < 100U; frames++) {
      wait_retrace();
      stars(1);
    }
    // crosshair:
    for (frames = 0; frames < 200U; frames++) {
      wait_retrace();
      draw_crosshair(frames>>1, -(frames>>2), 0x38);
      stars(1);
    }
    // shields:
    for (frames = 0; frames < 200U; frames++) {
      wait_retrace();
      intensity((char)pickrandom(64,127));
      if (pickrandom(1,20) > 1) draw_shields(0, 0, 0xF0);
      stars(1);
    }
#endif
  }
  return 0;
}
