1. We have added a Gift Upgrades feature that allows you to gift an account upgrade to another member, just in time for the holiday season. You can see the gift option when going to the Account Upgrades screen, or on any user profile screen.
    Dismiss Notice

Border and Area plot iterators

Discussion in 'Civ5 - SDK / LUA' started by whoward69, Sep 5, 2012.

  1. whoward69

    whoward69 DLL Minion

    Joined:
    May 30, 2011
    Messages:
    8,381
    Location:
    Near Portsmouth, UK
    The problem

    There are many situations where we want "all the plots at distance r from a centre plot". Because of anomalies in the co-ordinate system used by plots on a Civ 5 Map, all of the solutions I've seen to this use scanning techniques to locate actual plots from within a larger set of possible plots - these methods are inefficient and return unordered lists. If you want "the next plot on the border" they quickly become unwieldy.

    What we need is a Lua iterator that will efficiently return plots in order around the perimeter.


    The Theory

    In a rectangular based co-ordinate system, if we want to "walk" the perimeter of a box of "radius" r centered on (x, y) we need to traverse each side of the bounding square in turn, so we need to traverse the points (x-r, y-r) to (x-r, y+r) to (x+r, y+r) to (x+r, y-r) and finally back to (x-r, y-r). For each leg of the journey we keep either x or y constant and vary the other from -r to +r (or vice-versa)

    For our Civ 5 hex based map, we need to traverse each side of the bounding hexagon

    In trying to keep the Civ 5 map co-ordinate system "simple" by making it look like a rectangular grid, the developers introduced unnecessary complexity - anyone who has tried to iterate over plots on the map will know that the code needs to behave differently for odd and even values of y, and trying to (manually) calculate the distance between two plots is non-trivial (but fortunately there is a Map function for doing this)

    If we can use a true hex based co-ordinate system, coding becomes much cleaner as we no longer have to allow for odd/even values of y. Fortunately (at least) one developer was of the same opinion, as we have ToHexFromGrid() and ToGridFromHex() methods, so we can convert our "Map co-ordinate" to a true hex based co-ordinate (with ToHexFromGrid()), work with it and then convert our result(s) back to "Map co-ordinates" (with ToGridFromHex())

    Hex based co-ordinate systems have three axis - x, y and z - and have the property that for any (x, y, z) co-ordinate x+y+z is always equal to zero, which means we don't need to calculate or store one of the values (typically z) as it can always be calculated from the other two values, eg z = -(x+y)

    To "walk" the edges of a bounding hexagon on a (true) hex based co-ordinate system we need to traverse the points as follows
    • along the North edge of the hex (x-r, y+r, z) to (x, y+r, z-r)
    • along the North-East edge (x, y+r, z-r) to (x+r, y, z-r)
    • along the South-East edge (x+r, y, z-r) to (x+r, y-r, z)
    • along the South edge (x+r, y-r, z) to (x, y-r, z+r)
    • along the South-West edge (x, y-r, z+r) to (x-r, y, z+r)
    • along the North-West edge (x-r, y, z+r) to (x-r, y+r, z)
    which keeps one of x, y or z constant and varies one of the others from 0 to r (or vice-versa)

    When coding this we need to bear in mind that if we traverse each side from start to finish we will include each corner twice, so we need to vary from 1 to r (or 0 to r-1)

    We could calculate and store all the plots on the perimeter, but we can use Lua coroutines and closures to create an iterator function that will return one plot each time it is called

    The scary code!

    Spoiler :
    Code:
    function PlotRingIterator(pPlot, r)
      local hex = ToHexFromGrid({x=pPlot:GetX(), y=pPlot:GetY()})
      local x, y = hex.x, hex.y
    
      local function north(x, y, r, i) return {x=x-r+i, y=y+r} end
      local function northeast(x, y, r, i) return {x=x+i, y=y+r-i} end
      local function southeast(x, y, r, i) return {x=x+r, y=y-i} end
      local function south(x, y, r, i) return {x=x+r-i, y=y-r} end
      local function southwest(x, y, r, i) return {x=x-i, y=y-r+i} end
      local function northwest(x, y, r, i) return {x=x-r, y=y+i} end
      local sides = {north, northeast, southeast, south, southwest, northwest}
    
      -- This coroutine walks the edges of the hex centered on pPlot at radius r
      local next = coroutine.create(function ()
        for _, side in ipairs(sides) do
          for i=0, r-1, 1 do
            coroutine.yield(side(x, y, r, i))
          end
        end
    
        return nil
      end)
    
      -- This function returns the next edge plot in the sequence, ignoring those that fall off the edges of the map
      return function ()
        local pEdgePlot = nil
        local _, hex = coroutine.resume(next)
    
        while (hex ~= nil and pEdgePlot == nil) do
          pEdgePlot = Map.GetPlot(ToGridFromHex(hex.x, hex.y))
          if (pEdgePlot == nil) then _, hex = coroutine.resume(next) end
        end
    
        return pEdgePlot
      end
    end
    
    The above code is a simplified version of the actual iterator (see end of post), which is capable of iterating either clockwise or anticlockwise - default is clockwise as above - and can start in any of the six sectors (N, NE, SE, S, SW, NW) - default is North as above.


    Don't worry that it looks complex, as the good news is you don't need to understand it to use it!

    Code:
    for pEdgePlot in PlotRingIterator(pPlot, r) do
      print(pEdgePlot:GetX(), pEdgePlot:GetY())
    end
    
    As the Lua manual says "This is a common situation with iterators: They may be difficult to write, but are easy to use."


    With a working "ring iterator" it's a straightforward task to write an "area iterator" - we just need to iterate each plot in each ring in turn

    More scary code!

    Spoiler :
    Code:
    function PlotAreaSpiralIterator(pPlot, r, centre)
      local next = coroutine.create(function ()
        if (centre) then
          coroutine.yield(pPlot)
        end
    
        for i=1, r, 1 do
          for pEdgePlot in PlotRingIterator(pPlot, i, sector, anticlock) do
            coroutine.yield(pEdgePlot)
          end
        end
    
        return nil
      end)
    
      -- This function returns the next plot in the sequence
      return function ()
        local _, pAreaPlot = coroutine.resume(next)
        return pAreaPlot
      end
    end
    
    The above code is a simplified version of the actual iterator (see end of post), which, like the border iterator, is capable of iterating either clockwise or anticlockwise and can start in any of the six sectors, as well as iterating either outwards or inwards - the default is outwards as above.


    Once again, don't worry that it looks complex, as the good news is you don't need to understand it to use it!

    Code:
    for pAreaPlot in PlotAreaSpiralIterator(pPlot, r, centre) do
      print(pEdgePlot:GetX(), pEdgePlot:GetY())
    end
    
    The plots are returned in order spiraling outwards from the centre, which if we just want all the plots within the area will suffice, however, sometimes we want a "radar sweep" effect, where we get all the points on a radius before moving on to any others. We can write such a "radar sweep" iterator with our ring iterator.

    Yet more scary code!

    Spoiler :
    Code:
    function PlotAreaSweepIterator(pPlot, r, centre)
      local next = coroutine.create(function ()
        if (centre) then
          coroutine.yield(pPlot)
        end
    
        local iterators = {}
        for i=1, r, 1 do
          iterators[i] = PlotRingIterator(pPlot, i, sector, anticlock)
        end
    
        for s=1, 6, 1 do
          for i=1, r, 1 do
            for j=i, r, 1 do
              coroutine.yield(iterators[j]())
            end
          end
        end
    
        return nil
      end)
    
      -- This function returns the next plot in the sequence
      return function ()
        local _, pAreaPlot = coroutine.resume(next)
        return pAreaPlot
      end
    end
    
    The above code is a simplified version of the actual iterator (see end of post), which, like the spiral iterator, is capable of iterating either clockwise or anticlockwise, starting in any of the six sectors, and iterating either outwards or inwards.


    Once again, don't worry that it looks complex, as the good news is you don't need to understand it to use it!

    Code:
    for pAreaPlot in PlotAreaSweepIterator(pPlot, r, centre) do
      print(pEdgePlot:GetX(), pEdgePlot:GetY())
    end
    
    Some Full Examples

    PlotRingIterator(pBabylon, 4, SECTOR_NORTH, DIRECTION_CLOCKWISE)




    PlotRingIterator(pBabylon, 4, SECTOR_SOUTHWEST, DIRECTION_ANTICLOCKWISE)




    PlotAreaSpiralIterator(pBabylon, 4, SECTOR_NORTH, DIRECTION_CLOCKWISE, DIRECTION_OUTWARDS, CENTRE_EXCLUDE)




    PlotAreaRadarIterator(pBabylon, 4, SECTOR_NORTH, DIRECTION_CLOCKWISE, DIRECTION_OUTWARDS, CENTRE_EXCLUDE)




    The Full Code

    This should be placed in a separate "PlotIterators.lua" file with VFS set to true and then included into your own code, eg

    Code:
    include("PlotIterators")
    
    for pAreaPlot in PlotAreaSweepIterator(pPlot, r) do
      print(pEdgePlot:GetX(), pEdgePlot:GetY())
    end
    
    Code:
    --
    -- Plot iterator functions
    --   All functions create an iterator centered on pPlot starting in the specified sector and working in the given direction around the ring/area
    --   PlotRingIterator(pPlot, r, sector, anticlock) returns each plot around a border of radius r
    --   PlotAreaSpiralIterator(pPlot, r, sector, anticlock, inwards, centre) returns each plot within an area of radius r in concentric rings
    --   PlotAreaSweepIterator(pPlot, r, sector, anticlock, inwards, centre) returns each plot within an area of radius r by radial
    --
    -- By converting the Map (x,y) co-ordinates to hex (x,y,z) co-ordinates we can simply walk the edges of the bounding hex
    -- To walk an edge we keep one of x, y or z invariant, iterate the others from 0 to r-1 and r-1 to 0  (r-1 otherwise we include each vertex twice)
    --
    -- By using Lua coroutines we can keep the walking code (fairly) simple
    --
    -- Use these functions as follows
    --
    --    for pEdgePlot in PlotRingIterator(pPlot, r, sector, anticlock) do
    --      print(pEdgePlot:GetX(), pEdgePlot:GetY())
    --    end
    --
    --    for pAreaPlot in PlotAreaSpiralIterator(pPlot, r, sector, anticlock, inwards, centre) do
    --      print(pAreaPlot:GetX(), pAreaPlot:GetY())
    --    end
    --
    --    for pAreaPlot in PlotAreaSweepIterator(pPlot, r, sector, anticlock, inwards, centre) do
    --      print(pAreaPlot:GetX(), pAreaPlot:GetY())
    --    end
    --
    
    SECTOR_NORTH = 1
    SECTOR_NORTHEAST = 2
    SECTOR_SOUTHEAST = 3
    SECTOR_SOUTH = 4
    SECTOR_SOUTHWEST = 5
    SECTOR_NORTHWEST = 6
    
    DIRECTION_CLOCKWISE = false
    DIRECTION_ANTICLOCKWISE = true
    
    DIRECTION_OUTWARDS = false
    DIRECTION_INWARDS = true
    
    CENTRE_INCLUDE = true
    CENTRE_EXCLUDE = false
    
    function PlotRingIterator(pPlot, r, sector, anticlock)
      --print(string.format("PlotRingIterator((%i, %i), r=%i, s=%i, d=%s)", pPlot:GetX(), pPlot:GetY(), r, (sector or SECTOR_NORTH), (anticlock and "rev" or "fwd")))
      -- The important thing to remember with hex-coordinates is that x+y+z = 0
      -- so we never actually need to store z as we can always calculate it as -(x+y)
      -- See http://keekerdc.com/2011/03/hexagon-grids-coordinate-systems-and-distance-calculations/
    
      if (pPlot ~= nil and r > 0) then
        local hex = ToHexFromGrid({x=pPlot:GetX(), y=pPlot:GetY()})
        local x, y = hex.x, hex.y
    
        -- Along the North edge of the hex (x-r, y+r, z) to (x, y+r, z-r)
        local function north(x, y, r, i) return {x=x-r+i, y=y+r} end
        -- Along the North-East edge (x, y+r, z-r) to (x+r, y, z-r)
        local function northeast(x, y, r, i) return {x=x+i, y=y+r-i} end
        -- Along the South-East edge (x+r, y, z-r) to (x+r, y-r, z)
        local function southeast(x, y, r, i) return {x=x+r, y=y-i} end
        -- Along the South edge (x+r, y-r, z) to (x, y-r, z+r)
        local function south(x, y, r, i) return {x=x+r-i, y=y-r} end
        -- Along the South-West edge (x, y-r, z+r) to (x-r, y, z+r)
        local function southwest(x, y, r, i) return {x=x-i, y=y-r+i} end
        -- Along the North-West edge (x-r, y, z+r) to (x-r, y+r, z)
        local function northwest(x, y, r, i) return {x=x-r, y=y+i} end
    
        local side = {north, northeast, southeast, south, southwest, northwest}
        if (sector) then
          for i=(anticlock and 1 or 2), sector, 1 do
            table.insert(side, table.remove(side, 1))
          end
        end
    
        -- This coroutine walks the edges of the hex centered on pPlot at radius r
        local next = coroutine.create(function ()
          if (anticlock) then
            for s=6, 1, -1 do
              for i=r, 1, -1 do
                coroutine.yield(side[s](x, y, r, i))
              end
            end
          else
            for s=1, 6, 1 do
              for i=0, r-1, 1 do
                coroutine.yield(side[s](x, y, r, i))
              end
            end
          end
    
          return nil
        end)
    
        -- This function returns the next edge plot in the sequence, ignoring those that fall off the edges of the map
        return function ()
          local pEdgePlot = nil
          local success, hex = coroutine.resume(next)
          --if (hex ~= nil) then print(string.format("hex(%i, %i, %i)", hex.x, hex.y, -1 * (hex.x+hex.y))) else print("hex(nil)") end
    
          while (success and hex ~= nil and pEdgePlot == nil) do
            pEdgePlot = Map.GetPlot(ToGridFromHex(hex.x, hex.y))
            if (pEdgePlot == nil) then success, hex = coroutine.resume(next) end
          end
    
          return success and pEdgePlot or nil
        end
      else
        -- Iterators have to return a function, so return a function that returns nil
        return function () return nil end
      end
    end
    
    
    function PlotAreaSpiralIterator(pPlot, r, sector, anticlock, inwards, centre)
      --print(string.format("PlotAreaSpiralIterator((%i, %i), r=%i, s=%i, d=%s, w=%s, c=%s)", pPlot:GetX(), pPlot:GetY(), r, (sector or SECTOR_NORTH), (anticlock and "rev" or "fwd"), (inwards and "in" or "out"), (centre and "yes" or "no")))
      -- This coroutine walks each ring in sequence
      local next = coroutine.create(function ()
        if (centre and not inwards) then
          coroutine.yield(pPlot)
        end
    
        if (inwards) then
          for i=r, 1, -1 do
            for pEdgePlot in PlotRingIterator(pPlot, i, sector, anticlock) do
              coroutine.yield(pEdgePlot)
            end
          end
        else
          for i=1, r, 1 do
            for pEdgePlot in PlotRingIterator(pPlot, i, sector, anticlock) do
              coroutine.yield(pEdgePlot)
            end
          end
        end
    
        if (centre and inwards) then
          coroutine.yield(pPlot)
        end
    
        return nil
      end)
    
      -- This function returns the next plot in the sequence
      return function ()
        local success, pAreaPlot = coroutine.resume(next)
        return success and pAreaPlot or nil
      end
    end
    
    
    function PlotAreaSweepIterator(pPlot, r, sector, anticlock, inwards, centre)
      print(string.format("PlotAreaSweepIterator((%i, %i), r=%i, s=%i, d=%s, w=%s, c=%s)", pPlot:GetX(), pPlot:GetY(), r, (sector or SECTOR_NORTH), (anticlock and "rev" or "fwd"), (inwards and "in" or "out"), (centre and "yes" or "no")))
      -- This coroutine walks each radial in sequence
      local next = coroutine.create(function ()
        if (centre and not inwards) then
          coroutine.yield(pPlot)
        end
    
        local iterators = {}
        for i=1, r, 1 do
          iterators[i] = PlotRingIterator(pPlot, i, sector, anticlock)
        end
    
        for s=1, 6, 1 do
          -- In ring (n+1) there are (n+1)*6 or 6n + 6 plots,
          -- so we need to call the (n+1)th iterator six more times than the nth, or once more per outer loop
          -- eg for 3 rings we need a plot from ring 1, 2, 3, 2, 3, 3 repeated 6 times
          if (inwards) then
            for i=r, 1, -1 do
              for j=r, i, -1 do
                coroutine.yield(iterators[j]())
              end
            end
          else
            for i=1, r, 1 do
              for j=i, r, 1 do
                coroutine.yield(iterators[j]())
              end
            end
          end
        end
    
        if (centre and inwards) then
          coroutine.yield(pPlot)
        end
    
        return nil
      end)
    
      -- This function returns the next plot in the sequence
      return function ()
        local success, pAreaPlot = coroutine.resume(next)
        return success and pAreaPlot or nil
      end
    end
    
    
    --
    -- Test functions
    --
    
    highlights = {
      RED     = {x=1.0, y=0.0, z=0.0, w=1.0},
      GREEN   = {x=0.0, y=1.0, z=0.0, w=1.0},
      BLUE    = {x=0.0, y=0.0, z=1.0, w=1.0},
      CYAN    = {x=0.0, y=1.0, z=1.0, w=1.0},
      YELLOW  = {x=1.0, y=1.0, z=0.0, w=1.0},
      MAGENTA = {x=1.0, y=0.0, z=1.0, w=1.0},
      BLACK   = {x=0.5, y=0.5, z=0.5, w=1.0}
    }
    
    function TestPlotHighlight(pPlot, highlight)
      print(pPlot:GetX(), pPlot:GetY())
      if (highlight ~= nil) then
        Events.SerialEventHexHighlight(ToHexFromGrid(Vector2(pPlot:GetX(), pPlot:GetY())), true, highlight)
      end
    end
    
    function TestPlotRingIterator(pPlot, r, sector, anticlock, highlight)
      for pEdgePlot in PlotRingIterator(pPlot, r, sector, anticlock) do
        TestPlotHighlight(pEdgePlot, highlight)
      end
    end
    
    function TestPlotAreaSpiralIterator(pPlot, r, sector, anticlock, inwards, centre, highlight)
      for pAreaPlot in PlotAreaSpiralIterator(pPlot, r, sector, anticlock, inwards, centre) do
        TestPlotHighlight(pAreaPlot, highlight)
      end
    end
    
    function TestPlotAreaSweepIterator(pPlot, r, sector, anticlock, inwards, centre, highlight)
      for pAreaPlot in PlotAreaSweepIterator(pPlot, r, sector, anticlock, inwards, centre) do
        TestPlotHighlight(pAreaPlot, highlight)
      end
    end
    
    -- TestPlotRingIterator(Players[0]:GetCapitalCity():Plot(), 4, SECTOR_NORTH, DIRECTION_CLOCKWISE, highlights.RED)
    -- TestPlotAreaSpiralIterator(Players[0]:GetCapitalCity():Plot(), 3, SECTOR_SOUTH, DIRECTION_ANTICLOCKWISE, DIRECTION_OUTWARDS, CENTRE_INCLUDE)
    -- TestPlotAreaSweepIterator(Players[0]:GetCapitalCity():Plot(), 3, SECTOR_SOUTH, DIRECTION_ANTICLOCKWISE, DIRECTION_OUTWARDS, CENTRE_INCLUDE)
    
     
  2. DonQuiche

    DonQuiche Emperor

    Joined:
    May 23, 2011
    Messages:
    1,122
    This is very nice, Whoward, I am sure many modders will be glad to find this. :goodjob:
    Not only the code but also the explanations. I will definitely add a link to this post on the new wiki.

    Now may I ask why you did need those specific orderings? I am just curious.
     
  3. whoward69

    whoward69 DLL Minion

    Joined:
    May 30, 2011
    Messages:
    8,381
    Location:
    Near Portsmouth, UK
  4. ww2commander

    ww2commander Emperor

    Joined:
    Aug 23, 2003
    Messages:
    1,243
    Location:
    Australia
    Second Don's comment.

    Can you provide some examples that call for such functionality?
     
  5. ww2commander

    ww2commander Emperor

    Joined:
    Aug 23, 2003
    Messages:
    1,243
    Location:
    Australia
    WHoward,

    Notice a lack of response based on my previous comment which might have come across incorrectly.

    To rephrase the question, can you briefly describe some situations that might call for this code to be used.

    This would assist me in understand how to implement it in my mod if required.
     
  6. whoward69

    whoward69 DLL Minion

    Joined:
    May 30, 2011
    Messages:
    8,381
    Location:
    Near Portsmouth, UK
    Been busy with real work :(

    All will become apparent when/if I complete my next major mod :)
     
  7. ww2commander

    ww2commander Emperor

    Joined:
    Aug 23, 2003
    Messages:
    1,243
    Location:
    Australia
    No probs.

    Curiosity is getting the better of me and your projects always surprise :drool:
     
  8. ww2commander

    ww2commander Emperor

    Joined:
    Aug 23, 2003
    Messages:
    1,243
    Location:
    Australia
    Whoward,

    To revisit this post..I am about to embark on writing some resupply code for my mod and was wondering if this code might assist in purpose.

    My plan is to check if a unit needing resupply has enemy units nearby and determine priority of resupply. Thus units on the frontline get higher priority then quiet front units.

    Is this code useful for such things or overkill?

    I am not sure of any other way to get enemy units in area!
     
  9. whoward69

    whoward69 DLL Minion

    Joined:
    May 30, 2011
    Messages:
    8,381
    Location:
    Near Portsmouth, UK
    Like most things in computing the answer is "it depends". If there are only a few possible enemies it may be more efficient to iterate the units and calculate unit-to-enemyunit distances. If however there are many enemies (high chance of there actually being an enemy in that ring), it's probably more efficient to iterate the plots in the ring.
     
  10. whoward69

    whoward69 DLL Minion

    Joined:
    May 30, 2011
    Messages:
    8,381
    Location:
    Near Portsmouth, UK
    A teaser then :)

     
  11. whoward69

    whoward69 DLL Minion

    Joined:
    May 30, 2011
    Messages:
    8,381
    Location:
    Near Portsmouth, UK
    If you know the expected direction of attack (so enemies coming from the West with an expected flanking strike from the South) you can "tweak" the iterator to allow for this. In this case you would start in sector NORTHWEST and set direction to be ANTICLOCKWISE as that will scan those tiles where you expect enemies to be first.
     
  12. ww2commander

    ww2commander Emperor

    Joined:
    Aug 23, 2003
    Messages:
    1,243
    Location:
    Australia
    Thanks for the reply. Unfortunately I cant make out the text of the image too well so am not sure what it is!

    I have studied your code further and am still a tad bit confused on how it works...specifically the spiral version. It mentions a parameter of 4 yet the screenshot shows only the northern 4 tiles being highlighted with east, south and west being only 3 tiles out. I am obviously misunderstanding the example somehow.

    Another concern that came to mind was the processing time if over 200 units had to loop through this code for the relevant plot.

    Anyhow, I will wait patiently and see what you do with the code :)
     
  13. whoward69

    whoward69 DLL Minion

    Joined:
    May 30, 2011
    Messages:
    8,381
    Location:
    Near Portsmouth, UK
    The arrow head is meant to mean "et cetera" (If I'd highlighted all the hexes the start location and direction wouldn't have been clear)
     
  14. whoward69

    whoward69 DLL Minion

    Joined:
    May 30, 2011
    Messages:
    8,381
    Location:
    Near Portsmouth, UK
    If your resupply algorithm depends on the proximity of enemy units, you're going to have to iterate over X units and check for any one of Y units within range no matter what approach you adopt.

    However, there are different iteration strategies, some of which are significantly more efficient than others.

    The worst approach would be
    Code:
      for x = 1 to MaxX do
        for y = 1 to MaxY do
          if (Distance(x,y) < N) then
            resupply(x)
            break
          end
        end
      end
    as this is potentially MaxX * MaxY distance calculations, with an average if MaxX * MaxY/2

    The spiral area iterator requires no distance calculations and only a simple test to see if the owner of any unit in the tile is Mr Y

    For an area of 2 (N=2) this is MaxX * 18 comparisons (6 in ring 1 and 12 in ring 2), for an average of MaxX * 9, so if there are more than 18 enemy units, the area iterator will be much more efficient.

    If the ratio of MaxY to MaxX to small (ie many more X than Y) it will be even more efficient to iterate over the Y units and resupply any X units within their range.

    Also, if you know the expected direction of attack, you can focus the iterators (sector, direction, type) to check that region first, which will further decrease the number of comparisions (as you are more likely to find an enemy unit quickly)

    HTH

    W
     
  15. ww2commander

    ww2commander Emperor

    Joined:
    Aug 23, 2003
    Messages:
    1,243
    Location:
    Australia
    Thanks much for clarifying my concern.

    I will give it a try without consideration for attack direction. Reason being that I want to focus on units being cutoff as well as expected attacks.

    Whoward, you wealth of knowledge is a godsend to our community. If only I could convince you to somehow help me with my scenario UI screens! :joke:
     
  16. whoward69

    whoward69 DLL Minion

    Joined:
    May 30, 2011
    Messages:
    8,381
    Location:
    Near Portsmouth, UK
    There is nothing to lose from adding the expected direction as the iterators will always check all possible plots. Adding such information just reduces the predicted number of tiles checked as you are starting where you expect an enemy to be (ie don't look to the left, then behind, then to your right if you expect the an enemy to be in front of you!)

    For example, if you have a North vs South scenario with expected flank attacks from the East, start the iterator for the South player units in the NorthWest sector and proceed clockwise (NW, N, NE, SE, S, SW) and for the North player in the SouthWest sector and proceed anticlockwise (SW, S, SE, NE, N, NW)

    For an East vs West scenario where expected flank attacks for either side will come from the South, start the East player at NW and proceed anticlockwise (NW, SW, S etc) and the West player at NE and proceed clockwise (NE, SE, S etc)

    (draw them out, they make much more sense on paper!)
     
  17. FramedArchitect

    FramedArchitect Reluctant Modder

    Joined:
    Mar 25, 2012
    Messages:
    798
    Location:
    Missouri
    Thank you; very useful and easy to use.
     
  18. General Tso

    General Tso Panzer General

    Joined:
    Oct 12, 2007
    Messages:
    1,543
    Location:
    U. S. of A.
    Thank you for this truly awesome piece of code. I need to place resources and units relative to each player's starting plot and it definitely saved some major time for me. I made a small change to the PlotAreaSpiralIterator so that it gets passed a minimum radius value instead of the centre boolean value and it works great!

    Just curious - why did you use coroutines? I didn't even know that they existed until now, so I looked them up. Are you using them to increase speed or is there some other reason?
     
  19. whoward69

    whoward69 DLL Minion

    Joined:
    May 30, 2011
    Messages:
    8,381
    Location:
    Near Portsmouth, UK
    For me, they simplify the logic, "Set up processing, do some of it, pause, do something else, then resume processing" is a more logical flow than "stuff lots of state into external variables, process those variables for some amount, do something else (which may or may not inadvertantly affect those variables via bugs) and then come back and process them some more"

    Also, the sector iterator has N ring iterators, which would mean that I would need an array of states - oh the opportunity to index incorrectly into those arrays - so the sector iterator needs to know something about the ring iterator that it shouldn't have to know.
     
  20. General Tso

    General Tso Panzer General

    Joined:
    Oct 12, 2007
    Messages:
    1,543
    Location:
    U. S. of A.
    So if I understand properly you are using coroutine to keep the function alive so that it's values do not disappear. Very interesting.
     

Share This Page