さり海馬

Thoughts walk away, blog stays.

ダイス式を計算するプログラムを作ってみる(2)


photo by centralasian CC BY 2.1
前回からの続きです。

前回はダイスコードの構文を決め、(E)BNFでそれを書き下しました。そして、いろいろと置き換えて解析しやすい形にしました。

今回は、いよいよこいつをpythonのコードに落とします。…っていってもその過程をいろいろと説明するのはめんどうくさいので、一気にコードを提示しちゃいましょう。こんな感じです。

#!/usr/bin/env python
from random import choice

class DiceCodeParser(object):
    """
        Parses a dice code and get the value.
    """
    def __init__( self, code=''):
        self.code=code
        self.dice_results = []
        self.index_saved=0

    def parse( self, code=''):
        self.__init__( code )
        if self.code=='':
            raise ValueError
        # remove all spaces.
        # append one space to prevent 'subscript out of range' error
        self.code= self.code.replace(' ','') + ' '
        result = self._equation()
        return result[0]

    def show_info( self ):
        print "Code: %s" % self.code
        print "Rolled:", self.dice_results
        print "-------------------"

    def get_rolled( self ):
        return self.dice_results

    def get_code( self ):
        return self.code

    def _equation( self, index=0 ):
        self.index_saved = index
        [value, index] = self._factor( index )
        while(True):
            if self.code[index]=='+':
                [v,index] = self._factor( index+1 )
                value += v
            elif self.code[index]=='-':
                [v,index] = self._factor( index+1 )
                value -= v
            else:
                break
        # raise exception if none of tokens in a equation ware accepted
        if self.index_saved==index:
            raise ValueError
        return [value,index]

    def _factor( self, index=0 ):
        [value, index] = self._dterm( index )
        while(True):
            if self.code[index]=='*':
                [v,index] = self._dterm( index+1 )
                value = value*v
            elif self.code[index]=='/':
                [v,index] = self._dterm( index+1 )
                value = value/v
            else:
                break
        return [value, index]

    def _dterm(self, index=0):
        [value, index] = self._term( index )
        while(True):
            if self.code[index]=='d' or self.code[index]=='D':
                # get number of rolls
                n_of_r = value
                # get number of sides
                [n_of_s, index] = self._term( index+1 )
                # make a list of sides
                sides = [i+1 for i in range(n_of_s)]
                # make a list of rolled values
                rolled = [choice(sides) for i in range(n_of_r)]
                # add them altogether to get value of these rolls
                value = sum(rolled)
                self.dice_results.append(rolled)
            else:
                break
        return [value, index]

    def _term( self, index=0):
        if self.code[index] == '(':
            [value, index] = self._equation( index+1 )
            index +=1 # skipping the ')'
            return [value, index]
        else:
            value = 0
            while(self.code[index].isdigit()):
                value = value*10 + int(self.code[index])
                index +=1
            return [value, index]

def main():
    dp = DiceCodeParser()
    print dp.parse("(1 +2+3)*(4+ 5+6)")
    dp.show_info()
    print dp.parse("5d6")
    dp.show_info()
    print dp.parse("3d(6+2)")
    dp.show_info()
    print dp.parse("2D10+3D6")
    dp.show_info()
    print dp.parse("2d4d6")
    dp.show_info()
    print dp.parse("01")
    dp.show_info()
##    print dp.parse("wrong123code")
##    print dp.get_rolled()

if __name__ == '__main__':
    main()

んで、こいつを実行するとこんな感じになります。

>>> 
90
Code: (1+2+3)*(4+5+6) 
Rolled: []
-------------------
22
Code: 5d6 
Rolled: [[6, 6, 3, 2, 5]]
-------------------
14
Code: 3d(6+2) 
Rolled: [[3, 7, 4]]
-------------------
20
Code: 2D10+3D6 
Rolled: [[9, 2], [1, 5, 3]]
-------------------
35
Code: 2d4d6 
Rolled: [[3, 4], [6, 6, 6, 1, 6, 5, 5]]
-------------------
1
Code: 01 
Rolled: []
-------------------
>>> 

…あ、最後のところ、'01'を受容しちゃってますね。BNFで書いた定義とちょっと違っちゃってるな。まぁ実害はないからいいか…。

もしダイスの種類を増やしたり、振り方を変更するとかいう場合は、_dterm()の処理部分にちょっと追加か変更すればいいでしょう。たとえば:

  • 一番大きい目が出たダイスは振り足すとか、
  • 何個か振って出た目の中から大きい順に3つ選ぶとか

そういうのができますよね。