bcdice 3.0.0.pre.alpha.1

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 (258) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +21 -0
  3. data/.github/.codecov.yaml +10 -0
  4. data/.github/workflows/coverage.yaml +21 -0
  5. data/.github/workflows/lint.yaml +13 -0
  6. data/.github/workflows/test.yml +20 -0
  7. data/.gitignore +14 -0
  8. data/.rubocop.yml +96 -0
  9. data/.rubocop_todo.yml +113 -0
  10. data/CHANGELOG.md +868 -0
  11. data/Gemfile +11 -0
  12. data/LICENSE +29 -0
  13. data/README.md +38 -0
  14. data/ROADMAP.md +30 -0
  15. data/Rakefile +118 -0
  16. data/bcdice.gemspec +27 -0
  17. data/bin/repl.rb +21 -0
  18. data/docs/README.txt +2028 -0
  19. data/docs/dicebot_sort_key.md +72 -0
  20. data/docs/how_to_make_dicebot.md +453 -0
  21. data/lib/bcdice.rb +8 -0
  22. data/lib/bcdice/arithmetic_evaluator.rb +167 -0
  23. data/lib/bcdice/base.rb +423 -0
  24. data/lib/bcdice/command_parser.rb +242 -0
  25. data/lib/bcdice/common_command.rb +23 -0
  26. data/lib/bcdice/common_command/add_dice.rb +54 -0
  27. data/lib/bcdice/common_command/add_dice/node.rb +488 -0
  28. data/lib/bcdice/common_command/add_dice/parser.rb +282 -0
  29. data/lib/bcdice/common_command/add_dice/randomizer.rb +48 -0
  30. data/lib/bcdice/common_command/barabara_dice.rb +57 -0
  31. data/lib/bcdice/common_command/calc.rb +39 -0
  32. data/lib/bcdice/common_command/choice.rb +35 -0
  33. data/lib/bcdice/common_command/d66_dice.rb +63 -0
  34. data/lib/bcdice/common_command/reroll_dice.rb +190 -0
  35. data/lib/bcdice/common_command/upper_dice.rb +165 -0
  36. data/lib/bcdice/common_command/version.rb +26 -0
  37. data/lib/bcdice/dice_table.rb +8 -0
  38. data/lib/bcdice/dice_table/chain_table.rb +33 -0
  39. data/lib/bcdice/dice_table/d66_grid_table.rb +26 -0
  40. data/lib/bcdice/dice_table/d66_range_table.rb +26 -0
  41. data/lib/bcdice/dice_table/d66_table.rb +34 -0
  42. data/lib/bcdice/dice_table/range_table.rb +267 -0
  43. data/lib/bcdice/dice_table/roll_result.rb +43 -0
  44. data/lib/bcdice/dice_table/sai_fic_skill_table.rb +32 -0
  45. data/lib/bcdice/dice_table/table.rb +35 -0
  46. data/lib/bcdice/enum.rb +15 -0
  47. data/lib/bcdice/format.rb +36 -0
  48. data/lib/bcdice/game_system.rb +187 -0
  49. data/lib/bcdice/game_system/AFF2e.rb +141 -0
  50. data/lib/bcdice/game_system/AceKillerGene.rb +55 -0
  51. data/lib/bcdice/game_system/Airgetlamh.rb +117 -0
  52. data/lib/bcdice/game_system/Alsetto.rb +124 -0
  53. data/lib/bcdice/game_system/Alshard.rb +27 -0
  54. data/lib/bcdice/game_system/AlterRaise.rb +282 -0
  55. data/lib/bcdice/game_system/Amadeus.rb +622 -0
  56. data/lib/bcdice/game_system/Amadeus_Korean.rb +501 -0
  57. data/lib/bcdice/game_system/AnimaAnimus.rb +96 -0
  58. data/lib/bcdice/game_system/Arianrhod.rb +50 -0
  59. data/lib/bcdice/game_system/ArsMagica.rb +149 -0
  60. data/lib/bcdice/game_system/Avandner.rb +101 -0
  61. data/lib/bcdice/game_system/BBN.rb +143 -0
  62. data/lib/bcdice/game_system/BadLife.rb +435 -0
  63. data/lib/bcdice/game_system/BarnaKronika.rb +188 -0
  64. data/lib/bcdice/game_system/BattleTech.rb +530 -0
  65. data/lib/bcdice/game_system/BeastBindTrinity.rb +348 -0
  66. data/lib/bcdice/game_system/BeginningIdol.rb +2527 -0
  67. data/lib/bcdice/game_system/BeginningIdol_Korean.rb +2378 -0
  68. data/lib/bcdice/game_system/BladeOfArcana.rb +252 -0
  69. data/lib/bcdice/game_system/BlindMythos.rb +460 -0
  70. data/lib/bcdice/game_system/BloodCrusade.rb +423 -0
  71. data/lib/bcdice/game_system/BloodMoon.rb +213 -0
  72. data/lib/bcdice/game_system/CardRanker.rb +249 -0
  73. data/lib/bcdice/game_system/ChaosFlare.rb +181 -0
  74. data/lib/bcdice/game_system/Chill.rb +163 -0
  75. data/lib/bcdice/game_system/Chill3.rb +52 -0
  76. data/lib/bcdice/game_system/CodeLayerd.rb +132 -0
  77. data/lib/bcdice/game_system/ColossalHunter.rb +569 -0
  78. data/lib/bcdice/game_system/CrashWorld.rb +81 -0
  79. data/lib/bcdice/game_system/Cthulhu.rb +252 -0
  80. data/lib/bcdice/game_system/Cthulhu7th.rb +767 -0
  81. data/lib/bcdice/game_system/Cthulhu7th_ChineseTraditional.rb +386 -0
  82. data/lib/bcdice/game_system/Cthulhu7th_Korean.rb +383 -0
  83. data/lib/bcdice/game_system/CthulhuTech.rb +263 -0
  84. data/lib/bcdice/game_system/Cthulhu_ChineseTraditional.rb +243 -0
  85. data/lib/bcdice/game_system/Cthulhu_Korean.rb +240 -0
  86. data/lib/bcdice/game_system/DarkBlaze.rb +207 -0
  87. data/lib/bcdice/game_system/DarkDaysDrive.rb +425 -0
  88. data/lib/bcdice/game_system/DarkSouls.rb +91 -0
  89. data/lib/bcdice/game_system/DeadlineHeroes.rb +583 -0
  90. data/lib/bcdice/game_system/DemonParasite.rb +524 -0
  91. data/lib/bcdice/game_system/DetatokoSaga.rb +310 -0
  92. data/lib/bcdice/game_system/DetatokoSaga_Korean.rb +310 -0
  93. data/lib/bcdice/game_system/DiceBot.rb +17 -0
  94. data/lib/bcdice/game_system/DiceOfTheDead.rb +111 -0
  95. data/lib/bcdice/game_system/DoubleCross.rb +373 -0
  96. data/lib/bcdice/game_system/DoubleCross_Korean.rb +371 -0
  97. data/lib/bcdice/game_system/Dracurouge.rb +1856 -0
  98. data/lib/bcdice/game_system/Dracurouge_Korean.rb +310 -0
  99. data/lib/bcdice/game_system/DungeonsAndDragons.rb +19 -0
  100. data/lib/bcdice/game_system/EarthDawn.rb +225 -0
  101. data/lib/bcdice/game_system/EarthDawn3.rb +275 -0
  102. data/lib/bcdice/game_system/EarthDawn4.rb +272 -0
  103. data/lib/bcdice/game_system/EclipsePhase.rb +51 -0
  104. data/lib/bcdice/game_system/Elric.rb +36 -0
  105. data/lib/bcdice/game_system/Elysion.rb +1213 -0
  106. data/lib/bcdice/game_system/EmbryoMachine.rb +227 -0
  107. data/lib/bcdice/game_system/EndBreaker.rb +132 -0
  108. data/lib/bcdice/game_system/EtrianOdysseySRS.rb +27 -0
  109. data/lib/bcdice/game_system/Fiasco.rb +123 -0
  110. data/lib/bcdice/game_system/Fiasco_Korean.rb +122 -0
  111. data/lib/bcdice/game_system/FilledWith.rb +1543 -0
  112. data/lib/bcdice/game_system/FullMetalPanic.rb +27 -0
  113. data/lib/bcdice/game_system/FutariSousa.rb +994 -0
  114. data/lib/bcdice/game_system/FutariSousa_Korean.rb +994 -0
  115. data/lib/bcdice/game_system/Garako.rb +559 -0
  116. data/lib/bcdice/game_system/GardenOrder.rb +451 -0
  117. data/lib/bcdice/game_system/GehennaAn.rb +136 -0
  118. data/lib/bcdice/game_system/GeishaGirlwithKatana.rb +120 -0
  119. data/lib/bcdice/game_system/GoblinSlayer.rb +168 -0
  120. data/lib/bcdice/game_system/GoldenSkyStories.rb +77 -0
  121. data/lib/bcdice/game_system/Gorilla.rb +44 -0
  122. data/lib/bcdice/game_system/GranCrest.rb +518 -0
  123. data/lib/bcdice/game_system/Gundog.rb +52 -0
  124. data/lib/bcdice/game_system/GundogRevised.rb +294 -0
  125. data/lib/bcdice/game_system/GundogZero.rb +261 -0
  126. data/lib/bcdice/game_system/Gurps.rb +403 -0
  127. data/lib/bcdice/game_system/GurpsFW.rb +2330 -0
  128. data/lib/bcdice/game_system/HarnMaster.rb +208 -0
  129. data/lib/bcdice/game_system/HatsuneMiku.rb +706 -0
  130. data/lib/bcdice/game_system/Hieizan.rb +48 -0
  131. data/lib/bcdice/game_system/HouraiGakuen.rb +270 -0
  132. data/lib/bcdice/game_system/HuntersMoon.rb +503 -0
  133. data/lib/bcdice/game_system/Illusio.rb +89 -0
  134. data/lib/bcdice/game_system/InfiniteFantasia.rb +51 -0
  135. data/lib/bcdice/game_system/Insane.rb +822 -0
  136. data/lib/bcdice/game_system/Insane_Korean.rb +773 -0
  137. data/lib/bcdice/game_system/IthaWenUa.rb +38 -0
  138. data/lib/bcdice/game_system/JamesBond.rb +45 -0
  139. data/lib/bcdice/game_system/Kamigakari.rb +394 -0
  140. data/lib/bcdice/game_system/Kamigakari_Korean.rb +428 -0
  141. data/lib/bcdice/game_system/KanColle.rb +938 -0
  142. data/lib/bcdice/game_system/KemonoNoMori.rb +258 -0
  143. data/lib/bcdice/game_system/KillDeathBusiness.rb +1420 -0
  144. data/lib/bcdice/game_system/KillDeathBusiness_Korean.rb +1510 -0
  145. data/lib/bcdice/game_system/KurayamiCrying.rb +49 -0
  146. data/lib/bcdice/game_system/LiveraDoll.rb +375 -0
  147. data/lib/bcdice/game_system/LogHorizon.rb +3567 -0
  148. data/lib/bcdice/game_system/LogHorizon_Korean.rb +1407 -0
  149. data/lib/bcdice/game_system/LostRecord.rb +30 -0
  150. data/lib/bcdice/game_system/LostRoyal.rb +242 -0
  151. data/lib/bcdice/game_system/MagicaLogia.rb +1148 -0
  152. data/lib/bcdice/game_system/MeikyuDays.rb +415 -0
  153. data/lib/bcdice/game_system/MeikyuKingdom.rb +466 -0
  154. data/lib/bcdice/game_system/MeikyuKingdomBasic.rb +269 -0
  155. data/lib/bcdice/game_system/MetalHead.rb +249 -0
  156. data/lib/bcdice/game_system/MetalHeadExtream.rb +917 -0
  157. data/lib/bcdice/game_system/MetallicGuardian.rb +31 -0
  158. data/lib/bcdice/game_system/MonotoneMuseum.rb +354 -0
  159. data/lib/bcdice/game_system/MonotoneMuseum_Korean.rb +304 -0
  160. data/lib/bcdice/game_system/Nechronica.rb +211 -0
  161. data/lib/bcdice/game_system/Nechronica_Korean.rb +160 -0
  162. data/lib/bcdice/game_system/NeverCloud.rb +74 -0
  163. data/lib/bcdice/game_system/NightWizard.rb +231 -0
  164. data/lib/bcdice/game_system/NightWizard3rd.rb +24 -0
  165. data/lib/bcdice/game_system/NightmareHunterDeep.rb +117 -0
  166. data/lib/bcdice/game_system/NinjaSlayer.rb +274 -0
  167. data/lib/bcdice/game_system/NjslyrBattle.rb +54 -0
  168. data/lib/bcdice/game_system/Nuekagami.rb +293 -0
  169. data/lib/bcdice/game_system/OneWayHeroics.rb +134 -0
  170. data/lib/bcdice/game_system/OracleEngine.rb +265 -0
  171. data/lib/bcdice/game_system/OrgaRain.rb +77 -0
  172. data/lib/bcdice/game_system/Oukahoushin3rd.rb +162 -0
  173. data/lib/bcdice/game_system/Paradiso.rb +323 -0
  174. data/lib/bcdice/game_system/Paranoia.rb +64 -0
  175. data/lib/bcdice/game_system/ParanoiaRebooted.rb +109 -0
  176. data/lib/bcdice/game_system/ParasiteBlood.rb +231 -0
  177. data/lib/bcdice/game_system/Pathfinder.rb +21 -0
  178. data/lib/bcdice/game_system/Peekaboo.rb +324 -0
  179. data/lib/bcdice/game_system/Pendragon.rb +39 -0
  180. data/lib/bcdice/game_system/PhantasmAdventure.rb +73 -0
  181. data/lib/bcdice/game_system/Postman.rb +185 -0
  182. data/lib/bcdice/game_system/PulpCthulhu.rb +184 -0
  183. data/lib/bcdice/game_system/Raisondetre.rb +171 -0
  184. data/lib/bcdice/game_system/RecordOfLodossWar.rb +58 -0
  185. data/lib/bcdice/game_system/RecordOfSteam.rb +149 -0
  186. data/lib/bcdice/game_system/RokumonSekai2.rb +110 -0
  187. data/lib/bcdice/game_system/RoleMaster.rb +24 -0
  188. data/lib/bcdice/game_system/RuneQuest.rb +47 -0
  189. data/lib/bcdice/game_system/RyuTuber.rb +266 -0
  190. data/lib/bcdice/game_system/Ryutama.rb +199 -0
  191. data/lib/bcdice/game_system/SRS.rb +348 -0
  192. data/lib/bcdice/game_system/SamsaraBallad.rb +111 -0
  193. data/lib/bcdice/game_system/Satasupe.rb +1173 -0
  194. data/lib/bcdice/game_system/ScreamHighSchool.rb +128 -0
  195. data/lib/bcdice/game_system/SevenFortressMobius.rb +34 -0
  196. data/lib/bcdice/game_system/ShadowRun.rb +26 -0
  197. data/lib/bcdice/game_system/ShadowRun4.rb +49 -0
  198. data/lib/bcdice/game_system/ShadowRun5.rb +80 -0
  199. data/lib/bcdice/game_system/SharedFantasia.rb +69 -0
  200. data/lib/bcdice/game_system/ShinMegamiTenseiKakuseihen.rb +85 -0
  201. data/lib/bcdice/game_system/ShinkuuGakuen.rb +506 -0
  202. data/lib/bcdice/game_system/ShinobiGami.rb +680 -0
  203. data/lib/bcdice/game_system/ShoujoTenrankai.rb +677 -0
  204. data/lib/bcdice/game_system/Skynauts.rb +324 -0
  205. data/lib/bcdice/game_system/SteamPunkers.rb +485 -0
  206. data/lib/bcdice/game_system/StellarKnights.rb +856 -0
  207. data/lib/bcdice/game_system/SterileLife.rb +452 -0
  208. data/lib/bcdice/game_system/StrangerOfSwordCity.rb +103 -0
  209. data/lib/bcdice/game_system/StratoShout.rb +316 -0
  210. data/lib/bcdice/game_system/StratoShout_Korean.rb +314 -0
  211. data/lib/bcdice/game_system/Strave.rb +223 -0
  212. data/lib/bcdice/game_system/SwordWorld.rb +491 -0
  213. data/lib/bcdice/game_system/SwordWorld2_0.rb +295 -0
  214. data/lib/bcdice/game_system/SwordWorld2_5.rb +121 -0
  215. data/lib/bcdice/game_system/TherapieSein.rb +93 -0
  216. data/lib/bcdice/game_system/TokumeiTenkousei.rb +96 -0
  217. data/lib/bcdice/game_system/TokyoGhostResearch.rb +122 -0
  218. data/lib/bcdice/game_system/TokyoNova.rb +19 -0
  219. data/lib/bcdice/game_system/Torg.rb +364 -0
  220. data/lib/bcdice/game_system/Torg1_5.rb +139 -0
  221. data/lib/bcdice/game_system/TorgEternity.rb +414 -0
  222. data/lib/bcdice/game_system/TrinitySeven.rb +364 -0
  223. data/lib/bcdice/game_system/TunnelsAndTrolls.rb +287 -0
  224. data/lib/bcdice/game_system/TwilightGunsmoke.rb +527 -0
  225. data/lib/bcdice/game_system/Utakaze.rb +153 -0
  226. data/lib/bcdice/game_system/VampireTheMasquerade5th.rb +132 -0
  227. data/lib/bcdice/game_system/Villaciel.rb +555 -0
  228. data/lib/bcdice/game_system/WARPS.rb +33 -0
  229. data/lib/bcdice/game_system/WaresBlade.rb +34 -0
  230. data/lib/bcdice/game_system/Warhammer.rb +336 -0
  231. data/lib/bcdice/game_system/WitchQuest.rb +275 -0
  232. data/lib/bcdice/game_system/WorldOfDarkness.rb +135 -0
  233. data/lib/bcdice/game_system/YankeeYogSothoth.rb +440 -0
  234. data/lib/bcdice/game_system/YearZeroEngine.rb +134 -0
  235. data/lib/bcdice/game_system/ZettaiReido.rb +142 -0
  236. data/lib/bcdice/game_system/meikyu_kingdom/item_table.rb +383 -0
  237. data/lib/bcdice/game_system/meikyu_kingdom/kingdom_name_table.rb +89 -0
  238. data/lib/bcdice/game_system/meikyu_kingdom/landscape_table.rb +196 -0
  239. data/lib/bcdice/game_system/meikyu_kingdom/name_tables.rb +223 -0
  240. data/lib/bcdice/game_system/meikyu_kingdom/placename_table.rb +214 -0
  241. data/lib/bcdice/game_system/meikyu_kingdom/tables.rb +610 -0
  242. data/lib/bcdice/game_system/meikyu_kingdom/word_table.rb +117 -0
  243. data/lib/bcdice/game_system/meikyu_kingdom_basic/item_table.rb +380 -0
  244. data/lib/bcdice/game_system/meikyu_kingdom_basic/kingdom_table.rb +184 -0
  245. data/lib/bcdice/game_system/meikyu_kingdom_basic/name_table.rb +417 -0
  246. data/lib/bcdice/game_system/meikyu_kingdom_basic/table.rb +912 -0
  247. data/lib/bcdice/game_system/meikyu_kingdom_basic/word_table.rb +121 -0
  248. data/lib/bcdice/game_system/one_way_heroics/dungeon_table.rb +67 -0
  249. data/lib/bcdice/game_system/one_way_heroics/random_event_table.rb +166 -0
  250. data/lib/bcdice/game_system/one_way_heroics/tables.rb +668 -0
  251. data/lib/bcdice/loader.rb +37 -0
  252. data/lib/bcdice/normalize.rb +38 -0
  253. data/lib/bcdice/preprocessor.rb +87 -0
  254. data/lib/bcdice/randomizer.rb +137 -0
  255. data/lib/bcdice/repl.rb +155 -0
  256. data/lib/bcdice/user_defined_dice_table.rb +153 -0
  257. data/lib/bcdice/version.rb +3 -0
  258. metadata +304 -0
@@ -0,0 +1,242 @@
1
+ require "bcdice/arithmetic_evaluator"
2
+ require "bcdice/normalize"
3
+ require "bcdice/format"
4
+
5
+ module BCDice
6
+ # よくある形式のコマンドのパースを補助するクラス
7
+ #
8
+ # @example Literal by String
9
+ # parser = CommandParser.new("MC")
10
+ # parsed = parser.parse("MC+2*3@30<=10/2-3") #=> <CommandParser::Parsed>
11
+ #
12
+ # parsed.command #=> "MC"
13
+ # parsed.modify_number #=> 6
14
+ # parsed.critical #=> 30
15
+ # parsed.cmp_op #=> #>=
16
+ # parsed.target_number #=> 2
17
+ #
18
+ # @example Literal by Regexp
19
+ # parser = CommandParser.new(/^RE\d+$/)
20
+ # parsed = parser.parse("RE44+20") #=> <CommandParser::Parsed>
21
+ #
22
+ # parsed.command #=> "RE44"
23
+ # parsed.modify_number #=> 20
24
+ class CommandParser < ArithmeticEvaluator
25
+ # @param literals [Array<String, Regexp>]
26
+ def initialize(*literals)
27
+ @literals = literals
28
+ @round_type = RoundType::FLOOR
29
+ @allowed_cmp_op = nil
30
+
31
+ @enabled_question_target = false
32
+ end
33
+
34
+ # パース結果
35
+ class Parsed
36
+ # @return [String]
37
+ attr_accessor :command
38
+
39
+ # @return [Integer, nil]
40
+ attr_accessor :critical
41
+
42
+ # @return [Integer, nil]
43
+ attr_accessor :fumble
44
+
45
+ # @return [Integer, nil]
46
+ attr_accessor :dollar
47
+
48
+ # @return [Integer]
49
+ attr_accessor :modify_number
50
+
51
+ # @return [Symbol, nil]
52
+ attr_accessor :cmp_op
53
+
54
+ # @return [Integer, nil]
55
+ attr_accessor :target_number
56
+
57
+ # @param value [Boolean]
58
+ # @return [Boolean]
59
+ attr_writer :question_target
60
+
61
+ def initialize
62
+ @critical = nil
63
+ @fumble = nil
64
+ @dollar = nil
65
+ @cmp_op = nil
66
+ @target_number = nil
67
+ @question_target = false
68
+ end
69
+
70
+ # @return [Boolean]
71
+ def question_target?
72
+ @question_target
73
+ end
74
+
75
+ # @param suffix_position [Symbol] クリティカルなどの表示位置
76
+ # @return [String]
77
+ def to_s(suffix_position = :after_command)
78
+ c = @critical ? "@#{@critical}" : nil
79
+ f = @fumble ? "##{@fumble}" : nil
80
+ d = @dollar ? "$#{@dollar}" : nil
81
+ m = Format.modifier(@modify_number)
82
+ target = @question_target ? "?" : @target_number
83
+
84
+ case suffix_position
85
+ when :after_command
86
+ [@command, c, f, d, m, @cmp_op, target].join()
87
+ when :after_modify_number
88
+ [@command, m, c, f, d, @cmp_op, target].join()
89
+ when :after_target_number
90
+ [@command, m, @cmp_op, target, c, f, d].join()
91
+ end
92
+ end
93
+ end
94
+
95
+ # 特定の比較演算子のみ許可するようにする。
96
+ # 比較演算子なしを許可する場合には nil を指定してください。
97
+ #
98
+ # @param cmp_op [Array<Symbol, nil>] 許可する比較演算子の一覧
99
+ # @return [self]
100
+ def allow_cmp_op(*cmp_op)
101
+ @allowed_cmp_op = cmp_op
102
+ self
103
+ end
104
+
105
+ # 目標値 "?" を許容する
106
+ #
107
+ # @return [self]
108
+ def enable_question_target
109
+ @enabled_question_target = true
110
+ self
111
+ end
112
+
113
+ # 式をパースする
114
+ #
115
+ # @param expr [String]
116
+ # @param round_type [Symbol]
117
+ # @return [CommandParser::Parsed, nil]
118
+ def parse(expr, round_type = RoundType::FLOOR)
119
+ @tokens = tokenize(expr)
120
+ @idx = 0
121
+ @error = false
122
+ @round_type = round_type
123
+
124
+ @parsed = Parsed.new()
125
+
126
+ lhs()
127
+ if @error
128
+ return nil
129
+ end
130
+
131
+ @parsed.cmp_op = take_cmp_op()
132
+ rhs() if @parsed.cmp_op
133
+
134
+ if @idx < @tokens.size || @error
135
+ return nil
136
+ end
137
+
138
+ return @parsed
139
+ end
140
+
141
+ private
142
+
143
+ # @return [Array<String>]
144
+ def tokenize(expr)
145
+ expr.gsub(%r{[\(\)\+\-*/@#\$]|[<>!=]+}) { |e| " #{e} " }.split(" ")
146
+ end
147
+
148
+ def lhs
149
+ command = take()
150
+ unless literal?(command)
151
+ @error = true
152
+ return
153
+ end
154
+
155
+ command_suffix()
156
+
157
+ ret = 0
158
+ loop do
159
+ if consume("+")
160
+ ret += mul()
161
+ elsif consume("-")
162
+ ret -= mul()
163
+ else
164
+ break
165
+ end
166
+ end
167
+
168
+ command_suffix()
169
+
170
+ @parsed.command = command
171
+ @parsed.modify_number = ret
172
+ end
173
+
174
+ def command_suffix
175
+ loop do
176
+ if consume("@")
177
+ if @parsed.critical
178
+ @error = true
179
+ end
180
+ @parsed.critical = unary()
181
+ elsif consume("#")
182
+ if @parsed.fumble
183
+ @error = true
184
+ end
185
+ @parsed.fumble = unary()
186
+ elsif consume("$")
187
+ if @parsed.dollar
188
+ @error = true
189
+ end
190
+ @parsed.dollar = unary()
191
+ else
192
+ break
193
+ end
194
+ end
195
+ end
196
+
197
+ def rhs
198
+ if @enabled_question_target && consume("?")
199
+ @parsed.question_target = true
200
+ @parsed.target_number = 0
201
+ else
202
+ @parsed.question_target = false
203
+ @parsed.target_number = expr()
204
+ end
205
+ end
206
+
207
+ def literal?(command)
208
+ @literals.each do |lit|
209
+ case lit
210
+ when String
211
+ return true if command == lit
212
+ when Regexp
213
+ return true if command =~ lit
214
+ end
215
+ end
216
+
217
+ return false
218
+ end
219
+
220
+ def take
221
+ ret = @tokens[@idx]
222
+ @idx += 1
223
+
224
+ return ret
225
+ end
226
+
227
+ def take_cmp_op
228
+ cmp_op = Normalize.comparison_operator(take())
229
+ @error ||= denied_cmp_op?(cmp_op)
230
+
231
+ return cmp_op
232
+ end
233
+
234
+ def denied_cmp_op?(cmp_op)
235
+ if @allowed_cmp_op.nil?
236
+ false
237
+ else
238
+ !@allowed_cmp_op.include?(cmp_op)
239
+ end
240
+ end
241
+ end
242
+ end
@@ -0,0 +1,23 @@
1
+ require "bcdice/common_command/add_dice"
2
+ require "bcdice/common_command/barabara_dice"
3
+ require "bcdice/common_command/calc"
4
+ require "bcdice/common_command/choice"
5
+ require "bcdice/common_command/d66_dice"
6
+ require "bcdice/common_command/reroll_dice"
7
+ require "bcdice/common_command/upper_dice"
8
+ require "bcdice/common_command/version"
9
+
10
+ module BCDice
11
+ module CommonCommand
12
+ COMMANDS = [
13
+ AddDice,
14
+ BarabaraDice,
15
+ Calc,
16
+ Choice,
17
+ D66Dice,
18
+ RerollDice,
19
+ UpperDice,
20
+ Version,
21
+ ].freeze
22
+ end
23
+ end
@@ -0,0 +1,54 @@
1
+ require "bcdice/normalize"
2
+ require "bcdice/common_command/add_dice/parser"
3
+ require "bcdice/common_command/add_dice/randomizer"
4
+
5
+ module BCDice
6
+ module CommonCommand
7
+ class AddDice
8
+ PREFIX_PATTERN = /[\+\-\dD\(\[]+/.freeze
9
+
10
+ def initialize(command, randomizer, game_system)
11
+ @command = command
12
+ @bcdice = randomizer
13
+ @diceBot = game_system
14
+
15
+ @dice_list = []
16
+ @is_secret = false
17
+ end
18
+
19
+ def secret?
20
+ @is_secret
21
+ end
22
+
23
+ def eval()
24
+ parser = Parser.new(@command)
25
+
26
+ command = parser.parse()
27
+ if parser.error?
28
+ return nil
29
+ end
30
+
31
+ randomizer = Randomizer.new(@bcdice, @diceBot, command.cmp_op)
32
+ total = command.lhs.eval(randomizer)
33
+
34
+ output =
35
+ if randomizer.dice_list.size <= 1 && command.lhs.is_a?(Node::DiceRoll)
36
+ "(#{command}) > #{total}"
37
+ else
38
+ "(#{command}) > #{command.lhs.output} > #{total}"
39
+ end
40
+
41
+ dice_list = randomizer.dice_list
42
+
43
+ if command.cmp_op
44
+ dice_total = dice_list.inject(&:+)
45
+ output += @diceBot.check_result(total, dice_total, dice_list, randomizer.sides, command.cmp_op, command.rhs)
46
+ end
47
+
48
+ @is_secret = parser.secret?
49
+
50
+ return output
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,488 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BCDice
4
+ module CommonCommand
5
+ class AddDice
6
+ # 加算ロールの構文解析木のノードを格納するモジュール
7
+ module Node
8
+ # 加算ロールコマンドのノード。
9
+ #
10
+ # 目標値が設定されていない場合は +lhs+ のみを使用する。
11
+ # 目標値が設定されている場合は、+lhs+、+cmp_op+、+rhs+ を使用する。
12
+ class Command
13
+ # 左辺のノード
14
+ # @return [Object]
15
+ attr_reader :lhs
16
+ # 比較演算子
17
+ # @return [Symbol]
18
+ attr_reader :cmp_op
19
+ # 右辺のノード
20
+ # @return [Integer, String]
21
+ attr_reader :rhs
22
+
23
+ # ノードを初期化する
24
+ # @param [Object] lhs 左辺のノード
25
+ # @param [Symbol] cmp_op 比較演算子
26
+ # @param [Integer, String] rhs 右辺のノード
27
+ def initialize(lhs, cmp_op, rhs)
28
+ @lhs = lhs
29
+ @cmp_op = cmp_op
30
+ @rhs = rhs
31
+ end
32
+
33
+ # 文字列に変換する
34
+ # @return [String]
35
+ def to_s
36
+ @lhs.to_s + cmp_op_text + @rhs.to_s
37
+ end
38
+
39
+ # ノードのS式を返す
40
+ # @return [String]
41
+ def s_exp
42
+ if @cmp_op
43
+ "(Command (#{@cmp_op} #{@lhs.s_exp} #{@rhs}))"
44
+ else
45
+ "(Command #{@lhs.s_exp})"
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ # メッセージ中で比較演算子をどのように表示するかを返す
52
+ # @return [String]
53
+ def cmp_op_text
54
+ case @cmp_op
55
+ when :'!='
56
+ "<>"
57
+ when :==
58
+ "="
59
+ else
60
+ @cmp_op.to_s
61
+ end
62
+ end
63
+ end
64
+
65
+ # 二項演算子のノード
66
+ class BinaryOp
67
+ # ノードを初期化する
68
+ # @param [Object] lhs 左のオペランドのノード
69
+ # @param [Symbol] op 演算子
70
+ # @param [Object] rhs 右のオペランドのノード
71
+ def initialize(lhs, op, rhs)
72
+ @lhs = lhs
73
+ @op = op
74
+ @rhs = rhs
75
+ end
76
+
77
+ # ノードを評価する
78
+ #
79
+ # 左右のオペランドをそれぞれ再帰的に評価した後で、演算を行う。
80
+ #
81
+ # @param [Randomizer] randomizer ランダマイザ
82
+ # @return [Integer] 評価結果
83
+ def eval(randomizer)
84
+ lhs = @lhs.eval(randomizer)
85
+ rhs = @rhs.eval(randomizer)
86
+
87
+ return calc(lhs, rhs)
88
+ end
89
+
90
+ # 文字列に変換する
91
+ # @return [String]
92
+ def to_s
93
+ "#{@lhs}#{@op}#{@rhs}"
94
+ end
95
+
96
+ # メッセージへの出力を返す
97
+ # @return [String]
98
+ def output
99
+ "#{@lhs.output}#{@op}#{@rhs.output}"
100
+ end
101
+
102
+ # ノードのS式を返す
103
+ # @return [String]
104
+ def s_exp
105
+ "(#{op_for_s_exp} #{@lhs.s_exp} #{@rhs.s_exp})"
106
+ end
107
+
108
+ private
109
+
110
+ # 演算を行う
111
+ # @param [Integer] lhs 左のオペランド
112
+ # @param [Integer] rhs 右のオペランド
113
+ # @return [Integer] 演算の結果
114
+ def calc(lhs, rhs)
115
+ lhs.send(@op, rhs)
116
+ end
117
+
118
+ # S式で使う演算子の表現を返す
119
+ # @return [String]
120
+ def op_for_s_exp
121
+ @op
122
+ end
123
+ end
124
+
125
+ # 除算ノードの基底クラス
126
+ #
127
+ # 定数 +ROUNDING_METHOD+ で端数処理方法を示す記号
128
+ # ( +'U'+, +'R'+, +''+ ) を定義すること。
129
+ # また、除算および端数処理を行う +divide_and_round+ メソッドを実装すること。
130
+ class DivideBase < BinaryOp
131
+ # ノードを初期化する
132
+ # @param [Object] lhs 左のオペランドのノード
133
+ # @param [Object] rhs 右のオペランドのノード
134
+ def initialize(lhs, rhs)
135
+ super(lhs, :/, rhs)
136
+ end
137
+
138
+ # 文字列に変換する
139
+ #
140
+ # 通常の結果の末尾に、端数処理方法を示す記号を付加する。
141
+ #
142
+ # @return [String]
143
+ def to_s
144
+ "#{super}#{rounding_method}"
145
+ end
146
+
147
+ # メッセージへの出力を返す
148
+ #
149
+ # 通常の結果の末尾に、端数処理方法を示す記号を付加する。
150
+ #
151
+ # @return [String]
152
+ def output
153
+ "#{super}#{rounding_method}"
154
+ end
155
+
156
+ private
157
+
158
+ # 端数処理方法を示す記号を返す
159
+ # @return [String]
160
+ def rounding_method
161
+ self.class::ROUNDING_METHOD
162
+ end
163
+
164
+ # S式で使う演算子の表現を返す
165
+ # @return [String]
166
+ def op_for_s_exp
167
+ "#{@op}#{rounding_method}"
168
+ end
169
+
170
+ # 演算を行う
171
+ # @param [Integer] lhs 左のオペランド
172
+ # @param [Integer] rhs 右のオペランド
173
+ # @return [Integer] 演算の結果
174
+ def calc(lhs, rhs)
175
+ if rhs.zero?
176
+ return 1
177
+ end
178
+
179
+ return divide_and_round(lhs, rhs)
180
+ end
181
+
182
+ # 除算および端数処理を行う
183
+ # @param [Integer] _dividend 被除数
184
+ # @param [Integer] _divisor 除数(0以外)
185
+ # @return [Integer]
186
+ def divide_and_round(_dividend, _divisor)
187
+ raise NotImplementedError
188
+ end
189
+ end
190
+
191
+ # 除算(切り上げ)のノード
192
+ class DivideWithRoundingUp < DivideBase
193
+ # 端数処理方法を示す記号
194
+ ROUNDING_METHOD = "U"
195
+
196
+ private
197
+
198
+ # 除算および端数処理を行う
199
+ # @param [Integer] dividend 被除数
200
+ # @param [Integer] divisor 除数(0以外)
201
+ # @return [Integer]
202
+ def divide_and_round(dividend, divisor)
203
+ (dividend.to_f / divisor).ceil
204
+ end
205
+ end
206
+
207
+ # 除算(四捨五入)のノード
208
+ class DivideWithRoundingOff < DivideBase
209
+ # 端数処理方法を示す記号
210
+ ROUNDING_METHOD = "R"
211
+
212
+ private
213
+
214
+ # 除算および端数処理を行う
215
+ # @param [Integer] dividend 被除数
216
+ # @param [Integer] divisor 除数(0以外)
217
+ # @return [Integer]
218
+ def divide_and_round(dividend, divisor)
219
+ (dividend.to_f / divisor).round
220
+ end
221
+ end
222
+
223
+ # 除算(切り捨て)のノード
224
+ class DivideWithRoundingDown < DivideBase
225
+ # 端数処理方法を示す記号
226
+ ROUNDING_METHOD = ""
227
+
228
+ private
229
+
230
+ # 除算および端数処理を行う
231
+ # @param [Integer] dividend 被除数
232
+ # @param [Integer] divisor 除数(0以外)
233
+ # @return [Integer]
234
+ def divide_and_round(dividend, divisor)
235
+ dividend / divisor
236
+ end
237
+ end
238
+
239
+ # 符号反転のノード
240
+ class Negate
241
+ # 符号反転の対象
242
+ # @return [Object]
243
+ attr_reader :body
244
+
245
+ # ノードを初期化する
246
+ # @param [Object] body 符号反転の対象
247
+ def initialize(body)
248
+ @body = body
249
+ end
250
+
251
+ # ノードを評価する
252
+ #
253
+ # 対象オペランドを再帰的に評価した後、評価結果の符号を反転する。
254
+ #
255
+ # @param [Randomizer] randomizer ランダマイザ
256
+ # @return [Integer] 評価結果
257
+ def eval(randomizer)
258
+ -@body.eval(randomizer)
259
+ end
260
+
261
+ # 文字列に変換する
262
+ # @return [String]
263
+ def to_s
264
+ "-#{@body}"
265
+ end
266
+
267
+ # メッセージへの出力を返す
268
+ # @return [String]
269
+ def output
270
+ "-#{@body.output}"
271
+ end
272
+
273
+ # ノードのS式を返す
274
+ # @return [String]
275
+ def s_exp
276
+ "(- #{@body.s_exp})"
277
+ end
278
+ end
279
+
280
+ # ダイスロールのノード
281
+ class DiceRoll
282
+ # ノードを初期化する
283
+ # @param [Number] times ダイスを振る回数のノード
284
+ # @param [Number] sides ダイスの面数のノード
285
+ def initialize(times, sides)
286
+ @times = times.literal
287
+ @sides = sides.literal
288
+
289
+ # ダイスを振った結果の出力
290
+ @text = nil
291
+ end
292
+
293
+ # ノードを評価する(ダイスを振る)
294
+ #
295
+ # 評価結果は出目の合計値になる。
296
+ # 出目はランダマイザに記録される。
297
+ #
298
+ # @param [Randomizer] randomizer ランダマイザ
299
+ # @return [Integer] 評価結果(出目の合計値)
300
+ def eval(randomizer)
301
+ dice_groups = randomizer.roll(@times, @sides)
302
+
303
+ # TODO: Ruby 2.4以降では Array#sum が使える
304
+ total = dice_groups.flatten.reduce(0, &:+)
305
+
306
+ dice_str = dice_groups
307
+ .map { |dice_list| "[#{dice_list.join(',')}]" }
308
+ .join
309
+ @text = "#{total}#{dice_str}"
310
+
311
+ return total
312
+ end
313
+
314
+ # 文字列に変換する
315
+ # @return [String]
316
+ def to_s
317
+ "#{@times}D#{@sides}"
318
+ end
319
+
320
+ # メッセージへの出力を返す
321
+ # @return [String]
322
+ def output
323
+ @text
324
+ end
325
+
326
+ # ノードのS式を返す
327
+ # @return [String]
328
+ def s_exp
329
+ "(DiceRoll #{@times} #{@sides})"
330
+ end
331
+ end
332
+
333
+ # フィルタ処理付きダイスロールのノード。
334
+ #
335
+ # ダイスロール後、条件に従って出目を選択し、和を求める。
336
+ class DiceRollWithFilter
337
+ # フィルタの構造体
338
+ #
339
+ # 各フィルタには、あらかじめソートされた出目の配列が渡される。
340
+ #
341
+ # @!attribute abbr
342
+ # @return [Symbol] フィルタの略称
343
+ # @!attribute apply
344
+ # @return [Proc] フィルタ処理の内容
345
+ Filter = Struct.new(:abbr, :apply)
346
+
347
+ # 大きな出目から複数個取る
348
+ KEEP_HIGHEST = Filter.new(
349
+ :KH,
350
+ lambda { |sorted_values, n| sorted_values.reverse.take(n) }
351
+ ).freeze
352
+
353
+ # 小さな出目から複数個取る
354
+ KEEP_LOWEST = Filter.new(
355
+ :KL,
356
+ lambda { |sorted_values, n| sorted_values.take(n) }
357
+ ).freeze
358
+
359
+ # 大きな出目から複数個除く
360
+ DROP_HIGHEST = Filter.new(
361
+ :DH,
362
+ lambda { |sorted_values, n| sorted_values.reverse.drop(n) }
363
+ ).freeze
364
+
365
+ # 小さな出目から複数個除く
366
+ DROP_LOWEST = Filter.new(
367
+ :DL,
368
+ lambda { |sorted_values, n| sorted_values.drop(n) }
369
+ ).freeze
370
+
371
+ # ノードを初期化する
372
+ # @param [Number] times ダイスを振る回数のノード
373
+ # @param [Number] sides ダイスの面数のノード
374
+ # @param [Number] n_filtering ダイスを残す/減らす個数のノード
375
+ # @param [Filter] filter フィルタ
376
+ def initialize(times, sides, n_filtering, filter)
377
+ @times = times.literal
378
+ @sides = sides.literal
379
+ @n_filtering = n_filtering.literal
380
+ @filter = filter
381
+
382
+ # ダイスを振った結果の出力
383
+ @text = nil
384
+ end
385
+
386
+ # ノードを評価する(ダイスを振り、出目を選択して和を求める)
387
+ #
388
+ # 評価結果は出目の合計値になる。
389
+ # 出目はランダマイザに記録される。
390
+ #
391
+ # @param [Randomizer] randomizer ランダマイザ
392
+ # @return [Integer] 評価結果(出目の合計値)
393
+ def eval(randomizer)
394
+ sorted_values = randomizer.roll_once(@times, @sides).sort
395
+ total = @filter
396
+ .apply[sorted_values, @n_filtering]
397
+ .reduce(0, &:+)
398
+
399
+ @text = "#{total}[#{sorted_values.join(',')}]"
400
+
401
+ return total
402
+ end
403
+
404
+ # 文字列に変換する
405
+ # @return [String]
406
+ def to_s
407
+ "#{@times}D#{@sides}#{@filter.abbr}#{@n_filtering}"
408
+ end
409
+
410
+ # メッセージへの出力を返す
411
+ # @return [String]
412
+ def output
413
+ @text
414
+ end
415
+
416
+ # ノードのS式を返す
417
+ # @return [String]
418
+ def s_exp
419
+ "(DiceRollWithFilter #{@times} #{@sides} #{@filter.abbr.inspect} #{@n_filtering})"
420
+ end
421
+ end
422
+
423
+ # カッコで式をまとめるノード
424
+ class Parenthesis
425
+ # @param expr [Object] カッコ内のノード
426
+ def initialize(expr)
427
+ @expr = expr
428
+ end
429
+
430
+ # @param randomizer [Randomizer]
431
+ # @return [integer]
432
+ def eval(randomizer)
433
+ @expr.eval(randomizer)
434
+ end
435
+
436
+ # @return [String]
437
+ def to_s
438
+ "(#{@expr})"
439
+ end
440
+
441
+ # @return [String]
442
+ def output
443
+ "(#{@expr.output})"
444
+ end
445
+
446
+ # @return [String] S式
447
+ def s_exp
448
+ "(Parenthesis #{@expr.s_exp})"
449
+ end
450
+ end
451
+
452
+ # 数値のノード
453
+ class Number
454
+ # 値
455
+ # @return [Integer]
456
+ attr_reader :literal
457
+
458
+ # ノードを初期化する
459
+ # @param [Integer] literal 値
460
+ def initialize(literal)
461
+ @literal = literal
462
+ end
463
+
464
+ # 符号を反転した結果の数値ノードを返す
465
+ # @return [Number]
466
+ def negate
467
+ Number.new(-@literal)
468
+ end
469
+
470
+ # ノードを評価する
471
+ # @return [Integer] 格納している値
472
+ def eval(_randomizer)
473
+ @literal
474
+ end
475
+
476
+ # 文字列に変換する
477
+ # @return [String]
478
+ def to_s
479
+ @literal.to_s
480
+ end
481
+
482
+ alias output to_s
483
+ alias s_exp to_s
484
+ end
485
+ end
486
+ end
487
+ end
488
+ end