From d49bb51c1607427df56e311b9cd1c144bab08112 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20Kri=C5=A1to?= Date: Sun, 30 Aug 2020 14:08:15 +0200 Subject: [PATCH 1/3] Make it possible to create chessboards which are smaller than 8x8 (from 1x1 to 8x8, always a square). --- lib/chessboard.js | 157 +++++++++++++++++++++++++++++----------------- 1 file changed, 101 insertions(+), 56 deletions(-) diff --git a/lib/chessboard.js b/lib/chessboard.js index ed27db18..da70fea1 100644 --- a/lib/chessboard.js +++ b/lib/chessboard.js @@ -15,7 +15,6 @@ // Constants // --------------------------------------------------------------------------- - var COLUMNS = 'abcdefgh'.split('') var DEFAULT_DRAG_THROTTLE_RATE = 20 var ELLIPSIS = '…' var MINIMUM_JQUERY_VERSION = '1.8.3' @@ -179,7 +178,7 @@ rate >= 1 } - function validMove (move) { + function validMove (move, dimensions={rows: 8, columns: 8}) { // move should be a string if (!isString(move)) return false @@ -187,11 +186,22 @@ var squares = move.split('-') if (squares.length !== 2) return false - return validSquare(squares[0]) && validSquare(squares[1]) + return validSquare(squares[0], dimensions) && + validSquare(squares[1], dimensions) } - function validSquare (square) { - return isString(square) && square.search(/^[a-h][1-8]$/) !== -1 + function validSquare (square, dimensions={rows: 8, columns: 8}) { + if (!isString(square)) return false + var column = square[0] + if (column < columnIdentifier(0) || + column > columnIdentifier(dimensions.columns - 1)) { + return false + } + + var rowStr = square.substr(1) + var row = parseInt(rowStr, 10) + if (isNaN(row)) return false; + return 1 <= row && row <= dimensions.rows } if (RUN_ASSERTS) { @@ -223,6 +233,7 @@ } function validFen (fen) { + // NOTE: Fen (by definition) works only for 8x8 boards. if (!isString(fen)) return false // cut off any move, castling, etc info from the end @@ -261,13 +272,13 @@ console.assert(!validFen({})) } - function validPositionObject (pos) { + function validPositionObject (pos, dimensions={rows: 8, columns: 8}) { if (!$.isPlainObject(pos)) return false for (var i in pos) { if (!pos.hasOwnProperty(i)) continue - if (!validSquare(i) || !validPieceCode(pos[i])) { + if (!validSquare(i, dimensions) || !validPieceCode(pos[i])) { return false } } @@ -351,7 +362,7 @@ colIdx = colIdx + numEmptySquares } else { // piece - var square = COLUMNS[colIdx] + currentRow + var square = columnIdentifier(colIdx) + currentRow position[square] = fenToPieceCode(row[j]) colIdx = colIdx + 1 } @@ -373,7 +384,7 @@ var currentRow = 8 for (var i = 0; i < 8; i++) { for (var j = 0; j < 8; j++) { - var square = COLUMNS[j] + currentRow + var square = columnIdentifier(j) + currentRow // piece exists if (obj.hasOwnProperty(square)) { @@ -404,33 +415,25 @@ } function squeezeFenEmptySquares (fen) { - return fen.replace(/11111111/g, '8') - .replace(/1111111/g, '7') - .replace(/111111/g, '6') - .replace(/11111/g, '5') - .replace(/1111/g, '4') - .replace(/111/g, '3') - .replace(/11/g, '2') + return fen.replace(/1+/g, function(r) { + return (r.length).toString() + }) } function expandFenEmptySquares (fen) { - return fen.replace(/8/g, '11111111') - .replace(/7/g, '1111111') - .replace(/6/g, '111111') - .replace(/5/g, '11111') - .replace(/4/g, '1111') - .replace(/3/g, '111') - .replace(/2/g, '11') + return fen.replace(/[2-8]/g, function(r) { + return '1'.repeat(parseInt(r)) + }) } // returns the distance between two squares function squareDistance (squareA, squareB) { var squareAArray = squareA.split('') - var squareAx = COLUMNS.indexOf(squareAArray[0]) + 1 + var squareAx = columnDistance(squareAArray[0]) + 1 var squareAy = parseInt(squareAArray[1], 10) var squareBArray = squareB.split('') - var squareBx = COLUMNS.indexOf(squareBArray[0]) + 1 + var squareBx = columnDistance(squareBArray[0]) + 1 var squareBy = parseInt(squareBArray[1], 10) var xDelta = Math.abs(squareAx - squareBx) @@ -463,9 +466,9 @@ var squares = [] // calculate distance of all squares - for (var i = 0; i < 8; i++) { - for (var j = 0; j < 8; j++) { - var s = COLUMNS[i] + (j + 1) + for (var i = 0; i < config.numColumns; i++) { + for (var j = 0; j < config.numRows; j++) { + var s = columnIdentifier(i) + j // skip the square we're starting from if (square === s) continue @@ -510,6 +513,18 @@ return newPosition } + // returns the column identifier (a character, starting with 'a') based + // 0-offset column index. + function columnIdentifier (columnIndex) { + var aCharCode = 'a'.charCodeAt(0) + return String.fromCharCode(aCharCode + columnIndex) + } + + // returns the index of the given column ('a' is at 0th index). + function columnDistance (columnIdentifier) { + return columnIdentifier.charCodeAt(0) - 'a'.charCodeAt(0) + } + // TODO: add some asserts here for calculatePositionFromMoves // --------------------------------------------------------------------------- @@ -555,6 +570,31 @@ // validate config / set default options function expandConfig (config) { + // default number of rows and columns is 8. Force it for invalid config. + if (typeof config.numRows === 'undefined') { + config.numRows = 8 + } + if (!isInteger(config.numRows) || config.numRows <= 0 || config.numRows > 8) { + console.error("Number of rows must be in interval [1, 8]. Defaulting to 8.") + config.numRows = 8 + } + if (typeof config.numColumns === 'undefined') { + config.numColumns = 8 + } + if (!isInteger(config.numColumns) || config.numColumns <= 0 || config.numColumns > 8) { + console.error("Number of columns must be in interval [1, 8]. Defaulting to 8.") + config.numColumns = 8 + } + if (config.numRows != config.numColumns) { + console.error("Number of rows and columns must be equal. Defaulting to 8.") + config.numRows = 8 + config.numColumns = 8 + + } + // this field is not set from outside. It's a wrapper to avoid mistakes + // when setting the dimensions dictionary and improve the readability. + config._boardDimension = {rows: config.numRows, columns: config.numColumns} + // default for orientation is white if (config.orientation !== 'black') config.orientation = 'white' @@ -733,7 +773,7 @@ currentPosition = deepCopy(START_POSITION) } else if (validFen(config.position)) { currentPosition = fenToObj(config.position) - } else if (validPositionObject(config.position)) { + } else if (validPositionObject(config.position, config._boardDimension)) { currentPosition = deepCopy(config.position) } else { error( @@ -749,11 +789,10 @@ // 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 + // 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 config.numColumns for our square size function calculateSquareSize () { var containerWidth = parseInt($container.width(), 10) @@ -765,19 +804,19 @@ // pad one pixel var boardWidth = containerWidth - 1 - while (boardWidth % 8 !== 0 && boardWidth > 0) { + while (boardWidth % config.numColumns !== 0 && boardWidth > 0) { boardWidth = boardWidth - 1 } - return boardWidth / 8 + return boardWidth / config.numColumns } // 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 + for (var i = 0; i < config.numColumns; i++) { + for (var j = 1; j <= config.numRows; j++) { + var square = columnIdentifier(i) + j squareElsIds[square] = square + '-' + uuid() } } @@ -804,17 +843,20 @@ var html = '' // algebraic notation / orientation - var alpha = deepCopy(COLUMNS) - var row = 8 + var alpha = [] + for (var i = 0; i < config.numColumns; i++) { + alpha.push(columnIdentifier(i)) + } + var row = config.numRows if (orientation === 'black') { alpha.reverse() row = 1 } var squareColor = 'white' - for (var i = 0; i < 8; i++) { + for (var i = 0; i < config.numColumns; i++) { html += '
' - for (var j = 0; j < 8; j++) { + for (var j = 0; j < config.numRows; j++) { var square = alpha[j] + row html += '
' + alpha[j] + '
' } @@ -1330,12 +1372,12 @@ if (location === draggedPieceLocation) return // remove highlight from previous square - if (validSquare(draggedPieceLocation)) { + if (validSquare(draggedPieceLocation, config._boardDimension)) { $('#' + squareElsIds[draggedPieceLocation]).removeClass(CSS.highlight2) } // add highlight to new square - if (validSquare(location)) { + if (validSquare(location, config._boardDimension)) { $('#' + squareElsIds[location]).addClass(CSS.highlight2) } @@ -1374,19 +1416,22 @@ // position has not changed; do nothing // source piece is a spare piece and position is on the board - if (draggedPieceSource === 'spare' && validSquare(location)) { + if (draggedPieceSource === 'spare' && + validSquare(location, config._boardDimension)) { // 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') { + if (validSquare(draggedPieceSource, config._boardDimension) && + 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)) { + if (validSquare(draggedPieceSource, config._boardDimension) && + validSquare(location, config._boardDimension)) { // move the piece delete newPosition[draggedPieceSource] newPosition[location] = draggedPiece @@ -1465,7 +1510,7 @@ } // skip invalid arguments - if (!validMove(arguments[i])) { + if (!validMove(arguments[i], config._boardDimension)) { error(2826, 'Invalid move passed to the move method.', arguments[i]) continue } @@ -1529,7 +1574,7 @@ } // validate position object - if (!validPositionObject(position)) { + if (!validPositionObject(position, config._boardDimension)) { error(6482, 'Invalid value passed to the position method.', position) return } @@ -1556,7 +1601,7 @@ squareSize = calculateSquareSize() // set board width - $board.css('width', squareSize * 8 + 'px') + $board.css('width', squareSize * config.numColumns + 'px') // set drag piece size $draggedPiece.css({ @@ -1594,7 +1639,7 @@ // do nothing if there is no piece on this square var square = $(this).attr('data-square') - if (!validSquare(square)) return + if (!validSquare(square, config._boardDimension)) return if (!currentPosition.hasOwnProperty(square)) return beginDraggingPiece(square, currentPosition[square], evt.pageX, evt.pageY) @@ -1606,7 +1651,7 @@ // do nothing if there is no piece on this square var square = $(this).attr('data-square') - if (!validSquare(square)) return + if (!validSquare(square, config._boardDimension)) return if (!currentPosition.hasOwnProperty(square)) return e = e.originalEvent @@ -1696,7 +1741,7 @@ var square = $(evt.currentTarget).attr('data-square') // NOTE: this should never happen; defensive - if (!validSquare(square)) return + if (!validSquare(square, config._boardDimension)) return // get the piece on this square var piece = false @@ -1720,7 +1765,7 @@ var square = $(evt.currentTarget).attr('data-square') // NOTE: this should never happen; defensive - if (!validSquare(square)) return + if (!validSquare(square, config._boardDimension)) return // get the piece on this square var piece = false From 25c53a85dd74e854ed92d248977b4d94fd1297d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20Kri=C5=A1to?= Date: Sun, 30 Aug 2020 14:57:48 +0200 Subject: [PATCH 2/3] Properly propagate config for animations --- lib/chessboard.js | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/lib/chessboard.js b/lib/chessboard.js index da70fea1..4e5c3f66 100644 --- a/lib/chessboard.js +++ b/lib/chessboard.js @@ -178,7 +178,7 @@ rate >= 1 } - function validMove (move, dimensions={rows: 8, columns: 8}) { + function validMove (move, dimensions) { // move should be a string if (!isString(move)) return false @@ -445,9 +445,9 @@ // returns the square of the closest instance of piece // returns false if no instance of piece is found in position - function findClosestPiece (position, piece, square) { + function findClosestPiece (position, piece, square, dimensions) { // create array of closest squares from square - var closestSquares = createRadius(square) + var closestSquares = createRadius(square, dimensions) // search through the position in order of distance for the piece for (var i = 0; i < closestSquares.length; i++) { @@ -462,12 +462,12 @@ } // returns an array of closest squares from square - function createRadius (square) { + function createRadius (square, dimensions) { var squares = [] // calculate distance of all squares - for (var i = 0; i < config.numColumns; i++) { - for (var j = 0; j < config.numRows; j++) { + for (var i = 0; i < dimensions.columns; i++) { + for (var j = 0; j < dimensions.rows; j++) { var s = columnIdentifier(i) + j // skip the square we're starting from @@ -589,7 +589,6 @@ console.error("Number of rows and columns must be equal. Defaulting to 8.") config.numRows = 8 config.numColumns = 8 - } // this field is not set from outside. It's a wrapper to avoid mistakes // when setting the dimensions dictionary and improve the readability. @@ -1075,7 +1074,7 @@ // 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, dimensions) { // make copies of both pos1 = deepCopy(pos1) pos2 = deepCopy(pos2) @@ -1097,7 +1096,7 @@ for (i in pos2) { if (!pos2.hasOwnProperty(i)) continue - var closestPiece = findClosestPiece(pos1, pos2[i], i) + var closestPiece = findClosestPiece(pos1, pos2[i], i, dimensions) if (closestPiece) { animations.push({ type: 'move', @@ -1584,7 +1583,7 @@ if (useAnimation) { // start the animations - var animations = calculateAnimations(currentPosition, position) + var animations = calculateAnimations(currentPosition, position, config._boardDimension) doAnimations(animations, currentPosition, position) // set the new position From f4d2b990ff57c6fc9af5f1eb630e103482769076 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20Kri=C5=A1to?= Date: Sun, 30 Aug 2020 15:13:09 +0200 Subject: [PATCH 3/3] Fix rows/columns order in a for loop --- lib/chessboard.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/chessboard.js b/lib/chessboard.js index 4e5c3f66..6f2543ec 100644 --- a/lib/chessboard.js +++ b/lib/chessboard.js @@ -853,9 +853,9 @@ } var squareColor = 'white' - for (var i = 0; i < config.numColumns; i++) { + for (var i = 0; i < config.numRows; i++) { html += '
' - for (var j = 0; j < config.numRows; j++) { + for (var j = 0; j < config.numColumns; j++) { var square = alpha[j] + row html += '