Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AddDice::Node: 二項演算子をリファクタリングする #162

Merged
merged 4 commits into from
Apr 26, 2020
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
234 changes: 206 additions & 28 deletions src/dice/add_dice/node.rb
Original file line number Diff line number Diff line change
@@ -1,16 +1,36 @@
# -*- coding: utf-8 -*-
# frozen_string_literal: true

class AddDice
# 加算ロールの構文解析木のノードを格納するモジュール
module Node
# 加算ロールコマンドのノード。
#
# 目標値が設定されていない場合は +lhs+ のみを使用する。
# 目標値が設定されている場合は、+lhs+、+cmp_op+、+rhs+ を使用する。
class Command
attr_reader :lhs, :cmp_op, :rhs

# 左辺のノード
# @return [Object]
attr_reader :lhs
# 比較演算子
# @return [Symbol]
attr_reader :cmp_op
# 右辺のノード
# @return [Integer, String]
attr_reader :rhs

# ノードを初期化する
# @param [Object] lhs 左辺のノード
# @param [Symbol] cmp_op 比較演算子
# @param [Integer, String] rhs 右辺のノード
def initialize(lhs, cmp_op, rhs)
@lhs = lhs
@cmp_op = cmp_op
@rhs = rhs
end

# 文字列に変換する
# @return [String]
def to_s
@lhs.to_s + cmp_op_text + @rhs.to_s
end
Expand All @@ -27,6 +47,8 @@ def s_exp

private

# メッセージ中で比較演算子をどのように表示するかを返す
# @return [String]
def cmp_op_text
case @cmp_op
when :'!='
Expand All @@ -39,83 +61,210 @@ def cmp_op_text
end
end

# 二項演算子のノード
class BinaryOp
def initialize(lhs, op, rhs, round_type = nil)
# ノードを初期化する
# @param [Object] lhs 左のオペランドのノード
# @param [Symbol] op 演算子
# @param [Object] rhs 右のオペランドのノード
def initialize(lhs, op, rhs)
@lhs = lhs
@op = op
@rhs = rhs
@round_type = round_type
end

# ノードを評価する
#
# 左右のオペランドをそれぞれ再帰的に評価した後で、演算を行う。
#
# @param [Randomizer] randomizer ランダマイザ
# @return [Integer] 評価結果
def eval(randomizer)
lhs = @lhs.eval(randomizer)
rhs = @rhs.eval(randomizer)

calc(lhs, rhs)
return calc(lhs, rhs)
end

# 文字列に変換する
# @return [String]
def to_s
@lhs.to_s + @op.to_s + @rhs.to_s + round_type_suffix()
"#{@lhs}#{@op}#{@rhs}"
end

# メッセージへの出力を返す
# @return [String]
def output
@lhs.output + @op.to_s + @rhs.output + round_type_suffix()
"#{@lhs.output}#{@op}#{@rhs.output}"
end

# ノードのS式を返す
# @return [String]
def s_exp
"(#{@op}#{round_type_suffix} #{@lhs.s_exp} #{@rhs.s_exp})"
"(#{op_for_s_exp} #{@lhs.s_exp} #{@rhs.s_exp})"
end

private

# 演算を行う
# @param [Integer] lhs 左のオペランド
# @param [Integer] rhs 右のオペランド
# @return [Integer] 演算の結果
def calc(lhs, rhs)
if @op != :/
return lhs.send(@op, rhs)
end
lhs.send(@op, rhs)
end

# S式で使う演算子の表現を返す
# @return [String]
def op_for_s_exp
@op
end
end

# 除算ノードの基底クラス
#
# 定数 +ROUNDING_METHOD_SYMBOL+ で端数処理方法を示す記号
# ( +'U'+, +'R'+, +''+ ) を定義すること。
# また、除算および端数処理を行う +divide_and_round+ メソッドを実装すること。
class DivideBase < BinaryOp
# ノードを初期化する
# @param [Object] lhs 左のオペランドのノード
# @param [Object] rhs 右のオペランドのノード
def initialize(lhs, rhs)
super(lhs, :/, rhs)
end

# 文字列に変換する
#
# 通常の結果の末尾に、端数処理方法を示す記号を付加する。
#
# @return [String]
def to_s
"#{super}#{rounding_method_symbol}"
end

# メッセージへの出力を返す
#
# 通常の結果の末尾に、端数処理方法を示す記号を付加する。
#
# @return [String]
def output
"#{super}#{rounding_method_symbol}"
end

private

# 端数処理方法を示す記号を返す
# @return [String]
def rounding_method_symbol
self.class::ROUNDING_METHOD_SYMBOL
end

# S式で使う演算子の表現を返す
# @return [String]
def op_for_s_exp
"#{@op}#{rounding_method_symbol}"
end

# 演算を行う
# @param [Integer] lhs 左のオペランド
# @param [Integer] rhs 右のオペランド
# @return [Integer] 演算の結果
def calc(lhs, rhs)
if rhs.zero?
return 1
end

case @round_type
when :roundUp
(lhs.to_f / rhs).ceil
when :roundOff
(lhs.to_f / rhs).round
else
lhs / rhs
end
return divide_and_round(lhs, rhs)
end

def round_type_suffix
case @round_type
when :roundUp
"U"
when :roundOff
"R"
else
""
end
# 除算および端数処理を行う
# @param [Integer] _dividend 被除数
# @param [Integer] _divisor 除数(0以外)
# @return [Integer]
def divide_and_round(_dividend, _divisor)
raise NotImplementedError
end
end

# 除算(切り上げ)のノード
class DivideWithRoundingUp < DivideBase
# 端数処理方法を示す記号
ROUNDING_METHOD_SYMBOL = 'U'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SYMBOL だとどうしても Symbol が入っているのを連想してしまうので、何かしら名前を変更した方が良いと思います。

  • ROUNDING_METHOD
  • ROUNDING_METHOD_PREFIX
  • ROUNDING_METHOD_SIGN
  • ROUNDING_METHOD_STR
  • ROUNDING_METHOD_TYPE
  • ROUNDING_TYPE

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

どうせ外部から使わないし、他に良い名前がないならこのままでもいいです。

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ありがとうございます。それでは ROUNDING_METHOD にしてみます。


private

# 除算および端数処理を行う
# @param [Integer] dividend 被除数
# @param [Integer] divisor 除数(0以外)
# @return [Integer]
def divide_and_round(dividend, divisor)
(dividend.to_f / divisor).ceil
end
end

# 除算(四捨五入)のノード
class DivideWithRoundingOff < DivideBase
# 端数処理方法を示す記号
ROUNDING_METHOD_SYMBOL = 'R'

private

# 除算および端数処理を行う
# @param [Integer] dividend 被除数
# @param [Integer] divisor 除数(0以外)
# @return [Integer]
def divide_and_round(dividend, divisor)
(dividend.to_f / divisor).round
end
end

# 除算(切り捨て)のノード
class DivideWithRoundingDown < DivideBase
# 端数処理方法を示す記号
ROUNDING_METHOD_SYMBOL = ''

private

# 除算および端数処理を行う
# @param [Integer] dividend 被除数
# @param [Integer] divisor 除数(0以外)
# @return [Integer]
def divide_and_round(dividend, divisor)
dividend / divisor
end
end

# 符号反転のノード
class Negate
# 符号反転の対象
# @return [Object]
attr_reader :body

# ノードを初期化する
# @param [Object] body 符号反転の対象
def initialize(body)
@body = body
end

# ノードを評価する
#
# 対象オペランドを再帰的に評価した後、評価結果の符号を反転する。
#
# @param [Randomizer] randomizer ランダマイザ
# @return [Integer] 評価結果
def eval(randomizer)
[email protected](randomizer)
end

# 文字列に変換する
# @return [String]
def to_s
"-#{@body}"
end

# メッセージへの出力を返す
# @return [String]
def output
"-#{@body.output}"
end
Expand All @@ -127,20 +276,36 @@ def s_exp
end
end

# ダイスロールのノード
class DiceRoll
# ノードを初期化する
# @param [Number] times ダイスを振る回数のノード
# @param [Number] sides ダイスの面数のノード
# @param [Number, nil] critical クリティカル値のノード
def initialize(times, sides, critical)
@times = times.literal
@sides = sides.literal
@critical = critical.nil? ? nil : critical.literal

# ダイスを振った結果の出力
@text = nil
end

# ノードを評価する(ダイスを振る)
#
# 評価結果は出目の合計値になる。
# 出目はランダマイザに記録される。
#
# @param [Randomizer] randomizer ランダマイザ
# @return [Integer] 評価結果(出目の合計値)
def eval(randomizer)
total, @text = randomizer.roll(@times, @sides, @critical)

total
end

# 文字列に変換する
# @return [String]
def to_s
if @critical
"#{@times}D#{@sides}@#{@critical}"
Expand All @@ -149,6 +314,8 @@ def to_s
end
end

# メッセージへの出力を返す
# @return [String]
def output
@text
end
Expand All @@ -162,21 +329,32 @@ def s_exp
end
end

# 数値のノード
class Number
# 値
# @return [Integer]
attr_reader :literal

# ノードを初期化する
# @param [Integer] literal 値
def initialize(literal)
@literal = literal
end

# 符号を反転した結果の数値ノードを返す
# @return [Number]
def negate
Number.new(-@literal)
end

# ノードを評価する
# @return [Integer] 格納している値
def eval(_randomizer)
@literal
end

# 文字列に変換する
# @return [String]
def to_s
@literal.to_s
end
Expand Down
Loading