;; 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))))) )