<?php
// Bot info
$key = '########################';
$version = '0.1';
// Get the library
require 'SG_WBB.php';
// Turn handler
function wopr_turnHandler( SG_WBB_Bot $bot ) {
if( !isset($bot->angle) ) {
$bot->angle = rand(0,360);
$bot->targets = array();
$bot->priv_armor = $bot->getArmor();
}
if( $bot->getArmor() != $bot->priv_armor ) {
// TODO: Move out of here!
// What should happen to angle? try and recalc, reset or keep?
// Very poor evasive action impl. in the form of a random now coord
$bot->drive( rand(0,99), rand(0,99) );
$bot->priv_armor = $bot->getArmor();
}
if( count($bot->targets) > 0 ) {
// We know targets from last round
foreach( $bot->targets AS $k => $t ) {
$fire = $bot->fire( $t->getAngle(), 1 );
if( $fire == 0 ) {
// Target is no longer there
unset($bot->targets[$k]);
// Look 15 degrees prior to its last know angle
$bot->angle = $t->getAngle() - 15;
}
}
}
// Look for targets
while( count( $bot->targets ) == 0 && $bot->getEnergy() >= 7 ) {
$scan = $bot->scan( $bot->angle );
$bot->angle = ($bot->angle + 10) % 360;
if( $scan ) {
$bot->targets += $scan;
}
}
// Shoot at known targets
if( count($bot->targets) > 0 ) {
foreach( $bot->targets AS $t ) {
$bot->fire( $t->getAngle(), floor($bot->getEnergy() / count($bot->targets)) );
}
}
}
// Setup
SG_WBB::setKey($key);
SG_WBB::setBotVersion($version);
SG_WBB::setTurnHandler( 'wopr_turnHandler' );
//SG_WBB::setLogLevel( SG_WBB::LL_ALL ^ SG_WBB::LL_WS );
// Run
SG_WBB::takeTurn();
The latest version of SG_WBB can be found at http://github.com/fangel/sg_wbb/tree/master
<?php
/**
* SG_WBB is a class containing a lot of useful static functions for
* dealing with Paul Reinheimers Web Bot Battle
*
* Just register a turn handler function that takes a SG_WBB_Bot as its
* single argument
*
* @author Morten Fangel (C) 2008
* @license Creative Commons Share-Alike Attribution
*/
class SG_WBB {
const SG_WBB_VERSION = '0.3.1b';
const LL_NONE = 0;
const LL_DEBUG = 1;
const LL_NOTICE = 2;
const LL_ERROR = 4;
const LL_USER = 8;
const LL_WS = 16;
const LL_ALL = 31; // All of the LL_* consts added.
protected static $turnHandler = null;
protected static $key = '';
protected static $botVersion = '0.0';
protected static $gameId = '';
protected static $callType = false;
protected static $bot = null;
protected static $useErrorHandler = true;
protected static $logLevel = SG_WBB::LL_ALL;
protected static $arenaInfo = array();
/**
* Sets the key that the Bot functions under
* @param string $key
*/
public static function setKey( $key ) {
self::$key = $key;
}
/**
* Returns the key that the Bot functions under
* @return string
*/
public static function getKey() {
return self::$key;
}
/**
* Gets the game id
* @return string
*/
public static function getGameId() {
return self::$gameId;
}
/**
* Returns the bot
* @return SG_WBB_Bot
*/
public static function getBot() {
return self::$bot;
}
/**
* Sets the bot version (echo'ed during INIT)
* @param string $version
*/
public static function setBotVersion( $version ) {
self::$botVersion = $version;
}
/**
* Sets the turn handler
* @param callable $turn_handler
*/
public static function setTurnHandler( $turn_handler ) {
if( is_callable( $turn_handler ) ) {
self::$turnHandler = $turn_handler;
}
}
/**
* Sets wether or not mlog should be used as the error handler for php
* @param bool $use_eh
*/
public static function setUseErrorHandler( $use_eh ) {
self::$useErrorHandler = (bool) $use_eh;
}
/**
* Sets which log-levels should be make its way into the mlog
* Uses a bitmask of the SG_WBB::LL_* consts.
* @param int $log_level
*/
public static function setLogLevel( $log_level ) {
self::$logLevel = (int) $log_level;
}
/**
* "Starts" the bot, loads the relevant data and calles
* the turn handler
*/
public static function takeTurn() {
if( self::$useErrorHandler ) {
set_error_handler( array('SG_WBB', 'errorLogger'));
}
if( isset($_GET['displaySource']) || isset($_GET['describe'])) {
self::displaySource();
}
$state = self::getState();
self::$arenaInfo = $state['arena'];
SG_WBB::mlog('GET: ' . http_build_query($_GET), SG_WBB::LL_DEBUG);
SG_WBB::mlog('Info: ' .
' key: ' . self::getKey() .
' gameId: ' . self::getGameId() .
' callType: ' . self::$callType .
' turnHandler: ' . self::$turnHandler
, SG_WBB::LL_DEBUG
);
switch( self::$callType ) {
case 'gameInit':
$state = array('bot'=>array(), 'arena'=>array());
$state['arena']['scanRange'] = $_GET['scanRange'];
$state['arena']['scanDegrees'] = $_GET['scanDegrees'];
$state['arena']['scanCost'] = $_GET['scanCost'];
$state['arena']['driveCost'] = $_GET['driveCost'];
$state['arena']['driveBaseCost'] = $_GET['driveBaseCost'];
$state['arena']['fireWidth'] = $_GET['fireWidth'];
$state['arena']['fireRange'] = $_GET['fireRange'];
$state['arena']['fireBaseCost'] = $_GET['fireBaseCost'];
self::$arenaInfo = $state['arena'];
echo self::$botVersion . '-' . self::SG_WBB_VERSION;
break;
case 'round':
self::$bot = SG_WBB_Bot::spawnBot( $state['bot'] );
SG_WBB::mlog('Bot: ' . print_r(self::$bot, true), SG_WBB::LL_DEBUG);
call_user_func( self::$turnHandler, self::$bot );
$state['bot'] = self::$bot->getState();
break;
case 'death':
case 'victory':
SG_WBB::mlog('Game finished, removing state file', SG_WBB::LL_DEBUG);
$file = self::getTempFile('wbb-' . self::getGameId() . '-' . self::getKey() . '.state.txt');
if( file_exists( $file ) ) {
unlink( $file );
echo 'Thanks for playing';
return;
}
break;
default:
SG_WBB::mlog('Failed to understand call type: ' . self::$callType, LL_ERROR);
}
SG_WBB::mlog("\n", SG_WBB::LL_DEBUG);
$succ = self::setState($state);
if( ! $succ ) {
SG_WBB::mlog('Failed to save state', LL_ERROR);
}
}
/**
* Calls a method on the server hosting the game
* @return string
*/
public static function callServer( $method, array $uparams ) {
$params = array();
$params['method'] = $method;
$params['clientKey'] = self::getKey();
$params['gameID'] = self::getGameId();
$params += $uparams;
$url = $_GET['url'] . "?" . http_build_query($params);
SG_WBB::mlog('Call WS: ' . $url, SG_WBB::LL_WS);
$response = file_get_contents($url);
SG_WBB::mlog('WS response: ' . $response, SG_WBB::LL_WS);
return $response;
}
/**
* Saves a message on a local log on the server
*
* If the constant SG_WBB_USE_STDOUT is set to true, stdout will be
* used instead instead of the mlog file
*
* @param string $message
* @param int $log_level
* @return bool
*/
public static function mlog( $message, $log_level = 1 ) {
if( self::$logLevel & $log_level ) {
$file = self::getTempFile('wbb-' . self::getGameId() . '-' . self::getKey() . '.mlog.txt');
if( !$file ) {
return false;
}
if( defined('SG_WBB_USE_STDOUT') && SG_WBB_USE_STDOUT ) {
$file = 'php://stdout';
}
return file_put_contents($file, $message . "\n", FILE_APPEND);
}
}
/**
* A simple error logger used for piping phps errors into mlog
*/
public static function errorLogger($errno, $errstr, $errfile, $errline) {
SG_WBB::mlog('ERROR: ' . $errno . ', ' . $errstr . ' . in ' . $errfile . ' (' . $errline . ')', SG_WBB::LL_ERROR);
}
/**
* Loads the state that the bot is in
* @return array
*/
protected static function getState() {
if( ! isset($_GET['serverKey']) || $_GET['serverKey'] != self::$key ) {
throw new Exception("Invalid key");
}
if( ! isset($_GET['gameID']) ) {
throw new Exception("No game id found");
}
if( ! isset($_GET['callType']) ) {
throw new Exception("No call type found");
}
self::$gameId = $_GET['gameID'];
self::$callType = $_GET['callType'];
$file = self::getTempFile('wbb-' . self::getGameId() . '-' . self::getKey() . '.state.txt');
if( ! file_exists($file) ) {
return null;
}
$cont = file_get_contents($file);
if( !$cont ) {
return null;
}
$state = unserialize($cont);
if( $state === false ) {
return null;
}
return $state;
}
/**
* Returns the arena info (or a specific part of it)
* @param string $info
* @return mixed
*/
public static function getArenaInfo( $info = null ) {
if( $info == null ) {
return self::$arenaInfo;
} elseif( isset(self::$arenaInfo[$info]) ) {
return self::$arenaInfo[$info];
} else {
return null;
}
}
/**
* Sets the state - that is, stores it to the servers filesystem
* @param array $state
*/
protected static function setState( $state ) {
SG_WBB::mlog('setState: ' . var_export($state, true), SG_WBB::LL_DEBUG);
$file = self::getTempFile('wbb-' . self::getGameId() . '-' . self::getKey() . '.state.txt');
$succ = file_put_contents($file, serialize($state) );
return $succ;
}
/**
* Returns a filepath to a file called $name in a temp directory
* @TODO Ensure that the file is actually writable
* @TODO If sys_get_temp_dir doesnt exist, dont always use /tmp/
* @return string|false filename
*/
protected static function getTempFile( $name ) {
if( function_exists('sys_get_temp_dir') ) {
$dir = sys_get_temp_dir();
if($dir[strlen($dir) - 1] != '/') {
$dir .= '/';
}
} else {
$dir = '/tmp/';
}
$file = $dir . $name;
return $file;
}
protected static function displaySource() {
$source = file_get_contents( $_SERVER['SCRIPT_FILENAME']);
$source = str_replace(self::$key, "########################", $source);
echo '<h1>' . basename( $_SERVER['SCRIPT_FILENAME']) . ' (v' . self::$botVersion . ')</h1>';
highlight_string($source);
echo '<h1>' . basename(__FILE__) . ' (v' . self::SG_WBB_VERSION . ')</h1>';
echo '<p>The latest version of SG_WBB can be found at ';
echo '<a href="http://github.com/fangel/sg_wbb/tree/master">http://github.com/fangel/sg_wbb/tree/master</a>';
echo '</p>';
highlight_file(__FILE__);
exit;
}
}
/**
* SG_WBB_Bot is representing your bot in the battle
* If you need to store variables that are saved from turn to turn,
* then just set them like $bot->var, and they will be handled by the
* get and set functions, and save to disk between turns
*
* @author Morten Fangel (C) 2008
* @license Creative Commons Share-Alike Attribution
*/
class SG_WBB_Bot {
protected $state;
protected $pos_x;
protected $pos_y;
protected $energy;
protected $armor;
/**
* Returns a new SG_WBB_Bot with the info in $state
* and present in _GET
* @param array $state
* @return SG_WBB_Bot
*/
public static function spawnBot( array $state ) {
$bot = new SG_WBB_Bot();
$bot->state = $state;
$bot->pos_x = (int) $_GET['x'];
$bot->pos_y = (int) $_GET['y'];
$bot->energy = (int) $_GET['energy'];
$bot->armor = (int) $_GET['armor'];
return $bot;
}
/**
* Fires a shot
* @param int $energy How much force to shoot with
* @param int $angle Which direction to fire
* @return int Bots hit
*/
public function fire( $angle, $energy ) {
$energy = min( $energy, $this->energy );
$cont = SG_WBB::callServer('fire', array('energy' => $energy, 'degree' => $angle));
$this->energy -= $energy;
$xml = simplexml_load_string($cont);
return (int) $xml->responseValues->botsHit;
}
/**
* Moves towards $to_x, $to_y
* @param int $to_x
* @param int $to_y
*/
public function drive( $to_x, $to_y ) {
$dir = $this->drivingDirections( $to_x, $to_y );
if( $dir['distance'] * SG_WBB::getArenaInfo('driveCost') > $this->energy) {
// We are going to run out of fuel
$dir['distance'] = ceil($this->energy / SG_WBB::getArenaInfo('driveCost'));
}
$cont = SG_WBB::callServer('drive', $dir);
$this->energy -= $dir['distance'] * SG_WBB::getArenaInfo('driveCost');
switch( $dir['direction'] ) {
case 1:
$this->pos_x -= $dir['distance'];
break;
case 2:
$this->pos_y -= $dir['distance'];
break;
case 3:
$this->pos_x += $dir['distance'];
break;
case 4:
$this->pos_y += $dir['distance'];
break;
}
if( 0 < $this->energy && !($this->pos_x == $to_x && $this->pos_y == $to_y) ) {
$this->drive($to_x, $to_y);
}
}
/**
* Scans for targets in the direction of $angle
* Returns false if nothing found, and a array of
* angle-direction tuples if anything was found
* @param int $angle
* @return array|bool
*/
public function scan( $angle ) {
$cont = SG_WBB::callServer('scan', array('degree' => $angle));
$this->energy -= SG_WBB::getArenaInfo('scanCost');
$xml = simplexml_load_string($cont);
if( $xml->responseValues->hits > 0 ) {
$targets = array();
foreach($xml->responseValues->coords->bot AS $hit) {
$angle = (float) $hit->angle;
$distance = (float) $hit->distance;
$condition = (int) $hit->condition;
$targets[] = SG_WBB_Target::spawnTarget( $angle, $distance, $condition );
}
return $targets;
}
return false;
}
/**
* Returns a array with x and y position of the bot
* @return array
*/
public function getPosition() {
return array('x' => $this->pos_x, 'y' => $this->pos_y);
}
/**
* Returns the energy of the bot
* @return int
*/
public function getEnergy() {
return $this->energy;
}
/**
* Returns the armor of the bot
* @return int
*/
public function getArmor() {
return $this->armor;
}
/**
* Returns the "state" of the bot - that is, all user set variables
* @return array
*/
public function getState() {
return $this->state;
}
public function __get( $var ) {
if( isset($this->state[$var]) ) {
return $this->state[$var];
}
return false;
}
public function __set( $var, $val ) {
$this->state[$var] = $val;
}
public function __isset( $var ) {
return isset($this->state[$var]);
}
/**
* This function returns the driving diretions to get to x, y from
* the current positition
* Adapted from Paul Reinheimers original function found at
* http://example.preinheimer.com/wbb/
*
* @param int $x X co-ordinate of desired location
* @param int $y Y co-ordinate of desired location
* @author Paul Reinheimer
* @return array
*/
private function drivingDirections($x, $y) {
//Determine which direction to drive in, the one we have the furthest to go for
if (abs($this->pos_x - $x) > abs($this->pos_y > $y)) {
//We've got further to go along the X axis
if ($this->pos_x > $x) {
//too far to the right
$direction = 1;
} else {
//We're too far to the left
$direction = 3;
}
$distance = abs($this->pos_x - $x);
} else {
//We've got further to go along the y axis
if ($this->pos_y > $y) {
//too high
$direction = 4;
} else {
$direction = 2;
}
$distance = abs($this->pos_y - $y);
}
return array('direction'=>$direction, 'distance'=>$distance);
}
}
/**
* SG_WBB_Target represents a target
*
* Will store the target by its position. This is calculated from your
* bots current position and the angle and distance to the target
*
* Angle and direction can then be recalculated when your bot moves
*
* @author Morten Fangel (C) 2008
* @license Creative Commons Share-Alike Contribution
*/
class SG_WBB_Target {
private $pos_x;
private $pos_y;
private $condition;
/**
* Creates a new target object.
* @param int $angel
* @param int $distance
* @param int $condition
* @return SG_WBB_Target
*/
public static function spawnTarget( $angle, $distance, $condition ) {
$bot_pos = SG_WBB::getBot()->getPosition();
$target = new SG_WBB_Target();
$target->pos_x = round($bot_pos['x'] + cos( deg2rad($angle) ) * $distance);
$target->pos_y = round($bot_pos['y'] + sin( deg2rad($angle) ) * $distance);
$target->condition = $condition;
$msg = '';
$msg .= 'Calc: (' . $target->pos_x . ', ' . $target->pos_y . ')' . "\n";
$msg .= "\t" . 'Calc: ' . $target->getAngle() . ', ' . $target->getDistance() . "\n";
$msg .= "\t" . 'Expt: ' . $angle . ', ' . $distance . "\n";
SG_WBB::mlog('TARGET: ' . $msg, SG_WBB::LL_DEBUG);
return $target;
}
/**
* Returns the position of this target
* @return array x and y
*/
public function getPosition() {
return array('x' => $this->pos_x, 'y' => $this->pos_y);
}
/**
* Returns the angle to this target
*
* Adapted from Paul Reinheimers calculateAngle found at
* http://example.preinheimer.com/wbb/
*
* @return int (float?)
*/
public function getAngle() {
$this_pos = $this->getPosition();
$bot_pos = SG_WBB::getBot()->getPosition();
$angle = atan2(($this_pos['y']-$bot_pos['y']),($this_pos['x']-$bot_pos['x'])) * (180/M_PI);
$angle = ($angle < 0) ? $angle + 360 : $angle;
return $angle;
}
/**
* Returns the distance to this bot
*
* Adapted from Paul Reinheimers calculateAngle found at
* http://example.preinheimer.com/wbb/
*
* @return int (float?)
*/
public function getDistance() {
$this_pos = $this->getPosition();
$bot_pos = SG_WBB::getBot()->getPosition();
$distance = sqrt(pow(($this_pos['x']-$bot_pos['x']),2) + pow(($this_pos['y']-$bot_pos['y']),2));
return $distance;
}
/**
* Returns the condition of the target. Higher is more health.
* @return int
*/
public function getCondition() {
return $this->condition;
}
}