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 }