BerryBots forums

It is currently Fri Jul 19, 2019 11:28 pm

All times are UTC




Post new topic Reply to topic  [ 4 posts ] 
Author Message
PostPosted: Thu Jun 20, 2019 7:44 pm 
Offline

Joined: Wed Sep 23, 2015 12:17 pm
Posts: 22
I was wondering if it would be better to have some kind of penalty or damage taken for a bot hitting awall, from a "gameplay" point of view I mean. Right now it looks to me that hitting walls a lot might be an integral part of an effective movement strategy (like I guess Sanic of Frohman is doing in some way as well), which I would argue is maybe not as elegant as avoiding collisions...

I know I can do this fairly easily in specific stages, but I'm just trying to decide what's my "supreme" discipline/stage when it comes to 1v1 battles should be.

Any thoughts?

EDIT: Just read that this was discussed in another thread, so I guess it was already considered.


Top
 Profile  
 
PostPosted: Sun Jun 23, 2019 9:51 pm 
Offline

Joined: Wed Sep 23, 2015 12:17 pm
Posts: 22
So I never played Robocode before, but I checked and there one takes damage when hitting the wall and the robo is stopped.

I like the bouncing aspect in berrybots, but I would probably introduce wall damage as a default. I know one could do this in a stage, but still... I would use somewhat of a "physical" formula of the lost kinetic energy which depends on the square of the velocity or speed
Code:
damage = conversionFactor*speedDiff^2


Damage is obviously the damage taken by the ship and the speedDiff is just the velocity or speed lost in the collision. If conversionFactor should be something like 1.e-2, then high speed collision in typical stages (width ~1000 or something) do ~6 damage and typical "grazing" or low speed collisions maybe ~0.1 damage. So it's still not super punishing to hit a wall, but if one just speeds through everything it's not so good :).


Top
 Profile  
 
PostPosted: Mon Jun 24, 2019 2:14 pm 
Offline

Joined: Wed Sep 23, 2015 12:17 pm
Posts: 22
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.


Top
 Profile  
 
PostPosted: Mon Jun 24, 2019 10:57 pm 
Offline

Joined: Wed Sep 23, 2015 12:17 pm
Posts: 22
Here a sloppy reworked version of my old Mark II-B: Mark II-C ;). It beats BasicBattler and Nightshade again in the stage emptyBattleWallDamage.lua above. I'm actually a little bit confused that Nightshade and Basicbattler are not dodging my old "linear" gun (no guess factor or anything). BTW my old linear gun can predict multiple wall bounces without a problem, but only in a continuous time sense. Works well for low to intermediate velocities, but for high velocities the "random" nature of the wall bounces gets too large (see above).

Code:
-- Mark II-C.
-- This bot is optimised for 1v1 battles in an otherwise empty arena.
-- First version that tries to avoid wall collisions, since I do think
-- that all berrybots battle stages should include wall collision damage.


-- 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
local pi, floor = math.pi, math.floor


function init(shipIn, worldIn, gfxIn)
   
   ship = shipIn
   world = worldIn
   gfx = gfxIn
   ship:setName("Mark II-C")
   
    -- Get Berrybots constants
    const = world:constants()
    -- Add my own
    const.dangerMoveAngle = 0.25*pi    -- Angle at which not to directly move at enemy
    const.redWorldWidth = world:width() - const.SHIP_RADIUS
    const.redWorldHeight = world:height() - const.SHIP_RADIUS
    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
    local moveBreak, speedShipX, speedShipY
   
    if # enemyShips > 0 then
       
        if targetShip ~= enemyShips[1] then
            targetShip = enemyShips[1]
            -- TODO something else, e.g. "reset", and 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()-0) + 0
                ydes = sigmoid((random()-0.5)*4.) * (world:height()-0) + 0
                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)
        speedShipX = cos(ship:heading())*ship:speed()
        speedShipY = sin(ship:heading())*ship:speed()
        if speedShipX > 0 then
            if (speedShipX^2/4./0.25 - (const.redWorldWidth - ship:x())) > 0. then
                ship:fireThruster(-pi, 1.)
                approaching = 1
            end
        else
            if (speedShipX^2/4./0.25 - (ship:x()-const.SHIP_RADIUS)) > 0. then
                ship:fireThruster(-0., 1.)
                approaching = 1
            end 
        end
        if speedShipY > 0 then
            if (speedShipY^2/4./0.25 - (const.redWorldHeight - ship:y())) > 0. then
                ship:fireThruster(-pi*0.5, 1.)
                approaching = 1
            end
        else
            if (speedShipY^2/4./0.25 - (ship:y()-const.SHIP_RADIUS)) > 0. then
                ship:fireThruster(pi*0.5, 1.)
                approaching = 1
            end
        end
               
    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-pi/2.) % pi) < 0.1*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 = -0.5
    local bounceFactorX, bounceFactorY, nextWall
    local newTargetDistance, newTravelTime, newFiringAngle, wallCollisionTime, xWall, yWall, xWallOld, yWallOld
    local redWorldWidth = const.redWorldWidth
    local redWorldHeight = const.redWorldHeight
    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 = floor(travelTime+0.5) -- time is integer, this 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



------------------------------------------------------------------------------------------
-- 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


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 4 posts ] 

All times are UTC


Who is online

Users browsing this forum: No registered users and 0 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
cron
Powered by phpBB® Forum Software © phpBB Group