bcdice 3.12.0 → 3.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +27 -0
  3. data/lib/bcdice/arithmetic/parser.rb +4 -2
  4. data/lib/bcdice/base.rb +1 -1
  5. data/lib/bcdice/command/parser.rb +4 -2
  6. data/lib/bcdice/common_command/add_dice/parser.rb +4 -2
  7. data/lib/bcdice/common_command/barabara_dice/parser.rb +4 -2
  8. data/lib/bcdice/common_command/calc/parser.rb +4 -2
  9. data/lib/bcdice/common_command/reroll_dice/parser.rb +4 -2
  10. data/lib/bcdice/common_command/tally_dice/parser.rb +4 -2
  11. data/lib/bcdice/common_command/upper_dice/parser.rb +4 -2
  12. data/lib/bcdice/game_system/Airgetlamh.rb +1 -1
  13. data/lib/bcdice/game_system/Alsetto.rb +2 -2
  14. data/lib/bcdice/game_system/AniMalus.rb +221 -0
  15. data/lib/bcdice/game_system/ArknightsFan.rb +182 -0
  16. data/lib/bcdice/game_system/Avandner.rb +1 -1
  17. data/lib/bcdice/game_system/BBN.rb +1 -1
  18. data/lib/bcdice/game_system/BadLife.rb +3 -3
  19. data/lib/bcdice/game_system/BarnaKronika.rb +1 -1
  20. data/lib/bcdice/game_system/Chill3.rb +6 -6
  21. data/lib/bcdice/game_system/DarkSouls.rb +1 -1
  22. data/lib/bcdice/game_system/Elric.rb +1 -1
  23. data/lib/bcdice/game_system/FullFace.rb +92 -0
  24. data/lib/bcdice/game_system/InfiniteFantasia.rb +4 -1
  25. data/lib/bcdice/game_system/Irisbane.rb +1 -1
  26. data/lib/bcdice/game_system/KillDeathBusiness.rb +1 -1
  27. data/lib/bcdice/game_system/KyokoShinshoku.rb +158 -0
  28. data/lib/bcdice/game_system/Liminal.rb +136 -0
  29. data/lib/bcdice/game_system/MetalHead.rb +1 -1
  30. data/lib/bcdice/game_system/MetalHeadExtream.rb +1 -1
  31. data/lib/bcdice/game_system/NRR.rb +122 -0
  32. data/lib/bcdice/game_system/NSSQ.rb +1 -1
  33. data/lib/bcdice/game_system/NightWizard.rb +1 -1
  34. data/lib/bcdice/game_system/NightWizard3rd.rb +15 -2
  35. data/lib/bcdice/game_system/OneWayHeroics.rb +1 -1
  36. data/lib/bcdice/game_system/PastFutureParadox.rb +1238 -0
  37. data/lib/bcdice/game_system/Postman.rb +3 -3
  38. data/lib/bcdice/game_system/RecordOfSteam.rb +1 -1
  39. data/lib/bcdice/game_system/RuneQuestRoleplayingInGlorantha.rb +1 -1
  40. data/lib/bcdice/game_system/Ryutama.rb +4 -4
  41. data/lib/bcdice/game_system/Siren.rb +97 -0
  42. data/lib/bcdice/game_system/Skynauts.rb +1 -1
  43. data/lib/bcdice/game_system/Strave.rb +1 -1
  44. data/lib/bcdice/game_system/TherapieSein.rb +1 -1
  45. data/lib/bcdice/game_system/TrinitySeven.rb +1 -1
  46. data/lib/bcdice/game_system/VampireTheMasquerade5th.rb +12 -6
  47. data/lib/bcdice/game_system/WerewolfTheApocalypse5th.rb +12 -6
  48. data/lib/bcdice/game_system/cthulhu7th/full_auto.rb +1 -1
  49. data/lib/bcdice/game_system/sword_world/rating_parser.rb +4 -2
  50. data/lib/bcdice/game_system.rb +8 -0
  51. data/lib/bcdice/loader.rb +1 -1
  52. data/lib/bcdice/version.rb +1 -1
  53. metadata +27 -5
@@ -0,0 +1,182 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BCDice
4
+ module GameSystem
5
+ class ArknightsFan < Base
6
+ # ゲームシステムの識別子
7
+ ID = "ArknightsFan"
8
+
9
+ # ゲームシステム名
10
+ NAME = "アークナイツTRPG by Dapto"
11
+
12
+ # ゲームシステム名の読みがな
13
+ SORT_KEY = "ああくないつTRPGはいたふと"
14
+
15
+ HELP_MESSAGE = <<~TEXT
16
+ ■ 判定 (nADm>=x)
17
+ nDmのダイスロールをして、x 以下であれば成功。
18
+ 出目が91以上でエラー。
19
+ 出目が10以下でクリティカル。
20
+
21
+ ■ 判定 (nABm>=x)
22
+ nBmのダイスロールをして、
23
+ x 以下であれば成功数+1。
24
+ 出目が91以上でエラー。成功数+1。
25
+ 出目が10以下でクリティカル。成功数-1。
26
+ 上記による成功数をカウント。
27
+
28
+ ■ 判定 (nABm>=x--役職)
29
+ nBmのダイスロールをして、
30
+ 出目が x 以下であれば成功数+1。
31
+ 出目が91以上でエラー。成功数+1。
32
+ 出目が10以下でクリティカル。成功数-1。
33
+ 上記による成功数をカウントした上で、以下の役職による成功数増加効果を適応。
34
+ 狙撃(SNIPER) 成功数1以上のとき、成功数+1。
35
+ TEXT
36
+
37
+ register_prefix('\d*AD\d*', '\d*AB\d*', '--ADDICTION', '--WORSENING')
38
+
39
+ def eval_game_system_specific_command(command)
40
+ roll_ad(command) || roll_ab(command) || roll_addiction(command) || roll_worsening(command)
41
+ end
42
+
43
+ private
44
+
45
+ def roll_ad(command)
46
+ m = /^(\d*)AD(\d*)<=(\d+)$/.match(command)
47
+ return nil unless m
48
+
49
+ times = m[1]
50
+ sides = m[2]
51
+ target = m[3].to_i
52
+ times = !times.empty? ? times.to_i : 1
53
+ sides = !sides.empty? ? sides.to_i : 100
54
+ return roll_d(command, times, sides, target)
55
+ end
56
+
57
+ def roll_ab(command)
58
+ m = /^(\d*)AB(\d*)<=(\d+)(?:--([^\d\s]+)(0)?)?$/.match(command)
59
+ return nil unless m
60
+
61
+ times = m[1]
62
+ sides = m[2]
63
+ target = m[3].to_i
64
+ type = m[4]
65
+ suffix = m[5]
66
+ times = !times.empty? ? times.to_i : 1
67
+ sides = !sides.empty? ? sides.to_i : 100
68
+
69
+ if suffix || type.nil?
70
+ roll_b(command, times, sides, target)
71
+ else
72
+ roll_b_withtype(command, times, sides, target, type)
73
+ end
74
+ end
75
+
76
+ def roll_d(command, times, sides, target)
77
+ dice_list = @randomizer.roll_barabara(times, sides).sort
78
+ total = dice_list.sum
79
+ success = total <= target
80
+
81
+ crierror =
82
+ if total <= 10
83
+ "Critical"
84
+ elsif total >= 91
85
+ "Error"
86
+ else
87
+ "Neutral"
88
+ end
89
+
90
+ result =
91
+ if success && (crierror == "Critical")
92
+ "クリティカル!"
93
+ elsif success && (crierror == "Neutral")
94
+ "成功"
95
+ elsif success && (crierror == "Error")
96
+ "成功"
97
+ elsif !success && (crierror == "Critical")
98
+ "失敗"
99
+ elsif !success && (crierror == "Neutral")
100
+ "失敗"
101
+ elsif !success && (crierror == "Error")
102
+ "エラー"
103
+ end
104
+
105
+ if times == 1
106
+ return "(#{command}) > #{dice_list.join(',')} > #{result}"
107
+ else
108
+ return "(#{command}) > #{total}[#{dice_list.join(',')}] > #{result}"
109
+ end
110
+ end
111
+
112
+ def roll_b(command, times, sides, target)
113
+ dice_list, success_count, critical_count, error_count = process_b(times, sides, target)
114
+ result_count = success_count + critical_count - error_count
115
+
116
+ return "(#{command}) > [#{dice_list.join(',')}] > #{success_count}+#{critical_count}C-#{error_count}E > 成功数#{result_count}"
117
+ end
118
+
119
+ def roll_b_withtype(command, times, sides, target, type)
120
+ dice_list, success_count, critical_count, error_count = process_b(times, sides, target)
121
+ result_count = success_count + critical_count - error_count
122
+
123
+ type_effect =
124
+ if (type == "SNIPER") && (result_count > 0)
125
+ 1
126
+ else
127
+ 0
128
+ end
129
+ result_count += type_effect
130
+
131
+ return "(#{command}) > [#{dice_list.join(',')}] > #{success_count}+#{critical_count}C-#{error_count}E+#{type_effect}(#{type}) > 成功数#{result_count}"
132
+ end
133
+
134
+ def process_b(times, sides, target)
135
+ dice_list = @randomizer.roll_barabara(times, sides).sort
136
+
137
+ success_count = 0
138
+ critical_count = 0
139
+ error_count = 0
140
+
141
+ dice_list.each do |value|
142
+ success_count += 1 if value <= target
143
+ critical_count += 1 if value <= 10
144
+ error_count += 1 if value >= 91
145
+ end
146
+
147
+ return [dice_list, success_count, critical_count, error_count]
148
+ end
149
+
150
+ ADDICTION_TABLE = [
151
+ "中枢神経障害",
152
+ "多臓器不全",
153
+ "急性ストレス反応",
154
+ ].freeze
155
+
156
+ def roll_addiction(command)
157
+ return nil if command != "--ADDICTION"
158
+
159
+ value = @randomizer.roll_once(3)
160
+ chosen = ADDICTION_TABLE[value - 1]
161
+
162
+ return "--ADDICTION > #{chosen}"
163
+ end
164
+
165
+ WORSENING_TABLE = [
166
+ "末梢神経障害",
167
+ "内臓機能不全",
168
+ "精神症状",
169
+ ].freeze
170
+
171
+ def roll_worsening(command)
172
+ return nil if command != "--WORSENING"
173
+
174
+ value = @randomizer.roll_once(3)
175
+ chosen = WORSENING_TABLE[value - 1]
176
+ elapse = @randomizer.roll_once(6) + 1
177
+
178
+ return "--WORSENING > #{chosen}: #{elapse} rounds"
179
+ end
180
+ end
181
+ end
182
+ end
@@ -85,7 +85,7 @@ module BCDice
85
85
  totalDamage = totalSuccessCount * damage + totalCriticalCount * criticalTrigger
86
86
 
87
87
  result += "(#{diceCount}D10\<\=#{target}) > #{text} > Hits:#{totalSuccessCount}*#{damage}"
88
- result += " + Trigger:#{totalCriticalCount}*#{criticalTrigger}" if criticalTrigger > 0
88
+ result += " + Trigger:#{totalCriticalCount}*#{criticalTrigger}" if criticalTrigger > 0
89
89
  result += " > #{totalDamage}ダメージ"
90
90
  else
91
91
  result += "(#{diceCount}D10\<\=#{target}) > #{text} > 成功数:#{totalSuccessCount}"
@@ -72,7 +72,7 @@ module BCDice
72
72
  @roll_times = m[1].to_i
73
73
  @modify_str = m[2] || ''
74
74
  @modify = m[2].to_i
75
- @difficulty = m[4] ? m[4].to_i : nil
75
+ @difficulty = m[4]&.to_i
76
76
 
77
77
  base = critical_base(@roll_times)
78
78
  @critical = base + m[7].to_i
@@ -58,7 +58,7 @@ module BCDice
58
58
  fumble = 1
59
59
 
60
60
  isStormy = (m[2] == 'GL') # 波乱万丈
61
- if isStormy
61
+ if isStormy
62
62
  critical -= 3
63
63
  fumble += 1
64
64
  end
@@ -69,7 +69,7 @@ module BCDice
69
69
  critical, fumble = get_critival_fumble(critical, fumble, m[8], m[9])
70
70
 
71
71
  target = get_value(m[11])
72
- optionalText = (m[13] || '')
72
+ optionalText = m[13] || ''
73
73
 
74
74
  return checkRoll(diceCount, modify, critical, fumble, target, isStormy, optionalText)
75
75
  end
@@ -116,7 +116,7 @@ module BCDice
116
116
 
117
117
  if isCritical
118
118
  result += "成功(クリティカル)"
119
- elsif total >= target
119
+ elsif total >= target
120
120
  result += "成功"
121
121
  else
122
122
  result += "失敗"
@@ -121,7 +121,7 @@ module BCDice
121
121
  set += 1 if diceCount > 1
122
122
  end
123
123
 
124
- if criticalCallDice != 0
124
+ if criticalCallDice != 0
125
125
  c_cnt = diceCountList[criticalCallDice - 1]
126
126
  suc = c_cnt * 2
127
127
 
@@ -16,7 +16,7 @@ module BCDice
16
16
  HELP_MESSAGE = <<~INFO_MESSAGE_TEXT
17
17
  ・1D100で判定時に成否、Botchを判定
18
18
   例)1D100<=50
19
-    Chill3 : (1D100<=50) > 55 > Botch
19
+    (1D100<=50) > 55 > Botch
20
20
  INFO_MESSAGE_TEXT
21
21
 
22
22
  def result_1d100(total, dice_total, cmp_op, target)
@@ -30,21 +30,21 @@ module BCDice
30
30
  if tens == ones
31
31
  if (total > target) || (dice_total == 100) # 00は必ず失敗
32
32
  if target > 100 # 目標値が100を超えている場合は、00を振ってもBotchにならない
33
- return Result.failure("失敗")
33
+ return Result.failure("Failure")
34
34
  else
35
35
  return Result.fumble("Botch")
36
36
  end
37
37
  else
38
- return Result.critical("C成功")
38
+ return Result.critical("Colossal Success")
39
39
  end
40
40
  elsif (total <= target) || (dice_total == 1) # 01は必ず成功
41
41
  if total <= (target / 2)
42
- return Result.success("H成功")
42
+ return Result.success("High Success")
43
43
  else
44
- return Result.success("L成功")
44
+ return Result.success("Low Success")
45
45
  end
46
46
  else
47
- return Result.failure("失敗")
47
+ return Result.failure("Failure")
48
48
  end
49
49
  end
50
50
  end
@@ -64,7 +64,7 @@ module BCDice
64
64
  result += " > #{successValue}#{targetText}"
65
65
 
66
66
  if target > 0
67
- if successValue >= target
67
+ if successValue >= target
68
68
  result += " > 【成功】"
69
69
  else
70
70
  result += " > 【失敗】"
@@ -22,7 +22,7 @@ module BCDice
22
22
  if total <= 1
23
23
  Result.critical("貫通") # 1は常に貫通
24
24
  elsif total >= 100
25
- Result.fumble("致命的失敗") # 100は常に致命的失敗
25
+ Result.fumble("致命的失敗") # 100は常に致命的失敗
26
26
  elsif target == '?'
27
27
  Result.nothing
28
28
  elsif total <= (target / 5.0).ceil
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BCDice
4
+ module GameSystem
5
+ class FullFace < Base
6
+ # ゲームシステムの識別子
7
+ ID = 'FullFace'
8
+
9
+ # ゲームシステム名
10
+ NAME = 'フルフェイス'
11
+
12
+ # ゲームシステム名の読みがな
13
+ SORT_KEY = 'ふるふえいす'
14
+
15
+ # ダイスボットの使い方
16
+ HELP_MESSAGE = <<~INFO_MESSAGETEXT
17
+ ■判定 x+bFF<=t x:ヒート(省略時は3) b:判定修正 t:能力値
18
+
19
+ 例)FF<=2: 能力値2で判定し、その結果(成功数,1の目の数,バースト)を表示。
20
+ 6FF<=3: ヒート6,能力値3で戦闘判定し、その結果( 〃 )を表示。
21
+ 8+2FF<=3:ヒート8,判定修正+2,能力値3で戦闘判定し、その結果( 〃 )を表示。
22
+ INFO_MESSAGETEXT
23
+
24
+ register_prefix('([+\d]+)*FF')
25
+
26
+ def eval_game_system_specific_command(command)
27
+ resolute_action(command)
28
+ end
29
+
30
+ private
31
+
32
+ # 技能判定
33
+ # @param [String] command
34
+ # @return [Result]
35
+ def resolute_action(command)
36
+ m = /^(\d*)([+\d]+)*FF<=(\d)$/.match(command)
37
+ return nil unless m
38
+
39
+ heat_level = m[1].to_i
40
+ heat_level = 3 if heat_level == 0
41
+ modify = Arithmetic.eval("0#{m[2]}", @round_type)
42
+ status_no = m[3].to_i
43
+
44
+ dice_array = []
45
+
46
+ dice = @randomizer.roll_barabara(heat_level, 6)
47
+ ones = dice.count(1)
48
+ sixs = dice.count(6)
49
+ success_num = dice.count { |val| val <= status_no }
50
+ dice_array.push(dice.join(","))
51
+
52
+ if modify > 0
53
+ dice = @randomizer.roll_barabara(modify, 6)
54
+ ones += dice.count(1)
55
+ success_num += dice.count { |val| val <= status_no }
56
+ dice_array.push(dice.join(","))
57
+ end
58
+ ones_total = ones
59
+
60
+ while ones > 0
61
+ dice = @randomizer.roll_barabara(ones, 6)
62
+ ones = dice.count(1)
63
+ ones_total += ones
64
+ success_num += dice.count { |val| val <= status_no }
65
+ dice_array.push(dice.join(","))
66
+ end
67
+
68
+ return Result.new.tap do |result|
69
+ if sixs >= 2
70
+ result.fumble = true
71
+ result.condition = false
72
+ else
73
+ result.condition = (success_num > 0)
74
+ result.critical = (ones_total > 0)
75
+ end
76
+ result_txt = []
77
+ result_txt.push("成功度(#{success_num})")
78
+ result_txt.push("1の目(#{ones_total})") if ones_total > 0
79
+ result_txt.push("バースト") if result.fumble?
80
+
81
+ sequence = [
82
+ "(#{heat_level}#{Format.modifier(modify)}FF<=#{status_no})",
83
+ dice_array.join('+').to_s,
84
+ result_txt.join(',').to_s,
85
+ ].compact
86
+
87
+ result.text = sequence.join(" > ")
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -13,7 +13,10 @@ module BCDice
13
13
  SORT_KEY = 'むけんのふあんたしあ'
14
14
 
15
15
  # ダイスボットの使い方
16
- HELP_MESSAGE = "失敗、成功レベルの自動判定を行います。"
16
+ HELP_MESSAGE = <<~INFO_MESSAGE_TEXT
17
+ 1D20に目標値を設定した場合に、成功レベルの自動判定を行います。
18
+ 例: 1D20<=16
19
+ INFO_MESSAGE_TEXT
17
20
 
18
21
  # ゲーム別成功度判定(1d20)
19
22
  def result_1d20(total, _dice_total, cmp_op, target)
@@ -37,7 +37,7 @@ module BCDice
37
37
  SceneSituation, SSi
38
38
  HELP
39
39
 
40
- ATTACK_ROLL_REG = %r{^AT(TACK|K)?([+\-*/()\d]+)@([+\-*/()\d]+)<=([+\-*/()\d]+)(\[([+\-])([+\-*/()\d]+)\])?}i.freeze
40
+ ATTACK_ROLL_REG = %r{^AT(TACK|K)?([+\-*/()\d]+)@([+\-*/()\d]+)<=([+\-*/()\d]+)(\[([+-])([+\-*/()\d]+)\])?}i.freeze
41
41
  register_prefix('AT(TACK|K)?')
42
42
 
43
43
  def initialize(command)
@@ -101,7 +101,7 @@ module BCDice
101
101
  target = 5
102
102
  end
103
103
 
104
- if fumble < 2
104
+ if fumble < 2
105
105
  fumble = 2
106
106
  elsif fumble > 11
107
107
  result += "【#{command}】 > #{translate('KillDeathBusiness.JD.warning.over_fumble')}\n"
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BCDice
4
+ module GameSystem
5
+ class KyokoShinshoku < Base
6
+ # ゲームシステムの識別子
7
+ ID = "KyokoShinshoku"
8
+
9
+ # ゲームシステム名
10
+ NAME = "虚構侵蝕TRPG"
11
+
12
+ # ゲームシステム名の読みがな
13
+ SORT_KEY = "きよこうしんしよくTRPG"
14
+
15
+ # ダイスボットの使い方
16
+ HELP_MESSAGE = <<~MESSAGETEXT
17
+ ・判定
18
+  ダイスを指定数ダイスロールして、最も高い出目を出力します。難易度を指定すると成否を判定します。
19
+ KS(x,y)
20
+ x:ダイスサイズ。1=D4(能力値1、2以上の出目が出ていたとしても最大1)/2=D4(能力値2、3以上の出目が出ていたとしても最大2)/3=D4(能力値3、出目4が出ていたとしても最大3)/4=D4/6=D6/8=D8/10=D10/12=D12/20=D20
21
+ y:ダイス数(省略:1)
22
+
23
+ KS(x,y)>=z
24
+ x:ダイスサイズ。1=D4(能力値1、2以上の出目が出ていたとしても最大1)/2=D4(能力値2、3以上の出目が出ていたとしても最大2)/3=D4(能力値3、出目4が出ていたとしても最大3)/4=D4/6=D6/8=D8/10=D10/12=D12/20=D20
25
+ y:ダイス数(省略:1)
26
+ z:難易度
27
+
28
+ ・観測ロール
29
+  [現実乖離]の段階に応じたダイスを指定数ダイスロールして、最も高い出目を出力します。
30
+ KR(x)
31
+ x=[現実乖離]の段階(1=D4/2=D6/3=D8/4=D10/5=D12/6=D20)
32
+
33
+ KR(x,y) 観測ロール(リアリティラインあり)
34
+ x=[現実乖離]の段階(1=D4/2=D6/3=D8/4=D10/5=D12/6=D20)
35
+ y=[リアリティライン]のレベル(3=1個/2=2個/1=3個)
36
+
37
+ ・虚構の収束の侵蝕度減少ロール
38
+  [現実乖離]の段階に応じたダイスを指定数ダイスロールして、その合計を出力します。
39
+ KRS(x,y)
40
+ x=[現実乖離]の段階(1=D4/2=D6/3=D8/4=D10/5=D12/6=D20)
41
+ y=ダイスの個数
42
+ MESSAGETEXT
43
+
44
+ register_prefix('KS', 'KR', 'KRS')
45
+
46
+ def eval_game_system_specific_command(command)
47
+ roll_check(command) || roll_kansoku(command) || roll_shusoku(command)
48
+ end
49
+
50
+ private
51
+
52
+ DICE_SIZE_TO_SIDES = {
53
+ 1 => 4,
54
+ 2 => 4,
55
+ 3 => 4,
56
+ 4 => 4,
57
+ 6 => 6,
58
+ 8 => 8,
59
+ 10 => 10,
60
+ 12 => 12,
61
+ 20 => 20,
62
+ }.freeze
63
+
64
+ def roll_check(command)
65
+ m = /^KS(?:\((\d+),([-+\d]+)?\)|(\d+))(?:>=([-+\d]+))?$/.match(command)
66
+ return nil unless m
67
+
68
+ dice_size = m[1]&.to_i || m[3].to_i
69
+ times = m[2] ? Arithmetic.eval(m[2], @round_type) : 1
70
+ target = m[4] && Arithmetic.eval(m[4], @round_type)
71
+
72
+ sides = DICE_SIZE_TO_SIDES[dice_size]
73
+
74
+ return nil if sides.nil? || times.nil?
75
+
76
+ dice_list = @randomizer.roll_barabara(times, sides).sort
77
+ value = dice_list.max.clamp(1, dice_size)
78
+
79
+ result =
80
+ if value == 1
81
+ Result.fumble("ファンブル")
82
+ elsif target && value < target
83
+ Result.failure("失敗")
84
+ elsif target && value == sides
85
+ Result.critical("クリティカル")
86
+ elsif target && value >= target
87
+ Result.success("成功")
88
+ else
89
+ Result.new()
90
+ end
91
+
92
+ result.text = [
93
+ target ? "(KS(#{dice_size},#{times})>=#{target})" : "(KS(#{dice_size},#{times}))",
94
+ ("#{value}[#{dice_list.join(',')}]" if times > 1),
95
+ value,
96
+ result.text,
97
+ ].compact.join(" > ")
98
+
99
+ return result
100
+ end
101
+
102
+ GENJITU_KAIRI_TO_SIDES = [4, 6, 8, 10, 12, 20].freeze
103
+ REALITY_LINE_TO_TIMES = {
104
+ 3 => 1,
105
+ 2 => 2,
106
+ 1 => 3,
107
+ }.freeze
108
+
109
+ def roll_kansoku(command)
110
+ m = /^KR(?:(\d+)|\((\d),(\d)\))$/.match(command)
111
+ return nil unless m
112
+
113
+ dice_size = m[1]&.to_i || m[2].to_i
114
+ reality_line = m[3]&.to_i
115
+
116
+ if reality_line && (reality_line > 3 || reality_line < 1)
117
+ return nil
118
+ end
119
+
120
+ sides = GENJITU_KAIRI_TO_SIDES[dice_size - 1]
121
+ times = REALITY_LINE_TO_TIMES[reality_line] || 1
122
+
123
+ return nil unless sides
124
+
125
+ dice_list = @randomizer.roll_barabara(times, sides).sort
126
+ value = dice_list.max
127
+
128
+ cmd = reality_line ? "KR(#{dice_size},#{reality_line})" : "KR(#{dice_size})"
129
+
130
+ if times == 1
131
+ "(#{cmd}) > #{value}"
132
+ else
133
+ "(#{cmd}) > #{value}[#{dice_list.join(',')}] > #{value}"
134
+ end
135
+ end
136
+
137
+ def roll_shusoku(command)
138
+ m = /^KRS(?:\((\d),([-+\d]+)\))$/.match(command)
139
+ return nil unless m
140
+
141
+ dice_size = m[1].to_i
142
+ times = m[2] && Arithmetic.eval(m[2], @round_type)
143
+
144
+ sides = GENJITU_KAIRI_TO_SIDES[dice_size - 1]
145
+ return nil if sides.nil? || times.nil?
146
+
147
+ dice_list = @randomizer.roll_barabara(times, sides)
148
+ value = dice_list.sum
149
+
150
+ if times == 1
151
+ "(KRS(#{dice_size},#{times})) > #{value}"
152
+ else
153
+ "(KRS(#{dice_size},#{times})) > #{value}[#{dice_list.join(',')}] > #{value}"
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end