Python プログラムで動かすフェアリーチェスアプリ開発、連載第 18 回です。
はチェス960の初期配置を生成して、基本的な部分は遊べるようになりました。しかしチェス960のキャスリングは通常のキャスリングを拡大解釈する必要があります。
今回はキャスリングの部分のコードを書き換えていきます。
拡張前のキャスリングの実装は第12回に載せています。
チェス960におけるキャスリング
チェス960におけるキャスリングは、通常のチェスのキャスリング後のキングとルークの位置と同じになるようにキングとルークを動かします。
つまり、a ファイル(白から見て左側)へのキャスリング(通常のチェスでのロングキャスリング)ではキングが c ファイルに、ルークが d ファイルに移動し、
h ファイル(白から見て右側)へのキャスリング(通常のチェスでのショートキャスリング)ではキングが g ファイルに、ルークが f ファイルに移動します。
キャスリングの条件も通常のチェスと変わりません。
通常のチェスと異なるのは、初期位置が異なるために、
キングやルークが全く移動しなかったり、かなり大きく移動したりすることがあるということです。
キングが1歩しか移動しないキャスリングもありえ、その場合はキャスリングなのかキングが動いただけなのかが曖昧になります。
そのあたりも考えながらコードに落とし込んでいきます。
キャスリングのコードを編集
この時点では「キングは必ず2歩移動する」など固定的な性質を利用して書いていた部分がありますので、これを一般化していきます。
キャスリングの条件を満たすかどうかを返すメソッドでは、
common_req
, piece_req
, special_req
という3つの条件がすべてTrue
のときにTrue
を返すようにしていました。
main.py1def castling_requirements(self, piece, endpos, side, gameboard):2'''3キャスリングの条件を満たすとき,True4side == 0 -> aファイル側5side == 1 -> hファイル側67Parameters8----------9piece : obj10駒.キングでなければ return は False.11endpos : tuple > (int, int)12終了位置.絶対座標.13side : int > 0, 1140 -- クイーンサイド151 -- キングサイド16gameboard : dict > {(int, int): obj, ...}17盤面.1819Returns20-------21bool22'''23def create_tmp_board(startpos_y, endpos):24'''25キングの通過するマスが攻撃されていないことを確認するために,26キングがそのマスに動いたときに攻撃されるかを見るための27仮の盤面を出力する2829Parameters30----------31startpos_y : int32開始位置y座標.33endpos : tuple > (int, int)34終了位置.絶対座標.3536Returns37-------38gameboard_tmp : dict > {(int, int): obj, ...}39'''40...4142def path_is_not_attacked(startpos_y, king_route):43'''44キングが通るマスのどれかが相手の駒に攻撃されていれば False を返す4546Parameters47----------48startpos_y : int49開始位置y座標.50king_route : list > [int, ...]51キングが通る位置x座標のリスト.5253Returns54-------55bool56'''57...5859common_req = (self.can_castling[piece.color][side] # キャスリングに関与する駒が一度も動いていない60and not self.is_check(piece.color, gameboard)) # キングがチェックされていない61# 白のキャスリング62if piece.color == 'W':63piece_req = (piece.name == 'WK'64and (7*side, 0) in gameboard65and gameboard[(7*side, 0)].name == 'WR')66# クイーンサイド67if side == 0:68special_req = (endpos == (2, 0)69# キングとルークの間に駒がない70and (1, 0) not in self.gameboard71and (2, 0) not in self.gameboard72and (3, 0) not in self.gameboard73# キングが通過するマスが敵に攻撃されていない74and path_is_not_attacked(0, [2, 3])75)76# キングサイド77if side == 1:78special_req = (endpos == (6, 0)79# キングとルークの通過するマスに駒がない80and (6, 0) not in self.gameboard81and (5, 0) not in self.gameboard82# キングが通過するマスが敵に攻撃されていない83and path_is_not_attacked(0, [6, 5])84)85# 黒のキャスリング86if piece.color == 'B':87piece_req = (piece.name == 'BK'88and (7*side, 7) in gameboard89and gameboard[(7*side, 7)].name == 'BR')90# クイーンサイド91if side == 0:92special_req = (endpos == (2, 7)93# キングとルークの通過するマスに駒がない94and (1, 7) not in self.gameboard95and (2, 7) not in self.gameboard96and (3, 7) not in self.gameboard97# キングが通過するマスが敵に攻撃されていない98and path_is_not_attacked(7, [2, 3])99)100# キングサイド101if side == 1:102special_req = (endpos == (6, 7)103# キングとルークの通過するマスに駒がない104and (6, 7) not in self.gameboard105and (5, 7) not in self.gameboard106# キングが通過するマスが敵に攻撃されていない107and path_is_not_attacked(7, [6, 5])108)109110return common_req and piece_req and special_req
このうちcommon_req
については編集する必要がありません。
これは「キャスリングに関与する駒が一度も動いていない」および「キングがチェックされていない」ことを保証する変数ですが、
これらはキングやルークの初期位置が関わらないからです。
piece_req
はどうでしょう。
これは「動かす駒が自分のキングであること」と「キャスリングに関与するルークが初期位置にあること」を保証します。
前半は関係しませんが、後半は初期位置が関係してくるので、編集が必要です。
必要になるのはルークの初期位置です。
special_req
も編集が必要です。
これには「キングのキャスリング後の位置」「ルークの通過するマスの座標」「キングが通過するマスの座標」の3つが必要であり、
このうち後ろの2つを変更しなければなりません。
それには「ルークの初期位置」「キングの初期位置」が最低限必要になります。
ルーク・キングの初期位置を取得する
初期位置といっても x 座標しか使いませんので、それだけ取得できれば十分です。
初期位置はgames
モジュールに定義されている各クラスのプロパティplacers
に、次のような形で定義されています。
1placers = {1: [Rook, Knight, Bishop, Queen, King, Bishop, Knight, Rook],22: [Pawn] * size}
x 座標はリストのインデックスと等しいので、enumerate
関数を使って次のように初期位置のリストを取得することができます。
1rook_init_pos = [pos for pos, piece in enumerate(placers[1]) if piece == Rook]2king_init_pos = placers[1].index(King)
ルークは複数個存在しえますが、キングはひとつしか存在しえないので、index
を使えばよいでしょう。
これらを使って、piece_req
とspecial_req
を書き換えると、例えば次のようになります。
1piece_req = (piece.name == 'WK'2and (7*side, 0) in gameboard3and gameboard[(7*side, 0)].name == 'WR')4and (rook_init_pos[side], 0) in gameboard5and gameboard[(rook_init_pos[side], 0)].name == 'WR')
1gameboard_tmp = copy(gameboard)2# キャスリングに関与するキングとルークは除外して考える3if (king_init_pos, 0) in gameboard_tmp:4del gameboard_tmp[(king_init_pos, 0)]5if (rook_init_pos[side], 0) in gameboard_tmp:6del gameboard_tmp[(rook_init_pos[side], 0)]7# キングとルークの通過するマス8king_route = list(range(2, king_init_pos)) + list(range(2, king_init_pos, -1))9rook_route = list(range(3, rook_init_pos[side])) + list(range(3, rook_init_pos[side], -1))10special_req = (endpos == (2, 0)11# キングとルークの通過するマスに駒がない12and (1, 0) not in self.gameboard13and (2, 0) not in self.gameboard14and (3, 0) not in self.gameboard15and not any((x, 0) in gameboard_tmp16for x in king_route + rook_route)17# キングが通過するマスが敵に攻撃されていない18and path_is_not_attacked(0, [2, 3])19and path_is_not_attacked(0, list(x for x in range(2, king_init_pos))20)
同様にして他の場合についても書き換えると次のようになります。
盤面の大きさはゲームによっては8とは限らないので、size
から計算しています。
main.py1def castling_requirements(self, piece, endpos, side, gameboard):2...3size = self.kind.size45rook_init_pos = [pos for pos, piece in enumerate(self.kind.placers[1]) if piece == Rook]6king_init_pos = self.kind.placers[1].index(King)7...8common_req = (self.can_castling[piece.color][side] # キャスリングに関与する駒が一度も動いていない9and not self.is_check(piece.color, gameboard)) # キングがチェックされていない10# 白のキャスリング11if piece.color == 'W':12piece_req = (piece.name == 'WK'13and (7*side, 0) in gameboard14and gameboard[(7*side, 0)].name == 'WR')15and (rook_init_pos[side], 0) in gameboard16and gameboard[(rook_init_pos[side], 0)].name == 'WR')17gameboard_tmp = copy(gameboard)18# キャスリングに関与するキングとルークは除外して考える19if (king_init_pos, 0) in gameboard_tmp:20del gameboard_tmp[(king_init_pos, 0)]21if (rook_init_pos[side], 0) in gameboard_tmp:22del gameboard_tmp[(rook_init_pos[side], 0)]23# クイーンサイド24if side == 0:25# キングとルークの通過するマス26king_route = list(range(2, king_init_pos)) + list(range(2, king_init_pos, -1))27rook_route = list(range(3, rook_init_pos[side])) + list(range(3, rook_init_pos[side], -1))28special_req = (endpos == (2, 0)29# キングとルークの間に駒がない30and (1, 0) not in self.gameboard31and (2, 0) not in self.gameboard32and (3, 0) not in self.gameboard33and not any((x, 0) in gameboard_tmp34for x in king_route + rook_route)35# キングが通過するマスが敵に攻撃されていない36and path_is_not_attacked(0, [2, 3])37and path_is_not_attacked(0, list(x for x in range(2, king_init_pos)))38)39# キングサイド40if side == 1:41# キングとルークの通過するマス42king_route = list(range(size - 2, king_init_pos)) + list(range(size - 2, king_init_pos, -1))43rook_route = list(range(size - 3, rook_init_pos[side])) + list(range(size - 3, rook_init_pos[side], -1))44special_req = (endpos == (6, 0)45special_req = (endpos == (size - 2, 0)46# キングとルークの通過するマスに駒がない47and (6, 0) not in self.gameboard48and (5, 0) not in self.gameboard49and not any((x, 0) in gameboard_tmp50for x in king_route + rook_route)51# キングが通過するマスが敵に攻撃されていない52and path_is_not_attacked(0, [6, 5])53and path_is_not_attacked(0, list(x for x in range(size - 2, king_init_pos, -1)))54)55# 黒のキャスリング56if piece.color == 'B':57piece_req = (piece.name == 'BK'58and (7*side, 7) in gameboard59and gameboard[(7*side, 7)].name == 'BR')60and (rook_init_pos[side], size - 1) in gameboard61and gameboard[(rook_init_pos[side], size - 1)].name == 'BR')62gameboard_tmp = copy(gameboard)63# キャスリングに関与するキングとルークは除外して考える64if (king_init_pos, size - 1) in gameboard_tmp:65del gameboard_tmp[(king_init_pos, size - 1)]66if (rook_init_pos[side], size - 1) in gameboard_tmp:67del gameboard_tmp[(rook_init_pos[side], size - 1)]68# クイーンサイド69if side == 0:70# キングとルークの通過するマス71king_route = list(range(2, king_init_pos)) + list(range(2, king_init_pos, -1))72rook_route = list(range(3, rook_init_pos[side])) + list(range(3, rook_init_pos[side], -1))73special_req = (endpos == (2, 7)74special_req = (endpos == (2, size - 1)75# キングとルークの通過するマスに駒がない76and (1, 7) not in self.gameboard77and (2, 7) not in self.gameboard78and (3, 7) not in self.gameboard79and not any((x, size - 1) in gameboard_tmp80for x in king_route + rook_route)81# キングが通過するマスが敵に攻撃されていない82and path_is_not_attacked(7, [2, 3])83and path_is_not_attacked(size - 1, list(x for x in range(2, king_init_pos)))84)85# キングサイド86if side == 1:87# キングとルークの通過するマス88king_route = list(range(size - 2, king_init_pos)) + list(range(size - 2, king_init_pos, -1))89rook_route = list(range(size - 3, rook_init_pos[side])) + list(range(size - 3, rook_init_pos[side], -1))90special_req = (endpos == (6, 7)91special_req = (endpos == (size - 2, size - 1)92# キングとルークの通過するマスに駒がない93and (6, 7) not in self.gameboard94and (5, 7) not in self.gameboard95and not any((x, size - 1) in gameboard_tmp96for x in king_route + rook_route)97# キングが通過するマスが敵に攻撃されていない98and path_is_not_attacked(7, [6, 5])99and path_is_not_attacked(size - 1, list(x for x in range(size - 2, king_init_pos, -1)))100)101102return common_req and piece_req and special_req
create_tmp_board
の中身も変更しておきます。
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)]24if (king_init_pos, startpos_y) in gameboard_tmp:25gameboard_tmp[endpos] = gameboard_tmp[(king_init_pos, startpos_y)]26del gameboard_tmp[(king_init_pos, startpos_y)]27return gameboard_tmp
これでキャスリングの条件判定は正しく行えるようになりました。
ただし、まだ駒の再配置のコードの変更や、キングが1歩しか移動しない場合の曖昧性の排除が必要です。
お読みいただきありがとうございました。
ではまた