#include <vectrex.h>

/*

   The first vectrex release contained 42 puzzles and the second release added 75 more,
  but finding good games online started to be rather difficult, so I wrote a program to
  generate all possible games so I could then pick a subset to include in the next release
  of Unblockme.  Although there are millions of possible games, there were only a few
  thousand that scored over 30 (i.e. 30 moves to solve it).

  It would have been nice to include all of these in the next release so that the player
  would never get the same game twice, but with each game taking up 36 bytes, we just
  did not have the room to store them, and regenerating them on the Vectrex is simply
  impossible.

  My solution to this was to come up with an encoding scheme which could save a board
  in 8 bytes!  See the program "decompress.c" for details of how to decode two 32-bit
  integers into a 36-character board layout.  I'm not sure at the moment how much space
  will be available, but I strongly suspect that I will be able to store all 3282 high-
  scoring games *and* a random sample of lower-scoring (i.e. easier) problems in case a
  beginner picks up the new version (which will be called the Expert or Pro version)
  so they can get up to speed before tackling the hard levels.

  An Unblockme game for a larger system could include a version of the game generator -
  although it takes hours to generate all games, it can generate 'easy' problems (i.e.
  boards that can be solved in under 30 moves) pretty quickly.  So a hybrid that generates
  some easy boards quickly, but finds more difficult boards that take longer to compute
  by looking up a table, looks like an attractive option.

 */

static const char const *Version = "UNBLOCKME V0.2A 117 LEVELS";

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

#define uint8_t unsigned int
#define int8_t  int
#define int16_t long
#define int32_t long long

#define HORIZONTAL 0
#define VERTICAL 1

#define UP 0
#define LEFT 0
#define DOWN 1
#define RIGHT 1

#define SCALETEST 1 /* saves 5152 cycles */

// It's only the surrounding frame which needs vectors to be split in two,
// so we could if we preferred have two scales, one for blocks and one
// for the frame, so that the frame was drawn in single pieces.
// But since we are already down to an average of 30,000 cycles, there's
// no great advantage to be had from optimising more.

// Although, that said, one of the corners I had to cut to get down to 30K cycles
// was removing any permanent on-screen text.  If we ever want that back, then
// more scaling tweaks might be worth implementing.  But I like the clean interface.

#ifdef SCALETEST
#define DRAWING_SCALE 0x60
#define SCALE 40
#define GAP 4
#define FUDGE 2
#define CURSOR_SIZE 6
#else
#define DRAWING_SCALE 0xC0
#define SCALE 20
#define GAP 2
#define FUDGE 0
#define CURSOR_SIZE 3
#endif

#define XOFF (3*SCALE)
#define YOFF (3*SCALE)


static inline void set_scale(unsigned int s) {
  VIA_t1_cnt_lo = s;
}

#ifdef DEBUG
static char debug_buffer[64];
void debug_int32(int y, int x, char *var, int32_t i) {
  char *s = debug_buffer, *startpos, *endpos;
  (void)y; (void)x;
  while (*var != '\0') {
    char c;
    c = *var++;
    *s++ = c;
  }
  *s++ = ':'; *s++ = ' ';
  if (i < 0) *s++ = '-'; else i = -i;
  startpos = s;
  for (;;) {
    int32_t j = i / ((typeof(i))(10));
    char digit = (char)(i - j*((typeof(i))(10)));
    *s++ = '0'-digit;
    i = j;
    if (i == 0) break;
  }
  endpos = s-1;
  //reverse(startpos, s);
  while (startpos < endpos) {
    char t = *startpos;
    *startpos = *endpos;
    *endpos = t;
    startpos += 1; endpos -= 1;
  }
  *s++ = 0x80;
  *s = '\0';
  Reset0Ref(); set_scale(DRAWING_SCALE); Intensity_5F();
  Print_Str_d(y, x, debug_buffer);
}
#endif

#define NUM_GAMES (42U+75U)
const char const *puzzle[NUM_GAMES]  __attribute__ ((section(".text")))  = {
// new 75:

  "..JJFF"
  "AAG.ID"
  "XXG.ID"
  "B.HHHE"
  "B.KKKE"
  "CCC..E",

  "EEC..."
  "IHCAAA"
  "IHXXBK"
  "GG.FBK"
  "...FD."
  ".JJ.D.",

  "JBBKKA"
  "J.HHHA"
  "XXFE.A"
  "..FECC"
  ".GGGI."
  ".DDDI.",

  ".IDDD."
  ".I.AAE"
  "XXJBHE"
  "C.JBH."
  "CF.B.."
  "CFGGKK",

  "JJ..F."
  "IHHHFC"
  "I.EXXC"
  "BBE..C"
  ".ADD.."
  ".AKKGG",

  "G.EEKD"
  "G.I.KD"
  "..IXXJ"
  "BBBF.J"
  "..AFCC"
  "HHAF..",

  "..CCEE"
  "IILBJJ"
  "XXLB.."
  "KHHB.."
  "KF.DD."
  "KFAAGG",

  "J.IIC."
  "JHHHCG"
  "..EXXG"
  "BBEA.G"
  "...AFF"
  "DDDA..",

  "FDDD.J"
  "F.BEEJ"
  "XXBHIL"
  ".CCHIL"
  "...AKK"
  ".GGA..",

  "H....."
  "H.FBJ."
  "XXFBJ."
  ".A.BDD"
  "GA.IIC"
  "GAEE.C",

  "..AFFF"
  "..AHD."
  "XXEHDG"
  ".BEKKG"
  "JBII.G"
  "JLLCC.",

  "..DGGG"
  "CCD..H"
  "LFXXEH"
  "LFIIEH"
  "..ABJJ"
  "KKAB..",

  "H....."
  "H.FBJ."
  "XXFBJ."
  ".A.BDD"
  "GA.IIC"
  "GAEE.C",

  "..AFFF"
  "..AHD."
  "XXEHDG"
  ".BEKKG"
  "JBII.G"
  "JLLCC.",

  "..DGGG"
  "CCD..H"
  "LFXXEH"
  "LFIIEH"
  "..ABJJ"
  "KKAB..",

  "....II"
  "BBAAAK"
  "XXFGEK"
  ".CFGE."
  "JCF..."
  "JDDHHH",

  "..C.GG"
  "..C..E"
  "XXD..E"
  "..DIIF"
  "HHHB.F"
  "AA.B..",

  ".DJJKF"
  ".DCHKF"
  "XXCH.F"
  "..CEBB"
  "IGGE.."
  "I.AAA.",

  ".DGG.."
  ".DHAA."
  "XXHIBC"
  "EEEIBC"
  "K.FFF."
  "K..JJ.",

  "GIKKJF"
  "GI..JF"
  "G.AXXF"
  "DDAB.."
  "H.EB.."
  "H.EBCC",

  "..IIKB"
  "GCCCKB"
  "G.XXLF"
  "JJDALF"
  "..DAHH"
  "EEEA..",

  "..C.EE"
  "G.C.HH"
  "GXXIBJ"
  "FA.IBJ"
  "FA.ID."
  "F...D.",

  "....II"
  "..BAA."
  "XXBF.."
  "J.BFCC"
  "JEEFHD"
  "KKGGHD",

  ".FFFK."
  "JJ.EK."
  "XX.EBH"
  "ALL.BH"
  "A.DCGG"
  "IIDC..",

  "AII..."
  "AGGG.K"
  "XXC..K"
  "HFC.EE"
  "HFJ.DD"
  "H.JBB.",

  "JCCCFF"
  "J..GBB"
  "XXDG.E"
  "KLDAAE"
  "KLHHHE"
  "....II",

  "IFFKKE"
  "I.AAAE"
  "XXHD.E"
  "..HDBB"
  ".GGGC."
  "..JJC.",

  "H.IIB."
  "H.KKB."
  "GGXXDJ"
  "FC..DJ"
  "FCLEDJ"
  ".CLEAA",

  ".HKKBE"
  "GH.FBE"
  "GXXFBJ"
  "DLLF.J"
  "D.ACCC"
  "..A.II",

  "HJJDCC"
  "HGGD.."
  "XXBE.."
  "..BEAA"
  "..BKKF"
  ".II..F",

  "..BGG."
  "..BEE."
  "XXBA.."
  "FDDA.."
  "F..A.."
  "F..CC.",

  "..ACBB"
  "..ACK."
  "XXACKE"
  ".G...E"
  "JGDDFI"
  "J.HHFI",

  "...BKK"
  "..HBEE"
  "XXHI.F"
  "GG.I.F"
  "LDDDJC"
  "L.AAJC",

  "IJJF.."
  "IKKF.."
  "XXCF.."
  ".BCAA."
  "HBEGG."
  "HBEDD.",

  "FFB.J."
  "H.BCJ."
  "HXXC.."
  ".I.C.E"
  ".IDAAE"
  "..DGGE",

  "...FCC"
  "..BFK."
  "XXBHKG"
  "JJ.HEG"
  "IAAAED"
  "ILLL.D",

  "F.J..."
  "F.JIDH"
  "XXBIDH"
  ".CB.AA"
  "ECKK.."
  "ECGG..",

  "AIDDHJ"
  "AIBEHJ"
  "XXBE.."
  "..F..."
  "..FCGG"
  "..FC..",

  "I.KKDD"
  "I.AAAL"
  "XXEB.L"
  "..EBHH"
  ".GGGFC"
  "..JJFC",

  "L.GGGE"
  "L.BBBE"
  "XXFHAI"
  "JCFHAI"
  "JC.D.."
  ".C.DKK",

  "I.GGGC"
  "I....C"
  "XXEFLJ"
  "HHEFLJ"
  "D.EBBB"
  "DKKAAA",

  "DDDBJE"
  "LHHBJE"
  "L.XXJE"
  "CCK..."
  ".GKII."
  ".GAAFF",

  "FDDDKK"
  "F.AAJJ"
  "XXBGEI"
  "CCBGEI"
  ".H.LE."
  ".H.L..",

  "FKKIII"
  "F.JDEE"
  "XXJDBH"
  "..AABH"
  ".CCCBH"
  ".GG...",

  "JFFFBK"
  "J.E.BK"
  "XXE..."
  "..GADD"
  "IIGA.C"
  "HHHA.C",

  ".AA.D."
  "HG..D."
  "HGXXFI"
  "H..CFI"
  "BBBCF."
  "...CEE",

  "CCJJJH"
  "..KK.H"
  "XX..EL"
  "FAAAEL"
  "F.BIGG"
  "DDBI..",

  "..ADDD"
  "..ALLK"
  "XXAFMK"
  "HIIFMG"
  "HECCCG"
  ".EBBJJ",

  "..D.EE"
  "C.DHHH"
  "CXXJ.."
  "CB.J.A"
  "KB.FFA"
  "KBIIGG",

  "A..IKK"
  "AD.I.."
  "ADEXXJ"
  ".DE.CJ"
  ".FFFCG"
  "HHBBCG",

  ".LCCII"
  ".LEEEM"
  "XXFJ.M"
  "..FJDD"
  "G.KKHB"
  "GAAAHB",

  "BCJJJ."
  "BCIII."
  "XXFM.."
  "L.FMEE"
  "LGGDD."
  "KKAAHH",

  "IIHHGD"
  "EEE.GD"
  ".JKXXD"
  "FJKA.."
  "FBBA.."
  "F..ACC",

  "KK.CE."
  "BBBCEJ"
  "XX.CAJ"
  "H.DDA."
  "H.IGGL"
  "FFI..L",

  ".JJHHH"
  "EAAAGG"
  "EXX.FK"
  ".BB.FK"
  ".D.III"
  ".D.CCC",

  ".BFFAD"
  "GB.IAD"
  "GXXI.D"
  "J.CEEE"
  "J.C..."
  "..C.HH",

  "FFH.AA"
  "JJH.ID"
  "CXX.ID"
  "CBBBID"
  "C..LEE"
  "KK.LGG",

  "F.BBB."
  "F.A..."
  "XXA..."
  "..AE.."
  "DDDE.."
  "CCC...",

  "..HIII"
  "DDH..."
  "EXXB.."
  "E..BFF"
  "JJKKKC"
  "AAAGGC",

  ".IJJB."
  ".IEEB."
  "KXXFB."
  "K.HFCC"
  "L.HDDG"
  "L.AAAG",

  "F.IIKK"
  "F.JJGG"
  "AAAXXH"
  "..BBBH"
  ".....E"
  "CC.DDE",

  ".KJJEE"
  ".KDAAA"
  "XXD..G"
  "..DLBG"
  "FCCLB."
  "FIIHH.",

  "IIFB.."
  "..FB.."
  "AXXB.."
  "A.DDD."
  "ACCCEG"
  "JJHHEG",

  "..FBBB"
  "EEF.C."
  ".DXXC."
  ".DAAA."
  ".D...."
  "......",

  "..C.EE"
  "DDC.BK"
  "IXXABK"
  "ILLAHG"
  "IF.AHG"
  ".F.JJJ",

  "..FGGG"
  "DDFA.."
  "KXXAE."
  "KCCAE."
  "..BJJH"
  "IIB..H",

  "JIICCC"
  "JAAB.K"
  "XX.BHK"
  "EEEBHG"
  "...DDG"
  "..FF.G",

  "LG.DCC"
  "LG.D.B"
  "JG.XXB"
  "JKK.IE"
  "AAH.IE"
  "..HFF.",

  "GGG.CI"
  "KK..CI"
  "XX..EH"
  "JDFFEH"
  "JDBAA."
  "LLB...",

  "BEEK.."
  "B.FKHH"
  "XXFG.J"
  "CCCG.J"
  "DIA..."
  "DIA...",

  ".KJJFI"
  ".KB.FI"
  "XXB..I"
  "CCB..."
  "EGGH.."
  "EDDHAA",

  ".IICG."
  "FFFCGE"
  "XX.CHE"
  "B.JJH."
  "B.DAAL"
  "KKD..L",

  "IBBBHH"
  "I.GDDD"
  "XXGE.."
  "..GE.."
  ".AFFF."
  ".ACCJJ",

  "FFAB.."
  "C.AB.."
  "CXXB.."
  "CGIIHH"
  ".GEEJK"
  ".DD.JK",

  "EEEDA."
  "HHHDA."
  ".XXJA."
  "G..JCC"
  "G.BFFI"
  "..B..I",

// original 42:

  "II.B.."
  "JHHB.E"
  "JAAXXE"
  "D.F..E"
  "D.F.CC"
  "D.FGGG",
  
  "EDDD.."
  "E.KICC"
  "XXKIAG"
  ".JFFAG"
  ".J.BB."
  ".J..HH",
  
  ".IIIGG"
  "..B..."
  "XXB..."
  "HEEJJK"
  "HFFADK"
  "CC.ADK",
  
  "..A.JJ"
  "DDABBI"
  "XXAFGI"
  ".CCFGE"
  ".HH.GE"
  ".....E",
  
  "HIIDDD"
  "H.FEJJ"
  "XXFEC."
  "....C."
  ".AAACG"
  "..BBBG",
  
  "...KBB"
  "I.AK.D"
  "I.AXXD"
  "GCCCHL"
  "GFEEHL"
  ".FJJH.",
  
  "..FAAA"
  "..FBEE"
  "XXFB.."
  "CDDD.H"
  "C....H"
  "CGG...",
  
  ".HGGAK"
  ".HF.AK"
  "XXFE.."
  "DDBEJJ"
  "C.BE.."
  "C.II..",
  
  "IIE..."
  "J.EFFL"
  "JXXAGL"
  "...AGD"
  "HHHABD"
  "CCKKB.",
  
  ".AADD."
  "EEEBF."
  "JXXBFI"
  "JHCKKI"
  "GHC..."
  "G.....",
  
  "...BH."
  "I..BH."
  "IXXBJC"
  "G.A.JC"
  "GEADDC"
  "GEFFF.",
  
  ".AABI."
  "HHHBI."
  "XX.BFD"
  "G.KKFD"
  "G.ECCL"
  "JJE..L",
  
  "EFFFJJ"
  "E.HC.D"
  "XXHC.D"
  ".GAA.."
  ".GKK.."
  "IIBBB.",
  
  ".JAAAE"
  ".JC..E"
  "XXC..."
  "H.CGGG"
  "HBBDFI"
  "KK.DFI",
  
  ".KJJAA"
  ".K.BD."
  "XX.BDF"
  "I.CCCF"
  "I.HGGF"
  "EEH...",
  
  ".G...."
  ".G..FF"
  "XXBIAE"
  "C.BIAE"
  "CDB..."
  "CDHH..",
  
  "AIJJJ."
  "AID..."
  "XXDBF."
  "GGDBFL"
  "E..KKL"
  "E.HHCC",
  
  "AAJ..C"
  "I.JBBC"
  "I.XXKC"
  "HH.FK."
  "DDDFE."
  "GGGFE.",
  
  ".DGG.."
  ".D.AFF"
  "XXHA.."
  "..HA.."
  "IEEECC"
  "I.BB..",
  
  "A.EHHH"
  "A.EI.."
  "AXXI.."
  ".JCCFD"
  ".JB.FD"
  ".JBGGD",
  
  "JGGEII"
  "J..E.F"
  "XXA..F"
  "..ACCF"
  "..AHDD"
  "BBBH..",
  
  ".JJ..."
  ".FF.BB"
  "XXA..G"
  "CDAIIG"
  "CDK.HH"
  "CDKEEE",
  
  "GGLL.B"
  "DDDEEB"
  "J.AXXB"
  "J.AHC."
  "IKAHC."
  "IK.HFF",
  
  "C.FFKK"
  "C.BEHH"
  "XXBE.."
  ".LBEAA"
  ".LJJ.G"
  ".IIDDG",
  
  "J..GAA"
  "JFFGIB"
  "XXDHIB"
  "KEDHCC"
  "KEDLL."
  "KEMM..",
  
  "DIIH.."
  "DB.HGG"
  "DBEXXJ"
  "AAE.LJ"
  "CCC.LK"
  "FFF..K",
  
  "..BIIC"
  "..BE.C"
  "XXBEFC"
  "HGGGF."
  "HDDA.."
  "JJ.A..",
  
  ".EJJ.."
  ".E.DBB"
  "XXKD.."
  "GGKD.."
  "HCCCAA"
  "HIIFF.",
  
  "FFEEH."
  "IBBBHC"
  "I.KXXC"
  "JJKD.C"
  "...DAA"
  "GGGD..",
  
  "A..FFF"
  "A.LLCC"
  "XXH..I"
  "JJHG.I"
  "BBBGKE"
  "DDD.KE",
  
  ".G.EED"
  ".GIIID"
  "HG.XXD"
  "HFFFJJ"
  ".AB.CC"
  ".AB.KK",
  
  ".FFJJJ"
  "BB.C.I"
  "KXXCGI"
  "KDAAGL"
  "HD...L"
  "HD.EEE",
  
  ".JHHLL"
  ".JCCCE"
  "XXBG.E"
  "..BGKK"
  "I.AAMD"
  "IFFFMD",
  
  "AAA..G"
  "CC...G"
  "IEEXXF"
  "I....F"
  "HJJBBB"
  "H...DD",
  
  "K.JJLL"
  "K.GGHH"
  "XXFE.."
  "DDFE.C"
  "AAAE.C"
  ".IIBB.",
  
  "EEEHB."
  "FKKHB."
  "FXXD.."
  "F.GDCC"
  "..GAAJ"
  "III..J",
  
  "..EE.."
  "..DFF."
  "XXD..."
  "..DAAA"
  "CCCB.."
  "GGGB..",
  
  "HH..IL"
  "CCC.IL"
  "XXA..K"
  "BFAGGK"
  "BF.EDD"
  "BJJE..",
  
  ".JJFB."
  "DDDFB."
  "XX.FGH"
  "E.KKGH"
  "E.CAAL"
  "IIC..L",
  
  "F.HH.."
  "FDDB.."
  "XXEB.G"
  "..EAAG"
  "..C..."
  "..C...",
  
  "EDD.G."
  "E.A.G."
  "XXAF.."
  "KK.FJJ"
  "CCCHHL"
  ".IIBBL",
  
  ".GCCFF"
  ".G.HD."
  "XX.HD."
  "IBBBDJ"
  "I.AKKJ"
  "I.AEEJ",
};

static unsigned char patList[2];
static void drawline_patterned(int y, int x, unsigned char pat)
{
  patList[0]=(unsigned char)y;
  patList[1]=(unsigned char)x;
  *(volatile unsigned char *)0xC829 = pat;
  *(volatile unsigned char *)0xC823 =0;
  Draw_Pat_VL(patList);
}

// a lot of global state in this program.
int block_x, block_y;
char block_tag;
char dragged_block;
int dragged_block_orientation;
int can_move_forward, can_move_back;

void draw_block(char *p, int ox, int oy, int horizontal, char block) {
  int h,w,blocksize;
  if (horizontal) {
    h = 1; w = 2;
    if ((ox < 4) && (p[oy*6+ox+2] == block)) w = 3;
    blocksize = w;
  } else {
    h = 2; w = 1;
    if ((oy < 4) && (p[(oy+2)*6+ox] == block)) h = 3;
    blocksize = h;
  }

  Reset0Ref(); set_scale(DRAWING_SCALE); Intensity_5F();
  Moveto_d((6-oy)*SCALE-YOFF-GAP, ox*SCALE-XOFF+GAP); // top-left corner of where block will be drawn

  if (block == block_tag) {
    if (horizontal) {
      dragged_block_orientation = HORIZONTAL;
      can_move_forward = (((ox + w) < 6) && (p[oy*6 + ox + w] == '.'));
      can_move_back = (ox > 0) && (p[oy*6 + ox - 1] == '.');
    } else {
      dragged_block_orientation = VERTICAL;
      can_move_forward = (((oy + h) < 6) && (p[(oy+h)*6 + ox] == '.'));
      can_move_back = ((oy > 0) && (p[(oy-1)*6 + ox] == '.'));
    }
    // if there is room for this block to move, draw it dashed - otherwise draw it
    // solid but with Intensity_7F() ... make note of which direction movement is
    // possible. it may be both so need two flags: forward and back maps to right,down and up,left
    // depending on orientation
    Intensity_7F();
    if (can_move_forward || can_move_back) {
      drawline_patterned(0, w*SCALE-GAP*2, 0xC3);
      drawline_patterned(-h*SCALE+GAP*2, 0, 0xC3);
      drawline_patterned(0,-w*SCALE+GAP*2, 0xC3);
      drawline_patterned(h*SCALE-GAP*2, 0, 0xC3);
      if (block == 'X') {
        // The key block
        drawline_patterned(-h*SCALE+GAP*2, w*SCALE-GAP*2, 0xC3);
      }
      return;
    }
  }
  Draw_Line_d(0, w*SCALE-GAP*2);
  Draw_Line_d(-h*SCALE+GAP*2, 0);
  Draw_Line_d(0,-w*SCALE+GAP*2);
  Draw_Line_d(h*SCALE-GAP*2, 0);
  if (block == 'X') {
    Draw_Line_d(-h*SCALE+GAP*2, w*SCALE-GAP*2);
  }
}

void Move(char *p, char c, int dir) {
  int i,j;
  for (j = 0; j < 6; j++) {   // top to bottom
    for (i = 0; i < 6; i++) { // left to right
      if (p[j*6+i] == c) { // first one it hits, topmost or leftmost
        if ((i < 4) && (p[j*6+i+2] == c)) {
          // left of horizontal triple
          //fprintf(stderr, "[3] left/right\n");
          if ((dir==LEFT) && (i > 0) && (p[j*6+i-1] == '.')) {
            // horizontal triple move left
            //fprintf(stderr, "[3] left\n");
            p[j*6+i-1] = c; p[j*6+i+2] = '.';
          } else if ((dir==RIGHT) && (i < 3) && (p[j*6+i+3] == '.')) {
            // horizontal triple move right
            //fprintf(stderr, "[3] right\n");
            p[j*6+i+3] = c; p[j*6+i] = '.';
          }
        } else if ((i < 5) && (p[j*6+i+1] == c)) {
          // left of horizontal pair
          //fprintf(stderr, "[2] left/right\n");
          if ((dir==LEFT) && (i > 0) && (p[j*6+i-1] == '.')) {
            // horizontal pair move left
            //fprintf(stderr, "[2] left\n");
            p[j*6+i-1] = c; p[j*6+i+1] = '.';
          } else if ((dir==RIGHT) && (i < 4) && (p[j*6+i+2] == '.')) {
            // horizontal pair move right
            //fprintf(stderr, "[2] right\n");
            p[j*6+i+2] = c; p[j*6+i] = '.';
          }
        }

        if ((j < 4) && (p[(j+2)*6+i] == c)) {
          // top of vertical triple
          //fprintf(stderr, "[3] up/down\n");
          if ((dir==UP) && (j > 0) && (p[(j-1)*6+i] == '.')) {
            // vertical triple move up
            p[(j-1)*6+i] = c; p[(j+2)*6+i] = '.';
            //fprintf(stderr, "[3] up\n");
          } else if ((dir==DOWN) && (j < 3) && (p[(j+3)*6+i] == '.')) {
            // vertical triple down
            p[(j+3)*6+i] = c; p[j*6+i] = '.'; 
            //fprintf(stderr, "[3] down\n");
         }
        } else if ((j < 5) && (p[(j+1)*6+i] == c)) {
          // top of vertical pair
          //fprintf(stderr, "[2] up/down\n");
          if ((dir==UP) && (j > 0) && (p[(j-1)*6+i] == '.')) {
            // vertical pair move up
            p[(j-1)*6+i] = c; p[(j+1)*6+i] = '.';
            //fprintf(stderr, "[2] up\n");
          } else if ((dir==DOWN) && (j < 4) && (p[(j+2)*6+i] == '.')) {
            // vertical pair down
            p[(j+2)*6+i] = c; p[j*6+i] = '.';
            //fprintf(stderr, "[2] down\n");
          }
        }
        return;
      }
    }
  }
}

static unsigned int next_game = 0;

void draw_screen(char *p, int show_screen_no) {
  int i,j;
  char block;
  char lev[5] = { ' ', ' ', ' ', 0x80, '\0' };
  unsigned int tens, hundreds;
  Reset0Ref();
  if (show_screen_no || (Vec_Btn_State&1)) {
    // 2413 extra cycles for this: :-(
    int first_digit = 2;
    lev[0] = lev[1] = lev[2] = ' ';
    hundreds = 0U;
    tens = next_game/10U;
    if (tens >= 10U) { tens -= 10U; hundreds = 1U; first_digit = 0;}
    if (tens && !hundreds) first_digit = 1;

    if (hundreds) lev[first_digit++] = '1';
    if (tens || hundreds) lev[first_digit++] = (char)tens+'0';
    lev[first_digit] = (char)(next_game - tens*10 - hundreds*100)+'0';

    set_scale(DRAWING_SCALE); Intensity_7F();
    if (hundreds) {
      first_digit = -21;
    } else if (tens && !hundreds) {
      first_digit = -26;
    } else {
      // single_digit
      first_digit = -30;
    }
    Print_Str_d(-120, first_digit, lev);
  }
  Intensity_3F();
  set_scale(DRAWING_SCALE);
  Moveto_d(0*SCALE-YOFF-2-FUDGE, 0*SCALE-XOFF-2-FUDGE); // top-left corner of where block will be drawn
  // might be better to move to center of block at MOVESCALE before drawing at DRAWSCALE,
  // to allow for a small gap around the blocks...

#ifdef SCALETEST
  // on vectrex it is drawing with a small gap in the center of the three
  // long perimeters.  make sure there isn't a bug here in the handling of GAP...
  // (though most likely it is a vectrex issue.  It looks OK in the emulator)
  Draw_Line_d(0, 3*SCALE+GAP);
  Draw_Line_d(0, 3*SCALE+GAP);
#else
  Draw_Line_d(0, 6*SCALE+GAP*2);
#endif

  Draw_Line_d(3*SCALE, 0);
  Moveto_d(SCALE+GAP*2, 0);
  Draw_Line_d(2*SCALE, 0);

#ifdef SCALETEST
  Draw_Line_d(0, -3*SCALE-GAP);
  Draw_Line_d(0, -3*SCALE-GAP);
#else
  Draw_Line_d(0, -6*SCALE-GAP*2);
#endif

#ifdef SCALETEST
  Draw_Line_d(-3*SCALE-GAP, 0);
  Draw_Line_d(-3*SCALE-GAP, 0);
#else
  Draw_Line_d(-6*SCALE-GAP*2, 0);
#endif

  for (j = 0; j < 6; j++) {   // top to bottom
    for (i = 0; i < 6; i++) { // left to right
      block = p[j*6+i];
      if (block == '.') continue;
      if ((i > 0) && (p[j*6+i-1] == block)) {
        // horizontal block handled already
      } else if ((i < 5) && (p[j*6+i+1] == block)) {
        // horizontal block
        draw_block(p, i,j, TRUE, block);
      } else if ((j > 0) && (p[(j-1)*6+i] == block)) {
        // vertical block handled already
      } else if ((j < 5) && (p[(j+1)*6+i] == block)) {
        // vertical block
        draw_block(p, i,j, FALSE, block);
      } else {
        // error
      }
    }
  }
}

void fetch_new_game(char *p) {
  int i,j;

  if (next_game == NUM_GAMES) {
    // Need some bells and whsitles to congratulate the player.
    for (;;) {
      // Should we add a loud musical fanfare?
      Wait_Recal();
      Reset0Ref(); set_scale(DRAWING_SCALE); Intensity_7F();
      Print_Str_d(-120, -48, "CHAMPION!\x80");
      draw_screen(p, FALSE);
    }
  }
  for (j = 0; j < 6; j++) {   // top to bottom
    for (i = 0; i < 6; i++) { // left to right
      p[j*6+i] = puzzle[next_game][j*6+i]; // copy to writable string
    }
  }
  next_game += 1;
}

void Get_Cursor(char *p) {
  int mouse_x, mouse_y;

  // setting this to 0x7f or 0x80 freezes tracki
  //*(volatile int *)0xC81A = (int)0x80; // minimum analog resolution
  Joy_Analog();

  // snap mouse to nearest block
  // avoid divides.
  mouse_x = (((Vec_Joy_1_X>>1)+1) >> 4) + 3;
  mouse_y = (((Vec_Joy_1_Y>>1)+1) >> 4) + 2;

  if (mouse_x < 0) mouse_x = 0;
  if (mouse_y < 0) mouse_y = 0;
  if (mouse_x > 5) mouse_x = 5;
  if (mouse_y > 5) mouse_y = 5;

  block_x = mouse_x; // grid coordinates within p[]
  block_y = 5-mouse_y;
  mouse_y = (mouse_y-3) * SCALE + SCALE/2;
  mouse_x = (mouse_x-3) * SCALE + SCALE/2;

  block_tag = p[block_y*6+block_x]; // used to highlight selected block

  // There is a problem displaying the cursor, where it gets distorted depending
  // on the y coordinate.  Make sure that the cause is a vectrex problem and not
  // an actual bug, perhaps caused by wrong scaling of the positioning of the
  // beam before drawing the two bars of the X. (although it does look OK to me)
  Reset0Ref(); set_scale(DRAWING_SCALE); Intensity_7F();
  // Draw an X at the cursor
  Moveto_d(mouse_y-CURSOR_SIZE, mouse_x-CURSOR_SIZE);
  // lower left to top right
  Draw_Line_d(CURSOR_SIZE*2,CURSOR_SIZE*2); //   /
  Reset0Ref(); set_scale(DRAWING_SCALE); Intensity_7F();
  Moveto_d(mouse_y+CURSOR_SIZE, mouse_x-CURSOR_SIZE);
  // upper left to lower right
  Draw_Line_d(-CURSOR_SIZE*2,CURSOR_SIZE*2); //   \ ...
}


typedef enum {WAITING, MOVING, STOPPING, DONE} STATE;
STATE state = WAITING; // set to DONE to skip intro animation
#define set_scale(s) do { VIA_t1_cnt_lo = s; } while (0)
static long reset_size;
struct cartridge_t // from cartridge.c unfortunately
{
	char copyright[11];			// copyright string, must start with "g GCE" and must end with "\x80"
	const void* music;			// 16 bit memory adress of title music data
	signed int title_height;	// signed 8 bit value, height of game title letters
	unsigned int title_width;	// unsigned 8 bit value, width of game title letters
	int title_y;				// signed 8 bit value, y coordinate of game title
	int title_x;				// signed 8 bit value, x coordinate of game title
	char title[]; 				// game title string, must end with "\x80\x00"
};

extern struct cartridge_t game_header __attribute__((section(".cartridge"))); // dropped const.

void fake_title(int x, int y) {
    Reset0Ref(); set_scale(0x70);
    // default offset is -16 -72
    Moveto_d(15,73);
    Moveto_d(game_header.title_y,game_header.title_x);
    Moveto_d(y,x);
    *(volatile long *)0xC82A = reset_size;
    Print_Str_d(69,-45, game_header.title);
    Reset0Ref(); set_scale(0x70);
    Moveto_d(y,x);
    set_scale(0x38); Moveto_d(0,1);
    *(volatile long *)0xC82A = (long)0xf848; // ht wd
    Print_Str_d(21,-38, game_header.copyright);
}

static int sin, cos;

int main(void) {
  // int steps, direction; char block;
  int prev_x, prev_y;
  unsigned int Prev_Btn_State = 0;
  int mouse_down = 0, mouse_was_down = 0;
  char p[6*6];
  int delay = 127;
  sin = -96; cos = 1;
  reset_size = *(volatile long *)0xC82A;
  for (;;) {

    switch (state) {
      case WAITING:  Wait_Recal();
                     Intensity_7F();
                     fake_title(cos-31, sin);
                     delay -= 1;
                     if (delay == 0) {
                       state = MOVING;
                       delay = 2; // loops/2
                     }
                     break;
      case MOVING:   Wait_Recal();
                     Intensity_7F();
                     sin = sin - (cos >> 4);
                     cos = cos + (sin >> 4);
                     fake_title(cos-31, sin);
                     if (sin == -96 && cos == 1) {
                       delay -= 1;
                       if (delay == 0) {
                         state = STOPPING;
                         delay = 0x7F;
                       }
                     }
                     break;
      case STOPPING: Wait_Recal();
                     Intensity_a((unsigned int)delay);
                     fake_title(cos-31, sin); // fade out
                     delay -= 1;
                     if (delay == 0) state = DONE;
                     break;
      case DONE:     goto PLAY;
                     break;
    }

  }
PLAY:
  dragged_block = 0, dragged_block_orientation = -1;
  can_move_forward = FALSE; can_move_back = FALSE; // short for 'dragged block can move forward'
  fetch_new_game(p);
  Vec_Joy_Mux_1_X = 1; // enable analog joystick mode
  Vec_Joy_Mux_1_Y = 3;
  Get_Cursor(p); // place result in block_x, block_y
  prev_x = block_x; prev_y = block_y; // initialise.

  for (;;) {
    Wait_Recal();
    Reset0Ref(); set_scale(DRAWING_SCALE);

    block_tag = 0;
    Get_Cursor(p); // place result in block_x, block_y

    draw_screen(p, FALSE); // It is IMPORTANT that the screen is drawn before any
                           // drag/drop actions are taken.  If you don't understand why
                           // then DO NOT move this line...

    if (block_tag) { // cursor is over a block, not an empty square
      Read_Btns();
      // if we use buttons to skip forward and back through the levels, add that here...
      if (  ((Vec_Btn_State&3) == 3) && ((Prev_Btn_State&3) != 3)  ) {
        if (next_game > 1) {
          next_game -= 2;
          fetch_new_game(p);
        }
      } else if (  ((Vec_Btn_State&5) == 5) && ((Prev_Btn_State&5) != 5)  ) {
        if (next_game < NUM_GAMES) {
          fetch_new_game(p);
        }
      }
      Prev_Btn_State = Vec_Btn_State;
      mouse_down = ((Vec_Btn_State & 8) != 0);
      if (mouse_down && !mouse_was_down) {
        // this was a click
        dragged_block = block_tag; // all the cool stuff will happen on the next frame
      } else if (mouse_down) {
        // this is a continuing drag
        // constrain movement within allowed directions, move block to nearest square
        // which may be no movement at all
        block_tag = dragged_block;// force it to stay the same even of cursor wandering
        if (dragged_block_orientation == HORIZONTAL) {
          if ((block_x < prev_x) && can_move_back) {
            Move(p, block_tag, LEFT);
          } else if ((block_x > prev_x) && can_move_forward) {
            Move(p, block_tag, RIGHT);
          }
        } else if (dragged_block_orientation == VERTICAL) {
          if ((block_y < prev_y) && can_move_back) {
            Move(p, block_tag, UP);
          } else if ((block_y > prev_y) && can_move_forward) {
            Move(p, block_tag, DOWN);
          }
        }
      } else if (mouse_was_down) { // (but is no longer...)
        // end of drag - drop now.
        // actually if we were moving on the fly, nothing really needs to
        // be done except for unsetting a few variables
        dragged_block = 0; dragged_block_orientation = -1;
        can_move_forward = FALSE; can_move_back = FALSE;
        block_tag = 0; // probably should also do this.
      } else {
        // no change, just redraw
      }
      mouse_was_down = mouse_down;
    }

    if ((p[2*6+4] == 'X') && (p[2*6+5] == 'X')) {
      long t;
      for (t = 0L; t < 128L; t++) {
        // Freeze the display for a short time before starting next level!
        // should we add a small musical feedback?
        // Would also be nice to allow a pause here so that the player can
        // photograph the final position as proof of completion.
        // I considered freezing by default until a button was pressed to
        // move on, but rejected it as bad UI design.
        // But if we invent a pause button, need to show a "PAUSED" message
        // which then fades out to allow a clean photograph. And do we also
        // have to put up a message saying which button to use to unpause?
        // This is all getting rather messy... so in the end, doing nothing.
        Wait_Recal();
        Reset0Ref(); set_scale(DRAWING_SCALE);
        Print_Str_d(-120, -76, "LEVEL    SOLVED\x80");
        draw_screen(p, TRUE); // force screen no to be drawn
      }
      fetch_new_game(p);
    }
    prev_x = block_x;
    prev_y = block_y;
  }
  (void)Version;
  return 0;
}