zjowowen's picture
init space
079c32c
raw
history blame
5.07 kB
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);
}
}