מדיה ויקי:Gadget-pgnviewer.js: הבדלים בין גרסאות בדף

nit
מ 85 גרסאות של הדף wikipedia:he:מדיה_ויקי:Gadget-pgnviewer.js יובאו
 
(77 גרסאות ביניים של 3 משתמשים אינן מוצגות)
שורה 11: שורה 11:
*/
*/
"use strict";
"use strict";
$(function( $ ) {
window.mw.hook( 'wikipage.content' ).add( function ( $content ) {
var
var // const, really, but linter...
mw = window.mw,
brainDamage =  /MSIE|Trident/.test( window.navigator.userAgent ),
images = {},
pieceImageUrl = {},
boardImageUrl,
WHITE = 'l',
WHITE = 'l',
BLACK = 'd',
BLACK = 'd',
acode = 'a'.charCodeAt(0),
acode = 'a'.charCodeAt(0),
moveBucket = [], // this is a scratch thing, but since we access it from different objects, it's convenient to have it global
anim = 1000,
maxDelay = 8000,  // max delay between moves for autoplay, in ms
sides = ['n', 'e', 's', 'w'], // used for legends
defaultBlockSize = 45,
minBlockSize = 20,
minBlockSize = 20,
maxBlockSize = 60,
maxBlockSize = 60,
rtlregex = /[א-ת]/,
boardPadding = 20,
wrapperSelector = 'div.pgn-source-wrapper',
wrapperSelector = 'div.pgn-source-wrapper',
gameChangeEvent = 'pgn-geme-changed';
defaultBlockSize = 36,
sides = ['n', 'e', 's', 'w'], // used for legends
rowClassPrefix = 'pgn-prow-',
fileClassPrefix = 'pgn-pfile-',
allFilesAndColumnClasses = "01234567"
.split("")
.map( function( i ) { return rowClassPrefix + i + ' ' + fileClassPrefix + i; } )
.join(" "),
hiddenPiece = 'pgn-piece-hidden',
chessboardClass = 'pgn-board-img',
resetGameButtonClass = 'pgn-button-tostart',
ffButtonClass = 'pgn-button-toend',
advanceButtonClass = 'pgn-button-advance',
retreatButtonClass = 'pgn-button-retreat',
playButtonClass = 'pgn-button-play',
fasterButtonClass = 'pgn-button-faster',
slowerButtonClass = 'pgn-button-slower',
flipBoardButtonClass = 'pgn-button-flip',
ccButtonClass = 'pgn-button-cc',


mw = window.mw,
$ = window.$,
domParser = new DOMParser(),
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; }
שורה 39: שורה 52:
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] ) - 1 ) * 2 + ( match[2] === 'l' ? 1 : 2 );
return 0;
}
function boardToFen(board) {
function boardToFen(board) {
var res = [],
var res = [],
שורה 50: שורה 68:
}
}
return res.reverse().join('/'); // fen begins with row 8 file a - go figure...
return res.reverse().join('/'); // fen begins with row 8 file a - go figure...
}
function linkMoveClick(e) {
var
$this = $(this),
game = $this.data('game'),
index = $this.data('index'),
noAnim = $this.data('noAnim');
game.gs.toggleAutoPlay( false );
game.showMoveTo(index, noAnim);
}
}
function Button( iname, action, stateful, tooltip ) {
// Classes
var button = this,
function Button( className, action, stateful ) {
changer = $.isArray( iname ),
var button = this;
url = $.isArray( iname )
? [ images[iname[0]].url, images[iname[1]].url ]
: [ images[iname].url, images[iname].url ],
tooltip = $.isArray( tooltip )
? tooltip
: [ tooltip, tooltip ];
 
$.extend( button, {
$.extend( button, {
state: 0,
state: 0,
שורה 80: שורה 81:
if ( stateful )
if ( stateful )
this.elem
this.elem
.toggleClass( 'pgn-image-button-on', state )
.toggleClass( 'pgn-image-button-on', !!state )
.toggleClass( 'pgn-image-button-off', !state )
.toggleClass( 'pgn-image-button-off', !state );
.attr( { src: url[state], title: tooltip[state] || '' } );
if ( ( !stateful || state != oldState ) && typeof( action ) == 'function' )
if ( state != oldState && typeof( action ) == 'function' )
action( state );
action( state );
},
},
elem: $( '<img>', { src: url[0], 'class': 'pgn-image-button pgn-image-button-off', title: tooltip[0] } )
setVisible: function( visible ) {
this.elem.toggle( !! visible );
},
elem: $( '<div>' )
.addClass( 'pgn-image-button pgn-image-button-off ' + className )
.click( function() {
.click( function() {
button.setState( ! button.state );
button.setState( ! button.state );
שורה 100: שורה 104:
tabberDiv: null,
tabberDiv: null,
blockSize: defaultBlockSize,
blockSize: defaultBlockSize,
needRefresh: false,
allGames: [],
allGames: [],
currentGame: null,
currentGame: null,
showDetails:false,
showDetails:false,
timer: null,
timer: null,
flipButton: new Button( 'flip', function( state ) { gameSet.doFlip( state ); }, true ),
autoPlayDelay: 750,
autoPlayButton: new Button( [ 'play', 'pause' ], function( state ) { gameSet.toggleAutoPlay( state ); }, true ),
// actions
commentsToggleButton: new Button( 'comments', function( state ) { gameSet.hideComments( state ) }, true ),
faster: function() {  
autoPlayRate: 0,
gameSet.autoPlayDelay -= 500;
top: function(row, l) { return (((this.flipButton.state ? row : (7 - row)) + (l ? 0.3 : 0)) * this.blockSize + 20) + 'px'; },
if (gameSet.autoPlayDelay < 500) gameSet.autoPlayDelay = 500;
left: function(file, l) { return (((this.flipButton.state ? 7 - file : file) + (l ? 0.5 : 0)) * this.blockSize + 20) + 'px'; },
gameSet.reportDelay();
},
slower: function() {  
gameSet.autoPlayDelay += 500;
gameSet.reportDelay();  
},
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.isFlipped ? row : (7 - row)) + (l ? 0.3 : 0)) * this.blockSize + (l? boardPadding-6: boardPadding)) + 'px'; },
left: function(file, l) { return (((this.isFlipped ? 7 - file : file) + (l ? 0.5 : 0)) * this.blockSize + (l? boardPadding-6: boardPadding)) + 'px'; },
legendLocation: function(side, num) {
legendLocation: function(side, num) {
var n = 0.5 + num;
switch (side) {  
switch (side) {
case 'n': return {top: 0, left: this.left(num, true)};
case 'n':
case 'e': return {top: this.top(num, true), left: this.blockSize * 8 + boardPadding + 5};
return {top: 0, left: this.left(num, true)};
case 's': return {top: this.blockSize * 8 + 20, left: this.left(num, true)};
case 'e':
case 'w': return {top: this.top(num, true), left: 5};
return {top: this.top(num, true), left: this.blockSize * 8 + 20};
case 's':
return {top: this.blockSize * 8 + 20, left: this.left(num, true)};
case 'w':
return {top: this.top(num, true), left: 10};
}
}
},
},
שורה 134: שורה 148:
game.analyzePgn();
game.analyzePgn();
this.currentGame = game;
this.currentGame = game;
this.ccButton.setVisible( game.hasComments() );
game.show();
game.show();
$( '.pgn-comment-toggler' ).trigger( gameChangeEvent );
}
}
},
},
שורה 142: שורה 156:
this.fenDiv.text( boardToFen(board) );
this.fenDiv.text( boardToFen(board) );
},
},
drawIfNeedRefresh: function(again) {
drawIfNeedRefresh: function() {
this.refreshFEN();
if (this.currentGame)
if (this.needRefresh && this.currentGame)
this.currentGame.drawBoard();
this.currentGame.drawBoard();
this.needRefresh = false;
},
},
changeAppearance: function() {
changeAppearance: function() {
this.needRefresh = true;
this.currentGame.drawBoard();
this.currentGame.drawBoard();
this.relocateLegends();
this.relocateLegends();
שורה 157: שורה 168:
var
var
widthPx = width * 8,
widthPx = width * 8,
widthPxPlus = widthPx + 40,
widthPxPlus = widthPx + 40;
widthPxPlusPlus = widthPx + 80;
this.tabberDiv // disgusting, but i could not get heightStyle of jquery tabs to do what i need.
this.tabberDiv // disgusting, but i could not get heightStyle of jquery tabs to do what i need.
.css( { height: widthPxPlus } )
.css( { height: widthPxPlus } )
.find( 'div' ).css( { height: widthPx - 20 } );
.find( 'div' ).css( { height: mobile ? widthPxPlus : widthPx - 20 } );
this.blockSize = width;
this.blockSize = width;
this.boardImg.css({width: widthPx, height: widthPx});
this.piecesDiv.css({width: widthPx, height: widthPx});
this.currentGame.tds.boardDiv.css({width: widthPxPlus, height: widthPxPlus});
this.boardDiv.css({width: widthPxPlus, height: widthPxPlus});
this.changeAppearance();
this.changeAppearance();
this.currentGame.showCurrentMoveLink();
},
},
hideComments: function( state ) {
hideComments: function( state ) {
this.wrapperDiv.toggleClass( 'pgn-comments-hidden', state );
this.wrapperDiv.toggleClass( 'pgn-comments-hidden', state );
},
},
isFlipped: false,
doFlip: function( state ) {
doFlip: function( state ) {
this.isFlipped = state;
this.changeAppearance();
this.changeAppearance();
},
},
scheduleAutoPlay: function() {
playing: false,
clearTimeout( this.timer );
toggleAutoPlay: function( state ) {
if ( this.autoPlayButton.state ) {
clearInterval(this.timer);
                    if ( this.currentGame.done() )
if (state == undefined) state = this.playing;
                        this.toggleAutoPlay();
this.playing = state;
                    else {
if ( state ) {
                        var gs = this;
this.currentGame.wrapAround();
                        this.timer = setTimeout( function() { gs.autoPlayCallback(); }, this.autoPlayRate );
this.timer = setInterval(function() { gameSet.currentGame.advance() }, gameSet.autoPlayDelay);
                    }
}
}
},
},
autoPlayCallback: function() {
stopAutoPlay: function() {
var cg = this.currentGame;
gameSet.autoPlayButton.setState( false );
cg.wrapAround();
cg.advance();
},
toggleAutoPlay: function( state ) {
clearTimeout(this.timer);
this.wrapperDiv.find( '.pgn-chessPiece' ).finish();
this.autoPlayButton.setState( state );
if ( state )
this.autoPlayCallback();
}
}
});
});
שורה 204: שורה 205:
this.type = type;
this.type = type;
this.color = color;
this.color = color;
this.img = $('<img>', { src: images[type + color].url, 'class': 'pgn-chessPiece' } )
this.avatar = $('<div>')
.toggle(false);
.addClass('pgn-chessPiece pgn-ptype-color-' + type + color)
.on('transitionstart', function(){ $(this).addClass('moving'); } ) //supposedly elevates z-index
.on('transitionend', function(){ $(this).removeClass('moving'); } );
var piece = this;
$.extend( piece, {
appear: function(file, row) {
if (game.gs.isFlipped) {
file = 7 - file;
row = 7 - row;
}
this.avatar
.removeClass(hiddenPiece)
.removeClass(allFilesAndColumnClasses)  // remove them all
.addClass(rowClassPrefix + row) //
.addClass(fileClassPrefix + file);
},
disappear: function() { return this.avatar.addClass( hiddenPiece ); },
setSquare: function( file, row ) {
this.file = file;
this.row = row;
this.onBoard = true;
},
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);
},
move: function(file, row) {
// with chess960 castling, we sometimes have to test.
if ( this.game.pieceAt( this.file, this.row ) == this )
this.game.clearSquare(this.file, this.row);
this.game.pieceAt(file, row, this); // place it on the board)
},
pawnDirection: function() { return this.color == WHITE ? 1 : -1; },
toString: function() { return this.type + this.color; },
fen: function() { return this.color == WHITE ? this.type.toUpperCase() : this.type; },
pawnStart: function() { return this.color == WHITE ? 1 : 6; },
remove: function() { this.onBoard = false; },
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);
}
}, // function canMoveTo
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);
}
} ); // extend
}
}
   
   
ChessPiece.prototype.appear = function(file, row) {
this.img.css({top: this.game.gs.top(row), left: this.game.gs.left(file), width: this.game.gs.blockSize})
.fadeIn(anim);
};
ChessPiece.prototype.showMove = function(file, row) {
var gameSet = this.game.gs;
gameSet.refreshFEN();
this.img.animate( {top: gameSet.top(row), left: gameSet.left(file)},
anim,
'swing',
function() { gameSet.scheduleAutoPlay(); }
);
};
ChessPiece.prototype.disappear = function() { this.img.fadeOut(anim); };
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)
this.game.registerMove({what:'m', piece: this, file: file, row: row});
};
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);
};
ChessPiece.prototype.showAction = function(move) {
switch (move.what) {
case 'a':
this.appear(move.file, move.row);
break;
case 'm':
this.showMove(move.file, move.row);
break;
case 'r':
this.disappear();
break;
}
};
   
   
function Game(tds, gameSet) {
function Game(gameSet) {
$.extend(this, {
$.extend(this, {
board: [],
board: [],
boards: [],
boards: [],
pieces: [],
pieces: [],
notations: [],
moves: [],
moves: [],
linkOfIndex: [],
index: 0,
index: 0,
piecesByTypeCol: {},
piecesByTypeCol: {},
שורה 313: שורה 291:
comments: [],
comments: [],
analyzed: false,
analyzed: false,
tds: tds,
gs: gameSet});
gs: gameSet});
}
Game.prototype.moveLinkText = function(notation, color) {
var config = this.gs.config,
tpiece = config && config.translate && config.translate.piece,
tfile = config && config.translate && config.translate.file,
trow = config && config.translate && config.translate.row;
if (tpiece) {
tpiece =  color == WHITE && tpiece.white
|| color == BLACK && tpiece.black
|| tpiece;
try {
var regex = new RegExp('(' + Object.keys(tpiece).join("|") + ')', 'g');
notation = notation.replace(regex, function(c) { return tpiece[c] || c; } );
} catch (e) {
mw.log('bad config.translate.pieces');
throw e;
}
}
if (tfile) {
try {
notation = notation.replace(/[abcdefgh]/g, function(c) { return tfile[c] || c; } );
} catch (e) {
mw.log('bad config.translate.file');
throw e;
}
}
if (trow) {
try {
notation = notation.replace(/[12345678]/g, function(c) { return trow[c] || c; } );
} catch (e) {
mw.log('bad config.translate.row');
throw e;
}
}
return $.trim(notation.replace(/-/g, '\u2011')) + ' ';
}
}
   
   
שורה 320: שורה 335:
var desc = $.extend({}, this.descriptions),
var desc = $.extend({}, this.descriptions),
rtl = desc['Direction'] == 'rtl',
rtl = desc['Direction'] == 'rtl',
num = '',
gs = this.gs;
tds = this.tds;
   
   
// cleanup from previous game.
// cleanup from previous game.
this.gs.toggleAutoPlay( false );
gs.stopAutoPlay();
tds.descriptionsDiv.empty();
 
tds.pgnDiv.empty();
tds.boardDiv.find('img.pgn-chessPiece').toggle(false);
// setup descriptions
// setup descriptions
delete desc['Direction'];
delete desc['Direction'];
tds.descriptionsDiv.css({ direction: rtl ? 'rtl' : 'ltr', textAlign: rtl ? 'right' : 'left' });
gs.descriptionsDiv
$.each(desc, function(key, val) { tds.descriptionsDiv.append(key + ': ' + val + '<br />'); });
.empty()
.css( { direction: rtl ? 'rtl' : 'ltr', textAlign: rtl ? 'right' : 'left' } );
$.each(desc, function(key, val) {  
var line = mw.html.escape(key + ': ' + val)  + '<br />';
gs.descriptionsDiv.append(line);  
} );
   
   
// setup pgn section
// setup pgn section
this.linkOfIndex = [];
gs.pgnDiv.empty().append( this.notations );
for (var i = 0; i < Math.max(this.moves.length, this.comments.length); i++) {
var move = this.moves[i],
comment = this.comments[i];
if (move && move.s) {
if (move.a)
num = move.s;
if (move.turn == 'd')
num = num.replace(/\.*$/, '...');
var link = $('<span>', {'class': (move.a ? 'pgn-steplink' : 'pgn-movelink')})
.text(move.s.replace(/-/g, '\u2011')) // replace hyphens with non-breakable hyphens, to avoid linebreak within O-O or 1-0
.data({game: this, index: i, noAnim: move.a, moveNum: num})
.click(linkMoveClick);
tds.pgnDiv.append(link);
this.linkOfIndex[i] = link;
}
if (comment)
$('<p>', { 'class': 'pgn-comment', text: comment })
.toggleClass( 'pgn-rtl-comment' , rtlregex.test(comment) )
.appendTo( tds.pgnDiv );
}
   
   
// set the board.
// set the board.  
$(this.pieces).each(function(i, piece){piece.img.appendTo(tds.boardDiv);});
var hiddenAvatars = this.pieces.map( function(piece) { return piece.disappear(); } );
this.showMoveTo(this.index, true);
this.gs.piecesDiv.empty().append( hiddenAvatars );
this.gs.refreshFEN();
this.drawBoard();
}
}
   
   
     Game.prototype.done = function() { return this.moves.length - 1 <= this.index; }
     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) {
שורה 379: שורה 376:
   
   
Game.prototype.clearPieceAt = function(file, row) {
Game.prototype.clearPieceAt = function(file, row) {
var
var piece = this.pieceAt(file, row);
piece = this.pieceAt(file, row);
if (piece)
if (piece)
piece.remove();
piece.remove();
this.clearSquare(file, row);
this.clearSquare(file, row);
this.registerMove({what:'r', piece: piece, file: file, row: row})
}
}
   
   
שורה 390: שורה 386:
var file = file1,  
var file = file1,  
row = row1,  
row = row1,  
moves = 0,
steps = 0,
dfile = sign(file1, file2),
dfile = sign(file1, file2),
drow = sign(row1, row2);
drow = sign(row1, row2);
שורה 400: שורה 396:
if (this.pieceAt(file, row))
if (this.pieceAt(file, row))
return false;
return false;
if (moves++ > 10)
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 +
שורה 419: שורה 415:
byTypeCol.push(piece);
byTypeCol.push(piece);
};
};
Game.prototype.registerMove = function(move) {
function act() { this.piece.showAction(this) };
moveBucket.push($.extend(move, {act: act}));
}
Game.prototype.gotoBoard = function(index) {
this.index = index;
this.drawBoard();
}
   
   
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.moves.length) {
if (0 <= m && m < this.boards.length) {
this.showMoveTo(m);
this.drawBoard(m);
if (this.moves[this.index].a)
} else this.gs.autoPlayButton.setState(false);
this.advance(delta);
}
}
}
   
   
Game.prototype.showCurrentMoveLink = function() {
Game.prototype.showCurrentMoveLink = function() {
var moveLink = this.linkOfIndex[this.index];
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');
שורה 450: שורה 434:
}
}
   
   
Game.prototype.showMoveTo = function(index, noAnim) {
Game.prototype.drawBoard = function(index) {
var dif = index - this.index;
if ( index == undefined) index = this.index;
if (noAnim || dif < 1 || 2 < dif)
if ( index < 0 ) index += this.boards.length;
this.gotoBoard(index);
 
else
this.index = index;
while (this.index < index)
$.each(this.moves[++this.index].bucket, function(index, drop) {drop.act()});
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();
Game.prototype.drawBoard = function() {
var
saveAnim = anim,
board = this.boards[this.index];
this.refreshFEN();
anim = 0;
for (var i in this.pieces)
this.pieces[i].disappear();
for (var i in board)
board[i].appear(file(i), row(i));
anim = saveAnim;
}
}
   
   
Game.prototype.wrapAround = function() {
Game.prototype.wrapAround = function() {
if (this.index >= this.boards.length - 1)
if (this.index >= this.boards.length - 1)
this.gotoBoard(0);
this.drawBoard(0);
}
}


שורה 500: שורה 475:
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);
this.registerMove({what:'a', piece: newPiece, file: file, row: row});
}
}
   
   
שורה 521: שורה 495:
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) {
return false;
if (!match) return false;
}
   
   
var type = match[1] ? match[1].toLowerCase() : 'p',
var type = match[1] ? match[1].toLowerCase() : 'p',
שורה 541: שורה 514:
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];
שורה 550: שורה 524:
}
}
}
}
this.pieceAt(piece.file, piece.row, piece);
// put the piece back in place
this.board[bindex( piece.file, piece.row )] = piece;
if (ok)
if (ok)
break;
break;
שורה 569: שורה 544:
return moveStr;
return moveStr;
}
}
Game.prototype.addComment = function(str) {
if ( str )
Game.prototype.addComment = function(str) {
this.comments[this.moves.length] = str.replace( /[{}()]/g, '' );
if ( ! str ) return;
this.notations.push( $( '<p>' )
.addClass( 'pgn-comment' )
.text( str.replace( /[{}()]/g, '' ) )
);
}
}
 
Game.prototype.addDescription = function(description) {
Game.prototype.addDescription = function(description) {
description = $.trim(description);
description = $.trim(description);
שורה 581: שורה 560:
}
}
   
   
Game.prototype.description = function(pgn) {
Game.prototype.description = function() {
var d = this.descriptions,
var d = this.descriptions,
round = d['Round'] ? ' (' + d['Round'] + ')' : '',
event = d['Event'] || d['אירוע'],
s = d['Name']  
round =  d['Round'] ||  d['סיבוב'],
|| d['שם']  
white = d['White'] || d['לבן'],
|| ( (d['Event'] || d['אירוע'] || '') + ': ' + (d['White'] || d['לבן'] || '') + ' - ' + (d['Black'] || d['שחור'] || '') + round);
black = d['Black'] || d['שחור'],
return s;
name = d['Name'] || d['שם'];
}
return name
|| ( event ? event + ': ' : '')
Game.prototype.addMoveLink = function(str, noAnim, turn) {
+ (white ? white : '')
this.boards.push(this.board.slice());
+ (white || black ? ' – ' : '')
this.moves.push({bucket: moveBucket, s: str, a: noAnim, turn: turn});
+ (black ? black : '')
moveBucket = [];
+ (round ? ' (' + round + ')' : '');
}
}
 
 
Game.prototype.preAnalyzePgn = function(pgn) {
Game.prototype.preAnalyzePgn = function(pgn) {
function tryMatch(regex) {
function tryMatch(regex) {
שורה 616: שורה 595:
match,
match,
turn,
turn,
indexOfMove = {},
moveNum = '',
moveNum = '',
game = this,
pgn = this.pgn;
pgn = this.pgn;
   
   
שורה 630: שורה 609:
if (rmatch) {
if (rmatch) {
removeHead(rmatch[0]);
removeHead(rmatch[0]);
moveNum = rmatch[1] || moveNum;;
moveNum = rmatch[1] || moveNum;
}
}
return rmatch && rmatch[0];
return rmatch && rmatch[0];
}
function addMoveLink(str, isMove, color) {
var notation = $( '<span>' )
.addClass( isMove ? 'pgn-movelink' : 'pgn-steplink' )
.text( game.moveLinkText( str, color ) )
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.gs.stopAutoPlay();
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;
this.addMoveLink();
while (pgn.length) {
while (pgn.length) {
if (prevLen == pgn.length)
if (prevLen == pgn.length)
שורה 646: שורה 645:
this.addComment( tryMatch( /^\s*\([^\)]*\)\s*/ ) );
this.addComment( tryMatch( /^\s*\([^\)]*\)\s*/ ) );
if (match = tryMatch(/^\s*(\d+)\.+/)) {
if (match = tryMatch(/^\s*(\d+)\.+/)) {
turn = /\.\.\./.test(match) ? BLACK : WHITE;
turn = /\.{3}/.test(match) ? BLACK : WHITE;
this.addMoveLink(match, true);
addMoveLink(match, false, turn);
continue;
continue;
}
}
if (match = tryMatch(/^\s*[^ ]+ ?/)) {
if (match = tryMatch(/^\s*[^ ]+ ?/)) {
this.createMove(turn, match);
this.createMove(turn, match);
this.addMoveLink(match, false, turn);
addMoveLink(match, true, turn);
indexOfMove[moveNum + turn] = this.moves.length - 1;
turn = BLACK;
turn = BLACK;
}
}
}
}
this.index = indexOfMove[this.descriptions.FirstMove || 'nofirstnmove'] ||
            ( this.descriptions.FEN ? 0 : this.moves.length - 1 );
this.index = this.descriptions.FirstMove
? indexOfMoveNotation(this.descriptions.FirstMove)
: this.descriptions.FEN ? 0 : this.boards.length - 1;
}
}
   
   
שורה 678: שורה 678:
}
}
}
}
}  
}
function selectGame() {
var gameSet = $(this).data('gameSet');
Game.prototype.hasComments = function() {
gameSet.selectGame(this.value);
var hasComment = function( n ){ return n.hasClass( 'pgn-comment' ); };
return !! this.notations.filter( hasComment ).length;
}
}


function tooltip( which ) {
return ( which || '' ) + '';
}
function advanceButton(gameSet, which) {
return new Button( which, function() {
gameSet.toggleAutoPlay( false );
switch ( which ) {
case 'forward': gameSet.currentGame.advance( 1 );
break;
case 'back': gameSet.currentGame.advance( -1 );
break;
case 'to_start': gameSet.currentGame.linkOfIndex[1].click();
break;
case 'to_end': gameSet.currentGame.linkOfIndex.slice(-1).pop().click();
break;
}
gameSet.refreshFEN();
},
false,
tooltip( which )
);
}
function buildBoardDiv(container, selector, gameSet, ind) {
function buildBoardDiv(container, selector, gameSet, ind) {
var
var
id = container.attr( 'id' ) || 'pgn-viewer-' + ind,
id = container.attr( 'id' ) || 'pgn-viewer-' + ind,
config = container.data( 'config' ) || {},
config = gameSet.config = container.data( 'config' ) || {},
notationId = 'pgn-notation-' + id,
notationId = 'pgn-notation-' + id,
infoId = 'pgn-info-' + id,
infoId = 'pgn-info-' + id,
fenId = 'pgn-fen-' + id,
fenId = 'pgn-fen-' + id,
pgnDiv = $('<div>', {'class': 'pgn-pgndiv', id: notationId }),
descriptionsDiv = $('<div>', {'class': 'pgn-descriptions', id: infoId }),
controlsDiv,
controlsDiv,
gotoend = advanceButton( gameSet, 'to_end' ),
createBotton = function( className, param, todraw ) {
forward = advanceButton( gameSet, 'forward' ),
return new Button( className,  
backstep = advanceButton( gameSet, 'back' ),
function() {
gotostart = advanceButton( gameSet, 'to_start' ),
gameSet.stopAutoPlay();
slider = $('<div>', {'class': 'pgn-slider'})
if ( todraw ) gameSet.currentGame.drawBoard( param )
.slider({
else gameSet.currentGame.advance( param );
min: 0,
} )
max: maxDelay,
},
orientation: 'horizontal',
gotoend = createBotton( ffButtonClass, -1, true ),
value: maxDelay,
forward = createBotton( advanceButtonClass, 1 ),
stop: function() { gameSet.autoPlayRate = maxDelay - slider.slider( 'value' ); }
backstep = createBotton( retreatButtonClass, -1 ),
}),
gotostart = createBotton( resetGameButtonClass, 0, true ),
collapsibleDiv = $( '<div>' ),
flip = new Button( flipBoardButtonClass, function( state ){ gameSet.doFlip( state ); }, true ),
collapseText = mw.messages.get( 'collapsible-collapse' ),
slower = new Button( slowerButtonClass, function(){ gameSet.slower(); } ),
expandText = mw.messages.get( 'collapsible-expand' ),
faster = new Button( fasterButtonClass, function(){ gameSet.faster(); } ),
collapseState,
autoplay = new Button( playButtonClass, function( state ){ gameSet.toggleAutoPlay( state ); }, true ),
collapseSpeed = 0,
commentsToggle = new Button( ccButtonClass, function( state ){ gameSet.hideComments( state ); }, true ),
collapser = $( '<input>', { type: 'button', value: collapseText } )
.click( function(e) {
collapseState ^= true;
collapser.prop( 'disabled', true );
collapsibleDiv[ collapseState ? 'slideUp' : 'slideDown' ]( collapseSpeed, function() {
collapser.val( collapseState ? expandText : collapseText );
collapser.prop( 'disabled', false );
} );
} ),
tabnames = $.extend( {
tabnames = $.extend( {
notation: 'Game Notation',
notation: 'Game Notation',
שורה 749: שורה 715:
fen: 'FEN' },  
fen: 'FEN' },  
config.tab_names );
config.tab_names );
gameSet.autoPlayButton = autoplay;
gameSet.ccButton = commentsToggle
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.blockSize = Math.max( minBlockSize, Math.min( maxBlockSize, config.squareSize || defaultBlockSize ) );
gameSet.fenDiv = $('<div>', { id: fenId } )
gameSet.fenDiv = $('<div>', { id: fenId } )
.css( { 'word-wrap': 'break-word' } );  
.css( { 'word-wrap': 'break-word' } );  
gameSet.tabberDiv = $( '<div>', { 'class': 'pgn-tabber' } )
gameSet.tabberDiv = $( '<div>', { 'class': 'pgn-tabber' } );
.append($( '<ul>' )
if ( mobile ) {
.append( $( '<li>' ).append( $( '<a>', { href: '#' + notationId } ).text( tabnames.notation ) ) )  
gameSet.tabberDiv
.append($('<li>').append($('<a>', { href: '#' + infoId }).text(  tabnames.metadata ) ) )
.append( gameSet.pgnDiv );
.append($('<li>').append($('<a>', { href:'#' + fenId }).text( tabnames.fen ) ) )
} else {
)
gameSet.tabberDiv
.append( pgnDiv )
.append($( '<ul>' )
.append( descriptionsDiv )
.append( $( '<li>' ).append( $( '<a>', { href: '#' + notationId } ).text( tabnames.notation ) ) )  
.append( gameSet.fenDiv )
.append($('<li>').append($('<a>', { href: '#' + infoId }).text(  tabnames.metadata ) ) )
.tabs();
.append($('<li>').append($('<a>', { href:'#' + fenId }).text( tabnames.fen ) ) )
)
.append( gameSet.pgnDiv )
.append( gameSet.descriptionsDiv )
.append( gameSet.fenDiv )
.tabs();
}
var buttons = gameSet.fixedDelay
? [gotostart, backstep, autoplay, forward, gotoend, flip, commentsToggle]
: [gotostart, backstep, slower, autoplay, faster, forward, gotoend, flip, commentsToggle]
controlsDiv = $('<div>', {'class': 'pgn-controls'})
controlsDiv = $('<div>', {'class': 'pgn-controls'})
.css({clear: 'both', textAlign: 'center'})
.css( { textAlign: 'center' } ) // todo: move to css
.append(gotostart.elem)
.append( buttons.map( function(x){ return x.elem; } ) );
.append(backstep.elem)
 
.append(gameSet.autoPlayButton.elem)
gameSet.boardDiv = $('<div>')
.append(forward.elem)
.addClass('pgn-board-div');
.append(gotoend.elem)
.append(gameSet.flipButton.elem)
.append(gameSet.commentsToggleButton.elem)
.append(slider)
;


gameSet.boardDiv = $('<div>', {'class': 'pgn-board-div'});
gameSet.piecesDiv = $('<div>').css({position:'absolute', left: '20px', top: '20px' })
gameSet.boardImg = $('<img>', {'class': 'pgn-board-img', src: images.board.url } )
.addClass( chessboardClass )
.css({padding: 20})
.appendTo(gameSet.boardDiv);
.appendTo(gameSet.boardDiv);
var fl = 'abcdefgh'.split('');
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) {
for (var side in sides) {
var
var
שורה 788: שורה 775:
for (var i = 0; i < 8; i++) {
for (var i = 0; i < 8; i++) {
var sp = $('<span>', {'class': isFile ? 'pgn-file-legend' : 'pgn-row-legend'})
var sp = $('<span>', {'class': isFile ? 'pgn-file-legend' : 'pgn-row-legend'})
.text(isFile ? fl[i] : (i + 1))
.text(isFile ? fl[i] : rl[i])
.appendTo(gameSet.boardDiv)
.appendTo(gameSet.boardDiv)
.css(gameSet.legendLocation(s, i));
.css(gameSet.legendLocation(s, i));
שורה 796: שורה 783:
container
container
.append( config.collapsible ? collapser : '' )
.append( selector || '' )
.append( selector || '' )
.append( collapsibleDiv
.append($('<div>')
.append(gameSet.boardDiv)
.append(gameSet.boardDiv)
.append(gameSet.tabberDiv)
.append( gameSet.tabberDiv )
.append(controlsDiv)
.append(controlsDiv)
);
);
if ( config.collapsible && config.collapsed )
collapser.click();
collapseSpeed = 1500;
return { boardDiv: gameSet.boardDiv, pgnDiv: pgnDiv, descriptionsDiv: descriptionsDiv };
}
}
   
   
שורה 813: שורה 795:
var
var
wrapperDiv = $(this),
wrapperDiv = $(this),
initial = wrapperDiv.text(),
pgnSource = $('div.pgn-sourcegame', wrapperDiv),
pgnSource = $('div.pgn-sourcegame', wrapperDiv),
boardDiv,
selector,
selector,
gameSet = new Gameset( wrapperDiv );
gameSet = new Gameset( wrapperDiv );
try {
if (pgnSource.length > 1)
if (pgnSource.length > 1)
selector = $('<select>', { 'class': 'pgn-selector' } ).data({gameSet: gameSet}).change(selectGame);
selector = $('<select>', { 'class': 'pgn-selector' } )
.change( function() { gameSet.selectGame(this.value); } );
var tds = buildBoardDiv(wrapperDiv, selector, gameSet, ind);
 
var game, ind = 0;
buildBoardDiv(wrapperDiv, selector, gameSet, ind);
pgnSource.each(function() {
var game, ind = 0;
try {
pgnSource.each(function() {
game = new Game(tds, gameSet);
try {
game.preAnalyzePgn($(this).text());
game = new Game(gameSet);
wrapperDiv.data({currentGame: game});
game.preAnalyzePgn($(this).text());
ind++;
wrapperDiv.data({currentGame: game});
gameSet.allGames.push(game);
ind++;
if (selector)
gameSet.allGames.push(game);
selector.append($('<option>', {value: gameSet.allGames.length - 1, text: game.description()})
if (selector)
.css('direction', game.descriptions['Direction'] || 'ltr')
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 + '"');
} catch (e) {
if (game && game.descriptions)
mw.log('exception in game ' + ind + ' problem is: "' + e + '"');
for (var d in game.descriptions)
if (game && game.descriptions)
mw.log(d + ':' + game.descriptions[d]);
for (var d in game.descriptions)
}
mw.log(d + ':' + game.descriptions[d]);
});
}
gameSet.selectGame(0);
});
gameSet.setWidth();
gameSet.selectGame(0);
gameSet.setWidth();
} catch(e) {
console.error(e);
console.error( 'exception analyzing game :', initial );
wrapperDiv.empty();
}
})
})
}
}
   
   
function populateImages() {
if ( $(wrapperSelector, $content).length) {
var
if ( mobile )
flist = [];
doIt();
 
else
images = {
mw.loader.using( 'jquery.ui' ).done( doIt );
to_start: { fn: 'Ic skip previous 48px.svg' },
to_end:  { fn: 'Ic skip next 48px.svg' },
back:    { fn: 'Ic keyboard arrow left 48px.svg' },
forward:  { fn: 'Ic keyboard arrow right 48px.svg' },
flip:    { fn: 'Ic sync 48px.svg' },
play:    { fn: 'Ic play arrow 48px.svg' },
pause:    { fn: 'Ic pause 48px.svg' },
comments: { fn: 'Ic closed caption 48px.svg' },
board:    { fn: 'Chessboard480.png' }
};
 
$( [WHITE, BLACK] ).each( function( _, c ) {
$( ['p', 'r', 'n', 'b', 'q', 'k'] ).each( function( _, t ) {
                var tc = t + c;
images[tc] = { fn: 'Chess ' + tc + 't45.svg' };
            } );
        } );
       
for ( var i in images ) flist.push( 'File:' + images[i].fn );
var params = { titles: flist.join( '|' ), prop: 'imageinfo', iiprop: 'url' };
if ( brainDamage )
params.iiurlwidth = 48;
 
new mw.Api().get(params)
.done( function( data ) {
if (data && data.query)
$.each(data.query.pages, function(_, page) {
var
ia = page.imageinfo[0],
title = page.title,
url = ( brainDamage && /\.svg$/.test( title ) ) ? ia.thumburl : ia.url;
                        for ( var key in images )
                            if ( ~ title.indexOf( images[key].fn ) )
                                images[key].url = url;
} );
doIt();
} );
}
}
if ( $(wrapperSelector).length)
mw.loader.using( ['mediawiki.api', 'jquery.ui.tabs', 'jquery.ui.slider'] ).done( populateImages );
});
});