//
//	Do not change this code directly by uploading .js file. Edit post 1366696 instead
//

// Chess PGN Reader Program by Ray Wilson
// Copyright 2002
// Changed by buti_oxa to do Pgn2Mfo translation.
//
//	Use Pgn2MfoInit to initialize the engine, Pgn2MfoTranslate to do the translation.
//
//
//--------------------------------------------

var globalsFiles = "abcdefgh";
var globalsRanks = "12345678";
var globaliMoveIndex  = 0; // Keeps track of current move index.
var globaliMoveNumber = 0; // Keeps track of current displayable move number.

var globalPGN_MaxMvNum;
var globalPGN_MoveArray;
var globalWhitePieceArray;
var globalBlackPieceArray;
var globalChessBoardArray;
var globalPieceName;
var globalCastles;
var i2c;

var globalOrgPos  = null;
var globalDstPos  = null;
var globalPromotedRunk = null;

var mfoGameText = '';
var mfoGameComments = '';
var mfoGameCommentsLastNumber = '';
var mfoGameResult = 'U';	// + white wins, - loses, = draws, or Unfinished, Black2move, White2move 
var mfoGameError = '';

//
// Chessmen state storage
//
//--------------------------------------------
//
// ChessPiece object definition
//--------------------------------------------
function ChessPiece(bc, pT, ims, inx)
{
	this.color  = bc;    // true = white, false = black.
	this.pcType = pT;    // B,N,R,K,Q,p
	this.imgsrc = ims;   // name of image.
	this.moves  = 0;     // number of times moved during game.
	this.index  = inx;   // Index of piece 0 thru 15
	this.moveno = 0;     // Game Move Index at the time this piece was moved.
	                     // used to determine en-passant capture.
}

//--------------------------------------------
// ChessPiece state storage
// 
// ChessPiece objects are stored in two arrays
// globalWhitePieceArray and globalBlackPieceArray
// Each has 16 ChessPiece objects arranged as
// p,p,p,p,p,p,p,p,R,N,B,Q,K,B,N,R
//
// The ChessPiece's live throughout the game
// they may be moved from the board to the 
// capture area but they are the same pieces
// through out the game. You can access them
// through the reference in the BoardPosition
// objects or through the array they are 
// stored in.
//
// When a pawn is promoted a new piece is 
// associated with the position but the original
// piece in the array remains a pawn.
//--------------------------------------------
function PiecesToArray(arrRef, sColor, bColor, sPiecesLayout) // true = white, false = black
{
	var fileRook1 = 0;
	var fileKing, fileRook2;

	for(var k=0; k<8; ++k)
		arrRef[k] = new ChessPiece(bColor, "p", sColor + "pawn.gif", k); 
	
	for (var k=0; k<8; ++k)	
		{
		sPiece = sPiecesLayout.substr(k,1);

		if (sPiece == 'K')
			fileKing = i2c[k];

		if (sPiece == 'R')
			{
			if (fileRook1 == 0)
				fileRook1 = i2c[k];
			else
				fileRook2 = i2c[k];
			}

		arrRef[k+8] = new ChessPiece(bColor, 
										sPiece,  
										sColor + globalPieceName[sPiece] + ".gif",
										k+8); 
		}

	// remember starting positions of rooks and king for castling

	globalCastles[bColor] = fileRook1 + fileKing + fileRook2;
}

function InitializeChessSet(startingFen)
{
	var fenWhite;
	var fenBlack;
		
	globalPieceName = new Array();
	
	globalPieceName['K'] = 'king';
	globalPieceName['Q'] = 'queen';
	globalPieceName['R'] = 'rook';
	globalPieceName['B'] = 'bishop';
	globalPieceName['N'] = 'knight';
	globalPieceName['P'] = 'pawn';

	i2c = new Array();

	i2c[0] = 'a';
	i2c[1] = 'b';
	i2c[2] = 'c';
	i2c[3] = 'd';
	i2c[4] = 'e';
	i2c[5] = 'f';
	i2c[6] = 'g';
	i2c[7] = 'h';

	globalCastles = new Array();

	globalWhitePieceArray = new Array();
	globalBlackPieceArray = new Array();

	if (startingFen == '')
		{
		fenWhite = 'RNBQKBNR';
		fenBlack = 'RNBQKBNR';
		globalCastles[true] = 'aeh';
		globalCastles[false] = 'aeh';
		}
	else
		{
		// extract initial posision from FEN
		
		var re = new RegExp('^([qrkbn]+)\/');		
		var match = re.exec(startingFen); 	
		if (match) 
			fenBlack = match[1].toUpperCase();
		
		re = new RegExp('\/([KQRBN]+)\\s');		
		match = re.exec(startingFen); 	
		if (match) 
			fenWhite = match[1];
		}

	PiecesToArray(globalWhitePieceArray, "white", true, fenWhite);
	PiecesToArray(globalBlackPieceArray, "black", false, fenBlack);
}

function ResetChessPieceCounters()
{
	for(var k=0; k<16; ++k)
	{
		globalWhitePieceArray[k].moveno = 0;
		globalWhitePieceArray[k].moves  = 0;
		globalBlackPieceArray[k].moveno = 0;
		globalBlackPieceArray[k].moves  = 0;
	}
}


//--------------------------------------------
//
// Chessboard state storage
//
//--------------------------------------------
// BoardPosition object definition
// Notice that board positions have a
// reference to a ChessPiece. 
//--------------------------------------------
function BoardPosition(fr)
{
	this.filerank = fr;        // fr = file and rank a1 through h8
	this.file = fr.charAt(0);  // file designator a - h
	this.rank = fr.charAt(1);  // rank designator 1 - 8
	this.ChessPc = null;       // Reference to a ChessPiece
}

//--------------------------------------------
// BoardPosition state storage
//
// The BoardPosition's live throughout the game
// they are assigned pieces as the game progresses.
// A BoardPosition with a null ChessPc is an empty position.
//
// globalChessBoardArray is an array of 8 arrays.
// globalChessBoardArray[1] is rank 1 Its array hold a1,b1,c1,d1,e1,f1,g1,h1
// globalChessBoardArray[2] is rank 2 Its array hold a2,b2,c2,d2,e2,f2,g2,h2
// ...
// globalChessBoardArray[8] is rank 8 Its array hold a8,b8,c8,d8,e8,f8,g8,h8
// 
// Each of the 8 arrays hold 8 BoardPosition objects.
// (defined above)
//--------------------------------------------
function InitializeChessBoard()
{
	var m;
	var i;
	
	globalChessBoardArray = new Array();
	for(m=1; m<9; ++m) globalChessBoardArray[m]  = new Array();
	for(m=1; m<9; ++m)
	{
		for(i=0; i<8; ++i)
		{
			globalChessBoardArray[m][i] = new BoardPosition("" + globalsFiles.charAt(i) + m);
		}
	}

}


//--------------------------------------------
// Place ChessPiece objects into their initial
// positions on the Chess Board.
//--------------------------------------------
function BoardToStartState()
{
	var cbp;
	var fr;
	var m;
	var i;
		
	// BoardPosition.ChessPc = null for globalsRanks 3 through 6.
	for(m=3; m<7; ++m)
	{
		for(i=0; i<8; ++i)
		{
			getBoardPosition("" + globalsFiles.charAt(i) + m).ChessPc = null;
		}
	}
	
	// Set BoardPosition.ChessPc = initial piece for Ranks 8,7,2 and 1.
	for(i=0; i<8; ++i)
	{
		// Black side of board
		getBoardPosition("" + globalsFiles.charAt(i) + 8).ChessPc = globalBlackPieceArray[i+8];
		getBoardPosition("" + globalsFiles.charAt(i) + 7).ChessPc = globalBlackPieceArray[i];
		
		// White side of board
		getBoardPosition("" + globalsFiles.charAt(i) + 2).ChessPc = globalWhitePieceArray[i];
		getBoardPosition("" + globalsFiles.charAt(i) + 1).ChessPc = globalWhitePieceArray[i+8];
	}
}


function getParams(obj)
{
	var x = 0;
	var z = 0;
	var s = "";
	for (var m in obj)
	{
		++x;
		++z;
		s += "" + z + " " + m + " = " + obj[m] + "\n";
		if(x == 20)
		{
			if(!confirm(s))
			{
				return;
			}
			s = "";
			x = 0;
		}
	}
	if(s != "")
	{
		alert(s);
	}
}

//----------------------------------------------------------
// Used to paint ChessPiece associated with each board position
// Now it only calls HighlightMovePositions, because that is
// the only thing I needed.
//----------------------------------------------------------
function RecordNormalMove()
{
	var move;
	if(!(globalOrgPos == null))
	{
		move = globalOrgPos.filerank + '-' + globalDstPos.filerank;

		if (!(globalPromotedRunk == null))
			{
			move = move.substring(0, move.length-1)+globalPromotedRunk;
			}
		
		AddToOutput(move+":");
	}
}


//--------------------------------------------
// Return a board position by file/rank.
// returns: BoardPosition object
//--------------------------------------------
function getBoardPosition(fr)
{
	var file = fr.charAt(0);
	var rank = fr.charAt(1);
	var k = globalsFiles.indexOf(file);

	if ("abcdefgh".indexOf(file) == -1 ||
			"0123456789".indexOf(rank) == -1)
		{
		throw "invalid board position " + fr;
		}

	return globalChessBoardArray[rank][k];
}

//--------------------------------------------
// Chess PGN moves are stored in globalPGN_MoveArray.
//
// This...
//
// 
// d4 cxd4 9. cxd4 Be7 10. Nc3 O-O 11. Bg5 Nbd7 12. Nd5 Bd8 13. dxe5 dxe5 14. Qb3
// Nc5 15. Nxf6+ Bxf6 16. Qxe6 Nxe6 17. Bxf6 gxf6 18. Rac1 Rac8 19. Red1 Nc5 20.
// Nd2 Nd3 21. Rxc8 Rxc8 22. Nb3 Nxb2 23. Rd7 Nc4 24. f3 Rb8 25. g4 a5 26. Kf2 Kg7
// 27. h4 Kf8 28. Kg3 b6 29. Rc7 Nd6 30. Rc6 Ke7 31. Rc7+ Kd8 32. Rc6 Kd7 33. Rc1
// a4 34. Nd2 Nb5 35. Rc4 Ra8 36. Rb4 Kc6 37. Rc4+ Kb7 38. Nf1 Nd4 39. Ne3 b5 40.
// Rb4 Rc8 41. Nf5 Ne2+ 42. Kf2 Rc2 43. Rxb5+ Ka6 44. Rb8 Rxa2 45. Rf8 Nf4+ 46.
// Kf1 a3 47. Ra8+ Kb6 48. Rb8+ Kc5 49. Rc8+ Kb4 50. Rb8+ Kc3 51. Rc8+ Kd2 52.
// Rd8+ Nd3 53. Ra8 Ra1+ 54. Kg2 a2 55. Ng3 Nb4 56. Nh5 Rc1 57. Nxf6 a1=Q 58. Rxa1
// Rxa1 59. Nxh7 Nd3 60. Ng5 Ne1+ 61. Kh2 f6 62. Nh7 Nxf3+ 63. Kg3 Ke3 64. Ng5
// fxg5 65. h5 Rg1+ 66. Kh3 Kf4 67. h6 Rg3# 0-1
//
// is converted to this...
//
// globalPGN_MoveArray[0]   = 1.e4
// globalPGN_MoveArray[1]   = c5
// globalPGN_MoveArray[2]   = 2.Nf3
// globalPGN_MoveArray[3]   = d6
// globalPGN_MoveArray[4]   = 3.Bb5+
// ...
// globalPGN_MoveArray[n-2] = 67.h6
// globalPGN_MoveArray[n-1] = Rg3#
// globalPGN_MoveArray[n]   = 0-1
//
// Note:
// White moves begin with 'movenumber.' and black moves do not.
// White moves are at indices where (index % 2 == 0)
// Black moves are at indices where (index % 2 != 0)
//--------------------------------------------

function parsePGNmovetext(movetext)
{
	if(movetext.length < 1)
		return;
	
	// Replace linefeeds with BRs
	
	// movetext = movetext.replace(/\n/g,'<br>');

	// Replace all tabs, linefeeds, and returns with spaces.
	
	// movetext = movetext.replace(/\s/g,' ');

	// Get rid of initial codes and WatchBot's times
	
	movetext = movetext.replace(/[[].*?]/g,'');
	
	// Get rid of { } left of WatchBot's times
	
	movetext = movetext.replace(/{\s*}/g,'');
	
	// Strip comments and variants away into mfoGameComments
	
	movetext = stripCommentsAndVariants(movetext);
	
	// Normalize successive spaces to single space.

	movetext = movetext.replace(/\s+/g,' ');
	
	// Remove spaces after periods.
	
	movetext = movetext.replace(/\. /g,'.');

	// Remove spaces BEFORE periods.

	movetext = movetext.replace(/\s\./g,'.');

	// Remove leading spaces

	movetext = movetext.replace(/^(\s*)(\b[\w\W]*)$/,'$2');

	// Split into moves array
	
	globalPGN_MoveArray = movetext.split(' ');

	// Store highest move number.

	var parts;
	for(i = globalPGN_MoveArray.length-1; i > -1; --i)
	{
		if(globalPGN_MoveArray[i].indexOf('.') > -1)
		{
			parts = globalPGN_MoveArray[i].split('.');
			globalPGN_MaxMvNum = parts[0];
			break;
		}
	}
}

function stripCommentsAndVariants(movetext)
{
	var re = new RegExp("[{($]");			// regular expression to match ( and { 
		
	if(movetext.length < 1)
		return movetext;

	mfoGameComments = '';
	mfoGameCommentsLastNumber = "impossibleInitValue";

	for (;;)
		{
		var match = re.exec(movetext);

		if (match == null) break;		// nothing (left)

		var strBrace = match[0];

		if (strBrace == '{')
			{
			movetext = stripComment(movetext, match.index );	
			}
		else if (strBrace == '(')
			{
			movetext = stripVariant(movetext, match.index);	
			}
		else if (strBrace == '$')
			{
			movetext = stripNag(movetext, match.index);	
			}
		else 
			{
			throw "Never happens";
			}
		}

	return movetext;
}

function stripVariant(movetext, iStart)
{
	var iEnd;
	var i, level = 1;

	// find variant's end (there may be variants inside)

	for (i = iStart+1, movetext.length-1; i < movetext.length; i++)
		{
		var c = movetext.charAt(i);

		if (c == '(') level ++;
		if (c == ')') level --;

		if (level == 0)
			{
			iEnd = i;

			break;
			}
		}

	if (level != 0)
		{
		throw "Missing closing )" 
		}

	return moveComment(movetext, iStart, iEnd);
}

function stripComment(movetext, iStart)
{
	var iEnd = movetext.substring(iStart, movetext.length).indexOf('}');

	if (iEnd == -1)
		{
		throw "Missing closing }" 
		}

	iEnd += iStart;

	return moveComment(movetext, iStart, iEnd);
}

function stripNag(movetext, iStart)
{
	var re = new RegExp("\\$\\d+");			// regular expression to match $NN
		
	var match = re.exec(movetext);

	if (match == null) 
		{
		throw "Lone dollar" 
		}

	if (match.index != iStart)
		{
		throw "Never happens";
		}

	// add nagInterpretation
	
	var strDollarNN = match[0];
	var strNN = strDollarNN.substring(1, strDollarNN.length);
	var iNN = parseInt(strNN);

	if (!isNaN(iNN) && iNN > 0 && iNN < nagInterpretation.length)
		{
		addCommentNumber(movetext, iStart);

		mfoGameComments += '$' + nagInterpretation[iNN] + '$';
		}

	return movetext.substring(0, iStart) + movetext.substring(match.lastIndex, movetext.length);
}

function moveComment(movetext, iStart, iEnd)
{
	addCommentNumber(movetext, iStart);
	
	mfoGameComments += movetext.substring(iStart+1, iEnd);

	return movetext.substring(0, iStart) + movetext.substring(iEnd+1, movetext.length);
}

function addCommentNumber(movetext, iStart)
{
	var strN = getLastMoveNumber(movetext.substring(0, iStart));

	if (strN != mfoGameCommentsLastNumber)
		{
		mfoGameCommentsLastNumber = strN;
			
		if (strN != '')
			{			
			mfoGameComments += '|m' + strN + '|';
			}
		}
	
	return;
}

function getLastMoveNumber(str) {
   if (!str) return '';
   
	var re = /\.\d+/;				// regular expression to match some digits preceeded by a dot.

	var m = re.exec(strReverse(str));
	
	if (m == null) return 0;

	var strDotAndNumber = m[0];
	var strNumber = strReverse(strDotAndNumber.substring(1, strDotAndNumber.length));

   return strNumber;
}

function strReverse(str) {
   if (!str) return '';
   var revstr='';
   for (i = str.length-1; i>=0; i--)
       revstr+=str.charAt(i)
   return revstr;
}

// Clean off non-move decoration (e.g. #,+,x etc)
function cleanMoveText(s)
{	
	var ns = "";
	for(var i=0; i<s.length; ++i)
	{
		if("abcdefgh0123456789-oOKQNBR".indexOf(s.charAt(i)) > -1)
			ns += s.charAt(i);
	}
	return ns;
}

// Return just the portion of a movetext token that 
// specifies the move e.g. 'b1', 'cb1', 'c2b1';
function getMoveTextOnly(s)
{
	var ns = "";
	for(var i=0; i<s.length; ++i)
	{
		if("abcdefgh0123456789".indexOf(s.charAt(i)) > -1)
			ns += s.charAt(i);
	}
	return ns;
}

// Return Q,K,N,R,B, or p
function getMovePieceType(s)
{
	var x = "QKNRB".indexOf(s.charAt(0));
	if(x > -1)
		return "QKNRB".charAt(x);
	else
		return 'p';
}

// Return an array of found pieces.
function findBoardPositionsWithPieces(pType, bColor)
{
	var matches = new Array();
	var BoardPos;
	var px = 0;
	
	// Sweep board looking for pieces of the specified 
	// type (B, N, R, K, Q, p) and color: true/false;
	for(m=1; m<9; ++m)
	{	
		for(i=0; i<8; ++i)
		{	
			BoardPos = getBoardPosition("" + globalsFiles.charAt(i) + m);
			if(!(BoardPos.ChessPc == null))
			{
				if(BoardPos.ChessPc.pcType == pType && BoardPos.ChessPc.color == bColor)
				{
					matches[px++] = BoardPos;
				}
			}
		}
	}
	return matches;
}

function isNumber(s)
{
	if(s == null)
		return false;
		
	if(s.length == 0)
		return false;
		
	for(var i=0; i<s.length; ++i)
	{
		if("0123456789".indexOf(s.charAt(i)) < 0)
		{
			return false;
		}
	}
	return true;
}

function RunGame(pgnGameText, startingFen)
{
	try {		
		InitializeChessSet(startingFen);
		InitializeChessBoard();
		ResetChessPieceCounters();
		BoardToStartState();
		globaliMoveIndex = 0;
		globaliMoveNumber = 0;
		parsePGNmovetext(pgnGameText);
		globalOrgPos = null;
		globalDstPos = null;
		InitOutput();

		fGameFinished = false;
		
		while (!fGameFinished)
			{
			fGameFinished = makeNextMove(true);
			}
		} 
	
	catch(er) 
		{
		AddToOutput(' Error - '+er);
		mfoGameError = er + ' on move ' + globaliMoveNumber;
		} 
}

// returns true if game is finished, false otherwise

function makeNextMove()
{
	var moveText = globalPGN_MoveArray[globaliMoveIndex];

	if(moveText.indexOf("0-1") > -1 || moveText.indexOf("0:1") > -1)
		{
		mfoGameResult = '-';		// White lost.
		AddToOutput("re-a1:");
		return true;
		}
	else if (moveText.indexOf("1-0") > -1 || moveText.indexOf("1:0") > -1)
		{
		mfoGameResult = '+';	// White won.
		AddToOutput("re-a1:");
		return true;
		}
	else if (moveText.indexOf("1/2-1/2") > -1)
		{
		mfoGameResult = '=';		// Draw
		AddToOutput("tt-a1:");
		return true;
		}
	else if (moveText.indexOf("*") > -1)
		{
		return true;
		}
	else if (moveText == "")
		{
		// this only happens by mistake (space after last move, plus no end), but it happens often
		return true;	
		}
	
	var chkstatus = (moveText.indexOf('+') > -1) ? "Check!" : (moveText.indexOf('#') > -1) ? "Checkmate!" : "";
	
	var fWhite2Move; // true = white, false = black

	if (moveText.indexOf('.') == -1)
		{
		fWhite2Move = false;		// only black moves has no number before them
		}
	else
		{
		// there is a number, it may be white or black
		
		var parts = moveText.split('.');

		if (parts.length == 2)
			{
			// exactly one dot - must be white move

			fWhite2Move = true;
			
			globaliMoveNumber = parts[0];
			}
		else
			{
			// more than one dot - must 18...Rxe2 notation

			fWhite2Move = false;
			}

		// forget the number, we have no more need of it.
		
		moveText = parts[parts.length-1];
		}

	
	var fullMoveText = moveText;
	moveText = cleanMoveText(moveText);
	
	// Handle castling
	if(moveText.indexOf("O-O-O") > -1 || moveText.indexOf("o-o-o") > -1 ||
							moveText.indexOf("0-0-0") > -1)
	{
		QueenSideCastle(fWhite2Move);
	}
	else if(moveText.indexOf("O-O") > -1 || moveText.indexOf("o-o") > -1 ||
							moveText.indexOf("0-0") > -1)
	{
		KingSideCastle(fWhite2Move);
	}
	else
	{
		globalPromotedRunk = null;			// no promotion, can be changed by MovePawn
		
		switch(getMovePieceType(moveText))
		{
		case 'Q':
			MoveDirectionalPiece(moveText, fWhite2Move, 'Q', "N,E,S,W,NW,NE,SE,SW");
			break;
		case 'K':
			MoveKing(moveText, fWhite2Move);
			break;
		case 'N':
			MoveKnight(moveText, fWhite2Move);
			break;
		case 'R':
			MoveDirectionalPiece(moveText, fWhite2Move, 'R', "N,E,S,W");
			break;
		case 'B':
			MoveDirectionalPiece(moveText, fWhite2Move, 'B', "NW,NE,SE,SW");
			break;
		case 'p':
			MovePawn(moveText, fWhite2Move);
			break;
		}

		RecordNormalMove();	
	}
	++globaliMoveIndex;
	
	if (globaliMoveIndex == globalPGN_MoveArray.length)
		{
		return true;
		}

	return false;
}

//--------------------------------------------------------
// Return the next file rank in the direction indicated
// Return "" if the next position is off of the board.
// fr = current file/rank (a1 - h8) 
// dir = 'N','NE','E','SE','S','SW','W','NW'
//--------------------------------------------------------
function getNextFileRank(dir, fr)
{
	var fx = globalsFiles.indexOf(fr.charAt(0));
	var rx = globalsRanks.indexOf(fr.charAt(1));
	
	if(dir == 'NW' || dir == 'N' || dir == 'NE')
		++rx;
	else if(dir == 'SW' || dir == 'S' || dir == 'SE')
		--rx;
		
	if(dir == 'NW' || dir == 'W' || dir == 'SW')
		--fx;
	else if(dir == 'NE' || dir == 'E' || dir == 'SE')
		++fx;
	
	return (rx < 0 || fx < 0 || rx > 7 || fx > 7) ? "" : globalsFiles.charAt(fx) + globalsRanks.charAt(rx);
}

function getNextBoardPosition(dir, fr)
{
	var fr = getNextFileRank(dir, fr);
	if(fr.length == 0)
		return null;
	else
		return getBoardPosition(fr);
}

//-------------------------------------
// Check if the piece is pinned
//-------------------------------------
function isPiecePinned(posPinned)
{
	var piecePinned = posPinned.ChessPc;
	var bColor = piecePinned.color;
	var filerankPinned = posPinned.filerank;
	var boardPosPinned = getBoardPosition(filerankPinned);
	var posArray = findBoardPositionsWithPieces('K', bColor);
	if(posArray.length == 0)
		return false; // Something is very wrong, there is no King.
		
	var KPos = posArray[0];
	var Kfr  = KPos.filerank;
	var tmpKfr;
	var dirs = "N,NE,E,SE,S,SW,W,NW".split(",");
	var dir;
	var blocker;
	var incheck = false;
	var testPiece;
	var testPos;
	var testType;
	var testType;

	// temporarily remove the piece and see if that results in check

	boardPosPinned.ChessPc = null;
	
	for(var i=0; i<dirs.length; ++i)
	{
		tmpKfr = Kfr;
		dir = dirs[i];

		blocker = false;
		
		while(!blocker && !incheck)
		{
			testPos = getNextBoardPosition(dir, tmpKfr);
			
			if(testPos == null)
				break;
			else
			{
				testPiece = testPos.ChessPc;
				if(!(testPiece == null))
				{
					blocker = (testPiece.color == bColor);
					if(!blocker)
					{
						testType = testPiece.pcType;
						
						if(dir == 'NE' || dir == 'SE' || dir == 'SW' || dir == 'NW')
							incheck = (testType == 'B' || testType == 'Q');
						else // dir = N,S,E, or W
							incheck = (testType == 'R' || testType == 'Q');
					}
				}
				
				tmpKfr = testPos.filerank;
			}
		}
		
		if(incheck)
			break;
	}

	// restore the removed piece

	boardPosPinned.ChessPc = piecePinned;

	
	return incheck;
}

function MoveKing(mvTxt, bPc)
{
	var posArray = findBoardPositionsWithPieces('K', bPc);
	var KingPos = posArray[0];
	var len = mvTxt.length;
	var fr = mvTxt.substring(len-2,len);
	MovePiece(KingPos, getBoardPosition(fr));
}

function MoveKnight(mvTxt, bPc)
{
	//--------------------------------------------------------
	// If bareMvTxt.length == 4 assume fully disambiguated.
	//--------------------------------------------------------
	var bareMvTxt = getMoveTextOnly(mvTxt);
	if(bareMvTxt.length == 4)
	{
		var orgFR = bareMvTxt.substring(0,2);
		var dstFR = bareMvTxt.substring(2,4);
		MovePiece(getBoardPosition(orgFR), getBoardPosition(dstFR));
		return;
	}

	var posArray = findBoardPositionsWithPieces('N', bPc);
	if(posArray.length == 0)
		return; // No knight to move something is very wrong.
		
	if(posArray.length > 2)
	{
		alert("Move too ambiguous, make move with mouse");
		return;
	}
		
	var len = mvTxt.length;
	var destFR = mvTxt.substring(len-2,len);
	var moves0;
	var moves1;
	
	if(posArray.length == 1)
	{
		MovePiece(posArray[0], getBoardPosition(destFR));
	}
	else
	{
		var p1 = false;
		var p2 = false;
		var i;
		
		// See if destFR is in the possible moves for knight at posArray[0]
		moves0 = getKnightMoves(posArray[0], bPc);
		for(i=0; i<moves0.length; ++i)
		{
			if(destFR == moves0[i])
			{	
				p1 = true;
				break;
			}
		}
		
		// See if destFR is in the possible moves for knight at posArray[1]
		moves1 = getKnightMoves(posArray[1], bPc);
		for(i=0; i<moves1.length; ++i)
		{
			if(destFR == moves1[i])
			{	
				p2 = true;
				break;
			}
		}
		
		if(p1 && !p2)
			MovePiece(posArray[0], getBoardPosition(destFR));
		else if(p2 && !p1)
			MovePiece(posArray[1], getBoardPosition(destFR));
		else if(p1 && p2)
		{
			//-------------------------------------------------
			// Both pieces possible moves contain destFR.
			// Determine is the move has been disambiguated.
			//-------------------------------------------------
			if(bareMvTxt.length == 3)
			{
				var F_R = bareMvTxt.charAt(0);
				if(globalsFiles.indexOf(F_R) > -1) // disambiguated by file
				{
					if(posArray[0].file == F_R)
						MovePiece(posArray[0], getBoardPosition(destFR));
					else if(posArray[1].file == F_R)
						MovePiece(posArray[1], getBoardPosition(destFR));
				}
				else if(globalsRanks.indexOf(F_R) > -1) // disambiguated by rank
				{
					if(posArray[0].rank == F_R)
						MovePiece(posArray[0], getBoardPosition(destFR));
					else if(posArray[1].rank == F_R)
						MovePiece(posArray[1], getBoardPosition(destFR));
				}
			}
			else if(bareMvTxt.length == 4) // fully disambiguated by file and rank
			{
				var orgFR = bareMvTxt.substring(0,2);
				MovePiece(getBoardPosition(orgFR), getBoardPosition(destFR));
			}	
			else // bareMvTxt.length = 2 movetext not disambiguated, find pinned Knight.
			{
				if(isPiecePinned(posArray[0]))
				{
					MovePiece(posArray[1], getBoardPosition(destFR));
				}
				else
				{
					MovePiece(posArray[0], getBoardPosition(destFR));
				}
			}
		}
	}
}

// Return Array of positions to move to
function getKnightMoves(objPos, bColor)
{
	var moves = "";
	var fr = objPos.filerank;
	var fx = globalsFiles.indexOf(fr.charAt(0));
	var rx = globalsRanks.indexOf(fr.charAt(1));
	
	if(fx + 2 < 8 && rx + 1 < 8)
		moves += (globalsFiles.charAt(fx + 2) + globalsRanks.charAt(rx + 1) + ",");
	if(fx + 2 < 8 && rx - 1 > -1)
		moves += (globalsFiles.charAt(fx + 2) + globalsRanks.charAt(rx - 1) + ",");
	if(fx - 2 > -1 && rx + 1 < 8)
		moves += (globalsFiles.charAt(fx - 2) + globalsRanks.charAt(rx + 1) + ",");
	if(fx - 2 > -1 && rx - 1 > -1)
		moves += (globalsFiles.charAt(fx - 2) + globalsRanks.charAt(rx - 1) + ",");
	if(fx + 1 < 8 && rx + 2 < 8)
		moves += (globalsFiles.charAt(fx + 1) + globalsRanks.charAt(rx + 2) + ",");
	if(fx + 1 < 8 && rx - 2 > -1)
		moves += (globalsFiles.charAt(fx + 1) + globalsRanks.charAt(rx - 2) + ",");
	if(fx - 1 > -1 && rx + 2 < 8)
		moves += (globalsFiles.charAt(fx - 1) + globalsRanks.charAt(rx + 2) + ",");
	if(fx - 1 > -1 && rx - 2 > -1)
		moves += (globalsFiles.charAt(fx - 1) + globalsRanks.charAt(rx - 2) + ",");
		
	var len = moves.length;
	moves = moves.substring(0,len-1);
	var movesArray = moves.split(','); 
	
	// Only return valid moves.
	var finalArray = new Array();
	var ix = 0;
	for(var i=0; i<movesArray.length; ++i)
	{
		if(getBoardPosition(movesArray[i]).ChessPc == null) 
		{
			// No piece on space is valid
			finalArray[ix++] = movesArray[i];
		}
		else
		{
			// Opposite color piece on space is valid.
			if(getBoardPosition(movesArray[i]).ChessPc.color != bColor)
				finalArray[ix++] = movesArray[i];
		}
	}
	return finalArray;
}

function MoveDirectionalPiece(mvTxt, bPc, pType, sDirs)
{
	//--------------------------------------------------------
	// If bareMvTxt.length == 4 assume fully disambiguated.
	//--------------------------------------------------------
	var bareMvTxt = getMoveTextOnly(mvTxt);
	if(bareMvTxt.length == 4)
	{
		var orgFR = bareMvTxt.substring(0,2);
		var dstFR = bareMvTxt.substring(2,4);
		MovePiece(getBoardPosition(orgFR), getBoardPosition(dstFR));
		return;
	}
	
	var posArray = findBoardPositionsWithPieces(pType, bPc);
	if(posArray.length == 0)
		{
		alert("No "+bPc+pType+" to move, must be an error");
		return; // No piece to move something is very wrong.
		}
		
	if(posArray.length > 2)
	{
		alert("Move too ambiguous, make move with mouse");
		return;
	}
	
	var len = mvTxt.length;
	var destFR = mvTxt.substring(len-2,len);
	var moves0;
	var moves1;
	
	if(posArray.length == 1)
	{
		MovePiece(posArray[0], getBoardPosition(destFR));
	}
	else
	{
		var p1 = false;
		var p2 = false;
		var i;
		
		// See if destFR is in the possible moves for knight at posArray[0]
		moves0 = getDirectionalMoves(posArray[0], bPc, sDirs);
		for(i=0; i<moves0.length; ++i)
		{
			if(destFR == moves0[i])
			{	
				p1 = true;
				break;
			}
		}
		
		// See if destFR is in the possible moves for knight at posArray[1]
		moves1 = getDirectionalMoves(posArray[1], bPc, sDirs);
		for(i=0; i<moves1.length; ++i)
		{
			if(destFR == moves1[i])
			{	
				p2 = true;
				break;
			}
		}
		
		if(p1 && !p2)
			MovePiece(posArray[0], getBoardPosition(destFR));
		else if(p2 && !p1)
			MovePiece(posArray[1], getBoardPosition(destFR));
		else if(p1 && p2)
		{
			//-------------------------------------------------
			// Both pieces possible moves contain destFR.
			// Determine is the move has been disambiguated.
			//-------------------------------------------------
			if(bareMvTxt.length == 3)
			{
				var F_R = bareMvTxt.charAt(0);
				if(globalsFiles.indexOf(F_R) > -1) // disambiguated by file
				{
					if(posArray[0].file == F_R)
						MovePiece(posArray[0], getBoardPosition(destFR));
					else if(posArray[1].file == F_R)
						MovePiece(posArray[1], getBoardPosition(destFR));
				}
				else if(globalsRanks.indexOf(F_R) > -1) // disambiguated by rank
				{
					if(posArray[0].rank == F_R)
						MovePiece(posArray[0], getBoardPosition(destFR));
					else if(posArray[1].rank == F_R)
						MovePiece(posArray[1], getBoardPosition(destFR));
				}
			}
			else if(bareMvTxt.length == 4) // fully disambiguated by file and rank
			{
				var orgFR = bareMvTxt.substring(0,2);
				MovePiece(getBoardPosition(orgFR), getBoardPosition(destFR));
			}	
			else // bareMvTxt.length = 2 movetext not disambiguated, find pinned Knight.
			{
				if(isPiecePinned(posArray[0]))
				{
					MovePiece(posArray[1], getBoardPosition(destFR));
				}
				else
				{
					MovePiece(posArray[0], getBoardPosition(destFR));
				}
			}
		}
	}
}

function getDirectionalMoves(objPos, bColor, sDirs)
{
	var moves = "";
	var Rfr = objPos.filerank;
	var tmpRfr;
	var dirs = sDirs.split(",");
	var foundEnd;
	var testPos;
	for(var i=0; i<dirs.length; ++i)
	{
		tmpRfr = Rfr;
		dir = dirs[i];
		foundEnd = false;
		
		while(!foundEnd)
		{
			testPos = getNextBoardPosition(dir, tmpRfr);
			if(testPos == null)
				break;
			else
			{
				if(testPos.ChessPc == null) // Empty position valid
				{
					moves += testPos.filerank + ",";	
				}
				else 
				{
					moves += (testPos.ChessPc.color == bColor) ? "" : testPos.filerank + ",";
					foundEnd = true;	
				}				
				tmpRfr = testPos.filerank;
			}
		}
	}
		
	var len = moves.length;
	moves = moves.substring(0,len-1);
	return moves.split(',');
}

function MovePawn(mvTxt, bPc)
{
	//--------------------------------------------------
	// A pawn can only get to a space by a very limited 
	// number of moves. 
	// 
	// For white:
	// If an enemy is at the dest position then the pawn
	// came from an adjacent file.
	// If no enemy is at the dest position then the pawn
	// came from the same file.
	// If dest position has rank 4 then the pawn either
	// came from rank 2 (if no piece is on rank 3) or 
	// from rank 3
	// 
	// For black:
	// Reflect the white algorithm.
	//--------------------------------------------------
	var bareMvTxt = getMoveTextOnly(mvTxt); // fr (non-attack move) or ffr (attack move).
	var len       = bareMvTxt.length;
	var destFR    = bareMvTxt.substring(len-2,len);
	var destfile  = destFR.charAt(0);
	var orgfile  = bareMvTxt.charAt(0);
	var destrank  = parseInt(destFR.charAt(1));
	var destPos = getBoardPosition(destFR);
	var testPos;

	if (destfile == orgfile) 
	{
		//--------------------------------------------
		// Non-attacking moves Pawn is moving from 
		// the same file.
		//--------------------------------------------
		
		if(bPc) // White moves.
		{
			if(destrank == 4)
			{
			
				testPos = getBoardPosition("" + destfile + '3');
				if(testPos.ChessPc == null)
				{
					// moving from rank 2
		
					testPos = getBoardPosition("" + destfile + '2');
					MovePiece(testPos, destPos);
				}
				else
				{
					// moving from rank 3
					MovePiece(testPos, destPos);
				}
			}
			else
			{
				// moving from rank-1
				var rm1 = destrank-1;
				testPos = getBoardPosition("" + destfile + rm1);
				MovePiece(testPos, destPos);
			}
		}
		else // Black moves.
		{
			if(destrank == 5)
			{
				testPos = getBoardPosition("" + destfile + '6');
				if(testPos.ChessPc == null)
				{
					// moving from rank 7
					testPos = getBoardPosition("" + destfile + '7');
					MovePiece(testPos, destPos);
				}
				else
				{
					// moving from rank 6
					MovePiece(testPos, destPos);
				}
			}
			else
			{
				// moving from rank+1
				var rp1 = destrank-0 + 1;
				testPos = getBoardPosition("" + destfile + rp1);
				MovePiece(testPos, destPos);
			}
		}
	}
	else  // Attack moves
	{
		// Pawn attacks are denoted as fxfn (changed to ffn by getMoveTextOnly()) or ffn
		// The first f is the originating file.
		// the second f is the destination file.

		// Sometimes fnxfn is possible as well, make ffn of it

		if (bareMvTxt.length == 4)
			{
			bareMvTxt = bareMvTxt.charAt(0) + bareMvTxt.substring(2,4);
			}
		
		var origfile  = (bareMvTxt.length == 3) ? bareMvTxt.charAt(0) : "";
		
		if(bPc) // White moves.
		{
			var rm1 = destrank-1;
			testPos = getBoardPosition("" + origfile + rm1);
			MovePiece(testPos, destPos);
		}
		else // Black moves.
		{
			var rp1 = destrank-0 + 1;
			testPos = getBoardPosition("" + origfile + rp1);
			MovePiece(testPos, destPos);
		}
	}
	
	var promotes = "QNBR";
	var promoinx = promotes.indexOf(mvTxt.charAt(mvTxt.length-1));
	var pt = "";
	if(bPc && destPos.rank == 8)
	{
		var pcIndex = destPos.ChessPc.index;
		
		// Promote white pawn.
		if(promoinx > -1)
		{
			pt = promotes.charAt(promoinx);

			globalPromotedRunk = pt;
				
			switch(pt)
			{
			case 'Q':
				destPos.ChessPc = new ChessPiece(true, "Q", "whitequeen.gif", pcIndex); 
				break;
			case 'N':
				destPos.ChessPc = new ChessPiece(true, "N", "whiteknight.gif", pcIndex);
				break;
			case 'B':
				destPos.ChessPc = new ChessPiece(true, "B", "whitebishop.gif", pcIndex);
				break;
			case 'R':
				destPos.ChessPc = new ChessPiece(true, "R", "whiterook.gif", pcIndex);
				break;
			}
		}
	}
	else if(!bPc && destPos.rank == 1)
	{
		var pcIndex = destPos.ChessPc.index;
		
		// Promote black pawn.
		if(promoinx > -1)
		{
			pt = promotes.charAt(promoinx)

			globalPromotedRunk = pt;
				
			switch(pt)
			{
			case 'Q':
				destPos.ChessPc = new ChessPiece(false, "Q", "blackqueen.gif", pcIndex); 
				break;
			case 'N':
				destPos.ChessPc = new ChessPiece(false, "N", "blackknight.gif", pcIndex);
				break;
			case 'B':
				destPos.ChessPc = new ChessPiece(false, "B", "blackbishop.gif", pcIndex);
				break;
			case 'R':
				destPos.ChessPc = new ChessPiece(false, "R", "blackrook.gif", pcIndex);
				break;
			}
		}
	}
}

function QueenSideCastle(bc)
{
	var rank = (bc)?'1':'8';
	var initialPositions = globalCastles[bc];
	
	moveCastlingPieces(initialPositions.substr(1,1) + rank, 'c' + rank,
							initialPositions.substr(0,1) + rank, 'd' + rank);

	AddToOutput('oo-c'+rank+":");
}

function KingSideCastle(bc)
{
	var rank = (bc)?'1':'8';
	var initialPositions = globalCastles[bc];

	moveCastlingPieces(initialPositions.substr(1,1) + rank, 'g' + rank,
							initialPositions.substr(2,1) + rank, 'f' + rank);
	
	AddToOutput('oo-g'+rank+":");
}

function moveCastlingPieces(king_from, king_to, rook_from, rook_to)
{
	var bp_king_from = getBoardPosition(king_from);
	var bp_king_to = getBoardPosition(king_to);
	var bp_rook_from = getBoardPosition(rook_from);
	var bp_rook_to = getBoardPosition(rook_to);

	var king = bp_king_from.ChessPc;
	var rook = bp_rook_from.ChessPc;

	bp_king_from.ChessPc = null;
	bp_rook_from.ChessPc = null;
	
	bp_king_to.ChessPc = king;
	bp_rook_to.ChessPc = rook;
}


function MovePiece(orgPos, destPos)
{
	globalOrgPos = orgPos;
	globalDstPos = destPos;

	var bColor = orgPos.ChessPc.color;
	var file   = destPos.file;
	var rank   = parseInt(destPos.rank);

	if(destPos.ChessPc == null && orgPos.ChessPc.pcType == 'p') // Test for en passant captures
	{
		if(bColor && rank == 6) // White
		{
			// If space at destPos.file/destPos.rank-1 contains a black pawn, with moveno = globaliMoveIndex-1.
			// it has been captured en-passant.
			var rm1 = rank-1;
			var testPos = getBoardPosition("" + file + rm1);
			if(!(testPos.ChessPc == null))
				if(!testPos.ChessPc.color && testPos.ChessPc.pcType == 'p')
					if(testPos.ChessPc.moveno == globaliMoveIndex-1)
						{
						testPos.ChessPc = null;
						}
		}
		else if(!bColor && rank == 3)
		{
			// If space at destPos.file/destPos.rank+1 contains a white pawn, with moveno = globaliMoveIndex-1.
			// it has been captured en-passant.
			var rp1 = rank+1;
			var testPos = getBoardPosition("" + file + rp1);
			if(!(testPos.ChessPc == null))
				if(testPos.ChessPc.color && testPos.ChessPc.pcType == 'p')
					if(testPos.ChessPc.moveno == globaliMoveIndex-1)
						{
						testPos.ChessPc = null;
						}
		}
	}
	destPos.ChessPc = orgPos.ChessPc;
	++destPos.ChessPc.moves;
	destPos.ChessPc.moveno = globaliMoveIndex;
	orgPos.ChessPc = null;
}

function InitOutput()
{
	mfoGameText = '';
	mfoGameResult = 'U';	
	mfoGameError = '';
}

function AddToOutput(str)
{
	mfoGameText += str;
}

function extractTagFromPGN(strPgn, strTag)
{

var sRegExpression = '\\[' + 			// opening [
						strTag + 		// tag 
						'(.+?)' +		// tag value, braced for extraction
						'\\]';				//  closing ]

var re = new RegExp(sRegExpression); 
var match = re.exec(strPgn);
var strValue;

if (!match) { return ''; }

strValue = match[1];

strValue = strValue.replace(/^\s*|\s*$/g,'');	// strip leading and trailing whitespace
strValue = strValue.replace(/^"|"$/g,'');		// strip leading and trailing quotes

return strValue;
}

function Pgn2MfoTranslate(pgnGameText) {
	var game = new Object();
	var event = extractTagFromPGN(pgnGameText, 'Event');
	var round = extractTagFromPGN(pgnGameText, 'Round');

	game.white = extractTagFromPGN(pgnGameText, 'White');
	game.black = extractTagFromPGN(pgnGameText, 'Black');
	game.startingFen = extractTagFromPGN(pgnGameText, 'FEN');

	game.commentsBlack = '';

	if (event != '')
		{
		game.commentsBlack += event + '. '
		}

	if (round != '')
		{
		game.commentsBlack += 'Round ' + round + '. '
		}

	RunGame(pgnGameText, game.startingFen);

	game.moves = mfoGameText;
	game.commentsBlack += mfoGameComments;
	game.commentsWhite = '';
	game.result = mfoGameResult;
	game.error = mfoGameError;
	
	if (mfoGameError == '')
		{
		game.fen = generateFen();

		if (mfoGameResult == 'U')
			{
			tmp = Math.floor(mfoGameText.length / 6);

			if (tmp%2)
				{
				// odd number of half-moves

				game.result = 'B';
				}
			else
				{
				//even number of half-moves
				
				game.result = 'W';
				}
			}
		}
	else
		{
		game.fen = '';
		}

	return game;
}


function generateFen() {
	
	var i, piece, color;
	var mfoFen = '';

	if (globaliMoveIndex == 0) return '';

	for (r = 1; r < 9; r++) 
		{
		if (r > 1) mfoFen += '/';
		
		for (f = 1; f < 9; f++) 
			{		
			BoardPos = globalChessBoardArray[9-r][f-1];
			objPiece = BoardPos.ChessPc
			
			if (objPiece == null)
				{
				piece = 'x';
				}
			else
				{
				piece = objPiece.pcType;

				if (objPiece.color)
					{
					piece = piece.toUpperCase();		// whites are Caps
					}
				else
					{
					piece = piece.toLowerCase();		// blacks are not
					}
				}

			mfoFen += piece;
			}
		}

	// whose move is next

	if (globaliMoveIndex % 2 == 0)
		{
		mfoFen += " w";
		}
	else
		{
		mfoFen += ' b';
		}

	mfoFen = mfoFen.replace(/xxxxxxxx/g,'8');
	mfoFen = mfoFen.replace(/xxxxxxx/g,'7');
	mfoFen = mfoFen.replace(/xxxxxx/g,'6');
	mfoFen = mfoFen.replace(/xxxxx/g,'5');
	mfoFen = mfoFen.replace(/xxxx/g,'4');
	mfoFen = mfoFen.replace(/xxx/g,'3');
	mfoFen = mfoFen.replace(/xx/g,'2');
	mfoFen = mfoFen.replace(/x/g,'1');
	
	return mfoFen;
}

var nagInterpretation = new Array(
'null annotation',
'good move (traditional "!")',
'poor move (traditional "?")',
'very good move (traditional "!!")',
'very poor move (traditional "??")',
'speculative move (traditional "!?")',
'questionable move (traditional "?!")',
'forced move (all others lose quickly)',
'singular move (no reasonable alternatives)',
'worst move',
'drawish position',
'equal chances, quiet position',
'equal chances, active position',
'unclear position',
'White has a slight advantage',
'Black has a slight advantage',
'White has a moderate advantage',
'Black has a moderate advantage',
'White has a decisive advantage',
'Black has a decisive advantage',
'White has a crushing advantage (Black should resign)',
'Black has a crushing advantage (White should resign)',
'White is in zugzwang',
'Black is in zugzwang',
'White has a slight space advantage',
'Black has a slight space advantage',
'White has a moderate space advantage',
'Black has a moderate space advantage',
'White has a decisive space advantage',
'Black has a decisive space advantage',
'White has a slight time (development) advantage',
'Black has a slight time (development) advantage',
'White has a moderate time (development) advantage',
'Black has a moderate time (development) advantage',
'White has a decisive time (development) advantage',
'Black has a decisive time (development) advantage',
'White has the initiative',
'Black has the initiative',
'White has a lasting initiative',
'Black has a lasting initiative',
'White has the attack',
'Black has the attack',
'White has insufficient compensation for material deficit',
'Black has insufficient compensation for material deficit',
'White has sufficient compensation for material deficit',
'Black has sufficient compensation for material deficit',
'White has more than adequate compensation for material deficit',
'Black has more than adequate compensation for material deficit',
'White has a slight center control advantage',
'Black has a slight center control advantage',
'White has a moderate center control advantage',
'Black has a moderate center control advantage',
'White has a decisive center control advantage',
'Black has a decisive center control advantage',
'White has a slight kingside control advantage',
'Black has a slight kingside control advantage',
'White has a moderate kingside control advantage',
'Black has a moderate kingside control advantage',
'White has a decisive kingside control advantage',
'Black has a decisive kingside control advantage',
'White has a slight queenside control advantage',
'Black has a slight queenside control advantage',
'White has a moderate queenside control advantage',
'Black has a moderate queenside control advantage',
'White has a decisive queenside control advantage',
'Black has a decisive queenside control advantage',
'White has a vulnerable first rank',
'Black has a vulnerable first rank',
'White has a well protected first rank',
'Black has a well protected first rank',
'White has a poorly protected king',
'Black has a poorly protected king',
'White has a well protected king',
'Black has a well protected king',
'White has a poorly placed king',
'Black has a poorly placed king',
'White has a well placed king',
'Black has a well placed king',
'White has a very weak pawn structure',
'Black has a very weak pawn structure',
'White has a moderately weak pawn structure',
'Black has a moderately weak pawn structure',
'White has a moderately strong pawn structure',
'Black has a moderately strong pawn structure',
'White has a very strong pawn structure',
'Black has a very strong pawn structure',
'White has poor knight placement',
'Black has poor knight placement',
'White has good knight placement',
'Black has good knight placement',
'White has poor bishop placement',
'Black has poor bishop placement',
'White has good bishop placement',
'Black has good bishop placement',
'White has poor rook placement',
'Black has poor rook placement',
'White has good rook placement',
'Black has good rook placement',
'White has poor queen placement',
'Black has poor queen placement',
'White has good queen placement',
'Black has good queen placement',
'White has poor piece coordination',
'Black has poor piece coordination',
'White has good piece coordination',
'Black has good piece coordination',
'White has played the opening very poorly',
'Black has played the opening very poorly',
'White has played the opening poorly',
'Black has played the opening poorly',
'White has played the opening well',
'Black has played the opening well',
'White has played the opening very well',
'Black has played the opening very well',
'White has played the middlegame very poorly',
'Black has played the middlegame very poorly',
'White has played the middlegame poorly',
'Black has played the middlegame poorly',
'White has played the middlegame well',
'Black has played the middlegame well',
'White has played the middlegame very well',
'Black has played the middlegame very well',
'White has played the ending very poorly',
'Black has played the ending very poorly',
'White has played the ending poorly',
'Black has played the ending poorly',
'White has played the ending well',
'Black has played the ending well',
'White has played the ending very well',
'Black has played the ending very well',
'White has slight counterplay',
'Black has slight counterplay',
'White has moderate counterplay',
'Black has moderate counterplay',
'White has decisive counterplay',
'Black has decisive counterplay',
'White has moderate time control pressure',
'Black has moderate time control pressure',
'White has severe time control pressure',
'Black has severe time control pressure');
