OSDN Git Service

* [utils/eval_graph.rb]
[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         consume -= t
106         values << 1.0*y_max/full_time*consume
107       end
108       return values
109     end
110
111   end
112
113   class Black < Player
114     def name
115       @name ? "#{@name} (B)" : "black"
116     end
117
118     # Gluplot can not show nil vlaues so that a return value has to contain
119     # [[0], [0]] at least.
120     def eval_values
121       moves = []
122       comments.each_with_index do |c, i|
123         moves << i*2 + 1 if c
124       end
125       moves.unshift 0
126       [moves, comments.compact.unshift(0)]
127     end
128
129     def time_values(y_max, full_time)
130       values = super
131       moves = [0]
132       return [moves, values] if values.size <= 1
133
134       i = 1
135       values[1, values.size-1].each do |v|
136         moves << i
137         i += 2
138       end
139       return [moves, values]
140     end
141   end
142
143   class White < Player
144     def name
145       @name ? "#{@name} (W)" : "white"
146     end
147
148     def eval_values
149       moves = []
150       comments.each_with_index do |c, i|
151         moves << i*2 if c
152       end
153       moves.unshift 0
154       [moves, comments.compact.unshift(0)]
155     end
156
157     def time_values(y_max, full_time)
158       values = super
159       moves = [0]
160       return [moves, values] if values.size <= 1
161
162       i = 2
163       values[1, values.size-1].each do |v|
164         moves << i
165         i += 2
166       end
167       return [moves, values]
168    end
169   end
170
171   
172   def create_players
173     black = Black.new("+")
174     white = White.new("-")
175     black.theOther = white
176     white.theOther = black
177     return black,white
178   end
179   module_function :create_players
180 end
181
182 def max_time(game_name)
183   if /.*?\+.*?\-(\d*?)\-/ =~ game_name
184     return $1.to_i
185   end
186   return 0  
187 end
188
189 def plot(csa_file, title, black, white)
190   width = [black.comments.size, white.comments.size].max * 2 + 1
191   Gnuplot.open do |gp|
192     Gnuplot::Plot.new( gp ) do |plot|
193       plot.terminal "svg" # or png
194       plot.output   to_svg_file(csa_file)
195       
196       plot.title  title
197       plot.size   "ratio #{1/1.618}"
198       plot.xlabel "Moves"
199       plot.ylabel "Evaluation Value"
200       plot.xrange "[0:#{width}]"
201       plot.yrange "[-3000:3000]"
202       plot.xtics  "20"
203       plot.mxtics "2"
204       plot.ytics  %Q!("2000" 2000, "-2000" -2000)!
205       plot.xzeroaxis "lt -1"
206       plot.grid
207       plot.size   "0.9,0.9"
208       plot.key "left"
209      
210       plot.style "line 1 linewidth 5 linetype 0 linecolor rgbcolor \"red\"" 
211       plot.style "line 2 linewidth 5 linetype 0 linecolor rgbcolor \"blue\"" 
212
213       plot.data << Gnuplot::DataSet.new( black.eval_values ) do |ds|
214         ds.with  = "lines ls 1"
215         ds.title = black.name
216       end
217       
218       plot.data << Gnuplot::DataSet.new( white.eval_values ) do |ds|
219         ds.with  = "lines ls 2"
220         ds.title = white.name
221       end
222
223       full_time = max_time(csa_file)
224       return if full_time == 0
225
226       plot.style "line 5 linewidth 1 linetype 0 linecolor rgbcolor \"red\"" 
227       plot.style "line 6 linewidth 1 linetype 0 linecolor rgbcolor \"blue\"" 
228       plot.style "fill solid 0.25 noborder"
229
230       plot.data << Gnuplot::DataSet.new( black.time_values(2000, full_time) ) do |ds|
231         ds.with  = "boxes notitle ls 5"
232       end
233       
234       plot.data << Gnuplot::DataSet.new( white.time_values(-2000, full_time) ) do |ds|
235         ds.with  = "boxes notitle ls 6"
236       end
237
238     end
239   end  
240 end
241
242
243
244 def read(lines, file_name)
245   lines.map! {|l| l.strip}
246   
247   black,white = EvalGraph.create_players
248   while l = lines.shift do
249     black << l
250     white << l
251   end
252   
253   title = "#{file_name}" 
254   plot(file_name, title, black, white)
255 end
256
257
258 if $0 == __FILE__
259   def usage
260     puts "Usage: #{$0} [--update] <csa_files>..."
261     puts "Options:"
262     puts "  --update        Update .svg files if exist."
263     exit 1
264   end
265
266   usage if ARGV.empty?
267
268   parser = GetoptLong.new
269   parser.set_options(['--update', GetoptLong::NO_ARGUMENT])
270   begin
271     parser.each_option do |name, arg|
272       eval "$OPT_#{name.sub(/^--/, '').gsub(/-/, '_').upcase} = '#{arg}'"
273     end
274   rescue
275     usage
276   end
277   
278   while file = ARGV.shift
279     next if !$OPT_UPDATE && File.exists?(to_svg_file(file))
280     read(Pathname.new(file).readlines, file)
281     str = reformat_svg(Pathname.new(to_svg_file(file)).read)
282     open(to_svg_file(file),"w+") {|f| f << str}
283   end
284 end