Play tic tac toe with the help of advanced types in TypeScript

Barld Boot
5 min readAug 2, 2019

--

In this article we are going to build the right types in TypeScript to play tic tac toe (in Dutch: boter, kaas en eieren) in a type safe way. We use the compiler to check if the sets are legal and to decide if there a player has won. There is no knowledge of advanced types expected but some basic understanding of types in programming and Typescript is required.

The tic tac toe board

A cell on the tic tac toe board can have three different states namely an "X", an "O" or a blank cell (" "). In TypeScript we can create a union of string literals to define a type that support these cases. A string literal is a string limited to a specific string. A union is the choice between different types.

In the case of our game, the value of a cell is limited to the above cases.

The board contains 9 separate cells which all have an identifier named from A1 up on toC3 . Which name is derived from their column and row position: we use A,B,C for the columns, and 1,2,3 for the rows.

This is the structure we are going to use to play our little game.

To create an empty playing board, we are going to use our first advanced concept. We are going to map our basic board to an empty board with a mapped type.

We iterate through all keys of Board which is a union with all the cells "A1" | "A2" | "A3" | ... | "C3", We ignore the value type of all cells and set it to empty cell (" "). This makes that we have an empty board.

Finding empty cells

To ensure a fair game we should only allow sets on empty cell. Below you see the definition how to get all the empty cells as a union of string literals.

In the above example we have created a type alias with one generic parameter. The parameter board has the constrain that is should have at least the properties of a board defined before.

To explain the rest of the type we will reduce the code in steps. Imagine that our current board looks like the following:

The first step is that it is transformed to a new object with the help of the following rule

this will result in the following:

This iterates through all the fields in the object. With the help of a conditional type we check every field if it is empty as type so we will give the field name as type literal, every non empty field will be set to the type never. never is a special type in TypeScript for non existing types. It is used a lot for not reachable code.

In the next step the index operator is used to get all the value types of the object.

This is working because the new object type has exact the same field names as the new created object. It says literally give me the value types of the following fields. Because the field names are known we could replace it in the following way

This result in a union of all the value types in this case:

In a union type all value types will be reduced to a unique set so the following is the same:

And because never is a special case that stands for a not really existing value type it will be reduced away when there will be other types in the same union. thus the find type that the compiler will produce is the following which is the type of the empty cells.

Making a move

Now that we know which cells are free we can implement the move. For this we create a type alias that accepts three parameters: the current board, which kind of sign you want to use ("X" | "O") and in which cell you want to do this.

To make sure that the chosen cell is an available cell we use the type AvailableCells that we have created before as constraint on the chosen cell. We remap the board where everything is the same except for the cell chosen. This cell is changed to "X" or "O" depending on what is chosen. It returns the new changed type, but be aware that this is a new type: it is only possible to create new types not to mutating already existing types.

Check if someone has won

The last thing we have to do before we can start playing is be able to check if there is already some player that has won. Because this is a simple game, we can check all possible winning combinations wich are 8 combinations. We can check every cell by using the index operator, for example:

For a whole column we can do:

Here we use an intersection type. An intersection type is made by using the & operator. It says that the new type must have from both sides of the operator. Just like the union operator we can remove all elements that are not unique. For our above example it means we can reduce it to the following:

If we had a winning combination it would look like the following:

So if we want to know if X has won we just have to check if the type is "X". For all combinations that looks like this:

If the type of one of these combinations match on the char ofc we say that this char has won by returning true otherwise we return false. Make sure you use this order, when you change the order of the extends around it is not working correct because "X" & "O" extends "X" -> true and "X" extends "X" & "O" -> false which is the behavior that we actually want.

The last step is to make a type that checks if any of the players has won. This looks the following:

If X has won, than we return "X has won" if O has won than we return "O has won", otherwise return nobody has won.

Now we are ready to play

Conclusion

In this article I introduced you to some of the advanced types in TypeScript and a way to use them. If you want to know better how to use the only advise I can give you is trying it out and practice. Futher improvements include:

  • Creating a framework around this types so you could really use it (When it is now compiled to JavaScript you have only a blank file)
  • Tracking the state of the game an which player may do the next turn.

During the last 5 months I have done my graduation internship at Hoppinger for my study Informatica at Rotterdam University of Applied Sciences. My research was about using advanced types in TypeScript to validate a configuration for a tool used internally at Hoppinger. Here I have learned a lot about the possibilities of advanced types in TypeScript but maybe even more about the shortcomings of the type system and the implementation in the TypeScript compiler. In September I will start my first job by Hoppinger as Junior Software engineer.

--

--