﻿//
// Javascript PGN Chess library (c) 2007 by Chris Idzerda, chris@vertigo.com
// Version 3.0

///////////////////////////////////////////////////////////////////////////////
// Chess

var Chess= {};

///////////////////////////////////////////////////////////////////////////////
// Chess.Board

Chess.Board= function(pieces)
{
	this._board= [ [], [], [], [], [], [], [], [] ];
	this._pieces= [];
	
	// Place the pieces on the board.
	for(var id in pieces)
	{
		var piece= pieces[id];
		this._pieces.push(piece);
		piece.placeOn(this, this._placePieceAt, this._removePieceAt);
	}
}

Chess.Board.prototype.getPieceAt= function(position)
{
	if(position.row < 0 || position.row >= 8 || position.column < 0 || position.column >= 8)
		return null;
	return this._board[position.row][position.column];
}

Chess.Board.prototype.getState= function()
{
	var s= "";
	for(var i= 0; i < this._pieces.length; ++i)
		s += this._pieces[i];
	return s;
}

// private methods

Chess.Board.prototype._placePieceAt= function(piece, position)
{
	var replacedPiece= this.getPieceAt(position);
	if(replacedPiece != null)
		replacedPiece.onremoved();
	this._board[position.row][position.column]= piece;
	return replacedPiece;
}

Chess.Board.prototype._removePieceAt= function(position)
{
	this._board[position.row][position.column]= null;
}

///////////////////////////////////////////////////////////////////////////////
// Chess.Effect

Chess.Effect= function(id, from, to, glyph)
{
	this.id= id;
	this.from= from;
	this.to= to;
	this.glyph= glyph;
}

///////////////////////////////////////////////////////////////////////////////
// Chess.Navigator

Chess.Navigator= function(states)
{
	this._states= states;
	this._plyIndex= 0;
	this._last= states.length - 1;
}

Chess.Navigator.prototype.back= function()
{
	return this.setCurrentPly(this._plyIndex - 1);
}

Chess.Navigator.prototype.begin= function()
{
	return this.setCurrentPly(0);
}

Chess.Navigator.prototype.end= function()
{
	return this.setCurrentPly(this._last);
}

Chess.Navigator.prototype.getPlyLabels= function()
{
	var labels= new Array(this._states.length);
	for(var i= 0; i < this._states.length; ++i)
		labels[i]= this._states[i].getPgnText();
	return labels;
}

Chess.Navigator.prototype.isAtFinish= function()
{
	return this._plyIndex == this._last;
}

Chess.Navigator.prototype.isAtStart= function()
{
	return this._plyIndex == 0;
}

Chess.Navigator.prototype.next= function()
{
	return this.setCurrentPly(this._plyIndex + 1);
}

Chess.Navigator.prototype.setCurrentPly= function(plyIndex)
{
	plyIndex= Math.max(0, Math.min(plyIndex, this._last));
	if(this._plyIndex == plyIndex)
		return null;
	var previousState= this._states[this._plyIndex];
	this._plyIndex= plyIndex;
	var currentState= this._states[this._plyIndex];
	return this._createMoveList(previousState, currentState);
}

// private methods

Chess.Navigator.prototype._createMoveList= function(previousState, currentState)
{
	var list= [];
	for(var i= 0; i < Chess.Parser.State.PartCount; ++i)
	{
		var p= previousState.getPart(i);
		var c= currentState.getPart(i);
		if(p != c)
		{
			var id= p.substr(0, 3);
			var from= new Chess.Position(parseInt(p.charAt(4)), parseInt(p.charAt(5)));
			var to= new Chess.Position(parseInt(c.charAt(4)), parseInt(c.charAt(5)));
			var glyph= p.charAt(3) != c.charAt(3) ? c.charAt(3) : null;
			list.push(new Chess.Effect(id, from, to, glyph));
		}
	}
	list.annotation= currentState.annotation;
	list.checkMode= currentState.checkMode;
	list.comments= currentState.getComments();
	list.pgnText= currentState.getPgnText();
	list.capturedPiece= currentState.capturedPiece;
	list.isEnPassant= currentState.isEnPassant;
	list.plyIndex= this._plyIndex;
	return list;
}

///////////////////////////////////////////////////////////////////////////////
// Chess.Parser

Chess.Parser= function(pgnText)
{
	this.setText(pgnText);
}

Chess.Parser.prototype.getResult= function()
{
	return this._result;
}

Chess.Parser.prototype.getText= function()
{
	var m= Chess.Parser._textRe.exec(this._pgnText);
	if(m != null && m.index != 0)
		this._updateText(m.index);
	return this._pgnText;
}

Chess.Parser.prototype.parse= function(pgnText)
{
	if(arguments.length > 0)
		this.setText(pgnText);
	this._reset();
	this._extractTags();
	this._extractMoves();
	if(this._result != null)
		return new Chess.Navigator(this._states);
	else
		return null;
}

Chess.Parser.prototype.setText= function(pgnText)
{
	this._pgnText= pgnText;
}

// private methods and static fields

Chess.Parser._annotations= { "": 0, "!": 1, "?": 2, "!!": 3, "??": 4, "!?": 5, "?!": 6 };
Chess.Parser._tagRe= /\[\s*(\w+)\s*"((?:[^"]|(?:\\.))*)"\]/m;
Chess.Parser._textRe= /\S/m;
Chess.Parser._r1= /(\d+)\.((?:\.+)?)/m;
Chess.Parser._r2= /([BKNPQR]?)([a-h]?[1-8]?)(x?)([a-h][1-8])((?:=[BNQR])?)([+#]?)([?!]{0,2})/m;
Chess.Parser._r3= /O-O((?:-O)?)([+#]?)([?!]{0,2})/m;
Chess.Parser._r4= /\{\s*([^}]*?)\s*\}/m;
Chess.Parser._r5= /;\s*(.*)/m;
Chess.Parser._r6= /((?:1-0)|(?:0-1)|(?:1\/2-1\/2)|(?:\*))/m;

Chess.Parser.prototype._addCastle= function(queenSide, check, annotation)
{
	var isKingSide= queenSide == "";
	var checkMode= check == "#" ? 2 : check == "+" ? 1 : 0;
	var annotationIndex= Chess.Parser._annotations[annotation];
	var side= this._isBlackMove() ? "B" : "W";
	var rook= this._pieces[side + (isKingSide ? "K" : "Q") + "R"];
	var king= this._pieces[side + "KK"];
	var row= this._isBlackMove() ? 0 : 7;
	var position= new Chess.Position(row, isKingSide ? 5 : 3);
	rook.moveTo(position);
	position= new Chess.Position(row, isKingSide ? 6 : 2);
	king.moveTo(position);
	var s= isKingSide ? "O-O" : "O-O-O";
	this._states.push(new Chess.Parser.State(this._board.getState(), checkMode, annotationIndex, s));
}

Chess.Parser.prototype._addComment= function(text)
{
	text= text.replace(/^\s+/, "").replace(/\s+$/, "");
	this._states[this._states.length - 1].addComment(text);
}

Chess.Parser.prototype._addMove= function(piece, from, capture, to, promotion, check, annotation)
{
	var s= piece + from + capture + to + promotion + check;
	var checkMode= check == "#" ? 2 : check == "+" ? 1 : 0;
	var annotationIndex= Chess.Parser._annotations[annotation];
	var promotionType= Chess.Piece.Types[promotion.charAt(1)];
	var side= this._isBlackMove() ? "B" : "W";
	var position= Chess.Position.fromAlgebraicPosition(to);
	if(from.length < 2)
		piece= this._findPiece(from, position, piece != "" ? piece : "P");
	else
		piece= this._board.getPieceAt(Chess.Position.fromAlgebraicPosition(from));
	var capturedPiece= piece.moveTo(position);
	if(capture != "" && capturedPiece == null)
	{
		var enPassant= new Chess.Position(position.row + (this._isBlackMove() ? -1 : 1), position.column);
		capturedPiece= this._board.getPieceAt(enPassant);
		capturedPiece.remove();
	}
	if(promotionType != null)
	{
		var collectionName= "_" + promotionType.name.toLowerCase() + "s";
		var collection= this[collectionName][this._isBlackMove()];
		piece.promoteTo(promotionType, collection);
	}
	this._states.push(new Chess.Parser.State(this._board.getState(), checkMode, annotationIndex, s, capturedPiece, enPassant != null));
}

Chess.Parser.prototype._checkPly= function(moveNumber, blackIndicator)
{
	var plyIndex= 2 * parseInt(moveNumber, 10);
	if(blackIndicator == "")
		--plyIndex;
	return this._states.length == plyIndex;
}

Chess.Parser.prototype._extractMoves= function()
{
	do
	{
		var m, s= this.getText();
		if(m= Chess.Parser._r1.exec(s), m != null && m.index == 0)
			this._checkPly(m[1], m[2]);
		else if(m= Chess.Parser._r2.exec(s), m != null && m.index == 0)
			this._addMove(m[1], m[2], m[3], m[4], m[5], m[6], m[7]);
		else if(m= Chess.Parser._r3.exec(s), m != null && m.index == 0)
			this._addCastle(m[1], m[2], m[3]);
		else if(m= Chess.Parser._r4.exec(s), m != null && m.index == 0)
			this._addComment(m[1]);
		else if(m= Chess.Parser._r5.exec(s), m != null && m.index == 0)
			this._addComment(m[1]);
		else if(m= Chess.Parser._r6.exec(s), m != null && m.index == 0)
			this._setResult(m[1]);
		else
			break;
		this._updateText(m.index + m[0].length);
	}
	while(this._result == null);
}

Chess.Parser.prototype._extractTags= function()
{
	var m;
	while(m= Chess.Parser._tagRe.exec(this.getText()), m != null && m.index == 0)
	{
		this._tags[m[1]]= m[2];
		this._updateText(m.index + m[0].length);
	}
}

Chess.Parser.prototype._findPawn= function(from, to)
{
	var m= this._isBlackMove() ? -1 : 1;
	if(from != "")
	{
		from= Chess.Position.fromAlgebraicPosition(from + (8 - to.row - m));
		return this._board.getPieceAt(from);
	}
	from= new Chess.Position(to.row + m, to.column);
	var pawn= this._board.getPieceAt(from);
	if(pawn != null)
		return pawn;
	from= new Chess.Position(to.row + 2 * m, to.column);
	return this._board.getPieceAt(from);
}

Chess.Parser.prototype._findPiece= function(from, to, pieceGlyph)
{
	switch(pieceGlyph)
	{
	case "P":
		return this._findPawn(from, to);
	case "R":
		return this._findPieceInCollection(from, to, this._rooks);
	case "N":
		return this._findPieceInCollection(from, to, this._knights);
	case "B":
		return this._findPieceInCollection(from, to, this._bishops);
	case "Q":
		return this._findPieceInCollection(from, to, this._queens);
	case "K":
		return this._pieces[(this._isBlackMove() ? "B" : "W") + "KK"];
	}
}

Chess.Parser.prototype._findPieceInCollection= function(from, to, collection)
{
	var fromRow= 8 - parseInt(from, 10), isRowKnown= !isNaN(fromRow);
	var fromColumn= from.charCodeAt(0) - 97, isColumnKnown= !isNaN(fromColumn);
	var collection= collection[this._isBlackMove()];
	for(var id in collection)
	{
		var piece= collection[id];
		if(piece.canMoveTo(to) && (isRowKnown ? piece.isInRow(fromRow) : isColumnKnown ? piece.isInColumn(fromColumn) : true))
			return piece;
	}
}

Chess.Parser.prototype._isBlackMove= function()
{
	return this._states.length % 2 == 0;
}

Chess.Parser.prototype._reset= function()
{
	this._pieces= {
		BQR: new Chess.Piece("BQR", Chess.Piece.Rook, new Chess.Position(0, 0)),
		BQN: new Chess.Piece("BQN", Chess.Piece.Knight, new Chess.Position(0, 1)),
		BQB: new Chess.Piece("BQB", Chess.Piece.Bishop, new Chess.Position(0, 2)),
		BQQ: new Chess.Piece("BQQ", Chess.Piece.Queen, new Chess.Position(0, 3)),
		BKK: new Chess.Piece("BKK", Chess.Piece.King, new Chess.Position(0, 4)),
		BKB: new Chess.Piece("BKB", Chess.Piece.Bishop, new Chess.Position(0, 5)),
		BKN: new Chess.Piece("BKN", Chess.Piece.Knight, new Chess.Position(0, 6)),
		BKR: new Chess.Piece("BKR", Chess.Piece.Rook, new Chess.Position(0, 7)),
		BP1: new Chess.Piece("BP1", Chess.Piece.Pawn, new Chess.Position(1, 0)),
		BP2: new Chess.Piece("BP2", Chess.Piece.Pawn, new Chess.Position(1, 1)),
		BP3: new Chess.Piece("BP3", Chess.Piece.Pawn, new Chess.Position(1, 2)),
		BP4: new Chess.Piece("BP4", Chess.Piece.Pawn, new Chess.Position(1, 3)),
		BP5: new Chess.Piece("BP5", Chess.Piece.Pawn, new Chess.Position(1, 4)),
		BP6: new Chess.Piece("BP6", Chess.Piece.Pawn, new Chess.Position(1, 5)),
		BP7: new Chess.Piece("BP7", Chess.Piece.Pawn, new Chess.Position(1, 6)),
		BP8: new Chess.Piece("BP8", Chess.Piece.Pawn, new Chess.Position(1, 7)),
		WP1: new Chess.Piece("WP1", Chess.Piece.Pawn, new Chess.Position(6, 0)),
		WP2: new Chess.Piece("WP2", Chess.Piece.Pawn, new Chess.Position(6, 1)),
		WP3: new Chess.Piece("WP3", Chess.Piece.Pawn, new Chess.Position(6, 2)),
		WP4: new Chess.Piece("WP4", Chess.Piece.Pawn, new Chess.Position(6, 3)),
		WP5: new Chess.Piece("WP5", Chess.Piece.Pawn, new Chess.Position(6, 4)),
		WP6: new Chess.Piece("WP6", Chess.Piece.Pawn, new Chess.Position(6, 5)),
		WP7: new Chess.Piece("WP7", Chess.Piece.Pawn, new Chess.Position(6, 6)),
		WP8: new Chess.Piece("WP8", Chess.Piece.Pawn, new Chess.Position(6, 7)),
		WQR: new Chess.Piece("WQR", Chess.Piece.Rook, new Chess.Position(7, 0)),
		WQN: new Chess.Piece("WQN", Chess.Piece.Knight, new Chess.Position(7, 1)),
		WQB: new Chess.Piece("WQB", Chess.Piece.Bishop, new Chess.Position(7, 2)),
		WQQ: new Chess.Piece("WQQ", Chess.Piece.Queen, new Chess.Position(7, 3)),
		WKK: new Chess.Piece("WKK", Chess.Piece.King, new Chess.Position(7, 4)),
		WKB: new Chess.Piece("WKB", Chess.Piece.Bishop, new Chess.Position(7, 5)),
		WKN: new Chess.Piece("WKN", Chess.Piece.Knight, new Chess.Position(7, 6)),
		WKR: new Chess.Piece("WKR", Chess.Piece.Rook, new Chess.Position(7, 7)) };
	this._board= new Chess.Board(this._pieces);
	this._rooks= {
		"true": { BQR: this._pieces.BQR, BKR : this._pieces.BKR },
		"false": { WQR: this._pieces.WQR, WKR : this._pieces.WKR } };
	this._knights= {
		"true": { BQN: this._pieces.BQN, BKN : this._pieces.BKN },
		"false": { WQN: this._pieces.WQN, WKN : this._pieces.WKN } };
	this._bishops= {
		"true": { BQB: this._pieces.BQB, BKB : this._pieces.BKB },
		"false": { WQB: this._pieces.WQB, WKB : this._pieces.WKB } };
	this._queens= { "true": { BQQ: this._pieces.BQQ }, "false": { WQQ: this._pieces.WQQ } };
	this._tags= {};
	this._result= null;
	this._states= [ new Chess.Parser.State(this._board.getState()) ];
}

Chess.Parser.prototype._setResult= function(result)
{
	this._result= result;
}

Chess.Parser.prototype._updateText= function(index)
{
	this._pgnText= this._pgnText.substr(index);
}

///////////////////////////////////////////////////////////////////////////////
// Chess.Parser.State

Chess.Parser.State= function(boardState, checkMode, annotationIndex, pgnText, capturedPiece, isEnPassant)
{
	this._boardState= boardState;
	this._comments= [];
	this.checkMode= isNaN(checkMode) ? 0 : checkMode;
	this.annotation= isNaN(annotationIndex) ? 0 : annotationIndex;
	this.capturedPiece= capturedPiece;
	this.isEnPassant= isEnPassant;
	this.getPgnText= function() { return pgnText == null ? "" : pgnText; };
}

Chess.Parser.State.PartCount= 32;

Chess.Parser.State.prototype.addComment= function(text)
{
	this._comments.push(text);
}

Chess.Parser.State.prototype.getComments= function()
{
	return this._comments;
}

Chess.Parser.State.prototype.getPart= function(index)
{
	return this._boardState.substr(index * 6, 6);
}

///////////////////////////////////////////////////////////////////////////////
// Chess.Piece

Chess.Piece= function(id, type, position)
{
	this._id= id;
	this._type= type;
	this._position= position;
}

// piece types
Chess.Piece.Pawn= { name: "Pawn", glyph: "P" };
Chess.Piece.Rook= { name: "Rook", glyph: "R", canMoveTo: function(position) {
		var rowIncrement, columnIncrement;
		if(this.isInRow(position.row))
		{
			// Look for intervening pieces in this row.
			rowIncrement= 0;
			columnIncrement= this._position.column < position.column ? 1 : -1;
		}
		else if(this.isInColumn(position.column))
		{
			// Look for intervening pieces in this column.
			rowIncrement= this._position.row < position.row ? 1 : -1;
			columnIncrement= 0;
		}
		else
			return false;
		for(var p= new Chess.Position(this._position.row + rowIncrement, this._position.column + columnIncrement);
			p.row != position.row || p.column != position.column;
			p.row += rowIncrement, p.column += columnIncrement)
		{
			if(this._board.getPieceAt(p) != null)
				return false;
		}
		return true;
	} };
Chess.Piece.Knight= { name: "Knight", glyph: "N", canMoveTo: function(position) {
		return this._getDistanceTo(position) == 5;
	} };
Chess.Piece.Bishop= { name: "Bishop", glyph: "B", canMoveTo: function(position) {
		if(Math.abs(this._position.row - position.row) != Math.abs(this._position.column - position.column))
			return false;
		
		// Look for intervening pieces in this diagonal.
		var rowIncrement= this._position.row < position.row ? 1 : -1;
		var columnIncrement= this._position.column < position.column ? 1 : -1;
		for(var p= new Chess.Position(this._position.row + rowIncrement, this._position.column + columnIncrement);
			p.row != position.row || p.column != position.column;
			p.row += rowIncrement, p.column += columnIncrement)
		{
			if(this._board.getPieceAt(p) != null)
				return false;
		}
		return true;
	} };
Chess.Piece.Queen= { name: "Queen", glyph: "Q", canMoveTo: function(position) {
		return Chess.Piece.Rook.canMoveTo.call(this, position) || Chess.Piece.Bishop.canMoveTo.call(this, position);
	} };
Chess.Piece.King= { name: "King", glyph: "K" };
Chess.Piece.Types= {
	P: Chess.Piece.Pawn, R: Chess.Piece.Rook, N: Chess.Piece.Knight,
	B: Chess.Piece.Bishop, Q: Chess.Piece.Queen, K: Chess.Piece.King };

Chess.Piece.prototype.canMoveTo= function(position)
{
	return this._type.canMoveTo.call(this, position);
}

Chess.Piece.prototype.getName= function()
{
	return this._type.name;
}

Chess.Piece.prototype.isInColumn= function(column)
{
	return this._position.column == column;
}

Chess.Piece.prototype.isInRow= function(row)
{
	return this._position.row == row;
}

Chess.Piece.prototype.moveTo= function(position)
{
	this._removePieceAt.call(this._board, this._position);
	this._position= position;
	return this._placePieceAt.call(this._board, this, position);
}

Chess.Piece.prototype.onremoved= function()
{
	this._position= Chess.Position.Off;
}

Chess.Piece.prototype.placeOn= function(board, placePieceAt, removePieceAt)
{
	this._board= board;
	this._placePieceAt= placePieceAt;
	this._removePieceAt= removePieceAt;
	placePieceAt.call(board, this, this._position);
}

Chess.Piece.prototype.promoteTo= function(promotionType, collection)
{
	this._type= promotionType;
	collection[this._id]= this;
}

Chess.Piece.prototype.remove= function()
{
	this._removePieceAt.call(this._board, this._position);
	this.onremoved();
}

Chess.Piece.prototype.toString= function()
{
	return this._id + this._type.glyph + this._position;
}

// private methods

Chess.Piece.prototype._getDistanceTo= function(position)
{
	return (this._position.row - position.row) * (this._position.row - position.row)
		+ (this._position.column - position.column) * (this._position.column - position.column);
}


///////////////////////////////////////////////////////////////////////////////
// Chess.Position

Chess.Position= function(row, column)
{
	this.row= row;
	this.column= column;
}

Chess.Position.Off= new Chess.Position(8, 8);

Chess.Position.fromAlgebraicPosition= function(ap)
{
	var rank= ap.charAt(1), file= ap.charCodeAt(0);
	return new Chess.Position(8 - parseInt(rank, 10), file - 97);
}

Chess.Position.prototype.getAlgebraicPosition= function()
{
	return String.fromCharCode(this.column + 97) + (8 - this.row);
}

Chess.Position.prototype.isOffBoard= function()
{
	return this.row == Chess.Position.Off.row;
}

Chess.Position.prototype.toString= function()
{
	return this.row + "" + this.column;
}

