nounai.output(spaghetiThinking);

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

【DesignPattern】 Decorator実装してみた in 将棋(2) 【Python】

前回の続きもの。同一方向への利きについてはループで走査可能となるようなやり方にしました。


今回の仕様というか概要というか、まぁそのあたりは以下。前回の主な差分にあたる、利き関係のみ。

  • ある駒の利きは、「方向(基底)ベクトル」と、その「長さ」を持つベクトルオブジェクトの集合によって表現される
  • 「ベクトルオブジェクト」はvecクラスで実装。イテレータプロトコルをサポート(__iter__の実装)しているので、任意の1方向について指定の「長さ」までその利きを順に見ていくことが可能。

前回記事からの大きな違いは「利き」の実現方法が違うってことです。今回の方法では利きをベクトルの集合で表現しているので、ある方向に対しての走査が可能になりました。これによってある駒の動けるマスを敵味方の駒や盤の端を考慮して算出しなければならないケースも容易に対応できるようになったのではないかと思います。

あともう1つ、駒の名前をen/ja表記で返すメソッドを追加しています。歩なら"Fu"/"歩"です。まぁこの辺はあって損はないだろ、ってことで追加しました。ただ、__str__()でこの機能を実装して、getName()の方では正式名称の方を返す仕様にしておいても良かったかもしれません。以下ソース。

ソースコード

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

class vec:
        """
        基底ベクトル+長さで移動可能な座標を表現する。
        筋、段の定義において、数の小さい側をマイナス方向とする。
        """
        SHORT=1
        LONG=9
        
        RIGHT=-1
        LEFT=1
        UPPER=-1
        DOWN=1

        def __init__(self,base,length=SHORT):
                if(not type(base)==tuple): raise Exception, "Vector.__init__: type(base)"
                if(not len(base)==2): raise Exception, "Vector: __init__(), len(base)"
                if(base[0]==0 and base[1]==0): raise Exception, "Vector: __init__()"
                self.direction = base
                self.length = length
        def __str__(self, vector=True):
                if(vector):
                        r=self.direction
                        return "("+self.direction.__str__()+", "+(lambda x: "SHORT" if x==vec.SHORT else "LONG")(self.length)+")"
                else:
                        pass
        
        def eqDirection(self, suji, dan):
                if(self.direction[0]==suji and self.direction[1]==dan):
                        return True
                return False
                        
        @classmethod
        def UpperLeft(cls, length=SHORT):
                return vec((vec.LEFT,vec.UPPER),length)
        @classmethod
        def Upper(cls,length=SHORT):
                return vec((0,vec.UPPER),length)
        @classmethod
        def UpperRight(cls,length=SHORT):
                return vec((vec.RIGHT,vec.UPPER),length)
        @classmethod
        def Left(cls, length=SHORT):
                return vec((vec.LEFT,0),length)
        @classmethod
        def Right(cls, length=SHORT):
                return vec((vec.RIGHT,0),length)
        @classmethod
        def DownLeft(cls, length=SHORT):
                return vec((vec.LEFT,vec.DOWN),length)
        @classmethod
        def Down(cls, length=SHORT):
                return vec((0,vec.DOWN), length)
        @classmethod
        def DownRight(cls, length=SHORT):
                return vec((vec.RIGHT,vec.DOWN), length)
        @classmethod
        def Ke_Right(cls, length=SHORT):
                return vec((vec.RIGHT,vec.UPPER*2), vec.SHORT)
        @classmethod
        def Ke_Left(cls, length=SHORT):
                return vec((vec.LEFT, vec.UPPER*2), vec.SHORT)

        def __iter__(self):
                for l in range(1,self.length):
                        yield tuple(map(lambda x: x*l, self.direction))

class Piece:
        def getName(self, en=True):
                return self.__class__.__name__
        def getEffects(self):
                raise MyException, "abstract method"
        def getPromote(self):
                raise MyException, "Not Implements yet"
        def getUnpromote(self):
                raise PromoteException, "cannot unpromote "+self.getName()
        def isPromote(self):
                return False

class Fu(Piece):
        def getName(self,en=True):
                return "Fu" if en else "歩"
        def getEffects(self):
                return [vec.Upper()]
        def getPromote(self):
                return To()

class Ky(Piece):
        def getName(self,en=True):
                return "Ky" if en else "香"
        def getEffects(self):
                return [vec.Upper(length=vec.LONG)]
        def getPromote(self):
                return Ny()

class Ke(Piece):
        def getName(self,en=True):
                return "Ke" if en else "桂"
        def getEffects(self):
                return [vec.Ke_Right(), vec.Ke_Left()]
        def getPromote(self):
                return Nk()

class Gi(Piece):
        def getName(self,en=True):
                return "Gi" if en else "銀"
        def getEffects(self):
                return [vec.UpperRight(),vec.Upper(),vec.UpperLeft(),
                                vec.DownRight(),vec.DownLeft()]
        def getPromote(self):
                return Ng()

class Ki(Piece):
        def getName(self,en=True):
                return "Ki" if en else "金"
        def getEffects(self):
                return [vec.UpperRight(),vec.Upper(),vec.UpperLeft(),
                                vec.Right(),vec.Left(),
                                vec.Down()]
        def getPromote(self):
                raise PromoteException, "Ki cannot promote"

class Ka(Piece):
        def getName(self,en=True):
                return "Ka" if en else "角"
        def getEffects(self):
                return [vec.UpperRight(length=vec.LONG),
                                vec.UpperLeft(length=vec.LONG),
                                vec.DownRight(length=vec.LONG),
                                vec.DownLeft(length=vec.LONG)]
        def getPromote(self):
                return Um()

class Hi(Piece):
        def getName(self,en=True):
                return "Hi" if en else "飛"
        def getEffects(self):
                return [vec.Upper(length=vec.LONG),
                                vec.Left(length=vec.LONG),vec.Right(length=vec.LONG),
                                vec.Down(length=vec.LONG)]
        def getPromote(self):
                return Ry()

class Ou(Piece):
        def getName(self,en=True):
                return "Ou" if en else "玉"
        def getEffects(self):
                return [vec.UpperRight(),vec.Upper(),vec.UpperLeft(),
                                vec.Right(),vec.Left(),
                                vec.DownRight(),vec.Down(),vec.DownLeft()]
        def getPromote(self):
                raise PromoteException, "Ou cannot promote"

class PromotedPiece(Piece):
        def isPromote(self): return True
        def getPromote(self):
                raise PromoteException, "Already promoted"
        def getUnpromote(self):
                raise MyException,"Not implements yet"

class To(PromotedPiece):
        def getName(self,en=True):
                return "To" if en else "と"
        def getEffects(self):
                return Ki().getEffects()
        def getUnpromote(self):
                return Fu()

class Ny(PromotedPiece):
        def getName(self,en=True):
                return "Ny" if en else "杏"
        def getEffects(self):
                return Ki().getEffects()
        def getUnpromote(self):
                return Ky()

class Nk(PromotedPiece):
        def getName(self,en=True):
                return "Nk" if en else "圭"
        def getEffects(self):
                return Ki().getEffects()
        def getUnpromote(self):
                return Ke()

class Ng(PromotedPiece):
        def getName(self,en=True):
                return "Ng" if en else "全"
        def getEffects(self):
                return Ki().getEffects()
        def getUnpromote(self):
                return Gi()

class Um(PromotedPiece):
        def getName(self,en=True):
                return "Um" if en else "馬"
        def __init__(self):
                self.org = Ka()
        def getEffects(self):
                return [vec.Upper(),vec.Down(),vec.Right(),vec.Left()]+self.org.getEffects()
        def getUnpromote(self):
                return self.org

class Ry(PromotedPiece):
        def getName(self,en=True):
                return "Ry" if en else "龍"
        def __init__(self):
                self.org = Hi()
        def getEffects(self):
                return [vec.UpperRight(),vec.UpperLeft(),vec.DownRight(),vec.DownLeft()]+self.org.getEffects()
        def getUnpromote(self):
                return self.org

class MyException(Exception):
        """
        実装してない場合に投げる例外とか割とデバッグ用途のexceptionクラス
        """
        pass
class PromoteException(MyException):
        pass

##############################################
# 衝突判定専用で定義した関数
# OO的にはあんまりよくないので今回の実装のテストコード専用といった位置づけ
def tpladd(t1,t2):
        """
        長さ=2のタプルを要素同士足し算
        """
        return (t1[0]+t2[0], t1[1]+t2[1])
def isOnBoard(tpl):
        """
        (筋, 段)のタプルを引数にとる
        """
        if( (1<=tpl[0] and tpl[0]<=9) and (1<=tpl[1] and tpl[1]<=9) ): return True
        return False
##############################################

def test():
        tests = [Fu(), Ky(), Ke(), Gi(), Ki(), Ka(), Hi(), Ou(),
                         To(), Ny(), Nk(), Ng(), Um(), Ry()]

        print "\n*************************************"
        print "[TEST] getName"
        for p in tests:
                print p.getName() + " : "+p.getName(en=False)
        
        print "\n*************************************"
        print "[TEST] getPromote"
        for p in tests:
                try:
                        print p.getName()+" => "+p.getPromote().getName() + " : "+p.getPromote().getName(en=False)
                except Exception, e:
                        print "**("+p.getName()+")"+e.__str__()

        print "\n*************************************"
        print "[TEST] getUnpromote"
        for p in tests:
                try:
                        print p.getName()+" => "+p.getUnpromote().getName()+" : "+p.getUnpromote().getName(en=False)
                except Exception, e:
                        print "**("+p.getName()+")"+e.__str__()
        
        print "\n*************************************"
        print "[TEST] getEffects"
        print "  ある1方向への利きを\n\t((筋,段), ベクトルの長さ)\nの形式で表示。"
        print "   ※あくまで__str__()で文字列化した時の表現方法。内部表現も限りなく似ているけど。"
        for p in tests:
                try:
                        print "\n"+p.getName(en=False)+":"
                        for pe in p.getEffects():
                                print "\t"+pe.__str__()
                except Exception,e:
                        print "**("+p.getName()+")"+e.__str__()
        
        print "\n*************************************"
        print "[TEST] getPromote->getEffects"
        for p in tests:
                try:
                        print "\n"+p.getPromote().getName(en=False)+":"
                        for pe in p.getPromote().getEffects():
                                print "\t"+pe.__str__()
                except Exception,e:
                        print "**("+p.getName()+")"+e.__str__()

        print "\n*************************************"
        print "[TEST] getUnpromote->getEffects"
        for p in tests:
                try:
                        print "\n"+p.getUnpromote().getName(en=False)+":"
                        for pe in p.getUnpromote().getEffects():
                                print "\t"+pe.__str__()
                except Exception,e:
                        print "**("+p.getName()+")"+e.__str__()

        print "\n*************************************"
        print "[TEST] 龍の上方向への利きをforで走査してみる"
        print "       現在この龍は3四にいるとする。"
        print "       利きを管理するリストから\"上\"を明示して指定する方法がない点は要改善か?"
        pos = (3,4) # 龍が3四にいるとする
        for eff in Ry().getEffects():
                if(eff.eqDirection(0,vec.UPPER)):
                        for ef in eff:
                                """
                                vecをイテレータプロトコルに対応させているため、
                                利きのある1方向に対してはfor文で順番に見ていくことが可能になっている。
                                forで回せるようになったことで、味方or敵の駒とか、盤外との利きの衝突判定を行うのが楽になる。
                                """
                                moved = tpladd(pos, ef)
                                if(not isOnBoard(moved)):
                                        print "*"+ef.__str__()+"  --- out of board"
                                else:
                                        print ef

if(__name__=="__main__"):
        test()

今回実装の小ネタとして挙げたいのは

です。まぁ"python イテレータプロトコル"とか、"python __iter__"でググればわかりやすい解説ページが出ますのでここで特に解説はしないことにします。要は、pythonのfor文はinが使えて何かと便利なのですが、自前で作ったクラスでもそういうforの書き方をしてみたい時にイテレータプロトコルの出番だよ、って話です。

yieldについてはC#でも同様の演算子が存在します。ちょっとわかりにくいですがpythonの方で分からなかったらそっちで調べるのもアリかと。まぁ、yieldを使わずにやる(next()StopIterationで実装する)方法もありますし、yieldよりはそっちのが理解しやすい気はします。

test()の実行結果は以下。

実行結果

*************************************
[TEST] getName
Fu : 歩
Ky : 香
Ke : 桂
Gi : 銀
Ki : 金
Ka : 角
Hi : 飛
Ou : 玉
To : と
Ny : 杏
Nk : 圭
Ng : 全
Um : 馬
Ry : 龍

*************************************
[TEST] getPromote
Fu => To : と
Ky => Ny : 杏
Ke => Nk : 圭
Gi => Ng : 全
**(Ki)Ki cannot promote
Ka => Um : 馬
Hi => Ry : 龍
**(Ou)Ou cannot promote
**(To)Already promoted
**(Ny)Already promoted
**(Nk)Already promoted
**(Ng)Already promoted
**(Um)Already promoted
**(Ry)Already promoted

*************************************
[TEST] getUnpromote
**(Fu)cannot unpromote Fu
**(Ky)cannot unpromote Ky
**(Ke)cannot unpromote Ke
**(Gi)cannot unpromote Gi
**(Ki)cannot unpromote Ki
**(Ka)cannot unpromote Ka
**(Hi)cannot unpromote Hi
**(Ou)cannot unpromote Ou
To => Fu : 歩
Ny => Ky : 香
Nk => Ke : 桂
Ng => Gi : 銀
Um => Ka : 角
Ry => Hi : 飛

*************************************
[TEST] getEffects
  ある1方向への利きを
        ((筋,段), ベクトルの長さ)
の形式で表示。
   ※あくまで__str__()で文字列化した時の表現方法。内部表現も限りなく似ているけど。

歩:
        ((0, -1), SHORT)

香:
        ((0, -1), LONG)

桂:
        ((-1, -2), SHORT)
        ((1, -2), SHORT)

銀:
        ((-1, -1), SHORT)
        ((0, -1), SHORT)
        ((1, -1), SHORT)
        ((-1, 1), SHORT)
        ((1, 1), SHORT)

金:
        ((-1, -1), SHORT)
        ((0, -1), SHORT)
        ((1, -1), SHORT)
        ((-1, 0), SHORT)
        ((1, 0), SHORT)
        ((0, 1), SHORT)

角:
        ((-1, -1), LONG)
        ((1, -1), LONG)
        ((-1, 1), LONG)
        ((1, 1), LONG)

飛:
        ((0, -1), LONG)
        ((1, 0), LONG)
        ((-1, 0), LONG)
        ((0, 1), LONG)

玉:
        ((-1, -1), SHORT)
        ((0, -1), SHORT)
        ((1, -1), SHORT)
        ((-1, 0), SHORT)
        ((1, 0), SHORT)
        ((-1, 1), SHORT)
        ((0, 1), SHORT)
        ((1, 1), SHORT)

と:
        ((-1, -1), SHORT)
        ((0, -1), SHORT)
        ((1, -1), SHORT)
        ((-1, 0), SHORT)
        ((1, 0), SHORT)
        ((0, 1), SHORT)

杏:
        ((-1, -1), SHORT)
        ((0, -1), SHORT)
        ((1, -1), SHORT)
        ((-1, 0), SHORT)
        ((1, 0), SHORT)
        ((0, 1), SHORT)

圭:
        ((-1, -1), SHORT)
        ((0, -1), SHORT)
        ((1, -1), SHORT)
        ((-1, 0), SHORT)
        ((1, 0), SHORT)
        ((0, 1), SHORT)

全:
        ((-1, -1), SHORT)
        ((0, -1), SHORT)
        ((1, -1), SHORT)
        ((-1, 0), SHORT)
        ((1, 0), SHORT)
        ((0, 1), SHORT)

馬:
        ((0, -1), SHORT)
        ((0, 1), SHORT)
        ((-1, 0), SHORT)
        ((1, 0), SHORT)
        ((-1, -1), LONG)
        ((1, -1), LONG)
        ((-1, 1), LONG)
        ((1, 1), LONG)

龍:
        ((-1, -1), SHORT)
        ((1, -1), SHORT)
        ((-1, 1), SHORT)
        ((1, 1), SHORT)
        ((0, -1), LONG)
        ((1, 0), LONG)
        ((-1, 0), LONG)
        ((0, 1), LONG)

*************************************
[TEST] getPromote->getEffects

と:
        ((-1, -1), SHORT)
        ((0, -1), SHORT)
        ((1, -1), SHORT)
        ((-1, 0), SHORT)
        ((1, 0), SHORT)
        ((0, 1), SHORT)

杏:
        ((-1, -1), SHORT)
        ((0, -1), SHORT)
        ((1, -1), SHORT)
        ((-1, 0), SHORT)
        ((1, 0), SHORT)
        ((0, 1), SHORT)

圭:
        ((-1, -1), SHORT)
        ((0, -1), SHORT)
        ((1, -1), SHORT)
        ((-1, 0), SHORT)
        ((1, 0), SHORT)
        ((0, 1), SHORT)

全:
        ((-1, -1), SHORT)
        ((0, -1), SHORT)
        ((1, -1), SHORT)
        ((-1, 0), SHORT)
        ((1, 0), SHORT)
        ((0, 1), SHORT)
**(Ki)Ki cannot promote

馬:
        ((0, -1), SHORT)
        ((0, 1), SHORT)
        ((-1, 0), SHORT)
        ((1, 0), SHORT)
        ((-1, -1), LONG)
        ((1, -1), LONG)
        ((-1, 1), LONG)
        ((1, 1), LONG)

龍:
        ((-1, -1), SHORT)
        ((1, -1), SHORT)
        ((-1, 1), SHORT)
        ((1, 1), SHORT)
        ((0, -1), LONG)
        ((1, 0), LONG)
        ((-1, 0), LONG)
        ((0, 1), LONG)
**(Ou)Ou cannot promote
**(To)Already promoted
**(Ny)Already promoted
**(Nk)Already promoted
**(Ng)Already promoted
**(Um)Already promoted
**(Ry)Already promoted

*************************************
[TEST] getUnpromote->getEffects
**(Fu)cannot unpromote Fu
**(Ky)cannot unpromote Ky
**(Ke)cannot unpromote Ke
**(Gi)cannot unpromote Gi
**(Ki)cannot unpromote Ki
**(Ka)cannot unpromote Ka
**(Hi)cannot unpromote Hi
**(Ou)cannot unpromote Ou

歩:
        ((0, -1), SHORT)

香:
        ((0, -1), LONG)

桂:
        ((-1, -2), SHORT)
        ((1, -2), SHORT)

銀:
        ((-1, -1), SHORT)
        ((0, -1), SHORT)
        ((1, -1), SHORT)
        ((-1, 1), SHORT)
        ((1, 1), SHORT)

角:
        ((-1, -1), LONG)
        ((1, -1), LONG)
        ((-1, 1), LONG)
        ((1, 1), LONG)

飛:
        ((0, -1), LONG)
        ((1, 0), LONG)
        ((-1, 0), LONG)
        ((0, 1), LONG)

*************************************
[TEST] 龍の上方向への利きをforで走査してみる
       現在この龍は3四にいるとする。
       利きを管理するリストから"上"を明示して指定する方法がない点は要改善か?
(0, -1)
(0, -2)
(0, -3)
*(0, -4)  --- out of board
*(0, -5)  --- out of board
*(0, -6)  --- out of board
*(0, -7)  --- out of board
*(0, -8)  --- out of board

まあ概ねうまくいってるのではないかと思います...あまり自信はないですが。何が自信ないかって、テストコードが一番自信ないです。ろくにテストはやったことないし、JUnitとかもほぼ知らないし。ちゃんと網羅できてるのか、冗長な部分はないか、とかが気がかりです。pythonにもJUnitと似た使い勝手のテストフレームワークがあった気がしますが今回それは使ってません。そろそろVimから本格的にEclipseにシフトした方がいいかなあ?