This section of the blog is devoted to Devember 2016 and the project that I worked on for that period of time, A-Maze-Balls, which is a simplistic clone of an old Soleau Software game called Bolo Ball. Unlike last year I didn’t have multiple goals, just the one: work on this game a little each day. This worked fairly similar to how it worked last year, albeit with some tooling changes.
After Devember finished up I continued working on that TypeScript game engine and used it for some other prototyping and research work, and so for this year’s Devember project I used the latest version of that engine. I also used GitLab for the project code again, but instead of using anything JetBrains related tool wise (I gave up on them shortly after Devember ended last year) I instead used Sublime Text 3.
Since I didn’t have any ultimate goal except to work on this particular game clone idea I used this as an excuse to get a little bit more experience under my belt with artificial intelligence as it pertains to having an automated player select a move in a two player game, as well as some extra granularity for controlling game state.
I am by no means an expert in this sort of thing, and when it comes to games that require the development of an AI I generally tend to choose something where the AI is more esoteric, such as chasing behaviour and things of that nature. For the most part I don’t know what the best way to go about creating an AI for something like a two player game where the computer plays the other player because I’m generally bad enough at games of that variety that it’s less of a technical gap and more of a “I don’t even know what I should do” kind of thing.
In short, the idea in the game is that each player gets a set of 29 balls arranged at the top of a maze, and on each turn a ball is selected and pushed into the maze, where it plinko’s its way down to the bottom, possibly getting stopped by obstacles. The ultimate goal is to get each ball as close to the bottom of the maze as possible, with bonus points being achieved for getting it directly to the bottom.
For the purposes of the AI my theory was to just evaluate each potential move that the computer can make to see where the ball would ultimately end up, and then it would choose the ball that would gain it the highest score. All in all, this seems to work fairly well as a strategy.
Due to time constraints this is not fully implemented, but my original vision was that there would be a difficulty level selection, and what is currently implemented would be the easier version, with the (unimplemented) more complete version being the hard variety.
What’s basically missing from the harder version is more recursion to get a better handle on possible outcomes. In particular:
- There are teleports that shift the ball to a new location; we only randomly select one when checking the move, but it could recursively check all possible destinations and use some probability weighting to see if there is an acceptable level of risk on such a move.
- There are arrows that change direction when a ball encounters them and gray bricks that temporarily block progress of the ball. The current code stops the run when it encounters another ball, but if it stopped to recursively allow the other ball to finish it’s move first before continuing on this ball, it would have a more correct idea of where the ball will ultimately end up.
- There are arrows that change direction spontaneously. Like the teleports, the simulation could run both ways to see if it might be worth the risk of hoping that the arrow switches directions before the ball gets there.
On the whole I’m fairly pleased with how this worked out, so I think the idea is ultimately sound. I had an original plan for how to lay out the classes to have the same code used to run the game “real time” also be used for the simulation for the AI player and it mostly worked the way that I expected it to. So I’ve learned some valuable insight into that aspect for future projects.
In my game engine code I have the idea of a Scene, with the idea being that this would provide the granularity of splitting up the logic between things like the Title Screen, the game screen, a pause screen, and so on. For the most part this works quite well, but when you get into more complex situations it doesn’t fully solve the problem.
In this particular example, we have several states that the game might be in at any given time:
- Computer player is selecting or executing a move
- Human player is selecting a move
- A ball is dropping through the maze
- We are removing blocked balls
- We are removing gray bricks
- We are dropping all remaining balls one last time
This doesn’t even name all of the states, just the big ones. The idea of a Scene is not appropriate in this case, so I used this Devember to come up with an idea for a reusable Finite State Machine. Basically this is just a wrapper that stores what the current state is, as well as the previous state and the next state.
The idea is that although we always need to know where we ARE, the decision of where to go next might rely on where we just were or where some transient code decided we should go next. This last bit is basically an end run of having to expose enough global state for any code anywhere to determine where to go next, and instead having the engine code determine at some point where it thinks we should go and store that for later handling elsewhere.
This also worked quite well, although I think a bit more research is in order to determine a way to make it more generic (I’m not sure enums are generic-able or extendable in TypeScript, for example).
Once again I had a great deal of fun working on this project over the course of the month. In stark contrast to last year I found it harder to carve out more than an hour of time per day (lots of stuff going on this year) so I didn’t quite make as much progress as last year (no music, for example) but I still think there are great gains to be made in working on a schedule that requires you to do something (and be accountable for it) on a daily basis.
This is a great recharge on the battery of getting back into the swing of regular updates, no matter how small.
So that’s it for this year’s Devember blog posts. See you in 2017!