nounai.output(spaghetiThinking);

趣味と実益を兼ねて将棋プログラム(研究ツールなど)を作ってみたいと思う私の試行錯誤とか勉強したことを綴ってゆく予定です。 主目的はプログラミングの経験値稼ぎですが、コンピュータ将棋の製作も目指してみたいとも考えています。

将棋 de Decorator実装してみた

将棋関係のプログラミングで何か題材ないかなと思い、実装してみました。


駒の種類を素直にサブクラス化しようとすると成駒をどうするか、という話が出てきます。 その辺の振る舞いをDecoratorを用いて実装します。正直なところ、歩~王までの種類をサブクラス化して「成り」はステート扱いするのが一番楽だとは思います。駒の定義って、八方桂とか鏡角とか、特殊ルールみたいなことでもしない限りは不変ですし。モデルになってる「将棋」部分では仕様が変わることがありえないので、駒でクラスを作る気ならどのようなインタフェースを持たせるかに頭使う方が良い気がします。まぁしかし、「委譲ってスゲー!OOスゲー!」と思う程度には勉強になったなと思ってます。

で、現物は(無駄っぽそうなサブクラス化も多そうですが)以下のようになりました。駒の利きを自分からの相対座標として表現しています。また、「利き」の英語が自信ないんですが、ここでは"effects"としています。

仕様(ざっくり)

  • 駒関係のスーパクラスは"AbstractPiece"
  • 不成状態の駒は"Piece",成り状態の駒は"PromotedPiece"を共通の親に持つ
  • AbstractPieceで定義するメソッドは四つ
    • getName() ... 駒の漢字表記を返す
    • promote() ... その駒の成り状態の駒を返す
    • unpromote() ... その(成)駒の不成状態にあたる駒を返す
    • getEffects() ... その駒の利きを盤上の相対座標(タプル形式)のリストにして返す
  • 不成り状態の駒をunpromoteしたり、成り駒をpromoteしようとすると例外を出すようにする
  • 全ての駒についての実装は面倒だったので、実装がない駒もあり。promote/unpromoteで都合が悪くなる(不成状態の方は実装したのに成状態の方はまだのケース)ので、その辺はひとまず例外で"未実装"メッセージを出すようにして対処

ソース

#!/usr/bin/env python
# -*- coding: utf-8 -*-

class AbstractPiece:
        def getEffects(self):
                return None
        def promote(self):
                pass
        def unpromote(self):
                pass
        def getName(self):
                pass

class Piece(AbstractPiece):
        def promote(self):
                raise CannotPromoting, "Not implements yet"
        def unpromote(self):
                raise CannotUnpromoting, "Non-promoted piece"

class Fu(Piece):
        def getEffects(self):
                return [(0,1)]
        def promote(self):
                return To()
        def getName(self):
                return "歩"

class Ky(Piece):
        def getEffects(self):
                return [(0,1),(0,2),(0,3),(0,4),(0,5),(0,6),(0,7),(0,8),(0,9)]
        def getName(self):
                return "香"

class Gi(Piece):
        def getEffects(self):
                return [(-1,1),(0,1),(1,1),(-1,-1),(-1,1)]
        def getName(self):
                return "銀"

class Ki(Piece):
        def getEffects(self):
                return [(-1,1),(0,1),(1,1),(0,-1),(0,1),(-1,0)]
        def promote(self):
                """
                CannotPromotingを投げるべき?
                """
                return self
        def getName(self):
                return "金"

class Hi(Piece):
        def getEffects(self):
                return [(0,-3),(0,-2),(0,-1),(0,1),(0,2),(0,3),
                                (-3,0),(-2,0),(-1,0),(1,0),(2,0),(3,0)]
        def promote(self):
                return Ry()
        def getName(self):
                return "飛"

# Docorator
class PromotedPiece(AbstractPiece):
        def promote(self):
                raise CannotPromoting, "This piece already promoted"
        def unpromote(self):
                raise CannotUnpromoting, "Not implements yet"

class To(PromotedPiece):
        def __init__(self):
                self.org = Fu()
        def getEffects(self):
                return Ki().getEffects()
        def unpromote(self):
                return Fu()
        def getName(self):
                return "と"

class Ry(PromotedPiece):
        def __init__(self):
                self.org = Hi()
        def getEffects(self):
                l=[(-1,-1),(-1,1),(1,-1),(1,1)]
                return l + self.org.getEffects()
        def unpromote(self):
                return Hi()
        def getName(self):
                return "龍"

class PieceOperationException(Exception):
        pass
class CannotUnpromoting(PieceOperationException):
        pass
class CannotPromoting(PieceOperationException):
        pass

def test():
        lis = [Fu(),Fu(),To(),Ky(),Hi(),Ry()]
        for piece in lis:
                print piece.getName()

        print "\ngetEffects()"
        for piece in lis:
                print piece.getName(),": ",piece.getEffects()

        print "\npromoting"
        for piece in lis:
                try:
                        print piece.promote().getName(),": ",piece.promote().getEffects()
                except PieceOperationException, e:
                        print piece.getName(),": ",e

        print "\nunpromoting"
        for piece in lis:
                try:
                        print piece.unpromote().getName(),": ",piece.unpromote().getEffects()
                except PieceOperationException, e:
                        print piece.getName(),": ",e

if __name__=="__main__":
        test()

これのtest()関数実行結果は以下。

実行結果

歩
歩
と
香
飛
龍

getEffects()
歩 :  [(0, 1)]
歩 :  [(0, 1)]
と :  [(-1, 1), (0, 1), (1, 1), (0, -1), (0, 1), (-1, 0)]
香 :  [(0, 1), (0, 2), (0, 3), (0, 4), (0, 5), (0, 6), (0, 7), (0, 8), (0, 9)]
飛 :  [(0, -3), (0, -2), (0, -1), (0, 1), (0, 2), (0, 3), (-3, 0), (-2, 0), (-1, 0), (1, 0), (2, 0), (3, 0)]
龍 :  [(-1, -1), (-1, 1), (1, -1), (1, 1), (0, -3), (0, -2), (0, -1), (0, 1), (0, 2), (0, 3), (-3, 0), (-2, 0), (-1, 0), (1, 0), (2, 0), (3, 0)]

promoting
と :  [(-1, 1), (0, 1), (1, 1), (0, -1), (0, 1), (-1, 0)]
と :  [(-1, 1), (0, 1), (1, 1), (0, -1), (0, 1), (-1, 0)]
と :  This piece already promoted
香 :  Not implements yet
龍 :  [(-1, -1), (-1, 1), (1, -1), (1, 1), (0, -3), (0, -2), (0, -1), (0, 1), (0, 2), (0, 3), (-3, 0), (-2, 0), (-1, 0), (1, 0), (2, 0), (3, 0)]
龍 :  This piece already promoted

unpromoting
歩 :  Non-promoted piece
歩 :  Non-promoted piece
歩 :  [(0, 1)]
香 :  Non-promoted piece
飛 :  Non-promoted piece
飛 :  [(0, -3), (0, -2), (0, -1), (0, 1), (0, 2), (0, 3), (-3, 0), (-2, 0), (-1, 0), (1, 0), (2, 0), (3, 0)]

仕様の良し悪しは自分ではちょっと評価しづらいです。あまり良いアイデアではない気はしますが。

成りについては表駒8種のみサブクラス化+Stateパターンがありそうな感じがします。パターン名の感じだけで言ってるので実際どうなのかは知りません。やる気があればまた今度実装してみようと思います。

参考書籍

p.s.

Stateの適用条件などを見ましたが、駒の成り/不成について適用すべきではなさそうです。適用可能性をデザパタ本より引用すると、次のいずれかに当てはまる場合にStateを使います。

  • オブジェクトの振る舞いが状態に依存し、実行時にはオブジェクトがその状態により振る舞いを変えなければならない場合。
  • オペレーションが、オブジェクトの状態に依存した多岐にわたる条件文を持っている場合。この状態はたいてい1つ以上の列挙型の定数で表されており、たびたび複数のオペレーションに同じ条件構造が現れる。Stateパターンでは、1つ1つの条件分岐を別々のクラスに受け持たせる。これにより、オブジェクトの各状態を1つのオブジェクトとして扱うことができる。

状態数が多岐に渡るわけではないことや、(例えばgetEffects()の場合)成り/不成りの「状態」がある振る舞いのキーとなるような場面が現状思いつかないことが理由です。

Stateを導入しても良さそうな場面...今思いついたのを挙げるとすれば、足の長い駒の移動先を計算する際に、とかでしょうか。正直、移動可能な座標をただリストで管理するだけでは、下段にある香車とかが盤上でどこまで動けるかを見る時に走査ができず不便です。ある方向に一直線に動く駒については、味方/敵の駒、もしくは盤の端までを走査するfor文的な実装によって対処したい。単純にリストを用意するだけではその辺(移動方向)の秩序がないため、盤上で移動可能なマスを走査するためのコードを書くのはかなり面倒だと思います。