Class: ICU::Tournament
- Inherits:
-
Object
- Object
- ICU::Tournament
- Defined in:
- lib/chess_icu/tournament.rb,
lib/chess_icu/tournament_fcsv.rb
Overview
Generic Tournament
Normally a tournament object is created by parsing a data file (e.g. with ICU::Tournament::ForeignCSV). However, it is also possible to build a tournament by first creating a bare tournament instance and then firstly adding all the players and then adding all the results.
require 'rubygems'
require 'chess_icu'
t = ICU::Tournament.new('Bangor Masters', '2009-11-09')
t.add_player(ICU::Player.new('Bobby', 'Fischer', 10))
t.add_player(ICU::Player.new('Garry', 'Kasparov', 20))
t.add_player(ICU::Player.new('Mark', 'Orr', 30))
t.add_result(ICU::Result.new(1, 10, 'D', :opponent => 30, :colour => 'W'))
t.add_result(ICU::Result.new(2, 20, 'W', :opponent => 30, :colour => 'B'))
t.add_result(ICU::Result.new(3, 20, 'L', :opponent => 10, :colour => 'W'))
[10, 20, 30].each { |n| p = t.player(n); puts "#{p.points} #{p.name}" }
Would result in the following output.
1.5 Bobby Fischer
1.0 Gary Kasparov
0.5 Mark Orr
Note that the players should be added first because the add_result method will raise an exception if the players it references through their tournament numbers (10, 20 and 30 in this example) have not already been added to the tournament.
A tournament can be validated with either the validate! or invalid methods. On success, the first returns true while the second returns false. On error, the first throws an exception while the second returns a description of the error.
Validations checks that:
-
there are at least two players
-
every player has a least one result
-
the round numbers of the players results are consistent
-
the tournament dates (start, finish, round dates) are consistent
-
the player ranks are consistent with their scores
Side effects of calling validate! or invalid include:
-
the number of rounds will be set if not set already
-
the finish date will be set if not set already and if there are round dates
If the rerank option is set, as in this example:
t.validate!(:rerank => true)
then there are additional side effects of validating a tournament:
-
the players will be ranked if there is no existing ranking
-
the players will be reranked if the existing ranking is inconsistent
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.
The default tie break method used to rank players on the same score is alphabetical (by last name then first name). Other methods can be specified via the rerank option. Instead of setting the option to true, alternatives are the following (both symbols or strings will work):
-
sum_of_scores: sum of opponents’ scores
-
name: this is the default and the same as setting the option to true
Since validate and invalid only rerank a tournament with absent or inconsistent ranking, to force a particular kind of ranking of a tournament which is already ranked, use the rerank method. This method takes one argument, the tie break method and it works the same as the rerank option to validate. For example:
t.rerank(:sum_of_scores) # rerank using sum of scores as tie break
t.rerank(:name) # rerank using player names as tie break
t.rerank # same as _name_, which is the default
The players in a tournament, whose reference numbers can be any set of unique integers (including zero and negative numbers), 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 of players.
t.renumber(:name) # renumber by name
t.renumber(:rank) # renumber by rank
t.renumber # same - rank is the default
A side effect of renumbering by rank is that if the tournament started without any player rankings or with inconsitent rankings, it will be reranked (i.e. the method rerank will be called).
Defined Under Namespace
Classes: ForeignCSV
Instance Attribute Summary collapse
-
#arbiter ⇒ Object
Returns the value of attribute arbiter.
-
#city ⇒ Object
Returns the value of attribute city.
-
#deputy ⇒ Object
Returns the value of attribute deputy.
-
#fed ⇒ Object
Returns the value of attribute fed.
-
#finish ⇒ Object
Returns the value of attribute finish.
-
#name ⇒ Object
Returns the value of attribute name.
-
#round_dates ⇒ Object
readonly
Returns the value of attribute round_dates.
-
#rounds ⇒ Object
Returns the value of attribute rounds.
-
#site ⇒ Object
Returns the value of attribute site.
-
#start ⇒ Object
Returns the value of attribute start.
-
#teams ⇒ Object
readonly
Returns the value of attribute teams.
-
#time_control ⇒ Object
Returns the value of attribute time_control.
-
#type ⇒ Object
Returns the value of attribute type.
Instance Method Summary collapse
-
#add_player(player) ⇒ Object
Add a new player to the tournament.
-
#add_result(result, reverse_rateable = true) ⇒ Object
Add a result to a tournament.
-
#add_round_date(round_date) ⇒ Object
Add a round date.
-
#add_team(team) ⇒ Object
Add a new team.
-
#find_player(player) ⇒ Object
Lookup a player in the tournament by player number, returning nil if the player number does not exist.
-
#get_team(name) ⇒ Object
Return the team object that matches a given name, or nil if not found.
-
#initialize(name, start, opt = {}) ⇒ Tournament
constructor
Constructor.
-
#invalid(options = {}) ⇒ Object
Is a tournament invalid? Either returns false (if it’s valid) or an error message.
-
#last_round ⇒ Object
Return the greatest round number according to the players results (which may not be the same as the set number of rounds).
-
#player(num) ⇒ Object
Get a player by their number.
-
#players ⇒ Object
Return an array of all players in order of their player number.
-
#renumber(criterion = :rank) ⇒ Object
Renumber the players according to a given criterion.
-
#rerank(tie_break_method = :name) ⇒ Object
Rerank the tournament by score first and if necessary using a configurable tie breaker method.
-
#round_date(round) ⇒ Object
Return the date of a given round, or nil if unavailable.
-
#validate!(options = {}) ⇒ Object
Raise an exception if a tournament is not valid.
Constructor Details
#initialize(name, start, opt = {}) ⇒ Tournament
Constructor. Name and start date must be supplied. Other attributes are optional.
96 97 98 99 100 101 102 103 |
# File 'lib/chess_icu/tournament.rb', line 96 def initialize(name, start, opt={}) self.name = name self.start = start [:finish, :rounds, :site, :city, :fed, :type, :arbiter, :deputy, :time_control].each { |a| self.send("#{a}=", opt[a]) unless opt[a].nil? } @player = {} @teams = [] @round_dates = [] end |
Instance Attribute Details
#arbiter ⇒ Object
Returns the value of attribute arbiter.
93 94 95 |
# File 'lib/chess_icu/tournament.rb', line 93 def arbiter @arbiter end |
#city ⇒ Object
Returns the value of attribute city.
93 94 95 |
# File 'lib/chess_icu/tournament.rb', line 93 def city @city end |
#deputy ⇒ Object
Returns the value of attribute deputy.
93 94 95 |
# File 'lib/chess_icu/tournament.rb', line 93 def deputy @deputy end |
#fed ⇒ Object
Returns the value of attribute fed.
93 94 95 |
# File 'lib/chess_icu/tournament.rb', line 93 def fed @fed end |
#finish ⇒ Object
Returns the value of attribute finish.
93 94 95 |
# File 'lib/chess_icu/tournament.rb', line 93 def finish @finish end |
#name ⇒ Object
Returns the value of attribute name.
93 94 95 |
# File 'lib/chess_icu/tournament.rb', line 93 def name @name end |
#round_dates ⇒ Object (readonly)
Returns the value of attribute round_dates.
93 94 95 |
# File 'lib/chess_icu/tournament.rb', line 93 def round_dates @round_dates end |
#rounds ⇒ Object
Returns the value of attribute rounds.
93 94 95 |
# File 'lib/chess_icu/tournament.rb', line 93 def rounds @rounds end |
#site ⇒ Object
Returns the value of attribute site.
93 94 95 |
# File 'lib/chess_icu/tournament.rb', line 93 def site @site end |
#start ⇒ Object
Returns the value of attribute start.
93 94 95 |
# File 'lib/chess_icu/tournament.rb', line 93 def start @start end |
#teams ⇒ Object (readonly)
Returns the value of attribute teams.
93 94 95 |
# File 'lib/chess_icu/tournament.rb', line 93 def teams @teams end |
#time_control ⇒ Object
Returns the value of attribute time_control.
93 94 95 |
# File 'lib/chess_icu/tournament.rb', line 93 def time_control @time_control end |
#type ⇒ Object
Returns the value of attribute type.
93 94 95 |
# File 'lib/chess_icu/tournament.rb', line 93 def type @type end |
Instance Method Details
#add_player(player) ⇒ Object
Add a new player to the tournament. Must have a unique player number.
234 235 236 237 238 |
# File 'lib/chess_icu/tournament.rb', line 234 def add_player(player) raise "invalid player" unless player.class == ICU::Player raise "player number (#{player.num}) should be unique" if @player[player.num] @player[player.num] = player end |
#add_result(result, reverse_rateable = true) ⇒ Object
Add a result to a tournament. An exception is raised if the players referenced in the result (by number) do not exist in the tournament. The result, which remember is from the perspective of one of the players, is added to that player’s results. Additionally, the reverse of the result is automatically added to the player’s opponent, unless the opponent does not exist (e.g. byes, walkovers). By default, if the result is rateable then the opponent’s result will also be rateable. To make the opponent’s result unrateable, set the optional second parameter to false.
261 262 263 264 265 266 267 268 269 270 271 272 |
# File 'lib/chess_icu/tournament.rb', line 261 def add_result(result, reverse_rateable=true) raise "invalid result" unless result.class == ICU::Result raise "result round number (#{result.round}) inconsistent with number of tournament rounds" if @rounds && result.round > @rounds raise "player number (#{result.player}) does not exist" unless @player[result.player] @player[result.player].add_result(result) if result.opponent raise "opponent number (#{result.opponent}) does not exist" unless @player[result.opponent] reverse = result.reverse reverse.rateable = false unless reverse_rateable @player[result.opponent].add_result(reverse) end end |
#add_round_date(round_date) ⇒ Object
Add a round date.
159 160 161 162 163 164 165 |
# File 'lib/chess_icu/tournament.rb', line 159 def add_round_date(round_date) round_date = round_date.to_s.strip parsed_date = Util.parsedate(round_date) raise "invalid round date (#{round_date})" unless parsed_date @round_dates << parsed_date @round_dates.sort! end |
#add_team(team) ⇒ Object
Add a new team. The argument is either a team (possibly already with members) or the name of a new team. The team’s name must be unique in the tournament. Returns the the team instance.
221 222 223 224 225 226 |
# File 'lib/chess_icu/tournament.rb', line 221 def add_team(team) team = Team.new(team.to_s) unless team.is_a? Team raise "a team with a name similar to '#{team.name}' already exists" if self.get_team(team.name) @teams << team team end |
#find_player(player) ⇒ Object
Lookup a player in the tournament by player number, returning nil if the player number does not exist.
251 252 253 |
# File 'lib/chess_icu/tournament.rb', line 251 def find_player(player) players.find { |p| p == player } end |
#get_team(name) ⇒ Object
Return the team object that matches a given name, or nil if not found.
229 230 231 |
# File 'lib/chess_icu/tournament.rb', line 229 def get_team(name) @teams.find{ |t| t.matches(name) } end |
#invalid(options = {}) ⇒ Object
Is a tournament invalid? Either returns false (if it’s valid) or an error message.
308 309 310 311 312 313 314 315 |
# File 'lib/chess_icu/tournament.rb', line 308 def invalid(={}) begin validate!() rescue => err return err. end false end |
#last_round ⇒ Object
Return the greatest round number according to the players results (which may not be the same as the set number of rounds).
173 174 175 176 177 178 179 180 181 |
# File 'lib/chess_icu/tournament.rb', line 173 def last_round last_round = 0 @player.values.each do |p| p.results.each do |r| last_round = r.round if r.round > last_round end end last_round end |
#player(num) ⇒ Object
Get a player by their number.
241 242 243 |
# File 'lib/chess_icu/tournament.rb', line 241 def player(num) @player[num] end |
#players ⇒ Object
Return an array of all players in order of their player number.
246 247 248 |
# File 'lib/chess_icu/tournament.rb', line 246 def players @player.values.sort_by{ |p| p.num } end |
#renumber(criterion = :rank) ⇒ Object
Renumber the players according to a given criterion.
287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 |
# File 'lib/chess_icu/tournament.rb', line 287 def renumber(criterion = :rank) map = Hash.new # Decide how to rank. if criterion == :name @player.values.sort_by{ |p| p.name }.each_with_index{ |p, i| map[p.num] = i + 1 } else begin check_ranks rescue rerank end @player.values.each{ |p| map[p.num] = p.rank} end # Apply ranking. @teams.each{ |t| t.renumber(map) } @player = @player.values.inject({}) do |hash, player| player.renumber(map) hash[player.num] = player hash end end |
#rerank(tie_break_method = :name) ⇒ Object
Rerank the tournament by score first and if necessary using a configurable tie breaker method.
275 276 277 278 279 280 281 282 283 284 |
# File 'lib/chess_icu/tournament.rb', line 275 def rerank(tie_break_method = :name) points, tie_break_scores, tie_break_order = rerank_data(tie_break_method) sortable = @player.values.map { |p| [p, points[p.num], tie_break_scores[p.num]] } sortable.sort do |a,b| diff = b[1] <=> a[1] diff == 0 ? (b[2] <=> a[2]) * tie_break_order : diff end.each_with_index do |s,i| s[0].rank = i + 1 end end |
#round_date(round) ⇒ Object
Return the date of a given round, or nil if unavailable.
168 169 170 |
# File 'lib/chess_icu/tournament.rb', line 168 def round_date(round) @round_dates[round-1] end |
#validate!(options = {}) ⇒ Object
Raise an exception if a tournament is not valid. Covers all the ways a tournament can be invalid not already enforced by the setters.
319 320 321 322 323 324 325 326 327 |
# File 'lib/chess_icu/tournament.rb', line 319 def validate!(={}) begin check_ranks rescue rerank([:rerank]) end if [:rerank] check_players check_rounds check_dates check_teams check_ranks(:allow_none => true) true end |