1
0
vt100-games/FindThatMine/ftm.c

892 lines
15 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* FIND THAT MINE!
A MineSweeper clone for CRTs and CP/M.
Derived from the plain CP/M version.
Copyright (c) 2012-2020 Miguel Garcia / FloppySoftware, Spain.
Color version by Anna Christina Naß <acn@acn.wtf>
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation; either version 3, or (at your option) any
later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
Author's contact:
www.floppysoftware.es
cpm-connections.blogspot.com
floppysoftware@gmail.com
To compile with MESCC:
cc ftm
ccopt ftm
zsm ftm
hextocom ftm
Usage:
ftm [tty_name]
If no tty_name is given, VT100COL is used
Revisions:
11 Sep 2012 : v1.0 : Initial version for Amstrad PCW & Samaruc GUI.
26 Jul 2013 : v1.2 : Adapted to Samaruc v1.5.
16 Feb 2014 : v1.3 : Adapted to last changes in Samaruc.
09 Jul 2014 : v1.4 : CP/M version.
07 Apr 2020 : v2.0 : Adapted to KS library for CP/M.
21 Apr 2020 : v2.1 : Added color option (by Anna Christina Naß <acn@acn.wtf>
Notes:
LEVELS: 1 2 3 NOTES
ROWS: 8 8 8
COLS: 8 12 16
SQUARES: 64 96 128 ROWS * COLS
MINES: 8 12 16 SQUARES / 8 (1 MINES EACH 8 SQUARES)
*/
/* Do you want debug tools?
*/
#define DEBUG_MINES 0
/* MESCC libraries
*/
#include <mescc.h>
#include <conio.h>
#include <ctype.h>
#include <sprintf.h>
/* KS libraries
*/
#include "ks.h"
/* DEFs for BOARD
*/
#define MAX_ROWS 8 /* Max. number of rows */
#define MAX_COLS 16 /* Max. number of columns */
#define MAX_SQ 128 /* Max. number of squares (MAX_ROWS * MAX_COLS) */
#define MAX_BUF 512 /* MAX_SQ * 4 */
/* DEFs for SPRITES
*/
#define SPR_MINE '*' /* Mine */
#define SPR_FLAG 'P' /* Flag */
#define SPR_UNK '?' /* Unknown */
#define SPR_ERR 'X' /* Flag error */
#define SPR_BLANK ' ' /* Blank */
/* DEFs for border elements (corners, top+bottom line, right+left line)
* either the normal ones (7-bit compatible) are used or codepage 437
*/
#define CP437
#ifdef CP437
#define B_TOP_LEFT 201
#define B_TOP_RIGHT 187
#define B_BOTTOM_LEFT 200
#define B_BOTTOM_RIGHT 188
#define B_TOP_BOTTOM 205
#define B_LEFT_RIGHT 186
#else
#define B_TOP_LEFT '+'
#define B_TOP_RIGHT '+'
#define B_BOTTOM_LEFT '+'
#define B_BOTTOM_RIGHT '+'
#define B_TOP_BOTTOM '-'
#define B_LEFT_RIGHT '|'
#endif
/* Global variables
*/
int brd_rows, /* Board size in rows */
brd_cols; /* Board size in columns */
WORD mines_row[MAX_ROWS], /* Map for mines */
visib_row[MAX_ROWS], /* Map for visible squares */
flags_row[MAX_ROWS], /* Map for squares with flag */
count_row[MAX_ROWS]; /* Map for counters */
BYTE sq_buf[MAX_BUF]; /* Buffer to hold the above maps */
int row_off[8], /* Array to help in calculation */
col_off[8]; /* of adjacent squares */
int gameover, /* NZ if game is over */
dead, /* NZ if player is dead */
level, /* Current level */
viscnt, /* Number of visible squares in board */
flgcnt, /* Number of flags in board */
squares, /* Number of squares in board */
mines, /* Number of mines in board */
random; /* Random number */
/* Code starts here
*/
main(argc, argv)
int argc, argv[]; /* char *argv[] - unsupported by MESCC */
{
int i;
int tty_n;
int *tty_s; /* char *[] */
/* Init KS
*/
if(argc == 1) {
KsHello(KS_VT100COL);
}
else {
if(strcmp(argv[1], "/?")==0) {
tty_s = KsGetNames();
tty_n = KsGetHowMany();
puts("Usage: ftm [tty_name]\n");
puts("Supported TTY names:");
for(i = 0; i < tty_n; ++i) {
putchar('\t'); puts(tty_s[i]);
}
puts("If no tty_name is given, VT100COL (VT100 with color) is used.");
return;
}
else {
if(KsHello(KsGetCode(argv[1])) == -1) {
puts("Unknown TTY name.\n\nRun 'ftm' to know supported TTYs.\n");
return;
}
}
}
/* Setup
*/
Setup();
/* Play game
*/
Play();
/* The end
*/
PrStr("\n\n\tBye, bye!\n\n\n");
}
/* Setup program
*/
Setup()
{
int i; BYTE *p;
/* Build array to help in calculation of adjacent squares
*/
row_off[0]=-1; col_off[0]= 0; /* Up */
row_off[1]=-1; col_off[1]=+1; /* Upper Right */
row_off[2]= 0; col_off[2]=+1; /* Right */
row_off[3]=+1; col_off[3]=+1; /* Lower Right */
row_off[4]=+1; col_off[4]= 0; /* Down */
row_off[5]=+1; col_off[5]=-1; /* Lower Left */
row_off[6]= 0; col_off[6]=-1; /* Left */
row_off[7]=-1; col_off[7]=-1; /* Upper Left */
/* Setup maps
*/
p = sq_buf;
for(i = 0; i < MAX_ROWS; ++i)
{
mines_row[i]=p; p+=MAX_COLS;
visib_row[i]=p; p+=MAX_COLS;
flags_row[i]=p; p+=MAX_COLS;
count_row[i]=p; p+=MAX_COLS;
}
/* Other
*/
random = 0;
}
/* Show copyright, etc.
*/
Copyright()
{
int r;
char str[100];
KsClear();
if(KsCan(KS_CAN_COLOR)) {
//sprintf(str, "%sFind %sThat %sMine%s!%s\n", KsFgCol(RED), KsFgCol(CYN), KsFgCol(BLU), KsFgCol(YEL), KsResetAttr());
KsPosCursor(r = 2, 32);
KsFgCol(BRED); KsPutStr("Find "); KsFgCol(BCYN); KsPutStr("That "); KsFgCol(BBLU); KsPutStr("Mine"); KsFgCol(BYEL); KsPutStr("!\n"); KsResetAttr();
}
else {
KsCenterStr(r = 2, "Find That Mine!\n");
}
KsCenterStr(++r , "v2.1 for CP/M & Z80\n\n");
++r;
KsCenterStr(++r ,"A minesweeper type game.\n");
++r;
KsCenterStr(++r ,"(c) 2012-2020 Miguel Garcia / Floppy Software, Spain.\n");
KsCenterStr(++r ,"www.floppysoftware.es\n");
KsCenterStr(++r ,"color version by acn@acn.wtf\n\n");
}
/* Select and play a level
*/
Play()
{
int run; char buf[2];
run = 1;
while(run)
{
Copyright();
PrStr("\t1 > Level 1 : 08 x 08 squares, 08 mines\n");
PrStr("\t2 > Level 2 : 08 x 12 squares, 12 mines\n");
PrStr("\t3 > Level 3 : 08 x 16 squares, 16 mines\n");
PrStr("\tQ > Quit game\n\n");
PrStr("\tYour Choice? ");
ReadLine(buf, 1);
switch(*buf)
{
case '1' : SetLevel(1); RunLevel(); break;
case '2' : SetLevel(2); RunLevel(); break;
case '3' : SetLevel(3); RunLevel(); break;
case 'Q' : run = 0; break;
default : break;
}
}
}
/* Setup level
*/
SetLevel(lev)
int lev;
{
/* I'm thinking...
*/
PrStr("\n\n\tI'm thinking... ");
/* Setup level values
*/
level = lev; brd_rows = 8;
switch(level)
{
case 1 : brd_cols = 8; break;
case 2 : brd_cols = 12; break;
case 3 : brd_cols = 16; break;
}
squares = brd_rows * brd_cols;
mines = squares / 8;
gameover = dead = viscnt = flgcnt = 0;
/* Setup board
*/
ClrBoard();
/* Setup counters
*/
ClrCounts();
}
/* Play level
*/
RunLevel()
{
int row, col; char buf[4];
DrawBoard();
for(;;)
{
ShowPlayfield();
if(gameover) {
KsPosCursor(16,0);
PrStr("\tRETURN > Quit level");
PrChTimes(' ', 15);
PrStr("\n\t");
PrChTimes(' ', 45);
PrStr("\n\t");
PrChTimes(' ', 25);
PrStr("\n\n\t");
PrChTimes(' ', 15);
KsPosCursor(18,0);
PrStr("\t? ");
ReadLine(buf, 0);
break;
}
KsPosCursor(16,0);
PrStr("\trc / cr > Select (ie 0B or B0)\n");
PrStr("\trcF / crF > Set/remove flag (ie: 3DF or D3F)\n");
PrStr("\tQ > Quit level\n\n");
PrStr("\tYour Move? ");
KsPosCursor(20, 19);
ReadLine(buf, 3);
if(buf[0] >= '0' && buf[0] < '0' + brd_cols)
{
row = buf[0] - '0';
if(buf[1] >= 'A' && buf[1] < 'A' + brd_cols)
{
col = buf[1] - 'A';
if(!buf[2])
Choice(row, col);
else if(buf[2] == 'F' && !buf[3])
ChoiceFlag(row, col);
}
}
if(buf[1] >= '0' && buf[1] < '0' + brd_cols)
{
row = buf[1] - '0';
if(buf[0] >= 'A' && buf[0] < 'A' + brd_cols)
{
col = buf[0] - 'A';
if(!buf[2])
Choice(row, col);
else if(buf[2] == 'F' && !buf[3])
ChoiceFlag(row, col);
}
}
else if(buf[0] == 'Q' && !buf[1])
gameover = 1;
}
}
/* Test if player has win the game
*/
TestIfWin()
{
if(flgcnt == mines && flgcnt + viscnt == squares)
gameover = 1;
}
/* Set or remove a flag in a square
*/
ChoiceFlag(row, col)
int row, col;
{
if(!TstVis(row, col))
{
if(TstFlag(row, col))
{
SetFlag(row, col, 0); --flgcnt;
}
else
{
SetFlag(row, col, 1); ++flgcnt;
TestIfWin();
}
}
}
/* Select a square
*/
Choice(row, col)
int row, col;
{
if(!TstVis(row, col) && !TstFlag(row, col))
{
if(TstMine(row, col))
{
gameover = dead = 1; return;
}
/* Make visible this square.
*/
SetVis(row, col, 1); ++viscnt;
/* Make visible the adjacent squares, if there are not mines around.
*/
if(!GetCount(row, col))
VisAdj(row, col);
TestIfWin();
}
}
/* Get square counter
*/
GetCount(row, col)
int row, col;
{
BYTE *p;
p = count_row[row];
return p[col];
}
/* Set square counter
*/
SetCount(row, col, val)
int row, col, val;
{
BYTE *p;
p=count_row[row];
p[col]=val;
}
/* Return 1 if there is a mine in the square, else 0
*/
TstMine(row, col)
int row, col;
{
BYTE *p;
p=mines_row[row];
return p[col] ? 1 : 0;
}
/* Set/remove a mine in a square
*/
SetMine(row, col, val)
int row, col, val;
{
BYTE *p;
p=mines_row[row];
p[col]=val;
}
/* Return 1 if there is a flag in the square, else 0
*/
TstFlag(row, col)
int row, col;
{
BYTE *p;
p=flags_row[row];
return p[col] ? 1 : 0;
}
/* Set/remove a flag in a square
*/
SetFlag(row, col, val)
int row, col, val;
{
BYTE *p;
p=flags_row[row];
p[col]=val;
}
/* Return 1 if it is a visible square, else 0
*/
TstVis(row, col)
int row, col;
{
BYTE *p;
p=visib_row[row];
return p[col] ? 1 : 0;
}
/* Set square to visible or invisible
*/
SetVis(row, col, val)
int row, col, val;
{
BYTE *p;
p=visib_row[row];
p[col]=val;
}
/* Return the number of mines in adjacent squares
*/
FindAdj(row, col)
int row, col;
{
int mines, rn, cn, i;
for((mines = i = 0); i < 8; ++i)
{
rn = row + row_off[i];
cn = col + col_off[i];
if(rn >= 0 && rn < brd_rows && cn >= 0 && cn < brd_cols)
if(TstMine(rn, cn))
++mines;
}
return mines;
}
/* Set adjacent squares visible - recursively
*/
VisAdj(row, col)
int row, col;
{
int rn, cn, i;
for(i=0; i < 8; ++i)
{
rn = row + row_off[i];
cn = col + col_off[i];
if(rn >= 0 && rn < brd_rows && cn >= 0 && cn < brd_cols)
{
if(!TstVis(rn, cn) && !TstFlag(rn, cn))
{
SetVis(rn, cn, 1); ++viscnt;
if(!GetCount(rn, cn))
VisAdj(rn, cn);
}
}
}
}
/* Draw the board (all around the playfield)
*/
DrawBoard()
{
int r, c;
KsClear();
PrStr("\n\n");
PrStr("\t ");
for(c = 0; c < brd_cols; ++c) {
PrCh(' '); PrCh('A' + c);
}
PrCh('\n');
KsFgCol(BBLK);
PrStr("\t "); PrCh(B_TOP_LEFT);
PrChTimes(B_TOP_BOTTOM, brd_cols + brd_cols + 1);
PrCh(B_TOP_RIGHT); PrCh('\n');
KsResetAttr();
for(r = 0; r < brd_rows; ++r)
{
PrCh('\t'); PrCh('0' + r);
KsFgCol(BBLK); PrCh(B_LEFT_RIGHT); KsResetAttr();
PrChTimes(' ', brd_cols*2);
PrCh(' '); KsFgCol(BBLK); PrCh(B_LEFT_RIGHT); KsResetAttr(); PrCh('0' + r);
PrCh('\n');
}
KsFgCol(BBLK);
PrStr("\t "); PrCh(B_BOTTOM_LEFT);
PrChTimes(B_TOP_BOTTOM, brd_cols + brd_cols + 1);
PrCh(B_BOTTOM_RIGHT); PrCh('\n');
KsResetAttr();
PrStr("\t ");
for(c = 0; c < brd_cols; ++c) {
PrCh(' '); PrCh('A' + c);
}
}
/* Draw the playfield
*/
ShowPlayfield()
{
int r, c;
for(r = 0; r < brd_rows; ++r)
{
KsPosCursor(4 + r, 10);
for(c = 0; c < brd_cols; ++c)
{
PrCh(' ');
if(TstVis(r, c))
{
if(GetCount(r, c)) {
if(KsCan(KS_CAN_COLOR)) {
switch(GetCount(r, c))
{
case 1: KsFgCol(BBLU); break;
case 2: KsFgCol(GRN); break;
case 3: KsFgCol(BRED); break;
case 4: KsFgCol(BLU); break;
case 5: KsFgCol(RED); break;
default: KsFgCol(CYN); break;
}
PrCh('0' + GetCount(r, c));
KsResetAttr();
}
else
PrCh('0' + GetCount(r, c));
}
else
PrCh(SPR_BLANK);
}
else if(gameover)
{
if(TstMine(r, c))
{
if(TstFlag(r, c)) {
KsFgCol(RED);
PrCh(SPR_FLAG);
KsResetAttr();
}
else {
KsFgCol(RED);
PrCh(SPR_MINE);
KsResetAttr();
}
}
else if(TstFlag(r, c))
PrCh(SPR_ERR);
else {
KsFgCol(BBLK);
PrCh(SPR_UNK);
KsResetAttr();
}
}
else
{
#if DEBUG_MINES
if(TstFlag(r, c))
PrCh(SPR_FLAG);
else if(TstMine(r, c))
PrCh(SPR_MINE);
else
PrCh(SPR_UNK);
#else
if(TstFlag(r, c)) {
KsFgCol(RED);
PrCh(SPR_FLAG);
KsResetAttr();
}
else {
KsFgCol(BBLK);
PrCh(SPR_UNK);
KsResetAttr();
}
#endif
}
}
switch(r)
{
case 0 :
KsPosCursor(5, (brd_cols*2)+16);
PrStr("Find That Mine!");
break;
case 2 :
KsPosCursor(7, (brd_cols*2)+16);
if(gameover) {
PrStr(" GAME OVER ");
}
else {
PrStr("Level: "); PrNumber(level);
}
break;
case 4 :
KsPosCursor(9, (brd_cols*2)+16);
if(gameover) {
if(dead) {
PrStr(" YOU'RE DEAD ");
}
else if(flgcnt + viscnt == squares) {
PrStr(" YOU WIN ");
}
else {
PrStr(" SEE YOU LATER");
}
}
else {
PrStr("Mines: "); PrNumber(mines);
}
break;
case 6 :
KsPosCursor(11, (brd_cols*2)+16);
if(!gameover) {
PrStr("Flags: "); PrNumber(flgcnt);
}
else {
PrChTimes(' ', 9);
}
break;
}
}
}
/* Setup board.
All squares without mines, invisibles and without flags.
Set the mines randomly.
*/
extern char RandStr[];
ClrBoard()
{
int r, c; char *p;
/*
*/
p = RandStr + 8 * (random & 0x0F);
for(r = 0; r < brd_rows; ++r)
{
for(c = 0; c < brd_cols; ++c)
{
/* Set square
*/
SetVis(r, c, 0);
SetFlag(r, c, 0);
SetMine(r, c, *p == '@' ? 1 : 0);
/* Update next value
*/
if((*++p)=='$')
p = RandStr;
}
}
}
/* Setup counters of adjacent squares
*/
ClrCounts()
{
int r, c; char *p;
for(r = 0; r < brd_rows; ++r)
for(c = 0; c < brd_cols; ++c)
SetCount(r, c, FindAdj(r, c));
}
/* String to setup board randomly (total of 128 bytes). 1 mine each 8 bytes.
*/
#asm
RandStr:
; v-------v-------v-------v-------
defb '----@---@-------------@----@----'
defb '-@-----------@----@----------@--'
defb '--@---------@----------@--@-----'
defb '----@----@-----------@---@------'
defb '$'
#endasm
/* Read line from keyboard.
Setup random number.
*/
ReadLine(buf, width)
char *buf;
int width;
{
int len; char ch;
len = 0;
while(!KsGetKb())
++random;
while(1)
{
switch(ch = KsGetCh())
{
case 8 :
case 127 :
if(len)
{
PrCh(8); PrCh(' '); PrCh(8); --len;
}
break;
case '\r' :
case '\n' :
buf[len] = 0;
return;
default :
if(ch >= ' ' && len < width)
PrCh(buf[len++] = toupper(ch));
}
}
}
/* Print character. Supports simple '\t'.
*/
PrCh(c)
int c;
{
if(c != '\t') {
KsPutCh(c);
}
else {
PrChTimes(' ', 8);
}
}
/* Print string
*/
PrStr(s)
char *s;
{
while(*s) {
PrCh(*s++);
}
}
/* Print character X times
*/
PrChTimes(ch, n)
int ch, n;
{
while(n--) {
PrCh(ch);
}
}
/* Print decimal number
*/
PrNumber(n)
int n;
{
int s[7];
sprintf(s, "%d", n);
PrStr(s);
}