The problem I had encountered was a result of introducing a
Glyph abstraction to the library. It’s main goal was to map all characters, along with the fancy non-standard ones to a single enumeration type for easy access and use.
Glyph enum values were directly translated into a number, which represented an index of a character in the font spritesheet embedded in Malison itself.
While the approach of wrapping all known characters into an enum is not a bad choice per se, it introduces a tightly coupled abstractions which make supplying your custom font to use along the library basically impossible.
The character limit
Going through most important issues, the first one are the supplied enum values in the
There were roughly 180 values, from which some were unused, which gives us less than 180 characters.
Malison’s built in font spritesheet. Notice empty tiles on the bottom along with custom non-standard characters
While you could really go and make a text-based game with such a number of characters, this fixed limit makes it impossible to supply new, different characters not present there in the enumerator.
Technically you could expand the enum and add more values but that’s not a good choice, considering the fact that fonts commonly have more than a thousand of characters.
Another problem is the character encoding. The embedded custom font in Malison is based on ASCII encoding.
That means that it gets the proper index of a character in spritesheet based on it’s ASCII encoding value.
But, if I would like to use the earlier mentioned CP437, I would have to use it’s encoding too if I wanted to use it with it’s proper spritesheet.
CP437 font’s spritesheet. Notice a bigger amount of characters and different order of certain characters.
Without the ability to supply the proper encoding, the code values would be incorrect which then would require me to change the spritesheet to follow the ASCII encoding further down limiting the Malison’s abilities.
The problems put us in the place where we need to refactor this. The coupling is tight, the library’s capabilities are limited.
As I have said before, such abstraction can prove to be handy, but definitely not in the library layer. The user should map his own supplied font in the way he wants.
An universal abstraction which would work for all fonts and encodings is kind of impossible to create here or at least it requires too much effort.
So, do we even need this abstraction in library? Definitely not.
My choice here is to just unwrap the underlying int values.
In the end, that’s what the enumeration type does - give certain numbers some fancy hats and names.
We would now operate on the code of the character and don’t really care what character it is - the library doesn’t need to know. What we need is just an integer or a char variable along with proper encoding to translate it into the correct number.
So the first step is to of course remove this enum once and for all.
Unfortunately the enum was coupled very tightly, scoring 100 references in the Malison project.
Seeing 100 references on abstraction you want to remove is surely motivating.
Leaving you without the boring details and having done all the dirty work I’ll just go ahead and walk you through the results of the refactoring.
Starting from the smaller changes, the
Character structure that is a combination of a foreground colour, background colour and text character now keeps the character code instead.
It also has a static
Encode method, encoding the given
char variable and returning proper code.
By default it returns the character in Unicode encoding, but it can be supplied with any
TerminalBase abstract class now also has the
Encoding property to keep currently used encoding, set in the constructor.
Having done that, I also changed the
WindowTerminal classes to keep track of the encoding too.
The next step was to change all the
Write methods to act accordingly and encode any
char that’s passed inside.
I also had to make a few refinements in the
GlyphSheet class beyond changing the
Glyph into a code. The
GlyphSheet helps in caching the characters and operating on the given spritesheet for easy bitmap rendering.
I made it so it now works for any spritesheet sizes, which was previously set as a constant.
There was also a number of small changes here and there, but now let’s get onto the example of library’s new API.
If we would now take the Malison’s WindowsForms renderer and made a new form inherting from
TerminalForm and want to supply custom font, here’s what we gonna do.
Firstly, in the constructor we set the
TerminalControl’s glyphsheet to a new one, created from our custom font, with given number of rows and columns.
The next step is to set the form’s terminal to a new one, with given size and specified encoding for our font.
And that’s it! Having done that we can go on and test writing with our new font just like before refactoring the library.
And the result of code above is this. We have our font and everything works fine, starting from strings, chars, non-standard char values and ending in integer values passed to
Wrapping it up
To sum it up, the refactoring was successful. The library users can now supply any font as long as they give proper spritesheet for given encoding and just roll with it.
If you have a good eye for details you might have noticed another thing I’ll be working on to refactor.
That thing is the
TermColor enumerator. Oh dear here we go again…
Basically it’s the same problem, there are fixed values which user cannot change, that are then translated to some colours inside the implemented renderer. That’s bad, the user cannot set his own colours so I’ll have to change that into some RGBA structure independent of the platform or framework along with some static methods to easily get the Red, Blue, Green, Black and so on.
I could even go crazy and implement some fancy methods for colour operations but that’s definitely not high on my roadmap.
That’s it for today, I wanted to keep it short but failed miserably so forgive me for that.
Hopefully you learned a few things or maybe got some ideas about tight coupling in your projects?
See you next time!