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

RangeTable: 範囲指定による表の定義 #132

Merged
merged 12 commits into from
Mar 15, 2020
Merged

RangeTable: 範囲指定による表の定義 #132

merged 12 commits into from
Mar 15, 2020

Conversation

ochaochaocha3
Copy link
Member

@ochaochaocha3 ochaochaocha3 commented Mar 11, 2020

このPRは、表を少ない量のコードで記述できるクラス RangeTable を導入します。このクラスは、現在 DiceBot#get_table_by_number に実装されている、短く書かれた表から項目を取り出す処理を、クラスとして整備し直したものです。また、再整備にあたり、汎用性を高めました。

背景・目的

「疎らな表」の定義方法

表のクラスである TableD66GridTable がカバーしていない表の定義方法として、DiceBot#get_table_by_number で項目を取り出せる表の定義方法(以下では、これを「『疎らな表』方式」と呼びます)があります。この方法では、各項目を

[出目の合計の最大値, 内容]

という形で記述します。そのため、重複項目を1つにまとめて記述でき、結果として表の定義が短く書けます。これは、ダイスボットのコードを読みやすくするのに便利です。

「疎らな表」方式の改善:範囲指定による表の定義

ただし、「疎らな表」方式には、出目の合計と対応する項目が分かりにくいという欠点があります。改善案として、以下のように、各項目と対応する出目の合計の範囲を指定する方法が考えられます。

[s1..s2, 内容] (s1 <= s2)

以下では、この方法を「範囲指定による表の定義」と呼びます。

表の拡張

以下は「疎らな表」方式とは独立した話題です。

表に文字列以外の内容を表に格納したり、ダイスロール結果の情報を取得したりできると、表がより使いやすくなります。例えば、バトルテックのダイスボットでは、ダメージ(整数値)を表に格納して、ダイスロールによって取り出すことが行われています。表を振った際のダイスロール結果の情報を取得できれば、メタルヘッドのダイスボットで見られるような独自の結果整形処理も、表のクラスを用いて簡潔に書ける可能性があります。

目的

以上を踏まえて、このPRでは、以下の2つを目的とします。

  1. 範囲指定による表の定義を、クラス RangeTable に実装すること。
  2. 表のクラスを「ダイスロールによって文章を含む様々なオブジェクトを取り出せる汎用コンテナ」として整備すること。ただし、今回は新しく作る RangeTable のみを対象として、今後、他の表のクラスでも便利に使えそうか検討するための題材とします。

RangeTable クラスの使い方

表を定義する

RangeTable.new(name, dice_roll_method, items)

引数名 説明
name String 表の名前
dice_roll_method String ダイスロール方法。"2D6" など
items Array<(Range, Object)>, Array<(Integer, Object)> 表の項目の配列

各項目は [出目の合計の範囲, 内容] という形で記述します。「出目の合計の範囲」には、整数の Range または整数を指定できます。

例:獣ノ森の「ランダム天気持続表」

rwdt = RangeTable.new(
  'ランダム天気持続表',
  '1D12',
  [
    [1..2,   '1ターン'],
    [3..4,   '3ターン'],
    [5..6,   '6ターン'],
    [7..8,   '24ターン'],
    [9..10,  '72ターン'],
    [11..12, '156ターン'],
  ]
)

各項目の「内容」は、通常は文字列ですが、文字列に限らずあらゆるオブジェクトを格納できます。

例:バトルテックの命中部位表

# 命中部位を表す構造体
# [+name+]                   部位名
# [+critical_hit_may_occur+] 致命的命中が発生し得るか
HitPart = Struct.new(:name, :critical_hit_may_occur)

hit_part_table_center = RangeTable.new(
  '命中部位表(正面)',
  '2D6',
  [
    [2,      HitPart.new('胴中央', true)],
    [3..4,   HitPart.new('右腕', false)],
    [5,      HitPart.new('右脚', false)],
    [6,      HitPart.new('右胴', false)],
    [7,      HitPart.new('胴中央', false)],
    [8,      HitPart.new('左胴', false)],
    [9,      HitPart.new('左脚', false)],
    [10..11, HitPart.new('左腕', false)],
    [12,     HitPart.new('頭', false)],
  ]
)

表の定義時に出目の合計の範囲に誤りがあった(カバーしていない範囲があった、重なっていた)場合は、例外が発生します。

表を振る

RangeTable#roll(bcdice)

引数名 説明
bcdice BCDice BCDice本体

呼び出しは Table クラスなどと同じインターフェースです。結果は RangeTable::RollResult クラスのオブジェクトとして返ります。結果のオブジェクトには、以下の情報が格納されます。

メンバ名 説明
sum Integer 出目の合計値
values Array<Integer> 出目の配列
content Object 選ばれた項目の内容
formatted String 整形された結果。to_s でも呼び出せる。

例:バトルテックの「致命的命中表」

ct = RangeTable.new(
  '致命的命中表',
  '2D6',
  [
    [2..7,   '致命的命中はなかった'],
    [8..9,   '1箇所の致命的命中'],
    [10..11, '2箇所の致命的命中'],
    [12,     'その部位が吹き飛ぶ(腕、脚、頭)または3箇所の致命的命中(胴)'],
  ]
)

result = ct.roll(bcdice)

result.formatted
# 出目の合計が 7 => "致命的命中表(7) > 致命的命中はなかった"
# 出目の合計が 8 => "致命的命中表(8) > 1箇所の致命的命中"
# 出目の合計が 9 => "致命的命中表(9) > 1箇所の致命的命中"
# 出目の合計が10 => "致命的命中表(10) > 2箇所の致命的命中"
#
# result.to_s でも呼び出せる

# 以下、出目が [5, 3] だった場合

result.table.name
#=> "致命的命中表"
result.sum
#=> 8
result.values
#=> [5, 3]
result.content
#=> "1箇所の致命的命中"

# 文字列の式展開では to_s が呼び出される
"致命的命中の判定結果: #{result}"
#=> "致命的命中の判定結果: 致命的命中表(8) > 1箇所の致命的命中"

独自の結果整形処理を指定する

RangeTable.new で表を定義するときにブロックを指定すると、独自の結果整形処理を指定できます。ブロックは以下の引数を受け取ります。

引数名 説明
table RangeTable 振った表
result RangeTable::RollResult 表を振った結果

例:メタルヘッドの「アクシデントチャート(射撃・投擲)」

acl_table = RangeTable.new(
  'アクシデントチャート(射撃・投擲)',
  '1D10',
  [
    [1..7, 'ささいなミス。特にペナルティーはない。'],
    [8,    '不発、またはジャム。弾を取り出さねばならない物は次のターンは射撃できない。'],
    [9,    'ささいな故障。可能なら次のターンから個別武器のスキルロールで修理を行える。'],
    [10,   '武器の暴発、または爆発。頭部HWの心理効果ロール。さらに、その武器は破壊されPERとDEXのどちらか、または両方に計2ポイントのマイナスを与える。(遠隔操作の場合、射手への被害は無し)'],
  ]
) do |table, result|
  [table.name, result.sum, result.content].join(' > ')
end

acl_table.roll(bcdice).format
#=> "アクシデントチャート(射撃・投擲) > 6 > ささいなミス。特にペナルティーはない。"

ダイスボットへの RangeTable の導入について

3つのダイスボット(獣ノ森、バトルテック、メタルヘッド)に、今回作った RangeTable を導入してみました。それぞれ、以下のことに重点を置いています。

  • 獣ノ森:表の定義を短くすること。
  • バトルテック:表に文字列以外の項目を格納して、活用すること。
  • メタルヘッド:独自の結果整形処理。

各項目について、Rangeを用いて出目の合計の範囲を指定する、表のクラス。
表のロール結果の新しいインターフェースに対応させる
表の定義において重複した項目が多かったため、RangeTableを使って短く書く
テーブルのリファクタリングの準備段階
表を振った結果の整形処理が通常と異なるため、独自のformatterを使う
目的:命中部位を求める際の複雑な文字列処理をなくすこと。

* 命中部位表をRangeTableで書く。
* 命中部位表の項目を構造体で書く。
    * 致命的命中が発生し得るかを、文字列ではなくBooleanで表す。
@ochaochaocha3 ochaochaocha3 changed the title RangeTable: RangeTable: 範囲指定による表の定義 Mar 11, 2020
@ochaochaocha3 ochaochaocha3 added the refactoring 内部構造の改良 label Mar 11, 2020
@ochaochaocha3 ochaochaocha3 self-assigned this Mar 11, 2020
Copy link
Member

@ysakasin ysakasin left a comment

Choose a reason for hiding this comment

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

RollResult がクラスになっていて、こいつが表からの抽出作業をしている理由がわからないのですが、こうしないと面倒な記述を迫られるのでしょうか? コードを見た限りだと、RollResultはただのstructでよく、 table, formatter はいらないように思えます、RangeTable#roll内で値は決定できるのではないでしょうか?

@ochaochaocha3
Copy link
Member Author

コードを見た限りだと、RollResultはただのstructでよく、 table, formatter はいらないように思えます、RangeTable#roll内で値は決定できるのではないでしょうか?

少し変えたら RangeTable#roll ですべて決定できました。整形した結果(formatted に入る文字列)を RollResult のコンストラクタでどう代入すればいいかで悩んでいたのですが(formatterRollResult を受け取るため)、単純に後から代入すればいいだけでした。

Copy link
Member

@ysakasin ysakasin left a comment

Choose a reason for hiding this comment

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

LGTM!

@ysakasin ysakasin merged commit 2d71424 into master Mar 15, 2020
@ysakasin ysakasin deleted the range_table branch March 15, 2020 09:44
@ochaochaocha3
Copy link
Member Author

マージありがとうございました。続いて、ロール結果について、通常の Table などにも同様の構造体を作り、結果を利用しやすくしてみたいと思います。

@ysakasin
Copy link
Member

@ochaochaocha3 マージしてから、#rollは文字列だけを返して、今回のように構造体で返すメソッドは別にするという手もあったなと考え付いたのですが、この案についてはどう思いますか?

Tableで結果文字列以外の詳細な値を参照するケースは少数派だと思うので、 Table#roll の挙動は従来と変えずにいても十分使えるのかなと思っています。

@ochaochaocha3
Copy link
Member Author

@ysakasin インターフェースを統一するために、すべてのテーブルクラスが #roll で文字列を返すようにするのには賛成です。

また、他のテーブルでも、今回のRangeTableのような詳細版ロールが必要な場合(標準と違う形式で文字列を出力する場合など)が散見されるので、メソッド名を変えたうえで詳細版を用意したいと考えています。名前は…長いですが、#roll_in_detail あたりでしょうか?

ysakasin added a commit that referenced this pull request Aug 30, 2020
RangeTable: 範囲指定による表の定義
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
refactoring 内部構造の改良
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants