Added a tool to investicate statistics of csa kifu files
@@ -28,6 +28,7 @@ | ||
28 | 28 | require 'time' |
29 | 29 | require 'pathname' |
30 | 30 | require 'getoptlong' |
31 | +require 'nkf' | |
31 | 32 | |
32 | 33 | class CsaFileReader |
33 | 34 | WIN_MARK = "win" |
@@ -35,9 +36,11 @@ | ||
35 | 36 | DRAW_MARK = "draw" |
36 | 37 | |
37 | 38 | attr_reader :file_name |
39 | + attr_reader :str | |
38 | 40 | attr_reader :black_name, :white_name |
39 | 41 | attr_reader :black_id, :white_id |
40 | 42 | attr_reader :winner, :loser |
43 | + attr_reader :state | |
41 | 44 | attr_reader :start_time, :end_time |
42 | 45 | |
43 | 46 | def initialize(file_name) |
@@ -46,13 +49,14 @@ | ||
46 | 49 | end |
47 | 50 | |
48 | 51 | def grep |
49 | - str = File.open(@file_name).read | |
52 | + @str = File.open(@file_name, "r:Shift_JIS:EUC-JP").read | |
50 | 53 | |
51 | - if /^N\+(.*)$/ =~ str then @black_name = $1.strip end | |
52 | - if /^N\-(.*)$/ =~ str then @white_name = $1.strip end | |
53 | - if /^'summary:(.*)$/ =~ str | |
54 | - state, p1, p2 = $1.split(":").map {|a| a.strip} | |
55 | - return if state == "abnormal" | |
54 | + | |
55 | + if /^N\+(.*)$/ =~ @str then @black_name = $1.strip end | |
56 | + if /^N\-(.*)$/ =~ @str then @white_name = $1.strip end | |
57 | + if /^'summary:(.*)$/ =~ @str | |
58 | + @state, p1, p2 = $1.split(":").map {|a| a.strip} | |
59 | + return if @state == "abnormal" | |
56 | 60 | p1_name, p1_mark = p1.split(" ") |
57 | 61 | p2_name, p2_mark = p2.split(" ") |
58 | 62 | if p1_name == @black_name |
@@ -65,13 +69,13 @@ | ||
65 | 69 | raise "Never reach!: #{black} #{white} #{p3} #{p2}" |
66 | 70 | end |
67 | 71 | end |
68 | - if /^\$START_TIME:(.*)$/ =~ str | |
72 | + if /^\$START_TIME:(.*)$/ =~ @str | |
69 | 73 | @start_time = Time.parse($1.strip) |
70 | 74 | end |
71 | - if /^'\$END_TIME:(.*)$/ =~ str | |
75 | + if /^'\$END_TIME:(.*)$/ =~ @str | |
72 | 76 | @end_time = Time.parse($1.strip) |
73 | 77 | end |
74 | - if /^'rating:(.*)$/ =~ str | |
78 | + if /^'rating:(.*)$/ =~ @str | |
75 | 79 | black_id, white_id = $1.split(":").map {|a| a.strip} |
76 | 80 | @black_id = identify_id(black_id) |
77 | 81 | @white_id = identify_id(white_id) |
@@ -90,6 +94,14 @@ | ||
90 | 94 | end |
91 | 95 | end |
92 | 96 | |
97 | + def movetimes | |
98 | + ret = [] | |
99 | + @str.gsub(%r!^T(\d+)!) do |match| | |
100 | + ret << $1.to_i | |
101 | + end | |
102 | + return ret | |
103 | + end | |
104 | + | |
93 | 105 | def to_s |
94 | 106 | return "Summary: #{@file_name}\n" + |
95 | 107 | "BlackName #{@black_name}, WhiteName #{@white_name}\n" + |
@@ -0,0 +1,205 @@ | ||
1 | +#!/usr/bin/ruby1.9.1 | |
2 | +# This program filters CSA files. For example, if you want only CSA files | |
3 | +# played by GPS vs Bonanza, | |
4 | +# $ ./csa-filter.rb --players gps-l,bonanza some_dir | |
5 | +# you will see such files under the some_dir directory. | |
6 | +# | |
7 | +# Author:: Daigo Moriwaki <daigo at debian dot org> | |
8 | +# Copyright:: Copyright (C) 2006-2008 Daigo Moriwaki <daigo at debian dot org> | |
9 | +# | |
10 | +# $Id$ | |
11 | +# | |
12 | +#-- | |
13 | +# This program is free software; you can redistribute it and/or modify | |
14 | +# it under the terms of the GNU General Public License as published by | |
15 | +# the Free Software Foundation; either version 2 of the License, or | |
16 | +# (at your option) any later version. | |
17 | +# | |
18 | +# This program is distributed in the hope that it will be useful, | |
19 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of | |
20 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
21 | +# GNU General Public License for more details. | |
22 | +# | |
23 | +# You should have received a copy of the GNU General Public License | |
24 | +# along with this program; if not, write to the Free Software | |
25 | +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | |
26 | +#++ | |
27 | + | |
28 | +$:.unshift File.dirname(__FILE__) | |
29 | +require 'csa-filter' | |
30 | +require 'set' | |
31 | + | |
32 | +class Monthly | |
33 | + def initialize | |
34 | + @games = Hash.new {|hash,key| hash[key] = 0} | |
35 | + @players = Hash.new {|hash,key| hash[key] = Set.new} | |
36 | + end | |
37 | + | |
38 | + def add(csa) | |
39 | + st = csa.start_time | |
40 | + month = st.strftime("%Y%m") | |
41 | + | |
42 | + @games[month] += 1 | |
43 | + | |
44 | + [csa.black_id, csa.white_id].each do |player| | |
45 | + @players[month].add(player) | |
46 | + end | |
47 | + end | |
48 | + | |
49 | + def print | |
50 | + puts "YYYYMM\t#games\t#players" | |
51 | + @games.sort {|a,b| a[0] <=> b[0]}.each do |key,value| | |
52 | + puts "%s\t% 6d\t% 2d" % [key, value, @players[key].size] | |
53 | + end | |
54 | + end | |
55 | +end | |
56 | + | |
57 | +class Values | |
58 | + def initialize | |
59 | + @v = [] | |
60 | + end | |
61 | + | |
62 | + def add(value) | |
63 | + case value | |
64 | + when Array | |
65 | + @v.concat value | |
66 | + else | |
67 | + @v << value | |
68 | + end | |
69 | + end | |
70 | + | |
71 | + def print(file) | |
72 | + total = @v.inject(0){|sum, item| sum+item} | |
73 | + avg = 1.0*total/@v.size | |
74 | + puts "avg: %f sec (size: %d)" % [avg, @v.size] | |
75 | + | |
76 | + File.open(file, "w") do |f| | |
77 | + @v.each {|v| f.puts v} | |
78 | + end | |
79 | + end | |
80 | +end | |
81 | + | |
82 | +class State | |
83 | + def initialize | |
84 | + @hash = Hash.new {|hash,key| hash[key] = 0} | |
85 | + end | |
86 | + | |
87 | + def add(value) | |
88 | + if value.nil? || value.empty? | |
89 | + value = "error" | |
90 | + end | |
91 | + @hash[value] += 1 | |
92 | + end | |
93 | + | |
94 | + def print | |
95 | + puts "status\t#games" | |
96 | + @hash.sort {|a,b| b[1] <=> a[1]}.each do |key, value| | |
97 | + puts "%s\t% 6d" % [key, value] | |
98 | + end | |
99 | + end | |
100 | +end | |
101 | + | |
102 | +$monthly = Monthly.new | |
103 | +$gametime = Values.new | |
104 | +$movetime = Values.new | |
105 | +$moves = Values.new | |
106 | +$states = State.new | |
107 | + | |
108 | +def do_file(file) | |
109 | + $OPT_REPEAT -= 1 if $OPT_REPEAT > 0 | |
110 | + csa = CsaFileReader.new(file) | |
111 | + | |
112 | + # Want to see complete games | |
113 | + $states.add csa.state | |
114 | + return unless csa.state == "toryo" | |
115 | + | |
116 | + # See games between 2008/03 to 2009/07 | |
117 | + return if csa.start_time < Time.parse("2008/03/01") || | |
118 | + csa.start_time >= Time.parse("2009/08/01") | |
119 | + | |
120 | + # Process monthly | |
121 | + $monthly.add(csa) | |
122 | + | |
123 | + # Process gametime | |
124 | + duration = (csa.end_time - csa.start_time).to_i | |
125 | + if duration > 2200 | |
126 | + $stderr.puts "Too long game: #{file}" | |
127 | + return | |
128 | + end | |
129 | + $gametime.add duration.to_i | |
130 | + | |
131 | + # Process movetime | |
132 | + values = csa.movetimes | |
133 | + $movetime.add values | |
134 | + | |
135 | + #Process moves | |
136 | + $moves.add values.size | |
137 | + | |
138 | +rescue => ex | |
139 | + $stderr.puts "ERROR: %s" % [file] | |
140 | + throw ex | |
141 | +end | |
142 | + | |
143 | +if $0 == __FILE__ | |
144 | + def usage | |
145 | + puts "Usage: #{$0} [OPTIONS] dir [...]" | |
146 | + puts "Options:" | |
147 | + exit 1 | |
148 | + end | |
149 | + | |
150 | + usage if ARGV.empty? | |
151 | + | |
152 | + parser = GetoptLong.new( | |
153 | + ['--repeat', '-n', GetoptLong::REQUIRED_ARGUMENT] | |
154 | + ) | |
155 | + begin | |
156 | + parser.each_option do |name, arg| | |
157 | + eval "$OPT_#{name.sub(/^--/, '').gsub(/-/, '_').upcase} = '#{arg}'" | |
158 | + end | |
159 | + rescue | |
160 | + usage | |
161 | + end | |
162 | + | |
163 | + $OPT_REPEAT = $OPT_REPEAT.to_i | |
164 | + if $OPT_REPEAT == 0 | |
165 | + $OPT_REPEAT = -1 | |
166 | + end | |
167 | + | |
168 | + while (cmd = ARGV.shift) | |
169 | + | |
170 | + if FileTest.directory?(cmd) | |
171 | + Dir.glob(File.join(cmd, "**", "*.csa")).each do |file| | |
172 | + break if $OPT_REPEAT == 0 | |
173 | + do_file(file) | |
174 | + end | |
175 | + elsif FileTest.file?(cmd) | |
176 | + break if $OPT_REPEAT == 0 | |
177 | + do_file(cmd) | |
178 | + else | |
179 | + throw "Unknown file or directory: #{cmd}" | |
180 | + end | |
181 | + | |
182 | + puts "States" | |
183 | + puts "------" | |
184 | + $states.print | |
185 | + puts | |
186 | + puts "=== Toryo Games ===" | |
187 | + puts | |
188 | + puts "Montly" | |
189 | + puts "------" | |
190 | + $monthly.print | |
191 | + puts | |
192 | + puts "Play Time" | |
193 | + puts "---------" | |
194 | + $gametime.print("gametime.dat") | |
195 | + puts | |
196 | + puts "Move Time" | |
197 | + puts "---------" | |
198 | + $movetime.print("movetime.dat") | |
199 | + puts | |
200 | + puts "Moves" | |
201 | + puts "-----" | |
202 | + $moves.print("moves.dat") | |
203 | + end | |
204 | +end | |
205 | + |