请考虑以下两个图,两个图都描述了一个决策树,一个决策树将被第一个线程遍历,另一个则将被第二个线程遍历:(对不起,因为有水印,所以很难看到)
这两个树分别是 PATH A 树和 PATH B 树,代表棋盘游戏的两种不同状态,在AI进行移动后,分支代表可以执行的其他可能动作,在随后的回合中进一步改变电路板的状态,而节点代表做出移动后的电路板状态。
这些树的用途如下:线程,应将树构建到给定深度为止(在此图中您可以看到3个深度,但在我的实际示例中,我当前使用的深度为5) ,量化树的所有叶子,然后将此量化值传递到基节点,并在每个节点取最大值。因此,例如,如果 A1-3-2-2 是叶子,并且量化为50,而 A1-3-2-1 也是叶子,量化为20, A1-3-2 的值为50。max(50,20)
所有内容将以相同的方式传播回去,直到到达树的根为止。准备就绪后,AI应该选择量化值最高的移动(即树),然后执行该移动。
两个不同的树很可能具有相似的节点(可以通过执行的不同动作来达到相似的板状态),并且为了使事情更快,我不想对其进行多次降级。为此,我对所有线程使用一个共享变量,如果它们开始在分支上工作,它们将在其中声明状态(它们开始将其降级)。
上面的图片中用绿色和红色框表示了这一点: THREAD 1 保留在类似于 A1-3 的任何状态下工作的权利,因此,当 THREAD 2 遇到类似状态时,他会完全跳过该状态。
为了不丢失量化,线程不会为每棵树返回一个固定的量化数,而是返回一个可以包含整数和字符串的数组。例如,当 THREAD 2 将树的值返回到 PATH B 时,他将返回两个整数(对于 B1-1 ,一个整数,另一个表示 B1-2 ,他将返回字符串 A1-3 ,表示该树将是这3个数字中的最高数字。
过去,某些分支的数据可能会丢失。这两个png描绘了可能发生的情况:THREAD 1,保留 A1-3 ,但无法准确量化,就像他到达节点 A1-3-2时一样-2 和 A1-3-2-1 ,它们已被 THREAD 2 保留。这样, A1-3 从未被量化。我已经修复了这一问题,方法是针对基节点以及每个其他节点保留大量具有所有可能值的数组,并在线程完成后计算并替换这些值。
但是现在,另一个问题出现了:某些路径,例如 PATH A ,获得了 PATH B 的量化值,但实际上,它的量化值要多得多低,因此AI做出不好的举动,例如当他可以清楚地看到对他最好的举动时。
我一生都无法找出导致此错误的原因。我将在下面向您展示代码,我的代码是否有问题,或者我的逻辑有问题?
这是线程的工作方式:他获得战场状态,计算下一个英雄的动作,执行每个动作,然后递归地回叫自己,直到到达叶节点并对其进行量化。
/** Perform think ahead moves
*
* @params int $thinkAheadLeft (the number of think ahead moves left)
* @params int $innerIterator (the iterator for the move)
* @params Battlefield $originalBattlefield (the original battlefield)
* @params Battlefield $iteratedBattlefield (the iterated battlefield)
*
* @returns int (returns an integer with the quantify value)
*/
public function performThinkAheadMoves($thinkAheadLeft, $innerIterator, Battlefield $originalBattlefield, Battlefield $iteratedBattlefield, $previousMoves = array()) {
if ($thinkAheadLeft == 0) return $iteratedBattlefield->quantify($originalBattlefield); // returns an integer
$dataSet = $this->worker->getDataSet();
$bfHash = $iteratedBattlefield->createBattlefieldHash();
$bfHash=$thinkAheadLeft."H".$bfHash;
$returnValue = null;
$dataSet->synchronized(function($dataSet) use ($bfHash, &$returnValue) {
if (isset($dataSet['workingOnBranch'][$bfHash])) $returnValue = true;
else $dataSet['workingOnBranch'][$bfHash] = true;
}, $dataSet);
if ($returnValue !== null) {
return $bfHash; // returns a string hash
}
$nextThinkAhead = $thinkAheadLeft-1;
$moves = $iteratedBattlefield->getPossibleHeroMoves($innerIterator);
$Hero = $iteratedBattlefield->getHero($innerIterator);
$innerIterator++;
$nextInnerIterator = $innerIterator;
$currentBattlefieldState = $iteratedBattlefield->getState();
$selectedMoves = array();
$toUnset = array();
foreach ($moves as $moveid => $move) {
$Attack = $move['Attack'];
$iteratedBattlefield->performMove($move, true, 0, $dataSet);
if ($iteratedBattlefield->isBattleFinished()) $moves[$moveid]['quantify'] = $iteratedBattlefield->quantify($originalBattlefield);
else $moves[$moveid]['quantify'] = $this->performThinkAheadMoves($nextThinkAhead, $nextInnerIterator, $originalBattlefield, $iteratedBattlefield, $previousMoves);
if (is_string($moves[$moveid]['quantify'])) {
$selectedMoves[] = $moves[$moveid]['quantify'];
$toUnset[] = $moveid;
} else if (is_array($moves[$moveid]['quantify'])) {
$selectedMoves = array_merge($selectedMoves, $moves[$moveid]['quantify']);
$toUnset[] = $moveid;
}
array_pop($previousMoves);
$iteratedBattlefield->setState($currentBattlefieldState);
}
foreach ($toUnset as $moveid) unset($moves[$moveid]);
if (count($moves) > 0) {
usort($moves, function($a, $b) use(&$selectedMoves) {
if ($a['quantify'] === $b['quantify']) return 0;
else return ($a['quantify'] > $b['quantify']) ? -1 : 1;
});
$selectedMoves[] = $moves[0]['quantify'];
}
$dataSet->synchronized(function($dataSet) use($bfHash, $selectedMoves) {
$hashLoc = $dataSet['battlefieldHashes'];
if (count($selectedMoves) == 1 && !is_string($selectedMoves[0])) $hashLoc[$bfHash] = $selectedMoves[0];
else $hashLoc[$bfHash] = (array) $selectedMoves;
}, $dataSet);
return $selectedMoves; // either an integer, with a move number, or an array, containing possible values
}
我偏向事实,这是我的逻辑,因为如果我运行上述情况,则只有一个线程(即,一个线程将解析两个树,一个又一个地解析) ),我得到了正确的结果。