チェスアプリ開発(14) フェアリー駒の作成

Python プログラムで動かすフェアリーチェスアプリ開発、連載第 14 回です。

までで通常のチェスが問題なくプレイできるようになり、はコードを整形しました。

今回は、いよいよフェアリーチェスの幕開けです!


ところで、フェアリーチェスってなんでしたっけ?

フェアリーチェスは、広く言えば通常のチェスとは異なるルールのもとでプレイされるチェスのことです。

つまり、なんでもありなんです。

盤面が小さくても大きくても丸くても、駒の動きが弱くても強くても、なんでもありです。

とはいっても、盤面の大きさや駒の動きを変えることがほとんどです。


今回は手始めに、駒の動きを変えてしまいましょう。


駒の動きの定義

各駒の動きは、pieces.pyにて定義されています。

例えば、ナイトとルークはこのようになっています。

pieces.py
1
class Knight(Piece):
2
abbr = 'N'
3
4
def available_moves(self, x, y, gameboard):
5
return [(xx, yy) for xx, yy in leaper(x, y, 2, 1) if self.no_conflict(gameboard, self.color, xx, yy)]
6
7
8
class Rook(Piece):
9
abbr = 'R'
10
11
def available_moves(self, x, y, gameboard):
12
return self.rider(x, y, gameboard, self.color, chess_cardinals)

available_movesで駒の動きを定義しています。

返り値は2要素のリストです。


これを少しいじってみましょう。

pieces.py
1
class Knight(Piece):
2
abbr = 'N'
3
4
def available_moves(self, x, y, gameboard):
5
return [(xx, yy) for xx, yy in leaper(x, y, 2, 3) if self.no_conflict(gameboard, self.color, xx, yy)]
6
7
8
class Rook(Piece):
9
abbr = 'R'
10
11
def available_moves(self, x, y, gameboard):
12
return self.rider(x, y, gameboard, self.color, [(1, 2), (2, 1)])

すると、このような動きになりました。

おわかりいただけただろうか…?笑


ナイトのほうは、横に2マス、縦に3マスいったところ((2, 3)方向)に動けるようになりました。

もともとは横に2マス、縦に1マス((2, 1)方向)でしたね。

1を3に変更したので、このような動きになりました。

leaperという関数によって、一方向を指定しただけでも8方向に広げてくれます。


ルークのほうは、(1, 2)方向と(2, 1)方向に直線的に走るようになりました。

こちらはleaperがないため、指定した2方向にしか動けず、

動画で動かしたもの以外の3つのルークは一歩も動けません。

しかしriderメソッドによって、ナイトとは違って走ることができます。

もともとはchess_cardinalsで、これは[(1, 0), (0, 1), (-1, 0), (0, -1)]のことで、

この方向は十字方向です。


他の駒の動きも、この要領で変えていくことができるし、

自分で新しい駒を定義することもできます。


新しい駒の作成

試しに「ユニコーン」という駒を作成してみましょう。

この駒は、ナイトが走るように、つまりナイト方向に一直線に動きます。

他の駒定義にならって、ユニコーンクラスを定義します。

pieces.py
1
class Unicorn(Piece):
2
abbr = 'Un'
3
4
def available_moves(self, x, y, gameboard):
5
return self.rider(x, y, gameboard, self.color, leaper(0, 0, 1, 2))

riderメソッドとleaper関数を組み合わせています。

ところで、それぞれの中身は次のようになっています。

pieces.py
1
class Piece:
2
...
3
def rider(self, x, y, gameboard, color, intervals):
4
'''
5
Parameters
6
----------
7
x, y : int
8
駒の絶対座標.
9
gameboard : dict > {(int, int): obj, ...}
10
盤面.
11
color : str > 'W', 'B'
12
駒色.
13
intervals : list > [(int, int), ...]
14
移動の方向.相対座標.
15
16
Returns
17
-------
18
answers : list > [(int, int), ...]
19
駒の可能な移動先.
20
'''
21
answers = []
22
for xint, yint in intervals:
23
# intervals 中の (xint, yint) 方向について
24
# (xtemp, ytemp) : 盤面上の現在位置絶対座標 (x, y) から
25
# (xint, yint) 方向に動いたときの絶対座標
26
xtemp, ytemp = x + xint, y + yint
27
while self.is_in_bounds(xtemp, ytemp):
28
# 盤面上に収まるとき
29
# target : 盤面上の (xtemp, ytemp) の位置に駒があればその駒(obj).
30
# なければ None.
31
target = gameboard.get((xtemp, ytemp), None)
32
if target is None:
33
# 駒がないのでそのマスには動ける
34
answers.append((xtemp, ytemp))
35
elif target.color != color:
36
# 相手の駒があるので取れる
37
answers.append((xtemp, ytemp))
38
# 駒にぶつかったので,これ以上進めない
39
break
40
else:
41
# 自分の駒にぶつかったので,これ以上進めない
42
break
43
44
# (xtemp, ytemp) から (xint, yint) 方向に一回動く
45
xtemp, ytemp = xtemp + xint, ytemp + yint
46
return answers
47
...
48
def leaper(x, y, int1, int2):
49
'''
50
Parameters
51
----------
52
x, y : int
53
駒の位置.
54
int1, int2 : int
55
駒の移動方向 (int1, int2).
56
57
Returns
58
-------
59
list > [(int, int), ...]
60
駒の可能な移動先.
61
'''
62
return [(x+int1, y+int2), (x-int1, y+int2), (x+int1, y-int2), (x-int1, y-int2),
63
(x+int2, y+int1), (x-int2, y+int1), (x+int2, y-int1), (x-int2, y-int1)]

leaperの最初の2つの引数を0にしたのは、riderメソッドに渡すのは方向(相対座標)であって位置(絶対座標)ではないからです。

盤上に配置

さて、駒は作れたわけですが、このままではまだユニコーンが盤上に登場しません。

盤面に駒を置いているのは、main.pyplace_piecesメソッドです。

main.py
1
class Game:
2
...
3
def place_pieces(self):
4
for i in range(0, 8):
5
self.gameboard[(i, 1)] = Pawn(W, 1)
6
self.gameboard[(i, 6)] = Pawn(B, -1)
7
8
placers = [Rook, Knight, Bishop, Queen, King, Bishop, Knight, Rook]
9
10
for i in range(0, 8):
11
self.gameboard[(i, 0)] = placers[i](W)
12
self.gameboard[(i, 7)] = placers[i](B)

この部分の詳細な説明はでしましたが、

ざっくり言うとgameboardの特定の位置に特定の駒オブジェクトを配置しています。

とりあえず、次の行を追加します。

main.py
1
class Game:
2
...
3
def place_pieces(self):
4
for i in range(0, 8):
5
self.gameboard[(i, 1)] = Pawn(W, 1)
6
self.gameboard[(i, 6)] = Pawn(B, -1)
7
8
placers = [Rook, Knight, Bishop, Queen, King, Bishop, Knight, Rook]
9
10
for i in range(0, 8):
11
self.gameboard[(i, 0)] = placers[i](W)
12
self.gameboard[(i, 7)] = placers[i](B)
13
14
self.gameboard[(0, 2)] = Unicorn(W)

これで、a3 の位置に白のユニコーンが配置されるはずです。


あと、駒を表示するための画像も用意しなければなりません。

/img/W/WUn.png/img/B/BUn.pngを作成します。

画像は Alfaerie Variant Chess Graphics をもとに作成しています。


それと、画像と ID の紐づけもしておきます。

pieces.py
1
piece_names = [Knight, Rook, Bishop, Queen, King, Pawn, Unicorn]
2
3
# 画像IDの割り当て
4
piece_ID = {}
5
for i, piece in enumerate(piece_names):
6
piece_ID['W' + piece.abbr] = i + 1
7
piece_ID['B' + piece.abbr] = -(i + 1)

これで動かしてみると、このようになりました。

思った通りの動きになりました!


実際にはこんなところに駒を置くわけにもいかないので、

ナイトの代わりに置いたりするのですが、

その場合は次のように書きます。

main.py
1
class Game:
2
...
3
def place_pieces(self):
4
for i in range(0, 8):
5
self.gameboard[(i, 1)] = Pawn(W, 1)
6
self.gameboard[(i, 6)] = Pawn(B, -1)
7
8
placers = [Rook, Knight, Bishop, Queen, King, Bishop, Unicorn, Rook]
9
10
for i in range(0, 8):
11
self.gameboard[(i, 0)] = placers[i](W)
12
self.gameboard[(i, 7)] = placers[i](B)

これで黒白双方のキングサイドのナイトがユニコーンに変わります。

ユニコーンチェス

これも一種のフェアリーチェスですね!


今回はフェアリー駒の導入をしました。

しかし、このままだと一種類のチェスしかできないので、

はさまざまな種類のゲームを切り替えられるようにします。

お読みいただきありがとうございました。

ではまた👋