view magestats.pl @ 71:3eca3030c175 misc

Added a simplistic Perl-based utility for generating Mage-guild related statistics and graphs in HTML + image formats from log file input.
author Matti Hamalainen <ccr@tnsp.org>
date Fri, 09 Apr 2010 10:49:31 +0000
parents
children bc05f9d391bb
line wrap: on
line source

#!/usr/bin/perl -w
use strict;
use Data::Dumper;
use GD::Graph;
use GD::Graph::linespoints;

my %spell_data = (
  "acid" => [ "acid blast",              "acid storm" ],
  "fire" => [ "lava blast",              "lava storm" ],
  "elec" => [ "electrocution",           "lightning storm" ],
  "pois" => [ "summon carnal spores",    "killing cloud" ],
  "cold" => [ "cold ray",                "hailstorm" ],
  "mana" => [ "golden arrow",            "magic eruption" ],
  "asph" => [ "blast vacuum",            "vacuum globe" ],
);

my $progName = "magestats.pl";
my $opt_cachefile;
my $opt_verbosity = 1;
my $opt_prefix = "magestats";
my $opt_imgfmt = "png";
my $opt_noinput = 0;
my $opt_width = 500;
my $opt_height = 250;

sub mlog($$)
{
  my $level = shift;
  my $msg = shift;
  print STDERR "* $msg\n" if ($opt_verbosity >= $level);
}

my $opt_mode = shift or die(
"Magestats v0.3 by ccr/TNSP (C) 2010 TNSP

Usage: $progName dump [options] < logfile
       $progName stats [options] < logfile

 -v                Verbose mode
 -c <cachefile>    Specify a cache file to restore from
 -p <prefix>       Output filename prefix ('$opt_prefix')
 -t <png|gif>      Image format to be used ('$opt_imgfmt')
 -s <WxH>          Graph dimensions in pixels ($opt_width x $opt_height)
 -n                No input (only handle cache, if specified)
");

die("Invalid operation mode '$opt_mode'!\n") unless ($opt_mode =~ /^(dump|stats)$/);

while (my $opt = shift) {
  if ($opt eq "-c") {
    # Restore cache from file
    $opt_cachefile = shift or die("-c option requires an argument.\n");
  } elsif ($opt eq "-v") {
    $opt_verbosity++;
  } elsif ($opt eq "-t") {
    $opt_imgfmt = shift or die("-t option requires an argument.\n");
    die("Invalid format '$opt_imgfmt' specified!\n") unless ($opt_imgfmt =~ /^(png|gif)$/);
  } elsif ($opt eq "-p") {
    $opt_prefix = shift or die("-p option requires an argument.\n");
  } elsif ($opt eq "-n") {
    $opt_noinput = 1;
  }
}


###
### Construct full data structure
###
mlog(1, "Initializing structures.");
my $spells = {};
foreach my $type (keys %spell_data) {
  my $src = $spell_data{$type};

  foreach my $spell (@{$src}) {
    $spells->{$spell}{"type"} = $type;
    $spells->{$spell}{"blasts"} = 0;
  }

  $spells->{$type}{"essence"}{"increase"} = 0;
  $spells->{$type}{"essence"}{"blasts"}{"single"} = [];
  $spells->{$type}{"essence"}{"blasts"}{"area"} = [];

  $spells->{$type}{"reagents"} = 0;

  $spells->{$type}{"single"} = $src->[0];
  $spells->{$type}{"area"} = $src->[1];
  
  push(@{$spells->{"single"}}, $src->[0]);
  push(@{$spells->{"area"}}, $src->[1]);
}

sub get_spell_type($)
{
  my $spell = $_[0];
  die("get_spell_type($spell): No such spell.\n") unless exists($spells->{$spell}{"type"});
  return $spells->{$spell}{"type"};
}


###
### Read cache
###
if (defined($opt_cachefile)) {
  mlog(1, "Restoring cache from '$opt_cachefile'.");
  open(CACHE, "<", $opt_cachefile) or die("Could not open cache file '$opt_cachefile'!\n");
  my $s = <CACHE>;
  close(CACHE);
  eval $s;
}


###
### Scan input for blasts etc.
###
my $match_single = join("|", @{$spells->{"single"}});
my $match_area = join("|", @{$spells->{"area"}});
my $essence_flag = 0;
my $last_spell = "";

if ($opt_noinput) {
 
} else {
mlog(1, "Parsing log from stdin...");
while (defined(my $s = <STDIN>)) {
  if ($s =~ /^You watch with selfpride as your ($match_single) hits / || $s =~ /^You hit .+ with your ($match_area)\.$/) {
    $last_spell = $1;

    $spells->{$last_spell}{"blasts"}++;
    $spells->{"total"}{"blasts"}++;

    if ($essence_flag) {
      my $type = get_spell_type($last_spell);
      $spells->{$type}{"essence"}{"increase"}++;

      foreach my $class ("single", "area") {
        my $name = $spells->{$type}{$class};
        push(@{$spells->{$type}{"essence"}{"blasts"}{$class}}, $spells->{$name}{"blasts"});
        
#        foreach my $crit ("1", "2", "3", "") {
#          push(@{$spells->{$type}{"essence"}{"crits_".$crit}{$class}}, $spells->{$name}{"crits_".$crit});
#        }
      }

      $essence_flag = 0;
    }
  } elsif ($s =~ /^Your knowledge in elemental powers helps you to save the reagent for further use\./) {
    $spells->{get_spell_type($last_spell)}{"reagents"}++;
    $spells->{"total"}{"reagents"}++;
  } elsif ($s =~ /^You feel your skills in handling elemental forces improve\./) {
    $essence_flag = 1;
    $spells->{"total"}{"essence"}++;
  }
}
}


###
### Dump cache to stdout
###
if ($opt_mode eq "dump") {
  $Data::Dumper::Indent = 0;
  $Data::Dumper::Useqq  = 1;
  $Data::Dumper::Purity = 1;
  
  my $dumper = Data::Dumper->new([$spells], ["spells"]);
  print $dumper->Dump();
  exit;
}


###
### Output statistics
###
if ($opt_mode eq "stats") {
my $filename = $opt_prefix.".html";
open(OUT, ">", $filename) or die("Could not create output file '$filename'.\n");

mlog(1, "Outputting stats HTML to '$filename'");

print OUT qq|
<html>
<head>
 <title>Mage statistics</title>
</head>
<body>

<h1>Mage statistics</h1>

<table border=\"1\">
<tr>
 <th>Type</th>
 <th>Blasts</th>
 <th>Reagents saved</th>
 <th>Essence gained</th>
 <th>Blasts per essence gain</th>
</tr>
|;

foreach my $type (sort { $a cmp $b } keys %spell_data) {
  my $s = $spells->{$type}{"single"};
  my $a = $spells->{$type}{"area"};
  
  print OUT "<tr><td>$type</td><td>
  <table border=\"1\"><th></th><th>Single</th><th>Area</th></tr>
  <tr><th>Name</th><td>$s</td><td>$a</td></tr>
  <tr><th>Blasts</th><td>".$spells->{$s}{"blasts"}."</td><td>".$spells->{$a}{"blasts"}."</td></tr>
  </table>";

  my $total_blasts = $spells->{$s}{"blasts"} + $spells->{$a}{"blasts"};
  
  printf OUT "</td><td><b>%d</b> (%1.2f %%)</td><td>%d</td><td>",
  $spells->{$type}{"reagents"},
  ($total_blasts > 0) ? ($spells->{$type}{"reagents"} * 100.0) / $total_blasts : 0.0,
  $spells->{$type}{"essence"}{"increase"};
  
  if (exists($spells->{$type}{"essence"}{"blasts"})) {
    print OUT "<img src=\"".$opt_prefix."_".$type.".".$opt_imgfmt."\" alt=\"?\" />";
  }
  
  print OUT "
  </td>
  </tr>\n";
}

print OUT "</table>

<p>
Total blasts: <b>".$spells->{"total"}{"blasts"}."</b><br />
Total reagents saved: <b>".$spells->{"total"}{"reagents"}."</b><br />
Total essence gained: <b>".$spells->{"total"}{"essence"}."</b><br />
</p>

</body>
</html>
";

mlog(1, "Outputting graphs '".$opt_prefix."_*.".$opt_imgfmt."'...");

foreach my $type (sort { $a cmp $b } keys %spell_data) {
  my $s = $spells->{$type}{"single"};
  my $a = $spells->{$type}{"area"};
  
  my $graph = GD::Graph::linespoints->new($opt_width, $opt_height);
  $graph->set(
    y_label => 'Blasts',
    x_label => 'Essence gained', 
  );

  my @titles = ();
  my @total = ();
  for (my $i = 1; $i <= $spells->{$type}{"essence"}{"increase"}; $i++) {
    push(@titles, "+".$i);
  }

  for (my $i = 0; $i < $spells->{$type}{"essence"}{"increase"}; $i++) {
    push(@total, $spells->{$type}{"essence"}{"blasts"}{"single"}[$i] + $spells->{$type}{"essence"}{"blasts"}{"area"}[$i]);
  }
  
  my @data = (
    \@titles,
    $spells->{$type}{"essence"}{"blasts"}{"single"},
    $spells->{$type}{"essence"}{"blasts"}{"area"},
    \@total,
  );
  
  my $gd = $graph->plot(\@data) or mlog(0, "Error creating graph ($type): ".$graph->error);

  if ($gd) {
    open(IMG, ">", $opt_prefix."_".$type.".".$opt_imgfmt) or die("Error openiong file ".$!."\n");
    binmode IMG;
    print IMG $gd->gif if ($opt_imgfmt eq "gif");
    print IMG $gd->png if ($opt_imgfmt eq "png");
    close IMG;
  }
}

}