מדיה ויקי:Gadget-pgnviewer.js: הבדלים בין גרסאות בדף
מראה
שינוי רדיקלי - לא טוענים יותר תמונות, הכל דרך ה-css. זה גם מפשט את הקוד - אין יותר animate (חוץ מהגלילה של הרישום...), ואין movebucket. |
ועכשיו באמת |
||
| שורה 12: | שורה 12: | ||
"use strict"; | "use strict"; | ||
window.mw.hook( 'wikipage.content' ).add( function ( $content ) { | window.mw.hook( 'wikipage.content' ).add( function ( $content ) { | ||
var | var // const, really, but linter... | ||
WHITE = 'l', | WHITE = 'l', | ||
BLACK = 'd', | BLACK = 'd', | ||
acode = 'a'.charCodeAt(0), | acode = 'a'.charCodeAt(0), | ||
minBlockSize = 20, | minBlockSize = 20, | ||
maxBlockSize = 60, | maxBlockSize = 60, | ||
boardPadding = 20, | boardPadding = 20, | ||
wrapperSelector = 'div.pgn-source-wrapper', | wrapperSelector = 'div.pgn-source-wrapper', | ||
defaultBlockSize = 36, | |||
sides = ['n', 'e', 's', 'w'], // used for legends | |||
allFilesAndColumnClasses = "01234567" | |||
.split("") | |||
.map(function(i) { return 'pgn-prow-' + i + ' pgn-pfile-' + i;}) | |||
.join(" "), | |||
rowClassPrefix = 'pgn-prow-', | |||
fileClassPrefix = 'pgn-pfile-', | |||
chessboardClass = 'pgn-board-img', | |||
buttonClass = 'pgn-button', | |||
resetGameButtonClass = 'pgn-button-tostart', | |||
ffButtonClass = 'pgn-button-toend', | |||
advanceButtonClass = 'pgn-button-advance', | |||
retreatButtonClass = 'pgn-button-retreat', | |||
playButtonClass = 'pgn-button-play', | |||
pauseButtonClass = 'pgn-button-pause', | |||
fasterButtonClass = 'pgn-button-faster', | |||
slowerButtonClass = 'pgn-button-slower', | |||
flipBoardButtonClass = 'pgn-button-flip', | |||
ccButtonClass = 'pgn-button-cc'; | |||
var | |||
mw = window.mw, | |||
$ = window.$, | |||
mobile = mw.config.get('skin') === 'minerva'; | |||
// some global, utility functions. | // some global, utility functions. | ||
function bindex(file, row) { return 8 * file + row; } | function bindex(file, row) { return row == undefined ? file : 8 * file + row; } | ||
function file(ind) { return Math.floor(ind / 8);} | function file(ind) { return Math.floor(ind / 8);} | ||
function row(ind) { return ind % 8; } | function row(ind) { return ind % 8; } | ||
| שורה 42: | שורה 53: | ||
function fileOfStr(file) { return file && file.charCodeAt(0) - acode;} | function fileOfStr(file) { return file && file.charCodeAt(0) - acode;} | ||
function rowOfStr(row) { return row && (row - 1);} | function rowOfStr(row) { return row && (row - 1);} | ||
function indexOfMoveNotation(notation) { | |||
var match = notation.match( /(\d+)([ld])/ ); | |||
if (match) return parseInt( match[1] ) + ( match[2] === 'l' ? 1 : 2 ); | |||
return 0; | |||
} | |||
function boardToFen(board) { | function boardToFen(board) { | ||
var res = [], | var res = [], | ||
| שורה 55: | שורה 71: | ||
} | } | ||
function Button( className, action, stateful) { | |||
function Button( | |||
var button = this, | var button = this, | ||
classArr = $.isArray( className ) | |||
? [ className[0], className[1] ] | |||
? [ | : [ className, className ]; | ||
: [ | |||
$.extend( button, { | $.extend( button, { | ||
state: 0, | state: 0, | ||
| שורה 85: | שורה 86: | ||
.toggleClass( 'pgn-image-button-on', state ) | .toggleClass( 'pgn-image-button-on', state ) | ||
.toggleClass( 'pgn-image-button-off', !state ) | .toggleClass( 'pgn-image-button-off', !state ) | ||
. | .removeClass( classArr.join(" ") ) | ||
.addClass( classArr[state] ); | |||
if ( ( !stateful || state != oldState ) && typeof( action ) == 'function' ) | if ( ( !stateful || state != oldState ) && typeof( action ) == 'function' ) | ||
action( state ); | action( state ); | ||
}, | }, | ||
elem: $( '< | elem: $( '<div>' ) | ||
.addClass( 'pgn-image-button pgn-image-button-off ' + classArr[0] ) | |||
.click( function() { | .click( function() { | ||
button.setState( ! button.state ); | button.setState( ! button.state ); | ||
| שורה 103: | שורה 106: | ||
tabberDiv: null, | tabberDiv: null, | ||
blockSize: defaultBlockSize, | blockSize: defaultBlockSize, | ||
allGames: [], | allGames: [], | ||
currentGame: null, | currentGame: null, | ||
| שורה 110: | שורה 112: | ||
faster: function() { | faster: function() { | ||
gameSet.autoPlayDelay -= 500; | gameSet.autoPlayDelay -= 500; | ||
if (gameSet.autoPlayDelay < | if (gameSet.autoPlayDelay < 500) gameSet.autoPlayDelay = 500; | ||
gameSet.reportDelay(); | gameSet.reportDelay(); | ||
}, | }, | ||
slower: function() { | slower: function() { | ||
gameSet.autoPlayDelay += 500; | gameSet.autoPlayDelay += 500; | ||
gameSet.reportDelay(); | gameSet.reportDelay(); | ||
}, | }, | ||
flipButton: new Button( | flipButton: new Button( flipBoardButtonClass, function( state ) { | ||
slowerButton: new Button( | gameSet.doFlip( state ); | ||
autoPlayButton: new Button( [ | }, true ), | ||
fasterButton: new Button( | slowerButton: new Button( slowerButtonClass, function() { gameSet.slower(); } ), | ||
commentsToggleButton: new Button( | autoPlayButton: new Button( [ playButtonClass, pauseButtonClass ], function( state ) { gameSet.toggleAutoPlay( state ); | ||
autoPlayDelay: | }, true ), | ||
fasterButton: new Button( fasterButtonClass, function() { gameSet.faster(); } ), | |||
commentsToggleButton: new Button( ccButtonClass, function( state ) { gameSet.hideComments( state ) }, true ), | |||
autoPlayDelay: 750, | |||
reportDelay: function() { | reportDelay: function() { | ||
gameSet.toggleAutoPlay(); // no param means keep state, but use new delay | |||
var message = gameSet.config.delay_msg | |||
? gameSet.config.delay_msg.replace('$sec$', 0.001 * gameSet.autoPlayDelay) | |||
: 0.001 * gameSet.autoPlayDelay; | |||
mw.notify( message, { tag: 'delay' } ); | |||
// eventurally use config and format better message. | |||
}, | }, | ||
// 6 is half letter size (assuming font-size 0.875em) | // 6 is half letter size (assuming font-size 0.875em) | ||
| שורה 131: | שורה 141: | ||
legendLocation: function(side, num) { | legendLocation: function(side, num) { | ||
var n = 0.5 + num; | var n = 0.5 + num; | ||
switch (side) { | switch (side) { | ||
case 'n': | case 'n': | ||
return {top: 0, left: this.left(num, true)}; | return {top: 0, left: this.left(num, true)}; | ||
| שורה 153: | שורה 163: | ||
this.currentGame = game; | this.currentGame = game; | ||
game.show(); | game.show(); | ||
} | } | ||
}, | }, | ||
| שורה 161: | שורה 170: | ||
}, | }, | ||
drawIfNeedRefresh: function(again) { | drawIfNeedRefresh: function(again) { | ||
if (this.currentGame) | |||
if ( | |||
this.currentGame.drawBoard(); | this.currentGame.drawBoard(); | ||
}, | }, | ||
changeAppearance: function() { | changeAppearance: function() { | ||
this.currentGame.drawBoard(); | this.currentGame.drawBoard(); | ||
this.relocateLegends(); | this.relocateLegends(); | ||
| שורה 180: | שורה 186: | ||
.find( 'div' ).css( { height: mobile ? widthPxPlus : widthPx - 20 } ); | .find( 'div' ).css( { height: mobile ? widthPxPlus : widthPx - 20 } ); | ||
this.blockSize = width; | this.blockSize = width; | ||
this. | this.piecesDiv.css({width: widthPx, height: widthPx}); | ||
this | this.boardDiv.css({width: widthPxPlus, height: widthPxPlus}); | ||
this.changeAppearance(); | this.changeAppearance(); | ||
}, | }, | ||
hideComments: function( state ) { | hideComments: function( state ) { | ||
| שורה 190: | שורה 195: | ||
doFlip: function( state ) { | doFlip: function( state ) { | ||
this.changeAppearance(); | this.changeAppearance(); | ||
}, | }, | ||
toggleAutoPlay: function( state ) { | toggleAutoPlay: function( state ) { | ||
clearInterval(this.timer); | |||
if (state == undefined) state = gameSet.autoPlayButton.state; | |||
if ( state ) { | |||
this.currentGame.wrapAround(); | |||
this.timer = setInterval(function() { gameSet.currentGame.advance() }, gameSet.autoPlayDelay); | |||
} else gameSet.autoPlayButton.setState( false ); | |||
} | } | ||
}); | }); | ||
| שורה 221: | שורה 211: | ||
this.type = type; | this.type = type; | ||
this.color = color; | this.color = color; | ||
this. | this.avatar = $('<div>') | ||
.addClass('pgn-chessPiece pgn-ptype-color-' + type + color); | |||
} | } | ||
ChessPiece.prototype.appear = function(file, row) { | ChessPiece.prototype.appear = function(file, row) { | ||
if (this.game.gs.flipButton.state) { | |||
. | file = 7 - file; | ||
row = 7 - row; | |||
} | |||
this.avatar | |||
.removeClass('pgn-piece-hidden ' + allFilesAndColumnClasses ) | |||
.addClass('pgn-piece-visible pgn-prow-' + row + ' pgn-pfile-' + file); | |||
}; | }; | ||
ChessPiece.prototype. | ChessPiece.prototype.disappear = function() { | ||
return this.avatar | |||
.removeClass('pgn-piece-visible') | |||
.addClass('pgn-piece-hidden'); | |||
}; | }; | ||
ChessPiece.prototype.setSquare = function(file, row) { | ChessPiece.prototype.setSquare = function(file, row) { | ||
| שורה 260: | שורה 249: | ||
this.game.clearSquare(this.file, this.row); | this.game.clearSquare(this.file, this.row); | ||
this.game.pieceAt(file, row, this); // place it on the board) | this.game.pieceAt(file, row, this); // place it on the board) | ||
}; | }; | ||
| שורה 304: | שורה 292: | ||
}; | }; | ||
function Game(gameSet) { | |||
function Game( | |||
$.extend(this, { | $.extend(this, { | ||
board: [], | board: [], | ||
boards: [], | boards: [], | ||
pieces: [], | pieces: [], | ||
notations: [], | |||
moves: [], | moves: [], | ||
index: 0, | index: 0, | ||
piecesByTypeCol: {}, | piecesByTypeCol: {}, | ||
| שורה 330: | שורה 304: | ||
comments: [], | comments: [], | ||
analyzed: false, | analyzed: false, | ||
gs: gameSet}); | gs: gameSet}); | ||
} | } | ||
| שורה 378: | שורה 351: | ||
rtl = desc['Direction'] == 'rtl', | rtl = desc['Direction'] == 'rtl', | ||
num = '', | num = '', | ||
gs = this.gs; | |||
// cleanup from previous game. | // cleanup from previous game. | ||
gs.toggleAutoPlay( false ); | |||
// setup descriptions | // setup descriptions | ||
delete desc['Direction']; | delete desc['Direction']; | ||
gs.descriptionsDiv | |||
$.each(desc, function(key, val) { | .empty() | ||
.css( { direction: rtl ? 'rtl' : 'ltr', textAlign: rtl ? 'right' : 'left' } ); | |||
$.each(desc, function(key, val) { gs.descriptionsDiv.append(key + ': ' + val + '<br />'); }); | |||
// setup pgn section | // setup pgn section | ||
gs.pgnDiv.empty().append( this.notations ); | |||
// set the board. | // set the board. | ||
var hiddenAvatars = this.pieces.map( function(piece) { return piece.disappear(); } ); | |||
this. | this.gs.piecesDiv.empty().append( hiddenAvatars ); | ||
this. | |||
this.drawBoard(); | |||
} | } | ||
Game.prototype.done = function() { return this. | Game.prototype.done = function() { return this.boards.length - 1 <= this.index; } | ||
Game.prototype.pieceAt = function(file, row, piece) { | Game.prototype.pieceAt = function(file, row, piece) { | ||
| שורה 445: | שורה 394: | ||
piece.remove(); | piece.remove(); | ||
this.clearSquare(file, row); | this.clearSquare(file, row); | ||
} | } | ||
| שורה 451: | שורה 399: | ||
var file = file1, | var file = file1, | ||
row = row1, | row = row1, | ||
steps = 0, | |||
dfile = sign(file1, file2), | dfile = sign(file1, file2), | ||
drow = sign(row1, row2); | drow = sign(row1, row2); | ||
| שורה 461: | שורה 409: | ||
if (this.pieceAt(file, row)) | if (this.pieceAt(file, row)) | ||
return false; | return false; | ||
if ( | if (steps++ > 10) | ||
throw 'something is wrong in function roadIsClear.' + | throw 'something is wrong in function roadIsClear.' + | ||
' file=' + file + ' file1=' + file1 + ' file2=' + file2 + | ' file=' + file + ' file1=' + file1 + ' file2=' + file2 + | ||
| שורה 480: | שורה 428: | ||
byTypeCol.push(piece); | byTypeCol.push(piece); | ||
}; | }; | ||
Game.prototype.advance = function(delta) { | Game.prototype.advance = function(delta) { | ||
var m = this.index + (delta || 1); // no param means 1 forward. | var m = this.index + (delta || 1); // no param means 1 forward. | ||
if (0 <= m && m < this. | if (0 <= m && m < this.boards.length) { | ||
this. | this.drawBoard(m); | ||
} else this.gs.toggleAutoPlay(false); | |||
} | } | ||
Game.prototype.showCurrentMoveLink = function() { | Game.prototype.showCurrentMoveLink = function() { | ||
var moveLink = this. | var moveLink = this.moves[this.index]; | ||
if (moveLink) { | if (moveLink) { | ||
moveLink.addClass('pgn-current-move').siblings().removeClass('pgn-current-move'); | moveLink.addClass('pgn-current-move').siblings().removeClass('pgn-current-move'); | ||
| שורה 511: | שורה 447: | ||
} | } | ||
Game.prototype. | Game.prototype.drawBoard = function(index) { | ||
if ( index == undefined) index = this.index; | |||
if ( | if ( index < 0 ) index += this.boards.length; | ||
this.index = index; | |||
var board = this.boards[index]; | |||
for (var i in this.pieces) this.pieces[i].disappear(); | |||
for (var i in board) if (board[i]) board[i].appear(file(i), row(i)); | |||
this.showCurrentMoveLink(); | this.showCurrentMoveLink(); | ||
this.gs.refreshFEN(); | this.gs.refreshFEN(); | ||
} | } | ||
Game.prototype.wrapAround = function() { | Game.prototype.wrapAround = function() { | ||
if (this.index >= this.boards.length - 1) | if (this.index >= this.boards.length - 1) | ||
this. | this.drawBoard(0); | ||
} | } | ||
| שורה 561: | שורה 488: | ||
this.clearPieceAt(file, row); | this.clearPieceAt(file, row); | ||
var newPiece = this.createPiece(type, piece.color, file, row); | var newPiece = this.createPiece(type, piece.color, file, row); | ||
} | } | ||
| שורה 582: | שורה 508: | ||
return moveStr; // end of game - white wins, black wins, draw, game halted/abandoned/unknown. | return moveStr; // end of game - white wins, black wins, draw, game halted/abandoned/unknown. | ||
var match = moveStr.match(/([RNBKQ])?([a-h])?([1-8])?(x)?([a-h])([1-8])(=[RNBKQ])?/); | var match = moveStr.match(/([RNBKQ])?([a-h])?([1-8])?(x)?([a-h])([1-8])(=[RNBKQ])?/); | ||
if (!match) | |||
if (!match) return false; | |||
var type = match[1] ? match[1].toLowerCase() : 'p', | var type = match[1] ? match[1].toLowerCase() : 'p', | ||
| שורה 602: | שורה 527: | ||
for (var i = 0; i < 2; i++) { | for (var i = 0; i < 2; i++) { | ||
var piece = thePiece[i]; | var piece = thePiece[i]; | ||
// lift the piece, check if the king is under threat | |||
delete this.board[bindex(piece.file, piece.row)]; | delete this.board[bindex(piece.file, piece.row)]; | ||
for (var j in this.board) { | for (var j in this.board) { | ||
var threat = this.board[j]; | var threat = this.board[j]; | ||
if (threat && threat.color != color && threat.canMoveTo(king.file, king.row, true)) { | if (threat && threat.color != color && threat.canMoveTo(king.file, king.row, true)) { // found that this piece can't move, so it's the other one... | ||
ok = true; | ok = true; | ||
thePiece = thePiece[1-i]; | thePiece = thePiece[1-i]; | ||
| שורה 611: | שורה 537: | ||
} | } | ||
} | } | ||
this. | // put the piece back in place | ||
this.board[bindex( piece.file, piece.row )] = piece; | |||
if (ok) | if (ok) | ||
break; | break; | ||
| שורה 630: | שורה 557: | ||
return moveStr; | return moveStr; | ||
} | } | ||
Game.prototype.addComment = function(str) { | Game.prototype.addComment = function(str) { | ||
if ( str ) | if ( ! str ) return; | ||
this.notations.push( $( '<p>' ) | |||
.addClass( 'pgn-comment' ) | |||
.text( str.replace( /[{}()]/g, '' ) ) | |||
); | |||
} | } | ||
| שורה 651: | שורה 582: | ||
} | } | ||
Game.prototype.preAnalyzePgn = function(pgn) { | Game.prototype.preAnalyzePgn = function(pgn) { | ||
function tryMatch(regex) { | function tryMatch(regex) { | ||
| שורה 677: | שורה 602: | ||
match, | match, | ||
turn, | turn, | ||
moveNum = '', | moveNum = '', | ||
game = this, | |||
pgn = this.pgn; | pgn = this.pgn; | ||
| שורה 694: | שורה 619: | ||
} | } | ||
return rmatch && rmatch[0]; | return rmatch && rmatch[0]; | ||
} | |||
function addMoveLink(str, isMove) { | |||
var notation = $( '<span>' ) | |||
.addClass( isMove ? 'pgn-movelink' : 'pgn-steplink' ) | |||
.text( str ) | |||
game.notations.push( notation ); | |||
if ( isMove ) { | |||
game.boards.push( game.board.slice() ); | |||
game.moves.push( notation ); | |||
} else if ( game.moves.length == 0 ) | |||
game.moves.push( notation ); | |||
var index = game.boards.length - 1; | |||
notation.on( 'click', function() { game.drawBoard(index); } ); | |||
} | } | ||
pgn = pgn.replace(/;(.*)\n/g, ' {$1} ').replace(/\s+/g, ' '); // replace to-end-of-line comments with block comments, remove newlines and noramlize spaces to 1 | pgn = pgn.replace(/;(.*)\n/g, ' {$1} ').replace(/\s+/g, ' '); // replace to-end-of-line comments with block comments, remove newlines and noramlize spaces to 1 | ||
this.populateBoard( this.descriptions.FEN || 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR' ); | this.populateBoard( this.descriptions.FEN || 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR' ); | ||
this.boards.push(this.board.slice()); | |||
var prevLen = -1; | var prevLen = -1; | ||
while (pgn.length) { | while (pgn.length) { | ||
if (prevLen == pgn.length) | if (prevLen == pgn.length) | ||
| שורה 707: | שורה 649: | ||
this.addComment( tryMatch( /^\s*\([^\)]*\)\s*/ ) ); | this.addComment( tryMatch( /^\s*\([^\)]*\)\s*/ ) ); | ||
if (match = tryMatch(/^\s*(\d+)\.+/)) { | if (match = tryMatch(/^\s*(\d+)\.+/)) { | ||
turn = /\. | turn = /\.{3}/.test(match) ? BLACK : WHITE; | ||
addMoveLink(match, false); | |||
continue; | continue; | ||
} | } | ||
if (match = tryMatch(/^\s*[^ ]+ ?/)) { | if (match = tryMatch(/^\s*[^ ]+ ?/)) { | ||
this.createMove(turn, match); | this.createMove(turn, match); | ||
addMoveLink(match, true); | |||
turn = BLACK; | turn = BLACK; | ||
} | } | ||
} | } | ||
this.index = this.descriptions.FirstMove | |||
? indexOfMoveNotation(this.descriptions.FirstMove) | |||
: this.descriptions.FEN ? 0 : this.boards.length - 1; | |||
} | } | ||
| שורה 745: | שורה 688: | ||
} | } | ||
function buildBoardDiv(container, selector, gameSet, ind) { | function buildBoardDiv(container, selector, gameSet, ind) { | ||
var | var | ||
| שורה 776: | שורה 695: | ||
infoId = 'pgn-info-' + id, | infoId = 'pgn-info-' + id, | ||
fenId = 'pgn-fen-' + id, | fenId = 'pgn-fen-' + id, | ||
controlsDiv, | controlsDiv, | ||
gotoend = | gotoend = new Button( ffButtonClass, | ||
forward = | function() { gameSet.currentGame.drawBoard( -1 ) } ), | ||
backstep = | forward = new Button( advanceButtonClass, | ||
gotostart = | function() { gameSet.currentGame.advance( 1 ) } ), | ||
backstep = new Button( retreatButtonClass, | |||
function() { gameSet.currentGame.advance( -1 ) } ), | |||
gotostart = new Button( resetGameButtonClass, | |||
function() { gameSet.currentGame.drawBoard( 0 ) } ), | |||
collapsibleDiv = $( '<div>' ), // residue. to be renamed or remove | collapsibleDiv = $( '<div>' ), // residue. to be renamed or remove | ||
tabnames = $.extend( { | tabnames = $.extend( { | ||
| שורה 788: | שורה 710: | ||
fen: 'FEN' }, | fen: 'FEN' }, | ||
config.tab_names ); | config.tab_names ); | ||
gameSet. | gameSet.descriptionsDiv = $('<div>', {'class': 'pgn-descriptions', id: infoId }) | ||
gameSet.fixedDelay = 'delay' in config; | gameSet.fixedDelay = 'delay' in config; | ||
if ( gameSet.fixedDelay ) gameSet.autoPlayDelay = config.delay; | if ( gameSet.fixedDelay ) gameSet.autoPlayDelay = Math.max(500, config.delay); | ||
if ( $( '#' + notationId ) ) notationId += '_1'; | if ( $( '#' + notationId ) ) notationId += '_1'; | ||
| שורה 811: | שורה 732: | ||
) | ) | ||
.append( gameSet.pgnDiv ) | .append( gameSet.pgnDiv ) | ||
.append( descriptionsDiv ) | .append( gameSet.descriptionsDiv ) | ||
.append( gameSet.fenDiv ) | .append( gameSet.fenDiv ) | ||
.tabs(); | .tabs(); | ||
| שורה 829: | שורה 750: | ||
; | ; | ||
gameSet.boardDiv = $('<div>' | gameSet.boardDiv = $('<div>') | ||
gameSet. | .addClass('pgn-board-div'); | ||
. | |||
gameSet.piecesDiv = $('<div>').css({position:'absolute', left: '20px', top: '20px' }) | |||
.addClass( chessboardClass ) | |||
.appendTo(gameSet.boardDiv); | .appendTo(gameSet.boardDiv); | ||
| שורה 866: | שורה 789: | ||
.append(controlsDiv) | .append(controlsDiv) | ||
); | ); | ||
} | } | ||
| שורה 873: | שורה 795: | ||
var | var | ||
wrapperDiv = $(this), | wrapperDiv = $(this), | ||
initial = wrapperDiv.text(), | |||
pgnSource = $('div.pgn-sourcegame', wrapperDiv), | pgnSource = $('div.pgn-sourcegame', wrapperDiv), | ||
boardDiv, | boardDiv, | ||
selector, | selector, | ||
gameSet = new Gameset( wrapperDiv ); | gameSet = new Gameset( wrapperDiv ); | ||
try { | |||
if (pgnSource.length > 1) | |||
selector = $('<select>', { 'class': 'pgn-selector' } ).data({gameSet: gameSet}).change(selectGame); | |||
buildBoardDiv(wrapperDiv, selector, gameSet, ind); | |||
var game, ind = 0; | |||
pgnSource.each(function() { | |||
try { | |||
game = new Game(gameSet); | |||
game.preAnalyzePgn($(this).text()); | |||
wrapperDiv.data({currentGame: game}); | |||
ind++; | |||
gameSet.allGames.push(game); | |||
if (selector) | |||
selector.append($('<option>', {value: gameSet.allGames.length - 1, text: game.description()}) | |||
.css('direction', game.descriptions['Direction'] || 'ltr') | |||
); | |||
} catch (e) { | |||
mw.log('exception in game ' + ind + ' problem is: "' + e + '"'); | |||
if (game && game.descriptions) | |||
for (var d in game.descriptions) | |||
mw.log(d + ':' + game.descriptions[d]); | |||
} | |||
}); | |||
gameSet.selectGame(0); | |||
gameSet.setWidth(); | |||
} catch(e) { | |||
console.error(e); | |||
console.error( 'exception analyzing game :', initial ); | |||
wrapperDiv.empty(); | |||
} | |||
}) | }) | ||
} | } | ||
if ( $(wrapperSelector, $content).length) { | |||
if ( mobile ) | |||
doIt(); | |||
else | |||
mw.loader.using( 'jquery.ui.tabs' ).done( doIt ); | |||
} | } | ||
}); | }); | ||
גרסה מ־02:58, 18 באפריל 2019
/*
this work is placed by its authors in the public domain.
it was created from scratch, and no part of it was copied from elsewhere.
it can be used, copied, modified, redistributed, as-is or modified,
whole or in part, without restrictions.
it can be embedded in a copyright protected work, as long as it's clear
that the copyright does not apply to the embedded parts themselves.
please do not claim for yourself copyrights for this work or parts of it.
the work comes with no warranty or guarantee, stated or implied, including
fitness for a particular purpose.
*/
"use strict";
window.mw.hook( 'wikipage.content' ).add( function ( $content ) {
var // const, really, but linter...
WHITE = 'l',
BLACK = 'd',
acode = 'a'.charCodeAt(0),
minBlockSize = 20,
maxBlockSize = 60,
boardPadding = 20,
wrapperSelector = 'div.pgn-source-wrapper',
defaultBlockSize = 36,
sides = ['n', 'e', 's', 'w'], // used for legends
allFilesAndColumnClasses = "01234567"
.split("")
.map(function(i) { return 'pgn-prow-' + i + ' pgn-pfile-' + i;})
.join(" "),
rowClassPrefix = 'pgn-prow-',
fileClassPrefix = 'pgn-pfile-',
chessboardClass = 'pgn-board-img',
buttonClass = 'pgn-button',
resetGameButtonClass = 'pgn-button-tostart',
ffButtonClass = 'pgn-button-toend',
advanceButtonClass = 'pgn-button-advance',
retreatButtonClass = 'pgn-button-retreat',
playButtonClass = 'pgn-button-play',
pauseButtonClass = 'pgn-button-pause',
fasterButtonClass = 'pgn-button-faster',
slowerButtonClass = 'pgn-button-slower',
flipBoardButtonClass = 'pgn-button-flip',
ccButtonClass = 'pgn-button-cc';
var
mw = window.mw,
$ = window.$,
mobile = mw.config.get('skin') === 'minerva';
// some global, utility functions.
function bindex(file, row) { return row == undefined ? file : 8 * file + row; }
function file(ind) { return Math.floor(ind / 8);}
function row(ind) { return ind % 8; }
function sign(a, b) { return a == b ? 0 : (a < b ? 1 : -1); }
function fileOfStr(file) { return file && file.charCodeAt(0) - acode;}
function rowOfStr(row) { return row && (row - 1);}
function indexOfMoveNotation(notation) {
var match = notation.match( /(\d+)([ld])/ );
if (match) return parseInt( match[1] ) + ( match[2] === 'l' ? 1 : 2 );
return 0;
}
function boardToFen(board) {
var res = [],
len = function(s) { return s.length; };
for (var r = 0; r < 8; r++) {
var row = '';
for (var f = 0; f < 8; f++)
row += board[bindex(f, r)] ? board[bindex(f, r)].fen() : ' ';
res.push(row.replace(/(\s+)/g, len));
}
return res.reverse().join('/'); // fen begins with row 8 file a - go figure...
}
function Button( className, action, stateful) {
var button = this,
classArr = $.isArray( className )
? [ className[0], className[1] ]
: [ className, className ];
$.extend( button, {
state: 0,
setState: function( state ) {
var oldState = this.state;
state = 0 + !!state;
this.state = state;
if ( stateful )
this.elem
.toggleClass( 'pgn-image-button-on', state )
.toggleClass( 'pgn-image-button-off', !state )
.removeClass( classArr.join(" ") )
.addClass( classArr[state] );
if ( ( !stateful || state != oldState ) && typeof( action ) == 'function' )
action( state );
},
elem: $( '<div>' )
.addClass( 'pgn-image-button pgn-image-button-off ' + classArr[0] )
.click( function() {
button.setState( ! button.state );
} )
} );
}
function Gameset( wrapperDiv ) { // set of functions and features that depend on blocksize, and currentGame.
var gameSet = this;
$.extend(gameSet, {
wrapperDiv: wrapperDiv,
tabberDiv: null,
blockSize: defaultBlockSize,
allGames: [],
currentGame: null,
showDetails:false,
timer: null,
faster: function() {
gameSet.autoPlayDelay -= 500;
if (gameSet.autoPlayDelay < 500) gameSet.autoPlayDelay = 500;
gameSet.reportDelay();
},
slower: function() {
gameSet.autoPlayDelay += 500;
gameSet.reportDelay();
},
flipButton: new Button( flipBoardButtonClass, function( state ) {
gameSet.doFlip( state );
}, true ),
slowerButton: new Button( slowerButtonClass, function() { gameSet.slower(); } ),
autoPlayButton: new Button( [ playButtonClass, pauseButtonClass ], function( state ) { gameSet.toggleAutoPlay( state );
}, true ),
fasterButton: new Button( fasterButtonClass, function() { gameSet.faster(); } ),
commentsToggleButton: new Button( ccButtonClass, function( state ) { gameSet.hideComments( state ) }, true ),
autoPlayDelay: 750,
reportDelay: function() {
gameSet.toggleAutoPlay(); // no param means keep state, but use new delay
var message = gameSet.config.delay_msg
? gameSet.config.delay_msg.replace('$sec$', 0.001 * gameSet.autoPlayDelay)
: 0.001 * gameSet.autoPlayDelay;
mw.notify( message, { tag: 'delay' } );
// eventurally use config and format better message.
},
// 6 is half letter size (assuming font-size 0.875em)
top: function(row, l) { return (((this.flipButton.state ? row : (7 - row)) + (l ? 0.3 : 0)) * this.blockSize + (l? boardPadding-6: boardPadding)) + 'px'; },
left: function(file, l) { return (((this.flipButton.state ? 7 - file : file) + (l ? 0.5 : 0)) * this.blockSize + (l? boardPadding-6: boardPadding)) + 'px'; },
legendLocation: function(side, num) {
var n = 0.5 + num;
switch (side) {
case 'n':
return {top: 0, left: this.left(num, true)};
case 'e':
return {top: this.top(num, true), left: this.blockSize * 8 + boardPadding + 5};
case 's':
return {top: this.blockSize * 8 + 20, left: this.left(num, true)};
case 'w':
return {top: this.top(num, true), left: 5};
}
},
relocateLegends: function() {
for (var si in sides)
for (var n = 0; n < 8; n++)
this[sides[si]][n].css(this.legendLocation(sides[si], n));
},
selectGame: function(val) {
var game = this.allGames[val];
if (game) {
game.analyzePgn();
this.currentGame = game;
game.show();
}
},
refreshFEN: function() {
var board = this.currentGame.boards[this.currentGame.index];
this.fenDiv.text( boardToFen(board) );
},
drawIfNeedRefresh: function(again) {
if (this.currentGame)
this.currentGame.drawBoard();
},
changeAppearance: function() {
this.currentGame.drawBoard();
this.relocateLegends();
},
setWidth: function(width) {
width = width || this.blockSize;
var
widthPx = width * 8,
widthPxPlus = widthPx + 40;
this.tabberDiv // disgusting, but i could not get heightStyle of jquery tabs to do what i need.
.css( { height: widthPxPlus } )
.find( 'div' ).css( { height: mobile ? widthPxPlus : widthPx - 20 } );
this.blockSize = width;
this.piecesDiv.css({width: widthPx, height: widthPx});
this.boardDiv.css({width: widthPxPlus, height: widthPxPlus});
this.changeAppearance();
},
hideComments: function( state ) {
this.wrapperDiv.toggleClass( 'pgn-comments-hidden', state );
},
doFlip: function( state ) {
this.changeAppearance();
},
toggleAutoPlay: function( state ) {
clearInterval(this.timer);
if (state == undefined) state = gameSet.autoPlayButton.state;
if ( state ) {
this.currentGame.wrapAround();
this.timer = setInterval(function() { gameSet.currentGame.advance() }, gameSet.autoPlayDelay);
} else gameSet.autoPlayButton.setState( false );
}
});
}
function ChessPiece(type, color, game) {
this.game = game;
this.type = type;
this.color = color;
this.avatar = $('<div>')
.addClass('pgn-chessPiece pgn-ptype-color-' + type + color);
}
ChessPiece.prototype.appear = function(file, row) {
if (this.game.gs.flipButton.state) {
file = 7 - file;
row = 7 - row;
}
this.avatar
.removeClass('pgn-piece-hidden ' + allFilesAndColumnClasses )
.addClass('pgn-piece-visible pgn-prow-' + row + ' pgn-pfile-' + file);
};
ChessPiece.prototype.disappear = function() {
return this.avatar
.removeClass('pgn-piece-visible')
.addClass('pgn-piece-hidden');
};
ChessPiece.prototype.setSquare = function(file, row) {
this.file = file;
this.row = row;
this.onBoard = true;
};
ChessPiece.prototype.capture = function(file, row) {
if (this.type == 'p' && !this.game.pieceAt(file, row)) // en passant
this.game.clearPieceAt(file, this.row);
else
this.game.clearPieceAt(file, row);
this.move(file, row);
};
ChessPiece.prototype.move = function(file, row) {
if ( this.game.pieceAt( this.file, this.row ) == this ) // with chess960 castling, we sometimes have to test.
this.game.clearSquare(this.file, this.row);
this.game.pieceAt(file, row, this); // place it on the board)
};
ChessPiece.prototype.pawnDirection = function() { return this.color == WHITE ? 1 : -1; };
ChessPiece.prototype.toString = function(fen) { return this.type + this.color; };
ChessPiece.prototype.fen = function() { return this.color == WHITE ? this.type.toUpperCase() : this.type; };
ChessPiece.prototype.pawnStart = function() { return this.color == WHITE ? 1 : 6; };
ChessPiece.prototype.remove = function() { this.onBoard = false; };
ChessPiece.prototype.canMoveTo = function(file, row, capture) {
if (!this.onBoard)
return false;
var rd = Math.abs(this.row - row), fd = Math.abs(this.file - file);
switch(this.type) {
case 'n':
return rd * fd == 2; // how nice that 2 is prime: its only factors are 2 and 1....
case 'p':
var dir = this.pawnDirection();
return (
((this.row == this.pawnStart() && row == this.row + dir * 2 && !fd && this.game.roadIsClear(this.file, file, this.row, row) && !capture)
|| (this.row + dir == row && fd == !!capture))); // advance 1, and either stay in file and no capture, or move exactly one
case 'k':
return (rd | fd) == 1; // we'll accept 1 and 1 or 1 and 0.
case 'q':
return (rd - fd) * rd * fd == 0 && this.game.roadIsClear(this.file, file, this.row, row); // same row, same file or same diagonal.
case 'r':
return rd * fd == 0 && this.game.roadIsClear(this.file, file, this.row, row);
case 'b':
return rd == fd && this.game.roadIsClear(this.file, file, this.row, row);
}
};
ChessPiece.prototype.matches = function(oldFile, oldRow, isCapture, file, row) {
if (typeof oldFile == 'number' && oldFile != this.file)
return false;
if (typeof oldRow == 'number' && oldRow != this.row)
return false;
return this.canMoveTo(file, row, isCapture);
};
function Game(gameSet) {
$.extend(this, {
board: [],
boards: [],
pieces: [],
notations: [],
moves: [],
index: 0,
piecesByTypeCol: {},
descriptions: {},
comments: [],
analyzed: false,
gs: gameSet});
}
Game.prototype.moveLinkText = function(afterCommentNum, move) {
var config = this.gs.config,
notation = move.s,
turn = move.turn,
piece = config && config.translate && config.translate.piece,
file = config && config.translate && config.translate.file,
row = config && config.translate && config.translate.row;
if (piece) {
piece = turn == 'l' && piece.white
|| turn == 'd' && piece.black
|| piece;
try {
var regex = new RegExp('(' + Object.keys(piece).join("|") + ')', 'g');
notation = notation.replace(regex, function(c) { return piece[c] || c; } );
} catch (e) {
mw.log('bad config.translate.pieces');
throw e;
}
}
if (file) {
try {
notation = notation.replace(/[abcdefgh]/g, function(c) { return file[c] || c; } );
} catch (e) {
mw.log('bad config.translate.file');
throw e;
}
}
if (row) {
try {
notation = notation.replace(/[12345678]/g, function(c) { return row[c] || c; } );
} catch (e) {
mw.log('bad config.translate.row');
throw e;
}
}
return afterCommentNum + notation.replace(/-/g, '\u2011'); // replace hyphen with unbreakable
}
Game.prototype.show = function() {
var desc = $.extend({}, this.descriptions),
rtl = desc['Direction'] == 'rtl',
num = '',
gs = this.gs;
// cleanup from previous game.
gs.toggleAutoPlay( false );
// setup descriptions
delete desc['Direction'];
gs.descriptionsDiv
.empty()
.css( { direction: rtl ? 'rtl' : 'ltr', textAlign: rtl ? 'right' : 'left' } );
$.each(desc, function(key, val) { gs.descriptionsDiv.append(key + ': ' + val + '<br />'); });
// setup pgn section
gs.pgnDiv.empty().append( this.notations );
// set the board.
var hiddenAvatars = this.pieces.map( function(piece) { return piece.disappear(); } );
this.gs.piecesDiv.empty().append( hiddenAvatars );
this.drawBoard();
}
Game.prototype.done = function() { return this.boards.length - 1 <= this.index; }
Game.prototype.pieceAt = function(file, row, piece) {
var i = bindex(file, row);
if (piece) {
this.board[i] = piece;
piece.setSquare(file, row);
}
return this.board[i];
}
Game.prototype.clearSquare = function(file, row) {
delete this.board[bindex(file, row)];
}
Game.prototype.clearPieceAt = function(file, row) {
var piece = this.pieceAt(file, row);
if (piece)
piece.remove();
this.clearSquare(file, row);
}
Game.prototype.roadIsClear = function(file1, file2, row1, row2) {
var file = file1,
row = row1,
steps = 0,
dfile = sign(file1, file2),
drow = sign(row1, row2);
while (true) {
file += dfile;
row += drow;
if (file == file2 && row == row2)
return true;
if (this.pieceAt(file, row))
return false;
if (steps++ > 10)
throw 'something is wrong in function roadIsClear.' +
' file=' + file + ' file1=' + file1 + ' file2=' + file2 +
' row=' + row + ' row1=' + row1 + ' row2=' + row2 +
' dfile=' + dfile + ' drow=' + drow;
}
};
Game.prototype.addPieceToDicts = function(piece) {
this.pieces.push(piece);
var type = piece.type, color = piece.color;
var byType = this.piecesByTypeCol[type];
if (! byType)
byType = this.piecesByTypeCol[type] = {};
var byTypeCol = byType[color];
if (!byTypeCol)
byTypeCol = byType[color] = [];
byTypeCol.push(piece);
};
Game.prototype.advance = function(delta) {
var m = this.index + (delta || 1); // no param means 1 forward.
if (0 <= m && m < this.boards.length) {
this.drawBoard(m);
} else this.gs.toggleAutoPlay(false);
}
Game.prototype.showCurrentMoveLink = function() {
var moveLink = this.moves[this.index];
if (moveLink) {
moveLink.addClass('pgn-current-move').siblings().removeClass('pgn-current-move');
var wannabe = moveLink.parent().height() / 2,
isNow = moveLink.position().top,
newScrolltop = moveLink.parent()[0].scrollTop + isNow - wannabe;
moveLink.parent().stop().animate({scrollTop: newScrolltop}, 500);
}
}
Game.prototype.drawBoard = function(index) {
if ( index == undefined) index = this.index;
if ( index < 0 ) index += this.boards.length;
this.index = index;
var board = this.boards[index];
for (var i in this.pieces) this.pieces[i].disappear();
for (var i in board) if (board[i]) board[i].appear(file(i), row(i));
this.showCurrentMoveLink();
this.gs.refreshFEN();
}
Game.prototype.wrapAround = function() {
if (this.index >= this.boards.length - 1)
this.drawBoard(0);
}
Game.prototype.castle = function( color, side, kingTargetFile, rookTargetFile ) {
var king = this.piecesByTypeCol['k'][color][0],
rook = this.piecesByTypeCol['r'][color][side];
if (!rook || rook.type != 'r')
throw 'attempt to castle without rook on appropriate square';
king.move(fileOfStr(kingTargetFile), king.row);
rook.move(fileOfStr(rookTargetFile), rook.row);
}
Game.prototype.kingSideCastle = function(color) {
this.castle( color, 1, 'g', 'f' );
}
Game.prototype.queenSideCastle = function(color) {
this.castle( color, 0, 'c', 'd' );
}
Game.prototype.promote = function(piece, type, file, row, capture) {
piece[capture ? 'capture' : 'move'](file, row);
this.clearPieceAt(file, row);
var newPiece = this.createPiece(type, piece.color, file, row);
}
Game.prototype.createPiece = function(type, color, file, row) {
var piece = new ChessPiece(type, color, this);
this.pieceAt(file, row, piece);
this.addPieceToDicts(piece);
return piece;
}
Game.prototype.createMove = function(color, moveStr) {
moveStr = moveStr.replace(/^\s+|[!?+# ]*(\$\d{1,3})?$/g, ''); // check, mate, comments, glyphs.
if (!moveStr.length)
return false;
if ($.inArray(moveStr, ['O-O', '0-0']) + 1)
return this.kingSideCastle(color);
if ($.inArray(moveStr, ['O-O-O', '0-0-0']) + 1)
return this.queenSideCastle(color);
if ($.inArray(moveStr, ['1-0', '0-1', '1/2-1/2', '*']) + 1)
return moveStr; // end of game - white wins, black wins, draw, game halted/abandoned/unknown.
var match = moveStr.match(/([RNBKQ])?([a-h])?([1-8])?(x)?([a-h])([1-8])(=[RNBKQ])?/);
if (!match) return false;
var type = match[1] ? match[1].toLowerCase() : 'p',
oldFile = fileOfStr(match[2]),
oldRow = rowOfStr(match[3]),
isCapture = !!match[4],
file = fileOfStr(match[5]),
row = rowOfStr(match[6]),
promotion = match[7],
thePiece = $(this.piecesByTypeCol[type][color]).filter(function() {
return this.matches(oldFile, oldRow, isCapture, file, row);
});
if (thePiece.length != 1) {
var ok = false;
if (thePiece.length == 2) { // maybe one of them can't move because it protects the king?
var king = this.piecesByTypeCol['k'][color][0];
for (var i = 0; i < 2; i++) {
var piece = thePiece[i];
// lift the piece, check if the king is under threat
delete this.board[bindex(piece.file, piece.row)];
for (var j in this.board) {
var threat = this.board[j];
if (threat && threat.color != color && threat.canMoveTo(king.file, king.row, true)) { // found that this piece can't move, so it's the other one...
ok = true;
thePiece = thePiece[1-i];
break;
}
}
// put the piece back in place
this.board[bindex( piece.file, piece.row )] = piece;
if (ok)
break;
}
}
if (!ok)
throw 'could not find matching pieces. type="' + type + ' color=' + color + ' moveAGN="' + moveStr + '". found ' + thePiece.length + ' matching pieces';
}
else
thePiece = thePiece[0];
if (promotion)
this.promote(thePiece, promotion.toLowerCase().charAt(1), file, row, isCapture);
else if (isCapture)
thePiece.capture(file, row);
else
thePiece.move(file, row);
return moveStr;
}
Game.prototype.addComment = function(str) {
if ( ! str ) return;
this.notations.push( $( '<p>' )
.addClass( 'pgn-comment' )
.text( str.replace( /[{}()]/g, '' ) )
);
}
Game.prototype.addDescription = function(description) {
description = $.trim(description);
var match = description.match(/\[([^"]+)"(.*)"\]/);
if (match)
this.descriptions[$.trim(match[1])] = match[2];
}
Game.prototype.description = function(pgn) {
var d = this.descriptions,
round = d['Round'] ? ' (' + d['Round'] + ')' : '',
s = d['Name']
|| d['שם']
|| ( (d['Event'] || d['אירוע'] || '') + ': ' + (d['White'] || d['לבן'] || '') + ' - ' + (d['Black'] || d['שחור'] || '') + round);
return s;
}
Game.prototype.preAnalyzePgn = function(pgn) {
function tryMatch(regex) {
var match = pgn.match(regex);
if (match)
pgn = pgn.replace( match, '' );
return match && match[0];
}
var match;
while (match = tryMatch(/^\s*\[[^\]]*\]/))
this.addDescription(match);
this.pgn = pgn;
}
Game.prototype.analyzePgn = function() {
if (this.analyzed) return;
this.analyzed = true;
var
match,
turn,
moveNum = '',
game = this,
pgn = this.pgn;
function removeHead(match) {
var ind = pgn.indexOf(match) + match.length;
pgn = pgn.substring(ind);
return match;
}
function tryMatch(regex) {
var rmatch = pgn.match(regex);
if (rmatch) {
removeHead(rmatch[0]);
moveNum = rmatch[1] || moveNum;;
}
return rmatch && rmatch[0];
}
function addMoveLink(str, isMove) {
var notation = $( '<span>' )
.addClass( isMove ? 'pgn-movelink' : 'pgn-steplink' )
.text( str )
game.notations.push( notation );
if ( isMove ) {
game.boards.push( game.board.slice() );
game.moves.push( notation );
} else if ( game.moves.length == 0 )
game.moves.push( notation );
var index = game.boards.length - 1;
notation.on( 'click', function() { game.drawBoard(index); } );
}
pgn = pgn.replace(/;(.*)\n/g, ' {$1} ').replace(/\s+/g, ' '); // replace to-end-of-line comments with block comments, remove newlines and noramlize spaces to 1
this.populateBoard( this.descriptions.FEN || 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR' );
this.boards.push(this.board.slice());
var prevLen = -1;
while (pgn.length) {
if (prevLen == pgn.length)
throw "analysePgn encountered a problem. pgn is: " + pgn;
prevLen = pgn.length;
this.addComment( tryMatch( /^\s*\{[^\}]*\}\s*/ ) );
this.addComment( tryMatch( /^\s*\([^\)]*\)\s*/ ) );
if (match = tryMatch(/^\s*(\d+)\.+/)) {
turn = /\.{3}/.test(match) ? BLACK : WHITE;
addMoveLink(match, false);
continue;
}
if (match = tryMatch(/^\s*[^ ]+ ?/)) {
this.createMove(turn, match);
addMoveLink(match, true);
turn = BLACK;
}
}
this.index = this.descriptions.FirstMove
? indexOfMoveNotation(this.descriptions.FirstMove)
: this.descriptions.FEN ? 0 : this.boards.length - 1;
}
Game.prototype.populateBoard = function(fen) {
var fenar = fen.split(/[\/\s]/);
if (fenar.length < 8)
throw 'illegal fen: "' + fen + '"';
for (var row = 0; row < 8; row++) {
var file = 0;
var filear = fenar[row].split('');
for (var i in filear) {
var p = filear[i], lp = p.toLowerCase();
if (/[1-8]/.test(p))
file += parseInt(p, 10);
else if (/[prnbkq]/.test(lp))
this.createPiece(lp, (p == lp ? BLACK : WHITE), file++, 7-row)
else
throw 'illegal fen: "' + fen + '"';
}
}
}
function selectGame() {
var gameSet = $(this).data('gameSet');
gameSet.selectGame(this.value);
}
function buildBoardDiv(container, selector, gameSet, ind) {
var
id = container.attr( 'id' ) || 'pgn-viewer-' + ind,
config = gameSet.config = container.data( 'config' ) || {},
notationId = 'pgn-notation-' + id,
infoId = 'pgn-info-' + id,
fenId = 'pgn-fen-' + id,
controlsDiv,
gotoend = new Button( ffButtonClass,
function() { gameSet.currentGame.drawBoard( -1 ) } ),
forward = new Button( advanceButtonClass,
function() { gameSet.currentGame.advance( 1 ) } ),
backstep = new Button( retreatButtonClass,
function() { gameSet.currentGame.advance( -1 ) } ),
gotostart = new Button( resetGameButtonClass,
function() { gameSet.currentGame.drawBoard( 0 ) } ),
collapsibleDiv = $( '<div>' ), // residue. to be renamed or remove
tabnames = $.extend( {
notation: 'Game Notation',
metadata: 'Information',
fen: 'FEN' },
config.tab_names );
gameSet.descriptionsDiv = $('<div>', {'class': 'pgn-descriptions', id: infoId })
gameSet.fixedDelay = 'delay' in config;
if ( gameSet.fixedDelay ) gameSet.autoPlayDelay = Math.max(500, config.delay);
if ( $( '#' + notationId ) ) notationId += '_1';
gameSet.pgnDiv = $('<div>', {'class': 'pgn-pgndiv', id: notationId } );
gameSet.blockSize = Math.max( minBlockSize, Math.min( maxBlockSize, config.squareSize || defaultBlockSize ) );
gameSet.fenDiv = $('<div>', { id: fenId } )
.css( { 'word-wrap': 'break-word' } );
gameSet.tabberDiv = $( '<div>', { 'class': 'pgn-tabber' } );
if ( mobile ) {
gameSet.tabberDiv
.append( gameSet.pgnDiv );
} else {
gameSet.tabberDiv
.append($( '<ul>' )
.append( $( '<li>' ).append( $( '<a>', { href: '#' + notationId } ).text( tabnames.notation ) ) )
.append($('<li>').append($('<a>', { href: '#' + infoId }).text( tabnames.metadata ) ) )
.append($('<li>').append($('<a>', { href:'#' + fenId }).text( tabnames.fen ) ) )
)
.append( gameSet.pgnDiv )
.append( gameSet.descriptionsDiv )
.append( gameSet.fenDiv )
.tabs();
}
controlsDiv = $('<div>', {'class': 'pgn-controls'})
.css({clear: 'both', textAlign: 'center'})
.append(gotostart.elem)
.append(backstep.elem);
if ( ! gameSet.fixedDelay ) controlsDiv.append( gameSet.slowerButton.elem );
controlsDiv.append(gameSet.autoPlayButton.elem);
if ( ! gameSet.fixedDelay ) controlsDiv.append( gameSet.fasterButton.elem );
controlsDiv
.append(forward.elem)
.append(gotoend.elem)
.append(gameSet.flipButton.elem)
.append(gameSet.commentsToggleButton.elem)
;
gameSet.boardDiv = $('<div>')
.addClass('pgn-board-div');
gameSet.piecesDiv = $('<div>').css({position:'absolute', left: '20px', top: '20px' })
.addClass( chessboardClass )
.appendTo(gameSet.boardDiv);
var fl = 'abcdefgh'.split('');
var fileCaption = config && config.translate && config.translate.file;
if (fileCaption) {
fl = fl.map(function(c) { return fileCaption[c] || '' ; } );
}
var rl = '12345678'.split('');
var rowCaption = config && config.translate && config.translate.row;
if (rowCaption) {
rl = rl.map(function(c) { return rowCaption[c] || '' ; } );
}
for (var side in sides) {
var
s = sides[side],
isFile = /n|s/.test(s);
gameSet[s] = [];
for (var i = 0; i < 8; i++) {
var sp = $('<span>', {'class': isFile ? 'pgn-file-legend' : 'pgn-row-legend'})
.text(isFile ? fl[i] : rl[i])
.appendTo(gameSet.boardDiv)
.css(gameSet.legendLocation(s, i));
gameSet[s][i] = sp;
}
}
container
.append( selector || '' )
.append( collapsibleDiv
.append(gameSet.boardDiv)
.append( gameSet.tabberDiv )
.append(controlsDiv)
);
}
function doIt() {
$(wrapperSelector, $content).each(function( ind ) {
var
wrapperDiv = $(this),
initial = wrapperDiv.text(),
pgnSource = $('div.pgn-sourcegame', wrapperDiv),
boardDiv,
selector,
gameSet = new Gameset( wrapperDiv );
try {
if (pgnSource.length > 1)
selector = $('<select>', { 'class': 'pgn-selector' } ).data({gameSet: gameSet}).change(selectGame);
buildBoardDiv(wrapperDiv, selector, gameSet, ind);
var game, ind = 0;
pgnSource.each(function() {
try {
game = new Game(gameSet);
game.preAnalyzePgn($(this).text());
wrapperDiv.data({currentGame: game});
ind++;
gameSet.allGames.push(game);
if (selector)
selector.append($('<option>', {value: gameSet.allGames.length - 1, text: game.description()})
.css('direction', game.descriptions['Direction'] || 'ltr')
);
} catch (e) {
mw.log('exception in game ' + ind + ' problem is: "' + e + '"');
if (game && game.descriptions)
for (var d in game.descriptions)
mw.log(d + ':' + game.descriptions[d]);
}
});
gameSet.selectGame(0);
gameSet.setWidth();
} catch(e) {
console.error(e);
console.error( 'exception analyzing game :', initial );
wrapperDiv.empty();
}
})
}
if ( $(wrapperSelector, $content).length) {
if ( mobile )
doIt();
else
mw.loader.using( 'jquery.ui.tabs' ).done( doIt );
}
});