Programming robots

From Bitfighter
Revision as of 05:14, 19 June 2009 by 97.120.110.236 (Talk)

Starting with release 011, server admins will be able to program their own robots in addition to designing their own levels. Robots will be coded in Lua, and will will have a number of special functions available to them.

For examples of fully functional bots, see the Robot Gallery. Please see the note at the top of that page if you are using Bitfighter 011 alpha 2.

Robot coding is still fluid, and everything is subject to change, but here is a list of commands being implemented for the next alpha release (see forums for the URL):


How to read the function notation:

returnType funtionName(argType1, argType2) - Description of function


Return types: Unlike most languages, Lua does not differentiate between integers and floating point numbers. Instead, everything is simply a number. In fact, Lua often will let you interchange strings and numbers, converting dynamically on an as-needed basis. For the most part, relying on this automatic type coercion is bad practice and will get you into trouble.


Points to remember in Lua:

  • Lua arrays are 1-based: the first index is 1 rather than 0 as is the case in most programming languages
  • Lua is case sensitive. That means that the variable foo is different than Foo and FOO
  • Any variable not declared as local will be global. Accessing global varibales is slower than local ones, and being sloppy about variable scope can lead to unexpected problems. Use the local keyword whenever appropriate.

We will follow the Lua formatting conventions used in the Programming in Lua book.

Navigation, configuration, and combat

There are two general methods for getting the current game time:
number getTime() - Returns time for this frame, in ms.
number getCPUTime() - Returns number representing the CPU time in ms. Can be used to time events

These functions will return slightly different results. Generally speaking, if you are timing things in the "real world", i.e. you want to pause for exactly 1 second, use getCPUTime(). If you want to use a timer to make the robot move smoothly around a mathematically derived course, use getTime(), as that is what is used internally when calculating ship movement.

Example:

lastTime = thisTime
thisTime = bot:getCPUTime()
local delta = thisTime - lastTime
logprint(delta .. " ms have elapsed in the real world since last frame.")

Example:

-- Will fly robot in a circular orbit around an item.
-- Assume we've already found one and stored it in "item"
 
local botLoc = bot:getLoc()
local itemLoc = item:getLoc()
 
local dist = botLoc:distanceTo(itemLoc)
 
-- For this example, we'll define orbitRadius as a local variable.  In a
-- real robot, this might be better declared a global variable in the header. 
local orbitRadius = 300		
 
-- Here we use the getTime() function to make the motion look smooth.
-- If we just advanced orbitAng by a fixed amount each frame, it would
-- appear jerky, as each frame represents a slightly different length in time.
 
-- .0015 determined experimentally
 
orbitAng = orbitAng + .0015 * bot:getTime()  -- orbitAng is a global variable
 
local dest = nil
if(dist <= orbitRadius * 1.1) then 
    dest = itemLoc
    dest:setxy(itemLoc:x() + orbitRadius * math.cos(orbitAng),
               itemLoc:y() + orbitRadius * math.sin(orbitAng) )
else
    dest = itemLoc
end
 
bot:setThrustToPt(dest)         -- Travel towards calculated point


getZoneCenter(x, y) - Return point representing center of zone containing point x,y
getGatewayFromZoneToZone(a, b ) - Return point representing fastest way from zone a to zone b. If zones a & b are not neighbors, returns nil
number getZoneCount() - Return number of zones
number getCurrentZone() - Return current zone that robot is in

number getAngle() - Return angle robot is currently facing
Point getLoc() - Return point representing location of robot

Example:

-- This code in getMove()
    local loc = bot:getLoc()                   -- Get robot's location once
    local items = bot:findItems(AsteroidType)  -- Get list of all asteroids
 
    -- Now find the closest one
    local minDist = 99999999                -- Or similarly big number
    local asteroid = nil
    for indx, item in ipairs(items) do      -- Iterate over our list
        -- Use distSquared because it is less computationally expensive
        -- and works great for comparing distances
        local d = loc:distSquared(item:getLoc())   -- Dist to asteroid
 
        if(d < minDist) then                -- Is it the closest yet?
           asteroid = item
           minDist = d
        end
    end
 
    if(asteroid == nil) then                -- Make sure we found one
        return
    end


void setAngle(ang) - Point robot at angle
void setAngleXY(x, y) - Point robot towards point x,y
number getAngleXY(x, y) - Compute angle to point x, y
boolean hasLosXY(x, y) - Return whether or not robot can see point x, y
number getFiringSolution (item) - Calculate angle at which robot needs to fire to hit moving or stationary object. Will return nil if the item is out of range, behind an obstacle, or firing will hit a friendly ship. Always check the return value for nil!

Example:

    local loc = bot:getLoc()                                -- Currend location
    local items = bot:findGlobalItems(SoccerBallItemType)   -- Find soccerball
 
    -- Now loop through all returned items and find the closest
    local minDist = 99999999
    local ball = nil
 
    for indx, sb in ipairs(items) do
        local l = sb:getLoc()           -- Ball's location
 
        -- Distance from ship to ball (use distSquared because
        -- it is easy to compute and good for comparison)
        local d = loc:distSquared(l)    
 
        if(d < minDist) then
           ball = sb
           minDist = d
        end
    end
 
    -- Check to make sure we found a soccer ball ... could be out of view
    if(ball == nil) then
        return
    end
 
 
    local ang = bot:getFiringSolution(sb)   -- Get shoot angle to hit moving ball
 
    -- Always check for nil: could happen if ball 
    -- were behind a wall or friendly ship
    if(ang == nil) then
        return
    end
 
    bot:setAngle(ang)    -- Ready... aim...
    bot:fire()           --  ...fire!


number getTeamIndx() - Return index of team this robot is on
boolean hasFlag() - Return whether or not robot currently has the flag


WeaponType getActiveWeapon() - Return currently selected weapon
Example:

weap = bot:getActiveWeapon()    -- Get info about our currently active weapon
weapInfo = WeaponInfo(weap)     -- Get information about it   
logprint(weapInfo:getName() .. " has a range of " .. weapInfo:getRange())


Modules are automatically disabled at the end of each game cycle. Therefore, if you want to keep a module on for a sustained period of time, you must activate it each time getMove() is called. To activate a module, use one of the activateModule() commands.

void activateModule(ModuleType) - Activate module, specified by ModuleType. If specified module is not included in the current loadout, this command will have no effect. Example:

-- Note that this line will do nothing if we don't have shields
bot:activateModule(ModuleShield)    -- Shields up!

void activateModuleIndex(indx) - Activate module, specified by its index (1 or 2)
Example:

-- Must specify an index, currently either 1 or 2
bot:activateModuleIndex(1)          -- Activate first module, whatever it is

void setReqLoadout(Loadout) - Set the requested loadout to Loadout
Loadout getCurrLoadout() - Returns current loadout
Example:

loadout = bot:getCurrLoadout()       -- Retrieve current bot configuration
 
weapType1 = loadout:getWeapon(1)     -- Get the first weapon
weapType2 = loadout:getWeapon(2)     -- Get the second weapon
weapType3 = loadout:getWeapon(3)     -- Get the third weapon
 
-- Check to see if the first weapon is a phaser
if (weapType1 == WeaponPhaser) then
   logprint("First weapon is a phaser!")
end
 
-- Print a list of our three current weapons
logprint("My three weapons are: " .. WeaponInfo(weapType1):getName() .. ", "
                                  .. WeaponInfo(weapType2):getName() .. ", and "
                                  .. WeaponInfo(weapType3):getName())


Loadout getReqLoadout() - Returns requested loadout



Navigation

[Item list]findItems(ObjectType, ...) - Return a list of items of the specified type or types that are within the robot's viewable area
[Item list]findGlobalItems(ObjectType, ...) - Return a list of items of the specified type or types that are anywhere in the game. Note that items that would ordinarily be out of scope will not be included in this list!

Example:

-- Find all resource items in the normal viewport
local items = bot:findItems(ResourceItemType)                   
 
-- You can specify multiple item types if you want
-- Get a list of all TestItems or Asteroids that are visible anywhere
-- (note that items that are out-of-scope will be omitted)
local globItems = bot:findGlobalItems(TestItemType, AsteroidType)  
 
-- Now iterate over the list ... there are many examples in the documentation!
for indx, item in ipairs(globItems) do
    -- Do something with item ... perhaps figure out which is closest, 
    -- which is heading towards us, etc.
end


getWaypoint(Point)


Ship control
void setThrust(vel, angle) - Set robot's velocity to vel (0-1), at angle
void setThrustXY(vel, Point) - Set robot's velocity to vel (0-1), toward coordinates x, y

void setThrustToPt(Point) - Thrust toward Point, but adjust speed in an attempt to land right on point, and not overshoot. If Point is distant, setThrustToPt() will cause robot to go at full speed.
Note that in 011 alpha 2, there is a bug in setThrustToPt() that can cause Bitfighter to hang. It will be fixed in alpha 3. Inserting the line:
   if(bot:getTime() == 0) then return end
as the first line of your getMove() function will fix the problem.

void fire() - Fires active weapon
void setWeaponIndex(index) - Activates weapon with index (1, 2, 3)
void setWeapon(WeaponType) - Activates weapon type specified (does nothing if WeaponType is not in current loadout)
boolean hasWeapon (WeaponType) - Does current configuation have specified weapon

Example:

if(bot:hasWeapon(WeaponMine)) then
    bot:setWeapon(WeaponMine)      -- Make mine layer active if it's 
                                   --     part of our current loadout
    bot:fire()                     -- Lay a mine
end

void globalMsg(msg) - Send a message to all players
void teamMsg(msg) - Send a message to players on the same team

void logprint(msg) - Print msg to game logfile

GameItems

The Lua object structure generally follows that used by Bitfighter. The GameItems group conisists of Ships, Robots, RepairItems, Asteroids, ResourceItems, SoccerBallItems, Flags, NexusFlags, and TestItems. These all share similar properties, and have similar methods. All of these implement the getLoc(), getVel(), and getRad() methods for querying location, velocity, and radius respectively. Some items have additional methods that apply only to them. See below for details on these additional methods.


Ships & Robots

Point getLoc() - Center of item
number getRad() - Radius of item
Point getVel() - Speed of item

number getTeamIndx() - Index of the team this ship/robot is on
boolean isModuleActive(ModuleType) - Returns true if specified module is active

TestItems, ResourceItems, and SoccerBallItems

These items all have the same methods and behave in a very similar manner. Category: GameItem

Point getLoc() - Center of item
number getRad() - Radius of item
Point getVel() - Speed of item

Example:

local items = bot:findItems(TestItemType)  -- Find all TestItems in view
 
-- Now cycle through all returned items and find the fastest one
local maxSpeed = -1
local fastest = nil
 
for indx, ti in ipairs(items) do
    -- getVel() returns a point representing the x and y components of
    -- the velocity.  Need to use len() to get actual speed.  Or, in this
    -- case, since actual speed isn't important, we can use lenSquared()
    -- which is less computationally intensive, and will yield perfectly
    -- good results for comparison purposes (such as finding the fastest).
    spd = ti:getVel():lenSquared()  
 
    if(spd > maxSpeed) then
        fastest = ti
        maxSpeed = spd 
    end
end
 
-- Better check to make sure fastest isn't nil before using it!
-- Could be nil if there are no TestItems in view
 
if(fastest != nil) then
   -- Do something
end

Asteroids

Category: GameItem

Point getLoc() - Center of asteroid
number getRad() - Radius of asteroid
Point getVel() - Speed of asteroid
number getSize() - Index of current asteroid size (0 = initial size, 1 = next smaller, 2 = ...)
number getSizeCount() - Number of indexes of size we can have

Example:

local asteroids = bot:findItems(AsteroidType) 
local target = asteroids[1]:getLoc()  -- Pick first asteroid

RepairItems

Category: GameItem

Point getLoc() - Center of RepairItem
number getRad() - Radius of RepairItem
Point getVel() - Speed of RepairItem (usually 0,0)
boolean isVis() - Is repair item currently visible?

Example:

local ri = bot:findItems(RepairItemType)   -- Get list of all known RepairItems  
local pos = ri[1]:getLoc()                 -- Get position of first

Flags

Category: GameItem

Point getLoc() - Center of Flag
number getRad() - Radius of Flag
Point getVel() - Speed of Flag (usually 0,0)
number getTeamIndx() - Get index of owning team (-1 for neutral flag)
boolean inInitLoc() - Is flag in it's initial location?
boolean inCaptureZone() - Is flag in a team's capture zone?
boolean isOnShip() - Is flag being carried by a ship?

NexusFlags

Category: GameItem
Note: Only appears in Nexus games.
Point getLoc() - Center of NexusFlag
number getRad() - Radius of NexusFlag
Point getVel() - Speed of NexusFlag

Turrets and ForceFieldProjectors

Category: GameItem

Point getLoc() - Center of NexusFlag
number getRad() - Radius of NexusFlag
Point getVel() - Speed of NexusFlag
number getTeamIndx() - Get index of owning team (or -1 for neutral, -2 for hostile)
number getHealth() - Get health of item (1 = full heath, 0 = totally dead)
boolean isActive() - True if item is active, false otherwise


Bullets, Mines, and SpyBugs

Category: GameItem
There are three GameItem types that have to do with bullets, mines, and spybugs: BulletType, MineType, and SpyBugType respectively. They all have the same methods, and can be used somewhat interchangeably, as shown in the example below. However, as shown, they can also be found independently.

BulletType encompases Phaseser, Bounce, Triple, Burst, and Turret shots, the properties of which can be retrieved with the WeaponInfo object.

Point getLoc() - Center of item
number getRad() - Radius of item
Point getVel() - Speed of item (usually 0,0)
WeaponType getWeapon() - Return WeaponType for this item
Example:

local bullets = bot:findItems(BulletType, MineType, SpyBugType) 
 
<some code>    -- Code here to select one of the found items
 
local weap = selectedBullet:getWeapon()
local weapInfo = WeaponInfo(weap)
logprint("Selected "  ..  weapInfo:getName())


Weapon Information

All WeaponInfo data will remain constant throughout the game. Therefore, if you need some information about a weapon (mines, say, for a minesweeping bot), it might make sense to retrieve it in the bot's header and store it in a global variable rather than instantiating a new WeaponInfo object during every loop of the robot's getMove() method.

Example:

local weap = bot:getActiveWeapon()      -- Get bot's currently active weapon
logprint(weap:getName() .. " shoots with a speed of " .. weap:getProjVel())
-- In header... we'd better not declare these variables as local!
turretInfo = WeaponInfo(WeaponTurret)   -- Get info about those infernal turrets
turretRange = turretInfo:getRange()     -- Stay this far from turrets to be safe


string getName() - Name of weapon ("Phaser", "Triple", etc.)
WeaponType getID() - ID of module (WeaponPhaser, WeaponTriple, etc.)
number getRange() - Get range of weapon (units)
number getFireDelay() - Delay between shots in ms
number getMinEnergy() - Minimum energy needed to use
number getEnergyDrain() - Amount of energy weapon consumes
number getProjVel() - Speed of projectile (units/sec)
number getProjLife() - Time projectile will live (ms) -1 == live forever)
number getDamage() - Damage projectile does (0-1, where 1 = total destruction)
boolean getCanDamageSelf() - Will weapon damage self?
boolean getCanDamageTeammate() - Will weapon damage teammates?

WeaponType constants

WeaponPhaserWeaponBounce
WeaponTripleWeaponBurst
WeaponMineWeaponSpyBug
WeaponTurret

Module Information

Example:

local mod = ModuleInfo(ModuleBoost)
logprint("This is a lame example!")

string getName() - Name of module ("Shield", "Turbo", etc.)
ModuleType getID() - ID of module (ModuleShield, ModuleBoost, etc.)

ModuleType constants

ModuleShieldModuleBoost
ModuleSensorModuleRepair
ModuleCloakModuleEngineer (maybe someday)

Loadouts

void setWeapon(index, WeaponType) - Set weapon at index
void setModule(index, ModuleType) - Set module at index
WeaponType getWeapon(index) - Return weapon at index
ModuleType getModule(index) - Return module at index

Example:

-- Get a new loadout (comes pre-filled with default values)
local loadout = Loadout()
 
-- Configure the loadout to suit our needs
loadout:setWeapon(1, WeaponPhaser)
loadout:setWeapon(2, WeaponBurst)
loadout:setWeapon(3, WeaponMine)
loadout:setModule(1, ModuleShield)
loadout:setModule(2, ModuleCloak)
 
-- Set the loadout, will become active when bot hits loadout zone
-- or spawns, depending on game
bot:setReqLoadout(loadout)     
 
if(loadout:getWeapon(1) == WeaponPhaser) then
   logprintf("This line always gets printed!")
end


boolean isValid() - Is loadout config valid?
boolean equals(Loadout) - is loadout the same as Loadout?

GameInfo

You can get information about the current game with the GameInfo object. You only need get this object once, then you can use it as often as you like. It will always reflect the latest data.
Example:

game = GameInfo()           -- Create the GameInfo object 
gameType = game:getGameType()
 
if(gameType == SoccerGame) then
   logprint("This bot is not very good at soccer!")
else
   gameTypeName = game:getGameTypeName()
   logprintf("I just love playing " .. gameTypeName .. "!")   
end

Example:

-- Create the GameInfo object in header (don't declare it local!!)
game = GameInfo()           
 
 ...
 
-- Even though we only create our GameInfo once, it is always current
 
 ...
 
-- Later, in getMove():
local remTime = game:getGameTimeReamaining()
local totTime = game:getGameTimeTotal()
local percent = (totTime - remTime) / remTime
logprint("Game is " .. percent .. "% over)

GameType getGameType() - Return current game typet)
string getGameTypeName() - Return current game type
number getFlagCount() - Return the number of flags in the game
number getWinningScore() - Returns the score required to win the level
number getGameTimeTotal() - Returns the time (in seconds) that the level will be played for
number getGameTimeRemaining() - Returns the time remaining (in seconds) for this level
number getLeadingScore() - Gets score of the leading team
number getLeadingTeam() - Gets index of leading team
number getTeamCount() - Return number of teams

Example:

-- Create the GameInfo object in header (don't declare it local!!)
game = GameInfo()           
 ...
-- Then later ...
 ...
local leadingTeam = game:getLeadingTeam()
local team = TeamInfo(leadingTeam)
bot:teamMsg("Hurry up! Team " .. team:getName() .. " is winning!" )

string getLevelName() - Gets the level's name
number getGridSize() - Gets the level's gridSize parameter
boolean getIsTeamGame() - Is this a team game?

getEventScore(ScoringEvent)


GameType constants

BitmatchGameCTFGame
HTFGameNexusGame
RabbitGameRetrieveGame
SoccerGameZoneControlGame

ScoringEvent constants

KillEnemyKillSelf
KillTeammateKillEnemyTurret
KillOwnTurretCaptureFlag
CaptureZoneUncaptureZone
HoldFlagInZoneRemoveFlagFromEnemyZone
RabbitHoldsFlagRabbitKilled
RabbitKillsReturnFlagsToNexus
ReturnFlagToZoneLostFlag
ReturnTeamFlagScoreGoalEnemyTeam
ScoreGoalHostileTeamScoreGoalOwnTeam

TeamInfo

string getName() - return team name
number getIndex() - return team's index, index of first team is 1
number getPlayerCount() - return player count
number getScore() - return team score

Example:

local gameInfo = GameInfo()
local teams = gameInfo:getTeamCount()
 
-- Note that while the number of teams will not change throughout the game,
-- the number of players on each team might.  Also, the robot may be initialized
-- before the players have been added, so if you run this code in the bot header, 
-- it may report some teams having 0 players.
 
for i = 1, teams do        -- First team has an index of 1
   team = TeamInfo(i)
   logprint("Team " .. i .. " is called " .. team:getName() .. 
      " and has " .. team:getPlayerCount() .. " players")
end

Timers

Timer is a utility class that makes it easier to manage timing periodic or delayed events.

void reset() - Reset the timer.
number getTime() - Returns time left on the timer (in ms)
number getFraction() - Returns the fraction of the timer left (1 = all time left, 0 = no time left)
void setPeriod(time) - Set the time on the timer, and resets it.
boolean update(time) - Update the timer by the specified amount of time (in ms), and return true if timer has gone off. Use bot:getTime() as the time parameter.
Example:

-- In header:
timer = Timer(1000)      -- Set timer that will go off every second
 ...
-- Then later, in getMove()
 ...
if(timer:update(bot:getTime())) then   -- Always update with bot:getTime()
   logprint("1 sec has elapsed!")      -- Msg will get printed every second
   timer:reset()                       -- Start timer over
end

Points

Point is a utility class to make handling coordinates or vectors simpler. You can create your own point objects using the constructor, or you can get them as return values from many functions.

Point Point(x, y) - Create a new Point object with coordinates x, y. You do not need to use setxy() unless you want to change the coords later.

number x() - Return x value
number y() - Return y value

setxy(x, y) - Update coordinates of point
setx(x) - Update x coordinate of point
sety(y) - Update y coordinate of point

number len() - Returns the length of the point, useful if point represents a velocity or other vector
number lenSquared() - Returns the length squared of the point, useful if point represents a velocity or other vector, and you want a less computationally expensive representation of length that will work for comparative purposes
boolean equals(Point) - Returns true if the point is the same as Point
void normalize(length) - If Point represents a vector, set its length to length number distanceTo(Point) - Returns distance from point to Point
number distSquared(Point) - Returns distance squared from point to Point (less computationally intensive, good for comparison)
number angleTo(Point) - Returns angle from point to Point

Example:

local point = Point(0, 0)      -- Create a new point
 
if(point:equals(Point(1, 1)) then       
   logprint("This never gets printed")      
end
 
if(point:distanceTo(Point(1, 1) < 5) then       
   logprint("This always gets printed")      
end
 
 
-- Here we'll use a point to represent a velocity, which has x & y components
local xspeed = 10
local yspeed = 15
local velocity = Point(xspeed, yspeed)
logprint("Speed = " .. velocity:len())

ItemTypes

ShipType
RobotType
BulletType
MineType
SpyBugType
NexusFlagType
RobotType
TeleportType (not yet fully implemented)
SpeedZoneType (not yet fully implemented)
AsteroidType
TurretType
ForceFieldProjectorType
TestItemType
FlagType
SoccerBallItemType
ResourceItemType

Things that are not really items

Polygonal-like
BotNavMeshZoneType
NexusType
GoalZoneType
LoadoutZoneType

Wall-like
BarrierType

ForceFieldType