I’m not talking about the kind where the robots rise up and take us down (my AI is not that sophisticated by far) but rather the black hole variety. I can’t believe that the Romulans power their starships with these things. This is a minor nerd alert; I just spent 10 minutes traipsing through Memory Alpha making sure I didn’t make that up.
In any case, today’s changes entirely encompass the black hole Teleport entities, and their (prior) flaws, such as clobbering balls when they’re blocked, and becoming semi-inactive when they became unblocked. This was only supposed to be a minor task (and in the end, it was) but I kept waffling about how I wanted to fix it and went down a few blind alleys before coming up with my ultimate design.
To start with, here’s how things are currently working:
This small sample contains only two active black holes, which means that entering one exits at the other end. This shows that if a ball blocks the exit, that exit cannot be chosen any longer, which in this case means the unblocked black hole does nothing at all. Once the blockage is cleared, everything works as expected again.
Behind the scenes, the Maze entity contains a single Teleport entity, which has a list of destination points that it can transfer a ball to when the ball is right on top of it. When the maze is generated, that same entity is stamped into all of the maze locations where one is needed. The rendering of the black holes is done based on the list of destinations and not based on their actually being in the content of the maze.
When we start a ball dropping through the maze, we remove it from the actual content of the maze and just track it’s position while it falls. Once it comes to a final stop, it’s re-inserted into the maze. Since the ball can overlay a Teleport entity, this meant that if the ball exited and could not drop any farther, it would be reinserted at the position in the maze where the teleport entity used to be.
Visually this works out OK, because that single Teleport entity still knows that a destination is at this point, which allows it to still be slightly visible under the ball. However, once the ball moves away from that location, the location would be handled as completely empty. This means that although entering the teleport at another location could still exit here, it could not be entered because there was no entity in the grid for the ball to touch.
I don’t know that I would actually classify this as bug, since I had a pretty good idea of exactly why it was working that way (more or less by design). However, I fixed it with a simple modification to the ball dropping code. Once we determine that the ball is definitely going to drop downward, we check to see if the position that the ball is currently hovering over is empty and also a teleport destination, and if so we can add the teleport entity back into the maze at this location. This fixes the problem and restores the ability of that location to be the source of a teleport.
While working on that change, I discovered an actual bug that I wasn’t aware of; no checking is done when a teleport actually happens to ensure that the ball isn’t landing at a blocked location. The worst case scenario here is that if an exit was blocked, a ball entering another teleport somewhere could exit at the same location that was blocked. That ball would be considered blocked, put back into the maze, and clobber the ball that was originally blocking the exit.
Clearly the fix for this is to ensure that we don’t select a destination that’s already blocked. However, in the case of multiple random exit points this is a bit more complicated; we can’t just keep randomly picking a destination until we hit one that is not blocked; depending on conditions in the maze (possibly contrived) this could be an endless loop. If there are a lot of destinations and most are blocked, it could also be an overly lengthy one.
I fixed that by implementing a check to verify before anything happens that any of the destinations is valid and unblocked by just checking them all. This is where I sort of went down the garden path because I waffled a bit on whether it was the calling code, the Teleport entity itself, or both working together that would determine if an exit was blocked or not, and what to do about it.
In the end I decided that this should be purely an entity only change; you ask it for a position, it gives you one and you can trust that it’s valid.This still brute forces by repeating the selection until one works, but for our cases such a slowdown is probably quite minimal in the degenerate case.
We’re rapidly running out of Devember here, so with this bug fixed I am definitely going to work out a simple title/game over screen system tomorrow. I want to have three possible games: short, normal and long. Short would have 50% fewer balls, normal is a single round like the prototype has been all along (except with no automatic arrows) and long is a three round game where the last round includes the automatic arrows.
I think I can easily boost most of the required support code from a previous project, since I haven’t added any menu type code to ts-game-engine yet.