tclog 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.mkd ADDED
@@ -0,0 +1,40 @@
1
+ # TCLog: parser for etconsole.log of TrueCombat:Elite (TC:E)
2
+
3
+ ## Install
4
+
5
+ $ gem install tclog
6
+
7
+ Coming soon!
8
+
9
+ ## Usage
10
+
11
+ require 'tclog'
12
+
13
+ g = TCLog.analyze("/path/to/etconsole.log")
14
+
15
+ p g.players
16
+ p g.rounds[0]
17
+
18
+ ## License
19
+
20
+ The MIT License
21
+
22
+ (c) Shota Fukumori (sora_h) 2010-
23
+
24
+ Permission is hereby granted, free of charge, to any person obtaining a copy
25
+ of this software and associated documentation files (the "Software"), to deal
26
+ in the Software without restriction, including without limitation the rights
27
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
28
+ copies of the Software, and to permit persons to whom the Software is
29
+ furnished to do so, subject to the following conditions:
30
+
31
+ The above copyright notice and this permission notice shall be included in
32
+ all copies or substantial portions of the Software.
33
+
34
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
35
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
36
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
37
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
38
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
39
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
40
+ THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ begin
2
+ require 'jeweler'
3
+ Jeweler::Tasks.new do |gemspec|
4
+ gemspec.name = "tclog"
5
+ gemspec.summary = "Parser for etconsole.log of TrueCombat:Elite (TC:E)"
6
+ gemspec.email = "sorah@tubusu.net"
7
+ gemspec.homepage = "http://github.com/sorah/tclog"
8
+ gemspec.description = "Parser for etconsole.log of TrueCombat:Elite (TC:E)"
9
+ gemspec.authors = ["Shota Fukumori"]
10
+ end
11
+ rescue LoadError
12
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
13
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/lib/tclog.rb ADDED
@@ -0,0 +1,289 @@
1
+ module TCLog
2
+ class Game
3
+ def initialize(orders = [], gametype = :obj)
4
+ @orders = orders
5
+ @gametype = gametype
6
+ @rounds = []
7
+ @round_n = -1
8
+ @round_r = -1
9
+ @players = {}
10
+ class << @players
11
+ def [](x)
12
+ if x.kind_of?(Integer)
13
+ self.map{|v|v}[x]
14
+ else
15
+ super x
16
+ end
17
+ end
18
+
19
+ def each
20
+ super &(Proc.new{|k,v| yield v })
21
+ end
22
+ end
23
+ end
24
+
25
+
26
+ def add_map(map_name)
27
+ @round_r += 1
28
+ @rounds << Round.new(self, nil, @round_r, :map, true, map_name); self
29
+ end
30
+
31
+ def add_round(specops,terrorists,won)
32
+ @round_n += 1
33
+ @round_r += 1
34
+ r = Round.new(self, @round_n, @round_r, won)
35
+ r.specops = specops.dup
36
+ r.terrorists = terrorists.dup
37
+ @rounds << r
38
+ self
39
+ end
40
+
41
+ def add_player(name)
42
+ @players[name] = Player.new(name)
43
+ if 0 <= @round_r
44
+ @players[name].push_result(@round_r)
45
+ end
46
+ self
47
+ end
48
+
49
+
50
+ def round; @round_n; end
51
+ def [](i); @rounds[i]; end
52
+ attr_reader :rounds, :players, :orders, :gametype, :round_r
53
+ end
54
+ class Round
55
+ def initialize(game, n, rn, win, map_changing = false, map_name = nil)
56
+ @game = game
57
+ @won = win
58
+ @round_number = n
59
+ @real_round_number = rn
60
+ @map_changing = map_changing
61
+ @specops = {}
62
+ @terrorists = {}
63
+ @map_name = map_name
64
+ end
65
+
66
+ def players
67
+ @game.players.map do |g|
68
+ g if @round_number && g.results[@real_round_number]
69
+ end.compact
70
+ end
71
+
72
+ def player_results
73
+ @game.players.map do |g|
74
+ g.results[@real_round_number] if @round_number
75
+ end.compact
76
+ end
77
+
78
+ def map_changing?; @map_changing; end
79
+ def map_changing=(x); @map_changing = x; end
80
+ attr_accessor :map_name, :specops, :terrorists, :won
81
+ attr_reader :round_number
82
+ end
83
+ class Player
84
+ def initialize(name)
85
+ @name = name
86
+ @results = []
87
+ end
88
+
89
+ def add_result(n, score)
90
+ @results[n] = score.merge(:round => n); self
91
+ end
92
+
93
+ def total
94
+ a = @results.compact.inject({
95
+ :name => @name,
96
+ :kill => 0,
97
+ :death => 0,
98
+ :sui => 0,
99
+ :tk => 0,
100
+ :eff => 0,
101
+ :dg => 0,
102
+ :dr => 0,
103
+ :td => 0,
104
+ :score => 0,
105
+ :rate => 0
106
+ }) do |r,i|
107
+ [:kill,:death,:sui,:tk,:eff,:dg,:dr,:td,:score].each do |l|
108
+ r[l] += i[l]
109
+ end
110
+ r
111
+ end
112
+ a[:rate] = (a[:dg].to_i-a[:dr].to_i)/100.0
113
+ a[:kd] = a[:kill].to_f / a[:death].to_f
114
+ a
115
+ end
116
+
117
+ def push_result(i)
118
+ @results[i] = nil
119
+ end
120
+ attr_reader :name, :results
121
+ end
122
+
123
+ # logfile = String: filename
124
+ # IO: io
125
+ #
126
+ # gametype = :obj => "Objective"
127
+ # :ctf => "Capture The Flag"
128
+ # :bc => "BodyCount"
129
+ def self.analyze(logfile, gametype = :obj)
130
+ # Load file
131
+ log = case logfile
132
+ when File, IO
133
+ logfile.readlines.map(&:chomp)
134
+ when String
135
+ File.readlines(logfile).map(&:chomp)
136
+ else
137
+ raise ArgumentError, "logfile must be kind of String, File or IO"
138
+ end
139
+ # Drop waste lines / Drop waste message
140
+ renames = {}
141
+ orders = log.map do |x|
142
+ next unless /^(\[skipnotify\])(\^.)?(.+renamed|Timelimit hit)/ =~ x ||
143
+ /^(\[skipnotify\])?(\^.)?(Specops|Terrorists)/ =~ x ||
144
+ /^(\[skipnotify\])?(\^.)?(Planted|Defused)/ =~ x ||
145
+ /^The .+ have completed the objective!/ =~ x ||
146
+ /^(\[skipnotify\])(\^.)(Overall stats for: |)/ =~ x ||
147
+ /^(LOADING\.\.\. maps|Match starting)/ =~ x
148
+ x.gsub!(/\[skipnotify\]/,"")
149
+ # Player Score
150
+ r = x.scan(/\^.(Terrorists|Specops)\^. *\^.(.+?)\^. *([\d\-]+) +([\d\-]+) +([\d\-]+) +([\d\-]+)\^. *([\d\-]+)\^. *([\d\-]+)\^. *([\d\-]+)\^. *([\d\-]+)\^. *([\d\-]+)\^. *([\d\-]+)/).flatten
151
+ if r.empty? # If the line not player's score
152
+ case x
153
+ when /^LOADING\.\.\. maps\/(.+?)\.bsp$/ # Map Changing?
154
+ m = $~.captures
155
+ ["Map",m[0]]
156
+ when /^Match starting/ # Match starting
157
+ ["Match"]
158
+ when /^(Planted) at (.+?) \[(.)\]/, /^The Terrorists have completed the objective!/ # TeroWin
159
+ m = $~.captures
160
+ m[0] = "TerroristsWin"
161
+ m
162
+ when /^(Defused) at (.+?) \[(.)\]/, /^The Specops have completed the objective!/ # Specops Win
163
+ m = $~.captures
164
+ m[0] = "SpecopsWin"
165
+ m
166
+ when /Timelimit hit/
167
+ if gametype == :obj
168
+ ["SpecopsWin"]
169
+ else
170
+ ["UnknownWin"]
171
+ end
172
+ when /^\^7Overall stats for:/ # /^(\[skipnotify\])(\^.)(Overall stats for: |)/
173
+ if [:bc, :obj].include?(gametype)
174
+ ["UnknownWin"]
175
+ else
176
+ nil
177
+ end
178
+ when /\^.(.+?)\^. \^.(Totals) *([\d\-]+) +([\d\-]+) +([\d\-]+) +([\d\-]+)\^. *([\d\-]+)\^. *([\d\-]+)\^. *([\d\-]+)\^. *([\d\-]+)\^. *([\d\-]+)\^. *([\d\-]+)/ # Team total score?
179
+ r = $~.captures
180
+ r[0] << "Total"
181
+ m = [r[0]]
182
+ m << {
183
+ :name => r[1],
184
+ :kill => r[2].to_i,
185
+ :death => r[3].to_i,
186
+ :sui => r[4].to_i,
187
+ :tk => r[5].to_i,
188
+ :eff => r[6].to_i,
189
+ :aa => r[7].to_i,
190
+ :dg => r[8].to_i,
191
+ :dr => r[9].to_i,
192
+ :td => r[10].to_i,
193
+ :score => r[11].to_i,
194
+ :rate => (r[8].to_i-r[9].to_i)/100.0,
195
+ }
196
+ when /^(.+) renamed to (.+)/ # nick renamed?
197
+ m = $~.captures
198
+ m.map!{|n|n.gsub(/ +$/,"")[0..14]}
199
+ renames[m[1]] = renames[m[0]] || m[0]
200
+ nil
201
+ end
202
+ else
203
+ # Process player's score
204
+ if r[1].kind_of?(String)
205
+ r[1].gsub!(/ +$/,"")
206
+ r[1] = r[1][0..14]
207
+ r[1] = renames[r[1]] || r[1]
208
+ end
209
+ m = [r[0]]
210
+ m << {
211
+ :name => r[1],
212
+ :kill => r[2].to_i,
213
+ :death => r[3].to_i,
214
+ :sui => r[4].to_i,
215
+ :tk => r[5].to_i,
216
+ :eff => r[6].to_i,
217
+ :aa => r[7].to_i,
218
+ :dg => r[8].to_i,
219
+ :dr => r[9].to_i,
220
+ :td => r[10].to_i,
221
+ :score => r[11].to_i,
222
+ :rate => (r[8].to_i-r[9].to_i)/100.0,
223
+ }
224
+ end
225
+ end.compact
226
+
227
+ #pp orders
228
+
229
+ match_flag = false
230
+ game = Game.new(orders, gametype)
231
+ match = [""]
232
+ match_wins = nil
233
+ terrorists_total = nil
234
+ specops_total = nil
235
+ vm = Proc.new do |o|
236
+ case o[0]
237
+ when "Match"
238
+ if match_flag
239
+ if specops_total && match_wins
240
+ game.add_round specops_total, terrorists_total, match_wins
241
+ match_wins = nil
242
+ terrorists_total = nil
243
+ specops_total = nil
244
+ end
245
+ else
246
+ match_flag = true
247
+ end
248
+ if match[-1][0] == "Map"
249
+ game.add_map match[-1][1]
250
+ end
251
+ when "UnknownWin"
252
+ match_wins = :unknown if match_flag
253
+ when "TerroristsWin"
254
+ match_wins = :terrorists if match_flag
255
+ when "SpecopsWin"
256
+ match_wins = :specops if match_flag
257
+ when "Terrorists", "Specops"
258
+ if match_flag
259
+ unless game.players[o[1][:name]]
260
+ game.add_player(o[1][:name])
261
+ end
262
+ game.players[o[1][:name]].add_result(game.round_r+1,o[1])
263
+ end
264
+ when "TerroristsTotal"
265
+ terrorists_total = o[1] if match_flag
266
+ when "SpecopsTotal"
267
+ if match_flag
268
+ specops_total = o[1]
269
+ match_wins = compare_score(terrorists_total, specops_total) if match_wins == :unknown
270
+ end
271
+ end
272
+ match << o
273
+ end
274
+ orders.each(&vm)
275
+
276
+ vm.call(["Match"]) if match_flag
277
+ game
278
+ end
279
+
280
+ def self.compare_score(terrorists, specops)
281
+ if terrorists[:score] > specops[:score]
282
+ :terrorists
283
+ else
284
+ :specops
285
+ end
286
+ end
287
+ end
288
+
289
+