チェスアプリ開発(20) プロモーションの拡張

 

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

前回でフェアリーチェスへのキャスリングの拡張が完了しました。

今回はポーンのプロモーションの拡張をしていきます。

拡張前のプロモーションの実装は第11回に載せています。

関連記事

チェスアプリ開発(11) プロモーションの実装

チェスアプリ開発(11) プロモーションの実装


ゲーム毎にプロモーション先候補を指定

まずはゲームによって異なるプロモーション先候補を、各ゲームクラスの プロパティ オブジェクトが参照できるデータ として定義することが必要です。

これは第15回ですでに完了しています。

games.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class Normal:
    '''通常のチェス'''
    # 盤面のサイズ
    size = 8
    # キャスリングの有無
    castling = True
    # プロモーション先
    promote2 = [Knight, Bishop, Rook, Queen]
    # 駒の配置
    placers = {1: [Rook, Knight, Bishop, Queen, King, Bishop, Knight, Rook],
        2: [Pawn] * size}
    # 画像IDの割り当て
    ID = {}
    for rk in placers:
        for fl in range(size):
            if placers[rk][fl] is not None:
                ID['W' + placers[rk][fl].abbr] = size * rk + fl
                ID['B' + placers[rk][fl].abbr] = -(size * rk + fl)

promote2というリストの中からポーンのプロモーション先が選択されます。


プロモーション時に表示する選択肢を一般化

次にプロモーションの選択肢として表示される駒を、このリストに合わせて変更しなければなりません。

現在プロモーション選択肢の表示部分のコードは次のようになっています。

main.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
...
class Game:
    ...
    def draw(self):
        '''描画コールバック'''
        ...
            # プロモーション
            if self.prom:
                draw_balloon(*self.endpos)
                piece_color = self.gameboard[self.endpos].color
                glEnable(GL_TEXTURE_2D)
                draw_img(2.0, 3.5, piece_ID[piece_color + 'N'])
                draw_img(3.0, 3.5, piece_ID[piece_color + 'B'])
                draw_img(4.0, 3.5, piece_ID[piece_color + 'R'])
                draw_img(5.0, 3.5, piece_ID[piece_color + 'Q'])
                glDisable(GL_TEXTURE_2D)
            ...

12-15行目がプロモーション先を固定してしまっているので、この部分を変更します。

どの位置座標にどの駒の画像を配置するかもゲームによって変化しますので、

表示幅は 4 列までとして行を増やしていく形で描画します。

main.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# プロモーション
if self.prom:
    draw_balloon(*self.endpos)
    piece_color = self.gameboard[self.endpos].color
    glEnable(GL_TEXTURE_2D)
    draw_img(2.0, 3.5, piece_ID[piece_color + 'N'])
    draw_img(3.0, 3.5, piece_ID[piece_color + 'B'])
    draw_img(4.0, 3.5, piece_ID[piece_color + 'R'])
    draw_img(5.0, 3.5, piece_ID[piece_color + 'Q'])
    for i in range(len(self.kind.promote2)):
        draw_img(2.0 + i % 4,
            3.5 + ((len(self.kind.promote2) - 1)//4)/2 - i//4,
            self.kind.ID[piece_color +
                self.kind.promote2[i].abbr])
    glDisable(GL_TEXTURE_2D)

また、それに伴って吹き出しの縦幅も増やす必要があります。

吹き出しはdraw_balloon関数が描画していますので、そちらの中身を書き換えます。

utils.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
def draw_balloon(x, y):
def draw_balloon(x, y, num=4):
    '''
    プロモーションのときの吹き出しを描画する

    Parameters
    ----------
    x, y : int
        駒の座標.
    num : int, default 4
        プロモーション選択肢数.
    '''
    glColor(0.5, 0.5, 0.5)  # 色の指定
    glBegin(GL_QUADS)       # 四角形を描画
    glVertex(1.0, 2.5)
    glVertex(1.0, 4.5)
    glVertex(6.0, 4.5)
    glVertex(6.0, 2.5)
    glVertex(1.0, 2.5 - ((num - 1) // 4) / 2)
    glVertex(1.0, 4.5 + ((num - 1) // 4) / 2)
    glVertex(2.0 + num if num <= 4 else 6.0, 4.5 + ((num - 1) // 4) / 2)
    glVertex(2.0 + num if num <= 4 else 6.0, 2.5 - ((num - 1) // 4) / 2)
    glEnd()
    glBegin(GL_TRIANGLES)   # 三角形を描画
    glVertex(3.0, 3.5)
    glVertex(4.0, 3.5)
    glVertex(x, y)
    glEnd()

glBeginからglEndまでの間の4つのglVertexが四角形の各頂点の位置を指定しています。

新たに追加いた引数num=プロモーション選択肢の数によってこの座標を調整しています。

(num - 1) // 4num4までのとき08までのとき1というように増えていくので、

縦方向の座標2.54.5を基準に縦幅が増えていくことになります。

横幅は2.0 + num if num <= 4 else 6.0というふうに、選択肢が4つ以下かどうかで変えています。


吹き出し描画部分のコードも変更します。

main.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# プロモーション
if self.prom:
    draw_balloon(*self.endpos)
    draw_balloon(*self.endpos, num=len(self.kind.promote2))
    piece_color = self.gameboard[self.endpos].color
    glEnable(GL_TEXTURE_2D)
    for i in range(len(self.kind.promote2)):
        draw_img(2.0 + i % 4,
            3.5 + ((len(self.kind.promote2) - 1)//4)/2 - i//4,
            self.kind.ID[piece_color +
                self.kind.promote2[i].abbr])
    glDisable(GL_TEXTURE_2D)

これで通常のゲームのプロモーションでは下のようになり、

プロモーション1

ユニコーンが加わったゲーム(promote2 = [Knight, Bishop, Rook, Queen, Unicorn])では下のようになります。

プロモーション2

縦幅が大きくなりました。


マウスクリックを調整

マウスクリックでこの選択肢の中から駒を選択するわけですが、今のコードは次のようになっています。

main.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
...
class Game:
    ...
    def mouse(self, button, state, x, y):
        '''
        マウス入力コールバック
        '''
        ...
            # プロモーション
            if self.prom:
                piece_color = self.gameboard[self.endpos].color
                if on_square(*self.mousepos, 1.5, 2.5, 3.0, 4.0):
                    self.gameboard[self.endpos] = Knight(piece_color)
                    self.prom = False
                if on_square(*self.mousepos, 2.5, 3.5, 3.0, 4.0):
                    self.gameboard[self.endpos] = Bishop(piece_color)
                    self.prom = False
                if on_square(*self.mousepos, 3.5, 4.5, 3.0, 4.0):
                    self.gameboard[self.endpos] = Rook(piece_color)
                    self.prom = False
                if on_square(*self.mousepos, 4.5, 5.5, 3.0, 4.0):
                    self.gameboard[self.endpos] = Queen(piece_color)
                    self.prom = False
            ...

座標も駒も固定になっていますので、これを変更します。

main.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# プロモーション
if self.prom:
    piece_color = self.gameboard[self.endpos].color
    for i in range(len(self.kind.promote2)):
        if on_square(*self.mousepos,
                        1.5 + i % 4,
                        2.5 + i % 4,
                        3.0 + ((len(self.kind.promote2) - 1)//4)/2 - i//4,
                        4.0 + ((len(self.kind.promote2) - 1)//4)/2 - i//4):
            self.gameboard[self.endpos] = self.kind.promote2[i](piece_color)
            self.prom = False

on_square関数の最後の4つの引数left, right, bottom, topが有効範囲を指定しています。

この範囲内にself.mousepos=クリック時のマウスカーソル位置があれば、ポーンが指定の駒に置き換わります。


これでプロモーションの拡張が完了しました!

今回の変更も GitHub に上げておきます。


これまで20回かけてチェスのコードの編集やフェアリーチェスへの拡張をやってきました。

これにさらにチェックやメイトを知らせるダイアログを表示したり、手数を戻せるようにしたり、

あるいはコンピュータ対戦を導入しても面白いでしょう。

が、いったんこのシリーズは一区切りついたことにしたいと思います。

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

では👋