/* STAR TERROR! */

// Given that Faw Labo has teased us over the last several years with many
// unreleased demos/games, all basically the same same code of a graphical
// intro and a star field,

// https://www.youtube.com/watch?v=qi0LYXnZ_IU
// https://www.youtube.com/watch?v=ZkawFHJf9Fk
// https://www.youtube.com/watch?v=pABpAVcFeiI
// https://www.youtube.com/watch?v=Felzgc1ynJc
// https://www.youtube.com/watch?v=cJIzZnYxnl4
// https://www.youtube.com/watch?v=L0CIdx2Ri3w
// https://www.youtube.com/watch?v=qK7plhQTxDc

// I thought I might encourage some of our coders to take on those projects,
// and finish/release something playable, ... so here is a skeleton for a
// similar game, with a firing sight and star movement (attacking or retreating, your choice).
// There are plenty of free cycles left for the gameplay.

// (I ripped this out of my tailgunner code as you probably guessed)

// Just add some bad guys to shoot at and maybe a big banner intro...

// Note that a 'star trek' banner could be extracted from:
//   https://commons.wikimedia.org/wiki/File:Star_Trek_TOS_Logo.svg#/media/File:Star_Trek_TOS_Logo.svg
// convert the SVG using https://atariage.com/forums/topic/237191-sinz-v07-vectrex-demo-playing-around-with-sinus/ ...

// and you can probably find the star trek or star wars themes online and massage them to work with
// the vectrex sound chip, eg http://www.lotro-abc.com/abc/startrek.abc

// The code below took me about 2 hours to put together; I'ld go on to make an actual game
// out of it if I had more time, but I swore to my colleagues in the PiTrex project that
// I would spend this weekend working on the MAME port, which is more urgent than this hack...

// Graham.  31 Aug 2019.

#include <vectrex.h>

static long rseed = 12345L;
static inline long urandom16 (void) {
  return (rseed = (rseed * 2053) + 13849);
}

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

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

static int stardots[8];         // fill with randoms ONCE - could move this into ROM...

static int rotated3[8];         // rotate stardots into place
static int rotated2[8];
static int rotated1[8];
static int rotated0[8];

static int *rotated[4] = {
  rotated0,
  rotated1,
  rotated2,
  rotated3
};

static unsigned int starfield_scale;
#define jsrx(v, func)       asm("ldx %0\n\t" \
                                "jsr " #func "\n\t" : : "g" (v) : "d", "x")
#define Draw_VLp(ra) jsrx(ra, 0xF410)   // (pattern y x)* 0x01

static inline void set_scale (unsigned int scale) {
  *(volatile unsigned char *) 0xD004 = scale;   /*VIA_t1_cnt_lo */
}

static inline void line (int y, int x) {
  *(volatile unsigned char *) 0xC823 = 0;
  Draw_Line_d ((int) y, (int) x);
}

static inline void dots (unsigned int size, const int *array) {
  *(volatile char *) 0xC823 = size - 1;
  Dot_List ((void *) array);
}

static inline void rotate_vl (unsigned int angle, unsigned int points, const void *original, void *rotated) {
  *(volatile unsigned int *) 0xC836 = angle;
  *(volatile unsigned int *) 0xC823 = points - 1;
  Rot_VL ((int *) original, rotated);
}

#define joybit Joy_Digital
#define tstat (*((volatile unsigned int *) 0xc856))

#define pot0 (*((volatile int *) 0xc81b))
#define pot1 (*((volatile int *) 0xc81c))
#define pot2 (*((volatile int *) 0xc81d))
#define pot3 (*((volatile int *) 0xc81e))

#define epot0 (*((volatile unsigned int *) 0xc81f))
#define epot1 (*((volatile unsigned int *) 0xc820))
#define epot2 (*((volatile unsigned int *) 0xc821))
#define epot3 (*((volatile unsigned int *) 0xc822))

#define t1lolc (*((volatile unsigned int *) 0xd004))

#define read_ram(x) (*((volatile int *) x))

#define joystick1_button1 (int)(read_ram(0xC80F) & 1U)  /* A */
#define joystick1_button2 (int)(read_ram(0xC80F) & 2U)  /* S */
#define joystick1_button3 (int)(read_ram(0xC80F) & 4U)  /* D */
#define joystick1_button4 (int)(read_ram(0xC80F) & 8U)  /* F */

static int XJoy, YJoy, button1, button2, button3, button4;

static void poll_joystick (void) {
  joybit ();
  if (pot0 < -10) {       XJoy = -1;      // Left
  } else if (pot0 > 10) { XJoy = 1;      // Right
  } else {                XJoy = 0;
  }
  if (pot1 < -10) {       YJoy = -1;      // Down
  } else if (pot1 > 10) { YJoy = 1;      // Up
  } else {                YJoy = 0;
  }
  asm ("pshs x,y,u,dp");
  asm ("lda  #0");
  asm ("jsr  0HF1B4 ; read_btns_mask":::"a", "b", "d");
  asm ("puls dp,u,y,x");
  button1 = joystick1_button1;
  button2 = joystick1_button2;
  button3 = joystick1_button3;
  button4 = joystick1_button4;
}

static void init_hardware (void) {
  // setup joystick read function to read only joystick 1
  epot0 = 1;
  epot1 = 3;
  epot2 = 0;
  epot3 = 0;
}

static inline void init_stars (void) {
  starfield_scale = (unsigned int) 0xFF;

  // origin at 0,0
  stardots[0] = -60;
  stardots[1] = (int) pickrandom (-45, 45);     // LEFT
  stardots[2] = 60;
  stardots[3] = (int) pickrandom (-45, 45);     // RIGHT
  stardots[4] = (int) pickrandom (-45, 45);
  stardots[5] = 60;             // TOP
  stardots[6] = (int) pickrandom (-45, 45);
  stardots[7] = -60;            // BOT

  stardots[6] -= stardots[4];
  stardots[7] -= stardots[5];   // abs to rel
  stardots[4] -= stardots[2];
  stardots[5] -= stardots[3];
  stardots[2] -= stardots[0];
  stardots[3] -= stardots[1];

  rotate_vl ((unsigned int) (pickrandom (0, 15)), 4U, stardots, rotated0);
  rotate_vl ((unsigned int) (pickrandom (16, 31)), 4U, stardots, rotated1);
  rotate_vl ((unsigned int) (pickrandom (32, 47)), 4U, stardots, rotated2);
  rotate_vl ((unsigned int) (pickrandom (48, 63)), 4U, stardots, rotated3);
}

static void stars (int speed) {                 // speed must be a power of 2
  unsigned int shifted_scale;
  int i;

  shifted_scale = starfield_scale - (unsigned int) 0xBF;
  for (i = 0; i < 4; i++) {
      if ((unsigned int) shifted_scale > (unsigned int) 0x20) {                 // disappear here
          Reset0Ref ();
          Intensity_a ((shifted_scale >> 1) - 0x10); // fade with distance
          set_scale (shifted_scale);
          dots (4, rotated[i]);
      } else {
          if (((unsigned int) shifted_scale & (unsigned int) 0x3F) == 0U) {
              // pull in new ring of stars
              rotate_vl ((unsigned int) (pickrandom (0, 63)), 4U, stardots,
                         rotated[i]);
          }
      }
      shifted_scale += 0x40;
  }
  starfield_scale -= (unsigned int) speed;      // must be power of 2
}

static const int crosshair[] = {
  0, 17, 40,                    // trying to move it to the center
  -1, -30, 0,
  0, 15, 20,
  -1, 0, -115,
  0, -15, 20,
  -1, 30, 0,
  0, -10, 20,
  -1, -10, 0,
  0, -10, 10,
  -1, 0, 10,
  0, -15, 10,
  -1, 0, -30,
  0, -20, 15,
  -1, 105, 0,
  0, -20, 15,
  -1, 0, -30,
  0, -20, 10,
  -1, 0, 10,
  0, -10, 10,
  -1, -10, 0,
  1
};

static int crosshair_x, crosshair_y;

static void follow_crosshair (void) {
  XJoy += XJoy;  XJoy += XJoy;  YJoy += YJoy;  YJoy += YJoy;
  // constrain joysticks but carefully avoid integer overflow
  if ((crosshair_x >= 0) && (XJoy > 0)
      && (crosshair_x >= 127 - XJoy)) /* ignore */ ;
  else if ((crosshair_x < 0) && (XJoy < 0)
           && (crosshair_x < -127 - XJoy)) /* ignore */ ;
  else
    crosshair_x += XJoy;

  if ((crosshair_y >= 0) && (YJoy > 0)
      && (crosshair_y >= 127 - YJoy)) /* ignore */ ;
  else if ((crosshair_y < 0) && (YJoy < 0)
           && (crosshair_y < -127 - YJoy)) /* ignore */ ;
  else
    crosshair_y += YJoy;

  Reset0Ref ();
  set_scale (0x7f);
  Moveto_d (crosshair_y, crosshair_x);
  set_scale (0x18);

  Draw_VLp (crosshair);
}

int main (void) {
  long i;
  int starspeed = -4;
  init_hardware ();
  init_stars ();
  while (1) {
      for (i = 0L; i < 400L; i++) {
          Wait_Recal ();
          Reset0Ref ();
          Intensity_a (0x7F);
          Print_Str_d (0, -50,
                       (starspeed < 0 ? "FORWARD\x80" : "REVERSE\x80"));
          poll_joystick ();
          follow_crosshair ();
          if (button4) { // firing lasers
// You could patterns these lines and flicker them off and on for a better effect, like tracer shot...
              Reset0Ref ();
              set_scale (0x7fU);
              Moveto_d (-128, -128);
              set_scale (0xff);
              line ((int) (((long) crosshair_y + 128L) / 2L),
                    (int) (((long) crosshair_x + 128L) / 2L));
              // draw line to bottom left corner

              Reset0Ref ();
              set_scale (0x7fU);
              Moveto_d (-128, 127);
              set_scale (0xff);
              line ((int) (((long) crosshair_y + 128L) / 2L),
                    (int) (((long) crosshair_x - 128L) / 2L));
              // draw line to bottom right corner

          }
          stars (starspeed);
      }
      starspeed = -starspeed; // just for demo.
  }
  return 0;
}