// Define this for a version that doesn't crash VecFlash carts when accessing calibration data:
// ***SET IN CARTRIDGE.C ALSO***
#define NO_DS 1
#define NO_MAZE_CYCLING
//#define NO_DOT_TOGGLE
static int INVINCIBLE = 0;  // normally 0.  Set to 1 for debugging or press 'i' on Linux

/*
These must now be modified to draw on linux as they do on vectrex, to make
debugging of draw_dots the same on both systems.  Previously we drew all the
dots on Linux more like actual Pacman games.
      DrawMaze[mapno]();
      DrawPuck();
      DrawGhosts();
      DrawPowerPills();
      Draw_Dots();

 */

// It's safe to use -O2 again.  smaller than -O3 and surprisingly now, faster.

// TO DO's...
//
// Medium term:
//  3) Better 'X' graphic for power pills
//  4) sound???
//  5) document the 1/2/3/4 line explanation of the ghost sprites
//  6) Clean up unneeded/out-of-date comments
//
// Long term:
//  7) Some method of scoring such as a point per pill, and points for touching a ghost while invulnerable.
//     and possibly a time element eg no. of times tried and time final attempt took.
//  8) manual - need images of what correct calibration looks like
//  9) A version specific for Vector Wars! to eliminate practising and learning the paths.


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

#ifndef LINUX
#define VECTREX 1
#endif

typedef int bool;

#ifdef VECTREX
#define int8_t int
#define uint8_t unsigned int
#define int16_t long int
#define uint16_t unsigned long int
#define uintptr_t unsigned long int
#define size_t unsigned long int
#ifndef NULL
#define NULL 0
#endif
#else // Linux, not Vectrex
#include <inttypes.h> // not supplied on Vectrex
#endif // Vectrex or Linux

static int8_t PUCK_SPEED = 1; // set to 2 for chasing ghosts or any other high-speed puck motion
static int8_t GHOST_SPEED = 1; // set to 2 for chasing puck or any other high-speed ghost motion


// These two arrays were previously in 'corridors.h' which has now been deleted.
// They give us the IDs of the junctions at the end of the given corridor index.
// Declarations must come before inclusion of arraychecks.h in utils.i

static const char *end1[4] = {
  "BFHABCDEFGABDEGHIIJKLMNOMNOPQRQRSTSTUVWYWXYZaabcdefghbdegijk",
  "ACAABCDBCEFEFGHIIJKLMOMNOPQQRSTVWSUVXYYZZabYbcdeecfghi",
  "BDHAABDEFACDFGHIJKMMILOMNOPQSUQSTVWYWXYZabcdeacdfgighijklmnokmpqsqs",
  "AABCDABCDKEGEFGHIJKLMNNOPMQSQRSTWUWUVYWXYZacabdeefhijfghikkl",
};

static const char *end2[4] = {
  "ACGBCDEFGHIJJKKLLMNOPNOPSQRVSVTUVUWXYZXZacfhibcdefghlijkljkl",
  "DBGBCDHEFGHIJKLJNOSXNPTQRWRUVTUWXYZabbcdaefgjdhifgjhij",
  "GCEGBCEFHIJKLNOJKLPNRUPWQVZRTVXbeYXZgafjbcdefhloihjqkptlmnoprnsrtrt",
  "IBCDLEFGHJFHIJKLMNOPQROSTPRTXUVYZVXbcZeadjbdgchmfgijnmklnlmn",
};

// ####################################### STATE ############################################

// So far I haven't written enough of the gameplay to have started on using a state machine
// to implement the game state, which I will eventually need to.  The timer below is the
// first hint of that mechanism appearing... so probably time soon to add a more properly
// structured state machine...

static uint8_t Puck_Speed_Timer = 0;
//static uint8_t Ghost_Speed_Timer = 0;  // not used yet
#define PUCK_TIMEOUT 90  /* Small enough that a player can't visit all 4 power pills without being vulnerable */

static bool reversals_allowed = FALSE;

static bool level_complete = FALSE;

static bool reboot_wanted = FALSE;

// ##########################################################################################

#ifdef PARM_ARRAY
#include ".utils.c"
#else
#include "utils.i"
#endif

#ifdef LINUX
// not used on linux but here so I can call set_scale() and not have to wrap it in an ifdef:
//static uint8_t spriteMoveScale = 0;

#define spriteYoffset 0 // defined here for when Malban uses them outside of Vectrex-specific
#define spriteXoffset 0 // code.  Using a value of 0 means they're ignored in a Linux build.

#endif // Linux

// cache pointers to slices of arrays indexed by mapno, and later, for any arrays
// with variably-sized slices, add the stride of elements within that array:
void assign_mapno(uint8_t new_mapno) {
  mapno = new_mapno;
  WM = wallmap[mapno];  // utils.i: const char * const * WM;
}

// A list of junctions that can be used to sneakily bisect the map, to force
// Puck into a smaller controllable 'kill zone' area: (not yet implemented)
static const char * const junctions[N] = {"MPON", "KLNO", "NORU", "QTRS"};

static void init_ghosts(void) {
  // initial ghost targets currently being set manually with the initial locations being
  // near the power pills.  Later we'll start them from the jail, like the pacman games.

  for (ghostno = 0; ghostno < 4; ghostno++) {
    CrashIf(mapno > 3, "JUNK4");
    const char *ghost_target = junctions[mapno];
    Ghost[ghostno].screen.Y = Map2VecRow(Ghost[ghostno].rowcol.row = PPy[mapno][ghostno]+1);
    Ghost[ghostno].screen.X = Map2VecCol(Ghost[ghostno].rowcol.col = PPx       [ghostno]);
    
    // Don't need to fill these in - they'll be set by the targetting code...
    // Or maybe not... revisit later. ( see test for 'STUCK' at the start of
    //  determine_ghost_directions )
    Ghost[ghostno].dir = DOWN;
    Ghost[ghostno].sdy = -((Ghost[ghostno].dy = dy[Ghost[ghostno].dir]) * GHOST_SPEED);
    Ghost[ghostno].sdx =   (Ghost[ghostno].dx = dx[Ghost[ghostno].dir]) * GHOST_SPEED;
  
    Target[ghostno] = Ghost[ghostno].target_junction = junction_code_to_index(ghost_target[ghostno]);
    Ghost[ghostno].State = ALIVE;
  }
}

// Malban added these rather than use the arrays to make the code faster.
// Again, the C compiler *should* have produced identical code for array
// elements with fixed literal indices.  I should do a comparison test and
// count cycles.  Later.  Much later.

int pp0y, pp0x, pp1y, pp1x, pp2y, pp2x, pp3y, pp3x;

static void init_power_pills(void) {
  PState[3] = PState[2] = PState[1] = PState[0] = UNEATEN;
  maxpill = pills_remaining = MAXPILL[mapno];

  pp0y = Map2VecRow(PPy[mapno][0]) + spriteYoffset;
  pp0x = Map2VecCol(PPx       [0]) + spriteXoffset;
  pp1y = Map2VecRow(PPy[mapno][1]) + spriteYoffset;
  pp1x = Map2VecCol(PPx       [1]) + spriteXoffset;
  pp2y = Map2VecRow(PPy[mapno][2]) + spriteYoffset;
  pp2x = Map2VecCol(PPx       [2]) + spriteXoffset;
  pp3y = Map2VecRow(PPy[mapno][3]) + spriteYoffset;
  pp3x = Map2VecCol(PPx       [3]) + spriteXoffset;

  // pill number 0 is reserved for 'no pill' so used indexes are 1:maxpill
  // No pill variable should ever hold a 0 unless 'pills_remaining' is 0, in
  // which case we won't even be attempting to draw pills anyway.

  clear_eaten_flags(); // must be after maxpill initialisation.
}

static void init_player_position(void) {
  // Maze columns are 1 through 27 so center position would be 13.5:  adding +4 to the
  // equivalent screen unit position for X=13 centers it on the Vectrex display.  The
  // ascii display was originally to the nearest map cell square which was doubled for
  // the ncurses display (since one character height is approximately two character
  // widths) but I've since fixed that so that every character position is used in the
  // ncurses display.  (Not really critical but was a minor annoyance to me)

  // Puck's starting position is fixed and same on all levels.

  Player.dir = LEFT;
  Player.screen.X = Map2VecCol(Player.rowcol.col = 13) + 4 /*center*/ ;
  Player.screen.Y = Map2VecRow(Player.rowcol.row = 23);
  Player.sdx =   (Player.dx = dx[Player.dir]) * PUCK_SPEED;
  Player.sdy = -((Player.dy = dy[Player.dir]) * PUCK_SPEED);

}

static inline int abs8(int8_t pm) {
  if (pm < 0) pm = -pm;
  return pm;
}

static bool puck_is_caught(void) {  // Are we on a dot?
  static int8_t *p;
  // More use of pointers and literal offsets into structs from Malban.
  // Be very careful if updating the Ghost structure definition, and
  // do some comparison of the compiler output later to see if this can
  // be avoided without loss of speed, to make code maintenance safer.
  // Question: Does gcc 6809 support offsetof() and _Alignof()/alignof()?
  // No... so write our own:
#define offsetof(st, m) ((size_t)((char *)&((st *)0)->m - (char *)0))

  if (INVINCIBLE) return FALSE; // for debugging only. Not part of the game.
  
#ifdef CHECKS
  if (sizeof(Ghost[0]) != 11) {
    debugf("1: Sizeof(Ghost[0]) = %d (not 11...)\n", sizeof(Ghost[0]));
    crash("SIZE");
  }
  if ((uintptr_t)&Ghost[1] - (uintptr_t)&Ghost[0] != 11) {
    debugf("2: Sizeof(Ghost[0]) = %d (not 11...) Alignment issue?\n", (uintptr_t)&Ghost[1] - (uintptr_t)&Ghost[0]);
    crash("SIZE");
  }
  if ((uintptr_t)&Player.screen.Y - (uintptr_t)&Player != 0) {
    debugf("1: Offsetof(Player.screen.Y) = %d (not 0...)\n", (uintptr_t)&Player.screen.Y - (uintptr_t)&Player);
    crash("OFFS1Y");
  }
  if ((uintptr_t)&Player.screen.X - (uintptr_t)&Player != 1) {
    debugf("1: Offsetof(Player.screen.X) = %d (not 1...)\n", (uintptr_t)&Player.screen.X - (uintptr_t)&Player);
    crash("OFFS1X");
  }
  if (offsetof(PLAYER, screen) + offsetof(ScreenCoord, Y) != 0) {
    debugf("2: Offsetof(Player.screen.Y) = %d (not 0...)\n", (uintptr_t)&Player.screen.Y - (uintptr_t)&Player);
    crash("OFFS2Y");
  }
  if (offsetof(PLAYER, screen) + offsetof(ScreenCoord, X) != 1) {
    debugf("2: Offsetof(Player.screen.X) = %d (not 1...)\n", (uintptr_t)&Player.screen.X - (uintptr_t)&Player);
    crash("OFFS2X");
  }
#endif // CHECKS
  p = (int8_t *) (&Ghost[0].screen.Y) /* -11 moved to end of loop */;
  //                                      ^^
  // p changed from int to int8_t because adding a constant
  // to a pointer to an int does not add that number, it
  // adds that number multiplied by the size of an int!
  // Using 'int' is not portable between Linux and Vectrex!

  if (PUCK_SPEED == 1) { /* hack test for now */
    for (ghostno = 0; ghostno < 4; ghostno++) {
      G = &Ghost[ghostno];
      // This first test guards against arithmetic errors in the second test that
      // might arise because of the range of screen coordinates: DO NOT REMOVE IT!
      if ( (abs8(G->rowcol.row - Player.rowcol.row) <= 1) &&
           (abs8(G->rowcol.col - Player.rowcol.col) <= 1)
         ) {      
        if (
            ((*p == Player.screen.Y) && (abs8(*(p+1) - Player.screen.X) < 8))
            ||
            ((*(p+1) == Player.screen.X) && (abs8(*(p) - Player.screen.Y) < 8))
           ) {
          // *p should be p->y and *(p+1) should be p->x
          if (0) debugf("G[%d] = (%d,%d)  P = (%d,%d)\n",
                 ghostno,
                 Ghost[ghostno].screen.Y,Ghost[ghostno].screen.X,
                 Player.screen.Y,Player.screen.X);
          return TRUE;
        }
      }
      // change both 11's to sizeof() but be sure to allow for alignment if present
      p = p + sizeof(Ghost[0]) /* 11 */;
      //                          ^^
    }
  }  
  return FALSE;
}

static void check_for_pill(void) {
  //debugf("%s\n", "check for pill");
  if (alignedPlayerX() && alignedPlayerY()) {
    static uint8_t pill; // static access *should* be faster than stack access
    //debugf("%s\n", "puck is aligned");
    
    if ((pill = dotmap[mapno][Player.rowcol.row][Player.rowcol.col]) != 0) { // a pill location (pill <= maxpill) or a powerpill location (pill > maxpill)
      // only do this when Puck is aligned
      //debugf("we are over dot #%d\n", pill);
      if (pill <= maxpill) { // small pills     IN FRAME TWO, PILL 240 IS NEVER EATEN!!!  DON'T KNOW WHY YET!
        //debugf("%s\n", "small pill");
        if (!is_eaten(pill)) { // then eat it.
          CrashIf(pills_remaining == 0, "0PILL");
          pills_remaining -= 1;
          set_eaten_flag(pill);
        }
        
      } else { // pill > maxpill means a power pill:

        //debugf("Checking for PP at X=%d Y=%d  PP=%d %d %d %d\n", Player.rowcol.col, Player.rowcol.row, PState[0], PState[1], PState[2], PState[3]);
        // Are we on a power pill?  If so, eat it and increase Puck's speed!
      
        for (pill = 0; pill < 4; pill++) {
          if ((PState[pill] == UNEATEN) && (PPy[mapno][pill] == Player.rowcol.row) && (PPx[pill] == Player.rowcol.col)) {

            PState[pill] = EATEN;

            PUCK_SPEED = 2; Puck_Speed_Timer = PUCK_TIMEOUT;
            
            // These X and Y corrections may look stupid but they actually essential because
            // a misaligned position can cause Puck to be 1 unit into a wall.  After a speed
            // change, Puck must be aligned to the same grid as SPEED, i.e. a speed of 2
            // means Puck must end up on a multiple of 2 relative to the base grid of 8.
      
            // TO DO: It occurs to me (much later) that I wouldn't need this alignment correction
            // if I only allowed speed changes when Puck was fully aligned to 8 units.  *WHICH COULD
            // BE ARRANGED* when it eats a Power Pill.  Something similar (but slightly more awkward)
            // would be necessary for the ghosts as well.

            // AS OF NOW WE ARE ALIGNED AND THE CODE BELOW CAN NOW BE SIMPLIFIED.
        
            /**/if (Player.screen.X&1) { if (Player.screen.X > 0) Player.screen.X -= 1; else if (Player.screen.X < 0) Player.screen.X += 1; }
            /**/else if (Player.screen.Y&1) { if (Player.screen.Y > 0) Player.screen.Y -= 1; else if (Player.screen.Y < 0) Player.screen.Y += 1; }

            Player.sdx = Player.dx * PUCK_SPEED; Player.sdy = -(Player.dy * PUCK_SPEED);
          }
        }
      }
    }
  }
}

// Vectrex joystick moves are mapped to a virtual keypress
#define clear_button() button = ' '

static void determine_puck_direction(void) {  // Either because next move would hit a corner when in a corridor (so change to 0)
                                     // Or because a change of direction has been requested by the player
                                     // and we are in a position to allow it

  // Now check for a wall in the direction of travel, if aligned in perpendicular axis.
  // DO NOT set Player.sdx or Player.sdy to 0 if against the wall. Needed for direction change tests.

  // Player.sdx and Player.sdy reflect the current movement, before applying a change of direction.
  //  If we do change direction, act on it immediately, don't wait for next frame.

  // this maybe where the misaligned turns happen.  BUT beware of breaking reversals when fixing

  // Has a turn been requested?
  if (Want_LEFT() && (Player.dir != LEFT)) {
    if (!wall_to_left_of_player()) {
      Player.dir = LEFT;
      Player.sdx =   (Player.dx = dx[Player.dir]) * PUCK_SPEED;
      Player.sdy = -((Player.dy = dy[Player.dir]) * PUCK_SPEED);
      clear_button();
    }
  } else if (Want_RIGHT() && (Player.dir != RIGHT)) {
    if (!wall_to_right_of_player()) {
      Player.dir = RIGHT;
      Player.sdx =   (Player.dx = dx[Player.dir]) * PUCK_SPEED;
      Player.sdy = -((Player.dy = dy[Player.dir]) * PUCK_SPEED);
      clear_button();
    }
  } else if (Want_UP() && (Player.dir != UP)) {
    if (!wall_above_player()) {
      Player.dir = UP;
      Player.sdx =   (Player.dx = dx[Player.dir]) * PUCK_SPEED;
      Player.sdy = -((Player.dy = dy[Player.dir]) * PUCK_SPEED);
      clear_button();
    }
  } else if (Want_DOWN() && (Player.dir != DOWN)) {
    if (!wall_below_player()) {
      Player.dir = DOWN;
      Player.sdx =   (Player.dx = dx[Player.dir]) * PUCK_SPEED;
      Player.sdy = -((Player.dy = dy[Player.dir]) * PUCK_SPEED);
      clear_button();
    }
  }

  // no joystick change of direction requested but we could still be crashing into a wall...
    
  if (Player.dir == LEFT) {
    if (wall_to_left_of_player()) {
      Player.dir = STUCK; Player.sdy = Player.sdx = Player.dy = Player.dx = 0;
    }
  } else if (Player.dir == RIGHT) {
    if (wall_to_right_of_player()) {
      Player.dir = STUCK; Player.sdy = Player.sdx = Player.dy = Player.dx = 0;
    }
  } else if (Player.dir == UP) {
    if (wall_above_player()) {
      Player.dir = STUCK; Player.sdy = Player.sdx = Player.dy = Player.dx = 0;
    }
  } else if (Player.dir == DOWN) {
    if (wall_below_player()) {
      Player.dir = STUCK; Player.sdy = Player.sdx = Player.dy = Player.dx = 0;
    }
  }

  // Default is to leave dir/sdx/sdy/dx/dy as they were and allow Puck to move.

}

// EXECUTE THE SIMPLE MOVE WHICH *SHOULD* ALREADY BE VALID AND EXPECTED TO SUCCEED:
static void move_puck(void) {
  if (Player.sdx && (!alignedPlayerX() || !wall_to_left_or_right_of_player(Player.dx))) {
    // (can safely use .dx instead of .sdx because we know we're aligned in X)
    // (and we *shouldn't* hit a wall...)
    Player.screen.X += Player.sdx; // Move sprite by screen amount
    // whether we moved into a new square or not depends on updated screen coordinate:
    Player.rowcol.col = Vec2MapCol(Player.screen.X);
    //if (Player.sdx < 0) {
    if (Player.dir == LEFT) {
      if (Player.rowcol.col == 0) {
        Player.rowcol.col = MAZECOLS28 - 1;  // wrap around
        Player.screen.X = Map2VecCol(Player.rowcol.col);
      }
      //} else if (Player.sdx > 0) {
    } else if (Player.dir == RIGHT) {
      if (Player.rowcol.col == MAZECOLS28 - 1) {
        Player.rowcol.col = 1;                    // wrap around
        Player.screen.X = Map2VecCol(Player.rowcol.col);
      }
    }
  } else if (Player.sdy && (!alignedPlayerY() || !wall_above_or_below_player(Player.dy))) {
    // Y move should be possible without checking for walls which should have been done earlier.
    Player.screen.Y += Player.sdy;
  }
  Player.rowcol.row = Vec2MapRow(Player.screen.Y);
  Player.rowcol.col = Vec2MapCol(Player.screen.X);
}


static void determine_new_ghost_targets(void) { // for all ghosts

  // ONLY RECALCULATE GHOST TARGETS WHEN PUCK IS AT A JUNCTION (DECISION POINT)...

  static uint8_t next_target; // only a static for speed. value not remembered between calls
  uint8_t t1, t2;
  
  next_target = 0;

#define add_target(r, c)                                                        \
    do { /* Find other end of adjacent corridor */                              \
         /* (eliminate the end which is us) */                                  \
      t1 = end1[mapno][decode_corridor_code(WM[r][c])];                         \
      t2 = end2[mapno][decode_corridor_code(WM[r][c])];                         \
      if (t1 == WM[Player.rowcol.row][Player.rowcol.col]) t1 = t2;              \
      if (next_target < 4) Target[next_target++] = junction_code_to_index(t1);  \
    } while (0)

  // decision tiles never on col 0 or 27 so wraparound tests not required.
  if (iscorridor(WM[Player.rowcol.row][Player.rowcol.col - 1]))
    add_target(Player.rowcol.row, Player.rowcol.col - 1);
  if (iscorridor(WM[Player.rowcol.row][Player.rowcol.col + 1]))
    add_target(Player.rowcol.row, Player.rowcol.col + 1);
  if (iscorridor(WM[Player.rowcol.row - 1][Player.rowcol.col]))
    add_target(Player.rowcol.row - 1, Player.rowcol.col);
  if (iscorridor(WM[Player.rowcol.row + 1][Player.rowcol.col]))
    add_target(Player.rowcol.row + 1, Player.rowcol.col);
#undef add_target

  // By now we should have 3 (or perhaps 4) targets.
  // One of them is behind us.  The others are in front of us.
  // We ought to be able to identify the one behind us using Player.dir
  // coupled with the known direction that the adjacent square is in.
  //
  // We should now look at the two junctions in front of Puck and select the
  // one that is closest, and locate two more junctions going down that
  // corridor to replace it and to set up as the missing Target[3].
  //
  // However until I write that far more complex code, I'm just going to take
  // the first available corridor and duplicate it, so that two ghosts will
  // be heading for the same target.  And this actually works quite well.
  
  while (next_target < 4) {
    Target[next_target++] = Target[0];  // Meh. Not what I had planned but surprisingly it works quite well.
  }

  // Having identified 4 targets, assign each one to a ghost ... intelligently, using the 4x4 matrix of distance from
  // target to ghost.

#ifdef SMARTGHOSTS  /* (which is not currently being done) */
#ifdef LINUX
  for (int8_t i = 0; i < 4; i++) {
    mvwprintw(debugwin, 12 + i, 1, "Before: Target[%d] = %c  ", i, junction_index_to_code(Target[i]));
  }
#endif // linux

  // By this point we have our targets - this code is only to assign a specific target to each ghost,
  // so if it is too expensive at runtime it *could* be skipped.
  constructDistanceMatrix(Target);  // build a matrix of distances from each ghost to each target.
  // (If not the actual ghost position, then the next junction that the ghost will hit as it carries on in its current direction)
  // The target locations are also junctions, by definition, so calculating the distance is a simple lookup of junction vs junction
  // ... and we have a precomputed table for that! :-)
  evaluateBestAssignment(balanced);

  printAssignmentsAndDistances();
  reassignTargets();

#ifdef LINUX
  for (int8_t i = 0; i < 4; i++) {
    mvwprintw(debugwin, 12 + i, 26, "After: Target[%d] = %c  ", i, junction_index_to_code(Target[i]));
  }
#endif // linux

#endif // smartghosts. Which turn out to not be needed...
  
  for (ghostno = 0; ghostno < 4; ghostno++) {
    Ghost[ghostno].target_junction = Target[ghostno];
  }

  // End of calculation of ghost targets
}

static inline void detect_if_corner_turn_needed(void) {
  // This can be better optimised but since there was originally a nasty bug in this
  // code, it's better to be verbose and be sure it works.  I can optimise later.

  // We update the stored *directions* but we don't actually
  // apply the move yet to rowcol or screen coordinates.
  
  static uint8_t newdir;
  static int8_t new_ghost_map_row, new_ghost_map_col;
  static bool blocked_left, blocked_right, blocked_above, blocked_below;

  newdir = G->dir; // continue in same direction if allowed.
  new_ghost_map_row = new_ghost_map_col = 0;
  blocked_left = blocked_right = blocked_above = blocked_below = FALSE;
  
  new_ghost_map_col = Vec2MapCol(G->screen.X + G->sdx);
  new_ghost_map_row = Vec2MapRow(G->screen.Y + G->sdy);

  if (new_ghost_map_col >= MAZECOLS28) {
    new_ghost_map_col = 1;
  } else if (new_ghost_map_col == 0) {
    new_ghost_map_col =  MAZECOLS28 - 1;
  }

  // Check going left
  //if (Ghost[ghostno].sdx < 0) {
  if (G->dir == LEFT) {
    // if going left, you cannot go left if a '#' square to your left no matter where in the square you are.
    // Even if when modifying screen.X appears to still be on the same square!

    if (wall_to_left_of_ghost()) blocked_left = TRUE;   // THESE TESTS MIGHT BE PROBLEMATICAL?
    if (WM[new_ghost_map_row][new_ghost_map_col] == '#') blocked_left = TRUE; 
    // we were moving horizontally.  look for a vertical exit
    if (WM[G->rowcol.row + 1][G->rowcol.col] != '#')
      newdir = DOWN;
    else if (WM[G->rowcol.row - 1][G->rowcol.col] != '#')
      newdir = UP;
  }

  // Check going right
  //else if (Ghost[ghostno].sdx > 0) {
  else if (G->dir == RIGHT) {
    if (wall_to_right_of_ghost()) blocked_right = TRUE; 
    // we were moving horizontally.  look for a vertical exit
    if (!wall_below_ghost())
      newdir = DOWN;
    else if (!wall_above_ghost())
      newdir = UP;
  }


  // Check going up
  //if (Ghost[ghostno].sdy > 0) {
  if (G->dir == UP) {
    if (wall_above_ghost()) blocked_above = TRUE;
    // we were moving vertically.  look for a horizontal exit
    if (!wall_to_right_of_ghost())
      newdir = RIGHT;
    else if (!wall_to_left_of_ghost())
      newdir = LEFT;
  }

  // Check going down
  //else if (Ghost[ghostno].sdy < 0) {
  else if (G->dir == DOWN) {
    if (wall_below_ghost()) blocked_below = TRUE;
    // we were moving vertically.  look for a horizontal exit
    if (!wall_to_right_of_ghost())
      newdir = RIGHT;
    else if (!wall_to_left_of_ghost())
      newdir = LEFT;
  }

  // If the ghost is not blocked by a wall, let it continue in the direction it was going
  if (!(blocked_left || blocked_right || blocked_above || blocked_below)) return;
  
  // Corner turn if necessary
  G->dir = newdir;
  G->sdy = -((G->dy = dy[newdir]) * GHOST_SPEED);
  G->sdx =   (G->dx = dx[newdir]) * GHOST_SPEED;
}

static void move_ghosts(void) { // Apply new position to internal coords.  Don't actually draw.
  for (ghostno = 0; ghostno < 4; ghostno++) {
    G = &Ghost[ghostno];

    // We move the ghosts by screen units.
    // (A LEFT or DOWN move of an aligned ghost may move it into another cell)

    G->screen.Y += G->sdy;
    G->screen.X += G->sdx;

    G->rowcol.col = Vec2MapCol(G->screen.X);
    G->rowcol.row = Vec2MapRow(G->screen.Y);
    if (G->rowcol.col >= MAZECOLS28) {
      G->screen.X = Map2VecCol(G->rowcol.col = 1);
    } else if (G->rowcol.col == 0) {
      G->screen.X = Map2VecCol(G->rowcol.col = MAZECOLS28 - 1);
    }
#ifdef CHECKS
    if (Ghost[ghostno].rowcol.row == 0) {
      crash("UNDERSHOT");
    } else if (Ghost[ghostno].rowcol.row >= MAZEROWS31-1) {
      debugf("ghost = %d  Y=%d X=%d  r=%d c=%c  dir=%s  sdy=%d sdx=%d  dy=%d dx=%d",
             ghostno+1, Ghost[ghostno].screen.Y, Ghost[ghostno].screen.X, Ghost[ghostno].rowcol.row, Ghost[ghostno].rowcol.col, name[Ghost[ghostno].dir],
             Ghost[ghostno].sdy, Ghost[ghostno].sdx, Ghost[ghostno].dy, Ghost[ghostno].dx);
      crash("OVERSHOT");
    }
#endif
  }  // end of loop over ghostno
}

#ifdef LINUX
static inline bool isjunction(char c);
static void handle_diagnostic_display(void) {
  int8_t y, x;
  uint8_t pillno;
  char ch;
  if (show_junctions && show_corridors) {
    wattron(mazewin, COLOR_PAIR(COLOR_RED));
    for (y = 0; y < MAZEROWS31; y++) {
      for (x = 0; x < MAZECOLS28; x++) {
        if (isjunction(ch = wallmap[mapno][y][x])) {
          pillno = dotmap[mapno][y][x];
          if (is_eaten(pillno)) {
            mvwprintw(mazewin, y, x * 2, "%c", ch);
          } else {
            wattron(mazewin, A_BOLD /* | A_UNDERLINE | A_STANDOUT */);
            mvwprintw(mazewin, y, x * 2, "%c", ch);
            wattroff(mazewin, A_BOLD /* | A_UNDERLINE | A_STANDOUT */);
          }
        }
      }
    }
    wattroff(mazewin, COLOR_PAIR(COLOR_RED));
    wattron(mazewin, COLOR_PAIR(COLOR_GREEN));
    for (y = 0; y < MAZEROWS31; y++) {
      for (x = 0; x < MAZECOLS28; x++) {
        if (iscorridor(ch = wallmap[mapno][y][x])) {
          ch = reencode_corridor_index(decode_corridor_code(ch)); // *VERY* TEMP HACK TO TEST
          pillno = dotmap[mapno][y][x];
          if (is_eaten(pillno)) {
            mvwprintw(mazewin, y, x * 2, "%c", ch);
          } else {
            wattron(mazewin, A_BOLD /* | A_UNDERLINE | A_STANDOUT */);
            mvwprintw(mazewin, y, x * 2, "%c", ch);
            wattroff(mazewin, A_BOLD /* | A_UNDERLINE | A_STANDOUT */);
          }
        }
      }
    }
    wattroff(mazewin, COLOR_PAIR(COLOR_GREEN));
  } else if (show_junctions) {
    wattron(mazewin, COLOR_PAIR(COLOR_RED));
    wattron(mazewin, A_BOLD /* | A_UNDERLINE | A_STANDOUT */);
    for (y = 0; y < MAZEROWS31; y++) {
      for (x = 0; x < MAZECOLS28; x++) {
        if (isjunction(ch = wallmap[mapno][y][x])) {
          mvwprintw(mazewin, y, x * 2, "%c", ch);
        }
      }
    }
    wattroff(mazewin, COLOR_PAIR(COLOR_RED));
    wattroff(mazewin, A_BOLD /* | A_UNDERLINE | A_STANDOUT */);
    for (y = 0; y < MAZEROWS31; y++) {
      for (x = 0; x < MAZECOLS28; x++) {
        if (iscorridor(ch = wallmap[mapno][y][x])) {
          mvwprintw(mazewin, y, x * 2, " ");  // could show both using different colours!
        }
      }
    }
    //show_junctions = false;
  } else if (show_corridors) {
    wattron(mazewin, COLOR_PAIR(COLOR_GREEN));
    for (y = 0; y < MAZEROWS31; y++) {
      for (x = 0; x < MAZECOLS28; x++) {
        if (iscorridor(ch = wallmap[mapno][y][x])) {
          ch = reencode_corridor_index(decode_corridor_code(ch)); // *VERY* TEMP HACK TO TEST
          mvwprintw(mazewin, y, x * 2, "%c", ch);
        }
      }
    }
    wattroff(mazewin, COLOR_PAIR(COLOR_GREEN));
    for (y = 0; y < MAZEROWS31; y++) {
      for (x = 0; x < MAZECOLS28; x++) {
        if (isjunction(ch = wallmap[mapno][y][x])) {
          mvwprintw(mazewin, y, x * 2, " ");
        }
      }
    }
    //show_corridors = false;
  } else if (redraw_maze) {
    for (y = 0; y < MAZEROWS31; y++) {
      for (x = 0; x < MAZECOLS28; x++) {
        if (iscorridor(ch = wallmap[mapno][y][x])) {
          mvwprintw(mazewin, y, x * 2, " ");  // could show both using different colours!
        }
      }
    }
    for (y = 0; y < MAZEROWS31; y++) {
      for (x = 0; x < MAZECOLS28; x++) {
        if (isjunction(ch = wallmap[mapno][y][x])) {
          mvwprintw(mazewin, y, x * 2, " ");
        }
      }
    }
    redraw_maze = FALSE;
    Draw_Dots();
  }
}
#endif

// static uint8_t dotScale = 125; // moved to utils.h
extern void myMovetod_open(uint16_t yx);
extern void myMovetod_close(void);

#ifdef VECTREX
extern void DrawGhost0(void);
extern void DrawGhost1(void);
extern void DrawGhost2(void);
extern void DrawGhost3(void);
#else
static void DrawGhost(void) {  // GY[G],GX[G] are center of character to be drawn
  wattron(mazewin, COLOR_PAIR(COLOR_CYAN));
  wattron(mazewin, A_BOLD /* | A_UNDERLINE | A_STANDOUT */);
  if (ghostno == 0) {
    mvwprintw(mazewin, Vec2CursesRow(Ghost[ghostno].screen.Y), Vec2CursesCol(Ghost[ghostno].screen.X), "𐌢" /*"🞅"*/);
  } else if (ghostno == 1) {
    mvwprintw(mazewin, Vec2CursesRow(Ghost[ghostno].screen.Y), Vec2CursesCol(Ghost[ghostno].screen.X), "+" /*"𐌢"*/);
  } else if (ghostno == 2) {
    mvwprintw(mazewin, Vec2CursesRow(Ghost[ghostno].screen.Y), Vec2CursesCol(Ghost[ghostno].screen.X), "△");
  } else /* if (ghostno == 3) */ {
    mvwprintw(mazewin, Vec2CursesRow(Ghost[ghostno].screen.Y), Vec2CursesCol(Ghost[ghostno].screen.X), "▭");
  }
  wattroff(mazewin, A_BOLD /* | A_UNDERLINE | A_STANDOUT */);
  wattroff(mazewin, COLOR_PAIR(COLOR_CYAN));
}
#endif


static void DrawGhosts(void) {  // GY[G],GX[G] are center of character to be drawn
#ifdef LINUX
  uint8_t saved = ghostno;
  for (ghostno = 0; ghostno <= 3; ghostno++) DrawGhost();
  ghostno = saved;
#else
  if (PUCK_SPEED == 1) Intensity(0x7F); else Intensity(0x5F);
    
  // MALBAN and Peer say that using pointers and offsets as below give faster
  // code that the safer C constructs.  I need to look at the generated code and
  // confirm that, because this kind of thing can lead to undetected errors for
  // example if the structure details are changed.
  int *p = (int *)Ghost;
    
  //  Reset0Ref_D0();
  set_scale(spriteMoveScale);
  Moveto_d(spriteYoffset+(int8_t)*p, spriteXoffset+(int8_t)*(p+1)); p += sizeof(GHOST);
  DrawGhost0(); 
  
  set_scale(spriteMoveScale);
  Moveto_d(spriteYoffset+(int8_t)*p, spriteXoffset+(int8_t)*(p+1)); p += sizeof(GHOST);
  DrawGhost1();
  
  set_scale(spriteMoveScale);
  Moveto_d(spriteYoffset+(int8_t)*p, spriteXoffset+(int8_t)*(p+1)); p += sizeof(GHOST);
  DrawGhost2();
  
  set_scale(spriteMoveScale);
  Moveto_d(spriteYoffset+(int8_t)*p, spriteXoffset+(int8_t)*(p+1)); p += sizeof(GHOST);
  DrawGhost3();
#endif
}


#ifdef VECTREX
extern void DrawMaze1(void);
extern void DrawMaze2(void);
extern void DrawMaze3(void);
extern void DrawMaze4(void);

static void (*DrawMaze[4])(void) = { &DrawMaze1, &DrawMaze2, &DrawMaze3, &DrawMaze4 };

// Maze calibration parameters:
extern int8_t yOffset;
extern int8_t xOffset;
extern int8_t calibrationValue;

// Sprite calibration parameters: (initial values OK for Vide)
static int8_t spriteYoffset = -9;
static int8_t spriteXoffset = 3;
static uint8_t spriteMoveScale = 132;


#ifndef NO_DS
typedef struct SaveToDS {

  int8_t _yOffset;           // -128:127
  int8_t _xOffset;           // -128:127
  int8_t _calibrationValue;  // -128:127
  int8_t _spriteYoffset;     //  -15:15 
  int8_t _spriteXoffset;     //  -23:31  
  uint8_t _spriteMoveScale;  //   65:167
  uint8_t _dotScale;         //  100:150 
  int8_t checksum; // last byte

} SaveToDS;

SaveToDS persistentData;

int writeToDS2431_8(int16_t adr); // or void * ?
int readToDS2431_8(int16_t adr);

void saveCalibration(void) {
  persistentData._yOffset = yOffset;
  persistentData._xOffset = xOffset;
  persistentData._calibrationValue = calibrationValue;
  persistentData._spriteYoffset = spriteYoffset;
  persistentData._spriteXoffset = spriteXoffset;
  persistentData._spriteMoveScale = spriteMoveScale;
  persistentData._dotScale = dotScale;
  // just do it regardless of whether it failed on intialization or not:
  writeToDS2431_8((int16_t)&persistentData);
}

bool loadCalibration(void) {
  // a failure is either DS2431 not present or data not initialised.
  if (readToDS2431_8((int16_t)&persistentData) == 0) return FALSE;
  // Since there is no room for an 'initialised' flag or a magic number,
  // we'll do a sanity check on the data values instead.
  if (persistentData._spriteYoffset < -15 ||
      persistentData._spriteYoffset > 15) return FALSE;         //  -15:15 
  if (persistentData._spriteXoffset < -23 ||
      persistentData._spriteXoffset > 31) return FALSE;         //  -23:31  
  if (persistentData._spriteMoveScale < 65U ||
      persistentData._spriteMoveScale > 167U)  return FALSE;  //   65:167
  if (persistentData._dotScale < 100U ||
      persistentData._dotScale > 150U) return FALSE;                //  100:150 
  yOffset = persistentData._yOffset;
  xOffset = persistentData._xOffset;
  calibrationValue = persistentData._calibrationValue;
  spriteYoffset = persistentData._spriteYoffset;
  spriteXoffset = persistentData._spriteXoffset;
  spriteMoveScale = persistentData._spriteMoveScale;
  dotScale = persistentData._dotScale;
  return TRUE;
}
#endif // DS

static void set_corner_markers(void) {
  pp0y = Map2VecRow(1)+spriteYoffset;
  pp0x = Map2VecCol(1)+spriteXoffset;
  pp1y = Map2VecRow(1)+spriteYoffset;
  pp1x = Map2VecCol(26)+spriteXoffset;
  pp2y = Map2VecRow(29)+spriteYoffset;
  pp2x = Map2VecCol(1)+spriteXoffset;
  pp3y = Map2VecRow(29)+spriteYoffset;
  pp3x = Map2VecCol(26)+spriteXoffset;
}

extern void DrawPill1(void);
static void determine_ghost_directions(void);

static void calibrate(void) {
  // for the moment I won't try to mirror the calibration code on Linux
  // but may do so later if there's any suggestion that something in here is
  // related to the end-of-level-2 bug.
  int state = 0;

  init_power_pills(); // set up dots
  init_ghosts(); // we'll set them in a loop...
  
  frame = (uint8_t)-1;
  set_corner_markers(); // override power pills with markers in the corners

  while (1) {
    Wait_Recal();
    Intensity(0x7E);
    Reset0Ref();
    check_buttons();
    Joy_Digital();

    if (button_1_4_held()) {        // B4: done.
      while (button_1_4_held()) check_buttons();
      break; // exit calibration
    }

    if ((frame&3) == 0) {
#ifndef NO_MAZE_CYCLING
      if (button_1_3_held()) {        // B3: next map
        while (button_1_3_held()) check_buttons();
        assign_mapno((mapno+1) & 3);
        // The 'change map' code is buggy and the 'obvious' fix did not fix it.
        // Easiest solution is just to remove the option altogether.
      } else
#endif
#ifdef NO_DOT_TOGGLE
      if (button_1_2_held()) { // B2: toggle drawing dots
        while (button_1_2_held()) check_buttons();
        // This isn't needed now either.  It originally drew *all* the dots, statically,
        // which of course was extremely slow, so not something you would want on all the time.
      } else
#endif
      if (button_1_1_held()) { // B1: next calibration step
        while (button_1_1_held()) check_buttons();
        state = (state+1)%5;          // 0..4
      }
    }

    // 5 calibration steps are rather much but unless I revert to using
    // both the joystick and the buttons in the same step, I don't see
    // a way to take fewer steps.  My preference is that the joystick
    // is only ever used for x,y positioning, and B1, B2 are used to
    // decrease/increase any sizes respectively.
    
    // However it is tempting to merge the scale/align dots procedures,
    // as well as the scale/align sprite procedures, bringing the
    // calibration process down to only 3 steps.
    
    if (state == 0) {
      Print_Str_d(127, -120, "<- STRAIGHTEN MAZE ->\x80");
      
      if ((joystick_1_x() > 0) )    calibrationValue++;
      if ((joystick_1_x() < 0) )    calibrationValue--;
      if (calibrationValue ==  127) calibrationValue =  126;
      if (calibrationValue == -128) calibrationValue = -127;

    } else if (state == 1) {
      Print_Str_d(127, -80, "SCALE DOTS <>\x80");

      if      ((joystick_1_x() > 0) )     dotScale++;
      else if ((joystick_1_x() < 0) )     dotScale--;

      if (dotScale ==  99) dotScale = 100;
      if (dotScale == 151) dotScale = 150;

      // numdebug('D', dotScale, 4, -39);  // TEMPORARY debug value of DotScale

    } else if (state == 2) {
      Print_Str_d(127, -96, "ALIGN TO DOTS <>ac\x80");
      
      if      ((joystick_1_x() > 0))     xOffset++;
      else if ((joystick_1_x() < 0))     xOffset--;
      if      ((joystick_1_y() < 0))     yOffset--;
      else if ((joystick_1_y() > 0))     yOffset++;
        
      if (yOffset ==  127) yOffset =  126;
      else if (yOffset == -128) yOffset = -127;
      if (xOffset ==  127) xOffset =  126;
      else if (xOffset == -128) xOffset = -127;
      
    } else if (state == 3) {
      Print_Str_d(127, -100, "SCALE SPRITES <>\x80");

      if      ((joystick_1_x() > 0) )     spriteMoveScale++;
      else if ((joystick_1_x() < 0) )     spriteMoveScale--;
        
      if (spriteMoveScale ==  64) spriteMoveScale =  65;
      else if (spriteMoveScale == 168) spriteMoveScale = 167;

    } else if (state == 4) {
      Print_Str_d(127, -100, "ALIGN SPRITES <>ac\x80");

      if      ((joystick_1_x() > 0) )     spriteXoffset++;
      else if ((joystick_1_x() < 0) )     spriteXoffset--;
      if      ((joystick_1_y() < 0) )     spriteYoffset--;
      else if ((joystick_1_y() > 0) )     spriteYoffset++;
      
      if (spriteYoffset <= -16) spriteYoffset = -15;
      else if (spriteYoffset >=  16) spriteYoffset =  15;
      if (spriteXoffset <= -24) spriteXoffset = -23;
      else if (spriteXoffset >=  32) spriteXoffset =  31;

    }
    
    set_corner_markers(); // pick up new scale if changed
    
    Reset0Ref_D0();
    Intensity(0x7F);
    DrawMaze[mapno]();
    set_scale(spriteMoveScale); Moveto_d(pp0y,pp0x); DrawPill1();
    set_scale(spriteMoveScale); Moveto_d(pp1y,pp1x); DrawPill1();
    set_scale(spriteMoveScale); Moveto_d(pp2y,pp2x); DrawPill1();
    set_scale(spriteMoveScale); Moveto_d(pp3y,pp3x); DrawPill1();
    Intensity(0x7E);
    DrawGhosts();
    Intensity(0x7F);
    Draw_Dots();

#ifdef NO_MAZE_CYCLING
    Print_Str_d( -127, -100, "B1 NEXT    B4 EXIT\x80");
#else
    Print_Str_d( -127, -127, "B1 NEXT\x80");
    Print_Str_d( -127,  -40, "B3 MAZE\x80");
    Print_Str_d( -127,  +50, "B4 EXIT\x80");
#endif    
    determine_ghost_directions();
    move_ghosts();
    
    frame += 1;
  }
#ifndef NO_DS
  saveCalibration();
#endif
  frame = (uint8_t)-1;
  assign_mapno((level+1) & 3); // restore current map to match current level
  init_power_pills();
}

#endif // VECTREX


//  #####   ######   ######   ###  #######  #######   #####
// #     #  #     #  #     #   #      #     #        #     #
// #        #     #  #     #   #      #     #        #        ##
//  #####   ######   ######    #      #     #####     #####   ##
//       #  #        #   #     #      #     #              #
// #     #  #        #    #    #      #     #        #     #  ##
//  #####   #        #     #  ###     #     #######   #####   ##



static inline void DrawPuck(void) {
#ifdef LINUX
  wattron(mazewin, COLOR_PAIR(COLOR_YELLOW));
  wattron(mazewin, A_BOLD /* | A_UNDERLINE | A_STANDOUT */);
  mvwprintw(mazewin, Vec2CursesRow(Player.screen.Y), Vec2CursesCol(Player.screen.X), "@" /* "😀" */);
  wattroff(mazewin, A_BOLD /* | A_UNDERLINE | A_STANDOUT */);
  wattroff(mazewin, COLOR_PAIR(COLOR_YELLOW));
#else
  extern void DrawPP(void);
  // Reset0Ref_D0();
  set_scale(spriteMoveScale);
  Moveto_d(Player.screen.Y+spriteYoffset, Player.screen.X+spriteXoffset);
  DrawPP(); // Badly named.  Not a Power Pill.  This is 'Puck'.  Update the .s file.  TO DO
#endif
}


static inline void DrawPowerPills(void) {
  Intensity(frame & 16U ? 0x00U : 0x7FU); /* flash */
#ifdef LINUX
  for (int8_t PP = 0; PP < 4; PP++)
    if (PState[PP] == UNEATEN) mvwprintw(mazewin, PPy[mapno][PP], 2 * PPx[PP], frame & 16 ? "●" : "○");
#else
  if (PState[0] == UNEATEN) { set_scale(spriteMoveScale); Moveto_d(pp0y,pp0x); DrawPill1(); }
  if (PState[1] == UNEATEN) { set_scale(spriteMoveScale); Moveto_d(pp1y,pp1x); DrawPill1(); }
  if (PState[2] == UNEATEN) { set_scale(spriteMoveScale); Moveto_d(pp2y,pp2x); DrawPill1(); }
  if (PState[3] == UNEATEN) { set_scale(spriteMoveScale); Moveto_d(pp3y,pp3x); DrawPill1(); }
#endif
  Intensity(0x7F);
}


static void Draw_current_state(void) {

#ifdef VECTREX
      DrawPuck(); // wobbles if drawn later.
      DrawMaze[mapno]();
#else
      for (int8_t row = 0; row < MAZEROWS31; row++)
        mvwprintw(mazewin, row, 0, "%s", UTFBoard[mapno][row]);
#endif

#ifdef LINUX
      Draw_Dots(); // draw first on Linux, last on Vectrex
      handle_diagnostic_display(); // 'c' and 'j' options to show corridors and junctions.
#endif

      DrawGhosts();

      DrawPowerPills();

#ifdef VECTREX
      Draw_Dots();
#endif

#ifdef LINUX
      DrawPuck(); // draw on top of everything else, specifically above dots
      wrefresh(mazewin);
#endif

}

static bool Poll_buttons_and_joystick(void) { // return FALSE if new level wanted (no longer done)
//recheck: clear_button();   // part of single-stepping hack
 
#ifdef VECTREX
  uint8_t b;
  check_buttons();   // Actual hardware buttons, not keypresses

  // We should be able to speed up Joy_Digital by setting a lower resolution...
  
  Joy_Digital(); // check during idle time on Vectrex if there is any...
  if      ((joystick_1_x() < 0) /* || (joystick_2_x() < 0) */) button = 'Z';
  else if ((joystick_1_x() > 0) /* || (joystick_2_x() > 0) */) button = 'X';
  if      ((joystick_1_y() < 0) /* || (joystick_2_y() < 0) */) button = '/';
  else if ((joystick_1_y() > 0) /* || (joystick_2_y() > 0) */) button = '\'';

  // I may put B4 back, to skip to the next map while testing.
  if ((b=(buttons_held()&0xF)) != 0) {
    //if (b == 0xF) {
    //  reboot_wanted = TRUE;  // All 4 buttons cause reboot.
    //  return FALSE;
    //}
    // any other button puts it in to calibration mode.
    while ((buttons_held()&0xF) != 0) check_buttons() /* wait - debounce */;
    // ^ we don't want any buttons to be being held down when calibration starts
    calibrate();
    return FALSE;
  }
#else
  int ch;
  do {
    ch = getch(); // a polling read.  0 or -1 (not sure which) means no data available.
    if (ch > 0) button = ch;
  } while (ch > 0);  // drain typeahead

  if ('a' <= button && button <= 'z') button = button - 'a' + 'A'; // RETURN UPPER CASE LETTERS

  // These conversions are primarily so that the cursor keys can be reported in the debug display:
  if ((button == KEY_LEFT) || (button == ('d'&31))) button = 'Z';
  else if ((button == KEY_RIGHT) || (button == ('e'&31))) button = 'X';
  else if ((button == KEY_UP) || (button == ('c'&31))) button = '\'';
  else if ((button == KEY_DOWN) || (button == ('b'&31))) button = '/';

  if (button == 'Z' || button == 'X' || button == '\'' || button == '/') {
    // movement keys
  } else if (button == 'Q' || button == KEY_ESC) {
    // ^C on Linux works too, by calling an exit handler to clean up the display settings.
    box(scrollwin, 0, 0);
    wrefresh(scrollwin);
    wrefresh(mazewin);
    wrefresh(debugwin);
    endwin();  // Fortunately ncurses traps ^C and cleans up anyway.
    exit(0);
  } else if (button == 'J') { // junctions
    show_junctions = TRUE;
    show_corridors = FALSE;
  } else if (button == 'C') { // corridors
    show_junctions = FALSE;
    show_corridors = TRUE;
  } else if (button == 'B') { // both
    show_junctions = TRUE;
    show_corridors = TRUE;
  } else if (button == ('L'&31) || button == 'N') { // neither
    show_junctions = FALSE;
    show_corridors = FALSE;
    redraw_maze = TRUE;
  } else if (button == 'I') {
    INVINCIBLE = !INVINCIBLE;
  } else if (button == 'P') {
    char ch;
    // immediately after drawing everything is the best place to pause:
    do {
      ch = getch();
      usleep(100000);
    } while (ch == 'p' || ch == 'P' || ch <= 0);
    button = 'P';
  } else if (button == 'S') {
    // Single step (or Skip) - advance to the next decision point.  TO DO.
  } else if (button == ' ') {
  } else {
    // Crude hack to allow single-stepping on Linux
    //usleep(10000);
    //goto recheck;
    clear_button();
  }
#endif
  return TRUE;
}

static inline void Wait_for_vsync(void) {
  framesync();
  Intensity(0x7F);
}

static inline void check_state_timers(void) {
  if (Puck_Speed_Timer != 0) {
    Puck_Speed_Timer -= 1;
    if (Puck_Speed_Timer == 0) {
      PUCK_SPEED = 1;
      // These X and Y corrections may look stupid but they actually essential because
      // a misaligned position can cause Puck to be 1 unit into a wall.  After a speed
      // change, Puck must be aligned to the same grid as SPEED, i.e. a speed of 2
      // means Puck must end up on a multiple of 2 relative to the base grid of 8.

      // If Puck were to only eat a power pill when fully aligned on the square containing it,
      // we would not have to do these corrections.  TO DO.
      
      if ((Player.screen.X > 0) && (Player.screen.X&1)) Player.screen.X -= 1;
      if ((Player.screen.X < 0) && (Player.screen.X&1)) Player.screen.X += 1;
      if ((Player.screen.Y > 0) && (Player.screen.Y&1)) Player.screen.Y -= 1;
      if ((Player.screen.Y < 0) && (Player.screen.Y&1)) Player.screen.Y += 1;
      Player.sdx =   Player.dx * PUCK_SPEED;
      Player.sdy = -(Player.dy * PUCK_SPEED);
    }
  }
}

static inline bool isjunction(char c) {
  // junctions are all alpha - it's only corridors that have too many elements to store only as alpha
  // c |= 32; return ('a' <= c) && (c <= 'z'); // Is this a safe trick to use?
  return (('a' <= c) && (c <= 'z')) || (('A' <= c) && (c <= 'Z')); // Is it safe to use "(c | 32)" ?
}

static inline bool alignedGhostX(void) {  // X is a Vectrex coordinate.
  return (G->screen.X & 7) == 0;
}

static inline bool alignedGhostY(void) {  // Y is a Vectrex coordinate
  return (G->screen.Y & 7) == 0;
}

static inline uint8_t junction_code_to_index(char code) {
  CrashIf(!isjunction(code), "BADJ");
  
  if (('A' <= code) && (code <= 'Z')) {
    return code - 'A';
  } else if (('a' <= code) && (code <= 'z')) {
    return code - 'a' + ('z'-'a'+1);
#ifdef CHECKS
  } else {
    debugf("junction code '%c' out of expected range\n", code);
    crash("CODE");
    return 0;
#endif
  }
  return 0; // result;
}

static inline uint8_t junction_dist_and_dir(uint8_t mapno, uint8_t j1_idx, uint8_t j2_idx) {
  uint8_t lim = jsize[mapno];
  uint16_t calculated_size = (uint16_t)lim*(uint16_t)lim;

  CrashIf((j1_idx >= lim) || (j2_idx >= lim), "JDIST");
  CrashIf((mapno == 0) && (sizeof(junction_distance0) != calculated_size), "JD0");
  CrashIf((mapno == 1) && (sizeof(junction_distance1) != calculated_size), "JD1");
  CrashIf((mapno == 2) && (sizeof(junction_distance2) != calculated_size), "JD2");
  CrashIf((mapno == 3) && (sizeof(junction_distance3) != calculated_size), "JD3");

  calculated_size = (uint16_t)j1_idx*(uint16_t)lim + (uint16_t)j2_idx;
  return junction_distance[mapno][calculated_size];
}

// MAZE WALLS, USED FOR LIMITING GHOST MOVEMENT

static inline bool wall_to_left_of_ghost(void) {
  if (G->rowcol.row >= MAZEROWS31-1) return TRUE;
  if (G->rowcol.col == 0) return FALSE; // tunnel, moving left
  return WM[G->rowcol.row][G->rowcol.col - 1] == '#';
}

static inline bool wall_to_right_of_ghost(void) {
  if (G->rowcol.row >= MAZEROWS31-1) return TRUE;
  if (G->rowcol.col == MAZECOLS28 - 1) return FALSE;  // tunnel, moving right
  return WM[G->rowcol.row][G->rowcol.col + 1] == '#';
}

static inline bool wall_below_ghost(void) {
  if (G->rowcol.row >= MAZEROWS31-2) return TRUE;
  return WM[G->rowcol.row + 1][G->rowcol.col] == '#';
  // increasing y is downwards in terms of cell indexes
}

static inline bool wall_above_ghost(void) {
  if (G->rowcol.row <= 1) return TRUE;
  return WM[G->rowcol.row - 1][G->rowcol.col] == '#';
  // decreasing y is upwards in terms of cell indexes
}


// Unpack a packed direction and distance

static inline uint8_t get_dist_from(uint8_t x) {
  return x&63U;
}
  
static inline uint8_t get_dir_from(uint8_t x) {
  x = x>>6U; // No need for &3 since it is now unsigned.

  CrashIf((x & (~3U)) != 0U, "NOT3");

  return x;
}

/*
          mapno = 0                      mapno = 1                      mapno = 2                      mapno = 3           
                                                                                                                            row 0
   ######  ##########  ######    #######  ##########  #######    #########  ####  #########     ###A####B########C####D###  row 1
   #    #  #        #  #    #          #  #        #  #          #       #  #  #  #       #     #  #    #        #    #  # 
   #    #  #        #  #    #          #  #        #  #          #       #  #  #  #       #     #  #    #        #    #  # 
   ##A##B##C##D##E##F##G##H##     #####A##B###  ###C##D#####     #  ###A#B##C  D##E#F###  #     #  #    #  ####  #    #  # 
     #  #     #  #     #  #       #       #  #  #  #       #     #  #  #    #  #    #  #  #     #  ##E##F  #  #  G##H##  # 
     #  #     #  #     #  #       #       #  #  #  #       #     ###G  #    #  #    #  H###     #    #  #  #  #  #  #    # 
     #  #     #  #     #  #       #  #####E  #  #  F#####  #        #  #    #  #    #  #        #    #  #  #  #  #  #    # 
  ###I  ###J###  ###K###  L###    #  #    #  ####  #    #  #        #  #I###J##K###L#  #        ##I###  ###J  K###  ###L## 
     #     #        #     #       #  #    #        #    #  #    #M##N   #          #   O##P#      #        #  #        #   
     #     #        #     #       ###G##  #        #  ##H###     #  #   #          #   #  #       #        #  #        #   
     M#####N########O#####P            #  I########J  #          #  ##Q#RS########TU#V##  #       M###  ###N##O###  ###P   
     #     #        #     #            #  #        #  #          #    #  #        #  #    #       #  #  #        #  #  #   
     #     #        #     #       #####K  #        #  L#####     #    #  #        #  #    #    ####  #  #        #  #  ####
     #  ###Q        R###  #       #    M##N        O##P    #     W##X##  #        #  ##Y##Z          Q##R        S##T      
     #  #  #        #  #  #       #    #  #        #  #    #     #  #    #        #    #  #          #  #        #  #      
     #  #  #        #  #  #       ###  #  #        #  #  ###     #  #    #        #    #  #    ####  #  #        #  #  ####
  ###S###  ###T##U###  ###V###      #  #  ##Q####R##  #  #       #  ##a##b##c##d##e##f##  #       #  #  ###U##V###  #  #   
     #        #  #        #         #  #    #    #    #  #       #    #     #  #     #    #       W##X     #  #     Y##Z   
     #        #  #        #         #  #    #    #    #  #       #    #     #  #     #    #       #  #     #  #     #  #   
     W#####X###  ###Y#####Z         S##T####U    V####W##X       ##g##h  ####  ####  i##j##       #  ###a##b  c##d###  #   
     #     #        #     #         #       #    #       #         #  #  #        #  #  #         #     #  #  #  #     #   
     #     #        #     #         #       #    #       #         #  #  #        #  #  #         #     #  #  #  #     #   
   ##a##b##c##d##e##f##g##h##    ###Y###  ##Z####a##  ###b###    ###  k##l##m##n##o##p  ###     ##e##f##g  ####  h##i##j## 
   #    #     #  #     #    #       #  #  #        #  #  #       #    #     #  #     #    #     #    #  #        #  #    # 
   #    #     #  #     #    #       #  #  #        #  #  #       #    #     #  #     #    #     #    #  #        #  #    # 
   #    #  ####  ####  #    #     ###  c##d###  ###e##f  ###     q####r  ####  ####  s####t     #  ###  ###k##l###  ###  # 
   #    #  #        #  #    #     #    #     #  #     #    #     #    #  #        #  #    #     #  #       #  #       #  # 
   #    #  #        #  #    #     #    #     #  #     #    #     #    #  #        #  #    #     #  #       #  #       #  # 
   #####i##j########k##l#####     #####g#####h##i#####j#####     ######  ##########  ######     ###m########  ########n###  row 29
                                                                                                                            row 30
  01                        22   01                        22   01                        22   01                        22
                            67                             67                             67                             67

 Proposal (not yet implemented) 'virtual' junction codes 'o-z' represent strategies to take when no explicit target is given:

     ghostno+'o'
  o  bisect 0
  p  bisect 1
  q  bisect 2
  r  bisect 3

     ghostno+'s'
  s  top left corner
  t  top right corner
  u  bottom left corner
  v  bottom right corner

     ghostno+'w'
  w  right/down/left/up
  x  left/down/right/up
  y  right/up/left/down
  z  left/up/right/down


void select_direction(void) {
  uint8_t jcode = Ghost[ghostno].target_junction;
  if ('o' <= jcode && jcode <= 'r') {
    // bisect
  } else if ('s' <= jcode && jcode <= 'v') {
    // scatter
  } else if ('w' <= jcode && jcode <= 'z') {
    // loop
  }
}

*/

static inline bool ghost_has_wall(uint8_t dir) {
  // lots of scope for optimisation but lets get everything working reliably first.
  if (dir == LEFT)  return wall_to_left_of_ghost();
  if (dir == RIGHT) return wall_to_right_of_ghost();
  if (dir == UP)    return wall_above_ghost();
  if (dir == DOWN)  return wall_below_ghost();
  return TRUE;
}


/*
GHOST *G;
const char* const* WM;

static void move_ghosts(void) { // Apply new position to internal coords.  Don't actually draw.
  for (ghostno = 0; ghostno < 4; ghostno++) {
  if (ghostno==0) G = &Ghost[0];
  else   if (ghostno==1) G = &Ghost[1];
  else   if (ghostno==2) G = &Ghost[2];
  else   G = &Ghost[3];
*/
static void determine_ghost_directions(void) { // Note, *directions*, not *targets*.

// moved from stac to ram -> faster access
static uint8_t sq_index;
static uint8_t sq_code;
static uint8_t jdistdir;
static uint8_t prevdir;
static uint8_t tmp_dist;
static int dirseq;
static uint8_t dir; 


  //ghostDirDetermined = 0;
  for (ghostno = 0; ghostno < 4; ghostno++) {
    G = &Ghost[ghostno];
    if (G->dir == STUCK) continue; // a stuck ghost will not be allowed to happen in the final code
    sq_code = WM[G->rowcol.row][G->rowcol.col];

    // If a ghost is aligned on his square:
    if (alignedGhostX() && alignedGhostY()) {
      if (isjunction(sq_code)) {
        //ghostDirDetermined = 1;
        // Get Ghosts current position as a junction index:
        sq_index = junction_code_to_index(sq_code);
        //debugf("sq code: '%c'  sq index: %d\n", sq_code, sq_index);
        // Look up the distance and direction from this intermediate junction to the target junction:
        jdistdir = junction_dist_and_dir(mapno, sq_index, G->target_junction);
        /*debugf("ghost %d: jdistdir(map %d, from_idx=%d ('%c'), to_idx=%d ('%c')) -> %02x\n",
               ghostno+1, mapno,
               sq_index, junction_index_to_code(sq_index),
               G->target_junction, junction_index_to_code(G->target_junction),
               jdistdir);*/      
        prevdir = G->dir;
        G->dir = get_dir_from(jdistdir);
        //debugf("changing ghost %d dir from %s to %s\n", ghostno+1, name[prevdir], name[G->dir]);
        if (
            ((tmp_dist=get_dist_from(jdistdir)) == 0) // We're already there.
            ||
            ((G->dir == reverse[prevdir]) && (!reversals_allowed)) // or recent change of target would have caused a reversal
           ) {
          //debugf("jdistdir: %02x -> dist=%d  dir=%d (%s)\n", jdistdir, tmp_dist, G->dir, name[G->dir]);
          const uint8_t individual[4][4] = {
            // ensure slightly different loops for each ghost:
            {LEFT, UP, RIGHT, DOWN}, // ghost 0
            {RIGHT, UP, LEFT, DOWN}, // ghost 1
            {LEFT, DOWN, RIGHT, UP}, // ghost 2
            {RIGHT, DOWN, LEFT, UP}, // ghost 3
          };
          for (dirseq = 0; dirseq < 4; dirseq++) { // One of the 4 directions has to work. All junctions have 3 or 4 exits.
            dir = individual[ghostno][dirseq];
            if ((dir != reverse[prevdir]) && !ghost_has_wall(dir)) {
              G->dir = dir; break;
            }
          }
        }
        G->sdy = -((G->dy = dy[G->dir]) * GHOST_SPEED);
        G->sdx =   (G->dx = dx[G->dir]) * GHOST_SPEED;
      } else if (iscorridor(sq_code)) { // Otherwise if the square is within a corridor:
        detect_if_corner_turn_needed();
#ifdef CHECKS
      } else {
        // has to be one or the other...
        debugf(" ghost %d: row = %d  col = %d\n", ghostno+1, G->rowcol.row, G->rowcol.col);
        crash("CORRUPT");
#endif
      }
    }
  }
  // At this point, the ghost dx can be safely applied by move_ghost
}


static inline void debug_actors(void) {
#ifdef LINUX
  for (ghostno = 0; ghostno < 4; ghostno++) {
    G = &Ghost[ghostno];
    uint8_t sq_code = wallmap[mapno][Ghost[ghostno].rowcol.row][Ghost[ghostno].rowcol.col];
    if (0) mvwprintw(debugwin, 5+ghostno, 40,
              "Ghost %d row/col = %4d,%4d  sy/sx = %4d%c,%4d%c  dir=%6s  sdy/sdx=%2d,%2d  my_j=%c  target_j=%c    ",
              ghostno+1,
              Ghost[ghostno].rowcol.row, Ghost[ghostno].rowcol.col,
              Ghost[ghostno].screen.Y,
              alignedGhostY() ? '!' : ' ',
              Ghost[ghostno].screen.X,
              alignedGhostX() ? '!' : ' ',
              name[Ghost[ghostno].dir],
              Ghost[ghostno].sdy, Ghost[ghostno].sdx,
              sq_code >= 0x80U ? ' ' : sq_code,
              junction_index_to_code(Ghost[ghostno].target_junction)
             );
  }
  
  if (0) mvwprintw(debugwin, 10, 40,
            "Puck  row/col = %4d,%4d  sy/sx = %4d%c,%4d%c  dir=%6s  sdy/sdx=%2d,%2d    ",
            Player.rowcol.row, Player.rowcol.col,
            Player.screen.Y,
            alignedPlayerY() ? '!' : ' ',
            Player.screen.X,
            alignedPlayerX() ? '!' : ' ',
            name[Player.dir],
            Player.sdy, Player.sdx
           );

  mvwprintw(debugwin, 1, console_width - 19, " Frame %3d", frame);
  mvwprintw(debugwin, 2, 1, "Button: %c    ", button <= 0 ? ' ' : button);
#endif
}

static inline void fadeout(void) {
#ifdef VECTREX
  for (frame = 127; frame > 0; frame--) {                          // one frame per loop
    Wait_for_vsync();
    Intensity(frame);
    DrawMaze[mapno]();
  } // next frame
#else
  sleep(1);
#endif
}


int main(void) {
  system_init();
  
  level = INIT_LEVEL; // -1 (255), 0, 1, 2 become 0, 1, 2, 3 on level initialisation below:
                      // I need to remove this '-1' hack.  There was a reason for it once but no longer (I think).
  assign_mapno((level+1) & 3); // for calibration

#ifdef VECTREX
  // default values work with Vide.  Actual hardware needs totally different values
  // which vary from machine to machine.  I've simplified the calibration process
  // as much as I can to make calibrating less of a burden.  Saving the calibration
  // in some form of backed up memory would be nice.
  calibrationValue = 18;
  yOffset = -8;
  xOffset = -18;
  WM = wallmap[mapno];

  // If calibration data present, just use it and skip the calibration stage.
  // If more calibration has to be done the user can press a button and do it again.
#ifdef NO_DS
  calibrate();
#else // DS
  if (!loadCalibration()) {
    calibrate();
  }
#endif // DS
#endif // VECTREX
  
  //  #       ######  #    #  ######  #               #        ####    ####   #####
  //  #       #       #    #  #       #               #       #    #  #    #  #    #  ##
  //  #       #####   #    #  #####   #               #       #    #  #    #  #    #  ##
  //  #       #       #    #  #       #               #       #    #  #    #  #####
  //  #       #        #  #   #       #               #       #    #  #    #  #       ##
  //  ######  ######    ##    ######  ######          ######   ####    ####   #       ##
   
  for (;;) {  // start of next level

    WM = wallmap[mapno];
    frame = (uint8_t)-1;

    level_complete = FALSE;
    Puck_Speed_Timer = 0; // timers expire when they count down to 0
    PUCK_SPEED = 1; /* GHOST_SPEED = 1; */  // 1 is normal, 2 is fast

    init_power_pills();
    init_ghosts();
    init_player_position();
    clear_button();

    // Fade previous level out here?  Or at least pause a little before drawing the new map?

    //  ######  #####     ##    #    #  ######          #        ####    ####   #####
    //  #       #    #   #  #   ##  ##  #               #       #    #  #    #  #    #  ##
    //  #####   #    #  #    #  # ## #  #####           #       #    #  #    #  #    #  ##
    //  #       #####   ######  #    #  #               #       #    #  #    #  #####
    //  #       #   #   #    #  #    #  #               #       #    #  #    #  #       ##
    //  #       #    #  #    #  #    #  ######          ######   ####    ####   #       ##

#ifdef LINUX
    // DEBUG:
    //show_junctions = TRUE;
#endif
    
    for (;;) {                          // one frame per loop
      
      Wait_for_vsync();
      determine_ghost_directions();

      // Apply the ghosts moves by updating their internal coordinates.
      // Don't draw them and don't bother checking to see if they're touching
      // Puck yet - we'll do that at the top of this loop on the next frame.
      move_ghosts();

      Draw_current_state(); debug_actors();

      if (!Poll_buttons_and_joystick()) break; // (will start calibration if any button held. B4 is best to bring it up.)
      
      check_state_timers();
      
      // is Puck touching a ghost?:
      //   Puck dies - restart this level
      //   .. or send ghost to jail if in Power Pill mode.
      if (puck_is_caught()) break;

      check_for_pill();

      // Was a change of direction requested via joystick?:
      //   change Puck's internal direction indicator
      if (alignedPlayerX() && alignedPlayerY()) { // Is Puck aligned to a square? (doesn't have to be a junction, could be a corner)
        determine_puck_direction(); // set up new direction for when next move is applied.

        if (isjunction(WM[Player.rowcol.row][Player.rowcol.col])) {
          determine_new_ghost_targets();  // The ghost will switch to the new target when it hits its next junction.
        }
        
      } /* else TO DO: only reversals allowed */
      
      // Apply Puck's movement to his internal position variables (but don't draw yet)
      // Testing frame&31 will make Puck marginally slower than the ghosts (by skipping 1 in 32 frames):
      if ((frame&31) != 0) move_puck();

      // Check to see if all pills eaten - if so, end this level
      if ( (pills_remaining | PState[0] | PState[1] | PState[2] | PState[3]) == 0 ) {
        level_complete = TRUE;
      }
        
      if (level_complete) break; // can also be set elsewhere than the statement immediately above
                                 // otherwise I would just 'break' above rather than setting level_complete...
      frame = frame + 1;
    } // next frame
    
    fadeout(); // fade out old level, not the new one.
    
    if (reboot_wanted) break;    

    if (level_complete) {
        level = level+1;
        assign_mapno((level+1) & 3); // select the map that corresponds to the next level
    }
    
  } // Go round again and set up the next level

  numdebug('X', 0, 4, -39);  // force numdebug to be used to keep Peer happy :-@
  return system_terminate();
}
