games_dice 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7c1046747fdad80e794bb7883aaaade1a14e737a39641b6e85e7c179e96a3375
4
- data.tar.gz: 54b1c1aa41b58b53e8a72ad59f835de932fde926a038626a0df165605677300c
3
+ metadata.gz: 1ccf1c8256d9cfa52fa5e99b06c0d0a32288ebdbf54e0e7d0d9f194e4fa21be8
4
+ data.tar.gz: 0b0935a31996519bcd74a8b9fe865911bc5d5d1d68a288c46a2d9450588ecefa
5
5
  SHA512:
6
- metadata.gz: 60e56aa2b32a4550ae6334c372ad1a9b0a07820c8a58ae4ce07c0ae3d5721ff91b8422d3d2d52b690e387738d3ee5bb6a7c05bed2d40145b776ef9a9d6295c0c
7
- data.tar.gz: 7a0e7c3759def3b6f482e4242ae23b770c50876fc26f5464bca4e77b91cbf64596c901c4eb9cc69e9eee351d63fa8978426ceae7125eb34aa7c3e0856a0a8e1e
6
+ metadata.gz: ea6550cb8a23fd28d311ce1b9227e3a9d8b20cfc6ac57459f157e138be850fc543e2e0b17e3dbcee3a6c0ea070c1d126b2b467c1fcb927f13da77acad33dcdbf
7
+ data.tar.gz: bb9b6efd51537a01de7ab325546431852ab96d1aa28042776481345e20e0ed60c64d9185b2161bc00e15d63aa0c8c859f33541cb34374fae736a3fa32a1fe3de
@@ -0,0 +1,92 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ pull_request:
8
+ schedule:
9
+ - cron: '0 6 8 * *'
10
+
11
+ permissions:
12
+ contents: read
13
+
14
+ jobs:
15
+ rspec:
16
+ name: RSpec (Ruby ${{ matrix.ruby }})
17
+ runs-on: ubuntu-latest
18
+ strategy:
19
+ fail-fast: false
20
+ matrix:
21
+ ruby: ['3.3', '3.4', '4.0']
22
+ steps:
23
+ - uses: actions/checkout@v6
24
+ - uses: ruby/setup-ruby@v1
25
+ with:
26
+ ruby-version: ${{ matrix.ruby }}
27
+ bundler-cache: true
28
+ - run: bundle exec rake compile spec
29
+
30
+ rubocop:
31
+ name: RuboCop (Ruby ${{ matrix.ruby }})
32
+ runs-on: ubuntu-latest
33
+ strategy:
34
+ fail-fast: false
35
+ matrix:
36
+ ruby: ['3.3', '3.4', '4.0']
37
+ steps:
38
+ - uses: actions/checkout@v6
39
+ - uses: ruby/setup-ruby@v1
40
+ with:
41
+ ruby-version: ${{ matrix.ruby }}
42
+ bundler-cache: true
43
+ - run: bundle exec rubocop
44
+
45
+ audit:
46
+ name: Dependency audit
47
+ runs-on: ubuntu-latest
48
+ steps:
49
+ - uses: actions/checkout@v6
50
+ - uses: ruby/setup-ruby@v1
51
+ with:
52
+ ruby-version: '3.3'
53
+ bundler-cache: true
54
+ - run: bundle exec bundle-audit check --update
55
+
56
+ native-quality:
57
+ name: C ${{ matrix.check }}
58
+ runs-on: ubuntu-latest
59
+ timeout-minutes: 20
60
+ strategy:
61
+ fail-fast: false
62
+ matrix:
63
+ check: [lint, coverage, sanitize]
64
+ steps:
65
+ - uses: actions/checkout@v6
66
+ - name: Install C coverage tools
67
+ if: matrix.check == 'coverage'
68
+ run: sudo apt-get update && sudo apt-get install -y gcovr
69
+ - uses: ruby/setup-ruby@v1
70
+ with:
71
+ ruby-version: '3.4'
72
+ bundler-cache: true
73
+ - name: Show Ruby extension compiler
74
+ run: |
75
+ ruby -rrbconfig -e 'puts RbConfig::CONFIG.fetch("CC")'
76
+ gcc --version
77
+ - name: Run native quality check
78
+ run: bundle exec rake c:${{ matrix.check }}
79
+ - name: Add C coverage summary to job summary
80
+ if: matrix.check == 'coverage' && always()
81
+ run: |
82
+ if test -f coverage/c/summary.txt; then
83
+ cat coverage/c/summary.txt >> "$GITHUB_STEP_SUMMARY"
84
+ fi
85
+ - name: Upload C coverage report
86
+ if: matrix.check == 'coverage' && always()
87
+ uses: actions/upload-artifact@v6
88
+ with:
89
+ name: c-coverage
90
+ path: coverage/c
91
+ retention-days: 14
92
+ if-no-files-found: warn
data/.gitignore CHANGED
@@ -16,3 +16,4 @@ spec/reports
16
16
  test/tmp
17
17
  test/version_tmp
18
18
  tmp
19
+ .DS_Store
data/.rubocop.yml CHANGED
@@ -1,15 +1,53 @@
1
+ plugins:
2
+ - rubocop-performance
3
+ - rubocop-rake
4
+ - rubocop-rspec
5
+
1
6
  AllCops:
2
- TargetRubyVersion: 2.6.0
7
+ TargetRubyVersion: 3.3
3
8
  NewCops: enable
9
+ SuggestExtensions: false
4
10
 
5
11
  Layout/LineLength:
6
12
  Max: 120
7
13
 
8
14
  Metrics/BlockLength:
9
- IgnoredMethods:
15
+ AllowedMethods:
10
16
  - context
11
17
  - describe
12
18
  - shared_examples
13
19
  - define
14
-
15
20
 
21
+ RSpec/ExampleLength:
22
+ Max: 25
23
+
24
+ RSpec/MultipleExpectations:
25
+ Max: 21
26
+
27
+ RSpec/NestedGroups:
28
+ Max: 4
29
+
30
+ # This project keeps specs flat so they are easy to run individually.
31
+ RSpec/SpecFilePathFormat:
32
+ Enabled: false
33
+
34
+ RSpec/IndexedLet:
35
+ Enabled: false
36
+
37
+ RSpec/InstanceVariable:
38
+ Exclude:
39
+ - spec/complex_die_spec.rb
40
+
41
+ RSpec/MultipleDescribes:
42
+ Exclude:
43
+ - spec/readme_spec.rb
44
+
45
+ RSpec/DescribeClass:
46
+ Exclude:
47
+ - spec/readme_spec.rb
48
+
49
+ # Probabilities implements #each without mixing in Enumerable; these specs
50
+ # intentionally collect yielded pairs manually.
51
+ Style/MapIntoArray:
52
+ Exclude:
53
+ - spec/probability_spec.rb
data/.yardopts CHANGED
@@ -1,5 +1,5 @@
1
1
  lib/**/*.rb
2
- --exclude ext
2
+ ext/**/*.c
3
3
  --no-private
4
4
  -
5
5
  README.md
data/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # GamesDice Changelog
2
2
 
3
+ ## 0.5.0 ( 22 July 2026 )
4
+
5
+ * Add support and CI coverage for Ruby 4.0.
6
+ * Add dependency auditing and native extension lint, sanitizer, and coverage checks to CI.
7
+ * Modernise native probability object memory management using Ruby's typed data API.
8
+ * Expand probability specifications to cover native extension edge cases.
9
+
10
+ ## 0.4.2 ( 19 July 2026 )
11
+
12
+ * Require MRI Ruby 3.3 or later and test against Ruby 3.3 and 3.4.
13
+ * Update development and runtime dependencies.
14
+ * Replace Travis CI with GitHub Actions.
15
+ * Modernise the RSpec suite and RuboCop configuration.
16
+ * Correct and clarify README documentation.
17
+
18
+ ## 0.4.1
19
+
20
+ * Tidy up documentation and address some Rubocop offences.
21
+
3
22
  ## 0.4.0 ( 19 September 2021 )
4
23
 
5
24
  * Dropping support for older Ruby versions (< 2.6)
data/Gemfile CHANGED
@@ -4,3 +4,14 @@ source 'https://rubygems.org'
4
4
 
5
5
  # Dependencies are in games_dice.gemspec
6
6
  gemspec
7
+
8
+ gem 'bundler-audit', '~> 0.9', require: false
9
+ gem 'rake', '~> 13.2'
10
+ gem 'rake-compiler', '~> 1.2'
11
+ gem 'redcarpet', '~> 3.6'
12
+ gem 'rspec', '~> 3.13'
13
+ gem 'rubocop', '~> 1.75', require: false
14
+ gem 'rubocop-performance', '~> 1.25', require: false
15
+ gem 'rubocop-rake', '~> 0.7', require: false
16
+ gem 'rubocop-rspec', '~> 3.6', require: false
17
+ gem 'yard', '~> 0.9'
data/README.md CHANGED
@@ -1,10 +1,4 @@
1
1
  # GamesDice
2
- [![Gem Version](https://badge.fury.io/rb/games_dice.png)](http://badge.fury.io/rb/games_dice)
3
- [![Build Status](https://travis-ci.org/neilslater/games_dice.png?branch=master)](http://travis-ci.org/neilslater/games_dice)
4
- [![Coverage Status](https://coveralls.io/repos/neilslater/games_dice/badge.png?branch=master)](https://coveralls.io/r/neilslater/games_dice?branch=master)
5
- [![Inline docs](http://inch-ci.org/github/neilslater/games_dice.png?branch=master)](http://inch-ci.org/github/neilslater/games_dice)
6
- [![Code Climate](https://codeclimate.com/github/neilslater/games_dice.png)](https://codeclimate.com/github/neilslater/games_dice)
7
- [![Dependency Status](https://gemnasium.com/neilslater/games_dice.png)](https://gemnasium.com/neilslater/games_dice)
8
2
 
9
3
  A library for simulating dice. Use it to construct dice-rolling systems used in role-playing and board games.
10
4
 
@@ -30,11 +24,10 @@ gem as-is, and add them as features within your project code.
30
24
 
31
25
  ## Supported Ruby Versions
32
26
 
33
- GamesDice is tested routinely on MRI Rubies 1.9.3, 2.0.0, 2.1.0, 2.1.1 and JRuby, and the
34
- "build passing" badge is based on those tests.
27
+ GamesDice 0.4.2 supports MRI Ruby 3.3 and 3.4. It uses a native extension and does not support JRuby.
35
28
 
36
- It *should* also work in versions from 1.8.7, in Ruby Enterprise and in Rubinius, but it
37
- is not tested routinely on those Rubies.
29
+ GamesDice 0.4.0 and 0.4.1 support MRI Ruby 2.6 and later. Versions before 0.4.0 also provide a
30
+ pure-Ruby implementation of the probability calculations and can be used with JRuby.
38
31
 
39
32
  ## Installation
40
33
 
@@ -50,21 +43,6 @@ Or install it yourself as:
50
43
 
51
44
  $ gem install games_dice
52
45
 
53
- When installed, GamesDice will attempt to install Ruby native extensions in C, for speeding up probabilities
54
- calculations. However, all the features are available in pure Ruby, and the gem should fall back to that
55
- automatically on installation if your system does not support C native extensions. You can verify which
56
- is being installed by installing the gem in verbose mode:
57
-
58
- $ gem install games_dice --verbose
59
-
60
- You can also verify which version you are using in Ruby by calling the class method:
61
-
62
- GamesDice::Probabilities.implemented_in
63
-
64
- which will return either *:ruby* or *:c*. Other than this method, and a speed difference between
65
- implementations, there should be no other difference. If you find one, then it will be considered
66
- as a bug.
67
-
68
46
  ## Usage
69
47
 
70
48
  require 'games_dice'
@@ -88,8 +66,8 @@ Converts a string such as '3d6+6' into a GamesDice::Dice object
88
66
 
89
67
  Parameters:
90
68
 
91
- * dice_description is a string such as '3d6' or '2d4-1'. See String Dice Descriptions below for possibilities.
92
- * prng is optional, if provided it should be an object that has a method 'rand( integer )' that works like Ruby's built-in rand method
69
+ * `dice_description` is a string such as `3d6` or `2d4-1`. See String Dice Descriptions below for possibilities.
70
+ * `prng` is optional. If provided, it must respond to `rand(integer)` in the same way as Ruby's built-in `rand` method.
93
71
 
94
72
  Returns a GamesDice::Dice object.
95
73
 
@@ -215,12 +193,12 @@ Returns the probability of a result less than or equal to the integer n.
215
193
 
216
194
  Returns the probability of a result less than the integer n.
217
195
 
218
- probabilities.p_lt( 17 ) # => 0.9953703703703
196
+ probabilities.p_lt( 17 ) # => 0.9814814814814
219
197
  probabilities.p_lt( 3 ) # => 0.0
220
198
 
221
199
  #### probabilities.expected
222
200
 
223
- Returns the mean result, weighted by probabality of each value.
201
+ Returns the mean result, weighted by the probability of each value.
224
202
 
225
203
  probabilities.expected # => 10.5 (rounded to nearest 1e-9)
226
204
 
@@ -254,7 +232,7 @@ A die modifier can also be a single letter plus an integer value, e.g.
254
232
 
255
233
  1d6r1
256
234
 
257
- You can add comma-seperated parameters to a modifier by using a ":" (colon) character after the
235
+ You can add comma-separated parameters to a modifier by using a ":" (colon) character after the
258
236
  modifier letter, and a "." (full stop) to signify the end of the parameters. What parameters are
259
237
  accepted, and what they mean, depends on the modifier:
260
238
 
@@ -272,7 +250,7 @@ are all equivalent.
272
250
  #### Rerolls
273
251
 
274
252
  You can specify that dice rolling certain values should be re-rolled, and how that re-roll should be
275
- interpretted.
253
+ interpreted.
276
254
 
277
255
  The simple form specifies a low value that will automatically trigger a re-roll and replace:
278
256
 
@@ -287,7 +265,7 @@ The full version of this modifier, allows you to specify from 1 to 3 parameters:
287
265
 
288
266
  Where:
289
267
 
290
- * VALUE_COMPARISON is one of >, >=, == (default), <= < plus an integer to set conditions on when the reroll should occur
268
+ * VALUE_COMPARISON is one of `>`, `>=`, `==` (default), `<=`, or `<`, plus an integer that determines when the reroll occurs
291
269
  * REROLL_TYPE is one of
292
270
  * replace (default) - use the new value in place of existing value for the die
293
271
  * add - add result of reroll to running total, and ignore any subtract rules
@@ -319,7 +297,7 @@ The full version of this modifier, allows you to specify from 1 to 3 parameters:
319
297
 
320
298
  Where:
321
299
 
322
- * VALUE_COMPARISON is one of >, >= (default), ==, <= < plus an integer to set conditions on when the map should occur
300
+ * VALUE_COMPARISON is one of `>`, `>=` (default), `==`, `<=`, or `<`, plus an integer that determines when the map occurs
323
301
  * MAP_VALUE is an integer that will be used in place of a result from a die, default value is 1
324
302
  * maps are tested in order that they are declared, and first one that matches is applied
325
303
  * when at least one map has been defined, all unmapped values default to 0
data/Rakefile CHANGED
@@ -1,18 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'bundler/gem_tasks'
4
+ require 'fileutils'
5
+ require 'open3'
6
+ require 'rbconfig'
4
7
  require 'rspec/core/rake_task'
5
8
  require 'rake/extensiontask'
9
+ require 'shellwords'
6
10
  require 'yard'
7
11
 
8
- def can_compile_extensions
9
- return false if RUBY_DESCRIPTION =~ /jruby/
10
-
11
- true
12
- end
13
-
14
12
  desc 'GamesDice unit tests'
15
- RSpec::Core::RakeTask.new(:test) do |t|
13
+ RSpec::Core::RakeTask.new(:spec) do |t|
16
14
  t.pattern = 'spec/*_spec.rb'
17
15
  t.verbose = false
18
16
  end
@@ -30,14 +28,81 @@ Rake::ExtensionTask.new do |ext|
30
28
  ext.gem_spec = gemspec
31
29
  end
32
30
 
33
- task :delete_compiled_ext do |_t|
34
- `rm lib/games_dice/games_dice.*`
31
+ task default: %i[compile spec]
32
+
33
+ rebuild_and_test_native = lambda do |mode, test: true|
34
+ tasks = %w[clobber compile]
35
+ tasks << 'spec' if test
36
+
37
+ sh(
38
+ { 'GAMES_DICE_NATIVE_MODE' => mode },
39
+ RbConfig.ruby,
40
+ '-S',
41
+ 'bundle',
42
+ 'exec',
43
+ 'rake',
44
+ *tasks
45
+ )
35
46
  end
36
47
 
37
- task pure_test: %i[delete_compiled_ext test]
48
+ # Native quality orchestration is kept together so each task shares the same rebuild contract.
49
+ # rubocop:disable Metrics/BlockLength
50
+ namespace :c do
51
+ desc 'Compile the C extension with strict warnings'
52
+ task :lint do
53
+ rebuild_and_test_native.call('lint', test: false)
54
+ end
55
+
56
+ desc 'Measure C coverage using the full Ruby spec suite'
57
+ task :coverage do
58
+ cc = RbConfig::CONFIG.fetch('CC')
59
+ compiler_version = Open3.capture2e(*Shellwords.split(cc), '--version').first
60
+
61
+ unless compiler_version.match?(/gcc/i) && !compiler_version.match?(/clang/i)
62
+ abort "c:coverage requires a GCC Ruby build (current compiler: #{cc})"
63
+ end
64
+
65
+ abort 'c:coverage requires gcovr on PATH' unless system('gcovr', '--version', out: File::NULL)
66
+
67
+ rebuild_and_test_native.call('coverage')
68
+
69
+ FileUtils.mkdir_p('coverage/c')
70
+ sh(
71
+ 'gcovr',
72
+ '--root', '.',
73
+ '--filter', 'ext/games_dice/',
74
+ '--html-details', 'coverage/c/index.html',
75
+ '--xml', 'coverage/c/cobertura.xml',
76
+ '--txt', 'coverage/c/summary.txt',
77
+ '--print-summary'
78
+ )
79
+ end
80
+
81
+ desc 'Run the Ruby specs with ASan and UBSan'
82
+ task :sanitize do
83
+ abort 'c:sanitize requires Linux' unless RUBY_PLATFORM.include?('linux')
84
+
85
+ cc = RbConfig::CONFIG.fetch('CC')
86
+ compiler_version = Open3.capture2e(*Shellwords.split(cc), '--version').first
87
+
88
+ unless compiler_version.match?(/gcc/i) && !compiler_version.match?(/clang/i)
89
+ abort "c:sanitize requires a GCC Ruby build (current compiler: #{cc})"
90
+ end
91
+
92
+ libasan = Open3.capture2e(*Shellwords.split(cc), '-print-file-name=libasan.so').first.strip
93
+ abort 'c:sanitize could not locate the GCC ASan runtime' if libasan.empty? || libasan == 'libasan.so'
94
+
95
+ rebuild_and_test_native.call('sanitize', test: false)
38
96
 
39
- if can_compile_extensions
40
- task default: %i[compile test]
41
- else
42
- task default: [:test]
97
+ sh(
98
+ { 'ASAN_OPTIONS' => 'detect_leaks=0', 'LD_PRELOAD' => libasan },
99
+ RbConfig.ruby,
100
+ '-S',
101
+ 'bundle',
102
+ 'exec',
103
+ 'rake',
104
+ 'spec'
105
+ )
106
+ end
43
107
  end
108
+ # rubocop:enable Metrics/BlockLength
@@ -3,5 +3,34 @@
3
3
  # ext/games_dice/extconf.rb
4
4
 
5
5
  require 'mkmf'
6
+ require 'rbconfig'
7
+
8
+ # mkmf exposes compiler and linker flags through these globals.
9
+ # rubocop:disable Style/GlobalVars
10
+ case ENV.fetch('GAMES_DICE_NATIVE_MODE', 'release')
11
+ when 'release'
12
+ # Use Ruby's normal extension build flags.
13
+ when 'lint'
14
+ $CFLAGS << ' -std=gnu2x -O0 -g'
15
+ $CFLAGS << ' -Wall -Wextra -Wpedantic -Wformat=2 -Werror'
16
+
17
+ cc = RbConfig::CONFIG.fetch('CC')
18
+ host_os = RbConfig::CONFIG.fetch('host_os')
19
+ if cc.include?('clang') || host_os.include?('darwin')
20
+ # Ruby callback signatures legitimately leave some parameters unused, and
21
+ # Ruby 4 headers use standard attributes that Clang treats as C23 syntax.
22
+ $CFLAGS << ' -Wno-strict-prototypes -Wno-unused-parameter -Wno-c23-extensions'
23
+ end
24
+ when 'coverage'
25
+ $CFLAGS << ' -O0 -g --coverage'
26
+ $LDFLAGS << ' --coverage'
27
+ when 'sanitize'
28
+ $CFLAGS << ' -O1 -g -fsanitize=address,undefined'
29
+ $CFLAGS << ' -fno-omit-frame-pointer'
30
+ $LDFLAGS << ' -fsanitize=address,undefined'
31
+ else
32
+ abort "Unknown GAMES_DICE_NATIVE_MODE: #{ENV.fetch('GAMES_DICE_NATIVE_MODE', nil)}"
33
+ end
34
+ # rubocop:enable Style/GlobalVars
6
35
 
7
36
  create_makefile('games_dice/games_dice')
@@ -6,7 +6,7 @@
6
6
  // To hold the module object
7
7
  VALUE GamesDice = Qnil;
8
8
 
9
- void Init_games_dice() {
9
+ void Init_games_dice(void) {
10
10
  GamesDice = rb_define_module("GamesDice");
11
- init_probabilities_class( GamesDice );
11
+ init_probabilities_class();
12
12
  }