sanichi-chess_icu 0.2.1 → 0.2.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|