[Lua] - GetDistance() Avoid Placing Near Cities

Come in here to talk about your sky-net style world-destroying super bots!

Moderator: Defcon moderators

User avatar
Ace Rimmer
level5
level5
Posts: 10803
Joined: Thu Dec 07, 2006 9:46 pm
Location: The Multiverse

[Lua] - GetDistance() Avoid Placing Near Cities

Postby Ace Rimmer » Wed Feb 17, 2010 8:23 pm

I know that this function is sort of broken at the moment, but assuming it wasn't, how in the world would you be able to tell if your x, y coordinates were <= 1.8 of an existing city?

At Defcon 5, I gather all city information:

Code: Select all

local allCities = GetCityIDs()
local us = GetOwnTeamID()

   for i, city in ipairs(allCities) do
      if city:GetTeamID() == us then
         table.insert(myCities, city)
      else
         table.insert(enemyCities, city)
      end
   end


Now that I know where cities are and how many there are, I begin to place ground units The command IsValidLocation() returns true if it's Land (ground only, sea for naval), and if it's not on the exact coordinates of a city. IsValidLocation should be modified (I think) to return false if the coordinates are within a radius of 1.8 of any given city. Nevertheless, I need to accomplish this some kind of way so I don't place too close to a city and take double damage (strikes to one cause damage to both).

I've tried various forms of : (to no avail)

Code: Select all

locations = {}

function TestTest()
   StartLongTask(function()
   
      local x, y, d = -74.000000, 40.750000, 1.8 -- New York
      
      -- or use:
      
      --local x = math.random(-110, -91)
      --local y = math.random(47, 51)
      --local d = 1.8
      
      WhiteboardDrawCircle(x, y, d)      
      
      counter = 1      
      repeat
         local x1 = x + math.random(-3.6, 3.6)
         local y1 = y + math.random(-3.6, 3.6)
         local dist = GetRealDistance(x, y, x1, y1)
         for i, city in ipairs(myCities) do            
            if GetRealDistance(x, y, city:GetLongitude(),city:GetLatitude()) > 1.8 then
            else
               table.insert(locations, city)
            end
         end
               
         -- try to use zero as good location
         --(didn't bump against any cities), > zero as too close
         if # locations > 0 then
            WhiteboardDrawSquare(x1, y1, 0.5)
         else
            WhiteboardDrawSquare(x1, y1, 0.1)
         end   
         
         for i, city in ipairs(locations) do -- empty the table ?
            city = nil
         end
         YieldLongTask()
         counter = counter + 1
      until counter == 100
      
   end)
end

function GetRealDistance(x1,y1,x2,y2)
   dist = math.sqrt((x2-x1)^2 + (y2-y1)^2)
   return dist
end


This code doesn't throw any errors, but it doesn't do what I'm expecting it to do, which is see if my original x, y are within a radius of 1.8 of any city.

A simple test version of it works really well, but only looks at one predefined location. In Defcon, the cities might change from game to game.

Working code:

Code: Select all

function TestTest()
   StartLongTask(function()
   
      local x, y, d = -74.000000, 40.750000, 1.8 -- New York
      
      -- or use:
      
      --local x = math.random(-110, -91)
      --local y = math.random(47, 51)
      --local d = 1.8
      
      WhiteboardDrawCircle(x, y, d)      
      
      counter = 1      
      repeat
         local x1 = x + math.random(-3.6, 3.6)
         local y1 = y + math.random(-3.6, 3.6)
         local dist = GetRealDistance(x, y, x1, y1)
         if dist > 1.8 then
            WhiteboardDrawSquare(x1, y1, 0.5)
         else
            WhiteboardDrawSquare(x1, y1, 0.1)
         end   
         YieldLongTask()
         counter = counter + 1
      until counter == 100
      
   end)
end

function GetRealDistance(x1,y1,x2,y2)
   dist = math.sqrt((x2-x1)^2 + (y2-y1)^2)
   return dist
end
User avatar
DinoSteve
level3
level3
Posts: 251
Joined: Fri Aug 21, 2009 10:36 pm
Location: California, US

Postby DinoSteve » Wed Feb 17, 2010 8:39 pm

Code: Select all

if GetRealDistance(x, y, city:GetLongitude(),city:GetLatitude()) > 1.8 then
else
    table.insert(locations, city)
end


Is that table.insert in the right place? Also, are you allowed to leave if statements empty?
The above post is not intended as an attack on you. It's not about making you look stupid for not searching. It merely states the facts. Please don't be offended.
User avatar
Ace Rimmer
level5
level5
Posts: 10803
Joined: Thu Dec 07, 2006 9:46 pm
Location: The Multiverse

Postby Ace Rimmer » Wed Feb 17, 2010 8:40 pm

As far as I know you are. I had SendChat() there (print function) before, but never got anything.

Edit:

Code: Select all

            if GetRealDistance(x, y, city:GetLongitude(),city:GetLatitude()) > 1.8 then
         SendChat("far away") -- inserted to not have empty IF
            else
         table.insert(locations, city)
            end


Nothing different.
Smoke me a kipper, I'll be back for breakfast...
User avatar
DinoSteve
level3
level3
Posts: 251
Joined: Fri Aug 21, 2009 10:36 pm
Location: California, US

Postby DinoSteve » Wed Feb 17, 2010 8:48 pm

How is myCities passed to the function?
The above post is not intended as an attack on you. It's not about making you look stupid for not searching. It merely states the facts. Please don't be offended.
User avatar
Ace Rimmer
level5
level5
Posts: 10803
Joined: Thu Dec 07, 2006 9:46 pm
Location: The Multiverse

Postby Ace Rimmer » Wed Feb 17, 2010 8:49 pm

Globally.

Code: Select all

myCities = {}
Smoke me a kipper, I'll be back for breakfast...
User avatar
DinoSteve
level3
level3
Posts: 251
Joined: Fri Aug 21, 2009 10:36 pm
Location: California, US

Postby DinoSteve » Wed Feb 17, 2010 10:04 pm

Are you able to edit GetRealDistance() to output the values of dist, x1, x2, y1, y2 and then paste a snippet of the output?
The above post is not intended as an attack on you. It's not about making you look stupid for not searching. It merely states the facts. Please don't be offended.
User avatar
Ace Rimmer
level5
level5
Posts: 10803
Joined: Thu Dec 07, 2006 9:46 pm
Location: The Multiverse

Postby Ace Rimmer » Wed Feb 17, 2010 10:09 pm

I suppose I could. However...

I've made some progress. One of the problems was due to the fact I was comparing x:city/y:city to x/y instead of x1/y1.

x/y is the starting point, x1/y1 is the potential placement location. This was an error as I used New York as x/y, thus I was in effect, trying to see if any other city in Default NA was within a radius of 1.8 (and none are). D'oh!

Progress!

Image

As you can see, the dots (which are small squares) are close to a city. The large Squares must be only looking at one particular city instead of them all, therefore they end up close to CityA but are evaluated against CityB.
User avatar
DinoSteve
level3
level3
Posts: 251
Joined: Fri Aug 21, 2009 10:36 pm
Location: California, US

Postby DinoSteve » Wed Feb 17, 2010 10:29 pm

You mean something like?

Code: Select all

locations = {}
index = 0

city = SelectCity(myCities, index)
x1 = city:GetLongitude()
y1 = city:GetLatitude()

counter = 1     
repeat
    x2 = x1 + math.random(-3.6, 3.6)
    y2 = y1 + math.random(-3.6, 3.6)

    if GetRealDistance(x1, y1, x2, y2) > 1.8 then
        table.insert(locations, city)
        WhiteboardDrawSquare(x1, y1, 0.5)
    else
        WhiteboardDrawSquare(x1, y1, 0.1)
    end

    for i, city in ipairs(locations) do -- empty the table ?
        city = nil
    end

    counter = counter + 1
until counter == 100



function SelectCity(cities, index)
    -- selection criteria, first city in list
    return cities[index]
end


made SelectCity a separate function incase you planned to select based on city size or placement.
The above post is not intended as an attack on you. It's not about making you look stupid for not searching. It merely states the facts. Please don't be offended.
User avatar
Ace Rimmer
level5
level5
Posts: 10803
Joined: Thu Dec 07, 2006 9:46 pm
Location: The Multiverse

Postby Ace Rimmer » Wed Feb 17, 2010 10:34 pm

Well, I'll try your code shortly, I finally stumbled around enough to get something that works, just very slowly and can cause lag. Still, a triumph for me!

:P

my code gets:

Image

The circles around the cities show the area in which a nuke can strike and cause damage to the city. Placing inside the circle would be bad, indicated by the large squares. Placing outside All circles is good, indicated by small squares (which look like dots).

I'll edit in my results from your code
User avatar
Ace Rimmer
level5
level5
Posts: 10803
Joined: Thu Dec 07, 2006 9:46 pm
Location: The Multiverse

Postby Ace Rimmer » Wed Feb 17, 2010 11:43 pm

I changed my mind, not going to edit (double post)...

So, I fumbled around with mine and with yours and couldn't really figure out a way to insert yours. Took a look at the example bot and refined mine some and now it works perfectly and with much less lag, but still makes things slightly choppy when it's running through the repeat section. I'm sure there's a way to clean this up or make it more efficient... right ?

Code: Select all

myCities = {}

function TestTest()
   StartLongTask(function()
   
   local cities = GetCityIDs()
   local us = GetOwnTeamID()
   for i, city in ipairs(cities) do
      local long, lat = city:GetLongitude(), city:GetLatitude()
      if city:GetTeamID() == us then
         table.insert(myCities, city)
         --WhiteboardDrawCircle(long, lat, 1.8)
      else
         SendChat("oops")
      end
      YieldLongTask()   
   end
   
   local x, y, d = -74.000000, 40.750000, 1.8 -- New York
   local counter = 0
   local rank = 0
      repeat
      local x1 = x + math.random(-3.6, 3.6)
      local y1 = y + math.random(-3.6, 3.6)
      local dist = GetRealDistance(x, y, x1, y1)
      local counter2 = 0
      for i, city in ipairs(myCities) do     
         local long, lat = city:GetLongitude(), city:GetLatitude()
         if GetRealDistance(x1, y1, long,lat) > 1.8 then
            rank = rank + 0
         else
            rank = rank + 1         
         end
      end
      
      if rank == 0 then
         WhiteboardDrawSquare(x1, y1, 0.3)
      else
         WhiteboardDrawSquare(x1, y1, 0.5)
      end
      YieldLongTask()
      rank = rank * 0      
      counter = counter + 1      
      until counter == 100   
   end)   
end


Edit: Forgot to mention that one thing that really slowed the game down (apparently) was the code for WhiteboardDrawCircle. I had redefined how many line segments make up a circle; from 20 to 40, for 'prettier' circles. Once I changed it back to 20, it sped right up. I must remember to keep all the functions in mind. :P (in the code up there, it's just a comment)
User avatar
roflamingo
level3
level3
Posts: 404
Joined: Fri Jan 19, 2007 10:25 am

Postby roflamingo » Thu Feb 18, 2010 12:21 am

I use something like this in my code...

Assume x,y is your proposed co-ordinate for your land unit....


Code: Select all

local tooclose = 1.8
counter = 0
for j, city in ipairs(myCities) do
if GetRealDistance(x,y,myCities[j]:GetLongitude(), myCities[j]:GetLatitude() ) <  tooclose  then
WhiteboardDraw(myCities[i]:GetLongitude(), myCities[i]:GetLatitude(), myCities[j]:GetLongitude(), myCities[j]:GetLatitude())
counter = counter + 1
end
end

if counter == 1 then
SendChat(x .." - " .. y .. " it too close to a city. Try Again"
end

User avatar
DinoSteve
level3
level3
Posts: 251
Joined: Fri Aug 21, 2009 10:36 pm
Location: California, US

Postby DinoSteve » Thu Feb 18, 2010 1:27 am

Sorry, misunderstood what exactly you were trying to do so my code wouldn't be much use to you.

There's a huge inefficiency in the loop 100 times as math.random(val1, val2) only returns integers. This reduces the number of valid positions in the range +-(1.8, 3.6) to approximately 44 (imagine a grid with the city in the middle, basically what your whiteboard shows). With so few values doing rand will get a lot of duplicates.

Here are some improvements for your existing code. Rather than

Code: Select all

local x2 = x1 + math.random(-3.6, 3.6)
local y2 = y1 + math.random(-3.6, 3.6)


Do

Code: Select all

local x2 = GeneratePosition(x1, 1.8, 3.6)
local y2 = GeneratePosition(y1, 1.8, 3.6)

function GeneratePosition(base, lowerlimit, upperlimit)
   local offset = math.random(lowerlimit, upperlimit)
   
   if math.random(0, 1) == 1 then
      offset = -offset
   end
   
   return base + offset   
end


That way valid locations are generated each time through the loop and there is no need to check if they are inside the blast radius of a city.

If you want there to be more variance on the placement, i.e. use real numbers instead of integer, you could do

Code: Select all

local x2 = GeneratePosition(x1, 1.8, 3.6)
local y2 = GeneratePosition(y1, 1.8, 3.6)

function GeneratePosition(base, lowerlimit, upperlimit)
   local scale = upperlimit - lowerlimit
   local offset = (math.random() * scale) + lowerlimit
   
   if math.random(0, 1) == 1 then
      offset = -offset
   end
   
   return base + offset   
end


A better improvement would be to imagine a square grid around the city representing possible positions and test each square to see if it's viable. Bit like this:

Code: Select all

function TestTest()
    StartLongTask(function()
      local goodLocations = {}   -- locations outside dangerzone
      local badLocations = {}      -- locations inside dangerzone
      local dangerzone = 1.8      -- nuke blast radius
      local maxdist = 3.6
                local stepsize = 1
      
      --
      -- Generate valid locations
      --
      
      -- Loop through each city
      for i, city in ipairs(myCities) do
         -- current city details
         local x1 = city:GetLongitude()
         local y1 = city:GetLatitude()
         
         -- draw city's danger zone
         WhiteboardDrawCircle(x1, y1, dangerzone)
         
         -- generate all integer values in the square around the city
         -- longitude
         local longitude = -maxdist
         repeat
            -- latitude
            local latitude = -maxdist
            repeat
               -- add some variance to positions
               local x2 = AddVariation(x1, longitude, dangerzone, maxdist, stepsize*0.5)
               local y2 = AddVariation(y1, latitude, dangerzone, maxdist, stepsize*0.5)
               -- local x2 = longitude
               -- local y2 = latitude
               
               -- validate positions
               local dist = GetRealDistance(x1, y1, x2, y2)
               if dist > dangerzone and dist < maxdist then
                  
                  --valid location found
                  table.insert(goodLocations, {longitude, latitude})
                  WhiteboardDrawSquare(x2, y2, 0.1)
               else
                  
                  --invalid location found, could be useful if locations are rare
                  table.insert(badLocations, {longitude, latitude})
                  WhiteboardDrawSquare(x2, y2, 0.5)
               end
               
               latitude = latitude + stepsize
            until latitude >= maxdist
            
            YieldLongTask()
            longitude = longitude + stepsize
         until longitude >= maxdist
      end
      
      --
      --remove valid locations inside another city's dangerzone
      --
      
      -- Loop through each city
      for i, city in ipairs(myCities) do
         -- current city details
         local x1 = city:GetLongitude()
         local y1 = city:GetLatitude()
         
         for i, location in ipairs(goodLocations) do
            -- current location details
            local x2 = location[0]
            local y2 = location[1]
            
            local dist = GetRealDistance(x1, y1, x2, y2)
            if dist < dangerzone then
               table.remove(goodLocations, i)
            end
                
                                YieldLongTask()
         end
      end
   end)
end
         
function GetRealDistance(x1, y1, x2, y2)
   return math.sqrt((x2-x1)^2 + (y2-y1)^2)
end

function AddVariation(city, pos, mindist, maxdist, variance)
   local offset = math.random() * variance
   if math.random(0, 1) == 1 then
      offset = -offset
   end
   
   local pos = pos + offset
   if math.abs(pos - city) < mindist then
      pos = mindist
   end
   
   if math.abs(pos - city) > maxdist then
      pos = maxdist
   end
   return pos
end


I added variance so the positions aren't centered in the imaginary squares. Also added a check to remove locations that lie inside another cities dangerzone (blast radius if hit by nuke)

Disclaimer: I've never programmed in Lua before, this code is untested and I'm not sure if all of it is valid code :P
Last edited by DinoSteve on Thu Feb 18, 2010 4:05 am, edited 1 time in total.
The above post is not intended as an attack on you. It's not about making you look stupid for not searching. It merely states the facts. Please don't be offended.
User avatar
Ace Rimmer
level5
level5
Posts: 10803
Joined: Thu Dec 07, 2006 9:46 pm
Location: The Multiverse

Postby Ace Rimmer » Thu Feb 18, 2010 2:37 am

I did change it to the first suggestion (right after 'do'), and this is what it gives me...

Image

Haven't had time to really digest it.
Smoke me a kipper, I'll be back for breakfast...
User avatar
DinoSteve
level3
level3
Posts: 251
Joined: Fri Aug 21, 2009 10:36 pm
Location: California, US

Postby DinoSteve » Thu Feb 18, 2010 4:04 am

Just downloaded a Lua interpreter to do some simple tests.

A possible problem is that math.rand(1.8, 3.6) rounds the numbers up to 2 and 4 so only generates the values -4, -3, -2, 2, 3, or 4. Not sure if that's ok for your needs. If not, use the second version which uses real/float numbers. I had to correct a mistake with brackets though (incase you copied it elsewhere)


Code: Select all

function GeneratePosition(base, lowerlimit, upperlimit)
   local scale = upperlimit - lowerlimit
   local offset = (math.random() * scale) + lowerlimit

   if math.random(0, 1) == 1 then
      offset = -offset
   end

   return base + offset
end


Not sure how programming savvy you are but math.rand() will generate the same sequence of values each time you run your code unless you add the following some where in your code

Code: Select all

math.randomseed( os.time() )


You only really need to call it the once so can add it to the beginning of your code. Would recommend having it commented out for testing though as it'll make it easier to spot problems.
The above post is not intended as an attack on you. It's not about making you look stupid for not searching. It merely states the facts. Please don't be offended.
User avatar
Ace Rimmer
level5
level5
Posts: 10803
Joined: Thu Dec 07, 2006 9:46 pm
Location: The Multiverse

Postby Ace Rimmer » Thu Feb 18, 2010 4:43 am

I did know about the same sequence thing and os.time, but don't think that's necessary because it's such a small set of numbers and I've seen enough variance so far.

I didn't really pick up on the math.random() using rounded numbers, so I'll need to fix that.

Thanks!
Smoke me a kipper, I'll be back for breakfast...

Return to “AI Bots”

Who is online

Users browsing this forum: No registered users and 1 guest