OSDN Git Service

Care about the byoyomi case where the consumed time should be zero.
[shogi-server/shogi-server.git] / utils / eval_graph.rb
1 #!/usr/bin/env ruby
2 #    This generates graphs of evaluation values from comments in CSA files.
3 #    Ruby libraries that are required: 
4 #      - RubyGems: http://rubyforge.org/projects/rubygems/
5 #      - rgplot:   http://rubyforge.org/projects/rgplot/
6 #    OS librariles that is required:
7 #      - Gnuplot:  http://www.gnuplot.info/
8 #                  On Debian, $ sudo apt-get install gnuplot
9 #    
10 #    Copyright (C) 2006  Daigo Moriwaki <daigo@debian.org>
11 #
12 #    Version: $Id$
13 #
14 #    This program is free software; you can redistribute it and/or modify
15 #    it under the terms of the GNU General Public License as published by
16 #    the Free Software Foundation; either version 2 of the License, or
17 #    (at your option) any later version.
18 #
19 #    This program is distributed in the hope that it will be useful,
20 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
21 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22 #    GNU General Public License for more details.
23 #
24 #    You should have received a copy of the GNU General Public License
25 #    along with this program; if not, write to the Free Software
26 #    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
27
28 require 'pathname'
29 require 'getoptlong'
30 require 'rubygems'
31 require 'gnuplot'
32
33 def to_svg_file(csa_file)
34   "#{csa_file}.svg"
35 end
36
37 def reformat_svg(str)
38   str.gsub(%r!<svg.*?>!m, <<-END) 
39 <svg viewBox="0 0 800 600" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
40 END
41 end
42
43 module EvalGraph
44   def parse_comment(str)
45     return nil unless str
46     str.strip!
47     items = str.split(" ")
48     if items.size > 0
49       return items[0]
50     else
51       return nil
52     end
53   end
54   module_function :parse_comment
55   
56   class Player
57     attr_accessor :theOther
58     attr_reader   :name, :comments, :start_time
59     
60     # type is '+' or '-'
61     def initialize(type)
62       @comments = []
63       @times = []
64       @type = type
65       @regexp_move = Regexp.new("^\\#{@type}\\d{4}\\w{2}")
66       @regexp_name = Regexp.new("^N\\#{@type}(.*)")
67       @regexp_time = Regexp.new(/^T(\d+)/)
68       @flag = false
69       @name = nil
70     end
71
72     def reset
73       if @flag
74         @comments << nil
75       end
76       @flag = false
77     end
78
79     def <<(comment)
80       case comment
81       when @regexp_move
82         @flag = true
83         @theOther.reset
84       when @regexp_time
85         if @flag
86           @times << $1.to_i
87         end
88       when /^'\*\*(.*)/
89         if @flag
90           @comments << EvalGraph::parse_comment($1)
91           @flag = false
92         end
93       when @regexp_name
94         @name = $1
95       when /\$START_TIME:(.*)/
96         @start_time = $1
97       end
98     end
99
100     def time_values(y_max, full_time)
101       consume = full_time
102       values = []
103       values << 1.0*y_max/full_time*consume
104       @times.each do |t|
105         if consume == 0
106           break
107         end
108         consume -= t
109         if consume < 0
110           consume = 0
111         end
112         values << 1.0*y_max/full_time*consume
113       end
114       return values
115     end
116
117   end
118
119   class Black < Player
120     def name
121       @name ? "#{@name} (B)" : "black"
122     end
123
124     # Gluplot can not show nil vlaues so that a return value has to contain
125     # [[0], [0]] at least.
126     def eval_values
127       moves = []
128       comments.each_with_index do |c, i|
129         moves << i*2 + 1 if c
130       end
131       moves.unshift 0
132       [moves, comments.compact.unshift(0)]
133     end
134
135     def time_values(y_max, full_time)
136       values = super
137       moves = [0]
138       return [moves, values] if values.size <= 1
139
140       i = 1
141       values[1, values.size-1].each do |v|
142         moves << i
143         i += 2
144       end
145       return [moves, values]
146     end
147   end
148
149   class White < Player
150     def name
151       @name ? "#{@name} (W)" : "white"
152     end
153
154     def eval_values
155       moves = []
156       comments.each_with_index do |c, i|
157         moves << i*2 if c
158       end
159       moves.unshift 0
160       [moves, comments.compact.unshift(0)]
161     end
162
163     def time_values(y_max, full_time)
164       values = super
165       moves = [0]
166       return [moves, values] if values.size <= 1
167
168       i = 2
169       values[1, values.size-1].each do |v|
170         moves << i
171         i += 2
172       end
173       return [moves, values]
174    end
175   end
176
177   
178   def create_players
179     black = Black.new("+")
180     white = White.new("-")
181     black.theOther = white
182     white.theOther = black
183     return black,white
184   end
185   module_function :create_players
186 end
187
188 def max_time(game_name)
189   if /.*?\+.*?\-(\d*?)\-/ =~ game_name
190     return $1.to_i
191   end
192   return 0  
193 end
194
195 def plot(csa_file, title, black, white)
196   width = [black.comments.size, white.comments.size].max * 2 + 1
197   Gnuplot.open do |gp|
198     Gnuplot::Plot.new( gp ) do |plot|
199       plot.terminal "svg" # or png
200       plot.output   to_svg_file(csa_file)
201       
202       plot.title  title
203       plot.size   "ratio #{1/1.618}"
204       plot.xlabel "Moves"
205       plot.ylabel "Evaluation Value"
206       plot.xrange "[0:#{width}]"
207       plot.yrange "[-3000:3000]"
208       plot.xtics  "20"
209       plot.mxtics "2"
210       plot.ytics  %Q!("2000" 2000, "-2000" -2000)!
211       plot.xzeroaxis "lt -1"
212       plot.grid
213       plot.size   "0.9,0.9"
214       plot.key "left"
215      
216       plot.style "line 1 linewidth 5 linetype 0 linecolor rgbcolor \"red\"" 
217       plot.style "line 2 linewidth 5 linetype 0 linecolor rgbcolor \"blue\"" 
218
219       plot.data << Gnuplot::DataSet.new( black.eval_values ) do |ds|
220         ds.with  = "lines ls 1"
221         ds.title = black.name
222       end
223       
224       plot.data << Gnuplot::DataSet.new( white.eval_values ) do |ds|
225         ds.with  = "lines ls 2"
226         ds.title = white.name
227       end
228
229       full_time = max_time(csa_file)
230       if full_time > 0
231         plot.style "line 5 linewidth 1 linetype 0 linecolor rgbcolor \"red\"" 
232         plot.style "line 6 linewidth 1 linetype 0 linecolor rgbcolor \"blue\"" 
233         plot.style "fill solid 0.25 noborder"
234
235         plot.data << Gnuplot::DataSet.new( black.time_values(2000, full_time) ) do |ds|
236           ds.with  = "boxes notitle ls 5"
237         end
238         
239         plot.data << Gnuplot::DataSet.new( white.time_values(-2000, full_time) ) do |ds|
240           ds.with  = "boxes notitle ls 6"
241         end
242       end
243     end
244   end  
245 end
246
247
248
249 def read(lines, file_name)
250   lines.map! {|l| l.strip}
251   
252   black,white = EvalGraph.create_players
253   while l = lines.shift do
254     black << l
255     white << l
256   end
257   
258   title = "#{file_name}" 
259   plot(file_name, title, black, white)
260 end
261
262
263 if $0 == __FILE__
264   def usage
265     puts "Usage: #{$0} [--update] <csa_files>..."
266     puts "Options:"
267     puts "  --update        Update .svg files if exist."
268     exit 1
269   end
270
271   usage if ARGV.empty?
272
273   parser = GetoptLong.new
274   parser.set_options(['--update', GetoptLong::NO_ARGUMENT])
275   begin
276     parser.each_option do |name, arg|
277       eval "$OPT_#{name.sub(/^--/, '').gsub(/-/, '_').upcase} = '#{arg}'"
278     end
279   rescue
280     usage
281   end
282   
283   while file = ARGV.shift
284     next if !$OPT_UPDATE && File.exists?(to_svg_file(file))
285     read(Pathname.new(file).readlines, file)
286     str = reformat_svg(Pathname.new(to_svg_file(file)).read)
287     open(to_svg_file(file),"w+") {|f| f << str}
288   end
289 end