/* MINESWEEPER Alvin Albrecht 09.2003 zcc +zx -vn minesweeper.c -o minesweeper.bin -lsplib2 -lmalloc -lndos TS2068 version available if 'TS2068' is defined A potential critical section problem exists with the variables 'time' and 'vbi' since both the main program and the interrupt service routine read and write these variables. The situation is helped out by the fact that these variables are written to atomically (an interrupt cannot occur between bytes written to the two-byte variable 'time'). I've considered each case below where 'time' and 'vbi' are written and found that although the critical section problem does exist, nothing bad can happen as a result of it, the worst being a measured time off by one second. */ /* #define TS2068 */ #include #include #include #include #include #pragma output STACKPTR=61440 #ifdef TS2068 #define RATE 60 /* SCLD interrupt rate */ #define AYREG 0xf5 /* sound effects haven't been added yet */ #define AYDAT 0xf6 /* maybe sometime in the near future */ #else #define RATE 50 /* ULA interrupt rate */ #define AYREG 0xfffd #define AYDAT 0xbffd #endif #define MENURATE 15 /* Sprite Pack Variables from Header File */ extern struct sp_Rect *sp_ClipStruct; #asm LIB SPCClipStruct ._sp_ClipStruct defw SPCClipStruct #endasm extern uchar *sp_NullSprPtr; #asm LIB SPNullSprPtr ._sp_NullSprPtr defw SPNullSprPtr #endasm /* Basic System Variables */ extern uchar *lastk; #asm ._lastk defw 23560; #endasm extern uint *frames; #asm ._frames defw 23672 #endasm /* Messages */ #define MSG_NONE 0 #define MSG_START 1 /* during intro screen */ #define MSG_STOP 1 /* during gameplay */ #define MSG_LEFT_UP 0 #define MSG_LEFT_DOWN 1 #define MSG_LEFT_CANCEL 2 #define MSG_RIGHT_DOWN 3 #define HPY_SMILEUP 0x81 /* these are specific graphic images */ #define HPY_SMILEDOWN 0x80 /* top two bits are important -- see MrHappy() */ #define HPY_COOLUP 0x41 /* bit 0 set if it's an up position */ #define HPY_COOLDOWN 0x40 #define HPY_OH 0x82 #define HPY_DEAD 0x83 #define HPY_DOWN 0 /* these are messages for down or up state of happy image */ #define HPY_UP 1 #define EXT_UP 0 #define EXT_DOWN 1 /* Graphics */ #define GR_BIG0 0x80 #define GR_BOMB 0x95 #define GR_QUESTION 0x96 #define GR_FLAG 0x97 #define GR_SQUAREUP 0x98 #define GR_SQUAREDOWN 0x99 #define GR_SMALL0 0x9a #define GR_HPYSMILEUP 0xa4 #define GR_HPYSMILEDOWN 0xa8 #define GR_HPYOH 0xac #define GR_HPYDEAD 0xb0 #define GR_HPYCOOLUP 0xb4 #define GR_HPYCOOLDOWN 0xb8 #define GR_XUP 0xc6 #define GR_XDOWN 0xc7 /* Function Prototypes */ uchar DoMouse(void); uchar ClickHappy(uchar msg); uchar ClickExit(uchar msg); uchar ClickLevel(uchar msg); uchar ClickBoard(uchar msg); void Loc2BoardCoord(uint loc, uchar *x, uchar *y); void VisitNeighbours(uint loc, void *f); void RevealLocation(uint loc, uchar attr); void bombcountup(uint loc); void Discover(uint loc); void NewSkillLevel(uchar s); void WriteScores(uchar *s); void InsertLatestScore(uint i, uint *table); void DrawNumber(uchar *s, uint i, void *f); void bignumber(uchar *s, uchar i); void smallnumber(uchar *s, uchar i); void MrHappy(uchar msg); void ExitButton(uchar msg); /* Game State */ uchar gamestate; /* 0 = intro no menu, 1 = intro with menu, 2 = playing */ uchar skill; /* 0 = beginner, 1 = average, 2 = expert */ uint mines; /* number of mines at this skill level */ uchar bx, by; /* game board dimensions at this skill level */ uchar cx, cy; /* game board location on screen */ uchar gameboard[480]; /* 'bx' by 'by' game board in column-major order */ /* bits 3..0: number of neighbour mines or 10 if bomb */ /* bits 7..5: question, flag, uncovered (one bit set) */ /* bit 4 : visible (set if contents appears on screen) */ /* (top nibble all set if square visited during discovery) */ #define BD_COVER 0xf0 #define BD_CONTENT 0x0f #define BD_QUESTION 0x80 #define BD_FLAG 0x40 #define BD_SQUARE 0x20 #define BD_VISIBLE 0x10 #define BD_DISCOVERED 0xf0 #define BD_MINE 0x0a int minesleft; /* player's guess at number of mines remaining */ uint covered; /* number of squares still covered; reaches zero when player wins */ uint scores[] = { /* top five scores at each skill level */ 100,200,300,400,500,999, /* beginner */ 300,400,500,600,700,999, /* average */ 500,600,700,800,900,999 /* expert */ }; /* extra 999 needed by tic-toc sound effect */ uchar cheat[] = "oracle"; /* an easter egg but you have to pay for it! */ uchar *cseq; uint time; /* counts time upward in seconds, stops at 999 */ uchar vbi; /* counts time in interrupts */ uint keyM; /* alternate fire 1 key (reveal) */ uint keyF; /* alternate fire 2 key (flag) */ struct sp_UDK keys; /* direction keys for keyboard mouse */ struct sp_MD deltas[] = { /* keyboard mouse acceleration profile */ {24, 0x0100, 0x0100}, /* 24 times, 1 pixel dx, 1 pixel dy */ {24, 0x0200, 0x0200}, /* 24 times, 2 pixel dx, 2 pixel dy */ {255, 0x0300, 0x0300} /* final delta must have a count of 255 */ }; struct sp_UDM keymouse; /* keyboard mouse structure */ void *mousein; /* function pointer for reading mouse */ void *mousemove; /* function pointer for moving mouse */ struct actions { /* describes clickable regions on screen */ struct sp_LargeRect lr; /* an active rectangle in pixel coordinates */ void *func; /* a function that deals with mouse clicks */ /* uchar (*func)(uchar msg) */ /* msg = MSG_LEFT_UP, MSG_LEFT_DOWN, MSG_LEFT_CANCEL, MSG_RIGHT_DOWN */ /* returns a status message = MSG_NONE, MSG_START, MSG_STOP */ }; struct sp_List *click; /* list of struct actions (clickable regions) */ extern struct actions *ActiveRegions; /* a bug in z88dk v1.5 prevents this struct */ #asm /* from being statically initialized because */ ._ActiveRegions defw _arhere /* it contains another struct. Dom has now */ ._arhere /* since fixed this in cvs but I will leave */ defw 1, 6, 249, 254, _ClickExit /* this work-around in place for now. */ defw 9, 14, 65, 230, _ClickLevel defw 33, 46, 121, 134, _ClickHappy defw 0, 0, 0, 0, _ClickBoard /* active area varies depending on skill level */ #endasm char bigdisp; /* belongs to bignumber() */ /* Graphics Related */ struct sp_PSS ps; extern uchar arrow[]; /* address of pointer graphic */ extern uchar UDGstart[]; /* address of first tile graphic following main() */ struct sp_SS *ptr; /* pointer sprite */ /* normally strings' contents are const and you are */ /* not allowed to change them, but I do in this program */ uchar gamewindow[] = "\x16\x00\x00\x14\x69\x95\x14\x38\x0e\x06\xc4\x0f\x14\x4f\x0e\x03 \x0f" \ "MINESWEEPER\x0e\x04 \x0f\x14\x38\x0e\x06\xc4\x0f\x14\x69\xc6" \ "\x0e\x16\x14\x38\xbd\x14\x28\x0e\x1e\x94\x0f\x14\x38\xc2\x0f" \ "\x16\x03\x00\xc0\x0e\x1e\xc4\x0f\xc5" \ "\x0e\x02\xbd\x0e\x1e\x94\x0f\xc2\x0f" \ "\xc0\x0e\x1e\xc4\x0f\xc5" \ "\x16\x17\x00\xbe\x0e\x1e\xc4\x0f\xc3"; uchar gamelvl[] = "\x16\x01\x01\x14\x0c GAME \x14\x19Novice\x14\x0b " \ "\x14\x0bAverage\x14\x0b \x14\x0bExpert\x14\x0b "; uchar bestscores[] = "\x16\x02\x01\x14\x0c BEST \x14\x0b 000 000 000 000 000 "; uchar happyface[] = "\x16\x04\x0f\x14\x30\xa4\xa6\x16\x05\x0f\xa5\xa7"; uchar bigdigits[] = "\x16\x04\x02\x14\x02\x80\x82\x80\x06\x1b\x80\x80\x80" \ "\x16\x05\x02\x81\x83\x81\x06\x1b\x81\x81\x81"; #ifdef TS2068 uchar controlmenu[] = "\x16\x0a\x07\x14\x041 = Keyboard QAOP\xc8F" \ "\x16\x0c\x072 = Kempston Mouse " \ "\x16\x0e\x073 = AMX Mouse\x0e\x06 \x0f" \ "\x16\x10\x074 = TS2068 Joystick" \ "\x16\x12\x03\x14\x06\x7f 2003 AA in C/Z88DK/SPLIB"; #else uchar controlmenu[] = "\x16\x0a\x07\x14\x041 = Keyboard QAOP\xc8F" \ "\x16\x0c\x072 = Kempston Mouse " \ "\x16\x0e\x073 = AMX Mouse\x0e\x06 \x0f" \ "\x16\x10\x074 = Kempston Joy " \ "\x16\x12\x03\x14\x06\x7f 2003 AA in C/Z88DK/SPLIB"; #endif uchar clearboard[] = "\x16\x07\x01\x14\x28\x0e\x10\x0e\x1e\x94\x0f\x17\x01\xe2\x0f"; uchar drawboard[] = "\x16\x0a\x0c\x14\x38\x0e\x08\x1a\x0e\x08\x98\x0f\x1c\x0b\x0f"; struct sp_Rect menuarea = {10,3,9,26}; /* Mouse State and Input */ uint x; uchar y, b, old_b; struct actions *current; int collide(void *dummy, struct actions *ar) { if (sp_IntPtLargeRect(x, y, ar)) /* first item in *ar is a large rect */ return_c(1); return_nc(0); } /* Delivers mouse click actions to clickable regions on screen. */ /* If a MSG_LEFT_DOWN is delivered, will *always* deliver a following */ /* MSG_LEFT_UP (indicating activation) or MSG_LEFT_CANCEL (indicating */ /* pointer left clickable region while left button pressed). */ /* Only delivers MSG_RIGHT_DOWN if left button is not pressed and */ /* does not change state. */ uchar DoMouse(void) { uchar n; n = MSG_NONE; (mousein)(&keymouse, &x, &y, &b); sp_MoveSprAbs(ptr, sp_ClipStruct, 0, y/8, x/8, x&7, y&7); if (sp_KeyPressed(keyM)) /* key M is alternative left mouse button */ b &= ~sp_BUT1; if (sp_KeyPressed(keyF)) /* key F is alternative right mouse button */ b &= ~sp_BUT2; /* deal with right button */ if ((((b ^ old_b) & sp_BUT1) == 0) && ((b & sp_BUT1) != 0)) { if ((((b ^ old_b) & sp_BUT2) != 0) && ((b & sp_BUT2) == 0)) { sp_ListFirst(click); if ((current = sp_ListSearch(click, collide, 0)) != NULL) n = (current->func)(MSG_RIGHT_DOWN); } } /* deal with left button */ if (((b ^ old_b) & sp_BUT1) != 0) { /* left click up or left click down */ if ((b & sp_BUT1) == 0) { /* left click down */ sp_ListFirst(click); if ((current = sp_ListSearch(click, collide, 0)) != NULL) n = (current->func)(MSG_LEFT_DOWN); else b |= sp_BUT1; } else if (current != NULL) { /* left click up (release, activation) */ n = (current->func)(MSG_LEFT_UP); current = NULL; } } else if (((b & sp_BUT1) == 0) && (current != NULL)) { /* left button being held down */ if (!sp_IntPtLargeRect(x, y, current)) { n = (current->func)(MSG_LEFT_CANCEL); current = NULL; b |= sp_BUT1; } } old_b = b; return n; } /* VBI Interrupt Service Routine */ #asm defw 0 /* an interrupt hook requires two free bytes prior to start */ #endasm void my_isr(void) { uchar c; if ((c = sp_GetKey()) != 0) /* if a key was pressed */ *lastk = c; if (++vbi == RATE) { /* count time */ vbi = 0; if (time != 999) time++; } } /* Clickable Area Handlers */ uchar ClickHappy(uchar msg) { if (msg == MSG_LEFT_UP) /* mr happy drawn in main loop for this case */ return (gamestate == 2) ? MSG_STOP : MSG_START; if (msg == MSG_LEFT_DOWN) MrHappy(HPY_DOWN); else if (msg == MSG_LEFT_CANCEL) MrHappy(HPY_UP); return MSG_NONE; } uchar ClickExit(uchar msg) { if (msg == MSG_LEFT_DOWN) ExitButton(EXT_DOWN); else if (msg == MSG_LEFT_CANCEL) ExitButton(EXT_UP); else if (msg == MSG_LEFT_UP) { ExitButton(EXT_UP); if (gamestate == 2) /* right now exit will only terminate a game */ return MSG_STOP; } return MSG_NONE; } uchar ClickLevel(uchar msg) { uchar n; if (gamestate == 2) /* can't change level while playing game */ return MSG_NONE; if (msg == MSG_LEFT_UP) { /* using sprite pointer coordinate to figure out what's been clicked */ if ((n = ptr->col) < 14) /* novice */ NewSkillLevel(0); else if ((n > 14) && (n < 22)) /* average */ NewSkillLevel(1); else if (n > 22) /* expert */ NewSkillLevel(2); else return MSG_NONE; sp_PrintString(&ps, gamelvl); sp_PrintString(&ps, clearboard); sp_PrintString(&ps, drawboard); gamestate = time = 0; /* move to intro screen without menu */ sp_GetTiles(&menuarea, gameboard); /* save new background tiles underneath menu */ bigdisp = 11; /* enter # mines into bigdigits string */ DrawNumber(bigdigits + 7, mines, bignumber); sp_PrintString(&ps, bigdigits); WriteScores(bestscores + 17); sp_PrintString(&ps, bestscores); } return MSG_NONE; } uchar cb_oldx, cb_oldy, cb_oldbrd; uint cb_oldloc; struct sp_LargeRect boardarea; uchar ClickBoard(uchar msg) { uchar x, y, n; uint loc; if (gamestate != 2) /* board only sensitive while game on */ return MSG_NONE; if (msg == MSG_LEFT_CANCEL) { /* restore clickable region to full board */ memcpy(&ActiveRegions[3].lr, &boardarea, sizeof(struct sp_LargeRect)); MrHappy(HPY_UP); if ((cb_oldbrd & BD_COVER) == BD_SQUARE) sp_PrintAtInv(cb_oldy, cb_oldx, INK_BLACK | PAPER_WHITE, GR_SQUAREUP); return MSG_NONE; } /* figure out what square in board has been clicked, with help of sprite coords */ x = ptr->col; y = ptr->row; loc = (x - cx) * by + y - cy; n = gameboard[loc]; if (msg == MSG_LEFT_DOWN) { cb_oldx = x; cb_oldy = y; cb_oldbrd = n; cb_oldloc = loc; /* set clickable region to this square only */ ActiveRegions[3].lr.bottom = (ActiveRegions[3].lr.top = y * 8) + 7; ActiveRegions[3].lr.right = (ActiveRegions[3].lr.left = x * 8) + 7; if ((n & BD_COVER) == BD_SQUARE) { MrHappy(HPY_OH); sp_PrintAtInv(y, x, INK_BLACK | PAPER_WHITE, GR_SQUAREDOWN); } } else if (msg == MSG_LEFT_UP) { /* restore clickable area to full board */ memcpy(&ActiveRegions[3].lr, &boardarea, sizeof(struct sp_LargeRect)); if ((cb_oldbrd & BD_COVER) == BD_SQUARE) { MrHappy(HPY_UP); if ((cb_oldbrd & BD_CONTENT) == BD_MINE) { sp_PrintAtInv(cb_oldy, cb_oldx, INK_BLACK | PAPER_RED, GR_BOMB); gameboard[cb_oldloc] |= BD_VISIBLE; return MSG_STOP; } Discover(cb_oldloc); } } else if ((msg == MSG_RIGHT_DOWN) && ((n & BD_VISIBLE) == 0)) { /* rotate so that square->flag, flag->question, question->square */ if ((n & BD_QUESTION) == 0) n = ((n & 0xe0) << 1) | (n & BD_CONTENT); else n += 0xa0; gameboard[loc] = n; /* draw new square's graphic */ if ((n & BD_SQUARE) == 0) sp_PrintAtInv(y, x, INK_BLACK | PAPER_CYAN, (n & BD_FLAG) ? GR_FLAG : GR_QUESTION); else sp_PrintAtInv(y, x, INK_BLACK | PAPER_WHITE, GR_SQUAREUP); /* adjust mine count if a flag or question mark */ if ((n & (BD_FLAG | BD_QUESTION)) != 0) { if ((n & BD_FLAG) != 0) minesleft--; else minesleft++; if (minesleft >= 0) { bigdisp = 11; DrawNumber(bigdigits + 7, minesleft, bignumber); /* no need to update on screen as that's done when time is printed in main loop */ } } } return MSG_NONE; } /* Game Board Operations */ void Loc2BoardCoord(uint loc, uchar *x, uchar *y) { *y = loc - (*x = loc / by) * by; } void VisitNeighbours(uint loc, void *f) { uchar x, y; Loc2BoardCoord(loc, &x, &y); if (y != 0) (f)(loc - 1); /* up */ if (x != 0) (f)(loc - by); /* left */ if (x != bx - 1) (f)(loc + by); /* right */ if (y != by - 1) (f)(loc + 1); /* down */ if ((x != 0) && (y != 0)) (f)(loc - by - 1); /* up left */ if ((x != bx - 1) && (y != 0)) (f)(loc + by - 1); /* up right */ if ((x != 0) && (y != by - 1)) (f)(loc - by + 1); /* down left */ if ((x != bx - 1) && (y != by - 1)) (f)(loc + by + 1); /* down right */ } void RevealLocation(uint loc, uchar attr) { uchar x, y, n; if (((n = gameboard[loc]) & BD_VISIBLE) == 0) { covered--; gameboard[loc] |= BD_VISIBLE; Loc2BoardCoord(loc, &x, &y); x += cx; y += cy; if ((n &= BD_CONTENT) == BD_MINE) sp_PrintAtInv(y, x, (INK_BLACK | PAPER_WHITE) + attr, GR_BOMB); else if (n == 0) sp_PrintAtInv(y, x, (INK_BLACK | PAPER_WHITE) + attr, GR_SMALL0); else sp_PrintAtInv(y, x, (INK_BLUE | PAPER_WHITE) + ((n - 1) & 0x03) + attr, n + GR_SMALL0); } } void bombcountup(uint loc) { if ((gameboard[loc] & BD_CONTENT) != BD_MINE) gameboard[loc]++; } void Discover(uint loc) { uchar n; n = gameboard[loc]; /* if this square has not been discovered already and it is not a mine */ if (((n & BD_COVER) != BD_DISCOVERED) && ((n & BD_CONTENT) != BD_MINE)) { /* if it's an invisible square (ie grey block) */ if ((n & (BD_VISIBLE | BD_SQUARE)) == BD_SQUARE) RevealLocation(loc, 0); /* if it's got 0 mines around it and it's not marked with flag or ? */ if (((n & (BD_QUESTION | BD_FLAG)) == 0) && ((n & BD_CONTENT) == 0)) { gameboard[loc] |= BD_DISCOVERED; VisitNeighbours(loc, Discover); /* warning stack depth could crash a 6502 */ } } } void NewSkillLevel(uchar s) { switch (skill = s) { case 0: /* beginner */ mines = 10; bx = by = drawboard[9] = drawboard[6] = 8; cy = drawboard[1] = 10; cx = drawboard[2] = 12; s = 13; break; case 1: /* average */ mines = 40; bx = by = drawboard[9] = drawboard[6] = 16; cy = drawboard[1] = 7; cx = drawboard[2] = 8; s = 24; break; default: /* expert */ mines = 99; by = drawboard[6] = 16; bx = drawboard[9] = 30; cy = drawboard[1] = 7; cx = drawboard[2] = 1; s = 36; break; } /* highlights current skill level */ gamelvl[13] = gamelvl[24] = gamelvl[36] = INK_MAGENTA | PAPER_BLUE; gamelvl[s] = INK_BLUE | PAPER_MAGENTA; /* sizes clickable board area */ boardarea.bottom = (boardarea.top = cy * 8) + (by * 8) - 1; boardarea.right = (boardarea.left = cx * 8) + (bx * 8) - 1; memcpy(&ActiveRegions[3].lr, &boardarea, sizeof(struct sp_LargeRect)); } /* Best Scores */ void WriteScores(uchar *s) { uint *sc; uchar n; sc = &scores[skill*6]; for (n=0; n!=5; n++) { DrawNumber(s, *sc, smallnumber); sc++; s+=4; } } void InsertLatestScore(uint i, uint *table) { uchar n; for (n=0; n!=5; n++, table++) if (*table > i) break; while (n!=5) { sp_Swap(table, &i, 2); n++; table++; } } /* For Writing Three Digit Numbers */ void DrawNumber(uchar *s, uint i, void *f) { uchar n; for (n = 0; n != 3; n++) { (f)(s--, i%10); i /= 10; } } void bignumber(uchar *s, uchar i) { *(s+bigdisp) = (*s = i*2 + GR_BIG0) + 1; } void smallnumber(uchar *s, uchar i) { *s = i + '0'; } /* Buttons That Know How to Draw Themselves */ uchar happystate; void MrHappy(uchar msg) { uchar n; if (msg == HPY_UP) n = happystate; else if (msg == HPY_DOWN) n = happystate & 0xc0; else { if ((msg & 0x01) != 0) happystate = msg; n = msg; } /* find background tile for mr happy's state */ switch(n) { case HPY_SMILEUP: n = GR_HPYSMILEUP; break; case HPY_SMILEDOWN: n = GR_HPYSMILEDOWN; break; case HPY_COOLUP: n = GR_HPYCOOLUP; break; case HPY_COOLDOWN: n = GR_HPYCOOLDOWN; break; case HPY_OH: n = GR_HPYOH; break; case HPY_DEAD: default: n = GR_HPYDEAD; break; } happyface[5] = n++; happyface[10] = n++; happyface[6] = n++; happyface[11] = n; sp_PrintString(&ps, happyface); } void ExitButton(uchar msg) { sp_PrintAtInv(0, 31, INK_BLUE | PAPER_CYAN, (msg == EXT_UP) ? GR_XUP : GR_XDOWN); } /* Blank Sprite -- A Trick for Reducing Memory Used by Sprites */ uint n; void BlankSpr(struct sp_CS *cs) { if (n == 0) cs->graphic = sp_NullSprPtr; else n--; } /* Memory Allocation Policy */ HEAPSIZE(256) void *u_malloc = malloc; void *u_free = free; main() { uint size; uchar *temp; uint loc; /* Register Interrupt Service Routine */ #asm di #endasm sp_InitIM2(0xf1f1); sp_CreateGenericISR(0xf1f1); sp_RegisterHook(255, my_isr); /* 50Hz VBI vector */ sp_MouseAMXInit(0, 2); #asm ei #endasm /* Associate UDG Graphics with Character Codes -- see below main() */ temp = UDGstart; for (n = 0x80; n != 0xc9; n++, temp += 8) sp_TileArray(n, temp); /* Various Initializations */ sp_Border(BLACK); sp_Initialize(INK_WHITE | PAPER_BLACK, ' '); heapinit(256); srand(*frames); click = sp_ListCreate(); for (n = 0; n != 4; n++) sp_ListAppend(click, &ActiveRegions[n]); /* Create Pointer Sprite */ ptr = sp_CreateSpr(sp_MASK_SPRITE, 3, arrow, 0, TRANSPARENT); sp_AddColSpr(ptr, arrow, TRANSPARENT); n = 3; sp_IterateSprChar(ptr, BlankSpr); /* Default User Input */ keys.up = sp_LookupKey('q'); keys.down = sp_LookupKey('a'); keys.left = sp_LookupKey('o'); keys.right = sp_LookupKey('p'); keys.fire = sp_LookupKey(' '); keyF = sp_LookupKey('f'); keyM = sp_LookupKey('m'); keymouse.keys = &keys; keymouse.joyfunc = sp_JoyKeyboard; keymouse.delta = deltas; mousein = sp_MouseSim; mousemove = sp_SetMousePosSim; sp_SetMousePosSim(&keymouse, 127, 64); /* Draw Minesweeper Window */ ps.bounds = sp_ClipStruct; ps.flags = sp_PSS_INVALIDATE | sp_PSS_XWRAP | sp_PSS_YINC; NewSkillLevel(0); sp_PrintString(&ps, gamewindow); sp_PrintString(&ps, gamelvl); sp_PrintString(&ps, bigdigits); sp_PrintString(&ps, drawboard); MrHappy(HPY_SMILEUP); old_b = 0xff; time = MENURATE; while(1) { WriteScores(bestscores + 17); sp_PrintString(&ps, bestscores); /* Introduction Screen */ gamestate = vbi = 0; temp = gameboard; /* use gameboard as free space during intro */ sp_GetTiles(&menuarea, temp); /* make copy of screen area underneath control menu */ while(1) { sp_Wait(1); /* syncing with vbl interrupt helps reduce */ sp_UpdateNow(); /* visibility of tearing during update */ if ((n = DoMouse()) == MSG_START) break; if (((n = getk()) != 0) && (gamestate == 0)) time = MENURATE; /* if key is pressed, throw up control menu */ switch(n) { case '1': mousein = sp_MouseSim; mousemove = sp_SetMousePosSim; sp_SetMousePosSim(&keymouse, x, y); keymouse.joyfunc = sp_JoyKeyboard; break; case '2': mousein = sp_MouseKempston; mousemove = sp_SetMousePosKempston; sp_SetMousePosKempston(x, y); break; case '3': mousein = sp_MouseAMX; mousemove = sp_SetMousePosAMX; sp_SetMousePosAMX(x, y); break; case '4': mousein = sp_MouseSim; mousemove = sp_SetMousePosSim; sp_SetMousePosSim(&keymouse, x, y); #ifdef TS2068 keymouse.joyfunc = sp_JoyTimexEither; #else keymouse.joyfunc = sp_JoyKempston; #endif break; default: break; } if (time >= MENURATE) { time = 0; if (gamestate++ == 0) sp_PrintString(&ps, controlmenu); else { gamestate = 0; sp_PutTiles(&menuarea, temp); sp_Invalidate(&menuarea, sp_ClipStruct); } } } gamestate = 2; sp_PrintString(&ps, clearboard); sp_PrintString(&ps, drawboard); MrHappy(HPY_SMILEUP); /* Create Minefield */ size = bx * by; memset(gameboard, BD_SQUARE, size); for (n = mines; n != 0; n--) { /* no need to be clever here as this is fast enough (notice the pause on expert?) */ do { loc = (rand()&0x7fff) % size; } while ((gameboard[loc] & BD_CONTENT) == BD_MINE); gameboard[loc] = BD_SQUARE | BD_MINE; VisitNeighbours(loc, bombcountup); } /* Play Game */ time = vbi = 0; /* let's be fair and start the clock right at zero */ covered = size - mines; /* number of squares to uncover to win */ minesleft = mines; /* player's guess at number of mines remaining */ cseq = cheat; bigdisp = 11; /* enter # mines into bigdigits string */ DrawNumber(bigdigits + 7, minesleft, bignumber); while((time < 999) && (covered != 0)) { bigdisp = 11; /* print time and player's guess at # mines remaining */ DrawNumber(bigdigits + 12, time, bignumber); sp_PrintString(&ps, bigdigits); sp_Wait(1); sp_UpdateNow(); if ((n = DoMouse()) == MSG_STOP) /* game over */ break; if (((n = getk()) != 0) && (time < 900)) { /* what's this? */ if (*cseq++ != n) cseq = cheat; else if (*cseq == 0) { cseq = cheat; n = ((rand() & 0x7fff) % covered) + 1; for (loc = 0; n != 0; loc++) if ((gameboard[loc] & BD_VISIBLE) == 0) if ((gameboard[loc] & BD_CONTENT) != BD_MINE) n--; loc--; (mousemove)(&keymouse, ((loc/by)+cx)*8 + 4, ((loc%by)+cy)*8 + 4); time += 75; } } } /* Game Over */ loc = time; bigdisp = 11; /* print final time */ DrawNumber(bigdigits + 12, loc, bignumber); sp_PrintString(&ps, bigdigits); if (covered == 0) { /* a winner !!! */ InsertLatestScore(loc, &scores[skill*6]); MrHappy(HPY_COOLUP); } else { /* bitter defeat */ MrHappy(HPY_DEAD); temp = gameboard; /* uncover mines */ for (loc = 0; loc != size; loc++, temp++) { if ((*temp & (BD_VISIBLE | BD_CONTENT)) == BD_MINE) { if ((*temp & (BD_QUESTION | BD_FLAG)) != 0) RevealLocation(loc, 0xf0); /* cyan background, marked square */ else RevealLocation(loc, 0xf8); /* yellow background, unmarked square */ } } } sp_WaitForNoKey(); time = 0; /* board will be visible for full time before menu appears */ } } /* Game Graphics Defined in Assembler Assembler labels are visible in C if they are declared 'extern' arrays. */ #asm ; SPRITE GRAPHICS defb @00000000, @11111111 defb @00000000, @11111111 defb @00000000, @11111111 defb @00000000, @11111111 defb @00000000, @11111111 defb @00000000, @11111111 defb @00000000, @11111111 ._arrow defb @00000000, @00111111 defb @01000000, @00011111 defb @01100000, @00001111 defb @01110000, @00000111 defb @01111000, @00000011 defb @01111100, @00000001 defb @01111110, @00000000 defb @01101000, @00000000 defb @01001000, @00000011 defb @00000100, @00110001 defb @00000100, @11110001 defb @00000010, @11111000 defb @00000000, @11111000 defb @00000000, @11111111 defb @00000000, @11111111 defb @00000000, @11111111 defb @00000000, @11111111 defb @00000000, @11111111 defb @00000000, @11111111 defb @00000000, @11111111 defb @00000000, @11111111 defb @00000000, @11111111 defb @00000000, @11111111 defb @00000000, @11111111 ; BACKGROUND TILES ; Created using SevenUp (Win95+, Linux) ; Another brilliant choice is BMP2SCR Pro (Win98+) ; Both these programs can also be used to create animated sprites. ; ; BMP2SCR PRO also includes excellent graphics conversion features ; from BMP, etc. to Spectrum AND Timex display modes (thanks LCD! ; this extra effort is appreciated by those of us with Timex machines). ; ; The tiles are associated with the hexadecimal character ; codes shown in the listing during initialization. ; ASM source file created by SevenuP v1.11 ; SevenuP (C) Copyright 2002-2003 by Jaime Tejedor G˘mez, aka Metalbrain ;GRAPHIC DATA: ;Pixel Size: ( 32, 192) ;Char Size: ( 4, 24) ;Sort Priorities: Char line, Y char, X char ;Attributes: No attributes ;Mask: No ._UDGstart ; 0x80 ._big0 defb 0x00,0x00,0x3C,0x5A,0x66,0x66,0x66,0x42 defb 0x00,0x42,0x66,0x66,0x66,0x5A,0x3C,0x00 ; 0x82 ._big1 defb 0x00,0x00,0x00,0x02,0x06,0x06,0x06,0x02 defb 0x00,0x02,0x06,0x06,0x06,0x02,0x00,0x00 ; 0x84 ._big2 defb 0x00,0x00,0x3C,0x1A,0x06,0x06,0x06,0x1A defb 0x3C,0x58,0x60,0x60,0x60,0x58,0x3C,0x00 ; 0x86 ._big3 defb 0x00,0x00,0x3C,0x1A,0x06,0x06,0x06,0x1A defb 0x3C,0x1A,0x06,0x06,0x06,0x1A,0x3C,0x00 ; 0x88 ._big4 defb 0x00,0x00,0x00,0x42,0x66,0x66,0x66,0x5A defb 0x3C,0x1A,0x06,0x06,0x06,0x02,0x00,0x00 ; 0x8a ._big5 defb 0x00,0x00,0x3C,0x58,0x60,0x60,0x60,0x58 defb 0x3C,0x1A,0x06,0x06,0x06,0x1A,0x3C,0x00 ; 0x8c ._big6 defb 0x00,0x00,0x3C,0x58,0x60,0x60,0x60,0x58 defb 0x3C,0x5A,0x66,0x66,0x66,0x5A,0x3C,0x00 ; 0x8e ._big7 defb 0x00,0x00,0x3C,0x1A,0x06,0x06,0x06,0x02 defb 0x00,0x02,0x06,0x06,0x06,0x02,0x00,0x00 ; 0x90 ._big8 defb 0x00,0x00,0x3C,0x5A,0x66,0x66,0x66,0x5A defb 0x3C,0x5A,0x66,0x66,0x66,0x5A,0x3C,0x00 ; 0x92 ._big9 defb 0x00,0x00,0x3C,0x5A,0x66,0x66,0x66,0x5A defb 0x3C,0x1A,0x06,0x06,0x06,0x02,0x00,0x00 ; 0x94 ._hash defb 0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA ; 0x95 ._bomb defb 0x00,0x55,0x38,0x7D,0x38,0x55,0x00,0x55 ; 0x96 ._question defb 0x01,0x31,0x09,0x11,0x01,0x11,0x01,0xFF ; 0x97 ._flag defb 0x01,0x39,0x39,0x09,0x09,0x19,0x01,0xFF ; 0x98 ._emptyup defb 0x55,0xAB,0x55,0xAB,0x55,0xAB,0x55,0xFF ; 0x99 ._emptydown defb 0xFF,0xD5,0xAA,0xD5,0xAA,0xD5,0xAA,0xD5 ; 0x9a ._small0 defb 0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x55 ; 0x9b ._small1 defb 0x00,0x11,0x30,0x11,0x10,0x39,0x00,0x55 ; 0x9c ._small2 defb 0x00,0x39,0x08,0x11,0x20,0x39,0x00,0x55 ; 0x9d ._small3 defb 0x00,0x39,0x08,0x39,0x08,0x39,0x00,0x55 ; 0x9e ._small4 defb 0x00,0x29,0x28,0x39,0x08,0x09,0x00,0x55 ; 0x9f ._small5 defb 0x00,0x39,0x20,0x39,0x08,0x39,0x00,0x55 ; 0xa0 ._small6 defb 0x00,0x19,0x20,0x39,0x28,0x39,0x00,0x55 ; 0xa1 ._small7 defb 0x00,0x39,0x08,0x11,0x10,0x11,0x00,0x55 ; 0xa2 ._small8 defb 0x00,0x39,0x28,0x39,0x28,0x39,0x00,0x55 ; 0xa3 ._small9 defb 0x00,0x39,0x28,0x39,0x08,0x09,0x00,0x55 ; 13 ; 24 ; 0xa4 ._happyup defb 0x07,0x18,0x20,0x40,0x48,0x80,0x80,0x80 defb 0x90,0x48,0x47,0x20,0x18,0x07,0x00,0xFF defb 0x81,0x61,0x11,0x09,0x49,0x05,0x05,0x05 defb 0x25,0x49,0x89,0x11,0x61,0x81,0x01,0xFF ; 0xa8 ._happydown defb 0xFF,0x80,0x81,0x86,0x88,0x90,0x92,0xA0 defb 0xA0,0xA0,0xA4,0x92,0x91,0x88,0x86,0x81 defb 0xFF,0x00,0xE0,0x18,0x04,0x02,0x12,0x01 defb 0x01,0x01,0x09,0x12,0xE2,0x04,0x18,0xE0 ; 0xac ._happyoh defb 0x07,0x18,0x20,0x40,0x4C,0x8C,0x80,0x83 defb 0x84,0x44,0x43,0x20,0x18,0x07,0x00,0xFF defb 0x81,0x61,0x11,0x09,0xC9,0xC5,0x05,0x05 defb 0x85,0x89,0x09,0x11,0x61,0x81,0x01,0xFF ; 0xb0 ._happydead defb 0x07,0x18,0x20,0x40,0x48,0x84,0x88,0x80 defb 0x87,0x48,0x40,0x20,0x18,0x07,0x00,0xFF defb 0x81,0x61,0x11,0x09,0x49,0x85,0x45,0x05 defb 0x85,0x49,0x09,0x11,0x61,0x81,0x01,0xFF ; 0xb4 ._happywinup defb 0x07,0x18,0x20,0x40,0x6C,0x9F,0x9C,0x80 defb 0x90,0x48,0x47,0x20,0x18,0x07,0x00,0xFF defb 0x81,0x61,0x11,0x09,0xD9,0xE5,0xE5,0x05 defb 0x25,0x49,0x89,0x11,0x61,0x81,0x01,0xFF ; 0xb8 ._happywindown defb 0xFF,0x80,0x81,0x86,0x88,0x90,0x9B,0xA7 defb 0xA7,0xA0,0xA4,0x92,0x91,0x88,0x86,0x81 defb 0xFF,0x00,0xE0,0x18,0x04,0x02,0x36,0xF9 defb 0x39,0x01,0x09,0x12,0xE2,0x04,0x18,0xE0 ; 0xbc - not used ._borderUL defb 0x00,0x2A,0x55,0x3F,0x50,0x32,0x55,0x32 ; 0xbd ._borderL defb 0x55,0x32,0x55,0x32,0x55,0x32,0x55,0x32 ; 0xbe ._borderBL defb 0x55,0x32,0x50,0x2A,0x55,0x7F,0x00,0xAA ; 0xbf - not used ._borderU defb 0x00,0xAA,0x55,0xFF,0x00,0xAA,0x55,0xAA ; 0xc0 ._borderML defb 0x55,0x32,0x50,0x2A,0x55,0x3F,0x50,0x32 ; 0xc1 - not used ._borderTR defb 0x00,0xAA,0x56,0xEA,0x06,0xAA,0x46,0xAA ; 0xc2 ._borderR defb 0x46,0xAA,0x46,0xAA,0x46,0xAA,0x46,0xAA ; 0xc3 ._borderBR defb 0x46,0xAA,0x06,0xAA,0x56,0xFE,0x00,0xAA ; 0xc4 ._borderB defb 0x55,0xAA,0x00,0xAA,0x55,0xFF,0x00,0xAA ; 0xc5 ._borderMR defb 0x46,0xAA,0x06,0xAA,0x56,0xFA,0x06,0xAA ; 0xc6 ._Xup defb 0x01,0x45,0x29,0x11,0x29,0x45,0x01,0xFF ; 0xc7 ._Xdown defb 0xFF,0x80,0xA2,0x94,0x88,0x94,0xA2,0x80 ; 0xc8 ._box defb 0xFF,0x81,0x81,0x81,0x81,0x81,0x81,0xFF #endasm