Despite what the topic title might make you think, what it means is that I actually finished implementing a first pass at the code that will drop the ball through the level. Not all of the level geometry has any effect just yet (black holes do not teleport the ball, for example). Still, this is a most-of-the-way-there solution, which is pretty good for a day’s work (well technically a couple of hour’s work).
My idea for this is that there should be a single method in the Maze entity that you can invoke, giving it a position (ostensibly the position of a ball) and it will tell you if the ball would move from this location, and if so, to where. The AI would use repeated calls to this method to decide what move it wants to make and how that move would play out, and it could also be invoked one step at a time to slowly move the ball through the maze graphically.
Such a method is fairly simple. First, if the ball is resting on the goal line (the bottom row), then it can’t move, but it’s also ready to be scored. Second, check the maze cell below the position given to see if it’s a place that the ball can move into (picking up bonus points along the way) and if so, move the ball there. Thirdly, if the position below the ball is blocked, check to see if this is an arrow that should push the ball to the left or right, and do that if the space the ball would be pushed into is clear.
I wrote an ugly first draft of this concept to ensure that it would do what I wanted, and hooked it up so that clicking on a ball in the maze would call the method repeatedly until the ball decided that it can’t move any longer. This was full of a lot of redundant code and instanceof checks to handle all of the elements correctly.
Since this just invokes the method in a loop, what you end up is the ball jumping directly to the end location. In order to verify that it’s actually taking the correct path, I implemented a dirt simple Marker entity. This is a MazeCell subclass with no sprite; it’s only purpose is to render its bounding rectangle so that you can tell that it’s there. The movement code drops a marker for every cell it visits while the ball is moving.
(The following is an animated GIF; if it’s not animating you can click on it to view it. Still trying to determine under what circumstances WordPress will animate it inline).
Once I was sure that this worked as I intended, I did a little refactoring. Logically I was able to remove all of the redundant code by implementing three new methods in the MazeCell base class and then selectively overloading them in the different subclasses as needed.
First, blocksBall() is a method that returns a boolean and indicates if this cell stops the ball from entering it. The base class version returns true, but the Brick entity returns false if this is a bonus brick or a gray brick that is visibly hidden. We also ensure that the Marker entity does not block the ball, since it’s only in the maze to show us the path the ball is taking. I’ve also marked the Teleport entity (the black hole) as not blocking the ball, so that the ball will pass through it (more on this later).
Secondly, changeBallLocation() is a method that takes a Point instance and potentially modifies it, returning a boolean to indicate if it changed the position or not. This will only be invoked when blocksBall() returns true, so that any maze cell that blocks the ball from moving can instead move it to a different position. The Arrow entity uses this to provide a position that is on the left or right of the direction passed in, based on the current facing of the arrow.
Lastly, didChangeDirection() is a method that will get invoked if we actually changed the location of the ball based on the position that it gave us in response to changeBallLocation(). In the Arrow entity, this invokes the flip() method, so that every time the arrow moves the ball, it changes directions.
With these methods defined the code for dropping the ball is quite simplistic and seems to work quite well. The addition of the proper arrow flipping allows for some of the more complicated Bolo Ball moves, where an arrow moves the ball onto another arrow that directs it back where it came from, sending it in the other direction due to the arrow flips.
There are currently only a couple of things missing from this implementation. Firstly, when an arrow pushes the ball to the left or right, and it ends up on top of a ball left from a previous play, the ball should continue moving in that direction as if it’s rolling over the ball (basically the ball should act like an arrow that always moves the ball in the same direction). This is not currently implemented, but I think it should be fairly simple by having the ball keep a notion of the last direction it moved.
The other thing is the Teleports not working. For this to be done I need to keep a list of the positions that the teleport entities were placed and give that to the single Teleport entity itself, so that it’s changeBallLocation() method can randomly select one and move the ball there. I think I also need to have some extra method that can be invoked if the ball is currently sitting on top of another maze cell, so that we can see the ball move to the position of the teleport entity and then teleport away.
For tomorrow I’ll be working on that aspect and some other structural changes. For example, if this method is to be used for AI calculations, it needs some method of undoing whatever entity changes are made during a run (for example arrow rotations), or while it’s deciding what to do it will modify the board. We also potentially need the ability for the ball to co-exist in a cell with another entity such as a Teleporter.