ダイス式を計算するプログラムを作ってみる(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つ選ぶとか
そういうのができますよね。