#!/usr/bin/ruby
## $Id$
-## Copyright (C) 2006 Daigo Moriwaki <daigo at debian dot org>
+## Copyright (C) 2006-2008 Daigo Moriwaki <daigo at debian dot org>
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
#
# Sample:
# $ ./mk_rate . > players.yaml
+# $ ./mk_rate . && ./mk_rate . > players.yaml
#
# The conditions that games and players are rated as following:
# * Rated games, which were played by both rated players.
require 'yaml'
require 'time'
+require 'getoptlong'
require 'gsl'
require 'rubygems'
require 'rgl/adjacency'
end
##
- # The initial value of the rate, which is of very importance for Newton method.
- # This is based on my huristics; the higher the win probablity of a player is,
- # the greater points he takes.
+ # The initial value of the rate, which is of very importance for Newton
+ # method. This is based on my huristics; the higher the win probablity of
+ # a player is, the greater points he takes.
#
def initial_rate
possibility =
$stderr.puts "f: %s -> %f" % [f.to_a.inspect, f.nrm2] if $DEBUG
# GSL::Linalg::LU.solve or GSL::Linalg::HH.solve would be available instead.
- a = GSL::Linalg::SV.solve(j, f)
+ #a = GSL::Linalg::HH.solve(j, f)
+ a, = GSL::MultiFit::linear(j, f)
a = self.class.average(a)
# $stderr.puts "a: %s -> %f" % [a.to_a.inspect, a.nrm2] if $DEBUG
end
##
+ # Translate by value
+ #
+ def translate!(value)
+ @rate += value
+ end
+
+ ##
# Make the values of @rate integer.
#
def integer!
end
##
- # Removes players who do not pass a criteria to be rated, and returns a new object.
+ # Removes players who do not pass a criteria to be rated, and returns a
+ # new object.
#
def filter
$stderr.puts @keys.inspect if $DEBUG
# 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
+ if days < $options["half-life-ignore"]
return 1.0
else
- Math::exp(-0.693/NHALF_LIFE*(days-7))
+ Math::exp(-0.693/$options["half-life"]*(days-$options["half-life-ignore"]))
end
end
exit 1
end
+def validate(yaml)
+ yaml["players"].each do |group_key, group|
+ group.each do |player_key, player|
+ rate = player['rate']
+ next unless rate
+ if rate > 10000 || rate < -10000
+ return false
+ end
+ end
+ end
+ return true
+end
+
+def usage(io)
+ io.puts <<EOF
+USAGE: #{$0} [options] DIR..
+ DIR where CSA files are looked up recursively
+OPTOINS:
+ --half-life n [days] (default 60)
+ --half-life-ignore m [days] (default 7)
+ after m days, half-life effect works
+ --fixed-rate-player player whose rate is fixed at the rate
+ --fixed-rate rate
+ --help show this message
+EOF
+end
+
def main
- usage if ARGV.empty?
+ $options = Hash::new
+ parser = GetoptLong.new(
+ ["--half-life", GetoptLong::REQUIRED_ARGUMENT],
+ ["--half-life-ignore", GetoptLong::REQUIRED_ARGUMENT],
+ ["--help", "-h", GetoptLong::NO_ARGUMENT],
+ ["--fixed-rate-player", GetoptLong::REQUIRED_ARGUMENT],
+ ["--fixed-rate", GetoptLong::REQUIRED_ARGUMENT])
+ parser.quiet = true
+ begin
+ parser.each_option do |name, arg|
+ name.sub!(/^--/, '')
+ $options[name] = arg.dup
+ end
+ if ( $options["fixed-rate-player"] && !$options["fixed-rate"]) ||
+ (!$options["fixed-rate-player"] && $options["fixed-rate"]) ||
+ ( $options["fixed-rate-player"] && $options["fixed-rate"].to_i <= 0)
+ usage($stderr)
+ exit 1
+ end
+ rescue
+ usage($stderr)
+ raise parser.error_message
+ end
+ if $options["help"]
+ usage($stdout)
+ exit 0
+ end
+ $options["half-life"] ||= 60
+ $options["half-life"] = $options["half-life"].to_i
+ $options["half-life-ignore"] ||= 7
+ $options["half-life-ignore"] = $options["half-life-ignore"].to_i
+ $options["fixed-rate"] = $options["fixed-rate"].to_i if $options["fixed-rate"]
+
while dir = ARGV.shift do
Dir.glob( File.join(dir, "**", "*.csa") ) {|f| grep(f)}
end
yaml = {}
yaml["players"] = {}
+ rating_group = 0
if $players.size > 0
obj = WinLossMatrix::mk_win_loss_matrix($players)
- rating_group = 0
obj.connected_subsets.each do |win_loss_matrix|
yaml["players"][rating_group] = {}
rating.average!(Rating::AVERAGE_RATE)
rating.integer!
+ if $options["fixed-rate-player"]
+ # first, try exact match
+ index = win_loss_matrix.keys.index($options["fixed-rate-player"])
+ # second, try regular match
+ unless index
+ win_loss_matrix.keys.each_with_index do |p, i|
+ if %r!#{$options["fixed-rate-player"]}! =~ p
+ index = i
+ end
+ end
+ end
+ if index
+ the_rate = rating.rate[index]
+ rating.translate!($options["fixed-rate"] - the_rate)
+ end
+ end
+
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
rating_group += 1
end
end
+ rating_group -= 1
+ non_rated_group = 999 # large enough
+ yaml["players"][non_rated_group] = {}
+ $players.each_key do |id|
+ # skip players who have already been rated
+ found = false
+ (0..rating_group).each do |i|
+ found = true if yaml["players"][i][id]
+ break if found
+ end
+ next if found
+
+ v = GSL::Vector[0, 0]
+ $players[id].each_value {|value| v += value}
+ next if v[0] < 1 && v[1] < 1
+
+ yaml["players"][non_rated_group][id] =
+ { 'name' => id.split("+")[0],
+ 'rating_group' => non_rated_group,
+ 'rate' => 0,
+ 'last_modified' => $players_time[id].dup,
+ 'win' => v[0],
+ 'loss' => v[1]}
+ end
+ unless validate(yaml)
+ $stderr.puts "Aborted. It did not result in valid ratings."
+ $stderr.puts yaml.to_yaml if $DEBUG
+ exit 10
+ end
puts yaml.to_yaml
end