bcdice 3.3.0 → 3.6.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 (96) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +158 -74
  3. data/README.md +2 -1
  4. data/i18n/BeginningIdol/ja_jp.yml +2806 -0
  5. data/i18n/BeginningIdol/ko_kr.yml +1400 -0
  6. data/i18n/Cthulhu/zh_hans.yml +11 -0
  7. data/i18n/FutariSousa/ja_jp.yml +705 -9
  8. data/i18n/KillDeathBusiness/ja_jp.yml +2 -2
  9. data/i18n/MagicaLogia/ja_jp.yml +1 -1
  10. data/i18n/MonotoneMuseum/ko_kr.yml +526 -53
  11. data/i18n/SwordWorld/ja_jp.yml +6 -0
  12. data/i18n/SwordWorld2_0/ja_jp.yml +37 -0
  13. data/i18n/SwordWorld2_5/ja_jp.yml +57 -0
  14. data/lib/bcdice/common_command/add_dice/node.rb +28 -4
  15. data/lib/bcdice/common_command/add_dice/parser.rb +149 -85
  16. data/lib/bcdice/common_command/choice.rb +82 -7
  17. data/lib/bcdice/common_command/tally_dice/node.rb +135 -0
  18. data/lib/bcdice/common_command/tally_dice/parser.rb +302 -0
  19. data/lib/bcdice/common_command/tally_dice.rb +19 -0
  20. data/lib/bcdice/common_command.rb +2 -0
  21. data/lib/bcdice/dice_table/d66_left_range_table.rb +29 -0
  22. data/lib/bcdice/dice_table/d66_parity_table.rb +8 -0
  23. data/lib/bcdice/dice_table/d66_table.rb +5 -1
  24. data/lib/bcdice/dice_table.rb +1 -0
  25. data/lib/bcdice/game_system/AngelGear.rb +9 -4
  26. data/lib/bcdice/game_system/Aoharubaan.rb +91 -0
  27. data/lib/bcdice/game_system/AssaultEngine.rb +91 -0
  28. data/lib/bcdice/game_system/Avandner.rb +1 -2
  29. data/lib/bcdice/game_system/Ayabito.rb +229 -0
  30. data/lib/bcdice/game_system/BadLife.rb +264 -289
  31. data/lib/bcdice/game_system/Bakenokawa.rb +244 -0
  32. data/lib/bcdice/game_system/BattleTech.rb +1 -1
  33. data/lib/bcdice/game_system/BeginningIdol.rb +116 -2390
  34. data/lib/bcdice/game_system/BeginningIdol_Korean.rb +10 -2312
  35. data/lib/bcdice/game_system/CastleInGray.rb +133 -0
  36. data/lib/bcdice/game_system/Cthulhu.rb +2 -2
  37. data/lib/bcdice/game_system/Cthulhu7th.rb +10 -10
  38. data/lib/bcdice/game_system/Cthulhu_SimplifiedChinese.rb +60 -0
  39. data/lib/bcdice/game_system/CyberpunkRed.rb +135 -0
  40. data/lib/bcdice/game_system/DarkDaysDrive.rb +76 -41
  41. data/lib/bcdice/game_system/DoubleCross.rb +5 -0
  42. data/lib/bcdice/game_system/Dracurouge.rb +2 -1
  43. data/lib/bcdice/game_system/Elysion.rb +36 -20
  44. data/lib/bcdice/game_system/Emoklore.rb +1 -1
  45. data/lib/bcdice/game_system/FateCoreSystem.rb +117 -0
  46. data/lib/bcdice/game_system/FutariSousa.rb +48 -4
  47. data/lib/bcdice/game_system/GranCrest.rb +171 -406
  48. data/lib/bcdice/game_system/Irisbane.rb +154 -0
  49. data/lib/bcdice/game_system/Karukami.rb +84 -0
  50. data/lib/bcdice/game_system/KemonoNoMori.rb +17 -17
  51. data/lib/bcdice/game_system/KurayamiCrying.rb +1 -1
  52. data/lib/bcdice/game_system/MonotoneMuseum_Korean.rb +4 -4
  53. data/lib/bcdice/game_system/NeverCloud.rb +19 -8
  54. data/lib/bcdice/game_system/PersonaO.rb +86 -0
  55. data/lib/bcdice/game_system/Revulture.rb +2 -2
  56. data/lib/bcdice/game_system/Shiranui.rb +145 -0
  57. data/lib/bcdice/game_system/ShuumatsuKikou.rb +298 -0
  58. data/lib/bcdice/game_system/StarryDolls.rb +318 -0
  59. data/lib/bcdice/game_system/SteamPunkers.rb +14 -1
  60. data/lib/bcdice/game_system/StellarKnights.rb +1 -1
  61. data/lib/bcdice/game_system/{SterileLife.rb → StellarLife.rb} +162 -226
  62. data/lib/bcdice/game_system/StrangerOfSwordCity.rb +18 -5
  63. data/lib/bcdice/game_system/SwordWorld.rb +11 -7
  64. data/lib/bcdice/game_system/SwordWorld2_0.rb +30 -38
  65. data/lib/bcdice/game_system/SwordWorld2_5.rb +9 -63
  66. data/lib/bcdice/game_system/TalesFromTheLoop.rb +83 -0
  67. data/lib/bcdice/game_system/TorgEternity.rb +2 -2
  68. data/lib/bcdice/game_system/ToshiakiHolyGrailWar.rb +92 -0
  69. data/lib/bcdice/game_system/VampireTheMasquerade5th.rb +29 -28
  70. data/lib/bcdice/game_system/YearZeroEngine.rb +40 -19
  71. data/lib/bcdice/game_system/beginning_idol/accessories_table.rb +31 -0
  72. data/lib/bcdice/game_system/beginning_idol/bad_status_table.rb +43 -0
  73. data/lib/bcdice/game_system/beginning_idol/chain_d66_table.rb +29 -0
  74. data/lib/bcdice/game_system/beginning_idol/chain_table.rb +37 -0
  75. data/lib/bcdice/game_system/beginning_idol/costume_table.rb +37 -0
  76. data/lib/bcdice/game_system/beginning_idol/d6_twice_table.rb +36 -0
  77. data/lib/bcdice/game_system/beginning_idol/item_table.rb +51 -0
  78. data/lib/bcdice/game_system/beginning_idol/my_skill_name_table.rb +44 -0
  79. data/lib/bcdice/game_system/beginning_idol/random_event_table.rb +31 -0
  80. data/lib/bcdice/game_system/beginning_idol/skill_table.rb +63 -0
  81. data/lib/bcdice/game_system/beginning_idol/table.rb +193 -0
  82. data/lib/bcdice/game_system/beginning_idol/with_abnormality.rb +74 -0
  83. data/lib/bcdice/game_system/beginning_idol/work_table.rb +57 -0
  84. data/lib/bcdice/game_system/cyberpunk_red/tables.rb +530 -0
  85. data/lib/bcdice/game_system/meikyu_kingdom/tables.rb +1 -1
  86. data/lib/bcdice/game_system/meikyu_kingdom_basic/table.rb +1 -1
  87. data/lib/bcdice/game_system/one_way_heroics/random_event_table.rb +4 -4
  88. data/lib/bcdice/game_system/satasupe/tables.rb +1 -1
  89. data/lib/bcdice/game_system/sword_world/rating_options.rb +47 -0
  90. data/lib/bcdice/game_system/sword_world/rating_parsed.rb +40 -45
  91. data/lib/bcdice/game_system/sword_world/rating_parser.rb +172 -141
  92. data/lib/bcdice/game_system/sword_world/transcendent_test.rb +14 -10
  93. data/lib/bcdice/game_system.rb +16 -1
  94. data/lib/bcdice/randomizer.rb +1 -1
  95. data/lib/bcdice/version.rb +1 -1
  96. metadata +45 -5
@@ -0,0 +1,37 @@
1
+ ja_jp:
2
+ SwordWorld2_0:
3
+ transcendent_critical_too_small: (%{expression}) > クリティカル値が小さすぎます。3以上を指定してください。
4
+ super_success: 超成功
5
+
6
+ GrowthTable:
7
+ name: 成長表
8
+ type: 1D6
9
+ items:
10
+ - 器用度
11
+ - 敏捷度
12
+ - 筋力
13
+ - 生命力
14
+ - 知力
15
+ - 精神力
16
+
17
+ FumbleTable:
18
+ name: 防御ファンブル表
19
+ type: 1D6
20
+ items:
21
+ - この表を2回振り、その両方を適用する。(同じ出目による影響は累積しない)。この自動失敗により得られる経験点は、+50点される
22
+ - ダメージに、攻撃者を強化している「剣のかけら」の数が追加される
23
+ - ダメージに、攻撃者の「レベル」が追加される
24
+ - ダメージ決定を2回行い、より高い方を採用する
25
+ - 合算ダメージを2倍する
26
+ - 防護点無効
27
+
28
+ TangleTable:
29
+ name: 絡み効果表
30
+ type: 1D6
31
+ items:
32
+ - 頭や顔:牙や噛みつきなどにおける命中力判定及び、魔法の行使やブレスに-2のペナルティ修正を受ける
33
+ - 武器や盾:武器の使用不可、又は盾の回避力修正及び防護点を無効化する
34
+ - 腕や手:武器や爪などにおける命中力判定に-2のペナルティ修正、盾を持つ腕方の腕ならその盾の回避力修正及び防護点を無効化する
35
+ - 脚や足:移動不可、更に回避力判定に-2のペナルティ修正を受ける ※両足に絡んでも累積しない
36
+ - 胴体:生命・精神抵抗力を基準値に用いる判定を除き、あらゆる行為判定に-1のペナルティ修正を受ける
37
+ - 特殊:尻尾や翼などに命中。絡められた部位を使用する判定において-2のペナルティ修正、またはそこが使えていたことによるボーナス修正を失う ※存在しない場合は決め直し
@@ -0,0 +1,57 @@
1
+ ja_jp:
2
+ SwordWorld2_5:
3
+ AbyssCurseTable:
4
+ name: アビスカース表
5
+ items:
6
+ -
7
+ - 「自傷の」 装備時 この武具を装備中に装備者がクリティカルを発生させた時、装備者のHPが5点減少する。
8
+ - 「嘆きの」 装備時 近くに敵がいたり、長い緊張状態が続くと涙が止まらなくなる。戦闘中なら「射程:術者(自身)」「射程:接触」以外の効果で対象を選べなくなる。
9
+ - 「優しき」 装備時 敵に同情してしまう。敵対するキャラクターを対象にする場合、対象のHPが1点以上減少しているなら命中力判定、魔法行使判定に-2のペナルティ修正を受ける。
10
+ - 「差別の」 装備時 特定の分類に対して与える物理ダメージ・魔法ダメージが2点減少する。分類は「分類決定表」で無作為に決定する。
11
+ - 「脆弱な」 装備時 魔法ダメージを受けるたび、そのダメージが+1点される。
12
+ - 「無謀な」 装備時 防護点が2点減少する(最低0)。
13
+ -
14
+ - 「重い」 装備時 強化した武具の必要筋力が+2される。威力、防護点などは変化なし。
15
+ - 「難しい」 装備時 いかなる威力表使用時でも、③④欄の数値が威力に関係なく「0」になる(自動失敗ではない)。
16
+ - 「軟弱な」 装備時 精神抵抗力判定に-1のペナルティ修正を受ける。
17
+ - 「病弱な」 装備時 生命抵抗力判定に-1のペナルティ修正を受ける。
18
+ - 「過敏な」 装備時 特定の属性から受ける物理ダメージ、魔法ダメージが2点上昇する。属性は「属性決定表」で無作為に決定する。
19
+ - 「陽気な」 装備時 精神抵抗判定に失敗するたび、笑いが止まらなくなる。次の手番終了時まで行動判定(『Ⅰ』123頁)に-1のペナルティ修正を受ける。この効果は累積する。
20
+ -
21
+ - 「たどたどしい」 装備時 話をする時に言葉に詰まったり、言い間違えたりしやすくなる。魔法行使判定に-1のペナルティ修正を受ける。
22
+ - 「代弁する」 装備時 自身の会話は、そのまま武具が魔法文明語の聞き取りづらい声で話す。装備中は魔法文明語以外の言語で会話は行えず、妖精魔法、魔動機術を行使できなくなる。
23
+ - 「施しは受けない」 装備時 戦闘中、「抵抗:任意」の効果を受け入れた場合、次の手番終了時まで生命抵抗力、精神抵抗力に-2のペナルティ修正を受ける。
24
+ - 「死に近い」 携行時 常に生死判定に「冒険者レベル」と同じ値のペナルティ修正を受ける。
25
+ - 「おしゃれな」 携行時 その武具を常に華美に飾りたくなる。収入を得るたび、その1割以上をこの武具の装飾に費やさなければならない(効果などに変化はない)。
26
+ - 「マナを吸う」 携行時 魔法や練技など、自身の意思でMPを消費する効果を使用する場合、すべてのMP消費が1点上昇する。
27
+ -
28
+ - 「鈍重な」 携行時 移動力が半分(端数切り上げ)になる。
29
+ - 「定まらない」 携行時 戦闘中の手番開始時に1dし、出目が「1」なら《ターゲッティング》とそれを前提とした戦闘特技を習得していないものとして扱う。
30
+ - 「錯乱の」 携行時 戦闘中の手番開始時に1dし、出目が「1」なら近接攻撃を含む「射程:接触」の対象に効果を使用する際、対象は同じ位置(エリア、座標)の全てのキャラクター(敵味方含む)から無作為に選ばれる。
31
+ - 「足絡みの」 携行時 戦闘中の手番開始時に1dし、出目が「1」ならその場で即座に転倒する。手番中には起き上がれない。
32
+ - 「滑り落ちる」 携行時 戦闘中の手番開始時に1dし、出目が「1」なら手に装備または保持しているものをすべてその場に落とす(その手番の主動作で拾う事は可能)。
33
+ - 「悪臭放つ」 携行時 強い悪臭を放つ。所持しているだけで他のキャラクターに不快感を与え、隠密判定に-2のペナルティ修正を受ける。さらに冒険者ランク(『Ⅱ』137頁)が1段階低いものとして扱われる。
34
+ -
35
+ - 「醜悪な」 携行時 武具の見た目が悪く、魅力がない。売却する際、基本取引価格の4分の1の価格で売却する。さらに冒険者ランク(『Ⅱ』137頁)が1段階低いものとして扱われる。
36
+ - 「唸る」 携行時 その武具から常に羽虫が飛び交うような音が響く。隠密判定、危険感知判定に-4のペナルティ修正を受ける。
37
+ - 「ふやけた」 携行時 水を吸ったようにふやけた質感をしている。追加ダメージ-1(武器)、防護点-1(鎧、盾)。病気属性の効果に対する生命抵抗力、精神抵抗力判定に-4のペナルティ修正を受ける。
38
+ - 「古傷の」 携行時 HPを回復する効果(休息による回復を含む)を受けた場合、その回復量が1点減少する。
39
+ - 「まばゆい」 携行時 光などを弾いて強く輝く。自身は常に視界が悪い事による-1のペナルティ修正を受ける。
40
+ - 「栄光なき」 携行時 行為判定で自動成功した際、自動成功とは扱わず、2dを振り直し、その後の出目に従う。この効果は1日に1回のみ発揮される。
41
+ -
42
+ - 「正直者の」 携行時 嘘、方便がすぐばれるようになる。真偽判定の対象となる場合、-4のペナルティ修正を受ける。
43
+ - 「乗り物酔いの」 携行時 揺れに弱くなる。自身の足以外の手段で10分以上移動した後、1時間、行動判定に-1のペナルティ修正を受ける。
44
+ - 「碧を厭う」 携行時 自然の中では落ち着かなくなる。自然環境(『Ⅰ』108頁)では行動判定に-1のペナルティ修正を受ける。
45
+ - 「我慢できない」 携行時 セッション中に1日の始まりを迎えるたび、趣味や嗜好品などに「冒険者レベル×10」ガメルを出費しなければならない。趣味や嗜好品が消費できない環境であれば、翌日の朝まで最大HP、最大MPが「冒険者レベル」点減少する。
46
+ - 「つきまとう」 携行時 この武具が気がつけば身の回りにある。この武具以外での命中力判定、魔法行使判定(武器)、回避力判定(鎧、盾)に-4のペナルティ修正を受ける。
47
+ - 「のろまな」 携行時 戦闘開始処理の「戦闘準備」をいっさい行えなくなる。
48
+
49
+ AbyssCurseCategoryTable:
50
+ name: アビスカース分類決定表
51
+ odd: ["蛮族", "動物", "植物", "アンデッド", "魔法生物", "「蛮族」「動物」「植物」「アンデッド」「魔法生物」から任意"]
52
+ even: ["魔動機", "幻獣", "妖精", "魔神", "人族", "「魔動機」「幻獣」「妖精」「魔神」「人族」から任意"]
53
+
54
+ AbyssCurseAttrTable:
55
+ name: アビスカース属性決定表
56
+ odd: ["土", "水・氷", "炎", "風", "雷", "純粋エネルギー"]
57
+ even: ["断空", "衝撃", "毒", "病気", "呪い", "精神効果"]
@@ -493,10 +493,14 @@ module BCDice
493
493
 
494
494
  # ノードを初期化する
495
495
  # @param [Object] times ダイスを振る回数のノード
496
- # @param [Object] sides ダイスの面数のノード
496
+ # @param [Object, :implicit] sides ダイスの面数のノード(暗黙の面数を参照したい場合は `:implicit`)
497
497
  # @param [Object] n_filtering ダイスを残す/減らす個数のノード
498
498
  # @param [Filter] filter フィルタ
499
499
  def initialize(times, sides, n_filtering, filter)
500
+ if sides != :implicit && !sides.respond_to?(:eval)
501
+ raise TypeError, "sides must be a Node or :implicit (#{sides.inspect})"
502
+ end
503
+
500
504
  @times = times
501
505
  @sides = sides
502
506
  @n_filtering = n_filtering
@@ -515,7 +519,7 @@ module BCDice
515
519
  # @return [Integer] 評価結果(出目の合計値)
516
520
  def eval(game_system, randomizer)
517
521
  times = @times.eval(game_system, nil)
518
- sides = @sides.eval(game_system, nil)
522
+ sides = eval_sides(game_system)
519
523
  n_filtering = @n_filtering.eval(game_system, nil)
520
524
 
521
525
  sorted_values = randomizer.roll(times, sides).sort
@@ -533,11 +537,17 @@ module BCDice
533
537
  true
534
538
  end
535
539
 
540
+ # 暗黙の面数を参照するか?
541
+ # @return [Boolean]
542
+ def implicit_sides?
543
+ @sides == :implicit
544
+ end
545
+
536
546
  # 文字列に変換する
537
547
  # @return [String]
538
548
  def expr(game_system)
539
549
  times = @times.eval(game_system, nil)
540
- sides = @sides.eval(game_system, nil)
550
+ sides = eval_sides(game_system)
541
551
  n_filtering = @n_filtering.eval(game_system, nil)
542
552
 
543
553
  "#{times}D#{sides}#{@filter.abbr}#{n_filtering}"
@@ -552,7 +562,21 @@ module BCDice
552
562
  # ノードのS式を返す
553
563
  # @return [String]
554
564
  def s_exp
555
- "(DiceRollWithFilter #{@times.s_exp} #{@sides.s_exp} #{@filter.abbr.inspect} #{@n_filtering.s_exp})"
565
+ sides_s_exp = implicit_sides? ? @sides.inspect : @sides.s_exp
566
+
567
+ "(DiceRollWithFilter #{@times.s_exp} #{sides_s_exp} #{@filter.abbr.inspect} #{@n_filtering.s_exp})"
568
+ end
569
+
570
+ private
571
+
572
+ # 面数を評価する
573
+ # @return [Integer] 指定された面数または暗黙の面数
574
+ def eval_sides(game_system)
575
+ if implicit_sides?
576
+ game_system.sides_implicit_d
577
+ else
578
+ @sides.eval(game_system, nil)
579
+ end
556
580
  end
557
581
  end
558
582
 
@@ -48,91 +48,102 @@ end
48
48
  ##### State transition tables begin ###
49
49
 
50
50
  racc_action_table = [
51
- 13, 16, 17, 18, 19, 3, 32, 13, 4, 13,
52
- 14, 8, 9, 22, 13, 12, 13, 26, 8, 9,
51
+ 13, 16, 17, 3, -27, -27, 33, 13, -27, 13,
52
+ 4, 46, 13, 47, 13, 14, 8, 9, 22, 13,
53
+ 12, 13, 26, 8, 9, 8, 9, 12, 13, 12,
53
54
  8, 9, 12, 13, 12, 8, 9, 8, 9, 12,
54
- 13, 12, 13, nil, 8, 9, 13, 13, 12, 13,
55
- nil, 8, 9, 8, 9, 12, 15, 12, 8, 9,
56
- nil, 12, 12, nil, 12, nil, 16, 17, 35, 37,
57
- 34, 36, 16, 17, 18, 19, 18, 19, 40, 39,
58
- 42, 43, 44, 45 ]
55
+ 13, 12, 18, 19, 8, 9, 15, 52, 12, 8,
56
+ 9, 13, 53, 12, -26, -26, 8, 9, -26, nil,
57
+ 12, 16, 17, 43, 42, 16, 17, 41, 18, 19,
58
+ nil, 12, 36, 38, 35, 37, 18, 19, 48, 49,
59
+ 50, 51 ]
59
60
 
60
61
  racc_action_check = [
61
- 15, 23, 23, 6, 6, 0, 23, 2, 1, 8,
62
- 4, 15, 15, 11, 9, 15, 12, 15, 2, 2,
63
- 8, 8, 2, 16, 8, 9, 9, 12, 12, 9,
64
- 17, 12, 18, nil, 16, 16, 22, 19, 16, 38,
65
- nil, 17, 17, 18, 18, 17, 5, 18, 19, 19,
66
- nil, 22, 19, nil, 38, nil, 5, 5, 30, 30,
67
- 30, 30, 25, 25, 27, 27, 28, 28, 31, 31,
68
- 39, 39, 40, 40 ]
62
+ 15, 23, 23, 0, 31, 31, 23, 2, 31, 8,
63
+ 1, 41, 40, 41, 9, 4, 15, 15, 11, 12,
64
+ 15, 16, 15, 2, 2, 8, 8, 2, 17, 8,
65
+ 9, 9, 40, 18, 9, 12, 12, 16, 16, 12,
66
+ 19, 16, 6, 6, 17, 17, 5, 46, 17, 18,
67
+ 18, 22, 47, 18, 22, 22, 19, 19, 22, nil,
68
+ 19, 5, 5, 32, 32, 25, 25, 32, 27, 27,
69
+ nil, 22, 30, 30, 30, 30, 28, 28, 42, 42,
70
+ 43, 43 ]
69
71
 
70
72
  racc_action_pointer = [
71
- 1, 8, 5, nil, 10, 43, -12, nil, 7, 12,
72
- nil, 8, 14, nil, nil, -2, 21, 28, 30, 35,
73
- nil, nil, 34, -12, nil, 49, nil, 49, 51, nil,
74
- 49, 63, nil, nil, nil, nil, nil, nil, 37, 63,
75
- 65, nil, nil, nil, nil, nil ]
73
+ -1, 10, 5, nil, 15, 43, 22, nil, 7, 12,
74
+ nil, 13, 17, nil, nil, -2, 19, 26, 31, 38,
75
+ nil, nil, 49, -17, nil, 47, nil, 48, 56, nil,
76
+ 58, -1, 58, nil, nil, nil, nil, nil, nil, nil,
77
+ 10, 1, 71, 73, nil, nil, 36, 39, nil, nil,
78
+ nil, nil, nil, nil ]
76
79
 
77
80
  racc_action_default = [
78
- -3, -31, -31, -4, -31, -1, -9, -12, -31, -31,
79
- -20, -24, -31, -30, 46, -31, -31, -31, -31, -31,
80
- -18, -19, -22, -31, -2, -5, -6, -7, -8, -10,
81
- -13, -21, -29, -11, -14, -15, -16, -17, -31, -31,
82
- -31, -23, -25, -26, -27, -28 ]
81
+ -3, -38, -38, -4, -38, -1, -9, -12, -38, -38,
82
+ -20, -25, -38, -37, 54, -38, -38, -38, -38, -38,
83
+ -18, -19, -22, -38, -2, -5, -6, -7, -8, -10,
84
+ -13, -21, -38, -36, -11, -14, -15, -16, -17, -23,
85
+ -34, -38, -38, -38, -35, -24, -38, -38, -30, -31,
86
+ -32, -33, -28, -29 ]
83
87
 
84
88
  racc_goto_table = [
85
- 31, 5, 20, 21, 27, 28, 1, 2, 24, 33,
86
- 38, 23, 29, 30, 25, nil, 41 ]
89
+ 31, 5, 20, 21, 27, 28, 1, 2, 24, 34,
90
+ 32, 23, 29, 30, 25, 39, 40, 44, 45 ]
87
91
 
88
92
  racc_goto_check = [
89
93
  9, 3, 6, 6, 5, 5, 1, 2, 4, 7,
90
- 10, 3, 6, 6, 3, nil, 9 ]
94
+ 10, 3, 6, 6, 3, 11, 12, 13, 9 ]
91
95
 
92
96
  racc_goto_pointer = [
93
97
  nil, 6, 7, -1, -7, -12, -6, -21, nil, -22,
94
- -21 ]
98
+ -12, -17, -16, -15 ]
95
99
 
96
100
  racc_goto_default = [
97
101
  nil, nil, nil, nil, nil, 6, 7, nil, 10, 11,
98
- nil ]
102
+ nil, nil, nil, nil ]
99
103
 
100
104
  racc_reduce_table = [
101
105
  0, 0, :racc_error,
102
- 2, 21, :_reduce_1,
103
- 4, 21, :_reduce_2,
104
- 0, 22, :_reduce_3,
105
- 1, 22, :_reduce_4,
106
- 1, 24, :_reduce_none,
107
- 1, 24, :_reduce_6,
108
- 3, 23, :_reduce_7,
109
- 3, 23, :_reduce_8,
110
- 1, 23, :_reduce_none,
111
- 3, 25, :_reduce_10,
112
- 4, 25, :_reduce_11,
113
- 1, 25, :_reduce_none,
114
- 0, 27, :_reduce_13,
115
- 1, 27, :_reduce_14,
116
- 1, 27, :_reduce_15,
117
- 1, 27, :_reduce_16,
118
- 1, 27, :_reduce_17,
119
- 2, 26, :_reduce_18,
120
- 2, 26, :_reduce_19,
121
- 1, 26, :_reduce_none,
122
- 3, 28, :_reduce_21,
123
- 2, 28, :_reduce_22,
124
- 5, 28, :_reduce_23,
106
+ 2, 26, :_reduce_1,
107
+ 4, 26, :_reduce_2,
108
+ 0, 27, :_reduce_3,
109
+ 1, 27, :_reduce_4,
110
+ 1, 29, :_reduce_none,
111
+ 1, 29, :_reduce_6,
112
+ 3, 28, :_reduce_7,
113
+ 3, 28, :_reduce_8,
125
114
  1, 28, :_reduce_none,
126
- 2, 30, :_reduce_25,
127
- 2, 30, :_reduce_26,
128
- 2, 30, :_reduce_27,
129
- 2, 30, :_reduce_28,
130
- 3, 29, :_reduce_29,
131
- 1, 29, :_reduce_30 ]
132
-
133
- racc_reduce_n = 31
134
-
135
- racc_shift_n = 46
115
+ 3, 30, :_reduce_10,
116
+ 4, 30, :_reduce_11,
117
+ 1, 30, :_reduce_none,
118
+ 0, 32, :_reduce_13,
119
+ 1, 32, :_reduce_14,
120
+ 1, 32, :_reduce_15,
121
+ 1, 32, :_reduce_16,
122
+ 1, 32, :_reduce_17,
123
+ 2, 31, :_reduce_18,
124
+ 2, 31, :_reduce_19,
125
+ 1, 31, :_reduce_none,
126
+ 3, 33, :_reduce_21,
127
+ 2, 33, :_reduce_22,
128
+ 4, 33, :_reduce_23,
129
+ 5, 33, :_reduce_24,
130
+ 1, 33, :_reduce_none,
131
+ 0, 35, :_reduce_26,
132
+ 1, 35, :_reduce_27,
133
+ 3, 38, :_reduce_28,
134
+ 3, 38, :_reduce_29,
135
+ 2, 37, :_reduce_30,
136
+ 2, 37, :_reduce_31,
137
+ 2, 37, :_reduce_32,
138
+ 2, 37, :_reduce_33,
139
+ 1, 36, :_reduce_none,
140
+ 1, 36, :_reduce_none,
141
+ 3, 34, :_reduce_36,
142
+ 1, 34, :_reduce_37 ]
143
+
144
+ racc_reduce_n = 38
145
+
146
+ racc_shift_n = 54
136
147
 
137
148
  racc_token_table = {
138
149
  false => 0,
@@ -144,19 +155,24 @@ racc_token_table = {
144
155
  :K => 6,
145
156
  :H => 7,
146
157
  :L => 8,
147
- :U => 9,
148
- :R => 10,
149
- :F => 11,
150
- :C => 12,
151
- :PLUS => 13,
152
- :MINUS => 14,
153
- :ASTERISK => 15,
154
- :SLASH => 16,
155
- :PARENL => 17,
156
- :PARENR => 18,
157
- :QUESTION => 19 }
158
-
159
- racc_nt_base = 20
158
+ :M => 9,
159
+ :A => 10,
160
+ :X => 11,
161
+ :I => 12,
162
+ :N => 13,
163
+ :U => 14,
164
+ :R => 15,
165
+ :F => 16,
166
+ :C => 17,
167
+ :PLUS => 18,
168
+ :MINUS => 19,
169
+ :ASTERISK => 20,
170
+ :SLASH => 21,
171
+ :PARENL => 22,
172
+ :PARENR => 23,
173
+ :QUESTION => 24 }
174
+
175
+ racc_nt_base = 25
160
176
 
161
177
  racc_use_result_var = true
162
178
 
@@ -186,6 +202,11 @@ Racc_token_to_s_table = [
186
202
  "K",
187
203
  "H",
188
204
  "L",
205
+ "M",
206
+ "A",
207
+ "X",
208
+ "I",
209
+ "N",
189
210
  "U",
190
211
  "R",
191
212
  "F",
@@ -207,7 +228,10 @@ Racc_token_to_s_table = [
207
228
  "round_type",
208
229
  "dice",
209
230
  "term",
210
- "filter_type" ]
231
+ "explicit_or_implicit_sides",
232
+ "filter_type_with_shorthand",
233
+ "filter_type",
234
+ "filter_shorthand" ]
211
235
 
212
236
  Racc_debug_parser = false
213
237
 
@@ -349,42 +373,82 @@ def _reduce_23(val, _values, result)
349
373
  times = val[0]
350
374
  sides = val[2]
351
375
  filter = val[3]
376
+
377
+ raise ParseError if sides != :implicit && sides.include_dice?
378
+ raise ParseError if times.include_dice?
379
+
380
+ n_filtering = Node::Number.new(1)
381
+ result = Node::DiceRollWithFilter.new(times, sides, n_filtering, filter)
382
+
383
+ result
384
+ end
385
+
386
+ def _reduce_24(val, _values, result)
387
+ times = val[0]
388
+ sides = val[2]
389
+ filter = val[3]
352
390
  n_filtering = val[4]
353
- raise ParseError if times.include_dice? || sides.include_dice? || n_filtering.include_dice?
391
+
392
+ raise ParseError if sides != :implicit && sides.include_dice?
393
+ raise ParseError if times.include_dice? || n_filtering.include_dice?
354
394
 
355
395
  result = Node::DiceRollWithFilter.new(times, sides, n_filtering, filter)
356
396
 
357
397
  result
358
398
  end
359
399
 
360
- # reduce 24 omitted
400
+ # reduce 25 omitted
401
+
402
+ def _reduce_26(val, _values, result)
403
+ result = :implicit
404
+ result
405
+ end
406
+
407
+ def _reduce_27(val, _values, result)
408
+ result = val[0]
409
+ result
410
+ end
411
+
412
+ def _reduce_28(val, _values, result)
413
+ result = Node::DiceRollWithFilter::KEEP_HIGHEST
414
+ result
415
+ end
416
+
417
+ def _reduce_29(val, _values, result)
418
+ result = Node::DiceRollWithFilter::KEEP_LOWEST
419
+ result
420
+ end
361
421
 
362
- def _reduce_25(val, _values, result)
422
+ def _reduce_30(val, _values, result)
363
423
  result = Node::DiceRollWithFilter::KEEP_HIGHEST
364
424
  result
365
425
  end
366
426
 
367
- def _reduce_26(val, _values, result)
427
+ def _reduce_31(val, _values, result)
368
428
  result = Node::DiceRollWithFilter::KEEP_LOWEST
369
429
  result
370
430
  end
371
431
 
372
- def _reduce_27(val, _values, result)
432
+ def _reduce_32(val, _values, result)
373
433
  result = Node::DiceRollWithFilter::DROP_HIGHEST
374
434
  result
375
435
  end
376
436
 
377
- def _reduce_28(val, _values, result)
437
+ def _reduce_33(val, _values, result)
378
438
  result = Node::DiceRollWithFilter::DROP_LOWEST
379
439
  result
380
440
  end
381
441
 
382
- def _reduce_29(val, _values, result)
442
+ # reduce 34 omitted
443
+
444
+ # reduce 35 omitted
445
+
446
+ def _reduce_36(val, _values, result)
383
447
  result = Node::Parenthesis.new(val[1])
384
448
  result
385
449
  end
386
450
 
387
- def _reduce_30(val, _values, result)
451
+ def _reduce_37(val, _values, result)
388
452
  result = Node::Number.new(val[0])
389
453
  result
390
454
  end
@@ -29,6 +29,17 @@ module BCDice
29
29
  # choice A,B X,Y -> "A,B" と "X,Y" から選ぶ
30
30
  # choice(A[], B[], C[]) -> "A[]", "B[]", "C[]" から選ぶ
31
31
  # choice[A(), B(), C()] -> "A()", "B()", "C()" から選ぶ
32
+ #
33
+ # "choice"の後に数を指定することで、列挙した要素から重複なしで複数個を選ぶ
34
+ # choice2[A,B,C] -> "A", "B", "C" から重複なしで2個選ぶ
35
+ #
36
+ # 指定したい要素が「AからD」のように連続する項目の場合に「A-D」と省略して記述できる
37
+ # 略記の展開はアルファベット1文字もしくは数字の範囲に限り、略記1つを指定したときのみ展開される
38
+ # choice[A-D] -> choice[A,B,C,D] と等価
39
+ # choice[b-g] -> choice[b,c,d,e,f,g] と等価
40
+ # choice[3-7] -> choice[3,4,5,6,7] と等価
41
+ # choice[A-D,Z] -> 展開されない。 "A-D", "Z" から選ぶ
42
+ # choice[D-A] -> 展開されない。
32
43
  class Choice
33
44
  PREFIX_PATTERN = /choice/.freeze
34
45
 
@@ -44,6 +55,12 @@ module BCDice
44
55
  space: /\s+/,
45
56
  }.freeze
46
57
 
58
+ DELIMITER_CHAR = {
59
+ bracket: ", ",
60
+ paren: ", ",
61
+ space: " ",
62
+ }.freeze
63
+
47
64
  TERMINATION = {
48
65
  bracket: /\]/,
49
66
  paren: /\)/,
@@ -77,6 +94,11 @@ module BCDice
77
94
  return nil
78
95
  end
79
96
 
97
+ takes = scanner.scan(/\d+/)&.to_i || 1
98
+ if takes == 0
99
+ return nil
100
+ end
101
+
80
102
  type =
81
103
  case scanner.scan(/\(|\[|\s+/)
82
104
  when "["
@@ -105,47 +127,100 @@ module BCDice
105
127
  items.push(last_item.delete_suffix(SUFFIX[type]))
106
128
 
107
129
  items = items.map(&:strip).reject(&:empty?)
108
- if items.empty?
130
+ if items.size == 1
131
+ items = parse_multi_item_shorthand(items.first)
132
+ end
133
+
134
+ if items.empty? || items.size < takes
109
135
  return nil
110
136
  end
111
137
 
112
138
  new(
113
139
  secret: secret,
114
140
  block_delimiter: type,
141
+ takes: takes,
115
142
  items: items
116
143
  )
117
144
  end
145
+
146
+ def parse_multi_item_shorthand(str)
147
+ parse_multi_nums_shorthand(str) || parse_multi_chars_shorthand(str) || []
148
+ end
149
+
150
+ def parse_multi_nums_shorthand(str)
151
+ m = /^(\d+)-(\d+)$/.match(str)
152
+ unless m
153
+ return nil
154
+ end
155
+
156
+ first = m[1].to_i
157
+ last = m[2].to_i
158
+ if first > last
159
+ return nil
160
+ end
161
+
162
+ return first.upto(last).to_a
163
+ end
164
+
165
+ def parse_multi_chars_shorthand(str)
166
+ m = /^([a-z])-([a-z])$/.match(str) || /^([A-Z])-([A-Z])$/.match(str)
167
+ unless m
168
+ return nil
169
+ end
170
+
171
+ first = m[1]
172
+ last = m[2]
173
+ if first > last
174
+ return nil
175
+ end
176
+
177
+ return first.upto(last).to_a
178
+ end
118
179
  end
119
180
 
120
181
  # @param secret [Boolean]
121
182
  # @param block_delimiter [BlockDelimiter::BRACKET, BlockDelimiter::PAREN, BlockDelimiter::SPACE]
183
+ # @param takes [Integer] 何個チョイスするか
122
184
  # @param items [Array<String>]
123
- def initialize(secret:, block_delimiter:, items:)
185
+ def initialize(secret:, block_delimiter:, takes:, items:)
124
186
  @secret = secret
125
187
  @block_delimiter = block_delimiter
188
+ @takes = takes
126
189
  @items = items
127
190
  end
128
191
 
129
192
  # @param randomizer [Randomizer]
130
193
  # @return [Result]
131
194
  def roll(randomizer)
132
- index = randomizer.roll_index(@items.size)
133
- chosen = @items[index]
195
+ if @items.size > 100
196
+ return Result.new("項目数は100以下としてください")
197
+ end
198
+
199
+ items = @items.dup
200
+ chosens = []
201
+ @takes.times do
202
+ index = randomizer.roll_index(items.size)
203
+ chosens << items.delete_at(index)
204
+ end
134
205
 
135
206
  Result.new.tap do |r|
207
+ chosen = chosens.join(DELIMITER_CHAR[@block_delimiter])
208
+
136
209
  r.secret = @secret
137
210
  r.text = "(#{expr()}) > #{chosen}"
138
211
  end
139
212
  end
140
213
 
141
214
  def expr
215
+ takes = @takes == 1 ? nil : @takes
216
+
142
217
  case @block_delimiter
143
218
  when BlockDelimiter::SPACE
144
- "choice #{@items.join(' ')}"
219
+ "choice#{takes} #{@items.join(' ')}"
145
220
  when BlockDelimiter::BRACKET
146
- "choice[#{@items.join(',')}]"
221
+ "choice#{takes}[#{@items.join(',')}]"
147
222
  when BlockDelimiter::PAREN
148
- "choice(#{@items.join(',')})"
223
+ "choice#{takes}(#{@items.join(',')})"
149
224
  end
150
225
  end
151
226
  end