駒の種類を素直にサブクラス化しようとすると成駒をどうするか、という話が出てきます。 その辺の振る舞いをDecoratorを用いて実装します。正直なところ、歩~王までの種類をサブクラス化して「成り」はステート扱いするのが一番楽だとは思います。駒の定義って、八方桂とか鏡角とか、特殊ルールみたいなことでもしない限りは不変ですし。モデルになってる「将棋」部分では仕様が変わることがありえないので、駒でクラスを作る気ならどのようなインタフェースを持たせるかに頭使う方が良い気がします。まぁしかし、「委譲ってスゲー!OOスゲー!」と思う程度には勉強になったなと思ってます。
で、現物は(無駄っぽそうなサブクラス化も多そうですが)以下のようになりました。駒の利きを自分からの相対座標として表現しています。また、「利き」の英語が自信ないんですが、ここでは"effects"としています。
#!/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パターンがありそうな感じがします。パターン名の感じだけで言ってるので実際どうなのかは知りません。やる気があればまた今度実装してみようと思います。
参考書籍
Stateの適用条件などを見ましたが、駒の成り/不成について適用すべきではなさそうです。適用可能性をデザパタ本より引用すると、次のいずれかに当てはまる場合にStateを使います。
状態数が多岐に渡るわけではないことや、(例えばgetEffects()の場合)成り/不成りの「状態」がある振る舞いのキーとなるような場面が現状思いつかないことが理由です。
Stateを導入しても良さそうな場面...今思いついたのを挙げるとすれば、足の長い駒の移動先を計算する際に、とかでしょうか。正直、移動可能な座標をただリストで管理するだけでは、下段にある香車とかが盤上でどこまで動けるかを見る時に走査ができず不便です。ある方向に一直線に動く駒については、味方/敵の駒、もしくは盤の端までを走査するfor文的な実装によって対処したい。単純にリストを用意するだけではその辺(移動方向)の秩序がないため、盤上で移動可能なマスを走査するためのコードを書くのはかなり面倒だと思います。