openskill 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/openskill/models/bradley_terry_full.rb +30 -30
- data/lib/openskill/models/bradley_terry_part.rb +32 -32
- data/lib/openskill/models/common.rb +3 -3
- data/lib/openskill/models/plackett_luce.rb +26 -26
- data/lib/openskill/models/thurstone_mosteller_full.rb +24 -24
- data/lib/openskill/models/thurstone_mosteller_part.rb +26 -26
- data/lib/openskill/statistics/normal.rb +1 -1
- data/lib/openskill/version.rb +1 -1
- data/lib/openskill.rb +8 -8
- metadata +26 -12
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f012ac1b290e25006cbe383d1ea48aacebc5d4aac2660d5e0388cbeaa91fd16e
|
|
4
|
+
data.tar.gz: b7de5159790ba4e6cd4017437e75706784906e8381211523c377eeaf31d6610d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: dd0515dac71798ba088d399f3a888a028106444803a9faaaa7e584aa7e05cdd5363729e923a34207c26c6eae98a736410920007385f578a47cb917781c2ca069
|
|
7
|
+
data.tar.gz: 3710b5bff94f1ae66799e390874580b3d40c7257b2e1465068ac9cb9afaef807ed50ed206eb52a9784359bab23fbb4f0e2086aa3481aa44dc5ce9fdadb4f6f3a
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require_relative
|
|
5
|
-
require_relative
|
|
3
|
+
require "securerandom"
|
|
4
|
+
require_relative "../statistics/normal"
|
|
5
|
+
require_relative "common"
|
|
6
6
|
|
|
7
7
|
module OpenSkill
|
|
8
8
|
module Models
|
|
@@ -71,8 +71,8 @@ module OpenSkill
|
|
|
71
71
|
# @raise [ArgumentError] if rating_array is invalid
|
|
72
72
|
def load_rating(rating_array, name: nil)
|
|
73
73
|
raise ArgumentError, "Rating must be an Array, got #{rating_array.class}" unless rating_array.is_a?(Array)
|
|
74
|
-
raise ArgumentError,
|
|
75
|
-
raise ArgumentError,
|
|
74
|
+
raise ArgumentError, "Rating array must have exactly 2 elements" unless rating_array.size == 2
|
|
75
|
+
raise ArgumentError, "Rating values must be numeric" unless rating_array.all? { |v| v.is_a?(Numeric) }
|
|
76
76
|
|
|
77
77
|
Rating.new(mu: rating_array[0], sigma: rating_array[1], name: name)
|
|
78
78
|
end
|
|
@@ -92,7 +92,7 @@ module OpenSkill
|
|
|
92
92
|
validate_scores!(teams, scores) if scores
|
|
93
93
|
validate_weights!(teams, weights) if weights
|
|
94
94
|
|
|
95
|
-
raise ArgumentError,
|
|
95
|
+
raise ArgumentError, "Cannot provide both ranks and scores" if ranks && scores
|
|
96
96
|
|
|
97
97
|
# Deep copy teams to avoid mutating input
|
|
98
98
|
original_teams = teams
|
|
@@ -299,25 +299,25 @@ module OpenSkill
|
|
|
299
299
|
end
|
|
300
300
|
|
|
301
301
|
def <(other)
|
|
302
|
-
raise ArgumentError,
|
|
302
|
+
raise ArgumentError, "comparison with non-Rating" unless other.is_a?(Rating)
|
|
303
303
|
|
|
304
304
|
ordinal < other.ordinal
|
|
305
305
|
end
|
|
306
306
|
|
|
307
307
|
def >(other)
|
|
308
|
-
raise ArgumentError,
|
|
308
|
+
raise ArgumentError, "comparison with non-Rating" unless other.is_a?(Rating)
|
|
309
309
|
|
|
310
310
|
ordinal > other.ordinal
|
|
311
311
|
end
|
|
312
312
|
|
|
313
313
|
def <=(other)
|
|
314
|
-
raise ArgumentError,
|
|
314
|
+
raise ArgumentError, "comparison with non-Rating" unless other.is_a?(Rating)
|
|
315
315
|
|
|
316
316
|
ordinal <= other.ordinal
|
|
317
317
|
end
|
|
318
318
|
|
|
319
319
|
def >=(other)
|
|
320
|
-
raise ArgumentError,
|
|
320
|
+
raise ArgumentError, "comparison with non-Rating" unless other.is_a?(Rating)
|
|
321
321
|
|
|
322
322
|
ordinal >= other.ordinal
|
|
323
323
|
end
|
|
@@ -376,8 +376,8 @@ module OpenSkill
|
|
|
376
376
|
|
|
377
377
|
# Validate teams structure
|
|
378
378
|
def validate_teams!(teams)
|
|
379
|
-
raise ArgumentError,
|
|
380
|
-
raise ArgumentError,
|
|
379
|
+
raise ArgumentError, "Teams must be an Array" unless teams.is_a?(Array)
|
|
380
|
+
raise ArgumentError, "Must have at least 2 teams" if teams.size < 2
|
|
381
381
|
|
|
382
382
|
teams.each_with_index do |team, idx|
|
|
383
383
|
raise ArgumentError, "Team #{idx} must be an Array" unless team.is_a?(Array)
|
|
@@ -392,7 +392,7 @@ module OpenSkill
|
|
|
392
392
|
# Validate ranks
|
|
393
393
|
def validate_ranks!(teams, ranks)
|
|
394
394
|
raise ArgumentError, "Ranks must be an Array, got #{ranks.class}" unless ranks.is_a?(Array)
|
|
395
|
-
raise ArgumentError,
|
|
395
|
+
raise ArgumentError, "Ranks must have same length as teams" if ranks.size != teams.size
|
|
396
396
|
|
|
397
397
|
ranks.each do |rank|
|
|
398
398
|
raise ArgumentError, "All ranks must be numeric, got #{rank.class}" unless rank.is_a?(Numeric)
|
|
@@ -402,7 +402,7 @@ module OpenSkill
|
|
|
402
402
|
# Validate scores
|
|
403
403
|
def validate_scores!(teams, scores)
|
|
404
404
|
raise ArgumentError, "Scores must be an Array, got #{scores.class}" unless scores.is_a?(Array)
|
|
405
|
-
raise ArgumentError,
|
|
405
|
+
raise ArgumentError, "Scores must have same length as teams" if scores.size != teams.size
|
|
406
406
|
|
|
407
407
|
scores.each do |score|
|
|
408
408
|
raise ArgumentError, "All scores must be numeric, got #{score.class}" unless score.is_a?(Numeric)
|
|
@@ -412,7 +412,7 @@ module OpenSkill
|
|
|
412
412
|
# Validate weights
|
|
413
413
|
def validate_weights!(teams, weights)
|
|
414
414
|
raise ArgumentError, "Weights must be an Array, got #{weights.class}" unless weights.is_a?(Array)
|
|
415
|
-
raise ArgumentError,
|
|
415
|
+
raise ArgumentError, "Weights must have same length as teams" if weights.size != teams.size
|
|
416
416
|
|
|
417
417
|
weights.each_with_index do |team_weights, idx|
|
|
418
418
|
raise ArgumentError, "Weights for team #{idx} must be an Array" unless team_weights.is_a?(Array)
|
|
@@ -450,11 +450,11 @@ module OpenSkill
|
|
|
450
450
|
|
|
451
451
|
sorted_team.each do |player|
|
|
452
452
|
balance_weight = if @balance
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
453
|
+
ordinal_diff = max_ordinal - player.ordinal
|
|
454
|
+
1 + (ordinal_diff / (max_ordinal + @kappa))
|
|
455
|
+
else
|
|
456
|
+
1.0
|
|
457
|
+
end
|
|
458
458
|
|
|
459
459
|
mu_sum += player.mu * balance_weight
|
|
460
460
|
sigma_squared_sum += (player.sigma * balance_weight)**2
|
|
@@ -474,10 +474,10 @@ module OpenSkill
|
|
|
474
474
|
return [] if game.empty?
|
|
475
475
|
|
|
476
476
|
team_scores = if ranks
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
477
|
+
ranks.each_with_index.map { |rank, idx| rank || idx }
|
|
478
|
+
else
|
|
479
|
+
game.each_index.to_a
|
|
480
|
+
end
|
|
481
481
|
|
|
482
482
|
sorted_scores = team_scores.sort
|
|
483
483
|
rank_map = {}
|
|
@@ -535,12 +535,12 @@ module OpenSkill
|
|
|
535
535
|
|
|
536
536
|
# Calculate s based on rank comparison
|
|
537
537
|
s = if team_q.rank > team_i.rank
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
538
|
+
1.0 # team_i won
|
|
539
|
+
elsif team_q.rank == team_i.rank
|
|
540
|
+
0.5 # draw
|
|
541
|
+
else
|
|
542
|
+
0.0 # team_i lost
|
|
543
|
+
end
|
|
544
544
|
|
|
545
545
|
# Accumulate omega
|
|
546
546
|
omega += sigma_squared_to_ciq * (s - piq)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require_relative
|
|
5
|
-
require_relative
|
|
3
|
+
require "securerandom"
|
|
4
|
+
require_relative "../statistics/normal"
|
|
5
|
+
require_relative "common"
|
|
6
6
|
|
|
7
7
|
module OpenSkill
|
|
8
8
|
module Models
|
|
@@ -74,8 +74,8 @@ module OpenSkill
|
|
|
74
74
|
# @raise [ArgumentError] if rating_array is invalid
|
|
75
75
|
def load_rating(rating_array, name: nil)
|
|
76
76
|
raise ArgumentError, "Rating must be an Array, got #{rating_array.class}" unless rating_array.is_a?(Array)
|
|
77
|
-
raise ArgumentError,
|
|
78
|
-
raise ArgumentError,
|
|
77
|
+
raise ArgumentError, "Rating array must have exactly 2 elements" unless rating_array.size == 2
|
|
78
|
+
raise ArgumentError, "Rating values must be numeric" unless rating_array.all? { |v| v.is_a?(Numeric) }
|
|
79
79
|
|
|
80
80
|
Rating.new(mu: rating_array[0], sigma: rating_array[1], name: name)
|
|
81
81
|
end
|
|
@@ -95,7 +95,7 @@ module OpenSkill
|
|
|
95
95
|
validate_scores!(teams, scores) if scores
|
|
96
96
|
validate_weights!(teams, weights) if weights
|
|
97
97
|
|
|
98
|
-
raise ArgumentError,
|
|
98
|
+
raise ArgumentError, "Cannot provide both ranks and scores" if ranks && scores
|
|
99
99
|
|
|
100
100
|
# Deep copy teams to avoid mutating input
|
|
101
101
|
original_teams = teams
|
|
@@ -302,25 +302,25 @@ module OpenSkill
|
|
|
302
302
|
end
|
|
303
303
|
|
|
304
304
|
def <(other)
|
|
305
|
-
raise ArgumentError,
|
|
305
|
+
raise ArgumentError, "comparison with non-Rating" unless other.is_a?(Rating)
|
|
306
306
|
|
|
307
307
|
ordinal < other.ordinal
|
|
308
308
|
end
|
|
309
309
|
|
|
310
310
|
def >(other)
|
|
311
|
-
raise ArgumentError,
|
|
311
|
+
raise ArgumentError, "comparison with non-Rating" unless other.is_a?(Rating)
|
|
312
312
|
|
|
313
313
|
ordinal > other.ordinal
|
|
314
314
|
end
|
|
315
315
|
|
|
316
316
|
def <=(other)
|
|
317
|
-
raise ArgumentError,
|
|
317
|
+
raise ArgumentError, "comparison with non-Rating" unless other.is_a?(Rating)
|
|
318
318
|
|
|
319
319
|
ordinal <= other.ordinal
|
|
320
320
|
end
|
|
321
321
|
|
|
322
322
|
def >=(other)
|
|
323
|
-
raise ArgumentError,
|
|
323
|
+
raise ArgumentError, "comparison with non-Rating" unless other.is_a?(Rating)
|
|
324
324
|
|
|
325
325
|
ordinal >= other.ordinal
|
|
326
326
|
end
|
|
@@ -379,8 +379,8 @@ module OpenSkill
|
|
|
379
379
|
|
|
380
380
|
# Validate teams structure
|
|
381
381
|
def validate_teams!(teams)
|
|
382
|
-
raise ArgumentError,
|
|
383
|
-
raise ArgumentError,
|
|
382
|
+
raise ArgumentError, "Teams must be an Array" unless teams.is_a?(Array)
|
|
383
|
+
raise ArgumentError, "Must have at least 2 teams" if teams.size < 2
|
|
384
384
|
|
|
385
385
|
teams.each_with_index do |team, idx|
|
|
386
386
|
raise ArgumentError, "Team #{idx} must be an Array" unless team.is_a?(Array)
|
|
@@ -395,7 +395,7 @@ module OpenSkill
|
|
|
395
395
|
# Validate ranks
|
|
396
396
|
def validate_ranks!(teams, ranks)
|
|
397
397
|
raise ArgumentError, "Ranks must be an Array, got #{ranks.class}" unless ranks.is_a?(Array)
|
|
398
|
-
raise ArgumentError,
|
|
398
|
+
raise ArgumentError, "Ranks must have same length as teams" if ranks.size != teams.size
|
|
399
399
|
|
|
400
400
|
ranks.each do |rank|
|
|
401
401
|
raise ArgumentError, "All ranks must be numeric, got #{rank.class}" unless rank.is_a?(Numeric)
|
|
@@ -405,7 +405,7 @@ module OpenSkill
|
|
|
405
405
|
# Validate scores
|
|
406
406
|
def validate_scores!(teams, scores)
|
|
407
407
|
raise ArgumentError, "Scores must be an Array, got #{scores.class}" unless scores.is_a?(Array)
|
|
408
|
-
raise ArgumentError,
|
|
408
|
+
raise ArgumentError, "Scores must have same length as teams" if scores.size != teams.size
|
|
409
409
|
|
|
410
410
|
scores.each do |score|
|
|
411
411
|
raise ArgumentError, "All scores must be numeric, got #{score.class}" unless score.is_a?(Numeric)
|
|
@@ -415,7 +415,7 @@ module OpenSkill
|
|
|
415
415
|
# Validate weights
|
|
416
416
|
def validate_weights!(teams, weights)
|
|
417
417
|
raise ArgumentError, "Weights must be an Array, got #{weights.class}" unless weights.is_a?(Array)
|
|
418
|
-
raise ArgumentError,
|
|
418
|
+
raise ArgumentError, "Weights must have same length as teams" if weights.size != teams.size
|
|
419
419
|
|
|
420
420
|
weights.each_with_index do |team_weights, idx|
|
|
421
421
|
raise ArgumentError, "Weights for team #{idx} must be an Array" unless team_weights.is_a?(Array)
|
|
@@ -453,11 +453,11 @@ module OpenSkill
|
|
|
453
453
|
|
|
454
454
|
sorted_team.each do |player|
|
|
455
455
|
balance_weight = if @balance
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
456
|
+
ordinal_diff = max_ordinal - player.ordinal
|
|
457
|
+
1 + (ordinal_diff / (max_ordinal + @kappa))
|
|
458
|
+
else
|
|
459
|
+
1.0
|
|
460
|
+
end
|
|
461
461
|
|
|
462
462
|
mu_sum += player.mu * balance_weight
|
|
463
463
|
sigma_squared_sum += (player.sigma * balance_weight)**2
|
|
@@ -477,10 +477,10 @@ module OpenSkill
|
|
|
477
477
|
return [] if game.empty?
|
|
478
478
|
|
|
479
479
|
team_scores = if ranks
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
480
|
+
ranks.each_with_index.map { |rank, idx| rank || idx }
|
|
481
|
+
else
|
|
482
|
+
game.each_index.to_a
|
|
483
|
+
end
|
|
484
484
|
|
|
485
485
|
sorted_scores = team_scores.sort
|
|
486
486
|
rank_map = {}
|
|
@@ -536,12 +536,12 @@ module OpenSkill
|
|
|
536
536
|
|
|
537
537
|
# Calculate s based on rank comparison
|
|
538
538
|
s = if team_q.rank > team_i.rank
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
539
|
+
1.0 # team_i won
|
|
540
|
+
elsif team_q.rank == team_i.rank
|
|
541
|
+
0.5 # draw
|
|
542
|
+
else
|
|
543
|
+
0.0 # team_i lost
|
|
544
|
+
end
|
|
545
545
|
|
|
546
546
|
# Accumulate omega and delta
|
|
547
547
|
omega_sum += (team_i.sigma_squared / c_iq) * (s - piq)
|
|
@@ -563,8 +563,8 @@ module OpenSkill
|
|
|
563
563
|
end
|
|
564
564
|
|
|
565
565
|
# Average over comparisons
|
|
566
|
-
omega = comparisons > 0 ? omega_sum / comparisons : 0.0
|
|
567
|
-
delta = comparisons > 0 ? delta_sum / comparisons : 0.0
|
|
566
|
+
omega = (comparisons > 0) ? omega_sum / comparisons : 0.0
|
|
567
|
+
delta = (comparisons > 0) ? delta_sum / comparisons : 0.0
|
|
568
568
|
|
|
569
569
|
# Update each player in the team
|
|
570
570
|
team_i.team.each_with_index.map do |player, j|
|
|
@@ -84,7 +84,7 @@ module OpenSkill
|
|
|
84
84
|
denominator = Statistics::Normal.cdf(xt)
|
|
85
85
|
|
|
86
86
|
if denominator < Float::EPSILON
|
|
87
|
-
return x < 0 ? 1.0 : 0.0
|
|
87
|
+
return (x < 0) ? 1.0 : 0.0
|
|
88
88
|
end
|
|
89
89
|
|
|
90
90
|
v_val = v(x, t)
|
|
@@ -102,11 +102,11 @@ module OpenSkill
|
|
|
102
102
|
b = Statistics::Normal.cdf(t - xx) - Statistics::Normal.cdf(-t - xx)
|
|
103
103
|
|
|
104
104
|
if b < 1e-5
|
|
105
|
-
return x < 0 ? (-x - t) : (-x + t)
|
|
105
|
+
return (x < 0) ? (-x - t) : (-x + t)
|
|
106
106
|
end
|
|
107
107
|
|
|
108
108
|
a = Statistics::Normal.pdf(-t - xx) - Statistics::Normal.pdf(t - xx)
|
|
109
|
-
(x < 0 ? -a : a) / b
|
|
109
|
+
((x < 0) ? -a : a) / b
|
|
110
110
|
end
|
|
111
111
|
|
|
112
112
|
# The W-tilde function for draws as defined in Weng-Lin 2011
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require_relative
|
|
5
|
-
require_relative
|
|
3
|
+
require "securerandom"
|
|
4
|
+
require_relative "../statistics/normal"
|
|
5
|
+
require_relative "common"
|
|
6
6
|
|
|
7
7
|
module OpenSkill
|
|
8
8
|
module Models
|
|
@@ -71,8 +71,8 @@ module OpenSkill
|
|
|
71
71
|
# @raise [ArgumentError] if rating_array is invalid
|
|
72
72
|
def load_rating(rating_array, name: nil)
|
|
73
73
|
raise ArgumentError, "Rating must be an Array, got #{rating_array.class}" unless rating_array.is_a?(Array)
|
|
74
|
-
raise ArgumentError,
|
|
75
|
-
raise ArgumentError,
|
|
74
|
+
raise ArgumentError, "Rating array must have exactly 2 elements" unless rating_array.size == 2
|
|
75
|
+
raise ArgumentError, "Rating values must be numeric" unless rating_array.all? { |v| v.is_a?(Numeric) }
|
|
76
76
|
|
|
77
77
|
Rating.new(mu: rating_array[0], sigma: rating_array[1], name: name)
|
|
78
78
|
end
|
|
@@ -93,7 +93,7 @@ module OpenSkill
|
|
|
93
93
|
validate_weights!(teams, weights) if weights
|
|
94
94
|
|
|
95
95
|
# Can't have both ranks and scores
|
|
96
|
-
raise ArgumentError,
|
|
96
|
+
raise ArgumentError, "Cannot provide both ranks and scores" if ranks && scores
|
|
97
97
|
|
|
98
98
|
# Deep copy teams to avoid mutating input
|
|
99
99
|
original_teams = teams
|
|
@@ -300,25 +300,25 @@ module OpenSkill
|
|
|
300
300
|
end
|
|
301
301
|
|
|
302
302
|
def <(other)
|
|
303
|
-
raise ArgumentError,
|
|
303
|
+
raise ArgumentError, "comparison with non-Rating" unless other.is_a?(Rating)
|
|
304
304
|
|
|
305
305
|
ordinal < other.ordinal
|
|
306
306
|
end
|
|
307
307
|
|
|
308
308
|
def >(other)
|
|
309
|
-
raise ArgumentError,
|
|
309
|
+
raise ArgumentError, "comparison with non-Rating" unless other.is_a?(Rating)
|
|
310
310
|
|
|
311
311
|
ordinal > other.ordinal
|
|
312
312
|
end
|
|
313
313
|
|
|
314
314
|
def <=(other)
|
|
315
|
-
raise ArgumentError,
|
|
315
|
+
raise ArgumentError, "comparison with non-Rating" unless other.is_a?(Rating)
|
|
316
316
|
|
|
317
317
|
ordinal <= other.ordinal
|
|
318
318
|
end
|
|
319
319
|
|
|
320
320
|
def >=(other)
|
|
321
|
-
raise ArgumentError,
|
|
321
|
+
raise ArgumentError, "comparison with non-Rating" unless other.is_a?(Rating)
|
|
322
322
|
|
|
323
323
|
ordinal >= other.ordinal
|
|
324
324
|
end
|
|
@@ -377,8 +377,8 @@ module OpenSkill
|
|
|
377
377
|
|
|
378
378
|
# Validate teams structure
|
|
379
379
|
def validate_teams!(teams)
|
|
380
|
-
raise ArgumentError,
|
|
381
|
-
raise ArgumentError,
|
|
380
|
+
raise ArgumentError, "Teams must be an Array" unless teams.is_a?(Array)
|
|
381
|
+
raise ArgumentError, "Must have at least 2 teams" if teams.size < 2
|
|
382
382
|
|
|
383
383
|
teams.each_with_index do |team, idx|
|
|
384
384
|
raise ArgumentError, "Team #{idx} must be an Array" unless team.is_a?(Array)
|
|
@@ -393,7 +393,7 @@ module OpenSkill
|
|
|
393
393
|
# Validate ranks
|
|
394
394
|
def validate_ranks!(teams, ranks)
|
|
395
395
|
raise ArgumentError, "Ranks must be an Array, got #{ranks.class}" unless ranks.is_a?(Array)
|
|
396
|
-
raise ArgumentError,
|
|
396
|
+
raise ArgumentError, "Ranks must have same length as teams" if ranks.size != teams.size
|
|
397
397
|
|
|
398
398
|
ranks.each do |rank|
|
|
399
399
|
raise ArgumentError, "All ranks must be numeric, got #{rank.class}" unless rank.is_a?(Numeric)
|
|
@@ -403,7 +403,7 @@ module OpenSkill
|
|
|
403
403
|
# Validate scores
|
|
404
404
|
def validate_scores!(teams, scores)
|
|
405
405
|
raise ArgumentError, "Scores must be an Array, got #{scores.class}" unless scores.is_a?(Array)
|
|
406
|
-
raise ArgumentError,
|
|
406
|
+
raise ArgumentError, "Scores must have same length as teams" if scores.size != teams.size
|
|
407
407
|
|
|
408
408
|
scores.each do |score|
|
|
409
409
|
raise ArgumentError, "All scores must be numeric, got #{score.class}" unless score.is_a?(Numeric)
|
|
@@ -413,7 +413,7 @@ module OpenSkill
|
|
|
413
413
|
# Validate weights
|
|
414
414
|
def validate_weights!(teams, weights)
|
|
415
415
|
raise ArgumentError, "Weights must be an Array, got #{weights.class}" unless weights.is_a?(Array)
|
|
416
|
-
raise ArgumentError,
|
|
416
|
+
raise ArgumentError, "Weights must have same length as teams" if weights.size != teams.size
|
|
417
417
|
|
|
418
418
|
weights.each_with_index do |team_weights, idx|
|
|
419
419
|
raise ArgumentError, "Weights for team #{idx} must be an Array" unless team_weights.is_a?(Array)
|
|
@@ -481,7 +481,7 @@ module OpenSkill
|
|
|
481
481
|
comparison_count += 1
|
|
482
482
|
end
|
|
483
483
|
|
|
484
|
-
adjusted_mu += comparison_count > 0 ? (margin_adjustment / comparison_count) : margin_adjustment
|
|
484
|
+
adjusted_mu += (comparison_count > 0) ? (margin_adjustment / comparison_count) : margin_adjustment
|
|
485
485
|
end
|
|
486
486
|
|
|
487
487
|
summed = Math.exp(adjusted_mu / c_value)
|
|
@@ -516,11 +516,11 @@ module OpenSkill
|
|
|
516
516
|
|
|
517
517
|
sorted_team.each do |player|
|
|
518
518
|
balance_weight = if @balance
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
519
|
+
ordinal_diff = max_ordinal - player.ordinal
|
|
520
|
+
1 + (ordinal_diff / (max_ordinal + @kappa))
|
|
521
|
+
else
|
|
522
|
+
1.0
|
|
523
|
+
end
|
|
524
524
|
|
|
525
525
|
mu_sum += player.mu * balance_weight
|
|
526
526
|
sigma_squared_sum += (player.sigma * balance_weight)**2
|
|
@@ -540,10 +540,10 @@ module OpenSkill
|
|
|
540
540
|
return [] if game.empty?
|
|
541
541
|
|
|
542
542
|
team_scores = if ranks
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
543
|
+
ranks.each_with_index.map { |rank, idx| rank || idx }
|
|
544
|
+
else
|
|
545
|
+
game.each_index.to_a
|
|
546
|
+
end
|
|
547
547
|
|
|
548
548
|
sorted_scores = team_scores.sort
|
|
549
549
|
rank_map = {}
|
|
@@ -603,7 +603,7 @@ module OpenSkill
|
|
|
603
603
|
comparison_count += 1
|
|
604
604
|
end
|
|
605
605
|
|
|
606
|
-
adjusted_mu_i += comparison_count > 0 ? (margin_adjustment / comparison_count) : margin_adjustment
|
|
606
|
+
adjusted_mu_i += (comparison_count > 0) ? (margin_adjustment / comparison_count) : margin_adjustment
|
|
607
607
|
end
|
|
608
608
|
|
|
609
609
|
i_mu_over_c = Math.exp(adjusted_mu_i / c_value)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require_relative
|
|
5
|
-
require_relative
|
|
3
|
+
require "securerandom"
|
|
4
|
+
require_relative "../statistics/normal"
|
|
5
|
+
require_relative "common"
|
|
6
6
|
|
|
7
7
|
module OpenSkill
|
|
8
8
|
module Models
|
|
@@ -74,8 +74,8 @@ module OpenSkill
|
|
|
74
74
|
# @raise [ArgumentError] if rating_array is invalid
|
|
75
75
|
def load_rating(rating_array, name: nil)
|
|
76
76
|
raise ArgumentError, "Rating must be an Array, got #{rating_array.class}" unless rating_array.is_a?(Array)
|
|
77
|
-
raise ArgumentError,
|
|
78
|
-
raise ArgumentError,
|
|
77
|
+
raise ArgumentError, "Rating array must have exactly 2 elements" unless rating_array.size == 2
|
|
78
|
+
raise ArgumentError, "Rating values must be numeric" unless rating_array.all? { |v| v.is_a?(Numeric) }
|
|
79
79
|
|
|
80
80
|
Rating.new(mu: rating_array[0], sigma: rating_array[1], name: name)
|
|
81
81
|
end
|
|
@@ -95,7 +95,7 @@ module OpenSkill
|
|
|
95
95
|
validate_scores!(teams, scores) if scores
|
|
96
96
|
validate_weights!(teams, weights) if weights
|
|
97
97
|
|
|
98
|
-
raise ArgumentError,
|
|
98
|
+
raise ArgumentError, "Cannot provide both ranks and scores" if ranks && scores
|
|
99
99
|
|
|
100
100
|
# Deep copy teams to avoid mutating input
|
|
101
101
|
original_teams = teams
|
|
@@ -302,25 +302,25 @@ module OpenSkill
|
|
|
302
302
|
end
|
|
303
303
|
|
|
304
304
|
def <(other)
|
|
305
|
-
raise ArgumentError,
|
|
305
|
+
raise ArgumentError, "comparison with non-Rating" unless other.is_a?(Rating)
|
|
306
306
|
|
|
307
307
|
ordinal < other.ordinal
|
|
308
308
|
end
|
|
309
309
|
|
|
310
310
|
def >(other)
|
|
311
|
-
raise ArgumentError,
|
|
311
|
+
raise ArgumentError, "comparison with non-Rating" unless other.is_a?(Rating)
|
|
312
312
|
|
|
313
313
|
ordinal > other.ordinal
|
|
314
314
|
end
|
|
315
315
|
|
|
316
316
|
def <=(other)
|
|
317
|
-
raise ArgumentError,
|
|
317
|
+
raise ArgumentError, "comparison with non-Rating" unless other.is_a?(Rating)
|
|
318
318
|
|
|
319
319
|
ordinal <= other.ordinal
|
|
320
320
|
end
|
|
321
321
|
|
|
322
322
|
def >=(other)
|
|
323
|
-
raise ArgumentError,
|
|
323
|
+
raise ArgumentError, "comparison with non-Rating" unless other.is_a?(Rating)
|
|
324
324
|
|
|
325
325
|
ordinal >= other.ordinal
|
|
326
326
|
end
|
|
@@ -379,8 +379,8 @@ module OpenSkill
|
|
|
379
379
|
|
|
380
380
|
# Validate teams structure
|
|
381
381
|
def validate_teams!(teams)
|
|
382
|
-
raise ArgumentError,
|
|
383
|
-
raise ArgumentError,
|
|
382
|
+
raise ArgumentError, "Teams must be an Array" unless teams.is_a?(Array)
|
|
383
|
+
raise ArgumentError, "Must have at least 2 teams" if teams.size < 2
|
|
384
384
|
|
|
385
385
|
teams.each_with_index do |team, idx|
|
|
386
386
|
raise ArgumentError, "Team #{idx} must be an Array" unless team.is_a?(Array)
|
|
@@ -395,7 +395,7 @@ module OpenSkill
|
|
|
395
395
|
# Validate ranks
|
|
396
396
|
def validate_ranks!(teams, ranks)
|
|
397
397
|
raise ArgumentError, "Ranks must be an Array, got #{ranks.class}" unless ranks.is_a?(Array)
|
|
398
|
-
raise ArgumentError,
|
|
398
|
+
raise ArgumentError, "Ranks must have same length as teams" if ranks.size != teams.size
|
|
399
399
|
|
|
400
400
|
ranks.each do |rank|
|
|
401
401
|
raise ArgumentError, "All ranks must be numeric, got #{rank.class}" unless rank.is_a?(Numeric)
|
|
@@ -405,7 +405,7 @@ module OpenSkill
|
|
|
405
405
|
# Validate scores
|
|
406
406
|
def validate_scores!(teams, scores)
|
|
407
407
|
raise ArgumentError, "Scores must be an Array, got #{scores.class}" unless scores.is_a?(Array)
|
|
408
|
-
raise ArgumentError,
|
|
408
|
+
raise ArgumentError, "Scores must have same length as teams" if scores.size != teams.size
|
|
409
409
|
|
|
410
410
|
scores.each do |score|
|
|
411
411
|
raise ArgumentError, "All scores must be numeric, got #{score.class}" unless score.is_a?(Numeric)
|
|
@@ -415,7 +415,7 @@ module OpenSkill
|
|
|
415
415
|
# Validate weights
|
|
416
416
|
def validate_weights!(teams, weights)
|
|
417
417
|
raise ArgumentError, "Weights must be an Array, got #{weights.class}" unless weights.is_a?(Array)
|
|
418
|
-
raise ArgumentError,
|
|
418
|
+
raise ArgumentError, "Weights must have same length as teams" if weights.size != teams.size
|
|
419
419
|
|
|
420
420
|
weights.each_with_index do |team_weights, idx|
|
|
421
421
|
raise ArgumentError, "Weights for team #{idx} must be an Array" unless team_weights.is_a?(Array)
|
|
@@ -453,11 +453,11 @@ module OpenSkill
|
|
|
453
453
|
|
|
454
454
|
sorted_team.each do |player|
|
|
455
455
|
balance_weight = if @balance
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
456
|
+
ordinal_diff = max_ordinal - player.ordinal
|
|
457
|
+
1 + (ordinal_diff / (max_ordinal + @kappa))
|
|
458
|
+
else
|
|
459
|
+
1.0
|
|
460
|
+
end
|
|
461
461
|
|
|
462
462
|
mu_sum += player.mu * balance_weight
|
|
463
463
|
sigma_squared_sum += (player.sigma * balance_weight)**2
|
|
@@ -477,10 +477,10 @@ module OpenSkill
|
|
|
477
477
|
return [] if game.empty?
|
|
478
478
|
|
|
479
479
|
team_scores = if ranks
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
480
|
+
ranks.each_with_index.map { |rank, idx| rank || idx }
|
|
481
|
+
else
|
|
482
|
+
game.each_index.to_a
|
|
483
|
+
end
|
|
484
484
|
|
|
485
485
|
sorted_scores = team_scores.sort
|
|
486
486
|
rank_map = {}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require_relative
|
|
5
|
-
require_relative
|
|
3
|
+
require "securerandom"
|
|
4
|
+
require_relative "../statistics/normal"
|
|
5
|
+
require_relative "common"
|
|
6
6
|
|
|
7
7
|
module OpenSkill
|
|
8
8
|
module Models
|
|
@@ -77,8 +77,8 @@ module OpenSkill
|
|
|
77
77
|
# @raise [ArgumentError] if rating_array is invalid
|
|
78
78
|
def load_rating(rating_array, name: nil)
|
|
79
79
|
raise ArgumentError, "Rating must be an Array, got #{rating_array.class}" unless rating_array.is_a?(Array)
|
|
80
|
-
raise ArgumentError,
|
|
81
|
-
raise ArgumentError,
|
|
80
|
+
raise ArgumentError, "Rating array must have exactly 2 elements" unless rating_array.size == 2
|
|
81
|
+
raise ArgumentError, "Rating values must be numeric" unless rating_array.all? { |v| v.is_a?(Numeric) }
|
|
82
82
|
|
|
83
83
|
Rating.new(mu: rating_array[0], sigma: rating_array[1], name: name)
|
|
84
84
|
end
|
|
@@ -98,7 +98,7 @@ module OpenSkill
|
|
|
98
98
|
validate_scores!(teams, scores) if scores
|
|
99
99
|
validate_weights!(teams, weights) if weights
|
|
100
100
|
|
|
101
|
-
raise ArgumentError,
|
|
101
|
+
raise ArgumentError, "Cannot provide both ranks and scores" if ranks && scores
|
|
102
102
|
|
|
103
103
|
# Deep copy teams to avoid mutating input
|
|
104
104
|
original_teams = teams
|
|
@@ -305,25 +305,25 @@ module OpenSkill
|
|
|
305
305
|
end
|
|
306
306
|
|
|
307
307
|
def <(other)
|
|
308
|
-
raise ArgumentError,
|
|
308
|
+
raise ArgumentError, "comparison with non-Rating" unless other.is_a?(Rating)
|
|
309
309
|
|
|
310
310
|
ordinal < other.ordinal
|
|
311
311
|
end
|
|
312
312
|
|
|
313
313
|
def >(other)
|
|
314
|
-
raise ArgumentError,
|
|
314
|
+
raise ArgumentError, "comparison with non-Rating" unless other.is_a?(Rating)
|
|
315
315
|
|
|
316
316
|
ordinal > other.ordinal
|
|
317
317
|
end
|
|
318
318
|
|
|
319
319
|
def <=(other)
|
|
320
|
-
raise ArgumentError,
|
|
320
|
+
raise ArgumentError, "comparison with non-Rating" unless other.is_a?(Rating)
|
|
321
321
|
|
|
322
322
|
ordinal <= other.ordinal
|
|
323
323
|
end
|
|
324
324
|
|
|
325
325
|
def >=(other)
|
|
326
|
-
raise ArgumentError,
|
|
326
|
+
raise ArgumentError, "comparison with non-Rating" unless other.is_a?(Rating)
|
|
327
327
|
|
|
328
328
|
ordinal >= other.ordinal
|
|
329
329
|
end
|
|
@@ -382,8 +382,8 @@ module OpenSkill
|
|
|
382
382
|
|
|
383
383
|
# Validate teams structure
|
|
384
384
|
def validate_teams!(teams)
|
|
385
|
-
raise ArgumentError,
|
|
386
|
-
raise ArgumentError,
|
|
385
|
+
raise ArgumentError, "Teams must be an Array" unless teams.is_a?(Array)
|
|
386
|
+
raise ArgumentError, "Must have at least 2 teams" if teams.size < 2
|
|
387
387
|
|
|
388
388
|
teams.each_with_index do |team, idx|
|
|
389
389
|
raise ArgumentError, "Team #{idx} must be an Array" unless team.is_a?(Array)
|
|
@@ -398,7 +398,7 @@ module OpenSkill
|
|
|
398
398
|
# Validate ranks
|
|
399
399
|
def validate_ranks!(teams, ranks)
|
|
400
400
|
raise ArgumentError, "Ranks must be an Array, got #{ranks.class}" unless ranks.is_a?(Array)
|
|
401
|
-
raise ArgumentError,
|
|
401
|
+
raise ArgumentError, "Ranks must have same length as teams" if ranks.size != teams.size
|
|
402
402
|
|
|
403
403
|
ranks.each do |rank|
|
|
404
404
|
raise ArgumentError, "All ranks must be numeric, got #{rank.class}" unless rank.is_a?(Numeric)
|
|
@@ -408,7 +408,7 @@ module OpenSkill
|
|
|
408
408
|
# Validate scores
|
|
409
409
|
def validate_scores!(teams, scores)
|
|
410
410
|
raise ArgumentError, "Scores must be an Array, got #{scores.class}" unless scores.is_a?(Array)
|
|
411
|
-
raise ArgumentError,
|
|
411
|
+
raise ArgumentError, "Scores must have same length as teams" if scores.size != teams.size
|
|
412
412
|
|
|
413
413
|
scores.each do |score|
|
|
414
414
|
raise ArgumentError, "All scores must be numeric, got #{score.class}" unless score.is_a?(Numeric)
|
|
@@ -418,7 +418,7 @@ module OpenSkill
|
|
|
418
418
|
# Validate weights
|
|
419
419
|
def validate_weights!(teams, weights)
|
|
420
420
|
raise ArgumentError, "Weights must be an Array, got #{weights.class}" unless weights.is_a?(Array)
|
|
421
|
-
raise ArgumentError,
|
|
421
|
+
raise ArgumentError, "Weights must have same length as teams" if weights.size != teams.size
|
|
422
422
|
|
|
423
423
|
weights.each_with_index do |team_weights, idx|
|
|
424
424
|
raise ArgumentError, "Weights for team #{idx} must be an Array" unless team_weights.is_a?(Array)
|
|
@@ -456,11 +456,11 @@ module OpenSkill
|
|
|
456
456
|
|
|
457
457
|
sorted_team.each do |player|
|
|
458
458
|
balance_weight = if @balance
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
459
|
+
ordinal_diff = max_ordinal - player.ordinal
|
|
460
|
+
1 + (ordinal_diff / (max_ordinal + @kappa))
|
|
461
|
+
else
|
|
462
|
+
1.0
|
|
463
|
+
end
|
|
464
464
|
|
|
465
465
|
mu_sum += player.mu * balance_weight
|
|
466
466
|
sigma_squared_sum += (player.sigma * balance_weight)**2
|
|
@@ -480,10 +480,10 @@ module OpenSkill
|
|
|
480
480
|
return [] if game.empty?
|
|
481
481
|
|
|
482
482
|
team_scores = if ranks
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
483
|
+
ranks.each_with_index.map { |rank, idx| rank || idx }
|
|
484
|
+
else
|
|
485
|
+
game.each_index.to_a
|
|
486
|
+
end
|
|
487
487
|
|
|
488
488
|
sorted_scores = team_scores.sort
|
|
489
489
|
rank_map = {}
|
|
@@ -567,8 +567,8 @@ module OpenSkill
|
|
|
567
567
|
end
|
|
568
568
|
|
|
569
569
|
# Average over comparisons
|
|
570
|
-
omega = comparisons > 0 ? omega_sum / comparisons : 0.0
|
|
571
|
-
delta = comparisons > 0 ? delta_sum / comparisons : 0.0
|
|
570
|
+
omega = (comparisons > 0) ? omega_sum / comparisons : 0.0
|
|
571
|
+
delta = (comparisons > 0) ? delta_sum / comparisons : 0.0
|
|
572
572
|
|
|
573
573
|
# Update each player in the team
|
|
574
574
|
team_i.team.each_with_index.map do |player, j|
|
data/lib/openskill/version.rb
CHANGED
data/lib/openskill.rb
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative
|
|
4
|
-
require_relative
|
|
5
|
-
require_relative
|
|
6
|
-
require_relative
|
|
7
|
-
require_relative
|
|
8
|
-
require_relative
|
|
9
|
-
require_relative
|
|
10
|
-
require_relative
|
|
3
|
+
require_relative "openskill/version"
|
|
4
|
+
require_relative "openskill/statistics/normal"
|
|
5
|
+
require_relative "openskill/models/common"
|
|
6
|
+
require_relative "openskill/models/plackett_luce"
|
|
7
|
+
require_relative "openskill/models/bradley_terry_full"
|
|
8
|
+
require_relative "openskill/models/bradley_terry_part"
|
|
9
|
+
require_relative "openskill/models/thurstone_mosteller_full"
|
|
10
|
+
require_relative "openskill/models/thurstone_mosteller_part"
|
|
11
11
|
|
|
12
12
|
module OpenSkill
|
|
13
13
|
class Error < StandardError; end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: openskill
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tamas Erdos
|
|
@@ -10,45 +10,45 @@ cert_chain: []
|
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
|
-
name:
|
|
13
|
+
name: distribution
|
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
|
15
15
|
requirements:
|
|
16
16
|
- - "~>"
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '
|
|
18
|
+
version: '0.8'
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: '
|
|
25
|
+
version: '0.8'
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
|
-
name:
|
|
27
|
+
name: bigdecimal
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
29
29
|
requirements:
|
|
30
|
-
- - "
|
|
30
|
+
- - ">="
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: '
|
|
32
|
+
version: '3.1'
|
|
33
33
|
type: :runtime
|
|
34
34
|
prerelease: false
|
|
35
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
36
|
requirements:
|
|
37
|
-
- - "
|
|
37
|
+
- - ">="
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: '
|
|
39
|
+
version: '3.1'
|
|
40
40
|
- !ruby/object:Gem::Dependency
|
|
41
41
|
name: prime
|
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|
|
43
43
|
requirements:
|
|
44
|
-
- - "
|
|
44
|
+
- - ">="
|
|
45
45
|
- !ruby/object:Gem::Version
|
|
46
46
|
version: '0.1'
|
|
47
47
|
type: :runtime
|
|
48
48
|
prerelease: false
|
|
49
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
50
50
|
requirements:
|
|
51
|
-
- - "
|
|
51
|
+
- - ">="
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
53
|
version: '0.1'
|
|
54
54
|
- !ruby/object:Gem::Dependency
|
|
@@ -79,6 +79,20 @@ dependencies:
|
|
|
79
79
|
- - "~>"
|
|
80
80
|
- !ruby/object:Gem::Version
|
|
81
81
|
version: '13.0'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: standardrb
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - ">="
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '0'
|
|
89
|
+
type: :development
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - ">="
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '0'
|
|
82
96
|
description: A Ruby implementation of the OpenSkill rating system, providing Bayesian
|
|
83
97
|
skill ratings for multiplayer games
|
|
84
98
|
email:
|
|
@@ -116,7 +130,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
116
130
|
- !ruby/object:Gem::Version
|
|
117
131
|
version: '0'
|
|
118
132
|
requirements: []
|
|
119
|
-
rubygems_version:
|
|
133
|
+
rubygems_version: 4.0.20
|
|
120
134
|
specification_version: 4
|
|
121
135
|
summary: Multiplayer rating system for Ruby
|
|
122
136
|
test_files: []
|