tl;dr
@TPF_Alexis is correct. I did a detailed explanation on this on a bug report in the forum ages ago when this bug was first reported. The reason it happens the way @TPF_Alexis stated is that in the code, the tiles don’t exist as directly referable objects. The board is kept as an array with states at each array reference. So when the board changes between the “shuffle” and the “steal” portions of the power, the power is unaware.
========
Full explanation & example:
the MPQ board is an 8x8 array where every tile can be referenced by an ordered pair of (row, column) starting in the upper left. So the upper left is (0,0) the upper right (0,7) the lower left (7,0) and the lower right is (7,7). When the code makes a check at a given tile, it would do something like this:
tile(3,5).attributes()
This will return something like the following:
{“row”: 3, “column”: 5, “color”: “blue”, “special”: {“type”: strike, “value”: 58, “ownership”: “friendly”}}
This referral-based identification is important when it comes to power like Medusa. Her power shuffles the tiles by swapping their attributes from the source tile to the target tile. Because the tiles don’t actually exist as discreet objects, only the attributes are being swapped. Her power specifically says to swap the tiles, then steal (and improve) and swapped specials. To do so, an “action stack” is created. The action stack (programmatically) looks something like this:
[swap_tiles(), resolve_stolen_special_tiles()]
The first item in the array is evaluated, then removed. As long as there are still actions in the stack, the code continues to evaluate the first item in the stack, then remove it. So lets walk through executing Medusa’s Hair Meddle:
-
Check if there are actions in the action stack (there are)
-
Evaluate and remove the first action (swap_tiles())
2.1 The action stack is now [resolve _stolen_special_tiles()]
2.2 the pairs of tiles are swapped and a list of each ordered pair representing the tiles that moved is kept.
2.3 Any movement of tiles adds the action “check_for_matches()” and “check_for_dead_board()” to the action queue (anyone remember the bug where the board could become unplayable with no matches and the only way to continue was resign the match?)
2.4 This makes the action stack look like this: [check_for_matches(), check_for_dead_board(), resolve_stolen_special_tiles()]
-
Now we go back to step 1 and check for actions (still more to do)
-
Evaluate the first action and remove (check_for_matches())
4.1 Matches are removed (any green ones making strike tiles for Daken)
4.2 any tile that has been removed steals the attributes of the first tile above it that has attributes (making that tile blank)
4.3 and blank tile with no tiles above it is randomly given a basic tile’s attribute.
4.4 action stack now looks like this: [check_for_dead_board(), resolve_stolen_special_tiles()]
-
Go back to step 1 and check for actions again (still more)
-
grab the next action and remove it from the stack (check_for_dead_board())
6.1 are there any available moves? yes, so do nothing
6.2 action stack = [resolve _stolen_special_tiles()]
-
check the action stack again (still have stuff to do)
-
grab the next action off the stack and evaluate (resolve _stolen_special_tiles())
8.1 Get the list of tile locations swapped in step 2.2 (note, these are the locations and NOT the attributes of the locations, which may have changed since then)
8.2 Loop through the list. If any of the attributes have the “special” key in t, check to see if the type of that key is in (“strike”, “protect”, “attack”)
8.3 if so, check the “ownership” key.
8.4 if ownership is “opponent”, then change the value to “friendly” and add 150 to the “value” key
- check the action stack again See that it’s empty and end the action.