# Homepage:: http://sourceforge.jp/projects/shogi-server/
#
#--
-# Copyright (C) 2006-2008 Daigo Moriwaki <daigo at debian dot org>
+# Copyright (C) 2006-2009 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
#
# == Synopsis
#
-# mk_rate reads CSA files, calculates rating scores of each player, and then
-# outputs a yaml file (players.yaml) that Shogi-server can recognize.
+# mk_rate reads game results files generated by the mk_game_results command,
+# calculates rating scores of each player, and then outputs a yaml file
+# (players.yaml) that Shogi-server can recognize.
#
# == Usage
#
-# ./mk_rate [options] DIR..
+# ./mk_rate [options] GAME_RESULTS_FILE [...]
+#
+# ./mk_rate [options]
#
-# DIR::
-# CSA files are recursively looked up the directories.
+# GAME_RESULTS_FILE::
+# a path to a file listing results of games, which is genrated by the
+# mk_game_results command.
+# In the second style above, the file content can be read from the stdin.
+#
+# --base-date::
+# a base time point for this calicuration (default now). Ex. '2009-10-31'
#
# --half-life::
# n [days] (default 60)
# --fixed-rate::
# rate
#
+# --skip-draw-games::
+# skip draw games. [default: draw games are counted in as 0.5 win and 0.5
+# lost.]
+#
# --help::
# show this message
#
#
# $ sudo gem install rgl
#
-# == Run
+# == Examples
#
-# $ ./mk_rate . > players.yaml
+# $ ./mk_rate game_results.txt > players.yaml
#
-# or, if you do not want the file to be update in case of errors,
+# $ ./mk_game_results . | ./mk_rate > players.yaml
#
-# $ ./mk_rate . && ./mk_rate . > players.yaml
+# If you do not want the file to be update in case of errors,
+#
+# $ ./mk_rate game_results.txt && ./mk_rate game_results.txt > players.yaml
#
# == How players are rated
#
old_f = f
old_f_nrm2 = old_f.nrm2
deaccelrate(1.0, old_rate, a, old_f_nrm2)
+ #@rate -= a # Instead, do not deaccelerate
@record.set(func_vector.nrm2, @rate)
$stderr.printf "|error| : %5.2e\n", a.nrm2 if $DEBUG
@rate = @record.get
$stderr.puts "resolved f: %s -> %f" %
[func_vector.to_a.inspect, func_vector.nrm2] if $DEBUG
+ $stderr.puts "Count: %d" % [@count] if $DEBUG
@rate *= 1.0/K
finite!
0
else
p2 = keys[j]
- v = p1_hash[p2] || Vector[0,0]
+ v = p1_hash[p2] || GSL::Vector[0,0]
v[0]
end
end)
end
##
- # Removes a delete_index'th player and returns a new object.
+ # Removes players in a rows such as [1,3,5], and then returns a new
+ # object.
#
- def delete_row(delete_index)
+ def delete_rows(rows)
+ rows = rows.sort.reverse
+
copied_cols = []
(0...size).each do |i|
- next if i == delete_index
+ next if rows.include?(i)
row = @matrix.row(i).clone
- row.delete_at(delete_index)
+ rows.each do |j|
+ row.delete_at(j)
+ end
copied_cols << row
end
if copied_cols.size == 0
else
new_matrix = GSL::Matrix[*copied_cols]
end
- 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)
+ new_keys = @keys.clone
+ rows.each do |j|
+ new_keys.delete_at(j)
end
- obj
+
+ return WinLossMatrix.new(new_keys, new_matrix)
end
##
end
def _add_win_loss(winner, loser, time)
- how_long_days = (Time.now - time)/(3600*24)
+ how_long_days = ($options["base-date"] - 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*half_life(how_long_days),0]
$players[loser][winner] += GSL::Vector[0,1.0*half_life(how_long_days)]
end
+def _add_draw(player1, player2, time)
+ how_long_days = ($options["base-date"] - time)/(3600*24)
+ $players[player1] ||= Hash.new { GSL::Vector[0,0] }
+ $players[player2] ||= Hash.new { GSL::Vector[0,0] }
+ $players[player1][player2] += GSL::Vector[0.5*half_life(how_long_days),0.5*half_life(how_long_days)]
+ $players[player2][player1] += GSL::Vector[0.5*half_life(how_long_days),0.5*half_life(how_long_days)]
+end
+
def _add_time(player, time)
$players_time[player] = time if $players_time[player] < time
end
elsif black_mark == LOSS_MARK && white_mark == WIN_MARK
_add_win_loss(white_name, black_name, time)
elsif black_mark == DRAW_MARK && white_mark == DRAW_MARK
- return
+ if $options["skip-draw-games"]
+ return
+ else
+ _add_draw(black_name, white_name, time)
+ end
else
raise "Never reached!"
end
id.gsub(/@.*?\+/,"+")
end
-def grep(file)
- str = File.open(file).read
-
- if /^N\+(.*)$/ =~ str then black_name = $1.strip end
- if /^N\-(.*)$/ =~ str then white_name = $1.strip end
-
- if /^'summary:(.*)$/ =~ str
- 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 = p1_name, p1_mark
- white_name, white_mark = p2_name, p2_mark
- elsif p2_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} #{p3} #{p2}"
- end
- end
- if /^'\$END_TIME:(.*)$/ =~ str
- time = Time.parse($1.strip)
- end
- if /^'rating:(.*)$/ =~ str
- black_id, white_id = $1.split(":").map {|a| a.strip}
- black_id = identify_id(black_id)
- white_id = identify_id(white_id)
- if black_id && white_id && (black_id != white_id)
- add(black_mark, black_id, white_id, white_mark, time)
- end
+# Parse a game result line
+#
+def parse(line)
+ time, state, black_mark, black_id, white_id, white_mark, file = line.split("\t")
+ unless time && state && black_mark && black_id &&
+ white_id && white_mark && file
+ $stderr.puts "Failed to parse the line : #{line}"
+ return
end
-end
-def usage
- $stderr.puts <<-EOF
-USAGE: #{$0} dir [...]
- EOF
- exit 1
+ return if state == "abnormal"
+ time = Time.parse(time)
+ return if $options["base-date"] < time
+ black_id = identify_id(black_id)
+ white_id = identify_id(white_id)
+
+ if black_id && white_id && (black_id != white_id) &&
+ black_mark && white_mark
+ add(black_mark, black_id, white_id, white_mark, time)
+ end
end
def validate(yaml)
def usage(io)
io.puts <<EOF
-USAGE: #{$0} [options] DIR..
- DIR where CSA files are looked up recursively
+USAGE: #{$0} [options] GAME_RESULTS_FILE [...]
+ #{$0} [options]
+
+GAME_RESULTS_FILE:
+ a path to a file listing results of games, which is genrated by the
+ mk_game_results command.
+ In the second style above, the file content can be read from the stdin.
+
OPTOINS:
+ --base-date a base time point for this calicuration (default now). Ex. '2009-10-31'
--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
+ --skip-draw-games skip draw games. [default: draw games are counted in
+ as 0.5 win and 0.5 lost]
--help show this message
EOF
end
def main
$options = Hash::new
parser = GetoptLong.new(
+ ["--base-date", GetoptLong::REQUIRED_ARGUMENT],
["--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])
+ ["--fixed-rate", GetoptLong::REQUIRED_ARGUMENT],
+ ["--skip-draw-games", GetoptLong::NO_ARGUMENT])
parser.quiet = true
begin
parser.each_option do |name, arg|
usage($stdout)
exit 0
end
+ if $options["base-date"]
+ $options["base-date"] = Time::parse $options["base-date"]
+ else
+ $options["base-date"] = Time.now
+ 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)}
+ if ARGV.empty?
+ while line = $stdin.gets do
+ parse line.strip
+ end
+ else
+ while file = ARGV.shift do
+ File.open(file) do |f|
+ f.each_line do |line|
+ parse line.strip
+ end
+ end
+ end
end
yaml = {}