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!
Managing Transactions with TypeORM and TypeScript
Enhancing Your Ruby on Rails Application with Turbo and Stimulus
カテゴリー: