├── .gitignore ├── board_test.py ├── chess_engine.py ├── flask_app.py ├── readme.txt ├── static ├── libs │ └── chessboard │ │ ├── LICENSE.txt │ │ ├── css │ │ ├── chessboard-0.3.0.css │ │ └── chessboard-0.3.0.min.css │ │ ├── img │ │ └── chesspieces │ │ │ └── wikipedia │ │ │ ├── bB.png │ │ │ ├── bK.png │ │ │ ├── bN.png │ │ │ ├── bP.png │ │ │ ├── bQ.png │ │ │ ├── bR.png │ │ │ ├── wB.png │ │ │ ├── wK.png │ │ │ ├── wN.png │ │ │ ├── wP.png │ │ │ ├── wQ.png │ │ │ └── wR.png │ │ └── js │ │ ├── chessboard-0.3.0.js │ │ └── chessboard-0.3.0.min.js ├── scripts.js └── style.css └── templates └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled source # 2 | ################### 3 | *.com 4 | *.class 5 | *.dll 6 | *.exe 7 | *.o 8 | *.so 9 | *.pyc 10 | 11 | # Packages # 12 | ############ 13 | # it's better to unpack these files and commit the raw source 14 | # git has its own built in compression methods 15 | *.7z 16 | *.dmg 17 | *.gz 18 | *.iso 19 | *.rar 20 | #*.tar 21 | *.zip 22 | 23 | # Logs and databases # 24 | ###################### 25 | *.log 26 | *.sqlite 27 | 28 | # OS generated files # 29 | ###################### 30 | .DS_Store 31 | ehthumbs.db 32 | Icon 33 | Thumbs.db 34 | .tmtags 35 | .idea 36 | tags 37 | vendor.tags 38 | tmtagsHistory 39 | *.sublime-project 40 | *.sublime-workspace 41 | .bundle 42 | notes/ -------------------------------------------------------------------------------- /board_test.py: -------------------------------------------------------------------------------- 1 | import chess 2 | 3 | board = chess.Board() 4 | 5 | for i in board.legal_moves: 6 | print(i) 7 | 8 | board.push_san("e4") 9 | board.push_san("e5") 10 | 11 | print(board) 12 | 13 | -------------------------------------------------------------------------------- /chess_engine.py: -------------------------------------------------------------------------------- 1 | import chess 2 | import random 3 | import signal 4 | import time 5 | import cProfile 6 | 7 | class Engine: 8 | 9 | def __init__(self, fen): 10 | self.board = chess.Board() 11 | self.MAX_DEPTH = 60 12 | self.piece_values = { 13 | # pawn 14 | 1:100, 15 | # bishop 16 | 2:310, 17 | # knight 18 | 3:300, 19 | # rook 20 | 4:500, 21 | # queen 22 | 5:900, 23 | # king 24 | 6:99999 25 | } 26 | self.square_table = square_table = { 27 | 1: [ 28 | 0, 0, 0, 0, 0, 0, 0, 0, 29 | 50, 50, 50, 50, 50, 50, 50, 50, 30 | 10, 10, 20, 30, 30, 20, 10, 10, 31 | 5, 5, 10, 25, 25, 10, 5, 5, 32 | 0, 0, 0, 20, 20, 0, 0, 0, 33 | 5, -5, -10, 0, 0, -10, -5, 5, 34 | 5, 10, 10, -20, -20, 10, 10, 5, 35 | 0, 0, 0, 0, 0, 0, 0, 0 36 | ], 37 | 2: [ 38 | -50, -40, -30, -30, -30, -30, -40, -50, 39 | -40, -20, 0, 0, 0, 0, -20, -40, 40 | -30, 0, 10, 15, 15, 10, 0, -30, 41 | -30, 5, 15, 20, 20, 15, 5, -30, 42 | -30, 0, 15, 20, 20, 15, 0, -30, 43 | -30, 5, 10, 15, 15, 10, 5, -30, 44 | -40, -20, 0, 5, 5, 0, -20, -40, 45 | -50, -40, -30, -30, -30, -30, -40, -50, 46 | ], 47 | 3: [ 48 | -20, -10, -10, -10, -10, -10, -10, -20, 49 | -10, 0, 0, 0, 0, 0, 0, -10, 50 | -10, 0, 5, 10, 10, 5, 0, -10, 51 | -10, 5, 5, 10, 10, 5, 5, -10, 52 | -10, 0, 10, 10, 10, 10, 0, -10, 53 | -10, 10, 10, 10, 10, 10, 10, -10, 54 | -10, 5, 0, 0, 0, 0, 5, -10, 55 | -20, -10, -10, -10, -10, -10, -10, -20, 56 | ], 57 | 4: [ 58 | 0, 0, 0, 0, 0, 0, 0, 0, 59 | 5, 10, 10, 10, 10, 10, 10, 5, 60 | -5, 0, 0, 0, 0, 0, 0, -5, 61 | -5, 0, 0, 0, 0, 0, 0, -5, 62 | -5, 0, 0, 0, 0, 0, 0, -5, 63 | -5, 0, 0, 0, 0, 0, 0, -5, 64 | -5, 0, 0, 0, 0, 0, 0, -5, 65 | 0, 0, 0, 5, 5, 0, 0, 0 66 | ], 67 | 5: [ 68 | -20, -10, -10, -5, -5, -10, -10, -20, 69 | -10, 0, 0, 0, 0, 0, 0, -10, 70 | -10, 0, 5, 5, 5, 5, 0, -10, 71 | -5, 0, 5, 5, 5, 5, 0, -5, 72 | 0, 0, 5, 5, 5, 5, 0, -5, 73 | -10, 5, 5, 5, 5, 5, 0, -10, 74 | -10, 0, 5, 0, 0, 0, 0, -10, 75 | -20, -10, -10, -5, -5, -10, -10, -20 76 | ], 77 | 6: [ 78 | -30, -40, -40, -50, -50, -40, -40, -30, 79 | -30, -40, -40, -50, -50, -40, -40, -30, 80 | -30, -40, -40, -50, -50, -40, -40, -30, 81 | -30, -40, -40, -50, -50, -40, -40, -30, 82 | -20, -30, -30, -40, -40, -30, -30, -20, 83 | -10, -20, -20, -20, -20, -20, -20, -10, 84 | 20, 20, 0, 0, 0, 0, 20, 20, 85 | 20, 30, 10, 0, 0, 10, 30, 20 86 | ] 87 | } 88 | self.board.set_fen(fen) 89 | self.leaves_reached = 0 90 | 91 | 92 | def random_response(self): 93 | response = random.choice(list(self.board.legal_moves)) 94 | return str(response) 95 | 96 | 97 | def material_eval(self): 98 | score = 0 99 | # iterate through the pieces 100 | for i in range(1, 7): 101 | score += len(self.board.pieces(i, chess.WHITE)) * self.piece_values[i] 102 | score -= len(self.board.pieces(i, chess.BLACK)) * self.piece_values[i] 103 | 104 | return score 105 | 106 | 107 | def position_eval(self): 108 | score = 0 109 | # iterate through the pieces 110 | for i in range(1, 7): 111 | # eval white pieces 112 | w_squares = self.board.pieces(i, chess.WHITE) 113 | score += len(w_squares) * self.piece_values[i] 114 | for square in w_squares: 115 | score += self.square_table[i][-square] 116 | 117 | b_squares = self.board.pieces(i, chess.BLACK) 118 | score -= len(b_squares) * self.piece_values[i] 119 | for square in b_squares: 120 | score -= self.square_table[i][square] 121 | 122 | return score 123 | 124 | 125 | 126 | def minimax(self, depth, move, maximiser): 127 | if depth == 0: 128 | # return move, self.material_eval() 129 | return move, self.position_eval() 130 | 131 | if maximiser: 132 | best_move = None 133 | best_score = -9999 134 | 135 | moves = list(self.board.legal_moves) 136 | 137 | for move in moves: 138 | self.leaves_reached += 1 139 | self.board.push(move) 140 | new_move, new_score = self.minimax(depth - 1, move, False) 141 | if new_score > best_score: 142 | best_score, best_move = new_score, move 143 | self.board.pop() 144 | 145 | return best_move, best_score 146 | 147 | if not maximiser: 148 | best_move = None 149 | best_score = 9999 150 | 151 | moves = list(self.board.legal_moves) 152 | 153 | for move in moves: 154 | self.leaves_reached += 1 155 | self.board.push(move) 156 | new_move, new_score = self.minimax(depth - 1, move, True) 157 | if new_score < best_score: 158 | best_score, best_move = new_score, move 159 | self.board.pop() 160 | 161 | return best_move, best_score 162 | 163 | 164 | def alpha_beta(self, depth_neg, depth_pos, move, alpha, beta, prev_moves, maximiser): 165 | 166 | move_sequence = [] 167 | 168 | # check if we're at the final search depth 169 | if depth_neg == 0: 170 | # return move, self.material_eval() 171 | move_sequence.append(move) 172 | return move_sequence, self.position_eval() 173 | 174 | 175 | moves = list(self.board.legal_moves) 176 | # moves = self.order_moves() 177 | 178 | # if there are no legal moves, check for checkmate / stalemate 179 | if not moves: 180 | if self.board.is_checkmate(): 181 | if self.board.result() == "1-0": 182 | move_sequence.append(move) 183 | return move_sequence, 1000000 184 | elif self.board.result() == "0-1": 185 | move_sequence.append(move) 186 | return move_sequence, -1000000 187 | else: 188 | move_sequence.append(move) 189 | return move_sequence, 0 190 | 191 | # initialise best move variables. What are these used for again? I need to simplify the logic here. 192 | best_move = None 193 | best_score = -10000001 if maximiser else 10000001 194 | 195 | # put the last calculated best move in first place of the list. Hopefully this improves pruning. 196 | if prev_moves and len(prev_moves) >= depth_neg: 197 | if depth_neg == 4 and not self.board.turn: 198 | print(prev_moves[depth_neg - 1]) 199 | if prev_moves[depth_neg - 1] in moves: 200 | # if prev_moves[depth_neg - 1] in self.board.legal_moves: 201 | # if not self.board.turn: 202 | # print(prev_moves[depth_neg - 1]) 203 | moves.insert(0, prev_moves[depth_neg - 1]) 204 | 205 | 206 | if maximiser: 207 | for move in moves: 208 | self.leaves_reached += 1 209 | 210 | # get score of the new move, record what it is 211 | self.board.push(move) 212 | new_sequence, new_score = self.alpha_beta(depth_neg - 1, depth_pos + 1, move, alpha, beta, prev_moves, False) 213 | self.board.pop() 214 | 215 | # Check whether the new score is better than the best score. If so, replace the best score. 216 | if new_score > best_score: 217 | move_sequence = new_sequence 218 | best_score, best_move = new_score, move 219 | 220 | # Check whether the new score is better than the beta. If it is, return and break the loop. 221 | # Need to rethink the check against best here. 222 | if new_score >= beta: 223 | # self.check_against_best(best_move, best_score, depth_pos, True) 224 | move_sequence.append(best_move) 225 | return move_sequence, best_score 226 | # Update alpha - upper bound 227 | if new_score > alpha: 228 | alpha = new_score 229 | # return the best of the results 230 | # self.check_against_best(best_move, best_score, depth_pos, True) 231 | move_sequence.append(best_move) 232 | return move_sequence, best_score 233 | 234 | if not maximiser: 235 | for move in moves: 236 | self.leaves_reached += 1 237 | 238 | # get score of the new move, record what it is 239 | self.board.push(move) 240 | new_sequence, new_score = self.alpha_beta(depth_neg - 1, depth_pos + 1, move, alpha, beta, prev_moves, True) 241 | self.board.pop() 242 | 243 | # Check whether the new score is better than the best score. If so, replace the best score. 244 | if new_score < best_score: 245 | move_sequence = new_sequence 246 | best_score, best_move = new_score, move 247 | 248 | # Check whether the new score is better than the alpha. If it is, return and break the loop 249 | if new_score <= alpha: 250 | # self.check_against_best(best_move, best_score, depth_pos, False) 251 | move_sequence.append(best_move) 252 | return move_sequence, best_score 253 | 254 | # update beta - lower bound 255 | if new_score < beta: 256 | beta = new_score 257 | 258 | # return the best of the results 259 | # self.check_against_best(best_move, best_score, depth_pos, False) 260 | move_sequence.append(best_move) 261 | return move_sequence, best_score 262 | 263 | def calculate_minimax(self, depth): 264 | # This shows up true for white & false for black 265 | maximiser = self.board.turn 266 | 267 | best_move, best_score = self.minimax(depth, None, maximiser) 268 | 269 | return str(best_move) 270 | 271 | 272 | def calculate_ab(self, depth): 273 | maximiser = self.board.turn 274 | 275 | move_sequence, best_score = self.alpha_beta(depth, 0, None, -10000001, 10000001, None, maximiser) 276 | for i in range(1, len(move_sequence)): 277 | print("move", move_sequence[-i]) 278 | return str(move_sequence[-1]) 279 | 280 | 281 | def total_leaves(self): 282 | leaves = self.leaves_reached 283 | self.leaves_reached = 0 284 | return leaves 285 | 286 | 287 | def order_moves(self): 288 | moves = list(self.board.legal_moves) 289 | scores = [] 290 | for move in moves: 291 | self.board.push(move) 292 | # scores.append(self.material_eval()) 293 | scores.append(self.material_eval()) 294 | self.board.pop() 295 | sorted_indexes = sorted(range(len(scores)), key=lambda i: scores[i], reverse=False) 296 | return [moves[i] for i in sorted_indexes] 297 | 298 | 299 | def iterative_deepening(self, depth): 300 | # depth_neg, depth_pos, move, alpha, beta, prev_moves, maximiser) 301 | move_list, score = self.alpha_beta(1, 0, None, -10000001, 10000001, None, self.board.turn) 302 | for i in range(2, depth + 1): 303 | print("Iteration", i) 304 | move_list, score = self.alpha_beta(i, 0, None, -10000001, 10000001, move_list, self.board.turn) 305 | print("Depth calculated:", len(move_list)) 306 | return str(move_list[-1]) 307 | 308 | 309 | 310 | # This is being used for testing at the moment, which is why there is so much commented code. 311 | # Will move to a standalone testing script when I get the chance. 312 | if __name__=="__main__": 313 | 314 | fen = "r2qkbr1/ppp1pppp/2n1b2n/8/8/5P2/PPPP2PP/RNB1KBNR b KQq - 0 6" 315 | 316 | newengine = Engine(fen) 317 | 318 | 319 | # squares = newengine.board.pieces(1, chess.WHITE) 320 | # for square in squares: 321 | # print (square) 322 | # print(squares) 323 | 324 | # print(newengine.board) 325 | # print(newengine.order_moves()) 326 | 327 | # print(newengine.material_eval()) 328 | # print(newengine.lazy_eval()) 329 | 330 | # start_time = time.time() 331 | # print(newengine.calculate(3)) 332 | # print(newengine.total_leaves()) 333 | # print("Time taken:", time.time() - start_time) 334 | 335 | start_time = time.time() 336 | print(newengine.calculate_ab(4)) 337 | print(newengine.total_leaves()) 338 | print("Time taken:", time.time() - start_time) 339 | 340 | start_time = time.time() 341 | print(newengine.iterative_deepening(4)) 342 | print(newengine.total_leaves()) 343 | print("Time taken:", time.time() - start_time) 344 | # cProfile.run('newengine.calculate(3)') 345 | # 346 | # cProfile.run('newengine.calculate_ab(3)') 347 | 348 | 349 | # print(newengine.board) -------------------------------------------------------------------------------- /flask_app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, render_template 2 | from chess_engine import * 3 | 4 | app = Flask(__name__) 5 | 6 | @app.route('/') 7 | def index(): 8 | return render_template("index.html") 9 | 10 | 11 | @app.route('/move///') 12 | def get_move(depth, fen): 13 | print(depth) 14 | print("Calculating...") 15 | engine = Engine(fen) 16 | move = engine.iterative_deepening(depth - 1) 17 | print("Move found!", move) 18 | print() 19 | return move 20 | 21 | 22 | @app.route('/test/') 23 | def test_get(tester): 24 | return tester 25 | 26 | 27 | if __name__ == '__main__': 28 | app.run(debug=True) -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | This is a simple chess engine/interface created using flask. 2 | It uses chessboard.js and chess.js for the logic of the frontend chessboard, and python chess for the 3 | logic of the backend chessboard. All calculation is done on the backend using python. 4 | 5 | In order to run this application on your own machine, please install flask and python chess. 6 | 7 | Install flask by running: 8 | pip install flask 9 | 10 | Install python chess by running: 11 | pip install python-chess[uci,gaviota] -------------------------------------------------------------------------------- /static/libs/chessboard/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2013 Chris Oakman 2 | http://chessboardjs.com/ 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /static/libs/chessboard/css/chessboard-0.3.0.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * chessboard.js v0.3.0 3 | * 4 | * Copyright 2013 Chris Oakman 5 | * Released under the MIT license 6 | * https://github.com/oakmac/chessboardjs/blob/master/LICENSE 7 | * 8 | * Date: 10 Aug 2013 9 | */ 10 | 11 | /* clearfix */ 12 | .clearfix-7da63 { 13 | clear: both; 14 | } 15 | 16 | /* board */ 17 | .board-b72b1 { 18 | border: 2px solid #404040; 19 | -moz-box-sizing: content-box; 20 | box-sizing: content-box; 21 | } 22 | 23 | /* square */ 24 | .square-55d63 { 25 | float: left; 26 | position: relative; 27 | 28 | /* disable any native browser highlighting */ 29 | -webkit-touch-callout: none; 30 | -webkit-user-select: none; 31 | -khtml-user-select: none; 32 | -moz-user-select: none; 33 | -ms-user-select: none; 34 | user-select: none; 35 | } 36 | 37 | /* white square */ 38 | .white-1e1d7 { 39 | background-color: #f0d9b5; 40 | color: #b58863; 41 | } 42 | 43 | /* black square */ 44 | .black-3c85d { 45 | background-color: #b58863; 46 | color: #f0d9b5; 47 | } 48 | 49 | /* highlighted square */ 50 | .highlight1-32417, .highlight2-9c5d2 { 51 | -webkit-box-shadow: inset 0 0 3px 3px yellow; 52 | -moz-box-shadow: inset 0 0 3px 3px yellow; 53 | box-shadow: inset 0 0 3px 3px yellow; 54 | } 55 | 56 | /* notation */ 57 | .notation-322f9 { 58 | cursor: default; 59 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 60 | font-size: 14px; 61 | position: absolute; 62 | } 63 | .alpha-d2270 { 64 | bottom: 1px; 65 | right: 3px; 66 | } 67 | .numeric-fc462 { 68 | top: 2px; 69 | left: 2px; 70 | } -------------------------------------------------------------------------------- /static/libs/chessboard/css/chessboard-0.3.0.min.css: -------------------------------------------------------------------------------- 1 | /*! chessboard.js v0.3.0 | (c) 2013 Chris Oakman | MIT License chessboardjs.com/license */ 2 | .clearfix-7da63{clear:both}.board-b72b1{border:2px solid #404040;-moz-box-sizing:content-box;box-sizing:content-box}.square-55d63{float:left;position:relative;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.white-1e1d7{background-color:#f0d9b5;color:#b58863}.black-3c85d{background-color:#b58863;color:#f0d9b5}.highlight1-32417,.highlight2-9c5d2{-webkit-box-shadow:inset 0 0 3px 3px yellow;-moz-box-shadow:inset 0 0 3px 3px yellow;box-shadow:inset 0 0 3px 3px yellow}.notation-322f9{cursor:default;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;position:absolute}.alpha-d2270{bottom:1px;right:3px}.numeric-fc462{top:2px;left:2px} -------------------------------------------------------------------------------- /static/libs/chessboard/img/chesspieces/wikipedia/bB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenloop/FlaskChess/2db72359d176cda9cc81d015cd3df8c2de5f5c13/static/libs/chessboard/img/chesspieces/wikipedia/bB.png -------------------------------------------------------------------------------- /static/libs/chessboard/img/chesspieces/wikipedia/bK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenloop/FlaskChess/2db72359d176cda9cc81d015cd3df8c2de5f5c13/static/libs/chessboard/img/chesspieces/wikipedia/bK.png -------------------------------------------------------------------------------- /static/libs/chessboard/img/chesspieces/wikipedia/bN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenloop/FlaskChess/2db72359d176cda9cc81d015cd3df8c2de5f5c13/static/libs/chessboard/img/chesspieces/wikipedia/bN.png -------------------------------------------------------------------------------- /static/libs/chessboard/img/chesspieces/wikipedia/bP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenloop/FlaskChess/2db72359d176cda9cc81d015cd3df8c2de5f5c13/static/libs/chessboard/img/chesspieces/wikipedia/bP.png -------------------------------------------------------------------------------- /static/libs/chessboard/img/chesspieces/wikipedia/bQ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenloop/FlaskChess/2db72359d176cda9cc81d015cd3df8c2de5f5c13/static/libs/chessboard/img/chesspieces/wikipedia/bQ.png -------------------------------------------------------------------------------- /static/libs/chessboard/img/chesspieces/wikipedia/bR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenloop/FlaskChess/2db72359d176cda9cc81d015cd3df8c2de5f5c13/static/libs/chessboard/img/chesspieces/wikipedia/bR.png -------------------------------------------------------------------------------- /static/libs/chessboard/img/chesspieces/wikipedia/wB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenloop/FlaskChess/2db72359d176cda9cc81d015cd3df8c2de5f5c13/static/libs/chessboard/img/chesspieces/wikipedia/wB.png -------------------------------------------------------------------------------- /static/libs/chessboard/img/chesspieces/wikipedia/wK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenloop/FlaskChess/2db72359d176cda9cc81d015cd3df8c2de5f5c13/static/libs/chessboard/img/chesspieces/wikipedia/wK.png -------------------------------------------------------------------------------- /static/libs/chessboard/img/chesspieces/wikipedia/wN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenloop/FlaskChess/2db72359d176cda9cc81d015cd3df8c2de5f5c13/static/libs/chessboard/img/chesspieces/wikipedia/wN.png -------------------------------------------------------------------------------- /static/libs/chessboard/img/chesspieces/wikipedia/wP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenloop/FlaskChess/2db72359d176cda9cc81d015cd3df8c2de5f5c13/static/libs/chessboard/img/chesspieces/wikipedia/wP.png -------------------------------------------------------------------------------- /static/libs/chessboard/img/chesspieces/wikipedia/wQ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenloop/FlaskChess/2db72359d176cda9cc81d015cd3df8c2de5f5c13/static/libs/chessboard/img/chesspieces/wikipedia/wQ.png -------------------------------------------------------------------------------- /static/libs/chessboard/img/chesspieces/wikipedia/wR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenloop/FlaskChess/2db72359d176cda9cc81d015cd3df8c2de5f5c13/static/libs/chessboard/img/chesspieces/wikipedia/wR.png -------------------------------------------------------------------------------- /static/libs/chessboard/js/chessboard-0.3.0.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * chessboard.js v0.3.0 3 | * 4 | * Copyright 2013 Chris Oakman 5 | * Released under the MIT license 6 | * http://chessboardjs.com/license 7 | * 8 | * Date: 10 Aug 2013 9 | */ 10 | 11 | // start anonymous scope 12 | ;(function() { 13 | 'use strict'; 14 | 15 | //------------------------------------------------------------------------------ 16 | // Chess Util Functions 17 | //------------------------------------------------------------------------------ 18 | var COLUMNS = 'abcdefgh'.split(''); 19 | 20 | function validMove(move) { 21 | // move should be a string 22 | if (typeof move !== 'string') return false; 23 | 24 | // move should be in the form of "e2-e4", "f6-d5" 25 | var tmp = move.split('-'); 26 | if (tmp.length !== 2) return false; 27 | 28 | return (validSquare(tmp[0]) === true && validSquare(tmp[1]) === true); 29 | } 30 | 31 | function validSquare(square) { 32 | if (typeof square !== 'string') return false; 33 | return (square.search(/^[a-h][1-8]$/) !== -1); 34 | } 35 | 36 | function validPieceCode(code) { 37 | if (typeof code !== 'string') return false; 38 | return (code.search(/^[bw][KQRNBP]$/) !== -1); 39 | } 40 | 41 | function validFen(fen) { 42 | if (typeof fen !== 'string') return false; 43 | 44 | // cut off any move, castling, etc info from the end 45 | // we're only interested in position information 46 | fen = fen.replace(/ .+$/, ''); 47 | 48 | // FEN should be 8 sections separated by slashes 49 | var chunks = fen.split('/'); 50 | if (chunks.length !== 8) return false; 51 | 52 | // check the piece sections 53 | for (var i = 0; i < 8; i++) { 54 | if (chunks[i] === '' || 55 | chunks[i].length > 8 || 56 | chunks[i].search(/[^kqrbnpKQRNBP1-8]/) !== -1) { 57 | return false; 58 | } 59 | } 60 | 61 | return true; 62 | } 63 | 64 | function validPositionObject(pos) { 65 | if (typeof pos !== 'object') return false; 66 | 67 | for (var i in pos) { 68 | if (pos.hasOwnProperty(i) !== true) continue; 69 | 70 | if (validSquare(i) !== true || validPieceCode(pos[i]) !== true) { 71 | return false; 72 | } 73 | } 74 | 75 | return true; 76 | } 77 | 78 | // convert FEN piece code to bP, wK, etc 79 | function fenToPieceCode(piece) { 80 | // black piece 81 | if (piece.toLowerCase() === piece) { 82 | return 'b' + piece.toUpperCase(); 83 | } 84 | 85 | // white piece 86 | return 'w' + piece.toUpperCase(); 87 | } 88 | 89 | // convert bP, wK, etc code to FEN structure 90 | function pieceCodeToFen(piece) { 91 | var tmp = piece.split(''); 92 | 93 | // white piece 94 | if (tmp[0] === 'w') { 95 | return tmp[1].toUpperCase(); 96 | } 97 | 98 | // black piece 99 | return tmp[1].toLowerCase(); 100 | } 101 | 102 | // convert FEN string to position object 103 | // returns false if the FEN string is invalid 104 | function fenToObj(fen) { 105 | if (validFen(fen) !== true) { 106 | return false; 107 | } 108 | 109 | // cut off any move, castling, etc info from the end 110 | // we're only interested in position information 111 | fen = fen.replace(/ .+$/, ''); 112 | 113 | var rows = fen.split('/'); 114 | var position = {}; 115 | 116 | var currentRow = 8; 117 | for (var i = 0; i < 8; i++) { 118 | var row = rows[i].split(''); 119 | var colIndex = 0; 120 | 121 | // loop through each character in the FEN section 122 | for (var j = 0; j < row.length; j++) { 123 | // number / empty squares 124 | if (row[j].search(/[1-8]/) !== -1) { 125 | var emptySquares = parseInt(row[j], 10); 126 | colIndex += emptySquares; 127 | } 128 | // piece 129 | else { 130 | var square = COLUMNS[colIndex] + currentRow; 131 | position[square] = fenToPieceCode(row[j]); 132 | colIndex++; 133 | } 134 | } 135 | 136 | currentRow--; 137 | } 138 | 139 | return position; 140 | } 141 | 142 | // position object to FEN string 143 | // returns false if the obj is not a valid position object 144 | function objToFen(obj) { 145 | if (validPositionObject(obj) !== true) { 146 | return false; 147 | } 148 | 149 | var fen = ''; 150 | 151 | var currentRow = 8; 152 | for (var i = 0; i < 8; i++) { 153 | for (var j = 0; j < 8; j++) { 154 | var square = COLUMNS[j] + currentRow; 155 | 156 | // piece exists 157 | if (obj.hasOwnProperty(square) === true) { 158 | fen += pieceCodeToFen(obj[square]); 159 | } 160 | 161 | // empty space 162 | else { 163 | fen += '1'; 164 | } 165 | } 166 | 167 | if (i !== 7) { 168 | fen += '/'; 169 | } 170 | 171 | currentRow--; 172 | } 173 | 174 | // squeeze the numbers together 175 | // haha, I love this solution... 176 | fen = fen.replace(/11111111/g, '8'); 177 | fen = fen.replace(/1111111/g, '7'); 178 | fen = fen.replace(/111111/g, '6'); 179 | fen = fen.replace(/11111/g, '5'); 180 | fen = fen.replace(/1111/g, '4'); 181 | fen = fen.replace(/111/g, '3'); 182 | fen = fen.replace(/11/g, '2'); 183 | 184 | return fen; 185 | } 186 | 187 | window['ChessBoard'] = window['ChessBoard'] || function(containerElOrId, cfg) { 188 | 'use strict'; 189 | 190 | cfg = cfg || {}; 191 | 192 | //------------------------------------------------------------------------------ 193 | // Constants 194 | //------------------------------------------------------------------------------ 195 | 196 | var MINIMUM_JQUERY_VERSION = '1.7.0', 197 | START_FEN = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR', 198 | START_POSITION = fenToObj(START_FEN); 199 | 200 | // use unique class names to prevent clashing with anything else on the page 201 | // and simplify selectors 202 | var CSS = { 203 | alpha: 'alpha-d2270', 204 | black: 'black-3c85d', 205 | board: 'board-b72b1', 206 | chessboard: 'chessboard-63f37', 207 | clearfix: 'clearfix-7da63', 208 | highlight1: 'highlight1-32417', 209 | highlight2: 'highlight2-9c5d2', 210 | notation: 'notation-322f9', 211 | numeric: 'numeric-fc462', 212 | piece: 'piece-417db', 213 | row: 'row-5277c', 214 | sparePieces: 'spare-pieces-7492f', 215 | sparePiecesBottom: 'spare-pieces-bottom-ae20f', 216 | sparePiecesTop: 'spare-pieces-top-4028b', 217 | square: 'square-55d63', 218 | white: 'white-1e1d7' 219 | }; 220 | 221 | //------------------------------------------------------------------------------ 222 | // Module Scope Variables 223 | //------------------------------------------------------------------------------ 224 | 225 | // DOM elements 226 | var containerEl, 227 | boardEl, 228 | draggedPieceEl, 229 | sparePiecesTopEl, 230 | sparePiecesBottomEl; 231 | 232 | // constructor return object 233 | var widget = {}; 234 | 235 | //------------------------------------------------------------------------------ 236 | // Stateful 237 | //------------------------------------------------------------------------------ 238 | 239 | var ANIMATION_HAPPENING = false, 240 | BOARD_BORDER_SIZE = 2, 241 | CURRENT_ORIENTATION = 'white', 242 | CURRENT_POSITION = {}, 243 | SQUARE_SIZE, 244 | DRAGGED_PIECE, 245 | DRAGGED_PIECE_LOCATION, 246 | DRAGGED_PIECE_SOURCE, 247 | DRAGGING_A_PIECE = false, 248 | SPARE_PIECE_ELS_IDS = {}, 249 | SQUARE_ELS_IDS = {}, 250 | SQUARE_ELS_OFFSETS; 251 | 252 | //------------------------------------------------------------------------------ 253 | // JS Util Functions 254 | //------------------------------------------------------------------------------ 255 | 256 | // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript 257 | function createId() { 258 | return 'xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx'.replace(/x/g, function(c) { 259 | var r = Math.random() * 16 | 0; 260 | return r.toString(16); 261 | }); 262 | } 263 | 264 | function deepCopy(thing) { 265 | return JSON.parse(JSON.stringify(thing)); 266 | } 267 | 268 | function parseSemVer(version) { 269 | var tmp = version.split('.'); 270 | return { 271 | major: parseInt(tmp[0], 10), 272 | minor: parseInt(tmp[1], 10), 273 | patch: parseInt(tmp[2], 10) 274 | }; 275 | } 276 | 277 | // returns true if version is >= minimum 278 | function compareSemVer(version, minimum) { 279 | version = parseSemVer(version); 280 | minimum = parseSemVer(minimum); 281 | 282 | var versionNum = (version.major * 10000 * 10000) + 283 | (version.minor * 10000) + version.patch; 284 | var minimumNum = (minimum.major * 10000 * 10000) + 285 | (minimum.minor * 10000) + minimum.patch; 286 | 287 | return (versionNum >= minimumNum); 288 | } 289 | 290 | //------------------------------------------------------------------------------ 291 | // Validation / Errors 292 | //------------------------------------------------------------------------------ 293 | 294 | function error(code, msg, obj) { 295 | // do nothing if showErrors is not set 296 | if (cfg.hasOwnProperty('showErrors') !== true || 297 | cfg.showErrors === false) { 298 | return; 299 | } 300 | 301 | var errorText = 'ChessBoard Error ' + code + ': ' + msg; 302 | 303 | // print to console 304 | if (cfg.showErrors === 'console' && 305 | typeof console === 'object' && 306 | typeof console.log === 'function') { 307 | console.log(errorText); 308 | if (arguments.length >= 2) { 309 | console.log(obj); 310 | } 311 | return; 312 | } 313 | 314 | // alert errors 315 | if (cfg.showErrors === 'alert') { 316 | if (obj) { 317 | errorText += '\n\n' + JSON.stringify(obj); 318 | } 319 | window.alert(errorText); 320 | return; 321 | } 322 | 323 | // custom function 324 | if (typeof cfg.showErrors === 'function') { 325 | cfg.showErrors(code, msg, obj); 326 | } 327 | } 328 | 329 | // check dependencies 330 | function checkDeps() { 331 | // if containerId is a string, it must be the ID of a DOM node 332 | if (typeof containerElOrId === 'string') { 333 | // cannot be empty 334 | if (containerElOrId === '') { 335 | window.alert('ChessBoard Error 1001: ' + 336 | 'The first argument to ChessBoard() cannot be an empty string.' + 337 | '\n\nExiting...'); 338 | return false; 339 | } 340 | 341 | // make sure the container element exists in the DOM 342 | var el = document.getElementById(containerElOrId); 343 | if (! el) { 344 | window.alert('ChessBoard Error 1002: Element with id "' + 345 | containerElOrId + '" does not exist in the DOM.' + 346 | '\n\nExiting...'); 347 | return false; 348 | } 349 | 350 | // set the containerEl 351 | containerEl = $(el); 352 | } 353 | 354 | // else it must be something that becomes a jQuery collection 355 | // with size 1 356 | // ie: a single DOM node or jQuery object 357 | else { 358 | containerEl = $(containerElOrId); 359 | 360 | if (containerEl.length !== 1) { 361 | window.alert('ChessBoard Error 1003: The first argument to ' + 362 | 'ChessBoard() must be an ID or a single DOM node.' + 363 | '\n\nExiting...'); 364 | return false; 365 | } 366 | } 367 | 368 | // JSON must exist 369 | if (! window.JSON || 370 | typeof JSON.stringify !== 'function' || 371 | typeof JSON.parse !== 'function') { 372 | window.alert('ChessBoard Error 1004: JSON does not exist. ' + 373 | 'Please include a JSON polyfill.\n\nExiting...'); 374 | return false; 375 | } 376 | 377 | // check for a compatible version of jQuery 378 | if (! (typeof window.$ && $.fn && $.fn.jquery && 379 | compareSemVer($.fn.jquery, MINIMUM_JQUERY_VERSION) === true)) { 380 | window.alert('ChessBoard Error 1005: Unable to find a valid version ' + 381 | 'of jQuery. Please include jQuery ' + MINIMUM_JQUERY_VERSION + ' or ' + 382 | 'higher on the page.\n\nExiting...'); 383 | return false; 384 | } 385 | 386 | return true; 387 | } 388 | 389 | function validAnimationSpeed(speed) { 390 | if (speed === 'fast' || speed === 'slow') { 391 | return true; 392 | } 393 | 394 | if ((parseInt(speed, 10) + '') !== (speed + '')) { 395 | return false; 396 | } 397 | 398 | return (speed >= 0); 399 | } 400 | 401 | // validate config / set default options 402 | function expandConfig() { 403 | if (typeof cfg === 'string' || validPositionObject(cfg) === true) { 404 | cfg = { 405 | position: cfg 406 | }; 407 | } 408 | 409 | // default for orientation is white 410 | if (cfg.orientation !== 'black') { 411 | cfg.orientation = 'white'; 412 | } 413 | CURRENT_ORIENTATION = cfg.orientation; 414 | 415 | // default for showNotation is true 416 | if (cfg.showNotation !== false) { 417 | cfg.showNotation = true; 418 | } 419 | 420 | // default for draggable is false 421 | if (cfg.draggable !== true) { 422 | cfg.draggable = false; 423 | } 424 | 425 | // default for dropOffBoard is 'snapback' 426 | if (cfg.dropOffBoard !== 'trash') { 427 | cfg.dropOffBoard = 'snapback'; 428 | } 429 | 430 | // default for sparePieces is false 431 | if (cfg.sparePieces !== true) { 432 | cfg.sparePieces = false; 433 | } 434 | 435 | // draggable must be true if sparePieces is enabled 436 | if (cfg.sparePieces === true) { 437 | cfg.draggable = true; 438 | } 439 | 440 | // default piece theme is wikipedia 441 | if (cfg.hasOwnProperty('pieceTheme') !== true || 442 | (typeof cfg.pieceTheme !== 'string' && 443 | typeof cfg.pieceTheme !== 'function')) { 444 | cfg.pieceTheme = 'static/libs/chessboard/img/chesspieces/wikipedia/{piece}.png'; 445 | } 446 | 447 | // animation speeds 448 | if (cfg.hasOwnProperty('appearSpeed') !== true || 449 | validAnimationSpeed(cfg.appearSpeed) !== true) { 450 | cfg.appearSpeed = 200; 451 | } 452 | if (cfg.hasOwnProperty('moveSpeed') !== true || 453 | validAnimationSpeed(cfg.moveSpeed) !== true) { 454 | cfg.moveSpeed = 200; 455 | } 456 | if (cfg.hasOwnProperty('snapbackSpeed') !== true || 457 | validAnimationSpeed(cfg.snapbackSpeed) !== true) { 458 | cfg.snapbackSpeed = 50; 459 | } 460 | if (cfg.hasOwnProperty('snapSpeed') !== true || 461 | validAnimationSpeed(cfg.snapSpeed) !== true) { 462 | cfg.snapSpeed = 25; 463 | } 464 | if (cfg.hasOwnProperty('trashSpeed') !== true || 465 | validAnimationSpeed(cfg.trashSpeed) !== true) { 466 | cfg.trashSpeed = 100; 467 | } 468 | 469 | // make sure position is valid 470 | if (cfg.hasOwnProperty('position') === true) { 471 | if (cfg.position === 'start') { 472 | CURRENT_POSITION = deepCopy(START_POSITION); 473 | } 474 | 475 | else if (validFen(cfg.position) === true) { 476 | CURRENT_POSITION = fenToObj(cfg.position); 477 | } 478 | 479 | else if (validPositionObject(cfg.position) === true) { 480 | CURRENT_POSITION = deepCopy(cfg.position); 481 | } 482 | 483 | else { 484 | error(7263, 'Invalid value passed to config.position.', cfg.position); 485 | } 486 | } 487 | 488 | return true; 489 | } 490 | 491 | //------------------------------------------------------------------------------ 492 | // DOM Misc 493 | //------------------------------------------------------------------------------ 494 | 495 | // calculates square size based on the width of the container 496 | // got a little CSS black magic here, so let me explain: 497 | // get the width of the container element (could be anything), reduce by 1 for 498 | // fudge factor, and then keep reducing until we find an exact mod 8 for 499 | // our square size 500 | function calculateSquareSize() { 501 | var containerWidth = parseInt(containerEl.css('width'), 10); 502 | 503 | // defensive, prevent infinite loop 504 | if (! containerWidth || containerWidth <= 0) { 505 | return 0; 506 | } 507 | 508 | // pad one pixel 509 | var boardWidth = containerWidth - 1; 510 | 511 | while (boardWidth % 8 !== 0 && boardWidth > 0) { 512 | boardWidth--; 513 | } 514 | 515 | return (boardWidth / 8); 516 | } 517 | 518 | // create random IDs for elements 519 | function createElIds() { 520 | // squares on the board 521 | for (var i = 0; i < COLUMNS.length; i++) { 522 | for (var j = 1; j <= 8; j++) { 523 | var square = COLUMNS[i] + j; 524 | SQUARE_ELS_IDS[square] = square + '-' + createId(); 525 | } 526 | } 527 | 528 | // spare pieces 529 | var pieces = 'KQRBNP'.split(''); 530 | for (var i = 0; i < pieces.length; i++) { 531 | var whitePiece = 'w' + pieces[i]; 532 | var blackPiece = 'b' + pieces[i]; 533 | SPARE_PIECE_ELS_IDS[whitePiece] = whitePiece + '-' + createId(); 534 | SPARE_PIECE_ELS_IDS[blackPiece] = blackPiece + '-' + createId(); 535 | } 536 | } 537 | 538 | //------------------------------------------------------------------------------ 539 | // Markup Building 540 | //------------------------------------------------------------------------------ 541 | 542 | function buildBoardContainer() { 543 | var html = '
'; 544 | 545 | if (cfg.sparePieces === true) { 546 | html += '
'; 548 | } 549 | 550 | html += '
'; 551 | 552 | if (cfg.sparePieces === true) { 553 | html += '
'; 555 | } 556 | 557 | html += '
'; 558 | 559 | return html; 560 | } 561 | 562 | /* 563 | var buildSquare = function(color, size, id) { 564 | var html = '
'; 567 | 568 | if (cfg.showNotation === true) { 569 | 570 | } 571 | 572 | html += '
'; 573 | 574 | return html; 575 | }; 576 | */ 577 | 578 | function buildBoard(orientation) { 579 | if (orientation !== 'black') { 580 | orientation = 'white'; 581 | } 582 | 583 | var html = ''; 584 | 585 | // algebraic notation / orientation 586 | var alpha = deepCopy(COLUMNS); 587 | var row = 8; 588 | if (orientation === 'black') { 589 | alpha.reverse(); 590 | row = 1; 591 | } 592 | 593 | var squareColor = 'white'; 594 | for (var i = 0; i < 8; i++) { 595 | html += '
'; 596 | for (var j = 0; j < 8; j++) { 597 | var square = alpha[j] + row; 598 | 599 | html += '
'; 604 | 605 | if (cfg.showNotation === true) { 606 | // alpha notation 607 | if ((orientation === 'white' && row === 1) || 608 | (orientation === 'black' && row === 8)) { 609 | html += '
' + 610 | alpha[j] + '
'; 611 | } 612 | 613 | // numeric notation 614 | if (j === 0) { 615 | html += '
' + 616 | row + '
'; 617 | } 618 | } 619 | 620 | html += '
'; // end .square 621 | 622 | squareColor = (squareColor === 'white' ? 'black' : 'white'); 623 | } 624 | html += '
'; 625 | 626 | squareColor = (squareColor === 'white' ? 'black' : 'white'); 627 | 628 | if (orientation === 'white') { 629 | row--; 630 | } 631 | else { 632 | row++; 633 | } 634 | } 635 | 636 | return html; 637 | } 638 | 639 | function buildPieceImgSrc(piece) { 640 | if (typeof cfg.pieceTheme === 'function') { 641 | return cfg.pieceTheme(piece); 642 | } 643 | 644 | if (typeof cfg.pieceTheme === 'string') { 645 | return cfg.pieceTheme.replace(/{piece}/g, piece); 646 | } 647 | 648 | // NOTE: this should never happen 649 | error(8272, 'Unable to build image source for cfg.pieceTheme.'); 650 | return ''; 651 | } 652 | 653 | function buildPiece(piece, hidden, id) { 654 | var html = ''; 667 | 668 | return html; 669 | } 670 | 671 | function buildSparePieces(color) { 672 | var pieces = ['wK', 'wQ', 'wR', 'wB', 'wN', 'wP']; 673 | if (color === 'black') { 674 | pieces = ['bK', 'bQ', 'bR', 'bB', 'bN', 'bP']; 675 | } 676 | 677 | var html = ''; 678 | for (var i = 0; i < pieces.length; i++) { 679 | html += buildPiece(pieces[i], false, SPARE_PIECE_ELS_IDS[pieces[i]]); 680 | } 681 | 682 | return html; 683 | } 684 | 685 | //------------------------------------------------------------------------------ 686 | // Animations 687 | //------------------------------------------------------------------------------ 688 | 689 | function animateSquareToSquare(src, dest, piece, completeFn) { 690 | // get information about the source and destination squares 691 | var srcSquareEl = $('#' + SQUARE_ELS_IDS[src]); 692 | var srcSquarePosition = srcSquareEl.offset(); 693 | var destSquareEl = $('#' + SQUARE_ELS_IDS[dest]); 694 | var destSquarePosition = destSquareEl.offset(); 695 | 696 | // create the animated piece and absolutely position it 697 | // over the source square 698 | var animatedPieceId = createId(); 699 | $('body').append(buildPiece(piece, true, animatedPieceId)); 700 | var animatedPieceEl = $('#' + animatedPieceId); 701 | animatedPieceEl.css({ 702 | display: '', 703 | position: 'absolute', 704 | top: srcSquarePosition.top, 705 | left: srcSquarePosition.left 706 | }); 707 | 708 | // remove original piece from source square 709 | srcSquareEl.find('.' + CSS.piece).remove(); 710 | 711 | // on complete 712 | var complete = function() { 713 | // add the "real" piece to the destination square 714 | destSquareEl.append(buildPiece(piece)); 715 | 716 | // remove the animated piece 717 | animatedPieceEl.remove(); 718 | 719 | // run complete function 720 | if (typeof completeFn === 'function') { 721 | completeFn(); 722 | } 723 | }; 724 | 725 | // animate the piece to the destination square 726 | var opts = { 727 | duration: cfg.moveSpeed, 728 | complete: complete 729 | }; 730 | animatedPieceEl.animate(destSquarePosition, opts); 731 | } 732 | 733 | function animateSparePieceToSquare(piece, dest, completeFn) { 734 | var srcOffset = $('#' + SPARE_PIECE_ELS_IDS[piece]).offset(); 735 | var destSquareEl = $('#' + SQUARE_ELS_IDS[dest]); 736 | var destOffset = destSquareEl.offset(); 737 | 738 | // create the animate piece 739 | var pieceId = createId(); 740 | $('body').append(buildPiece(piece, true, pieceId)); 741 | var animatedPieceEl = $('#' + pieceId); 742 | animatedPieceEl.css({ 743 | display: '', 744 | position: 'absolute', 745 | left: srcOffset.left, 746 | top: srcOffset.top 747 | }); 748 | 749 | // on complete 750 | var complete = function() { 751 | // add the "real" piece to the destination square 752 | destSquareEl.find('.' + CSS.piece).remove(); 753 | destSquareEl.append(buildPiece(piece)); 754 | 755 | // remove the animated piece 756 | animatedPieceEl.remove(); 757 | 758 | // run complete function 759 | if (typeof completeFn === 'function') { 760 | completeFn(); 761 | } 762 | }; 763 | 764 | // animate the piece to the destination square 765 | var opts = { 766 | duration: cfg.moveSpeed, 767 | complete: complete 768 | }; 769 | animatedPieceEl.animate(destOffset, opts); 770 | } 771 | 772 | // execute an array of animations 773 | function doAnimations(a, oldPos, newPos) { 774 | ANIMATION_HAPPENING = true; 775 | 776 | var numFinished = 0; 777 | function onFinish() { 778 | numFinished++; 779 | 780 | // exit if all the animations aren't finished 781 | if (numFinished !== a.length) return; 782 | 783 | drawPositionInstant(); 784 | ANIMATION_HAPPENING = false; 785 | 786 | // run their onMoveEnd function 787 | if (cfg.hasOwnProperty('onMoveEnd') === true && 788 | typeof cfg.onMoveEnd === 'function') { 789 | cfg.onMoveEnd(deepCopy(oldPos), deepCopy(newPos)); 790 | } 791 | } 792 | 793 | for (var i = 0; i < a.length; i++) { 794 | // clear a piece 795 | if (a[i].type === 'clear') { 796 | $('#' + SQUARE_ELS_IDS[a[i].square] + ' .' + CSS.piece) 797 | .fadeOut(cfg.trashSpeed, onFinish); 798 | } 799 | 800 | // add a piece (no spare pieces) 801 | if (a[i].type === 'add' && cfg.sparePieces !== true) { 802 | $('#' + SQUARE_ELS_IDS[a[i].square]) 803 | .append(buildPiece(a[i].piece, true)) 804 | .find('.' + CSS.piece) 805 | .fadeIn(cfg.appearSpeed, onFinish); 806 | } 807 | 808 | // add a piece from a spare piece 809 | if (a[i].type === 'add' && cfg.sparePieces === true) { 810 | animateSparePieceToSquare(a[i].piece, a[i].square, onFinish); 811 | } 812 | 813 | // move a piece 814 | if (a[i].type === 'move') { 815 | animateSquareToSquare(a[i].source, a[i].destination, a[i].piece, 816 | onFinish); 817 | } 818 | } 819 | } 820 | 821 | // returns the distance between two squares 822 | function squareDistance(s1, s2) { 823 | s1 = s1.split(''); 824 | var s1x = COLUMNS.indexOf(s1[0]) + 1; 825 | var s1y = parseInt(s1[1], 10); 826 | 827 | s2 = s2.split(''); 828 | var s2x = COLUMNS.indexOf(s2[0]) + 1; 829 | var s2y = parseInt(s2[1], 10); 830 | 831 | var xDelta = Math.abs(s1x - s2x); 832 | var yDelta = Math.abs(s1y - s2y); 833 | 834 | if (xDelta >= yDelta) return xDelta; 835 | return yDelta; 836 | } 837 | 838 | // returns an array of closest squares from square 839 | function createRadius(square) { 840 | var squares = []; 841 | 842 | // calculate distance of all squares 843 | for (var i = 0; i < 8; i++) { 844 | for (var j = 0; j < 8; j++) { 845 | var s = COLUMNS[i] + (j + 1); 846 | 847 | // skip the square we're starting from 848 | if (square === s) continue; 849 | 850 | squares.push({ 851 | square: s, 852 | distance: squareDistance(square, s) 853 | }); 854 | } 855 | } 856 | 857 | // sort by distance 858 | squares.sort(function(a, b) { 859 | return a.distance - b.distance; 860 | }); 861 | 862 | // just return the square code 863 | var squares2 = []; 864 | for (var i = 0; i < squares.length; i++) { 865 | squares2.push(squares[i].square); 866 | } 867 | 868 | return squares2; 869 | } 870 | 871 | // returns the square of the closest instance of piece 872 | // returns false if no instance of piece is found in position 873 | function findClosestPiece(position, piece, square) { 874 | // create array of closest squares from square 875 | var closestSquares = createRadius(square); 876 | 877 | // search through the position in order of distance for the piece 878 | for (var i = 0; i < closestSquares.length; i++) { 879 | var s = closestSquares[i]; 880 | 881 | if (position.hasOwnProperty(s) === true && position[s] === piece) { 882 | return s; 883 | } 884 | } 885 | 886 | return false; 887 | } 888 | 889 | // calculate an array of animations that need to happen in order to get 890 | // from pos1 to pos2 891 | function calculateAnimations(pos1, pos2) { 892 | // make copies of both 893 | pos1 = deepCopy(pos1); 894 | pos2 = deepCopy(pos2); 895 | 896 | var animations = []; 897 | var squaresMovedTo = {}; 898 | 899 | // remove pieces that are the same in both positions 900 | for (var i in pos2) { 901 | if (pos2.hasOwnProperty(i) !== true) continue; 902 | 903 | if (pos1.hasOwnProperty(i) === true && pos1[i] === pos2[i]) { 904 | delete pos1[i]; 905 | delete pos2[i]; 906 | } 907 | } 908 | 909 | // find all the "move" animations 910 | for (var i in pos2) { 911 | if (pos2.hasOwnProperty(i) !== true) continue; 912 | 913 | var closestPiece = findClosestPiece(pos1, pos2[i], i); 914 | if (closestPiece !== false) { 915 | animations.push({ 916 | type: 'move', 917 | source: closestPiece, 918 | destination: i, 919 | piece: pos2[i] 920 | }); 921 | 922 | delete pos1[closestPiece]; 923 | delete pos2[i]; 924 | squaresMovedTo[i] = true; 925 | } 926 | } 927 | 928 | // add pieces to pos2 929 | for (var i in pos2) { 930 | if (pos2.hasOwnProperty(i) !== true) continue; 931 | 932 | animations.push({ 933 | type: 'add', 934 | square: i, 935 | piece: pos2[i] 936 | }) 937 | 938 | delete pos2[i]; 939 | } 940 | 941 | // clear pieces from pos1 942 | for (var i in pos1) { 943 | if (pos1.hasOwnProperty(i) !== true) continue; 944 | 945 | // do not clear a piece if it is on a square that is the result 946 | // of a "move", ie: a piece capture 947 | if (squaresMovedTo.hasOwnProperty(i) === true) continue; 948 | 949 | animations.push({ 950 | type: 'clear', 951 | square: i, 952 | piece: pos1[i] 953 | }); 954 | 955 | delete pos1[i]; 956 | } 957 | 958 | return animations; 959 | } 960 | 961 | //------------------------------------------------------------------------------ 962 | // Control Flow 963 | //------------------------------------------------------------------------------ 964 | 965 | function drawPositionInstant() { 966 | // clear the board 967 | boardEl.find('.' + CSS.piece).remove(); 968 | 969 | // add the pieces 970 | for (var i in CURRENT_POSITION) { 971 | if (CURRENT_POSITION.hasOwnProperty(i) !== true) continue; 972 | 973 | $('#' + SQUARE_ELS_IDS[i]).append(buildPiece(CURRENT_POSITION[i])); 974 | } 975 | } 976 | 977 | function drawBoard() { 978 | boardEl.html(buildBoard(CURRENT_ORIENTATION)); 979 | drawPositionInstant(); 980 | 981 | if (cfg.sparePieces === true) { 982 | if (CURRENT_ORIENTATION === 'white') { 983 | sparePiecesTopEl.html(buildSparePieces('black')); 984 | sparePiecesBottomEl.html(buildSparePieces('white')); 985 | } 986 | else { 987 | sparePiecesTopEl.html(buildSparePieces('white')); 988 | sparePiecesBottomEl.html(buildSparePieces('black')); 989 | } 990 | } 991 | } 992 | 993 | // given a position and a set of moves, return a new position 994 | // with the moves executed 995 | function calculatePositionFromMoves(position, moves) { 996 | position = deepCopy(position); 997 | 998 | for (var i in moves) { 999 | if (moves.hasOwnProperty(i) !== true) continue; 1000 | 1001 | // skip the move if the position doesn't have a piece on the source square 1002 | if (position.hasOwnProperty(i) !== true) continue; 1003 | 1004 | var piece = position[i]; 1005 | delete position[i]; 1006 | position[moves[i]] = piece; 1007 | } 1008 | 1009 | return position; 1010 | } 1011 | 1012 | function setCurrentPosition(position) { 1013 | var oldPos = deepCopy(CURRENT_POSITION); 1014 | var newPos = deepCopy(position); 1015 | var oldFen = objToFen(oldPos); 1016 | var newFen = objToFen(newPos); 1017 | 1018 | // do nothing if no change in position 1019 | if (oldFen === newFen) return; 1020 | 1021 | // run their onChange function 1022 | if (cfg.hasOwnProperty('onChange') === true && 1023 | typeof cfg.onChange === 'function') { 1024 | cfg.onChange(oldPos, newPos); 1025 | } 1026 | 1027 | // update state 1028 | CURRENT_POSITION = position; 1029 | } 1030 | 1031 | function isXYOnSquare(x, y) { 1032 | for (var i in SQUARE_ELS_OFFSETS) { 1033 | if (SQUARE_ELS_OFFSETS.hasOwnProperty(i) !== true) continue; 1034 | 1035 | var s = SQUARE_ELS_OFFSETS[i]; 1036 | if (x >= s.left && x < s.left + SQUARE_SIZE && 1037 | y >= s.top && y < s.top + SQUARE_SIZE) { 1038 | return i; 1039 | } 1040 | } 1041 | 1042 | return 'offboard'; 1043 | } 1044 | 1045 | // records the XY coords of every square into memory 1046 | function captureSquareOffsets() { 1047 | SQUARE_ELS_OFFSETS = {}; 1048 | 1049 | for (var i in SQUARE_ELS_IDS) { 1050 | if (SQUARE_ELS_IDS.hasOwnProperty(i) !== true) continue; 1051 | 1052 | SQUARE_ELS_OFFSETS[i] = $('#' + SQUARE_ELS_IDS[i]).offset(); 1053 | } 1054 | } 1055 | 1056 | function removeSquareHighlights() { 1057 | boardEl.find('.' + CSS.square) 1058 | .removeClass(CSS.highlight1 + ' ' + CSS.highlight2); 1059 | } 1060 | 1061 | function snapbackDraggedPiece() { 1062 | // there is no "snapback" for spare pieces 1063 | if (DRAGGED_PIECE_SOURCE === 'spare') { 1064 | trashDraggedPiece(); 1065 | return; 1066 | } 1067 | 1068 | removeSquareHighlights(); 1069 | 1070 | // animation complete 1071 | function complete() { 1072 | drawPositionInstant(); 1073 | draggedPieceEl.css('display', 'none'); 1074 | 1075 | // run their onSnapbackEnd function 1076 | if (cfg.hasOwnProperty('onSnapbackEnd') === true && 1077 | typeof cfg.onSnapbackEnd === 'function') { 1078 | cfg.onSnapbackEnd(DRAGGED_PIECE, DRAGGED_PIECE_SOURCE, 1079 | deepCopy(CURRENT_POSITION), CURRENT_ORIENTATION); 1080 | } 1081 | } 1082 | 1083 | // get source square position 1084 | var sourceSquarePosition = 1085 | $('#' + SQUARE_ELS_IDS[DRAGGED_PIECE_SOURCE]).offset(); 1086 | 1087 | // animate the piece to the target square 1088 | var opts = { 1089 | duration: cfg.snapbackSpeed, 1090 | complete: complete 1091 | }; 1092 | draggedPieceEl.animate(sourceSquarePosition, opts); 1093 | 1094 | // set state 1095 | DRAGGING_A_PIECE = false; 1096 | } 1097 | 1098 | function trashDraggedPiece() { 1099 | removeSquareHighlights(); 1100 | 1101 | // remove the source piece 1102 | var newPosition = deepCopy(CURRENT_POSITION); 1103 | delete newPosition[DRAGGED_PIECE_SOURCE]; 1104 | setCurrentPosition(newPosition); 1105 | 1106 | // redraw the position 1107 | drawPositionInstant(); 1108 | 1109 | // hide the dragged piece 1110 | draggedPieceEl.fadeOut(cfg.trashSpeed); 1111 | 1112 | // set state 1113 | DRAGGING_A_PIECE = false; 1114 | } 1115 | 1116 | function dropDraggedPieceOnSquare(square) { 1117 | removeSquareHighlights(); 1118 | 1119 | // update position 1120 | var newPosition = deepCopy(CURRENT_POSITION); 1121 | delete newPosition[DRAGGED_PIECE_SOURCE]; 1122 | newPosition[square] = DRAGGED_PIECE; 1123 | setCurrentPosition(newPosition); 1124 | 1125 | // get target square information 1126 | var targetSquarePosition = $('#' + SQUARE_ELS_IDS[square]).offset(); 1127 | 1128 | // animation complete 1129 | var complete = function() { 1130 | drawPositionInstant(); 1131 | draggedPieceEl.css('display', 'none'); 1132 | 1133 | // execute their onSnapEnd function 1134 | if (cfg.hasOwnProperty('onSnapEnd') === true && 1135 | typeof cfg.onSnapEnd === 'function') { 1136 | cfg.onSnapEnd(DRAGGED_PIECE_SOURCE, square, DRAGGED_PIECE); 1137 | } 1138 | }; 1139 | 1140 | // snap the piece to the target square 1141 | var opts = { 1142 | duration: cfg.snapSpeed, 1143 | complete: complete 1144 | }; 1145 | draggedPieceEl.animate(targetSquarePosition, opts); 1146 | 1147 | // set state 1148 | DRAGGING_A_PIECE = false; 1149 | } 1150 | 1151 | function beginDraggingPiece(source, piece, x, y) { 1152 | // run their custom onDragStart function 1153 | // their custom onDragStart function can cancel drag start 1154 | if (typeof cfg.onDragStart === 'function' && 1155 | cfg.onDragStart(source, piece, 1156 | deepCopy(CURRENT_POSITION), CURRENT_ORIENTATION) === false) { 1157 | return; 1158 | } 1159 | 1160 | // set state 1161 | DRAGGING_A_PIECE = true; 1162 | DRAGGED_PIECE = piece; 1163 | DRAGGED_PIECE_SOURCE = source; 1164 | 1165 | // if the piece came from spare pieces, location is offboard 1166 | if (source === 'spare') { 1167 | DRAGGED_PIECE_LOCATION = 'offboard'; 1168 | } 1169 | else { 1170 | DRAGGED_PIECE_LOCATION = source; 1171 | } 1172 | 1173 | // capture the x, y coords of all squares in memory 1174 | captureSquareOffsets(); 1175 | 1176 | // create the dragged piece 1177 | draggedPieceEl.attr('src', buildPieceImgSrc(piece)) 1178 | .css({ 1179 | display: '', 1180 | position: 'absolute', 1181 | left: x - (SQUARE_SIZE / 2), 1182 | top: y - (SQUARE_SIZE / 2) 1183 | }); 1184 | 1185 | if (source !== 'spare') { 1186 | // highlight the source square and hide the piece 1187 | $('#' + SQUARE_ELS_IDS[source]).addClass(CSS.highlight1) 1188 | .find('.' + CSS.piece).css('display', 'none'); 1189 | } 1190 | } 1191 | 1192 | function updateDraggedPiece(x, y) { 1193 | // put the dragged piece over the mouse cursor 1194 | draggedPieceEl.css({ 1195 | left: x - (SQUARE_SIZE / 2), 1196 | top: y - (SQUARE_SIZE / 2) 1197 | }); 1198 | 1199 | // get location 1200 | var location = isXYOnSquare(x, y); 1201 | 1202 | // do nothing if the location has not changed 1203 | if (location === DRAGGED_PIECE_LOCATION) return; 1204 | 1205 | // remove highlight from previous square 1206 | if (validSquare(DRAGGED_PIECE_LOCATION) === true) { 1207 | $('#' + SQUARE_ELS_IDS[DRAGGED_PIECE_LOCATION]) 1208 | .removeClass(CSS.highlight2); 1209 | } 1210 | 1211 | // add highlight to new square 1212 | if (validSquare(location) === true) { 1213 | $('#' + SQUARE_ELS_IDS[location]).addClass(CSS.highlight2); 1214 | } 1215 | 1216 | // run onDragMove 1217 | if (typeof cfg.onDragMove === 'function') { 1218 | cfg.onDragMove(location, DRAGGED_PIECE_LOCATION, 1219 | DRAGGED_PIECE_SOURCE, DRAGGED_PIECE, 1220 | deepCopy(CURRENT_POSITION), CURRENT_ORIENTATION); 1221 | } 1222 | 1223 | // update state 1224 | DRAGGED_PIECE_LOCATION = location; 1225 | } 1226 | 1227 | function stopDraggedPiece(location) { 1228 | // determine what the action should be 1229 | var action = 'drop'; 1230 | if (location === 'offboard' && cfg.dropOffBoard === 'snapback') { 1231 | action = 'snapback'; 1232 | } 1233 | if (location === 'offboard' && cfg.dropOffBoard === 'trash') { 1234 | action = 'trash'; 1235 | } 1236 | 1237 | // run their onDrop function, which can potentially change the drop action 1238 | if (cfg.hasOwnProperty('onDrop') === true && 1239 | typeof cfg.onDrop === 'function') { 1240 | var newPosition = deepCopy(CURRENT_POSITION); 1241 | 1242 | // source piece is a spare piece and position is off the board 1243 | //if (DRAGGED_PIECE_SOURCE === 'spare' && location === 'offboard') {...} 1244 | // position has not changed; do nothing 1245 | 1246 | // source piece is a spare piece and position is on the board 1247 | if (DRAGGED_PIECE_SOURCE === 'spare' && validSquare(location) === true) { 1248 | // add the piece to the board 1249 | newPosition[location] = DRAGGED_PIECE; 1250 | } 1251 | 1252 | // source piece was on the board and position is off the board 1253 | if (validSquare(DRAGGED_PIECE_SOURCE) === true && location === 'offboard') { 1254 | // remove the piece from the board 1255 | delete newPosition[DRAGGED_PIECE_SOURCE]; 1256 | } 1257 | 1258 | // source piece was on the board and position is on the board 1259 | if (validSquare(DRAGGED_PIECE_SOURCE) === true && 1260 | validSquare(location) === true) { 1261 | // move the piece 1262 | delete newPosition[DRAGGED_PIECE_SOURCE]; 1263 | newPosition[location] = DRAGGED_PIECE; 1264 | } 1265 | 1266 | var oldPosition = deepCopy(CURRENT_POSITION); 1267 | 1268 | var result = cfg.onDrop(DRAGGED_PIECE_SOURCE, location, DRAGGED_PIECE, 1269 | newPosition, oldPosition, CURRENT_ORIENTATION); 1270 | if (result === 'snapback' || result === 'trash') { 1271 | action = result; 1272 | } 1273 | } 1274 | 1275 | // do it! 1276 | if (action === 'snapback') { 1277 | snapbackDraggedPiece(); 1278 | } 1279 | else if (action === 'trash') { 1280 | trashDraggedPiece(); 1281 | } 1282 | else if (action === 'drop') { 1283 | dropDraggedPieceOnSquare(location); 1284 | } 1285 | } 1286 | 1287 | //------------------------------------------------------------------------------ 1288 | // Public Methods 1289 | //------------------------------------------------------------------------------ 1290 | 1291 | // clear the board 1292 | widget.clear = function(useAnimation) { 1293 | widget.position({}, useAnimation); 1294 | }; 1295 | 1296 | /* 1297 | // get or set config properties 1298 | widget.config = function(arg1, arg2) { 1299 | // get the current config 1300 | if (arguments.length === 0) { 1301 | return deepCopy(cfg); 1302 | } 1303 | }; 1304 | */ 1305 | 1306 | // remove the widget from the page 1307 | widget.destroy = function() { 1308 | // remove markup 1309 | containerEl.html(''); 1310 | draggedPieceEl.remove(); 1311 | 1312 | // remove event handlers 1313 | containerEl.unbind(); 1314 | }; 1315 | 1316 | // shorthand method to get the current FEN 1317 | widget.fen = function() { 1318 | return widget.position('fen'); 1319 | }; 1320 | 1321 | // flip orientation 1322 | widget.flip = function() { 1323 | widget.orientation('flip'); 1324 | }; 1325 | 1326 | /* 1327 | widget.highlight = function() { 1328 | 1329 | }; 1330 | */ 1331 | 1332 | // move pieces 1333 | widget.move = function() { 1334 | // no need to throw an error here; just do nothing 1335 | if (arguments.length === 0) return; 1336 | 1337 | var useAnimation = true; 1338 | 1339 | // collect the moves into an object 1340 | var moves = {}; 1341 | for (var i = 0; i < arguments.length; i++) { 1342 | // any "false" to this function means no animations 1343 | if (arguments[i] === false) { 1344 | useAnimation = false; 1345 | continue; 1346 | } 1347 | 1348 | // skip invalid arguments 1349 | if (validMove(arguments[i]) !== true) { 1350 | error(2826, 'Invalid move passed to the move method.', arguments[i]); 1351 | continue; 1352 | } 1353 | 1354 | var tmp = arguments[i].split('-'); 1355 | moves[tmp[0]] = tmp[1]; 1356 | } 1357 | 1358 | // calculate position from moves 1359 | var newPos = calculatePositionFromMoves(CURRENT_POSITION, moves); 1360 | 1361 | // update the board 1362 | widget.position(newPos, useAnimation); 1363 | 1364 | // return the new position object 1365 | return newPos; 1366 | }; 1367 | 1368 | widget.orientation = function(arg) { 1369 | // no arguments, return the current orientation 1370 | if (arguments.length === 0) { 1371 | return CURRENT_ORIENTATION; 1372 | } 1373 | 1374 | // set to white or black 1375 | if (arg === 'white' || arg === 'black') { 1376 | CURRENT_ORIENTATION = arg; 1377 | drawBoard(); 1378 | return; 1379 | } 1380 | 1381 | // flip orientation 1382 | if (arg === 'flip') { 1383 | CURRENT_ORIENTATION = (CURRENT_ORIENTATION === 'white') ? 'black' : 'white'; 1384 | drawBoard(); 1385 | return; 1386 | } 1387 | 1388 | error(5482, 'Invalid value passed to the orientation method.', arg); 1389 | }; 1390 | 1391 | widget.position = function(position, useAnimation) { 1392 | // no arguments, return the current position 1393 | if (arguments.length === 0) { 1394 | return deepCopy(CURRENT_POSITION); 1395 | } 1396 | 1397 | // get position as FEN 1398 | if (typeof position === 'string' && position.toLowerCase() === 'fen') { 1399 | return objToFen(CURRENT_POSITION); 1400 | } 1401 | 1402 | // default for useAnimations is true 1403 | if (useAnimation !== false) { 1404 | useAnimation = true; 1405 | } 1406 | 1407 | // start position 1408 | if (typeof position === 'string' && position.toLowerCase() === 'start') { 1409 | position = deepCopy(START_POSITION); 1410 | } 1411 | 1412 | // convert FEN to position object 1413 | if (validFen(position) === true) { 1414 | position = fenToObj(position); 1415 | } 1416 | 1417 | // validate position object 1418 | if (validPositionObject(position) !== true) { 1419 | error(6482, 'Invalid value passed to the position method.', position); 1420 | return; 1421 | } 1422 | 1423 | if (useAnimation === true) { 1424 | // start the animations 1425 | doAnimations(calculateAnimations(CURRENT_POSITION, position), 1426 | CURRENT_POSITION, position); 1427 | 1428 | // set the new position 1429 | setCurrentPosition(position); 1430 | } 1431 | // instant update 1432 | else { 1433 | setCurrentPosition(position); 1434 | drawPositionInstant(); 1435 | } 1436 | }; 1437 | 1438 | widget.resize = function() { 1439 | // calulate the new square size 1440 | SQUARE_SIZE = calculateSquareSize(); 1441 | 1442 | // set board width 1443 | boardEl.css('width', (SQUARE_SIZE * 8) + 'px'); 1444 | 1445 | // set drag piece size 1446 | draggedPieceEl.css({ 1447 | height: SQUARE_SIZE, 1448 | width: SQUARE_SIZE 1449 | }); 1450 | 1451 | // spare pieces 1452 | if (cfg.sparePieces === true) { 1453 | containerEl.find('.' + CSS.sparePieces) 1454 | .css('paddingLeft', (SQUARE_SIZE + BOARD_BORDER_SIZE) + 'px'); 1455 | } 1456 | 1457 | // redraw the board 1458 | drawBoard(); 1459 | }; 1460 | 1461 | // set the starting position 1462 | widget.start = function(useAnimation) { 1463 | widget.position('start', useAnimation); 1464 | }; 1465 | 1466 | //------------------------------------------------------------------------------ 1467 | // Browser Events 1468 | //------------------------------------------------------------------------------ 1469 | 1470 | function isTouchDevice() { 1471 | return ('ontouchstart' in document.documentElement); 1472 | } 1473 | 1474 | // reference: http://www.quirksmode.org/js/detect.html 1475 | function isMSIE() { 1476 | return (navigator && navigator.userAgent && 1477 | navigator.userAgent.search(/MSIE/) !== -1); 1478 | } 1479 | 1480 | function stopDefault(e) { 1481 | e.preventDefault(); 1482 | } 1483 | 1484 | function mousedownSquare(e) { 1485 | // do nothing if we're not draggable 1486 | if (cfg.draggable !== true) return; 1487 | 1488 | var square = $(this).attr('data-square'); 1489 | 1490 | // no piece on this square 1491 | if (validSquare(square) !== true || 1492 | CURRENT_POSITION.hasOwnProperty(square) !== true) { 1493 | return; 1494 | } 1495 | 1496 | beginDraggingPiece(square, CURRENT_POSITION[square], e.pageX, e.pageY); 1497 | } 1498 | 1499 | function touchstartSquare(e) { 1500 | // do nothing if we're not draggable 1501 | if (cfg.draggable !== true) return; 1502 | 1503 | var square = $(this).attr('data-square'); 1504 | 1505 | // no piece on this square 1506 | if (validSquare(square) !== true || 1507 | CURRENT_POSITION.hasOwnProperty(square) !== true) { 1508 | return; 1509 | } 1510 | 1511 | e = e.originalEvent; 1512 | beginDraggingPiece(square, CURRENT_POSITION[square], 1513 | e.changedTouches[0].pageX, e.changedTouches[0].pageY); 1514 | } 1515 | 1516 | function mousedownSparePiece(e) { 1517 | // do nothing if sparePieces is not enabled 1518 | if (cfg.sparePieces !== true) return; 1519 | 1520 | var piece = $(this).attr('data-piece'); 1521 | 1522 | beginDraggingPiece('spare', piece, e.pageX, e.pageY); 1523 | } 1524 | 1525 | function touchstartSparePiece(e) { 1526 | // do nothing if sparePieces is not enabled 1527 | if (cfg.sparePieces !== true) return; 1528 | 1529 | var piece = $(this).attr('data-piece'); 1530 | 1531 | e = e.originalEvent; 1532 | beginDraggingPiece('spare', piece, 1533 | e.changedTouches[0].pageX, e.changedTouches[0].pageY); 1534 | } 1535 | 1536 | function mousemoveWindow(e) { 1537 | // do nothing if we are not dragging a piece 1538 | if (DRAGGING_A_PIECE !== true) return; 1539 | 1540 | updateDraggedPiece(e.pageX, e.pageY); 1541 | } 1542 | 1543 | function touchmoveWindow(e) { 1544 | // do nothing if we are not dragging a piece 1545 | if (DRAGGING_A_PIECE !== true) return; 1546 | 1547 | // prevent screen from scrolling 1548 | e.preventDefault(); 1549 | 1550 | updateDraggedPiece(e.originalEvent.changedTouches[0].pageX, 1551 | e.originalEvent.changedTouches[0].pageY); 1552 | } 1553 | 1554 | function mouseupWindow(e) { 1555 | // do nothing if we are not dragging a piece 1556 | if (DRAGGING_A_PIECE !== true) return; 1557 | 1558 | // get the location 1559 | var location = isXYOnSquare(e.pageX, e.pageY); 1560 | 1561 | stopDraggedPiece(location); 1562 | } 1563 | 1564 | function touchendWindow(e) { 1565 | // do nothing if we are not dragging a piece 1566 | if (DRAGGING_A_PIECE !== true) return; 1567 | 1568 | // get the location 1569 | var location = isXYOnSquare(e.originalEvent.changedTouches[0].pageX, 1570 | e.originalEvent.changedTouches[0].pageY); 1571 | 1572 | stopDraggedPiece(location); 1573 | } 1574 | 1575 | function mouseenterSquare(e) { 1576 | // do not fire this event if we are dragging a piece 1577 | // NOTE: this should never happen, but it's a safeguard 1578 | if (DRAGGING_A_PIECE !== false) return; 1579 | 1580 | if (cfg.hasOwnProperty('onMouseoverSquare') !== true || 1581 | typeof cfg.onMouseoverSquare !== 'function') return; 1582 | 1583 | // get the square 1584 | var square = $(e.currentTarget).attr('data-square'); 1585 | 1586 | // NOTE: this should never happen; defensive 1587 | if (validSquare(square) !== true) return; 1588 | 1589 | // get the piece on this square 1590 | var piece = false; 1591 | if (CURRENT_POSITION.hasOwnProperty(square) === true) { 1592 | piece = CURRENT_POSITION[square]; 1593 | } 1594 | 1595 | // execute their function 1596 | cfg.onMouseoverSquare(square, piece, deepCopy(CURRENT_POSITION), 1597 | CURRENT_ORIENTATION); 1598 | } 1599 | 1600 | function mouseleaveSquare(e) { 1601 | // do not fire this event if we are dragging a piece 1602 | // NOTE: this should never happen, but it's a safeguard 1603 | if (DRAGGING_A_PIECE !== false) return; 1604 | 1605 | if (cfg.hasOwnProperty('onMouseoutSquare') !== true || 1606 | typeof cfg.onMouseoutSquare !== 'function') return; 1607 | 1608 | // get the square 1609 | var square = $(e.currentTarget).attr('data-square'); 1610 | 1611 | // NOTE: this should never happen; defensive 1612 | if (validSquare(square) !== true) return; 1613 | 1614 | // get the piece on this square 1615 | var piece = false; 1616 | if (CURRENT_POSITION.hasOwnProperty(square) === true) { 1617 | piece = CURRENT_POSITION[square]; 1618 | } 1619 | 1620 | // execute their function 1621 | cfg.onMouseoutSquare(square, piece, deepCopy(CURRENT_POSITION), 1622 | CURRENT_ORIENTATION); 1623 | } 1624 | 1625 | //------------------------------------------------------------------------------ 1626 | // Initialization 1627 | //------------------------------------------------------------------------------ 1628 | 1629 | function addEvents() { 1630 | // prevent browser "image drag" 1631 | $('body').on('mousedown mousemove', '.' + CSS.piece, stopDefault); 1632 | 1633 | // mouse drag pieces 1634 | boardEl.on('mousedown', '.' + CSS.square, mousedownSquare); 1635 | containerEl.on('mousedown', '.' + CSS.sparePieces + ' .' + CSS.piece, 1636 | mousedownSparePiece); 1637 | 1638 | // mouse enter / leave square 1639 | boardEl.on('mouseenter', '.' + CSS.square, mouseenterSquare); 1640 | boardEl.on('mouseleave', '.' + CSS.square, mouseleaveSquare); 1641 | 1642 | // IE doesn't like the events on the window object, but other browsers 1643 | // perform better that way 1644 | if (isMSIE() === true) { 1645 | // IE-specific prevent browser "image drag" 1646 | document.ondragstart = function() { return false; }; 1647 | 1648 | $('body').on('mousemove', mousemoveWindow); 1649 | $('body').on('mouseup', mouseupWindow); 1650 | } 1651 | else { 1652 | $(window).on('mousemove', mousemoveWindow); 1653 | $(window).on('mouseup', mouseupWindow); 1654 | } 1655 | 1656 | // touch drag pieces 1657 | if (isTouchDevice() === true) { 1658 | boardEl.on('touchstart', '.' + CSS.square, touchstartSquare); 1659 | containerEl.on('touchstart', '.' + CSS.sparePieces + ' .' + CSS.piece, 1660 | touchstartSparePiece); 1661 | $(window).on('touchmove', touchmoveWindow); 1662 | $(window).on('touchend', touchendWindow); 1663 | } 1664 | } 1665 | 1666 | function initDom() { 1667 | // build board and save it in memory 1668 | containerEl.html(buildBoardContainer()); 1669 | boardEl = containerEl.find('.' + CSS.board); 1670 | 1671 | if (cfg.sparePieces === true) { 1672 | sparePiecesTopEl = containerEl.find('.' + CSS.sparePiecesTop); 1673 | sparePiecesBottomEl = containerEl.find('.' + CSS.sparePiecesBottom); 1674 | } 1675 | 1676 | // create the drag piece 1677 | var draggedPieceId = createId(); 1678 | $('body').append(buildPiece('wP', true, draggedPieceId)); 1679 | draggedPieceEl = $('#' + draggedPieceId); 1680 | 1681 | // get the border size 1682 | BOARD_BORDER_SIZE = parseInt(boardEl.css('borderLeftWidth'), 10); 1683 | 1684 | // set the size and draw the board 1685 | widget.resize(); 1686 | } 1687 | 1688 | function init() { 1689 | if (checkDeps() !== true || 1690 | expandConfig() !== true) return; 1691 | 1692 | // create unique IDs for all the elements we will create 1693 | createElIds(); 1694 | 1695 | initDom(); 1696 | addEvents(); 1697 | } 1698 | 1699 | // go time 1700 | init(); 1701 | 1702 | // return the widget object 1703 | return widget; 1704 | 1705 | }; // end window.ChessBoard 1706 | 1707 | // expose util functions 1708 | window.ChessBoard.fenToObj = fenToObj; 1709 | window.ChessBoard.objToFen = objToFen; 1710 | 1711 | })(); // end anonymous wrapper 1712 | -------------------------------------------------------------------------------- /static/libs/chessboard/js/chessboard-0.3.0.min.js: -------------------------------------------------------------------------------- 1 | /*! chessboard.js v0.3.0 | (c) 2013 Chris Oakman | MIT License chessboardjs.com/license */ 2 | (function(){function l(f){return"string"!==typeof f?!1:-1!==f.search(/^[a-h][1-8]$/)}function Q(f){if("string"!==typeof f)return!1;f=f.replace(/ .+$/,"");f=f.split("/");if(8!==f.length)return!1;for(var b=0;8>b;b++)if(""===f[b]||8m;m++){for(var l=f[m].split(""),r=0,w=0;wm;m++){for(var l=0;8>l;l++){var r=B[l]+n;!0===f.hasOwnProperty(r)?(r=f[r].split(""),r="w"===r[0]?r[1].toUpperCase(): 4 | r[1].toLowerCase(),b+=r):b+="1"}7!==m&&(b+="/");n--}b=b.replace(/11111111/g,"8");b=b.replace(/1111111/g,"7");b=b.replace(/111111/g,"6");b=b.replace(/11111/g,"5");b=b.replace(/1111/g,"4");b=b.replace(/111/g,"3");return b=b.replace(/11/g,"2")}var B="abcdefgh".split("");window.ChessBoard=window.ChessBoard||function(f,b){function n(){return"xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx".replace(/x/g,function(a){return(16*Math.random()|0).toString(16)})}function m(a){return JSON.parse(JSON.stringify(a))}function X(a){a= 5 | a.split(".");return{major:parseInt(a[0],10),minor:parseInt(a[1],10),patch:parseInt(a[2],10)}}function r(a,e,c){if(!0===b.hasOwnProperty("showErrors")&&!1!==b.showErrors){var d="ChessBoard Error "+a+": "+e;"console"===b.showErrors&&"object"===typeof console&&"function"===typeof console.log?(console.log(d),2<=arguments.length&&console.log(c)):"alert"===b.showErrors?(c&&(d+="\n\n"+JSON.stringify(c)),window.alert(d)):"function"===typeof b.showErrors&&b.showErrors(a,e,c)}}function w(a){return"fast"=== 6 | a||"slow"===a?!0:parseInt(a,10)+""!==a+""?!1:0<=a}function I(){for(var a=0;a=b;b++){var c=B[a]+b;s[c]=c+"-"+n()}b="KQRBNP".split("");for(a=0;a';!0===b.sparePieces&&(a+='
');a+='
';!0===b.sparePieces&&(a+='
'); 7 | return a+""}function A(a){"black"!==a&&(a="white");var e="",c=m(B),d=8;"black"===a&&(c.reverse(),d=1);for(var C="white",f=0;8>f;f++){for(var e=e+('
'),k=0;8>k;k++){var g=c[k]+d,e=e+('
');if(!0===b.showNotation){if("white"===a&&1===d||"black"===a&&8===d)e+='
'+c[k]+"
";0===k&&(e+='
'+d+"
")}e+="
";C="white"===C?"black":"white"}e+='
';C="white"===C?"black":"white";"white"===a?d--:d++}return e}function Y(a){if("function"===typeof b.pieceTheme)return b.pieceTheme(a);if("string"===typeof b.pieceTheme)return b.pieceTheme.replace(/{piece}/g,a);r(8272,"Unable to build image source for cfg.pieceTheme.");return""}function D(a,b,c){var d=''}function N(a){var b="wK wQ wR wB wN wP".split(" ");"black"===a&&(b="bK bQ bR bB bN bP".split(" "));a="";for(var c=0;c=d?c:d}function la(a){for(var b=[],c=0;8>c;c++)for(var d=0;8>d;d++){var g=B[c]+(d+1);a!==g&&b.push({square:g,distance:ka(a,g)})}b.sort(function(a,b){return a.distance-b.distance});a=[];for(c=0;c=d.left&&a=d.top&&b=a)p=0;else{for(a-=1;0!==a%8&&0=1E8*b.major+1E4*b.minor+b.patch;return a?!0:(window.alert("ChessBoard Error 1005: Unable to find a valid version of jQuery. Please include jQuery 1.7.0 or higher on the page.\n\nExiting..."), 29 | !1)}()){if("string"===typeof b||!0===F(b))b={position:b};"black"!==b.orientation&&(b.orientation="white");u=b.orientation;!1!==b.showNotation&&(b.showNotation=!0);!0!==b.draggable&&(b.draggable=!1);"trash"!==b.dropOffBoard&&(b.dropOffBoard="snapback");!0!==b.sparePieces&&(b.sparePieces=!1);!0===b.sparePieces&&(b.draggable=!0);if(!0!==b.hasOwnProperty("pieceTheme")||"string"!==typeof b.pieceTheme&&"function"!==typeof b.pieceTheme)b.pieceTheme="img/chesspieces/wikipedia/{piece}.png";if(!0!==b.hasOwnProperty("appearSpeed")|| 30 | !0!==w(b.appearSpeed))b.appearSpeed=200;if(!0!==b.hasOwnProperty("moveSpeed")||!0!==w(b.moveSpeed))b.moveSpeed=200;if(!0!==b.hasOwnProperty("snapbackSpeed")||!0!==w(b.snapbackSpeed))b.snapbackSpeed=50;if(!0!==b.hasOwnProperty("snapSpeed")||!0!==w(b.snapSpeed))b.snapSpeed=25;if(!0!==b.hasOwnProperty("trashSpeed")||!0!==w(b.trashSpeed))b.trashSpeed=100;!0===b.hasOwnProperty("position")&&("start"===b.position?g=m(fa):!0===Q(b.position)?g=K(b.position):!0===F(b.position)?g=m(b.position):r(7263,"Invalid value passed to config.position.", 31 | b.position));W=!0}W&&(I(),ya(),xa());return q};window.ChessBoard.fenToObj=K;window.ChessBoard.objToFen=L})(); -------------------------------------------------------------------------------- /static/scripts.js: -------------------------------------------------------------------------------- 1 | var board, 2 | game = new Chess(), 3 | statusEl = $('#status'), 4 | fenEl = $('#fen'), 5 | pgnEl = $('#pgn'); 6 | 7 | 8 | // do not pick up pieces if the game is over 9 | // only pick up pieces for the side to move 10 | var onDragStart = function(source, piece, position, orientation) { 11 | if (game.game_over() === true || 12 | (game.turn() === 'w' && piece.search(/^b/) !== -1) || 13 | (game.turn() === 'b' && piece.search(/^w/) !== -1)) { 14 | return false; 15 | } 16 | }; 17 | 18 | var onDrop = function(source, target) { 19 | // see if the move is legal 20 | var move = game.move({ 21 | from: source, 22 | to: target, 23 | promotion: 'q' // NOTE: always promote to a queen for example simplicity 24 | }); 25 | 26 | // illegal move 27 | if (move === null) return 'snapback'; 28 | 29 | updateStatus(); 30 | getResponseMove(); 31 | }; 32 | 33 | // update the board position after the piece snap 34 | // for castling, en passant, pawn promotion 35 | var onSnapEnd = function() { 36 | board.position(game.fen()); 37 | }; 38 | 39 | var updateStatus = function() { 40 | var status = ''; 41 | 42 | var moveColor = 'White'; 43 | if (game.turn() === 'b') { 44 | moveColor = 'Black'; 45 | } 46 | 47 | // checkmate? 48 | if (game.in_checkmate() === true) { 49 | status = 'Game over, ' + moveColor + ' is in checkmate.'; 50 | } 51 | 52 | // draw? 53 | else if (game.in_draw() === true) { 54 | status = 'Game over, drawn position'; 55 | } 56 | 57 | // game still on 58 | else { 59 | status = moveColor + ' to move'; 60 | 61 | // check? 62 | if (game.in_check() === true) { 63 | status += ', ' + moveColor + ' is in check'; 64 | } 65 | } 66 | 67 | setStatus(status); 68 | getLastCapture(); 69 | createTable(); 70 | updateScroll(); 71 | 72 | statusEl.html(status); 73 | fenEl.html(game.fen()); 74 | pgnEl.html(game.pgn()); 75 | }; 76 | 77 | var cfg = { 78 | draggable: true, 79 | position: 'start', 80 | onDragStart: onDragStart, 81 | onDrop: onDrop, 82 | onSnapEnd: onSnapEnd 83 | }; 84 | 85 | var randomResponse = function() { 86 | fen = game.fen() 87 | $.get($SCRIPT_ROOT + "/move/" + fen, function(data) { 88 | game.move(data, {sloppy: true}); 89 | // board.position(game.fen()); 90 | updateStatus(); 91 | }) 92 | } 93 | 94 | var getResponseMove = function() { 95 | var e = document.getElementById("sel1"); 96 | var depth = e.options[e.selectedIndex].value; 97 | fen = game.fen() 98 | $.get($SCRIPT_ROOT + "/move/" + depth + "/" + fen, function(data) { 99 | game.move(data, {sloppy: true}); 100 | updateStatus(); 101 | // This is terrible and I should feel bad. Find some way to fix this properly. 102 | // The animations would stutter when moves were returned too quick, so I added a 100ms delay before the animation 103 | setTimeout(function(){ board.position(game.fen()); }, 100); 104 | }) 105 | } 106 | 107 | 108 | // did this based on a stackoverflow answer 109 | // http://stackoverflow.com/questions/29493624/cant-display-board-whereas-the-id-is-same-when-i-use-chessboard-js 110 | setTimeout(function() { 111 | board = ChessBoard('board', cfg); 112 | // updateStatus(); 113 | }, 0); 114 | 115 | 116 | var setPGN = function() { 117 | var table = document.getElementById("pgn"); 118 | var pgn = game.pgn().split(" "); 119 | var move = pgn[pgn.length - 1]; 120 | } 121 | 122 | var createTable = function() { 123 | 124 | var pgn = game.pgn().split(" "); 125 | var data = []; 126 | 127 | for (i = 0; i < pgn.length; i += 3) { 128 | var index = i / 3; 129 | data[index] = {}; 130 | for (j = 0; j < 3; j++) { 131 | var label = ""; 132 | if (j === 0) { 133 | label = "moveNumber"; 134 | } else if (j === 1) { 135 | label = "whiteMove"; 136 | } else if (j === 2) { 137 | label = "blackMove"; 138 | } 139 | if (pgn.length > i + j) { 140 | data[index][label] = pgn[i + j]; 141 | } else { 142 | data[index][label] = ""; 143 | } 144 | } 145 | } 146 | 147 | $('#pgn tr').not(':first').remove(); 148 | var html = ''; 149 | for (var i = 0; i < data.length; i++) { 150 | html += '' + data[i].moveNumber + '' 151 | + data[i].whiteMove + '' 152 | + data[i].blackMove + ''; 153 | } 154 | 155 | $('#pgn tr').first().after(html); 156 | } 157 | 158 | var updateScroll = function() { 159 | $('#moveTable').scrollTop($('#moveTable')[0].scrollHeight); 160 | } 161 | 162 | var setStatus = function(status) { 163 | document.getElementById("status").innerHTML = status; 164 | } 165 | 166 | var takeBack = function() { 167 | game.undo(); 168 | if (game.turn() != "w") { 169 | game.undo(); 170 | } 171 | board.position(game.fen()); 172 | updateStatus(); 173 | } 174 | 175 | var newGame = function() { 176 | game.reset(); 177 | board.start(); 178 | updateStatus(); 179 | } 180 | 181 | var getCapturedPieces = function() { 182 | var history = game.history({ verbose: true }); 183 | for (var i = 0; i < history.length; i++) { 184 | if ("captured" in history[i]) { 185 | console.log(history[i]["captured"]); 186 | } 187 | } 188 | } 189 | 190 | var getLastCapture = function() { 191 | var history = game.history({ verbose: true }); 192 | var index = history.length - 1; 193 | 194 | if (history[index] != undefined && "captured" in history[index]) { 195 | console.log(history[index]["captured"]); 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /static/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 70px; 3 | align-items: center; 4 | } 5 | 6 | #board { 7 | padding: 10px; 8 | max-width: 500px; 9 | min-width: 300px; 10 | } 11 | 12 | .navbar { 13 | background-color: red; 14 | } 15 | 16 | #logo { 17 | position: relative; 18 | left: 10%; 19 | } 20 | 21 | #secondWindow { 22 | max-width: 800px; 23 | margin: 10px; 24 | min-height: 370px; 25 | } 26 | 27 | #moveTable { 28 | width: 100%; 29 | max-height: 250px; 30 | overflow: auto; 31 | } 32 | 33 | #controls { 34 | position: absolute; 35 | bottom: 0; 36 | } 37 | -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ChessBot 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 15 | 16 | 18 | 19 | 20 | 21 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |

White to move

49 |
50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 |
#WhiteBlack
59 |
60 |
61 |
62 | 63 | 64 | 71 |
72 |
73 |
74 |
75 |
76 |
77 | 78 | 79 | --------------------------------------------------------------------------------