UNPKG

ai-for-shogi-like-games

Version:

javascript library of A.I. algorithms for Shogi-like games

194 lines (166 loc) 8.46 kB
'use strict'; var _assert = require('assert'); var _assert2 = _interopRequireDefault(_assert); var _lodash = require('lodash'); var _lodash2 = _interopRequireDefault(_lodash); var _numberPrototype = require('./number-prototype.js'); var _numberPrototype2 = _interopRequireDefault(_numberPrototype); var _boardLib = require('./board-lib.js'); var _trees = require('./trees.js'); var _side = require('./side.js'); var _utils = require('./utils.js'); var _moves = require('./moves.js'); var _evalModel = require('./eval-model.js'); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } (function () { var sourceMapSupport = require('source-map-support'); sourceMapSupport.install(); })(); function moveTreeBuilder(gb, sideToMoveNext, depth) { var root = new _trees.Node(gb); function _moveTreeBuilder(root, gb, sideToMoveNext, depthRemaining) { if (depthRemaining === 0) return; // put a floor to the recursion if we reached maximum depth if (gb.boardImmediateWinSide() !== null) return; // put a floor to the recursion if we reached a game end state var nextMoves = gb.nextStates(sideToMoveNext); nextMoves.forEach(function (board, k) { var n = new _trees.Node(board); root.set(k, n); _moveTreeBuilder(n, board, !sideToMoveNext, depthRemaining - 1); }); } _moveTreeBuilder(root, gb, sideToMoveNext, depth); assertConsistent(root); return root; } function bestMove(gb, sideA, depth, evalModel, pieceSet) { // BoardMove | DropMove // TODO var moveTree = moveTreeBuilder(gb, sideA, depth); evaluateLeaves(moveTree, evalModel); if (false) console.log('\n\n\n**** Upon leaves evaluation:\n' + moveTree.print(true)); pullEvaluationsUp(sideA, moveTree); if (false) console.log('\n\n\n**** When evaluations are pulled up:\n' + moveTree.print(true)); var scoreSelector = sideA ? Math.max : Math.min; var cmp = sideA ? function (a, b) { return a > b; } : function (a, b) { return a < b; }; var currentlyBestScore = sideA ? -Infinity : Infinity; var currentlyShallowestDepth = Infinity; var selectedEdge = null; if (moveTree.children != null) { moveTree.children.forEach(function (v, e) { (0, _assert2.default)(v.isAdorned()); if (selectedEdge === null) // initialize the selected edge (for hopeless situations where every move leads to the same infinity outcome and the if below would never be triggered) // TODO maybe I can lose that guard as the newly added currentlyShallowestDepth check should take care of this edge case selectedEdge = e; // if ((scoreSelector(v.adornment.v, currentlyBestScore)!=currentlyBestScore) || // ((scoreSelector(v.adornment.v, currentlyBestScore)==currentlyBestScore) && if (cmp(v.adornment.v, currentlyBestScore) || v.adornment.v === currentlyBestScore && v.adornment.d < currentlyShallowestDepth) { if (false) console.log('trying the edge ' + e + ' led to a score of ' + v.adornment.v + ' which is preferrable (given moving side ' + (sideA ? 'side A' : 'side B') + ') to that of ' + currentlyBestScore + ' or the depth of ' + v.adornment.d + ' is shallower than the currently shallowest depth of ' + currentlyShallowestDepth); selectedEdge = e; currentlyBestScore = scoreSelector(v.adornment.v, currentlyBestScore); currentlyShallowestDepth = v.adornment.d; } }); if (selectedEdge != null) { return _moves.Move.fromString(pieceSet, selectedEdge); } else throw new Error('inconceivable that it was impossible to select an edge'); } else throw new Error('inconceivable to ask for bestMove on a leaf!'); } function dynamicEvaluationOfBoard(gb, sideAIsMoving, depth, evalModel, pieceSet) { var moveTree = moveTreeBuilder(gb, sideAIsMoving, depth); evaluateLeaves(moveTree, evalModel); if (false) console.log('\n\n\n**** Upon leaves evaluation:\n' + moveTree.print(true, null, function (x) { if (x === null) return 'null';else return x.v; })); pullEvaluationsUp(sideAIsMoving, moveTree); if (false) console.log('\n\n\n**** Upon leaves pulling evaluation:\n' + moveTree.print(true, null, function (x) { if (x === null) return 'null';else return x.v; })); if (moveTree.adornment != null) { return moveTree.adornment.v; } else throw new Error('root node should be adorned after evaluations are pulled up'); } function evaluateLeaves(moveTree, evalModel) { function adornLeaf(node) { if (node.isLeaf()) node.adorn({ v: evalModel.evaluateBoard(node.value), d: 0 }); } moveTree.depthFirstTraversal(adornLeaf, true); } function pullEvaluationsUp(sideA, currentNode) { var assertNoRecursion = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; if (false) // $SuppressFlowFinding: access of computed property/element. Indexable signature not found in ... console.log('call to PEU at node: ' + currentNode[Symbol.for(_trees.TREE_NODE_ID_SYMBOL_KEY)]); function isAdorned(n) { return n.isAdorned(); } if (currentNode.allChildrenSatisfy(isAdorned)) { var allChildrenValues = currentNode.getChildrenAdornments(); var thisNodeAdorn = function () { var cmp = sideA ? function (a, b) { return a > b; } : function (a, b) { return a < b; }; var bestNodeAdornSoFar = void 0; allChildrenValues.forEach(function (adorn) { if (bestNodeAdornSoFar === undefined) bestNodeAdornSoFar = Object.assign({}, adorn, { d: adorn.d + 1 });else { if (cmp(adorn.v, bestNodeAdornSoFar.v)) bestNodeAdornSoFar = Object.assign({}, adorn, { d: adorn.d + 1 }); } }); return bestNodeAdornSoFar; }(); (0, _assert2.default)(currentNode.adorn(thisNodeAdorn) === null); } else { if (assertNoRecursion) throw new Error('recursion was not expected'); if (currentNode.children != null) { currentNode.children.forEach(function (n, e) { if (!n.isAdorned()) pullEvaluationsUp(!sideA, n); }); pullEvaluationsUp(sideA, currentNode, true); } else throw new Error('bug'); } (0, _assert2.default)(currentNode.isAdorned()); } function assertConsistent(tree) { function _assertConsistent(tree, expectedSide) { if (!tree.isLeaf()) { if (tree.children != null) { if (expectedSide != null) { tree.children.forEach(function (newBoard, moveS) { var side = tree.value.sideOfMoveSNoPieceSetInfo(moveS); if (side !== expectedSide) throw new Error(); _assertConsistent(newBoard, side.theOther()); }); } else { tree.children.forEach(function (newBoard, moveS) { var side = tree.value.sideOfMoveSNoPieceSetInfo(moveS); _assertConsistent(newBoard, side.theOther()); }); } } else throw new Error('bug'); } } _assertConsistent(tree, null); } function sideThatMovesNext(tree) { (0, _assert2.default)(!tree.isLeaf()); assertConsistent(tree); if (tree.children != null) { var discoveredSide = null; tree.children.forEach(function (_, moveS) { var side = tree.value.sideOfMoveSNoPieceSetInfo(moveS); if (discoveredSide === null) discoveredSide = side; if (discoveredSide !== null && discoveredSide != side) throw new Error(); }); if (discoveredSide != null) return discoveredSide;else throw new Error('impossible to be unable to pronounce discovered side on a non-leaf tree'); } else throw new Error('bug'); } exports.moveTreeBuilder = moveTreeBuilder; exports.sideThatMovesNext = sideThatMovesNext; exports.evaluateLeaves = evaluateLeaves; exports.pullEvaluationsUp = pullEvaluationsUp; exports.bestMove = bestMove; exports.dynamicEvaluationOfBoard = dynamicEvaluationOfBoard; //# sourceMappingURL=move-tree-builder.js.map