[Lua] Table Help

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

Postby Ace Rimmer » Thu May 13, 2010 6:54 pm

Hehe, I was just watching it play out with a similar thought, although I added teamID to see if it was limited to my team or not.

The error only happens with Nukes, Fighters, and Bombers, all of which are included in GetAllUnits(). I wonder if the problem lies in they way some (not all O_o) Nukes, Fighters, and Bombers recive an ID in the 60000+ range instead of using the next available number. Then when it tried to change the condition, it uses the normal id vs the 60000+ id..

Example:

Enemy fighter shows up on radar, logged as 600012 (ID)
Enemy fighter gets killed, true ID is 175 not 600012, no 175 in table
Error



:?:
Defcon Archive wrote:Hello!

Please use version 1.56 instead, and let me know if your problem persists.
Also, Ids in the 60k+ range are deliberate, however there was a bug with the
conversion in an earlier version (probably 1.51b2 is affected, too).

Cheers,
Robin

2009/8/7 Kai K.

> Hi Guys,
>
>
>
> in the current version 1.51(build2) all calls to retrieve unit IDs return
> wrong ids for Fighters and Bombers. The returned IDs are in the 60000+
> range, Using these ids as arguments does not work, however using the ids
> show during debug mode does.
>
>
>
> Anyone noticed this as well? This might be a solution to Spooqs question
> posted last month or so.
>
>
>
> With best regards

There's no clear answer if this was 'fixed'.

<0> = Me
<1> = CPU (TeamID)

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

Postby Ace Rimmer » Fri May 14, 2010 4:17 pm

Update: I have implemented a workaround; simply create a new entry for non-existent units when thy die (vs when they are first seen). This isn't ideal, but get's rid of the errors.

Only seems to happen with enemy Fighters/Bombers and my own Nukes (some, not all).

Hopefully this won't affect coding down the road (like calculating success ratio for hitting cities, keeping track of enemy bombers spotted/downed, and the number of fighters in a give area -to determine fleet strength/airbase proximity-, etc)
Smoke me a kipper, I'll be back for breakfast...
User avatar
Ace Rimmer
level5
level5
Posts: 10803
Joined: Thu Dec 07, 2006 9:46 pm
Location: The Multiverse

Postby Ace Rimmer » Tue May 18, 2010 8:47 pm

Alright, so my workaround for units that show up with 60k+ ID (which you can't use) works. Perhaps now I can finally have the Bot perform to 'real' naval combat.

X = my unit killed
Check = CPU unit killed
Big = ships/ground units
Small = planes
Medium 'x' near Icleland = ICBM shot down

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

Postby Ace Rimmer » Mon May 24, 2010 9:16 pm

New table related problem (well, not technically new):

I now GetAllUnits () [Returns an array containing the unitIDs of all visible units. ] every tick, throw each instance of any 'new' units in a master table using the unitID as the key, and then perform all actions based on my master table (or tables within).

My issue now is trying with trying maneuver ships around I've found that it wants to give multiple orders to a single ship. I'm thinking the 'problem' is caused by the way I am telling it to give orders to ships, which is based on distance to enemy ships. Specifically, if myUnit is within 10-21 distance units of enemyUnit, then move to x,y. I don't yet know how to sort the tables so each of my ships moves based on which enemy ships is closest. Therefore, it seems to pick psuedo randomly from any number of enemy ships within 10-21 distance units vs the closest.

I think another piece of the problem comes from the fact that I have leaders and followers in order to keep formations intact during movement and wait for ships to become 'idle' (velocity of zero) before giving them status of a leader (all other surrounding ships become followers regardless of velocity). However, there are times when all ships come to a stop at the same time and perhaps searching through the tables happens so fast, multiple ships become leaders when I'm really only looking to have a couple.
:?:

I'm sure this is probably horribly ugly to anybody that has real knowledge of code, but here you go:

Sometimes this works all the way through the end of the game, but most of the time it hangs fairly quickly. It's definitely not combat related as I've ran it while allied to the cpu (allowing my ships to move without fighting/dieing). Feel free to criticize anything you see.

Code: Select all

main_unit_table = {}

function RadarSweep()
   for loop, do some stuff like get new units, update old units, etc ...
      main_unit_table[unit].Long = unit:GetLongitude()
      main_unit_table[unit].Lat = unit:GetLatitude()
      main_unit_table[unit].Speed = unit:GetVelocity()
      main_unit_table[unit].Team = unit:GetTeamID()
      main_unit_table[unit].Type = unit:GetUnitType()   
   end
   MoveFleet()
end

function MoveFleet()
   StartLongTask(function()

      for b, v in pairs(main_unit_table) do
         local us = GetOwnTeamID()
         if main_unit_table[b].Team ~= us then
            if main_unit_table[b].Condition ~= "Killed" then
               if (main_unit_table[b].Type == "Carrier" or main_unit_table[b].Type == "BattleShip") then
                  local ex, ey = main_unit_table[b].Long, main_unit_table[b].Lat -- Get position of visible/alive enemy ship
                  for c, v in pairs(main_unit_table) do
                     if main_unit_table[c].Team == us then
                        if (main_unit_table[c].Type == "Carrier" or main_unit_table[c].Type == "BattleShip") then
                           local MyCVx, MyCVy = main_unit_table[c].Long, main_unit_table[c].Lat
                           if main_unit_table[c].Condition ~= "Killed" then
                              if (GetDistance(MyCVx, MyCVy, ex, ey) > 15 and GetDistance(MyCVx, MyCVy, ex, ey) < 21) then
                                 if main_unit_table[c].Speed == 0 then -- Get position of non-dead non-moving unit
                                    if MyCVx > ex then                                    
                                       CVx = ex + 11
                                    else
                                       CVx = ex - 11
                                    end
                                    if MyCVy > ey then
                                       CVy = ey + 11
                                    else
                                       CVy = ey -11
                                    end
                                    if IsSea(CVx, CVy) then
                                       c:SetMovementTarget(CVx, CVy) -- Set new x,y (move toward/away from enemy)
                                       main_unit_table[c].Mode = "FleetLeader" -- Set unit as leader
--Start following========================================================
                                       for d, v in pairs(main_unit_table) do -- combine close units into fleet, follow FleetLeader
                                          if main_unit_table[d].Team == us then
                                             if (main_unit_table[d].Type == "Carrier" or main_unit_table[d].Type == "BattleShip") then
                                                local MyCVbx, MyCVby = main_unit_table[d].Long, main_unit_table[d].Lat
                                                if main_unit_table[c].Condition ~= "Killed" then
                                                   if main_unit_table[d].Mode ~= "FleetLeader" then
                                                      if GetDistance(MyCVx, MyCVy, MyCVbx, MyCVby) < 16 then
                                                         local CVbx, CVby = MyCVbx + CVx - MyCVx, MyCVby + CVy - MyCVy
                                                         if IsSea(CVbx, CVby) then
                                                            d:SetMovementTarget(CVbx, CVby) -- Stay in formation with fleet leader
                                                            main_unit_table[d].Mode = "FleetFollower"
                                                         end
                                                      end
                                                   end
                                                end
                                             end   
                                          end
                                          
                                       end -- For end
                                       YieldLongTask()
                                    end
                                 end   
                              end
                           end
                        end
                     end
                  end -- For End
                  YieldLongTask()
               end
            end
         end
      end -- For End
      YieldLongTask()
   end
   )
end


I put in some whiteboard code to show which of my units was getting an order, which enemy unit/s it was responding to, and where exactly it was being told to go. Obviously, it will travel to the 'last' valid (IsSea = true) x,y it is given. I would expect to see only one line going from myUnit to one enemyUnit along with a single line showing the new x,y, instead of the multiple reactions seen here. A lot of the time it does work that way (got lucky with this image).

Image
User avatar
martin
level5
level5
Posts: 3210
Joined: Fri Nov 19, 2004 8:37 pm
Location: ::1
Contact:

Postby martin » Mon May 24, 2010 9:26 pm

I'll have a read through that. While I am, something comes to mind



Wow, I honestly have no idea what that code does :shock:

You should really split your code up a bit more into functions, I know it often seems pointless to make a function which will only be sued once, but it makes code a lot simpler to read!
User avatar
Ace Rimmer
level5
level5
Posts: 10803
Joined: Thu Dec 07, 2006 9:46 pm
Location: The Multiverse

Postby Ace Rimmer » Mon May 24, 2010 9:51 pm

In short, it:

Loops through enemy ships that aren't dead, loops through my ships that are within a certain distance and then gives my ship (one that's not moving already) a new x,y based on the enemy ship's x,y. I.e., keep my ships exactly 11 units away from your ships. Once one of my ships is moving, loop through my ships again, find any close to that moving ship, and follow it into battle (keep formation).
Smoke me a kipper, I'll be back for breakfast...
User avatar
martin
level5
level5
Posts: 3210
Joined: Fri Nov 19, 2004 8:37 pm
Location: ::1
Contact:

Postby martin » Mon May 24, 2010 10:24 pm

Can I suggest rewriting it with functions which do all of the things you said in your description. That way, it can be inspected one function at a time.

Code: Select all

function getLiveUnits(table)
    local result = {}
    for unitId, unitData in table
        if (not unitData.dead)
            result[unitId] = unitData
        end
    end
    return result
end


then you can write something like

Code: Select all

function GetUnitsOfType(table, type)
    --foreach unit in table, return unit if type == type
end


and you're massive epic uber function becomes so much simpler, with things like

Code: Select all

for k, v in GetLiveUnits(GetUnitsOfType(main_unit_table, SHIP_TYPE))
   
end


once you've done that, we have a chance of helping you, and you may fix it yourself :/
Last edited by martin on Mon May 24, 2010 10:26 pm, edited 1 time in total.
GENERATION 22:The first time you see this, copy it into your sig on any forum and add 1 to the generation. Social experiment.
User avatar
Montyphy
level5
level5
Posts: 6745
Joined: Tue Apr 19, 2005 2:28 pm
Location: London, England

Postby Montyphy » Mon May 24, 2010 10:25 pm

Ace Rimmer wrote:

Code: Select all

for b, v in pairs(main_unit_table) do
   local us = GetOwnTeamID()


Your team ID doesn't change, does it? If it doesn't you should only need to ever call this once during initialization and assign it to a global to cut out the API delay.

Your if statements will be a LOT more readable if you join them rather than nest them:
Ace Rimmer wrote:

Code: Select all

if main_unit_table[b].Team ~= us and
   main_unit_table[b].Condition ~= "Killed" and
   (main_unit_table[b].Type == "Carrier" or main_unit_table[b].Type == "BattleShip") then
   
    local ex, ey = main_unit_table[b].Long, main_unit_table[b].Lat -- Get position of visible/alive enemy ship
    for c, v in pairs(main_unit_table) do
        if main_unit_table[c].Team == us and
      (main_unit_table[c].Type == "Carrier" or main_unit_table[c].Type == "BattleShip") then
           
            local MyCVx, MyCVy = main_unit_table[c].Long, main_unit_table[c].Lat
            if main_unit_table[c].Condition ~= "Killed" and
          GetDistance(MyCVx, MyCVy, ex, ey) > 15 and GetDistance(MyCVx, MyCVy, ex, ey) < 21) and
          main_unit_table[c].Speed == 0 then -- Get position of non-dead non-moving unit
          
                if MyCVx > ex then                                    
          CVx = ex + 11
      else
          CVx = ex - 11
                end
         
                if MyCVy > ey then
          CVy = ey + 11
                else
          CVy = ey -11
                end
         
                if IsSea(CVx, CVy) then
          c:SetMovementTarget(CVx, CVy) -- Set new x,y (move toward/away from enemy)
                    main_unit_table[c].Mode = "FleetLeader" -- Set unit as leader
                       
                    --Start following========================================================
          for d, v in pairs(main_unit_table) do -- combine close units into fleet, follow FleetLeader
              if main_unit_table[d].Team == us and
                           (main_unit_table[d].Type == "Carrier" or main_unit_table[d].Type == "BattleShip") then
                             
                            local MyCVbx, MyCVby = main_unit_table[d].Long, main_unit_table[d].Lat
                            if main_unit_table[c].Condition ~= "Killed" and
                            main_unit_table[d].Mode ~= "FleetLeader" and
                       GetDistance(MyCVx, MyCVy, MyCVbx, MyCVby) < 16 then
                           
                                local CVbx, CVby = MyCVbx + CVx - MyCVx, MyCVby + CVy - MyCVy
                           if IsSea(CVbx, CVby) then
                               d:SetMovementTarget(CVbx, CVby) -- Stay in formation with fleet leader
                          main_unit_table[d].Mode = "FleetFollower"
                           end
             end
                   end
                    end -- For end
          YieldLongTask()
                end   
            end
        end
    end -- For End
    YieldLongTask()
end


I think I may have broken the code though since I haven't tested it and had extra stuff at the end. :P You could make it more readable by turning the chunky if statements into a function, particularly since it seems to change the similar properties.

EDIT: And yeah, what martin said.

EDIT2: Whoops, broken whitespace.
Uplink help: Check out the Guide or FAQ.
Latest Uplink patch is v1.55.
User avatar
Ace Rimmer
level5
level5
Posts: 10803
Joined: Thu Dec 07, 2006 9:46 pm
Location: The Multiverse

Postby Ace Rimmer » Wed May 26, 2010 6:08 pm

Ok, I'm trying to get a grasp on using functions instead of IF statements. I'm trying to figure out how to use your suggested code to return different/multiple types (there are 9 types). This doesn't work (it only displays carriers).

Code: Select all

function GetLiveUnits(table)
    local result = {}
    for unitId, unitData in pairs(table) do
        if unitData.Condition ~= "Killed" then
            result[unitId] = unitData
        end
    end
    return result
end

function GetUnitsOfType(table, type1, type2, type3, type4, type5, type6, type7, type8, type9)
   local result = {}
    for unitId, unitData in pairs(table) do
        if (unitData.Type == type1 or unitData.Type == type2 or unitData.Type == type3) then
            result[unitId] = unitData
        end
    end
    return result
end

function MoveFleet()
   for k, v in pairs(GetLiveUnits(GetUnitsOfType(main_unit_table, "Carrier", "Battleship"))) do
      SendChat(tostring(k) .. " " .. k:GetUnitType())      
   end
end


What am I doing wrong?

Edit: Never mind, "Battleship" is not a type. "BattleShip" is. :P

Edit2: Ok, I think I found part of the problem; it seems I had two BattleShips as part of the same fleet (by accident) even though my intention is to have no two ships in the same fleet. Therefore, trying to give orders to one ship in the fleet crossed with trying to give separate orders to the other ship (which you can't do). I still believe the other half of the issue is because I don't know how to tell it to only respond to the closest enemy ship.

Any ideas on how to implement this (determine which enemy ship is nearest to each of my ships as I give them orders)?
Smoke me a kipper, I'll be back for breakfast...
User avatar
Ace Rimmer
level5
level5
Posts: 10803
Joined: Thu Dec 07, 2006 9:46 pm
Location: The Multiverse

Postby Ace Rimmer » Fri May 28, 2010 5:26 pm

I've decided to kind of 'start over' and rework all of my code to clean things up a bit and hopefully work out a solution to the above along the way.

My first step was to re-replace the original co-routine with martin's and once again found it to be fast. However, on my work laptop (2.1 Core2 Duo, 2GB, X2300 Mobility Radeon) every time RadarSweep() was called and the CPU sent out multiple planes at once, it would 'hiccup' (go from 60 to 40+ fps). This didn't happen when multiple ships/ground units appeared at the same time, only with planes and only when there were multiple ones. When I replaced the co-routine, it improved slightly, but was still there. On my home computer, it almost always hangs instantly (with new co-routine) no matter the vsync setting. When it doesn't hang, it does everything super fast (e.g. places all ships instantly so they become six-ship fleets, unintentionally).

My current question is regarding this:

Code: Select all

local teamIDs = GetAllTeamIDs()

for i, team in pairs(teamIDs) do

      local territory = team:GetTeamTerritories()
      local alliance = team:GetAllianceID()

      SendChat(tostring(team) .. " = "
                .. territory[i] .. " allied with "
                .. tostring(alliance))      
end


which outputs this:

Image
GetAllTeamIDs ()

Returns an array containing all of the teamIDs in the current game.


GetTeamTerritories (teamID)
teamID:GetTeamTerritories ()


Returns an array containing the names of the territories assigned to the given teamID (see §2.3.3).

2.3.3 - Territories

Functions like GetTeamTerritories and GetTerritoryName will return strings from the following list (or in the former case, an array containing one or more of these):

* "Africa"
* "Europe"
* "NorthAmerica"
* "Russia"
* "SouthAmerica"
* "SouthAsia"


GetAllianceID (teamID)
teamID:GetAllianceID ()


Returns the allianceID of the given teamID.



How come I'm not seeing more than one territory name being displayed? A quick check of # territory shows it contains only one entry. ?
User avatar
Ace Rimmer
level5
level5
Posts: 10803
Joined: Thu Dec 07, 2006 9:46 pm
Location: The Multiverse

Postby Ace Rimmer » Mon Jun 21, 2010 10:02 pm

Whee! I now have a (crude but effective) city avoidance code for placing ground units.

Also, I fixed the above, for 1v1 anyway (also crudely I'm sure).
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 6 guests