Skip to content

Instantly share code, notes, and snippets.

@binji
Created December 17, 2021 22:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save binji/cf03a0d4679591d9c208dd0b787ba972 to your computer and use it in GitHub Desktop.
Save binji/cf03a0d4679591d9c208dd0b787ba972 to your computer and use it in GitHub Desktop.
;; Memory map:
;;
;; [0x19a3 .. 0x019a4] x, y mouse click position
;; [0x1a60 .. 0x01aa0] 16 RGBA colors u32[16]
;; [0x1aa0 .. 0x01ea0] 16x16 emojis 4bpp u8[8][128]
;; [0x1ea0 .. 0x01ef0] 8x8 digits 1bpp u8[10][8]
;; [0x1ef0 .. 0x01f18] gameover 1bpp u8[5][8]
;; [0x1f18 .. 0x01f60] 18 match patterns u32[18]
;; [0x1f60 .. 0x01ff0] 18 shift masks u64[18]
;; [0x20a0 .. 0x020e0] 8x8 grid bitmap u64[8]
;; [0x22a0 .. 0x023a0] current offset {s8 x, s8 y, s8 w, s8 h}[64]
;; [0x23a0 .. 0x024a0] start offset {s8 x, s8 y, s8 w, s8 h}[64]
;; [0x24a0 .. 0x025a0] end offset {s8 x, s8 y, s8 w, s8 h}[64]
;; [0x25a0 .. 0x026a0] time [0..1) f32[64]
;; [0x26a0 .. 0x02a3c] compressed data
(import "env" "memory" (memory 1))
(global $score (mut i32) (i32.const 0))
(global $matched (mut i64) (i64.const -1))
(global $state (mut i32) (i32.const 0)) ;; init
(global $animating (mut i32) (i32.const 1))
(global $prev-mouse-bit (mut i64) (i64.const 0))
(global $click-mouse-bit (mut i64) (i64.const 0))
(; Algorithm "xor" from p. 4 of Marsaglia, "Xorshift RNGs"
modified to return uniform float in range [0,1) ;)
(global $random-state (mut i32) (i32.const 1))
(func $random (result f32)
(local $x i32)
(global.set $random-state
(local.tee $x
(i32.xor
(local.tee $x
(i32.xor
(local.tee $x
(i32.xor
(local.tee $x (global.get $random-state))
(i32.shl
(local.get $x)
(i32.const 13))))
(i32.shr_u
(local.get $x)
(i32.const 17))))
(i32.shl
(local.get $x)
(i32.const 5)))))
(f32.mul
(f32.convert_i32_u (i32.shr_u (local.get $x) (i32.const 8)))
(f32.const 0x1p-24))
)
(func (export "update")
(local $src i32)
(local $dst i32)
(local $dist i32)
(local $copy-end i32)
(local $byte-or-len i32)
(local $i i32)
(local $grid-offset i32)
(local $random-grid i32)
(local $t-addr i32)
(local $i-addr i32)
(local $a i32)
(local $mouse-src*4 i32)
(local $click-mouse-src*4 i32)
(local $x i32)
(local $divisor i32)
(local $mouse-bit i64)
(local $empty i64)
(local $idx i64)
(local $1<<idx i64)
(local $above-bits i64)
(local $above-idx i64)
(local $mouse-dx f32)
(local $mouse-dy f32)
(local $t f32)
(local $mul-t f32)
block $done
block $matched
block $gameover
block $falling
block $removing
block $init
block $reset-prev-mouse
block $reset-all-mouse
block $mouse-down
block $idle
(br_table $init $idle $mouse-down $removing $falling $gameover
(global.get $state))
end $idle
;; Animate mouse-bit scaling up, as long as it isn't the same as
;; prev-mouse-bit: mouse-bit & ~prev-mouse-bit
(call $animate-cells
(i64.and
(local.tee $mouse-bit
(call $get-mouse-bit
(i32.load16_u (i32.const 0x1a)) ;; mousex
(i32.load16_u (i32.const 0x1c)))) ;; mousey
(i64.xor (global.get $prev-mouse-bit) (i64.const -1)))
(i32.const 0x08_08_fc_fc))
(global.set $click-mouse-bit (local.get $mouse-bit))
;; If the mouse was not clicked, or if it is an invalid cell, then skip.
(br_if $reset-prev-mouse
(i32.or
(i32.eqz (i32.load8_u (i32.const 0x1e)))
(i64.eqz (local.get $mouse-bit))))
;; Save the current mouse x/y.
(i32.store (i32.const 0x19a3) (i32.load (i32.const 0x1a)))
;; Set the current state to $mouse-down.
(global.set $state (i32.const 2))
(br $reset-prev-mouse)
end $mouse-down
;; if abs(mouse-dx) < abs(mouse-dy) ...
(if (result f32)
(f32.lt
(f32.abs
;; mouse-dx = mouse-x - mouse-click-x
(local.tee $mouse-dx
(f32.convert_i32_s
(i32.sub (i32.load16_u (i32.const 0x1a))
(i32.load16_u (i32.const 0x19a3))))))
(f32.abs
;; mouse-dy = mouse-y - mouse-click-y
(local.tee $mouse-dy
(f32.convert_i32_s
(i32.sub (i32.load16_u (i32.const 0x1c))
(i32.load16_u (i32.const 0x19a5)))))))
(then
;; mouse-dy = copysign(min(abs(mouse-dy), 17), mouse-dy)
(local.set $mouse-dy
(f32.copysign
(f32.min (f32.abs (local.get $mouse-dy)) (f32.const 17))
(local.get $mouse-dy)))
;; mouse-dx = 0
(f32.const 0))
(else
;; mouse-dy = 0
(local.set $mouse-dy (f32.const 0))
;; mouse-dx = copysign(min(abs(mouse-dx), 17), mouse-dx)
(f32.copysign
(f32.min (f32.abs (local.get $mouse-dx)) (f32.const 17))
(local.get $mouse-dx))))
(local.set $mouse-src*4
(call $bit-to-src*4
(local.tee $mouse-bit
(call $get-mouse-bit
(i32.add (i32.trunc_f32_s (local.tee $mouse-dx (; `if` result ;)))
(i32.load16_u (i32.const 0x19a3)))
(i32.add (i32.trunc_f32_s (local.get $mouse-dy))
(i32.load16_u (i32.const 0x19a5)))))))
;; If mouse-bit is valid
(if (i32.eqz (i64.eqz (local.get $mouse-bit)))
(then
;; end[click-mouse-bit].x = mouse-dx
;; end[click-mouse-bit].y = mouse-dy
(i32.store16 offset=0x24a0
(local.tee $click-mouse-src*4
(call $bit-to-src*4 (global.get $click-mouse-bit)))
(i32.or
(i32.shl
(i32.trunc_f32_s (local.get $mouse-dy))
(i32.const 8))
(i32.trunc_f32_s (local.get $mouse-dx))))
;; If mouse-bit != click-mouse-bit
(if (i64.ne (global.get $click-mouse-bit) (local.get $mouse-bit))
(then
;; end[mouse-bit].x = -mouse-dx
;; end[mouse-bit].y = -mouse-dy
(i32.store16 offset=0x24a0
(local.get $mouse-src*4)
(i32.or
(i32.shl
(i32.trunc_f32_s (f32.neg (local.get $mouse-dy)))
(i32.const 8))
(i32.trunc_f32_s (f32.neg (local.get $mouse-dx)))))))))
;; Skip the following if the button is still pressed.
(br_if $reset-prev-mouse (i32.load8_u (i32.const 0x1e)))
(global.set $state (i32.const 1))
;; Skip the following if mouse-bit is not valid or is different from
;; the clicked cell. Since we know that the button was released, we branch
;; to $reset-all-mouse, which will reset the clicked mouse too.
(br_if $reset-all-mouse
(i32.or
(i64.eqz (local.get $mouse-bit))
(i64.eq (global.get $click-mouse-bit) (local.get $mouse-bit))))
;; swap the mouse-bit and click-mouse-bit bits in all grids.
(call $swap-all-grids-bits
(local.get $mouse-bit)
(global.get $click-mouse-bit))
(global.set $matched (call $match-all-grids-patterns (i32.const 8)))
;; Try to find matches. If none, then reset the swap.
(if (i64.eqz (global.get $matched))
(then
;; Swap back
(call $swap-all-grids-bits
(local.get $mouse-bit)
(global.get $click-mouse-bit)))
(else
;; force the cells back to 0,0
(i32.store16 offset=0x22a0
(local.get $mouse-src*4) (i32.const 0))
(i32.store16 offset=0x22a0
(local.get $click-mouse-src*4) (i32.const 0))
(i32.store16 offset=0x24a0
(local.get $mouse-src*4) (i32.const 0))
(i32.store16 offset=0x24a0
(local.get $click-mouse-src*4) (i32.const 0))
(br $matched)))
;; fallthrough
end $reset-all-mouse
;; Animate mouse and click-mouse cells back to their original place
(call $animate-cells
(i64.or (local.get $mouse-bit) (global.get $click-mouse-bit))
(i32.const 0))
;; fallthrough
end $reset-prev-mouse
;; Reset prev-mouse-bit, as long as it isn't the same as mouse-bit:
;; prev-mouse-bit & ~mouse-bit
(call $animate-cells
(i64.and
(global.get $prev-mouse-bit)
(i64.xor (local.get $mouse-bit) (i64.const -1)))
(i32.const 0))
(global.set $prev-mouse-bit (local.get $mouse-bit))
(br $done)
end $init
;; Decompress from [0xd00,0xfe5] -> 0x1a60.
;;
;; While src < 0xfe5:
;; byte = readbyte()
;; if byte <= 32:
;; len = byte + 3
;; dist = readbyte()
;; copy data from mem[dst-dist:dst-dist+len] to mem[dst:dst+len]
;; else:
;; mem[dst] = byte + 172
;;
(loop $loop
(if (result i32)
(i32.le_s
(local.tee $byte-or-len (i32.load8_u offset=0x26a0 (local.get $src)))
(i32.const 32))
(then
;; back-reference
(local.set $copy-end
(i32.add (i32.add (local.get $dst) (local.get $byte-or-len))
(i32.const 3)))
(loop $copy-loop
(i32.store8 offset=0x1a60
(local.get $dst)
(i32.load8_u offset=0x1a60
(i32.sub (local.get $dst)
(i32.load8_u offset=0x26a1 (local.get $src)))))
(br_if $copy-loop
(i32.lt_s (local.tee $dst (i32.add (local.get $dst) (i32.const 1)))
(local.get $copy-end))))
;; src addend
(i32.const 2))
(else
;; literal data
(i32.store8 offset=0x1a5f
(local.tee $dst (i32.add (local.get $dst) (i32.const 1)))
(i32.add (local.get $byte-or-len) (i32.const 172)))
;; src addend
(i32.const 1)))
(br_if $loop
(i32.lt_s (local.tee $src (i32.add (; result ;) (local.get $src)))
(i32.const 0x2e5))))
;; init palette
(i32.store (i32.const 4) (i32.load (i32.const 0x1a60)))
(i32.store (i32.const 8) (i32.load (i32.const 0x1a64)))
(i32.store (i32.const 12) (i32.load (i32.const 0x1a68)))
(i32.store (i32.const 16) (i32.load (i32.const 0x1a6c)))
;; init system flags (clear framebuffer, no virtual gamepad)
(i32.store (i32.const 0x1f) (i32.const 2))
;; fallthrough
end $removing
(br_if $done (global.get $animating))
;; Remove the matched cells...
(loop $loop
;; grid-bitmap[grid-offset] &= ~pattern
(i64.store offset=0x20a0
(local.get $grid-offset)
(i64.and
(i64.load offset=0x20a0 (local.get $grid-offset))
(i64.xor (global.get $matched) (i64.const -1))))
;; grid-offset += 8
;; loop if grid-offset < 64
(br_if $loop
(i32.lt_u
(local.tee $grid-offset
(i32.add (local.get $grid-offset) (i32.const 8)))
(i32.const 64))))
;; ... and move down cells to fill the holes
(local.set $empty (global.get $matched))
(block $move-down-exit
(loop $move-down-loop
;; Exit the loop if there are no further bits.
(br_if $move-down-exit (i64.eqz (local.get $empty)))
(local.set $1<<idx
(i64.shl
(i64.const 1)
;; Get the index of the lowest set bit
(local.tee $idx (i64.ctz (local.get $empty)))))
;; If there is not a cell above this one...
(if (i64.eqz
;; Find the next cell above that is not empty: invert the empty
;; pattern and mask it with a column, shifted by idx.
(local.tee $above-bits
(i64.and
(i64.xor (local.get $empty) (i64.const -1))
(i64.shl (i64.const 0x0101010101010101) (local.get $idx)))))
(then
;; then we need to fill with a new random cell.
;;
;; random-grid = int(random() * 8) << 3
;; grid-bitmap[random-grid] |= (1 << idx)
(i64.store offset=0x20a0
(local.tee $random-grid
(i32.shl
(i32.trunc_f32_u (f32.mul (call $random) (f32.const 8)))
(i32.const 3)))
(i64.or
(i64.load offset=0x20a0 (local.get $random-grid))
(local.get $1<<idx)))
;; Set above-idx so it is always the maximum value (used below)
(local.set $above-idx (i64.add (local.get $idx) (i64.const 56))))
(else
;; If there is cell above, move iti down
(call $swap-all-grids-bits
(i64.shl
(i64.const 1)
;; Find the lowest set bit in $above-bits
(local.tee $above-idx (i64.ctz (local.get $above-bits))))
(local.get $1<<idx))
;; Set above-bit in empty so we will fill it.
(local.set $empty
(i64.or (local.get $empty)
(i64.shl (i64.const 1) (local.get $above-idx))))))
;; Reset the x,w,h to 0, but set the y pixel offset to the y cell
;; difference * 17.
(i64.store32 offset=0x22a0
(i32.wrap_i64
(i64.shl (local.get $idx) (i64.const 2)))
(i64.shl
(i64.and
(i64.mul
(i64.shr_s
(i64.sub (local.get $idx) (local.get $above-idx))
(i64.const 3))
(i64.const 17))
(i64.const 0xff))
(i64.const 8)))
;; Now animate it back to 0.
(call $animate-cells (local.get $1<<idx) (i32.const 0))
;; Clear this bit (it has now been filled).
(local.set $empty
(i64.and
(local.get $empty)
(i64.sub (local.get $empty) (i64.const 1))))
;; Always loop
(br $move-down-loop)))
;; Set state to $falling
(global.set $state (i32.const 4))
(br $done)
end $falling
(br_if $done (global.get $animating))
;; Check whether any new matches (without swaps) occurred.
(global.set $matched (call $match-all-grids-patterns (i32.const 8)))
;; If there are any matches (including swaps), then keep going.
(br_if $matched
(i32.eqz (i64.eqz (call $match-all-grids-patterns (i32.const 72)))))
;; otherwise fallthrough to gameover, with a brief animation.
(call $animate-cells (i64.const -1) (i32.const 0x04_04_fe_fe))
(global.set $state (i32.const 5))
end $gameover
;; draw game over sprite
(call $draw-sprite
(i32.const 8) (i32.const 1)
(i32.const 0x450)
(i32.const 40) (i32.const 8)
(i32.const 80) (i32.const 8)
(i32.const 3) (i32.const 7) (i32.const 1))
;; don't reset until animation is finished and mouse is clicked
(br_if $done (i32.or (global.get $animating)
(i32.eqz (i32.load8_u (i32.const 0x1e)))))
;; Reset the entire board.
(global.set $matched (i64.const -1))
;; Reset the score (use -64 since 64 will be added below)
(global.set $score (i32.const -64))
end $matched
;; Add score
(global.set $score
(i32.add
(global.get $score)
(i32.wrap_i64 (i64.popcnt (global.get $matched)))))
;; Animate the matched cells
(call $animate-cells (global.get $matched) (i32.const 0xf1_f1_08_08))
;; If there are new matches, then remove them, otherwise go back to $idle
(global.set $state
(select (i32.const 1) (i32.const 3) (i64.eqz (global.get $matched))))
end $done
;; Animate
;; mul-t = 1
(local.set $mul-t (f32.const 1))
(loop $animate-loop
;; ilerp = (a,b,t) => return a + (b - a) * t
;; easeOutCubic(t) = t => t * (3 + t * (t - 3))
;; current[i] = ilerp(start[i], end[i], easeOutCubic(t))
(i32.store8 offset=0x22a0
(local.get $i-addr)
(i32.add
(local.tee $a (i32.load8_s offset=0x23a0 (local.get $i-addr)))
(i32.trunc_f32_s
(f32.mul
(f32.convert_i32_s
(i32.sub
(i32.load8_s offset=0x24a0 (local.get $i-addr))
(local.get $a)))
(f32.mul
;; t = Math.min(t[i] + speed, 1)
(local.tee $t
(f32.min
(f32.add
(f32.load offset=0x25a0 (local.get $t-addr))
(f32.const 0.005))
(f32.const 1)))
(f32.add
(f32.const 3)
(f32.mul
(local.get $t)
(f32.sub (local.get $t) (f32.const 3)))))))))
;; t[i] = t
(f32.store offset=0x25a0 (local.get $t-addr) (local.get $t))
;; mul-t *= t
(local.set $mul-t (f32.mul (local.get $mul-t) (local.get $t)))
;; i-addr += 1
;; t-addr = i-addr & ~3
(local.set $t-addr
(i32.and
(local.tee $i-addr (i32.add (local.get $i-addr) (i32.const 1)))
(i32.const 0xfc)))
;; loop if i-addr < 256
(br_if $animate-loop (i32.lt_s (local.get $i-addr) (i32.const 256))))
;; If all t values are 1 (i.e. all animations are finished), then multiplying
;; them together will also be 1.
(global.set $animating (f32.ne (local.get $mul-t) (f32.const 1)))
(call $draw-grids (i64.const -1)) ;; Mask with all 1s
;; Draw the moused-over cell again, so they're on top
(call $draw-grids (local.get $mouse-bit))
;; Draw score
(local.set $x (i32.const 111))
(local.set $divisor (i32.const 1000))
(loop $digit-loop
(call $draw-sprite
(local.tee $x (i32.add (local.get $x) (i32.const 8)))
(i32.const 1)
(i32.add
(i32.const 0x400)
(i32.shl
(i32.rem_u
(i32.div_u (global.get $score) (local.get $divisor))
(i32.const 10))
(i32.const 3)))
(i32.const 8) (i32.const 8)
(i32.const 8) (i32.const 8)
(i32.const 3) (i32.const 7) (i32.const 1))
;; divisor /= 10
;; looop if divisor != 0
(br_if $digit-loop
(local.tee $divisor (i32.div_u (local.get $divisor) (i32.const 10)))))
)
(func $match-all-grids-patterns (param $last-pattern i32) (result i64)
(local $result i64)
(local $grid i64)
(local $pattern i64)
(local $shifts i64)
(local $grid-offset i32)
(local $i i32)
(loop $grid-loop
;; grid = grids[i]
(local.set $grid (i64.load offset=0x20a0 (local.get $grid-offset)))
;; i = 0;
(local.set $i (i32.const 0))
(loop $pattern-loop
;; pattern = match-patterns[i]
(local.set $pattern (i64.load32_u offset=0x1f18 (local.get $i)))
;; shifts = match-shifts[i]
(local.set $shifts
(i64.load offset=0x1f60 (i32.shl (local.get $i) (i32.const 1))))
(loop $bit-loop
;; if ((shifts & 1) && ((grid & pattern) == pattern)) ...
(if (i32.and
(i32.wrap_i64 (i64.and (local.get $shifts) (i64.const 1)))
(i64.eq (i64.and (local.get $grid) (local.get $pattern))
(local.get $pattern)))
(then
;; result |= pattern
(local.set $result (i64.or (local.get $result) (local.get $pattern)))))
;; pattern <<= 1
(local.set $pattern (i64.shl (local.get $pattern) (i64.const 1)))
;; shifts >>= 1
;; loop if shifts != 0
(br_if $bit-loop
(i32.eqz
(i64.eqz
(local.tee $shifts
(i64.shr_u (local.get $shifts) (i64.const 1)))))))
;; i += 4
;; loop if i < last-pattern
(br_if $pattern-loop
(i32.lt_u
(local.tee $i (i32.add (local.get $i) (i32.const 4)))
(local.get $last-pattern))))
;; grid-offset += 8
;; loop if grid-offset < 64
(br_if $grid-loop
(i32.lt_u
(local.tee $grid-offset (i32.add (local.get $grid-offset) (i32.const 8)))
(i32.const 64))))
;; return result
(local.get $result)
)
(func $swap-all-grids-bits (param $a i64) (param $b i64)
(local $grid-offset i32)
(local $bits i64)
(local $a|b i64)
(loop $loop
;; if popcnt(bits & (a | b)) == 1 ;; i.e. bits are different
(if (i64.eq
(i64.popcnt
(i64.and
;; bits = mem[grid-offset]
(local.tee $bits
(i64.load offset=0x20a0 (local.get $grid-offset)))
(local.tee $a|b (i64.or (local.get $a) (local.get $b)))))
(i64.const 1))
(then
;; mem[grid-offset] = bits ^ (a | b)
(i64.store offset=0x20a0
(local.get $grid-offset)
(i64.xor (local.get $bits) (local.get $a|b)))))
;; grid-offset += 8
;; loop if grid-offset < 64
(br_if $loop
(i32.lt_s
(local.tee $grid-offset
(i32.add (local.get $grid-offset) (i32.const 8)))
(i32.const 64))))
)
(func $get-mouse-bit (param $x i32) (param $y i32) (result i64)
;; return ...
(select
;; 1 << ((y / 17) * 8 + (x / 17))
(i64.shl
(i64.const 1)
(i64.extend_i32_u
(i32.add
(i32.mul
(i32.div_s
;; y = 152 - y
(local.tee $y (i32.sub (i32.const 152) (local.get $y)))
(i32.const 17))
(i32.const 8))
(i32.div_s
;; x -= 12
(local.tee $x (i32.sub (local.get $x) (i32.const 12)))
(i32.const 17)))))
;; -1
(i64.const 0)
;; if (x < 136) && (y < 136)
(i32.and
(i32.lt_u (local.get $x) (i32.const 136))
(i32.lt_u (local.get $y) (i32.const 136))))
)
(func $bit-to-src*4 (param $bit i64) (result i32)
(i32.shl (i32.wrap_i64 (i64.ctz (local.get $bit))) (i32.const 2))
)
(func $animate-cells (param $bits i64) (param $h_w_y_x i32)
(local $src*4 i32)
(loop $loop
;; Exit the function if there are no further bits.
(br_if 1 (i64.eqz (local.get $bits)))
;; Set the start x/y/w/h to the current x/y/w/h.
(i32.store offset=0x23a0
(local.tee $src*4 (call $bit-to-src*4 (local.get $bits)))
(i32.load offset=0x22a0 (local.get $src*4)))
;; Set the destination x/y/w/h
(i32.store offset=0x24a0 (local.get $src*4) (local.get $h_w_y_x))
;; Set the time value to 1 - time.
(f32.store offset=0x25a0
(local.get $src*4)
(f32.sub
(f32.const 1)
(f32.load offset=0x25a0 (local.get $src*4))))
;; Clear the lowest set bit: bits &= bits - 1
(local.set $bits
(i64.and
(local.get $bits)
(i64.sub (local.get $bits) (i64.const 1))))
;; Always loop
(br $loop)
)
)
(func $draw-grids (param $mask i64)
(local $grid-offset i32)
(local $cell-idx i32)
(local $anim-idx i32)
(local $bits i64)
(loop $grid-loop
;; bits = grid[grid-offset] & mask
(local.set $bits
(i64.and
(i64.load offset=0x20a0 (local.get $grid-offset))
(local.get $mask)))
(block $cell-exit
(loop $cell-loop
;; Break out of the loop if bits == 0
(br_if $cell-exit (i64.eqz (local.get $bits)))
;; Draw the cell at that index
(call $draw-sprite
(i32.add
;; base x-coordinate: 12 + (idx & 7) * 17
(i32.add
(i32.const 12)
(i32.mul
(i32.and
;; Get the index of the lowest set bit
(local.tee $cell-idx
(i32.wrap_i64 (i64.ctz (local.get $bits))))
(i32.const 7))
(i32.const 17)))
;; x offset
(i32.load8_s offset=0x22a0
(local.tee $anim-idx
(i32.shl (local.get $cell-idx) (i32.const 2)))))
(i32.add
;; base y-coordinate: (160 - 17 - 7) - (idx >> 3) * 17
(i32.sub
(i32.const 136)
(i32.mul
(i32.shr_u (local.get $cell-idx) (i32.const 3))
(i32.const 17)))
;; y offset
(i32.load8_s offset=0x22a1 (local.get $anim-idx)))
;; src
(i32.shl (local.get $grid-offset) (i32.const 4))
;; sw / sh
(i32.const 16) (i32.const 16)
;; base w
(i32.add
(i32.const 16)
;; w offset
(i32.load8_s offset=0x22a2 (local.get $anim-idx)))
;; base h
(i32.add
(i32.const 16)
;; h offset
(i32.load8_s offset=0x22a3 (local.get $anim-idx)))
(i32.const 1)
(i32.const 1)
(i32.const 0xf))
;; Clear the lowest set bit: bits &= bits - 1
(local.set $bits
(i64.and
(local.get $bits)
(i64.sub (local.get $bits) (i64.const 1))))
;; Always loop
(br $cell-loop)))
;; grid-offset += 8
;; loop if grid-offset < 64
(br_if $grid-loop
(i32.lt_s
(local.tee $grid-offset (i32.add (local.get $grid-offset) (i32.const 8)))
(i32.const 64))))
)
(func $draw-sprite (param $x i32) (param $y i32)
(param $src i32)
(param $sw i32) (param $sh i32)
(param $dw i32) (param $dh i32)
(param $pixels-per-byte i32)
(param $src-offset-mask i32)
(param $palidx-mask i32)
(local $i i32)
(local $j i32)
(local $x+i i32)
(local $y+j i32)
(local $src-offset i32)
(local $palidx i32)
(local $addr i32)
(local $shift 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))))
;; for (j = 0; j < dh; j++)
(loop $y-loop
(local.set $i (i32.const 0))
;; for (i = 0; i < dw; i++)
(loop $x-loop
;; src-offset = (sw * j * dy) + i * dx
;; palidx = (mem[src + (src-offset >> pixels-per-byte)] >>
;; ((src-offset & src-offset-mask) << (3 - pixels-per-byte))) &
;; palidx-mask;
(local.set $palidx
(i32.and
(i32.shr_u
(i32.load8_u offset=0x1aa0
(i32.add
(local.get $src)
(i32.shr_u
(local.tee $src-offset
(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)))))
(local.get $pixels-per-byte))))
(i32.shl
(i32.and (local.get $src-offset) (local.get $src-offset-mask))
(i32.sub (i32.const 3) (local.get $pixels-per-byte))))
(local.get $palidx-mask)))
;; if (palidx != 0)
(if (local.get $palidx)
(then
;; skip if the x/y coordinate is out of bounds
(br_if 0
(i32.or
(i32.ge_u
(local.tee $x+i (i32.add (local.get $x) (local.get $i)))
(i32.const 160))
(i32.ge_u
(local.tee $y+j (i32.add (local.get $y) (local.get $j)))
(i32.const 160))))
;; addr = 0xa0 + (y * 40 + (x >> 2))
;; shift = (x&3)<<1
;; mem[addr] = mem[addr] & ~(3 << shift) | (palidx << shift)
(i32.store8 offset=0xa0
(local.tee $addr
(i32.add
(i32.mul (local.get $y+j) (i32.const 40))
(i32.shr_u
(local.get $x+i)
(i32.const 2))))
(i32.or
(i32.and
(i32.load8_u offset=0xa0 (local.get $addr))
(i32.xor
(i32.shl
(i32.const 3)
(local.tee $shift
(i32.shl
(i32.and
(local.get $x+i)
(i32.const 3))
(i32.const 1))))
(i32.const 255)))
(i32.shl
(local.get $palidx)
(local.get $shift))))
))
;; loop if i < w
(br_if $x-loop
(i32.lt_s
;; i += 1
(local.tee $i (i32.add (local.get $i) (i32.const 1)))
(local.get $dw)))
)
;; loop if j < h
(br_if $y-loop
(i32.lt_s
;; j += 1
(local.tee $j (i32.add (local.get $j) (i32.const 1)))
(local.get $dh)))
)
)
(data (i32.const 0x26a0)
"\53\00\01\38\21\b3\53\85\8d\ba\53\8a\46\4f\53\54"
"\20\01\0c\01\87\87\04\07\02\09\84\01\08\57\03\0f"
"\00\11\00\0f\76\00\02\57\05\08\00\14\01\08\87\87"
"\86\01\08\77\01\0e\00\01\01\08\02\01\87\84\77\98"
"\00\01\86\00\30\02\0f\57\54\87\77\76\76\86\00\59"
"\84\00\09\01\58\05\68\05\78\84\01\08\00\18\00\43"
"\00\55\54\87\00\2d\01\2a\01\3e\00\3a\01\01\86\01"
"\08\00\04\01\64\01\0c\03\08\86\01\08\77\86\74\01"
"\7c\86\87\56\02\b7\00\b9\10\80\98\98\88\18\80\06"
"\08\00\31\01\09\84\01\38\01\90\02\7b\00\0f\00\6b"
"\77\76\57\84\01\93\87\76\57\01\0d\03\13\05\9c\96"
"\78\96\78\00\08\66\03\08\84\66\02\18\57\84\65\75"
"\01\24\57\64\65\65\00\a8\86\54\00\08\01\47\54\54"
"\65\85\03\68\64\06\80\65\65\04\07\02\09\00\29\65"
"\65\55\03\0f\00\11\02\0f\00\11\05\08\65\65\75\66"
"\65\76\01\0b\05\08\0b\01\68\65\78\98\96\78\68\55"
"\68\65\01\87\66\55\58\68\02\10\58\54\68\02\3f\58"
"\54\58\01\08\58\58\54\01\09\54\00\07\54\54\76\76"
"\02\80\74\76\76\56\00\06\56\01\38\03\06\11\01\86"
"\87\01\fa\87\77\84\87\02\01\57\00\08\76\00\02\09"
"\08\04\18\01\2e\87\57\54\01\30\86\00\ea\84\00\09"
"\00\0f\54\00\f7\04\f9\04\07\00\ea\03\07\98\98\01"
"\09\64\95\00\01\55\00\0f\75\00\e3\65\54\64\65\96"
"\66\00\03\55\64\75\98\78\00\03\55\65\03\08\00\1c"
"\04\08\03\20\65\86\85\85\85\76\67\67\77\74\87\00"
"\68\86\87\56\74\00\a4\76\86\77\56\74\87\86\00\08"
"\86\00\18\86\00\9f\86\77\01\06\87\00\19\86\77\01"
"\f8\74\04\83\54\54\00\fb\01\01\74\76\03\08\00\bc"
"\74\04\04\0b\01\74\76\66\65\01\03\00\1a\00\9f\75"
"\00\68\02\01\01\08\96\78\98\07\08\01\42\00\01\01"
"\4a\00\a4\00\08\01\39\75\01\66\03\17\02\09\05\7d"
"\04\7c\0e\84\02\11\00\52\05\08\02\19\76\98\76\98"
"\02\48\78\00\02\01\17\00\10\02\08\02\88\04\78\01"
"\88\98\98\98\02\c2\96\98\78\07\ca\06\28\04\01\92"
"\d3\b7\00\01\d3\92\84\90\90\84\84\84\d2\d2\92\d2"
"\b4\d2\93\57\d3\d3\00\18\cc\8c\b7\d3\d2\ba\bb\b7"
"\d3\d3\b4\b4\b4\d3\d3\57\93\d3\b4\d3\93\90\92\5b"
"\93\d3\00\30\d3\d3\84\6c\6c\60\60\60\70\92\00\0d"
"\00\03\00\30\d3\d2\84\92\72\e2\2d\72\a7\cb\33\4f"
"\f2\ab\4b\95\fe\d6\a8\e5\31\ff\f2\a8\4b\31\df\f2"
"\a8\cb\a5\de\00\0f\b3\de\f2\cb\eb\a2\de\72\77\eb"
"\5b\00\7c\55\55\55\54\57\58\54\54\59\00\8e\5a\55"
"\54\54\58\57\54\54\56\59\00\18\5a\54\54\5f\00\20"
"\61\02\24\56\54\55\56\55\54\56\00\2c\56\02\08\01"
"\10\00\04\55\03\03\93\04\01\53\02\01\54\54\04\0f"
"\20\08\03\08\73\0c\01\d3\02\01\54\54\20\08\02\08"
"\04\77\02\08\53"
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment