#include <vectrex.h>

// position-independent code to draw a circle at x,y extracted from circle.bin using:
#ifdef NEVER
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv) {
  FILE *circ = fopen("pang8_0.bin", "rb");
  int c1, c2, c3, c4;
  c1 = fgetc(circ);  c2 = fgetc(circ);  c3 = fgetc(circ);  c4 = fgetc(circ);
  for (;;) {
    if (c1 == EOF) exit(1);
    if (c1 == c2 && c3 == c4 && c2 == c3 && c1 == 'x') break; // remainder of file is relocatable code!
    c1 = c2; c2 = c3; c3 = c4; c4 = fgetc(circ);
  }
  fprintf(stdout, "const unsigned char drawcirc[] = {\n ");
  c1 = 15; c3 = 0;
  for (;;) {
    c2 = fgetc(circ);
    if (c2 == EOF) break;
    fprintf(stdout, " %3d,", c2);
    c3++; if ((c3&c1) == 0) fprintf(stdout, "\n ");
  }
  fprintf(stdout, "\n};\n");
  exit(0);
}
#endif
// the position-independent code in circle.asm follows the string "xxxx" which is used
// as a delimeter...
const unsigned char drawcirc[] = {
  134, 204, 151,  12, 134, 129, 151,   0, 134,  79, 151,   1, 134, 132, 151,   0,
   12,   0, 134, 152, 151,  11, 134, 127, 183, 208,   4, 134, 129, 151,   0, 134,
  204, 151,  12, 134,  95, 151,   1, 134, 132, 151,   0,  12,   0, 134, 152, 151,
   11,  55,   6, 151,   1,  15,   0,  52,   6, 134, 206, 151,  12,  15,  10,  12,
    0, 215,   1,  15,   5,  53,   6,  77,  42,   4,  64,  40,   1,  74,  93,  42,
    4,  80,  40,   1,  90, 231, 127, 170, 127, 198,  64, 129,  64,  35,  19, 129,
  100,  35,   4, 134,   8,  32,   2, 134,   4, 213,  13,  39, 252,  74,  38, 253,
   32,   4, 213,  13,  39, 252,  15,  11, 134, 128, 151,   0, 134, 246, 151,   1,
  134, 129, 151,   0, 134, 255, 151,   1, 134, 238, 151,  12, 134,   1, 151,   0,
  134, 253, 151,   1, 134, 250, 151,   1, 134, 246, 151,   1, 134, 226, 151,   1,
  134, 129, 151,   0, 134, 128, 151,   0, 134,  10, 151,   1, 134, 129, 151,   0,
  134, 226, 151,   1, 134,   1, 151,   0, 134, 246, 151,   1, 134, 250, 151,   1,
  134, 253, 151,   1, 134, 255, 151,   1, 134, 129, 151,   0, 134,   1, 151,   1,
  134,   1, 151,   0, 134,   3, 151,   1, 134,   3, 151,   1, 134,   6, 151,   1,
  134,  10, 151,   1, 134,  30, 151,   1, 134, 129, 151,   0, 134, 128, 151,   0,
  134, 246, 151,   1, 134, 129, 151,   0, 134,  30, 151,   1, 134,   1, 151,   0,
  134,  10, 151,   1, 134,   6, 151,   1, 134,   3, 151,   1, 134,   1, 151,   1,
  134, 129, 151,   0, 134, 204, 151,  12, 134, 111, 151,   1, 134, 132, 151,   0,
   12,   0, 134, 152, 151,  11, 134,  65, 198, 131, 151,   1,  15,   0,  52,   6,
  134, 206, 151,  12,  15,  10,  12,   0, 215,   1,  15,   5,  53,   6,  77,  42,
    4,  64,  40,   1,  74,  93,  42,   4,  80,  40,   1,  90, 231, 127, 170, 127,
  198,  64, 129,  64,  35,  19, 129, 100,  35,   4, 134,   8,  32,   2, 134,   4,
  213,  13,  39, 252,  74,  38, 253,  32,   4, 213,  13,  39, 252,  57,
};

#define gosub(x,y,func) \
  asm(" lda %0\n"                       \
      " ldb %1\n"                       \
      " leau -16,s\n"                    \
      " pshu d\n"                       \
      " jsr _drawcirc\n" :                      /* outputs */ \
                         : "g" (x),  "g" (y)    /* inputs */  \
                         : "d", "x", "y", "u"); /* clobbered */

void draw_circle(int x, int y) {
  gosub(x,y,drawcirc);
}
// sound does not work yet.  As well as feedback sound for placing or failing
// to place your mark, also need a little tune when either player wins.

#define pot0 (*((volatile int *) 0xc81b))
#define pot1 (*((volatile int *) 0xc81c))
int XJoy = 0, YJoy = 0; // convert analog value to digital -1 0 +1

unsigned int _x, _a, _b, _c; 
void initRandom(unsigned int s1,unsigned int s2,unsigned int s3, unsigned int x0) {
  _x = x0; _a = s1; _b = s2; _c = s3; _x++; _a = (_a^_c^_x); _b = (_b+_a); _c = ((_c+(_b>>1))^_a);
}

int 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 (int)(_c&127);
}

int debug = 0;

// Note that machine originally appeared to play 'O' even if it played first.
// Was able to fix this in the board display by noting who played first, and
// swapping X for O accordingly on output.

#ifndef FALSE
#define FALSE (0 != 0)
#define TRUE (0 == 0)
#endif

// In the original BASIC code, a square was marked as 1 for a human play
// and 5 for a computer play, and 1/8 for a potential computer play.  That way,
// adding up the fractional parts over 4 squares could never reach 1 and so
// the fractional part could be ignored.  Since we are using integers, the
// easiest way to handle this awkward coding was to first separate the relevant
// variables into separate integer and fractional parts, and finally convert
// the fractional parts into a more simple boolean array of flags.

int X[64+1];   // X is the 3d array, flattened. empty=0
#define HUMAN_PLAY 1
#define COMPUTER_PLAY 5

int Marked[64+1];
#define MARKED 1
#define UNMARKED 0

#define Y(I) y[(I)-1]
const int y[16] = { 1,49,52,4,13,61,64,16,22,39,23,38,26,42,27,43, };

#define ALL_POSSIBLE_LINES 76
#define M(I,J) m[(I)-1][(J)-1]
int Winning_Line = 0;
const int m[ALL_POSSIBLE_LINES][4] = { // winning lines
      { 1,2,3,4},      { 5,6,7,8},   { 9,10,11,12},  { 13,14,15,16},  // <-- fourth column may have special significance in FIND_MOVE
  { 17,18,19,20},  { 21,22,23,24},  { 25,26,27,28},  { 29,30,31,32},
  { 33,34,35,36},  { 37,38,39,40},  { 41,42,43,44},  { 45,46,47,48},
  { 49,50,51,52},  { 53,54,55,56},  { 57,58,59,60},  { 61,62,63,64},
   { 1,17,33,49},   { 5,21,37,53},   { 9,25,41,57},  { 13,29,45,61},
   { 2,18,34,50},   { 6,22,38,54},  { 10,26,42,58},  { 14,30,46,62},
   { 3,19,35,51},   { 7,23,39,55},  { 11,27,43,59},  { 15,31,47,63},
   { 4,20,36,52},   { 8,24,40,56},  { 12,28,44,60},  { 16,32,48,64},
     { 1,5,9,13},  { 17,21,25,29},  { 33,37,41,45},  { 49,53,57,61},
    { 2,6,10,14},  { 18,22,26,30},  { 34,38,42,46},  { 50,54,58,62},
    { 3,7,11,15},  { 19,23,27,31},  { 35,39,43,47},  { 51,55,59,63},
    { 4,8,12,16},  { 20,24,28,32},  { 36,40,44,48},  { 52,56,60,64},
    { 1,6,11,16},  { 17,22,27,32},  { 33,38,43,48},  { 49,54,59,64},
    { 13,10,7,4},  { 29,26,23,20},  { 45,42,39,36},  { 61,58,55,52},
   { 1,21,41,61},   { 2,22,42,62},   { 3,23,43,63},   { 4,24,44,64},
  { 49,37,25,13},  { 50,38,26,14},  { 51,39,27,15},  { 52,40,28,16},
   { 1,18,35,52},   { 5,22,39,56},   { 9,26,43,60},  { 13,30,47,64},
   { 49,34,19,4},   { 53,38,23,8},  { 57,42,27,12},  { 61,46,31,16},
   { 1,22,43,64},  { 16,27,38,49},   { 4,23,42,61},  { 13,26,39,52},
};

#define animation_delay 6
#define status_delay 255U
#define lockout_delay 255U

unsigned int status_ticker = 0U;
static char status[32];
void STATUS(char *s) {
  int i;
  status_ticker = status_delay;
  for (i = 0; i < 30; i++) {
    status[i] = s[i];
    if (s[i] == '\0') {
      status[i++] = 0x80; status[i] = 0; return;
    }
  };
  status[30] = '\x80'; status[31] = '\0';
}

int xyz(int x, int y, int z) {
  x -= 1; y -= 1; z -= 1;
  return 1 + (z*16 + y*4 + x);
}

void swap(int x1, int y1, int z1, int x2, int y2, int z2) {
  int p1, p2, temp;
  p1 = xyz(x1, y1, z1); p2 = xyz(x2, y2, z2);
  temp = X[p1]; X[p1] = X[p2]; X[p2] = temp;
}


#define Lineto_d(y,x) Draw_Line_d(y,x)
#define MoveRel(x,y) Moveto_d(y, x)
#define LineRel(x,y) Draw_Line_d(y, x)
#define set_scale(s) do { VIA_t1_cnt_lo = s; } while (0)

// perhaps this should be rewritten.  Currently drawing an X or an O involves 3 different
// scaling values.  First we position the level, then the square within the level, and
// finally the letter itself.

void Join(int c1, int r1, int l1,
          int c2, int r2, int l2) {
//            x       y       z
//           col     row    layer

//   Using global coordinates, locate the center of any square
//                                                (found by inspection)
//   Y(row, layer) = -72+(layer-1)*38+(row-1)*8
//   X(row, column, layer) = -39+(row-1)*8+(column-1)*8
//
  int x1, y1, x2, y2;

  if (((l1 == 4) && (l2 == 1))
   || ((l1 == 1) && (l2 == 4))) { // only endpoints on extreme levels (1 and 4) need special handling
    // Long vector draws have to be split because > 127 units

    // not sure if a coding error or a compiler bug, but the /3 version fails
    // on some particular inputs :-(
    int Dc = 0; //= (c1-c2)/3;
    int Dr = 0; //= (r1-r2)/3;  // Dr or Dc will be 0 if in the same plane.
    int Dl = 0; //= (l1-l2)/3;

    if (c2>c1) Dc = -1; else if (c2<c1) Dc = 1;
    if (r2>r1) Dr = -1; else if (r2<r1) Dr = 1;
    if (l2>l1) Dl = -1; else if (l2<l1) Dl = 1;

    // A winning line from the computer is galling.
    // Gallia est omnis divisa in partes tres
    Join(c1,   r1,   l1,       c1-Dc,r1-Dr,l1-Dl);
    Join(c1-Dc,r1-Dr,l1-Dl,    c2+Dc,r2+Dr,l2+Dl);
    Join(c2+Dc,r2+Dr,l2+Dl,    c2,   r2,   l2);
    return;
  }

//   int SY, SX;
//  z -= 1; x -= 1; y -= 1;
//  SX = y*16+x*16-127+51;   // 3,3,3 ->  3*16 + 3*16 - 127 +51 = 48+48 -127 +51
//  SY = y*8+z*64-127;   //           3*16 + 3*75 - 127 -16 = 48 + 225 - 127 -16 = 273-127-16 = 130




//  x1 = -39+(r1-1)*8+(c1-1)*8; y1 = -72+(l1-1)*38+(r1-1)*8;
//  x2 = -39+(r2-1)*8+(c2-1)*8; y2 = -72+(l2-1)*38+(r2-1)*8;

r1 -= 1; c1 -= 1; l1 -= 1;
r2 -= 1; c2 -= 1; l2 -= 1;
  x1 = r1*16+c1*16-127+51;   // 3,3,3 ->  3*16 + 3*16 - 127 +51 = 48+48 -127 +51
  y1 = r1*8+l1*64-127;   //           3*16 + 3*75 - 127 -16 = 48 + 225 - 127 -16 = 273-127-16 = 130

  x2 = r2*16+c2*16-127+51;   // 3,3,3 ->  3*16 + 3*16 - 127 +51 = 48+48 -127 +51
  y2 = r2*8+l2*64-127;   //           3*16 + 3*75 - 127 -16 = 48 + 225 - 127 -16 = 273-127-16 = 130

  Intensity_7F(); Reset0Ref(); set_scale(0x80);
  Moveto_d(y1,x1); Lineto_d(y2-y1,x2-x1);
}
// I manually worked out a formula for going directly to the square independent of level, which
// I used in the "Join" procedure to draw a (graphical) line through a winning line.  Using
// that same formula here would eliminate one of the vector moves.  The only question is whether
// the letters would be placed as perfectly within the boxes as they currently are, or whether
// they would be a smidgeon off center depending on level (due to coarseness of the 256x256 grid)

static inline void draw_O(int x, int y, int z) {
#ifdef DIAMOND_APPROXIMATION
  z -= 3; x -= 1; y -= 1; y *= 30;
  Reset0Ref(); set_scale(0xf0); Moveto_d(z*40+3, -44);
  set_scale(0x41);
  MoveRel((x<<5)-x, y);MoveRel(y-3, 0);
  set_scale(0x10); LineRel(96, 40); MoveRel(-40,-40); LineRel(-16, 40);
#else
  int SY, SX;
  Reset0Ref();
  z -= 1; x -= 1; y -= 1;
  SX = y*16+x*16-127+51;   // 3,3,3 ->  3*16 + 3*16 - 127 +51 = 48+48 -127 +51
  SY = y*8+z*64-(z>>1)-127;   //           3*16 + 3*75 - 127 -16 = 48 + 225 - 127 -16 = 273-127-16 = 130
  draw_circle(SY,SX);
#endif
}

static inline void draw_X(int x, int y, int z) {
#ifdef DIAMOND_APPROXIMATION
  z -= 3; x -= 1; y -= 1; y *= 30;
  Reset0Ref(); set_scale(0xf0); Moveto_d(z*40+3, -44);
  set_scale(0x41); MoveRel((x<<5)-x, y);MoveRel(y, 0);
  set_scale(0x0c); LineRel(16, 32); LineRel(80, 32); LineRel(-16, -32); LineRel(-80, -32);
#else
  int SY, SX;
//  0 1 2 3
//  0 0 1 2
//  0 0 2 4
  z -= 1; x -= 1; y -= 1;
  SX = y*16+x*16-127+51;   // 3,3,3 ->  3*16 + 3*16 - 127 +51 = 48+48 -127 +51
  SY = y*8+z*64-(z>>1)-127;   //           3*16 + 3*75 - 127 -16 = 48 + 225 - 127 -16 = 273-127-16 = 130
  Reset0Ref(); set_scale(0x80); Moveto_d(SY,SX);
  set_scale(0x10); MoveRel(-70,-16); LineRel(127, 40); MoveRel(-54,-40); LineRel(-4, 40);
#endif
}

void DrawGrid(int z) {
  z -= 3;
  Reset0Ref(); set_scale(0xd2); Moveto_d(z*39-2, -57);
  set_scale(0x40);
                   Lineto_d(62, 124);
  Moveto_d(0, 31); Lineto_d(-62, -124);
  Moveto_d(0, 31); Lineto_d(62, 124);
  Moveto_d(0, 31); Lineto_d(-62, -124);
  Moveto_d(0, 31); Lineto_d(62, 124);

  Reset0Ref(); set_scale(0xd2); Moveto_d(z*39-2, -57);
  set_scale(0x40);
                   Lineto_d(0, 124);
  Moveto_d(16,31); Lineto_d(0, -124);
  Moveto_d(16,31); Lineto_d(0, 124);
  Moveto_d(16,31); Lineto_d(0, -124);
  Moveto_d(16,31); Lineto_d(0, 124);
}


#define MACHINE 0
#define HUMAN 1
static int first_player = HUMAN;
// If you would rather ask on first time, then alternate, set this to  MACHINE-HUMAN

static int Move, RandBase, R, I, J, SumOfPlays, SumOfMarks, K;

static void PRINT_MACHINE_MOVE(char *message, int M) {
  int i, K1, K2, K3, J2;
  K1=((M-1)>>4) + 1;            // Z
  J2=M-((K1-1)<<4);
  K2=((J2-1)>>2) + 1;           // Y
  K3=M-((K1-1)<<4)-((K2-1)<<2); // X
  status_ticker = status_delay;
  for (i = 0; i < 27; i++) {
    status[i] = message[i];
    if (message[i] == '\0') {
      status[i++] = (char)(K3+'0');
      status[i++] = (char)(K2+'0');
      status[i++] = (char)(K1+'0');
      status[i++] = 0x80; status[i] = 0; return;
    }
  };
  status[30] = '\x80'; status[31] = '\0';
}

int FIND_MOVE(int I, int mark) { // mark is 1 first time called, then 0.
  if ((((I-1)&3) == 3) || (((I-1)&3) == 0)) {
    // I suspect that placing corner squares is handled differently,
    // and blocking options will never be in the corners.
    for (J=2; J <= 3; J+=1) {
      Move=M(I,J);
      if ((X[Move] == 0) && (Marked[Move] == mark)) {
        X[Move]=COMPUTER_PLAY;
        PRINT_MACHINE_MOVE("I TAKE ", Move);
        return HUMAN; // tell caller to "goto HUMAN_MOVE"...
      }
    }
  } else {
    for (J=1; J <= 4; J+=3) {
      Move=M(I,J);
      if ((X[Move] == 0) && (Marked[Move] == mark)) {
        X[Move]=COMPUTER_PLAY;
        PRINT_MACHINE_MOVE("I TAKE ", Move);
        return HUMAN; // tell caller to "goto HUMAN_MOVE"...
      }
    }
  }
  return 0;
}

// static int Yes_or_No;
// state machine:
#define COMPUTER_MOVE_WANTED 1
#define HUMAN_MOVE_WANTED 2
#define PLAY_AGAIN_WANTED 3

// user input:
// these will be on the RHS and selectable using the joystick... not yet implemented
#define FLIP_REQUESTED 101
#define FIRST_MENU_ITEM FLIP_REQUESTED
#define SCRAMBLE_REQUESTED 102
#define INVERT_REQUESTED 103
#define ROTATE_REQUESTED 104
#define TUMBLE_REQUESTED 105
#define WHIRL_REQUESTED 106
//#define HELP_REQUESTED 107 ... too much to do right now...
#define QUIT_REQUESTED 107
#define LAST_MENU_ITEM QUIT_REQUESTED

static int highlighted_menu_item = ROTATE_REQUESTED;

void DrawMenu(int selected) {
  Intensity_5F();
  if (selected == FLIP_REQUESTED) Intensity_7F();
  Print_Str_d(-60, 40, "FLIP\x80");
  if (selected == FLIP_REQUESTED) Intensity_5F();
  if (selected == SCRAMBLE_REQUESTED) Intensity_7F();
  Print_Str_d(-40, 40, "SCRAMBLE\x80");
  if (selected == SCRAMBLE_REQUESTED) Intensity_5F();
  if (selected == INVERT_REQUESTED) Intensity_7F();
  Print_Str_d(-20, 40, "INVERT\x80");
  if (selected == INVERT_REQUESTED) Intensity_5F();
  if (selected == ROTATE_REQUESTED) Intensity_7F();
  Print_Str_d(0, 40, "ROTATE\x80");
  if (selected == ROTATE_REQUESTED) Intensity_5F();
  if (selected == TUMBLE_REQUESTED) Intensity_7F();
  Print_Str_d(20, 40, "TUMBLE\x80");
  if (selected == TUMBLE_REQUESTED) Intensity_5F();
  if (selected == WHIRL_REQUESTED) Intensity_7F();
  Print_Str_d(40, 40, "WHIRL\x80");
  if (selected == WHIRL_REQUESTED) Intensity_5F();
  //if (selected == HELP_REQUESTED) Intensity_7F();
  //Print_Str_d(60, 40, "HELP\x80");
  //if (selected == HELP_REQUESTED) Intensity_5F();
  if (selected == QUIT_REQUESTED) Intensity_7F();
  Print_Str_d(80, 40, "QUIT\x80");
}

int check_human_input(int request) {
  int x,y,z, temp[5][5][5];
  if (request == FLIP_REQUESTED) {
	int flip[5] = {0,  2,1,4,3};
	for (z = 1; z <= 4; z++) for (y = 1; y <= 4; y++) for (x = 1; x <= 4; x++) temp[x][y][z] = X[xyz(flip[x], flip[y], flip[z])];
	for (z = 1; z <= 4; z++) for (y = 1; y <= 4; y++) for (x = 1; x <= 4; x++) X[xyz(x, y, z)] = temp[x][y][z];
  } else if (request == SCRAMBLE_REQUESTED) {
	int scramble[5] = {0,  1,3,2,4};
	for (z = 1; z <= 4; z++) for (y = 1; y <= 4; y++) for (x = 1; x <= 4; x++) temp[x][y][z] = X[xyz(scramble[x], scramble[y], scramble[z])];
	for (z = 1; z <= 4; z++) for (y = 1; y <= 4; y++) for (x = 1; x <= 4; x++) X[xyz(x, y, z)] = temp[x][y][z];
  } else if (request == INVERT_REQUESTED) { // mirror, top for bottom
	for (z = 1; z <= 2; z++) for (y = 1; y <= 4; y++) for (x = 1; x <= 4; x++) swap(x,y,z,  x,y,5-z);
  } else if (request == ROTATE_REQUESTED) { // clockwise round each face
	for (z = 1; z <= 4; z++) {
	  for (y = 1; y <= 4; y++) for (x = 1; x <= 4; x++) temp[x][y][z] = X[xyz(x, y, z)]; // z is unchanged
	  for (y = 1; y <= 4; y++) for (x = 1; x <= 4; x++) X[xyz(x, y, z)] = temp[y][5-x][z];
	}
  } else if (request == TUMBLE_REQUESTED) { // Imagine a cube in front of you, now roll it farther away...
	for (z = 1; z <= 4; z++) for (y = 1; y <= 4; y++) for (x = 1; x <= 4; x++) temp[x][y][z] = X[xyz(x, y, z)];
	for (z = 1; z <= 4; z++) for (y = 1; y <= 4; y++) for (x = 1; x <= 4; x++) X[xyz(x, y, z)] = temp[x][5-z][y]; // x is unchanged
  } else if (request == WHIRL_REQUESTED) { // Imagine a cube in front of you, now roll it to the right...
	for (z = 1; z <= 4; z++) for (y = 1; y <= 4; y++) for (x = 1; x <= 4; x++) temp[x][y][z] = X[xyz(x, y, z)];
	for (z = 1; z <= 4; z++) for (y = 1; y <= 4; y++) for (x = 1; x <= 4; x++) X[xyz(x, y, z)] = temp[5-z][y][x]; // y is unchanged
  //} else if (request == HELP_REQUESTED) {
  } else if (request == QUIT_REQUESTED) {
    STATUS("YOU RESIGN!"); // wipe table immediately or leave it for a few secs?
    //for (I=1; I <= 64; I++) X[I]=0;
    //first_player = 1-first_player;
    //if (first_player == MACHINE) return COMPUTER_MOVE_WANTED;
    return PLAY_AGAIN_WANTED;
  }

  return HUMAN_MOVE_WANTED; // because nothing significant could be done (yet)
}

int computer_move(void) {
    for (I=1; I <= 64; I++) Marked[I]=UNMARKED;      // Reset temporary markers before computer evaluation

    //if (debug) fprintf(stdout, "Check to see if player has won (4 X's)\n");
    for (I=1; I <= ALL_POSSIBLE_LINES; I++) {
      SumOfPlays=X[M(I,1)]+X[M(I,2)]+X[M(I,3)]+X[M(I,4)]; // Sum the X's and O's in each line.
      // SumOfPlays now holds the score for every line
      if (SumOfPlays == 4) { // Human has 4 pieces in a line!
        Winning_Line = I;
        STATUS("YOU WIN!");
        //first_player = 1-first_player; // these can all be moved to the 'play again' code
        return PLAY_AGAIN_WANTED;
      }
    }

    //if (debug) fprintf(stdout, "Check for forced win (3 O's and an empty 4th space)\n");
    for (I=1; I <= ALL_POSSIBLE_LINES; I++) {
      SumOfPlays=X[M(I,1)]+X[M(I,2)]+X[M(I,3)]+X[M(I,4)]; // Sum the X's and O's in each line.
      // SumOfPlays now holds the score for every line
      if (SumOfPlays == 15) { // Computer has 3 pieces in a line.
        for (J=1; J <= 4; J++) {
          Move=M(I,J);
          if (X[Move] == 0) {
            X[Move]=COMPUTER_PLAY;  // place computer's mark on the only remaining open position to win
            PRINT_MACHINE_MOVE("I MOVE TO ", Move); // suppress redundant board display
            // change to MACHINE WINS WITH ... and remove status below, because
            // unless we queue messages, this one will be imediately overwritten...
          }
        }
        Winning_Line = I;
        STATUS("I WIN!"); // SET LOCKOUT? OR IS IT DONE ELSEWHERE?
        //first_player = 1-first_player;
        return PLAY_AGAIN_WANTED;
      }
    }

    //if (debug) fprintf(stdout, "Human has placed 3 X's in a row, so computer blocks 4th position...\n");
    for (I=1; I <= ALL_POSSIBLE_LINES; I++) {
      SumOfPlays=X[M(I,1)]+X[M(I,2)]+X[M(I,3)]+X[M(I,4)]; // Sum the X's and O's in each line.
      // SumOfPlays now holds the score for every line
      if (SumOfPlays == 3) { // human almost has a line, so computer has to block it...
        for (J=1; J <= 4; J++) {
          Move=M(I,J);
          if (X[Move] == 0) {
            X[Move]=COMPUTER_PLAY;
            PRINT_MACHINE_MOVE("NICE! I MOVE TO ", Move);
            return HUMAN_MOVE_WANTED;
          }
        }
        break; // WHY IS THIS A BREAK??? may be correct but need to check...
      }
    }

    // nothing was forced, so can we create a forcing move?
    //if (debug) fprintf(stdout, "Create forcing move?\n");
    RandBase = (int)irand127();
    for (R=0; R < ALL_POSSIBLE_LINES; R++) { I = (int)((((unsigned)R + (unsigned)RandBase) % (unsigned)ALL_POSSIBLE_LINES) + 1U); // Shuffle order of scan
                                          // I = ((R + RandBase) % ALL_POSSIBLE_LINES) + 1; // Shuffle order of scan
      SumOfPlays=X[M(I,1)]+X[M(I,2)]+X[M(I,3)]+X[M(I,4)];
      SumOfMarks=Marked[M(I,1)]+Marked[M(I,2)]+Marked[M(I,3)]+Marked[M(I,4)];
      if (SumOfPlays <  10) continue; // Only one of computer's
      if (SumOfPlays >= 11) continue; // Two of computer's and one of human's - cannot make this line
      if ((SumOfPlays == 10) && (SumOfMarks > 0)) { // Two of computer's, plus one of the spare squares is already marked as being useful for another line
        for (J=1; J <= 4; J++) {
          Move=M(I,J);
          if ((X[Move] == 0) && (Marked[Move] == MARKED)) {
            X[Move]=COMPUTER_PLAY;
            // PROBLEM!!! SOME STRINGS ARE TOO LONG!!!!!!
            PRINT_MACHINE_MOVE("SMART MOVE IS ", Move);
            return HUMAN_MOVE_WANTED;
          }
        }
        STATUS("I CONCEDE.");
        //first_player = 1-first_player;
        return PLAY_AGAIN_WANTED;
      }
      // this part is executed if SumOfPlays == 10, i.e. if the computer has placed two O's,
      // mark the remaining 2 free positions as potential plays (marker is '1/8')
      // If the computer plays in a square that is marked, it means that the computer
      // has created two different lines that have three squares with one free, i.e.
      // can win on next turn unless human wins with next move...
      for (J=1; J <= 4; J++) if (X[M(I,J)] == 0) Marked[M(I,J)]=MARKED; // SQUARES ARE MARKED HERE
    }

    //if (debug) fprintf(stdout, "Four empty places or one of computer's marks and 3 empty places?\n");
    // no great moves, so can we find a better-than-nothing move?
    RandBase = irand127();
    for (R=0; R < ALL_POSSIBLE_LINES; R++) { I = (int)((((unsigned)R + (unsigned)RandBase) % (unsigned)ALL_POSSIBLE_LINES) + 1U); // Shuffle order of scan
      SumOfPlays=X[M(I,1)]+X[M(I,2)]+X[M(I,3)]+X[M(I,4)];
      SumOfMarks=Marked[M(I,1)]+Marked[M(I,2)]+Marked[M(I,3)]+Marked[M(I,4)];
      if ( (SumOfMarks == 4) || ((SumOfPlays == 5) && (SumOfMarks == 3)) ) { // four empty places or one of computer's marks and 3 empty places
	   if (FIND_MOVE(I, MARKED)) return HUMAN_MOVE_WANTED;
      }
    }
    // Remove temporary markers?
    for (I=1; I <= 64; I++) Marked[I]=UNMARKED;

    //if (debug) fprintf(stdout, "Can I block human from creating a pin?\n");
    RandBase = irand127();
    for (R=0; R < ALL_POSSIBLE_LINES; R++) { I = (int)((((unsigned)R + (unsigned)RandBase) % (unsigned)ALL_POSSIBLE_LINES) + 1U); // Shuffle order of scan
      SumOfPlays=X[M(I,1)]+X[M(I,2)]+X[M(I,3)]+X[M(I,4)];
      SumOfMarks=Marked[M(I,1)]+Marked[M(I,2)]+Marked[M(I,3)]+Marked[M(I,4)];
      if ((SumOfPlays < 2) || (SumOfPlays >= 3)) continue;
      if ((SumOfPlays == 2) && (SumOfMarks > 0)) {
        for (J=1; J <= 4; J++) {
          Move=M(I,J);
          if (Marked[Move] == MARKED) { // it is only marked if a previous line like this has also been examined. The first one is not.
            X[Move]=COMPUTER_PLAY;
            //if (debug) fprintf(stdout, "SumOfPlays=%d Marks=%d\n", SumOfPlays, SumOfMarks);
            PRINT_MACHINE_MOVE("WHEW! I MOVE TO ", Move);
	        // I've seen this work well once and I've seen it pick a square that makes no sense once. Keep an eye on this...
            return HUMAN_MOVE_WANTED;
          }
        }
	// Can't block pin
        STATUS("I CONCEDE.");
        //first_player = 1-first_player;
        return PLAY_AGAIN_WANTED;
      }
      for (J=1; J <= 4; J++) if (X[M(I,J)] == 0) Marked[M(I,J)]=MARKED;  // SQUARES ARE MARKED HERE TOO!
    }

    //if (debug) fprintf(stdout, "Scan for 4 empty squares or 1 human play and 3 empty squares and a potential play marker...\n");
    RandBase = irand127();
    for (R=0; R < ALL_POSSIBLE_LINES; R++) { I = (int)((((unsigned)R + (unsigned)RandBase) % (unsigned)ALL_POSSIBLE_LINES) + 1U); // Shuffle order of scan
      SumOfPlays=X[M(I,1)]+X[M(I,2)]+X[M(I,3)]+X[M(I,4)];
      SumOfMarks=Marked[M(I,1)]+Marked[M(I,2)]+Marked[M(I,3)]+Marked[M(I,4)];
      // SumOfPlays now holds the score for every line
      if ((SumOfMarks == 4) || ((SumOfPlays == 1) && (SumOfMarks == 3))) { // 4 empty squares or 1 human play and 3 empty squares
	   if (FIND_MOVE(I, MARKED)) {
          //if (debug) fprintf(stdout, "SumOfPlays=%d Marks=%d\n", SumOfPlays, SumOfMarks);
	      return HUMAN_MOVE_WANTED;
	   }
      }
    }

    //if (debug) fprintf(stdout, "Scan over groups of 4 lines...\n");
    RandBase = irand127();
    for (R=0; R < 18; R++) { K = (int)((((unsigned)R + (unsigned)RandBase) % 18U) + 1U); // Shuffle order of scan
                          // K = ((R + RandBase) % 18) + 1;
      // loop over 72 possible winning lines - not 76 ... maybe because last 4 are the 3D diagonals?
      int P=0;
      // adding up groups of 4 lines (e.g. 1,2,3,4,   5,6,7,8 ...) - perhaps trying to find planes that human is concentrating in?
      for (I=4*K-3; I <= 4*K; I++) for (J=1; J <= 4; J++) P+=X[M(I,J)];
      //if (debug) fprintf(stdout, "K%d P%d; ", K, P);
      if (P<4) {
        continue;
      } else if (P<5) {
        for (I=4*K-3; I < 4*K; I++) {
          if (FIND_MOVE(I, MARKED)) {
            //if (debug) fprintf(stdout, " [choice #1 P<5, marked]\n");
            return HUMAN_MOVE_WANTED;
          }
        }
        for (I=4*K-3; I < 4*K; I++) {
          if (FIND_MOVE(I, UNMARKED)) {
            //if (debug) fprintf(stdout, " [choice #2 P<5, unmarked]\n");
            return HUMAN_MOVE_WANTED;
          }
        }
        // goto HUMAN_MOVE;
      } else if (P<9) {
        continue;
      } else if (P<10) {
        for (I=4*K-3; I < 4*K; I++) {
          if (FIND_MOVE(I, MARKED)) {
            //if (debug) fprintf(stdout, " [choice #3 P<10, marked]\n");
            return HUMAN_MOVE_WANTED;
          }
        }
        for (I=4*K-3; I < 4*K; I++) {
          if (FIND_MOVE(I, UNMARKED)) {
            //if (debug) fprintf(stdout, " [choice #4 P<10, unmarked]\n");
            return HUMAN_MOVE_WANTED;
          }
        }
        // goto HUMAN_MOVE;
      }
    }
    //if (debug) fprintf(stdout, "\n");
    
    //if (debug) fprintf(stdout, "Place a corner square if any available?\n");
    // Place a corner square
    {int Z;
      RandBase = irand127();
      for (R=0; R < 16; R++) {Z = (int)((((unsigned)R + (unsigned)RandBase) % 16U) + 1U); // Shuffle order of scan
                           // Z = ((R + RandBase) % 16) + 1;
        if (X[Y(Z)] == 0) { // Heuristic: are any of these positions free? 1,49,52,4,13,61,64,16,22,39,23,38,26,42,27,43
          Move=Y(Z);
          X[Move]=COMPUTER_PLAY;
          PRINT_MACHINE_MOVE("I MOVE TO ", Move);
          return HUMAN_MOVE_WANTED;
        }
      }
    }

    //if (debug) fprintf(stdout, "Otherwise just take next free square anywhere...\n");
    for (I=1; I <= 64; I++) { // add the randbase tweak!
      if (X[I] == 0) { // looks like it just choses first available space?
        X[I]=COMPUTER_PLAY;
        Move=I;
        PRINT_MACHINE_MOVE("I LIKE ", Move);
        return HUMAN_MOVE_WANTED;
      }
    }
    //if (debug) fprintf(stdout, "But board is completely full!\n");

    STATUS("IT'S A DRAW!");
    //first_player = 1-first_player;
    return PLAY_AGAIN_WANTED;
}

// for pulsing flashing :-) (overkill)
static const long 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
  };

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

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



int main(void)
{
  int sound_wanted = 0;
  int whats_next;
  int tick = 0;
  unsigned int flash_timer = 0;
  unsigned int lockout_timer = 0;
  int HintLoc = 127; //-110;
  unsigned long hint_timer = 0L;
#define HINT_DELAY 1000L
  int buttons;
  int row, column, layer;
//  int debug_row = 1, debug_column = 1, debug_layer = 1;
//  int debug_x = 0, debug_y = 0;
  int player_x = 1, player_y = 1, player_z = 1;
  int letter, idx; // x, y, z;
  int step_x = 0, step_y = 0, step_z = 0, place_mark = 0;
  int previous_step_x = 0, previous_step_y = 0, previous_step_z = 0, previous_place_mark = 0;
  volatile unsigned int *rand = (volatile unsigned int *)0xc87b;
  volatile unsigned int *timer_lo = (volatile unsigned int *)0xD004;

  // unfortunately, on emulator at least, very first random is always the same,
  // which means if computer plays first, it would always make the same play...
  // so we always let the human play first!
  initRandom(rand[0],rand[1],rand[2],*timer_lo);
  for (I=1; I <= 64; I++) X[I]=0; // Init board

  //if (first_player < 0) { // first time only...
    first_player = HUMAN; // until logic is added to decide who's on first...
    whats_next = HUMAN_MOVE_WANTED;
    STATUS("YOU PLAY FIRST");
  //}

  for (;;) {
    // poll joystick and buttons:
    Joy_Digital();
    XJoy = YJoy = 0;
    if (pot0 < -10) XJoy = -1;        // Left has no significance at the moment
    else if (pot0 > 10) XJoy = 1;     // Right means menu is displayed!
    if (pot1 < -10) YJoy = -1;	      // Down - highlight selection if menu displayed
    else if (pot1 > 10) YJoy = 1;     // Up   - ditto

    asm("pshs x,y,u,d,dp");
    asm("lda  #0");
    asm("jsr  0HF1B4 ; read_btns_mask");
    asm("puls dp,d,u,y,x");
    buttons = (*(volatile int *)(0xC80F));
    // latch any buttons pressed any time during debounce timeout
    if (buttons & 1) step_x = 1;
    if (buttons & 2) step_y = 1;
    if (buttons & 4) step_z = 1;
    if (buttons & 8) place_mark = 1;

    if ((!lockout_timer) && (whats_next == HUMAN_MOVE_WANTED)) {
      if ((XJoy > 0) && place_mark && (!previous_place_mark)) {
        // highlighted menu item is selected
        previous_place_mark = place_mark;
        whats_next = check_human_input(highlighted_menu_item);
      }
      // return values are:
      // HUMAN_MOVE_WANTED     (didn't get anything from the human that I can act on yet)
      // PLAY_AGAIN_WANTED     (human resigned)
    }

    if ((!lockout_timer) && (whats_next == COMPUTER_MOVE_WANTED)) {
      whats_next = computer_move(); // might blank screen while computing
      // return values are:
      // HUMAN_MOVE_WANTED
      // PLAY_AGAIN_WANTED
    }
    // now draw board...
    Wait_Recal(); // sets dp to d0, and pos at 0, 0  No drawing above this line.

    if (sound_wanted) {
      // trying to replicate http://vectorgaming.proboards.com/thread/1850/explosion-snd-example-never-before
      // but not working too well!!!
      asm("                pshs    d,x,u"); // D-reg, X-reg, U-reg trashed
      asm("                Jsr     0HF289 ; Do_Sound"); // Do Sound set up below
      asm("                puls    u,x,d");
      sound_wanted = 0;
    }
    Intensity_3F();

    // if a new game was requested, let's leave the old game on the screen for a short while
    // to allow the human to see how he lost or to revel in his win...
    if ((lockout_timer == 0) && (whats_next == PLAY_AGAIN_WANTED)) lockout_timer = lockout_delay;

    if ((lockout_timer == 1) && (whats_next == PLAY_AGAIN_WANTED)) {
      for (I=1; I <= 64; I++) X[I]=0; // Re-init board
      first_player = 1-first_player;
      hint_timer = 0L;
      if (first_player == HUMAN) {
        whats_next = HUMAN_MOVE_WANTED;
      } else {
        whats_next = COMPUTER_MOVE_WANTED;
      }
    }

    // draw underlying board
    for (layer = 1; layer <= 4; layer++) {
      DrawGrid(layer);
    }

    Intensity_5F();
    idx = 1;
    for (layer = 1; layer <= 4; layer++) {
      for (row = 1; row <= 4; row++) {
        for (column = 1; column <= 4; column++) {
          letter = X[idx++]; // xyz(column,row,layer)  or idx++ for efficiency, once I'm sure this is correct
          if (first_player == HUMAN) {
            if (letter == HUMAN_PLAY) {
              draw_X(column,row,layer);
            } else if (letter == COMPUTER_PLAY) {
              draw_O(column,row,layer);
            }
          } else {
            if (letter == COMPUTER_PLAY) {
              draw_X(column,row,layer);
            } else if (letter == HUMAN_PLAY) {
              draw_O(column,row,layer);
            }
          }
        }
      }
    }

    Intensity_7F();
    Reset0Ref();
    
    if ((!lockout_timer) && (XJoy > 0)) {
      DrawMenu(highlighted_menu_item);
    }

    if (!lockout_timer) {
       if (hint_timer >= HINT_DELAY) {
        // could fade this in if the human is taking an inordinate amount of time to press
        // any buttons, i.e. they're not sure what to do...
        if (!(XJoy > 0)) {
          set_scale(0xdf);
          Print_Str_d(HintLoc, -100, "USE THE BUTTONS:\x80"); // when it's the human's turn to play
          Print_Str_d(HintLoc-17, -100, "X   Y   Z   PLAY\x80"); // when it's the human's turn to play
        }
      }
    }

    Intensity_a(70+((((unsigned int)(fp2_14_sin(((unsigned int)flash_timer)&255U)>>9U))))); // this will be the human's piece that is to be played...
    flash_timer += 4;

    if ((whats_next == PLAY_AGAIN_WANTED) && Winning_Line) { // highlight the winning row
      int K1, K2, K3, Q1, Q2, Q3, J2, M;

      M=M(Winning_Line, 1);
      letter=X[M];
      K1=((M-1)>>4) + 1;             // Z
      J2=M-((K1-1)<<4);
      K2=((J2-1)>>2) + 1;            // Y
      K3=M-((K1-1)<<4)-((K2-1)<<2);  // X

      M=M(Winning_Line, 4);
      letter=X[M];
      Q1=((M-1)>>4) + 1;             // Z
      J2=M-((Q1-1)<<4);
      Q2=((J2-1)>>2) + 1;            // Y
      Q3=M-((Q1-1)<<4)-((Q2-1)<<2);  // X

      Join(K3,K2,K1, Q3,Q2,Q1); // draw a line through winning line
      // SORT OF A BUG ... the Join above changes the intensity away from flashing,
      // which messes up all the drawing below that should also be flashing, but
      // since this is only called on a win, it's not really a serious problem...

      for (J = 1; J <= 4; J++) {
        M=M(Winning_Line, J);
        letter=X[M];
        K1=((M-1)>>4) + 1;
        J2=M-((K1-1)<<4);
        K2=((J2-1)>>2) + 1;
        K3=M-((K1-1)<<4)-((K2-1)<<2);
        if (first_player == HUMAN) {
          if (letter == HUMAN_PLAY) {
            draw_X(K3,K2,K1);
          } else if (letter == COMPUTER_PLAY) {
            draw_O(K3,K2,K1);
          }
        } else {
          if (letter == COMPUTER_PLAY) {
            draw_X(K3,K2,K1);
          } else if (letter == HUMAN_PLAY) {
            draw_O(K3,K2,K1);
          }
        }

      }
      if (lockout_timer == 1) Winning_Line = 0;
    }

    if (status_ticker > 0U) {
      Reset0Ref();
      //set_scale(0xdf);
      Print_Str_d(-15, -100, status);
      status_ticker -= 1U;
    }

    if (!lockout_timer && (!(XJoy > 0))) {
      // suppress player's piece when rotating board etc
      if (first_player == HUMAN) {
        draw_X(player_x, player_y, player_z);
      } else { // first_player == MACHINE
        draw_O(player_x, player_y, player_z);
      }
    }

    if (tick++ == animation_delay) { // doubling as key debouncing interval
      (void)irand127(); // use timing to further randomise
      if (!lockout_timer) {

      if (YJoy < 0) {
        if (highlighted_menu_item > FIRST_MENU_ITEM) highlighted_menu_item -= 1;
      } else if (YJoy > 0) {
        if (highlighted_menu_item < LAST_MENU_ITEM) highlighted_menu_item += 1;
      }

      if (step_x || step_y || step_z) {
        hint_timer = 0L; HintLoc = 127; /*-110;*/ // reset timeout if any button pressed...
        status[0] = '\x80'; status[1] = '\0'; status_ticker = 0;
      }

      if (step_x && !previous_step_x) player_x++;
      if (player_x == 5) {
        player_x = 1;
      }
      if (step_y && !previous_step_y) player_y++;
      if (player_y == 5) {
          player_y = 1;
      }
      if (step_z && !previous_step_z) player_z++;
      if (player_z == 5) {
        player_z = 1;
      }

      if (place_mark && (!previous_place_mark) && (!(XJoy > 0))) {
        for (I=1; I <= 64; I++) Marked[I]=UNMARKED;      // Remove temporary markers which are not used by human
        // Move 'M' is stored packed, 2 bits per coord.
        //  (Range 1..64 (not 0..63 due to BASIC arrays based at 1))
        Move=xyz(player_x, player_y, player_z);
        if (X[Move] == 0) {   // square is free, no X's or O's on it.
          X[Move]=HUMAN_PLAY; // Place human's mark in the selected square
          whats_next=COMPUTER_MOVE_WANTED;
          // TO DO: make a cheerful 'ding' sound to indicate human has placed their marker?
        } else {
          // Square is occupied. Trying to play a sound to let the player know...
          // trying to replicate http://vectorgaming.proboards.com/thread/1850/explosion-snd-example-never-before
          // but not working too well!!!
          asm("                pshs    d,x,y,u,dp");
          asm("                bra     skipxxx");
          asm("EXP1:           .byte 25");
          asm("                .byte   63");
          asm("                .byte   0");
          asm("                .byte   2");
          asm("EXP2:           .byte   63");
          asm("                .byte   0");
          asm("                .byte   0");
          asm("                .byte   1");
          asm("EXP3:           .byte   63");
          asm("                .byte   1");
          asm("                .byte   255");
          asm("                .byte   2");
          asm("skipxxx:        Lda     #255   ; negative bit set");
          asm("                Sta     0HC867 ; Vec_Expl_Flag");
          asm("                Jsr     0HF1AF ; DP_to_C8");
          asm("                Ldu     #EXP3");
          asm("                Jsr     0HF92E ; Explosion_Snd");
          asm("                puls    dp,u,y,x,d");
          sound_wanted = 1;
          STATUS("OCCUPIED");
        }
      } // button 1 pressed
      previous_step_x = step_x; previous_step_y = step_y; previous_step_z = step_z; previous_place_mark = place_mark;
      step_x = step_y = step_z = place_mark = 0;
      } // lockout timer
      tick = 0;
      //if (hint_timer >= HINT_DELAY) {    // no longer scrolling this...
        //HintLoc++; if (HintLoc == 110) HintLoc = -110;
      //}
    } // animation delay
    if (lockout_timer) lockout_timer -= 1U; // every frame
    if (hint_timer < 32767L) hint_timer += 1L; // max out to avoid wraparound
  }
  return 0;
}