1
0
hugo/static/wasm/pico/dino.wat

810 lines
29 KiB
Plaintext
Raw Permalink Normal View History

2024-04-23 13:21:26 +00:00
;; Import Math.random from JavaScript.
(import "Math" "random" (func $random (result f32)))
;; *** Memory map ***
;; 0x0 Input: 1=up, 2=down, 3=up+down
;; [0x4, 0x5000) See below
;; [0x5000, 0x1c000) Color[300*75] canvas
(memory (export "mem") 2)
;; Some float constants to reduce size.
(global $f0 f32 (f32.const 0))
(global $f50 f32 (f32.const 50))
;; A timer used for animation.
(global $timer (mut i32) (i32.const 0))
;; The current score (only increments while playing).
(global $score (mut i32) (i32.const 0))
;; The current game state (see the main br_table below)
;; 0 => initial state
;; 1 => dino is running
;; 2 => dino is rising (first part of jump)
;; 3 => dino is falling (second part of jump)
;; 4 => dino is dead
(global $game_state (mut i32) (i32.const 0))
;; The dino's y velocity. Negative is going up, positive is going down.
(global $dino_jump_vy (mut f32) (f32.const 0))
;; The camera's scrolling speed. In actuality there is no camera, this speed is
;; instead used to move all game objects. Each object multiplies its movement
;; speed by this global $speed to provide a parallax effect.
(global $speed (mut f32) (f32.const -0.5))
(data (i32.const 40)
;; *** Object Table ***
;; addr:4 size:(#14 * 9 => 126 bytes)
;;
;; The first 4 objects (3 obstacles and dino) must be reset every game, so we
;; store the data just once and initialize it in the "init" game state.
;;
;; struct {
;; i8 kind; // index to the "Kind table" below.
;; f32 y, x; // y and x coordinate of the object, in pixels.
;; }
;;
;; kind y x
;;(i8 11) (f32 50 22) ;; dino
;; (i8 1) (f32 55 300) ;; obstacles x 3
;; (i8 1) (f32 55 600)
;; (i8 1) (f32 55 900)
(i8 7) (f32 67 0) ;; ground x 6
(i8 7) (f32 67 64)
(i8 7) (f32 67 128)
(i8 7) (f32 67 192)
(i8 7) (f32 67 256)
(i8 7) (f32 67 320)
(i8 6) (f32 40 0) ;; clouds x 4
(i8 6) (f32 40 128)
(i8 6) (f32 40 256)
(i8 6) (f32 40 384)
;; *** Kind Table ***
;; addr:130 size:(#12 * 7 = 84 bytes)
;;
;; The kind table contains all information about an object that is not
;; specific to that individual object.
;;
;; struct {
;; i8 type; // Random index (see "Random Table" below)
;; i8 anim; // Animation index (see "Animation Table" below)
;; i8 img; // Image offset (not index) (see "Image Table" below)
;; i8 random_8x; // Add at most 8x this to x-coord for new random object
;; i8 start_y; // The starting y coordinate for new random object
;; i8 random_y; // Add at most this to y-coord for new random object
;; i8 speed_4dx; // Add 4x this value * $speed to object when moving
;; }
;;
;;type anim img +8x y +y *4dx
(i8 0 0 18 75 46 0 4) ;; 0 cactus1
(i8 0 0 21 75 54 0 4) ;; 1 cactus2
(i8 0 0 24 75 54 0 4) ;; 2 cactus3
(i8 0 0 27 75 54 0 4) ;; 3 cactus4
(i8 0 0 30 75 46 0 4) ;; 4 cactus5
(i8 0 3 39 75 25 25 5) ;; 5 bird
(i8 1 0 33 30 15 25 1) ;; 6 cloud
(i8 2 0 36 0 67 0 4) ;; 7 ground
(i8 3 0 3 0 0 0 0) ;; 8 dino stand
(i8 3 1 6 0 0 0 0) ;; 9 dino run
(i8 3 2 12 0 0 0 0) ;; 10 dino duck
(i8 3 0 0 0 0 0 0) ;; 11 dino dead
;; *** Random Table ***
;; addr:214 size:(#3 * 2 = 6 bytes)
;;
;; Each element of the random table produces a new "kind" given a current
;; kind's "type". This way an obstacle always creates a new obstacle, a
;; ground object creates a new ground object, and a cloud creates a new
;; cloud. The dino is never respawned, so it doesn't need an entry.
;;
;; struct {
;; i8 start; // Minimum kind value for this type.
;; i8 len; // Number of kinds for this type.
;; }
;;
;; start len
(i8 0 6)
(i8 6 1)
(i8 7 1)
;; *** Animation Y Table ***
;; addr:220 size:(#4 * 4 = 16 bytes)
;;
;; An object has an "anim" value which index into a row of this table. Each
;; element is the value to add to the object's y-coordinate over time. This
;; allows us to move the dino's graphic down without chaning the dino y
;; coordinate, and to properly animate the bird so its head doesn't move when
;; its wings flap.
;;
;; time ->
(i8 0 0 0 0) ;; 0 none
(i8 0 0 0 0) ;; 1 run
(i8 9 9 9 9) ;; 2 duck
(i8 0 0 3 3) ;; 3 bird
;; *** Animation Image Table ***
;; addr:236 size:(#4 * 4 = 16 bytes)
;;
;; An object has a "anim" value which index into a row of this table. Each
;; element is the value to add to the object's img offset over time. This
;; animates the dino's legs and the bird's wings.
;;
;; time ->
(i8 0 0 0 0) ;; 0 none
(i8 0 3 0 3) ;; 1 run
(i8 0 3 0 3) ;; 2 duck
(i8 0 0 3 3) ;; 3 bird
;; *** Image Table ***
;; addr:252 size:(#15 * 3 = 45 bytes)
;;
;; Each image is a pair of the whcol (width/height/color) data, and an offset
;; to the actual graphics data after compression.
;;
;; struct {
;; i8 whcol; // Width/Height/Color offset (see "WHcol Table" below)
;; i16 data; // Offset of image data.
;; }
;;
;; whcol data
(i8 0) (i16 0) ;; dead = 0
(i8 0) (i16 440) ;; stand = 3
(i8 0) (i16 880) ;; run1 = 6
(i8 0) (i16 1320) ;; run2 = 9
(i8 3) (i16 1760) ;; duck1 = 12
(i8 3) (i16 2124) ;; duck2 = 15
(i8 6) (i16 2488) ;; cactus1 = 18
(i8 9) (i16 2826) ;; cactus2 = 21
(i8 12) (i16 3168) ;; cactus3 = 24
(i8 15) (i16 3672) ;; cactus4 = 27
(i8 18) (i16 3834) ;; cactus5 = 30
(i8 21) (i16 4874) ;; cloud = 33
(i8 24) (i16 5082) ;; ground = 36
(i8 27) (i16 5402) ;; bird1 = 39
(i8 30) (i16 5724) ;; bird2 = 42
;; *** WHCol Table (Width/Height/Color) ***
;; addr:297 size:(#13 * 3 = 39 bytes)
;;
;; The WHcol table stores a width and height in pixels, and a color. Some of
;; these values are shared by multiple images, which can save a few bytes.
;; The color stores the alpha of a black pixel. Since the background is drawn
;; in white, this produces various shades of gray. Collision only occurs w/
;; color 172 (the obstacles).
;;
;; struct {
;; i8 width; // Width in pixels.
;; i8 height; // Height in pixels.
;; i8 alpha; // Alpha value of a black pixel, e.g. AA000000.
;; }
;;
;; w h col
(i8 20 22 171) ;; 0 dead,stand,run1,run2
(i8 28 13 171) ;; 3 duck1,duck2
(i8 13 26 172) ;; 6 cactus1
(i8 19 18 172) ;; 9 cactus2
(i8 28 18 172) ;; 12 cactus3
(i8 9 18 172) ;; 15 cactus4
(i8 40 26 172) ;; 18 cactus5
(i8 26 8 37) ;; 21 cloud
(i8 64 5 171) ;; 24 ground
(i8 23 14 172) ;; 27 bird1
(i8 23 16 172) ;; 30 bird2
(i8 3 5 172) ;; 33 digits
(i8 50 8 172) ;; 36 gameover
;; *** Compressed Graphics Data ***
;; addr:336 size:491 bytes
;;
;; The original graphics data are 13 images stored as 1 bit-per-pixel.
;;
;; 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, ...
;;
;; To compress the data, first the bits from all images are grouped. Next
;; the bits are grouped into alternating runs of 0 and 1, starting with 0.
;; The maximum run length is 15, so longer runs can be marked as 15, 0, N.
;;
;; 1, 1, 3, 4, 4, 3, 4, ...
;;
;; Then repeated patterns are found and marked with a backward reference and
;; length (shown as (-dist, len) below). If the length is longer than the
;; distance, then the values will be repeated, e.g. 1, (-1, 4) is equivalent
;; to 1, 1, 1, 1, 1.
;;
;; 1, 1, 3, 4, 4, (-3, 2), ...
;;
;; Now the regions of literal values and back-references are grouped so they
;; alternate. The literal values are prefixed by a count. (The count is shown
;; as #n below). As before, if there are two adjacent back-references then
;; they must be separated by a count of 0, e.g. (-2, 5), [#0], (-6, 3).
;;
;; [#5, 1, 1, 3, 4, 4], (-3, 2), ...
;;
;; These values are then encoded as bits, where the count, distance and
;; length are 7 bits, and the literal values are 4 bits. The distance is
;; always stored as a positive number.
;;
;; [#0000101, 0001, 0001, 0011, 0100, 0100], (0000011, 0000010), ...
;;
;; Finally these bits are stored, little-endian. The final byte is
;; zero-extended.
;;
;; 10000101 10001000 00100001 00011010 00001000 00000000
;;
;; 0x85 0x44 0x21 0x1a 0x08 0x00
;;
;; When data is decompressed, the process is reversed. The decompression is
;; separated into two passes. First the literals and back-references are
;; written to one region (see "First Pass Decompressed output" below). Then
;; this region is scanned and the 1 bit-per-pixel data is written (see "Final
;; Decompressed Output" below). The final data is written as one-byte-per
;; pixel, so it's easier to blit to the screen.
"\91\c5\95\29\95\88\28\95\29\0d\18\72\28\81\65\71\66\42\4a\23\19\41\6e\7e"
"\9c\ab\c9\e7\13\e2\32\e1\41\e1\32\f2\40\b8\aa\12\07\3c\08\ea\85\e3\87\0c"
"\cb\c3\c4\03\c9\43\02\61\48\11\79\91\78\a0\78\20\c1\78\18\8c\a1\94\87\8f"
"\8b\07\96\87\9d\07\a5\07\52\96\43\9b\78\98\90\19\79\10\09\79\d8\d4\21\20"
"\40\15\62\64\42\1e\c4\b7\28\e8\64\a1\20\91\90\90\90\0a\0b\82\a6\f0\b1\83"
"\32\05\40\60\e8\9c\a0\dc\dc\d8\48\84\c4\44\d0\84\84\c4\c4\88\02\21\a0\50"
"\60\2a\16\14\26\15\14\55\7c\25\75\63\43\d0\08\4e\0c\08\80\d1\30\20\09\46"
"\63\10\32\60\44\41\42\42\e0\0b\00\04\15\30\c7\c1\55\41\51\22\31\21\d2\24"
"\31\21\44\52\64\21\24\36\85\24\36\57\25\36\37\67\a8\00\04\86\41\50\10\20"
"\07\8c\0a\a3\12\21\2a\36\36\29\0f\2a\4a\19\4d\49\38\6c\58\2c\70\82\92\70"
"\42\4a\d1\38\54\0c\89\90\e3\30\90\12\c7\87\10\12\e6\68\14\6a\42\d0\91\28"
"\0c\c9\9c\83\21\20\73\28\18\21\21\21\43\22\c1\90\05\01\63\c5\a1\28\1c\59"
"\99\0a\41\48\b2\62\28\08\38\81\08\c3\42\8c\28\09\87\c4\10\a1\d4\db\03\49"
"\c5\83\c4\c9\8b\85\0c\92\4b\c5\43\05\c5\83\49\c4\3c\08\10\81\4c\20\18\15"
"\0f\1b\0f\1f\2a\1f\45\71\84\23\e3\21\e5\c1\e6\a1\e8\21\44\c8\47\8a\2b\4c"
"\2d\0e\df\d5\1f\02\99\7b\f3\2f\e5\a1\e6\81\ea\41\ec\21\40\09\e0\7a\2e\6b"
"\d3\03\cd\43\c9\83\c9\83\85\53\48\48\98\84\84\44\98\94\98\90\54\48\90\84"
"\94\8c\a0\50\1c\c8\20\92\8a\0a\0c\92\90\08\9a\19\11\11\9a\11\09\09\0a\0a"
"\03\1d\04\15\12\83\e0\24\67\20\60\08\0d\0f\87\90\91\a0\12\19\06\12\24\3a"
"\00\80\06\82\c4\84\86\02\40\22\00\82\03\83\24\24\2a\64\24\10\b3\38\90\94"
"\8c\90\ef\d1\90\a0\19\8a\a1\90\10"
;; A few extra bytes to bump up to 2020 :-)
"\00\00\00"
;; *** First Pass Decompressed Output ***
;; addr:827 size:1490
;; *** Final Decompressed Output ***
;; addr:2317 size:6640 end=8957
;; Most graphic addresses are used above in the Image Table, but a few
;; additional graphics are used in the code below:
;;
;; Digits 0-9, 15 bytes each. addr:6092
;; GameOver addr:6242
)
(data (i32.const 0x4fdb)
;; Starting obstacles + dino
;; id x y
(i8 9) (f32 50 22) ;; dino
(i8 1) (f32 55 300) ;; obstacle1
(i8 1) (f32 55 600) ;; obstacle2
(i8 1) (f32 55 900) ;; obstacle3
;; Byte to replicate when clearing the screen.
(i8 0xff)
)
;; Copy len bytes from $src to $dst. We can emulate a fill if the regions
;; overlap. Always copies at least one byte!
(func $memcpy (param $dst i32) (param $src i32) (param $dstend i32)
(loop $copy
(i32.store8 (local.get $dst) (i32.load8_u (local.get $src)))
(local.set $src (i32.add (local.get $src) (i32.const 1)))
(br_if $copy
(i32.lt_u
(local.tee $dst (i32.add (local.get $dst) (i32.const 1)))
(local.get $dstend))))
)
;; The main decompression routine, which runs when the module is loaded (in the
;; start function).
(start $decompress)
(func $decompress
(local $data_left i32) ;; rest of currently read byte
(local $bits_left i32) ;; number of bits available
(local $bits_to_read i32) ;; number of bits to read
(local $read_data i32) ;; currently read value
(local $lit_count i32) ;; number of 4-bit literals to read
(local $ref_dist i32) ;; backreference distance
(local $src i32)
(local $dst i32)
(local $temp_dst i32)
(local $state i32) ;; see below
(local $run_count i32) ;; number of bits in this run
(local $run_byte i32) ;; byte to write
(local.set $bits_to_read (i32.const 7))
(local.set $dst (i32.const 827))
;; First pass, decode back-references.
(loop $loop
;; Read in new bits when the number of bits left is less than 16. This
;; works because we never read more than 7 bits.
(if
(i32.lt_u (local.get $bits_left) (i32.const 16))
(then
;; Read 16 bits into the top of $data_left
(local.set $data_left
(i32.or
(local.get $data_left)
(i32.shl
(i32.load16_u offset=336 (local.get $src))
(local.get $bits_left))))
;; Add 16 bits to count
(local.set $bits_left
(i32.add (local.get $bits_left) (i32.const 16)))
;; Increment the src pointer
(local.set $src (i32.add (local.get $src) (i32.const 2)))))
;; Save bits that were read (masked)
(local.set $read_data
(i32.and (local.get $data_left)
(i32.sub
(i32.shl (i32.const 1) (local.get $bits_to_read))
(i32.const 1))))
;; Remove bits that were read from $data_left
(local.set $data_left
(i32.shr_u (local.get $data_left) (local.get $bits_to_read)))
;; Reduce the number of $bits_left
(local.set $bits_left
(i32.sub (local.get $bits_left) (local.get $bits_to_read)))
block $read_backref_dist
block $read_backref_len
block $goto2
block $read_literal
block $read_lit_count
(br_table $read_lit_count $read_literal $read_backref_dist $read_backref_len
(local.get $state))
;; 0: read literal count (7 bits)
end $read_lit_count
;; skip if count is 0
(br_if $goto2 (i32.eqz (local.tee $lit_count (local.get $read_data))))
(local.set $state (i32.const 1))
(local.set $bits_to_read (i32.const 4))
(br $loop)
;; 1: read literal (4 bits)
end $read_literal
;; Write literal and increment $dst, as long as $lit_count is > 0.
(i32.store8 (local.get $dst) (local.get $read_data))
(local.set $dst (i32.add (local.get $dst) (i32.const 1)))
(br_if $loop
(local.tee $lit_count (i32.sub (local.get $lit_count) (i32.const 1))))
;; fallthrough
end $goto2
(local.set $state (i32.const 2))
(local.set $bits_to_read (i32.const 7))
(br $loop)
;; 3: read backreference length
end $read_backref_len
;; $memcpy always copies at least one byte. That may happen here but it's
;; OK because we'll not advance $dst, so the value we copied will be
;; overwritten.
(call $memcpy
(local.get $dst)
(i32.sub (local.get $dst) (local.get $ref_dist))
(local.tee $dst (i32.add (local.get $dst) (local.get $read_data))))
(local.set $state (i32.const 0))
(br $loop)
;; 2: read backreference distance
end $read_backref_dist
;; Check for end, since compressed data ends with literals.
(local.set $ref_dist (local.get $read_data))
(local.set $state (i32.const 3))
(br_if $loop (i32.lt_u (local.get $src) (i32.const 494)))
;; fall out of loop
)
;; Second pass, decode 1bpp runs
;;
;; We know the values of $src and $dst here, since they will have been set by
;; the loop above:
;;
;; $src == 494
;; $dst == 2317
;;
;; We want to read from 827, so we can use a load offset which is the
;; difference between the two: 827-494 = 333. Similarly, we want to end
;; reading at 2317, so we can instead stop when $src is 2317-333 = 1984.
;;
(loop $loop
(if (local.tee $run_count (i32.load8_u offset=333 (local.get $src)))
(then
;; Set first byte.
(i32.store8 (local.get $dst) (local.get $run_byte))
;; Then replicate with memcpy.
(call $memcpy
(i32.add (local.get $dst) (i32.const 1))
(local.get $dst)
(local.tee $dst (i32.add (local.get $dst) (local.get $run_count))))))
;; Flip the written byte between 0 and 1.
(local.set $run_byte (i32.eqz (local.get $run_byte)))
(br_if $loop
(i32.lt_u
(local.tee $src (i32.add (local.get $src) (i32.const 1)))
(i32.const 1984))))
)
;; Blit an 8bpp image to the screen at ($x, $y). The $whcol_offset and
;; $src_offset values are the same ones as are stored in the Image Table above.
;; They're supplied as parameters here so the Digits and GameOver images can
;; provide them directly without having to add extra entries for each of them
;; in the Image Table.
(func $blit
(param $x i32) (param $y i32)
(param $whcol_offset i32)
(param $src_offset i32) (result i32)
(local $w i32) ;; destination width of the sprite (maybe clipped)
(local $h i32) ;; height of the sprite (never clipped)
(local $color i32) ;; full 32-bit ARGB color
(local $dst_offset i32) ;; destination offset, updated per-row
(local $dst_addr i32) ;; destination address, calculated per-pixel
(local $src_stride i32) ;; the original width of the sprite
(local $ix i32)
(local $hit i32) ;; 0=no collision, 1=collision
(local.set $src_stride
(local.tee $w (i32.load8_u offset=297 (local.get $whcol_offset))))
(local.set $h (i32.load8_u offset=298 (local.get $whcol_offset)))
(local.set $color
(i32.shl
(i32.load8_u offset=299 (local.get $whcol_offset))
(i32.const 24)))
;; if (x < 0)
(if
(i32.lt_s (local.get $x) (i32.const 0))
(then
;; reduce width by x
(local.set $w (i32.add (local.get $w) (local.get $x)))
;; advance src_addr by x
(local.set $src_offset (i32.sub (local.get $src_offset) (local.get $x)))
;; set x to 0
(local.set $x (i32.const 0)))
(else
;; if (x + w > SCREEN_WIDTH)
(if
(i32.gt_s (i32.add (local.get $x) (local.get $w)) (i32.const 300))
(then
;; w = SCREEN_WIDTH - x
(local.set $w (i32.sub (i32.const 300) (local.get $x)))))))
;; if (w <= 0) { return 0; }
(if (i32.gt_s (local.get $w) (i32.const 0))
(then
;; dst_addr = y * SCREEN_WIDTH + x
(local.set $dst_offset
(i32.add (i32.mul (local.get $y) (i32.const 300)) (local.get $x)))
(loop $yloop
;; ix = 0;
(local.set $ix (i32.const 0))
(loop $xloop
;; data = i8_mem[src_addr]
(if
(i32.load8_u offset=2317
(i32.add (local.get $src_offset) (local.get $ix)))
(then
;; get alpha value of previous pixel. If it is 172, then it is an
;; obstacle.
(local.set $hit
(i32.or
(local.get $hit)
(i32.eq
(i32.load8_u offset=0x5003
(local.tee $dst_addr
(i32.shl (i32.add (local.get $dst_offset)
(local.get $ix))
(i32.const 2))))
(i32.const 172))))
;; set new pixel
(i32.store offset=0x5000 (local.get $dst_addr) (local.get $color))))
;; loop while (++ix < w)
(br_if $xloop
(i32.lt_s
(local.tee $ix (i32.add (local.get $ix) (i32.const 1)))
(local.get $w))))
;; dst_addr += SCREEN_WIDTH
(local.set $dst_offset (i32.add (local.get $dst_offset) (i32.const 300)))
;; src_addr += src_stride;
(local.set $src_offset (i32.add (local.get $src_offset) (local.get $src_stride)))
;; loop while (--h != 0)
(br_if $yloop
(local.tee $h (i32.sub (local.get $h) (i32.const 1)))))
))
(local.get $hit)
)
(func (export "run")
(local $input i32) ;; button input read per frame
(local $dino_id i32) ;; new dino id to set, updated each frame
(local $obj_addr i32) ;; object address when drawing/moving
(local $kind i32) ;; kind of the current object
(local $anim_offset i32) ;; offset into either Animation Table
(local $img_offset i32) ;; offset into the Image Table
(local $kind_offset i32) ;; offset into the Kind Table
(local $rand_offset i32) ;; offset into the Random Table
(local $score_x i32)
(local $score i32) ;; temporarily stored $score value
(local $obj_x f32) ;; x-coord of the current object
(local $dino_y f32) ;; new dino y, updated each frame
;; Clear the screen (the byte at 0x4fff is initialized to 0xff)
(call $memcpy (i32.const 0x5000) (i32.const 0x4fff) (i32.const 0x1af90))
;; Animation timer
(global.set $timer (i32.add (global.get $timer) (i32.const 1)))
;; Slowly increment the speed, up to -1. More negative speeds are faster.
(global.set $speed
(f32.max
(f32.sub (global.get $speed) (f32.const 0.001953125))
(f32.const -1)))
(local.set $input (i32.load8_u (i32.const 0)))
(local.set $dino_y (f32.load (i32.const 5)))
block $done
block $playing
block $falling
block $rising
block $running
block $init
block $dead
(br_table $init $running $rising $falling $dead (global.get $game_state))
end $dead
(local.set $dino_id (i32.const 11))
(global.set $speed (global.get $f0))
;; Wait until button pressed.
(br_if $done
(i32.or
(i32.eqz (local.get $input))
;; Wait at least 20 frames before restarting.
(i32.le_u
(i32.sub (global.get $timer) (global.get $score))
(i32.const 20))))
;; only need to reset score, dino state, and obstacles.
(global.set $score (i32.const 0))
(global.set $timer (i32.const 0))
(global.set $dino_jump_vy (global.get $f0))
(global.set $speed (f32.const -0.5))
;; fallthrough
end $init
;; init dino
(local.set $dino_id (i32.const 9)) ;; dino running animation
(local.set $dino_y (global.get $f50))
;; reset obstacles
(call $memcpy (i32.const 4) (i32.const 0x4fdb) (i32.const 40))
(global.set $game_state (i32.const 1)) ;; running state
;; fallthrough
end $running
;; If down pressed, duck (id=10) else running (id=9)
(local.set $dino_id
(i32.add (i32.eq (local.get $input) (i32.const 2)) (i32.const 9)))
;; if up is not pressed, skip over jumping code
(br_if $playing (i32.ne (local.get $input) (i32.const 1)))
;; start jumping.
(global.set $game_state (i32.const 2)) ;; rising state
(global.set $dino_jump_vy (f32.const -6))
;; fallthrough
end $rising
;; Stop jumping if the button is released and we've reached the minimum
;; height, or we've reached the maximum height.
(br_if $falling
(i32.and
(i32.or
(i32.eq (local.get $input) (i32.const 1))
(f32.ge (local.get $dino_y) (f32.const 30))) ;; min height
(f32.ge (local.get $dino_y) (f32.const 10)))) ;; max height
;; start falling.
(global.set $game_state (i32.const 3)) ;; falling state
(global.set $dino_jump_vy (f32.const -1))
;; fallthrough
end $falling
(local.set $dino_id (i32.const 8))
(local.set $dino_y (f32.add (local.get $dino_y) (global.get $dino_jump_vy)))
(global.set $dino_jump_vy (f32.add (global.get $dino_jump_vy) (f32.const 0.4)))
;; Stop falling if the ground is reached.
(br_if $playing (f32.le (local.get $dino_y) (global.get $f50)))
(global.set $game_state (i32.const 1)) ;; running state
(local.set $dino_y (global.get $f50))
(global.set $dino_jump_vy (global.get $f0))
;; fallthrough
end $playing
;; Add 1 to the score
(global.set $score (i32.add (global.get $score) (i32.const 1)))
;; fallthrough
end $done
;; Update dino id and y-coordinate.
(i32.store8 (i32.const 4) (local.get $dino_id))
(f32.store (i32.const 5) (local.get $dino_y))
;; loop over objects backward, drawing and moving
(local.set $obj_addr (i32.const 121))
(loop $loop
;;; Draw and check for collision.
(if
(i32.and
;; $blit returns true if we collided
(call $blit
;; x => obj.x
(i32.trunc_f32_s (f32.load offset=5 (local.get $obj_addr)))
;; y => obj.y + AnimYTable[anim_offset]
(i32.add
(i32.trunc_f32_s (f32.load offset=1 (local.get $obj_addr)))
(i32.load8_u offset=220
;; $anim_offset = KindTable[$kind_offset].anim + ((timer&15)>>2)
(local.tee $anim_offset
(i32.add
(i32.shl
(i32.load8_u offset=131
;; $kind_offset = $obj_addr->kind * sizeof(Kind)
(local.tee $kind_offset
(i32.mul
(i32.load8_u (local.get $obj_addr))
(i32.const 7))))
(i32.const 2))
(i32.shr_u
(i32.and (global.get $timer) (i32.const 15))
(i32.const 2))))))
;; whcol_offset => ImageTable[$img_offset].whcol
(i32.load8_u offset=252
;; $img_offset = KindTable[$kind_offset].img + AnimImgTable[$anim_offset]
(local.tee $img_offset
(i32.add
(i32.load8_u offset=132 (local.get $kind_offset))
(i32.load8_u offset=236 (local.get $anim_offset)))))
;; src_offset => ImageTable[$img_offset].data
(i32.load16_u offset=253 (local.get $img_offset)))
;; ... if this object is a dino...
(i32.eq (local.get $obj_addr) (i32.const 4)))
(then
;; ... then set state to dead.
(global.set $game_state (i32.const 4))))
;;; Move
(if
;; If object goes off screen to the left...
(f32.lt
;; $obj_x = $obj_addr->x + KindTable[$kind_offset].speed_4dx * $speed
(local.tee $obj_x
(f32.add
(f32.load offset=5 (local.get $obj_addr))
(f32.mul
(f32.convert_i32_u (i32.load8_u offset=136 (local.get $kind_offset)))
(global.get $speed))))
(f32.const -64))
(then
;; Write new object kind.
(i32.store8
(local.get $obj_addr)
;; Pick a random item.
;; $kind = RandTable[$rand_offset].start +
;; (random() * RandTable[$rand_offset].len)
(local.tee $kind
(i32.add
(i32.trunc_f32_s
(f32.mul
(call $random)
(f32.convert_i32_u
;; RandTable[$rand_offset].len
(i32.load8_u offset=215
;; $rand_offset = KindTable[$kind_offset].type << 1
(local.tee $rand_offset
(i32.shl
(i32.load8_u offset=130 (local.get $kind_offset))
(i32.const 1)))))))
;; RandTable[$rand_offset].start
(i32.load8_u offset=214 (local.get $rand_offset)))))
;; Set new object x (stored below).
;; $obj_x = SCREEN_WIDTH + 64 + (KindTable[$kind_offset].random_8x << 3)
(local.set $obj_x
(f32.add
(f32.add
(local.get $obj_x)
(f32.const 384)) ;; SCREEN_WIDTH + 64
(f32.convert_i32_u
(i32.shl
(i32.load8_u offset=133
(local.tee $kind_offset
(i32.mul (local.get $kind) (i32.const 7))))
(i32.const 3)))))
;; Write new object y.
;; $obj_addr->y = KindTable[$kind_offset].start_y +
;; (random() * KindTable[$kind_offset].random_y)
(f32.store offset=1
(local.get $obj_addr)
(f32.add
(f32.convert_i32_u
(i32.load8_u offset=134 (local.get $kind_offset)))
(f32.mul
(call $random)
(f32.convert_i32_u
(i32.load8_u offset=135 (local.get $kind_offset))))))))
;; Write object x coordinate.
(f32.store offset=5 (local.get $obj_addr) (local.get $obj_x))
;; loop over all objects backward.
(br_if $loop
(i32.gt_s
(local.tee $obj_addr (i32.sub (local.get $obj_addr) (i32.const 9)))
(i32.const 0))))
;; draw score
(local.set $score (global.get $score))
(local.set $score_x (i32.const 300))
(loop $loop
(drop
(call $blit
;; x
(local.tee $score_x (i32.sub (local.get $score_x) (i32.const 4)))
;; y
(i32.const 4)
;; whcol_offset
(i32.const 33)
;; src_offset
(i32.add
(i32.const 6092)
(i32.mul
(i32.rem_u (local.get $score) (i32.const 10))
(i32.const 15)))))
(br_if $loop
(local.tee $score (i32.div_u (local.get $score) (i32.const 10)))))
;; draw game over
(if (i32.eq (global.get $game_state) (i32.const 4))
(then
;; GAME OVER
(drop
(call $blit
;; x y
(i32.const 125) (i32.const 33)
;; whcol_offset
(i32.const 36)
;; src_offset
(i32.const 6242)))))
)