用于国际象棋移动(SAN)的正则表达式帮助

7
我正在编写一个程序,它应该能够读取和解析棋局走法(标准代数记谱)。
以下是可能被接受的走法示例:
e4
Nf3
Nbd2
Nb1c3
R1a3
d8=Q
exd5
Nbxd2
...

我先写了NFA,然后将其转换为文法,最后将其转换为正则表达式。
按照我的惯例,它看起来是这样的。
pln + plxln + plnxln + plnln + plln + pxln + lxln=(B+R+Q+N) + lxln + lnxln=(B+R+Q+N) + lnxln + lnln=(B+R+Q+N) + lnln + ln=(B+R+Q+N) + ln + pnxln + pnln

其中:

p 是集合 {B,R,Q,N,K} 中的一个字符(或者可以理解为 (B+R+Q+N+K) = [BRQNK]

l 是区间 [a-h] 中的一个字符(区分大小写)

n 是区间 [1-8] 中的一个数字

+ 表示并集运算……如果我没理解错的话,在正则表达式的编程语言中,(B+R+Q+N) 就是 [BRQN]

= 只是一个普通的字符……在棋局中,它用于升变(例如 e8=Q)

x 也是一个普通的字符……当你移动棋子到那个位置时,用它来表示你吃掉了对手的棋子。

(/):就像数学中的一样

我尝试在在线Java正则表达式测试器中解析第一部分pln,格式为:[BRQN][a-h][1-8],并成功匹配了像Nf3这样的走法。但我不知道如何将复合表达式(例如pln+plxln)进行联合处理...还有,我该如何为正则表达式的各个部分添加标签,以便在检测到它时获取所有信息?我试图阅读相关文档,但没有搞清楚。

有什么建议吗?


有趣的问题。你需要注意王车易位、将军和将死吗? - Sebastian Proske
我还需要进行王车易位操作(可以用“O-O”字符串或“O-O-O”表示)。检查和将军可能是一个不错的功能,但在我的程序中并非必需,虽然它们似乎很容易添加,但会使表达式变得更长。我会看看是否需要更完整地编写表达式。 - BlackBox
更新:我注意到ChessBase忽略了放置(不是将军/将死)的+或#等符号,因为它们并非必要,实际上程序应该能够理解何时发生将军/将死。 - BlackBox
5个回答

3
你提到的“+”符号在正则表达式中对应的是“|”。因此,你可以使用以下正则表达式。
[BRQNK][a-h][1-8]|[BRQNK][a-h]x[a-h][1-8]|[BRQNK][a-h][1-8]x[a-h][1-8]|[BRQNK][a-h][1-8][a-h][1-8]|[BRQNK][a-h][a-h][1-8]|[BRQNK]x[a-h][1-8]|[a-h]x[a-h][1-8]=(B+R+Q+N)|[a-h]x[a-h][1-8]|[a-h][1-8]x[a-h][1-8]=(B+R+Q+N)|[a-h][1-8]x[a-h][1-8]|[a-h][1-8][a-h][1-8]=(B+R+Q+N)|[a-h][1-8][a-h][1-8]|[a-h][1-8]=(B+R+Q+N)|[a-h][1-8]|[BRQNK][1-8]x[a-h][1-8]|[BRQNK][1-8][a-h][1-8]

这明显有些难看。我可以想到两种可能使它更好看:

  • 使用COMMENTS标志,您可以添加空格。
  • 将可能性结合在一起以更美观的方式。例如,[BRQNK][a-h]x[a-h][1-8]|[BRQNK][a-h][1-8]x[a-h][1-8]可以重写为[BRQNK][a-h][1-8]?x[a-h][1-8]

我还知道另外一种不同于Java的改进方法。(也许不是很多语言都可以,但你可以在Perl中实现。)子表达式(?1)(同样的(?2)等)有点像\1,不过它匹配的不是精确匹配第一个捕获组的字符串,而是可以匹配该捕获组的任何字符串。换句话说,它等效于再次编写捕获组。所以(在Perl中),您可以用([BRQNK])替换第一个[BRQNK],然后用(?1)替换所有后续出现。


是的!它实际上识别出了移动!我还需要对移动进行子分析,我在想是否将它们分组命名会更好。我考虑了一些像这样的东西:(?<P>[BRQNK])(?<dl>[a-h])(?<dn>[1-8])|(?<P>[BRQNK]..... ,其中 P 代表棋子,dl 代表目的地字母,dn 代表目的地数字,而对于其他类型的移动,则有 olod(o = 起点)。问题在于即使在匹配中它们永远不会同时被找到,我也不能为同一组给出相同的名称...该怎么办?最终,我每个移动需要至少3个变量,并且添加详细信息(例如列规范)时需要更多变量。 - BlackBox
我想不出在Java中有更好的方法了。当然,您可以为等效捕获组使用略微不同的名称。仍然比编号组稍微好一些。(在Perl中,我会说您可以使用(?|...)组,但是除非所有备选项中的组名相同,否则它与命名捕获组不兼容。) - David Knipe

2

/^([NBRQK])?([a-h])?([1-8])?(x)?([a-h][1-8])(=[NBRQK])?(\+|#)?$|^O-O(-O)?$/

这是与2599个案例进行单元测试的。请参见下面的单元测试。


.

.

.

.

这是与2599个案例进行单元测试的。请参见下面的单元测试。

describe('Importer/Game', function() {
    let Importer, Game;
    beforeEach(function() {
        Importer = require(`${moduleDir}/import`).Importer;
        Game = require(`${moduleDir}/import`).Game;
    });
    describe('moveRegex', function() {
        describe('non-castling', function() {
            //  ([NBRQK])?  ([a-h])?   ([1-8])?   (x)?     ([a-h][1-8]) (=[NBRQK])? (+|#)?/
            //  unitType?   startFile? startRank? capture? end          promotion?  checkState?
            for(let unitType of ['', 'N', 'B', 'R', 'Q', 'K']) {
                for(let startFile of ['', 'b']) {
                    for(let startRank of ['', '3']) {
                        for(let capture of ['', 'x']) {
                            for(let promotion of ['', '=Q']) {
                                for(let checkState of ['', '+', '#']) {
                                    //TODO: castling
                                    const dest = 'e4';
                                    const san = unitType + startFile + startRank + capture + dest + promotion + checkState;
                                    testPositive(san);
                                    //TODO: negative substitutions here.
                                    testNagative('Y'      + startFile + startRank + capture + dest + promotion + checkState);
                                    testNagative(unitType + 'i'       + startRank + capture + dest + promotion + checkState);
                                    testNagative(unitType + startFile + '9'       + capture + dest + promotion + checkState);
                                    testNagative(unitType + startFile + startRank + 'X'     + dest + promotion + checkState);
                                    testNagative(unitType + startFile + startRank + capture + 'i9' + promotion + checkState);
                                    // testNagative(unitType + startFile + startRank + capture + ''   + promotion + checkState);
                                    testNagative(unitType + startFile + startRank + capture + dest + '='       + checkState);
                                    testNagative(unitType + startFile + startRank + capture + dest + 'Q'       + checkState);
                                    testNagative(unitType + startFile + startRank + capture + dest + promotion + '++');
                                }
                            }
                        }
                    }
                }
            }
        });
        describe('castling', function() {
            testPositive('O-O');
            testPositive('O-O-O');
            testNagative('OOO');
            testNagative('OO');
            testNagative('O-O-');
            testNagative('O-O-O-O');
            testNagative('O');
        });
        function testPositive(san) {
            it(`should handle this san: ${san}`, function(done) {
                const matches = san.match(Importer.moveRegex);
                assert(matches);
                done();
            });
        }
        function testNagative(san) {
            it(`should not match this: ${san}`, function(done) {
                const matches = san.match(Importer.moveRegex);
                assert(!matches);
                done();
            });
        }
    });
});

0
我已经做了这个:
/(^([PNBRQK])?([a-h])?([1-8])?(x|X|-)?([a-h][1-8])(=[NBRQ]| ?e\.p\.)?|^O-O(-O)?)(\+|\#|\$)?$/

包括:O-O+O-O-O+O-O#O-O-O#

还有:e.p.N-f6NXf6Pe4Pe5xd6

更新:

感谢@Toto改进了我上面的正则表达式版本:

^([PNBRQK]?[a-h]?[1-8]?[xX-]?[a-h][1-8](=[NBRQ]| ?e\.p\.)?|^O-O(?:-O)?)[+#$]?$

为什么需要这些捕获组? - Toto
不,你的正则表达式不是可选项,匹配你的8个示例需要2.0毫秒和268步。当你移除无用的捕获组后,它只需要1.0毫秒和146步就可以完成匹配。证明 - Toto
你看过我上面提供的链接了吗?这个简化的正则表达式可以匹配所有你的例子,并且效率是原来的两倍。 - Toto
@Toto 是的,你说得对。 - Geras1mleo
@Toto 你的版本更快更好,但我需要在我的C#代码中解析SAN,所以我使用捕获组来“分割”输入...无论如何,谢谢兄弟。 - Geras1mleo
显示剩余2条评论

0
Re: /^([NBRQK])?([a-h])?([1-8])?(x)?([a-h][1-8])(=[NBRQK])?(\+|#)?$|^O-O(-O)?$/

它既不够全面,也不够精确。

它排除了可能合法的走法 O-O+、O-O-O+、O-O# 和 O-O-O#

它包含了许多永远不可能合法的字符串:e8=K、Kaa4、Nf5=B、Qa1xb7 等等。

等等。


-1

我在我的网站门户中使用了这个东西一段时间了。

[BRQNK][a-h][1-8]| [a-h][1-8]|[BRQNK][a-h][a-h][1-8]|O-O|0-0-0|[BRQNK]x[a-h][1-8]|[a-h]x[a-h][1-8]|1\/2-1\/2|1\/-O|O-\/1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接