When I first started writing BASeTris I had imagined that I could eventually turn it into a sort of “engine” in the sense that there are many different games that use a similar approach- where you have falling blocks of some kind on a 2-D grid, and something happens (or can happen) each time they “fall” and lock into place. Some time ago I started to entertain the idea of implementing a way to play a “Dr Mario” style game with it.
Since I had largely focussed on Implementing a Tetris game, there was still a lot of strong coupling between Tetris-specific features and the main engine. Furthermore, There were many other peripheral aspects which would need to have features added or redesigned, such as the visual themes.
Game Handlers
The first change I made was to establish the concept of a “Game Handler”. Initially, I had imagined that different games would just different core Game States, however, these sorts of puzzle games share a lot of different features and aspects and I feel making that a series of subclasses would probably be more annoying than useful. Instead, I created an interface, which I called IGameCustomizationHandler.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public interface IGameCustomizationHandler { String Name { get; } FieldChangeResult ProcessFieldChange(GameplayGameState state, IStateOwner pOwner, Nomino Trigger); IHighScoreList GetHighScores(); Choosers.BlockGroupChooser Chooser { get; } IGameCustomizationHandler NewInstance(); TetrominoTheme DefaultTheme { get; } void PrepareField(GameplayGameState state, IStateOwner pOwner); //prepare field for a new game. (or level or whatever- basically depends on the type of game) BaseStatistics Statistics { get; } Nomino[] GetNominos(); StandardGameOptions GameOptions { get; } GameOverStatistics GetGameOverStatistics(GameplayGameState state, IStateOwner pOwner); IGameCustomizationStatAreaRenderer<TRenderTarget, GameplayGameState, TDataElement, IStateOwner> GetStatAreaRenderer<TRenderTarget, TDataElement>(); } |
This establishes the responsibility of things like the Theme, handling when the Field changes, preparing the field before a game starts, the valid nominoes for the game, the standard game options, and so on and so forth to be through this interface, where before many of these things were hard-coded into the main gameplay state itself. This also facilitates a custom statistics renderer, custom stats, and handler-specific high scores.
Themes
Another stumbling spot was the aforementioned theme. After all, a “Tetris 2”, or “Dr.Mario” handler ought not to use the existing themes for Tetris, they would need their own theme. I went through several attempted designs before settling on one. In this case it became clear that themes generally ended up being largely pixel-based, and many of the similar blocks would have a few colour variations. This resulted in CustomPixelTheme.
CustomPixelTheme codified some of the patterns I had noticed. It takes two generic arguments, which are intended to codify the different Block Types and the different pixel types for the available components of the theme. As an example, the Nintendo NES Version has three different blocks, which I codified in an enum as Darker, Lighter, and Boxed.
The idea is that for each of these block types, there is a different arrangement of Pixel types- So each block type has it’s own bitmap, but then those pixel types are used to map to create colours based on other attributes. For the NES Theme for example the current level is used to change the “palette”.
A similar approach was used to handle the SNES Tetris Tetromino style, but with 28 colour schemes and more pixels being present, as well as a different theme for “Active” versus “field” blocks. The base class caches the resulting drawn images for performance, so it’s not redrawing them every time, only when needed. And in the case of the SNES style I created a way to use bitmap resources. I could draw the bitmaps directly in a way I could see, map the colours in the bitmap to standard pixel types for the theme, then those can be used with appropriate colour themes and mapped back to new colours to create the appropriate bitmap.
Once that had been refactored (which came with a bonus of improved performance) I set about creating a simple theme to be used for a “Dr. Mario” handler. This more or less duplicates the NES Title’s visuals for the pills and viruses. It also introduced animation, given I wanted the viruses to animate, which itself was a refactoring of the core engine to mimic BASeBlock and keep animated blocks separate from those that would otherwise be static in the field.
Theming was of course only the first of the problems. As anybody who has played Tetris as well as Games like Dr.Mario know, there are a number of different “rules” that vary between titles. In the case of Dr Mario, you do not clear “lines” of blocks, instead, your goal is to eliminate coloured virus blocks by having series of the same colour of pill blocks lined up. Tetris 2 uses a rather similar scheme, but instead of pills there are tetris-type blocks that fall. For this reason the custom blocks intended for this “Game style” I’ve come to call “LineSeries” Blocks.
Even with all this refactoring- The main display output uses SkiaSharp pretty much exclusively now, and I’ve significantly refactored a lot of parts of the program to properly allow easier addition of unique game styles, there is still a lot of room for improvement. I expect this really is the case for almost any program. the “AI” for example can be improved such that the Handler type has some bearing on the evaluation, as right now it simply cannot play Dr.Mario- it tries to form lines which isn’t exactly helpful. I’d like to also implement a “Tetris 2” Handler which would have a lot in common with the Dr.Mario handler but still introduces it’s own challenges, such as how some Nominos “breaking apart” when they don’t have pieces connected adjacently (eg. the “Y” piece in said game).
There is also a bunch of incomplete code that I have been neglecting for things like displaying High score details.
As with most of my personal Projects, BASeTris is open source and the source code is available on github not just for BASeTris but for some of the libraries I’ve created for the project.
Have something to say about this post? Comment!