dbell@pdact.pd.necisa.oz.au (David I. Bell) (04/30/91)
Here is a game that uses mini-X graphics. So now you have a client program that actually does something useful (assuming ANY game can be useful :-). Landmine is one of the best deductive style of games I have seen in years. -dbell- #! /bin/sh # This is a shell archive. Remove anything before this line, then unpack # it by saving it into a file and typing "sh file". To overwrite existing # files, type "sh file -c". You can also feed this as standard input via # unshar, or by typing "sh <file", e.g.. If this archive is complete, you # will see the following message at the end: # "End of archive 1 (of 1)." # Contents: landmine.c landmine.doc # Wrapped by dbell@elm on Tue Apr 30 12:28:14 1991 PATH=/bin:/usr/bin:/usr/ucb ; export PATH if test -f 'landmine.c' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'landmine.c'\" else echo shar: Extracting \"'landmine.c'\" \(28343 characters\) sed "s/^X//" >'landmine.c' <<'END_OF_FILE' X/* X * Landmine, the game. X * Written for mini-X by David I. Bell. X */ X X#include <stdio.h> X#include <stdarg.h> X#include <graphics.h> X X X#define MINSIZE 3 /* minimum size of board */ X#define MAXSIZE 30 /* maximum size of board */ X#define SIZE 15 /* default size of playing board */ X#define MINEPERCENT 15 /* default percentage of mines */ X#define SAVEFILE "landmine.save" /* default save file name */ X#define MAGIC 649351261 /* magic number in save files */ X#define MAXPARAMS 1000 /* maximum different game parameters */ X X#define FULLSIZE (MAXSIZE + 2) /* board size including borders */ X X#define BOARDGAP 10 /* millimeter gap around board */ X#define RIGHTGAP 15 /* mm gap between board, right side */ X#define BUTTONGAP 20 /* mm gap between buttons */ X#define STATUSGAP 35 /* mm gap between buttons and status */ X X#define BUTTONWIDTH 80 /* width of buttons (pixels) */ X#define BUTTONHEIGHT 25 /* height of buttons (pixels) */ X#define RIGHTSIDE 150 /* pixels to guarantee for right side */ X#define BOARDBORDER 2 /* border size around board */ X X#define BLUE 1 /* colors */ X#define GREEN 2 X#define RED 4 X#define GREY 7 X X X/* X * Print the number of steps taken. X * This is used twice, and is a macro to guarantee that X * the two printouts match. X */ X#define PRINTSTEPS printline(2, "Steps: %3d\n", steps) X X X/* X * Typedefs local to this program. X */ Xtypedef unsigned short CELL; /* cell value */ Xtypedef int POS; /* cell position */ X X X/* X * For defining bitmaps easily. X */ X#define X ((unsigned) 1) X#define _ ((unsigned) 0) X X#define BITS(a,b,c,d,e,f,g,h,i) \ X ((((((((a*2+b)*2+c)*2+d)*2+e)*2+f)*2+g)*2+h)*2+i << 7) X X Xstatic GR_BITMAP twolegs_fg[] = { /* two legs foreground */ X BITS(_,_,_,_,_,_,_,_,_), X BITS(_,_,_,X,X,X,_,_,_), X BITS(_,_,_,X,X,X,_,_,_), X BITS(_,_,_,X,X,X,_,_,_), X BITS(_,_,_,_,X,_,_,_,_), X BITS(_,_,X,X,X,X,X,_,_), X BITS(_,X,_,X,_,X,_,X,_), X BITS(_,X,_,X,X,X,_,X,_), X BITS(_,_,_,X,_,X,_,_,_), X BITS(_,_,_,X,_,X,_,_,_), X BITS(_,_,X,X,_,X,X,_,_), X BITS(_,_,_,_,_,_,_,_,_) X}; X Xstatic GR_BITMAP twolegs_bg[] = { /* two legs background */ X BITS(_,_,X,X,X,X,X,_,_), X BITS(_,_,X,X,X,X,X,_,_), X BITS(_,_,X,X,X,X,X,_,_), X BITS(_,_,X,X,X,X,X,_,_), X BITS(_,X,X,X,X,X,X,X,_), X BITS(X,X,X,X,X,X,X,X,X), X BITS(X,X,X,X,_,X,X,X,X), X BITS(X,X,X,X,X,X,X,X,X), X BITS(X,X,X,X,X,X,X,X,X), X BITS(_,X,X,X,X,X,X,X,_), X BITS(_,X,X,X,X,X,X,X,_), X BITS(_,X,X,X,X,X,X,X,_) X}; X X Xstatic GR_BITMAP oneleg_fg[] = { /* one leg foreground */ X BITS(_,_,_,_,_,_,_,_,_), X BITS(_,_,_,X,X,X,_,_,_), X BITS(_,_,_,X,X,X,_,_,_), X BITS(_,_,_,X,X,X,_,_,_), X BITS(_,_,_,_,X,_,_,_,_), X BITS(_,_,X,X,X,X,X,_,_), X BITS(_,X,_,X,_,X,_,X,_), X BITS(_,_,_,X,X,X,_,X,_), X BITS(_,_,_,_,_,X,_,_,_), X BITS(_,_,_,_,_,X,_,_,_), X BITS(_,_,_,_,_,X,X,_,_), X BITS(_,_,_,_,_,_,_,_,_), X}; X X Xstatic GR_BITMAP oneleg_bg[] = { /* one leg background */ X BITS(_,_,X,X,X,X,X,_,_), X BITS(_,_,X,X,X,X,X,_,_), X BITS(_,_,X,X,X,X,X,_,_), X BITS(_,_,X,X,X,X,X,_,_), X BITS(_,X,X,X,X,X,X,X,_), X BITS(X,X,X,X,X,X,X,X,X), X BITS(X,X,X,X,_,X,X,X,X), X BITS(X,X,X,X,X,X,X,X,X), X BITS(_,_,X,X,X,X,X,X,X), X BITS(_,_,_,_,X,X,X,X,_), X BITS(_,_,_,_,X,X,X,X,_), X BITS(_,_,_,_,X,X,X,X,_) X}; X X Xstatic GR_BITMAP noleg_fg[] = { /* no legs foreground */ X BITS(_,_,_,_,_,_,_,_,_), X BITS(_,_,_,X,X,X,_,_,_), X BITS(_,_,_,X,X,X,_,_,_), X BITS(_,_,_,X,X,X,_,_,_), X BITS(_,_,_,_,X,_,_,_,_), X BITS(_,_,X,X,X,X,X,_,_), X BITS(_,X,_,X,_,X,_,X,_), X BITS(_,_,_,X,X,X,_,_,_), X BITS(_,_,_,_,_,_,_,_,_), X BITS(_,_,_,_,_,_,_,_,_), X BITS(_,_,_,_,_,_,_,_,_), X BITS(_,_,_,_,_,_,_,_,_), X}; X X Xstatic GR_BITMAP noleg_bg[] = { /* no legs background */ X BITS(_,_,X,X,X,X,X,_,_), X BITS(_,_,X,X,X,X,X,_,_), X BITS(_,_,X,X,X,X,X,_,_), X BITS(_,_,X,X,X,X,X,_,_), X BITS(_,X,X,X,X,X,X,X,_), X BITS(X,X,X,X,X,X,X,X,X), X BITS(X,X,X,X,_,X,X,X,X), X BITS(X,X,X,X,X,X,X,X,X), X BITS(_,_,X,X,X,X,X,_,_), X BITS(_,_,_,_,_,_,_,_,_), X BITS(_,_,_,_,_,_,_,_,_), X BITS(_,_,_,_,_,_,_,_,_) X}; X X X/* X * Components of a cell. X */ X#define F_EMPTY ' ' /* default value for empty square */ X#define F_REMEMBER '*' /* character to remember mine */ X#define F_WRONG 'X' /* character to remember wrong guess */ X#define F_DISPLAY 0xff /* character to be displayed here */ X#define F_MINE 0x100 /* TRUE if a mine is here */ X#define F_EDGE 0x200 /* TRUE if this is edge of the world */ X#define F_OLD 0x400 /* TRUE if been at this square before */ X#define F_REACH 0x800 /* TRUE if can reach this square */ X#define F_FLAGS 0xff00 /* all flags */ X X X/* X * The status of the game. X * This structure is read and written from the save file. X */ Xstatic struct status { /* status of games */ X long s_magic; /* magic number */ X short s_playing; /* TRUE if playing a game */ X short s_size; /* current size of board */ X short s_mines; /* current number of mines on board */ X short s_legs; /* number of legs left */ X short s_steps; /* number of steps taken this game */ X short s_index; /* current game parameter index */ X short s_sizeparam[MAXPARAMS]; /* table of size parameters */ X short s_mineparam[MAXPARAMS]; /* table of mine parameters */ X long s_games0[MAXPARAMS]; /* games finished with no legs */ X long s_games1[MAXPARAMS]; /* games finished with one leg */ X long s_games2[MAXPARAMS]; /* games finished with two legs */ X long s_steps0[MAXPARAMS]; /* steps taken in no leg games */ X long s_steps1[MAXPARAMS]; /* steps taken in one leg games */ X long s_steps2[MAXPARAMS]; /* steps taken in two leg games */ X CELL s_board[FULLSIZE*FULLSIZE]; /* board layout */ X} st; X X X/* X * Definitions to make structure references easy. X */ X#define magic st.s_magic X#define playing st.s_playing X#define size st.s_size X#define mines st.s_mines X#define legs st.s_legs X#define steps st.s_steps X#define index st.s_index X#define sizeparam st.s_sizeparam X#define mineparam st.s_mineparam X#define games0 st.s_games0 X#define games1 st.s_games1 X#define games2 st.s_games2 X#define steps0 st.s_steps0 X#define steps1 st.s_steps1 X#define steps2 st.s_steps2 X#define board st.s_board X X X#define boardpos(row, col) (((row) * FULLSIZE) + (col)) X#define ismine(cell) ((cell) & F_MINE) X#define isedge(cell) ((cell) & F_EDGE) X#define isold(cell) ((cell) & F_OLD) X#define isseen(cell) (((cell) & F_DISPLAY) == F_REMEMBER) X#define isknown(cell) (((cell) & F_DISPLAY) != F_EMPTY) X#define displaychar(cell) ((cell) & F_DISPLAY) X#define badsquare(n) (((n) <= 0) || ((n) > size)) X X X/* X * Offsets for accessing adjacent cells. X */ Xstatic POS steptable[8] = { X FULLSIZE, -FULLSIZE, 1, -1, FULLSIZE-1, X FULLSIZE+1, -FULLSIZE-1, -FULLSIZE+1 X}; X X Xstatic GR_WINDOW_ID mainwid; /* main window id */ Xstatic GR_WINDOW_ID boardwid; /* board window id */ Xstatic GR_WINDOW_ID statwid; /* status display window id */ Xstatic GR_WINDOW_ID quitwid; /* window id for quit button */ Xstatic GR_WINDOW_ID savewid; /* window id for save button */ Xstatic GR_WINDOW_ID newgamewid; /* window id for new game button */ X Xstatic GR_GC_ID boardgc; /* graphics context for board */ Xstatic GR_GC_ID cleargc; /* GC for clearing cell of board */ Xstatic GR_GC_ID redgc; /* GC for drawing red */ Xstatic GR_GC_ID greengc; /* GC for drawing green */ Xstatic GR_GC_ID blackgc; /* GC for drawing black */ Xstatic GR_GC_ID delaygc; /* GC for delaying */ Xstatic GR_GC_ID statgc; /* GC for status window */ Xstatic GR_GC_ID buttongc; /* GC for drawing buttons */ Xstatic GR_GC_ID xorgc; /* GC for inverting things */ X Xstatic GR_SIZE xp; /* pixels for x direction per square */ Xstatic GR_SIZE yp; /* pixels for y direction per square */ Xstatic GR_SIZE statwidth; /* width of window drawing text in */ Xstatic GR_SIZE statheight; /* height of window drawing text in */ Xstatic GR_SIZE charheight; /* height of characters */ Xstatic GR_COORD charxpos; /* current X position for characters */ Xstatic GR_COORD charypos; /* current Y position for characters */ X Xstatic GR_SCREEN_INFO si; /* window information */ Xstatic GR_FONT_INFO fi; /* font information */ X Xstatic char *savefile; /* filename for saving game */ X X X/* X * Procedures. X */ Xstatic void printline(); Xstatic void newline(); Xstatic void delay(); Xstatic void dokey(); Xstatic void readevent(); Xstatic void doexposure(); Xstatic void dobutton(); Xstatic void drawbomb(); Xstatic void drawstatus(); Xstatic void drawbutton(); Xstatic void drawboard(); Xstatic void drawcell(); Xstatic void cellcenter(); Xstatic void clearcell(); Xstatic void newgame(); Xstatic void moveto(); Xstatic void setcursor(); Xstatic void togglecell(); Xstatic void gameover(); Xstatic void readgame(); Xstatic void findindex(); Xstatic POS findcell(); Xstatic GR_BOOL checkpath(); Xstatic GR_BOOL writegame(); X X X Xmain(argc, argv) X char **argv; X{ X GR_COORD x; X GR_COORD y; X GR_SIZE width; X GR_SIZE height; X GR_COORD rightx; /* x coordinate for right half stuff */ X GR_BOOL setsize; /* TRUE if size of board is set */ X GR_BOOL setmines; /* TRUE if number of mines is set */ X GR_SIZE newsize; /* desired size of board */ X GR_COUNT newmines; /* desired number of mines */ X X setmines = GR_FALSE; X setsize = GR_FALSE; X X argc--; X argv++; X while ((argc > 0) && (**argv == '-')) { X switch (argv[0][1]) { X case 'm': X if (argc <= 0) { X fprintf(stderr, "Missing mine count\n"); X exit(1); X } X argc--; X argv++; X newmines = atoi(*argv); X setmines = GR_TRUE; X break; X X case 's': X if (argc <= 0) { X fprintf(stderr, "Missing size\n"); X exit(1); X } X argc--; X argv++; X newsize = atoi(*argv); X setsize = GR_TRUE; X break; X X default: X fprintf(stderr, "Unknown option \"-%c\"\n", X argv[0][1]); X exit(1); X } X argc--; X argv++; X } X if (argc > 0) X savefile = *argv; X X srand(time(0)); X X readgame(savefile); X X if (setsize) { X if ((newsize < MINSIZE) || (newsize > MAXSIZE)) { X fprintf(stderr, "Illegal board size\n"); X exit(1); X } X if (newsize != size) { X if (steps && playing) { X fprintf(stderr, X "Cannot change size while game is in progress\n"); X exit(1); X } X playing = GR_FALSE; X size = newsize; X if (!playing) X mines = (size * size * MINEPERCENT) / 100; X } X } X X if (setmines) { X if ((newmines <= 0) || ((newmines > (size * size) / 2))) { X fprintf(stderr, "Illegal number of mines\n"); X exit(1); X } X if (newmines != mines) { X if (steps && playing) { X fprintf(stderr, X "Cannot change mines while game is in progress\n"); X exit(1); X } X playing = GR_FALSE; X mines = newmines; X } X } X X findindex(); X X /* X * Parameters of the game have been verified. X * Now open the graphics and play the game. X */ X if (GrOpen() < 0) { X fprintf(stderr, "Cannot open graphics\n"); X exit(1); X } X X GrGetScreenInfo(&si); X GrGetFontInfo(0, &fi); X charheight = fi.height; X X /* X * Create the main window which will contain all the others. X */ X mainwid = GrNewWindow(GR_ROOT_WINDOW_ID, 0, 0, si.cols, si.rows, X 0, si.black, si.white); X X /* X * Create the board window which lies at the left side. X * Make the board square, and as large as possible while still X * leaving room to the right side for statistics and buttons. X */ X width = si.cols - RIGHTSIDE - (si.xdpcm * RIGHTGAP / 10) - BOARDBORDER * 2; X height = (((long) width) * si.ydpcm) / si.xdpcm; X if (height > si.rows - y * 2) { X height = si.rows - BOARDBORDER * 2; X width = (((long) height) * si.xdpcm) / si.ydpcm; X } X xp = width / size; X yp = height / size; X width = xp * size - 1; X height = yp * size - 1; X x = BOARDBORDER; X y = (si.rows - height) / 2; X rightx = x + width + (si.xdpcm * RIGHTGAP / 10); X boardwid = GrNewWindow(mainwid, x, y, width, height, BOARDBORDER, X BLUE, si.white); X X /* X * Create the buttons. X */ X x = rightx; X y = (si.ydpcm * BOARDGAP / 10); X quitwid = GrNewWindow(mainwid, x, y, BUTTONWIDTH, BUTTONHEIGHT, X 1, RED, si.white); X X y += (si.ydpcm * BUTTONGAP / 10); X savewid = GrNewWindow(mainwid, x, y, BUTTONWIDTH, BUTTONHEIGHT, X 1, GREEN, si.white); X X y += (si.ydpcm * BUTTONGAP / 10); X newgamewid = GrNewWindow(mainwid, x, y, BUTTONWIDTH, BUTTONHEIGHT, X 1, GREEN, si.white); X X /* X * Create the statistics window. X */ X x = rightx; X y += (si.ydpcm * STATUSGAP / 10); X width = si.cols - x; X height = si.rows - y; X statwid = GrNewWindow(mainwid, x, y, width, height, 0, X 0, 0); X statwidth = width; X statheight = height; X X /* X * Create the GC for drawing the board. X */ X boardgc = GrNewGC(); X cleargc = GrNewGC(); X delaygc = GrNewGC(); X redgc = GrNewGC(); X greengc = GrNewGC(); X statgc = GrNewGC(); X blackgc = GrNewGC(); X buttongc = GrNewGC(); X xorgc = GrNewGC(); X GrSetGCBackground(boardgc, BLUE); X GrSetGCForeground(cleargc, BLUE); X GrSetGCForeground(redgc, RED); X GrSetGCForeground(greengc, GREEN); X GrSetGCForeground(statgc, GREY); X GrSetGCForeground(delaygc, 0); X GrSetGCForeground(blackgc, si.black); X GrSetGCMode(delaygc, GR_MODE_XOR); X GrSetGCMode(xorgc, GR_MODE_XOR); X GrSetGCUseBackground(boardgc, GR_FALSE); X GrSetGCUseBackground(buttongc, GR_FALSE); X X GrSelectEvents(boardwid, GR_EVENT_MASK_EXPOSURE | X GR_EVENT_MASK_BUTTON_DOWN | GR_EVENT_MASK_KEY_DOWN); X X GrSelectEvents(statwid, GR_EVENT_MASK_EXPOSURE); X X GrSelectEvents(quitwid, GR_EVENT_MASK_EXPOSURE | X GR_EVENT_MASK_BUTTON_DOWN); X X GrSelectEvents(newgamewid, GR_EVENT_MASK_EXPOSURE | X GR_EVENT_MASK_BUTTON_DOWN); X X GrSelectEvents(savewid, GR_EVENT_MASK_EXPOSURE | X GR_EVENT_MASK_BUTTON_DOWN); X X setcursor(); X X GrMapWindow(mainwid); X GrMapWindow(boardwid); X GrMapWindow(statwid); X GrMapWindow(quitwid); X GrMapWindow(savewid); X GrMapWindow(newgamewid); X X if (!playing) X newgame(); X X while (GR_TRUE) X readevent(); X} X X X/* X * Read the next event and handle it. X */ Xstatic void Xreadevent() X{ X GR_EVENT event; X X GrGetNextEvent(&event); X X switch (event.type) { X case GR_EVENT_TYPE_BUTTON_DOWN: X dobutton(&event.button); X break; X X case GR_EVENT_TYPE_EXPOSURE: X doexposure(&event.exposure); X break; X X case GR_EVENT_TYPE_KEY_DOWN: X dokey(&event.keystroke); X break; X } X} X X X/* X * Handle exposure events. X */ Xstatic void Xdoexposure(ep) X GR_EVENT_EXPOSURE *ep; X{ X if (ep->wid == boardwid) { X drawboard(); X return; X } X X if (ep->wid == statwid) { X drawstatus(); X return; X } X X if (ep->wid == quitwid) { X drawbutton(quitwid, "QUIT"); X return; X } X X if (ep->wid == savewid) { X drawbutton(savewid, "SAVE GAME"); X return; X } X X if (ep->wid == newgamewid) { X drawbutton(newgamewid, "NEW GAME"); X return; X } X} X X X/* X * Here when we get a button down event. X */ Xstatic void Xdobutton(bp) X GR_EVENT_BUTTON *bp; X{ X if (bp->wid == boardwid) { X moveto(findcell(bp->x, bp->y)); X return; X } X X if (bp->wid == quitwid) { X GrFillRect(quitwid, xorgc, 0, 0, BUTTONWIDTH, BUTTONHEIGHT); X GrFlush(); X if (savefile) X writegame(savefile); X GrClose(); X exit(0); X } X X if (bp->wid == savewid) { X GrFillRect(savewid, xorgc, 0, 0, BUTTONWIDTH, BUTTONHEIGHT); X GrFlush(); X if (savefile == NULL) X savefile = SAVEFILE; X if (writegame(savefile)) X write(1, "\007", 1); X else X delay(); X GrFillRect(savewid, xorgc, 0, 0, BUTTONWIDTH, BUTTONHEIGHT); X } X X if (bp->wid == newgamewid) { X GrFillRect(newgamewid, xorgc, 0, 0, BUTTONWIDTH, BUTTONHEIGHT); X GrFlush(); X if (playing) X write(1, "\007", 1); X else { X newgame(); X delay(); X } X GrFillRect(newgamewid, xorgc, 0, 0, BUTTONWIDTH, BUTTONHEIGHT); X } X} X X X/* X * Here when we get a keypress in a window. X */ Xstatic void Xdokey(kp) X GR_EVENT_KEYSTROKE *kp; X{ X if ((kp->wid != boardwid) || !playing) X return; X X switch (kp->ch) { X case ' ': /* remember or forget mine */ X togglecell(findcell(kp->x, kp->y)); X break; X } X} X X X/* X * Redraw the board. X */ Xstatic void Xdrawboard() X{ X GR_COORD row; X GR_COORD col; X X for (row = 1; row < size; row++) { X GrLine(boardwid, boardgc, 0, row * yp - 1, size * xp - 1, X row * yp - 1); X GrLine(boardwid, boardgc, row * xp - 1, 0, X row * xp - 1, size * yp - 1); X } X for (row = 0; row < FULLSIZE; row++) { X for (col = 0; col < FULLSIZE; col++) { X drawcell(boardpos(row, col)); X } X } X} X X X/* X * Draw a cell on the board. X */ Xstatic void Xdrawcell(pos) X POS pos; /* position to be drawn */ X{ X GR_COORD x; X GR_COORD y; X GR_SIZE chwidth; X GR_SIZE chheight; X GR_SIZE chbase; X CELL cell; X GR_CHAR ch; X X cell = board[pos]; X if (!isknown(cell)) X return; X X ch = displaychar(cell); X if (ch == F_WRONG) { X drawbomb(pos, greengc, GR_FALSE); X return; X } X X if (isold(cell)) { X clearcell(pos); X cellcenter(pos, &x, &y); X GrGetGCTextSize(boardgc, &ch, 1, &chwidth, &chheight, X &chbase); X GrText(boardwid, boardgc, x - chwidth / 2 + 1, X y + chheight / 2, &ch, 1); X return; X } X X drawbomb(pos, redgc, GR_FALSE); X} X X X/* X * Clear a particular cell. X */ Xstatic void Xclearcell(pos) X POS pos; /* position to be cleared */ X{ X GR_COORD row; X GR_COORD col; X X row = pos / FULLSIZE; X col = pos % FULLSIZE; X GrFillRect(boardwid, cleargc, col * xp - xp, row * yp - yp, X xp - 1, yp - 1); X} X X X/* X * Draw a bomb in a window using the specified GC. X * The bomb is animated and the terminal is beeped if necessary. X */ Xstatic void Xdrawbomb(pos, gc, animate) X POS pos; /* position to draw bomb at */ X GR_GC_ID gc; /* GC for drawing (red or green) */ X GR_BOOL animate; /* TRUE to animate the bomb */ X{ X GR_COORD x; X GR_COORD y; X GR_COUNT count; X X if (animate) X write(1, "\007", 1); X X cellcenter(pos, &x, &y); X X count = (animate ? 8 : 1); X for (;;) { X GrFillEllipse(boardwid, gc, x, y, xp / 2 - 3, yp / 2 - 3); X if (--count == 0) X return; X delay(); X clearcell(pos); X delay(); X } X} X X X/* X * Draw a button which has a specified label string centered in it. X */ Xstatic void Xdrawbutton(window, label) X GR_WINDOW_ID window; X char *label; X{ X GR_SIZE width; X GR_SIZE height; X GR_SIZE base; X X GrGetGCTextSize(buttongc, label, strlen(label), &width, &height, &base); X GrText(window, buttongc, (BUTTONWIDTH - width) / 2, X (BUTTONHEIGHT - height) / 2 + height - 1, X label, strlen(label)); X} X X X/* X * Set the cursor as appropriate. X * The cursor changes depending on the number of legs left. X */ Xstatic void Xsetcursor() X{ X GR_BITMAP *fgbits; /* bitmap for foreground */ X GR_BITMAP *bgbits; /* bitmap for background */ X X switch (legs) { X case 0: X fgbits = noleg_fg; X bgbits = noleg_bg; X break; X case 1: X fgbits = oneleg_fg; X bgbits = oneleg_bg; X break; X default: X fgbits = twolegs_fg; X bgbits = twolegs_bg; X break; X } X GrSetCursor(boardwid, 9, 12, 4, 6, si.white, si.black, fgbits, bgbits); X} X X X/* X * Delay for a while so that something can be seen. X * This is done by drawing a large rectangle over the window using a mode X * of XOR with the value of 0, (which does nothing except waste time). X */ Xstatic void Xdelay() X{ X GR_COUNT i; X X for (i = 0; i < 3; i++) { X GrFillRect(boardwid, delaygc, 0, 0, xp * size - 1, X yp * size - 1); X GrFlush(); X } X} X X X/* X * Calculate the coordinates of the center of a cell on the board. X * The coordinates are relative to the origin of the board window. X */ Xstatic void Xcellcenter(pos, retx, rety) X POS pos; /* position to find center of */ X GR_COORD *retx; /* returned X coordinate */ X GR_COORD *rety; /* returned Y coordinate */ X{ X *retx = (pos % FULLSIZE) * xp - 1 - xp / 2; X *rety = (pos / FULLSIZE) * yp - 1 - yp / 2; X} X X X/* X * Draw the status information in the status window. X */ Xstatic void Xdrawstatus() X{ X long score; X long allsteps; X long games; X X score = 0; X games = games0[index]; X allsteps = steps0[index]; X score += games1[index]; X games += games1[index]; X allsteps += steps1[index]; X score += games2[index] * 2; X games += games2[index]; X allsteps += steps2[index]; X X printline(0, "Size: %2d\n", size); X printline(1, "Mines: %3d\n", mines); X PRINTSTEPS; X printline(3, "Legs: %d\n", legs); X X printline(5, "Won games: %3d\n", games2[index]); X printline(6, "1-leg games:%3d\n", games1[index]); X printline(7, "Lost games: %3d\n", games0[index]); X X if (games) { X printline(9, "Legs/game: %3d.%03d\n", score / games, X ((score * 1000) / games) % 1000); X X printline(10, "Steps/game:%3d.%03d\n", allsteps / games, X ((allsteps * 1000) / games) % 1000); X } X} X X X/* X * Printf routine for windows, which can print at particular lines. X * A negative line number means continue printing at the previous location. X * Assumes the status window for output. X */ Xstatic void Xprintline(row, fmt) X GR_COORD row; /* row to print line on (or -1) */ X char *fmt; /* printf-stype format string */ X{ X va_list ap; X GR_COUNT cc; X GR_SIZE width; X char *cp; X char buf[256]; X X va_start(ap, fmt); X vsprintf(buf, fmt, ap); X va_end(ap); X X if (row >= 0) { X charxpos = 0; X charypos = charheight * row + charheight - 1; X } X X cp = buf; X for (;;) { X cc = 0; X width = 0; X while (*cp >= ' ') { X width += fi.widths[*cp++]; X cc++; X } X if (width) { X GrText(statwid, statgc, charxpos, charypos, X cp - cc, cc); X charxpos += width; X } X X switch (*cp++) { X case '\0': X return; X case '\n': X newline(); X break; X case '\r': X charxpos = 0; X break; X } X } X} X X X/* X * Clear the remainder of the line and move to the next line. X * This assumes output is in the status window. X */ Xstatic void Xnewline() X{ X GrFillRect(statwid, blackgc, charxpos, charypos - charheight + 1, X statwidth - charxpos, charheight); X charxpos = 0; X charypos += charheight; X} X X X/* X * Translate a board window coordinate into a cell position. X * If the coordinate is outside of the window, or exactly on one X * of the interior lines, then a coordinate of 0 is returned. X */ Xstatic POS Xfindcell(x, y) X GR_COORD x; X GR_COORD y; X{ X GR_COORD row; X GR_COORD col; X X if (((x % xp) == 0) || ((y % yp) == 0)) X return 0; X row = (y / yp) + 1; X col = (x / xp) + 1; X if ((row <= 0) || (row > size) || (col <= 0) || (col > size)) X return 0; X return boardpos(row, col); X} X X X/* X * Initialize the board for playing X */ Xstatic void Xnewgame() X{ X GR_COORD row; X GR_COORD col; X GR_COUNT count; X CELL cell; X POS pos; X X for (row = 0; row < FULLSIZE; row++) { X for (col = 0; col < FULLSIZE; col++) { X cell = F_EMPTY; X if (badsquare(row) || badsquare(col)) X cell |= F_EDGE; X board[boardpos(row, col)] = cell; X } X } X X playing = GR_TRUE; X count = 0; X legs = 2; X steps = 0; X drawstatus(); X setcursor(); X X while (count < mines) { X do { X row = (rand() / 16) % (size * size + 1); X } while (row == (size * size)); X X col = (row % size) + 1; X row = (row / size) + 1; X pos = boardpos(row, col); X X if ((pos == boardpos(1,1)) || (pos == boardpos(1,2)) || X (pos == boardpos(2,1)) || (pos == boardpos(2,2)) || X (pos == boardpos(size,size))) X continue; X X if (!ismine(board[pos]) && checkpath(pos)) X count++; X } X X board[boardpos(1,1)] = (F_OLD | '0'); X X GrClearWindow(boardwid, GR_TRUE); X} X X X/* X * Check to see if there is still a path from the top left corner to the X * bottom right corner, if a new mine is placed at the indicated position. X * Returns GR_TRUE if mine was successfully placed. X */ Xstatic GR_BOOL Xcheckpath(pos) X POS pos; /* position to place mine at */ X{ X CELL *bp; /* current board position */ X CELL *endbp; /* ending position */ X POS endpos; /* ending position */ X GR_COUNT count; /* number of neighbors */ X GR_COUNT i; /* loop counter */ X GR_BOOL more; /* GR_TRUE if new square reached */ X X /* X * Begin by assuming there is a mine at the specified location, X * and then count neighbors. If there are less than two other X * mines or edge squares, then there must still be a path. X */ X board[pos] |= F_MINE; X X count = 0; X X for (i = 7; i >= 0; i--) { X if (board[pos + steptable[i]] & (F_MINE | F_EDGE)) X count++; X } X X if (count < 2) X return GR_TRUE; X X /* X * Two or more neighbors, so we must do the full check. X * First clear the reach flag, except for the top left corner. X */ X endpos = boardpos(size, size); X bp = &board[endpos]; X endbp = bp; X while (bp != board) X *bp-- &= ~F_REACH; X board[boardpos(1,1)] |= F_REACH; X X /* X * Now loop looking for new squares next to already reached squares. X * Stop when no more changes are found, or when the lower right X * corner is reached. X */ X do { X more = GR_FALSE; X for (bp = &board[boardpos(1,1)]; bp != endbp; bp++) { X if (*bp & F_REACH) { X for (i = 7; i >= 0; i--) { X if ((bp[steptable[i]] & (F_MINE | F_REACH | F_EDGE)) == 0) { X bp[steptable[i]] |= F_REACH; X more = GR_TRUE; X } X } X } X } X X if (board[endpos] & F_REACH) X return GR_TRUE; X } while (more); X X /* X * Cannot reach the lower right corner, so remove the mine and fail. X */ X board[pos] &= ~F_MINE; X X return GR_FALSE; X} X X X/* X * Move to a particular position and see if we hit a mine. X * If not, then count the number of mines adjacent to us so it can be seen. X * If we are stepping onto a location where we remembered a mine is at, X * then don't do it. Moving is only allowed to old locations, or to X * locations adjacent to old ones. X */ Xstatic void Xmoveto(newpos) X POS newpos; /* position to move to */ X{ X POS fixpos; /* position to fix up */ X CELL cell; /* current cell */ X GR_COUNT count; /* count of cells */ X GR_COUNT i; /* index for neighbors */ X X if ((newpos < 0) || (newpos >= (FULLSIZE * FULLSIZE)) || !playing) X return; X X cell = board[newpos]; X X if (isedge(cell) || (isseen(cell)) || isold(cell)) X return; X X count = isold(cell); X for (i = 0; i < 8; i++) X if (isold(board[newpos + steptable[i]])) X count++; X X if (count <= 0) X return; X X cell = (cell & F_FLAGS) | F_OLD; X steps++; X X PRINTSTEPS; X X if (ismine(cell)) { /* we hit a mine */ X legs--; X board[newpos] = (F_REMEMBER | F_MINE); X cell = (F_EMPTY | F_OLD); X board[newpos] = cell; X drawbomb(newpos, redgc, GR_TRUE); X clearcell(newpos); X setcursor(); X for (i = 0; i < 8; i++) { X fixpos = newpos + steptable[i]; X if (isold(board[fixpos])) { X board[fixpos]--; X drawcell(fixpos); X } X } X drawstatus(); X } X X count = 0; X for (i = 0; i < 8; i++) X if (ismine(board[newpos + steptable[i]])) X count++; X board[newpos] = cell | (count + '0'); X X drawcell(newpos); X X if ((legs <= 0) || (newpos == boardpos(size,size))) X gameover(); X} X X X/* X * Remember or forget the location of a mine. X * This is for informational purposes only and does not affect anything. X */ Xstatic void Xtogglecell(pos) X POS pos; /* position to toggle */ X{ X CELL cell; X X if ((pos <= 0) || !playing) X return; X X cell = board[pos]; X if (isknown(cell)) { X if (!isseen(cell)) X return; X board[pos] = (board[pos] & F_FLAGS) | F_EMPTY; X clearcell(pos); X return; X } X X board[pos] = (board[pos] & F_FLAGS) | F_REMEMBER; X drawcell(pos); X} X X X/* X * Here when the game is over. X * Show where the mines are, and give the results. X */ Xstatic void Xgameover() X{ X POS pos; X CELL cell; X X playing = GR_FALSE; X switch (legs) { X case 0: X games0[index]++; X steps0[index] += steps; X break; X case 1: X games1[index]++; X steps1[index] += steps; X break; X case 2: X games2[index]++; X steps2[index] += steps; X break; X } X X for (pos = 0; pos < (FULLSIZE * FULLSIZE); pos++) { X cell = board[pos]; X if (isseen(cell)) X cell = (cell & F_FLAGS) | F_WRONG; X if (ismine(cell)) X cell = (cell & F_FLAGS) | F_REMEMBER; X board[pos] = cell; X } X X drawboard(); X drawstatus(); X} X X X/* X * Search the game parameter table for the current board size and X * number of mines, and set the index for those parameters so that X * the statistics can be accessed. Allocates a new index if necessary. X */ Xstatic void Xfindindex() X{ X for (index = 0; index < MAXPARAMS; index++) { X if ((sizeparam[index] == size) && (mineparam[index] == mines)) X return; X } X for (index = 0; index < MAXPARAMS; index++) { X if (sizeparam[index] == 0) { X sizeparam[index] = size; X mineparam[index] = mines; X return; X } X } X fprintf(stderr, "Too many parameters in save file\n"); X exit(1); X} X X X/* X * Read in a saved game if available, otherwise start from scratch. X * Exits if an error is encountered. X */ Xstatic void Xreadgame(name) X char *name; /* filename */ X{ X int fd; X X fd = -1; X if (name) X fd = open(name, 0); X X if (fd < 0) { X magic = MAGIC; X size = SIZE; X mines = (size * size * MINEPERCENT) / 100; X playing = GR_FALSE; X return; X } X X if (read(fd, &st, sizeof(st)) != sizeof(st)) X magic = 0; X close(fd); X X if ((magic != MAGIC) || (size > MAXSIZE)) { X fprintf(stderr, "Save file format is incorrect\n"); X exit(1); X } X} X X X/* X * Write the current game to a file. X * Returns nonzero on an error. X */ Xstatic GR_BOOL Xwritegame(name) X char *name; /* filename */ X{ X int fd; X X if (name == NULL) X return GR_TRUE; X X fd = creat(name, 0666); X if (fd < 0) X return GR_TRUE; X X if (write(fd, &st, sizeof(st)) != sizeof(st)) { X close(fd); X return GR_TRUE; X } X close(fd); X return GR_FALSE; X} X X/* END CODE */ END_OF_FILE if test 28343 -ne `wc -c <'landmine.c'`; then echo shar: \"'landmine.c'\" unpacked with wrong size! fi # end of 'landmine.c' fi if test -f 'landmine.doc' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'landmine.doc'\" else echo shar: Extracting \"'landmine.doc'\" \(5909 characters\) sed "s/^X//" >'landmine.doc' <<'END_OF_FILE' X LANDMINE X X XLandmine is a game in which you try to safely move step by step from the Xtop left corner of a square board to the bottom right corner of the board. XScattered among the cells of the board are invisible mines which blow up Xif you step on them. When you step on a mine, one of your legs is blown Xoff, and if you lose both legs, then the game is over. X XSteps are made one at a time from any cell you have already visited to Xany of the eight possible cells which are adjacent to it. At the start Xof the game you are placed in the top left corner of the board, which is Xguaranteed to have no mine in it. There are also no mines immediately Xadjacent to the starting cell. There is also a guaranteed path from the Xstarting cell to the finishing cell which does not encounter any mines. X XYou cannot see the mines, however you have a detector which indicates Xthe presence of the mines. The detector cannot locate the direction of Xany individual mine, but it does indicate the number of mines immediately Xadjacent to your cell. By using this information from all the cells you Xhave already visited, you can usually deduce where the mines must be. XIn this way, you can find your way safely to the destination cell. Some Xof the deductions that can be made are very subtle, and experience will Ximprove your scores dramatically. X XWhen landmine is started up under mini-X, the board is displayed at the Xleft side, some buttons are displayed at the upper right, and some statistics Xare displayed at the lower right. To make a step, use the mouse to move the Xcursor onto the cell that you wish to step on, and press a button. A number Xwill appear in that square (if there is no mine there!) indicating now many Xmines are adjacent to that square. You can step onto any cell which is Xadjacent to any cell you have already stepped on. X XIn order to help you deduce where the mines are and what steps are safe, Xyou can mark any non-visited cell as containing a mine. To do this, move Xthe mouse to that cell, and then type a space. A red circle will then Xappear in that cell to indicate that you think it contains a mine. A Xside effect of doing this is that you cannot accidentally step on the Xsquare while it is marked. Notice that the cell is marked as you request Xwhether or not a mine is actually there, thus if you mistakenly mark a Xcell, you are likely to get confused and later step on a real mine! If Xyou think that a cell is marked in error, you can clear the marking by Xmoving the mouse to the cell and then typing another space. X XIf you step on a mine, you will hear a beep, and a red circle will flash Xon and off for a few seconds to indicate that the mine has exploded. XThen the mine will be removed, and the counts in the adjacent cells Xwill be adjusted to reflect the new situation with one less mine. Your Xnumber of legs is also reduced by 1, and the cursor shape is changed to Xindicate this. If you lose both legs, then the game is lost. If you Xsuccessfully make it to the lower right corner, then the game is won. X XWhen the game is over, the location of all mines are marked with red circles. XIn addition, if you had marked cells as containing mines, but the cells were Xwrongly marked, then those cells are shown with green circles. After you Xhave analyzed the results of the game, you can start a new game by using Xthe NEW GAME button. X XThe three buttons on the top right of the screen are QUIT, SAVE GAME, Xand NEW GAME. The buttons are activated by moving the cursor to the Xinterior of the button, and pressing any mouse button. No confirmation Xis asked for these actions. X XQUIT will immediately quit playing and return you to text mode and exit. XHowever, if you had started landmine with a filename to restore from, Xthen the current status will be saved back to that file. If you had not Xsupplied a filename on starting, and have not saved the game using the XSAVE GAME button, then QUIT will quit without saving anything. X XSAVE GAME is used to save the current status of the game without exiting. XThis lets you make sure that the game is saved away when you have been Xplaying for a long time and are worried about crashes. If you had given Xa file for restoring of previous games, then the game will be saved back Xinto that file. If you had not specified a filename, then a default name Xof "landmine.save" will be used. If the save operation works, the button Xwill momentarily flash. If the save operation fails, then a beep will Xbe sounded. X XNEW GAME is used to start a new game. This button can only be used after Xa game has just been completed. That is, after both legs have been blown Xoff or you have reached the destination cell, you use this button to Xbegin another game. X XWhen starting landmine, you can specify some options on the command line. XThe options are the following: X X landmine [-s n] [-m n] [savefile] X XThe savefile is the filename to save the game into when the SAVE GAME or XQUIT buttons are used, as described above. X XThe -s option sets the size of the board. The size is the number of cells Xacross the board, and also down the board. The board size can be set to Xany size from 3 to 30. The default size is 15. X XThe -m option sets the number of mines on the board. This can be set from X1 to 50% of the number of cells on the board. The default number of mines Xis 15% of the number of cells on the board. For the default board size, Xthe default number of mines is 33. X XThe statistics saved in the save file are kept for each combination of Xboard size and mines, thus you can play with many different combinations Xof values and the statistics will be kept separately. There is a limit Xof 1000 different combinations that can be saved. X XYou cannot change the board size or number of mines while a game is in Xprogress. To change the parameters, finish the game, save the game into a Xfile and exit, then restart landmine giving the new parameters. END_OF_FILE if test 5909 -ne `wc -c <'landmine.doc'`; then echo shar: \"'landmine.doc'\" unpacked with wrong size! fi # end of 'landmine.doc' fi echo shar: End of archive 1 \(of 1\). cp /dev/null ark1isdone MISSING="" for I in 1 ; do if test ! -f ark${I}isdone ; then MISSING="${MISSING} ${I}" fi done if test "${MISSING}" = "" ; then echo You have the archive. rm -f ark[1-9]isdone else echo You still need to unpack the following archives: echo " " ${MISSING} fi ## End of shell archive. exit 0