
//	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-
//
//	Epd2Wb
//
//	Copyright (c) 2001, Bruce Moreland.  All rights reserved.
//
//	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-
//
//	This file is part of the Epd2Wb EPD/Winboard harness project.
//
//	Epd2Wb 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 2 of the License, or (at your option)
//	any later version.
//
//	Epd2Wb 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 Epd2Wb; if not, write to the Free Software Foundation, Inc.,
//	59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
//	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-

#include "epd2wb.h"
#include <ctype.h>

//	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-

//	This module tries to figure out if two strings refer to the same move.  It
//	does this without having any chess knowledge.  Instead, it knows how
//	standard algebraic notation works, and it's able to make enough assertions
//	about the form of moves as describe in algebraic notation that it is able
//	to differentiate between moves represented in various types of algebraic
//	notations.
//
//	It can also successfully compare SAN strings in English to attempted
//	solutions that are in another language, if it knows what the language is.

//	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-

//	Remove 'x', '-', '+', '=', ':', '!', '?', and '#' from a string.  The idea
//	is that I want to make a bare-bones move from a more verbose one.  These
//	symbols don't really add any information.
//
//	So this turns Nxe4+ into Ne4, e8=Q# become e8Q, etc.

void VCompact(char * szIn, char * szOut)
{
	for (; *szIn; szIn++)
		switch (*szIn) {
		case 'x':
		case '-':
		case '+':
		case '=':
		case ':':
		case '#':
		case '?':
		case '!':
			break;
		default:
			*szOut++ = *szIn;
			break;
		}
	*szOut = '\0';
}

//	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-

#define	rnkNIL	-1				// These are used to indicate that the value
#define	filNIL	-1				//  of a rnk, fil, or pc is unknown or empty
#define	pcNIL	-1				//  or what have you.

typedef	struct	tagSQ {
	int	rnk;
	int	fil;
}	SQ, * PSQ;

typedef	struct	tagMOV {
	SQ	sqFrom;					// Source square.
	SQ	sqTo;					// Destination square.
	int	pcFrom;					// Piece that's moving.
	int	pcTo;					// pcNIL or piece promoted to.
}	MOV, * PMOV;

//	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-

//	This figures out if "ch" is a piece, and if so which one (it returns
//	"pcPAWN", "pcKNIGHT", etc.
//
//	The function is told what language to use to do its conversion.  If you
//	pass the German language record in here, for instance, "T" becomes pcROOK.

int PcFromCh(int ch, PLANG plang, BOOL fCaseSensitive)
{
	int	pc;
	
	for (pc = pcPAWN; pc <= pcKING; pc++) {
		if (ch == plang->argbPc[pc])
			return pc;
		if ((!fCaseSensitive) && (tolower(ch) == tolower(plang->argbPc[pc])))
			return pc;
	}
	return pcNIL;
}

//	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-

//	These functions try to get a rank, file, or full coordinate from "sz"

BOOL FGetRnk(char * sz, int * prnk)
{
	if ((sz[0] < '1') || (sz[0] > '8'))
		return fFALSE;
	*prnk = sz[0] - '1';
	return fTRUE;
}

BOOL FGetFil(char * sz, int * pfil)
{
	if ((sz[0] < 'a') || (sz[0] > 'h'))
		return fFALSE;
	*pfil = sz[0] - 'a';
	return fTRUE;
}

BOOL FGetCoord(char * sz, PSQ psq)
{
	SQ	sq;		// I use this to avoid writing crap into the output square if
				//  I get a file but not a rank.
	
	if (!FGetFil(sz, &sq.fil))
		return fFALSE;
	if (!FGetRnk(sz + 1, &sq.rnk))
		return fFALSE;
	*psq = sq;
	return fTRUE;
}

//	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-

//	Converts a coordinate into an ISQ (an integer from 0..63).

int IsqFromSq(PSQ psq)
{
	return psq->rnk * 8 + psq->fil;
}

//	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-

//	This derives as much information as possible from "szMov" and stuffs it
//	into "pmov".  It returns FALSE if the move made no sense.
//
//	"szMov" is either in full coordinate notation, coordinate notation
//	preceded by piece type, or SAN.  Promoted pieces can be in either upper
//	or lower case.
//
//	I use a few goto's to compact the code a little.

BOOL FDecipher(PBD pbd, PLANG plang, char * szMov, PMOV pmov)
{
	//	Check for a couple of obvious exception cases (remember, the "-" is
	//	gone by this time).
	//
	if (!strcmp(szMov, "OO"))
		szMov = (pbd->coMove == coWHITE) ? "e1g1" : "e8g8";
	else if (!strcmp(szMov, "OOO"))
		szMov = (pbd->coMove == coWHITE) ? "e1c1" : "e8c8";
	//
	//	Set all the fields to unknown except the "from" piece, which is going
	//	to be set on the line after this.
	//
	pmov->sqFrom.rnk = pmov->sqTo.rnk = rnkNIL;
	pmov->sqFrom.fil = pmov->sqTo.fil = filNIL;
	pmov->pcTo = pcNIL;
	//
	//	The full form here is something like Nb1c3.  We can also handle
	//	b1c3, N1c3, Nbc3, or Nc3.
	//
	//	If I get something like "bc3", "b3", "b8Q", or "b8q", I'm going
	//	to assume the "from" piece must be a pawn.
	//
	//	First, check for an explicit piece.
	//
	if ((pmov->pcFrom = PcFromCh(*szMov, plang, fTRUE)) != pcNIL)
		szMov++;
	//
	//	Next, try to get a coord.
	//
	if (FGetCoord(szMov, &pmov->sqFrom)) {
		szMov += 2;
		//
		//	If we got one coord, try to get another one.
		//
		if (FGetCoord(szMov, &pmov->sqTo)) {
			int	pcFrom;

			szMov += 2;
			//
			//	If we're here, we have two coords.  We can go to the "from"
			//	square and get the piece there.  If this conflicts with an
			//	explicit piece, bomb out, otherwise remember it.
			//
			pcFrom = pbd->argpcco[IsqFromSq(&pmov->sqFrom)].pc;
			if (pcFrom == pcNIL)	// <-- This indicates an illegal move.
				return fFALSE;
			if ((pmov->pcFrom != pcNIL) && (pcFrom != pmov->pcFrom))
				return fFALSE;
			pmov->pcFrom = pcFrom;
			//
			//	Try to get the piece promoted to if it is there.  If we don't
			//	know what the moving piece was -- surprise! -- it was a pawn,
			//	so remember that.
			//
			//	I am careful to allow "e7e8q" here, as well as "e7e8Q".
			//
lblPromote:	if ((pmov->pcTo = PcFromCh(*szMov, plang, fFALSE)) != pcNIL) {
				szMov++;
				if ((pmov->pcFrom != pcNIL) && (pmov->pcFrom != pcPAWN))
					return fFALSE;
				pmov->pcFrom = pcPAWN;
			}
			if (*szMov == '\0')
				return fTRUE;
		} else {
			//
			//	If we're here, we got one coordinate, but not a second.  I
			//	have mistakenly put this coordinate into the "from" coord.
			//	It should actually go into the "to" coord, so fix that.
			//
			//	If we don't know what the piece being moved was, it was a
			//	pawn (we've gotten something like "e4").
			//
			pmov->sqTo = pmov->sqFrom;
			pmov->sqFrom.rnk = rnkNIL;
			pmov->sqFrom.fil = filNIL;
			if (pmov->pcFrom == pcNIL)
				pmov->pcFrom = pcPAWN;
			goto lblPromote;
		}
	} else if (FGetFil(szMov, &pmov->sqFrom.fil)) {
		//
		//	If I am here, I couldn't get a coord, but I got a file.  This is
		//	fine whether or not there is an explicit piece, but if there isn't
		//	one, the piece must be a pawn.  Examples here are "Nbd7" and
		//	"ab4".
		//
		if (pmov->pcFrom == pcNIL)
			pmov->pcFrom = pcPAWN;
		//
		//	Go get the destination square and then deal with promotion.
		//
lblTo:	szMov++;
		if (FGetCoord(szMov, &pmov->sqTo)) {
			szMov += 2;
			goto lblPromote;
		}
	} else if (FGetRnk(szMov, &pmov->sqFrom.rnk)) {
		//
		//	If I'm here, I couldn't get a coord, but I got a rank.  That's
		//	fine as long as we had an explicit piece.  "N6d7" is okay, but
		//	"6d7" is not.  "P6d7" is also not allowed.
		//
		if ((pmov->pcFrom == pcNIL) || (pmov->pcFrom == pcPAWN))
			return fFALSE;
		//
		//	Go get the destination square.
		//
		goto lblTo;
	}
	return fFALSE;
}

//	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-

//	This takes two moves and compares them.  It's possible that one or both
//	may be partially filled in, rather than completely filled in.

BOOL FCompare(PMOV pmovTry, PMOV pmovAnswer)
{
	//	We have a destination square for sure, so let's compare that.
	//
	if (pmovTry->sqTo.rnk != pmovAnswer->sqTo.rnk)
		return fFALSE;
	if (pmovTry->sqTo.fil != pmovAnswer->sqTo.fil)
		return fFALSE;
	//
	//	The promotion piece should also be accurate.
	//
	if (pmovTry->pcTo != pmovAnswer->pcTo)
		return fFALSE;
	//
	//	Okay, at this point we know that both moves are going to the right
	//	square, and the promotion problem has been ruled out.  Let's check
	//	to see if anything else definitely does not match.  If I'm comparing
	//	two values, both of which are known, and they aren't the same, that's
	//	an obvious failure.
	//
	if ((pmovTry->sqFrom.rnk != rnkNIL) &&
		(pmovAnswer->sqFrom.rnk != rnkNIL) &&
		(pmovTry->sqFrom.rnk != pmovAnswer->sqFrom.rnk))
		return fFALSE;
	if ((pmovTry->sqFrom.fil != filNIL) &&
		(pmovAnswer->sqFrom.fil != filNIL) &&
		(pmovTry->sqFrom.fil != pmovAnswer->sqFrom.fil))
		return fFALSE;
	if ((pmovTry->pcFrom != pcNIL) && (pmovAnswer->pcFrom != pcNIL) &&
		(pmovTry->pcFrom != pmovAnswer->pcFrom))
		return fFALSE;
	//
	//	The "to" coordinate is finished, so now I need to deal with the
	//	"from" coordinate.
	//
	//	If I am comparing two full coordinates, I am done, since the checks
	//	above test that case.
	//
	//	If I didn't get a full coordinate in both cases, and the piece being
	//	moved is a pawn, I am either capturing to the destination or moving
	//	there.  If I am moving there, the move cannot be ambiguous.  If I am
	//	capturing, I must have at least a "file" dis-ambiguator in all cases,
	//	and this would have passed the above checks.
	//
	//	If I didn't get a full coordinate, and the piece being moved is not
	//	a pawn, the move is either not ambiguous, in which case I'm fine, or
	//	the move is ambiguous and was marked as such.  That would have passed
	//	the checks above.
	//
	//	The only case I'm not catching here is when I have Ne4c3 given as
	//	Nec3 in the EPD (presumably, because that is correct), and N4c3
	//	passed in by the engine.  I can't tell if that's the same move, and I
	//	would call that a match, which it is.  But N2c3 is also a match if
	//	this knight on on e2.
	//
	//	The only reason this problem is not crushing is that N4c3 is not
	//	legal SAN if Nec3 adequately disambiguates (disambiguation via a file
	//	is preferred).
	//
	//	So:
	//
	return fTRUE;
}

//	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-

//	We want to figure out if "szTry" matches "szAnswer".  I'm expecting that
//	both are in SAN or some form of coordinate notation.  I'm going to lightly
//	assume that both moves are legal and give enough information to
//	disambiguate.  This is an advantage, and will let me figure out what is
//	going on without using any chess knowledge.

BOOL FCheckAnswer(char * szFen, char * szTry, char * szAnswer,
	PLANG plangTry, PLANG plangAnswer)
{
	char	aszAnswer[32];
	char	aszTry[32];
	MOV	movTry;
	MOV	movAnswer;
	BD	bd;

	VCompact(szTry, aszTry);
	VCompact(szAnswer, aszAnswer);
	if ((plangTry == plangAnswer) && (!strcmp(szAnswer, szTry)))
		return fTRUE;	// Identical string & language is a success.
	if (!FFenToBd(szFen, &bd))			// If the FEN is broken, the test
		return fFALSE;					//  can't have succeeded.
	//
	//	Try to interpret what I've been given from the EPD line and from the
	//	analysis line.
	//
	//	The case where the correct answer can't be deciphered is a really bad
	//	case, and it would be nice if I could report that to the user, but
	//	that's a bit complicated and I can't report it with 100% accuracy
	//	anyway.
	//
	if (!FDecipher(&bd, plangTry, aszTry, &movTry))
		return fFALSE;
	if (!FDecipher(&bd, plangAnswer, aszAnswer, &movAnswer))
		return fFALSE;
	//
	//	Compare these two deciphered moves.
	//
	if (FCompare(&movTry, &movAnswer))
		return fTRUE;
	return fFALSE;
}

//	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-
