Spaces:
Sleeping
Sleeping
| 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); | |
| } | |
| } | |