#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; }