bcdice 3.0.0.pre.alpha.1 → 3.0.0.pre.alpha.2

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 (255) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +1 -1
  3. data/.rubocop.yml +12 -0
  4. data/.rubocop_todo.yml +1 -0
  5. data/CHANGELOG.md +34 -0
  6. data/Gemfile +6 -3
  7. data/Rakefile +52 -18
  8. data/bcdice.gemspec +2 -0
  9. data/i18n/Amadeus/ja_jp.yml +386 -0
  10. data/i18n/Amadeus/ko_kr.yml +148 -0
  11. data/i18n/Cthulhu/ja_jp.yml +11 -0
  12. data/i18n/Cthulhu/ko_kr.yml +11 -0
  13. data/i18n/Cthulhu/zh_hant.yml +11 -0
  14. data/i18n/DetatokoSaga/ja_jp.yml +74 -0
  15. data/i18n/DetatokoSaga/ko_kr.yml +74 -0
  16. data/i18n/DoubleCross/ja_jp.yml +53 -0
  17. data/i18n/DoubleCross/ko_kr.yml +53 -0
  18. data/i18n/Dracurouge/ja_jp.yml +1374 -0
  19. data/i18n/Dracurouge/ko_kr.yml +295 -0
  20. data/i18n/Fiasco/ja_jp.yml +10 -0
  21. data/i18n/Fiasco/ko_kr.yml +10 -0
  22. data/i18n/FutariSousa/ja_jp.yml +720 -0
  23. data/i18n/FutariSousa/ko_kr.yml +746 -0
  24. data/i18n/Insane/ja_jp.yml +454 -0
  25. data/i18n/Insane/ko_kr.yml +502 -0
  26. data/i18n/Kamigakari/ja_jp.yml +276 -0
  27. data/i18n/Kamigakari/ko_kr.yml +204 -0
  28. data/i18n/KillDeathBusiness/ja_jp.yml +1092 -0
  29. data/i18n/KillDeathBusiness/ko_kr.yml +1062 -0
  30. data/i18n/LogHorizon/ja_jp.yml +3082 -0
  31. data/i18n/LogHorizon/ko_kr.yml +923 -0
  32. data/i18n/MagicaLogia/ja_jp.yml +564 -0
  33. data/i18n/MonotoneMuseum/ja_jp.yml +525 -0
  34. data/i18n/MonotoneMuseum/ko_kr.yml +89 -0
  35. data/i18n/Nechronica/ja_jp.yml +57 -0
  36. data/i18n/Nechronica/ko_kr.yml +14 -0
  37. data/i18n/StratoShout/ja_jp.yml +160 -0
  38. data/i18n/StratoShout/ko_kr.yml +160 -0
  39. data/i18n/ja_jp.yml +5 -0
  40. data/i18n/ko_kr.yml +7 -0
  41. data/i18n/zh_hant.yml +3 -0
  42. data/lib/bcdice/arithmetic/node.rb +154 -0
  43. data/lib/bcdice/base.rb +87 -101
  44. data/lib/bcdice/common_command.rb +2 -0
  45. data/lib/bcdice/common_command/add_dice.rb +5 -41
  46. data/lib/bcdice/common_command/add_dice/node.rb +92 -16
  47. data/lib/bcdice/common_command/add_dice/parser.rb +374 -274
  48. data/lib/bcdice/common_command/add_dice/parser.y +153 -0
  49. data/lib/bcdice/common_command/add_dice/randomizer.rb +25 -33
  50. data/lib/bcdice/common_command/barabara_dice.rb +6 -47
  51. data/lib/bcdice/common_command/barabara_dice/node.rb +84 -0
  52. data/lib/bcdice/common_command/barabara_dice/parser.rb +336 -0
  53. data/lib/bcdice/common_command/barabara_dice/parser.y +107 -0
  54. data/lib/bcdice/common_command/calc.rb +7 -30
  55. data/lib/bcdice/common_command/calc/node.rb +30 -0
  56. data/lib/bcdice/common_command/calc/parser.rb +271 -0
  57. data/lib/bcdice/common_command/calc/parser.y +79 -0
  58. data/lib/bcdice/common_command/choice.rb +137 -19
  59. data/lib/bcdice/common_command/d66_dice.rb +48 -34
  60. data/lib/bcdice/common_command/lexer.rb +46 -0
  61. data/lib/bcdice/common_command/repeat.rb +102 -0
  62. data/lib/bcdice/common_command/reroll_dice.rb +8 -158
  63. data/lib/bcdice/common_command/reroll_dice/node.rb +198 -0
  64. data/lib/bcdice/common_command/reroll_dice/parser.rb +421 -0
  65. data/lib/bcdice/common_command/reroll_dice/parser.y +158 -0
  66. data/lib/bcdice/common_command/upper_dice.rb +10 -129
  67. data/lib/bcdice/common_command/upper_dice/node.rb +181 -0
  68. data/lib/bcdice/common_command/upper_dice/parser.rb +412 -0
  69. data/lib/bcdice/common_command/upper_dice/parser.y +143 -0
  70. data/lib/bcdice/common_command/version.rb +9 -17
  71. data/lib/bcdice/deprecated/checker.rb +78 -0
  72. data/lib/bcdice/dice_table/d66_grid_table.rb +8 -0
  73. data/lib/bcdice/dice_table/d66_table.rb +10 -0
  74. data/lib/bcdice/dice_table/sai_fic_skill_table.rb +75 -17
  75. data/lib/bcdice/dice_table/sai_fic_skill_table/category.rb +25 -0
  76. data/lib/bcdice/dice_table/sai_fic_skill_table/skill.rb +23 -0
  77. data/lib/bcdice/dice_table/table.rb +8 -0
  78. data/lib/bcdice/game_system.rb +7 -1
  79. data/lib/bcdice/game_system/AFF2e.rb +1 -1
  80. data/lib/bcdice/game_system/AceKillerGene.rb +2 -2
  81. data/lib/bcdice/game_system/Airgetlamh.rb +3 -3
  82. data/lib/bcdice/game_system/Alsetto.rb +1 -1
  83. data/lib/bcdice/game_system/Alshard.rb +1 -1
  84. data/lib/bcdice/game_system/AlterRaise.rb +2 -2
  85. data/lib/bcdice/game_system/Amadeus.rb +46 -507
  86. data/lib/bcdice/game_system/Amadeus_Korean.rb +12 -456
  87. data/lib/bcdice/game_system/AnimaAnimus.rb +1 -1
  88. data/lib/bcdice/game_system/Arianrhod.rb +0 -1
  89. data/lib/bcdice/game_system/ArsMagica.rb +1 -1
  90. data/lib/bcdice/game_system/Avandner.rb +1 -1
  91. data/lib/bcdice/game_system/BBN.rb +1 -1
  92. data/lib/bcdice/game_system/BadLife.rb +1 -1
  93. data/lib/bcdice/game_system/BarnaKronika.rb +1 -1
  94. data/lib/bcdice/game_system/BeastBindTrinity.rb +1 -2
  95. data/lib/bcdice/game_system/BeginningIdol.rb +2 -3
  96. data/lib/bcdice/game_system/BeginningIdol_Korean.rb +2 -3
  97. data/lib/bcdice/game_system/BladeOfArcana.rb +2 -2
  98. data/lib/bcdice/game_system/BlindMythos.rb +1 -1
  99. data/lib/bcdice/game_system/BloodCrusade.rb +0 -1
  100. data/lib/bcdice/game_system/BloodMoon.rb +0 -1
  101. data/lib/bcdice/game_system/CardRanker.rb +1 -2
  102. data/lib/bcdice/game_system/ChaosFlare.rb +1 -1
  103. data/lib/bcdice/game_system/Chill.rb +1 -1
  104. data/lib/bcdice/game_system/CodeLayerd.rb +1 -1
  105. data/lib/bcdice/game_system/ColossalHunter.rb +1 -2
  106. data/lib/bcdice/game_system/Comes.rb +50 -0
  107. data/lib/bcdice/game_system/CrashWorld.rb +1 -1
  108. data/lib/bcdice/game_system/Cthulhu.rb +94 -97
  109. data/lib/bcdice/game_system/Cthulhu7th.rb +108 -67
  110. data/lib/bcdice/game_system/Cthulhu7th_ChineseTraditional.rb +1 -1
  111. data/lib/bcdice/game_system/Cthulhu7th_Korean.rb +1 -1
  112. data/lib/bcdice/game_system/CthulhuTech.rb +8 -14
  113. data/lib/bcdice/game_system/Cthulhu_ChineseTraditional.rb +7 -190
  114. data/lib/bcdice/game_system/Cthulhu_Korean.rb +5 -186
  115. data/lib/bcdice/game_system/DarkBlaze.rb +9 -5
  116. data/lib/bcdice/game_system/DarkDaysDrive.rb +17 -15
  117. data/lib/bcdice/game_system/DarkSouls.rb +1 -1
  118. data/lib/bcdice/game_system/DeadlineHeroes.rb +2 -2
  119. data/lib/bcdice/game_system/DemonParasite.rb +1 -2
  120. data/lib/bcdice/game_system/DetatokoSaga.rb +48 -149
  121. data/lib/bcdice/game_system/DetatokoSaga_Korean.rb +6 -274
  122. data/lib/bcdice/game_system/DiceOfTheDead.rb +9 -6
  123. data/lib/bcdice/game_system/DoubleCross.rb +102 -81
  124. data/lib/bcdice/game_system/DoubleCross_Korean.rb +13 -330
  125. data/lib/bcdice/game_system/Dracurouge.rb +376 -1678
  126. data/lib/bcdice/game_system/Dracurouge_Korean.rb +16 -271
  127. data/lib/bcdice/game_system/EarthDawn.rb +1 -1
  128. data/lib/bcdice/game_system/EarthDawn3.rb +1 -1
  129. data/lib/bcdice/game_system/EarthDawn4.rb +1 -1
  130. data/lib/bcdice/game_system/Elysion.rb +3 -4
  131. data/lib/bcdice/game_system/EmbryoMachine.rb +1 -1
  132. data/lib/bcdice/game_system/Emoklore.rb +131 -0
  133. data/lib/bcdice/game_system/EndBreaker.rb +1 -2
  134. data/lib/bcdice/game_system/EtrianOdysseySRS.rb +1 -1
  135. data/lib/bcdice/game_system/Fiasco.rb +87 -70
  136. data/lib/bcdice/game_system/Fiasco_Korean.rb +7 -95
  137. data/lib/bcdice/game_system/FilledWith.rb +462 -566
  138. data/lib/bcdice/game_system/FullMetalPanic.rb +1 -1
  139. data/lib/bcdice/game_system/FutariSousa.rb +103 -938
  140. data/lib/bcdice/game_system/FutariSousa_Korean.rb +7 -948
  141. data/lib/bcdice/game_system/{Gurps.rb → GURPS.rb} +184 -217
  142. data/lib/bcdice/game_system/Garako.rb +131 -39
  143. data/lib/bcdice/game_system/GardenOrder.rb +2 -2
  144. data/lib/bcdice/game_system/GehennaAn.rb +1 -1
  145. data/lib/bcdice/game_system/GeishaGirlwithKatana.rb +1 -1
  146. data/lib/bcdice/game_system/GoblinSlayer.rb +1 -1
  147. data/lib/bcdice/game_system/GoldenSkyStories.rb +1 -1
  148. data/lib/bcdice/game_system/Gorilla.rb +1 -1
  149. data/lib/bcdice/game_system/GranCrest.rb +2 -3
  150. data/lib/bcdice/game_system/GundogRevised.rb +1 -1
  151. data/lib/bcdice/game_system/GundogZero.rb +1 -1
  152. data/lib/bcdice/game_system/GurpsFW.rb +2 -3
  153. data/lib/bcdice/game_system/HarnMaster.rb +2 -2
  154. data/lib/bcdice/game_system/HatsuneMiku.rb +1 -2
  155. data/lib/bcdice/game_system/HouraiGakuen.rb +1 -1
  156. data/lib/bcdice/game_system/HuntersMoon.rb +1 -2
  157. data/lib/bcdice/game_system/Illusio.rb +2 -2
  158. data/lib/bcdice/game_system/Insane.rb +90 -717
  159. data/lib/bcdice/game_system/Insane_Korean.rb +7 -719
  160. data/lib/bcdice/game_system/Kamigakari.rb +47 -242
  161. data/lib/bcdice/game_system/Kamigakari_Korean.rb +6 -394
  162. data/lib/bcdice/game_system/KanColle.rb +3 -3
  163. data/lib/bcdice/game_system/KemonoNoMori.rb +1 -1
  164. data/lib/bcdice/game_system/KillDeathBusiness.rb +162 -1144
  165. data/lib/bcdice/game_system/KillDeathBusiness_Korean.rb +6 -1459
  166. data/lib/bcdice/game_system/KurayamiCrying.rb +1 -1
  167. data/lib/bcdice/game_system/LiveraDoll.rb +2 -2
  168. data/lib/bcdice/game_system/LogHorizon.rb +350 -3379
  169. data/lib/bcdice/game_system/LogHorizon_Korean.rb +55 -1357
  170. data/lib/bcdice/game_system/LostRecord.rb +0 -1
  171. data/lib/bcdice/game_system/LostRoyal.rb +1 -2
  172. data/lib/bcdice/game_system/MagicaLogia.rb +158 -1044
  173. data/lib/bcdice/game_system/MeikyuDays.rb +1 -2
  174. data/lib/bcdice/game_system/MeikyuKingdom.rb +4 -4
  175. data/lib/bcdice/game_system/MeikyuKingdomBasic.rb +4 -5
  176. data/lib/bcdice/game_system/MetalHead.rb +1 -1
  177. data/lib/bcdice/game_system/MetalHeadExtream.rb +2 -2
  178. data/lib/bcdice/game_system/MetallicGuardian.rb +1 -1
  179. data/lib/bcdice/game_system/MonotoneMuseum.rb +38 -239
  180. data/lib/bcdice/game_system/MonotoneMuseum_Korean.rb +10 -270
  181. data/lib/bcdice/game_system/NSSQ.rb +159 -0
  182. data/lib/bcdice/game_system/Nechronica.rb +34 -73
  183. data/lib/bcdice/game_system/Nechronica_Korean.rb +11 -128
  184. data/lib/bcdice/game_system/NeverCloud.rb +1 -1
  185. data/lib/bcdice/game_system/NightWizard.rb +1 -1
  186. data/lib/bcdice/game_system/NightWizard3rd.rb +1 -1
  187. data/lib/bcdice/game_system/NinjaSlayer.rb +12 -11
  188. data/lib/bcdice/game_system/NjslyrBattle.rb +1 -1
  189. data/lib/bcdice/game_system/OneWayHeroics.rb +1 -2
  190. data/lib/bcdice/game_system/OracleEngine.rb +1 -1
  191. data/lib/bcdice/game_system/OrgaRain.rb +2 -2
  192. data/lib/bcdice/game_system/Paradiso.rb +1 -1
  193. data/lib/bcdice/game_system/Paranoia.rb +1 -1
  194. data/lib/bcdice/game_system/ParanoiaRebooted.rb +1 -1
  195. data/lib/bcdice/game_system/ParasiteBlood.rb +1 -1
  196. data/lib/bcdice/game_system/Peekaboo.rb +22 -122
  197. data/lib/bcdice/game_system/Postman.rb +2 -2
  198. data/lib/bcdice/game_system/PulpCthulhu.rb +3 -3
  199. data/lib/bcdice/game_system/Raisondetre.rb +2 -2
  200. data/lib/bcdice/game_system/RecordOfLodossWar.rb +1 -1
  201. data/lib/bcdice/game_system/RecordOfSteam.rb +1 -1
  202. data/lib/bcdice/game_system/RokumonSekai2.rb +1 -1
  203. data/lib/bcdice/game_system/RuinBreakers.rb +296 -0
  204. data/lib/bcdice/game_system/Ryutama.rb +1 -1
  205. data/lib/bcdice/game_system/SRS.rb +1 -2
  206. data/lib/bcdice/game_system/SamsaraBallad.rb +1 -1
  207. data/lib/bcdice/game_system/Satasupe.rb +1 -2
  208. data/lib/bcdice/game_system/ScreamHighSchool.rb +2 -2
  209. data/lib/bcdice/game_system/SevenFortressMobius.rb +1 -1
  210. data/lib/bcdice/game_system/ShadowRun5.rb +2 -2
  211. data/lib/bcdice/game_system/SharedFantasia.rb +1 -1
  212. data/lib/bcdice/game_system/ShinMegamiTenseiKakuseihen.rb +1 -1
  213. data/lib/bcdice/game_system/ShinkuuGakuen.rb +2 -2
  214. data/lib/bcdice/game_system/ShinobiGami.rb +1 -2
  215. data/lib/bcdice/game_system/ShoujoTenrankai.rb +2 -2
  216. data/lib/bcdice/game_system/Skynauts.rb +1 -1
  217. data/lib/bcdice/game_system/SteamPunkers.rb +1 -1
  218. data/lib/bcdice/game_system/StellarKnights.rb +1 -2
  219. data/lib/bcdice/game_system/SterileLife.rb +1 -1
  220. data/lib/bcdice/game_system/StrangerOfSwordCity.rb +1 -2
  221. data/lib/bcdice/game_system/StratoShout.rb +44 -247
  222. data/lib/bcdice/game_system/StratoShout_Korean.rb +6 -282
  223. data/lib/bcdice/game_system/Strave.rb +2 -2
  224. data/lib/bcdice/game_system/SwordWorld.rb +8 -9
  225. data/lib/bcdice/game_system/SwordWorld2_0.rb +2 -3
  226. data/lib/bcdice/game_system/SwordWorld2_5.rb +2 -3
  227. data/lib/bcdice/game_system/TherapieSein.rb +1 -1
  228. data/lib/bcdice/game_system/TokumeiTenkousei.rb +1 -1
  229. data/lib/bcdice/game_system/TokyoGhostResearch.rb +2 -2
  230. data/lib/bcdice/game_system/Torg.rb +2 -2
  231. data/lib/bcdice/game_system/Torg1_5.rb +1 -1
  232. data/lib/bcdice/game_system/TorgEternity.rb +1 -1
  233. data/lib/bcdice/game_system/TrinitySeven.rb +1 -1
  234. data/lib/bcdice/game_system/TunnelsAndTrolls.rb +1 -1
  235. data/lib/bcdice/game_system/TwilightGunsmoke.rb +1 -2
  236. data/lib/bcdice/game_system/UnsungDuet.rb +139 -0
  237. data/lib/bcdice/game_system/Utakaze.rb +1 -1
  238. data/lib/bcdice/game_system/VampireTheMasquerade5th.rb +1 -1
  239. data/lib/bcdice/game_system/Villaciel.rb +1 -2
  240. data/lib/bcdice/game_system/Warhammer.rb +1 -1
  241. data/lib/bcdice/game_system/Warhammer4.rb +329 -0
  242. data/lib/bcdice/game_system/WitchQuest.rb +1 -1
  243. data/lib/bcdice/game_system/WorldOfDarkness.rb +1 -1
  244. data/lib/bcdice/game_system/YankeeYogSothoth.rb +15 -15
  245. data/lib/bcdice/game_system/YearZeroEngine.rb +1 -1
  246. data/lib/bcdice/game_system/ZettaiReido.rb +1 -1
  247. data/lib/bcdice/game_system/filled_with/cook_tables.rb +119 -0
  248. data/lib/bcdice/game_system/filled_with/lot_tables.rb +155 -0
  249. data/lib/bcdice/loader.rb +6 -8
  250. data/lib/bcdice/preprocessor.rb +15 -40
  251. data/lib/bcdice/repl.rb +1 -1
  252. data/lib/bcdice/result.rb +83 -0
  253. data/lib/bcdice/translate.rb +11 -0
  254. data/lib/bcdice/version.rb +1 -1
  255. metadata +80 -4
@@ -0,0 +1,79 @@
1
+ class BCDice::CommonCommand::Calc::Parser
2
+ token NUMBER R U C F S PLUS MINUS ASTERISK SLASH PARENL PARENR
3
+
4
+ rule
5
+ expr: secret C add
6
+ {
7
+ result = Node::Command.new(
8
+ secret: val[0],
9
+ expr: val[2]
10
+ )
11
+ }
12
+
13
+ secret: /* none */
14
+ { result = false }
15
+ | S
16
+ { result = true }
17
+
18
+ add: add PLUS mul
19
+ { result = Arithmetic::Node::BinaryOp.new(val[0], :+, val[2]) }
20
+ | add MINUS mul
21
+ { result = Arithmetic::Node::BinaryOp.new(val[0], :-, val[2]) }
22
+ | mul
23
+
24
+ mul: mul ASTERISK unary
25
+ { result = Arithmetic::Node::BinaryOp.new(val[0], :*, val[2]) }
26
+ | mul SLASH unary round_type
27
+ {
28
+ divied_class = val[3]
29
+ result = divied_class.new(val[0], val[2])
30
+ }
31
+ | unary
32
+
33
+ round_type: /* none */
34
+ { result = Arithmetic::Node::DivideWithGameSystemDefault }
35
+ | U
36
+ { result = Arithmetic::Node::DivideWithCeil }
37
+ | C
38
+ { result = Arithmetic::Node::DivideWithCeil }
39
+ | R
40
+ { result = Arithmetic::Node::DivideWithRound }
41
+ | F
42
+ { result = Arithmetic::Node::DivideWithFloor }
43
+
44
+ unary: PLUS unary
45
+ { result = val[1] }
46
+ | MINUS unary
47
+ { result = Arithmetic::Node::Negative.new(val[1]) }
48
+ | term
49
+
50
+ term: PARENL add PARENR
51
+ { result = val[1] }
52
+ | NUMBER
53
+ { result = Arithmetic::Node::Number.new(val[0]) }
54
+ end
55
+
56
+ ---- header
57
+
58
+ require "bcdice/common_command/lexer"
59
+ require "bcdice/common_command/calc/node"
60
+ require "bcdice/arithmetic/node"
61
+
62
+ ---- inner
63
+
64
+ def self.parse(source)
65
+ new.parse(source)
66
+ end
67
+
68
+ def parse(source)
69
+ @lexer = Lexer.new(source)
70
+ do_parse()
71
+ rescue ParseError
72
+ nil
73
+ end
74
+
75
+ private
76
+
77
+ def next_token
78
+ @lexer.next_token
79
+ end
@@ -1,34 +1,152 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "strscan"
4
+
1
5
  module BCDice
2
6
  module CommonCommand
7
+ # チョイスコマンド
8
+ #
9
+ # 列挙された項目の中から一つを選んで出力する。
10
+ #
11
+ # フォーマットは以下の通り
12
+ # choice[A,B,C,D]
13
+ # choice(A,B,C,D)
14
+ # choice A B C D
15
+ # choice(新クトゥルフ神話TRPG, ソード・ワールド2.5, Dungeons & Dragons)
16
+ #
17
+ # "choice"の次の文字によって区切り文字が変化する
18
+ # "[" -> "," で区切る
19
+ # "(" -> "," で区切る
20
+ # " " -> /\s+/ にマッチする文字列で区切る
21
+ #
22
+ # 各項目の前後に空白文字があった場合は除去される
23
+ # choice[A, B, C , D ] は choice[A,B,C,D] と等価
24
+ #
25
+ # 項目が空文字列である場合、その項目は無視する
26
+ # choice[A,,C] は choice[A,C] と等価
27
+ #
28
+ # フォーマットを選ぶことで、項目の文字列に()や,を含めることができる
29
+ # choice A,B X,Y -> "A,B" と "X,Y" から選ぶ
30
+ # choice(A[], B[], C[]) -> "A[]", "B[]", "C[]" から選ぶ
31
+ # choice[A(), B(), C()] -> "A()", "B()", "C()" から選ぶ
3
32
  class Choice
4
33
  PREFIX_PATTERN = /choice/.freeze
5
34
 
6
- def initialize(command, randomizer, game_system)
7
- @command = command
8
- @randomizer = randomizer
9
- @game_system = game_system
10
-
11
- @is_secret = false
35
+ module BlockDelimiter
36
+ BRACKET = :bracket # "["
37
+ PAREN = :paren # "("
38
+ SPACE = :space # /\s/
12
39
  end
13
40
 
14
- def secret?
15
- @is_secret
16
- end
41
+ DELIMITER = {
42
+ bracket: /,/,
43
+ paren: /,/,
44
+ space: /\s+/,
45
+ }.freeze
46
+
47
+ TERMINATION = {
48
+ bracket: /\]/,
49
+ paren: /\)/,
50
+ space: /$/,
51
+ }.freeze
52
+
53
+ SUFFIX = {
54
+ bracket: "]",
55
+ paren: ")",
56
+ space: "",
57
+ }.freeze
17
58
 
18
- def eval
19
- m = /^(S)?choice\[([^,]+(?:,[^,]+)+)\]$/i.match(@command)
20
- unless m
21
- return nil
59
+ class << self
60
+ # @param command [String]
61
+ # @param _game_system [BCDice::Base]
62
+ # @param randomizer [Randomizer]
63
+ # @return [Result, nil]
64
+ def eval(command, _game_system, randomizer)
65
+ cmd = parse(command)
66
+ cmd&.roll(randomizer)
22
67
  end
23
68
 
24
- @is_secret = !m[1].nil?
25
- items = m[2].split(",").map(&:strip)
69
+ private
70
+
71
+ def parse(command)
72
+ scanner = StringScanner.new(command)
73
+ scanner.skip(/\s+/)
74
+
75
+ secret = !scanner.scan(/S/i).nil?
76
+ unless scanner.scan(/choice/i)
77
+ return nil
78
+ end
79
+
80
+ type =
81
+ case scanner.scan(/\(|\[|\s+/)
82
+ when "["
83
+ BlockDelimiter::BRACKET
84
+ when "("
85
+ BlockDelimiter::PAREN
86
+ when String
87
+ BlockDelimiter::SPACE
88
+ else
89
+ return nil
90
+ end
91
+
92
+ delimiter = DELIMITER[type]
93
+ termination = TERMINATION[type]
94
+
95
+ items = []
96
+ while (item = scanner.scan_until(delimiter))
97
+ items.push(item.delete_suffix(","))
98
+ end
99
+
100
+ last_item = scanner.scan_until(termination)
101
+ unless last_item
102
+ return nil
103
+ end
26
104
 
27
- index = @randomizer.roll_index(items.size)
28
- chosen = items[index]
105
+ items.push(last_item.delete_suffix(SUFFIX[type]))
29
106
 
30
- expr = "choice[#{items.join(',')}]"
31
- return "(#{expr}) > #{chosen}"
107
+ items = items.map(&:strip).reject(&:empty?)
108
+ if items.empty?
109
+ return nil
110
+ end
111
+
112
+ new(
113
+ secret: secret,
114
+ block_delimiter: type,
115
+ items: items
116
+ )
117
+ end
118
+ end
119
+
120
+ # @param secret [Boolean]
121
+ # @param block_delimiter [BlockDelimiter::BRACKET, BlockDelimiter::PAREN, BlockDelimiter::SPACE]
122
+ # @param items [Array<String>]
123
+ def initialize(secret:, block_delimiter:, items:)
124
+ @secret = secret
125
+ @block_delimiter = block_delimiter
126
+ @items = items
127
+ end
128
+
129
+ # @param randomizer [Randomizer]
130
+ # @return [Result]
131
+ def roll(randomizer)
132
+ index = randomizer.roll_index(@items.size)
133
+ chosen = @items[index]
134
+
135
+ Result.new.tap do |r|
136
+ r.secret = @secret
137
+ r.text = "(#{expr()}) > #{chosen}"
138
+ end
139
+ end
140
+
141
+ def expr
142
+ case @block_delimiter
143
+ when BlockDelimiter::SPACE
144
+ "choice #{@items.join(' ')}"
145
+ when BlockDelimiter::BRACKET
146
+ "choice[#{@items.join(',')}]"
147
+ when BlockDelimiter::PAREN
148
+ "choice(#{@items.join(',')})"
149
+ end
32
150
  end
33
151
  end
34
152
  end
@@ -3,61 +3,75 @@ module BCDice
3
3
  class D66Dice
4
4
  PREFIX_PATTERN = /D66/.freeze
5
5
 
6
- def initialize(command, randomizer, game_system)
7
- @command = command
8
- @randomizer = randomizer
9
- @game_system = game_system
6
+ class << self
7
+ # @param command [String]
8
+ # @param game_system [BCDice::Base]
9
+ # @param randomizer [Randomizer]
10
+ # @return [Result, nil]
11
+ def eval(command, game_system, randomizer)
12
+ cmd = parse(command, game_system)
13
+ cmd&.eval(randomizer)
14
+ end
10
15
 
11
- @is_secret = false
12
- end
16
+ private
13
17
 
14
- def secret?
15
- @is_secret
16
- end
18
+ def parse(command, game_system)
19
+ command = command.split(" ", 2).first
17
20
 
18
- def eval
19
- unless @game_system.enabled_d66?
20
- return nil
21
+ m = /^(S)?D66([ANS])?$/i.match(command)
22
+ return nil unless m
23
+
24
+ new(
25
+ secret: !m[1].nil?,
26
+ sort_type: sort_type_from_suffix(m[2]) || game_system.d66_sort_type,
27
+ suffix: m[2]
28
+ )
21
29
  end
22
30
 
23
- m = /^(S)?D66([ANS])?$/i.match(@command)
24
- unless m
25
- return nil
31
+ def sort_type_from_suffix(suffix)
32
+ case suffix
33
+ when "A", "S"
34
+ D66SortType::ASC
35
+ when "N"
36
+ D66SortType::NO_SORT
37
+ end
26
38
  end
39
+ end
27
40
 
28
- @is_secret = !m[1].nil?
29
- @sort_type = sort_type_from_suffix(m[2])
41
+ # @param secret [Boolean]
42
+ # @param sort_type [Symbol]
43
+ # @param suffix [String, nil]
44
+ def initialize(secret:, sort_type:, suffix:)
45
+ @secret = secret
46
+ @sort_type = sort_type
47
+ @suffix = suffix
48
+ end
30
49
 
31
- value = roll()
50
+ # @param randomizer [Randomizer]
51
+ # @return [Result]
52
+ def eval(randomizer)
53
+ value = roll(randomizer)
32
54
 
33
- return "(D66#{m[2]}) > #{value}"
55
+ Result.new.tap do |r|
56
+ r.secret = @secret
57
+ r.text = "(D66#{@suffix}) > #{value}"
58
+ end
34
59
  end
35
60
 
36
61
  private
37
62
 
38
- def roll()
39
- dice_list = Array.new(2) { @randomizer.roll_once(6) }
63
+ def roll(randomizer)
64
+ dice_list = Array.new(2) { randomizer.roll_once(6) }
40
65
 
41
66
  case @sort_type
42
- when :asc
67
+ when D66SortType::ASC
43
68
  dice_list.sort!
44
- when :desc
69
+ when D66SortType::DESC
45
70
  dice_list.sort!.reverse!
46
71
  end
47
72
 
48
73
  return dice_list[0] * 10 + dice_list[1]
49
74
  end
50
-
51
- def sort_type_from_suffix(suffix)
52
- case suffix
53
- when "A", "S"
54
- :asc
55
- when "N"
56
- :no_sort
57
- else
58
- @game_system.d66_sort_type
59
- end
60
- end
61
75
  end
62
76
  end
63
77
  end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "strscan"
4
+ require "bcdice/normalize"
5
+
6
+ module BCDice
7
+ module CommonCommand
8
+ class Lexer
9
+ SYMBOLS = {
10
+ "+" => :PLUS,
11
+ "-" => :MINUS,
12
+ "*" => :ASTERISK,
13
+ "/" => :SLASH,
14
+ "(" => :PARENL,
15
+ ")" => :PARENR,
16
+ "[" => :BRACKETL,
17
+ "]" => :BRACKETR,
18
+ "?" => :QUESTION,
19
+ "@" => :AT,
20
+ }.freeze
21
+
22
+ def initialize(source)
23
+ source = source.split(" ", 2).first
24
+ @scanner = StringScanner.new(source)
25
+ end
26
+
27
+ def next_token
28
+ return [false, "$"] if @scanner.eos?
29
+
30
+ if (number = @scanner.scan(/\d+/))
31
+ [:NUMBER, number.to_i]
32
+ elsif (cmp_op = @scanner.scan(/[<>!=]+/))
33
+ [:CMP_OP, Normalize.comparison_operator(cmp_op)]
34
+ else
35
+ char = @scanner.getch.upcase
36
+ type = SYMBOLS[char] || char.to_sym
37
+ [type, char]
38
+ end
39
+ end
40
+
41
+ def source
42
+ @scanner.string
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "strscan"
4
+
5
+ module BCDice
6
+ module CommonCommand
7
+ # リピート
8
+ #
9
+ # 繰り返し回数を指定して、特定のコマンドを複数回実行する
10
+ # 例)
11
+ # x5 choice[A,B,C,D] #=> `choice[A,B,C,D]`を5回実行する
12
+ # rep2 CC<=75 #=> `CC<=75`を2回実行する
13
+ # repeat10 2D6+5 #=> `2D6+6`を10回実行する
14
+ #
15
+ # このコマンドを入れ子させることは許容しない。繰り返し回数の爆発に繋がるためである。
16
+ # 以下は実行時にエラーとなる。
17
+ # x10 repeat100 100D100
18
+ class Repeat
19
+ PREFIX_PATTERN = /(repeat|rep|x)\d+/.freeze
20
+
21
+ REPEAT_LIMIT = 100
22
+
23
+ class << self
24
+ def eval(command, game_system, randomizer)
25
+ cmd = parse(command)
26
+ cmd&.roll(game_system, randomizer)
27
+ end
28
+
29
+ private
30
+
31
+ def parse(command)
32
+ scanner = StringScanner.new(command)
33
+
34
+ secret = !scanner.scan(/s/i).nil?
35
+ keyword = scanner.scan(/repeat|rep|x/i)
36
+ repeat_times = scanner.scan(/\d+/)&.to_i
37
+ whitespace = scanner.scan(/\s+/)
38
+ if keyword.nil? || repeat_times.nil? || whitespace.nil?
39
+ return nil
40
+ end
41
+
42
+ trailer = scanner.post_match
43
+ if trailer.nil? || trailer.empty?
44
+ return nil
45
+ end
46
+
47
+ new(
48
+ secret: secret,
49
+ times: repeat_times,
50
+ trailer: trailer
51
+ )
52
+ end
53
+ end
54
+
55
+ def initialize(secret:, times:, trailer:)
56
+ @secret = secret
57
+ @times = times
58
+ @trailer = trailer
59
+ end
60
+
61
+ def roll(game_system, randomizer)
62
+ err = validate()
63
+ return err if err
64
+
65
+ results = Array.new(@times) do
66
+ cmd = game_system.class.new(@trailer)
67
+ cmd.randomizer = randomizer
68
+ cmd.eval()
69
+ end
70
+
71
+ if results.count(nil) == @times
72
+ return result_with_text("繰り返し対象のコマンドが実行できませんでした (#{@trailer})")
73
+ end
74
+
75
+ text = results.map.with_index(1) { |r, index| "\##{index}\n#{r.text}" }.join("\n\n")
76
+ secret = @secret || results.any?(&:secret?)
77
+
78
+ Result.new.tap do |r|
79
+ r.secret = secret
80
+ r.text = text
81
+ end
82
+ end
83
+
84
+ private
85
+
86
+ def validate
87
+ if /\A(repeat|rep|x)\d+/.match?(@trailer)
88
+ result_with_text("Repeatコマンドの重複はできません")
89
+ elsif @times < 1 || REPEAT_LIMIT < @times
90
+ result_with_text("繰り返し回数は1以上、#{REPEAT_LIMIT}以下としてください")
91
+ end
92
+ end
93
+
94
+ def result_with_text(text)
95
+ Result.new.tap do |r|
96
+ r.secret = @secret
97
+ r.text = text
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end