icu_tournament 1.3.10 → 1.3.11

Sign up to get free protection for your applications and to get access to all the features.
@@ -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