Checking for something at the same location (not stacked)

I need a method to check whether a piece that is moved (or activated; same thing) ends up occupying the same hex location as another type of piece (“terrain”). The terrain piece has the “do not stack” trait, otherwise I would be using SumStack() to achieve this.

So far, I can imagine two ways which I submit for your opinion …

1a. Maintain an array of GPs or a single GP string containing all the locations where “terrain” occurs. Populate this as terrain is placed (“refresh” existing maps using a GKC). Moving piece checks this using a beanshell expression. Probably doable for my application as there will not be more than 20 such locations per map.
1b. A variant whereby the hex grid is mapped as an array of global properties. I am instinctively less keen on this, as there may be 250+ such properties required. It would be little easier to code but take time to clone all the GPs and 250+ GPs doesn’t sound sensible for this small requirement.

  1. A GKC-based method:
  • piece that moves in sends a GKC targeted on terrain piece occupying the same location
  • terrain piece responds to the GKC by setting a GP with the relevant information and sending a GKC back (again using location criteria)
  • piece responds to the second GKC by setting a Dynamic Property to the value of the GP.

I am especially interested to hear if others have addressed this problem in published modules or if there is a much simpler method that I have missed.

Mark

I have used this method before. It is horrendously inefficient (A GKC is sent to and processed by every unit on the map.), but it is fairly simple to implement and works. If this is only happening once as each piece is dropped, you won’t notice the inefficiency.

However, you only need one GKC, the first one, not 2. When the first GKC completes, the value you need will be in the GP ready to read. All you need in the first piece is a Trigger that sends the initial GKC, then sends the command to itself to read the GP value into the Dynamic property.

With <= 20 Locations on a Map, I would probably go for option 1a though to minimise overheads. I would not attempt 250+ locations in Pure Vassal, but would do option 2, or a custom code solution.

There is no cheap method to find pieces on the map that are not stacked with you. Option 1a ‘saves’ the information when the Terrain pieces are placed, rather than having to go and ‘find’ it later.

What would actually be really nice to implement in Vassal is a Global Property ‘Map’ (in the Programming sense, a HashMap) where you can store and retrieve Key/value pairs. Instead of saying

Set/Read value ‘X’ to/from Global Property GP

you would say something like

Set/Read value ‘X’ for key ‘K’ to/from Global Property Map GM

So, in GlobalProperty Map ‘OwnedBy’, you set the value for key {LocationName} to {PlayerSide}

Any counter could read the key {LocationName} from the ‘OwnedBy’ map to find the Player Side the owns their current location.

Thanks Brent. Given that it is just on movement and that I only need 1 GKC for the operation, I will give that method a go first, because it will be easier for me to add to the module as current. If I need to improve piece movement performance, I will re-visit.

I experimented with both methods and found #1 to be far superior. GKCs can be horrendously ineffecient as Brent stated. I noticed GKC lags whenever I moved more than one piece. I’m not at all averse to having hundreds of GPs. It creates a lag of a few seconds when the module launches, but doesn’t slow down actually gameplay at all (as far as I can tell). The neat thing about GPs is that you can mass edit them from the buildfile document. I copied and pasted my 220 regions/zones from the buildfile into a CSV file, created text for one GP, did a find/replace for the other 219, then pasted into the buildfile. The whole thing took 15 minutes.

Anyway, I created a GP for every location that units increment/decrement whenever they enter/create or exit/delete. Each unit has a Calculated Property that uses its CurrentLocation to query that location’s GP. With a little Java math and string function trickery, I’m able to determine all of the following lag-free and in real-time:

Location Vacant/Occupied
Unit Count for each side by Location
Location Ownership
Piece awareness of all of the above

Brent’s new improved version(s) of SumStack (and the new sister function CountPieces or whatever it is) should make this stuff a lot faster. It will be in 3.3.3. Merged a couple days ago into master, so snapshots may start having it soon (try out Expression Builder on your snapshot and see if there’s abunchanewstuff)

Thanks all. Plenty of food for thought and helpful tips for me there.

They are not a magic bullet by any means. They do not make the ‘search through every piece on the map and see if it is one of the ones we want’ bit any faster. All they do is remove the need to play around with Global Properties to do the counting/totalling.

If only a small number of counters respond to the match expression, my new functions will make little difference. They will become more efficient as more counters respond to a given set of conditions as there is no need to generate/store/transmit the commands to update the GP.

And the danger is then to over-use them and make things worse.

A well-designed calculate on drop setup as m3tan describes will always be faster, but obviously has scaling problems if you have large or multiple maps.

Where can I find details on this new and improved SumStack? Are there additional switches / syntax options?

They will be fully documented in the updated reference guide, but the basics are:

Random(x)
Return a random number between 1 and x

Random(x, y)
Return a random number between x and y

IsRandom()
Returns true 50% of the time

IsRandom(p)
Returns true p% of the time

Sum (“Property”, “PropertyMatchExpression”)
Return the sum of the values of property “Property” in all pieces that match “PropertyMatchExpresson” on the current map. Non-integer property values would count as 0.

Sum (“Property”, “PropertyMatchExpression”, “MapName”)
Return the sum of the values of property “Property” in all pieces that match “PropertyMatchExpresson” on the map “MapName”.

Count (“PropertyMatchExpression”)
Count the number of pieces that match “PropertyMatchExpresson” on the current map.

Count (“PropertyMatchExpression”, “MapName”)
Count the number of pieces that match “PropertyMatchExpresson” on the map “MapName”.

$…$ properties can be used as per GKC to pre-evaluate on source piece in Sum and Count PropertyMatchExpressions

A couple of gotcha’s.

  1. The functions will be easier to use, but will be just as expensive as GKC’s to execute. You will save on overheads from not having to manipulate Global Properties to do the sum/count. The savings will be proportional to the number of counters that match the expression. (i.e. only 1 match, no real difference, 100’s of matches, should be quite a but faster).

  2. The “PropertyMatchExpression” is passed as a quoted String, so will need to have double quotes escaped, so will look like this:

“Strength==2&&GetProperty(“Level”+level)==“TestValue””

The Expression Builder has been enhanced to do this for you, but care will need to be taken if you are typing these in yourself. Not pretty, but I don’t see anyway around it as it has to be a valid Java String for BeanShell to be able to parse it as a function argument.

Already implementing Sum and Count in place of some kludgy workarounds I had using GKCs and whatnot. They work great. Good stuff!

I forgot to mention

GetZoneProperty(propertyName, zoneName) - Get the value of Zone level property propertyName in zone zoneName on the current map

GetZoneProperty(propertyName, zoneName, mapName) - Get the value of Zone level property propertyName in zone zoneName on the map mapName.

GetMapProperty(propertyName, mapName) - Get the value of the Map level property propertyName from the map mapName.

I suppose GetMapPropery fixes the issue where one cannot see the NumPieces for a Deck on another map…

So far, all comparison operators work except !~. For instance:

Count("{LocationName!=“Berlin”}")

works. As does:

Count("{LocationName=~“Berlin|East Prussia|Hamburg|Munich|Silesia|Vienna”}")

but not:

Count("{LocationName!~“Berlin|East Prussia|Hamburg|Munich|Silesia|Vienna”}")

It reports an “Unimplemented binary String operator”. Is this known to be non-functional? I can build a giant && as a workaround but wanted to report in case it’s a bug…

Excellent, thanks for the testing. Yes, !~ should be there, I will look into it.

Additonal comment, it is sort of a “dog” performance-wise about .5 second - 2 seconds depending on the complexity. I’d suggest to other module builders to use it at the trailing end of a sequence rather than at the beginning. In otherwords, it’s barely noticeable if a player is done interacting with a piece and already thinking about what piece to click on next. But if it triggers while a player is interacting with a piece, the lag feels clumsy. So definitely for situational use and agree with your caveats above…

Ah, found one more bug. It reports a blank error i.e. “Error=” when it evaluates a $…$ property and a Piece has a string value containing a blank space. For instance, Count("{LocationName==$LocationName$}") generates a bunch of errors if any Pieces are in locations with blank spaces such as LocationName==“East Prussia” or LocationName==“Discard Pile”.

That’s because your match expression is invalid. It should be

Count("{LocationName=="$LocationName$"}")

Ah, you’re right. That makes sense and was also generated by the expression builder. Not sure why, but it kept producing errors so I changed it manually. Must have been some other typo I missed.