screp: insert replay data directly into database
[perl/schtarr.git] / screp
diff --git a/screp b/screp
index 910f3d2f5ad4bc465805883e2b9589d1f641aa65..8fb41f5b4e5c03749660151d8857665479c58cde 100755 (executable)
--- a/screp
+++ b/screp
@@ -3,11 +3,19 @@ use strict;
 use warnings;
 use Data::Dumper;
 
+our $VERSION = '1.01';
+
 my $SHOWWARN = 0;
+my $APMSVG = undef;
+my $DBNAME = undef;
+my $DBGAME = undef;
 
-use Getopt::Long;
+use Getopt::Long qw(:config bundling auto_version auto_help);
 GetOptions(
        "verbose|v!" => \$SHOWWARN,
+       "apm|a=s" => \$APMSVG,
+       "dbname|D=s" => \$DBNAME,
+       "dbid|d=s" => \$DBGAME,
 );
 
 use constant { APM_FIRSTFRAME => 80 / .042 };
@@ -455,7 +463,7 @@ for (0 .. 11) {
        my $number = shift @playdata;
        defined $player[$number] and warn "Player #$number redefined";
        my ($data) = unpackhash("VcccZ25", shift @playdata, qw(
-               slot type race unknown name
+               slot type race team name
        ));
        defined $race[$_] ? ($data->{race} = $race[$_]) :
                warn "Unknown race #$_ for player $number"
@@ -516,11 +524,14 @@ for (@$map) {
 }
 
 for my $player (sort keys %stats) {
+       $stats{$player}{$_} = $player[$slot[$player]]{$_}
+               for keys %{ $player[$slot[$player]] };
        my $row = $stats{$player};
        $row->{last} ||= $map->[-1][0];
 #      printf("%-16s%6d actions (%3d micro,%4d macro);%4d APM\n",
        my $name = showplayer($player);
-       printf("%-16s%6d actions in%7d frames (%s) = %d APM\n",
+       printf("%d %-16s%6d actions in%7d frames (%s) = %d APM\n",
+               $row->{slot},
                $name, $row->{actions}, $row->{last},
                showtime($row->{last}),
 #              $row->{micro} / $row->{last} * 60 / .042 * 1.05,
@@ -557,3 +568,173 @@ for my $player (sort keys %stats) {
        ) if 0;
 }
 
+use Games::StarCraft::DB;
+my $Db = Games::StarCraft::DB->connect({RaiseError => 1})
+       or die "No database: $DBI::errstr\n";
+sub findaccount ($) {
+       my ($name) = @_;
+       my $query = $Db->query(q{
+               SELECT DISTINCT account FROM play
+               WHERE name = ? AND account IS NOT NULL
+       }, $name);
+       return $query->rows == 1 ? $query->list : undef;
+}
+
+if ($DBGAME) {{
+       print "\n";
+       my $game = $Db->query("SELECT * FROM game WHERE id=?", $DBGAME)->hash;
+       if (not $game) {
+               printf "Database game # %d not found\n", $DBGAME;
+               last;
+       }
+       if ($game->{map} ne $head->{map}) {
+               printf "Replay map (%s) does not match database map (%s)\n",
+                       $head->{map}, $game->{map};
+               last;
+       }
+
+       $Db->begin;
+       $Db->insert("game", {
+               frames => $head->{frames},
+#              map => $head->{map},
+#              start => time2str('%Y-%m-%d %X', $head->{time}),
+       #       endreplay => time2str('%Y-%m-%d %X', $repstats[9]), # mtime
+#              durationguess => \"endreplay - start",
+       });
+       $Db->update("play", {
+               name => $_->{name}, #TODO: --force
+               race => $_->{race}, #      --force
+               apm => $_->{gameactions} / ($_->{last} - APM_FIRSTFRAME) * 60 / .042,
+               team => $_->{team},
+               color => $_->{color},
+       }, {
+               game => $DBGAME,
+               slot => $_->{slot},
+       }) for values %stats;
+       $Db->commit;
+}}
+
+if ($DBNAME) {
+       print "\n";
+       my @repstats = stat $DBNAME or die "no rep: $!\n";
+       my ($name) = $DBNAME =~ m{.*/([^.]+)};
+
+       my %placetxt = (
+               bn => "bnet",
+               gr => "groningen",
+               md => "mdhq",
+       );
+       my ($placeid) = $name =~ /.*([a-z]{2})/;
+       my $place = defined $placetxt{$placeid} ? $placetxt{$placeid} : undef;
+
+       my $winslot;
+       if (@ARGV == 1 and $ARGV[0] =~ /^\d$/) {
+               $winslot = $ARGV[0];
+       }
+
+       $Db->begin;
+       $Db->insert("game", {
+               name => $name,
+               frames => $head->{frames},
+               map => $head->{map},
+               start => time2str('%Y-%m-%d %X', $head->{time}),
+               endreplay => time2str('%Y-%m-%d %X', $repstats[9]), # mtime
+#              durationguess => \"endreplay - start",
+               place => $place,
+       });
+       my $gameid = $Db->last_insert_id((undef)x4, {sequence => "game_id_seq"});
+       $Db->update("game", {durationguess => \"endreplay - start"}, {id => $gameid});
+       $Db->insert("play", {
+               game => $gameid,
+               slot => $_->{slot},
+               name => $_->{name},
+               race => $_->{race},
+               apm => $_->{gameactions} / ($_->{last} - APM_FIRSTFRAME) * 60 / .042,
+               team => $_->{team},
+               color => $_->{color},
+               account => findaccount($_->{name}),
+               result => defined $winslot ? $_->{slot} == $winslot ? 1 : -1 : 0,
+       }) for values %stats;
+       $Db->commit;
+}
+
+if ($APMSVG) {
+       my @seq;  # player => time (s) => actions
+       $seq[$_->[1]][$_->[0] * .042]++ for @$map;
+       my $flatten = 120;
+       my @apm;
+       for my $player (0 .. $#seq) {
+               my $range = 0;
+                  $range += $seq[$player][$_] || 0 for 0 .. $flatten - 1;
+               my $leadfill = $range / $flatten;
+               for my $frame (0 .. $#{$seq[$player]}) {
+                       $range += $seq[$player][$frame] || 0;
+                       $range -= $frame < $flatten ? $leadfill :
+                               $seq[$player][$frame - $flatten] || 0;
+                       $apm[$player][$frame] = $range / $flatten;
+               }
+       }
+
+       BEGIN { unshift @INC, '.' }
+       use SVG::TT::Graph::TimeSeries;
+       my $graph = SVG::TT::Graph::TimeSeries->new({
+               height => 1200,
+               width => 1600,
+               style_sheet => "apm.css",
+               show_data_values => 0,
+               show_data_points => 0,
+               x_label_format => '%k:%M',
+               key => 1,
+               timescale_divisions => "5 minutes",
+       #       compress => 1,
+       });
+
+       for my $player (0 .. $#apm) {
+               $graph->add_data({
+                       data => [map {
+                               time2str('%Y-%m-%d %X', 946681200 + $_),
+                               $apm[$player][$_] * 60
+                       } 0 .. $#{$apm[$player]} ],
+                       title => showplayer($player),
+               });
+       }
+
+       my ($name) = $APMSVG =~ /([^.]+)/;
+       my $title = "APM timeline" . ($name && " for $name");
+       my $lead = sprintf "\n<title>%s</title>", $title;
+
+       my $svg = $graph->burn();
+       s/^[ \t\r]+\n//gm,  # remove lines with only whitespace (many useless ^M)
+       s/[ \t\r]+$//gm,    # trailing whitespace
+       s/ {4}\r*/\t/g,     # tabs for indenting
+       s/^(<svg width=")1600(" height=")1200("[^>]*>)/${1}100%${2}100%$3$lead/m,
+               for $svg; # cleanup xml
+
+       open my $apmfile, '>', "$APMSVG.svg";
+       print $apmfile $svg;
+}
+
+__END__
+
+=head1 NAME
+
+screp - StarCraft replay parser
+
+=head1 SYNOPSIS
+
+screp [options] < [replay data]
+
+ Options:
+   --verbose
+   --apm
+   --dbname
+   --dbid
+
+=head1 OPTIONS
+
+=head1 AUTHOR
+
+Mischa POSLAWSKY <perl@shiar.org>
+
+=head1 STUFF
+