Python プログラムで動かすフェアリーチェスアプリ開発、連載第 17 回です。
は Pygame でゲームに効果音を追加しました。今回は変則チェスのひとつであるチェス960を追加してみたいと思います。
チェス960とは
チェスの初期配置を一定の条件のもとにランダム化するものです。
ポーンの位置は通常と同じで、残りの駒を次の条件を満たすよう白と黒が対称になるように配置します。
- 同色のビショップは色違いのマスに置く
- キングは自分のルークの間のどこかに置く
この条件を満たすような駒の配置は、
白マスのビショップの配置方法を決め、黒マスのビショップの配置方法を決め、
クイーンの配置方法を決め、そしてナイトの配置方法を決めれば、
キングがルークの間になければならないので、残りのルークとキングの配置は自動的に定まります。
よって合計で
より 960 通りの方法があります。
これがゲーム名の由来となっています。
通常の配置を除くと959通りになります。
初期配置生成
このチェス960をゲームに落とし込んでいきます。
まずは初期配置を生成します。
シャッフルで生成する方法
条件を満たすまでポーン以外の駒の配置をシャッフルで生成する方法です。
games.py1from random import shuffle23class Chess960:4'''チェス960'''5# 盤面のサイズ6size = 87# キャスリングの有無8castling = True9# プロモーション先10promote2 = [Knight, Bishop, Rook, Queen]11# 駒の配置12placers = {1: [Rook, Knight, Bishop, Queen, King, Bishop, Knight, Rook],132: [Pawn] * size}1415def __init__(self):16# 駒の配置の決定17while True:18shuffle(self.placers[1])19# 2つのビショップのマス色は違わなければならない20bishops = [pos for pos, piece in enumerate(self.placers[1]) if piece == Bishop]21if bishops[0] % 2 == bishops[1] % 2:22continue23# キングが2つのルークの間にいなければならない24rooks = [pos for pos, piece in enumerate(self.placers[1]) if piece == Rook]25if King not in self.placers[1][rooks[0]:rooks[1]]:26continue27# 通常の配置と異なる28if self.placers[1] == [Rook, Knight, Bishop, Queen, King, Bishop, Knight, Rook]:29continue30break3132# 画像IDの割り当て33ID = {}34...
全部で 通りになるので、最悪 4000 通りほどの無駄が発生します。
ID を使う方法
ID番号から配置を割り出す方法があります。
0 - 959 までのランダムな数値を生成してから、対応する配置を生成することで、
無駄なく初期配置を生成できます。
ID 番号から配置を割り出すには次のようにする。
- ID番号を4で割った剰余は、白枡ビショップの位置を表す: 0 は b ファイル、1 は d ファイル、2 は f ファイル、3 は h ファイル。
- 前項の商をさらに4で割った剰余は、黒枡ビショップの位置を表す: 0 は a ファイル、1 は c ファイル、2 は e ファイル、3 は g ファイル。
- 前項の商をさらに6で割った剰余は、(2個のビショップを除いた)6個の空き枡におけるクイーンの位置を表す: 0 は いちばん左の空き枡、5 は いちばん右の空き枡。
- 前項の商は、0 から 9 の間にある。これを KRN(カーン)コードと呼び、残り5つの枡におけるキング・ルーク・ナイトの位置を表す。
KRN コード | 位置 |
---|---|
0 | N N R K R |
1 | N R N K R |
2 | N R K N R |
3 | N R K R N |
4 | R N N K R |
5 | R N K N R |
6 | R N K R N |
7 | R K N N R |
8 | R K N R N |
9 | R K R N N |
(チェス960 - Wikipedia より引用)
KRN コードの定義が少し面倒です。
キングがルークの間になければならないという制約があるので、ただ単にありえる順列を列挙すればいいというわけではありません。
games.py1from random import randint23class Chess960:4'''チェス960'''5# 盤面のサイズ6size = 87# キャスリングの有無8castling = True9# プロモーション先10promote2 = [Knight, Bishop, Rook, Queen]11# 駒の配置12placers = {1: [None] * size,132: [Pawn] * size}1415ID = {}1617def __init__(self):18# 駒の配置の決定19pos_id = randint(0, 959)20while pos_id == 518:21pos_id = randint(0, 959)22krn = [23[Knight, Knight, Rook, King, Rook],24[Knight, Rook, Knight, King, Rook],25[Knight, Rook, King, Knight, Rook],26[Knight, Rook, King, Rook, Knight],27[Rook, Knight, Knight, King, Rook],28[Rook, Knight, King, Knight, Rook],29[Rook, Knight, King, Rook, Knight],30[Rook, King, Knight, Knight, Rook],31[Rook, King, Knight, Rook, Knight],32[Rook, King, Rook, Knight, Knight]33]3435q, r = pos_id//4, pos_id%436self.placers[1][2*r + 1] = Bishop3738q, r = q//4, q%439self.placers[1][2*r] = Bishop4041q, r = q//6, q%642for i in range(self.size):43if self.placers[1][i] is None:44if i == r:45self.placers[1][i] = Queen46break47else: r += 14849for piece in krn[q]:50self.placers[1][self.placers[1].index(None)] = piece5152# 画像IDの割り当て53for rk in self.placers:54for fl in range(self.size):55if self.placers[rk][fl] is not None:56self.ID['W' + self.placers[rk][fl].abbr] = self.size * rk + fl57self.ID['B' + self.placers[rk][fl].abbr] = -(self.size * rk + fl)
これで初期配置が生成できました。
ゲーム追加
の方法と同様にして、定義したゲームを追加していきます。games.py1...2# ゲーム選択画面に表示するゲーム3game_dict = {0: (Normal, withUnicorn)}4game_dict = {0: (Normal, Chess960, withUnicorn)}
utils.py1...2# ゲーム選択画面に表示するゲーム名3game_name_dict = {0: ('Normal Chess', 'Unicorn')}4game_name_dict = {0: ('Normal Chess', 'Chess 960', 'Unicorn')}5...
これでチェス960が遊べるようになりました。
ところが、チェス960のキャスリングは少々特殊な処理を加える必要があります。
はキャスリング部分の実装をしていきます。お読みいただきありがとうございました。
では