見出し画像

Rust and WebAssembly Game of Life - Adding Interactive -

Rust and WebAssembly book4.6. Debugging に引き続き、 4.7. Adding Interactive に進みます。

マウスインタラクティブ

アニメーションをマウスで操作できると楽しいですよね。
今回はアニメーションの「再生」「停止」ボタンを追加しましょう、というチュートリアルでした。これ自体は特に難所もなく実装出来ました。
これだけでは面白く無いので、何か追加したいと思います。ちなみにチュートリアルでは Exercises として「1フレーム間の世代数指定」「リセットボタン」「Ctrl+ClickGliderを、Shift+ClickPulserを追加(Glider/Pulserは Game of Life の特定の形状を指すものですね)」が用意されていましたが、今回は「マウスボタンを押下した状態でドラッグすると、ドラッグした場所をAliveにする」ことにします。

Rust  lib.rs 側の追記

impl Cell に set_alive (とset_dead) 関数を追加します。toggle はチュートリアルで作成した関数です。また、JavaScriptから見えるトレイトに set_alive / set_dead を呼び出す関数も定義しておきます。

impl Cell {
   fn set_alive(&mut self) {
       *self = Cell::Alive;
   }

   fn set_dead(&mut self) {
       *self = Cell::Dead;
   }
}

#[wasm_bindgen]
impl Universe {
    pub fn set_cell_alive(&mut self, row: u32, column: u32) {
       let idx = self.get_index(row, column);
       self.cells[idx].set_alive();
   }

   pub fn set_cell_dead(&mut self, row: u32, column: u32) {
       let idx = self.get_index(row, column);
       self.cells[idx].set_dead();
   }
}

JavaScript  index.js 側の追記

Canvas のイベントリスナに mousedown / mouseup / mousemove があるので、mousedown:フラグセット / mousemove:通った場所をAliveにセット / mousedown:フラグクリア という感じで実装しました。 mousemove 内の関数はチュートリアルの click イベントリスナの流用です。

let isDragging = false;

canvas.addEventListener("mousedown", event => {
    isDragging = true;
});

canvas.addEventListener("mouseup", event => {
    isDragging = false;
});

canvas.addEventListener("mousemove", event => {
    if (isDragging === true) {
        const boundingRect = canvas.getBoundingClientRect();

        const scaleX = canvas.width / boundingRect.width;
        const scaleY = canvas.height / boundingRect.height;

        const canvasLeft = (event.clientX - boundingRect.left) * scaleX;
        const canvasTop  = (event.clientY - boundingRect.top)  * scaleY;

        const row = Math.min(Math.floor(canvasTop / (CELL_SIZE + 1)), height - 1);
        const col = Math.min(Math.floor(canvasLeft / (CELL_SIZE + 1)), width - 1);

        universe.set_cell_alive(row, col);

        drawGrid();
        drawCells();
    }
});

また、 const pause() 内も以下のように変更しました。アニメーション停止して、マウスでcanvasをクリックすると描画が1世代進む(pauseした瞬間は内部データは更新済、描画が未実行という状態になる模様)ので、pauseした瞬間も draw することとしました。

const pause = () => {
    playPauseButton.textContent = "▶";
    cancelAnimationFrame(animationId);
    animationId = null;
    drawGrid();    // 追加
    drawCells();   // 追加
};

マウスでグリグリ

実装し、マウスでグリグリしている様子を下図に掲載します。マウスドラッグで経由したセルがAliveになっているのがわかります。それっぽいものが出来ました。

画像1

課題

お気づきの方もいらっしゃるかもしれませんが、今の実装ではところどころ怪しい動きをしています。今回は修正しませんが。。。

1. 素早くマウスドラッグすると、途中でラインが切れます。
これはドラッグ速度と比較してイベントリスナーの周期が長過ぎることに起因しています。ちゃんとするなら、イベントリスナ実行ごとに2点間にラインを引く、といったことをする必要がありますね。

画像2

2. ドラッグが終わり、マウスボタンから手を離した箇所がAliveだと、離した瞬間に Dead になります。これはイベントリスナーのclickがまだ有効となっていることに起因すると思われます。clickはマウスがDown/Upされて初めて認識されるものだとすれば、Up時にセルのAlive/Deadが反転する関数が呼び出されています。ドラッグが実装されているので、clickは不要になりますね。

画像3

おわりに

今回も大きなつまづきポイントがありませんでした。そんなに難しいことをやっていないということと、ちょっと Rust and WebAssembly に慣れてきた、ということはあるかもしれません。

最後まで読んでいただき、ありがとうございました。

この記事が気に入ったらサポートをしてみませんか?