Arwa Lokhandwala WTM Ambassador Singapore, Dev @Telstra, Ex @BookMyShow, Jio Organizer @WomenCoders01, @gdgsingapore. Writer @codewithmosh, @usejournal, Medium @arwa.lokhandwala

Create your first game in WebAssembly

5 min read 1445

Have you ever experienced the pain of porting legacy code in C/C++ to JavaScript? Have you ever wished you could reuse your legacy application or have near-native performance in your web app?

If your answer to any of the questions above is yes, WebAssembly can help!

Getting to know WebAssembly

According to the documentation:

It is a low-level assembly-like language with a compact binary format that runs with near-native performance and provides languages such as C/C++ and Rust with a compilation target so that they can run on the web.

What this basically means is, WebAssembly provides a way to convert our C/C++ code to a particular format which can then be easily consumed.

But how does WebAssembly do that?

WebAssembly is like a new language but you, as a developer, don’t have to write it because it acts as a compilation target for your low-level languages like C/C++, or Rust.

So, when you have your C or C++ application, you can compile it to a .wasm file which then can be imported into your web application.

WebAssembly even allows you to pass and access data/variables to and from your JS app.

This article here explains in detail how WebAssembly works.

Advantages of using WebAssembly

  • You can finally outsource performance-intensive tasks like computations for 3D games, virtual, and augmented reality or computer vision to C/C++ or Rust-like performant languages
  • There are tons of amazing legacy libraries written in C/C++ which can now be seamlessly integrated into your web application. You can find some of them here
  • You can write super fast and beautiful applications (Google Earth, for example)

WebAssembly gives extended capability to the web

So now that we have seen what WebAssembly can do, let us create our first game to see how it works.

Creating your very first tic-tac-toe game in WASM

tic tac toe WebAssembly
We will be building a 3×3 tic-tac-toe game. Our game logic includes:

  • Computing which player has won
  • Calculating if there is no winner
  • Indicating pending moves

The entire game logic is written in C++.

Getting to know Emscripten

As I mentioned before, we need to convert our C++ application in an intermediate format — which is .wasm. To do this, we need a tool that compiles our C++ file to this format.

There are a lot of options to doing this, however, for this post, I will be using Emscripten. The reasons for that are because:

  1. It directly converts your C/C++ code to JavaScript which means you don’t even need to write any glue code for reading your .wasm file. This gives you enough flexibility to solely focus on the logic rather than implementation
  2. Secondly, it is very intuitive in terms of calling functions written in your C++ file to your JS file. You will see this once we dive into the code

Make sure you install Emscripten before you start coding. You can learn more about Emscripten here.

Before we start, let us first break down our game into manageable chunks:

  1. We have to write our game logic in C++
  2. Transpile the file using Emscripten
  3. Call the C++ function in our game

The final game code can be found here.

Writing our game logic in C++

The logic for the game is written in the tic_tac_toe.cpp file.

To begin with, we first import all the necessary Emscripten libraries:

#include <emscripten/bind.h>
#include <emscripten/val.h>

The code that is shown above imports Embind which is used to bind our C++ code with JavaScript (i.e we can now call and manipulate our JavaScript variables within our C++ code).

Next, we move on to our tic_tac_toe() which includes the main logic for our game:

val tic_tac_toe() {
    val board_values = val::global("BoardValues");
    val moves_pending_label = val::global("movesPendingLabel");
    val no_winner_label = val::global("noWinnerLabel");
    val empty_block = val::global("emptyBlock");
    bool moves_pending = false;

    val solutions[8][3]= {
        { board_values[0][0], board_values[0][1], board_values[0][2]},
        { board_values[1][0], board_values[1][1], board_values[1][2]},
        { board_values[2][0], board_values[2][1], board_values[2][2]},
        { board_values[0][0], board_values[1][0], board_values[2][0]},
        { board_values[0][1], board_values[1][1], board_values[2][1]},
        { board_values[0][2], board_values[1][2], board_values[2][2]},
        { board_values[0][0], board_values[1][1], board_values[2][2]},
        { board_values[0][2], board_values[1][1], board_values[2][0]},
    };

    for ( int i = 0; i < 8; i++ ){
        if((solutions[i][0] != empty_block) && (solutions[i][1] != empty_block) && (solutions[i][2] != empty_block)&& (solutions[i][0] == solutions[i][1]) && ( solutions[i][1] == solutions[i][2] )) {
            return solutions[i][1];
        } else if((solutions[i][0] == empty_block) || (solutions[i][1] == empty_block) || (solutions[i][2] == empty_block)){
            moves_pending = true;
        }
   }

   if (moves_pending) {
       return moves_pending_label;
   }
    
    return no_winner_label;
}

Our function signature val tic_tac_toe() states that the value returned by our function is a JavaScript variable.



All the initial values are declared within our JavaScript, now we need a way to access these initial values and manipulate them.

Lucky for us, Embind provides a way to do this using:

val cpp_var_name = val::global("js_var_name");

Using this we will import all our necessary labels, game board values, and game state variables:

val board_values = val::global("BoardValues");  // Object representing our 3*3 board
val moves_pending_label = val::global("movesPendingLabel"); // Label returned if there are any pending moves/blocks remaining
val no_winner_label = val::global("noWinnerLabel"); // Label indicating who won
val empty_block = val::global("emptyBlock"); //  Indicator for an empty cell on board
bool moves_pending = false;  // State variable indicating if any blocks are pending

Now our next step is to create a solutions matrix indicating all our possible solutions. val solutions[8][3], is a 8*3 array representing all of our possible solutions combinations.

Note: There might be other ways to implement the solution for a tic-tac-toe game, however, for simplicity, we will go ahead with a list of all possible solutions and cross-validate that with our current board values to check if any single player has won.

Now, once we have all our possible solutions in place, we will compare our current board values with these solutions to see if any one player has won.

If our current board value matches any of our solutions then we return that cell value, which represents one of the players. However, if no matches are found and if there are any empty cells on the board then the moves_pending_label is returned otherwise no_winner_label is returned.

Now, let’s export this function so that we can call it in our web app:

 EMSCRIPTEN_BINDINGS(my_module) {
    function("tic_tac_toe", &tic_tac_toe);
}

The above code allows us to now call the function using tic_tac_toe(). This block runs when our glue code .js file is initially loaded.

Transpiling the .cpp file to .wasm and .js(glue code)

Once our .cpp file is ready, the next step is to transpile the file to .wasm file and .js file which can be used in our web app:

emcc --bind -o tic_tac_toe.js tic_tac_toe.cpp

The above command transpiles our tic_tac_toe.cpp file to two new files namely tic_tac_toe.wasm and tic_tac_toe.js. The tic_tac_toe.js file includes all the glue code necessary for loading our C++ function and exposing it as a module that can then be imported.

Calling C++ functions in our web app

All the code discussed below can be found here. Now that we have completely transpiled our .cpp file, it’s time for us to prepare our JavaScript code which will call our tic_tac_toe().

The first step is to declare the variables which hold the initial values of the board as discussed earlier:

 var currentBoardValues = []
 var BoardValues = {0:[],1:[],2:[]}
 var movesPendingLabel = 'Moves still pending!';
 var emptyBlock = '';
 var noWinnerLabel = 'Oops! Looks like no one won :('

Next,

var Module = {
    onRuntimeInitialized: function() {
            Module.tic_tac_toe()
         }
 };

You remember our tic_tac_toe() C++ function which was exposed by Embind, that is now available on the Emscripten module object.

However, we can only call our tic_tac_toe() once it’s fully loaded i.e, it’s runtime(.js glue code and .wasm file) is initialized. For this, we use onRuntimeInitialized callback which will run when the runtime is ready.


More great articles from LogRocket:


Now whenever any player clicks on any cell we call our C++ function as Module.tic_tac_toe() which will return the appropriate results.

And voila, our first game in WebAssembly is ready! You can check out the final game code here.

Conclusion

WebAssembly is truly remarkable and has indeed allowed us to do things that were previously impossible. I hope this post helps you take the first step on your WebAssembly journey.

Get setup with LogRocket's modern error tracking in minutes:

  1. Visit https://logrocket.com/signup/ to get an app ID.
  2. Install LogRocket via NPM or script tag. LogRocket.init() must be called client-side, not server-side.
  3. $ npm i --save logrocket 

    // Code:

    import LogRocket from 'logrocket';
    LogRocket.init('app/id');
    Add to your HTML:

    <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
    <script>window.LogRocket && window.LogRocket.init('app/id');</script>
  4. (Optional) Install plugins for deeper integrations with your stack:
    • Redux middleware
    • ngrx middleware
    • Vuex plugin
Get started now
Arwa Lokhandwala WTM Ambassador Singapore, Dev @Telstra, Ex @BookMyShow, Jio Organizer @WomenCoders01, @gdgsingapore. Writer @codewithmosh, @usejournal, Medium @arwa.lokhandwala

Leave a Reply