Stage 7 入力の棋譜を駒の動きに変換する
7-2 正規表現でふるいにかける
正規表現 (regular expressions) というのは電話番号やメールアドレスに代表されるような「形が決まり切った文字列」を扱うものです。チェスの棋譜の書き方はまさしく紋切り型の定型表現ですから、正規表現の使い所としてはドンピシャなんですよね。
文字列がこの形なら検証してあげてもいいけど、少しでも違ったらボクは君のことを棋譜とは認めないよ
と、毅然とした態度で型にハマらない無法者を排除してくれます。なんて頼もしいんでしょう。
ところで正規表現はもう勉強してますよね?
~~~~~~~~~~~~~~~
ふつう、棋譜の文字列は先頭からこのような構成になっています。
- 動かす駒 (R, N, B, ...)
- 移動元の位置のヒント (a, 3, ...)
- 相手の駒をとる (x)
- 移動先の座標 (a1, f5, ...)
- プロモーション (=Q, =N, ...)
- アンパッサン (e.p.)
- チェックとチェックメイト (+, #)
例えば c3 にいたクイーンが f3 にいた相手の駒をとってチェックしたら、Qxf3+ となりますね。
移動元のヒントってどういうことだ?
もし g3 にもクイーンがいて、「クイーンを f3 に動かしてください」って言われたりすると
このクイーンどっちのクイーンを言ってるんだ?
となってしまいますので、Qcxf3+ という表記になります。
~~~~~~~~~~~~~~~
では棋譜を表す正規表現を具体的にどう実装するか考えていきましょう。
まずは動かす駒から取り掛かります。選択肢は R, N, B, Q, K の 5 つ。
あれ?ポーンどうしたの
世話焼きな人は、ポーンの場合 P を省略することを知らなかった人が必要以上に困惑しないように P を入れてあげるのもいいかもしれません。いずれにせよ、この大文字の 5 ないし 6 文字が先頭で認められます。
ということで、文字列の先頭を表すキャレット "^" と一緒に
^[PRNBQK]?
とできます。ここでは P を入れましたが、入れなくても全く問題はありません。
~~~~~~~~~~~~~~~
お次は移動元のヒントです。file は a から h の 8 つ、rank は 1 から 8 までのどれかで表します。どこから動かすか自明な時は書かないので、動かす駒に続けて
[a-h]?[1-8]?
ですね。
a, b, ..., h ってまとめて a-h であらわせるんだな。便利ぃ
相手の駒をとったか判別する文字には小文字の x (エックス)を用います。こいつも 0 回か 1 回しか登場しないので "?" をつけましょう。
[x]?
~~~~~~~~~~~~~~~
今度は移動先の情報です。この情報は絶対に必要です。"?" をつけてはいけません。
file が先、rank が後の順で
[a-h][1-8]
と表せます。ここまでで
r"^[PRNBQK]?[a-h]?[1-8]?[x]?"
という文になっていれば正解ですね。いや、他に書き方あるのかもしれませんが、まあまあ動けばいいじゃないの。
~~~~~~~~~~~~~~~
さあ、プロモーションです。
プロモーションはポーンの時だけだから ... どうなるんだ?
とか難しいことは後で考えましょう。とりあえずこの正規表現は「書き方がなってない文字列を除外する」ためのものですから、とりあえずプロモーションだとわかればいい。
それと、ポーンやキングに成り上がることはできませんから、"=" の後ろに持ってこれるのは R/N/B/Q の 4 種類だけですよ。
プロモーションと同時にアンパッサンを表す e.p. も書いちゃいましょうか。プロモーションとアンパッサンを両方同時にはできませんから、
(=[RNBQ]|e.p.)?
と、どちらか一方だけが 0 回か 1 回現れるという形でいいんじゃないですか。
~~~~~~~~~~~~~~~
最後にチェックとチェックメイトです。チェックは "+", チェックメイトは "#" で表します。ただ厄介なことに、ダブルチェック(2 箇所同時チェック)やトリプルチェックを "++", "+++" と表す流派があるようです。そいつらが
俺の指示通りに動かねーじゃねえか、あ?
と腹を立てないよう配慮しないといけません。0 回以上の出現 + を使います。文末を表す $ も入れときましょう。
[\++#]?$
最初の + の前についてる \ は「+」を文字として表すためだな
その通りです。
~~~~~~~~~~~~~~~
すべてひっくるめて正規表現はこうなりました。
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 という変数に格納して、この後たくさんいたぶりますから、そのつもりで。