sanichi-chess_icu 0.5.2 → 0.5.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,4 @@
1
1
  ---
2
2
  :major: 0
3
3
  :minor: 5
4
- :patch: 2
4
+ :patch: 3
@@ -210,10 +210,10 @@ All other attributes are unaffected.
210
210
  end
211
211
 
212
212
  # Renumber the player according to the supplied hash. Return self.
213
- def renumber!(map)
213
+ def renumber(map)
214
214
  raise "player number #{@num} not found in renumbering hash" unless map[@num]
215
215
  self.num = map[@num]
216
- @results.each{ |r| r.renumber!(map) }
216
+ @results.each{ |r| r.renumber(map) }
217
217
  self
218
218
  end
219
219
 
@@ -171,7 +171,7 @@ The _points_ read-only accessor always returns a floating point number: either 0
171
171
  end
172
172
 
173
173
  # Renumber the player and opponent (if there is one) according to the supplied hash. Return self.
174
- def renumber!(map)
174
+ def renumber(map)
175
175
  raise "result player number #{@player} not found in renumbering hash" unless map[@player]
176
176
  self.player = map[@player]
177
177
  if @opponent
@@ -69,7 +69,7 @@ Attempting to add non-numbers or duplicate numbers as new team members results i
69
69
  end
70
70
 
71
71
  # Renumber the players according to the supplied hash. Return self.
72
- def renumber!(map)
72
+ def renumber(map)
73
73
  @members.each_with_index do |pnum, index|
74
74
  raise "player number #{pnum} not found in renumbering hash" unless map[pnum]
75
75
  @members[index] = map[pnum]
@@ -56,18 +56,33 @@ If the _rerank_ option is set, as in this example:
56
56
 
57
57
  then there are additional side effects of validating a tournament:
58
58
 
59
- * the players will be ranked if no players have any rank
59
+ * the players will be ranked if there is no existing ranking
60
60
  * the players will be reranked if the existing ranking is inconsistent
61
61
 
62
62
  Ranking is consistent if either no players have any rank or if all players have a rank and no player is ranked higher than another player with more points.
63
63
 
64
- The players in a tournament, whose reference numbers can be any set of unique integers (including zero and negative numbers),
65
- can be renumbered in order of rank or name. After renumbering the new player numbers will start at 1 and go up to the number
66
- of players.
64
+ The default tie break method used to rank players on the same score is alphabetical (by last name then first name).
65
+ Other methods can be specified via the _rerank_ option. Instead of setting the option to _true_, alternatives are
66
+ the following (both symbols or strings will work):
67
67
 
68
- t.renumber!(:name) # renumber by name
69
- t.renumber!(:rank) # renumber by rank
70
- t.renumber! # same - rank is the default
68
+ * _sum_of_scores_: sum of opponents' scores
69
+ * _name_: this is the default and the same as setting the option to true
70
+
71
+ Since _validate_ and _invalid_ only rerank a tournament with absent or inconsistent ranking, to force
72
+ a particular kind of ranking of a tournament which is already ranked, use the _rerank_ method. This method
73
+ takes one argument, the tie break method and it works the same as the _rerank_ option to _validate_. For example:
74
+
75
+ t.rerank(:sum_of_scores) # rerank using sum of scores as tie break
76
+ t.rerank(:name) # rerank using player names as tie break
77
+ t.rerank # same as _name_, which is the default
78
+
79
+ The players in a tournament, whose reference numbers can be any set of unique integers (including zero and
80
+ negative numbers), can be renumbered in order of rank or name. After renumbering the new player numbers will
81
+ start at 1 and go up to the number of players.
82
+
83
+ t.renumber(:name) # renumber by name
84
+ t.renumber(:rank) # renumber by rank
85
+ t.renumber # same - rank is the default
71
86
 
72
87
  A side effect of renumbering by rank is that if the tournament started without any player rankings or
73
88
  with inconsitent rankings, it will be reranked (i.e. the method _rerank_ will be called).
@@ -256,20 +271,20 @@ with inconsitent rankings, it will be reranked (i.e. the method _rerank_ will be
256
271
  end
257
272
  end
258
273
 
259
- # Rerank the tournament by score, resolving ties using name.
260
- def rerank
261
- @player.values.map{ |p| [p, p.points] }.sort do |a,b|
262
- d = b[1] <=> a[1]
263
- d = a[0].last_name <=> b[0].last_name if d == 0
264
- d = a[0].first_name <=> b[0].first_name if d == 0
265
- d
266
- end.each_with_index do |v,i|
267
- v[0].rank = i + 1
274
+ # Rerank the tournament by score first and if necessary using a configurable tie breaker method.
275
+ def rerank(tie_break_method = :name)
276
+ points, tie_break_scores, tie_break_order = rerank_data(tie_break_method)
277
+ sortable = @player.values.map { |p| [p, points[p.num], tie_break_scores[p.num]] }
278
+ sortable.sort do |a,b|
279
+ diff = b[1] <=> a[1]
280
+ diff == 0 ? (b[2] <=> a[2]) * tie_break_order : diff
281
+ end.each_with_index do |s,i|
282
+ s[0].rank = i + 1
268
283
  end
269
284
  end
270
-
271
- # Renumber the players according to a given criterion. Return self.
272
- def renumber!(criterion = :rank)
285
+
286
+ # Renumber the players according to a given criterion.
287
+ def renumber(criterion = :rank)
273
288
  map = Hash.new
274
289
 
275
290
  # Decide how to rank.
@@ -281,14 +296,12 @@ with inconsitent rankings, it will be reranked (i.e. the method _rerank_ will be
281
296
  end
282
297
 
283
298
  # Apply ranking.
284
- @teams.each{ |t| t.renumber!(map) }
299
+ @teams.each{ |t| t.renumber(map) }
285
300
  @player = @player.values.inject({}) do |hash, player|
286
- player.renumber!(map)
301
+ player.renumber(map)
287
302
  hash[player.num] = player
288
303
  hash
289
304
  end
290
-
291
- self
292
305
  end
293
306
 
294
307
  # Is a tournament invalid? Either returns false (if it's valid) or an error message.
@@ -304,7 +317,7 @@ with inconsitent rankings, it will be reranked (i.e. the method _rerank_ will be
304
317
  # Raise an exception if a tournament is not valid.
305
318
  # Covers all the ways a tournament can be invalid not already enforced by the setters.
306
319
  def validate!(options={})
307
- begin check_ranks rescue rerank end if options[:rerank]
320
+ begin check_ranks rescue rerank(options[:rerank]) end if options[:rerank]
308
321
  check_players
309
322
  check_rounds
310
323
  check_dates
@@ -401,5 +414,32 @@ with inconsitent rankings, it will be reranked (i.e. the method _rerank_ will be
401
414
  end
402
415
  end
403
416
  end
417
+
418
+ # Return a hash of total points, a hash of tie break scores and a tie break
419
+ # order (+1 for ascending, -1 for descending) depending on the tie break method.
420
+ def rerank_data(tie_break_method)
421
+ points = Hash.new
422
+ @player.values.each do |p|
423
+ points[p.num] = p.points
424
+ end
425
+ tie_break_scores = Hash.new
426
+ tie_break_method = tie_break_method.to_sym if tie_break_method.class == String
427
+ @player.values.each do |p|
428
+ if tie_break_method == :name || tie_break_method == true
429
+ tie_break_scores[p.num] = p.name
430
+ else
431
+ tie_break_scores[p.num] = 0.0
432
+ if (tie_break_method == :sum_of_scores)
433
+ p.results.each do |r|
434
+ tie_break_scores[p.num]+= points[r.opponent] if r.opponent
435
+ end
436
+ else
437
+ raise "invalid tie break method '#{tie_break_method}'"
438
+ end
439
+ end
440
+ end
441
+ tie_break_order = tie_break_method == :name ? -1 : 1
442
+ [points, tie_break_scores, tie_break_order]
443
+ end
404
444
  end
405
445
  end
@@ -245,13 +245,13 @@ module ICU
245
245
 
246
246
  it "should renumber successfully if the map has the relevant player numbers" do
247
247
  map = { 10 => 1, 20 => 2, 30 => 3 }
248
- @p.renumber!(map).num.should == 1
248
+ @p.renumber(map).num.should == 1
249
249
  @p.results.map{ |r| r.opponent }.sort.join('').should == '23'
250
250
  end
251
251
 
252
252
  it "should raise exception if a player number is not in the map" do
253
- lambda { @p.renumber!({ 100 => 1, 20 => 2, 30 => 3 }) }.should raise_error(/player.*10.*not found/)
254
- lambda { @p.renumber!({ 10 => 1, 200 => 2, 30 => 3 }) }.should raise_error(/opponent.*20.*not found/)
253
+ lambda { @p.renumber({ 100 => 1, 20 => 2, 30 => 3 }) }.should raise_error(/player.*10.*not found/)
254
+ lambda { @p.renumber({ 10 => 1, 200 => 2, 30 => 3 }) }.should raise_error(/opponent.*20.*not found/)
255
255
  end
256
256
  end
257
257
 
@@ -146,14 +146,14 @@ module ICU
146
146
 
147
147
  it "should renumber successfully if the map has the relevant player numbers" do
148
148
  map = { 4 => 1, 3 => 2 }
149
- @r1.renumber!(map).player.should == 1
150
- @r2.renumber!(map).player.should == 2
149
+ @r1.renumber(map).player.should == 1
150
+ @r2.renumber(map).player.should == 2
151
151
  @r1.opponent.should be_nil
152
152
  @r2.opponent.should == 1
153
153
  end
154
154
 
155
155
  it "should raise exception if a player number is not in the map" do
156
- lambda { @r1.renumber!({ 5 => 1, 3 => 2 }) }.should raise_error(/player.*4.*not found/)
156
+ lambda { @r1.renumber({ 5 => 1, 3 => 2 }) }.should raise_error(/player.*4.*not found/)
157
157
  end
158
158
  end
159
159
 
@@ -48,11 +48,12 @@ module ICU
48
48
 
49
49
  it "should renumber successfully if the map has the relevant player numbers" do
50
50
  map = { 0 => 1, 3 => 2, -7 => 3 }
51
- @t.renumber!(map).members.sort.join('').should == '123'
51
+ @t.renumber(map)
52
+ @t.members.sort.join('').should == '123'
52
53
  end
53
54
 
54
55
  it "should raise exception if a player is missing from the renumber map" do
55
- lambda { @t.renumber!({ 5 => 1, 3 => 2 }) }.should raise_error(/player.*not found/)
56
+ lambda { @t.renumber({ 5 => 1, 3 => 2 }) }.should raise_error(/player.*not found/)
56
57
  end
57
58
  end
58
59
  end
@@ -225,7 +225,7 @@ REORDERED
225
225
  end
226
226
 
227
227
  it "should serialise correctly after renumbering by rank" do
228
- @t.renumber!
228
+ @t.renumber
229
229
  @p.serialize(@t).should == @reordered
230
230
  end
231
231
  end
@@ -454,21 +454,88 @@ module ICU
454
454
  end
455
455
 
456
456
  it "should be renumberable by rank" do
457
- @t.renumber!.invalid.should be_false
457
+ @t.renumber
458
+ @t.invalid.should be_false
458
459
  @t.players.map{ |p| p.num }.join('|').should == '1|2|3'
459
460
  @t.players.map{ |p| p.first_name }.join('|').should == 'Mark|Gary|Bobby'
460
461
  end
461
462
 
462
463
  it "should be ranked after renumbering by rank" do
463
- @t.renumber!.invalid.should be_false
464
+ @t.renumber
465
+ @t.invalid.should be_false
464
466
  @t.players.map{ |p| p.rank }.join('|').should == '1|2|3'
465
467
  end
466
468
 
467
469
  it "should be renumberable by name" do
468
- @t.renumber!(:name).invalid.should be_false
470
+ @t.renumber(:name)
471
+ @t.invalid.should be_false
469
472
  @t.players.map{ |p| p.num }.join('|').should == '1|2|3'
470
473
  @t.players.map{ |p| p.last_name }.join('|').should == 'Fischer|Kasparov|Orr'
471
474
  end
472
475
  end
476
+
477
+ context "reranking" do
478
+ before(:each) do
479
+ @t = Tournament.new('Edinburgh Masters', '2009-11-09')
480
+ @t.add_player(@boby = Player.new('Bobby', 'Fischer', 1))
481
+ @t.add_player(@gary = Player.new('Gary', 'Kasparov', 2))
482
+ @t.add_player(@boby = Player.new('Micky', 'Mouse', 3))
483
+ @t.add_player(@boby = Player.new('Minnie', 'Mouse', 4))
484
+ @t.add_player(@boby = Player.new('Gearoidn', 'Ui Laighleis', 5))
485
+ @t.add_player(@mark = Player.new('Mark', 'Orr', 6))
486
+ @t.add_result(Result.new(1, 1, 'W', :opponent => 6))
487
+ @t.add_result(Result.new(2, 1, 'W', :opponent => 3))
488
+ @t.add_result(Result.new(3, 1, 'W', :opponent => 5))
489
+ @t.add_result(Result.new(1, 2, 'W', :opponent => 5))
490
+ @t.add_result(Result.new(2, 2, 'W', :opponent => 4))
491
+ @t.add_result(Result.new(3, 2, 'W', :opponent => 3))
492
+ @t.add_result(Result.new(1, 3, 'W', :opponent => 4))
493
+ @t.add_result(Result.new(3, 4, 'W', :opponent => 6))
494
+ @t.add_result(Result.new(2, 5, 'W', :opponent => 6))
495
+ end
496
+
497
+ it "should initially be valid but unranked" do
498
+ @t.invalid.should be_false
499
+ @t.player(1).rank.should be_nil
500
+ end
501
+
502
+ it "should use names for tie breaking by default" do
503
+ @t.rerank
504
+ @t.player(1).rank.should == 1 # 3/"Fischer"
505
+ @t.player(2).rank.should == 2 # 3/"Kasparov"
506
+ @t.player(3).rank.should == 3 # 1/"Mouse,Mickey"
507
+ @t.player(4).rank.should == 4 # 1/"Mouse,Minnie"
508
+ @t.player(5).rank.should == 5 # 1/"Ui"
509
+ @t.player(6).rank.should == 6 # 0
510
+ end
511
+
512
+ it "should be configurable to use sum-of-opponents score" do
513
+ @t.rerank('sum_of_scores')
514
+ @t.player(2).rank.should == 1 # 3/3
515
+ @t.player(1).rank.should == 2 # 3/2
516
+ @t.player(3).rank.should == 3 # 1/7
517
+ @t.player(5).rank.should == 4 # 1/6
518
+ @t.player(4).rank.should == 5 # 1/4
519
+ @t.player(6).rank.should == 6 # 0/5
520
+ end
521
+
522
+ it "should throw exception on invalid tie break method" do
523
+ lambda { @t.rerank(:no_such_tie_break_method) }.should raise_error(/invalid.*method/)
524
+ end
525
+
526
+ it "should throw exception on invalid tie break method via validation" do
527
+ lambda { @t.validate!(:rerank => :stupid_tie_break_method) }.should raise_error(/invalid.*method/)
528
+ end
529
+
530
+ it "should be possible as a side effect of validation" do
531
+ @t.invalid(:rerank => :sum_of_scores).should be_false
532
+ @t.player(2).rank.should == 1 # 3/3
533
+ @t.player(1).rank.should == 2 # 3/2
534
+ @t.player(3).rank.should == 3 # 1/7
535
+ @t.player(5).rank.should == 4 # 1/6
536
+ @t.player(4).rank.should == 5 # 1/4
537
+ @t.player(6).rank.should == 6 # 0/5
538
+ end
539
+ end
473
540
  end
474
541
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sanichi-chess_icu
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.2
4
+ version: 0.5.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mark Orr
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-09-03 00:00:00 -07:00
12
+ date: 2009-09-12 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -59,6 +59,7 @@ files:
59
59
  - spec/util_spec.rb
60
60
  has_rdoc: true
61
61
  homepage: http://github.com/sanichi/chess_icu
62
+ licenses:
62
63
  post_install_message:
63
64
  rdoc_options:
64
65
  - --charset=UTF-8
@@ -79,7 +80,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
79
80
  requirements: []
80
81
 
81
82
  rubyforge_project:
82
- rubygems_version: 1.2.0
83
+ rubygems_version: 1.3.5
83
84
  signing_key:
84
85
  specification_version: 2
85
86
  summary: For parsing files of chess tournament data into ruby classes.