Making Word Crack Mix 2: Decisions, Practices and Techniques We Used

Rodrigo Rearden
etermax technology
Published in
4 min readApr 12, 2019

--

In these series of articles (yes, this is only the first one) we’ll talk about architecture, design, and technical decisions taken for Word Crack Mix, a game about finding lots of words in a 4x4 board of tiles with letters, and competing with friends and strangers.

A typical Word Crack Mix board

Warming up

Today we’ll focus on the Core Domain of our board game, the simple and most atomic gameplay element: finding words in a board of letters, in a limited time. For this we had a few development techniques and practices we wanted to keep during development: Clean Architecture, Domain Driven Design (DDD), Test Driven Development (TDD), and Reactive Programming (Rx). Sounds like a lot, but each one quite leads to the other, once you get used to it!

In short, when we talk about Clean Architecture we’re basically talking about nested layers of software where the inner layers never depends on outter layers. That’s (basically) just about it, and that’s what we use for our project.

In general we try to keep our Core Domain independent from the Delivery (how players play the game) and Infrastructure (how services interact with the game) languages, and maintain it’s own.

We call the language of the core the Ubiquitous Language (UL). Keeping this UL simple, and clean from “external pollution” is the base of DDD (see? Clean Architecture is almost guiding the way!), this UL is no less than sacred for us, since it’s the language we talk as a team (artists, and producer included!), we talk about “Words”, “Tiles”, “Submit Word”, “Select Tile”, “Words Guessed”, etc.

Since the core is the most representative and important part of the project (the rules of the game itself) we wanted to keep it fully testable, so we started to outline the expected behavior from scratch with TDD. By writing our use cases in code, using the terms in our UL, we managed to keep it readable for us and anyone that comes into the project.

The tests were expressed in terms of preconditions, callable domain actions as input, and observable domain events as output with expectations to be met. Both actions and events were implemented using a Rx library.

The domain problem: finding words in a board

So, enough warm up, let’s get to it: how do we find words in a game of Word Crack Mix? We’ll have a quick look at the most important use cases of our core:

1) Selecting a Tile: given a board, when the player selects a tile, then the word being guessed is updated with the tile selected, only if: the tile is next to a previous one or the first, and the tile isn’t repeated.

2) Submitting a Word: given a board, a dictionary, and a word being guessed, when the player submits the word, the word is considered guessed, only if: the word is long enough (2 letters), the word wasn’t previously guessed, the word is in the dictionary.

Our use cases define our actions: SelectTile and SubmitWord; and also defines our events: OnWordUpdated and OnWordGuessed. The first one is used to inform the external layers the word that the player is forming, the later informs that the word being formed was submitted, is correct, and counts as a guessed.

The cases also define some of the entities that might appear in our model: Tiles, which contain letters of the alphabet, and Words, which are fromed by the player with Tiles, and of course, the Player.

The domain model: implementing the use cases through testing

First of all, since we decided to use a Reactive approach, we crafted our tests so our actions are Observers and our events Observables. Basically an Observer receives a message, and an Observable emits a message, this way we force our core to be expressed in terms of Rx as if it was part of the programing language, and not an external dependency.

These are typical tests of our core for the SelectTile use case (a really simple one):

GivenWordUpdatedIsListened subscribes to the event core.OnWordUpdated and returns a test object that can check the event emissions through ThenLastEmittedValueIs. The first case checks words are fromed by adjacent tiles, the second one that the non adjacent tiles are ignored. WhenTileIsSelected ends up calling the SelectTile once per tile.

The other use case was designed in a similar way. The interface for the Core of our board game ends up being expressed in a reactive fashion, here’s the results:

This way we expose a clean interface for the other layers to interact with the core, the actual implementation builds up on this, and is crafted using the classic Rx operators.

A final note

You might have noticed that the tests that we’ve shown imply that some internal state on the previously selected tiles must be kept. Our actual code doesn’t actually maintain any state, since we wanted it to be completely testable. We will explain how we achieved keeping the game state alive, but outside of the model, in a future article.

That’s all for now!

Next time we’ll be showing how we modeled other domain problems different from the board, with heavy dependencies with external services like multiplayer matches.

--

--