File indexing completed on 2024-04-28 15:07:46
0001 /* 0002 * SPDX-FileCopyrightText: 2016 Shubhendu Saurabh <me@shubhu.in> 0003 * SPDX-License-Identifier: MPL-2.0 0004 * 0005 * Code comes from https://github.com/shubhendusaurabh/draughts.js 0006 */ 0007 0008 /* 0009 ||================================================================================== 0010 || DESCRIPTION OF IMPLEMENTATION PRINCIPLES 0011 || A. Position for rules (internal representation): string with length 56. 0012 || Special numbering for easy applying rules. 0013 || Valid characters: b B w W 0 - 0014 || b (black) B (black king) w (white) W (white king) 0 (empty) (- unused) 0015 || Examples: 0016 || '-bbbBBB000w-wwWWWwwwww-bbbbbbbbbb-000wwwwwww-00bbbwwWW0-' 0017 || '-0000000000-0000000000-0000000000-0000000000-0000000000-' (empty position) 0018 || '-bbbbbbbbbb-bbbbbbbbbb-0000000000-wwwwwwwwww-wwwwwwwwww-' (start position) 0019 || B. Position (external respresentation): string with length 51. 0020 || Square numbers are represented by the position of the characters. 0021 || Position 0 is reserved for the side to move (B or W) 0022 || Valid characters: b B w W 0 0023 || b (black) B (black king) w (white) W (white king) 0 (empty) 0024 || Examples: 0025 || 'B00000000000000000000000000000000000000000000000000' (empty position) 0026 || 'Wbbbbbbbbbbbbbbbbbbbb0000000000wwwwwwwwwwwwwwwwwwww' (start position) 0027 || 'WbbbbbbBbbbbb00bbbbb000000w0W00ww00wwwwww0wwwwwwwww' (random position) 0028 || 0029 || External numbering Internal Numbering 0030 || -------------------- -------------------- 0031 || 01 02 03 04 05 01 02 03 04 05 0032 || 06 07 08 09 10 06 07 08 09 10 0033 || 11 12 13 14 15 12 13 14 15 16 0034 || 16 17 18 19 20 17 18 19 20 21 0035 || 21 22 23 24 25 23 24 25 26 27 0036 || 26 27 28 29 30 28 29 30 31 32 0037 || 31 32 33 34 35 34 35 36 37 38 0038 || 36 37 38 39 40 39 40 41 42 43 0039 || 41 42 43 44 45 45 46 47 48 49 0040 || 46 47 48 49 50 50 51 52 53 54 0041 || -------------------- -------------------- 0042 || 0043 || Internal numbering has fixed direction increments for easy applying rules: 0044 || NW NE -5 -6 0045 || \ / \ / 0046 || sQr >> sQr 0047 || / \ / \ 0048 || SW SE +5 +6 0049 || 0050 || DIRECTION-STRINGS 0051 || Strings of variable length for each of four directions at one square. 0052 || Each string represents the position in that direction. 0053 || Directions: NE, SE, SW, NW (wind directions) 0054 || Example for square 29 (internal number): 0055 || NE: 29, 24, 19, 14, 09, 04 b00bb0 0056 || SE: 35, 41, 47, 53 bww0 0057 || SW: 34, 39 b0 0058 || NW: 23, 17 bw 0059 || CONVERSION internal to external representation of numbers. 0060 || N: external number, values 1..50 0061 || M: internal number, values 0..55 (invalid 0,11,22,33,44,55) 0062 || Formulas: 0063 || M = N + floor((N-1)/10) 0064 || N = M - floor((M-1)/11) 0065 || 0066 ||================================================================================== 0067 */ 0068 var Draughts = function (fen) { 0069 var BLACK = 'B' 0070 var WHITE = 'W' 0071 // var EMPTY = -1 0072 var MAN = 'b' 0073 var KING = 'w' 0074 var SYMBOLS = 'bwBW' 0075 var DEFAULT_FEN = 'W:W31-50:B1-20' 0076 var position 0077 var DEFAULT_POSITION_INTERNAL = '-bbbbbbbbbb-bbbbbbbbbb-0000000000-wwwwwwwwww-wwwwwwwwww-' 0078 var DEFAULT_POSITION_EXTERNAL = 'Wbbbbbbbbbbbbbbbbbbbb0000000000wwwwwwwwwwwwwwwwwwww' 0079 var STEPS = {NE: -5, SE: 6, SW: 5, NW: -6} 0080 var POSSIBLE_RESULTS = ['2-0', '0-2', '1-1', '0-0', '*', '1-0', '0-1'] 0081 var FLAGS = { 0082 NORMAL: 'n', 0083 CAPTURE: 'c', 0084 PROMOTION: 'p' 0085 } 0086 0087 var UNICODES = { 0088 'w': '\u26C0', 0089 'b': '\u26C2', 0090 'B': '\u26C3', 0091 'W': '\u26C1', 0092 '0': '\u0020\u0020' 0093 } 0094 0095 var SIGNS = { 0096 n: '-', 0097 c: 'x' 0098 } 0099 var BITS = { 0100 NORMAL: 1, 0101 CAPTURE: 2, 0102 PROMOTION: 4 0103 } 0104 0105 var turn = WHITE 0106 var moveNumber = 1 0107 var history = [] 0108 var header = {} 0109 0110 if (!fen) { 0111 position = DEFAULT_POSITION_INTERNAL 0112 load(DEFAULT_FEN) 0113 } else { 0114 position = DEFAULT_POSITION_INTERNAL 0115 load(fen) 0116 } 0117 0118 function clear () { 0119 position = DEFAULT_POSITION_INTERNAL 0120 turn = WHITE 0121 moveNumber = 1 0122 history = [] 0123 header = {} 0124 update_setup(generate_fen()) 0125 } 0126 0127 function reset () { 0128 load(DEFAULT_FEN) 0129 } 0130 0131 function load (fen) { 0132 // TODO for default fen 0133 if (!fen || fen === DEFAULT_FEN) { 0134 position = DEFAULT_POSITION_INTERNAL 0135 update_setup(generate_fen(position)) 0136 return true 0137 } 0138 // fen_constants(dimension) //TODO for empty fens 0139 0140 var checkedFen = validate_fen(fen) 0141 if (!checkedFen.valid) { 0142 console.error('Fen Error', fen, checkedFen) 0143 return false 0144 } 0145 0146 clear() 0147 0148 // remove spaces 0149 fen = fen.replace(/\s+/g, '') 0150 // remove suffixes 0151 fen.replace(/\..*$/, '') 0152 0153 var tokens = fen.split(':') 0154 // which side to move 0155 turn = tokens[0].substr(0, 1) 0156 0157 // var positions = new Array() 0158 var externalPosition = DEFAULT_POSITION_EXTERNAL 0159 for (var i = 1; i <= externalPosition.length; i++) { 0160 externalPosition = setCharAt(externalPosition, i, 0) 0161 } 0162 externalPosition = setCharAt(externalPosition, 0, turn) 0163 // TODO refactor 0164 for (var k = 1; k <= 2; k++) { 0165 // TODO called twice 0166 var color = tokens[k].substr(0, 1) 0167 var sideString = tokens[k].substr(1) 0168 if (sideString.length === 0) continue 0169 var numbers = sideString.split(',') 0170 for (i = 0; i < numbers.length; i++) { 0171 var numSquare = numbers[i] 0172 var isKing = (numSquare.substr(0, 1) === 'K') 0173 numSquare = (isKing === true ? numSquare.substr(1) : numSquare) // strip K 0174 var range = numSquare.split('-') 0175 if (range.length === 2) { 0176 var from = parseInt(range[0], 10) 0177 var to = parseInt(range[1], 10) 0178 for (var j = from; j <= to; j++) { 0179 externalPosition = setCharAt(externalPosition, j, (isKing === true ? color.toUpperCase() : color.toLowerCase())) 0180 } 0181 } else { 0182 numSquare = parseInt(numSquare, 10) 0183 externalPosition = setCharAt(externalPosition, numSquare, (isKing === true ? color.toUpperCase() : color.toLowerCase())) 0184 } 0185 } 0186 } 0187 0188 position = convertPosition(externalPosition, 'internal') 0189 update_setup(generate_fen(position)) 0190 0191 return true 0192 } 0193 0194 function validate_fen (fen) { 0195 var errors = [ 0196 { 0197 code: 0, 0198 message: 'no errors' 0199 }, 0200 { 0201 code: 1, 0202 message: 'fen position not a string' 0203 }, 0204 { 0205 code: 2, 0206 message: 'fen position has not colon at second position' 0207 }, 0208 { 0209 code: 3, 0210 message: 'fen position has not 2 colons' 0211 }, 0212 { 0213 code: 4, 0214 message: 'side to move of fen position not valid' 0215 }, 0216 { 0217 code: 5, 0218 message: 'color(s) of sides of fen position not valid' 0219 }, 0220 { 0221 code: 6, 0222 message: 'squares of fen position not integer' 0223 }, 0224 { 0225 code: 7, 0226 message: 'squares of fen position not valid' 0227 }, 0228 { 0229 code: 8, 0230 message: 'empty fen position' 0231 } 0232 ] 0233 0234 if (typeof fen !== 'string') { 0235 return {valid: false, error: errors[0], fen: fen} 0236 } 0237 0238 fen = fen.replace(/\s+/g, '') 0239 0240 if (fen === 'B::' || fen === 'W::' || fen === '?::') { 0241 return {valid: true, fen: fen + ':B:W'} // exception allowed i.e. empty fen 0242 } 0243 fen = fen.trim() 0244 fen = fen.replace(/\..*$/, '') 0245 0246 if (fen === '') { 0247 return {valid: false, error: errors[7], fen: fen} 0248 } 0249 0250 if (fen.substr(1, 1) !== ':') { 0251 return {valid: false, error: errors[1], fen: fen} 0252 } 0253 0254 // fen should be 3 sections separated by colons 0255 var parts = fen.split(':') 0256 if (parts.length !== 3) { 0257 return {valid: false, error: errors[2], fen: fen} 0258 } 0259 0260 // which side to move 0261 var turnColor = parts[0] 0262 if (turnColor !== 'B' && turnColor !== 'W' && turnColor !== '?') { 0263 return {valid: false, error: errors[3], fen: fen} 0264 } 0265 0266 // check colors of both sides 0267 var colors = parts[1].substr(0, 1) + parts[2].substr(0, 1) 0268 if (colors !== 'BW' && colors !== 'WB') { 0269 return {valid: false, error: errors[4], fen: fen} 0270 } 0271 0272 // check parts for both sides 0273 for (var k = 1; k <= 2; k += 1) { 0274 var sideString = parts[k].substr(1) // Stripping color 0275 if (sideString.length === 0) { 0276 continue 0277 } 0278 var numbers = sideString.split(',') 0279 for (var i = 0; i < numbers.length; i++) { 0280 var numSquare = numbers[i] 0281 var isKing = (numSquare.substr(0, 1) === 'K') 0282 numSquare = (isKing === true ? numSquare.substr(1) : numSquare) 0283 var range = numSquare.split('-') 0284 if (range.length === 2) { 0285 if (isInteger(range[0]) === false) { 0286 return {valid: false, error: errors[5], fen: fen, range: range[0]} 0287 } 0288 if (!(range[0] >= 1 && range[0] <= 100)) { 0289 return {valid: false, error: errors[6], fen: fen} 0290 } 0291 if (isInteger(range[1]) === false) { 0292 return {valid: false, error: errors[5], fen: fen} 0293 } 0294 if (!(range[1] >= 1 && range[1] <= 100)) { 0295 return {valid: false, error: errors[6], fen: fen} 0296 } 0297 } else { 0298 if (isInteger(numSquare) === false) { 0299 return {valid: false, error: errors[5], fen: fen} 0300 } 0301 if (!(numSquare >= 1 && numSquare <= 100)) { 0302 return {valid: false, error: errors[6], fen: fen} 0303 } 0304 } 0305 } 0306 } 0307 0308 return {valid: true, error_number: 0, error: errors[0]} 0309 } 0310 0311 function generate_fen () { 0312 var black = [] 0313 var white = [] 0314 var externalPosition = convertPosition(position, 'external') 0315 for (var i = 0; i < externalPosition.length; i++) { 0316 switch (externalPosition[i]) { 0317 case 'w': 0318 white.push(i) 0319 break 0320 case 'W': 0321 white.push('K' + i) 0322 break 0323 case 'b': 0324 black.push(i) 0325 break 0326 case 'B': 0327 black.push('K' + i) 0328 break 0329 default: 0330 break 0331 } 0332 } 0333 return turn.toUpperCase() + ':W' + white.join(',') + ':B' + black.join(',') 0334 } 0335 0336 function generatePDN (options) { 0337 // for html usage {maxWidth: 72, newline_char: "<br />"} 0338 var newline = (typeof options === 'object' && typeof options.newline_char === 'string') 0339 ? options.newline_char : '\n' 0340 var maxWidth = (typeof options === 'object' && typeof options.maxWidth === 'number') 0341 ? options.maxWidth : 0 0342 var result = [] 0343 var headerExists = false 0344 0345 for (var i in header) { 0346 result.push('[' + i + ' "' + header[i] + '"]' + newline) 0347 headerExists = true 0348 } 0349 0350 if (headerExists && history.length) { 0351 result.push(newline) 0352 } 0353 0354 var tempHistory = clone(history) 0355 0356 var moves = [] 0357 var moveString = '' 0358 var moveNumber = 1 0359 0360 while (tempHistory.length > 0) { 0361 var move = tempHistory.shift() 0362 if (move.turn === 'W') { 0363 moveString += moveNumber + '. ' 0364 } 0365 moveString += move.move.from 0366 if (move.move.flags === 'c') { 0367 moveString += 'x' 0368 } else { 0369 moveString += '-' 0370 } 0371 moveString += move.move.to 0372 moveString += ' ' 0373 moveNumber += 1 0374 } 0375 0376 if (moveString.length) { 0377 moves.push(moveString) 0378 } 0379 0380 // TODO result from pdn or header?? 0381 if (typeof header.Result !== 'undefined') { 0382 moves.push(header.Result) 0383 } 0384 0385 if (maxWidth === 0) { 0386 return result.join('') + moves.join(' ') 0387 } 0388 0389 var currentWidth = 0 0390 for (i = 0; i < moves.length; i++) { 0391 if (currentWidth + moves[i].length > maxWidth && i !== 0) { 0392 if (result[result.length - 1] === ' ') { 0393 result.pop() 0394 } 0395 0396 result.push(newline) 0397 currentWidth = 0 0398 } else if (i !== 0) { 0399 result.push(' ') 0400 currentWidth++ 0401 } 0402 result.push(' ') 0403 currentWidth += moves[i].length 0404 } 0405 0406 return result.join('') 0407 } 0408 0409 function set_header (args) { 0410 for (var i = 0; i < args.length; i += 2) { 0411 if (typeof args[i] === 'string' && typeof args[i + 1] === 'string') { 0412 header[args[i]] = args[i + 1] 0413 } 0414 } 0415 return header 0416 } 0417 0418 /* called when the initial board setup is changed with put() or remove(). 0419 * modifies the SetUp and FEN properties of the header object. if the FEN is 0420 * equal to the default position, the SetUp and FEN are deleted 0421 * the setup is only updated if history.length is zero, ie moves haven't been 0422 * made. 0423 */ 0424 function update_setup (fen) { 0425 if (history.length > 0) { 0426 return false 0427 } 0428 if (fen !== DEFAULT_FEN) { 0429 header['SetUp'] = '1' 0430 header['FEN'] = fen 0431 } else { 0432 delete header['SetUp'] 0433 delete header['FEN'] 0434 } 0435 } 0436 0437 function parsePDN (pdn, options) { 0438 var newline_char = (typeof options === 'object' && 0439 typeof options.newline_char === 'string') 0440 ? options.newline_char : '\r?\n' 0441 var regex = new RegExp('^(\\[(.|' + mask(newline_char) + ')*\\])' + 0442 '(' + mask(newline_char) + ')*' + 0443 '1.(' + mask(newline_char) + '|.)*$', 'g') 0444 0445 function mask (str) { 0446 return str.replace(/\\/g, '\\') 0447 } 0448 0449 function parsePDNHeader (header, options) { 0450 var headerObj = {} 0451 var headers = header.split(new RegExp(mask(newline_char))) 0452 var key = '' 0453 var value = '' 0454 0455 for (var i = 0; i < headers.length; i++) { 0456 key = headers[i].replace(/^\[([A-Z][A-Za-z]*)\s.*\]$/, '$1') 0457 value = headers[i].replace(/^\[[A-Za-z]+\s"(.*)"\]$/, '$1') 0458 if (trim(key).length > 0) { 0459 headerObj[key] = value 0460 } 0461 } 0462 0463 return headerObj 0464 } 0465 0466 var headerString = pdn.replace(regex, '$1') 0467 if (headerString[0] !== '[') { 0468 headerString = '' 0469 } 0470 0471 reset() 0472 0473 var headers = parsePDNHeader(headerString, options) 0474 0475 for (var key in headers) { 0476 set_header([key, headers[key]]) 0477 } 0478 0479 if (headers['Setup'] === '1') { 0480 if (!(('FEN' in headers) && load(headers['FEN']))) { 0481 console.error('fen invalid') 0482 return false 0483 } 0484 } else { 0485 position = DEFAULT_POSITION_INTERNAL 0486 } 0487 0488 /* delete header to get the moves */ 0489 var ms = pdn.replace(headerString, '').replace(new RegExp(mask(newline_char), 'g'), ' ') 0490 0491 /* delete comments */ 0492 ms = ms.replace(/(\{[^}]+\})+?/g, '') 0493 0494 /* delete recursive annotation variations */ 0495 var rav_regex = /(\([^\(\)]+\))+?/g 0496 while (rav_regex.test(ms)) { 0497 ms = ms.replace(rav_regex, '') 0498 } 0499 0500 /* delete move numbers */ 0501 // TODO not working for move numbers with space 0502 ms = ms.replace(/\d+\./g, '') 0503 0504 /* delete ... indicating black to move */ 0505 ms = ms.replace(/\.\.\./g, '') 0506 0507 /* trim and get array of moves */ 0508 var moves = trim(ms).split(new RegExp(/\s+/)) 0509 0510 /* delete empty entries */ 0511 moves = moves.join(',').replace(/,,+/g, ',').split(',') 0512 0513 var move = '' 0514 for (var half_move = 0; half_move < moves.length - 1; half_move += 1) { 0515 move = getMoveObject(moves[half_move]) 0516 if (!move) { 0517 return false 0518 } else { 0519 makeMove(move) 0520 } 0521 } 0522 0523 var result = moves[moves.length - 1] 0524 if (POSSIBLE_RESULTS.indexOf(result) > -1) { 0525 if (headers['Result'] === 'undefined') { 0526 set_header(['Result', result]) 0527 } 0528 } else { 0529 move = getMoveObject(result) 0530 if (!move) { 0531 return false 0532 } else { 0533 makeMove(move) 0534 } 0535 } 0536 return true 0537 } 0538 0539 function getMoveObject (move) { 0540 // TODO move flags for both capture and promote?? 0541 var tempMove = {} 0542 var matches = move.split(/[x|-]/) 0543 tempMove.from = parseInt(matches[0], 10) 0544 tempMove.to = parseInt(matches[1], 10) 0545 var moveType = move.match(/[x|-]/)[0] 0546 if (moveType === '-') { 0547 tempMove.flags = FLAGS.NORMAL 0548 } else { 0549 tempMove.flags = FLAGS.CAPTURE 0550 } 0551 tempMove.piece = position.charAt(convertNumber(tempMove.from, 'internal')) 0552 var moves = getLegalMoves(tempMove.from) 0553 moves = convertMoves(moves, 'external') 0554 // if move legal then make move 0555 for (var i = 0; i < moves.length; i += 1) { 0556 if (tempMove.to === moves[i].to && tempMove.from === moves[i].from) { 0557 if (moves[i].takes.length > 0) { 0558 tempMove.flags = FLAGS.CAPTURE 0559 tempMove.captures = moves[i].takes 0560 tempMove.takes = moves[i].takes 0561 tempMove.piecesCaptured = moves[i].piecesTaken 0562 } 0563 return tempMove 0564 } 0565 } 0566 console.log(moves, tempMove) 0567 return false 0568 } 0569 0570 function makeMove (move) { 0571 move.piece = position.charAt(convertNumber(move.from, 'internal')) 0572 position = setCharAt(position, convertNumber(move.to, 'internal'), move.piece) 0573 position = setCharAt(position, convertNumber(move.from, 'internal'), 0) 0574 move.flags = FLAGS.NORMAL 0575 // TODO refactor to either takes or capture 0576 if (move.takes && move.takes.length) { 0577 move.flags = FLAGS.CAPTURE 0578 move.captures = move.takes 0579 move.piecesCaptured = move.piecesTaken 0580 for (var i = 0; i < move.takes.length; i++) { 0581 position = setCharAt(position, convertNumber(move.takes[i], 'internal'), 0) 0582 } 0583 } 0584 // Promoting piece here 0585 if (move.to <= 5 && move.piece === 'w') { 0586 move.flags = FLAGS.PROMOTION 0587 position = setCharAt(position, convertNumber(move.to, 'internal'), move.piece.toUpperCase()) 0588 } else if (move.to >= 46 && move.piece === 'b') { 0589 position = setCharAt(position, convertNumber(move.to, 'internal'), move.piece.toUpperCase()) 0590 } 0591 push(move) 0592 if (turn === BLACK) { 0593 moveNumber += 1 0594 } 0595 turn = swap_color(turn) 0596 } 0597 0598 function get (square) { 0599 var piece = position.charAt(convertNumber(square, 'internal')) 0600 return piece 0601 } 0602 0603 function put (piece, square) { 0604 // check for valid piece string 0605 if (SYMBOLS.match(piece) === null) { 0606 return false 0607 } 0608 0609 // check for valid square 0610 if (outsideBoard(convertNumber(square, 'internal')) === true) { 0611 return false 0612 } 0613 position = setCharAt(position, convertNumber(square, 'internal'), piece) 0614 update_setup(generate_fen()) 0615 0616 return true 0617 } 0618 0619 function remove (square) { 0620 var piece = get(square) 0621 position = setCharAt(position, convertNumber(square, 'internal'), 0) 0622 update_setup(generate_fen()) 0623 0624 return piece 0625 } 0626 0627 function build_move (board, from, to, flags, promotion) { 0628 var move = { 0629 color: turn, 0630 from: from, 0631 to: to, 0632 flags: flags, 0633 piece: board[from].type 0634 } 0635 0636 if (promotion) { 0637 move.flags |= BITS.PROMOTION 0638 } 0639 0640 if (board[to]) { 0641 move.captured = board[to].type 0642 } else if (flags & BITS.CAPTURE) { 0643 move.captured = MAN 0644 } 0645 return move 0646 } 0647 0648 function generate_moves (square) { 0649 var moves = [] 0650 0651 if (square) { 0652 moves = getLegalMoves(square.square) 0653 } else { 0654 var tempCaptures = getCaptures() 0655 // TODO change to be applicable to array 0656 if (tempCaptures.length) { 0657 for (var i = 0; i < tempCaptures.length; i++) { 0658 tempCaptures[i].flags = FLAGS.CAPTURE 0659 tempCaptures[i].captures = tempCaptures[i].jumps 0660 tempCaptures[i].piecesCaptured = tempCaptures[i].piecesTaken 0661 } 0662 return tempCaptures 0663 } 0664 moves = getMoves() 0665 } 0666 // TODO returns [] for on hovering for square no 0667 moves = [].concat.apply([], moves) 0668 return moves 0669 } 0670 0671 function getLegalMoves (index) { 0672 var legalMoves 0673 index = parseInt(index, 10) 0674 if (!Number.isNaN(index)) { 0675 index = convertNumber(index, 'internal') 0676 0677 var captures = capturesAtSquare(index, {position: position, dirFrom: ''}, {jumps: [index], takes: [], piecesTaken: []}) 0678 0679 captures = longestCapture(captures) 0680 legalMoves = captures 0681 if (captures.length === 0) { 0682 legalMoves = movesAtSquare(index) 0683 } 0684 } 0685 // TODO called on hover ?? 0686 return convertMoves(legalMoves, 'external') 0687 } 0688 0689 function getMoves (index) { 0690 var moves = [] 0691 var us = turn 0692 0693 for (var i = 1; i < position.length; i++) { 0694 if (position[i] === us || position[i] === us.toLowerCase()) { 0695 var tempMoves = movesAtSquare(i) 0696 if (tempMoves.length) { 0697 moves = moves.concat(convertMoves(tempMoves, 'external')) 0698 } 0699 } 0700 } 0701 return moves 0702 } 0703 0704 function setCharAt (position, idx, chr) { 0705 idx = parseInt(idx, 10) 0706 if (idx > position.length - 1) { 0707 return position.toString() 0708 } else { 0709 return position.substr(0, idx) + chr + position.substr(idx + 1) 0710 } 0711 } 0712 0713 function movesAtSquare (square) { 0714 var moves = [] 0715 var posFrom = square 0716 var piece = position.charAt(posFrom) 0717 // console.trace(piece, square, 'movesAtSquare') 0718 switch (piece) { 0719 case 'b': 0720 case 'w': 0721 var dirStrings = directionStrings(position, posFrom, 2) 0722 for (var dir in dirStrings) { 0723 var str = dirStrings[dir] 0724 0725 var matchArray = str.match(/^[bw]0/) // e.g. b0 w0 0726 if (matchArray !== null && validDir(piece, dir) === true) { 0727 var posTo = posFrom + STEPS[dir] 0728 var moveObject = {from: posFrom, to: posTo, takes: [], jumps: []} 0729 moves.push(moveObject) 0730 } 0731 } 0732 break 0733 case 'W': 0734 case 'B': 0735 dirStrings = directionStrings(position, posFrom, 2) 0736 for (dir in dirStrings) { 0737 str = dirStrings[dir] 0738 0739 matchArray = str.match(/^[BW]0+/) // e.g. B000, W0 0740 if (matchArray !== null) { 0741 for (var i = 1; i < matchArray[0].length; i++) { 0742 posTo = posFrom + (i * STEPS[dir]) 0743 moveObject = {from: posFrom, to: posTo, takes: [], jumps: []} 0744 moves.push(moveObject) 0745 } 0746 } 0747 } 0748 break 0749 default: 0750 return moves 0751 } 0752 return moves 0753 } 0754 0755 function getCaptures () { 0756 var us = turn 0757 var captures = [] 0758 for (var i = 0; i < position.length; i++) { 0759 if (position[i] === us || position[i] === us.toLowerCase()) { 0760 var posFrom = i 0761 var state = {position: position, dirFrom: ''} 0762 var capture = {jumps: [], takes: [], from: posFrom, to: '', piecesTaken: []} 0763 capture.jumps[0] = posFrom 0764 var tempCaptures = capturesAtSquare(posFrom, state, capture) 0765 if (tempCaptures.length) { 0766 captures = captures.concat(convertMoves(tempCaptures, 'external')) 0767 } 0768 } 0769 } 0770 captures = longestCapture(captures) 0771 return captures 0772 } 0773 0774 function capturesAtSquare (posFrom, state, capture) { 0775 var piece = state.position.charAt(posFrom) 0776 if (piece !== 'b' && piece !== 'w' && piece !== 'B' && piece !== 'W') { 0777 return [capture] 0778 } 0779 var dirString 0780 if (piece === 'b' || piece === 'w') { 0781 dirString = directionStrings(state.position, posFrom, 3) 0782 } else { 0783 dirString = directionStrings(state.position, posFrom) 0784 } 0785 var finished = true 0786 var captureArrayForDir = {} 0787 for (var dir in dirString) { 0788 if (dir === state.dirFrom) { 0789 continue 0790 } 0791 var str = dirString[dir] 0792 switch (piece) { 0793 case 'b': 0794 case 'w': 0795 var matchArray = str.match(/^b[wW]0|^w[bB]0/) // matches: bw0, bW0, wB0, wb0 0796 if (matchArray !== null) { 0797 var posTo = posFrom + (2 * STEPS[dir]) 0798 var posTake = posFrom + (1 * STEPS[dir]) 0799 if (capture.takes.indexOf(posTake) > -1) { 0800 continue // capturing twice forbidden 0801 } 0802 var updateCapture = clone(capture) 0803 updateCapture.to = posTo 0804 updateCapture.jumps.push(posTo) 0805 updateCapture.takes.push(posTake) 0806 updateCapture.piecesTaken.push(position.charAt(posTake)) 0807 updateCapture.from = posFrom 0808 var updateState = clone(state) 0809 updateState.dirFrom = oppositeDir(dir) 0810 var pieceCode = updateState.position.charAt(posFrom) 0811 updateState.position = setCharAt(updateState.position, posFrom, 0) 0812 updateState.position = setCharAt(updateState.position, posTo, pieceCode) 0813 finished = false 0814 captureArrayForDir[dir] = capturesAtSquare(posTo, updateState, updateCapture) 0815 } 0816 break 0817 case 'B': 0818 case 'W': 0819 matchArray = str.match(/^B0*[wW]0+|^W0*[bB]0+/) // matches: B00w000, WB00 0820 if (matchArray !== null) { 0821 var matchStr = matchArray[0] 0822 var matchArraySubstr = matchStr.match(/[wW]0+$|[bB]0+$/) // matches: w000, B00 0823 var matchSubstr = matchArraySubstr[0] 0824 var takeIndex = matchStr.length - matchSubstr.length 0825 posTake = posFrom + (takeIndex * STEPS[dir]) 0826 if (capture.takes.indexOf(posTake) > -1) { 0827 continue 0828 } 0829 for (var i = 1; i < matchSubstr.length; i++) { 0830 posTo = posFrom + ((takeIndex + i) * STEPS[dir]) 0831 updateCapture = clone(capture) 0832 updateCapture.jumps.push(posTo) 0833 updateCapture.to = posTo 0834 updateCapture.takes.push(posTake) 0835 updateCapture.piecesTaken.push(position.charAt(posTake)) 0836 updateCapture.posFrom = posFrom 0837 updateState = clone(state) 0838 updateState.dirFrom = oppositeDir(dir) 0839 pieceCode = updateState.position.charAt(posFrom) 0840 updateState.position = setCharAt(updateState.position, posFrom, 0) 0841 updateState.position = setCharAt(updateState.position, posTo, pieceCode) 0842 finished = false 0843 var dirIndex = dir + i.toString() 0844 captureArrayForDir[dirIndex] = capturesAtSquare(posTo, updateState, updateCapture) 0845 } 0846 } 0847 break 0848 default: 0849 captureArrayForDir = [] 0850 } 0851 } 0852 var captureArray = [] 0853 if (finished === true && capture.takes.length) { 0854 // fix for multiple capture 0855 capture.from = capture.jumps[0] 0856 captureArray[0] = capture 0857 } else { 0858 for (dir in captureArrayForDir) { 0859 captureArray = captureArray.concat(captureArrayForDir[dir]) 0860 } 0861 } 0862 return captureArray 0863 } 0864 0865 function push (move) { 0866 history.push({ 0867 move: move, 0868 turn: turn, 0869 moveNumber: moveNumber 0870 }) 0871 } 0872 0873 function undoMove () { 0874 var old = history.pop() 0875 if (!old) { 0876 return null 0877 } 0878 0879 var move = old.move 0880 turn = old.turn 0881 moveNumber = old.moveNumber 0882 0883 position = setCharAt(position, convertNumber(move.from, 'internal'), move.piece) 0884 position = setCharAt(position, convertNumber(move.to, 'internal'), 0) 0885 if (move.flags === 'c') { 0886 for (var i = 0; i < move.captures.length; i += 1) { 0887 position = setCharAt(position, convertNumber(move.captures[i], 'internal'), move.piecesCaptured[i]) 0888 } 0889 } else if (move.flags === 'p') { 0890 if(move.captures) { 0891 for (var i = 0; i < move.captures.length; i += 1) { 0892 position = setCharAt(position, convertNumber(move.captures[i], 'internal'), move.piecesCaptured[i]) 0893 } 0894 } 0895 position = setCharAt(position, convertNumber(move.from, 'internal'), move.piece.toLowerCase()) 0896 } 0897 return move 0898 } 0899 0900 function get_disambiguator (move) { 0901 0902 } 0903 0904 function swap_color (c) { 0905 return c === WHITE ? BLACK : WHITE 0906 } 0907 0908 function isInteger (int) { 0909 var regex = /^\d+$/ 0910 if (regex.test(int)) { 0911 return true 0912 } else { 0913 return false 0914 } 0915 } 0916 0917 function longestCapture (captures) { 0918 var maxJumpCount = 0 0919 for (var i = 0; i < captures.length; i++) { 0920 var jumpCount = captures[i].jumps.length 0921 if (jumpCount > maxJumpCount) { 0922 maxJumpCount = jumpCount 0923 } 0924 } 0925 0926 var selectedCaptures = [] 0927 if (maxJumpCount < 2) { 0928 return selectedCaptures 0929 } 0930 0931 for (i = 0; i < captures.length; i++) { 0932 if (captures[i].jumps.length === maxJumpCount) { 0933 selectedCaptures.push(captures[i]) 0934 } 0935 } 0936 return selectedCaptures 0937 } 0938 0939 function convertMoves (moves, type) { 0940 var tempMoves = [] 0941 if (!type || moves.length === 0) { 0942 return tempMoves 0943 } 0944 for (var i = 0; i < moves.length; i++) { 0945 var moveObject = {jumps: [], takes: []} 0946 moveObject.from = convertNumber(moves[i].from, type) 0947 for (var j = 0; j < moves[i].jumps.length; j++) { 0948 moveObject.jumps[j] = convertNumber(moves[i].jumps[j], type) 0949 } 0950 for (j = 0; j < moves[i].takes.length; j++) { 0951 moveObject.takes[j] = convertNumber(moves[i].takes[j], type) 0952 } 0953 moveObject.to = convertNumber(moves[i].to, type) 0954 moveObject.piecesTaken = moves[i].piecesTaken 0955 tempMoves.push(moveObject) 0956 } 0957 return tempMoves 0958 } 0959 0960 function convertNumber (number, notation) { 0961 var num = parseInt(number, 10) 0962 var result 0963 switch (notation) { 0964 case 'internal': 0965 result = num + Math.floor((num - 1) / 10) 0966 break 0967 case 'external': 0968 result = num - Math.floor((num - 1) / 11) 0969 break 0970 default: 0971 result = num 0972 } 0973 return result 0974 } 0975 0976 function convertPosition (position, notation) { 0977 var sub1, sub2, sub3, sub4, sub5, newPosition 0978 switch (notation) { 0979 case 'internal': 0980 sub1 = position.substr(1, 10) 0981 sub2 = position.substr(11, 10) 0982 sub3 = position.substr(21, 10) 0983 sub4 = position.substr(31, 10) 0984 sub5 = position.substr(41, 10) 0985 newPosition = '-' + sub1 + '-' + sub2 + '-' + sub3 + '-' + sub4 + '-' + sub5 + '-' 0986 break 0987 case 'external': 0988 sub1 = position.substr(1, 10) 0989 sub2 = position.substr(12, 10) 0990 sub3 = position.substr(23, 10) 0991 sub4 = position.substr(34, 10) 0992 sub5 = position.substr(45, 10) 0993 newPosition = '?' + sub1 + sub2 + sub3 + sub4 + sub5 0994 break 0995 default: 0996 newPosition = position 0997 } 0998 return newPosition 0999 } 1000 1001 function outsideBoard (square) { 1002 // internal notation only 1003 var n = parseInt(square, 10) 1004 if (n >= 0 && n <= 55 && (n % 11) !== 0) { 1005 return false 1006 } else { 1007 return true 1008 } 1009 } 1010 1011 function directionStrings (tempPosition, square, maxLength) { 1012 // Create direction strings for square at position (internal representation) 1013 // Output object with four directions as properties (four rhumbs). 1014 // Each property has a string as value representing the pieces in that direction. 1015 // Piece of the given square is part of each string. 1016 // Example of output: {NE: 'b0', SE: 'b00wb00', SW: 'bbb00', NW: 'bb'} 1017 // Strings have maximum length of given maxLength. 1018 if (arguments.length === 2) { 1019 maxLength = 100 1020 } 1021 var dirStrings = {} 1022 if (outsideBoard(square) === true) { 1023 return 334 1024 } 1025 1026 for (var dir in STEPS) { 1027 var dirArray = [] 1028 var i = 0 1029 var index = square 1030 do { 1031 dirArray[i] = tempPosition.charAt(index) 1032 i++ 1033 index = square + i * STEPS[dir] 1034 var outside = outsideBoard(index) 1035 } while (outside === false && i < maxLength) 1036 1037 dirStrings[dir] = dirArray.join('') 1038 } 1039 1040 return dirStrings 1041 } 1042 1043 function oppositeDir (direction) { 1044 var opposite = {NE: 'SW', SE: 'NW', SW: 'NE', NW: 'SE'} 1045 return opposite[direction] 1046 } 1047 1048 function validDir (piece, dir) { 1049 var validDirs = {} 1050 validDirs.w = {NE: true, SE: false, SW: false, NW: true} 1051 validDirs.b = {NE: false, SE: true, SW: true, NW: false} 1052 return validDirs[piece][dir] 1053 } 1054 1055 function ascii (unicode) { 1056 var extPosition = convertPosition(position, 'external') 1057 var s = '\n+-------------------------------+\n' 1058 var i = 1 1059 for (var row = 1; row <= 10; row++) { 1060 s += '|\t' 1061 if (row % 2 !== 0) { 1062 s += ' ' 1063 } 1064 for (var col = 1; col <= 10; col++) { 1065 if (col % 2 === 0) { 1066 s += ' ' 1067 i++ 1068 } else { 1069 if (unicode) { 1070 s += ' ' + UNICODES[extPosition[i]] 1071 } else { 1072 s += ' ' + extPosition[i] 1073 } 1074 } 1075 } 1076 if (row % 2 === 0) { 1077 s += ' ' 1078 } 1079 s += '\t|\n' 1080 } 1081 s += '+-------------------------------+\n' 1082 return s 1083 } 1084 1085 function gameOver () { 1086 // First check if any piece left 1087 for (var i = 0; i < position.length; i++) { 1088 if (position[i].toLowerCase() === turn.toLowerCase()) { 1089 // if moves left game not over 1090 return generate_moves().length === 0 1091 } 1092 } 1093 return true 1094 } 1095 1096 function getHistory (options) { 1097 var tempHistory = clone(history) 1098 var moveHistory = [] 1099 var verbose = (typeof options !== 'undefined' && 'verbose' in options && options.verbose) 1100 while (tempHistory.length > 0) { 1101 var move = tempHistory.shift() 1102 if (verbose) { 1103 moveHistory.push(makePretty(move)) 1104 } else { 1105 moveHistory.push(move.move.from + SIGNS[move.move.flags] + move.move.to) 1106 } 1107 } 1108 1109 return moveHistory 1110 } 1111 1112 function getPosition () { 1113 return convertPosition(position, 'external') 1114 } 1115 1116 function makePretty (uglyMove) { 1117 var move = {} 1118 move.from = uglyMove.move.from 1119 move.to = uglyMove.move.to 1120 move.flags = uglyMove.move.flags 1121 move.moveNumber = uglyMove.moveNumber 1122 move.piece = uglyMove.move.piece 1123 if (move.flags === 'c') { 1124 move.captures = uglyMove.move.captures.join(',') 1125 } 1126 return move 1127 } 1128 1129 function clone (obj) { 1130 var dupe = JSON.parse(JSON.stringify(obj)) 1131 return dupe 1132 } 1133 1134 function trim (str) { 1135 return str.replace(/^\s+|\s+$/g, '') 1136 } 1137 1138 // TODO 1139 function perft (depth) { 1140 var moves = generate_moves({legal: false}) 1141 var nodes = 0 1142 1143 for (var i = 0; i < moves.length; i++) { 1144 makeMove(moves[i]) 1145 if (depth - 1 > 0) { 1146 var child_nodes = perft(depth - 1) 1147 nodes += child_nodes 1148 } else { 1149 nodes++ 1150 } 1151 undoMove() 1152 } 1153 1154 return nodes 1155 } 1156 1157 return { 1158 WHITE: WHITE, 1159 BLACK: BLACK, 1160 MAN: MAN, 1161 KING: KING, 1162 FLAGS: FLAGS, 1163 SQUARES: 'A8', 1164 1165 load: function (fen) { 1166 return load(fen) 1167 }, 1168 1169 resetGame: function () { 1170 return reset() 1171 }, 1172 1173 moves: generate_moves, 1174 1175 gameOver: gameOver, 1176 1177 inDraw: function () { 1178 return false 1179 }, 1180 1181 validate_fen: validate_fen, 1182 1183 fen: generate_fen, 1184 1185 pdn: generatePDN, 1186 1187 load_pdn: function (pdn, options) {}, 1188 1189 parsePDN: parsePDN, 1190 1191 header: function () { 1192 return set_header(arguments) 1193 }, 1194 1195 ascii: ascii, 1196 1197 getTurn: function () { 1198 return turn.toLowerCase() 1199 }, 1200 1201 move: function move (myMove) { 1202 if (typeof myMove.to === 'undefined' && typeof myMove.from === 'undefined') { 1203 return false 1204 } 1205 myMove.to = parseInt(myMove.to, 10) 1206 myMove.from = parseInt(myMove.from, 10) 1207 var moves = generate_moves() 1208 for (var i = 0; i < moves.length; i++) { 1209 if ((myMove.to === moves[i].to) && (myMove.from === moves[i].from)) { 1210 makeMove(moves[i]) 1211 return moves[i] 1212 } 1213 } 1214 return false 1215 }, 1216 1217 getMoves: getMoves, 1218 1219 getLegalMoves: getLegalMoves, 1220 1221 undo: function () { 1222 var move = undoMove() 1223 return move || null 1224 }, 1225 1226 clear: function () { 1227 return clear() 1228 }, 1229 1230 put: function (piece, square) { 1231 return put(piece, square) 1232 }, 1233 1234 get: function (square) { 1235 return get(square) 1236 }, 1237 1238 remove: function (square) { 1239 return remove(square) 1240 }, 1241 1242 perft: function (depth) { 1243 return perft(depth) 1244 }, 1245 1246 history: getHistory, 1247 1248 convertMoves: convertMoves, 1249 1250 convertNumber: convertNumber, 1251 1252 convertPosition: convertPosition, 1253 1254 outsideBoard: outsideBoard, 1255 1256 directionStrings: directionStrings, 1257 1258 oppositeDir: oppositeDir, 1259 1260 validDir: validDir, 1261 1262 position: getPosition, 1263 1264 clone: clone, 1265 1266 makePretty: makePretty, 1267 1268 captures: getCaptures 1269 } 1270 } 1271 1272 if (typeof exports !== 'undefined') { 1273 exports.Draughts = Draughts 1274 } 1275 1276 if (typeof define !== 'undefined') { 1277 define(function () { 1278 return Draughts 1279 }) 1280 }