Today was a good day and a bad day. I successfully recreated my hangman CLI game in the browser with WebAssembly, but it was not without a lot of problems, on the way and in the final product. In spite of that, I do think this was overall more of a good day than a bad day. I learned a lot more than I expected to and I think I'm a better developer for it.
Rust Code
mod hangman {
use rand::seq::SliceRandom;
use rand::thread_rng;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub struct Hangman {
possible_words: [String; 8],
word_to_guess: String,
guessed_letters: Vec<char>,
guesses_left: i32,
current_guess: String,
}
#[wasm_bindgen]
impl Hangman {
pub fn init() -> Hangman {
let mut rng = thread_rng();
let mut hg: Hangman = Hangman {
possible_words: [
String::from("Rust"),
String::from("Challenge"),
String::from("WebAssembly"),
String::from("Day Six"),
String::from("Hello, World!"),
String::from("Programming"),
String::from("Thirty Days"),
String::from("Hangman"),
],
word_to_guess: String::new(),
guessed_letters: vec![],
guesses_left: 8,
current_guess: String::new(),
};
hg.word_to_guess = hg.possible_words.choose(&mut rng).unwrap().to_string();
for i in 0..hg.word_to_guess.chars().count() {
if &hg.word_to_guess.chars().nth(i).unwrap() == &' ' {
hg.current_guess.push_str(" ");
} else {
hg.current_guess.push_str("_");
}
}
return hg;
}
pub fn get_current_guess(&self) -> String {
return String::from(&(self.current_guess));
}
pub fn get_word_to_guess(&self) -> String {
return String::from(&(self.word_to_guess));
}
pub fn guess(&mut self, g: String) {
if !self.guessed_letters.contains(&g.chars().nth(0).unwrap()) {
self.guessed_letters.push(g.chars().nth(0).unwrap());
let mut new_guess = self.current_guess.chars().collect::<Vec<_>>();
for i in 0..self.word_to_guess.len() {
if self.word_to_guess.chars().collect::<Vec<_>>()[i]
.eq_ignore_ascii_case(&g.chars().nth(0).unwrap())
{
new_guess[i] = self.word_to_guess.chars().collect::<Vec<_>>()[i];
} else {
continue;
}
}
if new_guess.iter().collect::<String>() == self.current_guess {
self.guesses_left -= 1;
}
self.current_guess = new_guess.iter().collect::<String>();
}
}
pub fn did_win(&mut self) -> bool {
return self.guesses_left > 0;
}
}
}
This took me a while to get to. The first reason was because I was trying to use static global variables that for whatever reason I thought would update and be able to store state for the game via some of these functions. That did not work at all, for what should have been obvious reasons, but it didn't click with me immediately, so I spent time debugging, and decided to use a struct. I did this by looking up how to make a struct in Rust. Then I got an error with wasm_bindgen
because I had to pass the struct through the macro instead of each method, so I did that, tried to build the package, and got an error because I was using the rand
crate with a wasm project. The crate uses another crate that doesn't work with wasm out of the box, but I could fix it by adding that crate to my Cargo.toml
with an extra feature:
getrandom = { version = "0.2.4", features = ["js"] }
That finally allowed me to build the library, and then I just had to go back to where I was more comfortable: JavaScript.
JS and HTML
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>WebAssembly Hangman</title>
</head>
<body>
<div id="root">
<p id="currentGuess">
</p>
<form id="guesserForm">
<input type="text" id="guesser">
<input type="submit" value="Guess">
</form>
</div>
<noscript>This page contains webassembly and javascript content, please enable javascript in your browser.</noscript>
<script src="./bootstrap.js"></script>
</body>
</html>
And here's the JS:
import * as wasm from "../pkg";
let hangman = wasm.Hangman.init();
let root = document.querySelector("#root");
let currentGuess = document.querySelector("#currentGuess");
let guesserForm = document.querySelector("#guesserForm");
let guesser = guesserForm.querySelector("#guesser");
guesserForm.addEventListener("submit", (e) => {
e.preventDefault();
hangman.guess(guesser.value);
guesser.value = "";
guess();
});
const guess = () => {
if (hangman.get_word_to_guess() !== hangman.get_current_guess()) {
currentGuess.innerText = hangman.get_current_guess();
} else {
checkWin();
}
};
guess();
const checkWin = () => {
if (hangman.did_win()) {
root.innerText = "You won!";
} else {
root.innerText = "You lost :(";
}
};
The problems I had here were mostly minimal, but I did have some. By this point I was pretty worn out, and so something just didn't register in my brain that I wanted the guess() function to call every time a guess was made, and not all the time, so I used a while loop. After about 5 seconds, I realized that didn't make any sense and would break the webpage, and so I switched it to what I have now. I also still have a bug in the program, which is that I cannot get the "You lost :(" message to show up, but by now I've been working on this for like 3 hours and I don't really feel the urge to debug any more code today, even in a language I know. So I called it there, and just started writing this. If you have any ideas as to why this is happening, feel free to let me know, but I'll probably take another look at it tomorrow anyway. That's about it for me today, I didn't really want to spend this much time on today, but I guess it makes up for yesterday in a way because yesterday was very short.
Top comments (1)
Hey! Cool to see new people pickung up Rust! I did a small review/correction of the code. You probably won't understand everything. But if you come back and re-visit it in maybe a month or two you might understand all of the comments :p
nopaste.ml link