icu_tournament 1.3.5 → 1.3.6

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.
@@ -0,0 +1,92 @@
1
+ module ICU
2
+ #
3
+ # This class is used to recognise the names of tie break rules. The class method _identify_
4
+ # takes a string as it's only argument and returns a new intstance if it recognises a
5
+ # tie break rule from the string, or _nil_ otherwise. An instance has three read-only methods:
6
+ # _id_ (a shoet symbolic name), _code_ (a two letter code) and _name_ (the full name).
7
+ # For example:
8
+ #
9
+ # ICU::TieBreak.identify("no such rule") # => nil
10
+ # tb = ICU::TieBreak.identify("Neustadlt")
11
+ # tb.id # => :neustadtl
12
+ # tb.code # => "SB"
13
+ # tb.name # => "Sonneborn-Berger"
14
+ #
15
+ # The method is case insensitive and can cope with extraneous white space and,
16
+ # to a limited extent, name variations and spelling mistakes:
17
+ #
18
+ # ICU::TieBreak.identify("SB").name # => "Sonneborn-Berger"
19
+ # ICU::TieBreak.identify("NESTADL").name # => "Sonneborn-Berger"
20
+ # ICU::TieBreak.identify(" wins ").name # => "Number of wins"
21
+ # ICU::TieBreak.identify(:sum_ratings).name # => "Sum of opponent's ratings"
22
+ # ICU::TieBreak.identify("median").name # => "Harkness"
23
+ # ICU::TieBreak.identify("MODIFIED").name # => "Modified median"
24
+ # ICU::TieBreak.identify("Modified\nMedian").name # => "Modified median"
25
+ # ICU::TieBreak.identify("\tbuccholts\t").name # => "Buchholz"
26
+ # ICU::TieBreak.identify("progressive\r\n").name # => "Sum of progressive scores"
27
+ # ICU::TieBreak.identify("SumOfCumulative").name # => "Sum of progressive scores"
28
+ #
29
+ # The full list of supported tie break rules is:
30
+ #
31
+ # * Buchholz (:buchholz, "BH"): sum of opponents' scores
32
+ # * Harkness (:harkness, "HK"): like Buchholz except the highest and lowest opponents' scores are discarded (or two highest and lowest if 9 rounds or more)
33
+ # * Modified median (:modified_median, "MM"): same as Harkness except only lowest (or highest) score(s) are discarded for players with more (or less) than 50%
34
+ # * Number of blacks (:blacks, "NB") number of blacks
35
+ # * Number of wins (:wins, "NW") number of wins
36
+ # * Player's name (:name, "PN"): alphabetical by name
37
+ # * Sonneborn-Berger (:neustadtl, "SB"): sum of scores of players defeated plus half sum of scores of players drawn against
38
+ # * Sum of opponents' ratings (:ratings, "SR"): sum of opponents ratings (FIDE ratings are used in preference to local ratings if available)
39
+ # * Sum of progressive scores (:progressive, "SP"): sum of running score for each round
40
+ #
41
+ # An array of all supported TieBreak instances (ordered by name) is returned by the class method _rules_.
42
+ #
43
+ # rules = ICU::TieBreak.rules
44
+ # rules.size # => 9
45
+ # rules.first.name # => "Buchholz"
46
+ #
47
+ # Note that this class only deals with the recognition of tie break names, not the calculation of tie break scores.
48
+ # The latter is currently implemented in the ICU::Tournament class.
49
+ #
50
+ class TieBreak
51
+ attr_reader :id, :code, :name
52
+ private_class_method :new
53
+
54
+ RULES =
55
+ {
56
+ :blacks => ["NB", "Number of blacks", %r{(number[-_ ]?of[-_ ])?blacks?}],
57
+ :buchholz => ["BH", "Buchholz", %r{^buc{1,2}h{1,2}olt?[zs]}],
58
+ :harkness => ["HK", "Harkness", %r{^(harkness?|median)$}],
59
+ :modified_median => ["MM", "Modified median", %r{^modified([-_ ]?median)?$}],
60
+ :name => ["PN", "Player's name", %r{^(player('?s)?[-_ ]?)?name$}],
61
+ :neustadtl => ["SB", "Sonneborn-Berger", %r{^(sonn?eborn[-_ ]?berger|n[eu]{1,2}sta[dtl]{2,3})}],
62
+ :progressive => ["SP", "Sum of progressive scores", %r{^(sum[-_ ]?(of[-_ ]?)?)?(progressive|cumulative)([-_ ]?scores?)?}],
63
+ :ratings => ["SR", "Sum of opponent's ratings", %r{(sum[-_ ]?(of[-_ ]?)?)?(opponents'?[-_ ]?)?ratings}],
64
+ :wins => ["NW", "Number of wins", %r{(number[-_ ]?of[-_ ])?wins?}],
65
+ }
66
+
67
+ # Given a string, return the TieBreak rule it is recognised as, or return nil.
68
+ def self.identify(str)
69
+ return nil unless str
70
+ str = str.to_s.gsub(/\s+/, ' ').strip.downcase
71
+ return nil if str.length <= 1 || str.length == 3
72
+ RULES.each_pair { |id, rule| return new(id, rule[0], rule[1]) if str.upcase == rule[0] || str.match(rule[2]) }
73
+ nil
74
+ end
75
+
76
+ # Return an array of all tie break rules, ordered by name.
77
+ def self.rules
78
+ RULES.keys.sort_by{ |id| RULES[id][1] }.inject([]) do |rules, id|
79
+ rules << new(id, RULES[id][0], RULES[id][1])
80
+ end
81
+ end
82
+
83
+ # :enddoc:
84
+ private
85
+
86
+ def initialize(id, code, name)
87
+ @id = id
88
+ @code = code
89
+ @name = name
90
+ end
91
+ end
92
+ end
@@ -105,17 +105,7 @@ module ICU
105
105
  # t.tie_breaks = [:buchholz, :neustadtl, :blacks, :wins]
106
106
  # t.tie_breaks = [] # reset to the default
107
107
  #
108
- # The full list of supported methods is:
109
- #
110
- # * _Buchholz_: sum of opponents' scores
111
- # * _Harkness_ (or _median_): like Buchholz except the highest and lowest opponents' scores are discarded (or two highest and lowest if 9 rounds or more)
112
- # * _modified_median_: same as Harkness except only lowest (or highest) score(s) are discarded for players with more (or less) than 50%
113
- # * _Neustadtl_ (or _Sonneborn-Berger_): sum of scores of players defeated plus half sum of scores of players drawn against
114
- # * _progressive_ (or _cumulative_): sum of running score for each round
115
- # * _ratings_: sum of opponents ratings (FIDE ratings are used in preference to local ratings if available)
116
- # * _blacks_: number of blacks
117
- # * _wins_: number of wins
118
- # * _name_: alphabetical by name (if _tie_breaks_ is set to an empty array, as it is initially, then this will be used as the back-up tie breaker)
108
+ # See ICU::TieBreak for the full list of supported tie break methods.
119
109
  #
120
110
  # The return value from _rerank_ is the tournament object itself, to allow chaining, for example:
121
111
  #
@@ -226,28 +216,14 @@ module ICU
226
216
  @teams.find{ |t| t.matches(name) }
227
217
  end
228
218
 
229
- # Set the tie break methods.
219
+ # Canonicalise the names in the tie break array.
230
220
  def tie_breaks=(tie_breaks)
231
221
  raise "argument error - always set tie breaks to an array" unless tie_breaks.class == Array
232
- # Canonicalise the tie break method names.
233
- tie_breaks.map! do |m|
234
- m = m.to_s if m.class == Symbol
235
- m = m.downcase.gsub(/[-\s]/, '_') if m.class == String
236
- case m
237
- when true then 'name'
238
- when 'sonneborn_berger' then 'neustadtl'
239
- when 'modified_median' then 'modified'
240
- when 'median' then 'harkness'
241
- when 'cumulative' then 'progressive'
242
- else m
243
- end
222
+ @tie_breaks = tie_breaks.map do |str|
223
+ tb = ICU::TieBreak.identify(str)
224
+ raise "invalid tie break method '#{str}'" unless tb
225
+ tb.id
244
226
  end
245
-
246
- # Check they're all valid.
247
- tie_breaks.each { |m| raise "invalid tie break method '#{m}'" unless m.to_s.match(/^(blacks|buchholz|harkness|modified|name|neustadtl|progressive|ratings|wins)$/) }
248
-
249
- # Finally set them.
250
- @tie_breaks = tie_breaks;
251
227
  end
252
228
 
253
229
  # Add a new player to the tournament. Must have a unique player number.
@@ -508,7 +484,7 @@ module ICU
508
484
  end
509
485
  end
510
486
 
511
- # Return an array of tie break methods and an array of tie break orders (+1 for asc, -1 for desc).
487
+ # Return an array of tie break rules and an array of tie break orders (+1 for asc, -1 for desc).
512
488
  # The first and most important method is always "score", the last and least important is always "name".
513
489
  def tie_break_data
514
490
 
@@ -516,19 +492,19 @@ module ICU
516
492
  methods, order, data = Array.new, Hash.new, Hash.new
517
493
 
518
494
  # Score is always the most important.
519
- methods << 'score'
520
- order['score'] = -1
495
+ methods << :score
496
+ order[:score] = -1
521
497
 
522
498
  # Add the configured methods.
523
499
  tie_breaks.each do |m|
524
500
  methods << m
525
- order[m] = m == 'name' ? 1 : -1
501
+ order[m] = m == :name ? 1 : -1
526
502
  end
527
503
 
528
504
  # Name is included as the last and least important tie breaker unless it's already been added.
529
- unless methods.include?('name')
530
- methods << 'name'
531
- order['name'] = 1
505
+ unless methods.include?(:name)
506
+ methods << :name
507
+ order[:name] = 1
532
508
  end
533
509
 
534
510
  # We'll need the number of rounds.
@@ -537,7 +513,7 @@ module ICU
537
513
  # Pre-calculate some scores that are not in themselves tie break scores
538
514
  # but are needed in the calculation of some of the actual tie-break scores.
539
515
  pre_calculated = Array.new
540
- pre_calculated << 'opp-score' # sum scores where a non-played games counts 0.5
516
+ pre_calculated << :opp_score # sum scores where a non-played games counts 0.5
541
517
  pre_calculated.each do |m|
542
518
  data[m] = Hash.new
543
519
  @player.values.each { |p| data[m][p.num] = tie_break_score(data, m, p, rounds) }
@@ -557,20 +533,20 @@ module ICU
557
533
  # Return a tie break score for a given player and a given tie break method.
558
534
  def tie_break_score(hash, method, player, rounds)
559
535
  case method
560
- when 'score' then player.points
561
- when 'wins' then player.results.inject(0) { |t,r| t + (r.opponent && r.score == 'W' ? 1 : 0) }
562
- when 'blacks' then player.results.inject(0) { |t,r| t + (r.opponent && r.colour == 'B' ? 1 : 0) }
563
- when 'buchholz' then player.results.inject(0.0) { |t,r| t + (r.opponent ? hash['opp-score'][r.opponent] : 0.0) }
564
- when 'neustadtl' then player.results.inject(0.0) { |t,r| t + (r.opponent ? hash['opp-score'][r.opponent] * r.points : 0.0) }
565
- when 'opp-score' then player.results.inject(0.0) { |t,r| t + (r.opponent ? r.points : 0.5) } + (rounds - player.results.size) * 0.5
566
- when 'progressive' then (1..rounds).inject(0.0) { |t,n| r = player.find_result(n); s = r ? r.points : 0.0; t + s * (rounds + 1 - n) }
567
- when 'ratings' then player.results.inject(0) { |t,r| t + (r.opponent && (@player[r.opponent].fide_rating || @player[r.opponent].rating) ? (@player[r.opponent].fide_rating || @player[r.opponent].rating) : 0) }
568
- when 'harkness', 'modified'
569
- scores = player.results.map{ |r| r.opponent ? hash['opp-score'][r.opponent] : 0.0 }.sort
536
+ when :score then player.points
537
+ when :wins then player.results.inject(0) { |t,r| t + (r.opponent && r.score == 'W' ? 1 : 0) }
538
+ when :blacks then player.results.inject(0) { |t,r| t + (r.opponent && r.colour == 'B' ? 1 : 0) }
539
+ when :buchholz then player.results.inject(0.0) { |t,r| t + (r.opponent ? hash[:opp_score][r.opponent] : 0.0) }
540
+ when :neustadtl then player.results.inject(0.0) { |t,r| t + (r.opponent ? hash[:opp_score][r.opponent] * r.points : 0.0) }
541
+ when :opp_score then player.results.inject(0.0) { |t,r| t + (r.opponent ? r.points : 0.5) } + (rounds - player.results.size) * 0.5
542
+ when :progressive then (1..rounds).inject(0.0) { |t,n| r = player.find_result(n); s = r ? r.points : 0.0; t + s * (rounds + 1 - n) }
543
+ when :ratings then player.results.inject(0) { |t,r| t + (r.opponent && (@player[r.opponent].fide_rating || @player[r.opponent].rating) ? (@player[r.opponent].fide_rating || @player[r.opponent].rating) : 0) }
544
+ when :harkness, :modified_median
545
+ scores = player.results.map{ |r| r.opponent ? hash[:opp_score][r.opponent] : 0.0 }.sort
570
546
  1.upto(rounds - player.results.size) { scores << 0.0 }
571
547
  half = rounds / 2.0
572
548
  times = rounds >= 9 ? 2 : 1
573
- if method == 'harkness' || player.points == half
549
+ if method == :harkness || player.points == half
574
550
  1.upto(times) { scores.shift; scores.pop }
575
551
  else
576
552
  1.upto(times) { scores.send(player.points > half ? :shift : :pop) }
@@ -100,7 +100,7 @@ module ICU
100
100
  #
101
101
  # You may wish set the tie-break rules before ranking:
102
102
  #
103
- # tournament.tie_breaks = [:buchholz, ::neustadtl]
103
+ # tournament.tie_breaks = [:buchholz, :neustadtl]
104
104
  # spexport = tournament.rerank.renumber.serialize('SwissPerfect')
105
105
  #
106
106
  # See ICU::Tournament for more about tie-breaks.
@@ -2,6 +2,6 @@
2
2
 
3
3
  module ICU
4
4
  class Tournament
5
- VERSION = "1.3.5"
5
+ VERSION = "1.3.6"
6
6
  end
7
7
  end
@@ -3,7 +3,7 @@
3
3
  require 'icu_name'
4
4
 
5
5
  icu_tournament_files = Array.new
6
- icu_tournament_files.concat %w{util federation}
6
+ icu_tournament_files.concat %w{util federation tie_break}
7
7
  icu_tournament_files.concat %w{player result team tournament}
8
8
  icu_tournament_files.concat %w{fcsv krause sp spx}.map{ |f| "tournament_#{f}"}
9
9
 
@@ -0,0 +1,100 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ module ICU
4
+ describe TieBreak do
5
+ context "#identify which rule" do
6
+ it "should recognize Buchholz" do
7
+ TieBreak.identify(:buchholz).id.should == :buchholz
8
+ TieBreak.identify(" BucholtS ").id.should == :buchholz
9
+ TieBreak.identify(" bh ").id.should == :buchholz
10
+ TieBreak.identify(" buccholts ").code.should == "BH"
11
+ end
12
+
13
+ it "should recognize Harkness (Median)" do
14
+ TieBreak.identify(:harkness).id.should == :harkness
15
+ TieBreak.identify("median").id.should == :harkness
16
+ TieBreak.identify(" hARKNES ").id.should == :harkness
17
+ TieBreak.identify("HK").id.should == :harkness
18
+ TieBreak.identify("MEDIAN").code.should == "HK"
19
+ end
20
+
21
+ it "should recognize Modified Median" do
22
+ TieBreak.identify(:modified).id.should == :modified_median
23
+ TieBreak.identify(" modified MEDIAN ").id.should == :modified_median
24
+ TieBreak.identify("MM").code.should == "MM"
25
+ end
26
+
27
+ it "should recognize Number of Blacks" do
28
+ TieBreak.identify(:blacks).id.should == :blacks
29
+ TieBreak.identify("number\tof\tblacks\n").id.should == :blacks
30
+ TieBreak.identify("\tnb\t").id.should == :blacks
31
+ TieBreak.identify("number_blacks").code.should == "NB"
32
+ end
33
+
34
+ it "should recognize Number of Wins" do
35
+ TieBreak.identify(:wins).id.should == :wins
36
+ TieBreak.identify(" number-of-wins ").id.should == :wins
37
+ TieBreak.identify("NUMBER WINS\r\n").id.should == :wins
38
+ TieBreak.identify("nw").code.should == "NW"
39
+ end
40
+
41
+ it "should recognize Player's of Name" do
42
+ TieBreak.identify(:name).id.should == :name
43
+ TieBreak.identify("Player's Name").id.should == :name
44
+ TieBreak.identify("players_name").id.should == :name
45
+ TieBreak.identify("PN").id.should == :name
46
+ TieBreak.identify("PLAYER-NAME").code.should == "PN"
47
+ end
48
+
49
+ it "should recognize Sonneborn-Berger" do
50
+ TieBreak.identify(:sonneborn_berger).id.should == :neustadtl
51
+ TieBreak.identify(:neustadtl).id.should == :neustadtl
52
+ TieBreak.identify(" SONNEBORN\nberger").id.should == :neustadtl
53
+ TieBreak.identify("\t soneborn_berger \t").id.should == :neustadtl
54
+ TieBreak.identify("sb").id.should == :neustadtl
55
+ TieBreak.identify("NESTADL").code.should == "SB"
56
+ end
57
+
58
+ it "should recognize Sum of Progressive Scores" do
59
+ TieBreak.identify(:progressive).id.should == :progressive
60
+ TieBreak.identify("CUMULATIVE").id.should == :progressive
61
+ TieBreak.identify("sum of progressive scores").id.should == :progressive
62
+ TieBreak.identify("SUM-cumulative_SCORE").id.should == :progressive
63
+ TieBreak.identify(:cumulative_score).id.should == :progressive
64
+ TieBreak.identify("SumOfCumulative").id.should == :progressive
65
+ TieBreak.identify("SP").code.should == "SP"
66
+ end
67
+
68
+ it "should recognize Sum of Opponents' Ratings" do
69
+ TieBreak.identify(:ratings).id.should == :ratings
70
+ TieBreak.identify("sum of opponents ratings").id.should == :ratings
71
+ TieBreak.identify("Opponents' Ratings").id.should == :ratings
72
+ TieBreak.identify("SR").id.should == :ratings
73
+ TieBreak.identify("SUMOPPONENTSRATINGS").code.should == "SR"
74
+ end
75
+
76
+ it "should recognize player's name" do
77
+ TieBreak.identify(:name).id.should == :name
78
+ TieBreak.identify(" player's NAME ").id.should == :name
79
+ TieBreak.identify("pn").code.should == "PN"
80
+ end
81
+
82
+ it "should return nil for unrecognized tie breaks" do
83
+ TieBreak.identify("XYZ").should be_nil
84
+ TieBreak.identify(nil).should be_nil
85
+ end
86
+ end
87
+
88
+ context "return an array of tie break rules" do
89
+ before(:each) do
90
+ @rules = TieBreak.rules
91
+ end
92
+
93
+ it "should be an array in a specific order" do
94
+ @rules.size.should == 9
95
+ @rules.first.name.should == "Buchholz"
96
+ @rules.map(&:code).join("|").should == "BH|HK|MM|NB|NW|PN|SB|SR|SP"
97
+ end
98
+ end
99
+ end
100
+ end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 1
7
7
  - 3
8
- - 5
9
- version: 1.3.5
8
+ - 6
9
+ version: 1.3.6
10
10
  platform: ruby
11
11
  authors:
12
12
  - Mark Orr
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2011-02-19 00:00:00 +00:00
17
+ date: 2011-02-27 00:00:00 +00:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -135,6 +135,7 @@ files:
135
135
  - lib/icu_tournament/player.rb
136
136
  - lib/icu_tournament/result.rb
137
137
  - lib/icu_tournament/team.rb
138
+ - lib/icu_tournament/tie_break.rb
138
139
  - lib/icu_tournament/tournament.rb
139
140
  - lib/icu_tournament/tournament_fcsv.rb
140
141
  - lib/icu_tournament/tournament_krause.rb
@@ -148,6 +149,7 @@ files:
148
149
  - spec/result_spec.rb
149
150
  - spec/spec_helper.rb
150
151
  - spec/team_spec.rb
152
+ - spec/tie_break_spec.rb
151
153
  - spec/tournament_fcsv_spec.rb
152
154
  - spec/tournament_krause_spec.rb
153
155
  - spec/tournament_sp_spec.rb
@@ -194,6 +196,7 @@ test_files:
194
196
  - spec/result_spec.rb
195
197
  - spec/spec_helper.rb
196
198
  - spec/team_spec.rb
199
+ - spec/tie_break_spec.rb
197
200
  - spec/tournament_fcsv_spec.rb
198
201
  - spec/tournament_krause_spec.rb
199
202
  - spec/tournament_sp_spec.rb