As in previous posts, here is the updated progress sheet:
To spare you comparing this sheet with the previous one, there are changes in the following rows / areas:
- InGame – Tapestry Cards: most one-time-effect cards are implemented and tested
- InGame – Tech Cards: the remaining issues are here only to cover edge cases with unit tests
- InGame – Civilization Actions: a lot of shared internal functionality was done and a few civilizations have been fully implemented and are covered with unit tests.
- InGame – Audio: a good bit of progress especially regarding the music
- InGame – GameLog / Undo + General – Unit Testing: too technical to fully explain, but progress has been made 😉
Furthermore, a new row was added: “InGame – Porting Logic to Lua”, which is the topic into which I dive more deeply in this dev update.
In dev update 3, I mentioned refactoring (= cleaning up previously written code to increase maintainability without changing the functionality of the code). During this process I thought a good deal about how to best approach an upcoming issue: version management for online games.
But first, why is that an important topic? And what makes it not as straight-forward as “just fix the bugs and always update to the latest version”?
Let’s start with bugs. Bugs are annoying. Everyone agrees on that, and it would be best if all software were bug-free. Unfortunately, however, that is not realistic! The greater the complexity of a piece of software, the harder it is to write software that is bug-free. It might be an edge case that has not been thought of; it might be a timing condition between two things that run in parallel; it might be so many other things, unfortunately. This in turn means two things:
- Increased complexity leads to a greater probability of bugs.
- Increased complexity leads to a greater effort being needed to find and fix bugs (on a “per bug basis”)
(An interesting side note: the term ‘bug’ was coined in 1946 due to a moth causing a computer to malfunction…)
Now, while we want to minimise the number of bugs, and we do this with code reviews, unit and integration tests, as well as with QA; at the same time we need to think about what happens when there are bugs. One particular issue that complicates this are online games. Think about the following scenario. The current version, let’s say 1.0, has a bug in regards to a card rule (so a core game logic rule). Once the bug is fixed and the new version is published – let’s say 1.1 – two game clients, one version 1.0, the other 1.1, couldn’t play together as they would calculate the action for that specific card in different ways and thus present different game states to the respective players. So how to handle this problem:
Some games can force their players to always update to the latest version. You often then get a message like “update to keep playing” or sometimes this is hidden behind “server maintenance”. Most of the time this then forces players to finish their running game and restart. And for some games this works! But in a game like Tapestry, in which we want games that can be asynchronous with long times between moves, this is not possible. Consider what would happen if you have a game that has been running for a week: you start the game, it updates, and then it tells you “Your running game is the old version and cannot be finished”. Outrage! And with a good reason 😉
So, in such a scenario the game client that is version 1.1 must be able to play with 1.1 and 1.0 rules. And so on…
Thus, we need the ability to run different versions of the core game logic. With pre-compiled code, which is probably most code, this is quite a hassle for various technical reasons that I don’t want to bore you with. But, if we implement the core game logic with a scripting language, this problem is considerably reduced. A scripting language is interpreted by the computer at runtime, so basically just before starting (or loading) a game we just say “ok, the game is version v1.1, so just load the scripts in folder v1.1”. I realise the whole comparison is a bit oversimplified, but I’d prefer to try to make it understandable for most people, rather than to be 100% accurate!)
Furthermore, if the game logic is implemented in a scripting language, we have the following other advantages:
- We are forced to split the model from the visuals, making it a very clean and testable module.
- We can easily run game code without visuals on a server in order to run AI code and other game related code (e.g., verifying scores for leaderboards) – there will be more on that in a later dev update
- It would be easier to open up some code for modding (e.g., for implementing house rules or other interesting things). This does not mean we will do that, only that we could do that!
There are of course disadvantages:
- A scripting language is pretty much always slower than a precompiled language. However, in our case this doesn’t matter, as the game’s core logic only runs when there is a state change and is furthermore comparatively small, thus this has basically zero impact.
- There is mental overhead by switching between multiple languages during coding. Probably not much though and hard to measure.
But as the advantages are so much more impactful, we decided during the refactoring of the core game logic to port it to Lua. Lua is a very mature scripting language that is used in many other games (the first two that come to my mind are Factorio, which uses Lua a lot, and the UI in World of Warcraft which uses Lua also).
And that is why there is a new row “InGame – Porting logic to Lua”!