Most React developers I had the pleasure to meet are acquainted with how this technology can be used to build interactive web applications. Yet, very few of them realise that the very same technology can (and should!) be used to build terminal based user interfaces.
Thanks to the Ink library, we can now write React apps that are rendered on a terminal screen and are just as interactive as their web counterpart.
The days in which Bash alone reigned over the shells of the world are finally over, so let’s celebrate re-implementing the popular Snake game with the technology of the future. 🚀
The Ink library defines a new render method that converts a React component tree into a string for the terminal, and a Box component that can be used to group other components together.
Empowered with these tools and all of the React goodness, let’s get started!
This is how our package.json file looks like after installing all of them:
The App.js file is pretty straightforward. It simply imports React, the render method from Ink and the root component of our application and renders it to the screen.
This completes the scaffolding for our game, it’s time to cut to the chase. 😎
The Game component encodes the whole application logic. It stores the application state including the snake position, whether it is alive or dead, the position of the food bits the snake is supposed to eat, the snake current direction and, most notably, the score.
chosenDirectionrepresents the last snake direction chosen by the user, that will affect the direction of the snake at the next game update.
snake.directionis the current direction of the snake.
snake.bodyis the list of the snake body part coordinates where the last, by convention, represents the snake’s head.
- Whenever the snake eats some food, it will stop moving and will start growing for a while. The
snake.stocksprop encodes this by storing the number of game update frames (starting from the current frame) during which the snake will grow rather than move. This prop starts at zero, indicating that the snake is moving (and not growing), and is updated to a positive integer whenever the snake eats its food.
score, surprisingly, tracks the current game score.
Notice that we use the useRef hook as opposed to the useState hook since we want control over when the game state is rendered. This trick allows us to change the state as we please without re-rendering the user interface which, as will become apparent in a second, will only happen at fixed time intervals.
Next, we will handle user input.
When the user types an arrow key we will store his direction choice in our state. This choice will in turn be used to update the snake real direction at the next game state update.
Finally we get to the core logic of the game. We register a function that runs at a fixed interval that renders the current game state and then updates the state according to the user input.
N.B. The useForceUpdate hook is not a standard React hook, but can be easily implemented as follows:
It basically holds a hidden boolean in the internal component state and flips it whenever the function returned by the hook is called. As the state is guaranteed to change every time we do it, the component is re-rendered.
The function that updates the game state is a little tricky. It is responsible for updating the snake’s direction, determining the current game state (running/complete), handling collisions, generating food and translating the snake across the game board.
Take your time to read through the code and comments.
Finally, we must render the game state to the screen. A task that we will delegate to the Arena component.
This last component is pretty simple. It creates a matrix of coloured strings representing the game state, converts it to a string, and displays it.
cellSize to 3 makes the arena look like a square on my machine (since the characters of the Menlo font I am using are approximately 3 times as high as they are wide). Of course this largely depends on the font you are using, so feel free to experiment with other values. 😉
The hard work is done, let’s go drink a well deserved beer and play some Snake! 🐍
React is a wonderful technology to build user interfaces for a variety of platforms. As of today, with the Ink library, we can use React to build applications for the terminal, getting advantage of its inherent declarative coding style to simplify our business logic and ultimately making our life a lot easier.
I encourage you to give this library a try and check the other wonderful projects made with it, which are listed on Ink’s repository wiki page.
The full code of the game described in this article is publicly available on my Bitbucket: https://bitbucket.org/andryak/snake-ink/src/master/