Pythonプログラミングで

チェスを作る

Stage 7 入力の棋譜を駒の動きに変換する

7-2 正規表現でふるいにかける



正規表現 (regular expressions) というのは電話番号やメールアドレスに代表されるような「形が決まり切った文字列」を扱うものです。チェスの棋譜の書き方はまさしく紋切り型の定型表現ですから、正規表現の使い所としてはドンピシャなんですよね。

UNAVAILABLE

文字列がこの形なら検証してあげてもいいけど、少しでも違ったらボクは君のことを棋譜とは認めないよ

と、毅然とした態度で型にハマらない無法者を排除してくれます。なんて頼もしいんでしょう。

ところで正規表現はもう勉強してますよね?

UNAVAILABLE

~~~~~~~~~~~~~~~

ふつう、棋譜の文字列は先頭からこのような構成になっています。

  • 動かす駒 (R, N, B, ...)
  • 移動元の位置のヒント (a, 3, ...)
  • 相手の駒をとる (x)
  • 移動先の座標 (a1, f5, ...)
  • プロモーション (=Q, =N, ...)
  • アンパッサン (e.p.)
  • チェックとチェックメイト (+, #)

例えば c3 にいたクイーンが f3 にいた相手の駒をとってチェックしたら、Qxf3+ となりますね。

UNAVAILABLE

移動元のヒントってどういうことだ?

もし g3 にもクイーンがいて、「クイーンを f3 に動かしてください」って言われたりすると

UNAVAILABLE
UNAVAILABLE

このクイーンどっちのクイーンを言ってるんだ?

となってしまいますので、Qcxf3+ という表記になります。

~~~~~~~~~~~~~~~

では棋譜を表す正規表現を具体的にどう実装するか考えていきましょう。

まずは動かす駒から取り掛かります。選択肢は R, N, B, Q, K の 5 つ。

UNAVAILABLE

あれ?ポーンどうしたの

世話焼きな人は、ポーンの場合 P を省略することを知らなかった人が必要以上に困惑しないように P を入れてあげるのもいいかもしれません。いずれにせよ、この大文字の 5 ないし 6 文字が先頭で認められます。

ということで、文字列の先頭を表すキャレット "^" と一緒に

^[PRNBQK]?

とできます。ここでは P を入れましたが、入れなくても全く問題はありません。

~~~~~~~~~~~~~~~

お次は移動元のヒントです。file は a から h の 8 つ、rank は 1 から 8 までのどれかで表します。どこから動かすか自明な時は書かないので、動かす駒に続けて

[a-h]?[1-8]?

ですね。

UNAVAILABLE

a, b, ..., h ってまとめて a-h であらわせるんだな。便利ぃ

相手の駒をとったか判別する文字には小文字の x (エックス)を用います。こいつも 0 回か 1 回しか登場しないので "?" をつけましょう。

[x]?

~~~~~~~~~~~~~~~

今度は移動先の情報です。この情報は絶対に必要です。"?" をつけてはいけません。

file が先、rank が後の順で

[a-h][1-8]

と表せます。ここまでで

r"^[PRNBQK]?[a-h]?[1-8]?[x]?"

という文になっていれば正解ですね。いや、他に書き方あるのかもしれませんが、まあまあ動けばいいじゃないの。

~~~~~~~~~~~~~~~

さあ、プロモーションです。

UNAVAILABLE

プロモーションはポーンの時だけだから ... どうなるんだ?

とか難しいことは後で考えましょう。とりあえずこの正規表現は「書き方がなってない文字列を除外する」ためのものですから、とりあえずプロモーションだとわかればいい。

それと、ポーンやキングに成り上がることはできませんから、"=" の後ろに持ってこれるのは R/N/B/Q の 4 種類だけですよ。

プロモーションと同時にアンパッサンを表す e.p. も書いちゃいましょうか。プロモーションアンパッサンを両方同時にはできませんから、

(=[RNBQ]|e.p.)?

と、どちらか一方だけが 0 回か 1 回現れるという形でいいんじゃないですか。

~~~~~~~~~~~~~~~

最後にチェックチェックメイトです。チェックは "+", チェックメイトは "#" で表します。ただ厄介なことに、ダブルチェック(2 箇所同時チェック)やトリプルチェックを "++", "+++" と表す流派があるようです。そいつらが

UNAVAILABLE

俺の指示通りに動かねーじゃねえか、あ?

と腹を立てないよう配慮しないといけません。0 回以上の出現 + を使います。文末を表す $ も入れときましょう。

[\++#]?$

UNAVAILABLE

最初の + の前についてる \ は「+」を文字として表すためだな

その通りです。

~~~~~~~~~~~~~~~

すべてひっくるめて正規表現はこうなりました。

r"^[PRNBQK]?[a-h]?[1-8]?[x]?[a-h][1-8](=[RNBQ]|e.p.)?[\++#]?$"

プレーヤーが入力した self.s がこれに合致してるかは 7-1 の続きの部分

                        
        # the pattern of the normal format
        match = re.match(r'^[PRNBQK]?[a-h]?[1-8]?[x]?[a-h][1-8](=[RNBQ]|e.p.)?[\++#]?$', self.s)

        ### NORMAL FORMAT
        if match:
            line = match.group()
            logger.info('line = {}'.format(line))
                        
                    

とした match の値でわかります。下に if match という条件文がありますが、まさしくそれが「正規表現に合致している場合」を表してます。

条件文内の先頭、match.group() というのは正規表現を通過した文字列です。

                        
        ### NORMAL FORMAT
        if match:
            line = match.group()
            logger.info('line = {}'.format(line))
                        
                    

line という変数に格納して、この後たくさんいたぶりますから、そのつもりで。

NEXT 7-3 動かす駒を判定する