משתמש:קיפודנחש/common.js/pgn.js

הערה: לאחר הפרסום, ייתכן שיהיה צורך לנקות את זיכרון המטמון (cache) של הדפדפן כדי להבחין בשינויים.

  • פיירפוקס / ספארי: להחזיק את המקש Shift בעת לחיצה על טעינה מחדש (Reload) או ללחוץ על צירוף המקשים Ctrl-F5 או Ctrl-R (במחשב מק: ⌘-R).
  • גוגל כרום: ללחוץ על צירוף המקשים Ctrl-Shift-R (במחשב מק: ⌘-Shift-R).
  • אינטרנט אקספלורר / אדג': להחזיק את המקש Ctrl בעת לחיצה על רענן (Refresh) או ללחוץ על צירוף המקשים Ctrl-F5.
  • אופרה: ללחוץ על Ctrl-F5.
"use strict";
$(function() {
	var
		imageUrl = {},
		flipImageUrl,
		WHITE = 'l',
		BLACK = 'd',
		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,
		timer;

// some global, utility functions.
	function bindex(file, row) { return 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 colorDiff(a, b) {return (a == BLACK) - (b == BLACK);}
	function fileOfStr(file) { return file && file.charCodeAt(0) - acode;}
	function rowOfStr(row) { return row && (row - 1);}
	function clearTimer() {
		if (timer)
			clearInterval(timer);
		timer = null;
	}

	function linkMoveClick(e) {
		var
			$this = $(this),
			game = $this.data('game'),
			index = $this.data('index'),
			noAnim = $this.data('noAnim');
		clearTimer();
		$this.addClass('pgn-current-move').siblings().removeClass('pgn-current-move');
		game.showMoveTo(index, noAnim);
	}



	function Gameset() { // set of functions and features that depend on blocksize, flip and currentGame.
		$.extend(this, {
			blockSize: 40,
			flip: false,
			allGames: [],
			currentGame: null,

			top: function(row) { return ((this.flip ? row : (7 - row)) * this.blockSize) + 'px'; },
			left: function(file) { return ((this.flip ? 7 - file : file) * this.blockSize) + 'px'; },
			selectGame: function(val) {
				if (this.currentGame)
					this.currentGame.toggle(false);
				var game = this.allGames[val];
				if (game) {
					this.currentGame = game;
					game.show();
				}
			}
		});
	}

	function ChessPiece(type, color, game) {
		this.game = game;
		this.type = type;
		this.color = color;
		this.img = $('<img>', {src: imageUrl[type + color], 'class': 'pgn-chessPiece', opacity: 0})
			.appendTo(game.boardDiv);
	}

	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) {
		this.img.animate({top: this.game.gs.top(row), left: this.game.gs.left(file)}, anim);
	}

	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) {
		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.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 == 3 && rd * fd) // sum is 3, none is 0
					? this
					: false);
			case 'p':
				var dir = this.pawnDirection();
				return (
					((this.row == this.pawnStart() && row ==  this.row + dir * 2 && !fd && !capture)
					|| (this.row + dir == row && !fd && !capture)
					|| (this.row + dir == row && fd == 1 && capture))
						? this
						: false);
			case 'k':
				return ((rd < 2 && fd < 2)
					? this
					: false);
			case 'q':
				return ((! ((rd - fd) * rd * fd) && this.game.roadIsClear(this.file, file, this.row, row))
					? this
					: false);
			case 'r':
				return ((!(rd * fd) && this.game.roadIsClear(this.file, file, this.row, row))
					? this
					: false);
			case 'b':
				return (((rd == fd) && this.game.roadIsClear(this.file, file, this.row, row))
					? this
					: false);
		}
	}

	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) {
		$.extend(this, {
			board: [],
			boards: [],
			pieces: [],
			moves: [],
			linkOfIndex: [],
			index: 0,
			piecesByTypeCol: {},
			descriptions: {},
			tds: tds,
			gs: gameSet});
		tds.boardTd.append(this.boardDiv = $('<div>', {'class': 'pgn-board-div'}));
		tds.pgnTd.append(this.pgnDiv = $('<div>', {'class': 'pgn-pgn-display'}));
		tds.descriptionsTd.append(this.descriptionsDiv = $('<div>', {'class': 'pgn-descriptions'}));
		this.toggle(false);
	}

	Game.prototype.toggle = function(what) {
		this.boardDiv.toggle(what);
		this.pgnDiv.toggle(what);
		this.descriptionsDiv.toggle(what);
	}

	Game.prototype.show = function() {
		clearTimer();
		this.toggle(true);
		this.drawBoard();
	}

	Game.prototype.copyBoard = function() { return this.board.slice(); }

	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);
		this.registerMove({what:'r', piece: piece, file: file, row: row})
	}

	Game.prototype.roadIsClear = function(file1, file2, row1, row2) {
		var file, row, dfile, drow, moves = 0;
		dfile = sign(file1, file2);
		drow = sign(row1, row2);
		var file = file1, row = row1;
		while (true) {
			file += dfile;
			row += drow;
			if (file == file2 && row == row2)
				return true;
			if (this.pieceAt(file, row))
				return false;
			if (moves++ > 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.registerMove = function(move) {
		moveBucket.push(move);
	}

	Game.prototype.gotoBoard = function(index) {
		this.index = index;
		this.drawBoard();
	}

	Game.prototype.advance = function() {
		if (this.index < this.moves.length - 1)
			this.showMoveTo(this.index + 1);
		this.pgnDiv.find('span').removeClass('pgn-current-move');
		if (this.linkOfIndex[this.index])
			this.linkOfIndex[this.index].addClass('pgn-current-move');
	}

	Game.prototype.showMoveTo = function(index, noAnim) {
		var dif = index - this.index;
			if (noAnim || dif < 1 || 2 < dif)
				this.gotoBoard(index);
			else
				while (this.index < index) {
					var mb = this.moves[++this.index];
					for (var m in mb)
						mb[m].piece.showAction(mb[m]);
				}
	}

	Game.prototype.drawBoard = function() {
		var
			saveAnim = anim,
			board = this.boards[this.index];
		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.kingSideCastle = function(color) {
		var king = this.piecesByTypeCol['k'][color][0];
		var rook = this.piecesByTypeCol['r'][color][1];
		king.move(fileOfStr('g'), king.row);
		rook.move(fileOfStr('f'), rook.row);
	}

	Game.prototype.queenSideCastle = function(color) {
		var king = this.piecesByTypeCol['k'][color][0];
		var rook = this.piecesByTypeCol['r'][color][0];
		king.move(fileOfStr('c'), king.row);
		rook.move(fileOfStr('d'), rook.row);
	}

	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);
		this.registerMove({what:'a', piece: newPiece, file: file, row: 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 (moveStr == 'O-O')
			return this.kingSideCastle(color);
		if (moveStr == 'O-O-O')
			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';
		var oldFile = fileOfStr(match[2]);
		var oldRow = rowOfStr(match[3]);
		var isCapture = !!match[4];
		var file = fileOfStr(match[5]);
		var row = rowOfStr(match[6]);
		var promotion = match[7];
		var candidates = this.piecesByTypeCol[type][color];
		if (!candidates || !candidates.length)
			throw 'could not find matching pieces. type="' + type + ' color=' + color + ' moveAGN=' + moveStr;
		var found = false;
		for (var c in candidates) {
			found = candidates[c].matches(oldFile, oldRow, isCapture, file, row);
			if (found)
				break;
		}
		if (!found)
			throw 'could not find a piece that can execute this move. type="' + type + ' color=' + color + ' moveAGN=' + moveStr;
//		confirm('about to execute ' + moveStr + ' piece type is ' + found.type + ' at ' + found.file + found.row + ' file=' + file + ' row=' + row)
		if (promotion)
			this.promote(found, promotion.toLowerCase().charAt(1), file, row, isCapture);
		else if (isCapture)
			found.capture(file, row);
		else
			found.move(file, row);
		return moveStr;
	}

	Game.prototype.addMoveLink = function(str, noAnim) {
		if (!str || !noAnim) {
			this.boards.push(this.board.slice());
			this.moves.push(moveBucket);
			moveBucket = [];
		}
		if (str) {
			var index = this.moves.length-1,
				link = $('<span>', {'class': (noAnim ? 'pgn-steplink' : 'pgn-movelink')})
				.text(str)
				.data({game: this, index: index, noAnim: noAnim})
				.click(linkMoveClick);
			this.pgnDiv.append(link);
			if (!noAnim)
				this.linkOfIndex[index] = link;
		}
	}

	Game.prototype.addComment = function(str) {
		this.pgnDiv.append($('<span>', {'class': 'pgn-comment'}).text(str));
	}

	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;
		var s =
			d['Name'] || d['שם'] ||
			( (d['Event'] || d['אירוע'] || '') + ': ' + (d['White'] || d[''] || 'לבן') + ' - ' + (d['Black'] || d['שחור'] || '') );
		return s;
	}

	Game.prototype.analyzePgn = function(pgn) {


		function removeHead(match) {
			var ind = pgn.indexOf(match) + match.length;
			pgn = pgn.substring(ind);
			return match;
		}

		function tryMatch(regex) {
			var match = pgn.match(regex);
			if (match)
				removeHead(match[0]);
			return match && match[0];
		}

		var
			match,
			turn;

		while (match = tryMatch(/^\s*\[[^\]]*\]/))
			this.addDescription(match);

		if (this.descriptions['direction']) {
			this.descriptionsDiv.css({direction: this.descriptions['direction']});
			delete this.descriptions['direction'];
		}

		var dar = [];
		$.each(this.descriptions, function(key, val){dar.push(key + ': ' + val);})
		this.descriptionsDiv.html(dar.join('<br />'));

		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

		var fen = this.descriptions.FEN || 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR';
		this.populateBoard(fen); //

		var prevLen = -1;
		this.addMoveLink();
		while (pgn.length) {
			if (prevLen == pgn.length)
				throw "analysePgn encountered a problem. pgn is: " + pgn;
			prevLen = pgn.length;
			if (match = tryMatch(/^\s*\{[^\}]*\}/))
				this.addComment(match);
			if (match = tryMatch(/^\s*\([^\)]*\)/))
				this.addComment(match);
			if (match = tryMatch(/^\s*\d+\.+/)) {
				turn = /\.\.\./.test(match) ? BLACK : WHITE;
				this.addMoveLink(match, true);
				continue;
			}
			if (match = tryMatch(/^\s*[^ ]+ ?/)) {
				this.createMove(turn, match);
				this.addMoveLink(match);
				turn = BLACK;
			}
		}
	}

	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 createFlipper(gameSet) {
		var flipper =
			$('<img>', {src: flipImageUrl})
				.css({width: '37px', float:'right', clear: 'right', border: 'solid 1px gray', borderRadius: '4px', backgroundColor: '#ddd'})
				.click(function() {
					gameSet.flip ^= 1;
					var rotation = gameSet.flip ? 'rotate(180deg)' : 'rotate(0deg)';
					$(this).css({
						'-webkit-transform': rotation,
						'-moz-transform': rotation,
						'-ms-transform': rotation,
						'-o-transform': rotation,
						'transform': rotation})
					gameSet.currentGame.drawBoard();
					$(this).closest('table.pgn-table').find('td.pgn-row').each(function() {
						$(this).text(9 - $(this).text())
					})
				});
		return flipper;
	}

	function advanceButton(gameSet) {
		var button = $('<input>', {type: 'button', value: '<'})
			.css({float: 'right', clear: 'right', fontSize: '16px', width: 40})
			.click(function() {
				clearTimer();
				gameSet.currentGame.advance();
			});
		return button;
	}

	function slideShowButton(gameSet) {
		var button = $('<input>', {type: 'button', value: '\u25B6'})
			.css({float: 'right', clear: 'right', fontSize: '16px', width: 40})
			.click(function() {
				clearTimer();
				timer = setInterval(function(){gameSet.currentGame.advance()}, 1000 + anim);
			});
		return button;
	}

	function setWidth() {
		var
			$this = $(this),
			table = $this.closest('table.pgn-table'),
			width = parseInt($this.slider('value'), 10),
			gs = $this.data('gameSet');

		gs.blockSize = width;
		table.attr({width: width * 8 + 70}).css({width: width * 8 + 70});
		table.find('td.pgn-game-square').attr({width: width, height: width}).css({width: width, maxWidth: width, height: width});
		gs.currentGame.drawBoard();
	}

	function buildBoardDiv(container, selector, gameSet) {
		var
			boardTd, pgnTd, descriptionsTd,
			table,
			controlsTd,
			sliderTd,
			cdTd,
			cdTable,
			flipper = createFlipper(gameSet),
			advance = advanceButton(gameSet),
			slideShow = slideShowButton(gameSet),
			buttons = $('<div>').css({maxWidth: 40}).append(advance).append(slideShow),
			slider,
			fileLegend = ['', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', ''];


		slider = $('<div>', {'class': 'pgn-slider'})
			.slider({
				max: 60,
				min: 20,
				orientation: 'vertical',
				value: gameSet.blockSize,
				stop: setWidth
			}).data({gameSet: gameSet});
		table = $('<table>', {'class': 'pgn-table', border: 0, cellpadding: 0, cellspacing: 0}).appendTo(container);
		if (selector)
			table.append($('<tr>').append($('<td>', {colspan: 10, 'class': 'pgn-selector'}).append(selector)));

		table.append($('<tr>').append(cdTd = $('<td>', {colspan: 10})));
		//controlsDiv = $('<div>').css({textAlign: 'right', width: '100%'}).appendTo(controlsTd);
		cdTable = $('<table>').css({width: '100%'}).appendTo(cdTd);
		$('<tr>')
			.appendTo(cdTable)
			.append(descriptionsTd = $('<td>'))
			.append(controlsTd = $('<td>', {'class': 'pgn-controls'}))
			.append(sliderTd = $('<td>', {'vertical-align': 'top'}).append(slider));
		var tr = $('<tr>').appendTo(table);
		for (var i in fileLegend)
			tr.append($('<td>', {'class': 'pgn-legend'}).text(fileLegend[i]));
		var blackSq = {height: 40, width: 40, 'class': 'pgn-game-square pgn-game-square-black'};
		var whiteSq = {height: 40, width: 40, 'class': 'pgn-game-square pgn-game-square-white'};

		for (var i = 0; i < 8; i++) { // create row i: legend, 8
			tr = $('<tr>').appendTo(table);
			tr.append($('<td>', {'class': 'pgn-legend pgn-row'}).text(8 - i));
			for (var file = 0; file < 8; file++) {
				var td = $('<td>', (((i+file)%2) ? blackSq : whiteSq));
				if (!i && !file)
					boardTd = td;
				tr.append(td);
			}
			tr.append($('<td>', {'class': 'pgn-legend pgn-row'}).text(8 - i));
		}
		tr = $('<tr>').appendTo(table);
		for (var i in fileLegend)
			tr.append($('<td>', {'class': 'pgn-legend'}).text(fileLegend[i]));
		table.append($('<tr>').append(pgnTd = $('<td>', {colspan: 10, 'class': 'pgn-pgn-moves'})));
		controlsTd.append(advance).append(slideShow).append(flipper);
		return {boardTd: boardTd, pgnTd: pgnTd, descriptionsTd: descriptionsTd};
	}

	function doIt() {

		$('div.pgn-source-wrapper').each(function() {
			var
				wrapperDiv = $(this),
				pgnSource = $('div.pgn-sourcegame', wrapperDiv),
				boardDiv,
				selector,
				gameSet = new Gameset();

			if (pgnSource.length > 1)
				selector = $('<select>').data({gameSet: gameSet}).change(selectGame);

			var tds = buildBoardDiv(wrapperDiv, selector, gameSet);
			var ind = 0;
			pgnSource.each(function() {
				try {
					var
						pgnDiv = $(this),
						game = new Game(tds, gameSet);
						game.analyzePgn(pgnDiv.text());
						game.gotoBoard(0);
						wrapperDiv.data({currentGame: game});
					ind++;

					gameSet.allGames.push(game);
					if (selector)
						selector.append($('<option>', {value: gameSet.allGames.length - 1, text: game.description()}));
					else
						game.show();
				} catch (e) {
					mw.log('exception in game ' + ind + ' problem is: "' + e + '"');
				}
			});
			if (selector)
				selector.trigger('change');
		})
	}

	function pupulateImages() {
		var
			colors = [WHITE, BLACK],
			allPieces = [],
			types = ['p', 'r', 'n', 'b', 'q', 'k'];
		for (var c in colors) {
			for (var t in types)
				allPieces.push('File:Chess ' + types[t] + colors[c] + 't45.svg');
			allPieces.push('File:Chess ' + colors[c] + '45.svg')
		}
		allPieces.push('File:Chess Board, gray.png');
		allPieces.push('File:Yin and Yang.svg');

		new mw.Api().get(
			{titles: allPieces.join('|'), prop: 'imageinfo', iiprop: 'url'},
			function(data) {
				if (data && data.query) {
					$.each(data.query.pages, function(index, page) {
						var
							url = page.imageinfo[0].url,
							match =
								url.match(/Chess_([prnbqk][dl])t45\.svg/) // piece
								|| url.match(/Chess_([dl])45\.svg/); // empty square
						if (match)
							imageUrl[match[1]] = url;
						else if (/Yin/.test(url))
							flipImageUrl = url;
					});
					doIt();
				}
			}
		);
	}

	if ($('div.pgn-source-wrapper').length) {
		mw.util.addCSS(
			'img.pgn-chessPiece { position: absolute; z-index: 3;}\n' +
			'div.pgn-board-div { position: relative;}\n' +
			'div.pgn-slider { float: right; clear: right; height: 120px;}\n' +
			'table.pgn-table { direction: ltr; width: 360px;}\n' +
			'td.pgn-selector { height: 2em; text-align: center; vertical-aligh: middle;}\n' +
			'td.pgn-controls { height: 2em; text-align: right; vertical-align: top;}\n' +
			'td.pgn-legend { text-align: center; vertical-align: middle;}\n' +
			'td.pgn-game-square { opacity; 0.8; width: 40px; height: 40px; text-align: left; vertical-align: top; padding: 0;}\n' +
			'td.pgn-game-square-black { background-color: #d18b47;}\n' +
			'td.pgn-game-square-white { background-color: #ffce9e;}\n' +
			'div.pgn-pgn-display { padding: 0.5em 2em; }\n' +
			'div.pgn-descriptions { padding: 0.5em 2em;}\n' +
			'span.pgn-movelink { margin: 0 0.3em;}\n' +
			'span.pgn-steplink { margin: 0 0.3em; color: green; font-weight: bold;}\n' +
			'span.pgn-comment { margin: 0 0.3em; color: blue;}\n' +
			'span.pgn-current-move { background-color: yellow;}');
		mw.loader.using(['mediawiki.api', 'jquery.ui'], pupulateImages);
	}
});