/** Version: 0.6.7 **/ /** * Copyright 2008 Toomas Römer * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. **/ /* Convert PGN format to an easier format. The problem with PGN is that it is really difficult and ugly to accomplish backward moves. Let's say we have a move "e4" and we need to go one move back. The only info is that we have placed a pawn to e4. We also have to remember from where did we place the pawn. To make it easier and to have less calculations the PGN is converted into a format where the from square with contents is explicit and to square also. There are other problems also regarding backward moving and remembering which piece was taken. */ // AH: These comments (AH:) are made by Adrian Hanft, Overall changes: replaced all gif with png function Converter(pgn) { this.pgn = pgn; this.vBoard = new Array(8); this.initialBoard = new Array(8); this.moves = new Array(); this.iteIndex = 0; this.whiteToMove = true; this.startMoveNum = 1; this.flippedI = false; this.flippedV = false; this.wKing = new Array(); this.bKing = new Array(); this.wQueens = new Array(); this.bQueens = new Array(); this.wBishops = new Array(); this.bBishops = new Array(); this.wRooks = new Array(); this.bRooks = new Array(); for(var i = 0; i < 8; i++) { this.vBoard[i] = new Array(8); for (var j = 0; j < 8; j++) { this.vBoard[i][j] = new vSquare(); } } if (pgn.props['FEN']) { var val = pgn.props['FEN'].split(/\/| /g); for (var i=0;i<8;i++) { var file = 0; for (var j=0;jthis.iteIndex) return this.moves[this.iteIndex]; return null; }; this.getCurMoveNo = function() { return this.iteIndex; }; this.nextMove = function() { if (this.moves.length>this.iteIndex) return this.moves[this.iteIndex++]; return null; }; this.prevMove = function() { if (this.iteIndex>0) return this.moves[--this.iteIndex]; return null; }; this.resetToEnd = function() { this.iteIndex = this.moves.length; }; this.resetToStart = function() { this.iteIndex = 0; }; /* EOF Result Iterator */ this.getStartPos = function(flipped) { if (flipped!=this.flippedI) { this.flipBoard(this.initialBoard); this.flippedI = !this.flippedI; } return this.initialBoard; }; this.getEndPos = function(flipped) { if (flipped!=this.flippedV) { this.flipBoard(this.vBoard); this.flippedV = !this.flippedV; } return this.vBoard; }; this.flipBoard = function(board) { this.flipped = !this.flipped; for (var i = 0;i<8;i++) { for (var j = 0;j<4;j++) { var tmp = board[i][j]; board[i][j] = board[7-i][7-j]; board[7-i][7-j] = tmp; } } }; /* Convert a move. */ this.convertMove = function(board) { var to = this.pgn.nextMove(); var oldTo = to; if (to == null) return; var color = to[1]; to = to[0]; /* Check which piece has to move. Find the location of the piece. */ var pawnre = /^[a-z]+[1-8]/; var knightre = /^N[0-9]?[a-z]+[1-8]/i; var bishre = /^B[a-z]+[1-8]/; var queenre = /^Q([a-z]|[0-9])?[a-z]+[1-8]/i; var rookre = /^R([a-z]|[0-9])?[a-z]+[1-8]/i; var lCastlere = /^(0|O)-(0|O)-(0|O)/i; var sCastlere = /^(0|O)-(0|O)/i; var kingre = /^K[a-z]+[1-8]/i; var prom = ""; var toCoords = getSquare(to); var fromCoords, from, to, result, myMove = null, pawnM = false; if (knightre.test(to)) { fromCoords = findFromKnight(this, to, toCoords, color); } else if (bishre.test(to)) { fromCoords = findFromBish(this, this.vBoard, to, toCoords, color); } else if (queenre.test(to)) { fromCoords = findFromQueen(this, this.vBoard, to, toCoords, color); } else if (rookre.test(to)) { fromCoords = findFromRook(this, this.vBoard, to, toCoords, color); } else if (kingre.test(to)) { fromCoords = findFromKing(this, this.vBoard, color); } else if (sCastlere.test(to)) { var bCoords = new Array('e8','g8','h8','f8'); var wCoords = new Array('e1','g1','h1','f1'); if (lCastlere.test(to)) { bCoords = new Array('e8', 'c8', 'a8', 'd8'); wCoords = new Array('e1', 'c1', 'a1', 'd1'); } var coords = color=='white'?wCoords:bCoords; fromCoords = getSquare(coords[0]); toCoords = getSquare(coords[1]); from = this.vBoard[fromCoords[0]][fromCoords[1]]; to = this.vBoard[toCoords[0]][toCoords[1]]; // update king location if ('king' == from.piece && 'white' == from.color) this.wKingX = toCoords[0], this.wKingY = toCoords[1]; else if ('king' == from.piece && 'black' == from.color) this.bKingX = toCoords[0], this.bKingY = toCoords[1]; result = movePiece(this, from, to, prom); myMove = new MyMove(); myMove.moveStr = oldTo[0]; myMove.oPiece = result[2].piece; myMove.oColor = result[2].color; myMove.pPiece = result[3]; myMove.add(new MySquare(fromCoords[0], fromCoords[1] ,result[0].piece, result[0].color)); myMove.add(new MySquare(toCoords[0], toCoords[1] ,result[1].piece, result[1].color)); fromCoords = getSquare(coords[2]); toCoords = getSquare(coords[3]); } else if (pawnre.test(to)) { // let see if it is a promotional move if (/^[a-z]+[1-8]=[A-Z]/.test(to)) prom = to.charAt(to.indexOf('=')+1); fromCoords = findFromPawn(this.vBoard, to, toCoords, color); pawnM = true; } else { throw("Can't figure out which piece to move '"+oldTo+"'"); } from = this.vBoard[fromCoords[0]][fromCoords[1]]; to = this.vBoard[toCoords[0]][toCoords[1]]; // update king location if ('king' == from.piece && 'white' == from.color) { this.wKingX = toCoords[0], this.wKingY = toCoords[1]; } else if ('king' == from.piece && 'black' == from.color) { this.bKingX = toCoords[0], this.bKingY = toCoords[1]; // update bishops location } else if ('bishop' == from.piece) { var idx; if ('white' == from.color) { idx = findPieceIdx(this.wBishops,fromCoords); this.wBishops[idx][0] = toCoords[0]; this.wBishops[idx][1] = toCoords[1]; } else { idx = findPieceIdx(this.bBishops,fromCoords); this.bBishops[idx][0] = toCoords[0]; this.bBishops[idx][1] = toCoords[1]; } } else if ('queen' == from.piece) { var idx; if ('white' == from.color) { idx = findPieceIdx(this.wQueens,fromCoords); this.wQueens[idx][0] = toCoords[0]; this.wQueens[idx][1] = toCoords[1]; } else { idx = findPieceIdx(this.bQueens,fromCoords); this.bQueens[idx][0] = toCoords[0]; this.bQueens[idx][1] = toCoords[1]; } } else if ('rook' == from.piece) { var idx; if ('white' == from.color) { idx = findPieceIdx(this.wRooks,fromCoords); this.wRooks[idx][0] = toCoords[0]; this.wRooks[idx][1] = toCoords[1]; } else { idx = findPieceIdx(this.bRooks,fromCoords); this.bRooks[idx][0] = toCoords[0]; this.bRooks[idx][1] = toCoords[1]; } } if ('queen' == to.piece) { if ('white' == to.color) { idx = findPieceIdx(this.wQueens,toCoords); this.wQueens.splice(idx,1); } else { idx = findPieceIdx(this.bQueens,toCoords); this.bQueens.splice(idx,1); } } else if ('bishop' == to.piece) { if ('white' == to.color) { idx = findPieceIdx(this.wBishops,toCoords); this.wBishops.splice(idx,1); } else { idx = findPieceIdx(this.bBishops,toCoords); this.bBishops.splice(idx,1); } } else if ('rook' == to.piece) { if ('white' == to.color) { idx = findPieceIdx(this.wRooks,toCoords); this.wRooks.splice(idx,1); } else { idx = findPieceIdx(this.bRooks,toCoords); this.bRooks.splice(idx,1); } } // in case of castling we don't have a null value if (!myMove) myMove = new MyMove(); var enPassante = null; if (pawnM) enPassante = getEnPassante(this, fromCoords[0], fromCoords[1], toCoords[0], toCoords[1]); if (enPassante) { var sq = this.vBoard[enPassante[0]][enPassante[1]]; var enP = new MySquare(enPassante[0], enPassante[1] ,sq.piece, sq.color); myMove.enP = enP; this.vBoard[enPassante[0]][enPassante[1]].color = null; this.vBoard[enPassante[0]][enPassante[1]].piece = null; this.vBoard[enPassante[0]][enPassante[1]].type = null; } result = movePiece(this, from, to ,prom); myMove.oPiece = result[2].piece; myMove.oColor = result[2].color; myMove.pPiece = result[3]; myMove.moveStr = oldTo[0]; if (prom) { if ("queen" == result[1].piece) { if ('white' == result[1].color) { this.wQueens[this.wQueens.length] = [toCoords[0],toCoords[1]]; } else { this.bQueens[this.bQueens.length] = [toCoords[0],toCoords[1]]; } } else if ("bishop" == result[1].piece) { if ('white' == result[1].color) { this.wBishops[this.wBishops.length] = [toCoords[0],toCoords[1]]; } else { this.bBishops[this.bBishops.length] = [toCoords[0],toCoords[1]]; } } else if ("rook" == result[1].piece) { if ('white' == result[1].color) { this.wRooks[this.wRooks.length] = [toCoords[0],toCoords[1]]; } else { this.bRooks[this.bRooks.length] = [toCoords[0],toCoords[1]]; } } } myMove.add(new MySquare(fromCoords[0], fromCoords[1] ,result[0].piece, result[0].color)); myMove.add(new MySquare(toCoords[0], toCoords[1] ,result[1].piece, result[1].color)); return myMove; }; /* FINDING FROM LOCATION FUNCTIONS When a SAN (Standard Algebraic Notation) move is given we need to figure out from where the move is made. Lets say the SAN is "e4" - pawn moves to e4. The from location can be e2, e3 or e5. This depends on the color of the player and on where the pawn was located. All pieces have different logic on finding which piece exactly has to move to the location. */ findPieceIdx = function(arr, coords) { for (var i=0;i froms[i][0] && color == "white") continue; return new Array(froms[i][0], froms[i][1]); } //else // return new Array(froms[i][0], froms[i][1]) } } catch (e) {} } } else { // non-taking move try { var j; for(var i = 0; i < 8; i++) { j = (color == 'white')?7-i:i; if (pos[j][x].piece == 'pawn' && pos[j][x].color == color) { if (Math.abs(j-y)>2) { continue; } // we might be looking at the wrong pawn // there can be one between src and dst if (2 == Math.abs(j-y)) { var j2 = (color == 'white')?(j-1):j+1; if (pos[j2][x].piece == 'pawn' && pos[j2][x].color == color) { return new Array(j2, x); } } return new Array(j, x); } } } catch (e) {} } throw("Could not find a move with a pawn '"+to+"'"); }; /* Find the bishop from location. */ function findFromBish(board, pos, toSAN, toCoords, color) { if (toCoords[2][0] != -1 && toCoords[2][1] != -1) { return new Array(toCoords[2][1], toCoords[2][0]); } var arr; if (color == 'white') { arr = board.wBishops; } else { arr = board.bBishops; } for (var i=0;i 0) { rdx = 1; } else if (rdx < 0) { rdx = -1; } if (rdy > 0) { rdy = 1; } else if (rdy < 0) { rdy = -1; } if (dx == dy || dx == 0 || dy == 0) { //bishop-like move or rook-like move var x = arr[i][0]; var y = arr[i][1]; while (true) { x += rdx; y += rdy; if (x == to[0] && y == to[1]) { if (extra[0] != -1 || extra[1] != -1) { if (extra[0] != arr[i][1] && extra[1] != arr[i][0]) { break; } return new Array(arr[i][0],arr[i][1]); } rtrns[rtrns.length] = new Array(arr[i][0],arr[i][1]); break; } var tmp = pos[x][y]; if (tmp && tmp.piece) { //ran into another piece break; } } } } if (rtrns.length>1) { for (var i = 0; i< rtrns.length;i++) { var from = pos[rtrns[i][0]][rtrns[i][1]]; var oldTo = pos[to[0]][to[1]]; pos[rtrns[i][0]][rtrns[i][1]] = new vSquare(); pos[to[0]][to[1]] = from; var checked = isKingChecked(board,from.color, pos); pos[rtrns[i][0]][rtrns[i][1]] = from; pos[to[0]][to[1]] = oldTo; if (checked) continue; else return rtrns[i]; } } else if (rtrns.length == 1) return rtrns[0]; throw("No queen move found '"+toSAN+"'"); }; /* Find the rook's from location. */ function findFromRook(board, pos, toSAN, to, color) { var extra = to[2]; var rtrns = new Array(); var arr; if (color == 'white') { arr = board.wRooks; } else { arr = board.bRooks; } for (var i=0;i 0) { rdx = 1; } else if (rdx < 0) { rdx = -1; } if (rdy > 0) { rdy = 1; } else if (rdy < 0) { rdy = -1; } if (dx == 0 || dy == 0) { var x = arr[i][0]; var y = arr[i][1]; while (true) { x += rdx; y += rdy; if (x == to[0] && y == to[1]) { if (extra[0] != -1 || extra[1] != -1) { if (extra[0] != arr[i][1] && extra[1] != arr[i][0]) { break; } return new Array(arr[i][0],arr[i][1]); } rtrns[rtrns.length] = new Array(arr[i][0],arr[i][1]); break; } tmp = pos[x][y]; if (tmp && tmp.piece) { //ran into another piece break; } } } } if (rtrns.length>1) { for (var i = 0; i< rtrns.length;i++) { var from = pos[rtrns[i][0]][rtrns[i][1]]; var oldTo = pos[to[0]][to[1]]; pos[rtrns[i][0]][rtrns[i][1]] = new vSquare(); pos[to[0]][to[1]] = from; var checked = isKingChecked(board,from.color, pos); pos[rtrns[i][0]][rtrns[i][1]] = from; pos[to[0]][to[1]] = oldTo; if (checked) continue; else return rtrns[i]; } } else if (rtrns.length == 1) return rtrns[0]; throw("No rook move found '"+toSAN+"'"); }; /* Find the knight's from location. */ findFromKnight = function(brd, toSAN, toCoords, color) { var to = toCoords; var extra = to[2]; if (toCoords[2][0] != -1 && toCoords[2][1] != -1) { return new Array(toCoords[2][1], toCoords[2][0]); } var pos = brd.vBoard; var rtrns = new Array(); var froms = new Array( new Array(to[0]+2, to[1]+1), new Array(to[0]+2, to[1]-1), new Array(to[0]-2, to[1]+1), new Array(to[0]-2, to[1]-1), new Array(to[0]+1, to[1]+2), new Array(to[0]-1, to[1]+2), new Array(to[0]+1, to[1]-2), new Array(to[0]-1, to[1]-2) ); for (var i = 0;i1) { for (var i = 0; i< rtrns.length;i++){ var from = pos[rtrns[i][0]][rtrns[i][1]]; pos[rtrns[i][0]][rtrns[i][1]] = new vSquare(); var checked = isKingChecked(brd, from.color, pos); pos[rtrns[i][0]][rtrns[i][1]] = from; if (checked) continue; else return rtrns[i]; } return rtrns[0]; } else if (rtrns.length == 1) return rtrns[0]; throw("No knight move found. '"+toSAN+"'"); }; /* * Converts a SAN (Standard Algebraic Notation) into * board coordinates. The SAN is in the format of * eg e4, dxe4, R2b7. When SAN contains extra information * "taking move", "en passante", "check", "piece from a * specific file or rank" it is also extracted. */ function getSquare(coord) { if (arguments.length != 1) { throw "Wrong number of arguments"; } var map = new Object(); // if only from certain file we can make the move var extra = new Array(-1,-1); var taking = -1; map['a'] = 7, map['b'] = 6, map['c'] = 5; map['d'] = 4, map['e'] = 3, map['f'] = 2; map['g'] = 1, map['h'] = 0; // trim the everything from + if (coord.indexOf("+") != -1) coord = coord.substring(0, coord.indexOf("+")); // let's trim the piece prefix if (/^[A-Z]/.test(coord) || /^[nbrqk]{1,1}[abcdefgh]{1,1}/.test(coord)) { coord = coord.substr(1); } // the move is a taking move, we have to look for different // files then with pawns if (/x/.test(coord)) { var tmp = coord.split("x"); if (tmp[0].length) { if (/[a-z][0-9]/.test(tmp[0])) { extra[0] = 7-map[tmp[0].charAt(0)]; extra[1] = 8-tmp[0].charAt(1); } else if (/[a-z]/.test(tmp[0])) extra[0] = 7-map[tmp[0]]; else if (/[0-9]/.test(tmp[0])) extra[1] = 8-tmp[0]; } coord = tmp[1]; taking = 7-map[tmp[0]]; } // we have extra information on the from file // eg Rbd7 if (/^[a-z]{2,2}/.test(coord)) { extra[0] = 7-map[coord.substring(0,1)]; coord = coord.substring(1); } // we have the row no // eg R8d5 if (/^[0-9][a-z][0-9]/.test(coord)) { extra[1] = 8-coord.substring(0,1); coord = coord.substring(1); } // we have both Ng8e7 if (/^([a-z][0-9])[a-z][0-9]/.test(coord)) { var tmp = coord.match(/^([a-z][0-9])[a-z][0-9]/); extra[0] = 7-map[tmp[1].charAt(0)]; extra[1] = 8-tmp[1].charAt(1); coord = coord.replace(/[a-z][0-9]/,""); } var rtrn = new Array(8-coord.charAt(1), 7-map[coord.charAt(0)], extra, taking); return rtrn; }; getEnPassante = function(brd, x1, y1, x2, y2) { var from = brd.vBoard[x1][y1]; var to = brd.vBoard[x2][y2]; // pawn move if ("pawn" != from.piece) return null; // taking move if ((y1-y2) == 0) return null; // destination should be null if ( null != to.piece ) return null; // the piece we are looking for return new Array(x1, y2); }; getOppColor = function(color) { return "white"==color?"black":"white"; }; movePiece = function(board, from, to, prom) { var hist = to.clone(); var tmpPiece = from.piece; var pPiece = null; to.piece = from.piece; to.color = from.color; to.type = from.type; from.piece = null; from.color = null; from.type = null; // promoting the piece if (prom.length>0) { pPiece = tmpPiece; switch(prom) { case 'R': to.piece = 'rook'; break; case 'B': to.piece = 'bishop'; break; case 'N': to.piece = 'knight'; break; case 'Q': to.piece = 'queen'; break; default: throw('Unknown promotion'); } } return new Array(from, to, hist, pPiece); }; isKingChecked = function(brd, col) { var op = getOppColor(col); var x = brd.wKingX, y = brd.wKingY; if ("black" == col) { x = brd.bKingX, y = brd.bKingY; } // diagonals, looking for bishops, queens var tmp; try { for (var i = 1;i < 7; i++) { tmp = brd.vBoard[x-i][y-i]; if (tmp.color == col) break; if (tmp.color == op) { if("bishop" == tmp.piece || "queen" == tmp.piece) { return true; } break; } } } catch (e) {} try { for (var i = 1;i < 7; i++) { tmp = brd.vBoard[x+i][y+i]; if (tmp.color == col) break; if (tmp.color == op) { if("bishop" == tmp.piece || "queen" == tmp.piece) { return true; } break; } } } catch (e) {} try { for (var i = 1;i < 7; i++) { tmp = brd.vBoard[x+i][y-i]; if (tmp.color == col) break; if (tmp.color == op) { if("bishop" == tmp.piece || "queen" == tmp.piece) { return true; } break; } } } catch (e) {} try { for (var i = 1;i < 7; i++) { tmp = brd.vBoard[x-i][y+i]; if (tmp.color == col) break; if (tmp.color == op) { if("bishop" == tmp.piece || "queen" == tmp.piece) { return true; } break; } } } catch (e) {} // horizontals, verticals - looking for rooks and queens try { for (var i = 1;i < 7; i++) { tmp = brd.vBoard[x][y+i]; if (tmp.color == col) break; if (tmp.color == op) { if("rook" == tmp.piece || "queen" == tmp.piece) { return true; } break; } } } catch (e) {} try { for (var i = 1;i < 7; i++) { tmp = brd.vBoard[x][y-i]; if (tmp.color == col) break; if (tmp.color == op) { if("rook" == tmp.piece || "queen" == tmp.piece) { return true; } break; } } } catch (e) {} try { for (var i = 1;i < 7; i++) { tmp = brd.vBoard[x+i][y]; if (tmp.color == col) break; if (tmp.color == op) { if("rook" == tmp.piece || "queen" == tmp.piece) { return true; } break; } } } catch (e) {} try { for (var i = 1;i < 7; i++) { tmp = brd.vBoard[x-i][y]; if (tmp.color == col) break; if (tmp.color == op) { if("rook" == tmp.piece || "queen" == tmp.piece) { return true; } break; } } } catch (e) {} return false; }; }; function MyMove() { this.actions = new Array(); this.oPiece = null; this.oColor = null; // in case of promotion have to remember the prev // piece this.pPiece = null; // this.enP = null; // this.moveStr = null; this.add = function(action) { this.actions[this.actions.length] = action; }; this.toString = function() { return "MyMove -- no. actions "+this.actions.length; }; }; function MySquare(x, y, piece, color) { this.x = x; this.y = y; this.color = color; this.piece = piece; this.toString = function() { return "MySquare -- x = "+this.x+" y="+this.y +" color="+this.color + " piece="+this.piece; }; this.clone = function() { var sq = new MySquare(this.x, this.y, this.piece, this.color); return sq; }; }; function vSquare() { this.piece = null; this.color = null; this.type = ""; this.toString = function() { return "vSquare -- piece = "+this.piece+" color="+this.color +" type="+this.type; }; this.clone = function() { var sq = new vSquare(); sq.piece = this.piece; sq.color = this.color; sq.type = this.type; return sq; }; }; /** * Copyright 2008 Toomas Römer * * Licensed under the Apache License, Version 2.0 (the "License") * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. **/ /* Representation of the PGN format. Different meta information about the actual game(s) plus the moves and result of the game. */ function Pgn(pgn) { // properties of the game eg players, ELOs etc this.props = new Object(); this.validProps = ['Event','Site','Date','Round', 'White','Black','Result','FEN', 'WhiteElo','BlackElo','TimeControl']; // the moves, one move contains the black and white move this.moves = new Array(); // the current move in the game this.currentMove = 0; // for outputting white and black moves separately this.skip = 0; // strip newlines this.pgnOrig = pgn; pgn = pgn.replace(/\n/g," "); // replace dollar signs //"!", "?", "!!", "!?", "?!", and "??" pgn = pgn.replace(/\ \$1[0-9]*/g, "!"); pgn = pgn.replace(/\ \$2[0-9]*/g, "?"); pgn = pgn.replace(/\ \$3[0-9]*/g, "!!"); pgn = pgn.replace(/\ \$4[0-9]*/g, "??"); pgn = pgn.replace(/\ \$5[0-9]*/g, "!?"); pgn = pgn.replace(/\ \$6[0-9]*/g, "?!"); pgn = pgn.replace(/\ \$[0-9]+/g, ""); // make double spaces to single spaces pgn = pgn.replace(/\s+/g,' '); this.pgn = pgn; this.pgnRaw = pgn; if (isPGNBroken(pgn)) this.pgnStripped = stripItBroken(pgn); else this.pgnStripped = stripIt(pgn); /* constructor */ // strip comments if (isPGNBroken(pgn)) this.pgn = stripItBroken(pgn,true); else this.pgn = stripIt(pgn,true); // Match all properties var reprop = /\[([^\]]*)\]/gi; var matches = this.pgn.match(reprop); if (matches) { // extract information from each matched property for(var i = 0;i < matches.length; i++) { // lose the brackets tmpMatches = matches[i].substring(1, matches[i].length-1); // split by the first space var key = tmpMatches.substring(0, tmpMatches.indexOf(" ")); var value = tmpMatches.substring(tmpMatches.indexOf(" ")+1); if (value.charAt(0) == '"') value = value.substr(1); if (value.charAt(value.length-1) == '"') value = value.substr(0, value.length-1); this.props[key] = value; this.pgn = this.pgn.replace(matches[i], ""); } } // remove the properties this.pgn = this.pgn.replace(/\[[^\]]*\]/g,''); //trim this.pgn = this.pgn.replace(/^\s+|\s+$/g, ''); var gameOverre = new Array( /1\/2-1\/2/, /0-1/, /1-0/, /\*/ ); // the moves; var themoves = this.pgn.split(" "); var tmp = new Array(); tmp[1] = null; var tmpidx = 0; //make this 1 if FEN and black to move if (this.props["FEN"]) { var fen = this.props['FEN'].split(/\/| /g); if (fen[8] == 'b') { tmpidx = 1; this.skip = 1; } } if (themoves.length>0 && themoves[themoves.length-1] == "...") { themoves = themoves.slice(0, themoves.length-1); } var sizeOfTheMoves = themoves.length; if (themoves.length>0) { for (var i=0;i= '1' && c <= '9') { //move number c = themoves[i].charAt(themoves[i].length-1); if (c == '.') { //ends with . so nothing but a move continue; } var found = false; for (var j=0;j= '0' && c <= '9') { continue; } else { found = true; var idx = j; // 6.0-0 goes wrong as 0 is used for castling if (!(themoves[i].charAt(j) >= '0' && themoves[i].charAt(j)<='9')) { idx = j+1; } themoves[i] = themoves[i].substring(idx); //strip move number break; } } if (!found) { continue; } } tmp[tmpidx] = themoves[i]; if (tmpidx == 1) { //black's move or last move var move = new Move(tmp[0], tmp[1]); this.moves[this.moves.length] = move; tmpidx = 0; tmp = new Array(); tmp[1] = null; } else { tmpidx = 1; } } if (tmp[0] || tmp[1]) { var move = new Move(tmp[0], tmp[1]); this.moves[this.moves.length] = move; } this.nextMove = function() { var rtrn = null; try{ if (this.skip) { this.skip = 0; rtrn = new Array(this.moves[this.currentMove].black, 'black'); this.currentMove++; } else { this.skip = 1; rtrn = new Array(this.moves[this.currentMove].white, 'white'); } if (rtrn[0] == null || rtrn[0].length == 0) rtrn = null; return rtrn; } catch (e) { return null; } }; this.getComment = function(move, idx) { var i = this.pgnStripped.indexOf(move,idx); if (i == -1) { //throw("getComment error, could not find move '" // +move+"'"+", with index '"+idx+"'"); return [null,idx]; } for (var j=i+move.length;jk+1 && this.pgnStripped.charAt(k+1) == '_') { continue; } return [this.pgnRaw.substring(j,k),k]; } } break; default: //no comment return [null,idx]; } } return [null,idx]; }; }; function Move(white, black) { this.white = white; this.black = black; this.toString = function() { return this.white+" "+this.black; }; }; /* Strip game comments from a PGN string. If second parameter set false true then comments will be replaced by an underscore. */ function stripIt(val, strip) { var count = 0; var out = new Array(); for (var i=0;i 0) { if (!strip) { out[out.length] = '_'; } } else { out[out.length] = c; } } } return out.join(""); }; function isPGNBroken(val) { var pCount = 0; var cCount = 0; var lastOne = ""; for (var i=0;i 0) { if (!strip) { out[out.length] = '_'; } } else { out[out.length] = c; } } } return out.join(""); }; /** * Copyright 2008 Toomas Römer * * Licensed under the Apache License, Version 2.0 (the "License") * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. **/ function Board(divId, options) { var pgn = new Pgn(document.getElementById(divId).firstChild.nodeValue); this.conv = new Converter(pgn); this.conv.convert(); this.movesOnPane = new Array(); this.flipped = false; this.id = (new Date()).getTime(); window[this.id] = this; if (!options) options={}; this.moveInput = null; this.lastBold = null; this.lastBoldIdx = null; this.lastSquare = null; this.visuals = {"pgn":{}}; this.opts = []; this.opts['imagePrefix'] = "img/default/"; this.opts['buttonPrefix'] = "img/default/buttons/"; this.opts['imageSuffix'] = 'png'; this.opts['moveFontSize'] = "8pt"; this.opts['moveFontColor'] = "#537c3a"; this.opts['moveFont'] = 'Tahoma, Arial, sans-serif'; this.opts['commentFontSize'] = "8pt"; this.opts['commentFontColor'] = "#6060df"; this.opts['commentFont'] = 'Tahoma, Arial, sans-serif'; this.opts['boardSize'] = '257px'; this.opts['squareSize'] = '31px'; this.opts['blackSqColor'] = "#4b4b4b"; // AH: the white square must be blank in order to use background images this.opts['whiteSqColor'] = "transparent"; this.opts['squareBorder'] = "0px solid #000000"; this.opts['move_highlight_color'] = "#ff9900"; // AH: the board_background_image below would look something like this: url(http://yoururl.com/wp-content/plugins/chess-game-viewer-control-panel/images/boards/32/bamboo.jpg) this.opts['board_background_image'] = ""; this.opts['flipped'] = false; this.opts['showMovesPane'] = true; this.opts['showComments'] = true; this.opts['markLastMove'] = true; this.opts['altRewind'] = "Rewind to the beginning"; this.opts['altBack'] = "One move back"; this.opts['altFlip'] = "Flip the board"; this.opts['altShowMoves'] = "Show moves pane"; this.opts['altComments'] = "Show comments"; this.opts['altPlayMove'] = "Play one move"; this.opts['altFastForward'] = "Fast-forward to the end"; this.opts['moveBorder'] = "0px solid #000000"; this.opts['downloadURL'] = "http://www.chesspastebin.com/asPgn.php?PGN="; this.opts['skipToMove'] = null; var optionNames = ['flipped', 'moveFontSize', 'moveFontColor', 'moveFont', 'commentFontSize', 'commentFontColor', 'commentFont', 'boardSize', 'squareSize', 'blackSqColor', 'whiteSqColor', 'imagePrefix', 'showMovesPane', 'movesPaneWidth','imageSuffix', 'comments','squareBorder', 'markLastMove','altRewind', 'altBack','altFlip','altShowMoves', 'altComments','altPlayMove', 'altFastForward','moveBorder', 'skipToMove', 'downloadURL', 'buttonPrefix', 'move_highlight_color', 'board_background_image']; // AH: added move_highlight_color, and board_background_image options above // if keys in options define new values then // set the this.opts for that key with the // custom value for (var i=0;i2) { var color2 = tmp2%2==0?1:0; tmp2 = Math.round(tmp2/2); this.skipToMove(tmp2-1,color2); } else if (tmp2 == 1) { this.skipToMove(0,0); } else if (tmp2 == 2) { this.skipToMove(0,1); } }catch(e){} } }; flipBoard = function(board) { board.deMarkLastMove(true); var frst, snd, tmp; board.flipped = !board.flipped; for (var i = 0;i<8;i++) { for (var j = 0;j<4;j++){ frst = board.pos[i][j]; snd = board.pos[7-i][7-j]; try { tmp = frst.removeChild(frst.firstChild); } catch (e) {tmp=null} try{ frst.appendChild(snd.removeChild(snd.firstChild)); } catch (e) {} if (tmp) snd.appendChild(tmp); } } }; this.skipToMove = function(no, color) { var rNo = no*2+color+1; if (this.conv.getCurMoveNo()rNo) { var i = 0; while(this.conv.getCurMoveNo()>rNo && i < 200) { makeBwMove(this, true); i++; }; updateMoveInfo(this); updateMovePane(this); this.deMarkLastMove(); this.markLastMove(); } }; endPosition = function(board) { board.deMarkLastMove(); var vBoard = board.conv.getEndPos(board.flipped); board.syncBoard(vBoard);; board.conv.resetToEnd(); updateMoveInfo(board); updateMovePane(board, true); board.markLastMove(); }; this.startPosition = function() { startPosition(this) }; startPosition = function(board) { board.deMarkLastMove(true); var vBoard = board.conv.getStartPos(board.flipped); board.syncBoard(vBoard); board.conv.resetToStart(); updateMoveInfo(board); updateMovePane(board); }; makeBwMove = function(board, noUpdate) { var move = board.conv.prevMove(); if (move == null) return; if (!noUpdate) { board.deMarkLastMove(true); board.markLastMove(); updateMoveInfo(board); updateMovePane(board, true); } for(var i=move.actions.length;i > 1;i-=2) { var frst = move.actions[i-1].clone(); var snd = move.actions[i-2].clone(); var tmpM = new MySquare(); tmpM.piece = frst.piece; tmpM.color = frst.color; frst.piece = snd.piece; frst.color = snd.color; snd.piece = tmpM.piece; snd.color = tmpM.color; frst.piece = move.oPiece; frst.color = move.oColor; if (move.pPiece) snd.piece = move.pPiece; board.drawSquare(frst); board.drawSquare(snd); } if (move.enP) { var x = move.enP.x, y = move.enP.y; if (board.flipped) { x=7-x; y=7-y; } var sq = board.pos[x][y]; sq.appendChild(board.getImg(move.enP.piece, move.enP.color)); } }; this.markLastMove = function() { if (!this.opts['markLastMove']) return; try { var move = this.conv.moves[this.conv.iteIndex-1].actions[1];; var piece = this.pos[move.x][move.y]; if (this.flipped) { piece = this.pos[7-move.x][7-move.y]; } // on konq the bg contains "initial initial initial " // i guess xtra information. Anyways setting the // background to a color containing the "initial" // parts fails. Go figure piece.lastBg = piece.style.backgroundColor.replace(/initial/g, ""); // AH: Changed highlight color below to the variable. piece.style.backgroundColor = this.opts['move_highlight_color']; this.lastSquare = piece; } catch (e) {} }; this.deMarkLastMove = function() { var move = this.conv.moves[this.conv.iteIndex-2]; if (arguments.length && arguments[0]) { move = this.conv.moves[this.conv.iteIndex-1]; } if (this.conv.iteIndex+1 == this.conv.moves.length) move = this.conv.getCurMove(); if (move) { move = move.actions[1]; var piece = this.pos[move.x][move.y]; if (this.flipped) piece = this.pos[7-move.x][7-move.y]; if (piece.lastBg) piece.style.background = piece.lastBg; } if (this.lastSquare && this.lastSquare.lastBg) { this.lastSquare.style.backgroundColor = this.lastSquare.lastBg; this.lastSquare = null; } }; /* Toggle moves pane, actually not toggle but showing it depending the 'flag'. */ this.toggleMoves = function(flag) { if (flag == "flip") flag = this.movesTd.style.visibility=="hidden"; if (flag) { this.movesTd.style.display = "block"; this.movesTd.style.visibility = "visible"; } else { this.movesTd.style.display = "none"; this.movesTd.style.visibility = "hidden"; } }; this.toggleComments = function(flag) { if (flag == "flip") flag = !this.opts['showComments']; if (flag) { this.opts['showComments'] = true; } else { this.opts['showComments'] = false; } var list = this.movesTd.getElementsByTagName("span"); if (list) { for (var i=0;i0) set = arguments[0]; if (arguments.length>1) pref = arguments[1]; var img; for (var i in this.imageNames[set]) { for (var j in this.imageNames[set][i]) { img = new Image(); img.src = this.imageNames[set][i][j]; } } }; };