import Cache from "./cache"; import { FIVE, FOUR } from "./eval"; const MAX = 1000000000; // 缓存内容:depth, value, move export const cache_hits = { search: 0, total: 0, hit: 0 }; const onlyThreeThreshold = 6; const cache = new Cache(); // 放在这里,则minmax, vct和vcf会共用同一个缓存 const factory = (onlyThree = false, onlyFour = false) => { // depth 表示总深度,cDepth表示当前搜索深度 const helper = (board, role, depth, cDepth = 0, path = [], alpha = -MAX, beta = MAX) => { cache_hits.search++; if (cDepth >= depth || board.isGameOver()) { return [board.evaluate(role), null, [...path]]; } const hash = board.hash(); const prev = cache.get(hash); if (prev && prev.role === role) { if ((Math.abs(prev.value) >= FIVE || prev.depth >= depth - cDepth) && prev.onlyThree === onlyThree && prev.onlyFour === onlyFour) // 不能连五的,则minmax 和 vct vcf 的缓存不能通用 { cache_hits.hit++; return [prev.value, prev.move, [...path, ...prev.path]]; } } let value = -MAX; let move = null; let bestPath = [...path]; // Copy the current path let bestDepth = 0; let points = board.getValuableMoves(role, cDepth, onlyThree || cDepth > onlyThreeThreshold, onlyFour); if (cDepth === 0) { console.log('points:', points); } // board.display(points); if (!points.length) { // points = board.getValidMoves(role); return [board.evaluate(role), null, [...path]]; } for (let d = cDepth + 1; d <= depth; d += 1) { // 迭代加深过程中只找己方能赢的解,因此只搜索偶数层即可 if (d % 2 !== 0) continue; let breakAll = false; for (let i = 0; i < points.length; i++) { const point = points[i]; board.put(point[0], point[1], role); let newPath = [...path, point]; // Add current move to path let [currentValue, currentMove, currentPath] = helper(board, -role, d, cDepth + 1, newPath, -beta, -alpha); currentValue = -currentValue; board.undo(); // 迭代加深的过程中,除了能赢的棋,其他都不要 // 原因是:除了必胜的,其他评估不准。比如必输的棋,由于走的步数偏少,也会变成没有输,比如 5步之后输了,但是1步肯定不会输,这时候1步的分数是不准确的,显然不能选择。 if (currentValue >= FIVE || d === depth) { // 必输的棋,也要挣扎一下,选择最长的路径 if ((currentValue > value) || (currentValue <= -FIVE && value <= -FIVE && currentPath.length > bestDepth)) { value = currentValue; move = point; bestPath = currentPath; bestDepth = currentPath.length; } } alpha = Math.max(alpha, value); if (alpha >= FIVE) { // 自己赢了也结束,但是对方赢了还是要继续搜索的 breakAll = true; break; } if (alpha >= beta) { break; } } if (breakAll) { break; } } // 缓存 if ((cDepth < onlyThreeThreshold || onlyThree || onlyFour) && (!prev || prev.depth < depth - cDepth)) { cache_hits.total += 1; cache.put(hash, { depth: depth - cDepth, // 剩余搜索深度 value, move, role, path: bestPath.slice(cDepth), // 剩余搜索路径 onlyThree, onlyFour, }); } const res = [value, move, bestPath]; return res; } return helper; } const _minmax = factory(); export const vct = factory(true); export const vcf = factory(false, true); export const minmax = (board, role, depth = 4, enableVCT = true) => { if (enableVCT) { const vctDepth = depth + 8; // 先看自己有没有杀棋 let [value, move, bestPath] = vct(board, role, vctDepth); if (value >= FIVE) { return [value, move, bestPath]; } [value, move, bestPath] = _minmax(board, role, depth); // 假设对方有杀棋,先按自己的思路走,走完之后看对方是不是还有杀棋 // 如果对方没有了,那么就说明走的是对的 // 如果对方还是有,那么要对比对方的杀棋路径和自己没有走棋时的长短 // 如果走了棋之后路径变长了,说明走的是对的 // 如果走了棋之后,对方杀棋路径长度没变,甚至更短,说明走错了,此时就优先封堵对方 board.put(move[0], move[1], role); let [value2, move2, bestPath2] = vct(board.reverse(), role, vctDepth) board.undo(); if (value < FIVE && value2 === FIVE && bestPath2.length > bestPath.length) { let [value3, move3, bestPath3] = vct(board.reverse(), role, vctDepth) if (bestPath2.length <= bestPath3.length) { return [value, move2, bestPath2]; // value2 是被挡住的,所以这里还是用value } } return [value, move, bestPath]; } else { return _minmax(board, role, depth); } }