我正在制作游戏棋,并且除了一件事之外几乎已经得到了所有东西:我需要做到这一点,以便玩家无法将棋子移动到支票上。我在如何解决这个问题上遇到了麻烦。
我现在在伪代码中生成有效的动作是: 类getMoveLocations(我将一个位置定义为国际象棋中的一个方格): 如果此位置在边界内,并且此位置的棋子是敌人的棋子,并且模拟的移动不会导致棋盘被检查,则将此位置添加到棋子可移动到的可能位置。
这个问题是我如何检查棋盘是否“正在检查”。在我的代码中,它通过收集所有敌人的移动位置,并查看是否有任何敌人的移动位置与国王的位置重叠来考虑棋盘“检查”。
不幸的是,这是无限循环开始的地方;为了收集所有敌人的电影位置,每个敌人的可能移动位置需要确保其移动不会导致它被检查。为了确保没有任何敌人的位置受到控制,它必须收集所有盟友的潜在移动位置等等。
我很难理解如何获得有效的算法。虽然我的代码“理论上”具有逻辑意义,但它无法实现。我感兴趣的是A)一种更有效的方法来实现一种检查所有合法移动的方法,或者B)一种修复这种无限循环的方法
答案 0 :(得分:8)
有一种更有效的方法可以确定一方是否受到检查:你只需从国王那里向外扫描,看看你是否找到了可以攻击它的碎片。例如,从国王的位置,检查敌人的主教是否沿着对角线等等。您根本不需要生成移动列表,因此不需要递归。这是一些伪代码:
function leftInCheck(board, sideToCheck) {
// one of the four rays for bishop/queen attacks
d := 0
while (king rank + d, king file + d) is on the board
piece := board[king rank + d][king file + d]
if piece is an enemy bishop or queen
return true
if piece is not an empty square // a piece blocks any potential
break // attack behind it so we can stop
d := d + 1
// do this for all the other forms of attack
...
return false
}
正如您所看到的,有一些代码重复,但您可以缩短它。我把它保留原样所以它很容易理解。您可以通过生成现在正在执行的伪合法移动来生成合法移动,制作每个移动,并省略使用上述子例程检查的移动。这具有自然的附加优点。这是一些伪代码:
function legalMoves(board, sideToMove) {
moveList := empty
for each move in pseudoLegalMoves()
make(move)
if not leftInCheck(board, sideToMove)
moveList.add(move)
unmake(move) // you may not need this
return moveList
}
对于castling,你仍然需要检查国王和车之间的广场是否有攻击。幸运的是,这很简单,因为你可以将上面的子程序扩展到除了国王之外的方块。
我假设您没有使用位板或0x88,而是使用简单的数组表示。这使得实现合法移动生成(没有中间伪合法移动)有点困难,因为它需要非常快地生成攻击图以确定固定块。如果你雄心勃勃,这是一种可能性。
作为补充说明,我对此处的其他答案感到有些失望。而且我甚至不会向想要编写好的移动生成器的人推荐我自己的答案(它仅适用于那些不熟悉国际象棋编程的人)。这是一个经过彻底检查并具有众所周知的解决方案的主题,但却引出了原创性的想法。当然这没有什么不妥,但为什么重新发明轮子,更糟糕呢?看看well established methods of move generation。
答案 1 :(得分:2)
修改您的getMoveLocations
程序以接受一个标志,指示是否担心进入检查状态。例如,如果一块被固定,它仍然可以移动以捕获对方国王。如果该标志设置为忽略检查风险,则跳过检查测试将破坏递归。
或者(并且等效地)编写一个单独的方法来生成忽略进入检查问题的移动。使用该程序作为“使董事会受到检查”测试的一部分。
答案 2 :(得分:1)
如果移动允许敌人能够向你的国王进行攻击,那么移动会使你的身边受到控制(因此是不允许的)。
请注意,将自己置于检查状态与控制对手不同 - 当您对手进行检查时,他们会轮流作出回应。如果你可以自己检查,你将有机会回应。他们能够捕捉到你的国王,无论他们的位置多么糟糕,或者即使他们会“受到控制”,他们也会成为正确的举动 - 他们赢了!之后什么都没有留下。
因此,看看是否采取行动会让自己受到制约,看看是否有任何敌人可以攻击你的国王。而已。你不是递归地解决未来的检查或类似的事情 - 如果他们现在可以攻击国王,它就是无效的。
答案 3 :(得分:0)
有些海报似乎不太了解国际象棋引擎所以请在试图争辩之前仔细阅读和研究:
而不是检查他们是否可以移动到那里,检查他们是否可以在那里进行攻击。您可能希望以后再用于评估功能......
我不知道有多少引擎会这样做,但我知道Stockfish会这样做(请参阅src文件夹中的evaluate.cpp),所以我认为它在GOOD引擎中是相当标准的。如果需要进行评估,您也可以在运动生成中使用它。
答案 4 :(得分:0)
基于标题: 国际象棋:获取所有合法的国际象棋棋子 。
我想展示一种对我来说很好的方法。
这需要一种验证pgn文件的方法,我正在使用pgn-extract
,但是它与UCI引擎和其他工具的工作原理相似。
sudo apt install pgn-extract
这只能通过从有效的PGN开始开始而不是简单的FEN字符串来起作用。
在任何给定的回合中,无论有多少个,最多不超过8⁴ = 4096个组合,菜单64个是不可能的(例如:c4c4将永远无效)。
实际上,方法要少得多,但这没关系,我们在不解析类型代码的情况下对它们进行了全部测试。
通过生成所有组合的数组:
array(4032) {
[0]=>
string(4) "a1a2"
[1]=>
string(4) "a1a3"
[2]=>
string(4) "a1a4"
[3]=>
(...)
并将每个条目作为最后的步骤推送到PGN文件中。 如果验证失败,则移动无效。
这很好用,考虑了所有特殊动作,如果将死等,则没有任何动作有效。
这当然很残酷,要花几秒钟。
示例代码为 php 。
#!/usr/bin/php
<?php
$cp = file_get_contents("SINGLE_GAME-FILE.pgn");
$M = [];$ALL = [];$a = "a";
for ($i = 0;$i < 8;$i++){
for ($j = 1;$j < 9;$j++){
$M[] = $a.$j;
}
$a++;
}
$t=0;
for ($i = 0;$i < count($M);$i++){
for ($j = 0;$j < count($M);$j++){
if ($M[$i] != $M[$j]){
$move = $M[$i].$M[$j];
$ALL[] = $move;
$pgn = explode("\n",$cp);
$g = explode(" ",end($pgn));
$pgn[count($pgn)-1] = "";
$g[count($g)-1] = $move;$g[]= "*";
file_put_contents("mgen.pgn",implode("\n",$pgn).implode(" ",$g));
system("pgn-extract -r mgen.pgn 2> .gentest.fen");
if (!preg_match_all('~\b(Failed|Unknown|Missing)\b~i',file_get_contents(".gentest.fen"))){
// Valid move
echo "$move ";$t++;
}
}
}
}
echo "\nFOUND $t LEGAL MOVES\n";