sanichi-chess_icu 0.2.7 → 0.2.8
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.
- data/VERSION.yml +1 -1
- data/lib/player.rb +19 -9
- data/lib/tournament.rb +66 -4
- data/lib/tournament_fcsv.rb +39 -0
- data/spec/player_spec.rb +16 -0
- data/spec/tournament_fcsv_spec.rb +31 -0
- data/spec/tournament_spec.rb +124 -0
- metadata +1 -1
data/VERSION.yml
CHANGED
data/lib/player.rb
CHANGED
@@ -47,14 +47,15 @@ The player number is irrelevant.
|
|
47
47
|
john1 == john2 # => true (federations don't disagree because one is unset)
|
48
48
|
john2 == john3 # => false (federations disagree)
|
49
49
|
|
50
|
-
If, in addition,
|
50
|
+
If, in addition, _rating_, _dob_, _gender_ and _id_ do not disagree then two players are equal
|
51
|
+
according to the stricter criteria of _eql?_.
|
51
52
|
|
52
53
|
mark1 = ICU::Player.new('Mark', 'Orr', 31, :fed = 'IRL', :rating => 2100)
|
53
54
|
mark2 = ICU::Player.new('Mark', 'Orr', 33, :fed = 'IRL', :rating => 2100, :title => 'IM')
|
54
55
|
mark3 = ICU::Player.new('Mark', 'Orr', 37, :fed = 'IRL', :rating => 2200, :title => 'IM')
|
55
56
|
|
56
57
|
mark1.eql?(mark2) # => true (ratings agree and titles don't disagree)
|
57
|
-
mark2.eql?(mark3) # => false (the ratings are
|
58
|
+
mark2.eql?(mark3) # => false (the ratings are not the same)
|
58
59
|
|
59
60
|
The presence of two players in the same tournament that are equal according to _==_ but unequal
|
60
61
|
according to _eql?__ is likely to indicate a data entry error.
|
@@ -63,7 +64,7 @@ If two instances represent the same player and are equal according to _==_ then
|
|
63
64
|
_title_ and _fed_ attributes of the two can be merged. For example:
|
64
65
|
|
65
66
|
fox1 = ICU::Player.new('Tony', 'Fox', 12, :id => 456)
|
66
|
-
fox2 = ICU::Player.new('Tony', 'Fox', 21, :rating => 2100, :fed => 'IRL')
|
67
|
+
fox2 = ICU::Player.new('Tony', 'Fox', 21, :rating => 2100, :fed => 'IRL', :gender => 'M')
|
67
68
|
fox1.merge(fox2)
|
68
69
|
|
69
70
|
Any attributes present in the second player but not in the first are copied to the first.
|
@@ -71,11 +72,12 @@ All other attributes are unaffected.
|
|
71
72
|
|
72
73
|
fox1.rating # => 2100
|
73
74
|
fox1.fed # => 'IRL'
|
75
|
+
fox1.gender # => 'M'
|
74
76
|
|
75
77
|
=end
|
76
78
|
|
77
79
|
class Player
|
78
|
-
attr_accessor :first_name, :last_name, :num, :id, :fed, :title, :rating, :rank, :dob
|
80
|
+
attr_accessor :first_name, :last_name, :num, :id, :fed, :title, :rating, :rank, :dob, :gender
|
79
81
|
attr_reader :results
|
80
82
|
|
81
83
|
# Constructor. Must supply both names and a unique number for the tournament.
|
@@ -83,7 +85,7 @@ All other attributes are unaffected.
|
|
83
85
|
self.first_name = first_name
|
84
86
|
self.last_name = last_name
|
85
87
|
self.num = num
|
86
|
-
[:id, :fed, :title, :rating, :rank, :dob].each do |atr|
|
88
|
+
[:id, :fed, :title, :rating, :rank, :dob, :gender].each do |atr|
|
87
89
|
self.send("#{atr}=", opt[atr]) unless opt[atr].nil?
|
88
90
|
end
|
89
91
|
@results = []
|
@@ -174,7 +176,15 @@ All other attributes are unaffected.
|
|
174
176
|
raise "invalid DOB (#{dob})" if @dob.nil? && dob.length > 0
|
175
177
|
end
|
176
178
|
|
177
|
-
#
|
179
|
+
# Gender. Is either unknown (nil) or one of _M_ or _F_.
|
180
|
+
def gender=(gender)
|
181
|
+
@gender = gender.to_s.strip[0,1].upcase
|
182
|
+
@gender = nil if @gender == ''
|
183
|
+
@gender = 'F' if @gender == 'W'
|
184
|
+
raise "invalid gender (#{gender})" unless @gender.nil? || @gender.match(/^[MF]$/)
|
185
|
+
end
|
186
|
+
|
187
|
+
# Add a result. Use the same name method in ICU::Tournament instead.
|
178
188
|
def add_result(result)
|
179
189
|
raise "invalid result" unless result.class == ICU::Result
|
180
190
|
raise "player number (#{@num}) is not matched to result player number (#{result.player})" unless @num == result.player
|
@@ -202,11 +212,11 @@ All other attributes are unaffected.
|
|
202
212
|
true
|
203
213
|
end
|
204
214
|
|
205
|
-
# Strict equality test. Passes if the playes are loosly equal and also if their ID, rating and title are not different.
|
215
|
+
# Strict equality test. Passes if the playes are loosly equal and also if their ID, rating, gender and title are not different.
|
206
216
|
def eql?(other)
|
207
217
|
return true if equal?(other)
|
208
218
|
return false unless self == other
|
209
|
-
[:id, :rating, :title].each do |m|
|
219
|
+
[:id, :rating, :title, :gender].each do |m|
|
210
220
|
return false if self.send(m) && other.send(m) && self.send(m) != other.send(m)
|
211
221
|
end
|
212
222
|
true
|
@@ -215,7 +225,7 @@ All other attributes are unaffected.
|
|
215
225
|
# Merge in some of the details of another player.
|
216
226
|
def merge(other)
|
217
227
|
raise "cannot merge two players that are not equal" unless self == other
|
218
|
-
[:id, :rating, :title, :fed].each do |m|
|
228
|
+
[:id, :rating, :title, :fed, :gender].each do |m|
|
219
229
|
self.send("#{m}=", other.send(m)) unless self.send(m)
|
220
230
|
end
|
221
231
|
end
|
data/lib/tournament.rb
CHANGED
@@ -30,19 +30,19 @@ Would result in the following output.
|
|
30
30
|
0.5 Mark Orr
|
31
31
|
|
32
32
|
Note that the players should be added first because the _add_result_ method will
|
33
|
-
raise an exception if the players it references through their numbers
|
34
|
-
and 30 in this example) have not already been added to the tournament.
|
33
|
+
raise an exception if the players it references through their tournament numbers
|
34
|
+
(10, 20 and 30 in this example) have not already been added to the tournament.
|
35
35
|
|
36
36
|
=end
|
37
37
|
|
38
38
|
class Tournament
|
39
|
-
attr_reader :name, :start, :rounds, :site
|
39
|
+
attr_reader :name, :start, :finish, :rounds, :site, :city, :fed, :type, :arbiter, :deputy, :time_control
|
40
40
|
|
41
41
|
# Constructor. Name and start date must be supplied. Other attributes are optional.
|
42
42
|
def initialize(name, start, opt={})
|
43
43
|
self.name = name
|
44
44
|
self.start = start
|
45
|
-
[:rounds, :site].each { |a| self.send("#{a}=", opt[a]) unless opt[a].nil? }
|
45
|
+
[:finish, :rounds, :site, :city, :fed, :type, :arbiter, :deputy, :time_control].each { |a| self.send("#{a}=", opt[a]) unless opt[a].nil? }
|
46
46
|
@player = {}
|
47
47
|
end
|
48
48
|
|
@@ -52,6 +52,29 @@ and 30 in this example) have not already been added to the tournament.
|
|
52
52
|
@name = name.to_s.strip
|
53
53
|
end
|
54
54
|
|
55
|
+
# Set the tournament city. Can be _nil.
|
56
|
+
def city=(city)
|
57
|
+
city = city.to_s.strip
|
58
|
+
if city == ''
|
59
|
+
@city = nil
|
60
|
+
else
|
61
|
+
raise "invalid tournament city (#{city})" unless city.match(/[a-z]/i)
|
62
|
+
@city = city
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Set the tournament federation. Can be _nil.
|
67
|
+
def fed=(fed)
|
68
|
+
fed = fed.to_s.strip
|
69
|
+
if fed == ''
|
70
|
+
@fed = nil
|
71
|
+
else
|
72
|
+
raise "invalid tournament federation (#{fed})" unless fed.match(/^[-a-z ]+/i) && fed.length >= 3
|
73
|
+
fed.upcase! if fed.length == 3
|
74
|
+
@fed = fed
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
55
78
|
# Set a start date in yyyy-mm-dd format.
|
56
79
|
def start=(start)
|
57
80
|
start = start.to_s.strip
|
@@ -59,6 +82,17 @@ and 30 in this example) have not already been added to the tournament.
|
|
59
82
|
raise "invalid start date (#{start})" unless @start
|
60
83
|
end
|
61
84
|
|
85
|
+
# Set an end date in yyyy-mm-dd format.
|
86
|
+
def finish=(finish)
|
87
|
+
finish = finish.to_s.strip
|
88
|
+
if finish == ''
|
89
|
+
@finish = nil
|
90
|
+
else
|
91
|
+
@finish = Util.parsedate(finish)
|
92
|
+
raise "invalid finish date (#{finish})" unless @finish
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
62
96
|
# Set the number of rounds. Is either unknown (_nil_) or a positive integer.
|
63
97
|
def rounds=(rounds)
|
64
98
|
@rounds = case rounds
|
@@ -78,6 +112,34 @@ and 30 in this example) have not already been added to the tournament.
|
|
78
112
|
raise "invalid site (#{site})" unless @site.nil? || @site.match(/^https?:\/\/[-\w]+(\.[-\w]+)+(\/[^\s]*)?$/i)
|
79
113
|
end
|
80
114
|
|
115
|
+
# Set the tournament type. Should be either unknown (_nil_) or contain some letters.
|
116
|
+
def type=(type)
|
117
|
+
@type = type.to_s.strip
|
118
|
+
@type = nil if @type == ''
|
119
|
+
raise "invalid tournament type (#{type})" unless @type.nil? || @type.match(/[a-z]/i)
|
120
|
+
end
|
121
|
+
|
122
|
+
# Set the tournament arbiter. Should be either unknown (_nil_) or contain some letters.
|
123
|
+
def arbiter=(arbiter)
|
124
|
+
@arbiter = arbiter.to_s.strip
|
125
|
+
@arbiter = nil if @arbiter == ''
|
126
|
+
raise "invalid tournament arbiter (#{arbiter})" unless @arbiter.nil? || @arbiter.match(/[a-z]/i)
|
127
|
+
end
|
128
|
+
|
129
|
+
# Set the tournament deputy. Should be either unknown (_nil_) or contain some letters.
|
130
|
+
def deputy=(deputy)
|
131
|
+
@deputy = deputy.to_s.strip
|
132
|
+
@deputy = nil if @deputy == ''
|
133
|
+
raise "invalid tournament deputy (#{deputy})" unless @deputy.nil? || @deputy.match(/[a-z]/i)
|
134
|
+
end
|
135
|
+
|
136
|
+
# Set the time control. Should be either unknown (_nil_) or contain some numbers.
|
137
|
+
def time_control=(time_control)
|
138
|
+
@time_control = time_control.to_s.strip
|
139
|
+
@time_control = nil if @time_control == ''
|
140
|
+
raise "invalid tournament time control (#{time_control})" unless @time_control.nil? || @time_control.match(/[1-9]\d/)
|
141
|
+
end
|
142
|
+
|
81
143
|
# Add a new player to the tournament. Must have a unique player number.
|
82
144
|
def add_player(player)
|
83
145
|
raise "invalid player" unless player.class == ICU::Player
|
data/lib/tournament_fcsv.rb
CHANGED
@@ -73,6 +73,10 @@ However, none of the opponents' results are rateable. For example:
|
|
73
73
|
opponent = tournament.players(2)
|
74
74
|
opponent.name # => "Taylor, Peter P."
|
75
75
|
opponent.results[0].rateable # => false
|
76
|
+
|
77
|
+
A tournament can be serialized back to CSV format (the reverse of parsing) with the serialise method.
|
78
|
+
|
79
|
+
csv = parser.serialize(tournament)
|
76
80
|
|
77
81
|
=end
|
78
82
|
|
@@ -134,6 +138,41 @@ However, none of the opponents' results are rateable. For example:
|
|
134
138
|
|
135
139
|
@tournament
|
136
140
|
end
|
141
|
+
|
142
|
+
# Serialise a tournament back into CSV format.
|
143
|
+
def serialise(t)
|
144
|
+
return nil unless t.class == ICU::Tournament;
|
145
|
+
FasterCSV.generate do |csv|
|
146
|
+
csv << ["Event", t.name]
|
147
|
+
csv << ["Start", t.start]
|
148
|
+
csv << ["Rounds", t.rounds]
|
149
|
+
csv << ["Website", t.site]
|
150
|
+
t.players.each do |p|
|
151
|
+
next unless p.id
|
152
|
+
csv << []
|
153
|
+
csv << ["Player", p.id, p.last_name, p.first_name]
|
154
|
+
(1..t.rounds).each do |n|
|
155
|
+
data = []
|
156
|
+
data << n
|
157
|
+
r = p.find_result(n)
|
158
|
+
data << case r.score; when 'W' then '1'; when 'L' then '0'; else '='; end
|
159
|
+
if r.rateable
|
160
|
+
data << r.colour
|
161
|
+
o = t.player(r.opponent)
|
162
|
+
data << o.last_name
|
163
|
+
data << o.first_name
|
164
|
+
data << o.rating
|
165
|
+
data << o.title
|
166
|
+
data << o.fed
|
167
|
+
else
|
168
|
+
data << '-'
|
169
|
+
end
|
170
|
+
csv << data
|
171
|
+
end
|
172
|
+
csv << ["Total", p.points]
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
137
176
|
|
138
177
|
private
|
139
178
|
|
data/spec/player_spec.rb
CHANGED
@@ -144,6 +144,22 @@ module ICU
|
|
144
144
|
end
|
145
145
|
end
|
146
146
|
|
147
|
+
context "gender" do
|
148
|
+
it "defaults to nil" do
|
149
|
+
Player.new('Mark', 'Orr', 3).gender.should be_nil
|
150
|
+
Player.new('Mark', 'Orr', 3, :gender => ' ').gender.should be_nil
|
151
|
+
end
|
152
|
+
|
153
|
+
it "should be either M or F" do
|
154
|
+
Player.new('Mark', 'Orr', 3, :gender => 'male').gender.should == 'M'
|
155
|
+
Player.new('April', 'Cronin', 3, :gender => 'woman').gender.should == 'F'
|
156
|
+
end
|
157
|
+
|
158
|
+
it "should raise an exception if the gender is not specified properly" do
|
159
|
+
lambda { Player.new('Mark', 'Orr', 3, :gender => 'X') }.should raise_error(/invalid gender/)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
147
163
|
context "results and points" do
|
148
164
|
it "should initialise to an empty array" do
|
149
165
|
results = Player.new('Mark', 'Orr', 3).results
|
@@ -386,6 +386,37 @@ CSV
|
|
386
386
|
lambda { @f.parse!(csv) }.should raise_error(/line 13.*same name.*conflicting/i)
|
387
387
|
end
|
388
388
|
end
|
389
|
+
|
390
|
+
context "serialisation" do
|
391
|
+
before(:each) do
|
392
|
+
@csv = <<CSV
|
393
|
+
Event,"Edinburgh Masters, 2007"
|
394
|
+
Start,2007-08-09
|
395
|
+
Rounds,2
|
396
|
+
Website,http://www.chesscenter.com/twic/twic.html
|
397
|
+
|
398
|
+
Player,3364,Ui Laighleis,Gearoidin
|
399
|
+
1,0,W,Kasparov,Gary,2800,GM,RUS
|
400
|
+
2,1,B,Cronin,April,2005,,IRL
|
401
|
+
Total,1.0
|
402
|
+
|
403
|
+
Player,1350,Orr,Mark
|
404
|
+
1,=,W,Cronin,April,2005,,IRL
|
405
|
+
2,=,-
|
406
|
+
Total,1.0
|
407
|
+
CSV
|
408
|
+
@f = ForeignCSV.new
|
409
|
+
@t = @f.parse!(@csv)
|
410
|
+
end
|
411
|
+
|
412
|
+
it "should serialize back to the original" do
|
413
|
+
@f.serialise(@t).should == @csv
|
414
|
+
end
|
415
|
+
|
416
|
+
it "should return nil on invalid input" do
|
417
|
+
@f.serialise('Rubbish').should be_nil
|
418
|
+
end
|
419
|
+
end
|
389
420
|
end
|
390
421
|
end
|
391
422
|
end
|
data/spec/tournament_spec.rb
CHANGED
@@ -37,6 +37,61 @@ module ICU
|
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
40
|
+
# Tournament city.
|
41
|
+
context "city" do
|
42
|
+
before(:each) do
|
43
|
+
@t = Tournament.new('Edinburgh Masters', '2009-11-09', :city => 'Edinburgh')
|
44
|
+
end
|
45
|
+
|
46
|
+
it "may be specified in constructor" do
|
47
|
+
@t.city.should == 'Edinburgh'
|
48
|
+
end
|
49
|
+
|
50
|
+
it "can be replaced by accessor" do
|
51
|
+
@t.city = 'Glasgow'
|
52
|
+
@t.city.should == 'Glasgow'
|
53
|
+
end
|
54
|
+
|
55
|
+
it "can be set to nil" do
|
56
|
+
@t.city = ''
|
57
|
+
@t.city.should be_nil
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should not be without letters if set" do
|
61
|
+
lambda { @t.city = '123' }.should raise_error(/invalid.*city/)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Tournament federation.
|
66
|
+
context "federation" do
|
67
|
+
before(:each) do
|
68
|
+
@t = Tournament.new('Edinburgh Masters', '2009-11-09', :fed => 'SCO')
|
69
|
+
end
|
70
|
+
|
71
|
+
it "may be specified in constructor" do
|
72
|
+
@t.fed.should == 'SCO'
|
73
|
+
end
|
74
|
+
|
75
|
+
it "can be replaced by accessor" do
|
76
|
+
@t.fed = 'IRL'
|
77
|
+
@t.fed.should == 'IRL'
|
78
|
+
end
|
79
|
+
|
80
|
+
it "can be set to nil" do
|
81
|
+
@t.fed = ''
|
82
|
+
@t.fed.should be_nil
|
83
|
+
end
|
84
|
+
|
85
|
+
it "three letters will automatically be upcased" do
|
86
|
+
@t.fed = 'rus'
|
87
|
+
@t.fed.should == 'RUS'
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should not be without letters if set" do
|
91
|
+
lambda { @t.fed = '123' }.should raise_error(/invalid.*federation/)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
40
95
|
# Tournament start date.
|
41
96
|
context "start date" do
|
42
97
|
before(:each) do
|
@@ -58,6 +113,32 @@ module ICU
|
|
58
113
|
end
|
59
114
|
end
|
60
115
|
|
116
|
+
# Tournament finish date.
|
117
|
+
context "finish date" do
|
118
|
+
before(:each) do
|
119
|
+
@t = Tournament.new('Edinburgh Masters', '2009-11-09', :finish => '12th November 2009')
|
120
|
+
end
|
121
|
+
|
122
|
+
it "may be specified in constructor" do
|
123
|
+
@t.finish.should == '2009-11-12'
|
124
|
+
end
|
125
|
+
|
126
|
+
it "can be replaced by accessor" do
|
127
|
+
@t.finish = '16th December 2009'
|
128
|
+
@t.finish.should == '2009-12-16'
|
129
|
+
end
|
130
|
+
|
131
|
+
it "can be set to nil" do
|
132
|
+
@t.finish = ''
|
133
|
+
@t.finish.should be_nil
|
134
|
+
end
|
135
|
+
|
136
|
+
it "should be a valid date" do
|
137
|
+
lambda { Tournament.new('Edinburgh Masters', '2009-11-09', :finish => 'next week') }.should raise_error(/invalid.*date/)
|
138
|
+
lambda { @t.finish = 'X' }.should raise_error(/invalid.*date/)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
61
142
|
# Number of rounds.
|
62
143
|
context "rounds" do
|
63
144
|
it "defaults to nil" do
|
@@ -84,6 +165,49 @@ module ICU
|
|
84
165
|
end
|
85
166
|
end
|
86
167
|
|
168
|
+
# Type, arbiter, deputy and time control.
|
169
|
+
context "type, arbiter, deputy and time control" do
|
170
|
+
before(:each) do
|
171
|
+
@t = Tournament.new('Edinburgh Masters', '2009-11-09', :type => 'Swiss', :arbiter => 'Gerry Graham', :deputy => 'Herbert Scarry', :time_control => '120 minutes')
|
172
|
+
end
|
173
|
+
|
174
|
+
it "may be specified in constructor" do
|
175
|
+
@t.type.should == 'Swiss'
|
176
|
+
@t.arbiter.should == 'Gerry Graham'
|
177
|
+
@t.deputy.should == 'Herbert Scarry'
|
178
|
+
@t.time_control.should == '120 minutes'
|
179
|
+
end
|
180
|
+
|
181
|
+
it "can be replaced by accessor" do
|
182
|
+
@t.type = 'all-play-all'
|
183
|
+
@t.type.should == 'all-play-all'
|
184
|
+
@t.arbiter = 'Michael Crowe'
|
185
|
+
@t.arbiter.should == 'Michael Crowe'
|
186
|
+
@t.deputy = 'Mark Orr'
|
187
|
+
@t.deputy.should == 'Mark Orr'
|
188
|
+
@t.time_control = '90 minutes'
|
189
|
+
@t.time_control.should == '90 minutes'
|
190
|
+
end
|
191
|
+
|
192
|
+
it "can be set to nil" do
|
193
|
+
@t.type = ''
|
194
|
+
@t.type.should be_nil
|
195
|
+
@t.arbiter = ''
|
196
|
+
@t.arbiter.should be_nil
|
197
|
+
@t.deputy = ''
|
198
|
+
@t.deputy.should be_nil
|
199
|
+
@t.time_control = ''
|
200
|
+
@t.time_control.should be_nil
|
201
|
+
end
|
202
|
+
|
203
|
+
it "should be valid" do
|
204
|
+
lambda { @t.type = '123' }.should raise_error(/invalid.*type/)
|
205
|
+
lambda { @t.arbiter = '123' }.should raise_error(/invalid.*arbiter/)
|
206
|
+
lambda { @t.deputy = '123' }.should raise_error(/invalid.*deputy/)
|
207
|
+
lambda { @t.time_control = 'abc' }.should raise_error(/invalid.*time control/)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
87
211
|
# Tournament players.
|
88
212
|
context "players" do
|
89
213
|
before(:each) do
|