State of the machine
11 January 2020
So I started to play about and realised pretty quickly that there was a state machine in the logic just fighting to make itself known.
It seemed like a good thing to expose, so I refactored the logic to expose that, which caused some significant restructuring of my project files too.
But then it was clear that the code was now modular enough to write unit tests around, and that didn’t seem like an opportunity I could pass up. So I did (write tests, not pass it up ;) ).
Testing
Unit testing is a fantastic way to expose classes that have more than one responsibility, and it did just that. Suddenly it was evident that states were aware of each other. That kind of coupling was making testing really ugly.
After a bit of thought, I realised it would be relatively straightforward to decouple state transitions from state implementations by adding defined ‘exit points’ to each state. So now each state only needs to verify that, given some input, it reaches the correct exit point. It doesn’t care what followup action is attached to that exit point. Phew, one source of gnarly coupling down.
These exit points needed hooking up somewhere, so I built a class to do that. Now there is a single method in the project with a clear overview of all the possible state transitions. Brilliant!
I had a bit of a fight with getting the Pause/Resume functionality to work because I started out assuming ‘Paused’ was a state and that ‘Pausing’ was a user action. Turns out that isn’t exactly the case. The state is ‘ShowMenu’, the action is ‘UserPressedEscape’, and pausing? Pausing is a sort of side issue which I’m now handling in the state machine as its own concept. When I made that change, everything came together and was easy to write and reason about. So seems like the correct direction.
I’m also being aided in writing the unit tests by the superb mocking library FakeItEasy, it is so much more intuitive and straightforward than others I’ve tried. There is still room for improvement in the expressiveness of setting up Expectations on strict mocks, but it’s pretty good.
Flow diagrams
To make sure I wasn’t missing any necessary transitions, I was documenting my state graph in GraphViz as I went. GraphViz, btw, is an ugly, frustrating, and utterly invaluable tool. If you haven’t played with it before, take a look: GraphViz.org
Then it occurred to me: now, I have a single method with the transitions defined. Could I write some code to generate the GraphViz input file directly from the source using reflection? The answer is yes, easily! Since it’s only a tool for me, I banged this together as a unit test - ran the test, copied the output into GraphViz, and bang - a state flow chart was born.
)
(example state flow direct from the code)
I’m quite pleased with this as it makes it much easier to confirm the transitions are as expected. I’m very much a visual thinker, and linear text doesn’t always cut it.
Around and around we go
After all this testing and refactoring one area remained that was still a bit weird - the Playing state. It does a lot. It was a pain to test. It could be split it up into 5 smaller states, which, whilst a little less performant, would be so much easier to reason about.
I’ll pay a small performance price for clarity any day, at least until I see evidence it is actually causing a problem.
However, there is a significant downside to this approach, and that’s logging. At the moment, I write out nice state transition logs that are easy to follow and hit all the key moments of the game. If I split the playing state into little states, it would rattle around those transitions hundreds of times a second, which would destroy the logs. Further, all the little states would have largely the same dependencies. Ultimately, I decided the way it is is good enough. The code is clear and concise, I’ll just have to put up with the awkward tests.
Cleanup
So with this done, the solution has cleaned up a lot. I now have a clear separation between the engine project, the logic project, and the UI project. That feels good and should make it simpler to add changes going forward.
The architecture looks something like:
)
(idea of the internal structure)
Unfortunately, the audio side of this is still, shall we say, aspirational. The code there is a mess. I think it’s about time I tidied that up.
Github :(
In less positive news, I’ve run into a few issues with Github lately. In terms of hosting my git repo it’s all going fine, but the CI aspect has run into a terminal problem. All the historic CI builds have accumulated and consumed all my allotted storage space. BUT, and this blows me away, there is no way to delete the data. So my options are a] pay an ever-increasing amount for more storage, or b] find a different service. Obviously, the answer is going to be B, but I really don’t want to go through setting it all up again. Grrr.
In the same vein, I discovered (well, hadn’t realised if I’m entirely honest) that making releases on a private repo doesn’t make the release artifacts public. So the download button on this site is only working for me :ouch: I’m surprised there isn’t an option to make releases public, but there isn’t. So, another technical chore is coming up. Find another host and script the publishing of the releases to the new location. I’m stunned in a way that all these issues haven’t been completely resolved by now - surely every project has similar requirements?
more, More, MORE!
I’ve played my three levels through enough times now; I want some more! Creating them by hand is a real bugbear, so I’ve added a Level Creation Tool to my todo list. I think this is going to have pretty high prio after the code cleanup and fullscreen behaviour.