${buildTypeHTML(prop.type)}
${prop.default || 'n/a'}
${error.desc}
${p}
`).join('') : `${error.fix}
`}' + arg[1] + '
').join('')}' + d + '
'; + }); + + return html; + } + + function buildExamplesCellHTML(examplesIds) { + if (!Array.isArray(examplesIds)) { + examplesIds = [examplesIds]; + } + + let html = ''; + examplesIds.forEach(function (exampleId) { + const example = examples.find(x => x.id === exampleId); + if (!example) return; + html += ''; + }); + + return html; + } +} diff --git a/README.md b/README.md index 70df69f5..24015102 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # chessboard.js -chessboard.js is a JavaScript chessboard component. It depends on [jQuery] v3.4.1 (or higher). +chessboard.js is a _zero-dependency_ JavaScript chessboard component. Please see [chessboardjs.com] for documentation and examples. @@ -53,7 +53,6 @@ npm run website [MIT License](LICENSE.md) -[jQuery]:https://jquery.com/ [chessboardjs.com]:https://chessboardjs.com [chess.js]:https://github.com/jhlywa/chess.js [Example 5000]:https://chessboardjs.com/examples#5000 diff --git a/data/docs.json b/data/docs.json index a6e1392d..9c0ee9fe 100644 --- a/data/docs.json +++ b/data/docs.json @@ -426,18 +426,13 @@ "errors":[ { "id":1001, - "desc":"The first argument toChessboard() cannot be an empty string.",
- "fix":"The first argument to the Chessboard() constructor should be the id of a DOM element or a reference to a single DOM element."
+ "desc":"The first argument to new Chessboard() cannot be an empty string.",
+ "fix":"The first argument to the new Chessboard() constructor should be the id of a DOM element or a reference to a single DOM element."
},
{
"id":1003,
- "desc":"The first argument to Chessboard() must be an ID or a single DOM node.",
- "fix":"The first argument to the Chessboard() constructor should be the id of a DOM element or a reference to a single DOM element."
-},
-{
- "id":1005,
- "desc":"Unable to find a valid version of jQuery. Please include jQuery 1.8.3 or higher on the page.",
- "fix":"Chessboard requires jQuery version 1.8.3 or higher."
+ "desc":"The first argument to new Chessboard() must be an ID or a single DOM node.",
+ "fix":"The first argument to the new Chessboard() constructor should be the id of a DOM element or a reference to a single DOM element."
},
{
"id":2826,
diff --git a/examples/1000-empty-board.example b/examples/1000-empty-board.example
index b065604c..32990899 100644
--- a/examples/1000-empty-board.example
+++ b/examples/1000-empty-board.example
@@ -2,13 +2,13 @@
1000
===== Name
-Empty Board
-
+Empty Board
+
===== Description
Chessboard.js initializes to an empty board with no second argument.
-
+
===== HTML
-
+
===== JS
-var board = Chessboard('myBoard')
+var board = new Chessboard('myBoard')
diff --git a/examples/1001-start-position.example b/examples/1001-start-position.example
index d95b132b..ca9aca98 100644
--- a/examples/1001-start-position.example
+++ b/examples/1001-start-position.example
@@ -1,15 +1,15 @@
===== id
1001
-===== Name
-Start Position
-
+===== Name
+Start Position
+
===== Description
Pass 'start' as the second argument to initialize
the board to the start position.
-
+
===== HTML
-
+
===== JS
-var board = Chessboard('myBoard', 'start')
+var board = new Chessboard('myBoard', 'start')
diff --git a/examples/1002-fen.example b/examples/1002-fen.example
index fa7718a2..e9c2dc41 100644
--- a/examples/1002-fen.example
+++ b/examples/1002-fen.example
@@ -1,16 +1,16 @@
===== id
1002
-===== Name
-FEN String
-
+===== Name
+FEN String
+
===== Description
Pass a FEN String as the second argument to
initialize the board to a specific position.
-
+
===== HTML
-
+
===== JS
var ruyLopez = 'r1bqkbnr/pppp1ppp/2n5/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R'
-var board = Chessboard('myBoard', ruyLopez)
+var board = new Chessboard('myBoard', ruyLopez)
diff --git a/examples/1003-position-object.example b/examples/1003-position-object.example
index 6bfa237e..694fd453 100644
--- a/examples/1003-position-object.example
+++ b/examples/1003-position-object.example
@@ -1,19 +1,19 @@
===== id
1003
-===== Name
-Position Object
-
-===== Description
-Pass a Position Object as the second argument to initialize the board to a specific position.
-
-===== HTML
-
-
-===== JS
+===== Name
+Position Object
+
+===== Description
+Pass a Position Object as the second argument to initialize the board to a specific position.
+
+===== HTML
+
+
+===== JS
var position = {
d6: 'bK',
d4: 'wP',
e4: 'wK'
}
-var board = Chessboard('myBoard', position)
+var board = new Chessboard('myBoard', position)
diff --git a/examples/1004-multiple-boards.example b/examples/1004-multiple-boards.example
index 54c45e6d..7d5ba8bd 100644
--- a/examples/1004-multiple-boards.example
+++ b/examples/1004-multiple-boards.example
@@ -1,36 +1,36 @@
===== id
1004
-===== Name
-Multiple Boards
-
-===== Description
-You can have multiple boards on the same page.
-
+===== Name
+Multiple Boards
+
+===== Description
+You can have multiple boards on the same page.
+
===== CSS
.small-board {
display: inline-block;
margin-right: 5px;
width: 200px;
}
-
+
===== HTML
-
-===== JS
-var board1 = Chessboard('board1', {
+
+===== JS
+var board1 = new Chessboard('board1', {
position: 'start',
showNotation: false
})
-var board2 = Chessboard('board2', {
+var board2 = new Chessboard('board2', {
position: 'r1bqkbnr/pppp1ppp/2n5/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R',
showNotation: false
})
-var board3 = Chessboard('board3', {
+var board3 = new Chessboard('board3', {
position: 'r1k4r/p2nb1p1/2b4p/1p1n1p2/2PP4/3Q1NB1/1P3PPP/R5K1',
showNotation: false
})
diff --git a/examples/2000-config-position.example b/examples/2000-config-position.example
index ed5ba568..bfc9d409 100644
--- a/examples/2000-config-position.example
+++ b/examples/2000-config-position.example
@@ -1,17 +1,17 @@
===== id
2000
-===== Name
-Start Position
-
-===== Description
-Set the position property to 'start' to initialize the board to the start position.
-
-===== HTML
-
-
-===== JS
-var config = {
- position: 'start'
+===== Name
+Start Position
+
+===== Description
+Set the position property to 'start' to initialize the board to the start position.
+
+===== HTML
+
+
+===== JS
+var config = {
+ position: 'start'
}
-var board = Chessboard('myBoard', config)
+var board = new Chessboard('myBoard', config)
diff --git a/examples/2001-config-orientation.example b/examples/2001-config-orientation.example
index 1ce4048b..eae0ac38 100644
--- a/examples/2001-config-orientation.example
+++ b/examples/2001-config-orientation.example
@@ -1,18 +1,18 @@
===== id
2001
-===== Name
-Orientation
-
-===== Description
-Use the orientation property to set board orientation.
-
-===== HTML
-
-
-===== JS
-var config = {
- orientation: 'black',
- position: 'start'
+===== Name
+Orientation
+
+===== Description
+Use the orientation property to set board orientation.
+
+===== HTML
+
+
+===== JS
+var config = {
+ orientation: 'black',
+ position: 'start'
}
-var board = Chessboard('myBoard', config)
+var board = new Chessboard('myBoard', config)
diff --git a/examples/2002-config-notation.example b/examples/2002-config-notation.example
index a4585bed..b575b8dc 100644
--- a/examples/2002-config-notation.example
+++ b/examples/2002-config-notation.example
@@ -1,18 +1,18 @@
===== id
2002
-===== Name
-Notation
-
-===== Description
-Use the showNotation property to turn board notation on or off.
-
-===== HTML
-
-
-===== JS
-var config = {
- showNotation: false,
- position: 'start'
+===== Name
+Notation
+
+===== Description
+Use the showNotation property to turn board notation on or off.
+
+===== HTML
+
+
+===== JS
+var config = {
+ showNotation: false,
+ position: 'start'
}
-var board = Chessboard('myBoard', config)
+var board = new Chessboard('myBoard', config)
diff --git a/examples/2003-draggable-snapback.example b/examples/2003-draggable-snapback.example
index 194e8580..1614fc96 100644
--- a/examples/2003-draggable-snapback.example
+++ b/examples/2003-draggable-snapback.example
@@ -1,19 +1,19 @@
===== id
2003
-===== Name
-Draggable Snapback
-
-===== Description
-Set draggable to true to allow drag and drop of pieces. Pieces will return to their original square when dropped off the board (ie: the default for dropOffBoard is 'snapback').
-
-===== HTML
-
-
-===== JS
-var config = {
- draggable: true,
- dropOffBoard: 'snapback', // this is the default
- position: 'start'
+===== Name
+Draggable Snapback
+
+===== Description
+Set draggable to true to allow drag and drop of pieces. Pieces will return to their original square when dropped off the board (ie: the default for dropOffBoard is 'snapback').
+
+===== HTML
+
+
+===== JS
+var config = {
+ draggable: true,
+ dropOffBoard: 'snapback', // this is the default
+ position: 'start'
}
-var board = Chessboard('myBoard', config)
+var board = new Chessboard('myBoard', config)
diff --git a/examples/2004-piece-theme-string.example b/examples/2004-piece-theme-string.example
index fe503528..e9fd858d 100644
--- a/examples/2004-piece-theme-string.example
+++ b/examples/2004-piece-theme-string.example
@@ -15,4 +15,4 @@ var config = {
pieceTheme: 'img/chesspieces/alpha/{piece}.png',
position: 'start'
}
-var board = Chessboard('myBoard', config)
+var board = new Chessboard('myBoard', config)
diff --git a/examples/2005-animation-speed.example b/examples/2005-animation-speed.example
index e76fa249..c95bbeef 100644
--- a/examples/2005-animation-speed.example
+++ b/examples/2005-animation-speed.example
@@ -13,7 +13,7 @@ You can control animation speeds with the <
===== JS
-var board = Chessboard('myBoard', {
+const board = new Chessboard('myBoard', {
draggable: true,
moveSpeed: 'slow',
snapbackSpeed: 500,
@@ -21,7 +21,9 @@ var board = Chessboard('myBoard', {
position: 'start'
})
-$('#ruyLopezBtn').on('click', function () {
- board.position('r1bqkbnr/pppp1ppp/2n5/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R')
-})
-$('#startBtn').on('click', board.start)
+const ruyLopezFen = 'r1bqkbnr/pppp1ppp/2n5/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R';
+
+document.getElementById('ruyLopezBtn')
+ .onclick = () => board.setPosition(ruyLopezFen)
+document.getElementById('startBtn')
+ .onclick = () => board.start()
diff --git a/examples/2006-spare-pieces.example b/examples/2006-spare-pieces.example
index 25166835..c533748c 100644
--- a/examples/2006-spare-pieces.example
+++ b/examples/2006-spare-pieces.example
@@ -13,11 +13,13 @@ Set sparePiecesClear Board
===== JS
-var board = Chessboard('myBoard', {
+const board = new Chessboard('myBoard', {
draggable: true,
dropOffBoard: 'trash',
sparePieces: true
})
-$('#startBtn').on('click', board.start)
-$('#clearBtn').on('click', board.clear)
+document.getElementById('startBtn')
+ .onclick = () => board.start()
+document.getElementById('clearBtn')
+ .onclick = () => board.clear()
diff --git a/examples/2030-piece-theme-function.example b/examples/2030-piece-theme-function.example
index cf132718..61c49486 100644
--- a/examples/2030-piece-theme-function.example
+++ b/examples/2030-piece-theme-function.example
@@ -25,4 +25,4 @@ var config = {
pieceTheme: pieceTheme,
position: 'start'
}
-var board = Chessboard('myBoard', config)
+var board = new Chessboard('myBoard', config)
diff --git a/examples/2044-position-fen.example b/examples/2044-position-fen.example
index 1d31a14f..560c940b 100644
--- a/examples/2044-position-fen.example
+++ b/examples/2044-position-fen.example
@@ -14,4 +14,4 @@ You can set the posit
var config = {
position: 'r1bqkbnr/pppp1ppp/2n5/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R'
}
-var board = Chessboard('myBoard', config)
+var board = new Chessboard('myBoard', config)
diff --git a/examples/2063-position-object.example b/examples/2063-position-object.example
index 6b031000..5ea398ec 100644
--- a/examples/2063-position-object.example
+++ b/examples/2063-position-object.example
@@ -18,4 +18,4 @@ var config = {
e4: 'wK'
}
};
-var board = Chessboard('myBoard', config)
+var board = new Chessboard('myBoard', config)
diff --git a/examples/2082-draggable-trash.example b/examples/2082-draggable-trash.example
index 7d31ccee..8bb202a7 100644
--- a/examples/2082-draggable-trash.example
+++ b/examples/2082-draggable-trash.example
@@ -16,4 +16,4 @@ var config = {
dropOffBoard: 'trash',
position: 'start'
}
-var board = Chessboard('myBoard', config)
+var board = new Chessboard('myBoard', config)
diff --git a/examples/3000-get-position.example b/examples/3000-get-position.example
index 6aadc63d..5ff6ac68 100644
--- a/examples/3000-get-position.example
+++ b/examples/3000-get-position.example
@@ -1,29 +1,29 @@
===== id
3000
-===== Name
-Get Position
-
-===== Description
-Use the position and fen methods to retrieve the current position of the board.
-
-===== HTML
-
+===== Name
+Get Position
+
+===== Description
+Use the position and fen methods to retrieve the current position of the board.
+
+===== HTML
+
-
-===== JS
+
+===== JS
var config = {
draggable: true,
position: 'start'
}
-var board = Chessboard('myBoard', config)
+var board = new Chessboard('myBoard', config)
function clickShowPositionBtn () {
console.log('Current position as an Object:')
- console.log(board.position())
+ console.log(board.position)
console.log('Current position as a FEN string:')
- console.log(board.fen())
+ console.log(board.fen)
}
-$('#showPositionBtn').on('click', clickShowPositionBtn)
+document.getElementById('showPositionBtn').onclick = (evt) => clickShowPositionBtn(evt)
diff --git a/examples/3001-set-position.example b/examples/3001-set-position.example
index e999f107..7e0d4960 100644
--- a/examples/3001-set-position.example
+++ b/examples/3001-set-position.example
@@ -1,31 +1,21 @@
===== id
3001
-===== Name
-Set Position
-
-===== Description
-Use the start and position methods to set the board position.
-
-===== HTML
-
+===== Name
+Set Position
+
+===== Description
+Use the start and position methods to set the board position.
+
+===== HTML
+
-
-===== JS
-var board = Chessboard('myBoard')
-
-$('#setRuyLopezBtn').on('click', function () {
- board.position('r1bqkbnr/pppp1ppp/2n5/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R')
-})
-$('#setStartBtn').on('click', board.start)
+===== JS
+const board = new Chessboard('myBoard')
-$('#setRookCheckmateBtn').on('click', function () {
- board.position({
- a4: 'bK',
- c4: 'wK',
- a7: 'wR'
- })
-})
+document.getElementById('setRuyLopezBtn').onclick = () => board.setPosition('r1bqkbnr/pppp1ppp/2n5/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R')
+document.getElementById('setStartBtn').onclick = () => board.start()
+document.getElementById('setRookCheckmateBtn').onclick = () => board.setPosition({ a4: 'bK', c4: 'wK', a7: 'wR' })
diff --git a/examples/3002-set-position-instant.example b/examples/3002-set-position-instant.example
index 08e5ad15..50b21d20 100644
--- a/examples/3002-set-position-instant.example
+++ b/examples/3002-set-position-instant.example
@@ -1,35 +1,21 @@
===== id
3002
-===== Name
-Set Position Instantly
-
-===== Description
-Pass false as the second argument to the start and position methods to set the board position instantly.
-
-===== HTML
-
+===== Name
+Set Position Instantly
+
+===== Description
+Pass false as the second argument to the start and position methods to set the board position instantly.
+
+===== HTML
+
-
-===== JS
-var board = Chessboard('myBoard')
-
-$('#setRuyLopezBtn').on('click', function () {
- var ruyLopez = 'r1bqkbnr/pppp1ppp/2n5/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R'
- board.position(ruyLopez, false)
-})
-$('#setStartBtn').on('click', function () {
- board.start(false)
-})
+===== JS
+const board = new Chessboard('myBoard')
-$('#setRookCheckmateBtn').on('click', function () {
- var rookCheckmate = {
- a4: 'bK',
- c4: 'wK',
- a7: 'wR'
- }
- board.position(rookCheckmate, false)
-})
+document.getElementById('setRuyLopezBtn').onclick = () => board.setPosition('r1bqkbnr/pppp1ppp/2n5/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R', false)
+document.getElementById('setStartBtn').onclick = () => board.start(false)
+document.getElementById('setRookCheckmateBtn').onclick = () => board.setPosition({ a4: 'bK', c4: 'wK', a7: 'wR' }, false)
diff --git a/examples/3003-clear.example b/examples/3003-clear.example
index d1243b68..eec03646 100644
--- a/examples/3003-clear.example
+++ b/examples/3003-clear.example
@@ -1,25 +1,24 @@
===== id
3003
-===== Name
-Clear Board
-
-===== Description
-Use the clear method to remove all the pieces from the board.
-
-===== HTML
-
+===== Name
+Clear Board
+
+===== Description
+Use the clear method to remove all the pieces from the board.
+
+===== HTML
+
-
-===== JS
-var board = Chessboard('myBoard', 'start')
-
-$('#clearBoardBtn').on('click', board.clear)
-$('#startPositionBtn').on('click', board.start)
+===== JS
+const board = new Chessboard('myBoard', 'start')
-$('#clearBoardInstantBtn').on('click', function () {
- board.clear(false)
-})
+document.getElementById('clearBoardBtn')
+ .onclick = () => board.clear()
+document.getElementById('startPositionBtn')
+ .onclick = () => board.start()
+document.getElementById('clearBoardInstantBtn')
+ .onclick = () => board.clear(false)
diff --git a/examples/3004-move-pieces.example b/examples/3004-move-pieces.example
index c380527d..15d08cdd 100644
--- a/examples/3004-move-pieces.example
+++ b/examples/3004-move-pieces.example
@@ -1,27 +1,24 @@
===== id
3004
-===== Name
-Move Pieces
-
-===== Description
-Use the move method to make one or more moves on the board.
-
-===== HTML
-
+===== Name
+Move Pieces
+
+===== Description
+Use the move method to make one or more moves on the board.
+
+===== HTML
+
-===== JS
-var board = Chessboard('myBoard', 'start')
-
-$('#move1Btn').on('click', function () {
- board.move('e2-e4')
-})
-
-$('#move2Btn').on('click', function () {
- board.move('d2-d4', 'g8-f6')
-})
+===== JS
+const board = new Chessboard('myBoard', 'start')
-$('#startPositionBtn').on('click', board.start)
+document.getElementById('move1Btn')
+ .onclick = () => board.move('e2-e4')
+document.getElementById('move2Btn')
+ .onclick = () => board.move('d2-d4', 'g8-f6')
+document.getElementById('startPositionBtn')
+ .onclick = () => board.start()
diff --git a/examples/3005-orientation.example b/examples/3005-orientation.example
index b3221564..8be1571b 100644
--- a/examples/3005-orientation.example
+++ b/examples/3005-orientation.example
@@ -1,34 +1,25 @@
===== id
3005
-===== Name
-Orientation
-
-===== Description
-Use the orientation method to retrieve and set the orientation. Use the flip method to flip orientation.
-
-===== HTML
-
+===== Name
+Orientation
+
+===== Description
+Use the orientation method to retrieve and set the orientation. Use the flip method to flip orientation.
+
+===== HTML
+
-===== JS
-var ruyLopez = 'r1bqkbnr/pppp1ppp/2n5/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R'
-var board = Chessboard('myBoard', ruyLopez)
-
-$('#showOrientationBtn').on('click', function () {
- console.log('Board orientation is: ' + board.orientation())
-})
-
-$('#flipOrientationBtn').on('click', board.flip)
-
-$('#whiteOrientationBtn').on('click', function () {
- board.orientation('white')
-})
+===== JS
+const ruyLopez = 'r1bqkbnr/pppp1ppp/2n5/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R'
+const board = new Chessboard('myBoard', ruyLopez)
-$('#blackOrientationBtn').on('click', function () {
- board.orientation('black')
-})
+document.getElementById('showOrientationBtn').onclick = () => console.log('Board orientation is: ' + board.orientation())
+document.getElementById('flipOrientationBtn').onclick = () => board.flip()
+document.getElementById('whiteOrientationBtn').onclick = () => board.orientation('white')
+document.getElementById('blackOrientationBtn').onclick = () => board.orientation('black')
diff --git a/examples/3006-destroy.example b/examples/3006-destroy.example
index f7f30ed4..bfed7570 100644
--- a/examples/3006-destroy.example
+++ b/examples/3006-destroy.example
@@ -12,6 +12,6 @@ Use the destroyDestroy Board
===== JS
-var board = Chessboard('myBoard', 'start')
+const board = new Chessboard('myBoard', 'start')
-$('#destroyBtn').on('click', board.destroy)
+document.getElementById('destroyBtn').onclick = () => board.destroy()
diff --git a/examples/3007-resize.example b/examples/3007-resize.example
index 3a4fc1e4..517291e3 100644
--- a/examples/3007-resize.example
+++ b/examples/3007-resize.example
@@ -12,5 +12,5 @@ Use the resize<
===== JS
// NOTE: click "View example in new window." to see the full effect of this example
-var board = Chessboard('myBoard', 'start')
-$(window).resize(board.resize)
+const board = new Chessboard('myBoard', 'start')
+window.onresize = () => board.resize()
diff --git a/examples/4000-onchange.example b/examples/4000-onchange.example
index bc8c60da..6fb3853e 100644
--- a/examples/4000-onchange.example
+++ b/examples/4000-onchange.example
@@ -1,35 +1,32 @@
===== id
4000
-===== Name
-onChange
-
-===== Description
+===== Name
+onChange
+
+===== Description
The onChange event fires when the board position changes.
-
-===== HTML
+
+===== HTML
-===== JS
-function onChange (oldPos, newPos) {
- console.log('Position changed:')
- console.log('Old position: ' + Chessboard.objToFen(oldPos))
- console.log('New position: ' + Chessboard.objToFen(newPos))
- console.log('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
-}
-
-var config = {
+===== JS
+const board = new Chessboard('myBoard', {
draggable: true,
position: 'start',
- onChange: onChange
-}
-var board = Chessboard('myBoard', config)
-
-$('#ruyLopezBtn').on('click', function () {
- var ruyLopez = 'r1bqkbnr/pppp1ppp/2n5/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R'
- board.position(ruyLopez)
+ onChange(oldPos, newPos) {
+ console.log('Position changed:')
+ console.log('Old position: ' + Chessboard.objToFen(oldPos))
+ console.log('New position: ' + Chessboard.objToFen(newPos))
+ console.log('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
+ }
})
-$('#startPositionBtn').on('click', board.start)
+const ruyLopezFen = 'r1bqkbnr/pppp1ppp/2n5/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R';
+
+document.getElementById('ruyLopezBtn')
+ .onclick = () => board.setPosition(ruyLopezFen)
+document.getElementById('startPositionBtn')
+ .onclick = () => board.start()
diff --git a/examples/4001-ondragstart.example b/examples/4001-ondragstart.example
index c7f57954..799aecba 100644
--- a/examples/4001-ondragstart.example
+++ b/examples/4001-ondragstart.example
@@ -26,4 +26,4 @@ var config = {
onDragStart: onDragStart,
sparePieces: true
}
-var board = Chessboard('myBoard', config)
+var board = new Chessboard('myBoard', config)
diff --git a/examples/4002-ondragstart-prevent-drag.example b/examples/4002-ondragstart-prevent-drag.example
index d63cf86a..4b2cd9c5 100644
--- a/examples/4002-ondragstart-prevent-drag.example
+++ b/examples/4002-ondragstart-prevent-drag.example
@@ -12,20 +12,16 @@ Prevent the drag action by returning false from
===== JS
-// only allow pieces to be dragged when the board is oriented
-// in their direction
-function onDragStart (source, piece, position, orientation) {
- if ((orientation === 'white' && piece.search(/^w/) === -1) ||
- (orientation === 'black' && piece.search(/^b/) === -1)) {
- return false
- }
-}
-
-var config = {
+const board = new Chessboard('myBoard', {
draggable: true,
position: 'start',
- onDragStart: onDragStart
-}
-var board = Chessboard('myBoard', config)
+ // only allow pieces to be dragged when the board is oriented in their direction
+ onDragStart(source, piece, position, orientation) {
+ if ((orientation === 'white' && piece.search(/^w/) === -1) ||
+ (orientation === 'black' && piece.search(/^b/) === -1)) {
+ return false
+ }
+ }
+})
-$('#flipOrientationBtn').on('click', board.flip)
+document.getElementById('flipOrientationBtn').onclick = () => board.flip()
diff --git a/examples/4003-ondragmove.example b/examples/4003-ondragmove.example
index b91e9ffb..2604221f 100644
--- a/examples/4003-ondragmove.example
+++ b/examples/4003-ondragmove.example
@@ -28,4 +28,4 @@ var config = {
onDragMove: onDragMove,
sparePieces: true
}
-var board = Chessboard('myBoard', config)
+var board = new Chessboard('myBoard', config)
diff --git a/examples/4004-ondrop.example b/examples/4004-ondrop.example
index 0e6c6d32..b013dedd 100644
--- a/examples/4004-ondrop.example
+++ b/examples/4004-ondrop.example
@@ -27,4 +27,4 @@ var config = {
onDrop: onDrop,
sparePieces: true
}
-var board = Chessboard('myBoard', config)
+var board = new Chessboard('myBoard', config)
diff --git a/examples/4005-ondrop-snapback.example b/examples/4005-ondrop-snapback.example
index 9c578fa0..2c26f56d 100644
--- a/examples/4005-ondrop-snapback.example
+++ b/examples/4005-ondrop-snapback.example
@@ -23,4 +23,4 @@ var config = {
position: 'start',
onDrop: onDrop
}
-var board = Chessboard('myBoard', config)
+var board = new Chessboard('myBoard', config)
diff --git a/examples/4006-ondrop-trash.example b/examples/4006-ondrop-trash.example
index ff2e6afc..a0f9c665 100644
--- a/examples/4006-ondrop-trash.example
+++ b/examples/4006-ondrop-trash.example
@@ -23,4 +23,4 @@ var config = {
position: 'start',
onDrop: onDrop
}
-var board = Chessboard('myBoard', config)
+var board = new Chessboard('myBoard', config)
diff --git a/examples/4011-onsnapbackend.example b/examples/4011-onsnapbackend.example
index 62b3aea4..c046dd3a 100644
--- a/examples/4011-onsnapbackend.example
+++ b/examples/4011-onsnapbackend.example
@@ -24,4 +24,4 @@ var config = {
position: 'start',
onSnapbackEnd: onSnapbackEnd
}
-var board = Chessboard('myBoard', config)
+var board = new Chessboard('myBoard', config)
diff --git a/examples/4012-onmoveend.example b/examples/4012-onmoveend.example
index ed79823e..984b4ea3 100644
--- a/examples/4012-onmoveend.example
+++ b/examples/4012-onmoveend.example
@@ -15,24 +15,23 @@ The onMoveEnd
===== JS
-function onMoveEnd (oldPos, newPos) {
- console.log('Move animation complete:')
- console.log('Old position: ' + Chessboard.objToFen(oldPos))
- console.log('New position: ' + Chessboard.objToFen(newPos))
- console.log('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
-}
-
-var config = {
+const board = new Chessboard('myBoard', {
position: 'start',
- onMoveEnd: onMoveEnd
-}
-var board = Chessboard('myBoard', config)
-
-$('#ruyLopezBtn').on('click', function () {
- board.position('r1bqkbnr/pppp1ppp/2n5/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R')
-})
-$('#moveBtn').on('click', function () {
- board.move('a2-a4', 'h7-h5')
+ onMoveEnd(oldPos, newPos) {
+ console.log('Move animation complete:')
+ console.log('Old position: ' + Chessboard.objToFen(oldPos))
+ console.log('New position: ' + Chessboard.objToFen(newPos))
+ console.log('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
+ }
})
-$('#startBtn').on('click', board.start)
-$('#clearBtn').on('click', board.clear)
+
+const ruyLopezFen = 'r1bqkbnr/pppp1ppp/2n5/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R';
+
+document.getElementById('ruyLopezBtn')
+ .onclick = () => board.setPosition(ruyLopezFen)
+document.getElementById('moveBtn')
+ .onclick = () => board.move('a2-a4', 'h7-h5')
+document.getElementById('startBtn')
+ .onclick = () => board.start()
+document.getElementById('clearBtn')
+ .onclick = () => board.clear()
diff --git a/examples/5000-legal-moves.example b/examples/5000-legal-moves.example
index 1175a792..6296ba6b 100644
--- a/examples/5000-legal-moves.example
+++ b/examples/5000-legal-moves.example
@@ -22,9 +22,9 @@ You can integrate chessboard.js with the
+===== Name
+Piece Highlighting 1
+
+===== Description
+Use CSS to show piece highlighting.
+
+===== HTML
+
-
-
+
+
===== JS
// NOTE: this example uses the chess.js library:
// https://github.com/jhlywa/chess.js
var board = null
-var $board = $('#myBoard')
+var $board = document.getElementById('myBoard')
var game = new Chess()
var squareClass = 'square-55d63'
var squareToHighlight = null
@@ -53,7 +53,7 @@ function makeRandomMove () {
}
game.move(possibleMoves[randomIdx].san)
- board.position(game.fen())
+ board.setPosition(game.fen())
window.setTimeout(makeRandomMove, 1200)
}
@@ -67,6 +67,6 @@ var config = {
position: 'start',
onMoveEnd: onMoveEnd
}
-board = Chessboard('myBoard', config)
+board = new Chessboard('myBoard', config)
window.setTimeout(makeRandomMove, 500)
diff --git a/examples/5005-piece-highlight2.example b/examples/5005-piece-highlight2.example
index 933ed067..fc62bbf9 100644
--- a/examples/5005-piece-highlight2.example
+++ b/examples/5005-piece-highlight2.example
@@ -23,7 +23,7 @@ Use CSS to show piece highlighting.
// https://github.com/jhlywa/chess.js
var board = null
-var $board = $('#myBoard')
+var $board = document.getElementById('myBoard')
var game = new Chess()
var squareToHighlight = null
var squareClass = 'square-55d63'
@@ -59,7 +59,7 @@ function makeRandomMove () {
squareToHighlight = move.to
// update the board to the new position
- board.position(game.fen())
+ board.setPosition(game.fen())
}
function onDrop (source, target) {
@@ -90,7 +90,7 @@ function onMoveEnd () {
// update the board position after the piece snap
// for castling, en passant, pawn promotion
function onSnapEnd () {
- board.position(game.fen())
+ board.setPosition(game.fen())
}
var config = {
@@ -101,4 +101,4 @@ var config = {
onMoveEnd: onMoveEnd,
onSnapEnd: onSnapEnd
}
-board = Chessboard('myBoard', config)
+board = new Chessboard('myBoard', config)
diff --git a/lib/chessboard.css b/lib/chessboard.css
index 27ffdccb..e6f6b0f2 100644
--- a/lib/chessboard.css
+++ b/lib/chessboard.css
@@ -1,54 +1,59 @@
-/*! chessboard.js v@VERSION | (c) 2019 Chris Oakman | MIT License chessboardjs.com/license */
-
.clearfix-7da63 {
- clear: both;
-}
-
+ clear: both;
+}
+
.board-b72b1 {
border: 2px solid #404040;
box-sizing: content-box;
-}
-
+}
+
.square-55d63 {
- float: left;
- position: relative;
-
- /* disable any native browser highlighting */
- -webkit-touch-callout: none;
- -webkit-user-select: none;
- -khtml-user-select: none;
- -moz-user-select: none;
- -ms-user-select: none;
- user-select: none;
-}
-
+ float: left;
+ position: relative;
+
+ /* disable any native browser highlighting */
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+
+.spare-pieces-7492f {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
.white-1e1d7 {
- background-color: #f0d9b5;
- color: #b58863;
-}
-
+ background-color: #f0d9b5;
+ color: #b58863;
+}
+
.black-3c85d {
- background-color: #b58863;
- color: #f0d9b5;
-}
+ background-color: #b58863;
+ color: #f0d9b5;
+}
-.highlight1-32417, .highlight2-9c5d2 {
- box-shadow: inset 0 0 3px 3px yellow;
-}
+.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;
+.notation-322f9 {
+ cursor: default;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-size: 14px;
+ position: absolute;
}
-.alpha-d2270 {
- bottom: 1px;
- right: 3px;
+.alpha-d2270 {
+ bottom: 1px;
+ right: 3px;
}
-.numeric-fc462 {
- top: 2px;
- left: 2px;
+.numeric-fc462 {
+ top: 2px;
+ left: 2px;
}
diff --git a/lib/chessboard.js b/lib/chessboard.js
index ed27db18..6cf66d26 100644
--- a/lib/chessboard.js
+++ b/lib/chessboard.js
@@ -1,1823 +1,1160 @@
-// chessboard.js v@VERSION
-// https://github.com/oakmac/chessboardjs/
-//
-// Copyright (c) 2019, Chris Oakman
-// Released under the MIT license
-// https://github.com/oakmac/chessboardjs/blob/master/LICENSE.md
-
-// start anonymous scope
-;(function () {
- 'use strict'
-
- var $ = window['jQuery']
-
- // ---------------------------------------------------------------------------
- // Constants
- // ---------------------------------------------------------------------------
-
- var COLUMNS = 'abcdefgh'.split('')
- var DEFAULT_DRAG_THROTTLE_RATE = 20
- var ELLIPSIS = '…'
- var MINIMUM_JQUERY_VERSION = '1.8.3'
- var RUN_ASSERTS = true
- var START_FEN = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR'
- var START_POSITION = fenToObj(START_FEN)
-
- // default animation speeds
- var DEFAULT_APPEAR_SPEED = 200
- var DEFAULT_MOVE_SPEED = 200
- var DEFAULT_SNAPBACK_SPEED = 60
- var DEFAULT_SNAP_SPEED = 30
- var DEFAULT_TRASH_SPEED = 100
-
- // use unique class names to prevent clashing with anything else on the page
- // and simplify selectors
- // NOTE: these should never change
- var CSS = {}
- CSS['alpha'] = 'alpha-d2270'
- CSS['black'] = 'black-3c85d'
- CSS['board'] = 'board-b72b1'
- CSS['chessboard'] = 'chessboard-63f37'
- CSS['clearfix'] = 'clearfix-7da63'
- CSS['highlight1'] = 'highlight1-32417'
- CSS['highlight2'] = 'highlight2-9c5d2'
- CSS['notation'] = 'notation-322f9'
- CSS['numeric'] = 'numeric-fc462'
- CSS['piece'] = 'piece-417db'
- CSS['row'] = 'row-5277c'
- CSS['sparePieces'] = 'spare-pieces-7492f'
- CSS['sparePiecesBottom'] = 'spare-pieces-bottom-ae20f'
- CSS['sparePiecesTop'] = 'spare-pieces-top-4028b'
- CSS['square'] = 'square-55d63'
- CSS['white'] = 'white-1e1d7'
-
- // ---------------------------------------------------------------------------
- // Misc Util Functions
- // ---------------------------------------------------------------------------
-
- function throttle (f, interval, scope) {
- var timeout = 0
- var shouldFire = false
- var args = []
-
- var handleTimeout = function () {
- timeout = 0
- if (shouldFire) {
- shouldFire = false
- fire()
- }
- }
+///
+/* eslint-env browser */
+
+import * as pieces from './pieces.svg.js';
+
+// ------------------------ //
+// Constants //
+// ------------------------ //
+
+const COLUMNS = 'abcdefgh'.split('');
+const DEFAULT_DRAG_THROTTLE_RATE = 20;
+const START_FEN = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR';
+const START_POSITION = fenToObj(START_FEN);
+
+// default animation speeds
+const DEFAULT_APPEAR_SPEED = 200;
+const DEFAULT_MOVE_SPEED = 200;
+const DEFAULT_SNAPBACK_SPEED = 60;
+const DEFAULT_SNAP_SPEED = 30;
+const DEFAULT_TRASH_SPEED = 100;
+
+// use unique class names to prevent clashing with anything else on the page
+// and simplify selectors
+// NOTE: these should never change
+const ClassNameLookup = {
+ alpha: 'alpha-d2270',
+ black: 'black-3c85d',
+ board: 'board-b72b1',
+ chessboard: 'chessboard-63f37',
+ clearfix: 'clearfix-7da63',
+ highlight1: 'highlight1-32417',
+ highlight2: 'highlight2-9c5d2',
+ notation: 'notation-322f9',
+ numeric: 'numeric-fc462',
+ piece: 'piece-417db',
+ row: 'row-5277c',
+ sparePieces: 'spare-pieces-7492f',
+ sparePiecesBottom: 'spare-pieces-bottom-ae20f',
+ sparePiecesTop: 'spare-pieces-top-4028b',
+ square: 'square-55d63',
+ white: 'white-1e1d7'
+};
+
+// ------------------------ //
+// Misc Utils //
+// ------------------------ //
+
+function uuid() {
+ return 'xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx'.replace(/x/g, function () {
+ const r = (Math.random() * 16) | 0;
+ return r.toString(16);
+ });
+}
- var fire = function () {
- timeout = window.setTimeout(handleTimeout, interval)
- f.apply(scope, args)
- }
+/**
+ *
+ * @param {HTMLElement} element
+ * @returns {{ top: number, left: number }}
+ */
+function getJqueryStyleOffset(element) {
+ const { documentElement: { clientTop, clientLeft } } = document;
+ const { x, y } = element.getBoundingClientRect();
+ return {
+ top: y + scrollY - clientTop,
+ left: x + scrollX - clientLeft
+ };
+}
- return function (_args) {
- args = arguments
- if (!timeout) {
- fire()
- } else {
- shouldFire = true
- }
+/**
+ * @param {unknown} o
+ * @returns {o is {}}
+ */
+function isPlainObject(o) {
+ return Object.prototype.toString.call(o) === '[object Object]';
+}
+
+
+// ------------------------ //
+// Chess Utils //
+// ------------------------ //
+
+function validMove(move) {
+ // move should be a string
+ if (typeof move !== 'string') return false;
+
+ // move should be in the form of "e2-e4", "f6-d5"
+ const squares = move.split('-');
+ if (squares.length !== 2) return false;
+
+ const [start, end] = squares;
+ return validSquare(start) && validSquare(end);
+}
+
+function validSquare(square) {
+ return typeof square === 'string' && square.search(/^[a-h][1-8]$/) !== -1;
+}
+
+function validPieceCode(code) {
+ return typeof code === 'string' && code.search(/^[bw][KQRNBP]$/) !== -1;
+}
+
+/**
+ * @param {string} [fen]
+ * @returns {boolean}
+ */
+function validFen(fen) {
+ if (typeof fen !== 'string') return false;
+
+ // 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
+ const chunks = fen.split('/');
+ if (chunks.length !== 8) return false;
+
+ // check each section
+ for (let i = 0; i < 8; i++) {
+ if (chunks[i].length !== 8 ||
+ chunks[i].search(/[^kqrnbpKQRNBP1]/) !== -1) {
+ return false;
}
}
- // function debounce (f, interval, scope) {
- // var timeout = 0
- // return function (_args) {
- // window.clearTimeout(timeout)
- // var args = arguments
- // timeout = window.setTimeout(function () {
- // f.apply(scope, args)
- // }, interval)
- // }
- // }
-
- function uuid () {
- return 'xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx'.replace(/x/g, function (c) {
- var r = (Math.random() * 16) | 0
- return r.toString(16)
- })
- }
+ return true;
+}
- function deepCopy (thing) {
- return JSON.parse(JSON.stringify(thing))
- }
+function validPositionObject(pos) {
+ if (!isPlainObject(pos)) return false;
+
+ for (const i in pos) {
+ if (!Object.prototype.hasOwnProperty.call(pos, i)) continue;
- function parseSemVer (version) {
- var tmp = version.split('.')
- return {
- major: parseInt(tmp[0], 10),
- minor: parseInt(tmp[1], 10),
- patch: parseInt(tmp[2], 10)
+ if (!validSquare(i) || !validPieceCode(pos[i])) {
+ return false;
}
}
- // returns true if version is >= minimum
- function validSemanticVersion (version, minimum) {
- version = parseSemVer(version)
- minimum = parseSemVer(minimum)
+ return true;
+}
- var versionNum = (version.major * 100000 * 100000) +
- (version.minor * 100000) +
- version.patch
- var minimumNum = (minimum.major * 100000 * 100000) +
- (minimum.minor * 100000) +
- minimum.patch
+/**
+ * Convert FEN string to position object. Returns `false` if the FEN string is invalid
+ * @param {string} [fen]
+ * @returns {false | { [x: string]: string; }}
+ */
+function fenToObj(fen) {
+ if (!fen || !validFen(fen)) return false;
+
+ // cut off any move, castling, etc info from the end
+ // we're only interested in position information
+ fen = fen.replace(/ .+$/, '');
+
+ const rows = fen.split('/');
+ const position = /** @type {{ [x: string]: string; }} */({});
+
+ let currentRow = 8;
+ for (let i = 0; i < 8; i++) {
+ const row = rows[i].split('');
+ let colIdx = 0;
+
+ // loop through each character in the FEN section
+ for (let j = 0; j < row.length; j++) {
+ // number / empty squares
+ const piece = row[j];
+ if (piece.search(/[1-8]/) !== -1) {
+ const numEmptySquares = parseInt(piece, 10);
+ colIdx = colIdx + numEmptySquares;
+ } else {
+ // piece
+ const square = COLUMNS[colIdx] + currentRow;
+ position[square] = piece.toLowerCase() === piece
+ ? `b${piece.toUpperCase()}` // black
+ : `w${piece.toUpperCase()}`; // white;
+ colIdx = colIdx + 1;
+ }
+ }
- return versionNum >= minimumNum
+ currentRow = currentRow - 1;
}
- function interpolateTemplate (str, obj) {
- for (var key in obj) {
- if (!obj.hasOwnProperty(key)) continue
- var keyTemplateStr = '{' + key + '}'
- var value = obj[key]
- while (str.indexOf(keyTemplateStr) !== -1) {
- str = str.replace(keyTemplateStr, value)
+ return position;
+}
+
+/**
+ * Converts position object to FEN string. Returns false if the obj is not a valid position object
+ * @param {object} obj
+ * @returns {false | string}
+ */
+function objToFen(obj) {
+ if (!validPositionObject(obj)) return false;
+
+ let fen = '';
+
+ let currentRow = 8;
+ for (let i = 0; i < 8; i++) {
+ for (let j = 0; j < 8; j++) {
+ const square = COLUMNS[j] + currentRow;
+
+ // piece exists
+ if (Object.prototype.hasOwnProperty.call(obj, square)) {
+ const [color, pieceKey] = obj[square].split('');
+ fen += color === 'w'
+ ? pieceKey.toUpperCase() // white
+ : pieceKey.toLowerCase(); // black
+ } else {
+ fen += '1'; // empty space
}
}
- return str
- }
- if (RUN_ASSERTS) {
- console.assert(interpolateTemplate('abc', {a: 'x'}) === 'abc')
- console.assert(interpolateTemplate('{a}bc', {}) === '{a}bc')
- console.assert(interpolateTemplate('{a}bc', {p: 'q'}) === '{a}bc')
- console.assert(interpolateTemplate('{a}bc', {a: 'x'}) === 'xbc')
- console.assert(interpolateTemplate('{a}bc{a}bc', {a: 'x'}) === 'xbcxbc')
- console.assert(interpolateTemplate('{a}{a}{b}', {a: 'x', b: 'y'}) === 'xxy')
+ if (i !== 7) {
+ fen = fen + '/';
+ }
+
+ currentRow = currentRow - 1;
}
- // ---------------------------------------------------------------------------
- // Predicates
- // ---------------------------------------------------------------------------
+ // squeeze the empty numbers together
+ fen = squeezeFenEmptySquares(fen);
- function isString (s) {
- return typeof s === 'string'
- }
+ return fen;
+}
- function isFunction (f) {
- return typeof f === 'function'
- }
+/**
+ * @param {string} fen
+ * @returns {string}
+ */
+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');
+}
- function isInteger (n) {
- return typeof n === 'number' &&
- isFinite(n) &&
- Math.floor(n) === n
- }
+/**
+ * @param {string} fen
+ * @returns {string}
+ */
+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');
+}
- function validAnimationSpeed (speed) {
- if (speed === 'fast' || speed === 'slow') return true
- if (!isInteger(speed)) return false
- return speed >= 0
- }
+/**
+ * Returns the distance between two squares
+ * @param {string} squareA
+ * @param {string} squareB
+ * @returns {number}
+ */
+function squareDistance(squareA, squareB) {
+ const squareAArray = squareA.split('');
+ const squareAx = COLUMNS.indexOf(squareAArray[0]) + 1;
+ const squareAy = parseInt(squareAArray[1], 10);
+
+ const squareBArray = squareB.split('');
+ const squareBx = COLUMNS.indexOf(squareBArray[0]) + 1;
+ const squareBy = parseInt(squareBArray[1], 10);
+
+ const xDelta = Math.abs(squareAx - squareBx);
+ const yDelta = Math.abs(squareAy - squareBy);
+
+ if (xDelta >= yDelta) return xDelta;
+ return yDelta;
+}
- function validThrottleRate (rate) {
- return isInteger(rate) &&
- rate >= 1
+/**
+ * Returns the square of the closest instance of piece, or `false` if no piece found
+ * @param {*} position
+ * @param {*} piece
+ * @param {*} square
+ * @returns {string | false}
+ */
+function findClosestPiece(position, piece, square) {
+ // create array of closest squares from square
+ const closestSquares = createRadius(square);
+
+ // search through the position in order of distance for the piece
+ for (let i = 0; i < closestSquares.length; i++) {
+ const s = closestSquares[i];
+
+ if (Object.prototype.hasOwnProperty.call(position, s) && position[s] === piece) {
+ return s;
+ }
}
- function validMove (move) {
- // move should be a string
- if (!isString(move)) return false
-
- // 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'))
- console.assert(validSquare('e2'))
- console.assert(!validSquare('D2'))
- console.assert(!validSquare('g9'))
- console.assert(!validSquare('a'))
- console.assert(!validSquare(true))
- console.assert(!validSquare(null))
- console.assert(!validSquare({}))
- }
+ return false;
+}
- function validPieceCode (code) {
- return isString(code) && code.search(/^[bw][KQRNBP]$/) !== -1
- }
-
- if (RUN_ASSERTS) {
- console.assert(validPieceCode('bP'))
- console.assert(validPieceCode('bK'))
- console.assert(validPieceCode('wK'))
- console.assert(validPieceCode('wR'))
- console.assert(!validPieceCode('WR'))
- console.assert(!validPieceCode('Wr'))
- console.assert(!validPieceCode('a'))
- console.assert(!validPieceCode(true))
- console.assert(!validPieceCode(null))
- console.assert(!validPieceCode({}))
+/**
+ * Returns an array of closest squares from square
+ * @param {string} square
+ * @returns
+ */
+function createRadius(square) {
+ const squares = [];
+
+ // calculate distance of all squares
+ for (let i = 0; i < 8; i++) {
+ for (let j = 0; j < 8; j++) {
+ const s = COLUMNS[i] + (j + 1);
+
+ // skip the square we're starting from
+ if (square === s) continue;
+
+ squares.push({
+ square: s,
+ distance: squareDistance(square, s)
+ });
+ }
}
- 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(/ .+$/, '')
-
- // expand the empty square numbers to just 1s
- fen = expandFenEmptySquares(fen)
-
- // 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
- }
-
- if (RUN_ASSERTS) {
- console.assert(validFen(START_FEN))
- console.assert(validFen('8/8/8/8/8/8/8/8'))
- console.assert(validFen('r1bqkbnr/pppp1ppp/2n5/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R'))
- console.assert(validFen('3r3r/1p4pp/2nb1k2/pP3p2/8/PB2PN2/p4PPP/R4RK1 b - - 0 1'))
- console.assert(!validFen('3r3z/1p4pp/2nb1k2/pP3p2/8/PB2PN2/p4PPP/R4RK1 b - - 0 1'))
- console.assert(!validFen('anbqkbnr/8/8/8/8/8/PPPPPPPP/8'))
- console.assert(!validFen('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/'))
- console.assert(!validFen('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBN'))
- console.assert(!validFen('888888/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR'))
- console.assert(!validFen('888888/pppppppp/74/8/8/8/PPPPPPPP/RNBQKBNR'))
- console.assert(!validFen({}))
+ // sort by distance
+ squares.sort(function (a, b) {
+ return a.distance - b.distance;
+ });
+
+ // just return the square code
+ const surroundingSquares = [];
+ for (let i = 0; i < squares.length; i++) {
+ surroundingSquares.push(squares[i].square);
}
- 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
- }
-
- if (RUN_ASSERTS) {
- console.assert(validPositionObject(START_POSITION))
- console.assert(validPositionObject({}))
- console.assert(validPositionObject({e2: 'wP'}))
- console.assert(validPositionObject({e2: 'wP', d2: 'wP'}))
- console.assert(!validPositionObject({e2: 'BP'}))
- console.assert(!validPositionObject({y2: 'wP'}))
- console.assert(!validPositionObject(null))
- console.assert(!validPositionObject('start'))
- console.assert(!validPositionObject(START_FEN))
+ return surroundingSquares;
+}
+
+class Chessboard {
+ static pieces = pieces;
+ static fenToObj = fenToObj;
+ static objToFen = objToFen;
+
+ /**
+ * @type {HTMLElement}
+ */
+ #container;
+ /**
+ * @type {HTMLElement}
+ */
+ #board;
+ /**
+ * @type {'white' | 'black'}
+ */
+ #orientation = 'white';
+ /**
+ * @type {Map}
+ */
+ #position = new Map();
+ /**
+ * @type {Map}
+ */
+ #squares = new Map();
+ /**
+ * @type {Map}
+ */
+ #pieces = new Map();
+ /**
+ * @type {Map}
+ */
+ #sparePieces = new Map();
+ /**
+ * @type {HTMLElement}
+ */
+ #topShelf;
+ /**
+ * @type {HTMLElement}
+ */
+ #bottomShelf;
+
+ /**
+ * @typedef {object} ChessboardConfig
+ * @property {number} appearSpeed
+ * @property {number} dragThrottleRate
+ * @property {number} moveSpeed
+ * @property {number} snapbackSpeed
+ * @property {number} snapSpeed
+ * @property {number} trashSpeed
+ * @property {boolean} draggable
+ * @property {boolean} showNotation
+ * @property {boolean} useAnimation
+ * @property {string | boolean} sparePieces
+ * @property {'snapback' | 'trash'} dropOffBoard
+ * @property {'white' | 'black'} orientation
+ * @property {string | { [x: string]: string }} position
+ * @property {'alert' | 'console' | boolean | ((x: string, y: any, z: HTMLElement) => void)} showErrors
+ * @property {string | ((x: string) => string)} pieceTheme
+ * @property {(x: object, y: object) => void} [onChange]
+ * @property {(x: string, y: string, z: string, w: HTMLElement, r: object, s: 'white' | 'black') => void} [onDragMove]
+ * @property {(x: string | null | undefined, y: boolean, z: object, o: 'white' | 'black') => boolean} [onDragStart]
+ * @property {(x: string, y: any, z: HTMLElement, w: object, r: object, s: 'white' | 'black') => 'snapback' | 'trash'} [onDrop]
+ * @property {(x: object, y: object) => void} [onMoveEnd]
+ */
+ #config = /** @type {ChessboardConfig} */ ({
+ showErrors: 'alert' // default value only used in constructor prior to user config assignment
+ });
+
+ get fen() {
+ return objToFen(this.position);
}
- function isTouchDevice () {
- return 'ontouchstart' in document.documentElement
+ get orientation() {
+ return this.#orientation;
}
- function validJQueryVersion () {
- return typeof window.$ &&
- $.fn &&
- $.fn.jquery &&
- validSemanticVersion($.fn.jquery, MINIMUM_JQUERY_VERSION)
+ get position() {
+ return Object.fromEntries(this.#position.entries());
}
- // ---------------------------------------------------------------------------
- // Chess Util Functions
- // ---------------------------------------------------------------------------
+ /**
+ * Returns calculated square size based on the width of the container
+ * @type {number}
+ */
+ get squareSize() {
+ // 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
+ const containerWidth = this.#container.clientWidth;
- // convert FEN piece code to bP, wK, etc
- function fenToPieceCode (piece) {
- // black piece
- if (piece.toLowerCase() === piece) {
- return 'b' + piece.toUpperCase()
+ // defensive, prevent infinite loop
+ if (!containerWidth || containerWidth <= 0) {
+ return 0;
}
- // white piece
- return 'w' + piece.toUpperCase()
- }
-
- // convert bP, wK, etc code to FEN structure
- function pieceCodeToFen (piece) {
- var pieceCodeLetters = piece.split('')
+ // pad one pixel
+ let boardWidth = containerWidth - 1;
- // white piece
- if (pieceCodeLetters[0] === 'w') {
- return pieceCodeLetters[1].toUpperCase()
+ while (boardWidth % 8 !== 0 && boardWidth > 0) {
+ boardWidth = boardWidth - 1;
}
- // black piece
- return pieceCodeLetters[1].toLowerCase()
+ return boardWidth / 8;
}
- // 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('')
- 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) {
- var numEmptySquares = parseInt(row[j], 10)
- colIdx = colIdx + numEmptySquares
- } else {
- // piece
- var square = COLUMNS[colIdx] + currentRow
- position[square] = fenToPieceCode(row[j])
- colIdx = colIdx + 1
- }
- }
-
- currentRow = currentRow - 1
- }
-
- return position
- }
+ /**
+ * @param {string | HTMLElement} containerElOrString
+ * @param {string | Partial} config
+ * @returns
+ */
+ constructor(containerElOrString, config) {
+ // first things first: check basic dependencies
+ if (containerElOrString === '') {
+ this.#error(1001, 'The first argument to new Chessboard() cannot be an empty string.\n\nExiting…');
+ return;
+ }
- // 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
- if (obj.hasOwnProperty(square)) {
- fen = fen + pieceCodeToFen(obj[square])
- } else {
- // empty space
- fen = fen + '1'
- }
- }
-
- if (i !== 7) {
- fen = fen + '/'
+ if (containerElOrString instanceof HTMLElement) {
+ this.#container = containerElOrString;
+ } else if (typeof containerElOrString === 'string') {
+ // convert containerEl to query selector if it is a string
+ if (containerElOrString.startsWith('#') === false) {
+ containerElOrString = `#${containerElOrString}`;
}
-
- currentRow = currentRow - 1
- }
-
- // squeeze the empty numbers together
- fen = squeezeFenEmptySquares(fen)
-
- return fen
- }
-
- if (RUN_ASSERTS) {
- console.assert(objToFen(START_POSITION) === START_FEN)
- console.assert(objToFen({}) === '8/8/8/8/8/8/8/8')
- console.assert(objToFen({a2: 'wP', 'b2': 'bP'}) === '8/8/8/8/8/8/Pp6/8')
- }
-
- 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')
- }
- 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')
- }
+ // containerEl must be something that becomes a NodeList of size 1
+ const container = document.querySelectorAll(containerElOrString);
+ if (container.length !== 1) {
+ this.#error(1003, 'The first argument to new Chessboard() must be the ID of a DOM node, an ID query selector, or a single DOM node.\n\nExiting…');
+ return;
+ }
- // returns the distance between two squares
- function squareDistance (squareA, squareB) {
- var squareAArray = squareA.split('')
- var squareAx = COLUMNS.indexOf(squareAArray[0]) + 1
- var squareAy = parseInt(squareAArray[1], 10)
+ this.#container = /** @type {HTMLElement} */((container.item(0)));
+ this.#container.classList.add(ClassNameLookup.chessboard);
+ this.#container.replaceChildren();
+ } else {
+ this.#error(1003, 'The first argument to new Chessboard() must be the ID of a DOM node, an ID query selector, or a single DOM node.\n\nExiting…');
+ return;
+ }
- var squareBArray = squareB.split('')
- var squareBx = COLUMNS.indexOf(squareBArray[0]) + 1
- var squareBy = parseInt(squareBArray[1], 10)
+ this.#board = document.createElement('div');
+ this.#board.classList.add(ClassNameLookup.board);
+ this.#config = this.#validateConfig(config);
+ this.#orientation = this.#config.orientation;
- var xDelta = Math.abs(squareAx - squareBx)
- var yDelta = Math.abs(squareAy - squareBy)
+ this.#topShelf = document.createElement('div');
+ this.#topShelf.classList.add(ClassNameLookup.sparePieces, ClassNameLookup.sparePiecesTop);
+ this.#bottomShelf = document.createElement('div');
+ this.#bottomShelf.classList.add(ClassNameLookup.sparePieces, ClassNameLookup.sparePiecesBottom);
- if (xDelta >= yDelta) return xDelta
- return yDelta
- }
+ this.#container.appendChild(this.#topShelf);
+ this.#container.appendChild(this.#board);
+ this.#container.appendChild(this.#bottomShelf);
- // 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) {
- // create array of closest squares from square
- var closestSquares = createRadius(square)
+ if (this.#config.sparePieces) {
+ for (const pieceKey of 'KQRNBP'.split('')) {
+ const whitePieceName = `w${pieceKey}`;
+ const whitePiece = this.#buildPiece(whitePieceName);
- // search through the position in order of distance for the piece
- for (var i = 0; i < closestSquares.length; i++) {
- var s = closestSquares[i]
+ const blackPieceName = `b${pieceKey}`;
+ const blackPiece = this.#buildPiece(blackPieceName);
- if (position.hasOwnProperty(s) && position[s] === piece) {
- return s
+ this.#sparePieces.set(whitePieceName, whitePiece);
+ this.#sparePieces.set(blackPieceName, blackPiece);
}
}
- return false
+ this.render();
}
- // returns an array of closest squares from square
- function createRadius (square) {
- var squares = []
+ /**
+ * Clear the board of pieces
+ */
+ clear() {
+ this.setPosition('clear');
+ }
- // 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)
+ /**
+ * Set the board to the starting position
+ */
+ start() {
+ this.setPosition('start');
+ }
- // skip the square we're starting from
- if (square === s) continue
+ /**
+ * Flip orientation
+ */
+ flip() {
+ return this.setOrientation('flip');
+ }
- squares.push({
- square: s,
- distance: squareDistance(square, s)
- })
+ /**
+ * Move pieces
+ * @param {string | string[]} moveset
+ */
+ move(moveset) {
+ if (moveset.length === 0) return;
+ if (!Array.isArray(moveset)) moveset = [moveset];
+
+ // collect the moves into an object
+ const moves = moveset
+ .filter(move => validMove(move) || this.#error(2826, 'Invalid move passed to the move method.', move))
+ .map(move => move.split('-'));
+
+ for (const [source, destination] of moves) {
+ if (!this.#position.has(source)) continue;
+
+ const piece = this.#position.get(source);
+ this.#position.delete(source);
+ if (typeof piece === 'string' && piece.length > 0) {
+ this.#position.set(destination, piece);
}
}
- // sort by distance
- squares.sort(function (a, b) {
- return a.distance - b.distance
- })
+ this.setPosition(this.position);
+ }
- // just return the square code
- var surroundingSquares = []
- for (i = 0; i < squares.length; i++) {
- surroundingSquares.push(squares[i].square)
+ /**
+ * @param {'white' | 'black' | 'flip'} orientation
+ */
+ setOrientation(orientation) {
+ if (!orientation) {
+ return this.#orientation;
+ } else if (orientation === 'white' || orientation === 'black') {
+ this.#orientation = orientation;
+ } else if (orientation === 'flip') {
+ this.#orientation = this.#orientation === 'white' ? 'black' : 'white';
+ } else {
+ this.#error(5482, 'Invalid value passed to the orientation method.', orientation);
}
- return surroundingSquares
- }
+ this.render();
- // given a position and a set of moves, return a new position
- // with the moves executed
- function calculatePositionFromMoves (position, moves) {
- var newPosition = deepCopy(position)
+ return this.#orientation;
+ }
- for (var i in moves) {
- if (!moves.hasOwnProperty(i)) continue
+ /**
+ * @param {'clear' | 'start' | object} position
+ */
+ setPosition(position) {
+ const positionIsString = typeof position === 'string';
- // skip the move if the position doesn't have a piece on the source square
- if (!newPosition.hasOwnProperty(i)) continue
+ if (positionIsString) {
+ const positionLowerCase = position.toLowerCase();
- var piece = newPosition[i]
- delete newPosition[i]
- newPosition[moves[i]] = piece
+ if (positionLowerCase === 'clear') {
+ position = {};
+ } else if (positionLowerCase === 'start') {
+ position = START_POSITION;
+ }
}
- return newPosition
- }
-
- // TODO: add some asserts here for calculatePositionFromMoves
-
- // ---------------------------------------------------------------------------
- // HTML
- // ---------------------------------------------------------------------------
+ // convert FEN to position object
+ if (validFen(position)) {
+ position = fenToObj(position);
+ }
- function buildContainerHTML (hasSparePieces) {
- var html = ''
- if (hasSparePieces) {
- html += ''
+ // validate position object
+ if (validPositionObject(position)) {
+ position = { ...position };
+ } else {
+ this.#error(6482, 'Invalid value passed to the position method.', position);
+ return;
}
- html += ''
-
- if (hasSparePieces) {
- html += ''
+ const setCurrentPosition = () => {
+ const { position: currentPosition } = this;
+ if (objToFen(currentPosition) !== objToFen(position)) {
+ if (typeof this.#config.onChange === 'function') {
+ this.#config.onChange(currentPosition, position);
+ }
+ this.#position = new Map(Object.entries(position));
+ }
+ };
+
+ if (this.#config.useAnimation) {
+ const animations = this.#calculateAnimations({ ...position });
+ this.#doAnimations(animations, { ...position }).then(setCurrentPosition);
+ } else {
+ setCurrentPosition();
+ this.#drawPosition();
}
+ }
- html += ''
+ render() {
+ this.#drawBoard();
+ this.#drawPosition();
+ }
- return interpolateTemplate(html, CSS)
+ /**
+ * Irretrievably remove everything. The Chessboard instance will become useless.
+ */
+ destroy() {
+ this.#container.replaceChildren();
+ this.#container = /** @type {any} */(undefined);
}
- // ---------------------------------------------------------------------------
- // Config
- // ---------------------------------------------------------------------------
+ #error(code, msg, obj) {
+ // do nothing if showErrors is not set
+ if (
+ Object.prototype.hasOwnProperty.call(this.#config, 'showErrors') !== true ||
+ this.#config.showErrors === false
+ ) {
+ return;
+ }
- function expandConfigArgumentShorthand (config) {
- if (config === 'start') {
- config = {position: deepCopy(START_POSITION)}
- } else if (validFen(config)) {
- config = {position: fenToObj(config)}
- } else if (validPositionObject(config)) {
- config = {position: deepCopy(config)}
+ let errorText = 'Chessboard Error ' + code + ': ' + msg;
+
+ // print to console
+ if (
+ this.#config.showErrors === 'console' &&
+ typeof console === 'object' &&
+ typeof console.log === 'function'
+ ) {
+ console.log(errorText);
+ if (obj) {
+ console.log(obj);
+ }
+ return;
}
- // config must be an object
- if (!$.isPlainObject(config)) config = {}
+ // alert errors
+ if (this.#config.showErrors === 'alert') {
+ if (obj) {
+ errorText += '\n\n' + JSON.stringify(obj);
+ }
+ window.alert(errorText);
+ return;
+ }
- return config
+ // custom function
+ if (typeof this.#config.showErrors === 'function') {
+ this.#config.showErrors(code, msg, obj);
+ }
}
- // validate config / set default options
- function expandConfig (config) {
- // default for orientation is white
- if (config.orientation !== 'black') config.orientation = 'white'
-
- // default for showNotation is true
- if (config.showNotation !== false) config.showNotation = true
-
- // default for draggable is false
- if (config.draggable !== true) config.draggable = false
-
- // default for dropOffBoard is 'snapback'
- if (config.dropOffBoard !== 'trash') config.dropOffBoard = 'snapback'
-
- // default for sparePieces is false
- if (config.sparePieces !== true) config.sparePieces = false
-
- // draggable must be true if sparePieces is enabled
- if (config.sparePieces) config.draggable = true
+ /**
+ * @param {string | Partial} config
+ * @returns {ChessboardConfig}
+ */
+ #validateConfig(config) {
+ // ensure the config object is what we expect
+ if (typeof config === 'string') {
+ if (config === 'start') {
+ config = { position: { ...START_POSITION } };
+ } else if (validFen(config)) {
+ config = { position: fenToObj(config) || {} };
+ } else {
+ config = {};
+ }
+ } else if (validPositionObject(config)) {
+ config = { position: { .../** @type {{ [x: string]: string; }} */(config) } };
+ } else if (!isPlainObject(config)) {
+ config = {};
+ }
- // default piece theme is wikipedia
- if (!config.hasOwnProperty('pieceTheme') ||
- (!isString(config.pieceTheme) && !isFunction(config.pieceTheme))) {
- config.pieceTheme = 'img/chesspieces/wikipedia/{piece}.png'
+ if (config.orientation !== 'black') config.orientation = 'white';
+ if (config.useAnimation !== false) config.useAnimation = true;
+ if (config.showNotation !== false) config.showNotation = true;
+ if (config.draggable !== true) config.draggable = false;
+ if (config.dropOffBoard !== 'trash') config.dropOffBoard = 'snapback';
+ if (config.sparePieces !== true) config.sparePieces = false;
+ if (config.sparePieces) config.draggable = true; // draggable must be true if sparePieces is enabled
+
+ // default piece theme is built-in svg
+ if (!Object.prototype.hasOwnProperty.call(config, 'pieceTheme') ||
+ (typeof config.pieceTheme !== 'string' && typeof config.pieceTheme !== 'function')) {
+ config.pieceTheme = '{piece}';
}
- // animation speeds
- if (!validAnimationSpeed(config.appearSpeed)) config.appearSpeed = DEFAULT_APPEAR_SPEED
- if (!validAnimationSpeed(config.moveSpeed)) config.moveSpeed = DEFAULT_MOVE_SPEED
- if (!validAnimationSpeed(config.snapbackSpeed)) config.snapbackSpeed = DEFAULT_SNAPBACK_SPEED
- if (!validAnimationSpeed(config.snapSpeed)) config.snapSpeed = DEFAULT_SNAP_SPEED
- if (!validAnimationSpeed(config.trashSpeed)) config.trashSpeed = DEFAULT_TRASH_SPEED
+ const validThrottleRate = (rate) => Number.isInteger(rate) && rate >= 1;
// throttle rate
- if (!validThrottleRate(config.dragThrottleRate)) config.dragThrottleRate = DEFAULT_DRAG_THROTTLE_RATE
+ if (!validThrottleRate(config.dragThrottleRate))
+ config.dragThrottleRate = DEFAULT_DRAG_THROTTLE_RATE;
- return config
- }
+ const getValidAnimationSpeed = (speed, defaultValue) => {
+ if (speed === 'fast') return defaultValue / 2;
+ if (speed === 'slow') return defaultValue * 3;
+ if (Number.isInteger(speed) && speed >= 0) return speed;
+ return defaultValue;
+ };
- // ---------------------------------------------------------------------------
- // Dependencies
- // ---------------------------------------------------------------------------
-
- // check for a compatible version of jQuery
- function checkJQuery () {
- if (!validJQueryVersion()) {
- var errorMsg = 'Chessboard Error 1005: Unable to find a valid version of jQuery. ' +
- 'Please include jQuery ' + MINIMUM_JQUERY_VERSION + ' or higher on the page' +
- '\n\n' +
- 'Exiting' + ELLIPSIS
- window.alert(errorMsg)
- return false
+ // animation speeds
+ config.appearSpeed = getValidAnimationSpeed(config.appearSpeed, DEFAULT_APPEAR_SPEED);
+ config.snapbackSpeed = getValidAnimationSpeed(config.snapbackSpeed, DEFAULT_SNAPBACK_SPEED);
+ config.snapSpeed = getValidAnimationSpeed(config.snapSpeed, DEFAULT_SNAP_SPEED);
+ config.moveSpeed = getValidAnimationSpeed(config.moveSpeed, DEFAULT_MOVE_SPEED);
+ config.trashSpeed = getValidAnimationSpeed(config.trashSpeed, DEFAULT_TRASH_SPEED);
+
+ // make sure position is valid
+ if (config.position != null) {
+ if (typeof config.position === 'string') {
+ if (config.position === 'start') {
+ this.#position = new Map(Object.entries(START_POSITION));
+ } else if (validFen(config.position)) {
+ this.#position = new Map(Object.entries(fenToObj(config.position)));
+ }
+ } else if (validPositionObject(config.position)) {
+ this.#position = new Map(Object.entries(config.position));
+ } else {
+ this.#error(7263, 'Invalid value passed to config.position.', config.position);
+ }
}
- return true
+ return /** @type {ChessboardConfig} */(config);
}
- // return either boolean false or the $container element
- function checkContainerArg (containerElOrString) {
- if (containerElOrString === '') {
- var errorMsg1 = 'Chessboard Error 1001: ' +
- 'The first argument to Chessboard() cannot be an empty string.' +
- '\n\n' +
- 'Exiting' + ELLIPSIS
- window.alert(errorMsg1)
- return false
- }
+ #buildPiece(pieceName) {
+ const { squareSize } = this;
+ const piece = new Image(squareSize, squareSize);
- // convert containerEl to query selector if it is a string
- if (isString(containerElOrString) &&
- containerElOrString.charAt(0) !== '#') {
- containerElOrString = '#' + containerElOrString
+ let src = '';
+ if (typeof this.#config.pieceTheme === 'function') {
+ src = this.#config.pieceTheme(pieceName);
+ }
+ if (typeof this.#config.pieceTheme === 'string') {
+ src = this.#config.pieceTheme.replaceAll('{piece}', pieceName);
}
- // containerEl must be something that becomes a jQuery collection of size 1
- var $container = $(containerElOrString)
- if ($container.length !== 1) {
- var errorMsg2 = 'Chessboard Error 1003: ' +
- 'The first argument to Chessboard() must be the ID of a DOM node, ' +
- 'an ID query selector, or a single DOM node.' +
- '\n\n' +
- 'Exiting' + ELLIPSIS
- window.alert(errorMsg2)
- return false
+ piece.id = `${pieceName}-${uuid()}`;
+ piece.alt = '';
+ piece.src = src === pieceName ? pieces[src] : src;
+ piece.classList.add(ClassNameLookup.piece);
+ piece.dataset.piece = pieceName;
+
+ piece.draggable = this.#config.draggable;
+ if (piece.draggable) {
+ piece.ondragstart = (evt) => {
+ if (evt.target instanceof HTMLElement && evt.dataTransfer != null) {
+ const source = evt.target.dataset.square ?? "spare";
+ if (source !== "spare") {
+ const square = this.#squares.get(source);
+ if (square instanceof HTMLElement) {
+ square.classList.add(ClassNameLookup.highlight1);
+ }
+ }
+ evt.dataTransfer.setData("pieceName", pieceName);
+ evt.dataTransfer.setData("source", source);
+ evt.dataTransfer.effectAllowed = "move";
+ }
+ };
}
- return $container
+ return piece;
}
- // ---------------------------------------------------------------------------
- // Constructor
- // ---------------------------------------------------------------------------
+ /**
+ * Calculate an array of animations that need to happen in order to get from `pos1` to `pos2`
+ * @param {{ [index: string ]: string }} position
+ * @returns {any[]}
+ */
+ #calculateAnimations(position) {
+ const animations = [];
+
+ if (Object.keys(position).length === 0) {
+ for (const [square, piece] of this.#position.entries()) {
+ animations.push({ type: 'clear', square, piece });
+ }
+ return animations;
+ }
- function constructor (containerElOrString, config) {
- // first things first: check basic dependencies
- if (!checkJQuery()) return null
- var $container = checkContainerArg(containerElOrString)
- if (!$container) return null
+ const { position: currentPosition } = this;
- // ensure the config object is what we expect
- config = expandConfigArgumentShorthand(config)
- config = expandConfig(config)
-
- // DOM elements
- var $board = null
- var $draggedPiece = null
- var $sparePiecesTop = null
- var $sparePiecesBottom = null
-
- // constructor return object
- var widget = {}
-
- // -------------------------------------------------------------------------
- // Stateful
- // -------------------------------------------------------------------------
-
- var boardBorderSize = 2
- var currentOrientation = 'white'
- var currentPosition = {}
- var draggedPiece = null
- var draggedPieceLocation = null
- var draggedPieceSource = null
- var isDragging = false
- var sparePiecesElsIds = {}
- var squareElsIds = {}
- var squareElsOffsets = {}
- var squareSize = 16
-
- // -------------------------------------------------------------------------
- // Validation / Errors
- // -------------------------------------------------------------------------
-
- function error (code, msg, obj) {
- // do nothing if showErrors is not set
- if (
- config.hasOwnProperty('showErrors') !== true ||
- config.showErrors === false
- ) {
- return
- }
-
- var errorText = 'Chessboard Error ' + code + ': ' + msg
-
- // 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
- if (config.showErrors === 'alert') {
- if (obj) {
- errorText += '\n\n' + JSON.stringify(obj)
- }
- window.alert(errorText)
- return
- }
-
- // custom function
- if (isFunction(config.showErrors)) {
- config.showErrors(code, msg, obj)
- }
- }
+ const squaresMovedTo = {};
- function setInitialState () {
- currentOrientation = config.orientation
+ // remove pieces that are the same in both positions
+ for (const i in position) {
+ if (
+ Object.prototype.hasOwnProperty.call(position, i) &&
+ Object.prototype.hasOwnProperty.call(currentPosition, i) &&
+ currentPosition[i] === position[i]
+ ) {
+ delete currentPosition[i];
+ delete position[i];
+ }
+ }
- // make sure position is valid
- if (config.hasOwnProperty('position')) {
- if (config.position === 'start') {
- currentPosition = deepCopy(START_POSITION)
- } else if (validFen(config.position)) {
- currentPosition = fenToObj(config.position)
- } else if (validPositionObject(config.position)) {
- currentPosition = deepCopy(config.position)
- } else {
- error(
- 7263,
- 'Invalid value passed to config.position.',
- config.position
- )
+ // find all the "move" animations
+ for (const square in position) {
+ if (Object.prototype.hasOwnProperty.call(position, square)) {
+ const source = findClosestPiece(currentPosition, position[square], square);
+ if (source) {
+ animations.push({ type: 'move', source, destination: square, piece: position[square] });
+ delete currentPosition[source];
+ delete position[square];
+ squaresMovedTo[square] = true;
}
}
}
- // -------------------------------------------------------------------------
- // 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 () {
- var containerWidth = parseInt($container.width(), 10)
-
- // defensive, prevent infinite loop
- if (!containerWidth || containerWidth <= 0) {
- return 0
- }
-
- // pad one pixel
- var boardWidth = containerWidth - 1
-
- while (boardWidth % 8 !== 0 && boardWidth > 0) {
- boardWidth = boardWidth - 1
- }
-
- 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
- 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]
- sparePiecesElsIds[whitePiece] = whitePiece + '-' + uuid()
- sparePiecesElsIds[blackPiece] = blackPiece + '-' + uuid()
- }
- }
-
- // -------------------------------------------------------------------------
- // Markup Building
- // -------------------------------------------------------------------------
-
- function buildBoardHTML (orientation) {
- if (orientation !== 'black') {
- orientation = 'white'
+ // "add" animations
+ for (const square in position) {
+ if (Object.prototype.hasOwnProperty.call(position, square)) {
+ animations.push({ type: 'add', square, piece: position[square] });
+ delete position[square];
}
+ }
- var html = ''
-
- // algebraic notation / orientation
- var alpha = deepCopy(COLUMNS)
- var row = 8
- if (orientation === 'black') {
- alpha.reverse()
- row = 1
+ // "clear" animations
+ for (const square in currentPosition) {
+ if (
+ Object.prototype.hasOwnProperty.call(currentPosition, square) &&
+ // do not clear a piece if it is on a square that is the result
+ // of a "move", ie: a piece capture
+ !Object.prototype.hasOwnProperty.call(squaresMovedTo, square)
+ ) {
+ animations.push({ type: 'clear', square: square, piece: currentPosition[square] });
+ delete currentPosition[square];
}
+ }
- var squareColor = 'white'
- for (var i = 0; i < 8; i++) {
- html += ''
- for (var j = 0; j < 8; j++) {
- var square = alpha[j] + row
-
- html += ''
-
- if (config.showNotation) {
- // alpha notation
- if ((orientation === 'white' && row === 1) ||
- (orientation === 'black' && row === 8)) {
- html += '' + alpha[j] + ''
- }
+ return animations;
+ }
- // numeric notation
- if (j === 0) {
- html += '' + row + ''
+ /**
+ * Execute an array of animations
+ * @param {any[]} animations
+ * @param {{ [index: string]: string; }} newPosition
+ */
+ #doAnimations(animations, newPosition) {
+ if (animations.length === 0) return Promise.resolve();
+
+ let numFinished = 0;
+ return Promise.all(
+ animations.map(animation => /** @type {Promise} */(new Promise((resolve) => {
+ const onFinishAnimation = () => {
+ numFinished += 1;
+ if (numFinished === animations.length) {
+ if (typeof this.#config.onMoveEnd === 'function') {
+ this.#config.onMoveEnd(this.position, { ...newPosition });
}
+ resolve();
}
+ };
+ if (animation.type === 'add') {
+ this.#doAddAnimation(animation, onFinishAnimation);
+ } else if (animation.type === 'clear') {
+ this.#doClearAnimation(animation, onFinishAnimation);
+ } else if (animation.type === 'move') {
+ this.#doMoveAnimation(animation, onFinishAnimation);
+ }
+ })))
+ );
+ }
+
+ #doAddAnimation(animation, onFinishAnimation) {
+ const { piece: pieceName, square: squareName } = animation;
+ const { sparePieces } = this.#config;
- html += ' ' // end .square
+ const piece = this.#buildPiece(pieceName);
+ const destination = /** @type {HTMLElement} */(this.#squares.get(squareName));
- squareColor = (squareColor === 'white') ? 'black' : 'white'
+ if (destination != null) {
+ if (sparePieces) {
+ const sparePiece = this.#sparePieces.get(pieceName);
+ if (sparePiece == null) {
+ throw new Error(`Unable to locate spare piece for ${pieceName}`);
}
- html += ''
- squareColor = (squareColor === 'white') ? 'black' : 'white'
+ document.body.appendChild(piece);
+ piece.style.setProperty('position', 'absolute');
- if (orientation === 'white') {
- row = row - 1
- } else {
- row = row + 1
- }
- }
+ piece
+ .animate(
+ [getJqueryStyleOffset(sparePiece), getJqueryStyleOffset(destination)],
+ this.#config.moveSpeed
+ )
+ .onfinish = () => {
+ piece.style.removeProperty('position');
+ piece.dataset.square = squareName;
+ destination.prepend(piece);
- return interpolateTemplate(html, CSS)
- }
-
- 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 = '
'
-
- 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
- }
-
- // -------------------------------------------------------------------------
- // Animations
- // -------------------------------------------------------------------------
-
- 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()
- $('body').append(buildPieceHTML(piece, true, animatedPieceId))
- var $animatedPiece = $('#' + animatedPieceId)
- $animatedPiece.css({
- 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()
- }
- }
-
- // animate the piece to the destination square
- var opts = {
- duration: config.moveSpeed,
- complete: onFinishAnimation1
- }
- $animatedPiece.animate(destSquarePosition, opts)
- }
-
- 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()
- $('body').append(buildPieceHTML(piece, true, pieceId))
- var $animatedPiece = $('#' + pieceId)
- $animatedPiece.css({
- 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()
- }
- }
-
- // animate the piece to the destination square
- var opts = {
- duration: config.moveSpeed,
- complete: onFinishAnimation2
- }
- $animatedPiece.animate(destOffset, opts)
+ onFinishAnimation();
+ };
+ } else {
+ destination.prepend(piece);
+ piece
+ .animate([{ opacity: 0 }, { opacity: 1 }], this.#config.appearSpeed)
+ .onfinish = () => {
+ this.#pieces.delete(squareName);
+ this.#pieces.set(squareName, piece);
+ onFinishAnimation();
+ };
+ }
+ } else {
+ throw new Error(`Unable to locate destination square ${squareName}`);
}
+ }
- // execute an array of animations
- function doAnimations (animations, oldPos, newPos) {
- if (animations.length === 0) return
-
- 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]
-
- // clear a piece
- if (animation.type === 'clear') {
- $('#' + squareElsIds[animation.square] + ' .' + CSS.piece)
- .fadeOut(config.trashSpeed, onFinishAnimation3)
-
- // add a piece with no spare pieces - fade the piece onto the square
- } else if (animation.type === 'add' && !config.sparePieces) {
- $('#' + squareElsIds[animation.square])
- .append(buildPieceHTML(animation.piece, true))
- .find('.' + CSS.piece)
- .fadeIn(config.appearSpeed, onFinishAnimation3)
-
- // add a piece with spare pieces - animate from the spares
- } else if (animation.type === 'add' && config.sparePieces) {
- animateSparePieceToSquare(animation.piece, animation.square, onFinishAnimation3)
-
- // move a piece from squareA to squareB
- } else if (animation.type === 'move') {
- animateSquareToSquare(animation.source, animation.destination, animation.piece, onFinishAnimation3)
- }
- }
+ #doClearAnimation(animation, onFinishAnimation) {
+ const piece = this.#pieces.get(animation.square);
+ if (piece != null) {
+ piece
+ .animate([{ opacity: 1 }, { opacity: 0 }], this.#config.trashSpeed)
+ .onfinish = () => {
+ piece.remove();
+ this.#pieces.delete(animation.square);
+ this.#position.delete(animation.square);
+ onFinishAnimation();
+ };
}
+ }
- // calculate an array of animations that need to happen in order to get
- // from pos1 to pos2
- function calculateAnimations (pos1, pos2) {
- // make copies of both
- 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]
- }
- }
-
- // find all the "move" animations
- for (i in pos2) {
- 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
- }
- }
+ #doMoveAnimation(animation, onFinishAnimation) {
+ const { piece: pieceName, source: sourceName, destination: destinationName } = animation;
- // "add" animations
- for (i in pos2) {
- if (!pos2.hasOwnProperty(i)) continue
-
- 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
- }
-
- // -------------------------------------------------------------------------
- // Control Flow
- // -------------------------------------------------------------------------
-
- 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 () {
- $board.html(buildBoardHTML(currentOrientation, squareSize, config.showNotation))
- drawPositionInstant()
-
- if (config.sparePieces) {
- if (currentOrientation === 'white') {
- $sparePiecesTop.html(buildSparePiecesHTML('black'))
- $sparePiecesBottom.html(buildSparePiecesHTML('white'))
- } else {
- $sparePiecesTop.html(buildSparePiecesHTML('white'))
- $sparePiecesBottom.html(buildSparePiecesHTML('black'))
- }
- }
- }
-
- function setCurrentPosition (position) {
- var oldPos = deepCopy(currentPosition)
- var newPos = deepCopy(position)
- var oldFen = objToFen(oldPos)
- var newFen = objToFen(newPos)
-
- // do nothing if no change in position
- if (oldFen === newFen) return
-
- // run their onChange function
- if (isFunction(config.onChange)) {
- config.onChange(oldPos, newPos)
- }
-
- // update state
- currentPosition = position
- }
-
- 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 + squareSize) {
- return i
- }
- }
-
- return 'offboard'
- }
-
- // records the XY coords of every square into memory
- function captureSquareOffsets () {
- squareElsOffsets = {}
-
- for (var i in squareElsIds) {
- if (!squareElsIds.hasOwnProperty(i)) continue
-
- squareElsOffsets[i] = $('#' + squareElsIds[i]).offset()
- }
- }
-
- function removeSquareHighlights () {
- $board
- .find('.' + CSS.square)
- .removeClass(CSS.highlight1 + ' ' + CSS.highlight2)
- }
-
- function snapbackDraggedPiece () {
- // there is no "snapback" for spare pieces
- if (draggedPieceSource === 'spare') {
- trashDraggedPiece()
- return
- }
-
- removeSquareHighlights()
-
- // animation complete
- function complete () {
- drawPositionInstant()
- $draggedPiece.css('display', 'none')
-
- // run their onSnapbackEnd function
- if (isFunction(config.onSnapbackEnd)) {
- config.onSnapbackEnd(
- draggedPiece,
- draggedPieceSource,
- deepCopy(currentPosition),
- currentOrientation
- )
- }
- }
-
- // get source square position
- var sourceSquarePosition = $('#' + squareElsIds[draggedPieceSource]).offset()
-
- // animate the piece to the target square
- var opts = {
- duration: config.snapbackSpeed,
- complete: complete
- }
- $draggedPiece.animate(sourceSquarePosition, opts)
-
- // set state
- isDragging = false
- }
-
- function trashDraggedPiece () {
- removeSquareHighlights()
-
- // remove the source piece
- var newPosition = deepCopy(currentPosition)
- delete newPosition[draggedPieceSource]
- setCurrentPosition(newPosition)
-
- // redraw the position
- drawPositionInstant()
-
- // hide the dragged piece
- $draggedPiece.fadeOut(config.trashSpeed)
-
- // set state
- isDragging = false
- }
-
- function dropDraggedPieceOnSquare (square) {
- removeSquareHighlights()
-
- // update position
- var newPosition = deepCopy(currentPosition)
- delete newPosition[draggedPieceSource]
- newPosition[square] = draggedPiece
- setCurrentPosition(newPosition)
-
- // get target square information
- var targetSquarePosition = $('#' + squareElsIds[square]).offset()
-
- // animation complete
- function onAnimationComplete () {
- 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 = {
- duration: config.snapSpeed,
- complete: onAnimationComplete
- }
- $draggedPiece.animate(targetSquarePosition, opts)
-
- // set state
- isDragging = false
- }
-
- 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
- }
-
- // set state
- isDragging = true
- draggedPiece = piece
- draggedPieceSource = source
-
- // if the piece came from spare pieces, location is offboard
- if (source === 'spare') {
- draggedPieceLocation = 'offboard'
- } else {
- draggedPieceLocation = source
- }
-
- // capture the x, y coords of all squares in memory
- captureSquareOffsets()
-
- // create the dragged piece
- $draggedPiece.attr('src', buildPieceImgSrc(piece)).css({
- display: '',
- position: 'absolute',
- left: x - squareSize / 2,
- top: y - squareSize / 2
- })
-
- 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) {
- // 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)
-
- // 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,
- draggedPieceLocation,
- draggedPieceSource,
- draggedPiece,
- deepCopy(currentPosition),
- currentOrientation
- )
- }
-
- // update state
- draggedPieceLocation = location
- }
-
- function stopDraggedPiece (location) {
- // determine what the action should be
- var action = 'drop'
- if (location === 'offboard' && config.dropOffBoard === 'snapback') {
- action = 'snapback'
- }
- if (location === 'offboard' && config.dropOffBoard === '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,
- draggedPiece,
- newPosition,
- oldPosition,
- currentOrientation
- )
- if (result === 'snapback' || result === 'trash') {
- action = result
- }
- }
-
- // do it!
- if (action === 'snapback') {
- snapbackDraggedPiece()
- } else if (action === 'trash') {
- trashDraggedPiece()
- } else if (action === 'drop') {
- dropDraggedPieceOnSquare(location)
- }
- }
-
- // -------------------------------------------------------------------------
- // Public Methods
- // -------------------------------------------------------------------------
-
- // clear the board
- widget.clear = function (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')
- }
-
- // flip orientation
- widget.flip = function () {
- return widget.orientation('flip')
- }
+ const source = /** @type {HTMLElement} */(this.#squares.get(sourceName));
+ const destination = /** @type {HTMLElement} */(this.#squares.get(destinationName));
- // move pieces
- // TODO: this method should be variadic as well as accept an array of moves
- 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
-
- // collect the moves into an object
- 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
- }
-
- // 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]
- }
-
- // calculate position from moves
- var newPos = calculatePositionFromMoves(currentPosition, moves)
-
- // update the board
- widget.position(newPos, useAnimation)
-
- // return the new position object
- return newPos
- }
-
- widget.orientation = function (arg) {
- // no arguments, return the current orientation
- if (arguments.length === 0) {
- return currentOrientation
- }
-
- // set to white or black
- if (arg === 'white' || arg === 'black') {
- currentOrientation = arg
- drawBoard()
- return currentOrientation
- }
-
- // flip orientation
- if (arg === 'flip') {
- currentOrientation = currentOrientation === 'white' ? 'black' : 'white'
- drawBoard()
- return currentOrientation
- }
-
- error(5482, 'Invalid value passed to the orientation method.', arg)
+ const piece = this.#pieces.get(sourceName);
+ if (piece == null) {
+ throw new Error(`Unable to locate piece for square ${sourceName}`);
}
+ document.body.appendChild(piece);
+ piece.style.setProperty('position', 'absolute');
+
+ piece
+ .animate(
+ [getJqueryStyleOffset(source), getJqueryStyleOffset(destination)],
+ this.#config.moveSpeed
+ )
+ .onfinish = () => {
+ piece.style.removeProperty('position');
+
+ this.#pieces.delete(sourceName);
+ this.#position.delete(sourceName);
+ this.#pieces.get(destinationName)?.remove();
+ this.#pieces.set(destinationName, piece);
+ this.#position.set(destinationName, pieceName);
+
+ destination.appendChild(piece);
+ onFinishAnimation();
+ };
+ }
- widget.position = function (position, useAnimation) {
- // no arguments, return the current position
- if (arguments.length === 0) {
- return deepCopy(currentPosition)
- }
-
- // get position as FEN
- if (isString(position) && position.toLowerCase() === 'fen') {
- return objToFen(currentPosition)
+ // ------------------------
+ // Control Flow
+ // ------------------------
+ #clearPieces() {
+ if (this.#pieces.size === 0) return;
+
+ for (const piece of this.#pieces.values()) {
+ if (this.#config.useAnimation) {
+ piece
+ .animate([{ opacity: 1 }, { opacity: 0 }], this.#config.trashSpeed)
+ .onfinish = () => piece.remove();
+ } else {
+ piece.remove();
}
-
- // start position
- if (isString(position) && position.toLowerCase() === 'start') {
- position = deepCopy(START_POSITION)
- }
-
- // convert FEN to position object
- if (validFen(position)) {
- position = fenToObj(position)
- }
-
- // validate position object
- if (!validPositionObject(position)) {
- error(6482, 'Invalid value passed to the position method.', position)
- return
- }
-
- // default for useAnimations is true
- if (useAnimation !== false) useAnimation = true
-
- if (useAnimation) {
- // start the animations
- var animations = calculateAnimations(currentPosition, position)
- doAnimations(animations, currentPosition, position)
-
- // set the new position
- setCurrentPosition(position)
- } else {
- // instant update
- setCurrentPosition(position)
- drawPositionInstant()
- }
}
+ this.#pieces.clear();
+ this.#removeSquareHighlights();
+ }
- 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')
- }
-
- // redraw the board
- drawBoard()
+ #drawPosition() {
+ this.#clearPieces();
+
+ for (const [squareName, pieceName] of this.#position.entries()) {
+ const square = this.#squares.get(squareName);
+ if (square != null) {
+ const piece = this.#buildPiece(pieceName);
+ piece.dataset.square = squareName;
+ this.#pieces.set(squareName, piece);
+ square.appendChild(piece);
+ }
}
+ }
+
+ #drawBoard() {
+ this.#clearPieces();
+ this.#board.style.setProperty('width', `${this.squareSize * 8}px`);
- // set the starting position
- widget.start = function (useAnimation) {
- widget.position('start', useAnimation)
+ // algebraic notation / orientation
+ const alpha = COLUMNS.slice();
+ let currentRow = 8;
+ if (this.#orientation === 'black') {
+ alpha.reverse();
+ currentRow = 1;
}
- // -------------------------------------------------------------------------
- // Browser Events
- // -------------------------------------------------------------------------
+ this.#board.replaceChildren();
+
+ let squareColor = 'white';
+ for (let i = 0; i < 8; i++) {
+ const row = document.createElement('row');
+ row.classList.add(ClassNameLookup.row);
+
+ for (let j = 0; j < 8; j++) {
+ const { squareSize } = this;
+ const squareName = alpha[j] + currentRow;
+ const square = document.createElement('div');
+
+ square.id = `${squareName}-${uuid()}`;
+ square.dataset.square = squareName;
+ square.classList.add(ClassNameLookup.square, ClassNameLookup[squareColor], `square-${squareName}`);
+ square.style.setProperty('height', `${squareSize}px`);
+ square.style.setProperty('width', `${squareSize}px`);
+
+ square.ondrop = (evt) => {
+ evt.preventDefault();
+ if (evt.currentTarget != null && evt.dataTransfer != null) {
+ const source = evt.dataTransfer.getData("source");
+ const pieceName = evt.dataTransfer.getData("pieceName");
+ const piece = source === "spare"
+ ? this.#buildPiece(pieceName)
+ : /** @type {HTMLElement} */(this.#pieces.get(source));
+
+ piece.dataset.square = squareName;
+
+ this.#pieces.get(source)?.remove();
+ this.#pieces.delete(source);
+ this.#pieces.get(squareName)?.remove();
+ this.#pieces.set(squareName, piece);
+
+ this.#position.delete(source);
+ this.#position.set(squareName, pieceName);
+
+ /** @type {HTMLElement} */(evt.currentTarget).appendChild(piece);
+ this.#removeSquareHighlights();
+ }
+ };
+ square.ondragover = (evt) => {
+ evt.preventDefault();
+ if (evt.target != null && evt.dataTransfer != null) {
+ /** @type {HTMLElement} */(evt.target).classList.add(ClassNameLookup.highlight1);
+ evt.dataTransfer.dropEffect = "move";
+ }
+ return false;
+ };
+ square.ondragleave = (evt) => {
+ evt.preventDefault();
+ if (evt.target != null) {
+ /** @type {HTMLElement} */(evt.target).classList.remove(ClassNameLookup.highlight1);
+ }
+ };
+
+ if (this.#config.showNotation) {
+ // alpha notation
+ if ((this.#orientation === 'white' && currentRow === 1) ||
+ (this.#orientation === 'black' && currentRow === 8)) {
+ const notation = document.createElement('div');
+ notation.classList.add(ClassNameLookup.notation, ClassNameLookup.alpha);
+ notation.innerText = alpha[j];
+ square.appendChild(notation);
+ }
+
+ // numeric notation
+ if (j === 0) {
+ const notation = document.createElement('div');
+ notation.classList.add(ClassNameLookup.notation, ClassNameLookup.numeric);
+ notation.innerText = currentRow.toString();
+ square.appendChild(notation);
+ }
+ }
+
+ row.appendChild(square);
+ squareColor = (squareColor === 'white') ? 'black' : 'white';
+
+ this.#squares.set(squareName, square);
+ }
- function stopDefault (evt) {
- evt.preventDefault()
- }
-
- function mousedownSquare (evt) {
- // do nothing if we're not draggable
- if (!config.draggable) return
+ const clearfix = document.createElement('div');
+ clearfix.classList.add(ClassNameLookup.clearfix);
- // 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
+ row.appendChild(clearfix);
+ this.#board.appendChild(row);
- beginDraggingPiece(square, currentPosition[square], evt.pageX, evt.pageY)
+ squareColor = (squareColor === 'white') ? 'black' : 'white';
+
+ if (this.#orientation === 'white') {
+ currentRow = currentRow - 1;
+ } else {
+ currentRow = currentRow + 1;
+ }
}
- 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,
- currentPosition[square],
- 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')
-
- beginDraggingPiece('spare', piece, evt.pageX, evt.pageY)
- }
-
- 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
- )
- }
-
- function mousemoveWindow (evt) {
- if (isDragging) {
- updateDraggedPiece(evt.pageX, evt.pageY)
+ if (this.#config.sparePieces) {
+ this.#topShelf.replaceChildren();
+ this.#bottomShelf.replaceChildren();
+ for (const [pieceName, piece] of this.#sparePieces.entries()) {
+ if (this.#orientation === 'white') {
+ if (pieceName.startsWith('w')) {
+ this.#bottomShelf.appendChild(piece);
+ } else {
+ this.#topShelf.appendChild(piece);
+ }
+ } else {
+ if (pieceName.startsWith('w')) {
+ this.#topShelf.appendChild(piece);
+ } else {
+ this.#bottomShelf.appendChild(piece);
+ }
+ }
}
}
+ }
- var throttledMousemoveWindow = throttle(mousemoveWindow, config.dragThrottleRate)
-
- 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)
- }
-
- 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)
- }
-
- function mouseenterSquare (evt) {
- // do not fire this event if we are dragging a piece
- // NOTE: this should never happen, but it's a safeguard
- if (isDragging) return
-
- // 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
- 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
- if (isDragging) return
-
- // 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
- if (currentPosition.hasOwnProperty(square)) {
- piece = currentPosition[square]
- }
-
- // execute their function
- config.onMouseoutSquare(square, piece, deepCopy(currentPosition), currentOrientation)
- }
-
- // -------------------------------------------------------------------------
- // Initialization
- // -------------------------------------------------------------------------
-
- 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)
- .on('mouseleave', '.' + CSS.square, mouseleaveSquare)
-
- // piece drag
- var $window = $(window)
- $window
- .on('mousemove', throttledMousemoveWindow)
- .on('mouseup', mouseupWindow)
-
- // touch drag pieces
- if (isTouchDevice()) {
- $board.on('touchstart', '.' + CSS.square, touchstartSquare)
- $container.on('touchstart', '.' + CSS.sparePieces + ' .' + CSS.piece, touchstartSparePiece)
- $window
- .on('touchmove', throttledTouchmoveWindow)
- .on('touchend', touchendWindow)
- }
- }
-
- function initDOM () {
- // create unique IDs for all the elements we will create
- 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()
- $('body').append(buildPieceHTML('wP', true, draggedPieceId))
- $draggedPiece = $('#' + draggedPieceId)
-
- // TODO: need to remove this dragged piece element if the board is no
- // longer in the DOM
-
- // get the border size
- boardBorderSize = parseInt($board.css('borderLeftWidth'), 10)
-
- // set the size and draw the board
- widget.resize()
- }
-
- // -------------------------------------------------------------------------
- // Initialization
- // -------------------------------------------------------------------------
-
- setInitialState()
- initDOM()
- addEvents()
-
- // return the widget object
- return widget
- } // end constructor
-
- // TODO: do module exports here
- window['Chessboard'] = constructor
-
- // support legacy ChessBoard name
- window['ChessBoard'] = window['Chessboard']
-
- // expose util functions
- window['Chessboard']['fenToObj'] = fenToObj
- window['Chessboard']['objToFen'] = objToFen
-})() // end anonymous wrapper
-
-/* export Chessboard object if using node or any other CommonJS compatible
- * environment */
-if (typeof exports !== 'undefined') {
- exports.Chessboard = window.Chessboard
+ #removeSquareHighlights() {
+ for (const square of this.#squares.values()) {
+ square.classList.remove(ClassNameLookup.highlight1);
+ square.classList.remove(ClassNameLookup.highlight2);
+ }
+ }
}
+
+export {
+ Chessboard,
+ fenToObj,
+ objToFen,
+ validFen,
+ validPieceCode,
+ validPositionObject,
+ validSquare,
+ START_FEN,
+ START_POSITION,
+};
diff --git a/lib/chessboard.test.js b/lib/chessboard.test.js
new file mode 100644
index 00000000..5a5447ba
--- /dev/null
+++ b/lib/chessboard.test.js
@@ -0,0 +1,61 @@
+/* eslint-env node */
+import assert from 'node:assert';
+import test from 'node:test';
+
+import { objToFen, START_FEN, START_POSITION, validFen, validPieceCode, validPositionObject, validSquare } from './chessboard.js';
+
+test('objToFen', () => {
+ assert.equal(objToFen(START_POSITION), START_FEN);
+ assert.equal(objToFen({}), '8/8/8/8/8/8/8/8');
+ assert.equal(objToFen({ a2: 'wP', b2: 'bP' }), '8/8/8/8/8/8/Pp6/8');
+});
+
+test('validSquare', () => {
+ assert.ok(validSquare('a1'));
+ assert.ok(validSquare('e2'));
+ assert.ok(!validSquare('D2'));
+ assert.ok(!validSquare('g9'));
+ assert.ok(!validSquare('a'));
+ assert.ok(!validSquare(true));
+ assert.ok(!validSquare(null));
+ assert.ok(!validSquare({}));
+});
+
+test('validPieceCode', () => {
+ assert.ok(validPieceCode('bP'));
+ assert.ok(validPieceCode('bK'));
+ assert.ok(validPieceCode('wK'));
+ assert.ok(validPieceCode('wR'));
+ assert.ok(!validPieceCode('WR'));
+ assert.ok(!validPieceCode('Wr'));
+ assert.ok(!validPieceCode('a'));
+ assert.ok(!validPieceCode(true));
+ assert.ok(!validPieceCode(null));
+ assert.ok(!validPieceCode({}));
+});
+
+test('validFen', () => {
+ assert.ok(validFen(START_FEN));
+ assert.ok(validFen('8/8/8/8/8/8/8/8'));
+ assert.ok(validFen('r1bqkbnr/pppp1ppp/2n5/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R'));
+ assert.ok(validFen('3r3r/1p4pp/2nb1k2/pP3p2/8/PB2PN2/p4PPP/R4RK1 b - - 0 1'));
+ assert.ok(!validFen('3r3z/1p4pp/2nb1k2/pP3p2/8/PB2PN2/p4PPP/R4RK1 b - - 0 1'));
+ assert.ok(!validFen('anbqkbnr/8/8/8/8/8/PPPPPPPP/8'));
+ assert.ok(!validFen('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/'));
+ assert.ok(!validFen('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBN'));
+ assert.ok(!validFen('888888/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR'));
+ assert.ok(!validFen('888888/pppppppp/74/8/8/8/PPPPPPPP/RNBQKBNR'));
+ assert.ok(!validFen(''));
+});
+
+test('validPositionObject', () => {
+ assert.ok(validPositionObject(START_POSITION));
+ assert.ok(validPositionObject({}));
+ assert.ok(validPositionObject({ e2: 'wP' }));
+ assert.ok(validPositionObject({ e2: 'wP', d2: 'wP' }));
+ assert.ok(!validPositionObject({ e2: 'BP' }));
+ assert.ok(!validPositionObject({ y2: 'wP' }));
+ assert.ok(!validPositionObject(null));
+ assert.ok(!validPositionObject('start'));
+ assert.ok(!validPositionObject(START_FEN));
+});
diff --git a/lib/pieces.svg.js b/lib/pieces.svg.js
new file mode 100644
index 00000000..fe09cdf1
--- /dev/null
+++ b/lib/pieces.svg.js
@@ -0,0 +1,36 @@
+/**
+ * Piece SVGs courtesy of Wikimedia Commons
+ * @see https://commons.wikimedia.org/wiki/Category:SVG_chess_pieces
+ */
+
+/**
+ * White
+ */
+
+export const wK = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='45' height='45'%3E%3Cg fill='none' fill-rule='evenodd' stroke='%23000' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5'%3E%3Cpath stroke-linejoin='miter' d='M22.5 11.63V6M20 8h5'/%3E%3Cpath fill='%23fff' stroke-linecap='butt' stroke-linejoin='miter' d='M22.5 25s4.5-7.5 3-10.5c0 0-1-2.5-3-2.5s-3 2.5-3 2.5c-1.5 3 3 10.5 3 10.5'/%3E%3Cpath fill='%23fff' d='M12.5 37c5.5 3.5 14.5 3.5 20 0v-7s9-4.5 6-10.5c-4-6.5-13.5-3.5-16 4V27v-3.5c-2.5-7.5-12-10.5-16-4-3 6 6 10.5 6 10.5v7'/%3E%3Cpath d='M12.5 30c5.5-3 14.5-3 20 0m-20 3.5c5.5-3 14.5-3 20 0m-20 3.5c5.5-3 14.5-3 20 0'/%3E%3C/g%3E%3C/svg%3E`;
+
+export const wQ = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='45' height='45'%3E%3Cg style='fill:%23ffffff;stroke:%23000000;stroke-width:1.5;stroke-linejoin:round'%3E%3Cpath d='M 9,26 C 17.5,24.5 30,24.5 36,26 L 38.5,13.5 L 31,25 L 30.7,10.9 L 25.5,24.5 L 22.5,10 L 19.5,24.5 L 14.3,10.9 L 14,25 L 6.5,13.5 L 9,26 z'/%3E%3Cpath d='M 9,26 C 9,28 10.5,28 11.5,30 C 12.5,31.5 12.5,31 12,33.5 C 10.5,34.5 11,36 11,36 C 9.5,37.5 11,38.5 11,38.5 C 17.5,39.5 27.5,39.5 34,38.5 C 34,38.5 35.5,37.5 34,36 C 34,36 34.5,34.5 33,33.5 C 32.5,31 32.5,31.5 33.5,30 C 34.5,28 36,28 36,26 C 27.5,24.5 17.5,24.5 9,26 z'/%3E%3Cpath d='M 11.5,30 C 15,29 30,29 33.5,30' style='fill:none'/%3E%3Cpath d='M 12,33.5 C 18,32.5 27,32.5 33,33.5' style='fill:none'/%3E%3Ccircle cx='6' cy='12' r='2' /%3E%3Ccircle cx='14' cy='9' r='2' /%3E%3Ccircle cx='22.5' cy='8' r='2' /%3E%3Ccircle cx='31' cy='9' r='2' /%3E%3Ccircle cx='39' cy='12' r='2' /%3E%3C/g%3E%3C/svg%3E`;
+
+export const wB = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='45' height='45'%3E%3Cg style='opacity:1; fill:none; fill-rule:evenodd; fill-opacity:1; stroke:%23000000; stroke-width:1.5; stroke-linecap:round; stroke-linejoin:round; stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1;' transform='translate(0,0.6)'%3E%3Cg style='fill:%23ffffff; stroke:%23000000; stroke-linecap:butt;'%3E%3Cpath d='M 9,36 C 12.39,35.03 19.11,36.43 22.5,34 C 25.89,36.43 32.61,35.03 36,36 C 36,36 37.65,36.54 39,38 C 38.32,38.97 37.35,38.99 36,38.5 C 32.61,37.53 25.89,38.96 22.5,37.5 C 19.11,38.96 12.39,37.53 9,38.5 C 7.65,38.99 6.68,38.97 6,38 C 7.35,36.54 9,36 9,36 z'/%3E%3Cpath d='M 15,32 C 17.5,34.5 27.5,34.5 30,32 C 30.5,30.5 30,30 30,30 C 30,27.5 27.5,26 27.5,26 C 33,24.5 33.5,14.5 22.5,10.5 C 11.5,14.5 12,24.5 17.5,26 C 17.5,26 15,27.5 15,30 C 15,30 14.5,30.5 15,32 z'/%3E%3Cpath d='M 25 8 A 2.5 2.5 0 1 1 20,8 A 2.5 2.5 0 1 1 25 8 z'/%3E%3C/g%3E%3Cpath d='M 17.5,26 L 27.5,26 M 15,30 L 30,30 M 22.5,15.5 L 22.5,20.5 M 20,18 L 25,18' style='fill:none; stroke:%23000000; stroke-linejoin:miter;'/%3E%3C/g%3E%3C/svg%3E`;
+
+export const wN = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='45' height='45'%3E%3Cg style='opacity:1; fill:none; fill-opacity:1; fill-rule:evenodd; stroke:%23000000; stroke-width:1.5; stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1;' transform='translate(0,0.3)'%3E%3Cpath d='M 22,10 C 32.5,11 38.5,18 38,39 L 15,39 C 15,30 25,32.5 23,18' style='fill:%23ffffff; stroke:%23000000;' /%3E%3Cpath d='M 24,18 C 24.38,20.91 18.45,25.37 16,27 C 13,29 13.18,31.34 11,31 C 9.958,30.06 12.41,27.96 11,28 C 10,28 11.19,29.23 10,30 C 9,30 5.997,31 6,26 C 6,24 12,14 12,14 C 12,14 13.89,12.1 14,10.5 C 13.27,9.506 13.5,8.5 13.5,7.5 C 14.5,6.5 16.5,10 16.5,10 L 18.5,10 C 18.5,10 19.28,8.008 21,7 C 22,7 22,10 22,10' style='fill:%23ffffff; stroke:%23000000;' /%3E%3Cpath d='M 9.5 25.5 A 0.5 0.5 0 1 1 8.5,25.5 A 0.5 0.5 0 1 1 9.5 25.5 z' style='fill:%23000000; stroke:%23000000;' /%3E%3Cpath d='M 15 15.5 A 0.5 1.5 0 1 1 14,15.5 A 0.5 1.5 0 1 1 15 15.5 z' transform='matrix(0.866,0.5,-0.5,0.866,9.693,-5.173)' style='fill:%23000000; stroke:%23000000;' /%3E%3C/g%3E%3C/svg%3E`;
+
+export const wR = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='45' height='45'%3E%3Cg style='opacity:1; fill:%23ffffff; fill-opacity:1; fill-rule:evenodd; stroke:%23000000; stroke-width:1.5; stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1;' transform='translate(0,0.3)'%3E%3Cpath d='M 9,39 L 36,39 L 36,36 L 9,36 L 9,39 z ' style='stroke-linecap:butt;' /%3E%3Cpath d='M 12,36 L 12,32 L 33,32 L 33,36 L 12,36 z ' style='stroke-linecap:butt;' /%3E%3Cpath d='M 11,14 L 11,9 L 15,9 L 15,11 L 20,11 L 20,9 L 25,9 L 25,11 L 30,11 L 30,9 L 34,9 L 34,14' style='stroke-linecap:butt;' /%3E%3Cpath d='M 34,14 L 31,17 L 14,17 L 11,14' /%3E%3Cpath d='M 31,17 L 31,29.5 L 14,29.5 L 14,17' style='stroke-linecap:butt; stroke-linejoin:miter;' /%3E%3Cpath d='M 31,29.5 L 32.5,32 L 12.5,32 L 14,29.5' /%3E%3Cpath d='M 11,14 L 34,14' style='fill:none; stroke:%23000000; stroke-linejoin:miter;' /%3E%3C/g%3E%3C/svg%3E`;
+
+export const wP = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='45' height='45'%3E%3Cpath d='m 22.5,9 c -2.21,0 -4,1.79 -4,4 0,0.89 0.29,1.71 0.78,2.38 C 17.33,16.5 16,18.59 16,21 c 0,2.03 0.94,3.84 2.41,5.03 C 15.41,27.09 11,31.58 11,39.5 H 34 C 34,31.58 29.59,27.09 26.59,26.03 28.06,24.84 29,23.03 29,21 29,18.59 27.67,16.5 25.72,15.38 26.21,14.71 26.5,13.89 26.5,13 c 0,-2.21 -1.79,-4 -4,-4 z' style='opacity:1; fill:%23ffffff; fill-opacity:1; fill-rule:nonzero; stroke:%23000000; stroke-width:1.5; stroke-linecap:round; stroke-linejoin:miter; stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1;'/%3E%3C/svg%3E`;
+
+/**
+ * Black
+ */
+
+export const bK = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='45' height='45'%3E%3Cg style='fill:none; fill-opacity:1; fill-rule:evenodd; stroke:%23000000; stroke-width:1.5; stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1;'%3E%3Cpath d='M 22.5,11.63 L 22.5,6' style='fill:none; stroke:%23000000; stroke-linejoin:miter;' id='path6570'/%3E%3Cpath d='M 22.5,25 C 22.5,25 27,17.5 25.5,14.5 C 25.5,14.5 24.5,12 22.5,12 C 20.5,12 19.5,14.5 19.5,14.5 C 18,17.5 22.5,25 22.5,25' style='fill:%23000000;fill-opacity:1; stroke-linecap:butt; stroke-linejoin:miter;'/%3E%3Cpath d='M 12.5,37 C 18,40.5 27,40.5 32.5,37 L 32.5,30 C 32.5,30 41.5,25.5 38.5,19.5 C 34.5,13 25,16 22.5,23.5 L 22.5,27 L 22.5,23.5 C 20,16 10.5,13 6.5,19.5 C 3.5,25.5 12.5,30 12.5,30 L 12.5,37' style='fill:%23000000; stroke:%23000000;'/%3E%3Cpath d='M 20,8 L 25,8' style='fill:none; stroke:%23000000; stroke-linejoin:miter;'/%3E%3Cpath d='M 32,29.5 C 32,29.5 40.5,25.5 38.03,19.85 C 34.15,14 25,18 22.5,24.5 L 22.5,26.6 L 22.5,24.5 C 20,18 10.85,14 6.97,19.85 C 4.5,25.5 13,29.5 13,29.5' style='fill:none; stroke:%23ffffff;'/%3E%3Cpath d='M 12.5,30 C 18,27 27,27 32.5,30 M 12.5,33.5 C 18,30.5 27,30.5 32.5,33.5 M 12.5,37 C 18,34 27,34 32.5,37' style='fill:none; stroke:%23ffffff;'/%3E%3C/g%3E%3C/svg%3E`;
+
+export const bQ = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='45' height='45'%3E%3Cg style='fill:%23000000;stroke:%23000000;stroke-width:1.5; stroke-linecap:round;stroke-linejoin:round'%3E%3Cpath d='M 9,26 C 17.5,24.5 30,24.5 36,26 L 38.5,13.5 L 31,25 L 30.7,10.9 L 25.5,24.5 L 22.5,10 L 19.5,24.5 L 14.3,10.9 L 14,25 L 6.5,13.5 L 9,26 z' style='stroke-linecap:butt;fill:%23000000' /%3E%3Cpath d='m 9,26 c 0,2 1.5,2 2.5,4 1,1.5 1,1 0.5,3.5 -1.5,1 -1,2.5 -1,2.5 -1.5,1.5 0,2.5 0,2.5 6.5,1 16.5,1 23,0 0,0 1.5,-1 0,-2.5 0,0 0.5,-1.5 -1,-2.5 -0.5,-2.5 -0.5,-2 0.5,-3.5 1,-2 2.5,-2 2.5,-4 -8.5,-1.5 -18.5,-1.5 -27,0 z' /%3E%3Cpath d='M 11.5,30 C 15,29 30,29 33.5,30' /%3E%3Cpath d='m 12,33.5 c 6,-1 15,-1 21,0' /%3E%3Ccircle cx='6' cy='12' r='2' /%3E%3Ccircle cx='14' cy='9' r='2' /%3E%3Ccircle cx='22.5' cy='8' r='2' /%3E%3Ccircle cx='31' cy='9' r='2' /%3E%3Ccircle cx='39' cy='12' r='2' /%3E%3Cpath d='M 11,38.5 A 35,35 1 0 0 34,38.5' style='fill:none; stroke:%23000000;stroke-linecap:butt;' /%3E%3Cg style='fill:none; stroke:%23ffffff;'%3E%3Cpath d='M 11,29 A 35,35 1 0 1 34,29' /%3E%3Cpath d='M 12.5,31.5 L 32.5,31.5' /%3E%3Cpath d='M 11.5,34.5 A 35,35 1 0 0 33.5,34.5' /%3E%3Cpath d='M 10.5,37.5 A 35,35 1 0 0 34.5,37.5' /%3E%3C/g%3E%3C/g%3E%3C/svg%3E`;
+
+export const bB = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='45' height='45'%3E%3Cg style='opacity:1; fill:none; fill-rule:evenodd; fill-opacity:1; stroke:%23000000; stroke-width:1.5; stroke-linecap:round; stroke-linejoin:round; stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1;' transform='translate(0,0.6)'%3E%3Cg style='fill:%23000000; stroke:%23000000; stroke-linecap:butt;'%3E%3Cpath d='M 9,36 C 12.39,35.03 19.11,36.43 22.5,34 C 25.89,36.43 32.61,35.03 36,36 C 36,36 37.65,36.54 39,38 C 38.32,38.97 37.35,38.99 36,38.5 C 32.61,37.53 25.89,38.96 22.5,37.5 C 19.11,38.96 12.39,37.53 9,38.5 C 7.65,38.99 6.68,38.97 6,38 C 7.35,36.54 9,36 9,36 z'/%3E%3Cpath d='M 15,32 C 17.5,34.5 27.5,34.5 30,32 C 30.5,30.5 30,30 30,30 C 30,27.5 27.5,26 27.5,26 C 33,24.5 33.5,14.5 22.5,10.5 C 11.5,14.5 12,24.5 17.5,26 C 17.5,26 15,27.5 15,30 C 15,30 14.5,30.5 15,32 z'/%3E%3Cpath d='M 25 8 A 2.5 2.5 0 1 1 20,8 A 2.5 2.5 0 1 1 25 8 z'/%3E%3C/g%3E%3Cpath d='M 17.5,26 L 27.5,26 M 15,30 L 30,30 M 22.5,15.5 L 22.5,20.5 M 20,18 L 25,18' style='fill:none; stroke:%23ffffff; stroke-linejoin:miter;'/%3E%3C/g%3E%3C/svg%3E`;
+
+export const bN = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='45' height='45'%3E%3Cg style='opacity:1; fill:none; fill-opacity:1; fill-rule:evenodd; stroke:%23000000; stroke-width:1.5; stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1;' transform='translate(0,0.3)'%3E%3Cpath d='M 22,10 C 32.5,11 38.5,18 38,39 L 15,39 C 15,30 25,32.5 23,18' style='fill:%23000000; stroke:%23000000;' /%3E%3Cpath d='M 24,18 C 24.38,20.91 18.45,25.37 16,27 C 13,29 13.18,31.34 11,31 C 9.958,30.06 12.41,27.96 11,28 C 10,28 11.19,29.23 10,30 C 9,30 5.997,31 6,26 C 6,24 12,14 12,14 C 12,14 13.89,12.1 14,10.5 C 13.27,9.506 13.5,8.5 13.5,7.5 C 14.5,6.5 16.5,10 16.5,10 L 18.5,10 C 18.5,10 19.28,8.008 21,7 C 22,7 22,10 22,10' style='fill:%23000000; stroke:%23000000;' /%3E%3Cpath d='M 9.5 25.5 A 0.5 0.5 0 1 1 8.5,25.5 A 0.5 0.5 0 1 1 9.5 25.5 z' style='fill:%23ffffff; stroke:%23ffffff;' /%3E%3Cpath d='M 15 15.5 A 0.5 1.5 0 1 1 14,15.5 A 0.5 1.5 0 1 1 15 15.5 z' transform='matrix(0.866,0.5,-0.5,0.866,9.693,-5.173)' style='fill:%23ffffff; stroke:%23ffffff;' /%3E%3Cpath d='M 24.55,10.4 L 24.1,11.85 L 24.6,12 C 27.75,13 30.25,14.49 32.5,18.75 C 34.75,23.01 35.75,29.06 35.25,39 L 35.2,39.5 L 37.45,39.5 L 37.5,39 C 38,28.94 36.62,22.15 34.25,17.66 C 31.88,13.17 28.46,11.02 25.06,10.5 L 24.55,10.4 z ' style='fill:%23ffffff; stroke:none;' /%3E%3C/g%3E%3C/svg%3E`;
+
+export const bR = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='45' height='45'%3E%3Cg style='opacity:1; fill:%23000000; fill-opacity:1; fill-rule:evenodd; stroke:%23000000; stroke-width:1.5; stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1;' transform='translate(0,0.3)'%3E%3Cpath d='M 9,39 L 36,39 L 36,36 L 9,36 L 9,39 z ' style='stroke-linecap:butt;' /%3E%3Cpath d='M 12.5,32 L 14,29.5 L 31,29.5 L 32.5,32 L 12.5,32 z ' style='stroke-linecap:butt;' /%3E%3Cpath d='M 12,36 L 12,32 L 33,32 L 33,36 L 12,36 z ' style='stroke-linecap:butt;' /%3E%3Cpath d='M 14,29.5 L 14,16.5 L 31,16.5 L 31,29.5 L 14,29.5 z ' style='stroke-linecap:butt;stroke-linejoin:miter;' /%3E%3Cpath d='M 14,16.5 L 11,14 L 34,14 L 31,16.5 L 14,16.5 z ' style='stroke-linecap:butt;' /%3E%3Cpath d='M 11,14 L 11,9 L 15,9 L 15,11 L 20,11 L 20,9 L 25,9 L 25,11 L 30,11 L 30,9 L 34,9 L 34,14 L 11,14 z ' style='stroke-linecap:butt;' /%3E%3Cpath d='M 12,35.5 L 33,35.5 L 33,35.5' style='fill:none; stroke:%23ffffff; stroke-width:1; stroke-linejoin:miter;' /%3E%3Cpath d='M 13,31.5 L 32,31.5' style='fill:none; stroke:%23ffffff; stroke-width:1; stroke-linejoin:miter;' /%3E%3Cpath d='M 14,29.5 L 31,29.5' style='fill:none; stroke:%23ffffff; stroke-width:1; stroke-linejoin:miter;' /%3E%3Cpath d='M 14,16.5 L 31,16.5' style='fill:none; stroke:%23ffffff; stroke-width:1; stroke-linejoin:miter;' /%3E%3Cpath d='M 11,14 L 34,14' style='fill:none; stroke:%23ffffff; stroke-width:1; stroke-linejoin:miter;' /%3E%3C/g%3E%3C/svg%3E`;
+
+export const bP = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='45' height='45'%3E%3Cpath d='m 22.5,9 c -2.21,0 -4,1.79 -4,4 0,0.89 0.29,1.71 0.78,2.38 C 17.33,16.5 16,18.59 16,21 c 0,2.03 0.94,3.84 2.41,5.03 C 15.41,27.09 11,31.58 11,39.5 H 34 C 34,31.58 29.59,27.09 26.59,26.03 28.06,24.84 29,23.03 29,21 29,18.59 27.67,16.5 25.72,15.38 26.21,14.71 26.5,13.89 26.5,13 c 0,-2.21 -1.79,-4 -4,-4 z' style='opacity:1; fill:%23000000; fill-opacity:1; fill-rule:nonzero; stroke:%23000000; stroke-width:1.5; stroke-linecap:round; stroke-linejoin:miter; stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1;'/%3E%3C/svg%3E`;
diff --git a/package.json b/package.json
index bf071a30..4360b940 100644
--- a/package.json
+++ b/package.json
@@ -4,26 +4,41 @@
"description": "JavaScript chessboard widget",
"homepage": "https://chessboardjs.com",
"license": "MIT",
+ "type": "module",
"version": "1.0.0",
"repository": {
"type": "git",
"url": "git://github.com/oakmac/chessboardjs.git"
},
- "files": ["dist/"],
- "dependencies": {
- "jquery": ">=3.4.1"
- },
+ "files": [
+ "dist/"
+ ],
"devDependencies": {
- "csso": "3.5.1",
- "fs-plus": "3.1.1",
+ "@types/node": "18.7.23",
+ "eslint": "8.24.0",
+ "gulp": "4.0.2",
"kidif": "1.1.0",
"mustache": "2.3.0",
- "standard": "10.0.2",
- "uglify-js": "3.6.0"
+ "rollup": "2.79.1",
+ "rollup-plugin-terser": "7.0.2",
+ "sass": "1.55.0"
+ },
+ "resolutions": {
+ "chokidar": "3.5.3"
},
"scripts": {
- "build": "standard lib/chessboard.js && node scripts/build.js",
- "standard": "standard --fix lib/*.js website/js/*.js",
- "website": "node scripts/website.js"
+ "lint": "eslint Gulpfile.js lib website/js/examples.js",
+ "build": "npm run lint -- --fix && gulp",
+ "start": "gulp watch",
+ "test": "node --test lib"
+ },
+ "eslintConfig": {
+ "extends": "eslint:recommended",
+ "env": {
+ "es2022": true
+ },
+ "parserOptions": {
+ "sourceType": "module"
+ }
}
}
diff --git a/scripts/build.js b/scripts/build.js
deleted file mode 100644
index 798bec10..00000000
--- a/scripts/build.js
+++ /dev/null
@@ -1,46 +0,0 @@
-// -----------------------------------------------------------------------------
-// This file creates a build in the dist/ folder
-// -----------------------------------------------------------------------------
-
-// libraries
-const fs = require('fs-plus')
-const csso = require('csso')
-const uglify = require('uglify-js')
-
-const encoding = {encoding: 'utf8'}
-
-const package = JSON.parse(fs.readFileSync('package.json', encoding))
-const version = package.version
-const year = new Date().getFullYear()
-const cssSrc = fs.readFileSync('lib/chessboard.css', encoding)
- .replace('@VERSION', version)
-const jsSrc = fs.readFileSync('lib/chessboard.js', encoding)
- .replace('@VERSION', version)
- .replace('RUN_ASSERTS = true', 'RUN_ASSERTS = false')
-
-// TODO: need to remove the RUN_ASSERTS calls from the non-minified file
-
-const minifiedCSS = csso.minify(cssSrc).css
-const uglifyResult = uglify.minify(jsSrc)
-const minifiedJS = uglifyResult.code
-
-// quick sanity checks
-console.assert(!uglifyResult.error, 'error minifying JS!')
-console.assert(typeof minifiedCSS === 'string' && minifiedCSS !== '', 'error minifying CSS!')
-
-// add license to the top of minified files
-const minifiedJSWithBanner = banner() + minifiedJS
-
-// create a fresh dist/ folder
-fs.removeSync('dist')
-fs.makeTreeSync('dist')
-
-// copy lib files to dist/
-fs.writeFileSync('dist/chessboard-' + version + '.css', cssSrc, encoding)
-fs.writeFileSync('dist/chessboard-' + version + '.min.css', minifiedCSS, encoding)
-fs.writeFileSync('dist/chessboard-' + version + '.js', jsSrc, encoding)
-fs.writeFileSync('dist/chessboard-' + version + '.min.js', minifiedJSWithBanner, encoding)
-
-function banner () {
- return '/*! chessboard.js v' + version + ' | (c) ' + year + ' Chris Oakman | MIT License chessboardjs.com/license */\n'
-}
diff --git a/scripts/rename.js b/scripts/rename.js
deleted file mode 100644
index c6a99385..00000000
--- a/scripts/rename.js
+++ /dev/null
@@ -1,22 +0,0 @@
-// TODO: this is unfinished / not working
-
-var fs = require('fs')
-var glob = require('glob')
-
-var exampleFiles = glob.sync('examples/*.example')
-
-exampleFiles.forEach(processFile)
-
-function processFile (f) {
- var fileContents = fs.readFileSync(f, {encoding:'utf8'})
- var lines = fileContents.split('\n')
-
- lines = lines.map(processLine)
-
- fs.writeFileSync(f, lines.join('\n'))
-}
-
-function processLine (line) {
- line = line.replace('docs#', 'docs.html#')
- return line
-}
diff --git a/scripts/website.js b/scripts/website.js
deleted file mode 100755
index 1b107fa1..00000000
--- a/scripts/website.js
+++ /dev/null
@@ -1,406 +0,0 @@
-#! /usr/bin/env node
-
-// -----------------------------------------------------------------------------
-// This file builds the contents of the website/ folder.
-// -----------------------------------------------------------------------------
-
-// libraries
-const fs = require('fs-plus')
-const kidif = require('kidif')
-const mustache = require('mustache')
-const docs = require('../data/docs.json')
-
-const encoding = {encoding: 'utf8'}
-
-// toggle development version
-const useDevFile = false
-const jsCDNLink = ''
-const cssCDNLink = 'https://unpkg.com/@chrisoakman/chessboardjs@1.0.0/dist/chessboard-1.0.0.min.css'
-
-let chessboardJsScript = jsCDNLink
-if (useDevFile) {
- chessboardJsScript = ''
-}
-
-// grab some mustache templates
-const docsTemplate = fs.readFileSync('templates/docs.mustache', encoding)
-const downloadTemplate = fs.readFileSync('templates/download.mustache', encoding)
-const examplesTemplate = fs.readFileSync('templates/examples.mustache', encoding)
-const homepageTemplate = fs.readFileSync('templates/homepage.mustache', encoding)
-const singleExampleTemplate = fs.readFileSync('templates/single-example.mustache', encoding)
-const licensePageTemplate = fs.readFileSync('templates/license.mustache', encoding)
-const headTemplate = fs.readFileSync('templates/_head.mustache', encoding)
-const headerTemplate = fs.readFileSync('templates/_header.mustache', encoding)
-const footerTemplate = fs.readFileSync('templates/_footer.mustache', encoding)
-
-const latestChessboardJS = fs.readFileSync('lib/chessboard.js', encoding)
-const latestChessboardCSS = fs.readFileSync('lib/chessboard.css', encoding)
-
-// grab the examples
-const examplesArr = kidif('examples/*.example')
-console.assert(examplesArr, 'Could not load the Example files')
-console.assert(examplesArr.length > 1, 'Zero examples loaded')
-
-const examplesObj = examplesArr.reduce(function (examplesObj, example, idx) {
- examplesObj[example.id] = example
- return examplesObj
-}, {})
-
-const examplesGroups = [
- {
- name: 'Basic Usage',
- examples: [1000, 1001, 1002, 1003, 1004]
- },
- {
- name: 'Config',
- examples: [2000, 2044, 2063, 2001, 2002, 2003, 2082, 2004, 2030, 2005, 2006]
- },
- {
- name: 'Methods',
- examples: [3000, 3001, 3002, 3003, 3004, 3005, 3006, 3007]
- },
- {
- name: 'Events',
- examples: [4000, 4001, 4002, 4003, 4004, 4005, 4006, 4011, 4012]
- },
- {
- name: 'Integration',
- examples: [5000, 5001, 5002, 5003, 5004, 5005]
- }
-]
-
-const homepageExample1 = ''
-
-const homepageExample2 = `
-var board2 = Chessboard('board2', {
- draggable: true,
- dropOffBoard: 'trash',
- sparePieces: true
-})
-
-$('#startBtn').on('click', board2.start)
-$('#clearBtn').on('click', board2.clear)`.trim()
-
-function writeSrcFiles () {
- fs.writeFileSync('website/js/chessboard.js', latestChessboardJS, encoding)
- fs.writeFileSync('website/css/chessboard.css', latestChessboardCSS, encoding)
-}
-
-function writeHomepage () {
- const headHTML = mustache.render(headTemplate, {pageTitle: 'Homepage'})
-
- const html = mustache.render(homepageTemplate, {
- chessboardJsScript: chessboardJsScript,
- example2: homepageExample2,
- footer: footerTemplate,
- head: headHTML
- })
- fs.writeFileSync('website/index.html', html, encoding)
-}
-
-function writeExamplesPage () {
- const headHTML = mustache.render(headTemplate, {pageTitle: 'Examples'})
- const headerHTML = mustache.render(headerTemplate, {examplesActive: true})
-
- const html = mustache.render(examplesTemplate, {
- chessboardJsScript: chessboardJsScript,
- examplesJavaScript: buildExamplesJS(),
- footer: footerTemplate,
- head: headHTML,
- header: headerHTML,
- nav: buildExamplesNavHTML()
- })
- fs.writeFileSync('website/examples.html', html, encoding)
-}
-
-const configTableRowsHTML = docs.config.reduce(function (html, itm) {
- if (isString(itm)) return html
- return html + buildConfigDocsTableRowHTML('config', itm)
-}, '')
-
-const methodTableRowsHTML = docs.methods.reduce(function (html, itm) {
- if (isString(itm)) return html
- return html + buildMethodRowHTML(itm)
-}, '')
-
-const errorRowsHTML = docs.errors.reduce(function (html, itm) {
- if (isString(itm)) return html
- return html + buildErrorRowHTML(itm)
-}, '')
-
-function isIntegrationExample (example) {
- return (example.id + '').startsWith('5')
-}
-
-function writeSingleExamplePage (example) {
- if (isIntegrationExample(example)) {
- example.includeChessJS = true
- }
- example.chessboardJsScript = chessboardJsScript
- const html = mustache.render(singleExampleTemplate, example)
- fs.writeFileSync('website/examples/' + example.id + '.html', html, encoding)
-}
-
-function writeSingleExamplesPages () {
- examplesArr.forEach(writeSingleExamplePage)
-}
-
-function writeDocsPage () {
- const headHTML = mustache.render(headTemplate, {pageTitle: 'Documentation'})
- const headerHTML = mustache.render(headerTemplate, {docsActive: true})
-
- const html = mustache.render(docsTemplate, {
- configTableRows: configTableRowsHTML,
- errorRows: errorRowsHTML,
- footer: footerTemplate,
- head: headHTML,
- header: headerHTML,
- methodTableRows: methodTableRowsHTML
- })
- fs.writeFileSync('website/docs.html', html, encoding)
-}
-
-function writeDownloadPage () {
- const headHTML = mustache.render(headTemplate, {pageTitle: 'Download'})
- const headerHTML = mustache.render(headerTemplate, {downloadActive: true})
-
- const html = mustache.render(downloadTemplate, {
- footer: footerTemplate,
- head: headHTML,
- header: headerHTML
- })
- fs.writeFileSync('website/download.html', html, encoding)
-}
-
-function writeLicensePage () {
- const html = mustache.render(licensePageTemplate)
- fs.writeFileSync('website/license.html', html, encoding)
-}
-
-function writeWebsite () {
- writeSrcFiles()
- writeHomepage()
- writeExamplesPage()
- writeSingleExamplesPages()
- writeDocsPage()
- writeDownloadPage()
- writeLicensePage()
-}
-
-writeWebsite()
-
-// -----------------------------------------------------------------------------
-// HTML
-// -----------------------------------------------------------------------------
-
-function htmlEscape (str) {
- return (str + '')
- .replace(/&/g, '&')
- .replace(//g, '>')
- .replace(/"/g, '"')
- .replace(/'/g, ''')
- .replace(/\//g, '/')
- .replace(/`/g, '`')
-}
-
-function buildExampleGroupHTML (idx, groupName, examplesInGroup) {
- const groupNum = idx + 1
- let html = '' + groupName + '
' +
- ''
-
- return html
-}
-
-function buildExamplesNavHTML () {
- let html = ''
- examplesGroups.forEach(function (group, idx) {
- html += buildExampleGroupHTML(idx, group.name, group.examples)
- })
- return html
-}
-
-function buildExamplesJS () {
- let txt = 'window.CHESSBOARD_EXAMPLES = {}\n\n'
-
- examplesArr.forEach(function (ex) {
- txt += 'CHESSBOARD_EXAMPLES["' + ex.id + '"] = {\n' +
- ' description: ' + JSON.stringify(ex.description) + ',\n' +
- ' html: ' + JSON.stringify(ex.html) + ',\n' +
- ' name: ' + JSON.stringify(ex.name) + ',\n' +
- ' jsStr: ' + JSON.stringify(ex.js) + ',\n' +
- ' jsFn: function () {\n' + ex.js + '\n }\n' +
- '};\n\n'
- })
-
- return txt
-}
-
-function buildConfigDocsTableRowHTML (propType, prop) {
- let html = ''
-
- // table row
- html += ''
-
- // property and type
- html += '' + buildPropertyAndTypeHTML(propType, prop.name, prop.type) + ' '
-
- // default
- html += '' + buildDefaultHTML(prop.default) + '
'
-
- // description
- html += '' + buildDescriptionHTML(prop.desc) + ' '
-
- // examples
- html += '' + buildExamplesCellHTML(prop.examples) + ' '
-
- html += ' '
-
- return html
-}
-
-function buildMethodRowHTML (method) {
- const nameNoParens = method.name.replace(/\(.+$/, '')
-
- let html = ''
-
- // table row
- if (method.noId) {
- html += ''
- } else {
- html += ' '
- }
-
- // name
- html += ' '
-
- // args
- if (method.args) {
- html += ''
- method.args.forEach(function (arg) {
- html += '' + arg[1] + '
'
- })
- html += ' '
- } else {
- html += 'none '
- }
-
- // description
- html += '' + buildDescriptionHTML(method.desc) + ' '
-
- // examples
- html += '' + buildExamplesCellHTML(method.examples) + ' '
-
- html += ' '
-
- return html
-}
-
-function buildPropertyAndTypeHTML (section, name, type) {
- let html = '' +
- '' + buildTypeHTML(type) + '
'
- return html
-}
-
-function buildTypeHTML (type) {
- if (!Array.isArray(type)) {
- type = [type]
- }
-
- let html = ''
- for (var i = 0; i < type.length; i++) {
- if (i !== 0) {
- html += ' or
'
- }
- html += type[i]
- }
-
- return html
-}
-
-function buildRequiredHTML (req) {
- if (!req) return 'no'
- if (req === true) return 'yes'
- return req
-}
-
-function buildDescriptionHTML (desc) {
- if (!Array.isArray(desc)) {
- desc = [desc]
- }
-
- let html = ''
- desc.forEach(function (d) {
- html += '' + d + '
'
- })
-
- return html
-}
-
-function buildDefaultHTML (defaultValue) {
- if (!defaultValue) {
- return 'n/a'
- }
- return defaultValue
-}
-
-function buildExamplesCellHTML (examplesIds) {
- if (!Array.isArray(examplesIds)) {
- examplesIds = [examplesIds]
- }
-
- let html = ''
- examplesIds.forEach(function (exampleId) {
- var example = examplesObj[exampleId]
- if (!example) return
- html += ''
- })
-
- return html
-}
-
-function buildErrorRowHTML (error) {
- let html = ''
-
- // table row
- html += ''
-
- // id
- html += '' +
- ' '
-
- // desc
- html += '' + error.desc + '
'
-
- // more information
- if (error.fix) {
- if (!Array.isArray(error.fix)) {
- error.fix = [error.fix]
- }
-
- html += ''
- error.fix.forEach(function (p) {
- html += '' + p + '
'
- })
- html += ' '
- } else {
- html += 'n/a '
- }
-
- html += ' '
-
- return html
-}
-
-function isString (s) {
- return typeof s === 'string'
-}
diff --git a/templates/_head.mustache b/templates/_head.mustache
index 07511c7e..e3c04dd9 100644
--- a/templates/_head.mustache
+++ b/templates/_head.mustache
@@ -7,9 +7,8 @@
-
+
-
-
+
diff --git a/templates/docs.mustache b/templates/docs.mustache
index 39c0ce23..458b57a4 100644
--- a/templates/docs.mustache
+++ b/templates/docs.mustache
@@ -92,30 +92,27 @@
{{{footer}}}
-
-