/*	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);
}


