# parseAreaMap - extract positions from "map" table

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

Tags:
1. ### ColandusWell-Known Member

Joined:
Jun 6, 2007
Messages:
2,424
163
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.

Last edited: Feb 13, 2017
ahmed30, Printer, divegia and 3 others like this.
2. ### ColandusWell-Known Member

Joined:
Jun 6, 2007
Messages:
2,424
163
18
This function does the same as this guy charges 20€ for over here Maybe I should make a business of selling overpriced functions? 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
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€ You're welcome.

Last edited: Dec 27, 2016
3. ### tokenzz:thinking:

Joined:
Feb 2, 2013
Messages:
766
333
2
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

Joined:
Jan 6, 2009
Messages:
6,535
820
0
really?

5. ### ColandusWell-Known Member

Joined:
Jun 6, 2007
Messages:
2,424
163
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:thinking:

Joined:
Feb 2, 2013
Messages:
766
333
2
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. ### ColandusWell-Known Member

Joined:
Jun 6, 2007
Messages:
2,424
163
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:thinking:

Joined:
Feb 2, 2013
Messages:
766
333
2
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. ### cbrmJust another modStaff MemberGlobal Moderator

Joined:
Jan 6, 2009
Messages:
6,535
820
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. ### whitevoFeeling good, thats what I do.

Joined:
Jan 2, 2015
Messages:
3,413
594
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. ### ColandusWell-Known Member

Joined:
Jun 6, 2007
Messages:
2,424
163
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ボーカロイドSupport Team

Joined:
Jun 1, 2011
Messages:
1,120
885
53

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
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. ### ColandusWell-Known Member

Joined:
Jun 6, 2007
Messages:
2,424
163
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. ### whitevoFeeling good, thats what I do.

Joined:
Jan 2, 2015
Messages:
3,413
594
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. ### ahmed30Active Member

Joined:
Jun 7, 2011
Messages:
221
39
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.

Joined:
Jun 6, 2007
Messages:
2,424