チェスアプリ開発(15) ゲームの切り替え

 

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

前回はフェアリー駒を作成しました。

今回はさまざまな種類のフェアリーチェスを切り替えられるようにしていきます。

現在の状態では、スクリプトを起動するとすぐに盤面が表示されて遊べる状態になりますが、

この前にいったんゲーム種類を選択する画面を入れます。


選択画面では5行2列、1ページで10種類のゲームを並べることにします。

画面で特定のボタンを押すと、対応するゲームの盤面がセットされてゲームがスタートするようにします。

そのためには、押したボタンによってセットする盤面や駒を変化させることになります。


ゲームを定義する

ゲームによって変化するのは、盤面のサイズ、キャスリングの有無、ポーンのプロモーション先、

どの駒がどこに置かれているのかや、画像IDと駒の対応の情報です。


これらを定義したクラスをゲーム種類ごとに作成します。

これらのクラスの定義をまとめたモジュールgames.pyを作成します。

games.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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
'''ゲームの種類によって変わる駒の配置や名前、画像IDを記録したモジュール'''

from pieces import *


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)


class withUnicorn:
    '''キングサイドのナイトがユニコーンになったチェス'''
    # 盤面のサイズ
    size = 8
    # キャスリングの有無
    castling = True
    # プロモーション先
    promote2 = [Knight, Bishop, Rook, Queen, Unicorn]
    # 駒の配置
    placers = {1: [Rook, Knight, Bishop, Queen, King, Bishop, Unicorn, 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)

クラス名は駒クラス名と異なることに注意します。

画像IDには、各種各色の駒に一意的に整数が割り当てられます。


これらのクラスをもとにしてインスタンスを作成し、その属性を元に盤面や駒を設置します。

インスタンス作成のタイミングは、ゲームの種類が決まったとき=特定のボタンが押されたときですが、

ひとまずインスタンスを代入する変数の宣言をしておきます。

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
25
26
27
28
29
30
31
32
33
34
35
36
class Game:
    def __init__(self):
        self.playersturn = W
        self.gameboard = {}
        self.place_pieces()
        # ゲームの種類
        self.kind = None
        ...

    def after_deciding_kind(self):
        '''ゲーム種類決定後の処理'''
        # 駒の配置
        self.place_pieces() 
        # 画像の設定
        for name, num in self.kind.ID.items():
            set_img(name, name[0], num)

    def place_pieces(self):
        for i in range(0, 8):
            self.gameboard[(i, 1)] = Pawn(W, 1)
            self.gameboard[(i, 6)] = Pawn(B, -1)
 
        placers = [Rook, Knight, Bishop, Queen, King, Bishop, Unicorn, Rook]
 
        for i in range(0, 8):
            self.gameboard[(i, 0)] = placers[i](W)
            self.gameboard[(i, 7)] = placers[i](B)
        for fl in range(self.kind.size):
            for rk in self.kind.placers:
                # None を指定すれば駒が置かれることはなく次のマスへ進む
                if self.kind.placers[rk][fl] is not None:
                    # 白の駒
                    self.gameboard[(fl, rk - 1)] = self.kind.placers[rk][fl]('W')
                    # 黒の駒
                    self.gameboard[(fl, self.kind.size - rk)] = self.kind.placers[rk][fl]('B')
...

after_deciding_kindメソッドで、インスタンスの属性をもとに駒の配置と画像IDの設定をしています。


Pawnクラスでは独自にコンストラクタを設定していましたが、

色によって進む方向は明白なので特別に定義する必要はありません。

pieces.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
...

class Pawn(Piece):
    abbr = 'P'

    def __init__(self, color, direction):
        self.color = color
        # of course, the smallest piece is the hardest to code. direction should be either 1 or -1, should be -1 if the pawn is traveling "backwards"
        self.direction = direction
        self.name = color + self.abbr
 
    def available_moves(self, x, y, gameboard):
        self.direction = 1 if self.color == 'W' else -1
        ...

ゲームボタンを表示する

5行2列にボタンを並べる関数を定義します。

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
29
30
31
32
33
34
35
36
37
38
39
40
...
def draw_button(left, right, bottom, top,
        letter, back_color=(1.00, 0.81, 0.62), font_color=(0.82, 0.55, 0.28)):
    '''ボタンを描画する

    Parameters
    ----------
    left, right, bottom, top : float
        ボタンの左右下上端の座標.
    letter : str
        ボタン上に描画する文字.
    back_color : tuple or list, default (1.00, 0.81, 0.62)
        ボタンの色.
    font_color : tuple or list, default (0.82, 0.55, 0.28)
        文字の色.
    '''
    glColor(*back_color)
    glBegin(GL_QUADS)
    glVertex(left, bottom)
    glVertex(left, top)
    glVertex(right, top)
    glVertex(right, bottom)
    glEnd()
    glColor(*font_color)
    draw_str((left + right) / 2 - 0.1 * len(letter), (bottom + top) / 2, letter, gap=0.2)


# ゲーム選択画面に表示するゲーム名
game_name_dict = {0: ('Normal Chess', 'Unicorn', 'A', 'B', 'C'),
    1: ('D', 'E', 'F')}


def draw_game_menu():
    '''ゲーム選択メニューを描画する'''
    for i in range(2):
        for j in range(5):
            if i in game_name_dict and j < len(game_name_dict[i]):
                draw_button(4.5*i - 0.5, 4.5*i + 3.0, 6.5 - 1.5*j, 7.5 - 1.5*j,
                    game_name_dict[i][j])
...

25行目の0.10.2という数値は見え方によって調整します。

これを描画イベント内部で使用します。

main.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
from utils import *
...

class Game:
    ...
    def draw(self):
        ...
        if self.kind == None:
            draw_game_menu()
        else:
            if self.time == 1:
                self.main()
            ...
        ...

これでボタンが表示されました。

ゲームボタン


ボタンクリックに対応する

ボタンをクリックしたときに、指定のゲーム種類を内部で登録するようにします。

まず、表示位置に対応するゲームクラスを格納するディクショナリを定義します。

games.py
1
2
3
...
# ゲーム選択画面に表示するゲーム
game_dict = {0: (Normal, withUnicorn)}

表示用に使ったA B C ...はクラスを作っていないので省いています。


これを使ってマウスイベントの処理を書きます。

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
...
from games import *
...

class Game:
    ...
    def mouse(self, button, state, x, y):
        ...
        # 左クリック
        if (button == GLUT_LEFT_BUTTON
                and state == GLUT_DOWN):
            try:
                # ゲーム種類選択
                if self.kind == None:
                    for i in range(2):
                        for j in range(5):
                            if i in game_dict and j < len(game_dict[i]):
                                if on_square(*self.mousepos, 4.5*i - 0.5, 4.5*i + 3.0, 6.5 - 1.5*j, 7.5 - 1.5*j):
                                    self.kind = game_dict[i][j]()
                                    self.after_deciding_kind()
                else:
                    ...
    ...

これでゲームの選択を実現できました。

ボタンを選択すると盤面と駒が表示されます。


今回の変更は GitHub にて公開しています。


基本的にこれまで解説してきたことを使えば、フェアリー駒の作成やゲーム種類の拡張をすることができるようになります。

あとは特殊な挙動の駒をどう実装するか、などといった細かい話になってきます。

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

ではまた👋