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

Sign up to get free protection for your applications and to get access to all the features.
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