/** * @package HTML Chess * @version 1.0 revision #8 * @author Stefano Gioffre', see README.txt * @copyleft 2010 Stefano Gioffre' * See COPYRIGHT.txt for copyright notices and details. * @license GNU/GPL Version 3, see LICENSE.txt * HTML Chess 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; version 3 of the License. * * http://htmlchess.sourceforge.net/ * * The chess engine is written by Oscar Toledo (http://nanochess.110mb.com/), * the 3D canvas pieces and the 3D canvas renderer are written by Jacob * Seidelin (http://www.nihilogic.dk/). */ var chess = (function() { // 3d var oSolidBoard, bUseKeyboard = false, graphicsStatus = 0, // 2d oBoardTable = null, aCoords, aFlatSquares, sLstSqColr, // both visualizations oBoardsBox, bHumanSide = true, // resizing vars nDeskWidth = 512, nDeskHeight = 512, nFlatBVMargin = 12, // theese values are modificable nFlatBoardSide = nDeskHeight - nFlatBVMargin, nPageX, nPageY, iBoardsBoxX, iBoardsBoxY, nDscrsX, nDscrsY, oFilm, nMinWidth = nMinHeight = 512, // history motion picture nMotionId, bMotion = false, bBoundLock = false, nFrameRate = 1000, // DOM oPGNBtn, oMovesSelect, oInfoBox, oCtrlForm, oNtfArea = null, oNtfClsAll = null, bInfoBox = false, aCloseCalls = [], iNtfs = 0, rDeniedTagChrs = /(^\d)|\W/g, sAlgBoxEmpty = "digit your move...", bCtrlIsDown = false, // system sMovesList, sPGNHeader, flagHumanBlack, bReady = true, bAI = true, bCheck = false, bGameNotOver = true, lastStart = 0, lastEnd = 0, iHistPointr = -1, aHistory = [], kings = [0, 0], iRound = 1, oGameInfo = {}, oNewInfo = {}, etc = { // do not change theese values!! aBoard: [], aThreats: [], nPromotion: 0, bFlatView: false, bSolidView: false, bBlackSide: false, oFlatVwArea: null, oSolidVwArea: null, aPiecesLab: null, bKeyCtrl: true, i3DWidth: nDeskWidth, i3DHeight: nDeskHeight, lookAt: function(nGetPosX, nGetPosY) { return(this.aBoard[nGetPosY * 10 + nGetPosX + 21]); }, isValidMove: function(nPosX, nPosY, nTargetX, nTargetY) { var startSq = nPosY * 10 + nPosX + 21, nPiece = this.aBoard[startSq]; if (nPiece === 0) { return(true); } var endSq = nTargetY * 10 + nTargetX + 21, nTarget = this.aBoard[endSq], nPieceType = nPiece & 7, flagPcColor = nPiece & 8, bHasMoved = Boolean(nPiece & 16 ^ 16), flagTgColor = nTarget & 8, nWay = 4 - flagPcColor >> 2, nDiffX = nTargetX - nPosX, nDiffY = nTargetY - nPosY; switch (nPieceType) { case 1: // pawn if (((nDiffY | 7) - 3) >> 2 !== nWay) { return(false); } if (nDiffX === 0) { if ((nDiffY + 1 | 2) !== 2 && (nDiffY + 2 | 4) !== 4) { return(false); } if (nTarget > 0) { return(false); } if (nTargetY === nPosY + (2 * nWay)) { if (bHasMoved) { return(false); } if (this.lookAt(nTargetX, nTargetY - nWay) > 0) { return(false); } } } else if ((nDiffX + 1 | 2) === 2) { if (nDiffY !== nWay) { return(false); } if ((nTarget < 1 || flagTgColor === flagPcColor) && (/* not en passant: */ nPosY !== 7 + nWay >> 1 || /* if our pawn is not on the opening, or if it is but... */ nPawnStride % 10 - 1 !== nTargetX /* ...not near him another pawn has moved for first time. */)) { return(false); } } else { return(false); } break; case 3: // knight if (((nDiffY + 1 | 2) - 2 | (nDiffX + 2 | 4) - 2) !== 2 && ((nDiffY + 2 | 4) - 2 | (nDiffX + 1 | 2) - 2) !== 2) { return(false); } if (nTarget > 0 && flagTgColor === flagPcColor) { return(false); } break; case 6: // queen if (nTargetY !== nPosY && nTargetX !== nPosX && Math.abs(nDiffX) !== Math.abs(nDiffY)) { return(false); } break; case 5: // rook if (nTargetY !== nPosY && nTargetX !== nPosX) { return(false); } break; case 4: // bishop if (Math.abs(nDiffX) !== Math.abs(nDiffY)) { return(false); } break; case 2: // king var ourRook; if ((nDiffY === 0 || (nDiffY + 1 | 2) === 2) && (nDiffX === 0 || (nDiffX + 1 | 2) === 2)) { if (nTarget > 0 && flagTgColor === flagPcColor) { return(false); } } else if (ourRook = this.lookAt(30 - nDiffX >> 2 & 7, nTargetY), (nDiffX + 2 | 4) === 4 && nDiffY === 0 && !bCheck && !bHasMoved && ourRook > 0 && Boolean(ourRook & 16)) { // castling for (var passX = nDiffX * 3 + 14 >> 2; passX < nDiffX * 3 + 22 >> 2; passX++) { if (this.lookAt(passX, nTargetY) > 0 || isThreatened(passX, nTargetY, nTargetY / 7 << 3 ^ 1)) { return(false); } } if (nDiffX + 2 === 0 && this.aBoard[nTargetY * 10 + 22] > 0) { return(false); } } else { return(false); } break; } if (nPieceType === 5 || nPieceType === 6) { if (nTargetY === nPosY) { if (nPosX < nTargetX) { for (var iOrthogX = nPosX + 1; iOrthogX < nTargetX; iOrthogX++) { if (this.lookAt(iOrthogX, nTargetY) > 0) { return(false); } } } else { for (var iOrthogX = nPosX - 1; iOrthogX > nTargetX; iOrthogX--) { if (this.lookAt(iOrthogX, nTargetY) > 0) { return(false); } } } } if (nTargetX === nPosX) { if (nPosY < nTargetY) { for (var iOrthogY = nPosY + 1; iOrthogY < nTargetY; iOrthogY++) { if (this.lookAt(nTargetX, iOrthogY) > 0) { return(false); } } } else { for (var iOrthogY = nPosY - 1; iOrthogY > nTargetY; iOrthogY--) { if (this.lookAt(nTargetX, iOrthogY) > 0) { return(false); } } } } if (nTarget > 0 && flagTgColor === flagPcColor) { return(false); } } if (nPieceType === 4 || nPieceType === 6) { if (nTargetY > nPosY) { var iObliqueY = nPosY + 1; if (nPosX < nTargetX) { for (var iObliqueX = nPosX + 1; iObliqueX < nTargetX; iObliqueX++) { if (this.lookAt(iObliqueX, iObliqueY) > 0) { return(false); } iObliqueY++; } } else { for (var iObliqueX = nPosX - 1; iObliqueX > nTargetX; iObliqueX--) { if (this.lookAt(iObliqueX, iObliqueY) > 0) { return(false); } iObliqueY++; } } } if (nTargetY < nPosY) { var iObliqueY = nPosY - 1; if (nPosX < nTargetX) { for (var iObliqueX = nPosX + 1; iObliqueX < nTargetX; iObliqueX++) { if (this.lookAt(iObliqueX, iObliqueY) > 0) { return(false); } iObliqueY--; } } else { for (var iObliqueX = nPosX - 1; iObliqueX > nTargetX; iObliqueX--) { if (this.lookAt(iObliqueX, iObliqueY) > 0) { return(false); } iObliqueY--; } } } if (nTarget > 0 && flagTgColor === flagPcColor) { return(false); } } /* Although it might seem impossible that the target is the opponent's king, this condition is needed for certain hypothesis. */ if (nTarget + 6 & 7) { var bKingInCheck = false, oKing = nPieceType === 2 ? endSq : kings[flagPcColor >> 3]; this.aBoard[startSq] = 0; this.aBoard[endSq] = nPiece; if (isThreatened(oKing % 10 - 1, (oKing - oKing % 10) / 10 - 2, flagPcColor ^ 8)) { bKingInCheck = true; } this.aBoard[startSq] = nPiece; this.aBoard[endSq] = nTarget; if (bKingInCheck) { return(false); } } return(true); }, makeSelection: function(nSquareId, bFromSolid) { if (!bReady) { return; } fourBtsLastPc = (etc.aBoard[nSquareId] ^ flagWhoMoved) & 15; if (fourBtsLastPc > 8) { if (etc.bSolidView) { oSolidBoard.selectPiece(nSquareId, true, bFromSolid); } if (etc.bFlatView) { if (nFrstFocus) { squareFocus(nFrstFocus, false); } if (!bFromSolid) { squareFocus(nSquareId, true); } } nFrstFocus = nSquareId; } else if (nFrstFocus && fourBtsLastPc < 9) { if (iHistPointr + 1 < aHistory.length && etc.isValidMove(nFrstFocus % 10 - 1, (nFrstFocus - nFrstFocus % 10) / 10 - 2, nSquareId % 10 - 1, (nSquareId - nSquareId % 10) / 10 - 2)) { if (confirm("Moving now all subsequent moves will be lost. Are you sure?")) { trimHistory(); } else { return; } } nScndFocus = nSquareId; fourBtsLastPc = etc.aBoard[nFrstFocus] & 15; if ((fourBtsLastPc & 7) === 1 & (nScndFocus < 29 | nScndFocus > 90)) { fourBtsLastPc = 14 - etc.nPromotion ^ flagWhoMoved; } consider(0, 0, 0, 21, nPawnStride, 1); if (etc.bSolidView) { oSolidBoard.selectPiece(nSquareId, false, bFromSolid); } if (etc.bFlatView) { squareFocus(nFrstFocus, false); writeFlatPieces(); } if (bAI && flagWhoMoved === flagHumanBlack && fourBtsLastPc - flagHumanBlack < 9) { bReady = false; window.setTimeout(engineMove, 250); } } } }; function newPGNHeader() { var sOpp = bAI ? "HTMLChess" : "?"; for (var iOldKey in oGameInfo) { delete oGameInfo[iOldKey]; } oGameInfo.Event = "No name match"; oGameInfo.Site = document.domain || "?"; oGameInfo.Date = (new Date()).toLocaleDateString(); oGameInfo.Round = bAI ? String(iRound++) : "1"; if (flagHumanBlack) { oGameInfo.White = sOpp; oGameInfo.Black = "Human"; } else { oGameInfo.White = "Human"; oGameInfo.Black = sOpp; } oGameInfo.Result = "*"; updatePGNHeader(); } function isThreatened(nPieceX, nPieceY, flagFromColor) { var iMenacing, bIsThrtnd = false; for (var iMenaceY = 0; iMenaceY < 8; iMenaceY++) { for (var iMenaceX = 0; iMenaceX < 8; iMenaceX++) { iMenacing = etc.aBoard[iMenaceY * 10 + iMenaceX + 21]; if (iMenacing > 0 && (iMenacing & 8) === flagFromColor && etc.isValidMove(iMenaceX, iMenaceY, nPieceX, nPieceY)) { bIsThrtnd = true; break; } } if (bIsThrtnd) { break; } } return(bIsThrtnd); } function getInCheckPieces() { var iExamX, iExamY, iExamPc, bNoMoreMoves = true, myKing = kings[flagWhoMoved >> 3 ^ 1]; bCheck = isThreatened(myKing % 10 - 1, (myKing - myKing % 10) / 10 - 2, flagWhoMoved); etc.aThreats.splice(0); for (var iExamSq = 21; iExamSq < 99; iExamSq += iExamSq % 10 < 8 ? 1 : 3) { iExamX = iExamSq % 10 - 1; iExamY = (iExamSq - iExamSq % 10) / 10 - 2; iExamPc = etc.aBoard[iExamSq]; if (bNoMoreMoves && iExamPc > 0 && (iExamPc & 8 ^ 8) === flagWhoMoved) { for (var iWaySq = 21; iWaySq < 99; iWaySq += iWaySq % 10 < 8 ? 1 : 3) { if (etc.isValidMove(iExamX, iExamY, iWaySq % 10 - 1, (iWaySq - iWaySq % 10) / 10 - 2)) { bNoMoreMoves = false; break; } } } if ((!bCheck || (iExamPc & 7) === 2) && iExamPc > 0 && (iExamPc & 8 ^ 8) === flagWhoMoved && isThreatened(iExamX, iExamY, flagWhoMoved)) { etc.aThreats.push(iExamSq); } } if (bNoMoreMoves) { if (bCheck) { var sWinner = flagWhoMoved ? "Black" : "White"; oGameInfo.Result = flagWhoMoved ? "0-1" : "1-0"; sendMsg((oGameInfo.hasOwnProperty(sWinner) ? oGameInfo[sWinner] : sWinner) + " wins.", "The king is threatened and can not move (checkmate<\/em>).", 10000); sMovesList = sMovesList.replace(/\+$/, "#"); } else { oGameInfo.Result = "1/2-1/2"; sendMsg("Drawn game", "The opponent can not move (draw<\/em>).", 10000); } bGameNotOver = false; } else if (oGameInfo.hasOwnProperty("Result") && oGameInfo.Result.search(/^(\d+\-\d+)$/) > -1 && iHistPointr === aHistory.length - 1) { var sWinner = oGameInfo.Result.valueOf() === "1-0" ? "White" : "Black"; sendMsg((oGameInfo.hasOwnProperty(sWinner) ? oGameInfo[sWinner] : sWinner) + " wins.", "The opponent has withdrawn.", 10000); bGameNotOver = false; } else { oGameInfo.Result = "*"; bGameNotOver = true; } } function getPcByParams(nParamId, nWhere) { var nPieceId = aParams[nParamId]; if ((nPieceId & 7) === 2) { kings[nParamId >> 3 & 1] = nWhere; } return(nPieceId); } function resetBoard() { var iParamId = 0; nFrstFocus = fourBtsLastPc = nPawnStride = lastStart = lastEnd = 0; flagWhoMoved = 8; iHistPointr = -1; aHistory.splice(0); etc.aThreats.splice(0); for (var iPosition = 1; iPosition < 121; iPosition++) { etc.aBoard[iPosition - 1] = iPosition % 10 ? iPosition / 10 % 10 < 2 | iPosition % 10 < 2 ? 7 : iPosition / 10 & 4 ? 0 : getPcByParams(iParamId++, iPosition - 1) | 16 : 7; } sMovesList = new String(); oMovesSelect.innerHTML = "