icu_tournament 1.3.10 → 1.3.11

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.
@@ -169,10 +169,10 @@ module ICU
169
169
  # Add a result. Don't use this method directly - use ICU::Tournament#add_result instead.
170
170
  def add_result(result)
171
171
  raise "invalid result" unless result.class == ICU::Result
172
- raise "player number (#{@num}) is not matched to result player number (#{result.player})" unless @num == result.player
172
+ raise "player number (#{@num}) is not matched to result player number (#{result.inspect})" unless @num == result.player
173
173
  already = @results.find_all { |r| r.round == result.round }
174
174
  return if already.size == 1 && already[0].eql?(result)
175
- raise "round number (#{result.round}) of new result is not unique and new result is not the same as existing one" unless already.size == 0
175
+ raise "player #{@num} already has a result in round #{result.round} (#{already[0].inspect}) which does not match the one being added (#{result.inspect})" unless already.size == 0
176
176
  if @results.size == 0 || @results.last.round <= result.round
177
177
  @results << result
178
178
  else
@@ -51,13 +51,7 @@ module ICU
51
51
  # tluser.colour # => 'B'
52
52
  # tluser.round # => 2
53
53
  #
54
- # The reversed result copies the _rateable_ attribute of the original unless an
55
- # explicit override is supplied.
56
- #
57
- # result.rateable # => true
58
- # result.reverse.rateable # => true (copied from original)
59
- # result.reverse(false).rateable # => false (overriden)
60
- #
54
+ # The _rateable_ attribute is the same in a result and it's reverse.
61
55
  # A result which has no opponent is not reversible (the _reverse_ method returns _nil_).
62
56
  #
63
57
  # The return value from the _score_ method is always one of _W_, _L_ or _D_. However,
@@ -145,13 +139,13 @@ module ICU
145
139
  end
146
140
 
147
141
  # Return a reversed version (from the opponent's perspective) of a result.
148
- def reverse(rateable=nil)
142
+ def reverse
149
143
  return unless @opponent
150
144
  r = Result.new(@round, @opponent, @score == 'W' ? 'L' : (@score == 'L' ? 'W' : 'D'))
151
145
  r.opponent = @player
152
146
  r.colour = 'W' if @colour == 'B'
153
147
  r.colour = 'B' if @colour == 'W'
154
- r.rateable = rateable || @rateable
148
+ r.rateable = @rateable
155
149
  r
156
150
  end
157
151
 
@@ -167,6 +161,11 @@ module ICU
167
161
  end
168
162
  self
169
163
  end
164
+
165
+ # Short descrition mainly for debugging.
166
+ def inspect
167
+ "R#{@round}P#{@player}O#{@opponent || '-'}#{@score}#{@colour || '-'}#{@rateable ? 'R' : 'U'}"
168
+ end
170
169
 
171
170
  # Loose equality. True if the round, player and opponent numbers, colour and score all match.
172
171
  def ==(other)
@@ -44,6 +44,17 @@ module ICU
44
44
  # raise an exception if the players it references through their tournament numbers
45
45
  # (10, 20 and 30 in this example) have not already been added to the tournament.
46
46
  #
47
+ # Adding a result from the perspective of one player automatically adds it from the
48
+ # perspective of the opponent, if there is one. The result may subsequently be added
49
+ # explicitly from opponent's perspective as long as it does not contradict what was
50
+ # implicitly added previously.
51
+ #
52
+ # t.add_result(ICU::Result.new(3, 20, 'L', :opponent => 10, :colour => 'W'))
53
+ # t.add_result(ICU::Result.new(3, 10, 'W', :opponent => 20, :colour => 'B')) # unnecessary, but not a problem
54
+ #
55
+ # t.add_result(ICU::Result.new(3, 20, 'L', :opponent => 10, :colour => 'W'))
56
+ # t.add_result(ICU::Result.new(3, 10, 'D', :opponent => 20, :colour => 'B')) # would raise an exception
57
+ #
47
58
  # See ICU::Player and ICU::Result for more details about players and results.
48
59
  #
49
60
  # == Validation
@@ -255,20 +266,16 @@ module ICU
255
266
 
256
267
  # Add a result to a tournament. An exception is raised if the players referenced in the result (by number)
257
268
  # do not exist in the tournament. The result, which remember is from the perspective of one of the players,
258
- # is added to that player's results. Additionally, the reverse of the result is automatically added to the player's
259
- # opponent, unless the opponent does not exist (e.g. byes, walkovers). By default, if the result is rateable
260
- # then the opponent's result will also be rateable. To make the opponent's result unrateable, set the optional
261
- # second parameter to false.
262
- def add_result(result, reverse_rateable=true)
269
+ # is added to that player's results. Additionally, the reverse of the result is automatically added to the
270
+ # player's opponent, if there is one.
271
+ def add_result(result)
263
272
  raise "invalid result" unless result.class == ICU::Result
264
273
  raise "result round number (#{result.round}) inconsistent with number of tournament rounds" if @rounds && result.round > @rounds
265
274
  raise "player number (#{result.player}) does not exist" unless @player[result.player]
266
275
  @player[result.player].add_result(result)
267
276
  if result.opponent
268
277
  raise "opponent number (#{result.opponent}) does not exist" unless @player[result.opponent]
269
- reverse = result.reverse
270
- reverse.rateable = false unless reverse_rateable
271
- @player[result.opponent].add_result(reverse)
278
+ @player[result.opponent].add_result(result.reverse)
272
279
  end
273
280
  end
274
281
 
@@ -398,7 +405,11 @@ module ICU
398
405
  raise "player #{num} has no results" if p.results.size == 0
399
406
  p.results.each do |r|
400
407
  next unless r.opponent
401
- raise "opponent #{r.opponent} of player #{num} is not in the tournament" unless @player[r.opponent]
408
+ opponent = @player[r.opponent]
409
+ raise "opponent #{r.opponent} of player #{num} is not in the tournament" unless opponent
410
+ o = opponent.find_result(r.round)
411
+ raise "opponent #{r.opponent} of player #{num} has no result in round #{r.round}" unless o
412
+ raise "opponent's result (#{o.inspect}) is not reverse of player's (#{r.inspect})" unless o.reverse.eql?(r)
402
413
  end
403
414
  end
404
415
  end
@@ -36,17 +36,17 @@ module ICU
36
36
  # tournament.rounds # => 9
37
37
  # tournament.website # => "http://www.bcmchess.co.uk/monarch2007/"
38
38
  #
39
- # The main player (the player whose results are being reported for rating) played 9 rounds
39
+ # The main player (the ICU player whose results are being reported for rating) played 9 rounds
40
40
  # but only 8 other players (he had a bye in round 6), so the total number of players is 9.
41
41
  #
42
42
  # tournament.players.size # => 9
43
43
  #
44
- # Each player has a unique number for the tournament. The main player always occurs first in this type of file, so his number is 1.
44
+ # Each player has a unique number for the tournament, starting at 1 for the first ICU player.
45
45
  #
46
46
  # player = tournament.player(1)
47
47
  # player.name # => "Fox, Anthony"
48
48
  #
49
- # This player has 4 points from 9 rounds but only 8 of his results are are rateable (because of the bye).
49
+ # In the example, this player has 4 points from 9 rounds but only 8 of his results are are rateable (because of the bye).
50
50
  #
51
51
  # player.points # => 4.0
52
52
  # player.results.size # => 9
@@ -61,13 +61,6 @@ module ICU
61
61
  # opponents.size # => 8
62
62
  # opponents.find_all{ |o| o.results.size == 1 }.size # => 8
63
63
  #
64
- # However, none of the opponents' results are rateable because they are foreign to the domestic rating list
65
- # to which the main player belongs. For example:
66
- #
67
- # opponent = tournament.players(2)
68
- # opponent.name # => "Taylor, Peter P."
69
- # opponent.results[0].rateable # => false
70
- #
71
64
  # If the file contains errors, then the return value from <em>parse_file</em> is <em>nil</em> and
72
65
  # an error message is returned by the <em>error</em> method of the parser object. The method
73
66
  # <em>parse_file!</em> is similar except that it raises errors, and the methods <em>parse</em>
@@ -85,10 +78,11 @@ module ICU
85
78
  # Extra condtions, over and above the normal validation rules, apply before any tournament validates or can be serialized in this format.
86
79
  #
87
80
  # * the tournament must have a _site_ attribute
88
- # * there must be at least one player with an _id_ (interpreted as an ICU ID number)
81
+ # * there must be at least one player with an _id_ (ICU ID number)
89
82
  # * all foreign players (those without an ICU ID) must have a _fed_ attribute (federation)
90
83
  # * all ICU players must have a result in every round (even if it is just bye or is unrateable)
91
- # * the opponents of all ICU players must have a federation (this could include other ICU players)
84
+ # * all the opponents of each ICU player must have a federation (this could include other ICU players with federation _IRL_)
85
+ # * at least one of each ICU player's opponents must have a rating
92
86
  #
93
87
  # If any of these are not satisfied, then the following method calls will all raise an exception:
94
88
  #
@@ -98,7 +92,7 @@ module ICU
98
92
  #
99
93
  # You can also build the tournament object from scratch using your own data and then serialize it.
100
94
  # For example, here are the commands to reproduce the example above. Note that in this format
101
- # opponents' ratings are FIDE while players' IDs are ICU.
95
+ # opponents' ratings are FIDE.
102
96
  #
103
97
  # t = ICU::Tournament.new("Isle of Man Masters, 2007", '2007-09-22', :rounds => 9)
104
98
  # t.site = 'http://www.bcmchess.co.uk/monarch2007/'
@@ -217,7 +211,7 @@ module ICU
217
211
  data << n
218
212
  r = p.find_result(n)
219
213
  data << case r.score; when 'W' then '1'; when 'L' then '0'; else '='; end
220
- if r.rateable
214
+ if r.opponent
221
215
  data << r.colour
222
216
  o = t.player(r.opponent)
223
217
  data << o.last_name
@@ -243,11 +237,14 @@ module ICU
243
237
  foreign = t.players.find_all { |p| !p.id }
244
238
  raise "all foreign players must have a federation" if foreign.detect { |f| !f.fed }
245
239
  icu.each do |p|
240
+ rated = 0
246
241
  (1..t.rounds).each do |r|
247
242
  result = p.find_result(r)
248
243
  raise "ICU players must have a result in every round" unless result
249
244
  raise "all opponents of ICU players must have a federation" if result.opponent && !t.player(result.opponent).fed
245
+ rated += 1 if result.opponent && t.player(result.opponent).fide_rating
250
246
  end
247
+ raise "player #{p.num} (#{p.name}) has no rated opponents" if rated == 0
251
248
  end
252
249
  end
253
250
 
@@ -320,17 +317,16 @@ module ICU
320
317
  old_player.merge(opponent)
321
318
  old_result = @player.find_result(@round)
322
319
  raise "missing result for player (#{@player.name}) in round #{@round}" unless old_result
323
- raise "mismatched results for player (#{old_player.name}) in round #{@round}" unless result == old_result
324
- old_result.rateable = true
320
+ raise "mismatched results for player (#{old_player.name}): #{result.inspect} #{old_result.inspect}" unless result.eql?(old_result)
325
321
  else
326
322
  old_result = old_player.find_result(@round)
327
323
  raise "a player (#{old_player.name}) has more than one game in the same round (#{@round})" if old_result
328
- @tournament.add_result(result, false)
324
+ @tournament.add_result(result)
329
325
  end
330
326
  else
331
327
  @tournament.add_player(opponent)
332
328
  result.opponent = opponent.num
333
- @tournament.add_result(result, false)
329
+ @tournament.add_result(result)
334
330
  end
335
331
  end
336
332
  @state = 6 if @round == @tournament.rounds
@@ -2,6 +2,6 @@
2
2
 
3
3
  module ICU
4
4
  class Tournament
5
- VERSION = "1.3.10"
5
+ VERSION = "1.3.11"
6
6
  end
7
7
  end
data/spec/player_spec.rb CHANGED
@@ -227,7 +227,7 @@ module ICU
227
227
  player = Player.new('Mark', 'Orr', 3)
228
228
  player.add_result(Result.new(1, 3, 'W', :opponent => 1))
229
229
  player.add_result(Result.new(2, 3, 'D', :opponent => 2))
230
- lambda { player.add_result(Result.new(2, 3, 'L', :opponent => 4)) }.should raise_error(/round number .* unique/)
230
+ lambda { player.add_result(Result.new(2, 3, 'L', :opponent => 4)) }.should raise_error(/does not match/)
231
231
  end
232
232
  end
233
233
 
@@ -58,9 +58,13 @@ CSV
58
58
 
59
59
  it "should have correct player details" do
60
60
  check_player(1, 'Gearoidin', 'Ui Laighleis', 4, 3, 2.0, :id => 3364)
61
- check_player(2, 'April', 'Cronin', 1, 0, 1.0, :fide_rating => 2005, :fed => 'IRL')
62
- check_player(3, 'Suzanne', 'Connolly', 1, 0, 0.5, :fide_rating => 1950, :fed => 'IRL')
63
- check_player(4, 'Linda', 'Powell', 1, 0, 0.0, :fide_rating => 1850, :fed => 'WLS')
61
+ check_player(2, 'April', 'Cronin', 1, 1, 1.0, :fide_rating => 2005, :fed => 'IRL')
62
+ check_player(3, 'Suzanne', 'Connolly', 1, 1, 0.5, :fide_rating => 1950, :fed => 'IRL')
63
+ check_player(4, 'Linda', 'Powell', 1, 1, 0.0, :fide_rating => 1850, :fed => 'WLS')
64
+ end
65
+
66
+ it "should be valid" do
67
+ @t.invalid.should be_false
64
68
  end
65
69
  end
66
70
 
@@ -114,7 +118,7 @@ CSV
114
118
  @o.size.should == 8
115
119
  @o.find_all{ |o| o.results.size == 1}.size.should == 8
116
120
  @r.name.should == "Taylor, Peter P."
117
- @r.results[0].rateable.should be_false
121
+ @r.results[0].rateable.should be_true
118
122
  end
119
123
  end
120
124
 
@@ -154,9 +158,9 @@ CSV
154
158
  it "should have correct player details" do
155
159
  check_player(1, 'Gearoidin', 'Ui Laighleis', 2, 2, 1.0, :id => 3364)
156
160
  check_player(4, 'Mark', 'Orr', 2, 2, 1.5, :id => 1350)
157
- check_player(2, 'Gary', 'Kasparov', 1, 0, 0.5, :fide_rating => 2800, :fed => 'RUS', :title => 'GM')
158
- check_player(3, 'April', 'Cronin', 2, 0, 1.0, :fide_rating => 2005, :fed => 'IRL')
159
- check_player(5, 'Bobby', 'Fischer', 1, 0, 0.0, :fide_rating => 2700, :fed => 'USA', :title => 'GM')
161
+ check_player(2, 'Gary', 'Kasparov', 1, 1, 0.5, :fide_rating => 2800, :fed => 'RUS', :title => 'GM')
162
+ check_player(3, 'April', 'Cronin', 2, 2, 1.0, :fide_rating => 2005, :fed => 'IRL')
163
+ check_player(5, 'Bobby', 'Fischer', 1, 1, 0.0, :fide_rating => 2700, :fed => 'USA', :title => 'GM')
160
164
  end
161
165
  end
162
166
 
@@ -196,8 +200,8 @@ CSV
196
200
  it "should have correct player details" do
197
201
  check_player(1, 'Gearoidin', 'Ui Laighleis', 2, 2, 1.0, :fide_rating => 1800, :fed => 'IRL', :id => 3364)
198
202
  check_player(3, 'Mark', 'Orr', 2, 2, 1.0, :fide_rating => 2100, :fed => 'IRL', :id => 1350, :title => 'IM')
199
- check_player(2, 'Gary', 'Kasparov', 1, 0, 0.5, :fide_rating => 2800, :fed => 'RUS')
200
- check_player(4, 'April', 'Cronin', 1, 0, 0.5, :fide_rating => 2005, :fed => 'IRL')
203
+ check_player(2, 'Gary', 'Kasparov', 1, 1, 0.5, :fide_rating => 2800, :fed => 'RUS')
204
+ check_player(4, 'April', 'Cronin', 1, 1, 0.5, :fide_rating => 2005, :fed => 'IRL')
201
205
  end
202
206
  end
203
207
 
@@ -234,8 +238,8 @@ CSV
234
238
 
235
239
  it "should have correct player details" do
236
240
  check_player(1, 'Gearoidin', 'Ui Laighleis', 2, 2, 1.0, :id => 3364)
237
- check_player(2, 'Gary', 'Kasparov', 1, 0, 0.5, :fide_rating => 2800, :fed => 'RUS', :title => 'GM')
238
- check_player(3, 'Mark', 'Orr', 1, 0, 0.5, :fide_rating => 2100, :fed => 'IRL', :title => 'IM')
241
+ check_player(2, 'Gary', 'Kasparov', 1, 1, 0.5, :fide_rating => 2800, :fed => 'RUS', :title => 'GM')
242
+ check_player(3, 'Mark', 'Orr', 1, 1, 0.5, :fide_rating => 2100, :fed => 'IRL', :title => 'IM')
239
243
  end
240
244
 
241
245
  it "should still have original names" do
@@ -565,8 +569,8 @@ CSV
565
569
  it "should parse UTF-8" do
566
570
  lambda { @t = @f.parse!(@csv) }.should_not raise_error
567
571
  check_player(1, 'Gearoìdin', 'Uì Laighlèis', 2, 2, 1.0, :id => 3364)
568
- check_player(2, 'Gary', 'Kasparov', 1, 0, 0.5, :fide_rating => 2800, :fed => 'RUS', :title => 'GM')
569
- check_player(3, 'Mârk', 'Örr', 1, 0, 0.5, :fide_rating => 2100, :fed => 'IRL', :title => 'IM')
572
+ check_player(2, 'Gary', 'Kasparov', 1, 1, 0.5, :fide_rating => 2800, :fed => 'RUS', :title => 'GM')
573
+ check_player(3, 'Mârk', 'Örr', 1, 1, 0.5, :fide_rating => 2100, :fed => 'IRL', :title => 'IM')
570
574
  @t.name.should == "Brätto Open, 2001"
571
575
  end
572
576
 
@@ -574,8 +578,8 @@ CSV
574
578
  @csv = @csv.encode("ISO-8859-1")
575
579
  lambda { @t = @f.parse!(@csv) }.should_not raise_error
576
580
  check_player(1, 'Gearoìdin', 'Uì Laighlèis', 2, 2, 1.0, :id => 3364)
577
- check_player(2, 'Gary', 'Kasparov', 1, 0, 0.5, :fide_rating => 2800, :fed => 'RUS', :title => 'GM')
578
- check_player(3, 'Mârk', 'Örr', 1, 0, 0.5, :fide_rating => 2100, :fed => 'IRL', :title => 'IM')
581
+ check_player(2, 'Gary', 'Kasparov', 1, 1, 0.5, :fide_rating => 2800, :fed => 'RUS', :title => 'GM')
582
+ check_player(3, 'Mârk', 'Örr', 1, 1, 0.5, :fide_rating => 2100, :fed => 'IRL', :title => 'IM')
579
583
  @t.name.should == "Brätto Open, 2001"
580
584
  end
581
585
  end
@@ -614,8 +618,8 @@ CSV
614
618
  file = "#{@s}/utf-8.csv"
615
619
  lambda { @t = @p.parse_file!(file) }.should_not raise_error
616
620
  check_player(1, 'Gearoìdin', 'Uì Laighlèis', 2, 2, 1.0, :id => 3364)
617
- check_player(2, 'Gary', 'Kasparov', 1, 0, 0.5, :fide_rating => 2800, :fed => 'RUS', :title => 'GM')
618
- check_player(3, 'Mârk', 'Örr', 1, 0, 0.5, :fide_rating => 2100, :fed => 'IRL', :title => 'IM')
621
+ check_player(2, 'Gary', 'Kasparov', 1, 1, 0.5, :fide_rating => 2800, :fed => 'RUS', :title => 'GM')
622
+ check_player(3, 'Mârk', 'Örr', 1, 1, 0.5, :fide_rating => 2100, :fed => 'IRL', :title => 'IM')
619
623
  @t.name.should == "Brätto Open, 2001"
620
624
  end
621
625
 
@@ -623,8 +627,8 @@ CSV
623
627
  file = "#{@s}/latin-1.csv"
624
628
  lambda { @t = @p.parse_file!(file) }.should_not raise_error
625
629
  check_player(1, 'Gearoìdin', 'Uì Laighlèis', 2, 2, 1.0, :id => 3364)
626
- check_player(2, 'Gary', 'Kasparov', 1, 0, 0.5, :fide_rating => 2800, :fed => 'RUS', :title => 'GM')
627
- check_player(3, 'Mârk', 'Örr', 1, 0, 0.5, :fide_rating => 2100, :fed => 'IRL', :title => 'IM')
630
+ check_player(2, 'Gary', 'Kasparov', 1, 1, 0.5, :fide_rating => 2800, :fed => 'RUS', :title => 'GM')
631
+ check_player(3, 'Mârk', 'Örr', 1, 1, 0.5, :fide_rating => 2100, :fed => 'IRL', :title => 'IM')
628
632
  @t.name.should == "Brätto Open, 2001"
629
633
  end
630
634
  end
@@ -333,13 +333,10 @@ EOS
333
333
  @boby.points.should == 0.5
334
334
  end
335
335
 
336
- it "can be added symmetrically or asymmetrically with respect to rateability" do
336
+ it "asymmetric results cannot be added" do
337
337
  @t.add_result(Result.new(1, 1, 'W', :opponent => 2))
338
- @mark.results[0].rateable.should be_true
339
- @gary.results[0].rateable.should be_true
340
- @t.add_result(Result.new(2, 1, 'W', :opponent => 3), false)
341
- @mark.results[1].rateable.should be_true
342
- @boby.results[0].rateable.should be_false
338
+ lambda { @t.add_result(Result.new(1, 2, 'D', :opponent => 1)) }.should raise_error(/result.*match/)
339
+ lambda { @t.add_result(Result.new(1, 2, 'L', :opponent => 1, :rateable => false)) }.should raise_error(/result.*match/)
343
340
  end
344
341
 
345
342
  it "should have a defined player" do
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 1
7
7
  - 3
8
- - 10
9
- version: 1.3.10
8
+ - 11
9
+ version: 1.3.11
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-03-08 00:00:00 +00:00
17
+ date: 2011-03-11 00:00:00 +00:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency