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.
- data/VERSION.yml +1 -1
- data/lib/chess_icu/player.rb +2 -2
- data/lib/chess_icu/result.rb +1 -1
- data/lib/chess_icu/team.rb +1 -1
- data/lib/chess_icu/tournament.rb +64 -24
- data/spec/player_spec.rb +3 -3
- data/spec/result_spec.rb +3 -3
- data/spec/team_spec.rb +3 -2
- data/spec/tournament_krause_spec.rb +1 -1
- data/spec/tournament_spec.rb +70 -3
- metadata +4 -3
data/VERSION.yml
CHANGED
data/lib/chess_icu/player.rb
CHANGED
@@ -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
|
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
|
216
|
+
@results.each{ |r| r.renumber(map) }
|
217
217
|
self
|
218
218
|
end
|
219
219
|
|
data/lib/chess_icu/result.rb
CHANGED
@@ -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
|
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
|
data/lib/chess_icu/team.rb
CHANGED
@@ -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
|
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]
|
data/lib/chess_icu/tournament.rb
CHANGED
@@ -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
|
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
|
65
|
-
can be
|
66
|
-
|
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
|
-
|
69
|
-
|
70
|
-
|
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
|
260
|
-
def rerank
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
end.each_with_index do |
|
267
|
-
|
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.
|
272
|
-
def renumber
|
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
|
299
|
+
@teams.each{ |t| t.renumber(map) }
|
285
300
|
@player = @player.values.inject({}) do |hash, player|
|
286
|
-
player.renumber
|
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
|
data/spec/player_spec.rb
CHANGED
@@ -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
|
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
|
254
|
-
lambda { @p.renumber
|
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
|
|
data/spec/result_spec.rb
CHANGED
@@ -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
|
150
|
-
@r2.renumber
|
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
|
156
|
+
lambda { @r1.renumber({ 5 => 1, 3 => 2 }) }.should raise_error(/player.*4.*not found/)
|
157
157
|
end
|
158
158
|
end
|
159
159
|
|
data/spec/team_spec.rb
CHANGED
@@ -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
|
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
|
56
|
+
lambda { @t.renumber({ 5 => 1, 3 => 2 }) }.should raise_error(/player.*not found/)
|
56
57
|
end
|
57
58
|
end
|
58
59
|
end
|
data/spec/tournament_spec.rb
CHANGED
@@ -454,21 +454,88 @@ module ICU
|
|
454
454
|
end
|
455
455
|
|
456
456
|
it "should be renumberable by rank" do
|
457
|
-
@t.renumber
|
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
|
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
|
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.
|
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-
|
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.
|
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.
|