├── .eslintrc.yml ├── .github └── workflows │ ├── auto-update.yml │ └── ci.yml ├── .gitignore ├── .gitmodules ├── .npmignore ├── .rubocop.yml ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── Rakefile ├── examples ├── js │ ├── basic.js │ └── user_defined_dice_table.js └── ts │ ├── basic.ts │ ├── tsconfig.json │ └── user_defined_dice_table.ts ├── package-lock.json ├── package.json ├── patch.diff ├── ruby ├── emurators │ └── i18n.rb └── patch.rb ├── scripts ├── autopatch.md ├── autopatch_apply.sh ├── autopatch_rebase.sh └── autopatch_strip.sh ├── ts ├── base.test.ts ├── base.ts ├── bcdice │ ├── game_system_list.json.d.ts │ └── i18n_list.json.d.ts ├── game_system.ts ├── game_system_commands.test.ts ├── index.ts ├── internal │ ├── index.ts │ ├── opal.ts │ └── types │ │ ├── base.ts │ │ ├── game_system.ts │ │ ├── randomizer.ts │ │ ├── result.ts │ │ └── user_defined_dice_table.ts ├── loader │ ├── dynamic_loader.ts │ ├── loader.test.ts │ ├── loader.ts │ └── static_loader.ts ├── result.ts ├── test │ └── randomizer.ts ├── tsconfig.json ├── user_defined_dice_table.test.ts ├── user_defined_dice_table.ts ├── version.test.ts └── version.ts └── tsconfig-template.json /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | root: true 2 | parser: "@typescript-eslint/parser" 3 | plugins: 4 | - "@typescript-eslint" 5 | - eslint-plugin-tsdoc 6 | - import 7 | extends: 8 | - eslint:recommended 9 | - plugin:@typescript-eslint/recommended 10 | - plugin:import/errors 11 | - plugin:import/warnings 12 | - plugin:import/typescript 13 | - prettier 14 | - prettier/@typescript-eslint 15 | # ignorePatterns: 16 | # - lib/bcdice/opal.js 17 | rules: 18 | quotes: 19 | - error 20 | - single 21 | import/no-unresolved: 22 | - error 23 | - ignore: 24 | - ^..$ 25 | - ^../../lib 26 | import/order: error 27 | tsdoc/syntax: error 28 | overrides: 29 | - files: "**/*.test.*" 30 | env: 31 | jest: true 32 | - files: lib/**/*.js 33 | parser: espree 34 | parserOptions: 35 | sourceType: script 36 | ecmaVersion: 2021 37 | impliedStrict: true 38 | extends: 39 | - eslint:recommended 40 | - plugin:import/errors 41 | - plugin:import/warnings 42 | - prettier 43 | env: 44 | es2021: true 45 | browser: true 46 | node: true 47 | globals: 48 | Opal: readonly 49 | rules: 50 | "@typescript-eslint/no-empty-function": off 51 | "@typescript-eslint/no-this-alias": off 52 | "@typescript-eslint/no-unused-vars": off 53 | "@typescript-eslint/no-var-requires": off 54 | no-cond-assign: off 55 | no-constant-condition: off 56 | no-control-regex: off 57 | no-empty: off 58 | no-fallthrough: off 59 | no-prototype-builtins: off 60 | no-redeclare: off 61 | no-regex-spaces: off 62 | no-unreachable: off 63 | no-unused-vars: off 64 | no-useless-escape: off 65 | tsdoc/syntax: off 66 | no-undef: error 67 | no-undef-init: error 68 | -------------------------------------------------------------------------------- /.github/workflows/auto-update.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: Auto update BCDice 4 | 5 | on: 6 | push: 7 | branches: 8 | - bcdice-auto-update 9 | - ci/*-auto-update 10 | # schedule: 11 | # - cron: '0 0/6 * * *' 12 | 13 | jobs: 14 | auto_update: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v3 18 | with: 19 | ref: bcdice-auto-update 20 | submodules: true 21 | - uses: actions/setup-node@v3 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | cache: npm 25 | - uses: ruby/setup-ruby@v1 26 | with: 27 | ruby-version: 2.7.0 28 | bundler-cache: true 29 | - run: | 30 | git config --global user.name "github-actions" 31 | git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" 32 | git merge master || true 33 | git submodule update --init 34 | - run: | 35 | ./scripts/autopatch_rebase.sh master 36 | ./scripts/autopatch_apply.sh master 37 | - run: | 38 | npm ci 39 | npm run build 40 | npm test 41 | - run: | 42 | git add patch.diff BCDice Gemfile.lock 43 | git commit -m ":heavy_plus_sign: Update BCDice to $(git -C BCDice rev-parse --short HEAD)" 44 | git push --force origin bcdice-auto-update 45 | continue-on-error: true 46 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | 11 | strategy: 12 | matrix: 13 | node-version: 14 | - lts/* 15 | - current 16 | steps: 17 | - uses: actions/checkout@v3 18 | with: 19 | submodules: true 20 | - uses: actions/setup-node@v3 21 | with: 22 | node-version: ${{ matrix.node-version }} 23 | cache: npm 24 | registry-url: https://registry.npmjs.org/ 25 | - uses: ruby/setup-ruby@v1 26 | with: 27 | ruby-version: 2.7.0 28 | bundler-cache: true 29 | - run: npm ci 30 | - run: npm run clean 31 | - run: BUNDLE_PATH="$(pwd)/vendor/bundle" npm run build 32 | - run: npm run lint 33 | - run: npm test 34 | - run: npm run example 35 | 36 | - run: npm publish 37 | if: ${{ matrix.node-version == 'current' && startsWith(github.ref, 'refs/tags/v') }} 38 | env: 39 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /lib 3 | /patched 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "BCDice"] 2 | path = BCDice 3 | url = https://github.com/bcdice/BCDice.git 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /BCDice 2 | /ruby 3 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | NewCops: enable 3 | 4 | Metrics/AbcSize: 5 | Max: 19 6 | Metrics/MethodLength: 7 | Max: 24 8 | Style/CommandLiteral: 9 | Enabled: false 10 | Style/ClassVars: 11 | Enabled: false 12 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | eval_gemfile File.join(__dir__, 'BCDice/Gemfile') 2 | 3 | gem 'opal', "~> 1.7.4" 4 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: BCDice 3 | specs: 4 | bcdice (3.14.0) 5 | i18n (~> 1.8.5) 6 | racc (~> 1.7.3) 7 | 8 | GEM 9 | remote: https://rubygems.org/ 10 | specs: 11 | ast (2.4.2) 12 | concurrent-ruby (1.3.4) 13 | docile (1.4.1) 14 | i18n (1.8.11) 15 | concurrent-ruby (~> 1.0) 16 | json (2.9.0) 17 | language_server-protocol (3.17.0.3) 18 | opal (1.7.4) 19 | ast (>= 2.3.0) 20 | parser (~> 3.0, >= 3.0.3.2) 21 | parallel (1.26.3) 22 | parser (3.3.6.0) 23 | ast (~> 2.4.1) 24 | racc 25 | power_assert (2.0.4) 26 | racc (1.7.3) 27 | rainbow (3.1.1) 28 | rake (13.1.0) 29 | regexp_parser (2.9.3) 30 | rexml (3.3.9) 31 | rubocop (1.59.0) 32 | json (~> 2.3) 33 | language_server-protocol (>= 3.17.0) 34 | parallel (~> 1.10) 35 | parser (>= 3.2.2.4) 36 | rainbow (>= 2.2.2, < 4.0) 37 | regexp_parser (>= 1.8, < 3.0) 38 | rexml (>= 3.2.5, < 4.0) 39 | rubocop-ast (>= 1.30.0, < 2.0) 40 | ruby-progressbar (~> 1.7) 41 | unicode-display_width (>= 2.4.0, < 3.0) 42 | rubocop-ast (1.36.2) 43 | parser (>= 3.3.1.0) 44 | ruby-progressbar (1.13.0) 45 | simplecov (0.21.2) 46 | docile (~> 1.1) 47 | simplecov-html (~> 0.11) 48 | simplecov_json_formatter (~> 0.1) 49 | simplecov-cobertura (2.1.0) 50 | rexml 51 | simplecov (~> 0.19) 52 | simplecov-html (0.13.1) 53 | simplecov_json_formatter (0.1.4) 54 | test-unit (3.6.4) 55 | power_assert 56 | tomlrb (2.0.3) 57 | unicode-display_width (2.6.0) 58 | yard (0.9.37) 59 | 60 | PLATFORMS 61 | ruby 62 | x64-mingw32 63 | 64 | DEPENDENCIES 65 | bcdice! 66 | opal (~> 1.7.4) 67 | rake (~> 13.1.0) 68 | rubocop (~> 1.59.0) 69 | simplecov (~> 0.21.2) 70 | simplecov-cobertura (~> 2.1.0) 71 | test-unit (~> 3.6.1) 72 | tomlrb (~> 2.0.3) 73 | yard (~> 0.9.34) 74 | 75 | BUNDLED WITH 76 | 2.1.4 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2019, ukatama 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bcdice-js 2 | [![npm](https://img.shields.io/npm/v/bcdice.svg)](https://www.npmjs.com/package/bcdice) 3 | [![CI](https://github.com/bcdice/bcdice-js/workflows/CI/badge.svg)](https://github.com/bcdice/bcdice-js/actions) 4 | [![Snyk Vulnerabilities for npm package](https://img.shields.io/snyk/vulnerabilities/npm/bcdice.svg)](#) 5 | [![GitHub issues](https://img.shields.io/github/issues/bcdice/bcdice-js.svg)](https://github.com/bcdice/bcdice-js/issues) 6 | [![GitHub forks](https://img.shields.io/github/forks/bcdice/bcdice-js.svg)](https://github.com/bcdice/bcdice-js/network) 7 | [![GitHub stars](https://img.shields.io/github/stars/bcdice/bcdice-js.svg)](https://github.com/bcdice/bcdice-js/stargazers) 8 | [![GitHub license](https://img.shields.io/github/license/bcdice/bcdice-js.svg)](https://github.com/bcdice/bcdice-js/blob/master/LICENSE) 9 | [![Discord](https://img.shields.io/discord/597133335243784192.svg?color=7289DA&logo=discord&logoColor=fff)](https://discord.gg/x5MMKWA) 10 | 11 | BCDice ported package for TypeScript/JavaScript by Opal. 12 | 13 | ## Installation 14 | ```bash 15 | $ npm install --save bcdice 16 | ``` 17 | 18 | ## Usage 19 | JavaScript (CommonJS) 20 | ```js 21 | const { DynamicLoader, Version } = require('bcdice'); 22 | 23 | async function main() { 24 | console.log('BCDice Version:', Version); 25 | 26 | const loader = new DynamicLoader(); 27 | 28 | console.log(loader.listAvailableGameSystems().map(info => info.id)); 29 | 30 | const GameSystem = await loader.dynamicLoad('Cthulhu7th'); 31 | 32 | console.log(GameSystem.NAME); 33 | console.log(GameSystem.HELP_MESSAGE); 34 | 35 | const result = GameSystem.eval('CC<=54'); 36 | 37 | console.log(result && result.text); 38 | } 39 | 40 | main(); 41 | ``` 42 | 43 | TypeScript 44 | ```ts 45 | import { DynamicLoader, Version } from 'bcdice'; 46 | 47 | async function main(): Promise { 48 | console.log('BCDice Version:', Version); 49 | 50 | const loader = new DynamicLoader(); 51 | 52 | console.log(loader.listAvailableGameSystems().map(info => info.id)); 53 | 54 | const GameSystem = await loader.dynamicLoad('Cthulhu7th'); 55 | 56 | console.log(GameSystem.NAME); 57 | console.log(GameSystem.HELP_MESSAGE); 58 | 59 | const result = GameSystem.eval('CC<=54'); 60 | 61 | console.log(result?.text); 62 | } 63 | 64 | main(); 65 | ``` 66 | 67 | ### UserDefinedDiceTable 68 | JavaScript (CommonJS) 69 | ```js 70 | const { UserDefinedDiceTable } = require('bcdice'); 71 | 72 | const table = new UserDefinedDiceTable(`テスト表 73 | 1D6 74 | 1:いち 75 | 2:に 76 | 3:さん 77 | 4:し 78 | 5:ご 79 | 6:ろく`); 80 | 81 | const result = table.roll(); 82 | console.log(result && result.text); 83 | ``` 84 | 85 | TypeScript 86 | ```ts 87 | import { UserDefinedDiceTable } from 'bcdice'; 88 | 89 | const table = new UserDefinedDiceTable(`テスト表 90 | 1D6 91 | 1:いち 92 | 2:に 93 | 3:さん 94 | 4:し 95 | 5:ご 96 | 6:ろく`); 97 | 98 | console.log(table.roll()?.text); 99 | ``` 100 | 101 | ### Loaders 102 | * `StaticLoader`: `import StaticLoader from 'bcdice/lib/loader/static_loader';` 103 | * Load all GameSystems on startup. 104 | * `DynamicLoader`: `import { DynamicLoader } from 'bcdice';` 105 | 106 | or extend `Loader` (`import Loader from 'bcdice/lib/loader/loader'`) and make your custom loader. 107 | 108 | ## Internal BCDice Versions 109 | Since v2.x, you can get the version of internal BCDice by importing `Version` from '`bcdice`'. 110 | 111 | | bcdice-js | BCDice | 112 | |---|---| 113 | | 3.1.0 | Ver3.1.1 | 114 | | 3.0.0 | Ver3.0.0 | 115 | | 2.0.0 | Ver3.0.0 | 116 | | 1.x.x | Ver2.xx.xx | 117 | 118 | ## Development 119 | * Node.js >= v14 120 | * Ruby >= 2.7 121 | 122 | ```bash 123 | $ git clone https://github.com/bcdice/bcdice-js.git 124 | $ cd bcdice-js 125 | $ git submodule update --init 126 | $ bundle install 127 | $ npm install 128 | ``` 129 | 130 | ```bash 131 | $ npm run build 132 | $ npm test 133 | ``` 134 | 135 | ## Migration from 2.x to 3.x 136 | `Loader.dynamicImport(className: string)` now accepts a `className` instead of a `path`. Custom loaders need to search for `GameSystem` from known path. See also [`DynamicLoader`](ts/loader/dynamic_loader.ts). 137 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'opal' 4 | require 'json' 5 | require 'yaml' 6 | require 'tomlrb' 7 | 8 | task default: :build 9 | task build: %i[ 10 | racc 11 | build_opal 12 | build_opal_parser 13 | build_core 14 | build_game_system 15 | build_i18n 16 | build_i18n_json 17 | build_i18n_list 18 | build_test 19 | build_game_system_list 20 | ] 21 | 22 | def create_builder 23 | builder = Opal::Builder.new 24 | 25 | builder.compiler_options = { 26 | method_missing: false 27 | } 28 | builder.stubs = { 29 | # 'i18n' => {}, 30 | } 31 | 32 | builder.append_paths( 33 | 'patched/lib', 34 | 'ruby/emurators', 35 | *$LOAD_PATH 36 | ) 37 | 38 | builder 39 | end 40 | 41 | def decleation(source) 42 | File.write "lib/#{source}.d.ts", 'export default undefined;' 43 | end 44 | 45 | def build(source, prerequired: []) 46 | puts source 47 | builder = create_builder 48 | builder.prerequired = prerequired if prerequired 49 | 50 | builder.build source 51 | 52 | opal_path = Pathname.new('bcdice/opal').relative_path_from(File.dirname(source)) 53 | 54 | File.write "lib/#{source}.js", "require('./#{opal_path}');\n\n#{builder}" 55 | File.write "lib/#{source}.js.map", builder.source_map 56 | decleation(source) 57 | end 58 | 59 | directory 'patched' 60 | task copy: 'patched' do 61 | cp_r Dir.glob('BCDice/*'), 'patched/' 62 | end 63 | 64 | task patch: [:copy] do 65 | sh 'patch -p1 < ../patch.diff', { chdir: 'patched' }, {} 66 | $LOAD_PATH.prepend "#{__dir__}/patched/lib" 67 | end 68 | 69 | task racc: [:copy] do 70 | sh 'bundle exec rake racc', { chdir: 'patched' }, {} 71 | end 72 | 73 | directory 'lib/bcdice' 74 | task build_opal: [:patch, 'lib/bcdice'] do 75 | puts 'bcdice/opal' 76 | builder = create_builder 77 | builder.build('opal') 78 | builder.build('native') 79 | builder.build('./ruby/patch.rb') 80 | File.write 'lib/bcdice/opal.js', 81 | builder.to_s 82 | File.write 'lib/bcdice/opal.js.map', builder.source_map 83 | decleation('bcdice/opal') 84 | end 85 | 86 | directory 'lib/bcdice' 87 | task build_opal_parser: [:patch, 'lib/bcdice'] do 88 | puts 'bcdice/opal-parser' 89 | builder = create_builder 90 | builder.build('opal-parser') 91 | File.write 'lib/bcdice/opal-parser.js', 92 | builder.to_s 93 | File.write 'lib/bcdice/opal-parser.js.map', builder.source_map 94 | decleation('bcdice/opal-parser') 95 | end 96 | 97 | directory 'lib/bcdice' 98 | task build_core: [:patch, 'lib/bcdice'] do 99 | [ 100 | 'bcdice/arithmetic_evaluator', 101 | 'bcdice/base', 102 | 'bcdice/common_command', 103 | 'bcdice/preprocessor', 104 | 'bcdice/randomizer', 105 | 'bcdice/user_defined_dice_table', 106 | 'bcdice/version' 107 | ].each { |source| build(source) } 108 | end 109 | 110 | directory 'lib/bcdice/game_system' 111 | task build_game_system: 'lib/bcdice/game_system' do 112 | index_js = "require('../opal');\nrequire('../base');\n" 113 | 114 | File.read('patched/lib/bcdice/game_system.rb').scan(/require "([^"]+)"/).each do |m| 115 | source = m[0] 116 | build(source, prerequired: ['i18n']) 117 | index_js += "require('../../#{source}');\n" 118 | end 119 | 120 | puts 'bcdice/game_system' 121 | File.write 'lib/bcdice/game_system/index.js', index_js 122 | decleation('bcdice/game_system/index') 123 | end 124 | 125 | directory 'lib/bcdice/i18n' 126 | task build_i18n: 'lib/bcdice/i18n' do 127 | i18n = {} 128 | Dir['patched/i18n/*.yml'].each do |path| 129 | i18n = i18n.merge(YAML.load_file(path)) do |_key, oldval, newval| 130 | oldval.merge(newval) 131 | end 132 | end 133 | 134 | File.write 'lib/bcdice/i18n/i18n.yml', YAML.dump(i18n) 135 | File.write 'lib/bcdice/i18n/i18n.json', JSON.dump(i18n) 136 | end 137 | 138 | directory 'lib/bcdice/i18n' 139 | task build_i18n_json: 'lib/bcdice/i18n' do 140 | path_from = Pathname('patched/i18n') 141 | Dir['patched/i18n/**/*.yml'].each do |path| 142 | relative_path = Pathname(path).relative_path_from(path_from).to_s 143 | next unless relative_path.split('/').length > 1 144 | 145 | i18n = YAML.load_file(path) 146 | file_name = File.basename(relative_path.gsub('/', '.'), '.*') 147 | File.write "lib/bcdice/i18n/#{file_name}.json", JSON.dump(i18n) 148 | puts "bcdice/i18n/#{file_name}.json" 149 | end 150 | end 151 | 152 | directory 'lib/bcdice' 153 | task build_i18n_list: 'lib/bcdice' do 154 | ids = [] 155 | Dir['patched/i18n/**/'].each do |game_path| 156 | locales = [] 157 | Dir["#{game_path}*.yml"].each do |path| 158 | locales.push(File.basename(path, '.*')) 159 | end 160 | name = File.split(game_path.gsub('patched/i18n', '')).last 161 | next unless name.length > 1 162 | 163 | ids.push({ baseClassName: name, locales: locales }) 164 | end 165 | File.write 'lib/bcdice/i18n_list.json', JSON.dump({ i18nList: ids }) 166 | 167 | puts 'bcdice/i18n_list.json.d.ts' 168 | FileUtils.copy 'ts/bcdice/i18n_list.json.d.ts', 'lib/bcdice/i18n_list.json.d.ts' 169 | end 170 | 171 | directory 'lib/bcdice' 172 | task build_test: 'lib/bcdice' do 173 | puts 'bcdice/test_data.json' 174 | tests = {} 175 | Dir['patched/test/**/*.toml'].each do |path| 176 | id = File.basename(path, '.toml') 177 | tests[id] = Tomlrb.load_file(path) 178 | end 179 | File.write 'lib/bcdice/test_data.json', JSON.dump(tests) 180 | end 181 | 182 | directory 'lib/bcdice' 183 | task build_game_system_list: [:patch, 'lib/bcdice'] do 184 | puts 'bcdice/game_system_list.json' 185 | 186 | require 'bcdice' 187 | require 'bcdice/game_system' 188 | 189 | game_systems = BCDice.all_game_systems.map do |game_system_class| 190 | { 191 | id: game_system_class::ID, 192 | name: game_system_class::NAME, 193 | className: game_system_class.name.gsub(/^.*::/, ''), 194 | superClassName: game_system_class.superclass.name.gsub(/^.*::/, ''), 195 | sortKey: game_system_class::SORT_KEY, 196 | locale: game_system_class.new('none').instance_variable_get('@locale') 197 | } 198 | end 199 | 200 | File.write 'lib/bcdice/game_system_list.json', JSON.dump({ gameSystems: game_systems }) 201 | 202 | puts 'bcdice/game_system_list.json.d.ts' 203 | FileUtils.copy 'ts/bcdice/game_system_list.json.d.ts', 'lib/bcdice/game_system_list.json.d.ts' 204 | end 205 | -------------------------------------------------------------------------------- /examples/js/basic.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | /* eslint-disable @typescript-eslint/no-var-requires */ 3 | 4 | // const { DynamicLoader, Version } = require('bcdice'); 5 | const { DynamicLoader, Version } = require('../../lib'); 6 | 7 | async function main() { 8 | console.log('BCDice Version:', Version); 9 | 10 | const loader = new DynamicLoader(); 11 | 12 | console.log(loader.listAvailableGameSystems().map(info => info.id)); 13 | 14 | const GameSystem = await loader.dynamicLoad('Cthulhu7th'); 15 | console.log(GameSystem.NAME); 16 | console.log(GameSystem.HELP_MESSAGE); 17 | 18 | const result = GameSystem.eval('CC<=54'); 19 | 20 | console.log(result && result.text); 21 | } 22 | 23 | main(); 24 | -------------------------------------------------------------------------------- /examples/js/user_defined_dice_table.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | /* eslint-disable @typescript-eslint/no-var-requires */ 3 | 4 | // const { UserDefinedDiceTable } = require('bcdice'); 5 | const { UserDefinedDiceTable } = require('../../lib'); 6 | 7 | const table = new UserDefinedDiceTable(`テスト表 8 | 1D6 9 | 1:いち 10 | 2:に 11 | 3:さん 12 | 4:し 13 | 5:ご 14 | 6:ろく`); 15 | 16 | const result = table.roll(); 17 | console.log(result && result.text); 18 | -------------------------------------------------------------------------------- /examples/ts/basic.ts: -------------------------------------------------------------------------------- 1 | // import { DynamicLoader, Version } from 'bcdice'; 2 | import { DynamicLoader, Version } from '../../lib'; 3 | 4 | async function main(): Promise { 5 | console.log('BCDice Version:', Version); 6 | 7 | const loader = new DynamicLoader(); 8 | 9 | console.log(loader.listAvailableGameSystems().map(info => info.id)); 10 | 11 | const GameSystem = await loader.dynamicLoad('Cthulhu7th'); 12 | console.log(GameSystem.NAME); 13 | console.log(GameSystem.HELP_MESSAGE); 14 | 15 | const result = GameSystem.eval('CC<=54'); 16 | 17 | console.log(result?.text); 18 | } 19 | 20 | main(); 21 | -------------------------------------------------------------------------------- /examples/ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig-template.json", 3 | "compilerOptions": { 4 | "outDir": "../../lib/examples", 5 | "rootDir": ".", 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/ts/user_defined_dice_table.ts: -------------------------------------------------------------------------------- 1 | // import { UserDefinedDiceTable } from 'bcdice'; 2 | import { UserDefinedDiceTable } from '../../lib'; 3 | 4 | const table = new UserDefinedDiceTable(`テスト表 5 | 1D6 6 | 1:いち 7 | 2:に 8 | 3:さん 9 | 4:し 10 | 5:ご 11 | 6:ろく`); 12 | 13 | console.log(table.roll()?.text); 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bcdice", 3 | "version": "4.7.1", 4 | "description": "BCDice ported for JavaScript", 5 | "keywords": [ 6 | "dice" 7 | ], 8 | "homepage": "https://github.com/bcdice/bcdice-js#readme", 9 | "bugs": { 10 | "url": "https://github.com/bcdice/bcdice-js/issues" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/bcdice/bcdice-js.git" 15 | }, 16 | "license": "BSD-3-Clause", 17 | "author": "bcdice", 18 | "main": "lib/index.js", 19 | "files": [ 20 | "examples", 21 | "lib", 22 | "ts" 23 | ], 24 | "scripts": { 25 | "build": "run-s build:rake build:tsc", 26 | "build:rake": "bundle exec rake", 27 | "build:tsc": "tsc -b ts", 28 | "clean": "rimraf lib patched", 29 | "example": "run-s example:*", 30 | "example:js-basic": "cd examples/js && node ./basic", 31 | "example:js-user_defined_dice_table": "cd examples/js && node ./user_defined_dice_table", 32 | "example:ts-basic": "cd examples/ts && ts-node ./basic", 33 | "example:ts-user_defined_dice_table": "cd examples/ts && ts-node ./user_defined_dice_table", 34 | "lint": "run-s lint:*", 35 | "lint:eslint": "eslint ts/**/*.ts examples/**/*.ts", 36 | "lint:rubocop": "bundle exec rubocop ruby Rakefile", 37 | "lint:lib": "eslint lib", 38 | "test": "cd ts && mocha -r ts-node/register -r source-map-support/register --reporter dot --timeout 5000 --extension ts \"**/*.test.ts\"" 39 | }, 40 | "dependencies": { 41 | "lodash": "^4.17.20", 42 | "source-map-support": "^0.5.19" 43 | }, 44 | "devDependencies": { 45 | "@microsoft/tsdoc": "^0.13.0", 46 | "@types/chai": "^4.2.14", 47 | "@types/lodash": "^4.14.167", 48 | "@types/mocha": "^8.2.0", 49 | "@types/node": "^14.14.17", 50 | "@types/semver": "^7.3.4", 51 | "@types/sinon": "^9.0.10", 52 | "@types/sinon-chai": "^3.2.5", 53 | "@typescript-eslint/eslint-plugin": "^4.12.0", 54 | "@typescript-eslint/parser": "^4.12.0", 55 | "chai": "^4.2.0", 56 | "cross-env": "^7.0.3", 57 | "eslint": "^7.17.0", 58 | "eslint-config-prettier": "^7.1.0", 59 | "eslint-plugin-import": "^2.22.1", 60 | "eslint-plugin-tsdoc": "^0.2.11", 61 | "mocha": "^11.1.0", 62 | "npm-run-all": "^4.1.5", 63 | "prettier": "^2.2.1", 64 | "rimraf": "^3.0.2", 65 | "semver": "^7.3.4", 66 | "sinon": "^9.2.2", 67 | "sinon-chai": "^3.5.0", 68 | "ts-node": "^9.1.1", 69 | "typescript": "^4.1.3" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /patch.diff: -------------------------------------------------------------------------------- 1 | diff --git a/lib/bcdice/arithmetic/node.rb b/lib/bcdice/arithmetic/node.rb 2 | --- a/lib/bcdice/arithmetic/node.rb 3 | +++ b/lib/bcdice/arithmetic/node.rb 4 | @@ -48,6 +48,8 @@ module BCDice 5 | def eval(round_type) 6 | l = @lhs.eval(round_type) 7 | r = @rhs.eval(round_type) 8 | + 9 | + raise ZeroDivisionError if r == 0 10 | divide_and_round(l, r, round_type) 11 | end 12 | 13 | @@ -101,9 +103,10 @@ module BCDice 14 | when RoundType::CEIL 15 | (dividend.to_f / divisor).ceil 16 | when RoundType::ROUND 17 | - (dividend.to_f / divisor).round 18 | + x = dividend.to_f / divisor 19 | + x.negative? ? -(x.abs.round) : x.round 20 | else # RoundType::FLOOR 21 | - dividend / divisor 22 | + (dividend / divisor).floor 23 | end 24 | end 25 | end 26 | @@ -138,7 +141,8 @@ module BCDice 27 | # @param [Symbol] _round_type ゲームシステムの端数処理設定 28 | # @return [Integer] 29 | def divide_and_round(dividend, divisor, _round_type) 30 | - (dividend.to_f / divisor).round 31 | + x = dividend.to_f / divisor 32 | + x.negative? ? -(x.abs.round) : x.round 33 | end 34 | end 35 | 36 | @@ -155,7 +159,7 @@ module BCDice 37 | # @param [Symbol] _round_type ゲームシステムの端数処理設定 38 | # @return [Integer] 39 | def divide_and_round(dividend, divisor, _round_type) 40 | - dividend / divisor 41 | + (dividend / divisor).floor 42 | end 43 | end 44 | 45 | diff --git a/lib/bcdice/base.rb b/lib/bcdice/base.rb 46 | --- a/lib/bcdice/base.rb 47 | +++ b/lib/bcdice/base.rb 48 | @@ -20,8 +20,7 @@ module BCDice 49 | # 応答するコマンドのprefixを登録する 50 | # @param prefixes [Array] 51 | def register_prefix(*prefixes) 52 | - @prefixes ||= [] 53 | - @prefixes.concat(prefixes.flatten) 54 | + @prefixes = (@prefixes || []) + prefixes.flatten 55 | end 56 | 57 | def register_prefix_from_super_class 58 | @@ -328,7 +327,7 @@ module BCDice 59 | num = @randomizer.roll_sum(count, 6) 60 | debug("num", num) 61 | 62 | - index = ((num - 1) / 2) 63 | + index = ((num - 1) / 2).to_i 64 | debug("index", index) 65 | 66 | text = table[index] 67 | diff --git a/lib/bcdice/common_command/add_dice/node.rb b/lib/bcdice/common_command/add_dice/node.rb 68 | --- a/lib/bcdice/common_command/add_dice/node.rb 69 | +++ b/lib/bcdice/common_command/add_dice/node.rb 70 | @@ -140,7 +140,7 @@ module BCDice 71 | lhs = @lhs.eval(game_system, randomizer) 72 | rhs = @rhs.eval(game_system, randomizer) 73 | 74 | - return calc(lhs, rhs, game_system.round_type) 75 | + return calc(lhs, rhs, game_system.round_type).to_i 76 | end 77 | 78 | # @return [Boolean] 79 | @@ -177,7 +177,7 @@ module BCDice 80 | # @param _round_type [Symbol] ゲームシステムの端数処理設定 81 | # @return [Integer] 演算の結果 82 | def calc(lhs, rhs, _round_type) 83 | - lhs.send(@op, rhs) 84 | + lhs.send(@op, rhs).to_i 85 | end 86 | 87 | # S式で使う演算子の表現を返す 88 | @@ -242,7 +242,7 @@ module BCDice 89 | return 1 90 | end 91 | 92 | - return divide_and_round(lhs, rhs, round_type) 93 | + return divide_and_round(lhs, rhs, round_type).to_i 94 | end 95 | 96 | # 除算および端数処理を行う 97 | @@ -271,9 +271,10 @@ module BCDice 98 | when RoundType::CEIL 99 | (dividend.to_f / divisor).ceil 100 | when RoundType::ROUND 101 | - (dividend.to_f / divisor).round 102 | + x = dividend.to_f / divisor 103 | + x.negative? ? -(x.abs.round) : x.round 104 | else # RoundType::FLOOR 105 | - dividend / divisor 106 | + (dividend / divisor).floor 107 | end 108 | end 109 | end 110 | @@ -304,7 +305,8 @@ module BCDice 111 | # @param (see DivideWithGameSystemDefault#divide_and_round) 112 | # @return [Integer] 113 | def divide_and_round(dividend, divisor, _round_type) 114 | - (dividend.to_f / divisor).round 115 | + x = dividend.to_f / divisor 116 | + x.negative? ? -(x.abs.round) : x.round 117 | end 118 | end 119 | 120 | @@ -319,7 +321,7 @@ module BCDice 121 | # @param (see DivideWithGameSystemDefault#divide_and_round) 122 | # @return [Integer] 123 | def divide_and_round(dividend, divisor, _round_type) 124 | - dividend / divisor 125 | + (dividend / divisor).floor 126 | end 127 | end 128 | 129 | @@ -342,7 +344,7 @@ module BCDice 130 | # @param [Randomizer] randomizer ランダマイザ 131 | # @return [Integer] 評価結果 132 | def eval(game_system, randomizer) 133 | - -@body.eval(game_system, randomizer) 134 | + -@body.eval(game_system, randomizer).to_i 135 | end 136 | 137 | # @return [Boolean] 138 | @@ -398,7 +400,7 @@ module BCDice 139 | total = dice_list.sum() 140 | @text = "#{total}[#{dice_list.join(',')}]" 141 | 142 | - return total 143 | + return total.to_i 144 | end 145 | 146 | # @return [Boolean] 147 | @@ -529,7 +531,7 @@ module BCDice 148 | 149 | @text = "#{total}[#{sorted_values.join(',')}]" 150 | 151 | - return total 152 | + return total.to_i 153 | end 154 | 155 | # @return [Boolean] 156 | @@ -590,7 +592,7 @@ module BCDice 157 | # @param randomizer [Randomizer] 158 | # @return [integer] 159 | def eval(game_system, randomizer) 160 | - @expr.eval(game_system, randomizer) 161 | + @expr.eval(game_system, randomizer).to_i 162 | end 163 | 164 | # @return [Boolean] 165 | @@ -623,7 +625,7 @@ module BCDice 166 | # ノードを初期化する 167 | # @param [Integer] literal 値 168 | def initialize(literal) 169 | - @literal = literal 170 | + @literal = literal.to_i 171 | end 172 | 173 | # 符号を反転した結果の数値ノードを返す 174 | @@ -635,7 +637,7 @@ module BCDice 175 | # ノードを評価する 176 | # @return [Integer] 格納している値 177 | def eval(_game_system, _randomizer) 178 | - @literal 179 | + @literal.to_i 180 | end 181 | 182 | # @return [Boolean] 183 | diff --git a/lib/bcdice/common_command/calc/node.rb b/lib/bcdice/common_command/calc/node.rb 184 | --- a/lib/bcdice/common_command/calc/node.rb 185 | +++ b/lib/bcdice/common_command/calc/node.rb 186 | @@ -15,7 +15,7 @@ module BCDice 187 | def eval(round_type) 188 | value = 189 | begin 190 | - @expr.eval(round_type) 191 | + @expr.eval(round_type).to_i 192 | rescue ZeroDivisionError 193 | "ゼロ除算が発生したため計算できませんでした" 194 | end 195 | diff --git a/lib/bcdice/common_command/repeat.rb b/lib/bcdice/common_command/repeat.rb 196 | --- a/lib/bcdice/common_command/repeat.rb 197 | +++ b/lib/bcdice/common_command/repeat.rb 198 | @@ -68,7 +68,7 @@ module BCDice 199 | cmd.eval() 200 | end 201 | 202 | - if results.count(nil) == @times 203 | + if results.count { |i| i == nil } == @times 204 | return result_with_text("繰り返し対象のコマンドが実行できませんでした (#{@trailer})") 205 | end 206 | 207 | diff --git a/lib/bcdice/dice_table/d66_table.rb b/lib/bcdice/dice_table/d66_table.rb 208 | --- a/lib/bcdice/dice_table/d66_table.rb 209 | +++ b/lib/bcdice/dice_table/d66_table.rb 210 | @@ -37,7 +37,7 @@ module BCDice 211 | end 212 | 213 | key = dice[0] * 10 + dice[1] 214 | - chosen = @items[key] 215 | + chosen = @items[key] || @items[key.to_s] 216 | chosen = chosen.roll(randomizer) if chosen.respond_to?(:roll) 217 | RollResult.new(@name, key, chosen) 218 | end 219 | diff --git a/lib/bcdice/game_system/Amadeus.rb b/lib/bcdice/game_system/Amadeus.rb 220 | --- a/lib/bcdice/game_system/Amadeus.rb 221 | +++ b/lib/bcdice/game_system/Amadeus.rb 222 | @@ -101,7 +101,7 @@ module BCDice 223 | end 224 | 225 | if available_inga 226 | - inga = inga_table[dice - 1] 227 | + inga = inga_table[(dice - 1)] 228 | "#{total}_#{result}[#{dice}#{inga}]" 229 | else 230 | "#{total}_#{result}[#{dice}]" 231 | diff --git a/lib/bcdice/game_system/Arianrhod_Korean.rb b/lib/bcdice/game_system/Arianrhod_Korean.rb 232 | --- a/lib/bcdice/game_system/Arianrhod_Korean.rb 233 | +++ b/lib/bcdice/game_system/Arianrhod_Korean.rb 234 | @@ -1,5 +1,7 @@ 235 | # frozen_string_literal: true 236 | 237 | +require "bcdice/game_system/Arianrhod" 238 | + 239 | module BCDice 240 | module GameSystem 241 | class Arianrhod_Korean < Arianrhod 242 | diff --git a/lib/bcdice/game_system/AssaultEngine.rb b/lib/bcdice/game_system/AssaultEngine.rb 243 | --- a/lib/bcdice/game_system/AssaultEngine.rb 244 | +++ b/lib/bcdice/game_system/AssaultEngine.rb 245 | @@ -40,7 +40,7 @@ module BCDice 246 | 247 | if cmd.command.include?("AES") # SWAP初回 248 | total = @randomizer.roll_once(100) % 100 # 0-99 249 | - swap = (total % 10) * 10 + (total / 10) 250 | + swap = (total % 10) * 10 + (total / 10).floor 251 | r1 = judge(target, total) 252 | r2 = judge(target, swap) 253 | text = "(AES#{format00(target)}) > #{r1.text} / スワップ#{r2.text}" 254 | @@ -53,7 +53,7 @@ module BCDice 255 | else # リロール 256 | now = cmd.prefix_number 257 | die = @randomizer.roll_once(10) % 10 # 0-9 258 | - new1 = judge(target, (now / 10 * 10) + die) # 1の位を振り直す 259 | + new1 = judge(target, ((now / 10).floor * 10) + die) # 1の位を振り直す 260 | new2 = judge(target, now % 10 + die * 10) # 10の位を振り直す 261 | 262 | text = "(#{format00(now)}AE#{format00(target)}) > #{die} > #{new1.text} / #{new2.text}" 263 | @@ -78,7 +78,7 @@ module BCDice 264 | end 265 | 266 | def judge(target, total) 267 | - double = (total / 10) == (total % 10) 268 | + double = (total / 10).floor == (total % 10) 269 | total_text = format00(total) 270 | if total <= target 271 | double ? Result.critical("(#{total_text})クリティカル") : Result.success("(#{total_text})成功") 272 | diff --git a/lib/bcdice/game_system/ChaosFlare.rb b/lib/bcdice/game_system/ChaosFlare.rb 273 | --- a/lib/bcdice/game_system/ChaosFlare.rb 274 | +++ b/lib/bcdice/game_system/ChaosFlare.rb 275 | @@ -93,7 +93,7 @@ module BCDice 276 | return "因果表(#{num}) > #{FATE_TABLE[num][0]}" 277 | end 278 | 279 | - dice1 = num / 10 280 | + dice1 = (num / 10).to_i 281 | dice2 = num % 10 282 | if !(1..6).include?(dice1) || !(1..6).include?(dice2) 283 | return nil 284 | @@ -104,7 +104,7 @@ module BCDice 285 | end 286 | 287 | index1 = dice1 288 | - index2 = (dice2 / 2) - 1 289 | + index2 = (dice2 / 2).to_i - 1 290 | return "因果表(#{dice1}#{dice2}) > #{FATE_TABLE[index1][index2]}" 291 | end 292 | 293 | diff --git a/lib/bcdice/game_system/Chill.rb b/lib/bcdice/game_system/Chill.rb 294 | --- a/lib/bcdice/game_system/Chill.rb 295 | +++ b/lib/bcdice/game_system/Chill.rb 296 | @@ -34,9 +34,9 @@ module BCDice 297 | Result.failure("失敗") 298 | elsif total >= (target * 0.9) 299 | Result.success("L成功") 300 | - elsif total >= (target / 2) 301 | + elsif total >= (target / 2).to_i 302 | Result.success("M成功") 303 | - elsif total >= (target / 10) 304 | + elsif total >= (target / 10).to_i 305 | Result.success("H成功") 306 | else 307 | Result.critical("C成功") 308 | diff --git a/lib/bcdice/game_system/Chill3.rb b/lib/bcdice/game_system/Chill3.rb 309 | --- a/lib/bcdice/game_system/Chill3.rb 310 | +++ b/lib/bcdice/game_system/Chill3.rb 311 | @@ -24,7 +24,7 @@ module BCDice 312 | return nil unless cmp_op == :<= 313 | 314 | # ゾロ目ならC-ResultかBotch 315 | - tens = (dice_total / 10) % 10 316 | + tens = (dice_total / 10).to_i % 10 317 | ones = dice_total % 10 318 | 319 | if tens == ones 320 | @@ -38,7 +38,7 @@ module BCDice 321 | return Result.critical("Colossal Success") 322 | end 323 | elsif (total <= target) || (dice_total == 1) # 01は必ず成功 324 | - if total <= (target / 2) 325 | + if total <= (target / 2).to_i 326 | return Result.success("High Success") 327 | else 328 | return Result.success("Low Success") 329 | diff --git a/lib/bcdice/game_system/ColossalHunter.rb b/lib/bcdice/game_system/ColossalHunter.rb 330 | --- a/lib/bcdice/game_system/ColossalHunter.rb 331 | +++ b/lib/bcdice/game_system/ColossalHunter.rb 332 | @@ -72,24 +72,23 @@ module BCDice 333 | # 出力文の生成 334 | text = "(#{parsed}) > #{dice}[#{dice_str}]#{Format.modifier(modify)} > #{total}" 335 | 336 | - result = get_judge_result(dice, total, parsed) 337 | + result = get_judge_result(dice, total, parsed, text) 338 | 339 | - result.text = text + result.text 340 | return result 341 | end 342 | 343 | # 成否判定 344 | - def get_judge_result(dice, total, parsed) 345 | + def get_judge_result(dice, total, parsed, text) 346 | if dice <= 5 347 | - Result.fumble(" > ファンブル") 348 | + Result.fumble(text + " > ファンブル") 349 | elsif total >= 16 350 | - Result.critical(" > クリティカル") 351 | + Result.critical(text + " > クリティカル") 352 | elsif parsed.cmp_op.nil? 353 | - Result.new("") 354 | + Result.new(text) 355 | elsif total >= parsed.target_number 356 | - Result.success(" > 成功") 357 | + Result.success(text + " > 成功") 358 | else 359 | - Result.failure(" > 失敗") 360 | + Result.failure(text + " > 失敗") 361 | end 362 | end 363 | 364 | diff --git a/lib/bcdice/game_system/Cthulhu.rb b/lib/bcdice/game_system/Cthulhu.rb 365 | --- a/lib/bcdice/game_system/Cthulhu.rb 366 | +++ b/lib/bcdice/game_system/Cthulhu.rb 367 | @@ -170,7 +170,7 @@ module BCDice 368 | 369 | def compare(total, target, broken_number = 0) 370 | result = CompareResult.new(@locale) 371 | - target_special = (target * @special_percentage / 100).clamp(1, 100) 372 | + target_special = (target * @special_percentage / 100).to_i.clamp(1, 100) 373 | 374 | if (total <= target) && (total < 100) 375 | result.success = true 376 | diff --git a/lib/bcdice/game_system/Cthulhu7th.rb b/lib/bcdice/game_system/Cthulhu7th.rb 377 | --- a/lib/bcdice/game_system/Cthulhu7th.rb 378 | +++ b/lib/bcdice/game_system/Cthulhu7th.rb 379 | @@ -121,9 +121,9 @@ module BCDice 380 | ResultLevel.new(:critical) 381 | elsif total >= fumble 382 | ResultLevel.new(:fumble) 383 | - elsif total <= (difficulty / 5) 384 | + elsif total <= (difficulty / 5).to_i 385 | ResultLevel.new(:extreme_success) 386 | - elsif total <= (difficulty / 2) 387 | + elsif total <= (difficulty / 2).to_i 388 | ResultLevel.new(:hard_success) 389 | elsif total <= difficulty 390 | ResultLevel.new(:regular_success) 391 | @@ -194,9 +194,9 @@ module BCDice 392 | if difficulty == 0 393 | difficulty = nil 394 | elsif difficulty_level == "H" 395 | - difficulty /= 2 396 | + difficulty = (difficulty / 2).to_i 397 | elsif difficulty_level == "E" 398 | - difficulty /= 5 399 | + difficulty = (difficulty / 5).to_i 400 | elsif difficulty_level == "C" 401 | difficulty = 0 402 | end 403 | diff --git a/lib/bcdice/game_system/Cthulhu7th_ChineseTraditional.rb b/lib/bcdice/game_system/Cthulhu7th_ChineseTraditional.rb 404 | --- a/lib/bcdice/game_system/Cthulhu7th_ChineseTraditional.rb 405 | +++ b/lib/bcdice/game_system/Cthulhu7th_ChineseTraditional.rb 406 | @@ -113,8 +113,8 @@ module BCDice 407 | def getCheckResultText(total, diff, fumbleable = false) 408 | if total <= diff 409 | return "決定性的成功" if total == 1 410 | - return "極限的成功" if total <= (diff / 5) 411 | - return "困難的成功" if total <= (diff / 2) 412 | + return "極限的成功" if total <= (diff / 5).to_i 413 | + return "困難的成功" if total <= (diff / 2).to_i 414 | 415 | return "通常成功" 416 | end 417 | @@ -344,7 +344,7 @@ module BCDice 418 | end 419 | 420 | def getSetOfBullet(diff) 421 | - bullet_set_count = diff / 10 422 | + bullet_set_count = (diff / 10).to_i 423 | 424 | if (diff >= 1) && (diff < 10) 425 | bullet_set_count = 1 # 技能值9以下的最低限度保障處理 426 | @@ -354,7 +354,7 @@ module BCDice 427 | end 428 | 429 | def getHitBulletCountBase(diff, bullet_set_count) 430 | - hit_bullet_count_base = (bullet_set_count / 2) 431 | + hit_bullet_count_base = (bullet_set_count / 2).to_i 432 | 433 | if (diff >= 1) && (diff < 10) 434 | hit_bullet_count_base = 1 # 技能值9以下的最低限度保障處理 435 | diff --git a/lib/bcdice/game_system/Cthulhu7th_Korean.rb b/lib/bcdice/game_system/Cthulhu7th_Korean.rb 436 | --- a/lib/bcdice/game_system/Cthulhu7th_Korean.rb 437 | +++ b/lib/bcdice/game_system/Cthulhu7th_Korean.rb 438 | @@ -110,8 +110,8 @@ module BCDice 439 | def getCheckResultText(total, diff, fumbleable = false) 440 | if total <= diff 441 | return "대성공" if total == 1 442 | - return "대단한 성공" if total <= (diff / 5) 443 | - return "어려운 성공" if total <= (diff / 2) 444 | + return "대단한 성공" if total <= (diff / 5).to_i 445 | + return "어려운 성공" if total <= (diff / 2).to_i 446 | 447 | return "보통 성공" 448 | end 449 | @@ -302,7 +302,7 @@ module BCDice 450 | lost_bullet_count = bullet_count 451 | end 452 | 453 | - return hit_bullet_count, impale_bullet_count, lost_bullet_count 454 | + return hit_bullet_count.to_i, impale_bullet_count.to_i, lost_bullet_count.to_i 455 | end 456 | 457 | def getSuccessListImpaleBulletList(more_difficlty) 458 | @@ -341,7 +341,7 @@ module BCDice 459 | end 460 | 461 | def getSetOfBullet(diff) 462 | - bullet_set_count = diff / 10 463 | + bullet_set_count = (diff / 10).to_i 464 | 465 | if (diff >= 1) && (diff < 10) 466 | bullet_set_count = 1 # 기능 수치가 9 이하일 때의 최저수치 보장 처리 467 | @@ -351,7 +351,7 @@ module BCDice 468 | end 469 | 470 | def getHitBulletCountBase(diff, bullet_set_count) 471 | - hit_bullet_count_base = (bullet_set_count / 2) 472 | + hit_bullet_count_base = (bullet_set_count / 2).to_i 473 | 474 | if (diff >= 1) && (diff < 10) 475 | hit_bullet_count_base = 1 # 기능 수치가 9 이하일 때의 최저수치 보장 476 | diff --git a/lib/bcdice/game_system/CthulhuTech.rb b/lib/bcdice/game_system/CthulhuTech.rb 477 | --- a/lib/bcdice/game_system/CthulhuTech.rb 478 | +++ b/lib/bcdice/game_system/CthulhuTech.rb 479 | @@ -53,7 +53,7 @@ module BCDice 480 | dice_values = randomizer.roll_barabara(@num, 10) 481 | 482 | # ファンブル:出目の半分(小数点以下切り上げ)以上が1の場合 483 | - fumble = dice_values.count(1) >= (dice_values.length + 1) / 2 484 | + fumble = dice_values.count(1) >= ((dice_values.length + 1) / 2).to_i 485 | 486 | sorted_dice_values = dice_values.sort 487 | roll_result = calculate_roll_result(sorted_dice_values) 488 | diff --git a/lib/bcdice/game_system/DemonSpike.rb b/lib/bcdice/game_system/DemonSpike.rb 489 | --- a/lib/bcdice/game_system/DemonSpike.rb 490 | +++ b/lib/bcdice/game_system/DemonSpike.rb 491 | @@ -51,7 +51,7 @@ module BCDice 492 | 493 | is_fumble = step_list[0][:dice_sum] == 2 494 | total = is_fumble ? 0 : step_list.sum { |s| s[:dice_sum] } + parsed.modify_number 495 | - success_level = total / 10 496 | + success_level = (total / 10).floor 497 | is_success = total >= 10 498 | 499 | res = 500 | diff --git a/lib/bcdice/game_system/Dracurouge.rb b/lib/bcdice/game_system/Dracurouge.rb 501 | --- a/lib/bcdice/game_system/Dracurouge.rb 502 | +++ b/lib/bcdice/game_system/Dracurouge.rb 503 | @@ -89,7 +89,7 @@ module BCDice 504 | one_count = dice_list.count(1) 505 | six_count = dice_list.count(6) 506 | 507 | - return (one_count / 2) + (six_count / 2) 508 | + return (one_count / 2).to_i + (six_count / 2).to_i 509 | end 510 | 511 | def apply_thirsty_point(dice_list, thirsty_point) 512 | @@ -273,7 +273,7 @@ module BCDice 513 | list = Array.new(times) do 514 | randomizer.roll_barabara(sides.length, 6) 515 | .reverse # テスト系の互換性のために反転する 516 | - .map.with_index { |x, idx| x * (10**idx) } 517 | + .map.with_index { |x, idx| x * (10**idx).to_i } 518 | .sum() 519 | end 520 | 521 | diff --git a/lib/bcdice/game_system/EarthDawn3.rb b/lib/bcdice/game_system/EarthDawn3.rb 522 | --- a/lib/bcdice/game_system/EarthDawn3.rb 523 | +++ b/lib/bcdice/game_system/EarthDawn3.rb 524 | @@ -147,7 +147,7 @@ module BCDice 525 | 526 | result = [0, 0, 0, 0, 0, 0, 0] 527 | 528 | - loopCount = (overStep / stepRythm.size) 529 | + loopCount = (overStep / stepRythm.size).to_i 530 | 531 | loopCount.times do 532 | addStepToResult(result, baseStepInfo) 533 | diff --git a/lib/bcdice/game_system/EarthDawn4.rb b/lib/bcdice/game_system/EarthDawn4.rb 534 | --- a/lib/bcdice/game_system/EarthDawn4.rb 535 | +++ b/lib/bcdice/game_system/EarthDawn4.rb 536 | @@ -193,7 +193,7 @@ module BCDice 537 | 538 | result = [0, 0, 0, 0, 0, 0, 0] 539 | 540 | - loopCount = (overStep / stepRythm.size) 541 | + loopCount = (overStep / stepRythm.size).to_i 542 | 543 | loopCount.times do 544 | addStepToResult(result, overBounusStep) 545 | @@ -225,7 +225,7 @@ module BCDice 546 | return "失敗" 547 | end 548 | 549 | - level = (diff / 5) + 1 550 | + level = (diff / 5).to_i + 1 551 | 552 | return "成功 レベル:#{level}" 553 | end 554 | diff --git a/lib/bcdice/game_system/EclipsePhase.rb b/lib/bcdice/game_system/EclipsePhase.rb 555 | --- a/lib/bcdice/game_system/EclipsePhase.rb 556 | +++ b/lib/bcdice/game_system/EclipsePhase.rb 557 | @@ -21,7 +21,7 @@ module BCDice 558 | return nil unless cmp_op == :<= 559 | 560 | dice_value = total % 100 # 出目00は100ではなく00とする 561 | - dice_ten_place = dice_value / 10 562 | + dice_ten_place = (dice_value / 10).to_i 563 | dice_one_place = dice_value % 10 564 | 565 | if dice_ten_place == dice_one_place 566 | diff --git a/lib/bcdice/game_system/Emoklore.rb b/lib/bcdice/game_system/Emoklore.rb 567 | --- a/lib/bcdice/game_system/Emoklore.rb 568 | +++ b/lib/bcdice/game_system/Emoklore.rb 569 | @@ -65,7 +65,7 @@ module BCDice 570 | success_value = critical + success - fumble 571 | result = compare_result(success_value) 572 | 573 | - result.text = "#{values} > #{success_value} > #{translate('Emoklore.success_count', count: success_value)} #{result.text}" 574 | + result.text = "[#{values.join(', ')}] > #{success_value} > #{translate('Emoklore.success_count', count: success_value)} #{result.text}" 575 | return result 576 | end 577 | 578 | diff --git a/lib/bcdice/game_system/GardenOrder.rb b/lib/bcdice/game_system/GardenOrder.rb 579 | --- a/lib/bcdice/game_system/GardenOrder.rb 580 | +++ b/lib/bcdice/game_system/GardenOrder.rb 581 | @@ -52,12 +52,12 @@ module BCDice 582 | def get_critical_border(critical_border_text, success_rate) 583 | return critical_border_text.to_i unless critical_border_text.nil? 584 | 585 | - critical_border = [success_rate / 5, 1].max 586 | + critical_border = [success_rate / 5, 1].max.to_i 587 | return critical_border 588 | end 589 | 590 | def check_roll_repeat_attack(success_rate, repeat_count, critical_border) 591 | - success_rate_per_one = success_rate / repeat_count 592 | + success_rate_per_one = (success_rate / repeat_count).to_i 593 | # 連続攻撃は最終的な成功率が50%以上であることが必要 cf. p217 594 | if repeat_count > 1 && success_rate_per_one < 50 595 | return "D100<=#{success_rate_per_one}@#{critical_border} > 連続攻撃は成功率が50%以上必要です" 596 | diff --git a/lib/bcdice/game_system/Gundog.rb b/lib/bcdice/game_system/Gundog.rb 597 | --- a/lib/bcdice/game_system/Gundog.rb 598 | +++ b/lib/bcdice/game_system/Gundog.rb 599 | @@ -34,7 +34,7 @@ module BCDice 600 | elsif target == "?" 601 | Result.nothing 602 | elsif total <= target 603 | - dig10 = total / 10 604 | + dig10 = (total / 10).to_i 605 | dig1 = total - dig10 * 10 606 | dig10 = 0 if dig10 >= 10 607 | dig1 = 0 if dig1 >= 10 # 条件的にはあり得ない(笑 608 | diff --git a/lib/bcdice/game_system/GundogRevised.rb b/lib/bcdice/game_system/GundogRevised.rb 609 | --- a/lib/bcdice/game_system/GundogRevised.rb 610 | +++ b/lib/bcdice/game_system/GundogRevised.rb 611 | @@ -45,7 +45,7 @@ module BCDice 612 | elsif target == "?" 613 | Result.nothing 614 | elsif total <= target 615 | - dig10 = total / 10 616 | + dig10 = (total / 10).to_i 617 | dig1 = total - dig10 * 10 618 | dig10 = 0 if dig10 >= 10 619 | dig1 = 0 if dig1 >= 10 # 条件的にはあり得ない(笑 620 | diff --git a/lib/bcdice/game_system/GurpsFW.rb b/lib/bcdice/game_system/GurpsFW.rb 621 | --- a/lib/bcdice/game_system/GurpsFW.rb 622 | +++ b/lib/bcdice/game_system/GurpsFW.rb 623 | @@ -321,7 +321,7 @@ module BCDice 624 | tableName = "財宝テーブル" 625 | diff = Regexp.last_match(1) 626 | depth = Regexp.last_match(2).to_i 627 | - num = depth / 10 628 | + num = (depth / 10).to_i 629 | if num >= 6 630 | num = 5 631 | end 632 | diff --git a/lib/bcdice/game_system/HeroScale.rb b/lib/bcdice/game_system/HeroScale.rb 633 | --- a/lib/bcdice/game_system/HeroScale.rb 634 | +++ b/lib/bcdice/game_system/HeroScale.rb 635 | @@ -159,11 +159,13 @@ module BCDice 636 | 637 | def fate_body(natural_result) 638 | modified_result = natural_result.dup 639 | - modified_result.each do |result| 640 | - if result == 4 641 | - modified_result << @randomizer.roll_once(4) 642 | - end 643 | + additional = natural_result.count(4) 644 | + while additional > 0 645 | + value = @randomizer.roll_once(4) 646 | + modified_result.push(value) 647 | + additional -= 1 if value != 4 648 | end 649 | + 650 | subtotal = results_multiplication(natural_result) 651 | total = results_multiplication(modified_result) 652 | message = "肉体の超越 > #{subtotal}[#{natural_result.join(',')}] > #{total}[#{modified_result.join(',')}]" 653 | @@ -297,7 +299,7 @@ module BCDice 654 | def fate_acceptance(natural_result, order) 655 | subtotal = results_multiplication(natural_result) 656 | if order.length > 3 && order[2] =~ /^\d+$/ && order[3] =~ /^\d+$/ 657 | - change_result = natural_result.min(2) 658 | + change_result = natural_result.sort().take(2) 659 | modified_result = natural_result.dup 660 | modified_result[modified_result.index(change_result[0])] = order[2].to_i 661 | modified_result[modified_result.index(change_result[1])] = order[3].to_i 662 | @@ -460,7 +462,7 @@ module BCDice 663 | subtotal = results_multiplication(natural_result) 664 | modified_result = natural_result.dup 665 | depravity_num1 = natural_result[0] % 10 666 | - depravity_num10 = natural_result[0] / 10 667 | + depravity_num10 = (natural_result[0] / 10).to_i 668 | if depravity_num10 > 1 669 | modified_result << depravity_num10 670 | end 671 | @@ -476,7 +478,7 @@ module BCDice 672 | modified_result = natural_result.dup 673 | modified_result << @randomizer.roll_once(60) 674 | subtotal = results_multiplication(natural_result) 675 | - total = results_multiplication(modified_result) / 2 676 | + total = (results_multiplication(modified_result) / 2).to_i 677 | message = "忘却の報い > #{subtotal}[#{natural_result.join(',')}] > #{total}[#{modified_result.join(',')}]" 678 | return message 679 | end 680 | @@ -827,9 +829,9 @@ module BCDice 681 | 682 | def result_raoundup(result) 683 | if result.even? 684 | - return result / 2 685 | + return (result / 2).to_i 686 | else 687 | - return result / 2 + 1 688 | + return (result / 2).to_i + 1 689 | end 690 | end 691 | end 692 | diff --git a/lib/bcdice/game_system/Hieizan.rb b/lib/bcdice/game_system/Hieizan.rb 693 | --- a/lib/bcdice/game_system/Hieizan.rb 694 | +++ b/lib/bcdice/game_system/Hieizan.rb 695 | @@ -24,7 +24,7 @@ module BCDice 696 | Result.fumble("大失敗") 697 | elsif total >= 96 698 | Result.failure("自動失敗") 699 | - elsif total <= (target / 5) 700 | + elsif total <= (target / 5).to_i 701 | Result.critical("大成功") 702 | elsif total <= 1 703 | Result.success("自動成功") 704 | diff --git a/lib/bcdice/game_system/HunterTheReckoning5th.rb b/lib/bcdice/game_system/HunterTheReckoning5th.rb 705 | --- a/lib/bcdice/game_system/HunterTheReckoning5th.rb 706 | +++ b/lib/bcdice/game_system/HunterTheReckoning5th.rb 707 | @@ -141,7 +141,7 @@ module BCDice 708 | 709 | def get_critical_success(ten_dice) 710 | # 10の目が2個毎に追加2成功 711 | - return ((ten_dice / 2) * 2) 712 | + return ((ten_dice / 2).to_i * 2) 713 | end 714 | 715 | def make_dice_roll(dice_pool) 716 | diff --git a/lib/bcdice/game_system/InfiniteFantasia.rb b/lib/bcdice/game_system/InfiniteFantasia.rb 717 | --- a/lib/bcdice/game_system/InfiniteFantasia.rb 718 | +++ b/lib/bcdice/game_system/InfiniteFantasia.rb 719 | @@ -28,15 +28,15 @@ module BCDice 720 | end 721 | 722 | output = 723 | - if total <= (target / 32) 724 | + if total <= (target / 32).to_i 725 | "32レベル成功(32Lv+)" 726 | - elsif total <= (target / 16) 727 | + elsif total <= (target / 16).to_i 728 | "16レベル成功(16Lv+)" 729 | - elsif total <= (target / 8) 730 | + elsif total <= (target / 8).to_i 731 | "8レベル成功" 732 | - elsif total <= (target / 4) 733 | + elsif total <= (target / 4).to_i 734 | "4レベル成功" 735 | - elsif total <= (target / 2) 736 | + elsif total <= (target / 2).to_i 737 | "2レベル成功" 738 | else 739 | "1レベル成功" 740 | diff --git a/lib/bcdice/game_system/KemonoNoMori.rb b/lib/bcdice/game_system/KemonoNoMori.rb 741 | --- a/lib/bcdice/game_system/KemonoNoMori.rb 742 | +++ b/lib/bcdice/game_system/KemonoNoMori.rb 743 | @@ -71,7 +71,7 @@ module BCDice 744 | 745 | # 行為判定の成功度は [目標値の10の位の数+1] 746 | # 継続判定の成功度は固定で+1 747 | - success_degree = is_action_judge ? target_total / 10 + 1 : 1 748 | + success_degree = is_action_judge ? (target_total / 10).to_i + 1 : 1 749 | 750 | dice_total = @randomizer.roll_once(12) 751 | debug('dice_total, target_total, success_degree = ', dice_total, target_total, success_degree) 752 | diff --git a/lib/bcdice/game_system/LogHorizon.rb b/lib/bcdice/game_system/LogHorizon.rb 753 | --- a/lib/bcdice/game_system/LogHorizon.rb 754 | +++ b/lib/bcdice/game_system/LogHorizon.rb 755 | @@ -1,6 +1,7 @@ 756 | # frozen_string_literal: true 757 | 758 | require "bcdice/base" 759 | +require "bcdice/arithmetic_evaluator" 760 | 761 | module BCDice 762 | module GameSystem 763 | @@ -361,13 +362,13 @@ module BCDice 764 | if index <= 6 765 | translate("LogHorizon.TRS.below_lower_limit", value: 6) # 6以下の出目は未定義です 766 | elsif index <= 62 767 | - @items[index] 768 | + @items[index.to_s] 769 | elsif index <= 72 770 | - "#{@items[index - 10]}&80G" 771 | + "#{@items[(index - 10).to_s]}&80G" 772 | elsif index <= 82 773 | - "#{@items[index - 20]}&160G" 774 | + "#{@items[(index - 20).to_s]}&160G" 775 | elsif index <= 87 776 | - "#{@items[index - 30]}&260G" 777 | + "#{@items[(index - 30).to_s]}&260G" 778 | else 779 | translate("LogHorizon.TRS.exceed_upper_limit", value: 88) # 88以上の出目は未定義です 780 | end 781 | @@ -382,7 +383,7 @@ module BCDice 782 | if index <= 6 783 | translate("LogHorizon.TRS.below_lower_limit", value: 6) 784 | elsif index <= 53 785 | - @items[index] 786 | + @items[index.to_s] 787 | else 788 | translate("LogHorizon.TRS.exceed_upper_limit", value: 54) 789 | end 790 | @@ -397,13 +398,13 @@ module BCDice 791 | if index <= 6 792 | translate("LogHorizon.TRS.below_lower_limit", value: 6) 793 | elsif index <= 162 794 | - @items[index] 795 | + @items[index.to_s] 796 | elsif index <= 172 797 | - "#{@items[index - 10]}&200G" 798 | + "#{@items[(index - 10).to_s]}&200G" 799 | elsif index <= 182 800 | - "#{@items[index - 20]}&400G" 801 | + "#{@items[(index - 20).to_s]}&400G" 802 | elsif index <= 187 803 | - "#{@items[index - 30]}&600G" 804 | + "#{@items[(index - 30).to_s]}&600G" 805 | else 806 | translate("LogHorizon.TRS.exceed_upper_limit", value: 188) 807 | end 808 | @@ -512,7 +513,7 @@ module BCDice 809 | 810 | table_name = translate("LogHorizon.ESTL.name") 811 | table = translate("LogHorizon.ESTL.items") 812 | - chosen = table[total].chomp 813 | + chosen = table[total.to_s].chomp 814 | 815 | return "#{table_name}(#{total}#{dice_str})\n#{chosen}" 816 | end 817 | diff --git a/lib/bcdice/game_system/NightmareHunterDeep.rb b/lib/bcdice/game_system/NightmareHunterDeep.rb 818 | --- a/lib/bcdice/game_system/NightmareHunterDeep.rb 819 | +++ b/lib/bcdice/game_system/NightmareHunterDeep.rb 820 | @@ -81,8 +81,8 @@ module BCDice 821 | return total >= target ? "成功" : "失敗" 822 | end 823 | 824 | - success_lv = (total + 1) / 5 825 | - success_nl = (total - 5) / 5 826 | + success_lv = ((total + 1) / 5).to_i 827 | + success_nl = ((total - 5) / 5).to_i 828 | 829 | return success_lv > 0 ? "Lv#{success_lv}/NL#{success_nl}成功" : "失敗" 830 | end 831 | diff --git a/lib/bcdice/game_system/NinjaSlayer.rb b/lib/bcdice/game_system/NinjaSlayer.rb 832 | --- a/lib/bcdice/game_system/NinjaSlayer.rb 833 | +++ b/lib/bcdice/game_system/NinjaSlayer.rb 834 | @@ -50,18 +50,18 @@ module BCDice 835 | end 836 | 837 | # 難易度の値の正規表現 838 | - DIFFICULTY_VALUE_RE = /UH|[2-6KENH]/i.freeze 839 | + DIFFICULTY_VALUE_RE = 'UH|[2-6KENH]'.freeze 840 | # 難易度の正規表現 841 | - DIFFICULTY_RE = /\[(#{DIFFICULTY_VALUE_RE})\]|@(#{DIFFICULTY_VALUE_RE})/io.freeze 842 | + DIFFICULTY_RE = "(\\[(#{DIFFICULTY_VALUE_RE})\\]|@(#{DIFFICULTY_VALUE_RE}))".freeze 843 | 844 | # 通常判定の正規表現 845 | - NJ_RE = /\A(S)?NJ(\d+)#{DIFFICULTY_RE}?\z/io.freeze 846 | + NJ_RE = /^(S)?NJ(\d+)#{DIFFICULTY_RE}?$/io.freeze 847 | # 回避判定の正規表現 848 | - EV_RE = %r{\AEV(\d+)#{DIFFICULTY_RE}?(?:/(\d+))?\z}io.freeze 849 | + EV_RE = /^EV(\d+)#{DIFFICULTY_RE}?(?:\/(\d+))?$/io.freeze 850 | # 近接攻撃の正規表現 851 | - AT_RE = /\AAT(\d+)#{DIFFICULTY_RE}?\z/io.freeze 852 | + AT_RE = /^AT(\d+)#{DIFFICULTY_RE}?$/io.freeze 853 | # 電子戦の正規表現 854 | - EL_RE = /\AEL(\d+)#{DIFFICULTY_RE}?\z/io.freeze 855 | + EL_RE = /^EL(\d+)#{DIFFICULTY_RE}?$/io.freeze 856 | 857 | # 回避判定のノード 858 | EV = Struct.new(:num, :difficulty, :targetValue) 859 | @@ -83,7 +83,7 @@ module BCDice 860 | m = NJ_RE.match(str) 861 | return str unless m 862 | 863 | - b_roll = bRollCommand(m[2], integerValueOfDifficulty(m[3] || m[4])) 864 | + b_roll = bRollCommand(m[2], integerValueOfDifficulty(m[4] || m[5])) 865 | return "#{m[1]}#{b_roll}" 866 | end 867 | 868 | @@ -129,8 +129,8 @@ module BCDice 869 | # @return [EV] 870 | def parseEV(m) 871 | num = m[1].to_i 872 | - difficulty = integerValueOfDifficulty(m[2] || m[3]) 873 | - targetValue = m[4]&.to_i 874 | + difficulty = integerValueOfDifficulty(m[3] || m[4]) 875 | + targetValue = m[5]&.to_i 876 | 877 | return EV.new(num, difficulty, targetValue) 878 | end 879 | @@ -140,7 +140,7 @@ module BCDice 880 | # @return [AT] 881 | def parseAT(m) 882 | num = m[1].to_i 883 | - difficulty = integerValueOfDifficulty(m[2] || m[3]) 884 | + difficulty = integerValueOfDifficulty(m[3] || m[4]) 885 | 886 | return AT.new(num, difficulty) 887 | end 888 | @@ -150,7 +150,7 @@ module BCDice 889 | # @return [EL] 890 | def parseEL(m) 891 | num = m[1].to_i 892 | - difficulty = integerValueOfDifficulty(m[2] || m[3]) 893 | + difficulty = integerValueOfDifficulty(m[3] || m[4]) 894 | 895 | return EL.new(num, difficulty) 896 | end 897 | @@ -220,7 +220,7 @@ module BCDice 898 | def integerValueOfDifficulty(s) 899 | return 4 unless s 900 | 901 | - return s.to_i if /\A[2-6]\z/.match(s) 902 | + return s.to_i if /^[2-6]$/.match(s) 903 | 904 | return DIFFICULTY_SYMBOL_TO_INTEGER.fetch(s.upcase) 905 | end 906 | diff --git a/lib/bcdice/game_system/RuinBreakers.rb b/lib/bcdice/game_system/RuinBreakers.rb 907 | --- a/lib/bcdice/game_system/RuinBreakers.rb 908 | +++ b/lib/bcdice/game_system/RuinBreakers.rb 909 | @@ -67,8 +67,8 @@ module BCDice 910 | return nil 911 | end 912 | 913 | - success_rate = ArithmeticEvaluator.eval(m[1]) 914 | - critical_border = m[4]&.to_i || [success_rate / 5, 1].max 915 | + success_rate = ArithmeticEvaluator.eval(m[1]).to_i 916 | + critical_border = m[4]&.to_i || [(success_rate / 5).to_i, 1].max 917 | fumble_border = m[6]&.to_i || (success_rate < 100 ? 96 : 99) 918 | 919 | total = @randomizer.roll_once(100) 920 | diff --git a/lib/bcdice/game_system/RuneQuestRoleplayingInGlorantha.rb b/lib/bcdice/game_system/RuneQuestRoleplayingInGlorantha.rb 921 | --- a/lib/bcdice/game_system/RuneQuestRoleplayingInGlorantha.rb 922 | +++ b/lib/bcdice/game_system/RuneQuestRoleplayingInGlorantha.rb 923 | @@ -135,7 +135,7 @@ module BCDice 924 | Result.success("#{result_prefix_str} 成功\n#{note_str}") 925 | else 926 | # 上記全てが当てはまらない時に突破可能な能力値を算出 927 | - "#{result_prefix_str} 相手側能力値#{active_ability_value + (50 + modifiy_value - roll_value) / 5}まで成功\n#{note_str}" 928 | + "#{result_prefix_str} 相手側能力値#{active_ability_value + ((50 + modifiy_value - roll_value) / 5).floor}まで成功\n#{note_str}" 929 | end 930 | end 931 | 932 | diff --git a/lib/bcdice/game_system/Ryutama.rb b/lib/bcdice/game_system/Ryutama.rb 933 | --- a/lib/bcdice/game_system/Ryutama.rb 934 | +++ b/lib/bcdice/game_system/Ryutama.rb 935 | @@ -86,14 +86,14 @@ module BCDice 936 | 937 | diceBase = dice1 938 | 939 | - dice1 = diceBase / 10 940 | + dice1 = (diceBase / 10).to_i 941 | dice2 = diceBase % 10 942 | 943 | if isValidDice(dice1, dice2) 944 | return dice1, dice2 945 | end 946 | 947 | - dice1 = diceBase / 100 948 | + dice1 = (diceBase / 100).to_i 949 | dice2 = diceBase % 100 950 | 951 | if isValidDice(dice1, dice2) 952 | diff --git a/lib/bcdice/game_system/ShinMegamiTenseiKakuseihen.rb b/lib/bcdice/game_system/ShinMegamiTenseiKakuseihen.rb 953 | --- a/lib/bcdice/game_system/ShinMegamiTenseiKakuseihen.rb 954 | +++ b/lib/bcdice/game_system/ShinMegamiTenseiKakuseihen.rb 955 | @@ -45,7 +45,7 @@ module BCDice 956 | def split_tens(value) 957 | value %= 100 958 | 959 | - ones = value / 10 960 | + ones = (value / 10).to_i 961 | tens = value % 10 962 | 963 | return [ones, tens] 964 | diff --git a/lib/bcdice/game_system/Siren.rb b/lib/bcdice/game_system/Siren.rb 965 | --- a/lib/bcdice/game_system/Siren.rb 966 | +++ b/lib/bcdice/game_system/Siren.rb 967 | @@ -50,7 +50,7 @@ module BCDice 968 | return Result.failure("(1D100<=#{target}) > #{dice} > 失敗") 969 | end 970 | 971 | - dig10 = dice / 10 972 | + dig10 = (dice / 10).floor 973 | dig1 = dice % 10 974 | if dig10 == 0 975 | dig10 = 10 976 | @@ -74,7 +74,7 @@ module BCDice 977 | 978 | dice = @randomizer.roll_once(100) 979 | 980 | - dig10 = dice / 10 981 | + dig10 = (dice / 10).floor 982 | dig1 = dice % 10 983 | if dig10 == 0 984 | dig10 = 10 985 | diff --git a/lib/bcdice/game_system/Skynauts.rb b/lib/bcdice/game_system/Skynauts.rb 986 | --- a/lib/bcdice/game_system/Skynauts.rb 987 | +++ b/lib/bcdice/game_system/Skynauts.rb 988 | @@ -78,7 +78,7 @@ module BCDice 989 | debug("移動修正", bonus) 990 | 991 | total = @randomizer.roll_once(6) 992 | - move_point_base = (total / 2) <= 0 ? 1 : (total / 2) 993 | + move_point_base = (total / 2).to_i <= 0 ? 1 : (total / 2).to_i 994 | movePoint = move_point_base + bonus 995 | debug("移動エリア数", movePoint) 996 | 997 | diff --git a/lib/bcdice/game_system/TensaiGunshiNiNaro.rb b/lib/bcdice/game_system/TensaiGunshiNiNaro.rb 998 | --- a/lib/bcdice/game_system/TensaiGunshiNiNaro.rb 999 | +++ b/lib/bcdice/game_system/TensaiGunshiNiNaro.rb 1000 | @@ -182,7 +182,7 @@ module BCDice 1001 | # ダメージ計算 1002 | damage = @randomizer.roll_sum(parsed.prefix_number, 6) 1003 | # HP減少量計算 1004 | - dec = damage / parsed.target_number 1005 | + dec = (damage / parsed.target_number).floor 1006 | 1007 | # HP減少量の最大値は3 1008 | dec = 3 if dec > 3 1009 | diff --git a/lib/bcdice/game_system/ToshiakiHolyGrailWar.rb b/lib/bcdice/game_system/ToshiakiHolyGrailWar.rb 1010 | --- a/lib/bcdice/game_system/ToshiakiHolyGrailWar.rb 1011 | +++ b/lib/bcdice/game_system/ToshiakiHolyGrailWar.rb 1012 | @@ -80,7 +80,7 @@ module BCDice 1013 | if modifier <= 10 1014 | 0 1015 | else 1016 | - modifier / 10 1017 | + (modifier / 10).to_i 1018 | end 1019 | end 1020 | 1021 | diff --git a/lib/bcdice/game_system/TunnelsAndTrolls.rb b/lib/bcdice/game_system/TunnelsAndTrolls.rb 1022 | --- a/lib/bcdice/game_system/TunnelsAndTrolls.rb 1023 | +++ b/lib/bcdice/game_system/TunnelsAndTrolls.rb 1024 | @@ -253,7 +253,7 @@ module BCDice 1025 | end 1026 | 1027 | def success_level(total, dice_total) 1028 | - level = (total - 15) / 5 1029 | + level = ((total - 15) / 5).to_i 1030 | if level <= 0 1031 | "失敗 > 経験値#{dice_total}" 1032 | else 1033 | diff --git a/lib/bcdice/game_system/VampireTheMasquerade5th.rb b/lib/bcdice/game_system/VampireTheMasquerade5th.rb 1034 | --- a/lib/bcdice/game_system/VampireTheMasquerade5th.rb 1035 | +++ b/lib/bcdice/game_system/VampireTheMasquerade5th.rb 1036 | @@ -169,7 +169,7 @@ module BCDice 1037 | 1038 | def get_critical_success(ten_dice) 1039 | # 10の目が2個毎に追加2成功 1040 | - return ((ten_dice / 2) * 2) 1041 | + return ((ten_dice / 2).to_i * 2) 1042 | end 1043 | 1044 | def make_dice_roll(dice_pool) 1045 | diff --git a/lib/bcdice/game_system/Villaciel.rb b/lib/bcdice/game_system/Villaciel.rb 1046 | --- a/lib/bcdice/game_system/Villaciel.rb 1047 | +++ b/lib/bcdice/game_system/Villaciel.rb 1048 | @@ -210,8 +210,8 @@ module BCDice 1049 | 1050 | roll_result1 = @randomizer.roll_once(D6) 1051 | chart_text, roll_result2 = case chart_symbol 1052 | - when 'V' then get_table_by_1d6(VILLACIEL_PREVIOUS_JOB_CHART[(roll_result1 - 1) / 3]) 1053 | - when 'A' then get_table_by_1d6(ARMESEAR_PREVIOUS_JOB_CHART[(roll_result1 - 1) / 2]) 1054 | + when 'V' then get_table_by_1d6(VILLACIEL_PREVIOUS_JOB_CHART[((roll_result1 - 1) / 3).to_i]) 1055 | + when 'A' then get_table_by_1d6(ARMESEAR_PREVIOUS_JOB_CHART[((roll_result1 - 1) / 2).to_i]) 1056 | end 1057 | 1058 | chart_title = case chart_symbol 1059 | @@ -278,7 +278,7 @@ module BCDice 1060 | when 6 then 3 1061 | end 1062 | get_table_by_1d6(VILLACIEL_PETIT_QUEST_CHART[chart_index]) 1063 | - when 'A' then get_table_by_1d6(ARMESEAR_PETIT_QUEST_CHART[(roll_result1 - 1) / 2]) 1064 | + when 'A' then get_table_by_1d6(ARMESEAR_PETIT_QUEST_CHART[((roll_result1 - 1) / 2).to_i]) 1065 | end 1066 | 1067 | chart_title = case chart_symbol 1068 | diff --git a/lib/bcdice/game_system/Warhammer.rb b/lib/bcdice/game_system/Warhammer.rb 1069 | --- a/lib/bcdice/game_system/Warhammer.rb 1070 | +++ b/lib/bcdice/game_system/Warhammer.rb 1071 | @@ -56,9 +56,9 @@ module BCDice 1072 | return nil unless cmp_op == :<= 1073 | 1074 | if total <= target 1075 | - Result.success("成功(成功度#{(target - total) / 10})") 1076 | + Result.success("成功(成功度#{((target - total) / 10).to_i})") 1077 | else 1078 | - Result.failure("失敗(失敗度#{(total - target) / 10})") 1079 | + Result.failure("失敗(失敗度#{((total - target) / 10).to_i})") 1080 | end 1081 | end 1082 | 1083 | diff --git a/lib/bcdice/game_system/Warhammer4.rb b/lib/bcdice/game_system/Warhammer4.rb 1084 | --- a/lib/bcdice/game_system/Warhammer4.rb 1085 | +++ b/lib/bcdice/game_system/Warhammer4.rb 1086 | @@ -62,8 +62,8 @@ module BCDice 1087 | return Result.nothing if target == '?' 1088 | return nil unless cmp_op == :<= 1089 | 1090 | - t10 = total / 10 1091 | - d10 = target / 10 1092 | + t10 = (total / 10).to_i 1093 | + d10 = (target / 10).to_i 1094 | sl = d10 - t10 1095 | 1096 | if total <= 5 && sl < 1 1097 | @@ -301,7 +301,7 @@ module BCDice 1098 | if dice == 100 1099 | return 0, 0 1100 | else 1101 | - return dice / 10, dice % 10 1102 | + return (dice / 10).to_i, dice % 10 1103 | end 1104 | end 1105 | 1106 | diff --git a/lib/bcdice/game_system/WerewolfTheApocalypse5th.rb b/lib/bcdice/game_system/WerewolfTheApocalypse5th.rb 1107 | --- a/lib/bcdice/game_system/WerewolfTheApocalypse5th.rb 1108 | +++ b/lib/bcdice/game_system/WerewolfTheApocalypse5th.rb 1109 | @@ -76,7 +76,7 @@ module BCDice 1110 | if rage_dice_pool >= 0 1111 | rage_dice_text, rage_success_dice, rage_ten_dice, brutal_result_dice = make_dice_roll(rage_dice_pool) 1112 | 1113 | - brutal_outcome = brutal_result_dice / 2 1114 | + brutal_outcome = (brutal_result_dice / 2).floor 1115 | ten_dice += rage_ten_dice 1116 | success_dice += rage_success_dice 1117 | 1118 | @@ -161,7 +161,7 @@ module BCDice 1119 | 1120 | def get_critical_success(ten_dice) 1121 | # 10の目が2個毎に追加2成功 1122 | - return ((ten_dice / 2) * 2) 1123 | + return ((ten_dice / 2).floor * 2) 1124 | end 1125 | 1126 | def make_dice_roll(dice_pool) 1127 | diff --git a/lib/bcdice/game_system/Yggdrasill.rb b/lib/bcdice/game_system/Yggdrasill.rb 1128 | --- a/lib/bcdice/game_system/Yggdrasill.rb 1129 | +++ b/lib/bcdice/game_system/Yggdrasill.rb 1130 | @@ -198,7 +198,7 @@ module BCDice 1131 | 1 1132 | elsif value <= 11 1133 | dice = @randomizer.roll_once(6) 1134 | - total = dice / 2 1135 | + total = (dice / 2).to_i 1136 | "#{total}(#{dice}[#{dice}]/2)" 1137 | elsif value <= 14 1138 | dice = @randomizer.roll_once(6) 1139 | diff --git a/lib/bcdice/game_system/beginning_idol/chain_d66_table.rb b/lib/bcdice/game_system/beginning_idol/chain_d66_table.rb 1140 | --- a/lib/bcdice/game_system/beginning_idol/chain_d66_table.rb 1141 | +++ b/lib/bcdice/game_system/beginning_idol/chain_d66_table.rb 1142 | @@ -18,7 +18,7 @@ module BCDice 1143 | dice = randomizer.roll_barabara(2, 6).sort 1144 | 1145 | value = dice[0] * 10 + dice[1] 1146 | - chosen = @items[value] 1147 | + chosen = @items[value.to_s] 1148 | body = chosen.map { |item| item.respond_to?(:roll) ? item.roll(randomizer) : item }.join("\n") 1149 | 1150 | return "#{@name}(#{value}) > #{body}" 1151 | diff --git a/lib/bcdice/game_system/beginning_idol/costume_table.rb b/lib/bcdice/game_system/beginning_idol/costume_table.rb 1152 | --- a/lib/bcdice/game_system/beginning_idol/costume_table.rb 1153 | +++ b/lib/bcdice/game_system/beginning_idol/costume_table.rb 1154 | @@ -20,7 +20,7 @@ module BCDice 1155 | # @return [String] 1156 | def roll(randomizer) 1157 | value = randomizer.roll_d66(D66SortType::ASC) 1158 | - "#{@name}(#{value}) > #{@items[value]}" 1159 | + "#{@name}(#{value}) > #{@items[value.to_s]}" 1160 | end 1161 | 1162 | # @return [DiceTable::D66Table] 1163 | diff --git a/lib/bcdice/game_system/beginning_idol/random_event_table.rb b/lib/bcdice/game_system/beginning_idol/random_event_table.rb 1164 | --- a/lib/bcdice/game_system/beginning_idol/random_event_table.rb 1165 | +++ b/lib/bcdice/game_system/beginning_idol/random_event_table.rb 1166 | @@ -21,7 +21,7 @@ module BCDice 1167 | name = I18n.t("BeginningIdol.RE.name", locale: @locale) 1168 | result_format = I18n.t("BeginningIdol.RE.format", locale: @locale) 1169 | 1170 | - chosen = table[:items][d66_index] 1171 | + chosen = table[:items][d66_index.to_s] 1172 | 1173 | return "#{name} > (1D6) > #{first_index}\n#{table[:name]} > [#{d66_index}] > #{format(result_format, event: chosen[0], page: chosen[1])}" 1174 | end 1175 | diff --git a/lib/bcdice/game_system/beginning_idol/table.rb b/lib/bcdice/game_system/beginning_idol/table.rb 1176 | --- a/lib/bcdice/game_system/beginning_idol/table.rb 1177 | +++ b/lib/bcdice/game_system/beginning_idol/table.rb 1178 | @@ -76,9 +76,9 @@ module BCDice 1179 | I18n.t("BeginningIdol.GG.name", locale: locale), 1180 | I18n.t("BeginningIdol.GG.items", locale: locale).to_h do |index, value| 1181 | chain = 1182 | - if [23, 24, 25].include?(index) 1183 | + if ["23", "24", "25"].include?(index) 1184 | [value, rare_skill_table] 1185 | - elsif index == 56 1186 | + elsif index == "56" 1187 | [value, item_table] 1188 | else 1189 | [value] 1190 | @@ -88,9 +88,13 @@ module BCDice 1191 | end 1192 | ) 1193 | 1194 | + ha_items = I18n.t("BeginningIdol.HA.items", locale: locale) 1195 | + ha_items["22"] = [ha_items["22"], SkillHometown.new(skill_table)].flatten 1196 | + 1197 | + 1198 | ha = ChainD66Table.new( 1199 | I18n.t("BeginningIdol.HA.name", locale: locale), 1200 | - I18n.t("BeginningIdol.HA.items", locale: locale).dup.tap { |items| items[22].push(SkillHometown.new(skill_table)) } 1201 | + ha_items 1202 | ) 1203 | 1204 | { 1205 | diff --git a/lib/bcdice/game_system/cthulhu7th/full_auto.rb b/lib/bcdice/game_system/cthulhu7th/full_auto.rb 1206 | --- a/lib/bcdice/game_system/cthulhu7th/full_auto.rb 1207 | +++ b/lib/bcdice/game_system/cthulhu7th/full_auto.rb 1208 | @@ -43,7 +43,7 @@ module BCDice 1209 | broken_number = m[3].to_i 1210 | bonus_dice_count = m[4].to_i 1211 | stop_count = m[5]&.downcase || "" 1212 | - bullet_set_count_cap = m[6]&.to_i || diff / 10 1213 | + bullet_set_count_cap = m[6]&.to_i || (diff / 10).to_i 1214 | 1215 | output = "" 1216 | 1217 | @@ -55,8 +55,8 @@ module BCDice 1218 | end 1219 | 1220 | # ボレーの上限の設定がおかしい場合の注意表示 1221 | - if (bullet_set_count_cap > diff / 10) && (diff > 39) && !m[6].nil? 1222 | - bullet_set_count_cap = diff / 10 1223 | + if (bullet_set_count_cap > (diff / 10).to_i) && (diff > 39) && !m[6].nil? 1224 | + bullet_set_count_cap = (diff / 10).to_i 1225 | output += "ボレーの弾丸の数の上限は\[技能値÷10(切り捨て)\]発なので、それより高い数を指定できません。ボレーの弾丸の数を#{bullet_set_count_cap}発に変更します。\n" 1226 | elsif (diff <= 39) && (bullet_set_count_cap > 3) && !m[6].nil? 1227 | bullet_set_count_cap = 3 1228 | @@ -247,7 +247,7 @@ module BCDice 1229 | end 1230 | 1231 | def getSetOfBullet(diff, bullet_set_count_cap) 1232 | - bullet_set_count = diff / 10 1233 | + bullet_set_count = (diff / 10).to_i 1234 | 1235 | if bullet_set_count_cap < bullet_set_count 1236 | bullet_set_count = bullet_set_count_cap 1237 | @@ -261,7 +261,7 @@ module BCDice 1238 | end 1239 | 1240 | def getHitBulletCountBase(diff, bullet_set_count) 1241 | - hit_bullet_count_base = (bullet_set_count / 2) 1242 | + hit_bullet_count_base = (bullet_set_count / 2).to_i 1243 | 1244 | if (diff >= 1) && (diff < 30) 1245 | hit_bullet_count_base = 1 # 技能値29以下での最低値保障 1246 | diff --git a/lib/bcdice/loader.rb b/lib/bcdice/loader.rb 1247 | --- a/lib/bcdice/loader.rb 1248 | +++ b/lib/bcdice/loader.rb 1249 | @@ -28,7 +28,7 @@ module BCDice 1250 | class_name = id.tr(":.", "_") 1251 | 1252 | # 対象ディレクトリの外にあるファイルをロードされないように制約を設ける 1253 | - unless /\A[A-Z]\w*\z/.match?(class_name) 1254 | + unless /^[A-Z]\w*$/.match?(class_name) 1255 | return nil 1256 | end 1257 | 1258 | diff --git a/lib/bcdice/preprocessor.rb b/lib/bcdice/preprocessor.rb 1259 | --- a/lib/bcdice/preprocessor.rb 1260 | +++ b/lib/bcdice/preprocessor.rb 1261 | @@ -1,5 +1,7 @@ 1262 | # frozen_string_literal: true 1263 | 1264 | +require "bcdice/arithmetic" 1265 | + 1266 | module BCDice 1267 | # 入力文字列に対して前処理を行う 1268 | # 1269 | diff --git a/lib/bcdice/user_defined_dice_table.rb b/lib/bcdice/user_defined_dice_table.rb 1270 | --- a/lib/bcdice/user_defined_dice_table.rb 1271 | +++ b/lib/bcdice/user_defined_dice_table.rb 1272 | @@ -108,7 +108,7 @@ module BCDice 1273 | def parse 1274 | return if @rows 1275 | 1276 | - lines = @text.split(/\R/).map(&:rstrip).reject(&:empty?) 1277 | + lines = @text.split(/\r?\n/).map(&:rstrip).reject(&:empty?) 1278 | @name = lines.shift 1279 | @type = lines.shift.upcase 1280 | @rows = lines 1281 | diff --git a/test/data/calc.toml b/test/data/calc.toml 1282 | --- a/test/data/calc.toml 1283 | +++ b/test/data/calc.toml 1284 | @@ -256,3 +256,57 @@ input = "sc1+4*3/2" 1285 | output = "c(1+4*3/2) > 7" 1286 | secret = true 1287 | rands = [] 1288 | + 1289 | +[[ test ]] 1290 | +game_system = "DiceBot" 1291 | +input = "C(1/2F)" 1292 | +output = "c(1/2F) > 0" 1293 | +rands = [] 1294 | + 1295 | +[[ test ]] 1296 | +game_system = "DiceBot" 1297 | +input = "1d(3/2U)" 1298 | +output = "(1D2) > 1" 1299 | +rands = [{ sides = 2, value = 1 }] 1300 | + 1301 | +[[ test ]] 1302 | +game_system = "DiceBot" 1303 | +input = "(3/2F)d1" 1304 | +output = "(1D1) > 1" 1305 | +rands = [{ sides = 1, value = 1 }] 1306 | + 1307 | +[[ test ]] 1308 | +game_system = "DiceBot" 1309 | +input = "c((1+2)/2F) 切り捨て" 1310 | +output = "c((1+2)/2F) > 1" 1311 | +rands = [{ sides = 1, value = 1 }] 1312 | + 1313 | +[[ test ]] 1314 | +game_system = "DiceBot" 1315 | +input = "C(-5/10F)" 1316 | +output = "c(-5/10F) > -1" 1317 | +rands = [] 1318 | + 1319 | +[[ test ]] 1320 | +game_system = "DiceBot" 1321 | +input = "C(-5/10C)" 1322 | +output = "c(-5/10C) > 0" 1323 | +rands = [] 1324 | + 1325 | +[[ test ]] 1326 | +game_system = "DiceBot" 1327 | +input = "C(-6/10R)" 1328 | +output = "c(-6/10R) > -1" 1329 | +rands = [] 1330 | + 1331 | +[[ test ]] 1332 | +game_system = "DiceBot" 1333 | +input = "C(-5/10R)" 1334 | +output = "c(-5/10R) > -1" 1335 | +rands = [] 1336 | + 1337 | +[[ test ]] 1338 | +game_system = "DiceBot" 1339 | +input = "C(-4/10R)" 1340 | +output = "c(-4/10R) > 0" 1341 | +rands = [] 1342 | -------------------------------------------------------------------------------- /ruby/emurators/i18n.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'json' 4 | 5 | # Simple emulator 6 | module I18n 7 | class << self 8 | @@load_path = [] 9 | @@table = nil 10 | def load_path 11 | @@load_path 12 | end 13 | 14 | def default_locale 15 | @@default_locale 16 | end 17 | 18 | def default_locale=(locale) 19 | @@default_locale = locale 20 | end 21 | 22 | def fallbacks 23 | I18n::Locale::Fallbacks.new 24 | end 25 | 26 | def load_default_translation 27 | return unless @@table.nil? 28 | 29 | @@table = {} 30 | load_translation(`JSON.stringify(require('./i18n/i18n.json'))`) 31 | end 32 | 33 | def load_translation(json) 34 | load_default_translation 35 | 36 | table = JSON.parse(json, symbolize_names: true) 37 | @@table = @@table.merge(table) do |_key, oldval, newval| 38 | oldval.merge(newval) 39 | end 40 | end 41 | 42 | # only used to i18n dynamic import test 43 | def clear_translate_table 44 | @@table = nil 45 | end 46 | 47 | def translate(key, locale: nil, **options) 48 | load_default_translation 49 | 50 | path = key.split('.').map(&:to_sym) 51 | table = @@table 52 | default_locale = @@default_locale 53 | 54 | result = table.dig(locale, *path) || table.dig(default_locale, *path) 55 | begin 56 | result = format(result, **options) if result.is_a?(String) 57 | rescue ArgumentError, KeyError 58 | # Ignore 59 | end 60 | 61 | result || options[:default] 62 | end 63 | alias t translate 64 | end 65 | 66 | module Locale 67 | # Stub 68 | class Fallbacks < Hash 69 | def defaults=(value); end 70 | end 71 | end 72 | 73 | module Backend 74 | # Stub 75 | module Simple 76 | end 77 | end 78 | 79 | # Stub 80 | module Thread 81 | class << self 82 | def current 83 | { 84 | i18n_fallbacks: nil 85 | } 86 | end 87 | end 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /ruby/patch.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'strscan' 4 | 5 | # Stub 6 | class Dir 7 | class << self 8 | def [] 9 | [] 10 | end 11 | end 12 | end 13 | 14 | # Opal Fix 15 | module StringScannerPatch 16 | # rubocop:disable Lint/UnusedMethodArgument 17 | def scan_until(pattern) 18 | # rubocop:enable Lint/UnusedMethodArgument 19 | 20 | %x{ 21 | var self = this; 22 | 23 | 24 | pattern = self.$anchor(pattern); 25 | 26 | var pos = self.pos, 27 | working = self.working, 28 | result; 29 | 30 | while (true) { 31 | var isEmpty = working.length === 0; 32 | result = pattern.exec(working); 33 | pos += 1; 34 | working = working.substr(1); 35 | 36 | if (result == null) { 37 | if (isEmpty) { 38 | return self.matched = nil; 39 | } 40 | 41 | continue; 42 | } 43 | 44 | self.matched = self.string.substr(self.pos, pos - self.pos - 1 + result[0].length); 45 | self.prev_pos = pos - 1; 46 | self.pos = pos; 47 | self.working = working.substr(result[0].length - 1); 48 | 49 | return self.matched; 50 | } 51 | } 52 | end 53 | end 54 | StringScanner.prepend StringScannerPatch 55 | 56 | `Opal.top.$__dir__ = () => '/'` 57 | 58 | nil 59 | -------------------------------------------------------------------------------- /scripts/autopatch.md: -------------------------------------------------------------------------------- 1 | # autopatch 2 | パッチ更新補助スクリプト 3 | 4 | ## Requirements 5 | * git 6 | * diff 7 | 8 | ## Usage 9 | 1. `cd path/to/bcdice-js` 10 | 2. `git submodule update` (patch.diffが適用できるRevに) 11 | 3. `./scripts/autopatch_rebase.sh` 12 | 4. Conflictを解消、`git add` 13 | 5. `./scripts/autopatch_apply.sh` 14 | 15 | ## Notes 16 | * ローカルのBCDiceリポジトリに未コミット差分がないこと。 17 | * ローカルのBCDiceリポジトリ上でブランチ`bcdice-js-auto-patch`が使用されていないこと。 18 | * 失敗したら上記ブランチを手動で削除する。 19 | -------------------------------------------------------------------------------- /scripts/autopatch_apply.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | BRANCH="${1:-master}" 4 | 5 | cd BCDice 6 | git rebase --continue || true 7 | git diff $BRANCH > ../patch.diff 8 | git checkout $BRANCH 9 | git branch -D bcdice-js-auto-patch 10 | 11 | cd .. 12 | ./scripts/autopatch_strip.sh 13 | -------------------------------------------------------------------------------- /scripts/autopatch_rebase.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | BRANCH="${1:-master}" 4 | 5 | git submodule update -f 6 | cd BCDice 7 | git checkout -b bcdice-js-auto-patch 8 | patch -p1 < ../patch.diff 9 | git commit -am "bcdice-js-auto-patch" 10 | git checkout $BRANCH 11 | git pull --rebase 12 | git checkout bcdice-js-auto-patch 13 | git rebase $BRANCH 14 | -------------------------------------------------------------------------------- /scripts/autopatch_strip.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | sed -i -e '/^index [0-9a-f . ]*$/d' patch.diff 4 | -------------------------------------------------------------------------------- /ts/base.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import Base from './base'; 3 | import { mockRandomizer } from './test/randomizer'; 4 | 5 | describe('Base', () => { 6 | it('evaluates "2D6>=2"', () => { 7 | const base = new Base('2d6>=2'); 8 | 9 | mockRandomizer(base) 10 | .onCall(0).returns(1) 11 | .onCall(1).returns(2); 12 | 13 | const result = base.eval(); 14 | 15 | expect(result?.text).to.equal('(2D6>=2) > 3[1,2] > 3 > 成功'); 16 | expect(result?.rands).to.deep.equal([[1, 6], [2, 6]]); 17 | 18 | expect(result?.secret).to.be.false; 19 | expect(result?.success).to.be.true; 20 | expect(result?.failure).to.be.false; 21 | expect(result?.critical).to.be.false; 22 | expect(result?.fumble).to.be.false; 23 | 24 | expect(result?.detailedRands).to.deep.equal([ 25 | { kind: 'normal', sides: 6, value: 1 }, 26 | { kind: 'normal', sides: 6, value: 2 }, 27 | ]); 28 | }); 29 | 30 | it('evaluates static', () => { 31 | const result = Base.eval('2d6>=2'); 32 | expect(result?.text).to.match(/^\(2D6>=2\) > /); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /ts/base.ts: -------------------------------------------------------------------------------- 1 | import '../lib/bcdice/arithmetic_evaluator'; 2 | import '../lib/bcdice/common_command'; 3 | import '../lib/bcdice/base'; 4 | import '../lib/bcdice/preprocessor'; 5 | 6 | import { BCDice } from './internal'; 7 | import { BaseInstance } from './internal/types/base'; 8 | import Result, { parseResult } from './result'; 9 | import { RandomizerInstance } from './internal/types/randomizer'; 10 | 11 | export default class Base { 12 | static eval(command: string): Result | null { 13 | const result = BCDice.Base.$eval(command); 14 | return parseResult(result); 15 | } 16 | 17 | private readonly internal: BaseInstance; 18 | 19 | get randomizer(): RandomizerInstance { 20 | return this.internal.randomizer; 21 | } 22 | 23 | constructor(command: string, internal?: BaseInstance) { 24 | this.internal = internal ?? BCDice.Base.$new(command); 25 | } 26 | 27 | eval(): Result | null { 28 | const result = this.internal.$eval(); 29 | return parseResult(result); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /ts/bcdice/game_system_list.json.d.ts: -------------------------------------------------------------------------------- 1 | export interface GameSystemInfo { 2 | id: string; 3 | className: string; 4 | name: string; 5 | sortKey: string; 6 | superClassName: string; 7 | locale: string; 8 | } 9 | 10 | export declare const gameSystems: GameSystemInfo[]; 11 | -------------------------------------------------------------------------------- /ts/bcdice/i18n_list.json.d.ts: -------------------------------------------------------------------------------- 1 | export interface I18nInfo { 2 | baseClassName: string; 3 | locales: string[]; 4 | } 5 | 6 | export declare const i18nList: I18nInfo[]; 7 | -------------------------------------------------------------------------------- /ts/game_system.ts: -------------------------------------------------------------------------------- 1 | import Base from './base'; 2 | import { BaseInstance } from './internal/types/base'; 3 | import Result from './result'; 4 | 5 | /** 6 | * Definition of class of GameSystem 7 | **/ 8 | export default interface GameSystemClass { 9 | ID: string; 10 | NAME: string; 11 | SORT_KEY: string; 12 | HELP_MESSAGE: string; 13 | COMMAND_PATTERN: RegExp; 14 | 15 | new (command: string, internal?: BaseInstance): Base; 16 | eval(command: string): Result | null; 17 | } 18 | -------------------------------------------------------------------------------- /ts/game_system_commands.test.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import { expect } from 'chai'; 4 | import DynamicLoader from './loader/dynamic_loader'; 5 | import { mockRandomizer } from './test/randomizer'; 6 | import { I18n } from './internal'; 7 | 8 | type TestDataType = Record; 24 | 25 | const langPattern = /_(Korean)$/; 26 | 27 | const testData = JSON.parse(fs.readFileSync(path.join(__dirname, '../lib/bcdice/test_data.json')).toString()); 28 | Object.keys(testData).forEach(id => { 29 | describe(id, () => { 30 | (testData as TestDataType)[id].test.map((data) => { 31 | const test = { 32 | ...data, 33 | output: data.output === '' ? undefined : data.output, 34 | secret: data.secret ?? false, 35 | success: data.success ?? false, 36 | failure: data.failure ?? false, 37 | critical: data.critical ?? false, 38 | fumble: data.fumble ?? false, 39 | }; 40 | 41 | const loader = new DynamicLoader(); 42 | 43 | it('should be valid GameSystem', async () => { 44 | I18n.$clear_translate_table(); 45 | const loader = new DynamicLoader(); 46 | const GameSystemClass = await loader.dynamicLoad(test.game_system); 47 | 48 | expect(GameSystemClass.ID).is.not.empty; 49 | expect(GameSystemClass.NAME).is.not.empty; 50 | expect(GameSystemClass.SORT_KEY).is.not.empty; 51 | expect(GameSystemClass.HELP_MESSAGE).is.not.empty; 52 | expect(GameSystemClass.COMMAND_PATTERN).exist; 53 | 54 | switch (id) { 55 | case 'AddDice': 56 | case 'calc': 57 | case 'choice': 58 | case 'D66Dice': 59 | case 'dummyBot': 60 | case 'Misonzai': 61 | case 'None': 62 | case 'randomizer': 63 | case 'Repeat': 64 | case 'tally_ty': 65 | case 'tally_tz': 66 | case 'UpperDice': 67 | expect(GameSystemClass.ID).to.equals('DiceBot'); 68 | break; 69 | case 'tally_sort': 70 | break; 71 | default: 72 | expect(GameSystemClass.ID?.replace(/[^0-9A-Za-z_]/g, '_')?.replace(langPattern, '')).to.equal(id.replace(langPattern, '')); // ToDo: Language suffix 73 | break; 74 | } 75 | }); 76 | 77 | it(`evals ${test.input} to ${test.output}`, async () => { 78 | I18n.$clear_translate_table(); 79 | const GameSystemClass = await loader.dynamicLoad(test.game_system); 80 | const gameSystem = new GameSystemClass(test.input); 81 | 82 | const $random = mockRandomizer(gameSystem); 83 | test.rands.forEach(({ value }, i) => { 84 | $random.onCall(i).returns(value); 85 | }); 86 | $random.onCall(test.rands.length).throwsException(new Error('Unexpected call for $random')); 87 | 88 | const res = gameSystem.eval(); 89 | 90 | if (!res) { 91 | expect(test.output).is.undefined; 92 | return; 93 | } 94 | 95 | expect(res.text).to.equal(test.output); 96 | expect(res.secret).to.equal(test.secret); 97 | expect(res.success).to.equal(test.success); 98 | expect(res.failure).to.equal(test.failure); 99 | expect(res.critical).to.equal(test.critical); 100 | expect(res.fumble).to.equal(test.fumble); 101 | expect(test.input).to.match(GameSystemClass.COMMAND_PATTERN); 102 | }); 103 | }); 104 | }); 105 | }); 106 | -------------------------------------------------------------------------------- /ts/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Base } from './base'; 2 | export { default as DynamicLoader } from './loader/dynamic_loader'; 3 | export { default as UserDefinedDiceTable } from './user_defined_dice_table'; 4 | export { default as Version } from './version'; 5 | -------------------------------------------------------------------------------- /ts/internal/index.ts: -------------------------------------------------------------------------------- 1 | import '../../lib/bcdice/version'; 2 | 3 | import Opal_, { Module } from './opal'; 4 | import { GameSystemModule } from './types/game_system'; 5 | import { UserDefinedDiceTableClass } from './types/user_defined_dice_table'; 6 | import { RandomizerClass } from './types/randomizer'; 7 | import { BaseClass } from './types/base'; 8 | 9 | export interface BCDiceModule extends Module { 10 | Base: BaseClass; 11 | GameSystem: GameSystemModule; 12 | Randomizer: RandomizerClass; 13 | UserDefinedDiceTable: UserDefinedDiceTableClass; 14 | VERSION: string; 15 | } 16 | 17 | export interface I18nModule extends Module { 18 | $load_translation(json: string): void; 19 | $default_locale(): string; 20 | $clear_translate_table(): void; 21 | } 22 | 23 | export const BCDice = Opal_.module(null, 'BCDice'); 24 | export const I18n = Opal_.module(null, 'I18n') 25 | export { default as Opal } from './opal'; 26 | -------------------------------------------------------------------------------- /ts/internal/opal.ts: -------------------------------------------------------------------------------- 1 | import '../../lib/bcdice/opal'; 2 | 3 | export type Hash = unknown; 4 | 5 | export interface RubyObject { 6 | $constants(): string[]; 7 | $const_get(name: string): T; 8 | $remove_const(name: string): void; 9 | } 10 | 11 | export type Module = RubyObject; 12 | 13 | export interface Opal { 14 | require(path: string): boolean; 15 | module(parent?: string | null, name?: string): M; 16 | hash(obj: Record): Hash; 17 | nil: Record; 18 | Hash: Hash; 19 | config: Record; 20 | } 21 | 22 | declare const Opal: Opal; 23 | Opal.config.unsupported_features_severity = 'ignore'; 24 | 25 | export default Opal; 26 | 27 | export function nilToNull(value: T): T | null { 28 | return value === Opal.nil ? null : value; 29 | } 30 | -------------------------------------------------------------------------------- /ts/internal/types/base.ts: -------------------------------------------------------------------------------- 1 | import { RubyObject } from '../opal'; 2 | import { RandomizerInstance } from './randomizer'; 3 | import Result from './result'; 4 | 5 | export interface BaseInstance { 6 | $eval(): Result; 7 | randomizer: RandomizerInstance; 8 | } 9 | export interface BaseClass extends Function, RubyObject { 10 | $new(command: string): BaseInstance; 11 | $eval(command: string): Result; 12 | $command_pattern(): RegExp; 13 | } 14 | -------------------------------------------------------------------------------- /ts/internal/types/game_system.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '../opal'; 2 | import { BaseClass } from './base'; 3 | 4 | export type GameSystemModule = Module & { 5 | [className: string]: BaseClass; 6 | } 7 | -------------------------------------------------------------------------------- /ts/internal/types/randomizer.ts: -------------------------------------------------------------------------------- 1 | import Result from './result'; 2 | 3 | export interface RandomizerInstance { 4 | rand_results: [number, number][]; 5 | detailed_rand_results: Result['detailed_rands'][]; 6 | $random(sides: number): number; 7 | } 8 | 9 | export interface RandomizerClass { 10 | $new(): RandomizerInstance; 11 | } 12 | -------------------------------------------------------------------------------- /ts/internal/types/result.ts: -------------------------------------------------------------------------------- 1 | import TSResult from '../../result'; 2 | 3 | type Result = Omit & { 4 | detailed_rands: { 5 | $kind(): string; 6 | $sides(): number; 7 | $value(): number; 8 | $to_n(): TSResult['detailedRands'][0]; 9 | }[]; 10 | } 11 | export default Result; 12 | -------------------------------------------------------------------------------- /ts/internal/types/user_defined_dice_table.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Hash } from '../opal'; 3 | import Result from './result'; 4 | 5 | export interface UserDefinedDiceTableInstance { 6 | $roll($kwargs?: Hash): Result; 7 | ['$valid?'](): boolean; 8 | } 9 | 10 | export interface UserDefinedDiceTableClass extends Function { 11 | $new(text: string): UserDefinedDiceTableInstance; 12 | $roll(text: string): Result; 13 | } 14 | -------------------------------------------------------------------------------- /ts/loader/dynamic_loader.ts: -------------------------------------------------------------------------------- 1 | import Loader, { I18nJsonObject } from './loader'; 2 | 3 | export default class DynamicLoader extends Loader { 4 | async dynamicImportI18n(baseClassName: string, locale: string): Promise { 5 | return (await import(`../../lib/bcdice/i18n/${baseClassName}.${locale}.json`)).default as I18nJsonObject; 6 | } 7 | 8 | async dynamicImport(className: string): Promise { 9 | await import(`../../lib/bcdice/game_system/${className}`); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /ts/loader/loader.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { I18n } from '../internal'; 3 | import Loader from './loader'; 4 | 5 | const loaders: [string, string, boolean][] = [ 6 | ['./dynamic_loader', 'DynamicLoader', true], 7 | ['./static_loader', 'StaticLoader', false], 8 | ]; 9 | 10 | loaders.forEach(([path, title /*, dynamic */ ]) => { 11 | describe(title, () => { 12 | 13 | I18n.$clear_translate_table(); 14 | let loader: Loader; 15 | it('can be imported', async () => { 16 | loader = new (await import(path)).default(); 17 | }); 18 | 19 | it('loads game system class', async () => { 20 | const gameSystemClass = await loader.dynamicLoad('SwordWorld2.5'); 21 | expect(gameSystemClass?.eval('K12')?.text).to.match(/^KeyNo\.12c\[10\] > 2D:\[/); 22 | }); 23 | 24 | it('enumerates game systems', async () => { 25 | const gameSystems = loader.listLoadedGameSystems(); 26 | expect(Array.isArray(gameSystems)).to.be.true; 27 | expect(gameSystems.length).greaterThan(0); 28 | expect(gameSystems.every(a => Boolean(a))).to.be.true; 29 | }); 30 | 31 | it('returns class of game system', () => { 32 | const gameSystemClass = loader.getGameSystemClass('SwordWorld2.5'); 33 | expect(gameSystemClass?.eval('K20')?.text).to.match(/^KeyNo\.20c\[10\] > 2D:\[/); 34 | }); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /ts/loader/loader.ts: -------------------------------------------------------------------------------- 1 | import Base from '../base'; 2 | import { BaseClass, BaseInstance } from '../internal/types/base'; 3 | import { BCDice, I18n } from '../internal'; 4 | import Result, { parseResult } from '../result'; 5 | import GameSystemList, { GameSystemInfo } from '../../lib/bcdice/game_system_list.json'; 6 | import I18nList, { I18nInfo } from '../../lib/bcdice/i18n_list.json'; 7 | import GameSystemClass from '../game_system'; 8 | 9 | export type I18nJsonObject = Record; 10 | 11 | export function getGameSystemClass(gameSystemClass: BaseClass): GameSystemClass { 12 | return class extends Base { 13 | static readonly ID = gameSystemClass.$const_get('ID'); 14 | static readonly NAME = gameSystemClass.$const_get('NAME'); 15 | static readonly SORT_KEY = gameSystemClass.$const_get('SORT_KEY'); 16 | static readonly HELP_MESSAGE = gameSystemClass.$const_get('HELP_MESSAGE'); 17 | static readonly COMMAND_PATTERN = gameSystemClass.$command_pattern(); 18 | 19 | static eval(command: string): Result | null { 20 | return parseResult(gameSystemClass.$eval(command)); 21 | } 22 | 23 | constructor(command: string, internal?: BaseInstance) { 24 | super(command, internal ?? gameSystemClass.$new(command)); 25 | } 26 | }; 27 | } 28 | 29 | export default class Loader { 30 | getGameSystemIdByName(name: string): string { 31 | const id = this.listAvailableGameSystems().find(info => info.name === name)?.id; 32 | if (!id) throw new Error(`GameSystem named ${name} does not exists`); 33 | return id; 34 | } 35 | 36 | getGameSystemInfo(id: string): GameSystemInfo { 37 | const info = this.listAvailableGameSystems().find(info => info.id === id); 38 | if (!info) throw new Error('GameSystem is not found'); 39 | 40 | return info; 41 | } 42 | 43 | getGameSystemClass(id: string): GameSystemClass { 44 | const gameSystem = this.listLoadedGameSystems().find(a => a.ID === id); 45 | if (!gameSystem) throw new Error(`GameSystem ${id} is not loaded`); 46 | return gameSystem; 47 | } 48 | 49 | getI18nInfos(className: string): I18nInfo[] { 50 | const baseClassName = className.replace(/_[^0-9].*$/g, ''); 51 | const i18n = this.listAvailabletI18nInfoList().find(i18n => i18n.baseClassName === baseClassName || i18n.baseClassName === className); 52 | const i18ns = i18n ? [i18n] : []; 53 | const info = this.listAvailableGameSystems().find(info => info.className === className); 54 | return info ? Array.from(new Set(this.getI18nInfos(info.superClassName).concat(i18ns))) : i18ns; 55 | } 56 | 57 | listLoadedGameSystems(): GameSystemClass[] { 58 | return BCDice.GameSystem 59 | ?.$constants() 60 | ?.map(className => getGameSystemClass(BCDice.GameSystem[className])) ?? []; 61 | } 62 | 63 | listAvailableGameSystems(): GameSystemInfo[] { 64 | return GameSystemList.gameSystems; 65 | } 66 | 67 | listAvailabletI18nInfoList(): I18nInfo[] { 68 | return I18nList.i18nList; 69 | } 70 | 71 | async dynamicLoad(id: string): Promise { 72 | const info = this.getGameSystemInfo(id); 73 | const className = info?.className ?? id; 74 | if (!className.match(/^[A-Z]\w*$/)) throw new Error('Invalid id'); 75 | 76 | const i18nInfos = this.getI18nInfos(className); 77 | for (const i18nInfo of i18nInfos) { 78 | const locales = i18nInfo.locales.filter(locale => locale === info.locale || locale === I18n.$default_locale()); 79 | for (const locale of locales) { 80 | const json = JSON.stringify(await this.dynamicImportI18n(i18nInfo.baseClassName, locale)); 81 | I18n.$load_translation(json); 82 | } 83 | } 84 | 85 | await this.dynamicImport(className); 86 | 87 | const gameSystemClass = BCDice.GameSystem.$const_get(className); 88 | if (!gameSystemClass) throw new Error('Failed to load game system'); 89 | 90 | return getGameSystemClass(gameSystemClass); 91 | } 92 | 93 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 94 | dynamicImportI18n(baseClassName: string, locale: string): Promise { 95 | throw new Error('Not implemented'); 96 | } 97 | 98 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 99 | dynamicImport(className: string): Promise { 100 | throw new Error('Not implemented'); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /ts/loader/static_loader.ts: -------------------------------------------------------------------------------- 1 | import I18nList from '../../lib/bcdice/i18n_list.json'; 2 | import { I18n } from '../internal'; 3 | import Loader, { I18nJsonObject } from './loader'; 4 | 5 | staticImport(); 6 | 7 | export default class StaticLoader extends Loader { 8 | // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function 9 | async dynamicImportI18n(baseClassName: string, locale: string): Promise { 10 | return {}; 11 | } 12 | 13 | // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function 14 | async dynamicImport(className: string): Promise { 15 | } 16 | } 17 | 18 | function staticImport() { 19 | I18nList.i18nList.forEach(I18nInfo => { 20 | I18nInfo.locales.forEach(locale => { 21 | // eslint-disable-next-line @typescript-eslint/no-var-requires 22 | const json = JSON.stringify(require(`../../lib/bcdice/i18n/${I18nInfo.baseClassName}.${locale}.json`)); 23 | I18n.$load_translation(json); 24 | }); 25 | }); 26 | require('../../lib/bcdice/game_system'); 27 | } 28 | -------------------------------------------------------------------------------- /ts/result.ts: -------------------------------------------------------------------------------- 1 | import { Opal } from './internal'; 2 | import { BaseInstance } from './internal/types/base'; 3 | 4 | export default interface Result { 5 | text: string; 6 | rands: [number, number][]; 7 | detailedRands: { 8 | kind: string; 9 | sides: number; 10 | value: number; 11 | }[]; 12 | secret: boolean; 13 | success: boolean; 14 | failure: boolean; 15 | critical: boolean; 16 | fumble: boolean; 17 | } 18 | 19 | export function parseResult(opal: ReturnType): Result | null { 20 | if (opal === Opal.nil) return null; 21 | 22 | const { detailed_rands, ...result } = opal; 23 | return { 24 | ...result, 25 | detailedRands: detailed_rands.map(a => a.$to_n()), 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /ts/test/randomizer.ts: -------------------------------------------------------------------------------- 1 | import { SinonStub, stub } from 'sinon'; 2 | import { BCDice } from '../internal'; 3 | import { RandomizerInstance } from '../internal/types/randomizer'; 4 | 5 | export function mockRandomizer(base: { randomizer: RandomizerInstance }): SinonStub<[number], number> { 6 | const $random = stub<[number], number>(); 7 | Object.assign(base.randomizer, { $random }); 8 | return $random; 9 | } 10 | 11 | export function mockedRandomizer(rands?: [number, number][]): [RandomizerInstance, SinonStub<[number], number>] { 12 | const randomizer = BCDice.Randomizer.$new(); 13 | const $random = mockRandomizer({ randomizer }); 14 | 15 | if (rands) { 16 | rands.forEach((rand, index) => { 17 | $random.onCall(index).returns(rand[0]); 18 | }); 19 | } 20 | 21 | return [randomizer, $random]; 22 | } 23 | -------------------------------------------------------------------------------- /ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig-template.json", 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "esModuleInterop": true, 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /ts/user_defined_dice_table.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { mockedRandomizer } from './test/randomizer'; 3 | import UserDefinedDiceTable from './user_defined_dice_table'; 4 | 5 | describe('UserDefinedDiceTable', () => { 6 | const text_1d6 = `テスト表 7 | 1D6 8 | 1:いち 9 | 2:に 10 | 3:さん 11 | 4:し 12 | 5:ご 13 | 6:ろく`; 14 | 15 | const text_3d4 = `3-4テスト表 16 | 3D4 17 | 3:さん 18 | 4:し 19 | 5:ご 20 | 6:ろく 21 | 7:なな 22 | 8:はち 23 | 9:きゅう 24 | 10:じゅう 25 | 11:じゅういち 26 | 12:じゅうに`; 27 | 28 | const text_d66 = `ソートなし表 29 | D66 30 | 11:a 31 | 12:a 32 | 13:a 33 | 14:a 34 | 15:a 35 | 16:いちろく 36 | 21:a 37 | 22:a 38 | 23:a 39 | 24:a 40 | 25:a 41 | 26:a 42 | 31:a 43 | 32:a 44 | 33:a 45 | 34:a 46 | 35:a 47 | 36:a 48 | 41:a 49 | 42:a 50 | 43:a 51 | 44:a 52 | 45:a 53 | 46:a 54 | 51:a 55 | 52:a 56 | 53:a 57 | 54:a 58 | 55:a 59 | 56:a 60 | 61:ろくいち 61 | 62:a 62 | 63:a 63 | 64:a 64 | 65:a 65 | 66:a`; 66 | 67 | const text_d66a = `ソート昇順表 68 | D66a 69 | 11:a 70 | 12:a 71 | 13:a 72 | 14:a 73 | 15:a 74 | 16:いちろく 75 | 22:a 76 | 23:a 77 | 24:a 78 | 25:a 79 | 26:a 80 | 33:a 81 | 34:a 82 | 35:a 83 | 36:a 84 | 44:a 85 | 45:a 86 | 46:a 87 | 55:a 88 | 56:a 89 | 66:a`; 90 | 91 | const text_d66d = `ソート降順表 92 | D66d 93 | 11:a 94 | 21:a 95 | 22:a 96 | 31:a 97 | 32:a 98 | 33:a 99 | 41:a 100 | 42:a 101 | 43:a 102 | 44:a 103 | 51:a 104 | 52:a 105 | 53:a 106 | 54:a 107 | 55:a 108 | 61:ろくいち 109 | 62:a 110 | 63:a 111 | 64:a 112 | 65:a 113 | 66:a`; 114 | it('id6_1', () => { 115 | const table = new UserDefinedDiceTable(text_1d6); 116 | const result = table.roll(mockedRandomizer([[1, 6]])[0]); 117 | expect(result?.text).to.equal('テスト表(1) > いち'); 118 | }); 119 | 120 | it('1d6_6', () => { 121 | const table = new UserDefinedDiceTable(text_1d6) 122 | const result = table.roll(mockedRandomizer([[6, 6]])[0]); 123 | 124 | expect(result?.text).to.equal('テスト表(6) > ろく'); 125 | }); 126 | 127 | it('3d4_3', () => { 128 | const table = new UserDefinedDiceTable(text_3d4); 129 | const result = table.roll(mockedRandomizer([[1, 4], [1, 4], [1, 4]])[0]); 130 | 131 | expect(result?.text).to.equal('3-4テスト表(3) > さん'); 132 | }); 133 | 134 | it('3d4_12', () => { 135 | const table = new UserDefinedDiceTable(text_3d4); 136 | const result = table.roll(mockedRandomizer([[4, 4], [4, 4], [4, 4]])[0]); 137 | 138 | expect(result?.text).to.equal('3-4テスト表(12) > じゅうに'); 139 | }); 140 | 141 | it('d66_16', () => { 142 | const table = new UserDefinedDiceTable(text_d66); 143 | const result = table.roll(mockedRandomizer([[1, 6], [6, 6]])[0]); 144 | 145 | expect(result?.text).to.equal('ソートなし表(16) > いちろく'); 146 | }); 147 | 148 | it('d66_61', () => { 149 | const table = new UserDefinedDiceTable(text_d66); 150 | const result = table.roll(mockedRandomizer([[6, 6], [1, 6]])[0]); 151 | 152 | expect(result?.text).to.equal('ソートなし表(61) > ろくいち'); 153 | }); 154 | 155 | it('d66a_16', () => { 156 | const table = new UserDefinedDiceTable(text_d66a); 157 | const result = table.roll(mockedRandomizer([[1, 6], [6, 6]])[0]); 158 | 159 | expect(result?.text).to.equal('ソート昇順表(16) > いちろく'); 160 | }); 161 | 162 | it('d66a_61', () => { 163 | const table = new UserDefinedDiceTable(text_d66a) 164 | const result = table.roll(mockedRandomizer([[6, 6], [1, 6]])[0]); 165 | 166 | expect(result?.text).to.equal('ソート昇順表(16) > いちろく'); 167 | }); 168 | 169 | it('d66d_16', () => { 170 | const table = new UserDefinedDiceTable(text_d66d) 171 | const result = table.roll(mockedRandomizer([[1, 6], [6, 6]])[0]); 172 | 173 | expect(result?.text).to.equal('ソート降順表(61) > ろくいち'); 174 | }); 175 | 176 | it('d66d_61', () => { 177 | const table = new UserDefinedDiceTable(text_d66d) 178 | const result = table.roll(mockedRandomizer([[6, 6], [1, 6]])[0]); 179 | 180 | expect(result?.text).to.equal('ソート降順表(61) > ろくいち'); 181 | }); 182 | 183 | it('invalid_dice_type', () => { 184 | const text = `不正な表 185 | D100 186 | 100:ひゃく`; 187 | 188 | const table = new UserDefinedDiceTable(text); 189 | expect(table.roll()).to.be.null; 190 | }); 191 | 192 | it('verify_1d6', () => { 193 | const table = new UserDefinedDiceTable(text_1d6) 194 | 195 | expect(table.validate()).to.be.true; 196 | }); 197 | 198 | it('verify_d66', () => { 199 | const table = new UserDefinedDiceTable(text_d66) 200 | expect(table.validate()).to.be.true; 201 | }); 202 | 203 | it('verify_d66a', () => { 204 | const table = new UserDefinedDiceTable(text_d66a) 205 | expect(table.validate()).to.be.true; 206 | }); 207 | 208 | it('verify_d66d', () => { 209 | const table = new UserDefinedDiceTable(text_d66d) 210 | expect(table.validate()).to.be.true; 211 | }); 212 | 213 | it('verify_3d4_miss_rows', () => { 214 | const text = `抜けありテスト表 215 | 3D4 216 | 3:さん 217 | 4:し 218 | 5:ご 219 | 6:ろく 220 | 12:じゅうに`; 221 | 222 | const table = new UserDefinedDiceTable(text) 223 | expect(table.validate()).to.be.false; 224 | }); 225 | 226 | it('verify_invalid_dice_type', () => { 227 | const text = `不正な表 228 | D100 229 | 100:ひゃく`; 230 | 231 | const table = new UserDefinedDiceTable(text) 232 | expect(table.validate()).to.be.false; 233 | }); 234 | 235 | it('verify_dup_rows', () => { 236 | const text = `重複あり表 237 | 2D4 238 | 2:a 239 | 2:b 240 | 4:c 241 | 5:d 242 | 6:e 243 | 7:f 244 | 8:g`; 245 | 246 | const table = new UserDefinedDiceTable(text) 247 | expect(table.validate()).to.be.false; 248 | }); 249 | 250 | it('verify_outrange_row', () => { 251 | const text = `範囲外表 252 | 1D4 253 | 1:a 254 | 2:b 255 | 3:c 256 | 5:d`; 257 | 258 | const table = new UserDefinedDiceTable(text) 259 | expect(table.validate()).to.be.false; 260 | }); 261 | 262 | it('invalid_d66', () => { 263 | // 表の中身がD66aの物になっている 264 | const text = `フォーマット確認(D66) 265 | D66 266 | 11:a 267 | 12:a 268 | 13:a 269 | 14:a 270 | 15:a 271 | 16:a 272 | 22:a 273 | 23:a 274 | 24:a 275 | 25:a 276 | 26:a 277 | 33:a 278 | 34:a 279 | 35:a 280 | 36:a 281 | 44:a 282 | 45:a 283 | 46:a 284 | 55:a 285 | 56:a 286 | 66:a`; 287 | 288 | const table = new UserDefinedDiceTable(text) 289 | expect(table.validate()).to.be.false; 290 | }); 291 | 292 | it('invalid_d66a', () => { 293 | // 表の中身がD66dの物になっている 294 | const text = `フォーマット確認(D66d) 295 | D66a 296 | 11:a 297 | 21:a 298 | 22:a 299 | 31:a 300 | 32:a 301 | 33:a 302 | 41:a 303 | 42:a 304 | 43:a 305 | 44:a 306 | 51:a 307 | 52:a 308 | 53:a 309 | 54:a 310 | 55:a 311 | 61:a 312 | 62:a 313 | 63:a 314 | 64:a 315 | 65:a 316 | 66:a`; 317 | 318 | 319 | const table = new UserDefinedDiceTable(text) 320 | expect(table.validate()).to.be.false; 321 | }); 322 | 323 | it('invalid_d66d', () => { 324 | // 表の中身がD66aの物になっている 325 | const text = `フォーマット確認(D66d) 326 | D66d 327 | 11:a 328 | 12:a 329 | 13:a 330 | 14:a 331 | 15:a 332 | 16:a 333 | 22:a 334 | 23:a 335 | 24:a 336 | 25:a 337 | 26:a 338 | 33:a 339 | 34:a 340 | 35:a 341 | 36:a 342 | 44:a 343 | 45:a 344 | 46:a 345 | 55:a 346 | 56:a 347 | 66:a`; 348 | 349 | const table = new UserDefinedDiceTable(text) 350 | expect(table.validate()).to.be.false; 351 | }); 352 | }); 353 | -------------------------------------------------------------------------------- /ts/user_defined_dice_table.ts: -------------------------------------------------------------------------------- 1 | import '../lib/bcdice/user_defined_dice_table'; 2 | 3 | import { UserDefinedDiceTableInstance } from './internal/types/user_defined_dice_table'; 4 | import {BCDice, Opal} from './internal'; 5 | import { RandomizerInstance } from './internal/types/randomizer'; 6 | import Result, { parseResult } from './result'; 7 | 8 | export default class UserDefinedDiceTable { 9 | private readonly internal: UserDefinedDiceTableInstance; 10 | 11 | constructor(text: string, internal?: UserDefinedDiceTableInstance) { 12 | this.internal = internal ?? BCDice.UserDefinedDiceTable.$new(text); 13 | } 14 | 15 | roll(randomizer?: RandomizerInstance): Result | null { 16 | const result = this.internal.$roll(randomizer && Opal.hash({ randomizer })); 17 | return parseResult(result); 18 | } 19 | 20 | validate(): boolean { 21 | return this.internal['$valid?'](); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ts/version.test.ts: -------------------------------------------------------------------------------- 1 | import semver from 'semver'; 2 | import { expect } from 'chai'; 3 | import Version from './version'; 4 | 5 | describe('Version', () => { 6 | it('is valid VERSION', () => { 7 | // console.log(BCDice.VERSION); 8 | expect(semver.valid(Version)).not.be.null; 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /ts/version.ts: -------------------------------------------------------------------------------- 1 | import { BCDice } from './internal'; 2 | 3 | const Version = BCDice.VERSION; 4 | export default Version; 5 | -------------------------------------------------------------------------------- /tsconfig-template.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Basic Options */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ 8 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ 9 | // "lib": [], /* Specify library files to be included in the compilation. */ 10 | // "allowJs": true, /* Allow javascript files to be compiled. */ 11 | // "checkJs": true, /* Report errors in .js files. */ 12 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 13 | "declaration": true, /* Generates corresponding '.d.ts' file. */ 14 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 15 | "sourceMap": true, /* Generates corresponding '.map' file. */ 16 | // "outFile": "./", /* Concatenate and emit output to single file. */ 17 | "outDir": "lib", /* Redirect output structure to the directory. */ 18 | // "rootDir": ".", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 19 | // "composite": true, /* Enable project compilation */ 20 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 21 | // "removeComments": true, /* Do not emit comments to output. */ 22 | // "noEmit": true, /* Do not emit outputs. */ 23 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 24 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 25 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 26 | 27 | /* Strict Type-Checking Options */ 28 | "strict": true, /* Enable all strict type-checking options. */ 29 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 30 | // "strictNullChecks": true, /* Enable strict null checks. */ 31 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 32 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 33 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 34 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 35 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 36 | 37 | /* Additional Checks */ 38 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 39 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 40 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 41 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 42 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 43 | 44 | /* Module Resolution Options */ 45 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 46 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 47 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 48 | // "typeRoots": [], /* List of folders to include type definitions from. */ 49 | // "types": [], /* Type declaration files to be included in compilation. */ 50 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 51 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 52 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 53 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 54 | "resolveJsonModule": true, 55 | 56 | /* Source Map Options */ 57 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 58 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 59 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 60 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 61 | 62 | /* Experimental Options */ 63 | "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 64 | "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 65 | 66 | /* Advanced Options */ 67 | "skipLibCheck": true, /* Skip type checking of declaration files. */ 68 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 69 | } 70 | } 71 | --------------------------------------------------------------------------------