CRUISING WITH TOPSPEED by Alex Lane [LISTING ONE] /**************************************************************************** File: Kaboooom.c Purpose: Allows the user to 'walk' through a minefield; a detector shows how many mines are immediately adjacent to you. As you visit a cell, it leaves a marker telling you how many were next to you, and you have the ability to mark cells with a character (assumably to mark mines). Changes: 11/10/89 (tdeii) If you call the program with "/s" or "/S", it gives you a "safer" game, where it does not let you walk on spaces that you have marked (whether there is a mine there or not!) 11/11/89 (tdeii) Allows you to press "?" and get some help starting at your current position; will mark mines that it knows (by deducing their position), and "visit" places that it knows are safe. This propagates until it cannot deduce anything else (see EvaluatePosition). ****************************************************************************/ #include "stdio.h" #include "stdarg.h" #include "stdlib.h" #include "dos.h" #include "conio.h" #include "string.h" #define SCREEN_X 80 #define SCREEN_Y 25 #define GRID_X 15 #define GRID_Y 9 #define TRUE 1 #define FALSE 0 #define bEMPTY 0 #define bVISITED 1 #define bBOMB 2 #define bCURRENT 3 #define bFINISH 4 #define bEXPLODED 5 #define MAKECOLOR(fore,back) ((back)*16+(fore)) typedef int BOOL; typedef struct tagADJACENCYGROUP { int BombCount; /* Number of bombs located in this adj. group */ int CellCount; /* Number of cells filled */ int Cell[8][2]; /* x,y coordinates of up to 8 cells */ } ADJACENCYGROUP; int Board[GRID_X][GRID_Y]; /* Board; see codes above (bXXX) */ int UserMark[GRID_X][GRID_Y]; /* User marks; 0 = none, 'M' = mine */ int nNumMines; /* Number of mines on board */ int UserX, UserY; /* Current user X and Y position */ BOOL bShowBombs; /* TRUE if program shows bombs (it */ /* does this after you win or lose) */ BOOL bSafeGame; /* TRUE if program does not let you */ /* walk on mines you have marked */ ADJACENCYGROUP AdjacencyGroup[GRID_X][GRID_Y]; /* AG for each board pos */ char szClear[79] = " "; void Pause(void) { if (getch() == 0) getch(); } void GetXY_(int *pX, int *pY) { union REGS regs; regs.h.ah = 3; regs.h.bh = 0; /* display page 0 */ int86(0x10, ®s, ®s); if (pY != NULL) { (*pY) = regs.h.dh; } if (pX != NULL) { (*pX) = regs.h.dl; } } void GotoXY_(int x, int y) { union REGS regs; regs.h.ah = 2; regs.h.dh = (unsigned char)y; regs.h.dl = (unsigned char)x; regs.h.bh = 0; /* display page 0 */ int86(0x10, ®s, ®s); } int nRandom(int nMax) { return ((int)((double)rand() / RAND_MAX * (double)nMax)); } void DisplayChar(int x, int y, char cChar, int nColor) { char far *cPos; if ((x>=0 && x=0 && y= 0) && (x+i < GRID_X) && (y+j >= 0) && (y+j < GRID_Y)) { if ((Board[x+i][y+j] == bBOMB) || (Board[x+i][y+j] == bEXPLODED)) { nCount++; } } } } return (nCount); } void DisplayCell(int x, int y) { int Char; Char = UserMark[x][y]; if (Char == 0) { Char = 32; } DisplayChar(x*4+1, y*2+1, Char, MAKECOLOR(14,1)); DisplayChar(x*4+3, y*2+1, Char, MAKECOLOR(14,1)); switch (Board[x][y]) { case bEMPTY: /** Empty cell **/ Char = ' '; break; case bVISITED: /** Visited cell **/ Char = '0' + CountMines(x, y); break; case bBOMB: /** Bomb cell! **/ if (bShowBombs) { Char = 15; } else { Char =' '; } break; case bCURRENT: /** Current pos **/ Char = 2; break; case bFINISH: /** Finish cell **/ Char = 19; break; case bEXPLODED: /** Exploded! **/ Char = 15; break; } if (Char != 0) { DisplayChar(x*4+2, y*2+1, Char, MAKECOLOR(14,1)); } } void Initialize(void) { unsigned int nRand; /* seed for random number generator */ struct dostime_t sDosTime; /* time structure; used for above seed */ _dos_gettime(&sDosTime); nRand = (unsigned int)((sDosTime.hsecond * 600) + (sDosTime.second * 10) + (sDosTime.minute / 6)); srand(nRand); } void PaintBoard(void) { int x, y, i; for (x=0; x 40)) { GotoXY_(0, 24); printf("How many bombs do you want? (10-40)?? "); fgets(cBuffer, sizeof(cBuffer), stdin); sscanf(cBuffer, "%d", &nNumMines); } /** next, clear out board & user scratchpad **/ for (i=0; i= GRID_X - 2) && (j >= GRID_Y - 2))) ) { bDone = TRUE; } } Board[i][j] = bBOMB; /* Then you mark it with a 'B' */ } /* Set user at position 0, 0 */ UserX = 0; UserY = 0; Board[0][0] = bCURRENT; /* Set finish (hq) at position GRID_X, GRID_Y */ Board[GRID_X - 1][GRID_Y - 1] = bFINISH; /* Display board on screen */ PaintBoard(); } BOOL Travel(int dx, int dy) { int NewX, NewY; /* New X and Y coordinates of user */ BOOL bInvalid; /* TRUE if trying to walk off board */ BOOL bAbort; /* TRUE if user won or lost (abort game) */ BOOL bBombWalk; /* TRUE if user tried to walk on a bomb */ bAbort = FALSE; NewX = UserX + dx; NewY = UserY + dy; bInvalid = FALSE; bBombWalk = FALSE; if ((NewX < 0) || (NewX >= GRID_X)) { bInvalid = TRUE; } if ((NewY < 0) || (NewY >= GRID_Y)) { bInvalid = TRUE; } if ((!bInvalid) && (bSafeGame) && (UserMark[NewX][NewY] == 'M')) { bInvalid = TRUE; bBombWalk = TRUE; } if (bInvalid) { GotoXY_(0, SCREEN_Y - 1); printf("** INVALID MOVE ** ... press any key..."); if (bBombWalk) { printf("(You must un-mark it.)"); } Pause(); GotoXY_(0, SCREEN_Y - 1); printf(szClear); } else { if (Board[NewX][NewY] == bBOMB) { bAbort = TRUE; Board[UserX][UserY] = bVISITED; DisplayCell(UserX, UserY); Board[NewX][NewY] = bEXPLODED; DisplayCell(NewX, NewY); GotoXY_(0, 22); printf("******** YOU HAVE STEPPED ON A BOMB!! ********"); Pause(); GotoXY_(0, 22); printf(szClear); GotoXY_(0, 22); } else { if ((NewX == GRID_X-1) && (NewY == GRID_Y-1)) { bAbort = TRUE; Board[UserX][UserY] = bVISITED; DisplayCell(UserX, UserY); Board[NewX][NewY] = bCURRENT; DisplayCell(NewX, NewY); GotoXY_(0, 22); printf("************* YOU HAVE WON!! *************"); Pause(); GotoXY_(0, 22); printf(szClear); GotoXY_(0, 22); } else { Board[UserX][UserY] = bVISITED; DisplayCell(UserX, UserY); UserX = NewX; UserY = NewY; Board[UserX][UserY] = bCURRENT; DisplayCell(UserX, UserY); } } } GotoXY_(0, GRID_Y*2+2); printf("Number of mines around you: %d", CountMines(UserX, UserY)); GotoXY_(0, SCREEN_Y - 1); return (bAbort); } void PlaceUserMark(void) { BOOL bDone, bAbort; int Ch; int NewX, NewY; int dx, dy; bAbort = FALSE; GotoXY_(0, 24); printf("Mark in which direction? (ESC=abort)"); bDone = FALSE; while (!bDone) { bDone = TRUE; Ch = getch(); switch (Ch) { case 0: Ch = getch(); switch (Ch) { case 71: /* home */ dx = -1; dy = -1; break; case 72: /* up arrow */ dx = 0; dy = -1; break; case 73: /* page up */ dx = 1; dy = -1; break; case 75: /* left arrow */ dx = -1; dy = 0; break; case 77: /* right arrow */ dx = 1; dy = 0; break; case 79: /* end */ dx = -1; dy = 1; break; case 80: /* down arrow */ dx = 0; dy = 1; break; case 81: /* page down */ dx = 1; dy = 1; break; default: bDone = FALSE; break; } break; case '7': /* home */ dx = -1; dy = -1; break; case '8': /* up arrow */ dx = 0; dy = -1; break; case '9': /* page up */ dx = 1; dy = -1; break; case '4': /* left arrow */ dx = -1; dy = 0; break; case '6': /* right arrow */ dx = 1; dy = 0; break; case '1': /* end */ dx = -1; dy = 1; break; case '2': /* down arrow */ dx = 0; dy = 1; break; case '3': /* page down */ dx = 1; dy = 1; break; case 27: case 13: case 10: case 8: bAbort = TRUE; break; default: bDone = FALSE; break; } } GotoXY_(0, 24); printf(szClear); if (!bAbort) { NewX = UserX + dx; NewY = UserY + dy; if ((NewX < 0) || (NewX >= GRID_X) || (NewY < 0) || (NewY >= GRID_Y)) { GotoXY_(0, 24); printf("ERROR: Out of bounds!!"); Pause(); GotoXY_(0, 24); printf(szClear); } else { GotoXY_(0, 24); if (UserMark[NewX][NewY] != 0) { Ch = 0; } else { Ch = 'M'; } UserMark[NewX][NewY] = Ch; DisplayCell(NewX, NewY); } } GotoXY_(0, 24); } void ComputeAdjacency(int x, int y) { int dX, dY; int BombCount; int Cell; if ((x >= 0) && (x < GRID_X) && (y >= 0) && (y < GRID_Y)) { if ((Board[x][y] == bVISITED) || (Board[x][y] == bCURRENT)) { BombCount = CountMines(x, y); Cell = 0; for (dX=-1; dX<=1; dX++) { for (dY=-1; dY<=1; dY++) { if (!((dX == 0) && (dY == 0))) { if ((x+dX >= 0) && (x+dX < GRID_X) && (y+dY >= 0) && (y+dY < GRID_Y)) { if ((Board[x+dX][y+dY] != bVISITED) && (Board[x+dX][y+dY] != bCURRENT)) { if (UserMark[x+dX][y+dY] != 0) { BombCount--; } else { AdjacencyGroup[x][y].Cell[Cell][0] = x+dX; AdjacencyGroup[x][y].Cell[Cell][1] = y+dY; Cell++; } } } } } } AdjacencyGroup[x][y].BombCount = BombCount; AdjacencyGroup[x][y].CellCount = Cell; } else { AdjacencyGroup[x][y].CellCount = 0; AdjacencyGroup[x][y].BombCount = -1; /** Don't look flag */ } } } int AddToPositionList(int PositionList[GRID_X * GRID_Y][2], int PositionListHead, int x, int y) { int nIndex; BOOL bFound; ComputeAdjacency(x, y); bFound = FALSE; for (nIndex=0; (nIndex GRID_X * GRID_Y) { GotoXY_(0, 22); printf("ERROR! PositionListHead > max (%d)", PositionListHead); Pause(); GotoXY_(0, 22); printf(szClear); GotoXY_(0, 22); } return (PositionListHead); } int AddSurroundingToPositionList(int PositionList[GRID_X * GRID_Y][2], int PositionListHead, int x, int y) { int dX, dY; for (dX=-1; dX<=1; dX++) { for (dY=-1; dY<=1; dY++) { if ((x+dX >= 0) && (x+dX < GRID_X) && (y+dY >= 0) && (y+dY < GRID_Y)) { if ((Board[x+dX][y+dY] == bVISITED) || (Board[x+dX][y+dY] == bCURRENT)) { PositionListHead = AddToPositionList(PositionList, PositionListHead, x+dX, y+dY); } } } } return (PositionListHead); } BOOL FindPositionInAG(ADJACENCYGROUP *pAG, int x, int y) { int nIndex; BOOL bFound; bFound = FALSE; for (nIndex=0; nIndexCellCount; nIndex++) { if ((pAG->Cell[nIndex][0] == x) && (pAG->Cell[nIndex][1] == y)) { bFound = TRUE; } } return (bFound); } void MarkBombCell(int x, int y) { UserMark[x][y] = 'M'; DisplayCell(x, y); if (Board[x][y] != bBOMB) { GotoXY_(0, 22); printf("LOGIC ERROR: I tagged a phantom bomb @ (%d,%d).", x, y); Pause(); GotoXY_(0, 22); printf(szClear); GotoXY_(0, 24); } } void VisitCell(int x, int y) { if (Board[x][y] != bCURRENT) { if (Board[x][y] == bBOMB) { GotoXY_(0, 22); printf("LOGIC ERROR: I walked on a bomb @ (%d,%d).", x, y); Pause(); GotoXY_(0, 22); printf(szClear); GotoXY_(0, 24); } Board[x][y] = bVISITED; DisplayCell(x, y); } } int CountCommonCells(ADJACENCYGROUP *pGroup1, ADJACENCYGROUP *pGroup2) { int Cell, nCount; nCount = 0; for (Cell=0; CellCellCount; Cell++) { if (FindPositionInAG(pGroup2, pGroup1->Cell[Cell][0], pGroup1->Cell[Cell][1])) { nCount++; } } return (nCount); } BOOL ProcessRule3(ADJACENCYGROUP *pCurrentAG, ADJACENCYGROUP *pTempAG, int PositionList[GRID_X * GRID_Y][2], int *pPositionListHead) { int x; int BombCount, CellCount; int PositionListHead; int CellHolder[9][2]; int CellHolderHead; BOOL bRetVal; PositionListHead = *pPositionListHead; bRetVal = FALSE; BombCount = pCurrentAG->BombCount; CellCount = pCurrentAG->CellCount; if (pTempAG->CellCount == CountCommonCells(pTempAG, pCurrentAG)) { BombCount -= pTempAG->BombCount; CellCount -= pTempAG->CellCount; if ((CellCount > 0) && ((BombCount == CellCount) || (BombCount == 0))) { bRetVal = TRUE; CellHolderHead = 0; CellCount = pCurrentAG->CellCount; for (x=0; xCell[x][0], pCurrentAG->Cell[x][1])) { if (BombCount == 0) { VisitCell(pCurrentAG->Cell[x][0], pCurrentAG->Cell[x][1]); } else { MarkBombCell(pCurrentAG->Cell[x][0], pCurrentAG->Cell[x][1]); } /* Queue up cells to put in position list for later */ CellHolder[CellHolderHead][0] = pCurrentAG->Cell[x][0]; CellHolder[CellHolderHead][1] = pCurrentAG->Cell[x][1]; CellHolderHead++; } } for (x=0; x 0) { CurrentX = PositionList[0][0]; CurrentY = PositionList[0][1]; for (x=0; x 0) && (BombCount > -1)) { /* Rule 1: if number of bombs = number of cells, all are bombs! */ if (CellCount == BombCount) { for (Cell=0; Cell 0)) { for (Cell=0; Cell= 0) && (x+dX < GRID_X) && (y+dY >= 0) && (y+dY < GRID_Y)) { pTempAG = &AdjacencyGroup[x+dX][y+dY]; if (pTempAG->BombCount > 0) { /* if == 0, no help! */ bDone = ProcessRule3(&AdjacencyGroup[CurrentX][CurrentY], pTempAG, PositionList, &PositionListHead); if (bDone) { bModifiedAny = TRUE; } } } } } } } } } } if (bModifiedAny) { for (x=0; x 1) && (argv[1][0] == '/') && ((argv[1][1] == 's') || (argv[1][1] == 'S'))) { bSafeGame = TRUE; printf("SAFE GAME in effect.\n"); } else { bSafeGame = FALSE; } bDone = FALSE; while (!bDone) { SetUpBoard(); bDone = LetUserMove(); } return (0); }