Python プログラムで動かすフェアリーチェスアプリ開発、連載第11回です。
はチェスの特殊ルールのひとつ、プロモーションを実装しました。今回はキャスリングという特殊ルールを実装していきます。
キャスリングとは
castling と書き、キングの入城という意味です。
キングとルークを一手で同時に動かすことができますが、動き方や条件が決まっています。
キャスリングの条件は次の通りです。
- キングが一歩も動いたことがない
- キャスリングに関わるルークが一歩も動いたことがない
- キャスリングに関わるルークとキングの間に駒がひとつもない
- キング・キングが通るマス・キングの移動先のマスが敵の駒に攻撃されていない
キャスリングの動きは、キングサイドキャスリングとクイーンサイドキャスリングの2通りがあります。
いずれの場合でも、キングがルーク側に2歩動き、ルークがキングの反対側の隣のマスに回り込むという動きをします。
キャスリングの条件を定義
キャスリングの条件を満たさなければキャスリングはできません。
まずはキャスリングの条件を定義します。
キングとキャスリングに関わるルークが一度でも動くと、その方向へのキャスリングはできなくなります。
各方向へのキャスリングが可能かどうかを表す変数を定義します。
main.py1class Game:2def __init__(self):3...4# キャスリング5# キャスリングのポテンシャルが残っているか6self.can_castling = {'W': [True, True], 'B': [True, True]}7...8...910def main(self):11...12if target and target.color == self.playersturn:13...14# キングが動いた15# 白16if target.name == 'WK':17self.can_castling['W'] = [False, False]18# 黒19if target.name == 'BK':20self.can_castling['B'] = [False, False]21# ルークが動いた22# 白23if target.name == 'WR':24# クイーンサイド25if startpos[0] == 0:26self.can_castling['W'][0] = False27# キングサイド28if startpos[0] == 7:29self.can_castling['W'][1] = False30# 黒31if target.name == 'BR':32# クイーンサイド33if startpos[0] == 0:34self.can_castling['B'][0] = False35# キングサイド36if startpos[0] == 7:37self.can_castling['B'][1] = False38...
この変数を使うことで、特定の方向へのキャスリングが可能かどうかが判定できますね。
次は、キャスリングの条件を定義したを作ります。
main.py1class Game:2...3def castling_requirements(self, piece, endpos, side, gameboard):4'''5キャスリングの条件を満たすとき,True6side == 0 -> aファイル側7side == 1 -> hファイル側89Parameters10----------11piece : obj12駒.キングでなければ return は False.13endpos : tuple > (int, int)14終了位置.絶対座標.15side : int > 0, 1160 -- クイーンサイド171 -- キングサイド18gameboard : dict > {(int, int): obj, ...}19盤面.2021Returns22-------23bool24'''25common_req = (self.can_castling[piece.color][side] # キャスリングに関与する駒が一度も動いていない26and not self.is_check(piece.color, gameboard)) # キングがチェックされていない27# 白のキャスリング28if piece.color == 'W':29piece_req = (piece.name == 'WK'30and (7*side, 0) in gameboard31and gameboard[(7*side, 0)].name == 'WR')32# クイーンサイド33if side == 0:34special_req = (endpos == (2, 0)35# キングとルークの間に駒がない36and (1, 0) not in self.gameboard37and (2, 0) not in self.gameboard38and (3, 0) not in self.gameboard39# and キングが通過するマスが敵に攻撃されていない40)41# キングサイド42if side == 1:43special_req = (endpos == (6, 0)44# キングとルークの通過するマスに駒がない45and (6, 0) not in self.gameboard46and (5, 0) not in self.gameboard47# and キングが通過するマスが敵に攻撃されていない48)49# 黒のキャスリング50if piece.color == 'B':51piece_req = (piece.name == 'BK'52and (7*side, 7) in gameboard53and gameboard[(7*side, 7)].name == 'BR')54# クイーンサイド55if side == 0:56special_req = (endpos == (2, 7)57# キングとルークの通過するマスに駒がない58and (1, 7) not in self.gameboard59and (2, 7) not in self.gameboard60and (3, 7) not in self.gameboard61# and キングが通過するマスが敵に攻撃されていない62)63# キングサイド64if side == 1:65special_req = (endpos == (6, 7)66# キングとルークの通過するマスに駒がない67and (6, 7) not in self.gameboard68and (5, 7) not in self.gameboard69# and キングが通過するマスが敵に攻撃されていない70)7172return common_req and piece_req and special_req
さて、まだ「キングが通るマス・キングの移動先のマスが敵の駒に攻撃されていない」という条件を表現しなければなりません。
キングの通過するマスが敵に攻撃されていない
キャスリングで通るマスにキングを仮に動かして、その状態でチェック状態になるかどうか確認し、
どの場合でもチェックにならなければ True となるような関数を作り、これを利用します。
castling_requirements()
メソッドの内部でしか使わないので、内部で関数を定義しています。
main.py1def castling_requirements(self, piece, endpos, side, gameboard):2...3def create_tmp_board(startpos_y, endpos):4'''5キングの通過するマスが攻撃されていないことを確認するために,6キングがそのマスに動いたときに攻撃されるかを見るための7仮の盤面を出力する89Parameters10----------11startpos_y : int12開始位置y座標.13endpos : tuple > (int, int)14終了位置.絶対座標.1516Returns17-------18gameboard_tmp : dict > {(int, int): obj, ...}19'''20gameboard_tmp = copy(gameboard)21if (4, startpos_y) in gameboard_tmp:22gameboard_tmp[endpos] = gameboard_tmp[(4, startpos_y)]23del gameboard_tmp[(4, startpos_y)]24return gameboard_tmp2526def path_is_not_attacked(startpos_y, king_route):27'''28キングが通るマスのどれかが相手の駒に攻撃されていれば False を返す2930Parameters31----------32startpos_y : int33開始位置y座標.34king_route : list > [int, ...]35キングが通る位置x座標のリスト.3637Returns38-------39bool40'''41for pos in king_route:42if self.is_check(piece.color, create_tmp_board(startpos_y, (pos, startpos_y))):43return False44return True4546common_req = (self.can_castling[piece.color][side] # キャスリングに関与する駒が一度も動いていない47and not self.is_check(piece.color, gameboard)) # キングがチェックされていない48# 白のキャスリング49if piece.color == 'W':50piece_req = (piece.name == 'WK'51and (7*side, 0) in gameboard52and gameboard[(7*side, 0)].name == 'WR')53# クイーンサイド54if side == 0:55special_req = (endpos == (2, 0)56# キングとルークの間に駒がない57and (1, 0) not in self.gameboard58and (2, 0) not in self.gameboard59and (3, 0) not in self.gameboard60# キングが通過するマスが敵に攻撃されていない61and path_is_not_attacked(0, [2, 3])62)63# キングサイド64if side == 1:65special_req = (endpos == (6, 0)66# キングとルークの通過するマスに駒がない67and (6, 0) not in self.gameboard68and (5, 0) not in self.gameboard69# キングが通過するマスが敵に攻撃されていない70and path_is_not_attacked(0, [6, 5])71)72# 黒のキャスリング73if piece.color == 'B':74piece_req = (piece.name == 'BK'75and (7*side, 7) in gameboard76and gameboard[(7*side, 7)].name == 'BR')77# クイーンサイド78if side == 0:79special_req = (endpos == (2, 7)80# キングとルークの通過するマスに駒がない81and (1, 7) not in self.gameboard82and (2, 7) not in self.gameboard83and (3, 7) not in self.gameboard84# キングが通過するマスが敵に攻撃されていない85and path_is_not_attacked(7, [2, 3])86)87# キングサイド88if side == 1:89special_req = (endpos == (6, 7)90# キングとルークの通過するマスに駒がない91and (6, 7) not in self.gameboard92and (5, 7) not in self.gameboard93# キングが通過するマスが敵に攻撃されていない94and path_is_not_attacked(7, [6, 5])95)9697return common_req and piece_req and special_req
これで、castling_requirements()
はキャスリングの条件を満たすときのみ True を返すメソッドとなりました。
キャスリング可能なとき、キングの動けるマスを追加
動ける位置を出力しているvalid_moves()
メソッドを書き換えます。
main.py1def valid_moves(self, piece, startpos, gameboard):2...3# キャスリング4for endpos in [(2, 0), (6, 0), (2, 7), (6, 7)]:5if self.castling_requirements(piece, endpos, 0, gameboard):6result += [endpos]7if self.castling_requirements(piece, endpos, 1, gameboard):8result += [endpos]9...
これでキャスリングの条件を満たすときにのみ、キングの移動先にキャスリングの移動先が追加されました。
この時点では、まだルークが動きませんので、ルークの動きを追加していきます。
ルークを動かす
キングは正常に動いているので、盤面を更新するときにルークの位置を適切に処理すれば大丈夫です。
盤面の更新の処理が定義されているrenew_gameboard()
メソッドを書き換えます。
main.py1def renew_gameboard(self, startpos, endpos, gameboard):2...3# キャスリング4if (gameboard[endpos].abbr == 'K'5and abs(startpos[0] - endpos[0]) == 2):6# クイーンサイド7# 白8if endpos == (2, 0):9del gameboard[(0, 0)]10gameboard[(3, 0)] = Rook('W', 'WR')11# 黒12if endpos == (2, 7):13del gameboard[(0, 7)]14gameboard[(3, 7)] = Rook('B', 'BR')15# キングサイド16# 白17if endpos == (6, 0):18del gameboard[(7, 0)]19gameboard[(5, 0)] = Rook('W', 'WR')20# 黒21if endpos == (6, 7):22del gameboard[(7, 7)]23gameboard[(5, 7)] = Rook('B', 'BR')
キャスリングが起こるのはキングが2歩動いたときのみであることを利用しています。
位置によってキャスリングが起こる箇所を判定し、ルークを消して別の位置に新たなルークを置いています。
これでキャスリングの実装が完成しました!
今回の変更も GitHub に上げておきます。
これでようやく、普通のチェスができるようになりました!
いったんきりがついたので、はちょっとプログラムを整形したいと思います。
お読みいただきありがとうございました。
ではまた