Adding Interactivity

    Let's add a button to toggle whether the game is playing or paused. To, add the button right above the <canvas>:

    In the wasm-game-of-life/www/index.js JavaScript, we will make the followingchanges:

    • Keep track of the identifier returned by the latest call torequestAnimationFrame, so that we can cancel the animation by callingcancelAnimationFrame with that identifier.

    • When the play/pause button is clicked, check for whether we have theidentifier for a queued animation frame. If we do, then the game is currentlyplaying, and we want to cancel the animation frame so that renderLoop isn'tcalled again, effectively pausing the game. If we do not have an identifierfor a queued animation frame, then we are currently paused, and we would liketo call requestAnimationFrame to resume the game.

    Because the JavaScript is driving the Rust and WebAssembly, this is all we needto do, and we don't need to change the Rust sources.

    1. let animationId = null;
    2. // This function is the same as before, except the
    3. // result of `requestAnimationFrame` is assigned to
    4. // `animationId`.
    5. const renderLoop = () => {
    6. drawGrid();
    7. drawCells();
    8. universe.tick();
    9. animationId = requestAnimationFrame(renderLoop);
    10. };

    At any instant in time, we can tell whether the game is paused or not byinspecting the value of animationId:

    Now, when the play/pause button is clicked, we check whether the game iscurrently paused or playing, and resume the renderLoop animation or cancel thenext animation frame respectively. Additionally, we update the button's texticon to reflect the action that the button will take when clicked next.

    1. const playPauseButton = document.getElementById("play-pause");
    2. const play = () => {
    3. playPauseButton.textContent = "⏸";
    4. };
    5. const pause = () => {
    6. playPauseButton.textContent = "▶";
    7. cancelAnimationFrame(animationId);
    8. animationId = null;
    9. };
    10. playPauseButton.addEventListener("click", event => {
    11. if (isPaused()) {
    12. play();
    13. } else {
    14. pause();
    15. }
    16. });

    Finally, we were previously kick-starting the game and its animation by callingrequestAnimationFrame(renderLoop) directly, but we want to replace that with acall to play so that the button gets the correct initial text icon.

    Refresh http://localhost:8080/ and we should now beable to pause and resume the game by clicking on the button!

    Now that we can pause the game, it's time to add the ability to mutate the cellsby clicking on them.

    1. # #![allow(unused_variables)]
    2. #fn main() {
    3. impl Cell {
    4. fn toggle(&mut self) {
    5. *self = match *self {
    6. Cell::Dead => Cell::Alive,
    7. }
    8. }
    9. #}

    To toggle the state of a cell at given row and column, we translate the row andcolumn pair into an index into the cells vector and call the toggle method onthe cell at that index:

    This method is defined within the impl block that is annotated with#[wasm_bindgen] so that it can be called by JavaScript.

    In wasm-game-of-life/www/index.js, we listen to click events on the <canvas>element, translate the click event's page-relative coordinates intocanvas-relative coordinates, and then into a row and column, invoke thetoggle_cell method, and finally redraw the scene.

    1. canvas.addEventListener("click", event => {
    2. const boundingRect = canvas.getBoundingClientRect();
    3. const scaleX = canvas.width / boundingRect.width;
    4. const scaleY = canvas.height / boundingRect.height;
    5. const canvasLeft = (event.clientX - boundingRect.left) * scaleX;
    6. const canvasTop = (event.clientY - boundingRect.top) * scaleY;
    7. const row = Math.min(Math.floor(canvasTop / (CELL_SIZE + 1)), height - 1);
    8. const col = Math.min(Math.floor(canvasLeft / (CELL_SIZE + 1)), width - 1);
    9. universe.toggle_cell(row, col);
    10. drawGrid();
    11. drawCells();
    12. });

    Rebuild with wasm-pack build in wasm-game-of-life, then refresh again and we can now draw ourown patterns by clicking on the cells and toggling their state.

    • Introduce an <input type="range"> widget to control how manyticks occur per animation frame.

    • On , insert a) centered onthe target cell. On Shift + Click, insert a pulsar.