Falling objects behave according to the following strict rules:
If the tile below is empty, move down.
If the tile below is not empty, but is sloped left, check the tiles to the left and lower left. If those are empty, move left.
If the object cannot roll left, but the tile is sloped right, try to roll right.
This was easy enough to implement, but the hardest rule was:
Objects are predictably updated according to their position, starting in the lower right, moving left, and repeating row by row.
This was difficult for a few reasons. For example, a month ago, I didn't know that SortedDictionary and SortedList existed, and wasn't sure how to sort my objects. Another difficulty was that most coordinate systems in XNA use "unsortable" tuples, such as Point(x,y) or Vector2(x,y) - you can't necessarily say one is "greater" than another.
The solution was to use a packed value. My game will realistically use 128x128 maps at the absolute largest, so this basically means a coordinate pair here would be two bytes in size. I opted to use ushort/UInt16 to represent a particular coordinate, but this also had it's own challenges, mainly how to interpret them.
You can take a random value like 18,681 and do [y = n/256] and [x = n%256], but the world's Tile[] was only say, 20x40 in size, it was very easy to fall out of bounds. This is why I was flipflopping so much on my previous post - should the conversion go on GameState, because it has all the state changes? Should I make extension methods for ushort objects, so I can get adjacent tiles that way? Stuff still needs to reference the World's width so it can scale properly.
So ultimately, I put the logic in the GameState class within one or two small, clean functions, and have a few functions hook up to it. The main one is below, but you can call it with regular (XY) coordinates and it will do the magic under the hood:
The issue was making it all work elegantly. Calling stuff needed to be short and direct. It had to work in a very deterministic way (for replaying level solutions, much later). It couldn't do a ton of work because it might get called a few times per tick per object. The Tile array was just an array and can't store 256x256 values just for the hell of it, etc.
Anyways, this is a major progress milestone, I think, because now I mostly have the foundation laid and I can start doing cooler things.
(And yes, as a consequence, this was mostly to make the red gem roll left, instead of right, every time.)