parseAreaMap - extract positions from "map" table

Discussion in 'Mods & Lua Functions' started by Colandus, Dec 26, 2016.

  1. Colandus

    Colandus Well-Known Member

    Joined:
    Jun 6, 2007
    Messages:
    2,424
    Likes Received:
    162
    Best Answers:
    18
    This function will convert a table like this:
    Code (Lua):
    1.  
    2. local areaMap = {
    3.     {1,2,0,2,1},
    4.     {2,0,0,0,2},
    5.     {0,0,3,0,0},
    6.     {2,0,0,0,2},
    7.     {1,2,0,2,1}
    8. }
    9.  
    Into this:
    Code (Lua):
    1.  
    2. {
    3.    [1] = {
    4.       {x=-2, y=-2},
    5.       {x=2, y=-2},
    6.       {x=2, y=2},
    7.       {x=-2, y=2}
    8.    },
    9.    [2] = {
    10.       {x=-2, y=-1},
    11.       {x=-2, y=-1},
    12.       .... ETC (all coordinates where "2" is, relative to center "3").
    13.    },
    14. }
    15.  
    (Note: you will not get the center position into the table.)

    It's also possible to set center position coordinates, which would instead return a table with real positions, and not positions relative to the area maps center.

    You can define the number which represents the center position on your own. Also, to use numbers above 10, I recommend using variables such as A, B, C etc... Or even starting from 1, considering it might make your area map seem clearer.



    Usage:

    parseAreaMap(areaMapTable[, centerVal=2])
    parseAreaMap(areaMapTable[, centerPos[, centerVal=2] ])

    Parameters:
    areaMapTable: A multi-dimensional table consisting of values (preferably numbers).
    centerPos: [OPTIONAL]: The center position (e.g. a creature or tile position)
    centerVal: [OPTIONAL]: The number representing center position in the area map (default 2)

    Return value: Table consisting of tables with Position objects.



    Example:
    Code (Lua):
    1.  
    2. local areaMap = {
    3.     {1,2,0,2,1},
    4.     {2,0,0,0,2},
    5.     {0,0,3,0,0},
    6.     {2,0,0,0,2},
    7.     {1,2,0,2,1}
    8. }
    9.  
    10. local positions = parseAreaMap(areaMap, player:getPosition(), 3) -- setting 3 as center
    11. local firstPositions = positions[1]
    12. local secondPositions = positions[2]
    13.  
    14. for _, position in ipairs(firstPositions) do
    15.     position:sendMagicEffect(CONST_ME_MAGIC_BLUE)
    16.     -- and some more code here
    17. end
    18.  
    19. for _, position in ipairs(secondPositions) do
    20.     position:sendMagicEffect(CONST_ME_MAGIC_GREEN)
    21.     -- some other code here.....
    22. end
    23.  
    Or, you might loop all areas at the same time:
    Code (Lua):
    1.  
    2. local positionEffects = {[1] = CONST_ME_MAGIC_BLUE, [2] = CONST_ME_MAGIC_GREEN}
    3.  
    4. local areaPositions = parseAreaMap(areaMap, player:getPosition(), 3)
    5. for i, positions in ipairs(areaPositions) do
    6.     for _, position in ipairs(positions) do
    7.         position:sendMagicEffect(positionEffects[i]) -- send magic effect based on value on area map...
    8.     end
    9. end
    10.  

    The function:
    Code (Lua):
    1.  
    2. function parseAreaMap(t, ...)
    3.     if type(t) ~= 'table' then
    4.         return false, error("Error: table expected for 'parseAreaMap', got " .. type(t), 2)
    5.     end
    6.  
    7.     local args = {...}
    8.     local centerVal, centerPos
    9.     if type(args[1]) == 'table' then
    10.         centerPos = args[1]
    11.         centerVal = args[2]
    12.     end
    13.  
    14.     centerVal = centerVal or (not centerPos and args[1]) or 2
    15.  
    16.     local rawValues, values, center = {}, {}
    17.  
    18.     for iy, yt in ipairs(t) do
    19.         for ix, v in ipairs(yt) do
    20.             if v ~= 0 then
    21.                 if v == centerVal then
    22.                     if center then
    23.                         return error("Error: Duplicate center positions!", 2)
    24.                     end
    25.  
    26.                     center = Position(-ix, -iy, 0)
    27.                     if(centerPos) then
    28.                         center = centerPos + center
    29.                     end
    30.  
    31.                     -- With center pos just discovered, correct the raw values and insert into values table.
    32.                     for _, raw in ipairs(rawValues) do
    33.                         values[raw.value] = values[raw.value] or {}
    34.                         table.insert(values[raw.value], Position({x = center.x + raw.x, y = center.y + raw.y, z = center.z}))
    35.                     end
    36.                 elseif center then
    37.                     -- We have a center pos, place directly into values.
    38.                     values[v] = values[v] or {}
    39.                     table.insert(values[v], Position({x = center.x + ix, y = center.y + iy, z = center.z}))
    40.                 else
    41.                     -- We don't have a center pos yet. Store raw.
    42.                     table.insert(rawValues, {value = v, x = ix, y = iy})
    43.                 end
    44.             end
    45.         end
    46.     end
    47.  
    48.     if not center then
    49.         return false, error("Error: no center position found!", 2)
    50.     end
    51.  
    52.     return values
    53. end
    54.  


    Any questions, bugs, suggestions, please reply.
     
    Last edited: Feb 13, 2017
    ahmed30, Printer, divegia and 3 others like this.
  2. Colandus

    Colandus Well-Known Member

    Joined:
    Jun 6, 2007
    Messages:
    2,424
    Likes Received:
    162
    Best Answers:
    18
    This function does the same as this guy charges 20€ for over here :eek: Maybe I should make a business of selling overpriced functions? :D I would be rich!

    And this would be an almost complete conversion of his code (working, but missing a function, so it will only show effect but do no damage):
    Code (Text):
    1.  
    2. local n = 0
    3. local areaT = {
    4.     {n,n,4,4,4,n,n},
    5.     {n,4,3,3,3,4,n},
    6.     {4,3,2,2,2,3,4},
    7.     {4,3,2,1,2,3,4},
    8.     {4,3,2,2,2,3,4},
    9.     {n,4,3,3,3,4,n},
    10.     {n,n,4,4,4,n,n},
    11. }
    12.  
    13. function touchExplosiveBarrel(barrel)
    14.     local barrelPos = barrel:getPosition()
    15.     local area = parseAreaMap(areaT, barrelPos, 1)
    16.     local timer = 5000
    17.  
    18.     -- good job expensive scripter filling the queue stack with events!!
    19.     for i, posT in pairs(area) do
    20.         for _, pos in pairs(posT) do
    21.             addEvent(Position.sendMagicEffect, timer + (i-1)*150, pos, CONST_ME_FIREAREA)
    22.         end
    23.     end
    24.  
    25.     for msgDelay = 1000, timer, 1000 do
    26.         local secondsLeft = (timer - msgDelay) /1000
    27.         local msg =  secondsLeft.." seconds left"
    28.    
    29.         if secondsLeft == 0 then msg = "Kaboom!" end
    30.         addEvent(text, msgDelay, msg, barrelPos)
    31.     end
    32. end
    33.  
    34. function text(msg, pos)
    35.     local players = Game.getSpectators(pos, false, true)
    36.     for _, player in ipairs(players) do
    37.         player:say(msg, TALKTYPE_MONSTER_SAY, false, player, pos)
    38.     end
    39. end
    40.  
    Anyone who cares could make the "dealDamagePos" function as seen in his original code! This was solely for demonstration and to let you know you have profited 20€ :rolleyes: You're welcome.
     
    Last edited: Dec 27, 2016
  3. tokenzz

    tokenzz :thinking:

    Joined:
    Feb 2, 2013
    Messages:
    736
    Likes Received:
    305
    Best Answers:
    1
    replace all table.inserts with regular table inserts, e.g:

    Code (Text):
    1.  
    2. local t = {1, 2, 3}
    3. t[#t + 1] = 4
    4.  
    this would improve performance
     
    Last edited: Dec 27, 2016
  4. cbrm

    cbrm Just another mod Staff Member Global Moderator

    Joined:
    Jan 6, 2009
    Messages:
    6,528
    Likes Received:
    808
    Best Answers:
    0
    really?
     
  5. Colandus

    Colandus Well-Known Member

    Joined:
    Jun 6, 2007
    Messages:
    2,424
    Likes Received:
    162
    Best Answers:
    18
    Yes I know. I just feel using inserts are cleaner. The difference is too minimal to pay any great attention to imo.
     
    StreamSide likes this.
  6. tokenzz

    tokenzz :thinking:

    Joined:
    Feb 2, 2013
    Messages:
    736
    Likes Received:
    305
    Best Answers:
    1
    I'd say it looks much cleaner without the function calls. But it's just a personal preference I guess. Also, it'd require a little refactoring changing it.

    Performance-wise it doesn't really matter anyhow I guess since you don't even have to think about performance in terms of Lua scripts like these (as far as you don't do anything wrong) . However, regular table inserts are faster than using function calls with table.insert(). This is what I meant @cbrm . Thanks for the harsh comment though :)

    good job anyhow
     
  7. Colandus

    Colandus Well-Known Member

    Joined:
    Jun 6, 2007
    Messages:
    2,424
    Likes Received:
    162
    Best Answers:
    18
    Yes, it's certainly a personal preference. I used to be the other way first, hated table.insert. Then I started liking them. I guess I don't like the fact I have to repeat the table name twice (longerTableName[#longerTableName + 1]). If table names are shorter (as in your case, t), I usually go for the inline expression.

    Also, I didn't see anyone being harsh. I don't suppose you meant me. I believe cbrm asked out of sincere curiosity.

    All feedback is appreciated though. So, thank you.
     
    Last edited: Dec 27, 2016
    tokenzz likes this.
  8. tokenzz

    tokenzz :thinking:

    Joined:
    Feb 2, 2013
    Messages:
    736
    Likes Received:
    305
    Best Answers:
    1
    dunno, just got the general impression of sarcasm from his comment "really?". It didn't feel like a question or sense of surprise at all. If it was, I'm sorry for the misunderstanding.
     
  9. cbrm

    cbrm Just another mod Staff Member Global Moderator

    Joined:
    Jan 6, 2009
    Messages:
    6,528
    Likes Received:
    808
    Best Answers:
    0
    I just didn't find any basis for that, I also prefer using table.insert over the other one.
    Anyways, not a big deal for the script performance, imo. Sorry if it sounded harsh.
     
  10. whitevo

    whitevo Feeling good, thats what I do.

    Joined:
    Jan 2, 2015
    Messages:
    3,374
    Likes Received:
    580
    Best Answers:
    1
    Your parse function can give same final result as getAreaPos(), but it not the same thing.
    getAreaPos() in other words is sequence mapping with few features not shown in the example code.
    Yours formats the areaT to position map.

    Why act like you know my functions and say something like this?
    I charge per hour, not per script/function. I guess it only took you max 1 hour to do the above code, which in my world would be 5€
    Let customers/donators/collaborators decide am I overpriced or not.

    Other than that, your code looks good to me.

    suggestions:
    make area rotateable

    questions:

    What do you mean by that?
    If I'm doing something wrong, care to explain and improve instead of stacking events yourself xD
     
  11. Colandus

    Colandus Well-Known Member

    Joined:
    Jun 6, 2007
    Messages:
    2,424
    Likes Received:
    162
    Best Answers:
    18
    You should only call 1 event and inside the event callback call the event again. No it took me max 30 min. Rotateable is easy. I will add it later. No I still won't need 4h to make what you did. But that's not important. Regardless I wouldn't charge someone for 4h for a function. Doesn't make much sense.

    Whatever.
     
    Last edited: Dec 28, 2016
  12. Mkalo

    Mkalo ボーカロイド Support Team

    Joined:
    Jun 1, 2011
    Messages:
    1,118
    Likes Received:
    880
    Best Answers:
    53
    As you asked for suggestions:

    Here:
    Code (Text):
    1. for areaVal, posT in pairs(rawValues) do
    2.     values[areaVal] = {}
    3.     for _, pos in ipairs(posT) do
    4.         table.insert(values[areaVal], Position({x = center.x + pos.x, y = center.y + pos.y, z = center.z}))
    5.     end
    6. end
    7.  
    Instead of storing the raw position in a table indexed by the value, you store a table that has the raw and the value so you will need only 1 for to parse the raw values.

    Here:
    Code (Text):
    1. if not values[v] then values[v] = {} end
    More readable:
    Code (Text):
    1. values[v] = values[v] or {}
    And for me it's silly to have a function that loops through a table, to generate another table that will also be looped to do something, instead you could make a function that you pass the map and a table looking something like:
    Code (Text):
    1. {
    2.     [1] = function(pos) dosomethingwithpos(pos) end,
    3.     [2] = function(pos) doshitwithpos(pos) end,
    4. }
    Then for every 1 and 2 values in the table those functions are going to be called passing the position as parameter.

    It would make it easier to use imo.

    @cbrm https://springrts.com/wiki/Lua_Perf...ng_Table_Items_.28table.insert_vs._.5B_.5D.29
     
    tokenzz likes this.
  13. Colandus

    Colandus Well-Known Member

    Joined:
    Jun 6, 2007
    Messages:
    2,424
    Likes Received:
    162
    Best Answers:
    18
    1. I would argue that an if-statement is faster (which I have no proof of though), but then I'd be contradictory (considering my statement on a previous post), so I will agree with you on that.

    2. Yes, certainly right. The reason I did so is because I initially made the raw values code before I did the other one, thus I did not consider it.

    3. Yes, that would be convenient, however it wouldn't always be preferred. Should definitely be an option though. The reason is because I initially made this function to use them as static positions (basically a tile with some pillars around it) instead of having to write the positions manually. My main concern is also that people will find it too complex to use a callback. OT scripters aren't used to that type of coding. Also it's not always you want entirely different functions for the values, as in the second example. Perhaps an option to return an iterator instead?

    Thanks for your feedback, will update once I get home.

    Update: Fixed 1 and 2. I am not sure about 3. Need to figure what options to make. Input, @MatheusMkalo?
     
    Last edited: Dec 28, 2016
  14. whitevo

    whitevo Feeling good, thats what I do.

    Joined:
    Jan 2, 2015
    Messages:
    3,374
    Likes Received:
    580
    Best Answers:
    1
    Once again you assume you know everything what does getAreaPos() do. Yeah your function can be used for one of the things what getAreaPos() does, it's still not the same thing.
    Just leave it be, don't compare cog with clock.

    About events. I still don't understand.
    Why would I make event what loops trough positions and puts events in sequence instead of what I did before (loop trough positions and sequence the events)

    Or you mean that if 2 or more different positions should execute at the same time, I should group them into 1 addEvent?
    In that case I just prefer handling every position 1 by 1, code looks better and less complex + I haven't found a problem in performance by making addEvent for each position.
     
  15. ahmed30

    ahmed30 Active Member

    Joined:
    Jun 7, 2011
    Messages:
    221
    Likes Received:
    39
    Best Answers:
    0
    Awesome release @Colandus as always, but the code is kinda sophisticated, it could be much more cleaner by finding the centerPos before generating the positions. For example:
    Code (Text):
    1.  
    2. do
    3.    local function findCenterPos(t, centerValue)
    4.         for iy, yt in ipairs(t) do
    5.             for ix, v in ipairs(yt) do
    6.                 if v == centerValue then
    7.                     return iy, ix
    8.                 end
    9.             end
    10.         end
    11.         return nil
    12.     end
    13.  
    14.     function parseAreaMap(t, centerValue)
    15.         local positions = {}
    16.         local centerX, centerY = findCenterPos(t, centerValue)
    17.         for iy, yt in ipairs(t) do
    18.             for ix, v in ipairs(yt) do
    19.                 if v ~= 0 and v ~= centerValue then
    20.                     local pos = Position(centerX-ix, centerY-iy, 0)
    21.                     if not positions[v] then
    22.                         positions[v] = {}
    23.                     end
    24.                     table.insert(positions[v], pos)
    25.                 end
    26.             end
    27.         end
    28.         return positions
    29.     end
    30. end
    31.  
    Personally, i don't see how ternary operator would be more readable than an if statement in this case.

    @Colandus provided a useful function that reduces alot of code complexity and fits the majority of people. Unfortunately, creating a code that fit everyone's needs and preference too is impossible. So, you can always modify the function or override it to fit your needs and/or preference. For example:
    Code (Text):
    1.  
    2. do
    3.     local oldParseAreaMap = parseAreaMap
    4.     function parseAreaMap(t, centerValue, posFuncs)
    5.         local areaPositions = oldParseAreaMap(t, centerValue)
    6.         for i, positions in ipairs(areaPositions) do
    7.             for _, pos in ipairs(positions) do
    8.                 local func = posFuncs[i]
    9.                 if func then
    10.                     func(pos)
    11.                 end
    12.             end
    13.         end
    14.     end
    15. end
    16.  
    And, if you want to handle raised errors by yourself i suggest encapsulating the code and use pcall instead of direct function call.
     
    Last edited: Jan 10, 2017
    Colandus likes this.
  16. Colandus

    Colandus Well-Known Member

    Joined:
    Jun 6, 2007
    Messages:
    2,424
    Likes Received:
    162
    Best Answers:
    18
    Thanks. I just didn't want to loop twice just to find the center. I'm not sure how important it is that the function is cleaner to that level. Handling the optional parameters could be prettier though.
     
    Last edited: Jan 10, 2017

Share This Page

Loading...