| Ben Smith @binjimint | 
       
     | 
  
That's it :-)
    body {
      position: absolute;
      display: flex;
      flex-direction: column;
      background-color: #fff;
      margin: 0;
      width: 100%;
      height: 100%;
    
    
    canvas {
      object-fit: contain;
      width: 100%;
      height: 100%;
      image-rendering: pixelated;
      image-rendering: crisp-edges;
    }
    
    
    The canvas element itself is 150x150 pixels,
    const w = 150, h = 150;
    (async function start() {
      const response = await fetch('match3.wasm');
      const moduleBytes = await response.arrayBuffer();
      const {module, instance} =
        await WebAssembly.instantiate(moduleBytes);
      const exports = instance.exports;
      const buffer = exports.mem.buffer;
      const canvasData = new Uint8Array(buffer, 0x10000, w*h*4);
      // ...
    
    
      // ...
      const canvas = document.querySelector('canvas');
      const context = canvas.getContext('2d');
      const imageData = context.createImageData(w, h);
      (function update() {
        requestAnimationFrame(update);
        exports.run();
        imageData.data.set(canvasData);
        context.putImageData(imageData, 0, 0);
      })();
    })();
    
    
    ;; Memory map:
    ;;
    ;; [0x10000 .. 0x25f90)  150x150xRGBA data (4 bytes/pixel)
    (memory (export "mem") 3)
    (func (export "run")
    )
    
    
  (func $clear-screen (param $color i32)
    (local $i i32)
    (loop $loop
      ;; mem[0x10000 + i] = color
      (i32.store offset=0x10000
        (local.get $i) (local.get $color))
      ;; i += 4
      (local.set $i
        (i32.add (local.get $i) (i32.const 4)))
      ;; loop if i < 90000
      (br_if $loop
        (i32.lt_s (local.get $i) (i32.const 90000)))
    )
  )
  
  
  (func (export "run")
    (call $clear-screen
      (i32.const 0xff_00_00_ff))  ;; ABGR format
  )
  
  Clear the screen to red
  
  
  (func $put-pixel
    (param $x i32) (param $y i32) (param $color i32)
    ;; mem[0x10000 + (y * 150 + x) * 4] = color
    (i32.store offset=0x10000
      (i32.mul
        (i32.add
          (i32.mul (local.get $y) (i32.const 150))
          (local.get $x))
        (i32.const 4))
      (local.get $color))
  )
  
  
  (func (export "run")
    (call $put-pixel
      (i32.const 100) (i32.const 100)
      (i32.const 0xff_00_00_ff))
  )
  
  Draw a red pixel at (100,100)
  
      const input = new Uint8Array(exports.mem.buffer, 0x0000, 3);
      function mouseEventHandler(event) {
        // ...
        input[0] = event.offsetX;
        input[1] = event.offsetY;
        input[2] = event.buttons;
      }
      canvas.addEventListener('mousemove', mouseEventHandler);
      canvas.addEventListener('mousedown', mouseEventHandler);
      canvas.addEventListener('mouseup', mouseEventHandler);
  
  
  ;; [0x0..0x0)  X mouse position
  ;; [0x1..0x1)  Y mouse position
  ;; [0x2..0x2)  mouse buttons
  (func (export "run")
    // ...
    (call $put-pixel
      (i32.load8_u (i32.const 0))    ;; X
      (i32.load8_u (i32.const 1))    ;; Y
      (select
        (i32.const 0xff_00_00_ff)    ;; Red
        (i32.const 0xff_ff_00_00)    ;; Blue
        (i32.load8_u (i32.const 2))) ;; Buttons
    )
  )
  
  
  function fillRect(x, y, w, h, color) {
    var i, j;
    for (j = 0; j < h; j++) {
      for (i = 0; i < w; i++) {
        putPixel(x + i, y + j, color);
      }
    }
  }
  
  How you might write fillRect in JavaScript
  
  (func $fill-rect (param $x i32) (param $y i32)
                   (param $w i32) (param $h i32)
                   (param $color i32)
    (local $i i32) (local $j i32)
    (loop $y
      (local.set $i (i32.const 0))
      (loop $x
        (call $put-pixel
          (i32.add (local.get $x) (local.get $i))
          (i32.add (local.get $y) (local.get $j))
          (local.get $color))
        (local.set $i (i32.add (local.get $i) (i32.const 1)))
        (br_if $x (i32.lt_s (local.get $i) (local.get $w))))
      (local.set $j (i32.add (local.get $j) (i32.const 1)))
      (br_if $y (i32.lt_s (local.get $j) (local.get $h)))))
  
  
  (func $draw-sprite (param $x i32) (param $y i32)
                     (param $w i32) (param $h i32)
                     (param $src i32)
    ;; ...
  )
  
  Start with $fill-rect, but change the
  $color parameter to $src
  
  
  ;; ...
      ;; put-pixel(x + i, y + j, mem[src + (w * j + i) * 4])
      (call $put-pixel
        (i32.add (local.get $x) (local.get $i))
        (i32.add (local.get $y) (local.get $j))
        (i32.load
          (i32.add
            (local.get $src)
            (i32.mul
              (i32.add
                (i32.mul (local.get $w) (local.get $j))
                (local.get $i))
              (i32.const 4)))))
  ;; ...
  
  
  ;; Sprite Data  16x16x4 = 1024 bytes
  (data (i32.const 0x100)
    "\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00"
    "\00\00\00\00\00\00\00\00\df\71\26\ff\df\71\26\ff"
    "\df\71\26\ff\df\71\26\ff\00\00\00\00\00\00\00\00"
    "\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00"
    "\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00"
    "\df\71\26\ff\df\71\26\ff\fb\f2\36\ff\fb\f2\36\ff"
    "\fb\f2\36\ff\fb\f2\36\ff\df\71\26\ff\df\71\26\ff"
    "\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00"
    "\00\00\00\00\00\00\00\00\00\00\00\00\df\71\26\ff"
    "\fb\f2\36\ff\fb\f2\36\ff\fb\f2\36\ff\fb\f2\36\ff"
    "\fb\f2\36\ff\fb\f2\36\ff\fb\f2\36\ff\fb\f2\36\ff"
    ...
  )
  
  Store 1024 bytes of sprite data at 0x100
  
  (func $put-pixel (param $x i32) (param $y i32)
                   (param $color i32)
    ;; return if the x/y coordinate is out of bounds
    (br_if 0
      (i32.or
        (i32.ge_u (local.get $x) (i32.const 150))
        (i32.ge_u (local.get $y) (i32.const 150))))
    ...
  )
  
  
  (func $draw-sprite ...
        ;; pixel = mem[src + (w * j + i) * 4]
        (local.set $pixel (i32.load ...)))
        ;; if (pixel != 0)
        (if (local.get $pixel)
          (then
            (call $put-pixel ...))
  )
  
  Load the pixel, but only draw it if it is non-zero.
  
  β© Step 10: Draw Sprites With Palette
  (func $draw-sprite (param $x i32) (param $y i32)
                     (param $src i32)
                     (param $sw i32) (param $sh i32)
                     (param $dw i32) (param $dh i32)
    (local $dx f32)
    (local $dy f32)
    ;; dx = sw / dw
    (local.set $dx
      (f32.div (f32.convert_i32_s (local.get $sw))
               (f32.convert_i32_s (local.get $dw))))
    ;; dy = sh / dh
    (local.set $dy
      (f32.div (f32.convert_i32_s (local.get $sh))
               (f32.convert_i32_s (local.get $dh))))
  
  
      ;; pixel = mem[src + (sw * j * dy + i * dx)]
      (local.set $pixel
        (i32.load
          (i32.add
            (local.get $src)
            (i32.add
              (i32.mul
                (local.get $sw)
                (i32.trunc_f32_s
                  (f32.mul (f32.convert_i32_s (local.get $j))
                           (local.get $dy))))
              (i32.trunc_f32_s
                (f32.mul (f32.convert_i32_s (local.get $i))
                         (local.get $dx)))))))
  
  Use scale factors when reading source pixel
  
  Using 1 byte per cell
Using 1 bit per cell instead
  (func $draw-grid (param $grid i64) (param $gfx-src i32)
    (local $i i32)
    (loop $loop
      ;; Exit the function if $grid is zero
      (br_if 1 (i64.eqz (local.get $grid)))
      ;; Get the index of the lowest set bit
      (local.set $i (i32.wrap_i64 (i64.ctz (local.get $grid))))
      ;; Draw the cell at that index
      (call $draw-cell ...)
      ;; Clear the lowest set bit: bits &= bits - 1
      (local.set $grid
        (i64.and (local.get $grid)
                 (i64.sub (local.get $grid) (i64.const 1))))
      (br $loop)))
  
  Linear interpolation
  (func $ilerp (param $a i32) (param $b i32) (param $t f32)
               (result i32)
    ;; return a + (b - a) * t
    (i32.add
      (local.get $a)
      (i32.trunc_f32_s
        (f32.mul
          (f32.convert_i32_s
            (i32.sub (local.get $b) (local.get $a)))
          (local.get $t))))
  )
  
  Ease out cubic
  (func $ease-out-cubic (param $t f32) (result f32)
    ;; return t * (3 + t * (t - 3))
    (f32.mul
      (local.get $t)
      (f32.add
        (f32.const 3)
        (f32.mul
          (local.get $t)
          (f32.sub (local.get $t) (f32.const 3)))))
  )
  ...
  (call $ilerp (i32.const 10) (i32.const 30)
               (call $ease-out-cubic (f32.const 0.5)))
  
  
  ;; struct Cell { s8 x, y, w, h; };
  ;; [0x3200..0x3300)  current offset  Cell[64]
  ;; [0x3300..0x3400)  start offset    Cell[64]
  ;; [0x3400..0x3500)  end offset      Cell[64]
  ;; [0x3500..0x3600)  time [0..1)     f32[64]
  ...
  ;; t = t[i]
  (local.set $t (f32.load offset=0x3500 (local.get $t-addr)))
  ;; current[i] = ilerp(start[i], end[i], easeOutCubic(t))
  (i32.store8 offset=0x3200
    (local.get $i-addr)
    (call $ilerp
      (i32.load8_s offset=0x3300 (local.get $i-addr))
      (i32.load8_s offset=0x3400 (local.get $i-addr))
      (call $ease-out-cubic (local.get $t))))
  
  β© Step 19: Dragging an Emoji
β© Step 20: Clamping to 4 Adjacent Cells
β© Step 21: Swap Animation
β© Step 24: Swap Cells After Drag
i64.and to check if all 3 matchi64.or to add it to the
        result
    (if (i32.and
          (i32.wrap_i64
            (i64.and (local.get $valid) (i64.const 1)))
          (i64.eq
            (i64.and (local.get $grid) (local.get $pattern))
            (local.get $pattern)))
      (then
        (local.set $result
          (i64.or (local.get $result) (local.get $pattern)))))
    (local.set $pattern
      (i64.shl (local.get $pattern) (i64.const 1)))
    (local.set $valid
      (i64.shr_u (local.get $valid) (i64.const 1)))
  
  
  (i32  ;; ........    ........                 pattern
        ;; ........    x.......
        ;; ........    x.......
        ;; xxx.....    x.......
         0x00000007  0x00010101)
  (i64  ;;    xxxxxx..            ........      valid mask
        ;;    xxxxxx..            ........
        ;;    xxxxxx..            xxxxxxxx
        ;;    xxxxxx..            xxxxxxxx
        ;;    xxxxxx..            xxxxxxxx
        ;;    xxxxxx..            xxxxxxxx
        ;;    xxxxxx..            xxxxxxxx
        ;;    xxxxxx..            xxxxxxxx
        0x3f3f3f3f3f3f3f3f  0x0000ffffffffffff)
  
  β© Step 26: Swap Back If No Match
    ;; Get the index of the lowest set bit
    (local.set $i (i64.ctz (local.get $empty)))
    ;; Find the next cell above that is not empty:
    ;; invert the empty pattern and mask it with a column,
    ;; shifted by i.
    (local.set $above-bits
      (i64.and
        (i64.xor (local.get $empty) (i64.const -1))
        (i64.shl (i64.const 0x0101010101010101) (local.get $i))))
    ;; Now find the lowest set bit
    (local.set $above-idx (i64.ctz (local.get $above-bits)))
    ;; If there is a cell above this one...
    (if (i64.ne (local.get $above-bits) (i64.const 0))
      (then
        ;; Move the cell above down...
  
  β© Step 28: Randomize Board at Start
β© Step 29: Check Matches After Dropping
β© Step 30: Animate Match Removal
  (i32  ;; ..x.....    .x......    x.......    xx......  
        ;; xx......    x.x.....    .xx.....    ..x.....  
         0x00000403  0x00000205  0x00000106  0x00000304  
        ;; x.x.....    .xx.....    ........    ........ 
        ;; .x......    x.......    xx.x....    x.xx.... 
         0x00000502  0x00000601  0x0000000b  0x0000000d )
  (i64 0x003f3f3f3f3f3f3f   ;; ........      xxxxx...
       0x003f3f3f3f3f3f3f   ;; xxxxxx..      xxxxx...
       0x003f3f3f3f3f3f3f   ;; xxxxxx..      xxxxx...
       0x003f3f3f3f3f3f3f   ;; xxxxxx..      xxxxx...
       0x003f3f3f3f3f3f3f   ;; xxxxxx..      xxxxx...
       0x003f3f3f3f3f3f3f   ;; xxxxxx..      xxxxx...
       0x1f1f1f1f1f1f1f1f   ;; xxxxxx..      xxxxx...
       0x1f1f1f1f1f1f1f1f ) ;; xxxxxx.. *6   xxxxx... *2
  
  right?