チェスアプリ開発(5) チェックメイト・ステイルメイトの判定

 

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

前回は自らチェックされに行くような動きができないようにしました。

今回は チェックメイト キングがチェックされていて、一手でチェックから逃れることができない状態 ステイルメイト キングがチェックされていないが、打つ手がない状態 を判定できるようにします。

動かせる駒があるかどうかの判定

チェックメイトを判定できるようにしましょう。

チェックメイトとチェックは何が違うかというと、

チェックは一手でチェックじゃなくできるけど、チェックメイトはどうあがいてもチェックを免れない

という点です。

チェックというのがキングが敵駒に攻撃されている状態ですから、

チェックメイトはキングを相手の攻撃範囲外にすることが一手ではできないという状態ですね。

前回で自らチェックされに行くような動きは出来なくなりました。

つまりどう動いてもチェックされてしまうというときはどこにも動けません。

動かせる駒がないとき、チェックメイトまたはステイルメイトであると言えます。

どの駒も動かすことができないとき、Trueを返すような メソッド クラス内で定義される関数のようなもの を作りましょう。

main.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class Game:
    ...
    def cannotMove(self, color, gameboard):
        '''color側が駒を動かせないときTrueを返す'''
        for position, piece in gameboard.items():
            # 盤面上の駒の位置と駒について
            if color == piece.Color:
                # 駒色がcolorのとき
                for dest in piece.availableMoves(*position, gameboard, Color=color):
                    # 駒の移動先について
                    # 移動後にチェック回避できるならFalse
                    gameboardTmp = copy(gameboard)
                    gameboardTmp[dest] = gameboardTmp[position]
                    del gameboardTmp[position]
                    if not self.isCheck(color, gameboardTmp):
                        return False
        # colorのどの駒をどのように移動させてもチェック回避できなかったとき
        return True

13, 14 行目の形は今までも何度か出てきました。

この際この 2 行を表すメソッドを作ってしまいましょう。

main.py
1
2
3
4
5
6
class Game:
    ...
    def renewGameboard(self, startpos, endpos, gameboard):
        '''盤面を更新する'''
        gameboard[endpos] = gameboard[startpos]
        del gameboard[startpos]

これでmain()isValidMove()cannotMove()は次のようになります。

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
class Game:
    ...
    def main(self):
        while True:
            ...
            if target:
                ...
                if self.isValidMove(target, startpos, endpos, self.gameboard):
                    self.message = "that is a valid move"
                    self.renewGameboard(startpos, endpos, self.gameboard)
                    self.isCheck(target.Color, self.gameboard)
                    ...
    ...
    def isValidMove(self, piece, startpos, endpos, gameboard):
        '''pieceのstartpos -> endposの動きが可能であるときTrueを返す'''
        if endpos in piece.availableMoves(startpos[0], startpos[1], gameboard, Color=piece.Color):
            gameboardTmp = copy(gameboard)
            self.renewGameboard(startpos, endpos, gameboardTmp)
            if self.isCheck(piece.Color, gameboardTmp):
                return False
            else:
                return True
        else:
            return False
    ...
    def cannotMove(self, color, gameboard):
        '''color側が駒を動かせないときTrueを返す'''
        for position, piece in gameboard.items():
            if color == piece.Color:
                for dest in piece.availableMoves(*position, gameboard, Color=color):
                    gameboardTmp = copy(gameboard)
                    self.renewGameboard(position, dest, gameboardTmp)
                    if not self.isCheck(color, gameboardTmp):
                        return False
        return True

さて、これでcannotMove() and isCheck()ならチェックメイト、

cannotMove() and not isCheck()ならステイルメイトです。


判定してメッセージ表示

あとはこれを適切な位置においてメッセージも表示させれば OK です。

チェックと同じ場所に置きたいですね。

どこで判定してどこでメッセージ表示しているのでしょうか。

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
class Game:
    ...
    def main(self):
        while True:
            ...
            if target:
                ...
                if self.isValidMove(target, startpos, endpos, self.gameboard):
                    self.message = "that is a valid move"
                    self.renewGameboard(startpos, endpos, self.gameboard)
                    self.isCheck(target.Color, self.gameboard)
                    ...
    ...
    def isCheck(self, color, gameboard):
        kingDict = {}
        pieceDict = {BLACK : [], WHITE : []}
        for position, piece in gameboard.items():
            if type(piece) == King:
                kingDict[piece.Color] = position
            pieceDict[piece.Color].append((piece, position))
        if self.canSeeKing(kingDict[color], pieceDict[opponent[color]], gameboard):
            self.message = f"{color} player is in check"
            return True

どうやらメッセージはself.messageが請け負っているようです。

せっかくisCheck()に真偽判定をさせているので、こっちの書き方のほうがすっきりするかな。

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
class Game:
    ...
    def main(self):
        while True:
            ...
            if target:
                ...
                if self.isValidMove(target, startpos, endpos, self.gameboard):
                    self.message = "that is a valid move"
                    self.renewGameboard(startpos, endpos, self.gameboard)
                    self.isCheck(target.Color, self.gameboard)
                    if self.isCheck(target.Color, self.gameboard):
                        self.message = f"{target.Color} player is in check"
                    ...
    ...
    def isCheck(self, color, gameboard):
        kingDict = {}
        pieceDict = {BLACK : [], WHITE : []}
        for position, piece in gameboard.items():
            if type(piece) == King:
                kingDict[piece.Color] = position
            pieceDict[piece.Color].append((piece, position))
        if self.canSeeKing(kingDict[color], pieceDict[opponent[color]], gameboard):
            self.message = f"{color} player is in check"
            return True

移してみました。

同じ要領で、チェックメイトやステイルメイトのメッセージも盛り込めますね。

あと、メイトになるとゲームは終了するので、プログラムを終了させるようにましょう。

main.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import sys
 
def main(self):
    while True:
        ...
        if target:
            ...
            if self.isValidMove(target, startpos, endpos, self.gameboard):
                self.message = "that is a valid move"
                self.renewGameboard(startpos, endpos, self.gameboard)
                if self.isCheck(target.Color, self.gameboard):
                    self.message = f"{target.Color} player is in check"
                if self.cannotMove(target.Color, self.gameboard):
                    if self.isCheck(target.Color, self.gameboard):
                        self.message = f"Checkmate! {opponent[target.Color]} player won!"
                        sys.exit()
                    else:
                        self.message = "Stalemate! It's draw."
                        sys.exit()
                ...

これまでの回で、当初指摘されていた問題点のほとんどは解決しました。

キャスリングやアンパッサンなどの実装もしたいのですが、

その前に見た目や遊び方をゲームっぽくしていきたいと思います。

次回はこちら

それではまた👋