-- Podball Project https://sourceforge.net/projects/podball/
-- This is Lea, an example of a Lua control script.
-- You may use this code freely, as a base for your own control scripts.
-- We import the vector module.
-- Note that vector.lua is in the same directory as this file.
-- This works because Podball sets the Lua package path to the path given in match.info.
local vector = require "vector"
-- Module wide variables
-- These are part of the Podball API
local PB = {}
PB.GAME = {}
PB.MATCH = {}
-- Enumeration constants for convenience
PB.NO_TEAM = -1
PB.OUR_TEAM = 0
PB.OTHER_TEAM = 1
PB.ACTION_NOTHING = 0
PB.ACTION_MOVE = 1
PB.ACTION_SHOOT = 2
PB.BALL_FREE = 0
PB.BALL_CONTROLLED = 1
-- These are for this module only
local GOAL_KEEPER_POSITION = vector(-0.45, 0)
local FIELD_POSITIONS = {}
local FORWARD_POSITION = vector(0.45, 0)
local GOAL_POSITION = vector(0.5,0)
-- Last ball owning team
local last_owningteam = PB.OTHER_TEAM
-- Some useful physical constants.
-- They will be calculated during initMatchTeam.
-- Distance the ball travels until loosing half of his velocity.
local pass_distance
-- Time this will take.
local pass_time
-- The pod that wants to get the ball.
local acting_pod
----------------------------------------
-- Podball API function.
-- This stateless function should return static info about the module.
function getModuleInfo()
local t = {}
t["name"] = "Lea" -- The module's name
t["version"] = "1.1" -- A version number of the module
t["author"] = "Me" -- The author of the module
t["date"] = "2018-02-19" -- The release date of this module
t["min_pods"] = 1 -- The minimum number of pods per team this module supports
t["max_pods"] = 12 -- The maximum number of pods per team this module supports
return t
end
----------------------------------------
-- Podball API function.
-- Called for every new match and once per team.
-- For parameter gameSettingsParam, see struct GAMEINFO defined in podtypes.h.
-- For parameter matchSettingsParam, see struct CMatchSettings defined in PB.h.
function initMatchTeam(gameSettingsParam, matchSettingsParam)
PB.GAME = gameSettingsParam
PB.MATCH = matchSettingsParam
if PB.MATCH.debug then
print("Lea: init.")
-- The config string provided via the match settings. (Not used here)
print("Lea: Config: " .. PB.MATCH.strConfig)
-- Your script shouldn't be too verbose when PB.MATCH.debug == false !
print("Lea: Debug Mode: " .. tostring(PB.MATCH.debug))
end
pass_distance = 1.0 * PB.GAME.SHOOT_FORCE / PB.GAME.BALL_FRICTION
--print(pass_distance)
pass_time = -1 * (PB.GAME.BALL_MASS / PB.GAME.BALL_FRICTION) * math.log(0.5)
--print(pass_time)
acting_pod = 0
for i=1,PB.GAME.NPODS do
FIELD_POSITIONS[i] = vector(-math.cos(i*2*math.pi/PB.GAME.NPODS)*0.28,
math.sin(i*2*math.pi/PB.GAME.NPODS)*0.22)
end
end
----------------------------------------
-- Helper function.
-- This function returns the (normalized) direction [pod] needs to shoot to
-- when we want the [ball] to go off to direction [d].
-- It compensates for the shooting pod's proper motion.
function getShootDirection(pod, ball, d)
setmetatable(d, getmetatable(vector.zero))
local v0 = (d * PB.GAME.SHOOT_FORCE / PB.GAME.BALL_MASS):normalize_inplace()
return ((v0 - pod.v) * ball.mass):normalize_inplace()
end
----------------------------------------
-- Helper function.
-- If the [origin] object wants to move in the direction of the [target] object,
-- then given [origin] and [target]'s proper motions and inertia,
-- this function returns the direction of the acceleration force that [origin] needs to provide.
-- The function takes into account a first order lead pursuit course,
-- i.e. assuming the [target] will stay at its current velocity.
function getAccelDirectionToMovingTarget(origin, target)
-- Maximum velocity with which [origin] can travel.
local v_max = PB.GAME.ACCEL_FORCE / origin.fric;
-- Distance to target.
local d = (target.q - origin.q):len()
-- Time in which we can make the distance to [target] with that velocity.
-- This will be the time for which we predict the movement.
local t = d/v_max;
-- Predcited positions.
local target_pos = target.q + (target.v*t)
local origin_pos = origin.q + (origin.v*t)
return (target_pos - origin_pos):normalize_inplace()
end
----------------------------------------
-- Podball API function.
-- Called for every time step the engine needs a control module action for a team.
-- Input: world - The state of the game world containing all object positions among others.
-- See WORLDSTRUCT in podtypes.h.
-- NOTE: In order to respect common habits of Lua, pod indices go from 1 to NPODS.
-- This includes the variable world.ball.owningpod.
-- Return value: Actions of all the pods of the own team:
-- A table podActions[1..NPODS] with a vector "a" and the action "action" to perform.
-- action is one of 0-do nothing; 1-move; 2-shoot
function getTeamActions(world)
--print(world.time)
-- 'vectorize' some of the tables.
setmetatable(world.ball.q, getmetatable(vector.zero))
setmetatable(world.ball.v, getmetatable(vector.zero))
setmetatable(world.ball.f, getmetatable(vector.zero))
for i=1,PB.GAME.NPODS do
setmetatable(world.team.pods[i].q, getmetatable(vector.zero))
setmetatable(world.team.pods[i].v, getmetatable(vector.zero))
setmetatable(world.team.pods[i].f, getmetatable(vector.zero))
setmetatable(world.otherteam.pods[i].q, getmetatable(vector.zero))
setmetatable(world.otherteam.pods[i].v, getmetatable(vector.zero))
setmetatable(world.otherteam.pods[i].f, getmetatable(vector.zero))
end
-- Initialize return values.
local podActions = {}
for i=1,PB.GAME.NPODS do
podActions[i] = {}
podActions[i].action = PB.ACTION_NOTHING
podActions[i].a = vector()
end
-- The hard work starts here...
if world.ball.owningteam == PB.OUR_TEAM then
last_owningteam = PB.OUR_TEAM
elseif world.ball.owningteam == PB.OTHER_TEAM then
last_owningteam = PB.OTHER_TEAM
end
-- Find the pod which is nearest to the ball.
if acting_pod == 0 or last_owningteam == PB.OTHER_TEAM then
local nearest_pod = 0
local nearest_d = 9.9
for this_pod=1,PB.GAME.NPODS do
-- predict 50 timesteps ahead
local d = vector.zero.dist(world.team.pods[this_pod].q, world.ball.q + world.ball.v * 50.0)
if d < nearest_d then
nearest_d = d
nearest_pod = this_pod
end
end
acting_pod = nearest_pod
end
-- Do the same thing for every pod ...
for this_pod=1,PB.GAME.NPODS do
-- Does our team have the ball?
if last_owningteam == PB.OUR_TEAM then
-- Does this pod have the ball?
if world.ball.owningpod == this_pod and world.ball.status == PB.BALL_CONTROLLED then
-- Check if we can shoot on goal
if vector.zero.dist(GOAL_POSITION, world.team.pods[this_pod].q) < pass_distance then
podActions[this_pod].a = getShootDirection(world.team.pods[this_pod], world.ball, GOAL_POSITION - world.team.pods[this_pod].q)
podActions[this_pod].action = PB.ACTION_SHOOT
else
-- Check if we can pass the ball to someone in a forward position
for other_pod=1,PB.GAME.NPODS do
if other_pod ~= this_pod then
if world.team.pods[other_pod].q.x > world.team.pods[this_pod].q.x
and vector.zero.dist(world.team.pods[other_pod].q, world.team.pods[this_pod].q) < pass_distance then
podActions[this_pod].a = getShootDirection(world.team.pods[this_pod], world.ball, world.team.pods[other_pod].q - world.team.pods[this_pod].q)
podActions[this_pod].action = PB.ACTION_SHOOT
acting_pod = other_pod
break
end
end
end
end
-- If we haven't decided to shoot yet ...
if podActions[this_pod].action ~= PB.ACTION_SHOOT then
-- then move to goal
podActions[this_pod].a = FORWARD_POSITION - world.team.pods[this_pod].q
podActions[this_pod].a:normalize_inplace()
podActions[this_pod].action = PB.ACTION_MOVE
end
else
-- No, then move ...
if this_pod == acting_pod then
podActions[this_pod].a = getAccelDirectionToMovingTarget(world.team.pods[this_pod], world.ball)
podActions[this_pod].action = PB.ACTION_MOVE
else
-- No, then go offensive position.
podActions[this_pod].a = FIELD_POSITIONS[this_pod] + vector(0.1,0) - world.team.pods[this_pod].q
podActions[this_pod].a:normalize_inplace()
podActions[this_pod].action = PB.ACTION_MOVE
end
end
elseif last_owningteam == PB.OTHER_TEAM then
-- If I am the nearest pod, go for the ball!
if this_pod == acting_pod then
podActions[this_pod].a = getAccelDirectionToMovingTarget(world.team.pods[this_pod], world.ball)
podActions[this_pod].action = PB.ACTION_MOVE
else
-- otherwise, go defensive positions
podActions[this_pod].a = FIELD_POSITIONS[this_pod] - vector(0.1,0) - world.team.pods[this_pod].q
podActions[this_pod].a:normalize_inplace()
podActions[this_pod].action = PB.ACTION_MOVE
end
end
end
if PB.MATCH.debug then
-- Example for using the built-in debug drawing interface
-- You must use "--debug" on command line AND "debug 1" in team control settings for this to work.
if acting_pod > 0 then
drawCross(world.team.pods[acting_pod].q, 0.02) -- draw a cross of radius 0.02 (arena units) around acting_pod
end
--drawCircle(world.team.pods[2].q, 0.05) -- draw a circle of radius 0.05 (arena units) around pod[2]
--drawLine(world.team.pods[1].q, world.team.pods[2].q) -- draw a line between the two pod positions
end
return podActions
end
----------------------------------------
-- Podball API function.
-- Called once at the end of a match.
function disposeMatchTeam()
if PB.MATCH.debug then
print("Lea: dispose.")
end
end