diff --git a/lib/chessboard.css b/lib/chessboard.css
index 27ffdccb..3696561b 100644
--- a/lib/chessboard.css
+++ b/lib/chessboard.css
@@ -1,15 +1,15 @@
-/*! chessboard.js v@VERSION | (c) 2019 Chris Oakman | MIT License chessboardjs.com/license */
+/*! chessboard.js v@VERSION | (c) 2019 Chris Oakman | MIT License chessboardjs.com/license */
-.clearfix-7da63 {
+.clearfix-7da63 {
clear: both;
}
-.board-b72b1 {
- border: 2px solid #404040;
- box-sizing: content-box;
+.board-b72b1 {
+ border: 2px solid #404040;
+ box-sizing: content-box;
}
-.square-55d63 {
+.square-55d63 {
float: left;
position: relative;
@@ -22,33 +22,50 @@
user-select: none;
}
-.white-1e1d7 {
+.white-1e1d7 {
background-color: #f0d9b5;
color: #b58863;
}
-.black-3c85d {
+.black-3c85d {
background-color: #b58863;
color: #f0d9b5;
}
-
-.highlight1-32417, .highlight2-9c5d2 {
+
+.highlight1-32417, .highlight2-9c5d2 {
box-shadow: inset 0 0 3px 3px yellow;
}
-
+
.notation-322f9 {
cursor: default;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 14px;
position: absolute;
-}
-
+}
+
.alpha-d2270 {
bottom: 1px;
right: 3px;
-}
-
+}
+
.numeric-fc462 {
top: 2px;
left: 2px;
-}
+}
+
+.piece-wrapper {
+ position: relative;
+ display: inline-block;
+}
+
+.spare-count {
+ border: 1px solid #ccc;
+ padding: 0 4px;
+ border-radius: 37%;
+ font-size: 12px;
+ position: absolute;
+ z-index: 1;
+ background: #fff;
+ right: 0;
+ bottom: 1px;
+}
diff --git a/lib/chessboard.js b/lib/chessboard.js
index ed27db18..f066aeff 100644
--- a/lib/chessboard.js
+++ b/lib/chessboard.js
@@ -4,9 +4,9 @@
// Copyright (c) 2019, Chris Oakman
// Released under the MIT license
// https://github.com/oakmac/chessboardjs/blob/master/LICENSE.md
-
-// start anonymous scope
-;(function () {
+
+// start anonymous scope
+;(function () {
'use strict'
var $ = window['jQuery']
@@ -179,20 +179,20 @@
rate >= 1
}
- function validMove (move) {
- // move should be a string
+ function validMove (move) {
+ // move should be a string
if (!isString(move)) return false
-
- // move should be in the form of "e2-e4", "f6-d5"
+
+ // move should be in the form of "e2-e4", "f6-d5"
var squares = move.split('-')
if (squares.length !== 2) return false
-
+
return validSquare(squares[0]) && validSquare(squares[1])
- }
-
+ }
+
function validSquare (square) {
return isString(square) && square.search(/^[a-h][1-8]$/) !== -1
- }
+ }
if (RUN_ASSERTS) {
console.assert(validSquare('a1'))
@@ -207,7 +207,7 @@
function validPieceCode (code) {
return isString(code) && code.search(/^[bw][KQRNBP]$/) !== -1
- }
+ }
if (RUN_ASSERTS) {
console.assert(validPieceCode('bP'))
@@ -224,28 +224,28 @@
function validFen (fen) {
if (!isString(fen)) return false
-
- // cut off any move, castling, etc info from the end
- // we're only interested in position information
- fen = fen.replace(/ .+$/, '')
+
+ // cut off any move, castling, etc info from the end
+ // we're only interested in position information
+ fen = fen.replace(/ .+$/, '')
// expand the empty square numbers to just 1s
fen = expandFenEmptySquares(fen)
- // FEN should be 8 sections separated by slashes
- var chunks = fen.split('/')
+ // FEN should be 8 sections separated by slashes
+ var chunks = fen.split('/')
if (chunks.length !== 8) return false
// check each section
for (var i = 0; i < 8; i++) {
if (chunks[i].length !== 8 ||
chunks[i].search(/[^kqrnbpKQRNBP1]/) !== -1) {
- return false
- }
- }
-
- return true
- }
+ return false
+ }
+ }
+
+ return true
+ }
if (RUN_ASSERTS) {
console.assert(validFen(START_FEN))
@@ -263,17 +263,17 @@
function validPositionObject (pos) {
if (!$.isPlainObject(pos)) return false
-
- for (var i in pos) {
- if (!pos.hasOwnProperty(i)) continue
-
- if (!validSquare(i) || !validPieceCode(pos[i])) {
- return false
- }
- }
-
- return true
- }
+
+ for (var i in pos) {
+ if (!pos.hasOwnProperty(i)) continue
+
+ if (!validSquare(i) || !validPieceCode(pos[i])) {
+ return false
+ }
+ }
+
+ return true
+ }
if (RUN_ASSERTS) {
console.assert(validPositionObject(START_POSITION))
@@ -326,76 +326,76 @@
return pieceCodeLetters[1].toLowerCase()
}
- // convert FEN string to position object
- // returns false if the FEN string is invalid
- function fenToObj (fen) {
+ // convert FEN string to position object
+ // returns false if the FEN string is invalid
+ function fenToObj (fen) {
if (!validFen(fen)) return false
-
- // cut off any move, castling, etc info from the end
- // we're only interested in position information
- fen = fen.replace(/ .+$/, '')
-
- var rows = fen.split('/')
- var position = {}
-
- var currentRow = 8
- for (var i = 0; i < 8; i++) {
- var row = rows[i].split('')
+
+ // cut off any move, castling, etc info from the end
+ // we're only interested in position information
+ fen = fen.replace(/ .+$/, '')
+
+ var rows = fen.split('/')
+ var position = {}
+
+ var currentRow = 8
+ for (var i = 0; i < 8; i++) {
+ var row = rows[i].split('')
var colIdx = 0
-
- // loop through each character in the FEN section
- for (var j = 0; j < row.length; j++) {
- // number / empty squares
- if (row[j].search(/[1-8]/) !== -1) {
+
+ // loop through each character in the FEN section
+ for (var j = 0; j < row.length; j++) {
+ // number / empty squares
+ if (row[j].search(/[1-8]/) !== -1) {
var numEmptySquares = parseInt(row[j], 10)
colIdx = colIdx + numEmptySquares
} else {
- // piece
+ // piece
var square = COLUMNS[colIdx] + currentRow
- position[square] = fenToPieceCode(row[j])
+ position[square] = fenToPieceCode(row[j])
colIdx = colIdx + 1
- }
- }
+ }
+ }
currentRow = currentRow - 1
- }
-
- return position
+ }
+
+ return position
}
- // position object to FEN string
- // returns false if the obj is not a valid position object
- function objToFen (obj) {
+ // position object to FEN string
+ // returns false if the obj is not a valid position object
+ function objToFen (obj) {
if (!validPositionObject(obj)) return false
-
- var fen = ''
-
- var currentRow = 8
- for (var i = 0; i < 8; i++) {
- for (var j = 0; j < 8; j++) {
- var square = COLUMNS[j] + currentRow
-
- // piece exists
+
+ var fen = ''
+
+ var currentRow = 8
+ for (var i = 0; i < 8; i++) {
+ for (var j = 0; j < 8; j++) {
+ var square = COLUMNS[j] + currentRow
+
+ // piece exists
if (obj.hasOwnProperty(square)) {
fen = fen + pieceCodeToFen(obj[square])
- } else {
- // empty space
+ } else {
+ // empty space
fen = fen + '1'
- }
- }
-
+ }
+ }
+
if (i !== 7) {
fen = fen + '/'
}
-
+
currentRow = currentRow - 1
- }
-
+ }
+
// squeeze the empty numbers together
fen = squeezeFenEmptySquares(fen)
- return fen
- }
+ return fen
+ }
if (RUN_ASSERTS) {
console.assert(objToFen(START_POSITION) === START_FEN)
@@ -520,13 +520,13 @@
var html = '
'
if (hasSparePieces) {
- html += '
'
+ html += '
'
}
html += '
'
if (hasSparePieces) {
- html += '
'
+ html += '
'
}
html += '
'
@@ -576,7 +576,7 @@
// default piece theme is wikipedia
if (!config.hasOwnProperty('pieceTheme') ||
(!isString(config.pieceTheme) && !isFunction(config.pieceTheme))) {
- config.pieceTheme = 'img/chesspieces/wikipedia/{piece}.png'
+ config.pieceTheme = '../images/{piece}.png'
}
// animation speeds
@@ -663,8 +663,8 @@
var $sparePiecesBottom = null
// constructor return object
- var widget = {}
-
+ var widget = {}
+
// -------------------------------------------------------------------------
// Stateful
// -------------------------------------------------------------------------
@@ -677,6 +677,7 @@
var draggedPieceSource = null
var isDragging = false
var sparePiecesElsIds = {}
+ var sparePiecesCountElsIds = {}
var squareElsIds = {}
var squareElsOffsets = {}
var squareSize = 16
@@ -684,44 +685,64 @@
// -------------------------------------------------------------------------
// Validation / Errors
// -------------------------------------------------------------------------
-
- function error (code, msg, obj) {
- // do nothing if showErrors is not set
- if (
+
+ function validSpareCounts(counts) {
+ if (typeof counts !== 'object' || Object.getOwnPropertyNames(counts).length === 0) {
+ return false
+ }
+
+ for (var i in counts) {
+ if (isNaN(counts[i])) {
+ return false
+ }
+ }
+ ['w', 'b'].forEach(function(color) {
+ ['K', 'Q', 'R', 'B', 'N', 'P'].forEach(function(kind) {
+ if (!counts.hasOwnProperty(color + kind)) {
+ counts[color + kind] = 0;
+ }
+ });
+ });
+ return true
+ }
+
+ function error (code, msg, obj) {
+ // do nothing if showErrors is not set
+ if (
config.hasOwnProperty('showErrors') !== true ||
config.showErrors === false
- ) {
- return
- }
-
+ ) {
+ return
+ }
+
var errorText = 'Chessboard Error ' + code + ': ' + msg
-
- // print to console
- if (
+
+ // print to console
+ if (
config.showErrors === 'console' &&
- typeof console === 'object' &&
- typeof console.log === 'function'
- ) {
- console.log(errorText)
- if (arguments.length >= 2) {
- console.log(obj)
- }
- return
- }
-
- // alert errors
+ typeof console === 'object' &&
+ typeof console.log === 'function'
+ ) {
+ console.log(errorText)
+ if (arguments.length >= 2) {
+ console.log(obj)
+ }
+ return
+ }
+
+ // alert errors
if (config.showErrors === 'alert') {
- if (obj) {
- errorText += '\n\n' + JSON.stringify(obj)
- }
- window.alert(errorText)
- return
- }
-
+ if (obj) {
+ errorText += '\n\n' + JSON.stringify(obj)
+ }
+ window.alert(errorText)
+ return
+ }
+
// custom function
if (isFunction(config.showErrors)) {
config.showErrors(code, msg, obj)
- }
+ }
}
function setInitialState () {
@@ -743,55 +764,81 @@
)
}
}
+
+ //spare piece counts
+ if (config.hasOwnProperty('spareCounts') === true) {
+ if (config.spareCounts === 'default') {
+ config.spareCounts = {
+ 'wK': 0,
+ 'wQ': 0,
+ 'wR': 0,
+ 'wB': 0,
+ 'wN': 0,
+ 'wP': 0,
+ 'bK': 0,
+ 'bQ': 0,
+ 'bR': 0,
+ 'bB': 0,
+ 'bN': 0,
+ 'bP': 0
+ }
+ } else if (!validSpareCounts(config.spareCounts)) {
+ config.spareCounts = false;
+ }
+ } else {
+ config.spareCounts = false;
+ }
}
// -------------------------------------------------------------------------
// DOM Misc
// -------------------------------------------------------------------------
-
+
// calculates square size based on the width of the container
// got a little CSS black magic here, so let me explain:
// get the width of the container element (could be anything), reduce by 1 for
// fudge factor, and then keep reducing until we find an exact mod 8 for
// our square size
- function calculateSquareSize () {
+ function calculateSquareSize () {
var containerWidth = parseInt($container.width(), 10)
-
+
// defensive, prevent infinite loop
if (!containerWidth || containerWidth <= 0) {
- return 0
- }
-
+ return 0
+ }
+
// pad one pixel
- var boardWidth = containerWidth - 1
-
+ var boardWidth = containerWidth - 1
+
while (boardWidth % 8 !== 0 && boardWidth > 0) {
boardWidth = boardWidth - 1
- }
-
- return boardWidth / 8
- }
-
+ }
+
+ return boardWidth / 8
+ }
+
// create random IDs for elements
function createElIds () {
// squares on the board
for (var i = 0; i < COLUMNS.length; i++) {
for (var j = 1; j <= 8; j++) {
- var square = COLUMNS[i] + j
+ var square = COLUMNS[i] + j
squareElsIds[square] = square + '-' + uuid()
- }
- }
-
+ }
+ }
+
// spare pieces
var pieces = 'KQRNBP'.split('')
for (i = 0; i < pieces.length; i++) {
- var whitePiece = 'w' + pieces[i]
- var blackPiece = 'b' + pieces[i]
+ var whitePiece = 'w' + pieces[i]
+ var blackPiece = 'b' + pieces[i]
sparePiecesElsIds[whitePiece] = whitePiece + '-' + uuid()
sparePiecesElsIds[blackPiece] = blackPiece + '-' + uuid()
- }
- }
-
+ sparePiecesCountElsIds[whitePiece] = 'count' + whitePiece + ' - ' + uuid()
+ sparePiecesCountElsIds[blackPiece] = 'count' + blackPiece + ' - ' + uuid()
+ }
+ }
+
// -------------------------------------------------------------------------
// Markup Building
// -------------------------------------------------------------------------
@@ -853,158 +900,173 @@
return interpolateTemplate(html, CSS)
}
-
- function buildPieceImgSrc (piece) {
+
+ function buildPieceImgSrc (piece) {
if (isFunction(config.pieceTheme)) {
return config.pieceTheme(piece)
- }
-
+ }
+
if (isString(config.pieceTheme)) {
return interpolateTemplate(config.pieceTheme, {piece: piece})
- }
-
+ }
+
// NOTE: this should never happen
error(8272, 'Unable to build image source for config.pieceTheme.')
- return ''
- }
-
- function buildPieceHTML (piece, hidden, id) {
- var html = '
' + config.spareCounts[piece] + ''
+ }
+
+ function buildPieceHTML (piece, hidden, id, is_spare) {
+ var html = ' + ')
'
-
- return interpolateTemplate(html, CSS)
- }
-
+ if (is_spare) {
+ html += '" />' + spareCountHtml(piece, hidden)
+ } else {
+ html += '" />';
+ }
+
+ return interpolateTemplate(html + '
', CSS)
+ }
+
function buildSparePiecesHTML (color) {
- var pieces = ['wK', 'wQ', 'wR', 'wB', 'wN', 'wP']
- if (color === 'black') {
- pieces = ['bK', 'bQ', 'bR', 'bB', 'bN', 'bP']
- }
-
- var html = ''
- for (var i = 0; i < pieces.length; i++) {
- html += buildPieceHTML(pieces[i], false, sparePiecesElsIds[pieces[i]])
- }
-
- return html
- }
-
+ var pieces = ['wK', 'wQ', 'wR', 'wB', 'wN', 'wP']
+ if (color === 'black') {
+ pieces = ['bK', 'bQ', 'bR', 'bB', 'bN', 'bP']
+ }
+
+ var html = ''
+ for (var i = 0; i < pieces.length; i++) {
+ html += buildPieceHTML(pieces[i], false, sparePiecesElsIds[pieces[i]], true)
+ }
+
+ return html
+ }
+
// -------------------------------------------------------------------------
// Animations
// -------------------------------------------------------------------------
-
- function animateSquareToSquare (src, dest, piece, completeFn) {
+
+ function animateSquareToSquare (src, dest, piece, completeFn) {
// get information about the source and destination squares
var $srcSquare = $('#' + squareElsIds[src])
var srcSquarePosition = $srcSquare.offset()
var $destSquare = $('#' + squareElsIds[dest])
var destSquarePosition = $destSquare.offset()
-
+
// create the animated piece and absolutely position it
// over the source square
- var animatedPieceId = uuid()
+ var animatedPieceId = uuid()
$('body').append(buildPieceHTML(piece, true, animatedPieceId))
var $animatedPiece = $('#' + animatedPieceId)
$animatedPiece.css({
- display: '',
- position: 'absolute',
- top: srcSquarePosition.top,
- left: srcSquarePosition.left
- })
-
+ display: '',
+ position: 'absolute',
+ top: srcSquarePosition.top,
+ left: srcSquarePosition.left
+ })
+
// remove original piece from source square
$srcSquare.find('.' + CSS.piece).remove()
function onFinishAnimation1 () {
// add the "real" piece to the destination square
$destSquare.append(buildPieceHTML(piece))
-
+
// remove the animated piece
$animatedPiece.remove()
-
+
// run complete function
if (isFunction(completeFn)) {
- completeFn()
- }
- }
-
+ completeFn()
+ }
+ }
+
// animate the piece to the destination square
- var opts = {
+ var opts = {
duration: config.moveSpeed,
complete: onFinishAnimation1
- }
+ }
$animatedPiece.animate(destSquarePosition, opts)
- }
-
- function animateSparePieceToSquare (piece, dest, completeFn) {
+ }
+
+ function animateSparePieceToSquare (piece, dest, completeFn) {
var srcOffset = $('#' + sparePiecesElsIds[piece]).offset()
var $destSquare = $('#' + squareElsIds[dest])
var destOffset = $destSquare.offset()
-
+
// create the animate piece
- var pieceId = uuid()
+ var pieceId = uuid()
$('body').append(buildPieceHTML(piece, true, pieceId))
var $animatedPiece = $('#' + pieceId)
$animatedPiece.css({
- display: '',
- position: 'absolute',
- left: srcOffset.left,
- top: srcOffset.top
- })
-
+ display: '',
+ position: 'absolute',
+ left: srcOffset.left,
+ top: srcOffset.top
+ })
+
// on complete
function onFinishAnimation2 () {
// add the "real" piece to the destination square
$destSquare.find('.' + CSS.piece).remove()
$destSquare.append(buildPieceHTML(piece))
-
+
// remove the animated piece
$animatedPiece.remove()
-
+
// run complete function
if (isFunction(completeFn)) {
- completeFn()
- }
- }
-
+ completeFn()
+ }
+ }
+
// animate the piece to the destination square
- var opts = {
+ var opts = {
duration: config.moveSpeed,
complete: onFinishAnimation2
- }
+ }
$animatedPiece.animate(destOffset, opts)
}
// execute an array of animations
function doAnimations (animations, oldPos, newPos) {
if (animations.length === 0) return
-
- var numFinished = 0
+
+ var numFinished = 0
function onFinishAnimation3 () {
// exit if all the animations aren't finished
numFinished = numFinished + 1
if (numFinished !== animations.length) return
drawPositionInstant()
-
+
// run their onMoveEnd function
if (isFunction(config.onMoveEnd)) {
config.onMoveEnd(deepCopy(oldPos), deepCopy(newPos))
- }
- }
-
+ }
+ }
+
for (var i = 0; i < animations.length; i++) {
var animation = animations[i]
@@ -1027,178 +1089,178 @@
// move a piece from squareA to squareB
} else if (animation.type === 'move') {
animateSquareToSquare(animation.source, animation.destination, animation.piece, onFinishAnimation3)
- }
- }
+ }
+ }
}
// calculate an array of animations that need to happen in order to get
// from pos1 to pos2
- function calculateAnimations (pos1, pos2) {
+ function calculateAnimations (pos1, pos2) {
// make copies of both
- pos1 = deepCopy(pos1)
- pos2 = deepCopy(pos2)
-
- var animations = []
- var squaresMovedTo = {}
-
+ pos1 = deepCopy(pos1)
+ pos2 = deepCopy(pos2)
+
+ var animations = []
+ var squaresMovedTo = {}
+
// remove pieces that are the same in both positions
- for (var i in pos2) {
- if (!pos2.hasOwnProperty(i)) continue
-
- if (pos1.hasOwnProperty(i) && pos1[i] === pos2[i]) {
- delete pos1[i]
- delete pos2[i]
- }
- }
-
+ for (var i in pos2) {
+ if (!pos2.hasOwnProperty(i)) continue
+
+ if (pos1.hasOwnProperty(i) && pos1[i] === pos2[i]) {
+ delete pos1[i]
+ delete pos2[i]
+ }
+ }
+
// find all the "move" animations
for (i in pos2) {
- if (!pos2.hasOwnProperty(i)) continue
-
- var closestPiece = findClosestPiece(pos1, pos2[i], i)
+ if (!pos2.hasOwnProperty(i)) continue
+
+ var closestPiece = findClosestPiece(pos1, pos2[i], i)
if (closestPiece) {
- animations.push({
- type: 'move',
- source: closestPiece,
- destination: i,
- piece: pos2[i]
- })
-
- delete pos1[closestPiece]
- delete pos2[i]
- squaresMovedTo[i] = true
- }
+ animations.push({
+ type: 'move',
+ source: closestPiece,
+ destination: i,
+ piece: pos2[i]
+ })
+
+ delete pos1[closestPiece]
+ delete pos2[i]
+ squaresMovedTo[i] = true
+ }
}
// "add" animations
for (i in pos2) {
if (!pos2.hasOwnProperty(i)) continue
-
- animations.push({
- type: 'add',
- square: i,
- piece: pos2[i]
- })
-
- delete pos2[i]
- }
+
+ animations.push({
+ type: 'add',
+ square: i,
+ piece: pos2[i]
+ })
+
+ delete pos2[i]
+ }
// "clear" animations
for (i in pos1) {
if (!pos1.hasOwnProperty(i)) continue
-
+
// do not clear a piece if it is on a square that is the result
// of a "move", ie: a piece capture
if (squaresMovedTo.hasOwnProperty(i)) continue
-
- animations.push({
- type: 'clear',
- square: i,
- piece: pos1[i]
- })
-
- delete pos1[i]
- }
-
- return animations
- }
-
+
+ animations.push({
+ type: 'clear',
+ square: i,
+ piece: pos1[i]
+ })
+
+ delete pos1[i]
+ }
+
+ return animations
+ }
+
// -------------------------------------------------------------------------
// Control Flow
// -------------------------------------------------------------------------
- function drawPositionInstant () {
+ function drawPositionInstant () {
// clear the board
$board.find('.' + CSS.piece).remove()
-
+
// add the pieces
for (var i in currentPosition) {
if (!currentPosition.hasOwnProperty(i)) continue
-
+
$('#' + squareElsIds[i]).append(buildPieceHTML(currentPosition[i]))
- }
- }
-
- function drawBoard () {
+ }
+ }
+
+ function drawBoard () {
$board.html(buildBoardHTML(currentOrientation, squareSize, config.showNotation))
- drawPositionInstant()
-
+ drawPositionInstant()
+
if (config.sparePieces) {
if (currentOrientation === 'white') {
$sparePiecesTop.html(buildSparePiecesHTML('black'))
$sparePiecesBottom.html(buildSparePiecesHTML('white'))
- } else {
+ } else {
$sparePiecesTop.html(buildSparePiecesHTML('white'))
$sparePiecesBottom.html(buildSparePiecesHTML('black'))
- }
- }
- }
+ }
+ }
+ }
- function setCurrentPosition (position) {
+ function setCurrentPosition (position) {
var oldPos = deepCopy(currentPosition)
- var newPos = deepCopy(position)
- var oldFen = objToFen(oldPos)
- var newFen = objToFen(newPos)
-
+ var newPos = deepCopy(position)
+ var oldFen = objToFen(oldPos)
+ var newFen = objToFen(newPos)
+
// do nothing if no change in position
- if (oldFen === newFen) return
-
+ if (oldFen === newFen) return
+
// run their onChange function
if (isFunction(config.onChange)) {
config.onChange(oldPos, newPos)
- }
-
+ }
+
// update state
currentPosition = position
- }
-
- function isXYOnSquare (x, y) {
+ }
+
+ function isXYOnSquare (x, y) {
for (var i in squareElsOffsets) {
if (!squareElsOffsets.hasOwnProperty(i)) continue
-
+
var s = squareElsOffsets[i]
if (x >= s.left &&
x < s.left + squareSize &&
- y >= s.top &&
+ y >= s.top &&
y < s.top + squareSize) {
- return i
- }
- }
-
- return 'offboard'
- }
-
+ return i
+ }
+ }
+
+ return 'offboard'
+ }
+
// records the XY coords of every square into memory
- function captureSquareOffsets () {
+ function captureSquareOffsets () {
squareElsOffsets = {}
-
+
for (var i in squareElsIds) {
if (!squareElsIds.hasOwnProperty(i)) continue
-
+
squareElsOffsets[i] = $('#' + squareElsIds[i]).offset()
- }
- }
-
- function removeSquareHighlights () {
+ }
+ }
+
+ function removeSquareHighlights () {
$board
.find('.' + CSS.square)
.removeClass(CSS.highlight1 + ' ' + CSS.highlight2)
- }
-
- function snapbackDraggedPiece () {
+ }
+
+ function snapbackDraggedPiece () {
// there is no "snapback" for spare pieces
if (draggedPieceSource === 'spare') {
- trashDraggedPiece()
- return
- }
-
- removeSquareHighlights()
-
+ trashDraggedPiece()
+ return
+ }
+
+ removeSquareHighlights()
+
// animation complete
- function complete () {
- drawPositionInstant()
+ function complete () {
+ drawPositionInstant()
$draggedPiece.css('display', 'none')
-
+
// run their onSnapbackEnd function
if (isFunction(config.onSnapbackEnd)) {
config.onSnapbackEnd(
@@ -1206,313 +1268,318 @@
draggedPieceSource,
deepCopy(currentPosition),
currentOrientation
- )
- }
- }
-
+ )
+ }
+ }
+
// get source square position
var sourceSquarePosition = $('#' + squareElsIds[draggedPieceSource]).offset()
-
+
// animate the piece to the target square
- var opts = {
+ var opts = {
duration: config.snapbackSpeed,
- complete: complete
- }
+ complete: complete
+ }
$draggedPiece.animate(sourceSquarePosition, opts)
-
+
// set state
isDragging = false
- }
-
- function trashDraggedPiece () {
- removeSquareHighlights()
-
+ }
+
+ function trashDraggedPiece () {
+ removeSquareHighlights()
+
// remove the source piece
var newPosition = deepCopy(currentPosition)
delete newPosition[draggedPieceSource]
- setCurrentPosition(newPosition)
-
+ setCurrentPosition(newPosition)
+
// redraw the position
- drawPositionInstant()
-
+ drawPositionInstant()
+
// hide the dragged piece
$draggedPiece.fadeOut(config.trashSpeed)
-
+
// set state
isDragging = false
- }
-
- function dropDraggedPieceOnSquare (square) {
- removeSquareHighlights()
-
+ }
+
+ function dropDraggedPieceOnSquare (square) {
+ removeSquareHighlights()
+
// update position
var newPosition = deepCopy(currentPosition)
delete newPosition[draggedPieceSource]
newPosition[square] = draggedPiece
- setCurrentPosition(newPosition)
-
+ setCurrentPosition(newPosition)
+
// get target square information
var targetSquarePosition = $('#' + squareElsIds[square]).offset()
-
+
// animation complete
function onAnimationComplete () {
- drawPositionInstant()
+ drawPositionInstant()
$draggedPiece.css('display', 'none')
-
+
// execute their onSnapEnd function
if (isFunction(config.onSnapEnd)) {
config.onSnapEnd(draggedPieceSource, square, draggedPiece)
- }
- }
-
+ }
+ }
+
// snap the piece to the target square
- var opts = {
+ var opts = {
duration: config.snapSpeed,
complete: onAnimationComplete
- }
+ }
$draggedPiece.animate(targetSquarePosition, opts)
-
+
// set state
isDragging = false
- }
-
- function beginDraggingPiece (source, piece, x, y) {
+ }
+
+ function beginDraggingPiece (source, piece, x, y) {
// run their custom onDragStart function
// their custom onDragStart function can cancel drag start
if (isFunction(config.onDragStart) &&
- config.onDragStart(source, piece, deepCopy(currentPosition), currentOrientation) === false) {
- return
- }
-
+ config.onDragStart(source, piece, deepCopy(currentPosition), currentOrientation) === false ||
+ (source === 'spare' && (typeof config.spareCounts === 'object' && config.spareCounts[piece] < 1))) {
+ return
+ }
+
// set state
isDragging = true
draggedPiece = piece
draggedPieceSource = source
-
+
// if the piece came from spare pieces, location is offboard
- if (source === 'spare') {
+ if (source === 'spare') {
draggedPieceLocation = 'offboard'
- } else {
+ } else {
draggedPieceLocation = source
- }
-
+ }
+
// capture the x, y coords of all squares in memory
- captureSquareOffsets()
-
+ captureSquareOffsets()
+
// create the dragged piece
$draggedPiece.attr('src', buildPieceImgSrc(piece)).css({
- display: '',
- position: 'absolute',
+ display: '',
+ position: 'fixed',
left: x - squareSize / 2,
top: y - squareSize / 2
- })
-
- if (source !== 'spare') {
+ })
+
+ if (source !== 'spare') {
// highlight the source square and hide the piece
$('#' + squareElsIds[source])
.addClass(CSS.highlight1)
.find('.' + CSS.piece)
.css('display', 'none')
- }
- }
-
- function updateDraggedPiece (x, y) {
+ }
+ }
+
+ function updateDraggedPiece (x, y) {
// put the dragged piece over the mouse cursor
$draggedPiece.css({
left: x - squareSize / 2,
top: y - squareSize / 2
- })
-
+ })
+
// get location
- var location = isXYOnSquare(x, y)
-
+ var location = isXYOnSquare(x, y)
+
// do nothing if the location has not changed
if (location === draggedPieceLocation) return
-
+
// remove highlight from previous square
if (validSquare(draggedPieceLocation)) {
$('#' + squareElsIds[draggedPieceLocation]).removeClass(CSS.highlight2)
- }
-
+ }
+
// add highlight to new square
if (validSquare(location)) {
$('#' + squareElsIds[location]).addClass(CSS.highlight2)
- }
-
+ }
+
// run onDragMove
if (isFunction(config.onDragMove)) {
config.onDragMove(
- location,
+ location,
draggedPieceLocation,
draggedPieceSource,
draggedPiece,
deepCopy(currentPosition),
currentOrientation
- )
- }
-
+ )
+ }
+
// update state
draggedPieceLocation = location
- }
-
- function stopDraggedPiece (location) {
+ }
+
+ function stopDraggedPiece (location) {
// determine what the action should be
- var action = 'drop'
+ var action = 'drop'
if (location === 'offboard' && config.dropOffBoard === 'snapback') {
- action = 'snapback'
- }
+ action = 'snapback'
+ }
if (location === 'offboard' && config.dropOffBoard === 'trash') {
- action = 'trash'
- }
-
+ action = 'trash'
+ }
+
// run their onDrop function, which can potentially change the drop action
if (isFunction(config.onDrop)) {
var newPosition = deepCopy(currentPosition)
-
+
// source piece is a spare piece and position is off the board
// if (draggedPieceSource === 'spare' && location === 'offboard') {...}
// position has not changed; do nothing
-
+
// source piece is a spare piece and position is on the board
if (draggedPieceSource === 'spare' && validSquare(location)) {
// add the piece to the board
newPosition[location] = draggedPiece
- }
-
+ }
+
// source piece was on the board and position is off the board
if (validSquare(draggedPieceSource) && location === 'offboard') {
// remove the piece from the board
delete newPosition[draggedPieceSource]
- }
-
+ }
+
// source piece was on the board and position is on the board
if (validSquare(draggedPieceSource) && validSquare(location)) {
// move the piece
delete newPosition[draggedPieceSource]
newPosition[location] = draggedPiece
- }
-
+ }
+
var oldPosition = deepCopy(currentPosition)
-
+
var result = config.onDrop(
draggedPieceSource,
- location,
+ location,
draggedPiece,
- newPosition,
- oldPosition,
+ newPosition,
+ oldPosition,
currentOrientation
- )
- if (result === 'snapback' || result === 'trash') {
- action = result
- }
- }
-
+ )
+ if (result === 'snapback' || result === 'trash') {
+ action = result
+ }
+ }
+
// do it!
- if (action === 'snapback') {
- snapbackDraggedPiece()
- } else if (action === 'trash') {
- trashDraggedPiece()
- } else if (action === 'drop') {
- dropDraggedPieceOnSquare(location)
- }
- }
-
+ if (action === 'snapback') {
+ snapbackDraggedPiece()
+ } else if (action === 'trash') {
+ trashDraggedPiece()
+ } else if (action === 'drop') {
+ dropDraggedPieceOnSquare(location)
+ }
+ if (config.spareCounts && draggedPieceSource === 'spare' && action === 'drop') {
+ config.spareCounts[draggedPiece] -= 1;
+ var elem = document.getElementById(sparePiecesCountElsIds[draggedPiece]).innerText = config.spareCounts[draggedPiece];
+ }
+ }
+
// -------------------------------------------------------------------------
// Public Methods
// -------------------------------------------------------------------------
-
+
// clear the board
widget.clear = function (useAnimation) {
- widget.position({}, useAnimation)
+ widget.position({}, useAnimation)
}
-
+
// remove the widget from the page
widget.destroy = function () {
// remove markup
$container.html('')
$draggedPiece.remove()
-
+
// remove event handlers
$container.unbind()
}
-
+
// shorthand method to get the current FEN
- widget.fen = function () {
- return widget.position('fen')
- }
-
+ widget.fen = function () {
+ return widget.position('fen')
+ }
+
// flip orientation
widget.flip = function () {
- return widget.orientation('flip')
+ return widget.orientation('flip')
}
// move pieces
// TODO: this method should be variadic as well as accept an array of moves
- widget.move = function () {
+ widget.move = function () {
// no need to throw an error here; just do nothing
// TODO: this should return the current position
- if (arguments.length === 0) return
-
- var useAnimation = true
-
+ if (arguments.length === 0) return
+
+ var useAnimation = true
+
// collect the moves into an object
- var moves = {}
- for (var i = 0; i < arguments.length; i++) {
+ var moves = {}
+ for (var i = 0; i < arguments.length; i++) {
// any "false" to this function means no animations
- if (arguments[i] === false) {
- useAnimation = false
- continue
- }
-
+ if (arguments[i] === false) {
+ useAnimation = false
+ continue
+ }
+
// skip invalid arguments
if (!validMove(arguments[i])) {
- error(2826, 'Invalid move passed to the move method.', arguments[i])
- continue
- }
-
- var tmp = arguments[i].split('-')
- moves[tmp[0]] = tmp[1]
- }
-
+ error(2826, 'Invalid move passed to the move method.', arguments[i])
+ continue
+ }
+
+ var tmp = arguments[i].split('-')
+ moves[tmp[0]] = tmp[1]
+ }
+
// calculate position from moves
var newPos = calculatePositionFromMoves(currentPosition, moves)
-
+
// update the board
- widget.position(newPos, useAnimation)
-
+ widget.position(newPos, useAnimation)
+
// return the new position object
- return newPos
- }
-
- widget.orientation = function (arg) {
+ return newPos
+ }
+
+ widget.orientation = function (arg) {
// no arguments, return the current orientation
- if (arguments.length === 0) {
+ if (arguments.length === 0) {
return currentOrientation
- }
-
+ }
+
// set to white or black
- if (arg === 'white' || arg === 'black') {
+ if (arg === 'white' || arg === 'black') {
currentOrientation = arg
- drawBoard()
+ drawBoard()
return currentOrientation
- }
-
+ }
+
// flip orientation
- if (arg === 'flip') {
+ if (arg === 'flip') {
currentOrientation = currentOrientation === 'white' ? 'black' : 'white'
- drawBoard()
+ drawBoard()
return currentOrientation
- }
-
- error(5482, 'Invalid value passed to the orientation method.', arg)
+ }
+
+ error(5482, 'Invalid value passed to the orientation method.', arg)
}
widget.position = function (position, useAnimation) {
// no arguments, return the current position
- if (arguments.length === 0) {
+ if (arguments.length === 0) {
return deepCopy(currentPosition)
- }
-
+ }
+
// get position as FEN
if (isString(position) && position.toLowerCase() === 'fen') {
return objToFen(currentPosition)
@@ -1520,19 +1587,19 @@
// start position
if (isString(position) && position.toLowerCase() === 'start') {
- position = deepCopy(START_POSITION)
- }
-
+ position = deepCopy(START_POSITION)
+ }
+
// convert FEN to position object
if (validFen(position)) {
- position = fenToObj(position)
- }
-
+ position = fenToObj(position)
+ }
+
// validate position object
if (!validPositionObject(position)) {
- error(6482, 'Invalid value passed to the position method.', position)
- return
- }
+ error(6482, 'Invalid value passed to the position method.', position)
+ return
+ }
// default for useAnimations is true
if (useAnimation !== false) useAnimation = true
@@ -1541,43 +1608,41 @@
// start the animations
var animations = calculateAnimations(currentPosition, position)
doAnimations(animations, currentPosition, position)
-
+
// set the new position
- setCurrentPosition(position)
- } else {
+ setCurrentPosition(position)
+ } else {
// instant update
- setCurrentPosition(position)
- drawPositionInstant()
- }
+ setCurrentPosition(position)
+ drawPositionInstant()
+ }
}
widget.resize = function () {
// calulate the new square size
squareSize = calculateSquareSize()
-
+
// set board width
$board.css('width', squareSize * 8 + 'px')
-
+
// set drag piece size
$draggedPiece.css({
height: squareSize,
width: squareSize
- })
-
+ })
+
// spare pieces
if (config.sparePieces) {
- $container
- .find('.' + CSS.sparePieces)
- .css('paddingLeft', squareSize + boardBorderSize + 'px')
- }
-
+ $container.css('paddingLeft', ($board.clientWidth - $container.offsetWidth) / 2 + 'px');
+ }
+
// redraw the board
- drawBoard()
+ drawBoard()
}
// set the starting position
widget.start = function (useAnimation) {
- widget.position('start', useAnimation)
+ widget.position('start', useAnimation)
}
// -------------------------------------------------------------------------
@@ -1586,8 +1651,8 @@
function stopDefault (evt) {
evt.preventDefault()
- }
-
+ }
+
function mousedownSquare (evt) {
// do nothing if we're not draggable
if (!config.draggable) return
@@ -1600,48 +1665,48 @@
beginDraggingPiece(square, currentPosition[square], evt.pageX, evt.pageY)
}
- function touchstartSquare (e) {
+ function touchstartSquare (e) {
// do nothing if we're not draggable
if (!config.draggable) return
-
+
// do nothing if there is no piece on this square
var square = $(this).attr('data-square')
if (!validSquare(square)) return
if (!currentPosition.hasOwnProperty(square)) return
-
- e = e.originalEvent
- beginDraggingPiece(
- square,
+
+ e = e.originalEvent
+ beginDraggingPiece(
+ square,
currentPosition[square],
- e.changedTouches[0].pageX,
- e.changedTouches[0].pageY
- )
- }
-
+ e.changedTouches[0].pageX,
+ e.changedTouches[0].pageY
+ )
+ }
+
function mousedownSparePiece (evt) {
// do nothing if sparePieces is not enabled
if (!config.sparePieces) return
-
- var piece = $(this).attr('data-piece')
-
+
+ var piece = $(this).attr('data-piece')
+
beginDraggingPiece('spare', piece, evt.pageX, evt.pageY)
- }
-
- function touchstartSparePiece (e) {
+ }
+
+ function touchstartSparePiece (e) {
// do nothing if sparePieces is not enabled
if (!config.sparePieces) return
-
- var piece = $(this).attr('data-piece')
-
- e = e.originalEvent
- beginDraggingPiece(
- 'spare',
- piece,
- e.changedTouches[0].pageX,
- e.changedTouches[0].pageY
- )
- }
-
+
+ var piece = $(this).attr('data-piece')
+
+ e = e.originalEvent
+ beginDraggingPiece(
+ 'spare',
+ piece,
+ e.changedTouches[0].pageX,
+ e.changedTouches[0].pageY
+ )
+ }
+
function mousemoveWindow (evt) {
if (isDragging) {
updateDraggedPiece(evt.pageX, evt.pageY)
@@ -1653,37 +1718,37 @@
function touchmoveWindow (evt) {
// do nothing if we are not dragging a piece
if (!isDragging) return
-
+
// prevent screen from scrolling
evt.preventDefault()
-
+
updateDraggedPiece(evt.originalEvent.changedTouches[0].pageX,
evt.originalEvent.changedTouches[0].pageY)
- }
+ }
var throttledTouchmoveWindow = throttle(touchmoveWindow, config.dragThrottleRate)
function mouseupWindow (evt) {
// do nothing if we are not dragging a piece
if (!isDragging) return
-
+
// get the location
var location = isXYOnSquare(evt.pageX, evt.pageY)
-
- stopDraggedPiece(location)
- }
-
+
+ stopDraggedPiece(location)
+ }
+
function touchendWindow (evt) {
// do nothing if we are not dragging a piece
if (!isDragging) return
-
+
// get the location
var location = isXYOnSquare(evt.originalEvent.changedTouches[0].pageX,
evt.originalEvent.changedTouches[0].pageY)
-
- stopDraggedPiece(location)
- }
-
+
+ stopDraggedPiece(location)
+ }
+
function mouseenterSquare (evt) {
// do not fire this event if we are dragging a piece
// NOTE: this should never happen, but it's a safeguard
@@ -1691,23 +1756,23 @@
// exit if they did not provide a onMouseoverSquare function
if (!isFunction(config.onMouseoverSquare)) return
-
+
// get the square
var square = $(evt.currentTarget).attr('data-square')
-
+
// NOTE: this should never happen; defensive
if (!validSquare(square)) return
-
+
// get the piece on this square
- var piece = false
+ var piece = false
if (currentPosition.hasOwnProperty(square)) {
piece = currentPosition[square]
- }
-
+ }
+
// execute their function
config.onMouseoverSquare(square, piece, deepCopy(currentPosition), currentOrientation)
- }
-
+ }
+
function mouseleaveSquare (evt) {
// do not fire this event if we are dragging a piece
// NOTE: this should never happen, but it's a safeguard
@@ -1715,35 +1780,35 @@
// exit if they did not provide an onMouseoutSquare function
if (!isFunction(config.onMouseoutSquare)) return
-
+
// get the square
var square = $(evt.currentTarget).attr('data-square')
-
+
// NOTE: this should never happen; defensive
if (!validSquare(square)) return
-
+
// get the piece on this square
- var piece = false
+ var piece = false
if (currentPosition.hasOwnProperty(square)) {
piece = currentPosition[square]
- }
-
+ }
+
// execute their function
config.onMouseoutSquare(square, piece, deepCopy(currentPosition), currentOrientation)
- }
-
+ }
+
// -------------------------------------------------------------------------
// Initialization
// -------------------------------------------------------------------------
- function addEvents () {
+ function addEvents () {
// prevent "image drag"
$('body').on('mousedown mousemove', '.' + CSS.piece, stopDefault)
// mouse drag pieces
$board.on('mousedown', '.' + CSS.square, mousedownSquare)
$container.on('mousedown', '.' + CSS.sparePieces + ' .' + CSS.piece, mousedownSparePiece)
-
+
// mouse enter / leave square
$board
.on('mouseenter', '.' + CSS.square, mouseenterSquare)
@@ -1754,7 +1819,7 @@
$window
.on('mousemove', throttledMousemoveWindow)
.on('mouseup', mouseupWindow)
-
+
// touch drag pieces
if (isTouchDevice()) {
$board.on('touchstart', '.' + CSS.square, touchstartSquare)
@@ -1762,24 +1827,24 @@
$window
.on('touchmove', throttledTouchmoveWindow)
.on('touchend', touchendWindow)
- }
- }
-
+ }
+ }
+
function initDOM () {
// create unique IDs for all the elements we will create
- createElIds()
-
+ createElIds()
+
// build board and save it in memory
$container.html(buildContainerHTML(config.sparePieces))
$board = $container.find('.' + CSS.board)
-
+
if (config.sparePieces) {
$sparePiecesTop = $container.find('.' + CSS.sparePiecesTop)
$sparePiecesBottom = $container.find('.' + CSS.sparePiecesBottom)
- }
-
+ }
+
// create the drag piece
- var draggedPieceId = uuid()
+ var draggedPieceId = uuid()
$('body').append(buildPieceHTML('wP', true, draggedPieceId))
$draggedPiece = $('#' + draggedPieceId)
@@ -1788,10 +1853,10 @@
// get the border size
boardBorderSize = parseInt($board.css('borderLeftWidth'), 10)
-
+
// set the size and draw the board
- widget.resize()
- }
+ widget.resize()
+ }
// -------------------------------------------------------------------------
// Initialization
@@ -1811,10 +1876,10 @@
// support legacy ChessBoard name
window['ChessBoard'] = window['Chessboard']
- // expose util functions
+ // expose util functions
window['Chessboard']['fenToObj'] = fenToObj
window['Chessboard']['objToFen'] = objToFen
-})() // end anonymous wrapper
+})() // end anonymous wrapper
/* export Chessboard object if using node or any other CommonJS compatible
* environment */