File Coverage

blib/lib/Games/Quake/Stats.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package Games::Quake::Stats;
2              
3             ###################################################################################
4             #
5             # This module provides simple mechanisms for collecting and displaying game
6             # statisitics for the Quake, Quake2, Quake2world, and Quake 3 games. It works
7             # by reading the fraglog file created by Quake servers.
8             #
9             # You can specify the fraglog file when the object is constructed, the module
10             # compiles statistics for each player that appears in the log.
11             #
12             # The Games::Quake::Stats module can create simple bar charts showing
13             # the relative statistics of each player, and can generate textual and pre-
14             # formed HTML output (HTML output shows the graphs created).
15             #
16             ###################################################################################
17              
18 1     1   33735 use strict;
  1         2  
  1         37  
19 1     1   5 use warnings;
  1         3  
  1         25  
20              
21 1     1   591 use Games::Quake::Player;
  1         3  
  1         24  
22 1     1   558 use GD::Graph::hbars;
  0            
  0            
23             use GD::Graph::colour;
24             use Carp;
25              
26              
27             require Exporter;
28              
29             our @ISA = qw(Exporter);
30              
31             # Items to export into callers namespace by default. Note: do not export
32             # names by default without a very good reason. Use EXPORT_OK instead.
33             # Do not simply export all your public functions/methods/constants.
34              
35             # This allows declaration use Games::Quake::Stats ':all';
36             # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
37             # will save memory.
38             our %EXPORT_TAGS = ( 'all' => [ qw(
39            
40             ) ] );
41              
42             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
43              
44             our @EXPORT = qw(
45            
46             );
47              
48             our $VERSION = '0.05';
49              
50              
51             # Preloaded methods go here.
52              
53             ##################################################
54             # object constructor
55             #
56             sub new {
57             my $invocant = shift;
58             my $class = ref($invocant) || $invocant;
59             my $self = {
60             _stats_graph => undef,
61             _skill_graph => undef,
62             _frag_log => undef,
63             _frag_data => [],
64             _players => {},
65             @_, # Override previous attributes
66             };
67            
68             bless $self, $class;
69              
70             if($self->{_frag_log}){
71             $self->get_frag_data($self->{_frag_log});
72             $self->initialize();
73             }
74             else{
75             $self->initialize();
76             }
77            
78             return bless $self, $class;
79             }
80              
81              
82            
83             sub initialize{
84              
85             my ($self) = @_;
86             my $frag_data = $self->{_frag_data};
87              
88             foreach my $frag (@$frag_data){
89            
90             my $fragger = $frag->[0];
91             my $fraggee = $frag->[1];
92            
93             # fraggee
94             my $fragged_player = Player->new(
95             _name => $fraggee,
96             );
97            
98             my $found_fraggee = $self->{_players}->{$fraggee};
99             if(!$found_fraggee){
100             $self->{_players}->{$fraggee} = $fragged_player;
101             $fragged_player->inc_times_fragged();
102             }
103             else{
104             $found_fraggee->inc_times_fragged();
105             }
106            
107             # player
108             my $player = Player->new(
109             _name => $fragger,
110             );
111            
112             my $found_player = $self->{_players}->{$fragger};
113             if(!$found_player){
114             $self->{_players}->{$fragger} = $player;
115             $player->update_stats($fraggee);
116             }
117             else{
118             $found_player->update_stats($fraggee);
119             }
120             }
121            
122             foreach my $player_name (keys %{$self->{_players}}){
123             my $player = $self->{_players}->{$player_name};
124             my $total_frags = $player->total_frags();
125             my $times_fragged = $player->times_fragged();
126            
127             if($times_fragged == 0){
128             $times_fragged = 1; # avoid divide by zero
129             }
130            
131             $player->{_skill} = $total_frags/$times_fragged;
132             }
133              
134             }
135              
136              
137              
138              
139             ########################################################################
140             #
141             # Subroutines
142             #
143             ########################################################################
144              
145              
146              
147              
148             #-----------------------------------------------------------------------
149             #
150             # get_player
151             #
152             #-----------------------------------------------------------------------
153             sub get_player{
154             my ($self, $player_name) = @_;
155              
156             my $players = $self->{_players};
157              
158             return $players->{$player_name};
159             }
160              
161              
162              
163             #-----------------------------------------------------------------------
164             #
165             # times_fragged
166             #
167             #-----------------------------------------------------------------------
168             sub times_fragged{
169             my ($self, $player_name1, $player_name2) = @_;
170            
171             my $player1 = $self->{_players}->{$player_name1};
172            
173             if(!$player1){
174             croak "times_fragged: no such player ($player_name1)\n";
175             }
176            
177             if(!$player_name2){
178             return $player1->times_fragged();
179             }
180             else{
181             my $player2 = $self->{_players}->{$player_name2};
182            
183             if(!$player2){
184             croak "times_fragged: no such player ($player_name2)\n";
185             }
186             return $player1->times_fragged_player($player_name2);
187             }
188             }
189              
190              
191             #-----------------------------------------------------------------------
192             #
193             # total_frags
194             #
195             #-----------------------------------------------------------------------
196             sub total_frags{
197             my ($self, $player_name) = @_;
198              
199             my $player = $self->{_players}->{$player_name};
200            
201             if(!$player){
202             croak "total_frags: no such player ($player_name)\n";
203             }
204              
205             return $player->total_frags();
206             }
207              
208              
209              
210             #-----------------------------------------------------------------------
211             #
212             # skill_level
213             #
214             #-----------------------------------------------------------------------
215             sub skill_level{
216             my ($self, $player_name) = @_;
217              
218             my $player = $self->{_players}->{$player_name};
219            
220             if(!$player){
221             croak "total_frags: no such player ($player_name)\n";
222             }
223              
224             my $total_frags = $player->total_frags();
225             my $times_fragged = $player->times_fragged();
226              
227             return $total_frags/$times_fragged;
228             }
229              
230              
231              
232              
233              
234             #-----------------------------------------------------------------------
235             #
236             # generate_text
237             #
238             #-----------------------------------------------------------------------
239             sub generate_text{
240              
241             my ($self) = @_;
242            
243             my $players = $self->{_players};
244              
245             print "frag log statistics\n";
246              
247             foreach my $player_name (keys %$players){
248             my $player = $players->{$player_name};
249             print "Player: " . $player->name() . ", total_frags: " . $player->total_frags() . "\n";
250             foreach my $player_fragged_name (keys %$players){
251             my $player_fragged = $players->{$player_fragged_name};
252             print " " . $player_fragged->name() . " " . $player->times_fragged_player($player_fragged->name()) . "\n";
253             }
254             }
255             return 1;
256             }
257              
258              
259              
260             #-----------------------------------------------------------------------
261             #
262             # generate_graph
263             #
264             #-----------------------------------------------------------------------
265             sub generate_stats_graph
266             {
267             my ($self, $graph_file) = @_;
268             my $players = $self->{_players};
269              
270             if(!$graph_file){
271             $graph_file = $self->{_stats_graph};
272             }
273              
274             my $data_ref = [];
275             my @player_names;
276             my $max_y = 0;
277            
278             push(@player_names, "total (- self-inflicted)");
279              
280             foreach my $player_name (sort(keys %$players)){
281              
282             my $player = $players->{$player_name};
283              
284             push(@player_names, $player->name());
285             push(@{$data_ref->[0]}, $player->name());
286              
287             my $total_frags = $player->total_frags();
288             push(@{$data_ref->[1]}, $total_frags);
289             if ($total_frags > $max_y){
290             $max_y = $total_frags;
291             }
292              
293             my $i = 2;
294             foreach my $player_fragged_name (sort(keys %$players)){
295              
296             my $player_fragged = $players->{$player_fragged_name};
297             my $times_fragged_player = $player->times_fragged_player($player_fragged->name());
298            
299             push(@{$data_ref->[$i]}, $times_fragged_player);
300             $i++;
301             }
302             }
303            
304             my $my_graph = GD::Graph::hbars->new(550,550);
305            
306             $my_graph->set(
307             x_label => 'player',
308             y_label => 'frags',
309             title => 'manliness',
310             bar_spacing => 1,
311             bargroup_spacing => 10,
312             legend_spacing => 3,
313             legend_placement => 'RT',
314             show_values => 1,
315             y_max_value => $max_y + int($max_y/10),
316             #x_label_position => 0,
317             dclrs => [ ( "orange", "lgreen", "#0050FF", "dgreen", "#00BBBB",
318             "dblue", "dred", "blue", "dpurple", "lgray" ) ],
319             ) or warn $my_graph->error;
320              
321              
322             $my_graph->set_legend(@player_names);
323             $my_graph->plot($data_ref) or croak $my_graph->error;
324             my $ext = $my_graph->export_format;
325             my $outfile;
326             open($outfile, ">$graph_file") or croak "Could not open $graph_file: $!\n";
327             binmode $outfile;
328             print $outfile $my_graph->gd->$ext();
329             close $outfile;
330            
331             }
332              
333              
334             #-----------------------------------------------------------------------
335             #
336             # generate_skill_graph
337             #
338             #-----------------------------------------------------------------------
339             sub generate_skill_graph
340             {
341             my ($self, $skill_graph_file) = @_;
342             my $players = $self->{_players};
343              
344             if(!$skill_graph_file){
345             $skill_graph_file = $self->{_skill_graph};
346             }
347              
348             my $data_ref = [];
349             my @player_names;
350             my $max_y = 0;
351            
352             push(@player_names, "skill (frags/fragged * 100)");
353              
354             foreach my $player_name (sort(keys %$players)){
355            
356             my $player = $players->{$player_name};
357             push(@player_names, $player_name);
358             push(@{$data_ref->[0]}, $player_name);
359             my $skill = sprintf("%0.2f", $player->{_skill} * 100);
360             push(@{$data_ref->[1]}, $skill);
361             if ($skill > $max_y){
362             $max_y = $skill;
363             }
364             }
365            
366             my $my_graph = GD::Graph::hbars->new(550,550);
367            
368             $my_graph->set(
369             x_label => 'player',
370             y_label => 'skill (% frags/fragged)',
371             title => 'skill',
372             bar_spacing => 1,
373             bargroup_spacing => 50,
374             legend_spacing => 5,
375             legend_placement => 'RT',
376             show_values => 1,
377             y_max_value => $max_y + int($max_y/10),
378             #x_label_position => 0,
379             dclrs => [ ( "#017797", "dpurple", "dred", "dgreen", "blue", "green",
380             "lblue", "dgray", "dgreen", "dblue", "marine" ) ],
381             ) or warn $my_graph->error;
382            
383              
384             $my_graph->set_legend(@player_names);
385             $my_graph->plot($data_ref) or croak $my_graph->error;
386             my $ext = $my_graph->export_format;
387             my $outfile;
388              
389             open($outfile, ">$skill_graph_file") or croak "Could not open $skill_graph_file: $!\n";
390             binmode $outfile;
391            
392             print $outfile $my_graph->gd->$ext();
393             close $outfile;
394             }
395              
396              
397              
398              
399             #-----------------------------------------------------------------------
400             #
401             # generate_html
402             #
403             #-----------------------------------------------------------------------
404             sub generate_html{
405              
406             my ($self, $graph_base_url) = @_;
407              
408             my $players = $self->{_players};
409              
410             my $graph_file = $self->{_stats_graph};
411             my $skill_graph_file = $self->{_skill_graph};
412             my $graph_file_short;
413             my $skill_graph_file_short;
414              
415             if($graph_file){
416             my @path_components = split('/', $graph_file);
417             $graph_file_short = pop(@path_components);
418             }
419             if($skill_graph_file){
420             my @path_components = split('/', $skill_graph_file);
421             $skill_graph_file_short = pop(@path_components);
422             }
423            
424             print "\n";
425             print "frag log statistics\n";
426             print "\n";
427             print "

frag log statistics

";
428             print "
\n";
429             if($graph_file_short){
430             print "\n";
431             }
432             print "\n";
433             if($skill_graph_file_short){
434             print "\n";
435             }
436             print "
\n";
437             print "

the numbers don't lie
";
438             print "
\n"; 
439              
440              
441             foreach my $player_name (sort(keys %$players)){
442            
443             my $player = $players->{$player_name};
444             my $total_frags = $player->total_frags();
445             my $times_fragged = $player->times_fragged();
446             my $name = $player->name();
447             my $skill = $player->{_skill};
448             my $skill_str = sprintf("%0.2f", $skill * 100);
449              
450             print "
$name: total_frags: $total_frags\n";
451             print " times fragged: $times_fragged\n";
452             print " skill (total_frags/times_fragged): $skill_str\n";
453              
454             foreach my $player_fragged_name (keys %$players){
455              
456             my $player_fragged = $players->{$player_fragged_name};
457              
458             if($player_fragged->name() eq $player->name()){
459             print " " . $player_fragged->name() . " " . $player->times_fragged_player($player_fragged->name()) . " (self-inflicted)\n";
460             }
461             else{
462             print " " . $player_fragged->name() . " " . $player->times_fragged_player($player_fragged->name()) . "\n";
463             }
464             }
465             }
466              
467            
468             print "\n";
469             print "\n";
470             print "\n";
471              
472             return 1;
473             }
474              
475              
476             #-----------------------------------------------------------------------
477             #
478             # get_frag_data
479             #
480             #-----------------------------------------------------------------------
481             sub get_frag_data
482             {
483             my ($self, $in_file) = @_;
484              
485             open(READF, "<$in_file") || croak "Can't open input file: $in_file. $!";
486              
487             my @lines = ;
488             my @frags;
489              
490             my $line_num = 0;
491             my $orig_line;
492              
493              
494             foreach my $line (@lines){
495              
496             $orig_line = $line;
497             $line_num++;
498            
499              
500             # strip off the leading \ in a frag line: "\pigvana\ShovelTooth\\n" becomes "pigvana\ShovelTooth\\n"
501             $line =~ s/^(\s\s*)*\\//;
502             # strip off the trailing \\n in a frag line: "pigvana\ShovelTooth\\n" becomes "pigvana\ShovelTooth"
503             $line =~ s/\\\n$//;
504             my @names = split(/\\/, $line);
505              
506            
507              
508             if(my $num_names = @names != 2){
509             croak "Bad log file- format unknown: (line $line_num) '$orig_line'\n";
510             }
511              
512             push (@frags, \@names);
513             }
514            
515             $self->{_frag_data} = \@frags;
516            
517             return @frags;
518             }
519              
520              
521              
522             1; # load successful
523              
524              
525             __END__