sanichi-chess_icu 0.2.1 → 0.2.4
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/README.rdoc +7 -15
- data/VERSION.yml +1 -1
- data/lib/result.rb +8 -6
- data/lib/tournament.rb +51 -10
- data/lib/tournament_fcsv.rb +76 -1
- data/spec/tournament_fcsv_spec.rb +54 -0
- data/spec/tournament_spec.rb +1 -1
- metadata +2 -2
data/README.rdoc
CHANGED
|
@@ -13,38 +13,30 @@ For parsing files of chess tournament data into ruby classes.
|
|
|
13
13
|
A tournament (ICU::Tournament) has two or more players (ICU::Player), and each player has one or more results (ICU::Result).
|
|
14
14
|
|
|
15
15
|
Tournament objects are created by parsing files of various formats. An instance of a class that can handle the format
|
|
16
|
-
(see below for the available classes) is first created, and then it's parse or parse
|
|
17
|
-
contents as argument.
|
|
16
|
+
(see below for the available classes) is first created, and then it's <em>parse</em> or <em>parse!</em> method is called
|
|
17
|
+
with the file's contents as the only argument.
|
|
18
18
|
|
|
19
|
+
require 'chess_icu'
|
|
19
20
|
data = open('tournament.csv') { |f| f.read }
|
|
20
21
|
tournament = parser.parse(data)
|
|
21
22
|
|
|
22
|
-
On success, the parse method returns an object of type ICU::Tournament. On error, it returns
|
|
23
|
+
On success, the parse method returns an object of type ICU::Tournament. On error, it returns _nil_ and an error message
|
|
23
24
|
can be retrieved from the parser instance:
|
|
24
25
|
|
|
25
26
|
parser.error # => error message or nil on success
|
|
26
27
|
|
|
27
|
-
Alternatively, if an exception is preferred on error, the parse
|
|
28
|
+
Alternatively, if an exception is preferred on error, the <em>parse!</em> method can be used:
|
|
28
29
|
|
|
29
30
|
tournament = parser.parse!(data)
|
|
30
31
|
|
|
31
32
|
The file formats supported in the current version are:
|
|
32
33
|
|
|
33
|
-
* Foreign CSV
|
|
34
|
+
* Foreign CSV (ICU::Tournament::ForeignCSV) - for players to report their individual results in foreign tournaments
|
|
34
35
|
|
|
35
36
|
|
|
36
|
-
=== Foreign CSV
|
|
37
|
-
|
|
38
|
-
This is a format used by the ICU[http://icu.ie] ({specification}[http://www.icu.ie/articles/display.php?id=172])
|
|
39
|
-
for players to submit their individual results in foreign tournaments for domestic rating.
|
|
40
|
-
It's parsed by instances of ICU::Tournament::ForeignCSV.
|
|
41
|
-
|
|
42
|
-
parser = ICU::Tournament::ForeignCSV.new
|
|
43
|
-
|
|
44
|
-
|
|
45
37
|
== TODO
|
|
46
38
|
|
|
47
|
-
Future versions of this software will be able to parse
|
|
39
|
+
Future versions of this software will be able to parse other types of files such as SwissPerfect and FIDE's Krause format.
|
|
48
40
|
|
|
49
41
|
|
|
50
42
|
== Author
|
data/VERSION.yml
CHANGED
data/lib/result.rb
CHANGED
|
@@ -2,6 +2,8 @@ module ICU
|
|
|
2
2
|
class Result
|
|
3
3
|
attr_reader :round, :player, :score, :colour, :opponent, :rateable
|
|
4
4
|
|
|
5
|
+
# Constructor. Round number, player number and score must be supplied.
|
|
6
|
+
# Optional hash attribute are _opponent_, _colour_ and _rateable_.
|
|
5
7
|
def initialize(round, player, score, opt={})
|
|
6
8
|
self.round = round
|
|
7
9
|
self.player = player
|
|
@@ -25,7 +27,7 @@ module ICU
|
|
|
25
27
|
raise "invalid player number (#{player})" if @player == 0 && !player.to_s.match(/\d/)
|
|
26
28
|
end
|
|
27
29
|
|
|
28
|
-
# Score for the game, even if a default. One of 'W', 'L' or 'D'
|
|
30
|
+
# Score for the game, even if a default. One of 'W', 'L' or 'D'. Reasonable inputs like 1, 0, =, ½, etc will be converted.
|
|
29
31
|
def score=(score)
|
|
30
32
|
@score = case score.to_s.strip
|
|
31
33
|
when /^(1\.0|1|\+|W|w)$/ then 'W'
|
|
@@ -35,7 +37,7 @@ module ICU
|
|
|
35
37
|
end
|
|
36
38
|
end
|
|
37
39
|
|
|
38
|
-
#
|
|
40
|
+
# Return the score as a floating point number.
|
|
39
41
|
def points
|
|
40
42
|
case @score
|
|
41
43
|
when 'W' then 1.0
|
|
@@ -44,7 +46,7 @@ module ICU
|
|
|
44
46
|
end
|
|
45
47
|
end
|
|
46
48
|
|
|
47
|
-
# Colour. Either 'W' or 'B'
|
|
49
|
+
# Colour. Either 'W' (white) or 'B' (black).
|
|
48
50
|
def colour=(colour)
|
|
49
51
|
@colour = case colour.to_s
|
|
50
52
|
when '' then nil
|
|
@@ -54,7 +56,7 @@ module ICU
|
|
|
54
56
|
end
|
|
55
57
|
end
|
|
56
58
|
|
|
57
|
-
# Opponent player number. Either absent (
|
|
59
|
+
# Opponent player number. Either absent (_nil_) or any integer except the player number.
|
|
58
60
|
def opponent=(opponent)
|
|
59
61
|
@opponent = case opponent
|
|
60
62
|
when nil then nil
|
|
@@ -67,7 +69,7 @@ module ICU
|
|
|
67
69
|
self.rateable = true if @opponent
|
|
68
70
|
end
|
|
69
71
|
|
|
70
|
-
# Rateable flag. If false,
|
|
72
|
+
# Rateable flag. If false, result is not rateable. Can only be true if there is an opponent.
|
|
71
73
|
def rateable=(rateable)
|
|
72
74
|
if opponent.nil?
|
|
73
75
|
@rateable = false
|
|
@@ -91,7 +93,7 @@ module ICU
|
|
|
91
93
|
r
|
|
92
94
|
end
|
|
93
95
|
|
|
94
|
-
# Loose equality.
|
|
96
|
+
# Loose equality. True if the round, player and opponent numbers, colour and score all match.
|
|
95
97
|
def ==(other)
|
|
96
98
|
return unless other.is_a? Result
|
|
97
99
|
[:round, :player, :opponent, :colour, :score].each do |m|
|
data/lib/tournament.rb
CHANGED
|
@@ -1,8 +1,44 @@
|
|
|
1
1
|
module ICU
|
|
2
|
+
|
|
3
|
+
=begin rdoc
|
|
4
|
+
|
|
5
|
+
== Generic Tournament
|
|
6
|
+
|
|
7
|
+
Normally a tournament object is created by parsing a data file (e.g. with ICU::Tournament::ForeignCSV).
|
|
8
|
+
However, it is also possible to build a tournament by first creating a bare tournament instance and then
|
|
9
|
+
firstly adding all the players and then adding all the results.
|
|
10
|
+
|
|
11
|
+
require 'rubygems'
|
|
12
|
+
require 'chess_icu'
|
|
13
|
+
|
|
14
|
+
t = ICU::Tournament.new('Bangor Masters', '2009-11-09')
|
|
15
|
+
|
|
16
|
+
t.add_player(ICU::Player.new('Bobby', 'Fischer', 10))
|
|
17
|
+
t.add_player(ICU::Player.new('Garry', 'Kasparov', 20))
|
|
18
|
+
t.add_player(ICU::Player.new('Mark', 'Orr', 30))
|
|
19
|
+
|
|
20
|
+
t.add_result(ICU::Result.new(1, 10, 'D', :opponent => 30, :colour => 'W'))
|
|
21
|
+
t.add_result(ICU::Result.new(2, 20, 'W', :opponent => 30, :colour => 'B'))
|
|
22
|
+
t.add_result(ICU::Result.new(3, 20, 'L', :opponent => 10, :colour => 'W'))
|
|
23
|
+
|
|
24
|
+
[10, 20, 30].each { |n| puts "#{t.player(n).points} #{t.player(n).name}" }
|
|
25
|
+
|
|
26
|
+
Would result in the following output.
|
|
27
|
+
|
|
28
|
+
1.5 Bobby Fischer
|
|
29
|
+
1.0 Gary Kasparov
|
|
30
|
+
0.5 Mark Orr
|
|
31
|
+
|
|
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 (10, 20
|
|
34
|
+
and 30 in this example) have not already been added to the tournament.
|
|
35
|
+
|
|
36
|
+
=end
|
|
37
|
+
|
|
2
38
|
class Tournament
|
|
3
39
|
attr_reader :name, :start, :rounds, :site
|
|
4
40
|
|
|
5
|
-
# Constructor.
|
|
41
|
+
# Constructor. Name and start date must be supplied. Other attributes are optional.
|
|
6
42
|
def initialize(name, start, opt={})
|
|
7
43
|
self.name = name
|
|
8
44
|
self.start = start
|
|
@@ -10,20 +46,20 @@ module ICU
|
|
|
10
46
|
@player = {}
|
|
11
47
|
end
|
|
12
48
|
|
|
13
|
-
#
|
|
49
|
+
# Set the tournament name.
|
|
14
50
|
def name=(name)
|
|
15
51
|
raise "invalid tournament name (#{name})" unless name.to_s.match(/[a-z]/i)
|
|
16
52
|
@name = name.to_s.strip
|
|
17
53
|
end
|
|
18
54
|
|
|
19
|
-
#
|
|
55
|
+
# Set a start date in yyyy-mm-dd format.
|
|
20
56
|
def start=(start)
|
|
21
57
|
start = start.to_s.strip
|
|
22
58
|
@start = Util.parsedate(start)
|
|
23
59
|
raise "invalid start date (#{start})" unless @start
|
|
24
60
|
end
|
|
25
61
|
|
|
26
|
-
#
|
|
62
|
+
# Set the number of rounds. Is either unknown (_nil_) or a positive integer.
|
|
27
63
|
def rounds=(rounds)
|
|
28
64
|
@rounds = case rounds
|
|
29
65
|
when nil then nil
|
|
@@ -34,7 +70,7 @@ module ICU
|
|
|
34
70
|
raise "invalid number of rounds (#{rounds})" unless @rounds.nil? || @rounds > 0
|
|
35
71
|
end
|
|
36
72
|
|
|
37
|
-
#
|
|
73
|
+
# Set the tournament web site. Should be either unknown (_nil_) or a reasonably valid looking URL.
|
|
38
74
|
def site=(site)
|
|
39
75
|
@site = site.to_s.strip
|
|
40
76
|
@site = nil if @site == ''
|
|
@@ -42,7 +78,7 @@ module ICU
|
|
|
42
78
|
raise "invalid site (#{site})" unless @site.nil? || @site.match(/^https?:\/\/[-\w]+(\.[-\w]+)+(\/[^\s]*)?$/i)
|
|
43
79
|
end
|
|
44
80
|
|
|
45
|
-
# Add
|
|
81
|
+
# Add a new player to the tournament. Must have a unique player number.
|
|
46
82
|
def add_player(player)
|
|
47
83
|
raise "invalid player" unless player.class == ICU::Player
|
|
48
84
|
raise "player number (#{player.num}) should be unique" if @player[player.num]
|
|
@@ -54,17 +90,22 @@ module ICU
|
|
|
54
90
|
@player[num]
|
|
55
91
|
end
|
|
56
92
|
|
|
57
|
-
# Return an array of all
|
|
93
|
+
# Return an array of all players in order of their player numbers.
|
|
58
94
|
def players
|
|
59
|
-
@player.values
|
|
95
|
+
@player.values.sort_by{ |p| p.num }
|
|
60
96
|
end
|
|
61
97
|
|
|
62
|
-
# Lookup a player in the tournament.
|
|
98
|
+
# Lookup a player in the tournament by player number, returning _nil_ if the player number does not exist.
|
|
63
99
|
def find_player(player)
|
|
64
100
|
players.find { |p| p == player }
|
|
65
101
|
end
|
|
66
102
|
|
|
67
|
-
# Add
|
|
103
|
+
# Add a result to a tournament. An exception is raised if the players referenced in the result (by number)
|
|
104
|
+
# do not exist in the tournament. The result, which remember is from the perspective of one of the players,
|
|
105
|
+
# is added to that player's results. Additionally, the reverse of the result is automatically added to the player's
|
|
106
|
+
# opponent, unless the opponent does not exist (e.g. byes, walkovers). By default, if the result is rateable
|
|
107
|
+
# then the opponent's result will also be rateable. To make the opponent's result unrateable, set the optional
|
|
108
|
+
# second parameter to false.
|
|
68
109
|
def add_result(result, reverse_rateable=true)
|
|
69
110
|
raise "invalid result" unless result.class == ICU::Result
|
|
70
111
|
raise "result round number (#{result.round}) inconsistent with number of tournament rounds" if @rounds && result.round > @rounds
|
data/lib/tournament_fcsv.rb
CHANGED
|
@@ -2,10 +2,85 @@ require 'fastercsv'
|
|
|
2
2
|
|
|
3
3
|
module ICU
|
|
4
4
|
class Tournament
|
|
5
|
+
|
|
6
|
+
=begin rdoc
|
|
7
|
+
|
|
8
|
+
== Foreign CSV
|
|
9
|
+
|
|
10
|
+
This is a format ({specification}[http://www.icu.ie/articles/display.php?id=172]) used by the ICU[http://icu.ie]
|
|
11
|
+
for players to submit their individual results in foreign tournaments for domestic rating.
|
|
12
|
+
|
|
13
|
+
Suppose, for example, that the following data is the file <em>tournament.csv</em>:
|
|
14
|
+
|
|
15
|
+
Event,"Isle of Man Masters, 2007"
|
|
16
|
+
Start,2007-09-22
|
|
17
|
+
Rounds,9
|
|
18
|
+
Website,http://www.bcmchess.co.uk/monarch2007/
|
|
19
|
+
|
|
20
|
+
Player,456,Fox,Anthony
|
|
21
|
+
1,0,B,Taylor,Peter P.,2209,,ENG
|
|
22
|
+
2,=,W,Nadav,Egozi,2205,,ISR
|
|
23
|
+
3,=,B,Cafolla,Peter,2048,,IRL
|
|
24
|
+
4,1,W,Spanton,Tim R.,1982,,ENG
|
|
25
|
+
5,1,B,Grant,Alan,2223,,SCO
|
|
26
|
+
6,0,-
|
|
27
|
+
7,=,W,Walton,Alan J.,2223,,ENG
|
|
28
|
+
8,0,B,Bannink,Bernard,2271,FM,NED
|
|
29
|
+
9,=,W,Phillips,Roy,2271,,MAU
|
|
30
|
+
Total,4
|
|
31
|
+
|
|
32
|
+
This file can be parsed as follows.
|
|
33
|
+
|
|
34
|
+
data = open('tournament.csv') { |f| f.read }
|
|
35
|
+
parser = ICU::Tournament::ForeignCSV.new
|
|
36
|
+
tournament = parser.parse(data)
|
|
37
|
+
|
|
38
|
+
If the file is correctly specified, the return value from the <em>parse</em> method is an instance of
|
|
39
|
+
ICU::Tournament (rather than <em>nil</em>, which indicates an error). In this example the file is valid, so:
|
|
40
|
+
|
|
41
|
+
tournament.name # => "Isle of Man Masters, 2007"
|
|
42
|
+
tournament.start # => "2007-09-22"
|
|
43
|
+
tournament.rounds # => 9
|
|
44
|
+
tournament.website # => "http://www.bcmchess.co.uk/monarch2007/"
|
|
45
|
+
|
|
46
|
+
The main player (the player whose results are being reported for rating) played 9 rounds
|
|
47
|
+
but only 8 other players (he had a bye in round 6), so the total number of players is 9.
|
|
48
|
+
|
|
49
|
+
tournament.players.size # => 9
|
|
50
|
+
|
|
51
|
+
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.
|
|
52
|
+
|
|
53
|
+
player = tournament.player(1)
|
|
54
|
+
player.name # => "Fox, Anthony"
|
|
55
|
+
|
|
56
|
+
This player has 4 points from 9 rounds but only 8 of his results are are rateable (because of the bye).
|
|
57
|
+
|
|
58
|
+
player.points # => 4.0
|
|
59
|
+
player.results.size # => 9
|
|
60
|
+
player.results.find_all{ |r| r.rateable }.size # => 8
|
|
61
|
+
|
|
62
|
+
The other players all have numbers greater than 1.
|
|
63
|
+
|
|
64
|
+
opponents = tournamnet.players.reject { |o| o.num == 1 }
|
|
65
|
+
|
|
66
|
+
There are 8 opponents of the main player, each with exactly one game.
|
|
67
|
+
|
|
68
|
+
opponents.size # => 8
|
|
69
|
+
opponents.find_all{ |o| o.results.size == 1}.size # => 8
|
|
70
|
+
|
|
71
|
+
However, none of the opponents' results are rateable. For example:
|
|
72
|
+
|
|
73
|
+
opponent = tournament.players(2)
|
|
74
|
+
opponent.name # => "Taylor, Peter P."
|
|
75
|
+
opponent.results[0].rateable # => false
|
|
76
|
+
|
|
77
|
+
=end
|
|
78
|
+
|
|
5
79
|
class ForeignCSV
|
|
6
80
|
attr_reader :error
|
|
7
81
|
|
|
8
|
-
# Parse CSV data returning a Tournament on success or a nil on failure
|
|
82
|
+
# Parse CSV data returning a Tournament on success or a nil on failure.
|
|
83
|
+
# In the case of failure, an error message can be retrived via the <em>error</em> method.
|
|
9
84
|
def parse(csv)
|
|
10
85
|
begin
|
|
11
86
|
parse!(csv)
|
|
@@ -62,6 +62,60 @@ CSV
|
|
|
62
62
|
check_player(4, 'Linda', 'Powell', 1, 0, 0.0, :rating => 1850, :fed => 'WLS')
|
|
63
63
|
end
|
|
64
64
|
end
|
|
65
|
+
|
|
66
|
+
context "the rdoc example tournament" do
|
|
67
|
+
before(:all) do
|
|
68
|
+
@csv = <<CSV
|
|
69
|
+
Event,"Isle of Man Masters, 2007"
|
|
70
|
+
Start,2007-09-22
|
|
71
|
+
Rounds,9
|
|
72
|
+
Website,http://www.bcmchess.co.uk/monarch2007/
|
|
73
|
+
|
|
74
|
+
Player,456,Fox,Anthony
|
|
75
|
+
1,0,B,Taylor,Peter P.,2209,,ENG
|
|
76
|
+
2,=,W,Nadav,Egozi,2205,,ISR
|
|
77
|
+
3,=,B,Cafolla,Peter,2048,,IRL
|
|
78
|
+
4,1,W,Spanton,Tim R.,1982,,ENG
|
|
79
|
+
5,1,B,Grant,Alan,2223,,SCO
|
|
80
|
+
6,0,-
|
|
81
|
+
7,=,W,Walton,Alan J.,2223,,ENG
|
|
82
|
+
8,0,B,Bannink,Bernard,2271,FM,NED
|
|
83
|
+
9,=,W,Phillips,Roy,2271,,MAU
|
|
84
|
+
Total,4
|
|
85
|
+
CSV
|
|
86
|
+
@f = ForeignCSV.new
|
|
87
|
+
@t = @f.parse!(@csv)
|
|
88
|
+
@p = @t.player(1)
|
|
89
|
+
@o = @t.players.reject { |o| o.num == 1 }
|
|
90
|
+
@r = @t.player(2)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
it "should have correct basic details" do
|
|
94
|
+
@t.name.should == 'Isle of Man Masters, 2007'
|
|
95
|
+
@t.start.should == '2007-09-22'
|
|
96
|
+
@t.rounds.should == 9
|
|
97
|
+
@t.site.should == 'http://www.bcmchess.co.uk/monarch2007/'
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
it "should have the right number of players in the right order" do
|
|
101
|
+
@t.players.size.should == 9
|
|
102
|
+
@t.players.inject(''){ |a,o| a << o.num.to_s }.should == '123456789'
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
it "should have the right details for the main player" do
|
|
106
|
+
@p.name.should == "Fox, Anthony"
|
|
107
|
+
@p.results.size == 9
|
|
108
|
+
@p.results.find_all{ |r| r.rateable }.size.should == 8
|
|
109
|
+
@p.points.should == 4.0
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
it "should have the right details for the opponents" do
|
|
113
|
+
@o.size.should == 8
|
|
114
|
+
@o.find_all{ |o| o.results.size == 1}.size.should == 8
|
|
115
|
+
@r.name.should == "Taylor, Peter P."
|
|
116
|
+
@r.results[0].rateable.should be_false
|
|
117
|
+
end
|
|
118
|
+
end
|
|
65
119
|
|
|
66
120
|
context "a tournament with more than one player" do
|
|
67
121
|
before(:all) do
|
data/spec/tournament_spec.rb
CHANGED
|
@@ -7,7 +7,7 @@ module ICU
|
|
|
7
7
|
lambda do
|
|
8
8
|
t = Tournament.new('Bangor Bash', '2009-11-09')
|
|
9
9
|
t.add_player(Player.new('Bobby', 'Fischer', 1))
|
|
10
|
-
t.add_player(Player.new('Garry', '
|
|
10
|
+
t.add_player(Player.new('Garry', 'Kasparov', 2))
|
|
11
11
|
t.add_player(Player.new('Mark', 'Orr', 3))
|
|
12
12
|
t.add_result(Result.new(1, 1, '=', :opponent => 2, :colour => 'W'))
|
|
13
13
|
t.add_result(Result.new(2, 2, 'L', :opponent => 3, :colour => 'W'))
|
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.2.
|
|
4
|
+
version: 0.2.4
|
|
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-04-
|
|
12
|
+
date: 2009-04-18 00:00:00 -07:00
|
|
13
13
|
default_executable:
|
|
14
14
|
dependencies: []
|
|
15
15
|
|