#!/usr/bin/perl -wT use strict; $SIG{ __DIE__ } = sub { require Carp; Carp::confess(@_) }; use lib '..'; use robliterator::database; use robliterator::robot; use robliterator::board; use IO::Socket; use IO::Select; our $timeout = 5; our $maxLogs = 50; my $database = new robliterator::database; my $filename = "$robliterator::database::filebase.board"; my $board = new robliterator::board($filename); my %cachedStatus = (); sub loop { warn "\n\n" . scalar localtime . "\n\n"; # add pending robots my $pending = $database->selectall_arrayref('SELECT url FROM pending'); foreach my $row (@$pending) { $row = $row->[0]; } my @added = (); my $id = $database->selectall_arrayref('SELECT MAX(CAST(robot AS INTEGER)) FROM scores')->[0]->[0]; $id = 0 unless defined $id; @added = $board->addRobots($id+1, @$pending); if ($board->hasWork) { # (will be true if @added > 0) warn $board->getStateMessage(); warn "contacting robots...\n"; getMessages($board); warn "\n"; warn "running...\n"; # process messages foreach my $robot ($board->robots) { my $message; if ($robot->data->{message} =~ m/\r\n\r\n([-a-z]+)/os) { $message = $1; } else { $message = 'error'; } # warn "robot ", $robot->domain, " $message:\n", $robot->data->{message}, "\n"; # reset actions $robot->setMovement(0, 0); $robot->setFire(0, 0); # parse actions if ($message eq 'move-north') { $robot->setMovement(0, -1); } elsif ($message eq 'move-east') { $robot->setMovement(1, 0); } elsif ($message eq 'move-south') { $robot->setMovement(0, 1); } elsif ($message eq 'move-west') { $robot->setMovement(-1, 0); } elsif ($message eq 'fire-north') { $robot->setFire(0, -1); } elsif ($message eq 'fire-east') { $robot->setFire(1, 0); } elsif ($message eq 'fire-south') { $robot->setFire(0, 1); } elsif ($message eq 'fire-west') { $robot->setFire(-1, 0); } elsif ($message eq 'suicide') { $robot->setRespawn(0); # board takes care of it } elsif ($message ne 'idle') { $message = 'error'; } $robot->data->{lastAction} = $message; $robot->data->{lastResult} = $robliterator::robot::actionIdled; } # run the board $board->run(); warn "saving...\n"; # save board $board->save($filename); # remove mention of pending robots if (@added) { my $statement; $statement = $database->prepare("INSERT INTO scores (robot, domain) VALUES (?, ?)"); foreach my $robot (@added) { $statement->execute($robot->id, $robot->domain); } $statement = $database->prepare('DELETE FROM pending WHERE url=?'); foreach my $robot (@added) { $statement->execute($robot->url); } } # update scores my @robots = $board->robots; if (@robots) { my @values = (); foreach my $property (@robliterator::robot::properties) { push(@values, "$property = ?"); } local $" = ', '; my $statement = $database->prepare("UPDATE scores SET @values WHERE robot=?"); foreach my $robot (@robots) { $statement->execute($robot->values, $robot->id); } } return 1; } return 0; } while (1) { if (loop()) { warn "pruning...\n"; pruneLogs($filename, $maxLogs); warn "caching...\n"; cacheStatus($filename); } warn "sleeping...\n"; sleep 2; } sub getMessages { my($board) = @_; my $connections1 = IO::Select->new(); my $connections2 = IO::Select->new(); my $connectionRobotMap = {}; foreach my $robot ($board->robots) { my $socket = IO::Socket::INET->new(PeerHost => $robot->domain, PeerPort => 'http(80)', Proto => 'tcp', Blocking => 0); if ($socket) { $connectionRobotMap->{$socket->fileno} = $robot; $connections1->add($socket); } $robot->data->{message} = ''; warn " + $robot (" . $robot->domain . ")\n"; } my $time1 = time(); while ($connections1->count > 0 and time() - $time1 < $timeout) { foreach my $socket ($connections1->can_write(1)) { my $robot = $connectionRobotMap->{$socket->fileno}; my $id = $robot->id; my $path = $robot->path || '/'; my $host = $robot->domain; my $body = $board->getStateMessage($id); my $length = length($body); my $message = <domain . ") using " . $socket->fileno . ":\n$message\n\n"; if ($socket->syswrite($message)) { $connections2->add($socket); } else { $socket->close(); } $connections1->remove($socket); } } my $time2 = time(); while ($connections2->count > 0 and time() - $time2 < $timeout) { foreach my $socket ($connections2->can_read(1)) { my $s = ''; if ($socket->sysread($s, 4096) > 0) { $connectionRobotMap->{$socket->fileno}->data->{message} .= $s; } else { $connections2->remove($socket); $socket->close(); } } } foreach my $socket ($connections1->handles, $connections2->handles) { $socket->close(); } } sub pruneLogs { my($filename, $maxLogs) = @_; my @files = sort glob("$filename.*"); while (scalar(@files) > $maxLogs) { (shift @files) =~ m/(.*)/os; unlink $1; if (exists $cachedStatus{$1}) { delete $cachedStatus{$1}; } } } sub cacheStatus { my($filename) = @_; open(STATUS, '>', 'status.xml.new') or return; print STATUS ''; # past boards my @files = sort glob("$filename.*"); foreach my $file (sort @files) { if (not exists $cachedStatus{$file}) { my($s, undef) = convertBoardFileToXML($file); $cachedStatus{$file} = $s; } print STATUS $cachedStatus{$file}; } # current board my($s, $time) = convertBoardFileToXML($filename); print STATUS $cachedStatus{"$filename.$time"} = $s; # lifetime high scores print STATUS getHighScoreXML('kills'); print STATUS getHighScoreXML('lifetime'); print STATUS getHighScoreXML('distance'); print STATUS ''; close(STATUS); rename('status.xml.new', 'status.xml'); } sub convertBoardFileToXML { my($file) = @_; open(FILE, '<', $file) or return ''; my $time = (stat FILE)[9]; my %robots = (); while (defined($_ = ) and chomp $_, $_ ne '') { my @row = split; if ($row[1] =~ m|^http://([^/]+)|osi) { $row[1] = $1; } $robots{$row[0]} = \@row; } my $id; if ($file =~ m/\.([0-9]+$)/os) { $id = $1; } else { $id = $time; } my $eid = escapeXML($id); my $s = ""; while (defined($_ = ) and chomp $_, $_ ne '') { my @row = split; $s .= ''; foreach my $cell (@row) { my $e = escapeXML($cell); if (exists $robots{$cell}) { my $domain = escapeXML($robots{$cell}->[1]); $s .= "[$i]); $s .= "'"; $i += 1; } $s .= ">$e"; } else { $s .= "$e"; } } $s .= ''; } $s .= ''; close(FILE); return ($s, $time); } sub getHighScoreXML { my($name) = @_; my $results = $database->selectall_arrayref("SELECT domain, $name, alive, robot FROM scores ORDER BY CAST($name AS INTEGER) DESC"); $name = escapeXML($name); my $s = ""; my %domains = (); foreach my $row (@$results) { next if ($domains{$row->[0]}); my $domain = escapeXML($row->[0]); my $value = escapeXML($row->[1]); my $alive = escapeXML($row->[2]); my $id = escapeXML($row->[3]); $s .= ""; $domains{$row->[0]} = 1; } $s .= '
'; return $s; } sub escapeXML { my($s) = @_; for ($s) { s/&/&/gos; s//>/gos; s/"/"/gos; s/'/'/gos; } return $s; }