#include "types.h"
#include "memory.h"
extern UINT8 *sg_pu8RecompBase;
#include "graphics.h"
#include "mspacman.h"
//#define mem mem
#include <ncurses.h>


// Generates a MS Pac frame of 35 lines 55 columns at XL, YT with 0,0 at
// the top left corner of the terminal screen, i.e. the position after a
// clear screen.

// TO DO: Timing without SDL
//        ncurses keyboard input to replace SDL
//        pause key, single-frame-step key
//        colouring ncurses
//        ( some sound files at https://github.com/daleharvey/pacman/tree/master/audio if can't use vectrex chip )
//        Modified intermission scenes as 2x2 chars don't work:
/*

// BUG: leftmost digits of score missing when we hit 5 digits

     ,--.    ,--.          ,--.   ,--.
    |oo  | _  \  `.       | oo | |  oo|
o  o|~~  |(_) /   ;       | ~~ | |  ~~|o  o  o  o  o  o  o  o  o  o  o
    |/\/\|   '._,'        |/\/\| |/\/\|


     .-.   .-.     .--.                         
    | OO| | OO|   / _.-' .-.   .-.  .-.   .''.  
    |   | |   |   \  '-. '-'   '-'  '-'   '..'  
    '^^^' '^^^'    '--'                         


   ,##.                   ,==.
 ,#    #.                 \ o ',
#        #     _     _     \    \
#        #    (_)   (_)    /    ; 
 `#    #'                 /   .'  
   `##'                   "=="

  __        ___
 / o\      /o o\
|   <      |   |
 \__/      |,,,|

*/

/*
    ᗣ  ᗧ•••ᗣ•• ·····•····· ᗤ  👻   🍒,🍓,🍑,🍎,🍇,🚀,🔔,🔑

 */

//	4d9d	delay to update pacman movement
//		not 0xff - the game doesn't move pacman, but decrements instead
//		0x01	when eating pill
//		0x06	when eating big pill
//		0xff	when not eating a pill
//	4d9e	related to number of pills eaten before last pacman move
//	4e0e	number of pills eaten in this level
//	4e16-4e33 0x13 pill data entries. each bit means if a pill is there
//		or not (1=yes 0=no)
//		the pills start at upper right corner, go down, then left.
//		first pill is bit 7 of 4e16
//	4e34-4e37 big pills data entries

int debug_internal_state = 0;

// These are the calls to draw the screen using alternative graphics
// which can be implemented differently depending on the application.

// The initial purpose was for vector graphics for the Vectrex, but
// currently being used for a ncurses-based terminal interface.

extern void graphics_init(void) {
  /* Curses Initialisations */ 
  initscr();                  /* curses initialization */
  keypad(stdscr, TRUE);       /* enable KEY_UP/KEY_DOWN/KEY_RIGHT/KEY_LEFT */
  noecho();                   /* prevent displaying of other keys */
  nodelay(stdscr, TRUE);      /* return immediately from getch() */
  curs_set(0);                /* turn text cursor off */
  start_color();
  /*
#define COLOR_BLACK     0
#define COLOR_RED       1
#define COLOR_GREEN     2
#define COLOR_YELLOW    3
#define COLOR_BLUE      4
#define COLOR_MAGENTA   5
#define COLOR_CYAN      6
#define COLOR_WHITE     7
   */
  //                        Foreground     Background
  init_pair(COLOR_BLACK,    COLOR_BLACK,   COLOR_WHITE);
  init_pair(COLOR_RED,      COLOR_RED,     COLOR_RED/*BLACK*/); // setting foreground to background draws a blob
  // Turns out we have to use blobs to have any chance of keeping track
  // of the ghosts when playing.  The other chars are too small and not
  // noticeable.
  
  init_pair(COLOR_GREEN,    COLOR_GREEN,   COLOR_GREEN/*BLACK*/);
  init_color(COLOR_GREEN, 1000, 500, 500); // PINK! Will stay green if not supported
#define COLOR_PINK COLOR_GREEN
  /* param 1     : color name
   * param 2, 3, 4 : rgb content min = 0, max = 1000 */

  init_pair(COLOR_YELLOW,   COLOR_YELLOW,  COLOR_BLACK);
  init_pair(COLOR_BLUE,     COLOR_BLUE,    COLOR_BLUE/*BLACK*/);
  init_pair(COLOR_MAGENTA,  COLOR_MAGENTA, COLOR_MAGENTA/*BLACK*/);
  init_pair(COLOR_CYAN,     COLOR_CYAN,    COLOR_CYAN/*BLACK*/);
  init_pair(COLOR_WHITE,    COLOR_WHITE,   COLOR_BLACK);
  //map(0);
}

extern void graphics_terminate(void){
  endwin();
}

void graphics_trap(void) {  // Initialise table to optimise checking of writes
#define g0_y mem[0x4d00]
#define g0_x mem[0x4d01]
#define g0_o mem[0x4d2c]
#define g0_blue mem[0x4da7]
#define g0_feet mem[0x4c02]
#define g0_color mem[0x4c03]
#define g1_y mem[0x4d02]
#define g1_x mem[0x4d03]
#define g1_o mem[0x4d2d]
#define g1_blue mem[0x4da8]
#define g1_feet mem[0x4c04]
#define g1_color mem[0x4c05]
#define g2_y mem[0x4d04]
#define g2_x mem[0x4d05]
#define g2_o mem[0x4d2e]
#define g2_blue mem[0x4da9]
#define g2_feet mem[0x4c06]
#define g2_color mem[0x4c07]
#define g3_y mem[0x4d06]
#define g3_x mem[0x4d07]
#define g3_o mem[0x4d2f]
#define g3_blue mem[0x4daa]
#define g3_feet mem[0x4c08]
#define g3_color mem[0x4c09]
#define pac_y mem[0x4d08]
#define pac_x mem[0x4d09]
#define pac_n mem[0x4d0a]
#define pac_color mem[0x4d0b]
#define pac_o mem[0x4d30]
#define fruit_y mem[0x4dd2]
#define fruit_x mem[0x4dd3]
#define MAZE mem[0x4e13]
#define VBLANK mem[0x4c84]
#define CREDITS mem[0x4e6e]
#define Lives mem[0x4e6f]
#define LivesLeft mem[0x4e14]
#define LivesDisp mem[0x4e15]
#define Players mem[0x4e70]
#define S1_0 mem[0x4e80]
#define S1_1 mem[0x4e81]
#define S1_2 mem[0x4e82]
#define MPLAYERS mem[0x4e00]
#define PHASE mem[0x4ee0]

}

void graphics_monitor(UINT32 Address) {
  // This is called after writes to memory, so we don't need to pass in the
  // value explicitly Note that it is only called after indirect writes - we
  // still need to insert explicit calls in the generated code if it writes to
  // memory directly.

  // if (Address == 0x4c84) graphics_redraw();

  // See also http://donhodges.com/ms_pacman_bugs.htm

  // LED_state == 1 when the screen is static and asking for the
  // player to press the START button.  Otherwise 0 whether game
  // is started or not

  /*
     PACMAN SPRITE CODES

     00-07   fruits
     08-0D   naked ghosts for cutscenes
     0E-0F   empty
     10-1B   big pacman
     1C-1D   ghost in panic mode
     1E-1F   empty
     20-27   ghosts
     28-2B   points
     2C-2F   pacmans
     30      big pacman
     31      explosion
     32-33   broken ghost
     34-3F   pacman dead
   */

  // p_o: pac orientation when playing (live or attract,
  //         but meaningless during credit animation.)
  // 00 right
  // 01 down
  // 02 left
  // 03 up

  // starts at mode=1
  // coin moves to mode=2 ("press start")
  // 1player start ("1") moves to mode=3 after short 'ready' delay
  // 2player start ("2") moves to mode=3 after short 'ready' delay
  //
  //    03cc  d403    ;$03D4          ;$4E00 = 0
  //    03ce  fe03    ;$03FE          ;$4E00 = 1      ;ALL ATTRACT MODES
  // 03fe is 'demo mode' code.
  //    03d0  e505    ;$05E5          ;$4E00 = 2      ;PLAYER 1 OR 2 SCRN
  // 05e5 presumably where coined waits
  //    03d2  be06    ;$06BE          ;$4E00 = 3      ;PLAYER 1OR2PLAYING
  // 06be play...
  // 06a8 draw lives displayed onto the screen
  // 0263 clear ready message
  // 0344 display and blink 1UP/2UP depending on player up
  // 0369 draw "1UP"
  // 0376 draw "2UP"
  // 0383 clear "1UP"
  // 0390 clear "2UP"
  // 06a8 draw lives displayed onto the screen
  // 2ba1 draw # credits or free play
  // 0a7c we're about to start the next board, (it's about to be drawn)
  // 94ec probably draws the pellets
  // 2448 draw out the player pills
  // 2abe draw the score to the screen?
  // 2b57 draw the pacs on the screen (or another 4 character string)
  // 2b61 perhaps display the number of lives left on the screen?
  // 2b8f draw fruit
  // 039d displays "READY!"
  // 1490 display sprites in intro and game
  /*

     *Note* The code at 061b is commented as 'can't find a jump to here...
     The jump table is actually at:

     From 0x05e9 to 0x05f2 (10 byte(s))
     static const UINT8 sg_u8Region26[0x000a] =
     {
        0xf3, 0x05, 0x1b, 0x06, 0x74, 0x06, 0x0c, 0x00, 0xa8, 0x06
     };

     which is embedded after the two instruction subroutine at 05e5.

     Jumps are to 05f3, 061b, 0674, 000c (ret) and 06a8.
     The code at 0674 is a bunch of RST #28 calls.
     Depending on whether that RST drops through or returns
     via the subroutine stack, the other RSTs may also be
     jump targets.

0674  ef        rst     #28
0675  0001
0677  ef        rst     #28
0678  0101
067a  ef        rst     #28
067b  0200
067d  ef        rst     #28
067e  1200
0680  ef        rst     #28
0681  0300
0683  ef        rst     #28
0684  1c03
0686  ef        rst     #28
0687  1c06
0689  ef        rst     #28
068a  1800
068c  ef        rst     #28
068d  1b00

   */

  /*
     Main States

      4e01	main routine 0, subroutine #
      4e02	main routine 1, subroutine # (causes blue maze bug)
      4e03	main routine 2, subroutine #
      4e04	level state subroutine #
              3=ghost move, 2=ghost wait for start
              (set to 2 to pause game)

      4e06	state in first cutscene
      4e07	state in second cutscene
      4e08	state in third cutscene
   */

  /*
      The intermission scenes and credit screen should probably be rewritten
      from scratch.

      Act I - They Meet: Pac-Man enters the screen from the left side
      being chased by Inky. Ms. Pac-Man enters from the right side being
      chased by Pinky.  As the two of them are about to collide, they
      quickly move upwards, causing those two ghosts behind them to collide
      and disappear.  They face each other at the top of the screen, and a
      heart appears above them.

      Act II - The Chase: Pac-Man and Ms. Pac-Man are seen giving each other
      a playful chase.  One of them chases the other across the screen in one
      direction, and they then take turns alternating who chases who and in
      what direction across the screen. After three turns, they have a pause
      before chasing each other twice more across the screen at a faster speed.

      Act III - Junior: Pac-Man and Ms. Pac-Man anxiously await the arrival
      of a stork, who flies overhead with a bundle.  As the stork is about to
      pass over them, he releases the bundle, which drops to the ground in
      front of Pac-Man and Ms. Pac-Man, and opens up to reveal a baby Pac.
      This intermission would later serve as the introductory sequence to
      Jr. Pac-Man.
   */
}

void graphics_frames_taken(UINT32 FrameCount, UINT32 LastTime) {
}

void prbin(UINT8 b) {
  printw("%c%c%c%c%c%c%c%c ",
         (b&128)?'1':'0',
         (b&64) ?'1':'0',
         (b&32) ?'1':'0',
         (b&16) ?'1':'0',
         (b&8)  ?'1':'0',
         (b&4)  ?'1':'0',
         (b&2)  ?'1':'0',
         (b&1)  ?'1':'0'
        );
}

static char *CH[256];  
static int CH_init = 0;

static void CH_setup(void) {
  if (CH_init == 0) {
    for (CH_init = 0; CH_init <= 255; CH_init++) CH[CH_init] = NULL;

    // This only supports single-character ASCII but could be
    // rewritten to support wide Unicode characters, leaving
    // plain ASCII as a subset for simple terminals.  Since this
    // code is harkening back to the old days (and could have
    // been written in the 80's!) I'm just going to stick with
    // 7-bit ASCII (or ANSI or ISO) for now.

    // Single-byte alternatives involve 'code pages' and other
    // system-specific hacks which I'm not interested in doing.

    // See https://en.wikipedia.org/wiki/Box-drawing_characters
    // for what could be available as an alternative.

    // U+256D to U+2570 - rounded corners - would be ideal but
    // are not yet supported so the maze would have to be drawn
    // with square corners everywhere. https://unicode.org/charts/PDF/U2500.pdf
    
    CH[0x00] = "0 ";
    CH[0x01] = "1 ";
    CH[0x02] = "2 ";
    CH[0x03] = "3 ";
    CH[0x04] = "4 ";
    CH[0x05] = "5 ";
    CH[0x06] = "6 ";
    CH[0x07] = "7 ";
    CH[0x08] = "8 ";
    CH[0x09] = "9 ";
    
    CH[0x10] = ". "; // small pellet
    CH[0x14] = "O "; // large bonus pellet

    CH[0x21] = "%^"; CH[0x20] = "^."; // pacman
    CH[0x23] = "\\_"; CH[0x22] = "_/";
    
    CH[0x26] = "\" "; // lquo
    CH[0x27] = "\" "; // rquo

    CH[0x30] = "0 ";
    CH[0x31] = "1 ";
    CH[0x32] = "2 ";
    CH[0x33] = "3 ";
    CH[0x34] = "4 ";
    CH[0x35] = "5 ";
    CH[0x36] = "6 ";
    CH[0x37] = "7 ";
    CH[0x38] = "8 ";
    CH[0x39] = "9 ";
    CH[0x3a] = "/ ";
    CH[0x3b] = "- ";
    
    CH[0x40] = "  ";
    CH[0x41] = "A ";
    CH[0x42] = "B ";
    CH[0x43] = "C ";
    CH[0x44] = "D ";
    CH[0x45] = "E ";
    CH[0x46] = "F ";
    CH[0x47] = "G ";
    CH[0x48] = "H ";
    CH[0x49] = "I ";
    CH[0x4A] = "J ";
    CH[0x4B] = "K ";
    CH[0x4C] = "L ";
    CH[0x4D] = "M ";
    CH[0x4E] = "N ";
    CH[0x4F] = "O ";
    CH[0x50] = "P ";
    CH[0x51] = "Q ";
    CH[0x52] = "R ";
    CH[0x53] = "S ";
    CH[0x54] = "T ";
    CH[0x55] = "U ";
    CH[0x56] = "V ";
    CH[0x57] = "W ";
    CH[0x58] = "X ";
    CH[0x59] = "Y ";
    CH[0x5A] = "Z ";

    CH[0x5b] = "! ";
    CH[0x5c] = "()";
    CH[0x5d] = "p ";
    CH[0x5e] = "t ";
    CH[0x5f] = "s ";

    CH[0x80] = "..";// 81 top
    CH[0x81] = "*.";
    CH[0x82] = ".*";
    CH[0x83] = " .";// 84 left
    CH[0x84] = " *";
    CH[0x85] = " *"; // maybe not used? Left hand side of marquee
                     // skips every 2nd char when moving the '*' down the side
    CH[0x86] = "..";// 87 bottom
    CH[0x87] = ".*";
    CH[0x88] = "*.";
    CH[0x89] = ". ";// 8b right
    CH[0x8a] = "* ";
    CH[0x8b] = "* ";
                       
    CH[0x91] = "  ";    CH[0x90] = " /"; // cherry
    CH[0x93] = " o";    CH[0x92] = "/o";

    CH[0x95] = "  ";    CH[0x94] = " /"; // strawberry
    CH[0x97] = " o";    CH[0x96] = "/o";

    CH[0x99] = "  ";    CH[0x98] = " /"; // orange
    CH[0x9b] = " o";    CH[0x9a] = "/o";

    CH[0x9d] = "  ";    CH[0x9C] = " /"; // pretzel
    CH[0x9f] = " o";    CH[0x9e] = "/o";

    CH[0xa1] = "  ";    CH[0xa0] = " /"; // apple
    CH[0xa3] = " o";    CH[0xa2] = "/o";

    CH[0xa5] = "  ";    CH[0xa4] = " /"; // pear
    CH[0xa7] = " o";    CH[0xa6] = "/o";

    CH[0xa9] = "  ";    CH[0xa8] = " /"; // banana
    CH[0xab] = " o";    CH[0xaa] = "/o";

    CH[0xad] = "  ";    CH[0xac] = " /"; // not used in ms pacman
    CH[0xaf] = " o";    CH[0xae] = "/o";


    CH[0xbf] = "| ";    CH[0xbe] = " /";    CH[0xbd] = "\\ ";    CH[0xbc] = " |";
    CH[0xbb] = "| ";    CH[0xba] = "/ ";    CH[0xb9] = " \\";    CH[0xb8] = " |";
    CH[0xb7] = "| ";    CH[0xb6] = "  ";    CH[0xb5] = "  ";    CH[0xb4] = " |";
    CH[0xb3] = "| ";    CH[0xb2] = "  ";    CH[0xb1] = "  ";    CH[0xb0] = " |"; // midway logo

    CH[0xc0] = "  ";
    CH[0xc1] = "  ";
    CH[0xce] = "..";
    CH[0xcf] = "..";
    CH[0xd0] = "\\ ";
    CH[0xd1] = "/-";
    CH[0xd2] = "| ";
    CH[0xd3] = "| ";
    CH[0xd4] = "/ ";
    CH[0xd5] = "\\-";
    CH[0xd6] = "/ "; // rhs           first used on 2nd level
    CH[0xd7] = "\\-"; // lhs
    CH[0xd8] = "\\ "; // rhs
    CH[0xd9] = "/-"; // lhs
    CH[0xda] = "--";
    CH[0xdb] = "--";
    CH[0xdc] = "--";
    CH[0xdd] = "--";
    CH[0xde] = "--";
    CH[0xdf] = "--";
    CH[0xe4] = "--";
    CH[0xe5] = "--";
    CH[0xe6] = "\\ ";
    CH[0xe7] = "/-";
    CH[0xe8] = "| ";
    CH[0xe9] = "| ";
    CH[0xea] = "/ ";
    CH[0xeb] = "\\-";
    CH[0xec] = "+ ";
    CH[0xed] = "+-";
    CH[0xee] = "+ ";
    CH[0xef] = "+-";
    CH[0xf0] = ".-";
    CH[0xf1] = "-.";
    CH[0xf2] = "/-";
    CH[0xf3] = "\\ ";
    CH[0xfa] = "/-";
    CH[0xf4] = "\\-";
    CH[0xf5] = "/ ";
    CH[0xfb] = "\\ ";
    CH[0xfc] = "  ";
    CH[0xfd] = "  ";

    CH_init = 1;
  }
}

static int Global_top = 0, Global_left = 0;
void ascii_ch1(int y, int x, int c) {
  if (x < 0x37) {
    move(y+Global_top, x+Global_left);
    printw("%c", c);
  }
}

void phex(int y, int x, int c) {
    static const char hex[16] = { "0123456789ABCDEF" };
    ascii_ch1(y, x,   hex[(c>>4)&15]);
    ascii_ch1(y, x+1, hex[c&15]);
}

static int hex_debug = 0, mem_debug = 0;
void ch2(int y, int x, int c) {
  if (CH_init == 0) CH_setup();
  if (CH[c] && !hex_debug) {    
    ascii_ch1(y, x,   CH[c][0]);
    ascii_ch1(y, x+1, CH[c][1]);
  } else {
    phex(y, x, c);
  }
}

void ram(void) {
  int Addr, Offset, Line = 0;
  //#define LIMIT 0x4FF0
  #define LIMIT 0x5060
  move(Global_top, Global_left+60);
  printw("     0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F");
  for (Addr = 0x4C00; Addr < LIMIT; Addr += 0x20) {
    move(Global_top+Line+1, Global_left+60);
    printw("%04x ", Addr);
    for (Offset = 0; Offset < 0x20; Offset++) {
      if (Addr+Offset >= LIMIT) break;
      if (Addr>=0x5000) {
        printw("%02x ", sg_pu8RecompBase[Addr+Offset]);
      } else {
        printw("%02x ", mem[Addr+Offset]);
      }
    }
    Line += 1;
  }
}

#include <unistd.h>
#include <inttypes.h>
#include <time.h>

int64_t millis(void)
{
  struct timespec now;
  timespec_get(&now, TIME_UTC);
  return ((int64_t) now.tv_sec) * 1000 + ((int64_t) now.tv_nsec) / 1000000;
}

void graphics_redraw(int XL, int YT) {
  Global_left = XL; Global_top = YT;
  #define XSCALE(x) (-4+(255-(unsigned)(x))*64/256)
  #define YSCALE(y) (1+(unsigned)(y)/8)

  int x, y, i;

  //if (PHASE == 3)
  // map(MAZE&3); //else
  //clear();  // not actually correct but should work for first 4 mazes at least.  Needs a lookup table:
  //	;; map order table... (order that boards are played)
             // offset    0  1  2  3   4  5  6  7   8  9  a  b   c  d  e  f
             //000094d0                                                  00
             //000094e0  00 01 01 01  02 02 02 02  03 03 03 03


  // We'll just copy directly from the screen...

  // map color table: 95ae
  //  	;; color palette table for the first 21 mazes

  // offset    0  1  2  3   4  5  6  7   8  9  a  b   c  d  e  f
  //000095a0                                               1d 1d
  //000095b0  16 16 16 14  14 14 14 07  07 07 07 18  18 18 18 1d
  //000095c0  1d 1d 1d 

  for (i = 0x4000; i < 0x4400; i++) {
    y = i&0x1F;
    x = ((i-0x4000)>>5)&0x1F;
    if (x >= 0x1E) {
      // LEFT BANK -> TOP
      int tmp = x; x = y; y = (0x1F-tmp)-2;
      ch2(-1-y,   (0x1F-x)*2 - 4, mem[i]);
    } else if (x <= 1) {
      // RIGHT BANK -> BOT
      int tmp = x; x = y; y = tmp;
      ch2((y+33), (0x1f-x)*2 - 3, mem[i]);      
    } else {
      ch2(y+1,  (0x1F-x)*2 - 4, mem[i]);
    }
  }
  /* TO DO: draw ghosts in B/W when dead...
;               ghost states:
;               0 = alive
;               1 = dead
;               2 = entering home after being killed
;               3 = go left after entering home after dead (blue)
;               3 = go right after entering home after dead (orange)
;       4dac    red ghost state
;       4dad    pink ghost state
;       4dae    blue ghost state
;       4daf    orange ghost state


; Sprite variables
;
;       4c02    red ghost sprite number
;       4c03    red ghost color entry
;       4c04    pink ghost sprite number
;       4c05    pink ghost color entry
;       4c06    blue ghost sprite number
;       4c07    blue ghost color entry
;       4c08    orange ghost sprite number
;       4c09    orange ghost color entry
;       4c0a    pacman sprite number
;       4c0b    pacman color entry

i.e.

ms pacman skins: (eye direction and feet animation)

                     SHOULD THESE BE MASKED WITH 3F ??? - inconsistent with online doc...

  down  34 31 33
  left  35 ad af
  up    36 f1 f3
  right 37 2d 2f
                   - 3 frame chomping!

  dying 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F 
        ^^^^^^^^^^^
        turn in a circle

ghost + eyes:

  front 1c 1d  (what are 1e and 1f? - not used.)
  right 20 21 
  down  22 23
  left  24 25
  up    26 27

;       PACMAN SPRITE CODES
;
;       00-07   fruits
;       08-0D   naked ghosts for cutscenes
;       0E-0F   empty
;       10-1B   big pacman
;       1C-1D   ghost in panic mode
;       1E-1F   empty
;       20-27   ghosts
;       28-2B   points
;       2C-2F   pacmans
;       30      big pacman
;       31      explosion
;       32-33   broken ghost
;       34-3F   pacman dead

color:
  01 red
  03 pink
  05 cyan
  07 orange
             - ghost-specific normal colour  (x+1)/2 maps to 1,2,3,4 - colours wanted are 1,2,5,6 - lookup table?
  11 blue
  12 white (ie warning panic mode almost over)  maybe use black/white for blue/white? (x-0x11)*7 maps blue,white to BLACK(0),WHITE(7)


   */
  
  // Might use 'M' for the ghosts instead?
  if (g0_x || g0_y)   {
    if (XSCALE(g0_x) < 55) {
      if (1) {
        if (mem[0x4da7]) { // red ghost blue flag (0=not blue)
          attron(COLOR_PAIR(COLOR_BLUE)); // needs to handle flashing
        } else {
          attron(COLOR_PAIR(COLOR_RED));
        }
        ascii_ch1(YSCALE(g0_y),XSCALE(g0_x), '*');
        attroff(COLOR_PAIR(COLOR_RED));
        //phex(YSCALE(g0_y),XSCALE(g0_x), mem[0x4c03]);//color
        //move(YSCALE(g0_y),XSCALE(g0_x)-1); printw("/W\\");
      } else {
        move(YSCALE(g0_y),XSCALE(g0_x)-1);
        printw("/oo\\");
        move(YSCALE(g0_y)+1,XSCALE(g0_x)-1);
        printw("|  |");
      }
    }
  }
  if (g1_x || g1_y)   {
    if (XSCALE(g1_x) < 55) {
      if (1) {
        if (mem[0x4da8]) { // pink ghost blue flag (0=not blue)
          attron(COLOR_PAIR(COLOR_BLUE)); // needs to handle flashing
        } else {
          attron(COLOR_PAIR(COLOR_PINK));
        }
        ascii_ch1(YSCALE(g1_y),XSCALE(g1_x), '#');
        attroff(COLOR_PAIR(COLOR_PINK));
        //phex(YSCALE(g1_y),XSCALE(g1_x), mem[0x4c05]);
        //move(YSCALE(g1_y),XSCALE(g1_x)-1); printw("/w\\");
      } else {
        move(YSCALE(g1_y),XSCALE(g1_x)-1);
        printw("/oo\\");
        move(YSCALE(g1_y)+1,XSCALE(g1_x)-1);
        printw("|  |");
      }
    }
  }
  if (g2_x || g2_y)   {
    if (XSCALE(g2_x) < 55) {
      if (1) {
        if (mem[0x4da9]) { // cyan ghost blue flag (0=not blue)
          attron(COLOR_PAIR(COLOR_BLUE)); // needs to handle flashing
        } else {
          attron(COLOR_PAIR(COLOR_CYAN));
        }
        ascii_ch1(YSCALE(g2_y),XSCALE(g2_x),'&');
        attroff(COLOR_PAIR(COLOR_CYAN));
        //move(YSCALE(g2_y),XSCALE(g2_x)-1); printw("/X\\");
      } else {
        move(YSCALE(g2_y),XSCALE(g2_x)-1);
        printw("/oo\\");
        move(YSCALE(g2_y)+1,XSCALE(g2_x)-1);
        printw("|  |");
      }
    }
  }
  if (g3_x || g3_y)   {
    if (XSCALE(g3_x) < 55) {
      if (1) {
        if (mem[0x4daa]) { // orange ghost blue flag (0=not blue)
          attron(COLOR_PAIR(COLOR_BLUE)); // needs to handle flashing
        } else {
          attron(COLOR_PAIR(COLOR_MAGENTA));
        }
        ascii_ch1(YSCALE(g3_y),XSCALE(g3_x), '$');
        attroff(COLOR_PAIR(COLOR_MAGENTA));
        //move(YSCALE(g3_y),XSCALE(g3_x)-1); printw("/Y\\");
      } else {
        move(YSCALE(g3_y),XSCALE(g3_x)-1);
        printw("/oo\\");
        move(YSCALE(g3_y)+1,XSCALE(g3_x)-1);
        printw("|  |");
      }
    }
  }
  // TO DO: Pacman and Ms Pacman in the "They meet" animation screen...
  if (pac_x || pac_y) {
    if (XSCALE(pac_x) < 55) {
      if (1) {
        // 4da5    pacman dead animation state (0 if not dead)
        attron(COLOR_PAIR(COLOR_YELLOW));
        if (mem[0x4da5]) {
          // dead. Animation goes from 01 to 16
          move(YSCALE(pac_y),XSCALE(pac_x));
          const char anim[16] = {"/-\\|/-\\|/-\\|/-\\|"};
          ascii_ch1(YSCALE(pac_y),XSCALE(pac_x), anim[mem[0x4da5]-1]);
        } else {
          ascii_ch1(YSCALE(pac_y),XSCALE(pac_x), '@');  // need to handle direction and 'O' vs 'C' animation, also maybe '{', '<' or '}', '>'?
          //phex(YSCALE(pac_y),XSCALE(pac_x), mem[0x4c0a]);  // need to handle direction and 'O' vs 'C' animation, also maybe '{', '<' or '}', '>'?
        }
        attroff(COLOR_PAIR(COLOR_YELLOW));
        //move(YSCALE(pac_y),XSCALE(pac_x)-1); printw("(#)");
      } else {
        move(YSCALE(pac_y)-1,XSCALE(pac_x)-1);
        printw("/~\\");
        move(YSCALE(pac_y),XSCALE(pac_x)-1);
        printw("\\_/");
      }
    }
  }
  if (fruit_x || fruit_y) {
    if (XSCALE(fruit_x) < 55) {
      ascii_ch1(YSCALE(fruit_y),XSCALE(fruit_x), '%');
    }
  }

  if (mem_debug) ram();
  
  refresh();
  /*
; p1/p2 check.  see 187b above for info.

        ; p2 movement check
18bf  3a4050    ld      a,(#5040)       ;; check IN1 (P2)
18c2  c3c818    jp      #18c8

        ; p1 movement check
18c5  3a0050    ld      a,(#5000)       ; a = IN0 (P1)

18c8  cb4f      bit     1,a             ; left
18ca  cac91a    jp      z,#1ac9
18cd  cb57      bit     2,a             ; right
18cf  cad91a    jp      z,#1ad9
18d2  cb47      bit     0,a             ; up
18d4  cae81a    jp      z,#1ae8
18d7  cb5f      bit     3,a             ; down
18d9  caf81a    jp      z,#1af8
        ; no movement
18dc  2a1c4d    ld      hl,(#4d1c)
18df  22264d    ld      (#4d26),hl
18e2  0601      ld      b,#01
   */

#if ((!defined(USE_SDL_GRAPHICS)) && (!defined(USE_SDL2_GRAPHICS)))
  // Wait until the start of the next frame
  static int64_t next_wakeup_time_ms = 0LL,
                 time_now_ms,
                 one_frame_delay = 1000LL/60LL; // 60 FPS

  time_now_ms = millis();
  if (next_wakeup_time_ms == 0LL) {
    // first time round
    next_wakeup_time_ms = time_now_ms + one_frame_delay;
  } else if (time_now_ms < next_wakeup_time_ms) {
    // undershot
    // This might be a good time to write the score to a file, if it has changed.
    // Or even the entire memory.  However a better time to do it might be when
    // the game state is not 'playing' and where a small stutter won't matter...
    // Highscore on the other hand might as well only be written after a game has
    // ended.
    usleep((int)(next_wakeup_time_ms-time_now_ms) * 1000);
    next_wakeup_time_ms += one_frame_delay;
  } else {
    // overshot - don't sleep until we catch up
    next_wakeup_time_ms += one_frame_delay;
  }
  static int lastkey = KEY_LEFT;
  
  // Can save 'c' to a file here, indexed by the frame number.  This will allow
  // a replay of the game by presenting the same inputs at the same frame numbers.
  
  int c = getch(); move(40,60);
  if (c == -1) c = lastkey;
  if (mem_debug) printw("KEY = %03x", c);    // TO DO: key needs to be reset to KEY_LEFT on start of each round
  switch (c) {
  case '\'':
  case KEY_UP:    sg_pu8RecompBase[0x5000] = (sg_pu8RecompBase[0x5000]|15) & ~0x01;
                  //sg_pu8RecompBase[0x5040] |= 0x01;
                  lastkey = c;
                  break;
  case 'z': case 'Z': 
  case KEY_LEFT:  sg_pu8RecompBase[0x5000] = (sg_pu8RecompBase[0x5000]|15) & ~0x02;
                  //sg_pu8RecompBase[0x5040] |= 0x02;
                  lastkey = c;
                  break;
  case 'x': case 'X':
  case KEY_RIGHT: sg_pu8RecompBase[0x5000] = (sg_pu8RecompBase[0x5000]|15) & ~0x04;
                  //sg_pu8RecompBase[0x5040] |= 0x04;
                  lastkey = c;
                  break;
  case '/':
  case KEY_DOWN:  sg_pu8RecompBase[0x5000] = (sg_pu8RecompBase[0x5000]|15) & ~0x08;
                  //sg_pu8RecompBase[0x5040] |= 0x08;
                  lastkey = c;
                  break;
    
  case 'p': case 'P':
                  do c = getch(); while (c != 'p' && c != 'P');
                  next_wakeup_time_ms = millis() + one_frame_delay;
                  break;
  case 'H':
  case 'h':       hex_debug ^= 1; break;
    
  case 'M':
  case 'm':       mem_debug ^= 1; break;
    
  case 'Q':
  case 'q':       graphics_terminate() ; exit(0);
    
  case '3':       mem[0x4e6e] += 1;  // credit
                  // Other than the first time, this hack sometimes causes the frame to start immediately
                  // rather than waiting for the '1' "Player 1 start" button to be pressed...
                  // so let's try clearing it in case left over from earlier...
                  mem[0x5040] |= 0xE0; // Hey! That might have done it!
                  break;
// ; check for start button press
//95fd  3a8050    ld      a,(#5080)	;; check in0
//9600  e630      and     #30
//9602  fe30      cp      #30
//9604  78        ld      a,b
//9605  c0        ret     nz
//9606  3e20      ld      a,#20
//9608  0620      ld      b,#20
//960a  c9        ret     

  case '1':       mem[0x5040] &= ~0xE0;
                  //mem[0x5080] |= 0x30;    // also tried using sg_pu8RecompBase instead, no difference
                  break;
  case '2':       mem[0x5040] &= ~0xC0;
                  break;

  default:
    break;
    // To do: 'p'
  }
#endif
}

