Building a Tic-Tac-Toe Game with Rust and WebAssembly

  • 2024/8/22
  • Building a Tic-Tac-Toe Game with Rust and WebAssembly はコメントを受け付けていません

WebAssembly (WASM) is revolutionizing the way we build web applications by enabling near-native performance on the web. Rust, with its memory safety and performance, is an excellent language for compiling to WASM. In this blog, we’ll walk through the process of building a simple Tic-Tac-Toe game using Rust and deploying it to the web via WebAssembly. By the end of this tutorial, you’ll have a working Tic-Tac-Toe game that runs in the browser, powered by Rust.

Prerequisites

Before we dive in, make sure you have the following installed:

  • Rust
  • Wasm-pack

A basic understanding of Rust and JavaScript is also desirable

Step 1: Setting Up the Project

First, let’s create a new Rust project:

cargo new tic-tac-toe --lib

cd tic-tac-toe

Next, add the necessary dependencies to your Cargo.toml:

[dependencies]

wasm-bindgen = "0.2"

We’ll use wasm-bindgen to handle interactions between Rust and JavaScript.

Step 2: Writing the Game Logic in Rust

Let’s start by implementing the core game logic in Rust. We’ll represent the Tic-Tac-Toe board as a 3×3 array and create methods to handle moves and check for a winner.

Create a lib.rs file inside the src directory with the following content:

use wasm_bindgen::prelude::*;



#[wasm_bindgen]

pub struct TicTacToe {

    board: [[char; 3]; 3],

    current_player: char,

}

 

#[wasm_bindgen]

impl TicTacToe {

    pub fn new() -> TicTacToe {

        TicTacToe {

            board: [[' '; 3]; 3],

            current_player: 'X',

        }

    }

 

    pub fn make_move(&mut self, row: usize, col: usize) -> bool {

        if self.board[row][col] == ' ' {

            self.board[row][col] = self.current_player;

            true

        } else {

            false

        }

    }

 

    pub fn check_winner(&self) -> char {

        // Check rows, columns, and diagonals

        for i in 0..3 {

            if self.board[i][0] == self.board[i][1] && self.board[i][1] == self.board[i][2] && self.board[i][0] != ' ' {

                return self.board[i][0];

            }

            if self.board[0][i] == self.board[1][i] && theboard[1][i] == self.board[2][i] && self.board[0][i] != ' ' {

                return self.board[0][i];

            }

        }

        if self.board[0][0] == self.board[1][1] && self.board[1][1] == self.board[2][2] && self.board[0][0] != ' ' {

            return self.board[0][0];

        }

        if self.board[0][2] == self.board[1][1] && theboard[1][1] == self.board[2][0] && self.board[0][2] != ' ' {

            return self.board[0][2];

        }

        ' ' // No winner

    }

 

    pub fn reset(&mut self) {

        self.board = [[' '; 3]; 3];

        self.current_player = 'X';

    }

 

    pub fn get_current_player(&self) -> char {

        self.current_player

    }

 

    pub fn switch_player(&mut self) {

        if self.current_player == 'X' {

            self.current_player = 'O';

        } else {

            self.current_player = 'X';

        }

    }

 

    pub fn get_board(&self) -> Vec<char> {

        self.board.iter().flatten().cloned().collect()

    }

}

Step 3: Compiling to WebAssembly

To compile our Rust code to WebAssembly, use the following command:

wasm-pack build --target web

This command will generate a pkg folder containing the compiled WebAssembly and JavaScript bindings.

How is the pkg/tic_tac_toe.js File Generated?

When you run the wasm-pack build –target web command, it compiles your Rust code into WebAssembly and then generates a JavaScript file (pkg/tic_tac_toe.js) that serves as the bridge between the WebAssembly module and your web application. This file is crucial because it allows JavaScript to interact with the WebAssembly code, calling functions and passing data back and forth.

The generated JavaScript file includes:

  • Bindings for Rust Functions: Functions defined in Rust are exposed to JavaScript, allowing you to call them directly from your web application.
  • Loading and Initialization Logic: The JavaScript file handles loading the WebAssembly module and ensuring that it’s properly initialized before your application starts using it.
  • Memory Management: It also manages the memory allocated by WebAssembly, ensuring that your application can efficiently use and interact with the data.

Step 4: Creating the Frontend

Now, let’s create a simple HTML and JavaScript interface to interact with our Rust code.

Create an index.html file:

<!DOCTYPE html>

<html lang="en">

<head>

    <meta charset="UTF-8">

    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <title>Tic-Tac-Toe</title>

    <style>

        .board {

            display: grid;

            grid-template-columns: repeat(3, 100px);

            grid-gap: 5px;

        }

        .cell {

            width: 100px;

            height: 100px;

            background-color: lightgrey;

            display: flex;

            align-items: center;

            justify-content: center;

            font-size: 2rem;

            cursor: pointer;

        }

    </style>

</head>

<body>

    <h1>Tic-Tac-Toe</h1>

    <div class="board" id="board"></div>

    <button id="reset">Reset</button>

 

    <script type="module">

        import init, { TicTacToe } from './pkg/tic_tac_toe.js';

 

        async function run() {

            await init();

            const game = new TicTacToe();

            const boardElement = document.getElementById('board');

 

            function renderBoard() {

                boardElement.innerHTML = '';

                const board = game.get_board();

                board.forEach((cell, index) => {

                    const cellElement = document.createElement('div');

                    cellElement.className = 'cell';

                    cellElement.textContent = cell;

                    cellElement.addEventListener('click', () => {

                        const row = Math.floor(index / 3);

                        const col = index % 3;

                        if (game.make_move(row, col)) {

                            if (game.check_winner() !== ' ') {

                                alert(`Player ${game.get_current_player()} wins!`);

                                game.reset();

                            } else {

                                game.switch_player();

                            }

                            renderBoard();

                        }

                    });

                    boardElement.appendChild(cellElement);

                });

            }

 

            document.getElementById('reset').addEventListener('click', () => {

                game.reset();

                renderBoard();

            });

 

            renderBoard();

        }

 

        run();

    </script>

</body>

</html>

Step 5: Running the Game

To run the game, you can serve your files using a simple HTTP server. If you have Python installed, you can use:

python -m http.server 8000

Why Do We Need an HTTP Server?

Browsers implement strict security policies when it comes to loading files, especially when dealing with WebAssembly or JavaScript modules. If you try to open index.html directly from the file system (e.g., by double-clicking it), the browser may block the WebAssembly module or the JavaScript imports due to security restrictions. This happens because the browser treats files served from the local file system (file:// protocol) differently than files served over HTTP or HTTPS.

By using an HTTP server, we create a local web server that serves the files over the http:// protocol, which is how web servers typically deliver content. This allows the browser to properly load and execute the WebAssembly module and JavaScript files without running into security issues.

Conclusion

In this blog, we walked through creating a Tic-Tac-Toe game using Rust and WebAssembly. We explored the process of setting up a Rust project, writing the game logic, compiling it to WebAssembly, and creating a simple web interface to interact with it. We also discussed the importance of serving the files using an HTTP server and how the WebAssembly and JavaScript bridge file is generated.

This project demonstrates the power of combining Rust’s performance and safety with the flexibility of web technologies. With this foundation, you can expand the game or explore other WebAssembly projects. Happy coding!

関連記事

カテゴリー:

ブログ

情シス求人

  1. 登録されている記事はございません。
ページ上部へ戻る