# PREREQUIRE
# ==========
#
-# Ruby bindings for the GNU Scientific Library (GSL) is required.
-# You can download it from http://rb-gsl.rubyforge.org/
-# Or, if you use Debian,
-# $ sudo aptitude install libgsl-ruby1.8
+# Sample Commands to isntall prerequires will work for Debian.
+#
+# * Rubygems
+# $ sudo aptitude install rubygems
+#
+# * Ruby bindings for the GNU Scientific Library (GSL)
+# $ sudo aptitude install libgsl-ruby1.8
+# Or, download it from http://rb-gsl.rubyforge.org/ .
+#
+# * RGL: Ruby Graph Library
+# $ sudo gem install rgl
+# Or, download it from http://rubyforge.org/projects/rgl/ .
#
require 'yaml'
require 'time'
require 'gsl'
+require 'rubygems'
+require 'rgl/adjacency'
+require 'rgl/connected_components'
#################################################
# Constants
@record = Record.new
@n = win_loss_matrix
case @n
- when GSL::Matrix
+ when GSL::Matrix, GSL::Matrix::Int
@size = @n.size1
when ::Matrix
@size = @n.row_size
end
end
-
-
#################################################
-# Main methods
+# Encapsulate a pair of keys and win loss matrix.
+# - keys is an array of player IDs; [gps+123, foo+234, ...]
+# - matrix holds games # where player i (row index) beats player j (column index).
+# The row and column indexes match with the keys.
+#
+# This object should be immutable. If an internal state is being modified, a
+# new object is always returned.
#
+class WinLossMatrix
+
+ ###############
+ # Class methods
+ #
-def mk_win_loss_matrix(players)
- keys = players.keys.sort.reject do |k|
- players[k].values.inject(0) {|sum, v| sum + v[0] + v[1]} < $GAMES_LIMIT
+ def self.mk_matrix(players)
+ keys = players.keys.sort
+ size = keys.size
+ matrix =
+ Matrix[*
+ ((0...size).collect do |k|
+ p1 = keys[k]
+ p1_hash = players[p1]
+ ((0...size).collect do |j|
+ if k == j
+ 0
+ else
+ p2 = keys[j]
+ v = p1_hash[p2] || Vector[0,0]
+ v[0]
+ end
+ end)
+ end)]
+ return WinLossMatrix.new(keys, matrix)
end
- size = keys.size
+ def self.mk_win_loss_matrix(players)
+ obj = mk_matrix(players)
+ return obj.filter
+ end
- matrix =
- GSL::Matrix[*
- ((0...size).collect do |k|
- ((0...size).collect do |j|
- if k == j
- 0
- else
- v = players[keys[k]][keys[j]]
- v[0]
+ ##################
+ # Instance methods
+ #
+
+ # an array of player IDs; [gps+123, foo+234, ...]
+ attr_reader :keys
+
+ # matrix holds games # where player i (row index) beats player j (column index).
+ # The row and column indexes match with the keys.
+ attr_reader :matrix
+
+ def initialize(keys, matrix)
+ @keys = keys
+ @matrix = matrix
+ end
+
+ ##
+ # Returns the size of the keys/matrix
+ #
+ def size
+ if @keys
+ @keys.size
+ else
+ nil
+ end
+ end
+
+ ##
+ # Removes a delete_index'th player and returns a new object.
+ #
+ def delete_row(delete_index)
+ copied_cols = []
+ (0...size).each do |i|
+ next if i == delete_index
+ row = @matrix.get_row(i) # get_row returns a copy of the row
+ row.delete_at(delete_index)
+ copied_cols << row
+ end
+ new_matrix = Matrix[*copied_cols]
+ new_keys = @keys.clone
+ new_keys.delete_at(delete_index)
+ return WinLossMatrix.new(new_keys, new_matrix)
+ end
+
+ ##
+ # Removes players in a rows; [1,3,5]
+ #
+ def delete_rows(rows)
+ obj = self
+ rows.sort.reverse.each do |index|
+ obj = obj.delete_row(index)
+ end
+ obj
+ end
+
+ ##
+ # Removes players who do not pass a criteria to be rated, and returns a new object.
+ #
+ def filter
+ $stderr.puts @keys.inspect if $DEBUG
+ $stderr.puts @matrix.inspect if $DEBUG
+ delete = []
+ (0...size).each do |i|
+ row = @matrix.row(i)
+ col = @matrix.col(i)
+ win = row.sum
+ loss = col.sum
+ if win < 1 || loss < 1 || win + loss < $GAMES_LIMIT
+ delete << i
end
- end)
- end)]
-
- return matrix, keys
+ end
+
+ # The recursion ends if there is nothing to delete
+ return self if delete.empty?
+
+ new_obj = delete_rows(delete)
+ new_obj.filter
+ end
+
+ ##
+ # Cuts self into connecting groups such as each player in a group has at least
+ # one game with other players in the group. Returns them as an array.
+ #
+ def connected_subsets
+ g = RGL::AdjacencyGraph.new
+ (0...size).each do |k|
+ (0...size).each do |i|
+ next if k == i
+ if @matrix[k,i] > 0
+ g.add_edge(k,i)
+ end
+ end
+ end
+
+ subsets = []
+ g.each_connected_component do |c|
+ new_keys = []
+ c.each do |v|
+ new_keys << keys[v.to_s.to_i]
+ end
+ subsets << new_keys
+ end
+
+ subsets = subsets.sort {|a,b| b.size <=> a.size}
+
+ result = subsets.collect do |keys|
+ matrix =
+ Matrix[*
+ ((0...keys.size).collect do |k|
+ p1 = @keys.index(keys[k])
+ ((0...keys.size).collect do |j|
+ if k == j
+ 0
+ else
+ p2 = @keys.index(keys[j])
+ @matrix[p1][p2]
+ end
+ end)
+ end)]
+ WinLossMatrix.new(keys, matrix)
+ end
+
+ return result
+ end
+
+ def to_s
+ "size : #{@keys.size}" + "\n" +
+ @keys.inspect + "\n" +
+ @matrix.inspect
+ end
+
+end
+
+
+#################################################
+# Main methods
+#
+
+# Half-life effect
+# After NHAFE_LIFE days value will get half.
+# 0.693 is constant, where exp(0.693) ~ 0.5
+NHALF_LIFE=60
+def half_life(days)
+ if days < 7
+ return 1.0
+ else
+ Math::exp(-0.693/NHALF_LIFE*(days-7))
+ end
end
-def _add_win_loss(winner, loser)
+def _add_win_loss(winner, loser, time)
+ how_long_days = (Time.now - time)/(3600*24)
$players[winner] ||= Hash.new { GSL::Vector[0,0] }
$players[loser] ||= Hash.new { GSL::Vector[0,0] }
- $players[winner][loser] += GSL::Vector[1,0]
- $players[loser][winner] += GSL::Vector[0,1]
+ $players[winner][loser] += GSL::Vector[1.0*half_life(how_long_days),0]
+ $players[loser][winner] += GSL::Vector[0,1.0*half_life(how_long_days)]
end
def _add_time(player, time)
def add(black_mark, black_name, white_name, white_mark, time)
if black_mark == WIN_MARK && white_mark == LOSS_MARK
- _add_win_loss(black_name, white_name)
+ _add_win_loss(black_name, white_name, time)
elsif black_mark == LOSS_MARK && white_mark == WIN_MARK
- _add_win_loss(white_name, black_name)
+ _add_win_loss(white_name, black_name, time)
elsif black_mark == DRAW_MARK && white_mark == DRAW_MARK
return
else
if /^N\-(.*)$/ =~ str then white_name = $1.strip end
if /^'summary:(.*)$/ =~ str
- dummy, p1, p2 = $1.split(":").map {|a| a.strip}
+ state, p1, p2 = $1.split(":").map {|a| a.strip}
+ return if state == "abnormal"
p1_name, p1_mark = p1.split(" ")
p2_name, p2_mark = p2.split(" ")
if p1_name == black_name
black_name, black_mark = p2_name, p2_mark
white_name, white_mark = p1_name, p1_mark
else
- raise "Never reach!: #{black} #{white} #{p1} #{p2}"
+ raise "Never reach!: #{black} #{white} #{p3} #{p2}"
end
end
if /^'\$END_TIME:(.*)$/ =~ str
Dir.glob( File.join(dir, "**", "*.csa") ) {|f| grep(f)}
end
- win_loss_matrix, keys = mk_win_loss_matrix($players)
- $stderr.puts keys.inspect if $DEBUG
- $stderr.puts win_loss_matrix.inspect if $DEBUG
- rating = Rating.new(win_loss_matrix)
- rating.rating
- rating.average!(Rating::AVERAGE_RATE)
- rating.integer!
-
- yaml = {}
- keys.each_with_index do |p, i| # player_id, index#
- win_loss = $players[p].values.inject(GSL::Vector[0,0]) {|sum, v| sum + v}
- win = win_loss_matrix
- yaml[p] =
- { 'name' => p.split("+")[0],
- 'rate' => rating.rate[i],
- 'last_modified' => $players_time[p].dup,
- 'win' => win_loss[0],
- 'loss' => win_loss[1]}
+ obj = WinLossMatrix::mk_win_loss_matrix($players)
+ yaml = {}
+ yaml["players"] = {}
+ rating_group = 0
+ obj.connected_subsets.each do |win_loss_matrix|
+ yaml["players"][rating_group] = {}
+
+ rating = Rating.new(win_loss_matrix.matrix)
+ rating.rating
+ rating.average!(Rating::AVERAGE_RATE)
+ rating.integer!
+
+ win_loss_matrix.keys.each_with_index do |p, i| # player_id, index#
+ win = win_loss_matrix.matrix.row(i).sum
+ loss = win_loss_matrix.matrix.col(i).sum
+
+ yaml["players"][rating_group][p] =
+ { 'name' => p.split("+")[0],
+ 'rating_group' => rating_group,
+ 'rate' => rating.rate[i],
+ 'last_modified' => $players_time[p].dup,
+ 'win' => win,
+ 'loss' => loss}
+ end
+ rating_group += 1
end
puts yaml.to_yaml
end