A little help from Scotty

Today’s goals were well met despite having a doctor’s appointment in which I got a single eye dialated and then lasered (sadly there were no sharks in attendance; the laser beam was quite ordinary). Teleport entities now work the way we want them to, after a little bit of a logic shuffle internally to make that work the way we want it to.

As yesterday, he’s a little animated GIF preview of the day’s changes in action:

Sample black hole action

Sample black hole action (animated GIF)

I started off with some modifications to how my debug markers work. The original implementation just put them directly into the cells of the grid as needed to show the ball movement, which was not totally desirable since that destroys the previous contents of the cell. That was just a quick rough change, but things are a little more sophisticated now.

Instead of a multitude of Marker instances, there is just one, and the Maze entity now tracks a per-cell boolean value that indicates if it’s marked or not. There is an API to set, get and clear the marker “layer”, and the render() method uses it to overlay the single Marker instance on top of everything. A simple change but it makes testing a lot easier.

With that out of the way, I added in a new method in the MazeCell entity that allows it to know when it is being “touched” (i.e. the ball is on top of it”) so that it can act as it needs to. This went through a few iterations.

To start with only the Brick entity was using it (to make bonus tiles know they have been touched) and the logic for dropping the ball would invoke the method inline just as the ball was getting there. However this is not what we really want because for the case of Teleport entities the ball would jump to a new location instantly, which means it never actually seems to touch the teleporter at all. At this stage it doesn’t matter, but since this needs to be used for graphically displaying the ball during play, we need the ball to stay on the Teleporter briefly before moving away.

To this end I simplified the code even more so that instead of being an instantaneous thing, touch is now deferred; right at the top of the movement code, we detect if we’re on top of an entity, and if so we invoke its touch function. That means that the ball ends one movement cycle on the entity and then touches it at the start of the next.

At the same time, since we need the Teleport entity to actually change the location of the ball, that needs to be signaled back somehow. I chose the expedient of modifying the new method so that it takes a point, which the entity can optionally modify to indicate the ball position is changing. In this case, if the touch causes the ball to move, that is considered the entire movement for this cycle, so nothing else happens.

With that in place I added a simple new API to the Teleport entity; you can give it one or more destinations (via instances of Point), query the list of destinations, and have the entity randomly select one for you. In addition, it has a touch method which tries to randomly select a destination from the list, making sure not to select the location the ball is already at. A simple tweak to the maze generation code to add the location of teleports as they are added to the map is all that was needed to activate everything.

Now, if you’re smarter than I am, you might immediately notice a problem with this as I’ve described it. It only took a few seconds to realize what that was and fix it, but it was still a little embarrassing that it didn’t immediately occur to me while I was actually writing the code.

The issue is this: The ball movement code starts by noticing that the ball is on top of a Teleport entity, and so it tells that entity, which gives it the location of another Teleport entity for the exit point. That’s the whole movement, so we leave. On the next step, the first thing that happens is that the code notices that the ball is sitting on a Teleport entity, so it tells the entity, which gives it the location of another Teleport entity, and now we’re burning 100% of the CPU in a huge wormhole loop. Whoops.

The solution to this is rather simple; impose a rule that says that the ball position can’t be modified by a touch on an entity two movements in a row; something else has to happen in between (including nothing, if the ball stops moving). This is readily accomplished by a simple boolean variable. This is structured so that the touch method is still called, but any position change it makes will not be honored. This allows for the Teleport to drop the ball on another entity that supports touch and have it do something without moving the ball.

All in all a good day’s work, I think. For the next steps I want to build in a little bit more debugging in the way of being able to modify the grid layout, so that when we get to the part where we’re actually testing out the full drop code and the rest of the game logic we can easily set up the sort of level situations that we need.