Here my suggestion in form of a stage (
emptyBattleWallDamage.lua):
Code:
-- Simple empty stage with wall collision damage
worldTimeMax = 10000
function configure(stageBuilder)
stageBuilder:setSize(1500, 1000)
stageBuilder:setBattleMode(true)
end
local ships, world, admin
function init(shipsArg, worldArg, adminArg)
ships = shipsArg
world = worldArg
admin = adminArg
end
function run(stageSensors)
admin:drawText("Time: " .. world:time(), 4, -22, 16)
local hitWallEvents = stageSensors:shipHitWallEvents()
local currentShip
for ii, wallEvent in pairs(hitWallEvents) do
for jj, ship in pairs(ships) do
if ship:name() == wallEvent.shipName then
currentShip = ship
break
end
end
admin:setShipEnergy(currentShip, currentShip:energy()-0.01*wallEvent.bounceForce^2)
print(currentShip:name(), 'took wall damage.',
'speed', currentShip:speed()+wallEvent.bounceForce, 'damage', 0.01*wallEvent.bounceForce^2)
if currentShip:energy() < 0 then
admin:destroyShip(currentShip)
print(currentShip:name(), 'destroyed due to wall collision.')
end
end
--
if (world:time() >= worldTimeMax) then
for i, ship in pairs(ships) do
print(admin:shipDamage(ship) - admin:shipFriendlyDamage(ship))
if (ship:alive()) then
admin:destroyShip(ship)
admin:gameOver()
end
end
end
local nAliveShips = 0
for i, ship in pairs(ships) do
if (ship:alive()) then
nAliveShips = nAliveShips + 1
end
end
if nAliveShips <= 1 then
admin:roundOver()
end
end
I think the scaling for collisions is both reasonable from a gamplay point of view, as well as from a "physical" point of view (non-relativistic kinetic energy scales with square of velocity as well).
EDIT:
A good example for a bot which "abuses" wall collisions in the current state of berrybots is my old "Mark II-B"
Code:
-- Mark II-B.
-- This bot is optimised for 1v1 battles in an otherwise empty arena.
-- Constants and parameters
local const
-- General globals
local world = nil
local ship = nil
local gfx = nil
-- Globals for movement routine
local approaching = 1
local xdes = 0.
local ydes = 0.
-- Globals for firing routine
local targetShip = nil
-- Some functions localize for readability
-- Read something that it might be faster as well (no table lookup)
local sqrt, sin, cos, atan2 = math.sqrt, math.sin, math.cos, math.atan2
local abs, random, min, max = math.abs, math.random, math.min, math.max
function init(shipIn, worldIn, gfxIn)
ship = shipIn
world = worldIn
gfx = gfxIn
ship:setName("Mark II-B")
-- Get Berrybots constants
const = world:constants()
-- Add my own
const.dangerMoveAngle = 0.25*math.pi -- Angle at which not to directly move at enemy
const.COLLISION_FRAME = 4 -- From berrybot src; maybe available officially in the future
end
function run(enemyShips, sensors)
local targetingSuccess, firingAngle, targetDistance, travelTime, xNew, yNew
local angleShipToTarget, angleShipToDestination, speedTarget, accTarget, uu
if #enemyShips > 0 then
if targetShip ~= enemyShips[1] then
targetShip = enemyShips[1]
-- TODO a lot, e.g. handling for multiple enemies
end
-- Laser targeting and fire
if ship:laserGunHeat() < 1 then
targetingSuccess, firingAngle, targetDistance, travelTime, xNew, yNew =
targeting01c(targetShip, const.LASER_SPEED)
if targetingSuccess == 1 and
((cos(firingAngle)*cos(ship:heading()) +
sin(firingAngle)*sin(ship:heading()))*ship:speed() < 0.5*const.LASER_SPEED) then
ship:fireLaser(firingAngle)
elseif targetingSuccess == 0 then
print('Error: Targeting unsuccesssfull.',world:time())
end
end
-- Torpedo targeting
if ship:torpedoGunHeat() < 1 then
targetingSuccess, firingAngle, targetDistance, travelTime, xNew, yNew =
targeting01c(targetShip, const.TORPEDO_SPEED)
if (abs(cos(firingAngle)*cos(ship:heading()) +
sin(firingAngle)*sin(ship:heading()))*ship:speed() < 0.8*const.TORPEDO_SPEED) and
targetDistance > const.TORPEDO_BLAST_RADIUS then
ship:fireTorpedo(firingAngle, targetDistance)
end
end
-- Movement, random for now, but actually performs quite well
angleShipToTarget = calcAngle(targetShip.x, targetShip.y, ship:x(), ship:y())
angleShipToDestination = calcAngle(xdes, ydes, ship:x(), ship:y())
if approaching == 1 or (# sensors:hitByLaserEvents() > 0) or ship:hitWall() or
(abs(angleShipToTarget-ship:heading()) < const.dangerMoveAngle and
abs(angleShipToTarget-angleShipToDestination) < const.dangerMoveAngle) then
angleShipToDestination = angleShipToTarget
for ii = 1, 10 do
xdes = sigmoid((random()-0.5)*4.) * world:width()
ydes = sigmoid((random()-0.5)*4.) * world:height()
angleShipToDestination = atan2(ydes-ship:y(),xdes-ship:x())
if abs(angleShipToTarget-angleShipToDestination) > const.dangerMoveAngle then
break
end
end
approaching = 0
end
shipMoveTo(ship, xdes, ydes)
end
end
function shipMoveTo(ship, x, y)
local distance = sqrt((y - ship:y())^2 + (x - ship:x())^2)
local angle = atan2(y - ship:y(), x - ship:x())
if distance/ship:speed() < 5 or distance < 50 or
(abs(ship:heading()-angle-math.pi/2.) % math.pi) < 0.1*math.pi then
approaching = 1
end
local ax = sqrt(distance)*cos(angle)
local ay = sqrt(distance)*sin(angle)
local amag = sqrt(ax^2 + ay^2)
local aang = atan2(ay, ax)
local alim = max(amag,1.)
ax = ax / alim
ay = ay / alim
amag = amag / alim
ship:fireThruster(aang, amag)
if gfx:enabled() then
distance = sqrt((y - ship:y() - ship:speed()*sin(ship:heading()))^2 +
(x - ship:x() - ship:speed()*cos(ship:heading()))^2)
angle = atan2(y - ship:y() - ship:speed()*sin(ship:heading()),
x - ship:x()- ship:speed()*cos(ship:heading()))
gfx:drawLine(ship:x()+ship:speed()*cos(ship:heading()),
ship:y()+ship:speed()*sin(ship:heading()),angle,
distance,1,{r=255,g=255,b=255,a=155},0,{ },1)
gfx:drawCircle(x,y,5,{r=255,g=255,b=0,a=155},2,{r=255,g=255,b=0,a=155},1)
end
end
-- Linear targeting with outer walls
function targeting01c(targetShip, projSpeed)
local bounceFactor = -const.WALL_BOUNCE
local bounceFactorX, bounceFactorY, nextWall
local newTargetDistance, newTravelTime, newFiringAngle, wallCollisionTime, xWall, yWall, xWallOld, yWallOld
local redWorldWidth = world:width()-const.SHIP_RADIUS
local redWorldHeight = world:height()-const.SHIP_RADIUS
local speedTargetX = cos(targetShip.heading)*targetShip.speed
local speedTargetY = sin(targetShip.heading)*targetShip.speed
local targetX = targetShip.x
local targetY = targetShip.y
local wallCollisionTimes = {n=4}
local bounceLimit = 0
local success, firingAngle, targetDistance, travelTime, xNew, yNew =
laserHeadingDirect(ship:x(), ship:y(), targetX, targetY, speedTargetX, speedTargetY, projSpeed)
newTargetDistance = targetDistance
while success == 0 or (bounceLimit < 4 and
(xNew > redWorldWidth or xNew < 0 or yNew > redWorldHeight or yNew < 0)) do
wallCollisionTimes[1] = (redWorldWidth - targetX)/speedTargetX
wallCollisionTimes[2] = (-targetX+const.SHIP_RADIUS)/speedTargetX
wallCollisionTimes[3] = (redWorldHeight - targetY)/speedTargetY
wallCollisionTimes[4] = (-targetY+const.SHIP_RADIUS)/speedTargetY
wallCollisionTime = math.huge
if (redWorldWidth - targetX) > 0. and speedTargetX > 0. and wallCollisionTimes[1] < wallCollisionTime then
wallCollisionTime = wallCollisionTimes[1]
nextWall = 1
end
if (-targetX+const.SHIP_RADIUS) < 0. and speedTargetX < 0. and wallCollisionTimes[2] < wallCollisionTime then
wallCollisionTime = wallCollisionTimes[2]
nextWall = 2
end
if (redWorldHeight - targetY) > 0. and speedTargetY > 0. and wallCollisionTimes[3] < wallCollisionTime then
wallCollisionTime = wallCollisionTimes[3]
nextWall = 3
end
if (-targetY+const.SHIP_RADIUS) < 0. and speedTargetY < 0. and wallCollisionTimes[4] < wallCollisionTime then
wallCollisionTime = wallCollisionTimes[4]
nextWall = 4
end
xWallOld = xWall
yWallOld = yWall
if nextWall == 1 then
xWall = redWorldWidth
yWall = targetY + speedTargetY*wallCollisionTime
bounceFactorX = bounceFactor
bounceFactorY = 1.
elseif nextWall == 2 then
xWall = const.SHIP_RADIUS
yWall = targetY + speedTargetY*wallCollisionTime
bounceFactorX = bounceFactor
bounceFactorY = 1.
elseif nextWall == 3 then
xWall = targetX + speedTargetX*wallCollisionTime
yWall = redWorldHeight
bounceFactorX = 1.
bounceFactorY = bounceFactor
elseif nextWall == 4 then
xWall = targetX + speedTargetX*wallCollisionTime
yWall = const.SHIP_RADIUS
bounceFactorX = 1.
bounceFactorY = bounceFactor
end
if bounceLimit == 0 then
gfx:drawLine(targetX,targetY,atan2(speedTargetY,speedTargetX),
calcDistance(targetX,targetY,xWall,yWall),1,{r=255,g=255,b=255,a=155},0,{ },1)
else
gfx:drawLine(xWallOld,yWallOld,atan2(speedTargetY,speedTargetX),
calcDistance(xWallOld,yWallOld,xWall,yWall),1,{r=255,g=255,b=255,a=155},0,{ },1)
end
speedTargetX = speedTargetX*bounceFactorX
speedTargetY = speedTargetY*bounceFactorY
targetX = xWall-speedTargetX*wallCollisionTime
targetY = yWall-speedTargetY*wallCollisionTime
success, newFiringAngle, newTargetDistance, newTravelTime, xNew, yNew =
laserHeadingDirect(ship:x(), ship:y(), targetX, targetY, speedTargetX, speedTargetY, projSpeed)
bounceLimit = bounceLimit + 1
if gfx:enabled() and not (xNew > redWorldWidth or xNew < 0 or yNew > redWorldHeight or yNew < 0) then
gfx:drawLine(xWall,yWall,atan2(speedTargetY,speedTargetX),
calcDistance(xWall,yWall,xNew,yNew),1,{r=255,g=255,b=255,a=155},0,{ },1)
gfx:drawCircle(xNew,yNew,5,{r=255,g=0,b=0,a=155},2,{r=255,g=0,b=0,a=155},1)
end
end
if bounceLimit == 0 then
if gfx:enabled() and not (xNew > redWorldWidth or xNew < 0 or yNew > redWorldHeight or yNew < 0) then
gfx:drawLine(targetX,targetY,atan2(speedTargetY,speedTargetX),
calcDistance(targetX,targetY,xNew,yNew),1,{r=255,g=255,b=255,a=155},0,{ },1)
gfx:drawCircle(xNew,yNew,5,{r=0,g=255,b=0,a=155},2,{r=0,g=255,b=0,a=155},1)
end
end
newTravelTime = newTargetDistance/projSpeed
newFiringAngle = calcAngle(xNew, yNew, ship:x(), ship:y())
return success, newFiringAngle, newTargetDistance, newTravelTime, xNew, yNew
end
function laserHeadingDirect(shipX, shipY, targetX, targetY, speedTargetX, speedTargetY, projSpeed)
local success, firingAngle, travelTime, targetDistance, xNew, yNew
local temp1, temp2, temp3, temp4
local speedTarget = sqrt(speedTargetX^2 + speedTargetY^2)
local headingTarget = atan2(speedTargetY, speedTargetX)
temp1 = projSpeed^2 - speedTarget^2
temp2 = speedTarget*
((targetX - shipX)*cos(headingTarget) +
(targetY - shipY)*sin(headingTarget))
temp3 = temp1*((targetX - shipX)^2 + (targetY - shipY)^2)
temp4 = temp3 + temp2^2
if temp4 < 0. then
travelTime = 0.
success = 0
elseif (temp2 + sqrt(temp4))/temp1 > 0. then
travelTime = (temp2 + sqrt(temp4))/temp1
success = 1
elseif (temp2 - sqrt(temp4))/temp1 > 0. then
travelTime = (temp2 - sqrt(temp4))/temp1
success = 1
else
travelTime = 0.
success = 0
end
traveltime = math.floor(travelTime+0.5) -- time discrete integer, seems to improve accuracy, not sure
targetDistance = projSpeed*travelTime
xNew = targetX + speedTargetX*travelTime
yNew = targetY + speedTargetY*travelTime
firingAngle = calcAngle(xNew, yNew, shipX, shipY)
return success, firingAngle, targetDistance, travelTime, xNew, yNew
end
-------------------------------------------------------------------------
-- Small helper function from here on
function calcAngle(x1, y1, x2, y2)
return atan2(y1 - y2, x1 - x2)
end
function calcDistance(x1, y1, x2, y2)
return sqrt((y1 - y2)^2 + (x1 - x2)^2)
end
function sigmoid(x)
if x > 17. then
return 1.
elseif x < -17. then
return 0.
else
return 1./(1.+math.exp(-x))
end
end
It usually does 2-4 times more damage than the enemy against both BasicBattler and NightShade, but the movement routine is fairly dumb (mostly thrusting to random destinations and bouncing on walls recklessly). The wall bounces just throw off any targeting due to their somewhat random nature, i.e. Berrybots might not promote clever movement rountines at least in semi-sophisticated bots.
If wall damage is enabled like in my example stage above my Mark II-B actually loses by the same margin, just reversed

.
As a side note: A billiard ball bouncing between two wall with slight uncertainties in initial position and momentum is actually an introductory example of chaos theory, and similar to what we're dealing with here, especially assuming people are not supposed to just look at berrybots code and copy it more or less. But even for prediction it's a problem: If my predicted thrust usage/acceleration of the target ship a bot is shooting at is only a little off the error gets vastly amplified by a (or multiple!) wall bounces.