4 ## Copyright (C) 2006 Daigo Moriwaki <daigo at debian dot org>
6 ## This program is free software; you can redistribute it and/or modify
7 ## it under the terms of the GNU General Public License as published by
8 ## the Free Software Foundation; either version 2 of the License, or
9 ## (at your option) any later version.
11 ## This program is distributed in the hope that it will be useful,
12 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
13 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 ## GNU General Public License for more details.
16 ## You should have received a copy of the GNU General Public License
17 ## along with this program; if not, write to the Free Software
18 ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 # This calculates rating scores of every players from CSA files, and outputs a
22 # yaml file (players.yaml) that Shogi Server can read.
25 # $ ./mk_rate . > players.yaml
27 # The conditions that games and players are rated as following:
28 # * Rated games, which were played by both rated players.
29 # * Rated players, who logged in the server with a name followed by a trip:
31 # * (Rated) players, who played more than $GAMES_LIMIT [ten] (rated) games.
38 #################################################
41 $GAMES_LIMIT = $DEBUG ? 0 : 10
47 $players_time = Hash.new { Time.at(0) }
50 #################################################
51 # Calculates rates of every player from a Win Loss Matrix
56 K = Math.log(10.0) / 400.0
59 def Rating.average(vector, mean=0.0)
60 sum = Array(vector).inject(0.0) {|sum, n| sum + n}
61 vector -= Vector[*Array.new(vector.size, sum/vector.size - mean)]
65 def initialize(win_loss_matrix)
66 @win_loss_matrix = win_loss_matrix
67 @size = @win_loss_matrix.row_size
72 # 0 is the initial value
73 @rate = Vector[*Array.new(@size,0)]
76 # the probability that x wins y
77 @win_rate_matrix = Matrix[*
83 1.0/(1.0+exp(K*(y-x)))
88 # delta in Newton method
89 errorVector = Vector[*
90 ((0...@size).collect do |k|
93 #---------------------
96 (0...@size).each do |i|
98 numerator += @win_loss_matrix[k,i] * @win_rate_matrix[i,k] -
99 @win_rate_matrix[k,i] * @win_loss_matrix[i,k]
100 #------------------------------------------------------
101 denominator += @win_rate_matrix[i,k] * @win_rate_matrix[k,i] *
102 (@win_loss_matrix[k,i] + @win_loss_matrix[i,k])
105 # Remained issue: what to do if zero?
106 (numerator == 0) ? 0 : numerator / denominator
110 # gets the next value
111 @rate += errorVector * (1.0/K)
112 $stderr.printf "|error| : %5.2e\n", errorVector.r if $DEBUG
114 end while (errorVector.r > ERROR_LIMIT * @rate.r)
119 def average!(mean=0.0)
120 @rate = self.class.average(@rate, mean)
124 @rate = @rate.map {|a| a.to_i}
130 #################################################
134 def mk_win_loss_matrix(players)
135 keys = players.keys.sort.reject do |k|
136 players[k].values.inject(0) {|sum, v| sum + v[0] + v[1]} < $GAMES_LIMIT
143 ((0...size).collect do |k|
144 ((0...size).collect do |j|
148 v = players[keys[k]][keys[j]]
157 def _add_win_loss(winner, loser)
158 $players[winner] ||= Hash.new { Vector[0,0] }
159 $players[loser] ||= Hash.new { Vector[0,0] }
160 $players[winner][loser] += Vector[1,0]
161 $players[loser][winner] += Vector[0,1]
164 def _add_time(player, time)
165 $players_time[player] = time if $players_time[player] < time
168 def add(black_mark, black_name, white_name, white_mark, time)
169 if black_mark == WIN_MARK && white_mark == LOSS_MARK
170 _add_win_loss(black_name, white_name)
171 elsif black_mark == LOSS_MARK && white_mark == WIN_MARK
172 _add_win_loss(white_name, black_name)
174 raise "Never reached!"
176 _add_time(black_name, time)
177 _add_time(white_name, time)
181 str = File.open(file).read
183 black_mark = ""; white_mark = ""
186 str.gsub(/^'summary:(.*)$/) do |line|
187 dummy, sente, gote = $1.split(":").map {|a| a.strip}
188 black_mark, white_mark = [sente,gote].map {|p| p.split(" ")[1]}
190 str.gsub(/^'\$END_TIME:(.*)$/) do |line|
191 time = Time.parse($1.strip)
193 if /^'rating:(.*)$/ =~ str
194 black_name, white_name = $1.split(":").map {|a| a.strip}
195 add(black_mark, black_name, white_name, white_mark, time)
201 USAGE: #{$0} dir [...]
208 while dir = ARGV.shift do
209 Dir.glob( File.join("**", "*.csa") ) {|f| grep(f)}
212 win_loss_matrix, keys = mk_win_loss_matrix($players)
213 rating = Rating.new(win_loss_matrix)
215 rating.average!(AVERAGE_RATE)
219 keys.each_with_index do |p, i| # player_id, index#
220 win_loss = $players[p].values.inject(Vector[0,0]) {|sum, v| sum + v}
221 win = win_loss_matrix
223 { 'name' => p.split("+")[0],
224 'rate' => rating.rate[i],
225 'last_modified' => $players_time[p],
226 'win' => win_loss[0],
227 'loss' => win_loss[1]}
236 # vim: ts=2 sw=2 sts=0