In this blog post we’ll explore how we can build the classic Snake game using Lightning Web Components (LWC). We’ll also explore how we can optimize the performance of our components and deploy them to any platform of our choice. We built this game during a Trailhead Live session. Check out the recording here.
With this in mind, let’s dive in. We’ll start by creating the game area, to which we’ll add the snake and the food. We’ll then add the logic to move the snake based on keyboard controls and make the snake longer every time it eats the food. Finally, we’ll deploy the game to GitHub Pages and Heroku.
Creating the game area
At the heart of the game is the area on which the snake moves. Since we aren’t going to use the HTML5 Canvas, the trick is to divide the available space into blocks of equal size, where each block is a
To calculate the number of blocks needed to cover the area, we divide the available area by the block size. To get the available area we can use the
clientHeight properties of the HTMLElement.
Once we’ve identified the number of blocks needed to cover the area, the next step is to actually create the blocks. One approach is to create a
A better way to do it is to create an array of objects, where each object represents a block. We can then iterate over these objects in the template using the
for:each directive to render the blocks. Each block is given an ID which we can use throughout the game to find that block. This ID is made up of the X and Y coordinates of the block in the form of X:Y.
This presents an interesting problem. If we use
@track on a property, then changing it in the
renderedCallback() will create an infinite loop of rendering cycles. To fix this, we can add a limiter using a boolean variable. But that doesn’t solve the problem completely.
Every time we push a new element to the array inside a for-loop, a rerender is triggered, which impacts the performance. To overcome this problem, we can create a temporary array that holds all the elements until the for-loop is complete. We can then simply assign its final value to the main array. This approach even lets us get rid of
@track in the first place.
Creating the snake and food
The next step is to show the snake and food in the game area. One of the ways we can do this is to use CSS classes to color the
div blocks where the snake and food are to be shown. We can then keep adding and removing these CSS classes to each block to create the effect of the snake moving.
We can add new properties to the block objects to denote if a block is the snake, food, or is empty.
In the template, we can use the
if:true directive to decide which type of block we want to show.
This approach works but causes slight performance degradation. This is because each time the snake moves, the
if:true template tags have to be evaluated and the corresponding DOM nodes have to be created/destroyed which is an expensive operation. So to improve performance, we can create another property called
class on the block object, and assign its value to the
class attribute of the
div element. This simple update of an element’s attribute value results in better performance as there are lesser evaluations.
Now moving on to creating the food. Since food is placed randomly on the game area, we can use the
Math.random() function to randomly decide the
div block where the food must be present.
Moving the snake and making it longer
The snake can be moved across the game area by incrementing the position of the head of the snake based on the direction it’s moving. For example, if the snake is moving right, then the X value of the head is incremented by 1, and Y value stays 0.
To change the direction of the snake, we just need to update the
ySpeed properties based on what arrow key is pressed.
move() function can be called at regular intervals depending on how fast we want the snake to move. We can call this whenever we want to start the game. For example, on click of a “Start” button, on page load, and so on.
The trickiest part of creating the game is to increase the length of the snake’s tail whenever it eats the food. To achieve this, we’ll need to store the block IDs of the tail in a new array. To move the tail along with the head, we’ll need to remove the first element of the tail array and add the current position of the head to the end of the tail array. Whenever the snake eats the food, we just don’t remove the first element of the array.
Deploying to multiple platforms
To deploy the game to multiple platforms, we’ll need to create a new Lightning Web Components Open Source (LWC OSS) project and add the game component to it. Depending on where we want to deploy the app, we can choose one of the different app types when creating the project — Standard, Progressive Web App (PWA), or Electron App.
The LWC OSS project includes the tools and services we need to build, test, and run our project locally. Each of these tools and services can be configured based on our needs using the corresponding config files. For example, the
lwc-services.config.json file lets us configure settings like the directory for the build output, port numbers for development and production servers, as well as many more. The
scripts property in the
package.json file lists all the scripts we can run on the project. For example, the
watch script runs the project locally in “development” mode, meaning that any changes to the source files will auto-refresh the browser. The
serve script on the other hand runs the project in “production” mode, where the executable files are served from the build directory.
During our Trailhead Live session, we created a PWA so that it can run on the browser, and can be installed on your desktop to be run offline. We also deployed the project to GitHub pages and Heroku. There are many ways to do this, and we picked the easiest one which involves pushing the code to GitHub and triggering the deployments to both environments from there.
GitHub Pages needs the executable files to be present either in the
docs folder or the
gh-pages branch. We can choose one or the other based on our preference.
During the Trailhead Live session, we chose to use the
docs folder, which means that the build output of the project must go into the
docs folder. To do this, we’ll need to update the
lwc-services.config.json to configure the build directory (
buildDir) to be
docs. This is how the updated file looks:
Once this is done, we can run the command
npm run build using Command Line or Terminal, which will create the
docs folder with the required files.
build is one of the scripts in the
package.json file that creates the executable files in the configured build folder. We can then push the folder to GitHub, and enable GitHub Pages from the Settings menu as shown below.
This auto triggers a deployment to GitHub Pages. Alternately, we can also create GitHub actions to automatically run the build command and update the
docs folder or
gh-pages branch whenever code is pushed to the repo.
To deploy the same code to Heroku, we’ll need to create a file with the name
Procfile in the root of the project, with the below contents.
serve script from
package.json spins up an Express server to serve the static HTML content from the build directory. Since we configured the build directory to be
docs in the previous step, we also need to update the
scripts/server.js file to reflect the same.
These changes can then be committed to the GitHub Repo. We can then create a new app on Heroku, select GitHub as the Deployment Method, choose your GitHub repo, and enable Automatic Deploys.
This way, once we push code to GitHub, it automatically deploys to both GitHub pages and Heroku.
Building a game is a great and fun way to dive deep into Lightning Web Components. As we have seen, there can be multiple ways to achieve the desired result, but not every way lead to optimal performance. Also, since the apps we build with Lightning Web Components are truly reusable across different platforms, there is no better time than now to start thinking of scaling your solutions outside the Salesforce Ecosystem.
Hopefully, this has sparked your imagination, and inspired you to build other games. Hint: Other classic games like Tetris, Ludo, and many more. We can’t wait to see what you build. Also, don’t forget to check out the source code of this game. Feel free to fork it, raise issues, or submit a PR to improve the game.
About the author
Aditya Naag Topalli is a 13x Certified Lead Developer Evangelist at Salesforce. He focuses on Lightning Web Components, Einstein Platform Services, and integrations. He writes technical content and speaks frequently at webinars and conferences around the world. Follow him on Twitter @adityanaag.