Alex is a knowledge engineer for Technology Applications Inc. in Jacksonville, Florida. He can be reached on BIX as a.lane or through MCI mail as ALANE.
It is fashionable in some circles to yawn upon hearing that a new C compiler has hit the market. Such folks see the C world as divided into two main camps -- the Microsofts and the Turbos -- with a smattering of fanatics representing an insignificant fringe. That fringe, however, has lately succeeded in whipping up the C market, the result being a lively free-for-all as new arrivals such as Watcom, Zortech, and now TopSpeed C attempt to prove their worth to programmers. Readers, familiar with the TopSpeed name, will associate it with a popular Modula-2 compiler and a recently introduced Pascal compiler, marketed by Jensen & Partners International. JPI, a company established in 1987 by a group of former Borland employees, is a relatively small company with unquestionably large vision. They intend to develop an integrated multilanguage environment that will let programmers seamlessly mix and match routines from a broad spectrum of languages, including ISO Pascal, C, C++, Modula-2, and Ada. What JPI in effect proposes to do is to link each language dynamically into the system as an overlay at run time, thus allowing each language compiler to use the same optimizing code generator. If TopSpeed C is any indicator, JPI has its sights set on a worthwhile goal.
What You Get
I reviewed the TopSpeed C, Version 1.02, Extended Edition, which is basically the standard package (comprised of an optimizing 100 percent ANSI C compiler and high-speed linker, an automatic make facility, an editing environment, and source-level debugger) combined with the TopSpeed C TechKit, which provides enhanced functionality in the form of library source code, Windows support, DOS dynamic linking, profiling, and post-mortem debugging, among other features. The standard TopSpeed package consists of seven diskettes and nearly three inches of paperback documentation, consisting of a user manual, a language reference, a library reference, and a language tutorial. The TechKit comes on an additional four disks and has separate documentation.
Finding Files
I dread installing software that needs to use DOS environment variables to find files on my disk. For one thing, I only have so much DOS environment space; for another, I often find that two different packages use the same DOS environment variable name in different ways, and that no amount of fiddling with SET statements shall allow the twain to meet on a consistent basis. If you have two or more C compilers installed on your hard disk, you probably know what I mean.
If you want TopSpeed C to use DOS environment variables, you can so specify by using the /y flag from the command line, but why bother? I took an immediate liking to TopSpeed's redirection file feature, which acts as a sort of private environment. A sample redirection file, TS.RED, is reproduced in Figure 1. The syntax is similar to that of DOS paths. The first line indicates that all KABOOOOM.* files are found in the directory C: \KABOOOOM. Analogously, all other *.C files may be found either in the current directory (denoted by the '.'), or in C:\TS \EXAMPLES, C:\TS\ SRC, or in D:\ FRAC \PROGS. The remaining lines are self-explanatory, except perhaps for the line that refers to *.A files that are TopSpeed assembler files.
Figure 1: A sample redirection file
KABOOOOM.* = C:\KABOOOOM *.C = .; C:\TS\EXAMPLES; C:\TS\SRC; D:\FRAC\PROGS; *.PRJ = .; C:\TS\PRJ; C:\TS\EXAMPLES; *.H = .; C:\TS\INCLUDE;C:\TS\EXAMPLES; D:\FRAC\PROGS; *.A = .; C:\TS\LIB; C:\TS\EXAMPLES; C:\TS\SRC *.OBJ = C:\TS\OBJ; C:\TS\LIB; *.LIB = .; C:\TS\LIB; D:\FRAC\PROGS, *.DOC = .; C:\TS\DOC; TS.RED = .; C:\TS\SYS;
By editing TS.REG, then automatically saving and reloading it, you can change the file search (and storage) behavior of the environment on-the-fly. If, despite your best intentions, you repeatedly end up with all your *.c, *.obj, *.h, and *.exe files in one giant directory, this feature is for you.
Integrated Development Environment
The cornerstone of the integrated environment is, of course, the editor. Out of the box, TopSpeed's editor is configured to use the WordStar command set. You can change part or all of that by editing a configuration file.
Actually, the TopSpeed configuration file TSCFG.TXT affords the user quite a bit of control over not just the editor but the TopSpeed environment in general. The file is a 33K ASCII text file that defines the menu structure, every menu option, the editor commands, and even compilation error messages used by the TopSpeed system. This is the file you edit if you want to make the environment editor act more like, say, Brief than WordStar. A disk file explains how to make the changes, and after only a few minutes, I was able to change the main menu format from vertical to horizontal and to define the Ctrl-F10 keychord as a way to directly access the optimization menu. While there are many things you can change, there are others (such as an inability to extend the undo feature past one event) you have no control over. Once you've finished making changes, you can incorporate them into the TopSpeed environment by running the TSCFG.EXE program.
Although the editor handles up to ten windows (0 - 9), you'd normally edit in windows #1 through #9, because window #O is a special window, called the "error editor window." This window comes into play after errors and warnings are found during compilation of a source file. The flawed file is displayed in this window with the cursor positioned at the first error, and the corresponding error message appears at the bottom of the window. Pressing F8 moves you forward to the location of the next error; F7, back to the previous one.
When you exit from the TopSpeed environment, the system remembers the contents and status of each window and reloads the same files the next time you enter the environment. You can alternatively start with a "clean slate" by supplying the /n option on the command line. Another nice touch is a prompt reminding you to save your work when you call up the source-level visual interactive debugger (VID) from the TopSpeed menu.
There are a number of useful options available under the Utilities menu, including an ASCII table, a programmer's calculator that works in decimal, hex, or binary, and a window that lets you see the scan codes for keyboard keys. There is a multiple-file string search capability that works a bit such as grep, albeit without the powerful regular expression capabilities of that Unix utility. Other options include the ability to print files, to view files as data (that is, in hex), and to display system information. "System Info" shows the current date, time, and directory, the names of the files being edited in the TopSpeed windows, and a summary of free space on all disks. Be prepared to wait for this report if you have a CD-ROM disk attached to your system, for even though there is no "free space" available on a CD-ROM, there is no way to tell TopSpeed to ignore the drive, which gets interrogated in turn along with the other drives in your system.
There are a number of other features I found useful in this environment, too many in fact to list. Particularly noteworthy to me, however, are the ability to have up to nine generations of backup copies (I have mine set to 3), and the ability to record, load, save, and playback keystroke macros. The instructions for recording a macro were clear, and it took only a couple of minutes for me to create a macro that toggled all optimization on and off. About the only criticism I have of the editing environment is the lack of mouse support and the lack of Unix-style regular expression parsing (as in Brief, for example), but those are relatively minor annoyances.
Compiling
While the editor and its features form an important part of the TopSpeed C package, you can't forget that this is, after all, a C compiler, and the worth of the package ultimately hinges on how well it compiles code.
To help put TopSpeed C through its paces, I worked with a file that had been written in Microsoft C for a fairly simple-minded game of deduction. The playing "field" for this game is a 9 x 15 grid that contains some number of hidden mines, and the player uses the numeric keypad to "walk" a happy-face character through this mine field to a goal. To give the player a fighting chance, the number of mines in adjacent squares is displayed as the player moves from square to square, thus allowing the player to deduce the location of the mines without "stepping" on them. To make life easier, the location of mines may be marked by pressing M and an appropriate key on the numeric keypad. In addition, if an /s parameter is passed on the command line when the program is invoked, the program won't let the player step on such marked squares. Finally, typing? at the start of the game causes the program to start playing by itself until it either gets to the goal or is unable to proceed further through the grid. Aside from being fun to play, the file KABOOOOM.C (see Listing One, page 109) is just under 1000 lines in length and offers a substantial chunk of source code for the compiler to process.
The first attempt to compile the code resulted in a couple of errors. The first error, in the function DisplayCell, had to do with a failure to read an embedded ASCII 2 (the happy-face) in the source code. The TopSpeed editor did not read this character, resulting in the assignment Char = "; and a subsequent error. Changing the line to Char = 2; fixed the problem.
The second error came in a line of the DisplayChar function, which read: FP_SEG(cPos) = OxObOOO;
While a legitimate statement in Microsoft C, this use of FP_SEG( ) generated a "left operand of assignment must be a modifiable lvalue" error message from the TopSpeed compiler. Pressing F1 for help brought up an explanation of the message, which is comfortably verbose as it is. I moved off the offending line and again pressed F1, and shortly was reading the help screen associated with FP_SEG( ). It referred me to MK_FP( ), which permitted me to replace the offending line (and the line after it) with: cPos = MK_FP(OxOb800,((x+y*80)<< 1)); which eliminated the problem. A quick check showed that Turbo C 2.0 also required the MK_FP( ) syntax in order to compile without error. I later learned that the FP_SEG macro is defined differently for the Microsoft and TopSpeed compilers, which explains the failure to compile.
Once the bugs were corrected, the initial compilation pass with TopSpeed C took about 13 seconds on my 16-MHz ARC 386i computer, and optimization took another 18 seconds or so. With all optimizations turned off (there are nine forms of optimization, including optimization for time and space as well as constant, jump, peephole, loop, and alias optimization), the compile time was cut down to 28 seconds overall. This compared favorably with a run through Microsoft C 5.1, which took 37 seconds to compile without optimization, yet was slower than Turbo C 2.0, which compiled and linked the program in about 13 seconds.
I ran the compiler from within the editing environment, but you can also run TopSpeed C from the command line, where a comprehensive set of command-line options give the programer complete control of compilation and linking. In fact, there are four ways to set compiler options in the TopSpeed C compiler: From a menu, from the command line, via directives in TopSpeed's make facility, and by including pragmas in the source code.
One very topical option in the compiler is the ability to check for ANSI compatibility. JPI has made a point of maintaining 100 percent conformance to the ANSI C definition, and now that the seemingly interminable deliberations of the ANSI C committee have apparently come to a close, this feature should be a point in TopSpeed C's favor.
Making it With Project Files
TopSpeed's project files make it easy to admit to hating traditional make programs. Like its traditional counterpart, TopSpeed's Make uses a text file (called a "project file") to figure out what kind of file to produce with what objects and libraries, and using which memory model. Project files are collections of "directives" that, in addition to the usual specification of object modules, libraries, and so on, establish various compiler and linker options (such as inclusion of debugger information in the .EXE file), override options for specific files or groups of files, and specify what programs to run (if any) after the make process is complete. You could, for example, copy the .EXE file to a diskette every time you compiled and linked the source code. In short, the project file is the mechanism by which TopSpeed source code is transformed into executable files.
Two valuable features aid in the link process. Type-safe linking involves catching function calls made with the wrong parameter types. You will bless this feature the first time it saves you from calling an external function with the wrong parameter types. The technique of smart linking helps keep executable file size down and reduces the complexity associated with maintaining libraries. When you link a program, TopSpeed will only include those routines that are referenced in the code, leaving all the other routines out of the executable file. Strangely enough, this means that sometimes you must make an extraneous reference to a variable in order to make sure certain routines are linked into the .EXE file. A case in point is the need to include a line includePMD = 1; in one of the functions that handles critical program errors so that TopSpeed loads the appropriate routines to perform a post-mortem dump in case the program bombs.
Debugging
The VID is a full source-level symbolic debugger that uses overlapping windows in an interactive environment. Like the parent TopSpeed environment, there is no mouse support in VID.
VID is easy to run from the TopSpeed environment, which wisely prompts you to save your files before it swaps itself to disk, leaving room for your program and VID. To use VID, you need to generate VID information during compilation and a .MAP file during the link process. All required files are found using the redirection file, if necessary.
All the usual debugging features are here. You can set and clear breakpoints, create "sticky" breakpoints, examine different types of variables, find procedures, evaluate expressions, all the usual stuff. While not as powerful as, say, the Borland Turbo Debugger (there is no equivalent to the Inspect command, for example, which shows record structure, or the CPU window, which shows registers, memory, and disassembled code all at once) the VID is nevertheless a competent piece of software that is able to do the job.
TechKit
The TechKit is what distinguishes the Extended Edition ($395 list price) from the Standard Edition ($195) of TopSpeed C. What you get for your money is a collection of programs, files, and utilities that add functionality to TopSpeed C. It includes support for Windows programming and dynamic link libraries (DLLs), including DLLs that can be used under DOS. (A DLL is an OS/2 innovation that allows applications to share common data and code by linking library routines at run time.)
A major piece of the Techkit is the source code to the TopSpeed libraries. This collection of files fills over 1.5 Mbytes of disk space. The code is designed not only for use by TopSpeed C, but also for use with other TopSpeed languages. Many of the files are written in TopSpeed's assembler language, which makes for speedy routines, but also requires you to learn a new dialect of assembler.
The TopSpeed Assembler attempts to gain in simplicity and speed by deviating from "standard" 8086 assemblers in several ways. For example, the lexical structure of the assembler is derived from Modula-2, memory operands and segment overrides must always be explicitly stated, and there are no macros used in the language. While I can understand JPI's reason for doing it this way, I don't look forward to becoming familiar with yet another assembler scheme.
An interesting utility included in the TechKit is WATCH, which lets you specify groups or individual DOS functions to monitor during program execution. I ran the program and specified the date/time functions for monitoring, with output to be sent to my printer (as opposed to the screen or disk file). When I ran KABOOOOM.EXE, a brief report was sent to the printer when the program called DOS to get the time during the initialization phase. The Alt-backspace keychord toggles WATCH on and off, and in order to change the scope of the DOS functions monitored, I found it necessary to unload WATCH and then reload it from scratch. (If you send WATCH's output to the screen, however, you are able to interact more with the program [setting and clearing functions to monitor], albeit at the expense of interfering greatly with the screen.) WATCH, of course, will not work if the program it is monitoring does not use DOS to accomplish its ends.
Other pieces of the TechKit aid in the debug and streamline process. The post-mortem debugger was undoubtedly created for those who've wished their bug-ridden programs could leave some indication behind them of what went wrong before they exit to never-never land. This feature is set by including the PMD.H file and referencing the includePMD variable in the source code. Should anything go wrong and a critical error function is called, your program creates a file that details the state of the system just before lights out. This file can be examined using the VID.
I've always gritted my teeth when sitting down to work with a profiler, but I found the TopSpeed TSPROF profiler easy to use. All you need to use TSPROF is a .MAP file, which is created when the program is made. I ran the profiler for KABOOOOM and found that over half the program's time is spent executing DOS routines, nearly half the program's time is spent in the BIOS, and only three percent or so of the time is in the code.
Conclusion
Working with the TopSpeed C compiler was a wholly pleasant experience. The advantage of having the file redirection and project file features are alone almost worth the price of admission, and the overall flexibility of the system is a big plus. Though it remains to be seen whether JPI will be able to successfully market the idea of a common programming environment with plug-in language modules, TopSpeed C certainly deserves to be a contender in the fight for a share of the C compiler market.
Acknowledgments
The author would like to thank Thomas D. Eldredge II for the use of his source code for KABOOOOM.C. Tom's program represents an enhancement of a game called RELENTLESS LOGIC by Conway, Hong, and Smith, which was found on the RBBS-IN-A-BOX CD-ROM.
PRODUCT INFORMATION
TopSpeed C Jensen & Partners International 1101 San Antonio Rd., Ste. 301 Mountain View, CA 94043 Price: Standard Edition $199 Extended Edition $395 OS/2 Edition $495 Requirements: Extended Edition -- IBM PC or compatible, DOS 2.0 or later, 640K RAM. Hard disk recommended.
CRUISING WITH TOPSPEED by Alex Lane
[LISTING ONE]<a name="00da_0010">
/***************************************************************************
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<SCREEN_X) &&
(y>=0 && y<SCREEN_Y)) {
cPos = MK_FP( 0x0b800,((x + y*80) << 1));
*cPos = cChar;
*(cPos+1) = (char)nColor;
}
}
int CountMines(int x, int y)
{
int i, j;
int nCount;
nCount = 0;
for (i=-1; i<=1; i++) {
for (j=-1; j<=1; j++) {
if ((x+i >= 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<SCREEN_X; x++) {
for (y=0; y<SCREEN_Y; y++) {
DisplayChar(x, y, ' ', MAKECOLOR(14, 1));
}
}
/** Draw left and right sides **/
DisplayChar(0, 0, 218, MAKECOLOR(14,1)); /*upper left corner */
DisplayChar(GRID_X*4, 0, 191, MAKECOLOR(14,1)); /*upper right corner */
for (y=1; y<=GRID_Y; y++) {
DisplayChar(0, y*2, 195, MAKECOLOR(14,1)); /* left edge */
DisplayChar(GRID_X*4, y*2, 180, MAKECOLOR(14,1)); /* right edge */
}
DisplayChar(0, GRID_Y*2, 192, MAKECOLOR(14,1)); /* lower left corner */
DisplayChar(GRID_X*4, GRID_Y*2, 217, MAKECOLOR(14,1)); /*lower right */
/** Draw inside corners **/
for (x=1; x<GRID_X; x++) {
DisplayChar(x*4, 0, 194, MAKECOLOR(14,1)); /* top edge */
for (y=1; y<GRID_Y; y++) {
DisplayChar(x*4, y*2, 197, MAKECOLOR(14,1)); /* intersections */
}
DisplayChar(x*4, GRID_Y*2, 193, MAKECOLOR(14,1)); /* bottom edge */
}
/** Draw connecting lines **/
for (x=0; x<=GRID_X; x++) {
for (y=0; y<=GRID_Y; y++) {
if (y != GRID_Y) {
DisplayChar(x*4, y*2+1, 179, MAKECOLOR(14,1)); /* verticals */
}
if (x != GRID_X) {
for (i=1; i<4; i++) {
DisplayChar(x*4+i, y*2, 196 , MAKECOLOR(14,1)); /* horizontals */
}
}
}
}
GotoXY_(0, SCREEN_Y - 1);
for (x=0; x<GRID_X; x++) {
for (y=0; y<GRID_Y; y++) {
DisplayCell(x, y);
}
}
}
void SetUpBoard(void)
{
int i, j;
int nMines;
BOOL bDone;
char cBuffer[80];
bShowBombs = FALSE;
/** First, get number of bombs **/
nNumMines = 0;
while ((nNumMines < 10) || (nNumMines > 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; i++) {
for (j=0; j<GRID_Y; j++) {
Board[i][j] = 0;
UserMark[i][j] = 0;
}
}
for (nMines=0; nMines<nNumMines; nMines++) {
bDone = FALSE;
while (!bDone) {
i = nRandom(GRID_X); /* First you roll it, */
j = nRandom(GRID_Y); /* Then you pat it, */
if ((Board[i][j] == bEMPTY) &&
(!((i <= 1) && (j <= 1))) &&
(!((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<PositionListHead) && (!bFound); nIndex++) {
if ((PositionList[nIndex][0] == x) && (PositionList[nIndex][1] == y)) {
bFound = TRUE;
}
}
if (!bFound) {
PositionList[PositionListHead][0] = x;
PositionList[PositionListHead][1] = y;
PositionListHead++;
}
if (PositionListHead > 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; nIndex<pAG->CellCount; 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; Cell<pGroup1->CellCount; 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; x<CellCount; x++) {
if (!FindPositionInAG(pTempAG, pCurrentAG->Cell[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<CellHolderHead; x++) {
PositionListHead = AddSurroundingToPositionList(
PositionList,
PositionListHead,
CellHolder[x][0],
CellHolder[x][1]);
}
}
}
*pPositionListHead = PositionListHead;
return (bRetVal);
}
void EvaluatePosition(void)
{
int CurrentX, CurrentY;
int x, y;
int Cell;
int dX, dY;
int BombCount, CellCount;
int PositionList[GRID_X * GRID_Y][2], PositionListHead;
ADJACENCYGROUP *pTempAG;
BOOL bDone;
BOOL bModifiedAny;
bModifiedAny = TRUE;
for (x=0; x<GRID_X; x++) {
for (y=0; y<GRID_Y; y++) {
ComputeAdjacency(x, y);
}
}
PositionList[0][0] = UserX;
PositionList[0][1] = UserY;
PositionListHead = 1;
while (bModifiedAny) {
bModifiedAny = FALSE;
while (PositionListHead > 0) {
CurrentX = PositionList[0][0];
CurrentY = PositionList[0][1];
for (x=0; x<PositionListHead-1; x++) {
PositionList[x][0] = PositionList[x+1][0];
PositionList[x][1] = PositionList[x+1][1];
}
PositionListHead--;
ComputeAdjacency(CurrentX, CurrentY);
BombCount = AdjacencyGroup[CurrentX][CurrentY].BombCount;
CellCount = AdjacencyGroup[CurrentX][CurrentY].CellCount;
if ((CellCount > 0) && (BombCount > -1)) {
/*
Rule 1: if number of bombs = number of cells, all are bombs!
*/
if (CellCount == BombCount) {
for (Cell=0; Cell<CellCount; Cell++) {
x = AdjacencyGroup[CurrentX][CurrentY].Cell[Cell][0];
y = AdjacencyGroup[CurrentX][CurrentY].Cell[Cell][1];
MarkBombCell(x, y);
PositionListHead = AddSurroundingToPositionList(PositionList,
PositionListHead,
x, y);
bModifiedAny = TRUE;
}
} else {
/*
Rule 2: if number of bombs = 0, all cells are ok!
*/
if ((BombCount == 0) && (CellCount > 0)) {
for (Cell=0; Cell<CellCount; Cell++) {
x = AdjacencyGroup[CurrentX][CurrentY].Cell[Cell][0];
y = AdjacencyGroup[CurrentX][CurrentY].Cell[Cell][1];
VisitCell(x, y);
PositionListHead = AddToPositionList(PositionList,
PositionListHead,
x, y);
PositionListHead = AddSurroundingToPositionList(PositionList,
PositionListHead,
x, y);
bModifiedAny = TRUE;
}
} else {
/*
Rule 3: if AG completely overlaps another AG, subtract 2nd
# of bombs from 1st; check rules 1 & 2. If rule 1 or
2 is true in this case, stop looking in rule 3.
*/
bDone = FALSE;
for (Cell=0; (Cell<CellCount) && (!bDone); Cell++) {
x = AdjacencyGroup[CurrentX][CurrentY].Cell[Cell][0];
y = AdjacencyGroup[CurrentX][CurrentY].Cell[Cell][1];
for (dX=-1; (dX<=1) && (!bDone); dX++) {
for (dY=-1; (dY<=1) && (!bDone); dY++) {
if ((x+dX >= 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<GRID_X; x++) {
for (y=0; y<GRID_Y; y++) {
if ((Board[x][y] == bVISITED) || (Board[x][y] == bCURRENT)) {
PositionListHead = AddToPositionList(PositionList,
PositionListHead,
x, y);
}
}
}
}
}
}
BOOL LetUserMove(void)
{
BOOL bDone;
BOOL bQuit;
int Ch;
bDone = FALSE;
while (!bDone) {
Ch = getch();
switch (Ch) {
case 0:
Ch = getch();
switch (Ch) {
case 71: /* home */
bDone = Travel(-1, -1);
break;
case 72: /* up arrow */
bDone = Travel(0, -1);
break;
case 73: /* page up */
bDone = Travel(1, -1);
break;
case 75: /* left arrow */
bDone = Travel(-1, 0);
break;
case 77: /* right arrow */
bDone = Travel(1, 0);
break;
case 79: /* end */
bDone = Travel(-1, 1);
break;
case 80: /* down arrow */
bDone = Travel(0, 1);
break;
case 81: /* page down */
bDone = Travel(1, 1);
break;
}
break;
case '7': /* home */
bDone = Travel(-1, -1);
break;
case '8': /* up arrow */
bDone = Travel(0, -1);
break;
case '9': /* page up */
bDone = Travel(1, -1);
break;
case '4': /* left arrow */
bDone = Travel(-1, 0);
break;
case '6': /* right arrow */
bDone = Travel(1, 0);
break;
case '1': /* end */
bDone = Travel(-1, 1);
break;
case '2': /* down arrow */
bDone = Travel(0, 1);
break;
case '3': /* page down */
bDone = Travel(1, 1);
break;
case 'Q':
case 'q':
case 27:
bDone = TRUE;
break;
case 'M':
case 'm':
PlaceUserMark();
break;
case '?':
EvaluatePosition();
break;
}
}
bShowBombs = TRUE;
PaintBoard();
GotoXY_(0, SCREEN_Y - 1);
printf("Again (Y/n)? ");
bDone = FALSE;
while (!bDone) {
Ch = getch();
if ((Ch == 'Y') || (Ch == 'y') || (Ch == 13) || (Ch == 10)) {
bDone = TRUE;
bQuit = FALSE;
printf("Y\n");
}
if ((Ch == 'N') || (Ch == 'n')) {
bDone = TRUE;
bQuit = TRUE;
printf("N\n");
}
if (Ch == NULL) {
getch();
}
}
return (bQuit);
}
int main(int argc, char *argv[])
{
BOOL bDone;
Initialize();
if ((argc > 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);
}