├── .gitignore ├── Gruntfile.js ├── LICENSE ├── README.md ├── bower.json ├── css ├── chessboard.css └── chessboard.min.css ├── js ├── chessboard.js └── chessboard.min.js ├── package.json ├── test ├── manual │ ├── bower.json │ ├── js │ │ └── app.js │ └── test.html ├── mocha │ ├── chess-utils-tests.js │ └── test.html └── selenium │ ├── selenium-tests.js │ └── test.html └── yuidoc.json /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | Thumbs.db 3 | 4 | bower_components 5 | node_modules 6 | 7 | doc -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /*jslint node:true*/ 2 | 3 | 'use strict'; 4 | 5 | module.exports = function (grunt) { 6 | 7 | require('time-grunt')(grunt); 8 | 9 | grunt.initConfig({ 10 | 11 | uglify: { 12 | my_target: { 13 | files: { 14 | 'js/chessboard.min.js': 'js/chessboard.js' 15 | } 16 | } 17 | }, 18 | 19 | cssmin: { 20 | my_target: { 21 | src: 'css/chessboard.css', 22 | dest: 'css/chessboard.min.css' 23 | } 24 | } 25 | 26 | }); 27 | 28 | grunt.loadNpmTasks('grunt-contrib-uglify'); 29 | grunt.loadNpmTasks('grunt-contrib-cssmin'); 30 | 31 | grunt.registerTask('minify', ['uglify', 'cssmin']); 32 | 33 | }; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Attila Szantner 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #ChessboardJS 2 | 3 | A responsive mobile-first javascript chessboard library. 4 | 5 | ##Quick start 6 | 7 | Clone the repo: `https://github.com/caustique/chessboard-js.git` 8 | 9 | or 10 | 11 | Install with [Bower](http://bower.io): `bower install chessboard-js`. 12 | 13 | ##Developer notes 14 | 15 | To start run `npm install` and `bower install` in the project directory. 16 | 17 | To generate documentation run `yuidoc` (after having installed it with `npm -g install yuidocjs`) 18 | 19 | To regenerate minified versions of the css and js files run `grunt minify` 20 | 21 | The tests are far from complete. 22 | 23 | Using [Brackets](http://brackets.io/) - all js files should be without any JSLint problems. 24 | 25 | ##License 26 | 27 | ChessboardJS is released under the [MIT License](https://github.com/caustique/chessboard-js/blob/master/LICENSE). 28 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chessboard-js", 3 | "description": "A responsive mobile-first javascript chessboard library.", 4 | "main": [ 5 | "js/chessboard.js", 6 | "css/chessboard.css" 7 | ], 8 | "license": "MIT", 9 | "ignore": [ 10 | "Gruntfile.js", 11 | ".gitignore", 12 | "test/", 13 | "package.json", 14 | "yuidoc.json" 15 | ], 16 | "keywords" : ["chess", "chessboard", "js", "javascript", "responsive", "chess-gui", "chessboard-gui", "mobile"], 17 | "authors": [ 18 | { "name": "Attila Szantner" } 19 | ], 20 | "dependencies": { 21 | "jquery": "~2.1.0" 22 | }, 23 | "devDependencies": { 24 | "mocha": "~1.18.2", 25 | "chai": "~1.9.1" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /css/chessboard.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SKIN 4 | 5 | VALUES TO PLAY WITH 6 | These values are safe to set and can be used to customize the appearence of the board. 7 | 8 | */ 9 | 10 | 11 | /* 12 | Customizing the chessboard 13 | */ 14 | /* The color of the light and dark square on the board. */ 15 | div.chess_board div.chess_square_light { 16 | background-color: #ffffff; 17 | } 18 | div.chess_board div.chess_square_dark { 19 | background-color: #e5e1d1; 20 | } 21 | /* The color of the light and dark squares when selected. */ 22 | div.chess_board div.chess_square_light.chess_square_selected { 23 | background-color: #eeeeee; 24 | } 25 | div.chess_board div.chess_square_dark.chess_square_selected { 26 | background-color: #c5c1a1; 27 | } 28 | 29 | 30 | /* 31 | Customizing chess pieces 32 | */ 33 | div.chess_board div.chess_player_white { 34 | color: #285e8e; 35 | } 36 | div.chess_board div.chess_player_black { 37 | color: #BD4932; 38 | } 39 | 40 | div.chess_board div.chess_square_selected div.chess_player_white { 41 | color: #487eae; 42 | } 43 | div.chess_board div.chess_square_selected div.chess_player_black { 44 | color: #dd6952; 45 | } 46 | 47 | 48 | /*div.chess_board div.chess_square_light.chess_square_valid_move { 49 | background-color: #CDE855; 50 | } 51 | div.chess_board div.chess_square_dark.chess_square_valid_move { 52 | background-color: #A7C520; 53 | }*/ 54 | 55 | div.chess_board div.chess_square_light.chess_square_valid_move { 56 | -webkit-box-shadow: inset 0 0 3px 3px #CDE855; 57 | -moz-box-shadow: inset 0 0 3px 3px #CDE855; 58 | box-shadow: inset 0 0 3px 3px #CDE855; 59 | } 60 | div.chess_board div.chess_square_dark.chess_square_valid_move { 61 | -webkit-box-shadow: inset 0 0 3px 3px #A7C520; 62 | -moz-box-shadow: inset 0 0 3px 3px #A7C520; 63 | box-shadow: inset 0 0 3px 3px #A7C520; 64 | } 65 | 66 | 67 | div.chess_board div.chess_square { 68 | -webkit-font-smoothing: antialiased; 69 | } 70 | 71 | 72 | div.chess_board div.chess_square { 73 | cursor: pointer; 74 | } 75 | div.chess_board div.chess_piece_none { 76 | cursor: auto; 77 | } 78 | 79 | 80 | 81 | div.chess_board div.chess_square_light > div.chess_label { 82 | color: #e5e1d1; 83 | } 84 | 85 | div.chess_board div.chess_square_dark > div.chess_label { 86 | color: #ffffff; 87 | } 88 | 89 | div.chess_board div.chess_label { 90 | font-family: "Helvetica Neue","Helvetica",Helvetica,Arial,sans-serif; 91 | font-weight: normal; 92 | font-style: normal; 93 | font-variant: small-caps; 94 | } 95 | 96 | 97 | /* Chess pieces with unicode characters */ 98 | div.chess_board div.chess_player_black.chess_piece_pawn:after { 99 | content: '\265F'; 100 | } 101 | div.chess_board div.chess_player_black.chess_piece_rook:after { 102 | content: '\265C'; 103 | } 104 | div.chess_board div.chess_player_black.chess_piece_knight:after { 105 | content: '\265E'; 106 | } 107 | div.chess_board div.chess_player_black.chess_piece_bishop:after { 108 | content: '\265D'; 109 | } 110 | div.chess_board div.chess_player_black.chess_piece_queen:after { 111 | content: '\265B'; 112 | } 113 | div.chess_board div.chess_player_black.chess_piece_king:after { 114 | content: '\265A'; 115 | } 116 | div.chess_board div.chess_player_white.chess_piece_pawn:after { 117 | content: '\2659'; 118 | } 119 | div.chess_board div.chess_player_white.chess_piece_rook:after { 120 | content: '\2656'; 121 | } 122 | div.chess_board div.chess_player_white.chess_piece_knight:after { 123 | content: '\2658'; 124 | } 125 | div.chess_board div.chess_player_white.chess_piece_bishop:after { 126 | content: '\2657'; 127 | } 128 | div.chess_board div.chess_player_white.chess_piece_queen:after { 129 | content: '\2655'; 130 | } 131 | div.chess_board div.chess_player_white.chess_piece_king:after { 132 | content: '\2654'; 133 | } 134 | 135 | 136 | /* Chess pieces with images */ 137 | 138 | /* Use this section if you prefer to use images to fonts */ 139 | 140 | /* 141 | div.chess_board div.chess_player_black.chess_piece_pawn { 142 | background-image: url('http://upload.wikimedia.org/wikipedia/commons/c/c7/Chess_pdt45.svg'); 143 | } 144 | div.chess_board div.chess_player_black.chess_piece_rook { 145 | background-image: url('http://upload.wikimedia.org/wikipedia/commons/f/ff/Chess_rdt45.svg'); 146 | } 147 | div.chess_board div.chess_player_black.chess_piece_knight { 148 | background-image: url('http://upload.wikimedia.org/wikipedia/commons/e/ef/Chess_ndt45.svg'); 149 | } 150 | div.chess_board div.chess_player_black.chess_piece_bishop { 151 | background-image: url('http://upload.wikimedia.org/wikipedia/commons/9/98/Chess_bdt45.svg'); 152 | } 153 | div.chess_board div.chess_player_black.chess_piece_queen { 154 | background-image: url('http://upload.wikimedia.org/wikipedia/commons/4/47/Chess_qdt45.svg'); 155 | } 156 | div.chess_board div.chess_player_black.chess_piece_king { 157 | background-image: url('http://upload.wikimedia.org/wikipedia/commons/f/f0/Chess_kdt45.svg'); 158 | } 159 | div.chess_board div.chess_player_white.chess_piece_pawn { 160 | background-image: url('http://upload.wikimedia.org/wikipedia/commons/4/45/Chess_plt45.svg'); 161 | } 162 | div.chess_board div.chess_player_white.chess_piece_rook { 163 | background-image: url('http://upload.wikimedia.org/wikipedia/commons/7/72/Chess_rlt45.svg'); 164 | } 165 | div.chess_board div.chess_player_white.chess_piece_knight { 166 | background-image: url('http://upload.wikimedia.org/wikipedia/commons/7/70/Chess_nlt45.svg'); 167 | } 168 | div.chess_board div.chess_player_white.chess_piece_bishop { 169 | background-image: url('http://upload.wikimedia.org/wikipedia/commons/b/b1/Chess_blt45.svg'); 170 | } 171 | div.chess_board div.chess_player_white.chess_piece_queen { 172 | background-image: url('http://upload.wikimedia.org/wikipedia/commons/1/15/Chess_qlt45.svg'); 173 | } 174 | div.chess_board div.chess_player_white.chess_piece_king { 175 | background-image: url('http://upload.wikimedia.org/wikipedia/commons/4/42/Chess_klt45.svg'); 176 | } 177 | */ 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | /* 190 | 191 | STRUCTURE 192 | 193 | VALUES TO LET ALONE! 194 | If you set these values, you risk to mess up the chessboard structure 195 | */ 196 | 197 | div.chess_board, 198 | div.chess_board div.chess_square, 199 | div.chess_board div.chess_square > div { 200 | -moz-box-sizing: border-box; 201 | -webkit-box-sizing: border-box; 202 | box-sizing: border-box; 203 | 204 | -webkit-user-select: none; 205 | -khtml-user-select: none; 206 | -moz-user-select: none; 207 | -ms-user-select: none; 208 | user-select: none; 209 | } 210 | 211 | div.chess_board div.chess_square { 212 | position: relative; 213 | float: left; 214 | width: 12.5%; 215 | z-index: 0; 216 | } 217 | 218 | div.chess_board div.chess_square_last_column { 219 | float: none; 220 | overflow: hidden; 221 | width: auto !important; 222 | } 223 | 224 | div.chess_board div.chess_piece { 225 | text-align: center; 226 | line-height: 100%; 227 | background-repeat: no-repeat; 228 | background-position: center; 229 | background-size: cover; 230 | height: 100% 231 | } 232 | 233 | div.chess_board div.chess_piece:after { 234 | position: absolute; 235 | top: 0px; 236 | left: 0px; 237 | width: 100%; 238 | } 239 | 240 | div.chess_board div.chess_label { 241 | z-index: 0; 242 | position: absolute; 243 | } 244 | 245 | div.chess_board div.chess_label_row, 246 | div.chess_board div.chess_label_column_reversed { 247 | top: 2px; 248 | left: 2px; 249 | } 250 | div.chess_board div.chess_label_column, 251 | div.chess_board div.chess_label_row_reversed { 252 | bottom: 2px; 253 | right: 2px; 254 | } 255 | 256 | 257 | div.chess_board div.chess_label_hidden { 258 | visibility: hidden; 259 | } -------------------------------------------------------------------------------- /css/chessboard.min.css: -------------------------------------------------------------------------------- 1 | div.chess_board div.chess_square_light{background-color:#fff}div.chess_board div.chess_square_dark{background-color:#e5e1d1}div.chess_board div.chess_square_light.chess_square_selected{background-color:#eee}div.chess_board div.chess_square_dark.chess_square_selected{background-color:#c5c1a1}div.chess_board div.chess_player_white{color:#285e8e}div.chess_board div.chess_player_black{color:#BD4932}div.chess_board div.chess_square_selected div.chess_player_white{color:#487eae}div.chess_board div.chess_square_selected div.chess_player_black{color:#dd6952}div.chess_board div.chess_square_light.chess_square_valid_move{-webkit-box-shadow:inset 0 0 3px 3px #CDE855;-moz-box-shadow:inset 0 0 3px 3px #CDE855;box-shadow:inset 0 0 3px 3px #CDE855}div.chess_board div.chess_square_dark.chess_square_valid_move{-webkit-box-shadow:inset 0 0 3px 3px #A7C520;-moz-box-shadow:inset 0 0 3px 3px #A7C520;box-shadow:inset 0 0 3px 3px #A7C520}div.chess_board div.chess_square{-webkit-font-smoothing:antialiased;cursor:pointer}div.chess_board div.chess_piece_none{cursor:auto}div.chess_board div.chess_square_light>div.chess_label{color:#e5e1d1}div.chess_board div.chess_square_dark>div.chess_label{color:#fff}div.chess_board div.chess_label{font-family:"Helvetica Neue",Helvetica,Helvetica,Arial,sans-serif;font-weight:400;font-style:normal;font-variant:small-caps}div.chess_board div.chess_player_black.chess_piece_pawn:after{content:'\265F'}div.chess_board div.chess_player_black.chess_piece_rook:after{content:'\265C'}div.chess_board div.chess_player_black.chess_piece_knight:after{content:'\265E'}div.chess_board div.chess_player_black.chess_piece_bishop:after{content:'\265D'}div.chess_board div.chess_player_black.chess_piece_queen:after{content:'\265B'}div.chess_board div.chess_player_black.chess_piece_king:after{content:'\265A'}div.chess_board div.chess_player_white.chess_piece_pawn:after{content:'\2659'}div.chess_board div.chess_player_white.chess_piece_rook:after{content:'\2656'}div.chess_board div.chess_player_white.chess_piece_knight:after{content:'\2658'}div.chess_board div.chess_player_white.chess_piece_bishop:after{content:'\2657'}div.chess_board div.chess_player_white.chess_piece_queen:after{content:'\2655'}div.chess_board div.chess_player_white.chess_piece_king:after{content:'\2654'}div.chess_board,div.chess_board div.chess_square,div.chess_board div.chess_square>div{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}div.chess_board div.chess_square{position:relative;float:left;width:12.5%;z-index:0}div.chess_board div.chess_square_last_column{float:none;overflow:hidden;width:auto!important}div.chess_board div.chess_piece{text-align:center;line-height:100%;background-repeat:no-repeat;background-position:center;background-size:cover;height:100%}div.chess_board div.chess_piece:after{position:absolute;top:0;left:0;width:100%}div.chess_board div.chess_label{z-index:0;position:absolute}div.chess_board div.chess_label_column_reversed,div.chess_board div.chess_label_row{top:2px;left:2px}div.chess_board div.chess_label_column,div.chess_board div.chess_label_row_reversed{bottom:2px;right:2px}div.chess_board div.chess_label_hidden{visibility:hidden} -------------------------------------------------------------------------------- /js/chessboard.js: -------------------------------------------------------------------------------- 1 | /*jslint plusplus: true, nomen: true, es5: true, regexp: true, browser: true, devel: true*/ 2 | /*global $, ChessUtils, module */ 3 | 4 | 5 | /* 6 | ---------------------------------------------------------------------------- 7 | ---------------------------------------------------------------------------- 8 | 9 | ChessboardJS 10 | 11 | ---------------------------------------------------------------------------- 12 | ---------------------------------------------------------------------------- 13 | */ 14 | /** 15 | Chessboard class to create a responsive chessboard from javascript. 16 | Further info https://github.com/caustique/chessboard-js 17 | 18 | @class Chessboard 19 | @constructor 20 | */ 21 | 22 | function Chessboard(containerId, config) { 23 | 24 | 'use strict'; 25 | 26 | /* 27 | ---------------------------------------------------------------------------- 28 | ---------------------------------------------------------------------------- 29 | 30 | CONSTANTS 31 | 32 | ---------------------------------------------------------------------------- 33 | ---------------------------------------------------------------------------- 34 | */ 35 | 36 | Chessboard.ANIMATION = { 37 | fadeInTime: 1000, 38 | fadeOutTime: 1000 39 | }; 40 | 41 | Chessboard.CSS_PREFIX = 'chess_'; 42 | Chessboard.CSS = { 43 | pathSeparator: '_', 44 | board: { 45 | id: Chessboard.CSS_PREFIX + 'board', 46 | className: Chessboard.CSS_PREFIX + 'board' 47 | }, 48 | square: { 49 | className: Chessboard.CSS_PREFIX + 'square', 50 | lastColumn: { className: Chessboard.CSS_PREFIX + 'square_last_column'}, 51 | idPrefix: Chessboard.CSS_PREFIX + 'square', 52 | dark: { className: Chessboard.CSS_PREFIX + 'square_dark' }, 53 | light: { className: Chessboard.CSS_PREFIX + 'square_light' }, 54 | createClassName: function (index) { 55 | return ' ' + (((index + ChessUtils.convertIndexToRow(index)) % 2 === 0) ? 56 | Chessboard.CSS.square.dark.className : Chessboard.CSS.square.light.className); 57 | }, 58 | selected: { className: Chessboard.CSS_PREFIX + 'square_selected'}, 59 | validMove: { className: Chessboard.CSS_PREFIX + 'square_valid_move' } 60 | }, 61 | player: { 62 | black: { className: Chessboard.CSS_PREFIX + 'player_' + ChessUtils.PLAYER.black.className }, 63 | white: { className: Chessboard.CSS_PREFIX + 'player_' + ChessUtils.PLAYER.white.className }, 64 | createClassName: function (player) { 65 | return (player === 'white') ? 66 | Chessboard.CSS.player.white.className : 67 | Chessboard.CSS.player.black.className; 68 | } 69 | }, 70 | piece: { 71 | idPrefix: Chessboard.CSS_PREFIX + 'piece', 72 | className: Chessboard.CSS_PREFIX + 'piece', 73 | createClassName: function (piece) { 74 | return Chessboard.CSS.piece.className + '_' + piece; 75 | }, 76 | none: { className: Chessboard.CSS_PREFIX + 'piece_none' } 77 | }, 78 | label: { 79 | className: Chessboard.CSS_PREFIX + 'label', 80 | hidden: { className: Chessboard.CSS_PREFIX + 'label_hidden' }, 81 | row: { 82 | className: Chessboard.CSS_PREFIX + 'label_row', 83 | reversed: { 84 | className: Chessboard.CSS_PREFIX + 'label_row_reversed' 85 | } 86 | }, 87 | column: { 88 | className: Chessboard.CSS_PREFIX + 'label_column', 89 | reversed: { className: Chessboard.CSS_PREFIX + 'label_column_reversed' } 90 | } 91 | }, 92 | style: { 93 | id: Chessboard.CSS_PREFIX + 'style' 94 | } 95 | 96 | }; 97 | 98 | /* 99 | ---------------------------------------------------------------------------- 100 | ---------------------------------------------------------------------------- 101 | 102 | VARIABLE AND METHOD DECLARATIONS 103 | 104 | ---------------------------------------------------------------------------- 105 | ---------------------------------------------------------------------------- 106 | */ 107 | // PRIVATE METHODS 108 | var init, 109 | initConfig, 110 | initDOM, 111 | // Event handlers 112 | bindEvents, 113 | unbindEvents, 114 | onSizeChanged, 115 | onSquareClicked, 116 | // Managing pieces on board 117 | getSquareElement, 118 | getSquareIndexFromId, 119 | clearSelection, 120 | setSelectedSquareElement, 121 | isSquareEmpty, 122 | getPieceElement, 123 | drawPiece, 124 | drawPosition, 125 | drawAnimations, 126 | // ---------------- 127 | // HELPER FUNCTIONS 128 | // ---------------- 129 | // CSS helper functions 130 | cssGetUniquePrefix, 131 | cssGetBoardUniqueId, 132 | cssGetSquareUniqueId, 133 | cssGetPieceUniqueId, 134 | cssGetStyleUniqueId, 135 | // PRIVATE VARIABLES 136 | // All private variables are prefixed with _ 137 | _that = this, // For event handlers 138 | _containerSelector, // Element selector that is given by the client to create chessboard in 139 | _userInputEnabled = false, // Shows if user input is enabled like clicks 140 | _position = ChessUtils.convertFenToPosition(ChessUtils.FEN.positions.empty), 141 | // Current position on the table string a8-h1 of pieces and '0' (white is capital letter), 142 | _selectedSquareIndex = null, // Index of the selected square 143 | _validMoves, // Array of valid moves for selected piece 144 | _orientation = ChessUtils.ORIENTATION.white, // Who sits south position 145 | _config = { 146 | useAnimation: true, // Setting default for useAnimation parameters 147 | showBoardLabels: true, // Show columns and row numbers 148 | showNextMove: true // Show the valid moves of the selected piece 149 | }, 150 | _eventHandlers = { 151 | /** 152 | * Fired when the user clicks on one of the available moves. Next position after move should be returned. Any format is accepted that setPosition() accepts. Returning null cancels move. 153 | * 154 | * @event onMove 155 | * @param {Object} move in notation format (ie. {from: 'e2', to: 'e4'}) 156 | */ 157 | onMove: null, 158 | onPieceSelected: null, 159 | onChange: null, 160 | onChanged: null 161 | }, // Function references to event handlers 162 | _preventPositionChange = false, // If true position change is not allowed 163 | _preventCallingEvents = true; // If true the system is not calling events. Used during running of constructor function. 164 | 165 | 166 | /* 167 | ---------------------------------------------------------------------------- 168 | ---------------------------------------------------------------------------- 169 | 170 | PUBLIC METHODS 171 | 172 | ---------------------------------------------------------------------------- 173 | ---------------------------------------------------------------------------- 174 | */ 175 | 176 | /** 177 | Clears the chessboard of all pieces. 178 | Change chessboard orientation to white. 179 | 180 | @method clear 181 | @public 182 | */ 183 | this.clear = function () { 184 | if (_preventPositionChange) { return; } 185 | this.setPosition(ChessUtils.FEN.positions.empty); 186 | this.setOrientation(ChessUtils.ORIENTATION.white); 187 | }; 188 | /** 189 | Destroys the DOM structure that was created 190 | 191 | @method destroy 192 | @public 193 | */ 194 | this.destroy = function () { 195 | $(_containerSelector).html(''); 196 | 197 | unbindEvents(); 198 | }; 199 | /** 200 | Sets or gets the chessboard position. 201 | The method doesn't change the chessboard orientation, so it has to be manually if needed. 202 | Deprecated method for compatibility reasons, use setPosition and getPosition instead. 203 | (No parameter checking!) 204 | 205 | @method position 206 | @public 207 | @deprecated 208 | @param {Object} [position] If omitted returns the internal position string. If ChessUtils.FEN.id (which is 'fen' for compatibility reasons) which returns a fen string (see http://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation). If Chessboard.NOTATION.id then returns a notation object. Otherwise it can be a position string, fen string or notation object to set the current position. 209 | @param {Boolean} [useAnimation=true] Whether to use animation for the process. 210 | @return {Object} The current position of the chessboard in different format. 211 | */ 212 | this.position = function (position, useAnimation) { 213 | var format; 214 | 215 | if (((arguments.length === 0) || typeof position === 'undefined') || 216 | (typeof position === 'string' && position.toLowerCase() === ChessUtils.FEN.id) || 217 | (typeof position === 'string' && position.toLowerCase() === ChessUtils.NOTATION.id)) { 218 | return this.getPosition(position); 219 | } else { 220 | this.setPosition(position, useAnimation); 221 | } 222 | }; 223 | /** 224 | Sets the chessboard position. 225 | The method doesn't change the chessboard orientation, so it has to be manually if needed. 226 | (No parameter checking!) 227 | 228 | @method setPosition 229 | @public 230 | @param {Object} [position] It can be a position string, fen string or notation object to set the current position. 231 | @param {Boolean} [useAnimation=true] Whether to use animation for the process. 232 | */ 233 | this.setPosition = function (position, useAnimation) { 234 | var prevUserInputEnabled = _userInputEnabled; 235 | 236 | if (_preventPositionChange) { return; } 237 | 238 | clearSelection(); 239 | 240 | useAnimation = (arguments.length === 1 || typeof useAnimation === 'undefined') ? _config.useAnimation : useAnimation; 241 | 242 | // start position 243 | if (typeof position === 'string' && (position.toLowerCase() === ChessUtils.FEN.startId)) { 244 | position = ChessUtils.convertFenToPosition(ChessUtils.FEN.positions.start); 245 | } else if (typeof position === 'string' && (position.toLowerCase() === ChessUtils.FEN.emptyId)) { 246 | position = ChessUtils.convertFenToPosition(ChessUtils.FEN.positions.empty); 247 | } else if (typeof position === 'string') { 248 | if (position.indexOf(ChessUtils.FEN.rowSeparator) !== -1) { 249 | position = ChessUtils.convertFenToPosition(position); 250 | } 251 | } else if (typeof position === 'object') { 252 | position = ChessUtils.convertNotationToPosition(position); 253 | } 254 | 255 | 256 | if (_position === position) { return; } 257 | 258 | // run the onChange function 259 | if (_eventHandlers.hasOwnProperty('onChange') === true && 260 | typeof _eventHandlers.onChange === 'function' && 261 | !_preventCallingEvents) { 262 | _preventPositionChange = true; 263 | if (!_eventHandlers.onChange(_position, position)) { return; } 264 | _preventPositionChange = false; 265 | } 266 | 267 | _userInputEnabled = false; 268 | if (useAnimation === true) { 269 | drawAnimations(position); 270 | _position = position; 271 | } else { 272 | _position = position; 273 | drawPosition(); 274 | } 275 | 276 | // run the onChanged function 277 | if (_eventHandlers.hasOwnProperty('onChanged') === true && 278 | typeof _eventHandlers.onChanged === 'function' && 279 | !_preventCallingEvents) { 280 | _preventPositionChange = true; 281 | _eventHandlers.onChanged(_position); 282 | _preventPositionChange = false; 283 | } 284 | 285 | _userInputEnabled = prevUserInputEnabled; 286 | }; 287 | /** 288 | Gets the chessboard position. 289 | (No strict parameter checking!) 290 | 291 | @method getPosition 292 | @public 293 | @param {String} [format] If omitted returns the internal position string. If ChessUtils.FEN.id (which is 'fen' for compatibility reasons) which returns a fen string (see http://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation). If Chessboard.NOTATION.id then returns a notation object. 294 | @return {Object} The current position of the chessboard in different format. 295 | */ 296 | this.getPosition = function (format) { 297 | // no arguments, return the current position 298 | if ((arguments.length === 0) || !format) { 299 | return _position; 300 | } 301 | // get position as fen 302 | if (format.toLowerCase() === ChessUtils.FEN.id) { 303 | return ChessUtils.convertPositionToFen(_position); 304 | } 305 | // get position as notation object 306 | if (format.toLowerCase() === ChessUtils.NOTATION.id) { 307 | return ChessUtils.convertPositionToNotation(_position); 308 | } 309 | }; 310 | 311 | this.move = function (firstMove) { 312 | var movesFrom = [], 313 | movesTo = [], 314 | position = _position, 315 | i, 316 | useAnimation = _config.useAnimation, 317 | count; 318 | 319 | if (_preventPositionChange) { return; } 320 | 321 | // TODO: parameter checking 322 | 323 | if (typeof arguments[arguments.length - 1] === 'boolean') { 324 | useAnimation = arguments[arguments.length - 1]; 325 | count = arguments.length - 1; 326 | } else { 327 | count = arguments.length; 328 | } 329 | 330 | if (typeof firstMove === 'string') { 331 | if (firstMove.search('-') !== -1) { 332 | for (i = 0; i < count; i++) { 333 | movesFrom.push(ChessUtils.convertNotationSquareToIndex(arguments[i].split('-')[0])); 334 | movesTo.push(ChessUtils.convertNotationSquareToIndex(arguments[i].split('-')[1])); 335 | } 336 | } else { 337 | for (i = 0; i < count; i += 2) { 338 | movesFrom.push(ChessUtils.convertNotationSquareToIndex(arguments[i])); 339 | movesTo.push(ChessUtils.convertNotationSquareToIndex(arguments[i + 1])); 340 | } 341 | } 342 | } else { 343 | for (i = 0; i < count; i += 2) { 344 | movesFrom.push(arguments[i]); 345 | movesTo.push(arguments[i + 1]); 346 | } 347 | } 348 | 349 | for (i = 0; i < movesFrom.length; i++) { 350 | if (_position[movesFrom[i]] !== ChessUtils.POSITION.empty) { 351 | position = ChessUtils.replaceStringAt(position, movesFrom[i], ChessUtils.POSITION.empty); 352 | position = ChessUtils.replaceStringAt(position, movesTo[i], _position[movesFrom[i]]); 353 | } 354 | } 355 | 356 | this.setPosition(position, useAnimation); 357 | 358 | }; 359 | /** 360 | Public method to set or get the chessboard position with a fen string (see http://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation). 361 | A shortcut to the position method. Use setPosition and getPosition instead. 362 | The method doesn't change the chessboard orientation, so it has to be manually if needed. 363 | (No parameter checking!) 364 | 365 | @method fen 366 | @public 367 | @deprecated 368 | @param {String} [fen] If omitted returns the fen string of the current postion on the checkboard. If given it is a fen string to set the current position. 369 | @param {Boolean} [useAnimation=true] Whether to use animation for the process. 370 | @return {String or Object} The current position of the chessboard in different format. 371 | */ 372 | this.fen = function (fen, useAnimation) { 373 | return this.position(ChessUtils.FEN.id, fen, useAnimation); 374 | }; 375 | this.orientation = function (orientation) { 376 | if (arguments.length === 0) { 377 | return this.getOrientation(); 378 | } else { 379 | this.setOrientation(orientation); 380 | } 381 | }; 382 | 383 | this.setOrientation = function (orientation) { 384 | var position; 385 | 386 | if (orientation === ChessUtils.ORIENTATION.flip || _orientation !== orientation) { 387 | 388 | clearSelection(); 389 | 390 | _orientation = (_orientation === ChessUtils.ORIENTATION.white) ? 391 | ChessUtils.ORIENTATION.black : ChessUtils.ORIENTATION.white; 392 | 393 | if (_orientation === ChessUtils.ORIENTATION.white) { 394 | $('.' + Chessboard.CSS.label.row.className).removeClass(Chessboard.CSS.label.hidden.className); 395 | $('.' + Chessboard.CSS.label.column.className).removeClass(Chessboard.CSS.label.hidden.className); 396 | $('.' + Chessboard.CSS.label.row.reversed.className).addClass(Chessboard.CSS.label.hidden.className); 397 | $('.' + Chessboard.CSS.label.column.reversed.className).addClass(Chessboard.CSS.label.hidden.className); 398 | } else { 399 | $('.' + Chessboard.CSS.label.row.className).addClass(Chessboard.CSS.label.hidden.className); 400 | $('.' + Chessboard.CSS.label.column.className).addClass(Chessboard.CSS.label.hidden.className); 401 | $('.' + Chessboard.CSS.label.row.reversed.className).removeClass(Chessboard.CSS.label.hidden.className); 402 | $('.' + Chessboard.CSS.label.column.reversed.className).removeClass(Chessboard.CSS.label.hidden.className); 403 | } 404 | 405 | drawPosition(); 406 | } 407 | }; 408 | this.getOrientation = function () { 409 | return _orientation; 410 | }; 411 | /** 412 | Sets the chessboard size to match the container element size. 413 | 414 | @method resize 415 | @public 416 | @deprecated 417 | */ 418 | this.resize = function () { 419 | onSizeChanged(); 420 | }; 421 | /** 422 | Sets the chessboard position to the classical start position. 423 | The method sets the chessboard orientation to Chessboard.ORIENTATION.white. 424 | 425 | @method start 426 | @public 427 | @param {Boolean} [useAnimation=true] Whether to use animation for the process. 428 | */ 429 | this.start = function (useAnimation) { 430 | 431 | if (_preventPositionChange) { return; } 432 | 433 | useAnimation = (arguments.length === 0) ? _config.useAnimation : useAnimation; 434 | this.position(ChessUtils.FEN.positions.start, useAnimation); 435 | _orientation = ChessUtils.ORIENTATION.white; 436 | }; 437 | 438 | /** 439 | Sets wether the board reacts to user clicks and other inputs. After initialization it is set to true. 440 | The game controller logic should take care of setting it according to who plays. 441 | 442 | @method enableUserInput 443 | @public 444 | @param {Boolean} [enabled=true] Whether to enable user interaction. 445 | */ 446 | this.enableUserInput = function (enabled) { 447 | if (arguments.length === 0) { 448 | enabled = true; 449 | } 450 | _userInputEnabled = enabled; 451 | }; 452 | /** 453 | Gets wether the board reacts to user clicks and other inputs. 454 | 455 | @method isUserInputEnabled 456 | @public 457 | @return {Boolean} Returns whether to enable user interaction. 458 | */ 459 | this.isUserInputEnabled = function () { 460 | return _userInputEnabled; 461 | }; 462 | 463 | 464 | 465 | /* 466 | ---------------------------------------------------------------------------- 467 | ---------------------------------------------------------------------------- 468 | 469 | PRIVATE METHODS 470 | 471 | ---------------------------------------------------------------------------- 472 | ---------------------------------------------------------------------------- 473 | */ 474 | /* 475 | ---------------------------------------------------------------------------- 476 | Init methods 477 | ---------------------------------------------------------------------------- 478 | */ 479 | /** 480 | Initializes the chessboard. 481 | 482 | @method init 483 | @private 484 | @param {String} containerId The html id of the container div where the chessboard will be created. 485 | @param {Object} config Configuration object which is either a position string or an object. 486 | */ 487 | init = function (containerId, config) { 488 | var position; 489 | 490 | position = initConfig(config); 491 | initDOM(containerId); 492 | bindEvents(); 493 | 494 | _userInputEnabled = true; 495 | 496 | return position; 497 | }; 498 | /** 499 | Processes the config object given at init. 500 | 501 | @method initConfig 502 | @private 503 | @param {Object} config Configuration object which is either a position string or an object with the following attributes: position, useAnimation, orientation, showBoardLabels. 504 | */ 505 | initConfig = function (config) { 506 | var position; 507 | 508 | if (typeof config === 'undefined') { 509 | position = ChessUtils.convertFenToPosition(ChessUtils.FEN.positions.start); 510 | return position; 511 | } else if (typeof config === 'string') { 512 | if (config.toLowerCase() === ChessUtils.FEN.startId) { 513 | position = ChessUtils.convertFenToPosition(ChessUtils.FEN.positions.start); 514 | } else if (config.toLowerCase() === ChessUtils.FEN.emptyId) { 515 | position = ChessUtils.convertFenToPosition(ChessUtils.FEN.positions.empty); 516 | } else { 517 | position = config; 518 | } 519 | return position; 520 | } else if (typeof config.position === 'undefined') { 521 | position = ChessUtils.convertNotationToPosition(config); 522 | return position; 523 | } else if (config.position !== null) { 524 | if (ChessUtils.isValidFen(config.position)) { 525 | config.position = ChessUtils.convertFenToPosition(config.position); 526 | } 527 | position = config.position; 528 | } 529 | _orientation = config.orientation === ChessUtils.ORIENTATION.black ? 530 | ChessUtils.ORIENTATION.black : ChessUtils.ORIENTATION.white; 531 | 532 | _config.useAnimation = config.useAnimation === false ? false : true; 533 | _config.showBoardLabels = (config.showNotation === false) || (config.showBoardLabels === false) ? false : true; 534 | _config.showNextMove = config.showNextMove === false ? false : true; 535 | 536 | if (config.eventHandlers) { 537 | 538 | if (config.eventHandlers.onChange && 539 | typeof config.eventHandlers.onChange === 'function') { 540 | _eventHandlers.onChange = config.eventHandlers.onChange; 541 | } 542 | if (config.eventHandlers.onChanged && 543 | typeof config.eventHandlers.onChanged === 'function') { 544 | _eventHandlers.onChanged = config.eventHandlers.onChanged; 545 | } 546 | if (config.eventHandlers.onPieceSelected && 547 | typeof config.eventHandlers.onPieceSelected === 'function') { 548 | _eventHandlers.onPieceSelected = config.eventHandlers.onPieceSelected; 549 | } 550 | if (config.eventHandlers.onMove && 551 | typeof config.eventHandlers.onMove === 'function') { 552 | _eventHandlers.onMove = config.eventHandlers.onMove; 553 | } 554 | 555 | } 556 | 557 | return position; 558 | // TODO: Deprecated compatibility settings 559 | }; 560 | /** 561 | Initialises the DOM tree for the chessboard. 562 | 563 | @method initDOM 564 | @private 565 | @param {String} containerId The html id of the container div where the chessboard will be created. 566 | */ 567 | initDOM = function (containerId) { 568 | var i, 569 | html = '', 570 | id, 571 | className; 572 | 573 | _containerSelector = '#' + containerId; 574 | 575 | if (!$(_containerSelector)) { throw new Error("ContainerId provided doesn't point to a DOM element."); } 576 | 577 | // Adding dynamic style for resize events 578 | html += ''; 579 | 580 | 581 | // Board div 582 | html += '
'; 584 | 585 | for (i = 0; i < 64; i++) { 586 | // Square div 587 | id = cssGetSquareUniqueId(i); 588 | className = Chessboard.CSS.square.className; 589 | className += ' ' + Chessboard.CSS.square.createClassName(i); 590 | if (i % 8 === 7) { 591 | className += ' ' + Chessboard.CSS.square.lastColumn.className; 592 | } 593 | html += '
'; 594 | 595 | // Column indicators 596 | if (ChessUtils.convertIndexToRow(i) === 0) { 597 | html += '
' + 599 | ChessUtils.NOTATION.columnConverter[ChessUtils.convertIndexToColumn(i)] + '
'; 600 | } 601 | if (ChessUtils.convertIndexToRow(i) === 7) { 602 | html += '
' + 605 | ChessUtils.NOTATION.columnConverter[ChessUtils.convertIndexToColumn(7 - i)] + '
'; 606 | } 607 | // Row indicators 608 | if (ChessUtils.convertIndexToColumn(i) === 0) { 609 | html += '
' + 611 | ChessUtils.NOTATION.rowConverter[ChessUtils.convertIndexToRow(i)] + '
'; 612 | } 613 | if (ChessUtils.convertIndexToColumn(i) === 7) { 614 | html += '
' + 617 | ChessUtils.NOTATION.rowConverter[7 - ChessUtils.convertIndexToRow(i)] + '
'; 618 | } 619 | 620 | // Piece placeholders 621 | className = Chessboard.CSS.piece.className; 622 | className += ' ' + Chessboard.CSS.piece.none.className; 623 | html += '
'; 624 | 625 | html += '
'; 626 | } 627 | 628 | html += '
'; 629 | 630 | $(_containerSelector).html(html); 631 | $(_containerSelector).css('display', 'inline-block'); 632 | }; 633 | 634 | /* 635 | ---------------------------------------------------------------------------- 636 | Event handling related methods 637 | ---------------------------------------------------------------------------- 638 | */ 639 | /** 640 | Binds chessboard events to elements. 641 | 642 | @method bindEvents 643 | @private 644 | */ 645 | bindEvents = function () { 646 | $(window).on('resize.chessEvents', onSizeChanged); 647 | 648 | $('div' + _containerSelector + ' div.' + Chessboard.CSS.square.className).on('click', onSquareClicked); 649 | }; 650 | /** 651 | Unbinds chessboard events to elements in case the board is detroyed. 652 | 653 | @method unbindEvents 654 | @private 655 | */ 656 | unbindEvents = function () { 657 | $(window).unbind('resize.chessEvents'); 658 | $('div' + _containerSelector + ' div.' + Chessboard.CSS.square.className).unbind('click'); 659 | }; 660 | /** 661 | Resizes elements in case the window is resized. 662 | 663 | @method onSizeChanged 664 | @private 665 | */ 666 | onSizeChanged = function () { 667 | var newSquareWidth, 668 | newPieceFontSize, 669 | newLabelFontSize, 670 | html; 671 | 672 | newSquareWidth = Math.floor($(_containerSelector).width() / 8); 673 | newPieceFontSize = newSquareWidth * 0.85; 674 | newLabelFontSize = Math.min(Math.max(newSquareWidth * 0.5, 8), 20); 675 | 676 | html = '\ 677 | div' + _containerSelector + ' div.' + Chessboard.CSS.piece.className + ' {\ 678 | font-size: ' + newPieceFontSize + 'px;\ 679 | height: ' + newSquareWidth + 'px;\ 680 | }\ 681 | div' + _containerSelector + ' div.' + Chessboard.CSS.label.className + ' {\ 682 | font-size: ' + newLabelFontSize + 'px;\ 683 | ' + (!_config.showBoardLabels ? 'display: none;' : '') + '\ 684 | }'; 685 | $('#' + cssGetStyleUniqueId()).html(html); 686 | }; 687 | /** 688 | Handles onClick event on squares. 689 | 690 | @method onSquareClicked 691 | @private 692 | */ 693 | onSquareClicked = function () { 694 | var index = getSquareIndexFromId($(this).context.id), 695 | i, 696 | nextPosition; 697 | 698 | if (!_userInputEnabled) { return; } 699 | 700 | if (_selectedSquareIndex !== null && _validMoves.indexOf(index) > -1) { 701 | if (_eventHandlers.onMove) { 702 | nextPosition = _eventHandlers.onMove({from: ChessUtils.convertIndexToNotationSquare(_selectedSquareIndex), 703 | to: ChessUtils.convertIndexToNotationSquare(index)}); 704 | if (nextPosition !== null) { 705 | _that.setPosition(nextPosition); 706 | clearSelection(); 707 | } 708 | } 709 | } else { 710 | 711 | if (_selectedSquareIndex !== index) { 712 | if (_eventHandlers.onPieceSelected) { 713 | _validMoves = _eventHandlers.onPieceSelected(ChessUtils.convertIndexToNotationSquare(index)); 714 | 715 | if (_validMoves && _validMoves.length !== 0) { 716 | setSelectedSquareElement(index); 717 | 718 | if (!isSquareEmpty(index)) { 719 | for (i = 0; i < _validMoves.length; i++) { 720 | getSquareElement(_validMoves[i]).addClass(Chessboard.CSS.square.validMove.className); 721 | } 722 | } 723 | } else { 724 | clearSelection(); 725 | } 726 | } 727 | } 728 | } 729 | 730 | }; 731 | /** 732 | Clears the current selection. 733 | 734 | @method clearSelection 735 | @private 736 | */ 737 | clearSelection = function () { 738 | if (_selectedSquareIndex !== null) { 739 | getSquareElement(_selectedSquareIndex).removeClass(Chessboard.CSS.square.selected.className); 740 | $('div' + _containerSelector + ' div.' + Chessboard.CSS.square.validMove.className). 741 | removeClass(Chessboard.CSS.square.validMove.className); 742 | } 743 | _selectedSquareIndex = null; 744 | }; 745 | /** 746 | Sets the index-th square to selected if there is a piece on it. It deletes the previous selection. 747 | 748 | @method setSelectedSquareElement 749 | @private 750 | @param {Integer} index The index of the quare to be selected. 751 | */ 752 | setSelectedSquareElement = function (index) { 753 | clearSelection(); 754 | if (!isSquareEmpty(index)) { 755 | _selectedSquareIndex = index; 756 | getSquareElement(_selectedSquareIndex).addClass(Chessboard.CSS.square.selected.className); 757 | } 758 | }; 759 | 760 | /* 761 | ---------------------------------------------------------------------------- 762 | Managing board 763 | ---------------------------------------------------------------------------- 764 | */ 765 | /** 766 | Returns the a JQuery object of the square div selected by the index. 767 | 768 | @method getSquareElement 769 | @private 770 | @param {Integer} index Index of a square (0-63) 771 | @return {Object} The JQuery object of the square div 772 | */ 773 | getSquareElement = function (index) { 774 | return $('#' + cssGetSquareUniqueId(index)); 775 | }; 776 | /** 777 | Returns the index of a square div based on the html id attribute. 778 | 779 | @method getSquareIndexFromId 780 | @private 781 | @param {String} htmlId The html id attribute of the square (The last piece contains the id part) 782 | @return {Integer} The index of the square (0-63) 783 | */ 784 | getSquareIndexFromId = function (htmlId) { 785 | var classParts, 786 | originalIndex; 787 | 788 | classParts = htmlId.split(Chessboard.CSS.pathSeparator); 789 | originalIndex = parseInt(classParts[classParts.length - 1], 10); 790 | return _orientation === ChessUtils.ORIENTATION.white ? originalIndex : 63 - originalIndex; 791 | }; 792 | /** 793 | Returns the a JQuery object of the piece div selected by the index. 794 | 795 | @method getPieceElement 796 | @private 797 | @param {Integer} index Index of a square (0-63) 798 | @return {Object} The JQuery object of the piece div 799 | */ 800 | getPieceElement = function (index) { 801 | return $('#' + cssGetPieceUniqueId(index)); 802 | }; 803 | /** 804 | Returns if a selected index is empty. 805 | 806 | @method isSquareEmpty 807 | @private 808 | @param {Integer} index Index of a square (0-63) 809 | @return {Boolean} 810 | */ 811 | isSquareEmpty = function (index) { 812 | // TODO: Check why not using position string 813 | return $(getPieceElement(index)).hasClass(Chessboard.CSS.piece.none.className); 814 | }; 815 | /** 816 | Draws a piece at the selected position. 817 | 818 | @method drawPiece 819 | @private 820 | @param {Integer} index Index of a square (0-63) 821 | @param {String} positionPiece The string representing one piece in the position string. 822 | @param {Boolean} isHidden Whether the piece should be placed there as a hidden piece for aimation purposes. 823 | */ 824 | drawPiece = function (index, positionPiece, isHidden) { 825 | var className = '', 826 | player = ChessUtils.getPlayerNameFromPiece(positionPiece), 827 | piece = ChessUtils.PIECE.codeToPieceName[positionPiece.toLowerCase()]; 828 | 829 | if (isHidden !== true) { isHidden = false; } 830 | 831 | className = Chessboard.CSS.piece.className; 832 | if (positionPiece !== ChessUtils.POSITION.empty) { 833 | className += ' ' + Chessboard.CSS.piece.createClassName(piece); 834 | className += ' ' + Chessboard.CSS.player.createClassName(player); 835 | } else { 836 | className += ' ' + Chessboard.CSS.piece.none.className; 837 | } 838 | if (getPieceElement(index).attr('class') !== className) { 839 | getPieceElement(index).attr('class', className); 840 | } 841 | getPieceElement(index).css('opacity', isHidden ? 0 : 1); 842 | }; 843 | /** 844 | Draws the entire board from the position string using the drawPiece method. 845 | 846 | @method drawPosition 847 | @private 848 | */ 849 | drawPosition = function () { 850 | var i; 851 | 852 | for (i = 0; i < 64; i++) { 853 | drawPiece(i, _position[i]); 854 | } 855 | }; 856 | /** 857 | Draws animated the entire board from the position string. 858 | 859 | @method drawAnimations 860 | @private 861 | @param {String} position A position string to animate to from the actual position. 862 | */ 863 | drawAnimations = function (position) { 864 | var i; 865 | 866 | for (i = 0; i < 64; i++) { 867 | if (_position[i] !== position[i]) { 868 | if ((_position[i] !== '0') && (position[i] !== '0')) { 869 | drawPiece(i, position[i], true); 870 | $(getPieceElement(i)).animate({'opacity': '1'}, Chessboard.ANIMATION.fadeInTime); 871 | 872 | // Replacing characters 873 | } else if (_position[i] === '0') { 874 | // New piece on square 875 | drawPiece(i, position[i], true); 876 | $(getPieceElement(i)).animate({'opacity': '1'}, Chessboard.ANIMATION.fadeInTime); 877 | } else if (position[i] === '0') { 878 | // Removing piece from square 879 | drawPiece(i, position[i], true); 880 | } 881 | } 882 | } 883 | }; 884 | 885 | 886 | 887 | /* 888 | ---------------------------------------------------------------------------- 889 | CSS helper methods 890 | ---------------------------------------------------------------------------- 891 | */ 892 | /** 893 | Creates a unique prefix for css classnames based on enclosing div id 894 | in order to be able to handle multiple boards on one page. 895 | 896 | @method cssGetUniquePrefix 897 | @private 898 | @return {String} 899 | */ 900 | cssGetUniquePrefix = function () { 901 | return $(_containerSelector).attr('id') + '_'; 902 | }; 903 | /** 904 | Returns unique css classname for the board div. 905 | 906 | @method cssGetBoardUniqueId 907 | @private 908 | @return {String} 909 | */ 910 | cssGetBoardUniqueId = function (index) { 911 | return cssGetUniquePrefix() + Chessboard.CSS.board.id; 912 | }; 913 | /** 914 | Returns unique css id for a square div. 915 | 916 | @method cssGetSquareUniqueId 917 | @private 918 | @return {String} 919 | */ 920 | cssGetSquareUniqueId = function (index) { 921 | return cssGetUniquePrefix() + Chessboard.CSS.square.idPrefix + '_' + 922 | (_orientation === ChessUtils.ORIENTATION.white ? index : 63 - index); 923 | }; 924 | /** 925 | Returns unique css id for a piece div. 926 | 927 | @method cssGetPieceUniqueId 928 | @private 929 | @return {String} 930 | */ 931 | cssGetPieceUniqueId = function (index) { 932 | return cssGetUniquePrefix() + Chessboard.CSS.piece.idPrefix + '_' + 933 | (_orientation === ChessUtils.ORIENTATION.white ? index : 63 - index); 934 | }; 935 | /** 936 | Returns unique css id for the style div. 937 | 938 | @method cssGetStyleUniqueId 939 | @private 940 | @return {String} 941 | */ 942 | cssGetStyleUniqueId = function () { 943 | return cssGetUniquePrefix() + Chessboard.CSS.style.id; 944 | }; 945 | 946 | 947 | /* 948 | ---------------------------------------------------------------------------- 949 | ---------------------------------------------------------------------------- 950 | 951 | CONSTRUCTOR CODE 952 | 953 | ---------------------------------------------------------------------------- 954 | ---------------------------------------------------------------------------- 955 | */ 956 | 957 | this.setPosition(init(containerId, config)); 958 | onSizeChanged(); 959 | 960 | // It is a bit of a hack. 961 | if (_orientation === ChessUtils.ORIENTATION.black) { 962 | this.setOrientation(ChessUtils.ORIENTATION.flip); 963 | _orientation = ChessUtils.ORIENTATION.black; 964 | } 965 | 966 | _preventCallingEvents = false; 967 | 968 | } 969 | 970 | 971 | 972 | 973 | 974 | 975 | 976 | 977 | 978 | 979 | 980 | 981 | 982 | 983 | 984 | 985 | 986 | /* 987 | ---------------------------------------------------------------------------- 988 | ---------------------------------------------------------------------------- 989 | 990 | CHESSUTILS 991 | 992 | ---------------------------------------------------------------------------- 993 | ---------------------------------------------------------------------------- 994 | */ 995 | 996 | 997 | /** 998 | ChessUtils class to contain static utility functions. 999 | 1000 | @class ChessUtils 1001 | */ 1002 | 1003 | (function () { 1004 | 'use strict'; 1005 | 1006 | /* 1007 | ---------------------------------------------------------------------------- 1008 | ---------------------------------------------------------------------------- 1009 | 1010 | CONSTANTS 1011 | 1012 | ---------------------------------------------------------------------------- 1013 | ---------------------------------------------------------------------------- 1014 | */ 1015 | 1016 | var ChessUtils = {}; 1017 | 1018 | ChessUtils.PLAYER = { 1019 | black: { 1020 | code: 'b', 1021 | notation: 'b', 1022 | className: 'black' 1023 | }, 1024 | white: { 1025 | code: 'w', 1026 | notation: 'w', 1027 | className: 'white' 1028 | } 1029 | }; 1030 | 1031 | ChessUtils.ORIENTATION = { 1032 | white: 'w', 1033 | black: 'b', 1034 | flip: 'flip' 1035 | }; 1036 | 1037 | ChessUtils.PIECE = { 1038 | none: '0', 1039 | pawn: 'p', 1040 | rook: 'r', 1041 | knight: 'n', 1042 | bishop: 'b', 1043 | queen: 'q', 1044 | king: 'k', 1045 | codeToPieceName: { 1046 | p: 'pawn', 1047 | r: 'rook', 1048 | n: 'knight', 1049 | b: 'bishop', 1050 | q: 'queen', 1051 | k: 'king' 1052 | } 1053 | }; 1054 | 1055 | ChessUtils.POSITION = { 1056 | empty: '0', 1057 | piece: { 1058 | pawn: 'p', 1059 | rook: 'r', 1060 | knight: 'n', 1061 | bishop: 'b', 1062 | queen: 'q', 1063 | king: 'k' 1064 | }, 1065 | validator: /^[kqrbnpKQRNBP0]+$/, 1066 | }; 1067 | 1068 | 1069 | ChessUtils.NOTATION = { 1070 | id: 'notation', 1071 | positionValidator: /^[a-h][1-8]$/, 1072 | pieceValidator: /^[bw][KQRNBP]$/, 1073 | columns: String.prototype.split.call('abcdefgh', ''), 1074 | rows: String.prototype.split.call('12345678', ''), 1075 | columnConverter: 'abcdefgh', 1076 | rowConverter: '12345678' 1077 | }; 1078 | 1079 | ChessUtils.FEN = { 1080 | // Commands 1081 | id: 'fen', 1082 | startId: 'start', 1083 | emptyId: 'empty', 1084 | // Syntax 1085 | rowValidator: /^[kqrbnpKQRNBP1-8]+$/, 1086 | rowSeparator: '/', 1087 | // Misc 1088 | positions: { 1089 | // Standard empty and start position 1090 | empty: '8/8/8/8/8/8/8/8', 1091 | start: 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR', 1092 | // Some well known positions 1093 | ruyLopez: 'r1bqkbnr/pppp1ppp/2n5/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R' 1094 | } 1095 | }; 1096 | 1097 | 1098 | /* 1099 | ---------------------------------------------------------------------------- 1100 | ---------------------------------------------------------------------------- 1101 | 1102 | PUBLIC STATIC METHODS 1103 | 1104 | ---------------------------------------------------------------------------- 1105 | ---------------------------------------------------------------------------- 1106 | */ 1107 | 1108 | /* 1109 | ---------------------------------------------------------------------------- 1110 | Piece, player etc. 1111 | ---------------------------------------------------------------------------- 1112 | */ 1113 | /** 1114 | Checks wether a piece code represents a white player. 1115 | 1116 | @method isPieceWhite 1117 | @public 1118 | @static 1119 | @param {String} piece A piece code (B or k) 1120 | @return {Boolean} 1121 | */ 1122 | ChessUtils.isPieceWhite = function (piece) { 1123 | return (piece.toUpperCase() === piece) && (piece !== ChessUtils.PIECE.none); 1124 | }; 1125 | /** 1126 | Checks wether a piece code represents a black player. 1127 | 1128 | @method isPieceBlack 1129 | @public 1130 | @static 1131 | @param {String} piece A piece code (B or k) 1132 | @return {Boolean} 1133 | */ 1134 | ChessUtils.isPieceBlack = function (piece) { 1135 | return (piece.toUpperCase() !== piece); 1136 | }; 1137 | /** 1138 | Puts to piece into the valid format according to player information. 1139 | 1140 | @method getPieceForPlayer 1141 | @public 1142 | @static 1143 | @param {String} piece A piece code (B or k) 1144 | @param {String} piece A player code 1145 | @return {String} The piece code in the proper case 1146 | */ 1147 | ChessUtils.getPieceForPlayer = function (piece, playerCode) { 1148 | return playerCode === ChessUtils.PLAYER.white.code ? piece.toUpperCase() : piece.toLowerCase(); 1149 | }; 1150 | /** 1151 | Gets the player of the piece. 1152 | 1153 | @method convertPieceToPlayerName 1154 | @public 1155 | @static 1156 | @param {String} piece A piece code (B or k) 1157 | @return {String} The player name (white or black) 1158 | */ 1159 | ChessUtils.getPlayerNameFromPiece = function (piece) { 1160 | if (ChessUtils.isPieceWhite(piece)) { return ChessUtils.PLAYER.white.className; } 1161 | if (ChessUtils.isPieceBlack(piece)) { return ChessUtils.PLAYER.black.className; } 1162 | }; 1163 | /** 1164 | Gets the player of the piece. 1165 | 1166 | @method getPlayerCodeFromPiece 1167 | @public 1168 | @static 1169 | @param {String} piece A piece code (B or k) 1170 | @return {String} The player code (w or b) 1171 | */ 1172 | ChessUtils.getPlayerCodeFromPiece = function (piece) { 1173 | if (ChessUtils.isPieceWhite(piece)) { return ChessUtils.PLAYER.white.code; } 1174 | if (ChessUtils.isPieceBlack(piece)) { return ChessUtils.PLAYER.black.code; } 1175 | }; 1176 | 1177 | /* 1178 | ---------------------------------------------------------------------------- 1179 | Validating methods 1180 | ---------------------------------------------------------------------------- 1181 | */ 1182 | /** 1183 | Checks wether a position string is valid. 1184 | 1185 | @method isValidPosition 1186 | @public 1187 | @static 1188 | @param {String} position A position string 1189 | @return {Boolean} 1190 | */ 1191 | ChessUtils.isValidPosition = function (position) { 1192 | if (typeof position !== 'string') { return false; } 1193 | if ((position.length !== 64) || 1194 | position.search(ChessUtils.POSITION.validator) !== -1) { 1195 | return false; 1196 | } 1197 | }; 1198 | /** 1199 | Checks wether a fen string is valid. 1200 | 1201 | @method isValidFen 1202 | @public 1203 | @static 1204 | @param {String} fen A fen string 1205 | @return {Boolean} 1206 | */ 1207 | ChessUtils.isValidFen = function (fen) { 1208 | var fenRows, 1209 | i; 1210 | 1211 | if (typeof fen !== 'string') { return false; } 1212 | 1213 | fen = fen.split(' ')[0]; 1214 | 1215 | fenRows = fen.split('/'); 1216 | if (fenRows.length !== 8) { return false; } 1217 | 1218 | // check the piece sections 1219 | for (i = 0; i < 8; i++) { 1220 | if (fenRows[i] === '' || 1221 | fenRows[i].length > 8 || 1222 | fenRows[i].search(ChessUtils.FEN.rowValidator) !== -1) { 1223 | return false; 1224 | } 1225 | } 1226 | 1227 | return true; 1228 | }; 1229 | 1230 | /** 1231 | Checks wether a notation position string is valid. 1232 | Notation position string example: 'a1' which represents the first column and row 1233 | 1234 | @method isValidNotationPosition 1235 | @public 1236 | @static 1237 | @param {String} position A notation position string 1238 | @return {Boolean} 1239 | */ 1240 | ChessUtils.isValidNotationPosition = function (position) { 1241 | if (typeof position !== 'string') { return false; } 1242 | return (position.search(ChessUtils.NOTATION.positionValidator) !== -1); 1243 | }; 1244 | /** 1245 | Checks wether a notation piece string is valid. 1246 | Notation piece string example: 'bK' which represents black king 1247 | 1248 | @method isValidNotationPiece 1249 | @public 1250 | @static 1251 | @param {String} piece A notation piece string 1252 | @return {Boolean} 1253 | */ 1254 | ChessUtils.isValidNotationPiece = function (piece) { 1255 | if (typeof piece !== 'string') { return false; } 1256 | return (piece.search(ChessUtils.NOTATION.pieceValidator) !== -1); 1257 | }; 1258 | /** 1259 | Checks wether a notation object is valid. 1260 | Notation object example: {a4: 'bK',c4: 'wK',a7: 'wR'} 1261 | 1262 | @method isValidNotation 1263 | @public 1264 | @static 1265 | @param {Object} notation An object containing valid notations 1266 | @return {Boolean} 1267 | */ 1268 | ChessUtils.isValidNotation = function (notation) { 1269 | var i; 1270 | 1271 | if (typeof notation !== 'object') { 1272 | return false; 1273 | } 1274 | 1275 | for (i in notation) { 1276 | if (notation.hasOwnProperty(i)) { 1277 | if (!ChessUtils.isValidNotationPosition(i) || !ChessUtils.isValidNotationPiece(notation[i])) { 1278 | return false; 1279 | } 1280 | } 1281 | } 1282 | 1283 | return true; 1284 | }; 1285 | 1286 | /* 1287 | ---------------------------------------------------------------------------- 1288 | Conversion of chessboard notation methods 1289 | ---------------------------------------------------------------------------- 1290 | */ 1291 | /** 1292 | Creates a position string from a fen string. (see http://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation) 1293 | 1294 | @method convertFenToPosition 1295 | @public 1296 | @static 1297 | @param {String} fen The fen string. Only the first part is used. 1298 | @return {String} The position string representation of the fen string. 1299 | */ 1300 | ChessUtils.convertFenToPosition = function (fen) { 1301 | var i, 1302 | position; 1303 | 1304 | if (ChessUtils.isValidFen(fen)) { 1305 | throw new Error('Invalid fen string "' + fen + '".'); 1306 | } 1307 | 1308 | // Keeping the first part of fen 1309 | position = fen.split(' ')[0]; 1310 | 1311 | for (i = 1; i <= 8; i++) { 1312 | position = position.replace(new RegExp(i, 'g'), ChessUtils.repeatString('0', i)); 1313 | } 1314 | position = position.replace(new RegExp(ChessUtils.FEN.rowSeparator, 'g'), ''); 1315 | 1316 | return position; 1317 | }; 1318 | /** 1319 | Creates a fen string from a position string. (see http://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation) 1320 | 1321 | @method convertPositionToFen 1322 | @public 1323 | @static 1324 | @param {String} position The position string. 1325 | @return {String} The fen string representation of the position string. 1326 | */ 1327 | ChessUtils.convertPositionToFen = function (position) { 1328 | var i, 1329 | fen = ''; 1330 | 1331 | if (ChessUtils.isValidPosition(position)) { 1332 | throw new Error('Invalid position string "' + position + '".'); 1333 | } 1334 | 1335 | fen = position.substr(0, 8); 1336 | for (i = 1; i < 8; i++) { 1337 | fen += ChessUtils.FEN.rowSeparator + position.substr(i * 8, 8); 1338 | } 1339 | for (i = 8; i > 0; i--) { 1340 | fen = fen.replace(new RegExp(ChessUtils.repeatString('0', i), 'g'), i); 1341 | } 1342 | 1343 | return fen; 1344 | }; 1345 | 1346 | 1347 | /** 1348 | Returns the notation piece string from a piece code string that is used in fen and position strings (K -> wK or k -> bK). 1349 | 1350 | @method convertPieceToNotationPiece 1351 | @public 1352 | @static 1353 | @param {String} piece The piece code string. 1354 | @return {String} The notation piece string. 1355 | */ 1356 | ChessUtils.convertPieceToNotationPiece = function (piece) { 1357 | return (ChessUtils.isPieceWhite(piece) ? 1358 | ChessUtils.PLAYER.white.notation : ChessUtils.PLAYER.black.notation) + 1359 | piece.toUpperCase(); 1360 | }; 1361 | /** 1362 | Returns the piece code string that is used in fen and position strings from a notation piece string (wK -> K or bK -> k). 1363 | 1364 | @method convertNotationPieceToPiece 1365 | @public 1366 | @static 1367 | @param {String} piece The notation piece string. 1368 | @return {String} The piece code string. 1369 | */ 1370 | ChessUtils.convertNotationPieceToPiece = function (notationPiece) { 1371 | return ((notationPiece.split('')[0] === ChessUtils.PLAYER.white.notation) ? 1372 | notationPiece.split('')[1].toUpperCase() : 1373 | notationPiece.split('')[1].toLowerCase()); 1374 | }; 1375 | /** 1376 | Returns the notation square from an index (0-63) (0 -> a8 or 63 -> h1). 1377 | 1378 | @method convertIndexToNotationSquare 1379 | @public 1380 | @static 1381 | @param {Integer} index The index of the square 1382 | @return {String} The notation form of the square 1383 | */ 1384 | ChessUtils.convertIndexToNotationSquare = function (index) { 1385 | return ChessUtils.NOTATION.columns[ChessUtils.convertIndexToColumn(index)] + 1386 | ChessUtils.NOTATION.rows[ChessUtils.convertIndexToRow(index)]; 1387 | 1388 | }; 1389 | /** 1390 | Returns the index (0-63) from a notation square (a8 -> 0 or h1 -> 63). 1391 | 1392 | @method convertNotationSquareToIndex 1393 | @public 1394 | @static 1395 | @param {String} notationSquare The notation form of the square 1396 | @return {Integer} The index of the square 1397 | */ 1398 | ChessUtils.convertNotationSquareToIndex = function (notationSquare) { 1399 | var index, 1400 | i, 1401 | row, 1402 | column; 1403 | 1404 | if (notationSquare[notationSquare.length - 1] === '+') { 1405 | notationSquare = notationSquare.substring(0, notationSquare.length - 1); 1406 | } 1407 | column = notationSquare.split('')[notationSquare.length - 2]; 1408 | row = notationSquare.split('')[notationSquare.length - 1]; 1409 | 1410 | return ChessUtils.convertRowColumnToIndex( 1411 | ChessUtils.NOTATION.rowConverter.search(row), 1412 | ChessUtils.NOTATION.columnConverter.search(column) 1413 | ); 1414 | }; 1415 | /** 1416 | Creates a position string from a notation object. 1417 | 1418 | @method convertNotationToPosition 1419 | @public 1420 | @static 1421 | @param {Object} notation The notation object (For example {a1:bK, b1:wQ}). 1422 | @return {String} The position string representation of the notation object. 1423 | */ 1424 | ChessUtils.convertNotationToPosition = function (notation) { 1425 | var position = ChessUtils.convertFenToPosition(ChessUtils.FEN.positions.empty), 1426 | square; 1427 | 1428 | if (ChessUtils.isValidNotation(position)) { 1429 | throw new Error('Invalid notation object "' + notation.toString() + '".'); 1430 | } 1431 | 1432 | for (square in notation) { 1433 | if (notation.hasOwnProperty(square)) { 1434 | position = 1435 | ChessUtils.replaceStringAt(position, 1436 | ChessUtils.convertNotationSquareToIndex(square), 1437 | ChessUtils.convertNotationPieceToPiece(notation[square])); 1438 | } 1439 | } 1440 | 1441 | return position; 1442 | 1443 | }; 1444 | /** 1445 | Creates a notation object from a position string. 1446 | 1447 | @method convertPositionToNotation 1448 | @public 1449 | @static 1450 | @param {String} position The position string. 1451 | @return {Object} The notation object representation of the position string. 1452 | */ 1453 | ChessUtils.convertPositionToNotation = function (position) { 1454 | var notation = {}, 1455 | i; 1456 | 1457 | if (ChessUtils.isValidPosition(position)) { 1458 | throw new Error('Invalid position string "' + position + '".'); 1459 | } 1460 | 1461 | for (i = 0; i < 64; i++) { 1462 | if (position[i] !== ChessUtils.POSITION.empty) { 1463 | notation[ChessUtils.convertIndexToNotationSquare(i)] = ChessUtils.convertPieceToNotationPiece(position[i]); 1464 | } 1465 | } 1466 | 1467 | return notation; 1468 | }; 1469 | 1470 | /* 1471 | ---------------------------------------------------------------------------- 1472 | Conversion of coordinates and connected methods 1473 | ---------------------------------------------------------------------------- 1474 | */ 1475 | /** 1476 | Checks wether a row index (0-7) and a column index (0-7) is valid. 1477 | 1478 | @method isOutOfBoard 1479 | @public 1480 | @static 1481 | @param {Integer} row 1482 | @param {Integer} column 1483 | @return {Integer} 1484 | */ 1485 | ChessUtils.isOutOfBoard = function (row, column) { 1486 | return (row < 0 || row > 7 || column < 0 || column > 7); 1487 | }; 1488 | /** 1489 | Converts an index (0-63) to a column index (0-7). No parameter checking. 1490 | 1491 | @method convertIndexToColumn 1492 | @public 1493 | @static 1494 | @param {Integer} index 1495 | @return {Integer} 1496 | */ 1497 | ChessUtils.convertIndexToColumn = function (index) { 1498 | return index % 8; 1499 | }; 1500 | /** 1501 | Converts an index (0-63) to a row index (0-7). No parameter checking. 1502 | 1503 | @method convertIndexToRow 1504 | @public 1505 | @static 1506 | @param {Integer} index 1507 | @return {Integer} 1508 | */ 1509 | ChessUtils.convertIndexToRow = function (index) { 1510 | return 7 - Math.floor(index / 8); 1511 | }; 1512 | /** 1513 | Converts a row index (0-7) and a column index (0-7) to an index (0-63). No parameter checking. 1514 | 1515 | @method convertRowColumnToIndex 1516 | @public 1517 | @static 1518 | @param {Integer} row 1519 | @param {Integer} column 1520 | @return {Integer} 1521 | */ 1522 | ChessUtils.convertRowColumnToIndex = function (row, column) { 1523 | return (7 - row) * 8 + column; 1524 | }; 1525 | 1526 | 1527 | /* 1528 | ---------------------------------------------------------------------------- 1529 | Utility functions 1530 | ---------------------------------------------------------------------------- 1531 | */ 1532 | /** 1533 | Repeats a given string (or character) x times. 1534 | Example: repeatString('0', 3) returns '000'. 1535 | (No parameter checking!) 1536 | 1537 | @method repeatString 1538 | @public 1539 | @static 1540 | @param {String} what The string to copy 1541 | @param {Integer} times The number of repeats 1542 | @return {String} The 'what' string repeated 'times' times. 1543 | */ 1544 | ChessUtils.repeatString = function (what, times) { 1545 | var helper = []; 1546 | 1547 | helper.length = times + 1; 1548 | return helper.join(what); 1549 | 1550 | }; 1551 | 1552 | /** 1553 | Replaces a character/string in a given string (or character) x times. 1554 | Example: replaceStringAt('Hong', 0, 'K') returns 'Kong', replaceStringAt('Hong', 3, 'g Kong') returns 'Hong Kong'. 1555 | (No parameter checking!) 1556 | 1557 | @method replaceStringAt 1558 | @public 1559 | @static 1560 | @param {String} inputString The string to insert the character into 1561 | @param {Integer} index The index where the insertion should happen 1562 | @param {Integer} what What to insert 1563 | @return {String} The result after replacement 1564 | */ 1565 | ChessUtils.replaceStringAt = function (inputString, index, what) { 1566 | 1567 | var a; 1568 | 1569 | a = inputString.split(''); 1570 | a[index] = what; 1571 | return a.join(''); 1572 | }; 1573 | 1574 | 1575 | if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { 1576 | module.exports.ChessUtils = ChessUtils; 1577 | } else { 1578 | window.ChessUtils = ChessUtils; 1579 | } 1580 | 1581 | }()); 1582 | -------------------------------------------------------------------------------- /js/chessboard.min.js: -------------------------------------------------------------------------------- 1 | function Chessboard(a,b){"use strict";Chessboard.ANIMATION={fadeInTime:1e3,fadeOutTime:1e3},Chessboard.CSS_PREFIX="chess_",Chessboard.CSS={pathSeparator:"_",board:{id:Chessboard.CSS_PREFIX+"board",className:Chessboard.CSS_PREFIX+"board"},square:{className:Chessboard.CSS_PREFIX+"square",lastColumn:{className:Chessboard.CSS_PREFIX+"square_last_column"},idPrefix:Chessboard.CSS_PREFIX+"square",dark:{className:Chessboard.CSS_PREFIX+"square_dark"},light:{className:Chessboard.CSS_PREFIX+"square_light"},createClassName:function(a){return" "+((a+ChessUtils.convertIndexToRow(a))%2===0?Chessboard.CSS.square.dark.className:Chessboard.CSS.square.light.className)},selected:{className:Chessboard.CSS_PREFIX+"square_selected"},validMove:{className:Chessboard.CSS_PREFIX+"square_valid_move"}},player:{black:{className:Chessboard.CSS_PREFIX+"player_"+ChessUtils.PLAYER.black.className},white:{className:Chessboard.CSS_PREFIX+"player_"+ChessUtils.PLAYER.white.className},createClassName:function(a){return"white"===a?Chessboard.CSS.player.white.className:Chessboard.CSS.player.black.className}},piece:{idPrefix:Chessboard.CSS_PREFIX+"piece",className:Chessboard.CSS_PREFIX+"piece",createClassName:function(a){return Chessboard.CSS.piece.className+"_"+a},none:{className:Chessboard.CSS_PREFIX+"piece_none"}},label:{className:Chessboard.CSS_PREFIX+"label",hidden:{className:Chessboard.CSS_PREFIX+"label_hidden"},row:{className:Chessboard.CSS_PREFIX+"label_row",reversed:{className:Chessboard.CSS_PREFIX+"label_row_reversed"}},column:{className:Chessboard.CSS_PREFIX+"label_column",reversed:{className:Chessboard.CSS_PREFIX+"label_column_reversed"}}},style:{id:Chessboard.CSS_PREFIX+"style"}};var c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z=this,A=!1,B=ChessUtils.convertFenToPosition(ChessUtils.FEN.positions.empty),C=null,D=ChessUtils.ORIENTATION.white,E={useAnimation:!0,showBoardLabels:!0,showNextMove:!0},F={onMove:null,onPieceSelected:null,onChange:null,onChanged:null},G=!1,H=!0;this.clear=function(){G||(this.setPosition(ChessUtils.FEN.positions.empty),this.setOrientation(ChessUtils.ORIENTATION.white))},this.destroy=function(){$(x).html(""),g()},this.position=function(a,b){return 0===arguments.length||"undefined"==typeof a||"string"==typeof a&&a.toLowerCase()===ChessUtils.FEN.id||"string"==typeof a&&a.toLowerCase()===ChessUtils.NOTATION.id?this.getPosition(a):void this.setPosition(a,b)},this.setPosition=function(a,b){var c=A;if(!G&&(l(),b=1===arguments.length||"undefined"==typeof b?E.useAnimation:b,"string"==typeof a&&a.toLowerCase()===ChessUtils.FEN.startId?a=ChessUtils.convertFenToPosition(ChessUtils.FEN.positions.start):"string"==typeof a&&a.toLowerCase()===ChessUtils.FEN.emptyId?a=ChessUtils.convertFenToPosition(ChessUtils.FEN.positions.empty):"string"==typeof a?-1!==a.indexOf(ChessUtils.FEN.rowSeparator)&&(a=ChessUtils.convertFenToPosition(a)):"object"==typeof a&&(a=ChessUtils.convertNotationToPosition(a)),B!==a)){if(F.hasOwnProperty("onChange")===!0&&"function"==typeof F.onChange&&!H){if(G=!0,!F.onChange(B,a))return;G=!1}A=!1,b===!0?(r(a),B=a):(B=a,q()),F.hasOwnProperty("onChanged")!==!0||"function"!=typeof F.onChanged||H||(G=!0,F.onChanged(B),G=!1),A=c}},this.getPosition=function(a){return 0!==arguments.length&&a?a.toLowerCase()===ChessUtils.FEN.id?ChessUtils.convertPositionToFen(B):a.toLowerCase()===ChessUtils.NOTATION.id?ChessUtils.convertPositionToNotation(B):void 0:B},this.move=function(a){var b,c,d=[],e=[],f=B,g=E.useAnimation;if(!G){if("boolean"==typeof arguments[arguments.length-1]?(g=arguments[arguments.length-1],c=arguments.length-1):c=arguments.length,"string"==typeof a)if(-1!==a.search("-"))for(b=0;c>b;b++)d.push(ChessUtils.convertNotationSquareToIndex(arguments[b].split("-")[0])),e.push(ChessUtils.convertNotationSquareToIndex(arguments[b].split("-")[1]));else for(b=0;c>b;b+=2)d.push(ChessUtils.convertNotationSquareToIndex(arguments[b])),e.push(ChessUtils.convertNotationSquareToIndex(arguments[b+1]));else for(b=0;c>b;b+=2)d.push(arguments[b]),e.push(arguments[b+1]);for(b=0;b',e+='
',b=0;64>b;b++)c=u(b),d=Chessboard.CSS.square.className,d+=" "+Chessboard.CSS.square.createClassName(b),b%8===7&&(d+=" "+Chessboard.CSS.square.lastColumn.className),e+='
',0===ChessUtils.convertIndexToRow(b)&&(e+='
'+ChessUtils.NOTATION.columnConverter[ChessUtils.convertIndexToColumn(b)]+"
"),7===ChessUtils.convertIndexToRow(b)&&(e+='
'+ChessUtils.NOTATION.columnConverter[ChessUtils.convertIndexToColumn(7-b)]+"
"),0===ChessUtils.convertIndexToColumn(b)&&(e+='
'+ChessUtils.NOTATION.rowConverter[ChessUtils.convertIndexToRow(b)]+"
"),7===ChessUtils.convertIndexToColumn(b)&&(e+='
'+ChessUtils.NOTATION.rowConverter[7-ChessUtils.convertIndexToRow(b)]+"
"),d=Chessboard.CSS.piece.className,d+=" "+Chessboard.CSS.piece.none.className,e+='
',e+="
";e+="
",$(x).html(e),$(x).css("display","inline-block")},f=function(){$(window).on("resize.chessEvents",h),$("div"+x+" div."+Chessboard.CSS.square.className).on("click",i)},g=function(){$(window).unbind("resize.chessEvents"),$("div"+x+" div."+Chessboard.CSS.square.className).unbind("click")},h=function(){var a,b,c,d;a=Math.floor($(x).width()/8),b=.85*a,c=Math.min(Math.max(.5*a,8),20),d=" div"+x+" div."+Chessboard.CSS.piece.className+" { font-size: "+b+"px; height: "+a+"px; } div"+x+" div."+Chessboard.CSS.label.className+" { font-size: "+c+"px; "+(E.showBoardLabels?"":"display: none;")+" }",$("#"+w()).html(d)},i=function(){var a,b,c=k($(this).context.id);if(A)if(null!==C&&y.indexOf(c)>-1)F.onMove&&(b=F.onMove({from:ChessUtils.convertIndexToNotationSquare(C),to:ChessUtils.convertIndexToNotationSquare(c)}),null!==b&&(z.setPosition(b),l()));else if(C!==c&&F.onPieceSelected)if(y=F.onPieceSelected(ChessUtils.convertIndexToNotationSquare(c)),y&&0!==y.length){if(m(c),!n(c))for(a=0;aa;a++)p(a,B[a])},r=function(a){var b;for(b=0;64>b;b++)B[b]!==a[b]&&("0"!==B[b]&&"0"!==a[b]?(p(b,a[b],!0),$(o(b)).animate({opacity:"1"},Chessboard.ANIMATION.fadeInTime)):"0"===B[b]?(p(b,a[b],!0),$(o(b)).animate({opacity:"1"},Chessboard.ANIMATION.fadeInTime)):"0"===a[b]&&p(b,a[b],!0))},s=function(){return $(x).attr("id")+"_"},t=function(){return s()+Chessboard.CSS.board.id},u=function(a){return s()+Chessboard.CSS.square.idPrefix+"_"+(D===ChessUtils.ORIENTATION.white?a:63-a)},v=function(a){return s()+Chessboard.CSS.piece.idPrefix+"_"+(D===ChessUtils.ORIENTATION.white?a:63-a)},w=function(){return s()+Chessboard.CSS.style.id},this.setPosition(c(a,b)),h(),D===ChessUtils.ORIENTATION.black&&(this.setOrientation(ChessUtils.ORIENTATION.flip),D=ChessUtils.ORIENTATION.black),H=!1}!function(){"use strict";var a={};a.PLAYER={black:{code:"b",notation:"b",className:"black"},white:{code:"w",notation:"w",className:"white"}},a.ORIENTATION={white:"w",black:"b",flip:"flip"},a.PIECE={none:"0",pawn:"p",rook:"r",knight:"n",bishop:"b",queen:"q",king:"k",codeToPieceName:{p:"pawn",r:"rook",n:"knight",b:"bishop",q:"queen",k:"king"}},a.POSITION={empty:"0",piece:{pawn:"p",rook:"r",knight:"n",bishop:"b",queen:"q",king:"k"},validator:/^[kqrbnpKQRNBP0]+$/},a.NOTATION={id:"notation",positionValidator:/^[a-h][1-8]$/,pieceValidator:/^[bw][KQRNBP]$/,columns:String.prototype.split.call("abcdefgh",""),rows:String.prototype.split.call("12345678",""),columnConverter:"abcdefgh",rowConverter:"12345678"},a.FEN={id:"fen",startId:"start",emptyId:"empty",rowValidator:/^[kqrbnpKQRNBP1-8]+$/,rowSeparator:"/",positions:{empty:"8/8/8/8/8/8/8/8",start:"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR",ruyLopez:"r1bqkbnr/pppp1ppp/2n5/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R"}},a.isPieceWhite=function(b){return b.toUpperCase()===b&&b!==a.PIECE.none},a.isPieceBlack=function(a){return a.toUpperCase()!==a},a.getPieceForPlayer=function(b,c){return c===a.PLAYER.white.code?b.toUpperCase():b.toLowerCase()},a.getPlayerNameFromPiece=function(b){return a.isPieceWhite(b)?a.PLAYER.white.className:a.isPieceBlack(b)?a.PLAYER.black.className:void 0},a.getPlayerCodeFromPiece=function(b){return a.isPieceWhite(b)?a.PLAYER.white.code:a.isPieceBlack(b)?a.PLAYER.black.code:void 0},a.isValidPosition=function(b){return"string"!=typeof b?!1:64!==b.length||-1!==b.search(a.POSITION.validator)?!1:void 0},a.isValidFen=function(b){var c,d;if("string"!=typeof b)return!1;if(b=b.split(" ")[0],c=b.split("/"),8!==c.length)return!1;for(d=0;8>d;d++)if(""===c[d]||c[d].length>8||-1!==c[d].search(a.FEN.rowValidator))return!1;return!0},a.isValidNotationPosition=function(b){return"string"!=typeof b?!1:-1!==b.search(a.NOTATION.positionValidator)},a.isValidNotationPiece=function(b){return"string"!=typeof b?!1:-1!==b.search(a.NOTATION.pieceValidator)},a.isValidNotation=function(b){var c;if("object"!=typeof b)return!1;for(c in b)if(b.hasOwnProperty(c)&&(!a.isValidNotationPosition(c)||!a.isValidNotationPiece(b[c])))return!1;return!0},a.convertFenToPosition=function(b){var c,d;if(a.isValidFen(b))throw new Error('Invalid fen string "'+b+'".');for(d=b.split(" ")[0],c=1;8>=c;c++)d=d.replace(new RegExp(c,"g"),a.repeatString("0",c));return d=d.replace(new RegExp(a.FEN.rowSeparator,"g"),"")},a.convertPositionToFen=function(b){var c,d="";if(a.isValidPosition(b))throw new Error('Invalid position string "'+b+'".');for(d=b.substr(0,8),c=1;8>c;c++)d+=a.FEN.rowSeparator+b.substr(8*c,8);for(c=8;c>0;c--)d=d.replace(new RegExp(a.repeatString("0",c),"g"),c);return d},a.convertPieceToNotationPiece=function(b){return(a.isPieceWhite(b)?a.PLAYER.white.notation:a.PLAYER.black.notation)+b.toUpperCase()},a.convertNotationPieceToPiece=function(b){return b.split("")[0]===a.PLAYER.white.notation?b.split("")[1].toUpperCase():b.split("")[1].toLowerCase()},a.convertIndexToNotationSquare=function(b){return a.NOTATION.columns[a.convertIndexToColumn(b)]+a.NOTATION.rows[a.convertIndexToRow(b)]},a.convertNotationSquareToIndex=function(b){var c,d;return"+"===b[b.length-1]&&(b=b.substring(0,b.length-1)),d=b.split("")[b.length-2],c=b.split("")[b.length-1],a.convertRowColumnToIndex(a.NOTATION.rowConverter.search(c),a.NOTATION.columnConverter.search(d))},a.convertNotationToPosition=function(b){var c,d=a.convertFenToPosition(a.FEN.positions.empty);if(a.isValidNotation(d))throw new Error('Invalid notation object "'+b.toString()+'".');for(c in b)b.hasOwnProperty(c)&&(d=a.replaceStringAt(d,a.convertNotationSquareToIndex(c),a.convertNotationPieceToPiece(b[c])));return d},a.convertPositionToNotation=function(b){var c,d={};if(a.isValidPosition(b))throw new Error('Invalid position string "'+b+'".');for(c=0;64>c;c++)b[c]!==a.POSITION.empty&&(d[a.convertIndexToNotationSquare(c)]=a.convertPieceToNotationPiece(b[c]));return d},a.isOutOfBoard=function(a,b){return 0>a||a>7||0>b||b>7},a.convertIndexToColumn=function(a){return a%8},a.convertIndexToRow=function(a){return 7-Math.floor(a/8)},a.convertRowColumnToIndex=function(a,b){return 8*(7-a)+b},a.repeatString=function(a,b){var c=[];return c.length=b+1,c.join(a)},a.replaceStringAt=function(a,b,c){var d;return d=a.split(""),d[b]=c,d.join("")},"undefined"!=typeof module&&"undefined"!=typeof module.exports?module.exports.ChessUtils=a:window.ChessUtils=a}(); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chessboard-js", 3 | "dependencies": {}, 4 | "devDependencies": { 5 | "grunt": "~0.4.1", 6 | "grunt-contrib-cssmin": "~0.9.0", 7 | "grunt-contrib-uglify": "~0.4.0", 8 | "time-grunt": "~0.3.1", 9 | 10 | "selenium-webdriver": "~2.41.0", 11 | 12 | "mocha": "~1.18.2", 13 | "chai": "~1.9.1" 14 | }, 15 | "engines": { 16 | "node": ">=0.10.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test/manual/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chessboardjs-manual-testing-page", 3 | "version": "0.1.0", 4 | "dependencies": { 5 | "jquery": "~2.1.0", 6 | "bootstrap": "~3.1.1", 7 | "chess.js": "git://github.com/jhlywa/chess.js" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/manual/js/app.js: -------------------------------------------------------------------------------- 1 | /*jslint node: true, plusplus: true, devel: true, browser: true*/ 2 | /*globals $, Chess, Chessboard, ChessUtils*/ 3 | 4 | 'use strict'; 5 | 6 | var chess, 7 | board; 8 | 9 | function updateGameInfo(status) { 10 | $('#info-status').html(status); 11 | $('#info-fen').html(chess.fen()); 12 | $('#info-pgn').html(chess.pgn()); 13 | } 14 | 15 | function resetGame() { 16 | board.setPosition(ChessUtils.FEN.startId); 17 | chess.reset(); 18 | 19 | updateGameInfo('Next player is white.'); 20 | } 21 | 22 | function pieceMove(move) { 23 | 24 | var nextPlayer, 25 | status, 26 | chessMove = chess.move({ 27 | from: move.from, 28 | to: move.to, 29 | promotion: 'q' 30 | }); 31 | 32 | 33 | nextPlayer = 'white'; 34 | if (chess.turn() === 'b') { 35 | nextPlayer = 'black'; 36 | } 37 | 38 | if (chessMove !== null) { 39 | if (chess.in_checkmate() === true) { 40 | status = 'CHECKMATE! Player ' + nextPlayer + ' lost.'; 41 | } else if (chess.in_draw() === true) { 42 | status = 'DRAW!'; 43 | } else { 44 | status = 'Next player is ' + nextPlayer + '.'; 45 | 46 | if (chess.in_check() === true) { 47 | status = 'CHECK! ' + status; 48 | } 49 | } 50 | 51 | updateGameInfo(status); 52 | } 53 | 54 | return chess.fen(); 55 | } 56 | 57 | function pieceSelected(notationSquare) { 58 | var i, 59 | movesNotation, 60 | movesPosition = []; 61 | 62 | movesNotation = chess.moves({square: notationSquare, verbose: true}); 63 | for (i = 0; i < movesNotation.length; i++) { 64 | movesPosition.push(ChessUtils.convertNotationSquareToIndex(movesNotation[i].to)); 65 | } 66 | return movesPosition; 67 | } 68 | 69 | 70 | function updateUserInputEnabledInfo() { 71 | $('#isUserInputEnabled').html('User input is ' + (board.isUserInputEnabled() ? 'enabled' : 'disabled')); 72 | } 73 | 74 | $(document).ready(function () { 75 | chess = new Chess(); 76 | board = new Chessboard('board', { 77 | position: ChessUtils.FEN.startId, 78 | eventHandlers: { 79 | onPieceSelected: pieceSelected, 80 | onMove: pieceMove 81 | } 82 | }); 83 | 84 | $('#btn-reset-game').click(function () { 85 | resetGame(); 86 | }); 87 | resetGame(); 88 | 89 | //UserInputEnabled 90 | updateUserInputEnabledInfo(); 91 | $('#btnEnableUserInput').click(function () { 92 | board.enableUserInput(true); 93 | updateUserInputEnabledInfo(); 94 | }); 95 | $('#btnDisableUserInput').click(function () { 96 | board.enableUserInput(false); 97 | updateUserInputEnabledInfo(); 98 | }); 99 | 100 | }); 101 | 102 | 103 | -------------------------------------------------------------------------------- /test/manual/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ChessboardJS - Manual tesing page 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 |
26 |
27 |

28 |

Public interface

29 |
this.enableUserInput = function (enabled)
30 |
31 | 34 | 37 |
38 |
this.isUserInputEnabled = function ()
39 | 40 | 41 | 42 | 43 |

44 |
45 |
46 |
47 |
48 |
49 |
50 | Game information 51 |
52 |
53 |
    54 |
  • Status: 55 | Restart game 56 |
  • 57 |
  • Fen:
  • 58 |
  • Pgn:
  • 59 |
60 |
61 |
62 |
63 |
64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /test/mocha/chess-utils-tests.js: -------------------------------------------------------------------------------- 1 | /*jslint node: true, plusplus: true*/ 2 | /*globals chai, describe,it */ 3 | /*globals ChessUtils*/ 4 | 5 | 'use strict'; 6 | 7 | if (typeof chai === 'undefined') { var chai = require('chai'); } 8 | if (typeof ChessUtils === 'undefined') { var ChessUtils = require('../js/chessboard.js').ChessUtils; } 9 | 10 | var expect = chai.expect; 11 | 12 | describe('ChessUtils', function () { 13 | 14 | describe('#repeatString()', function () { 15 | it('should copy "a" 3 times', function () { 16 | expect(ChessUtils.repeatString('a', 3)).to.equal('aaa'); 17 | }); 18 | it('should copy "Hong" 2 times', function () { 19 | expect(ChessUtils.repeatString('Hong', 2)).to.equal('HongHong'); 20 | }); 21 | }); 22 | 23 | describe('#replaceStringAt()', function () { 24 | it('should change "Hong" to "Kong"', function () { 25 | expect(ChessUtils.replaceStringAt('Hong', 0, 'K')).to.equal('Kong'); 26 | }); 27 | it('should change "Hong" to "Hong Kong"', function () { 28 | expect(ChessUtils.replaceStringAt('Hong', 3, 'g Kong')).to.equal('Hong Kong'); 29 | }); 30 | }); 31 | 32 | describe('#convertIndexToColumn()', function () { 33 | 34 | it('should return column=7 to index=63', function () { 35 | expect(ChessUtils.convertIndexToColumn(63)).to.equal(7); 36 | }); 37 | 38 | it('should return 0 for all indexes below 8', function () { 39 | var i; 40 | 41 | for (i = 0; i < 8; i++) { 42 | expect(ChessUtils.convertIndexToColumn(i)).to.equal(i); 43 | } 44 | }); 45 | }); 46 | 47 | }); -------------------------------------------------------------------------------- /test/mocha/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ChessUtils tests 6 | 7 | 8 | 9 | 10 |
11 |
12 | 13 | 14 | 15 | 18 | 19 | 22 | 23 | -------------------------------------------------------------------------------- /test/selenium/selenium-tests.js: -------------------------------------------------------------------------------- 1 | /*jslint node: true, plusplus: true*/ 2 | 3 | 'use strict'; 4 | 5 | var chai = require('chai'); 6 | var ChessUtils = require('../../js/chessboard.js').ChessUtils; 7 | var expect = chai.expect; 8 | var test = require('selenium-webdriver/testing'); 9 | var webdriver = require('selenium-webdriver'); 10 | var driver; 11 | 12 | test.describe('ChessBoard', function () { 13 | 14 | test.before(function () { 15 | driver = new webdriver.Builder() 16 | .withCapabilities(webdriver.Capabilities.chrome()) 17 | .build(); 18 | 19 | webdriver.promise.controlFlow().on('uncaughtException', function (e) { 20 | console.error('Unhandled error: ' + e); 21 | }); 22 | }); 23 | 24 | test.describe('start', function () { 25 | 26 | test.beforeEach(function () { 27 | driver.get('http://localhost:8080/test/selenium/test.html'); 28 | driver.executeScript('board.clear()'); 29 | 30 | }); 31 | 32 | test.it('should setup board to opening position', function () { 33 | var i; 34 | 35 | driver.executeScript('board.setPosition(ChessUtils.FEN.startId)'); 36 | driver.executeScript('return board.getPosition(ChessUtils.FEN.id)') 37 | .then(function (position) { 38 | expect(position).to.be.a('string'); 39 | expect(position).to.equal(ChessUtils.FEN.positions.start); 40 | }); 41 | 42 | }); 43 | }); 44 | 45 | test.after(function () { 46 | driver.quit(); 47 | }); 48 | 49 | }); -------------------------------------------------------------------------------- /test/selenium/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ChessboardJS - A responsive, mobile-first javascript chessboard library. 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /yuidoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chessboard-js", 3 | "description": "A responsive mobile-first javascript chessboard library.", 4 | "version": "0.1.0", 5 | "url": "https://github.com/caustique/chessboard-js", 6 | "options": { 7 | "linkNatives": "true", 8 | "outdir": "./doc", 9 | "paths": ["./js"] 10 | } 11 | } --------------------------------------------------------------------------------