// $Id: pieces.js,v 1.21 2009/03/26 21:31:59 preston Exp $
// http://www.howtocreate.co.uk/tutorials/javascript/objects

var LAST_MOVED_PIECE = false;
var blank = null;
var frBoard = null;

function resetBoardModel()
{
    frBoard = null;
    frBoard = [[blank, blank, blank, blank, blank, blank, blank, blank],
               [blank, blank, blank, blank, blank, blank, blank, blank],
               [blank, blank, blank, blank, blank, blank, blank, blank],
               [blank, blank, blank, blank, blank, blank, blank, blank],
               [blank, blank, blank, blank, blank, blank, blank, blank],
               [blank, blank, blank, blank, blank, blank, blank, blank],
               [blank, blank, blank, blank, blank, blank, blank, blank],
               [blank, blank, blank, blank, blank, blank, blank, blank]];
} // resetBoardModel

function getPiece(ff, rr)
{
    if ((ff < 0) || (7 < ff) || (rr < 0) || (7 < rr))  {  return false;  }
    var p = frBoard[ff][rr];
    return ((blank == p) ? false : p);
} // getPiece

function isBlank(file, rank)  {  return (blank == frBoard[file][rank]);  }

/* END OF BOARD - START OF PIECES */

function createKing(idString, f, r, c)
{
    var piece = new King();
    piece.setProperties(idString, f, r, c, piece);
    return piece;
} // createKing

function promote(piece)
{
    frBoard[piece.file][piece.rank] = piece;
    return piece;
} // promote

Piece.prototype.constructor = Piece;
Piece.prototype.element = null;
Piece.prototype.color = false;
Piece.prototype.myKing = null;
Piece.prototype.numberMoves = 0;
Piece.prototype.file = -1;
Piece.prototype.rank = -1;
function Piece()
{
} // constructor

Piece.prototype.toString = function()
{
    return " f:" + this.file + " r:" + this.rank +
           " c:" + (this.color ? "true" : "false") +
           " m:" + this.numberMoves +
           " t:" + this.getType();
} // toString

Piece.prototype.setProperties = function(idString, f, r, c, myK)
{
    this.element = idString;
    this.color = c; // true is white; false is black
    this.myKing = myK;
    this.reset(f, r);
    return;
} // setProperties

Piece.prototype.reset = function(f, r)
{
    this.numberMoves = 0; // useful for pawns & kings
    this.file = f;
    this.rank = r;
    frBoard[f][r] = this;
    return;
} // reset

Piece.prototype.isWhite = function()  {  return (this.color);  }
Piece.prototype.isBlack = function()  {  return (!this.color);  }
Piece.prototype.isOpponent = function(piece)  {  return (this.color != piece.color);  }

/**
 * does NOT validate isLegalMove()
 * @return piece if taken; false otherwise
 */
Piece.prototype.setMove = function(nf, nr)
{
    var oldPiece = getPiece(nf, nr);
    frBoard[this.file][this.rank] = blank;
    this.file = nf;
    this.rank = nr;
    frBoard[this.file][this.rank] = this;    // note any previous piece is gone
    this.numberMoves = this.numberMoves + 1; // useful for pawns & kings
    LAST_MOVED_PIECE = this;
    return oldPiece;
} // setMove

/**
 * checks Range, Movement existence, to Blank or capture Opponent, self-Check
 * @return true (if bad); false otherwise
 */
Piece.prototype.isCannotMove = function(f, r)
{
    // check ranges - should probably raise exception
    if ((f < 0) || (7 < f) || (r < 0) || (7 < r))  {  return true;  }

    // check if moving at all!
    if ((this.file == f) && (this.rank == r))  {  return true;  }

    // check if destination is blank/opponent
    var destPiece = frBoard[f][r];
    if ((blank != destPiece) && !this.isOpponent(destPiece))  {  return true;  }

    // do not attack King
    if (destPiece instanceof King)  {  return true;  }

    // now, check if removing the piece will place king in check
    return this.isPlacingKingInCheck(f, r)
} // isCannotMove

/**
 * @return true (if bad); false otherwise
 */
Piece.prototype.isPlacingKingInCheck = function(f, r)
{
    var flag = false;
    // remove piece temporarily
    var destPiece = frBoard[f][r];
    frBoard[f][r] = this;
    frBoard[this.file][this.rank] = blank;
    var oldFile = this.file;
    var oldRank = this.rank;
    this.file = f;
    this.rank = r;

    // now check if my king in check!
    try
    {
        this.myKing.verifyFileCheck(); // looks for R,Q,K    
        this.myKing.verifyRankCheck(); // looks for R,Q,K    
        this.myKing.verifyDiagonalCheck(); // looks for P,B,Q,K    
        this.myKing.verifyKnightCheck(); // looks for Kn    
    }
    catch (e)
    {
        /* useful debug code
        alert(" check validation  " + e + "  "
             + this.myKing.file + "  " + this.myKing.rank);
        alert(" p1  " + getPiece(2, 6).toString() +
              "     " + getPiece(4, 6).toString());
        */
        flag = true;
    }

    // replace piece
    this.file = oldFile;
    this.rank = oldRank;
    frBoard[f][r] = destPiece;
    frBoard[this.file][this.rank] = this;

    return flag;
} // isPlacingKingInCheck

Piece.prototype.isRankFree = function(end)
{
    var b = Math.min(this.file, end) + 1; // we're already at the beginning
    var e = Math.max(this.file, end);

    // if b==e -> single space move
    // when b==e -> we're at end; which has already been checked
    while (b < e)
    {
        if (!isBlank(b, this.rank))  {  return false;  }
        b = b + 1; 
    }
    return true;
} // isRankFree

Piece.prototype.isFileFree = function(end)
{
    var b = Math.min(this.rank, end) + 1;
    var e = Math.max(this.rank, end);

    while (b < e) 
    {
        if (!isBlank(this.file, b))  {  return false;  }
        b = b + 1; 
    }
    return true;
} // isFileFree

Piece.prototype.isDiagonalFree = function(ef, er)
{
    var f1 = ef - this.file;
    var r1 = er - this.rank;
    var bf = this.file + f1;
    var br = this.rank + r1;

    // we move bf, br in lockstep; @see isFileFree
    while (bf != ef) // don't bother with second check - lockstep!
    {
        if (!isBlank(bf, br))  {  return false;  }
        bf = bf + f1;
        br = br + r1;
    }
    return true;
} // isDiagonalFree

/* end of generic Piece - start of actual pieces */

function Rook(idString, f, r, c, myK)
{
    this.setProperties(idString, f, r, c, myK);
} // constructor
Rook.prototype = new Piece;
Rook.prototype.constructor = Rook;

Rook.prototype.isLegalMove = function(f, r)
{
    if (this.isCannotMove(f, r))  {  return false;  }
    if ((this.file != f) && (this.rank != r))  {  return false;  } // rook movement
    // is along file?
    if (this.file == f)  {  return this.isFileFree(r);  }
    // is along rank?
    if (this.rank == r)  {  return this.isRankFree(f);  }
    return false; // must have already returned; should probably raise exception
} // isLegalMove


function Bishop(idString, f, r, c, myK)
{
    this.setProperties(idString, f, r, c, myK);
} // constructor
Bishop.prototype = new Piece;
Bishop.prototype.constructor = Bishop;

Bishop.prototype.isLegalMove = function(f, r)
{
    if (this.isCannotMove(f, r))  {  return false;  }
    if ((this.file == f) || (this.rank == r))  {  return false;  } // rook movement; zero division
    // is along diagonal?
    if (1 != (Math.abs(this.file - f) / Math.abs(this.rank - r)))  {  return false;  }

    return this.isDiagonalFree(f, r);
} // isLegalMove


function Queen(idString, f, r, c, myK)
{
    this.setProperties(idString, f, r, c, myK);
} // constructor
Queen.prototype = new Piece;
Queen.prototype.constructor = Queen;

Queen.prototype.isLegalMove = function(f, r)
{
    if (this.isCannotMove(f, r))  {  return false;  }

    // is along file?
    if (this.file == f)  {  return this.isFileFree(r);  }
    // is along rank?
    if (this.rank == r)  {  return this.isRankFree(f);  }
    // is along diagonal?
    if (1 != (Math.abs(this.file - f) / Math.abs(this.rank - r)))  {  return false;  }

    return this.isDiagonalFree(f, r);
} // isLegalMove


function Knight(idString, f, r, c, myK)
{
    this.setProperties(idString, f, r, c, myK);
} // constructor
Knight.prototype = new Piece;
Knight.prototype.constructor = Knight;

Knight.prototype.isLegalMove = function(f, r)
{
    if (this.isCannotMove(f, r))  {  return false;  }
    
    return (2 == (Math.abs(this.file - f) * Math.abs(this.rank - r)));
} // isLegalMove


King.prototype = new Piece;
King.prototype.constructor = King;
function King()
{
}

/**
 * note castle is checked separatelyKing.prototype.isLegalMove(f, r)
 */
King.prototype.isLegalMove = function(f, r)
{
    if (this.isCannotMove(f, r))  {  return false;  }

    var ff = Math.abs(this.file - f); // 0, 1, 2(castle) permitted
    var rr = Math.abs(this.rank - r); // 0, 1 permitted
    if ((rr > 1) || (ff > 1))  {  return false;  }

    return true;
} // isLegalMove

/**
 * @return castled rook or false if not legal
 */
King.prototype.isLegalCastle = function(f, r)
{
    var ff = this.file - f; // positive is to left (white, kr; black, qr)
    if ((this.numberMoves > 0) || (this.rank != r) || (2 != Math.abs(ff)))  {  return false;  }
    
    var rook = getPiece((2 == ff) ? 0 : 7, this.rank);
    if (!rook || !(rook instanceof Rook) || (rook.numberMoves > 0))  {  return false;  }

    // ok, we have a non-moved rook (non-moved; don't bother testing color)
    // must be empty between rook&king
    if (!this.isRankFree(rook.file))  {  return false;  }

    // is King in check?
    if (this.isPlacingKingInCheck(this.file, this.rank))  {  return false;  }
    // will King be placed in check?
    if (this.isPlacingKingInCheck(this.file + ff, this.rank))  {  return false;  }
    // will King cross check?
    if (this.isPlacingKingInCheck(this.file + (ff/2), this.rank))  {  return false;  }

    return rook;
} // isLegalCastle


function Pawn(idString, f, r, c, myK)
{
    this.setProperties(idString, f, r, c, myK);
} // constructor
Pawn.prototype = new Piece;
Pawn.prototype.constructor = Pawn;

/**
 * note en passant is checked separately
 */
Pawn.prototype.isLegalMove = function(f, r)
{
    // enforce moving forward
    if (this.isWhite() && (this.rank > r))  {  return false;  }
    if (this.isBlack() && (this.rank < r))  {  return false;  }

    if (this.isCannotMove(f, r))  {  return false;  } // verifies check
    // isCannotMove will be incorrect if pawn advances into enemy pawn
    // easiest to just verify all moves again

    var rr = Math.abs(this.rank - r);
    if (!((1 == rr) || ((2 == rr) && (0 == this.numberMoves))))  {  return false;  }

    // check moves forward
    var ff = Math.abs(this.file - f);
    if (0 == ff)
    {
        if (!isBlank(f, r))  {  return false;  }
        // rank=1->3  ==>  (r-rank)/2 = +1
        // rank=7->5  ==>  (r-rank)/2 = -1
        return ((1 == rr) || isBlank(f, this.rank + (r-this.rank)/2));
    }

    // check captures
    if ((1 == ff) && (1 == rr))
    {
        var piece = getPiece(f, r);
        return (piece && this.isOpponent(piece));
    }
    return false;
} // isLegalMove

/**
 * @return ep pawn or false is not legal - note 2 pieces may be taken
 */
Pawn.prototype.isEnPassant = function(f, r)
{
    // enforce moving forward
    if (this.isWhite() && (this.rank > r))  {  return false;  }
    if (this.isBlack() && (this.rank < r))  {  return false;  }

    if (((5 == this.rank) && this.isWhite()) ||
        ((4 == this.rank) && this.isBlack()))
    {
        var rr = Math.abs(this.rank - r);
        var ff = Math.abs(this.file - f);
        if ((1 == rr) && (1 == ff) && isBlank(f, r))
        {
            // a5b6 - r=6,rr=+1 => pp=b5
            // a4b3 - r=3,rr=-1 => pp=b4
            var passedPawn = getPiece(f, r - rr);
            if (passedPawn && this.isOpponent(passedPawn) &&
                (passedPawn == LAST_PIECE_MOVED) &&
                (passedPawn instanceof Pawn) &&
                (1 == passedPawn.numberMoves))
            {
                // now we verify check! - isPlacingKingInCheck won't remove the pp
                frBoard[passedPawn.file][passedPawn.rank] = blank;     // remove pp
                var chk = this.isPlacingKingInCheck(f, r);                  // check move
                frBoard[passedPawn.file][passedPawn.rank] = passedPawn;// replace pp
                if (!chk)  {  return passedPawn;  }
            }
        }
    }
    return false;
} // isEnPassant





/**
 * @return true if in check
 */
King.prototype.verifyRankCheck = function() // looks for R,Q,K
{
    var f = this.file + 1;
    while (f < 8)
    {
        var piece = getPiece(f, this.rank);
        if (piece)
        {
            this.verifyRQK(piece);
            break; // found a blocker
        }
        f = f + 1;
    }
    f = this.file - 1;
    while (f > -1)
    {
        var piece = getPiece(f, this.rank);
        if (piece)      
        {
            this.verifyRQK(piece);
            break; // found a blocker
        }
        f = f - 1;
    }
    return false; // not in check
} // verifyRankCheck

King.prototype.verifyFileCheck = function() // looks for R,Q,K
{
    var r = this.rank + 1;
    while (r < 8)
    {
        var piece = getPiece(this.file, r);
        if (piece)
        {
            this.verifyRQK(piece);
            break; // found a blocker
        }
        r = r + 1;
    }
    r = this.rank - 1;
    while (r > -1)
    {
        var piece = getPiece(this.file, r);
        if (piece)      
        {
            this.verifyRQK(piece);
            break; // found a blocker
        }
        r = r - 1;
    }
    return;
} // verifyFileCheck

King.prototype.verifyRQK = function(piece)
{
    if (this.isOpponent(piece))
    {
        if ((piece instanceof Rook) || (piece instanceof Queen))
        {
            throw "verifyRQK  " + piece.toString();
        }
        else if ((1 == Math.abs(this.file - piece.file)) && (piece instanceof King))
        {
            throw "verifyRQK  " + piece.toString();
        }
    }
    return;
} // verifyRQK

King.prototype.verifyPBQK = function(piece)
{
    if (this.isOpponent(piece))
    {
        if ((piece instanceof Bishop) || (piece instanceof Queen))
        {
            throw "verifyBQ  " + piece.toString();
        }
        // Kings - 0,1,2 are ok; Pawns MUST be 1+1=2
        var dist = Math.abs(this.file - piece.file) +
                   Math.abs(this.rank - piece.rank);
        if ((piece instanceof Pawn) && (2 == dist))
        {
            // opponent pawn +1 diagonal
            // white attacks up; black attacks down
            if ((piece.isWhite() && (+1 == (this.rank - piece.rank))) ||
                (piece.isBlack() && (-1 == (this.rank - piece.rank))));
            {
                 throw "verifyP  " + piece.toString();
            }
        }
        else if ((piece instanceof King) && (dist < 3))
        {
            throw "verifyK  " + piece.toString();
        }
    }
    return;
} // verifyPBQK

King.prototype.verifyDiagonalCheck = function() // looks for P,B,Q,K
{
    var index = 1;
    var neBlocker = false;
    var seBlocker = false;
    var swBlocker = false;
    var nwBlocker = false;
    while (index < 8)
    {
        if (!neBlocker)
        {
            var f = this.file + index;
            var r = this.rank + index;
            neBlocker = ((7 == f) || (7 == r)); // next is outside board
            var piece = getPiece(f, r);
            if (piece)
            {
                this.verifyPBQK(piece);
                neBlocker = true;
            }
        }
        if (!seBlocker)
        {
            var f = this.file + index;
            var r = this.rank - index;
            seBlocker = ((7 == f) || (0 == r)); // next is outside board
            var piece = getPiece(f, r);
            if (piece)
            {
                this.verifyPBQK(piece);
                seBlocker = true;
            }
        }
        if (!swBlocker)
        {
            var f = this.file - index;
            var r = this.rank - index;
            swBlocker = ((0 == f) || (0 == r)); // next is outside board
            var piece = getPiece(f, r);
            if (piece)
            {
                this.verifyPBQK(piece);
                swBlocker = true;
            }
        }
        if (!nwBlocker)
        {
            var f = this.file - index;
            var r = this.rank + index;
            nwBlocker = ((0 == f) || (7 == r)); // next is outside board
            var piece = getPiece(f, r);
            if (piece)
            {
                this.verifyPBQK(piece);
                nwBlocker = true;
            }
        }
        index = index + 1;
    }
    return;
} // verifyDiagonalCheck

var knightArray = [[-2,+1], [-2,-1], [-1,+2], [-1,-2],
                   [+1,+2], [+1,-2], [+2,+1], [+2,-1]];
King.prototype.verifyKnightCheck = function() // looks for Kn
{
    var index = 0;
    while (index < 8)
    {
        var piece = getPiece(this.file + knightArray[index][0],
                             this.rank + knightArray[index][1]);
        if (piece && this.isOpponent(piece) && (piece instanceof Knight))
        {
            throw "verifyKnightCheck  " + piece.toString();
        }
        index = index + 1;
    }
    return;
} // verifyKnightCheck


Piece.prototype.getType = function()  {  return "piece";  }
Pawn.prototype.getType = function()  {  return "Pawn";  }
Rook.prototype.getType = function()  {  return "Rook";  }
Knight.prototype.getType = function()  {  return "Knight";  }
Bishop.prototype.getType = function()  {  return "Bishop";  }
Queen.prototype.getType = function()  {  return "Queen";  }
King.prototype.getType = function()  {  return "King";  }

/* end of pieces.js */


