########################################### # # Slippery Slope # # built for Octojam 2018. # level editor: http://beyondloom.com/tools/slipedit.html # # John Earnest # ########################################### :const MODE-INPUT 0 :const MODE-MOVE 1 :const MODE-NEXT 2 :const MODE-RESET 3 :const FINAL_LEVEL 0xA # v0-v2 are reserved as temporaries. :alias px vD # player x position (tiles) :alias py vC # player y position (tiles) :alias pdir vB # player direction ( E N W S ) :alias pmode vA # see MODE-XXX :alias gx v9 # goal x position :alias gy v8 # goal y position :alias gf v7 # goal frame (0,5,10,15) :alias level v6 # current level no. :alias move-dx v5 # x amount player moved this step :alias move-dy v4 # y amount player moved this step :alias move-dir v3 # temporary direction :alias load-ti vD # tile index :alias load-tx vC # x position :alias load-ty vB # y position :alias copy-off vE # when unpacking, offset :alias copy-lvl vD # when unpacking, level index ########################################### # # Utilities # ########################################### :macro temp-begin { :calc there { HERE } :org 0 } :macro temp-end { :org there } :macro xor_ { :byte { ( @ a + HERE - to ) ^ @ b + HERE - to } } :macro xor A B { :calc to { HERE } :calc a { A } :calc b { B } xor_ xor_ xor_ xor_ xor_ } :macro sync N { loop vf := delay if vf != 0 then again vf := N delay := vf } :macro copy-2 SRC DST { i := SRC load v1 i := DST save v1 } :macro times-5 SRC DST { DST := SRC DST <<= DST DST <<= DST DST += SRC } :macro times-6 SRC DST { DST := SRC DST += SRC DST += SRC DST += DST } :macro to-tile SX SY DX DY { times-5 SX DX DX += 2 times-5 SY DY DY += 1 } ########################################### # # Step Counter # ########################################### : step-total 0 0 # high 2 digits, low 2 digits : step-level 0 0 : step-bcd 0 0 0 :macro steps-clear { v0 := 0 v1 := 0 i := step-total save v1 i := step-level save v1 } :macro steps-inc { i := step-level load v1 v1 += 1 if v1 == 100 begin v0 += 1 v1 := 0 end if v0 == 100 begin # cap at 9999 v1 := 99 v0 := 99 end i := step-level save v1 } :macro steps-reset-level { copy-2 step-total step-level } :macro steps-next-level { copy-2 step-level step-total } :macro steps-show-digits SRC { i := SRC load v0 i := step-bcd bcd v0 load v2 i := hex v1 sprite v3 v4 5 v3 += 5 i := hex v2 sprite v3 v4 5 v3 += 5 } :macro steps-show { # at v3,v4 steps-show-digits step-level :calc low-digits { 1 + step-level } steps-show-digits low-digits } ########################################### # # Level Representation # ########################################### :const board-rows 6 :const board-cols 12 :calc board-size { board-rows * board-cols } : board 00 00 00 00 00 00 00 00 00 05 00 00 00 00 05 00 05 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 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : goal-position 10 03 # (x tiles, y tiles) : start-position 02 03 # (x tiles, y tiles) : level-unpack-stash 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 :macro level-base-addr { v0 := copy-lvl i := level-data loop while v0 != 1 v0 += -1 vf := 76 i += vf again } : level-unpack # we need most of the registers for bulk copying, # so stash the entire register file in a buffer: vf := level i := level-unpack-stash save vf # copy tile data copy-off := 0 # source/dest offset copy-lvl := vf # level index loop level-base-addr i += copy-off load vb i := board i += copy-off save vb copy-off += 12 if copy-off != 72 then again # copy goal/start positions level-base-addr i += copy-off load v3 i := goal-position save v3 i := level-unpack-stash load vf ; ########################################### # # Text # ########################################### : let-A 0xF0 0xB0 0xF0 0xB0 : let-D 0xE0 0xB0 0xB0 0xE0 : let-E 0xF0 0xE0 0xC0 0xF0 : let-G 0xF0 0xC0 0xD0 0xF0 : let-H 0xB0 0xF0 0xB0 0xB0 : let-I 0xC0 0x00 0xC0 0xC0 : let-L 0xC0 0xC0 0xC0 0xE0 : let-M 0xD8 0xE8 0xC8 0xC8 : let-N 0xE0 0xF0 0xD0 0xD0 : let-O 0xF0 0xB0 0xB0 0xF0 : let-P 0xF0 0xB0 0xF0 0x80 : let-R 0xF0 0xF0 0xA0 0x90 : let-S 0xF0 0xE0 0x30 0xF0 : let-T 0xF0 0x60 0x60 0x60 : let-V 0xB0 0xB0 0xB0 0x60 : let-Y 0xB0 0xF0 0x30 0xF0 :stringmode print " " { v0 += 4 } :stringmode print "ADEGHILMNOPRSTVY" { :calc S { let-A + VALUE * 4 } i := S sprite v0 v1 4 # compute the width of each glyph statically: :calc p { ( @ S ) | ( @ 1 + S ) | ( @ 2 + S ) | ( @ 3 + S ) } :calc w { 9 - ( log p & - p ) / log 2 } v0 += w } :macro draw-title { v0 := 14 v1 := 4 print "SLIPPERY" v0 := 20 v1 := 24 print "SLOPE" } :macro draw-gameover { v0 := 15 v1 := 12 print "THE END" v3 := 8 v4 := 20 steps-show v0 := v3 v1 := v4 print " STEPS" } :macro draw-levelno { v0 := 17 v1 := 12 print "LEVEL " i := hex level sprite v0 v1 5 } ########################################### # # Player And Movement # ########################################### : stand 0x20 0x70 0x70 0x30 0x20 0x20 0x70 0x70 0x70 0x50 0x20 0x70 0x70 0x60 0x20 0x20 0x70 0xF8 0x70 0x50 : slide 0x20 0x38 0x30 0x38 0x48 0x20 0xF0 0x78 0xB0 0x90 0x20 0xE0 0x60 0xE0 0x90 0x20 0xF8 0x70 0x70 0x48 :macro draw-player { times-5 pdir v0 i += v0 to-tile px py v0 v1 sprite v0 v1 5 } :macro draw-stand { i := stand draw-player } :macro draw-slide { i := slide draw-player } : deltas 1 0 0 -1 -1 0 0 1 : dirs-\ 3 2 1 0 : dirs-/ 1 0 3 2 :macro at-edge REG DELTA VAL { if REG == VAL begin REG -= DELTA pmode := MODE-INPUT end } :macro get-current-tile { times-6 px v0 v0 += py i := board i += v0 load v0 v1 := v0 } :macro set-current-tile { times-6 px v0 v0 += py i := board i += v0 v0 := v1 save v0 } :macro draw-current-tile { i := tiles i += v1 times-5 px v0 times-5 py v1 v0 += 2 v1 += 1 sprite v0 v1 5 } :macro change-dir TABLE { i := TABLE i += pdir load v0 pdir := v0 } :macro move-player { loop # move in facing direction i := deltas i += pdir i += pdir load v1 move-dx := v0 move-dy := v1 px += move-dx py += move-dy # out of bounds? at-edge px move-dx -1 at-edge px move-dx 12 at-edge py move-dy -1 at-edge py move-dy 6 # at goal? to-tile px py v0 v1 v0 ^= gx v1 ^= gy v0 |= v1 if v0 == 0 then pmode := MODE-NEXT # check current cell (into v1) get-current-tile if v1 == tile-wall begin px -= move-dx py -= move-dy pmode := MODE-INPUT end if v1 == tile-hole begin pmode := MODE-RESET end if v1 == tile-mir-\ begin change-dir dirs-\ end if v1 == tile-mir-/ begin change-dir dirs-/ end if v1 == tile-flip-\ begin change-dir dirs-\ draw-current-tile v1 := tile-flop-/ set-current-tile draw-current-tile v1 := 0 # avoid fallthrough! end if v1 == tile-flop-/ begin change-dir dirs-/ draw-current-tile v1 := tile-flip-\ set-current-tile draw-current-tile v1 := 0 # avoid fallthrough! end if v1 >= tile-ground begin draw-current-tile pmode := MODE-INPUT end while pmode == MODE-MOVE draw-slide animate-goal sync 5 draw-slide again } :macro poll-key KEY DIR { vf := KEY if vf key begin pmode := MODE-MOVE move-dir := DIR end } :macro poll-player { poll-key OCTO_KEY_D 0 poll-key OCTO_KEY_W 1 poll-key OCTO_KEY_A 2 poll-key OCTO_KEY_S 3 if pmode == MODE-MOVE begin steps-inc draw-stand get-current-tile if v1 >= tile-ground begin draw-current-tile end pdir := move-dir move-player draw-stand end if level != 0 begin vf := OCTO_KEY_E if vf key then pmode := MODE-RESET end } ########################################### # # Level Display # ########################################### : line 0xFF # top and bottom edge : corner 0x80 # 1px : wall 0xC0 0xC0 0xC0 0xC0 0xC0 # 2x15 pixels 0xC0 0xC0 0xC0 0xC0 0xC0 0xC0 0xC0 0xC0 0xC0 0xC0 :macro draw-border { i := wall v0 := 0 v1 := 62 v2 := 1 sprite v0 v2 15 sprite v1 v2 15 v2 := 16 sprite v0 v2 15 sprite v1 v2 15 i := line v1 := 0 v2 := 31 loop sprite v0 v1 1 sprite v0 v2 1 v0 += 8 if v0 != 64 then again i := corner v0 := 2 v1 := 61 v2 := 1 sprite v0 v2 1 sprite v1 v2 1 v2 := 30 sprite v0 v2 1 sprite v1 v2 1 } temp-begin : tgoal-1 0x50 0x80 0x08 0x80 0x50 : tgoal-2 0x50 0x88 0x00 0x88 0x20 : tgoal-3 0x50 0x08 0x80 0x08 0x50 temp-end : goal-0 0x20 0x88 0x00 0x88 0x50 : goal-1 xor goal-0 tgoal-1 : goal-2 xor tgoal-1 tgoal-2 : goal-3 xor tgoal-2 tgoal-3 : goal-4 xor tgoal-3 goal-0 :macro init-goal { gf := 0 i := goal-0 sprite gx gy 5 } :macro animate-goal { i := goal-1 i += gf sprite gx gy 5 gf += 5 if gf == 20 then gf := 0 } : tiles 0x00 0x00 0x00 0x00 0x00 # empty space 0xF8 0xF8 0xF8 0xF8 0xF8 :const tile-wall 5 0x88 0x50 0x20 0x50 0x88 :const tile-hole 10 0x80 0x40 0x20 0x10 0x08 :const tile-mir-\ 15 0x08 0x10 0x20 0x40 0x80 :const tile-mir-/ 20 0x80 0x60 0x50 0x30 0x08 :const tile-flip-\ 25 0x08 0x30 0x70 0x60 0x80 :const tile-flop-/ 30 0x50 0x88 0x88 0x88 0x70 :const tile-ground 35 0x58 0x80 0x80 0x80 0x78 0x88 0x88 0x88 0x88 0x70 0x80 0x80 0x80 0x80 0x78 0x50 0x08 0x08 0x08 0xF0 0x58 0x00 0x00 0x00 0xF8 0x08 0x08 0x08 0x08 0xF0 0x00 0x00 0x00 0x00 0xF8 0x50 0x88 0x88 0x88 0x88 0x58 0x80 0x80 0x80 0x80 0x88 0x88 0x88 0x88 0x88 0x80 0x80 0x80 0x80 0x80 0x50 0x08 0x08 0x08 0x08 0x58 0x00 0x00 0x00 0x00 0x08 0x08 0x08 0x08 0x08 0x00 0x00 0x00 0x00 0x00 :macro load-tile REG Y { i := tiles i += REG load-ty := Y sprite load-tx load-ty 5 } : load-level clear draw-border load-ti := 0 load-tx := 2 loop i := board i += load-ti load v5 load-tile v0 1 load-tile v1 6 load-tile v2 11 load-tile v3 16 load-tile v4 21 load-tile v5 26 load-tx += 5 load-ti += board-rows if load-ti != board-size then again i := goal-position load v3 to-tile v0 v1 gx gy init-goal px := v2 py := v3 pdir := 3 pmode := MODE-INPUT draw-stand ; :macro try-key KEY { vf := KEY while vf -key } :macro wait-key { v4 := random 3 i := deltas i += v4 i += v4 load v1 v0 += v0 v2 := random 63 v3 := random 31 i := slide i += v4 i += v4 i += v4 i += v4 i += v4 sprite v2 v3 5 loop sprite v2 v3 5 v2 += v0 v3 += v1 sprite v2 v3 5 try-key OCTO_KEY_E try-key OCTO_KEY_A try-key OCTO_KEY_S try-key OCTO_KEY_W try-key OCTO_KEY_D again } ########################################### # # Main # ########################################### : main load-level draw-title loop loop poll-player while pmode == MODE-INPUT animate-goal sync 5 again if pmode == MODE-RESET begin steps-reset-level level-unpack load-level end if pmode == MODE-NEXT begin clear if level == FINAL_LEVEL begin level := 1 draw-gameover steps-clear steps-inc # start screen takes 1 move else level += 1 draw-levelno end steps-next-level level-unpack wait-key load-level end again ########################################### # # Level Data # ########################################### : level-data : level-1 # just walls and sliding 05 05 05 00 00 00 05 00 00 00 00 00 05 00 00 00 00 00 00 00 05 00 00 05 00 00 05 00 00 05 05 05 05 00 00 05 00 00 00 00 00 05 00 00 00 05 05 05 00 00 00 00 00 05 05 00 00 00 00 00 05 00 00 00 05 00 05 05 05 05 05 00 10 03 04 01 : level-2 # more walls and sliding 00 00 00 00 00 05 00 05 00 00 00 05 05 00 00 00 00 05 05 00 00 00 05 05 05 05 00 00 05 05 00 00 00 00 00 05 05 05 00 00 00 05 05 00 00 00 00 05 05 00 00 00 05 05 05 00 00 00 00 05 00 00 00 00 00 05 00 00 00 00 05 05 05 00 01 04 : level-3 # introduce mirrors 05 05 05 05 05 05 05 05 00 00 00 05 05 05 00 00 00 05 05 00 00 00 00 05 05 00 00 20 00 00 05 00 00 00 00 00 05 05 00 00 00 05 05 05 05 00 05 05 05 05 00 00 00 05 05 05 00 00 00 05 05 05 00 00 00 05 05 05 05 05 05 05 09 03 02 03 : level-4 # many mirrors 00 00 00 00 00 00 00 00 00 00 00 20 15 00 00 00 00 00 00 05 00 00 00 00 00 00 00 00 15 00 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 15 00 00 05 00 20 00 15 00 00 00 00 00 00 00 00 00 00 00 00 00 15 00 00 10 03 01 01 : level-5 # mild mirror maze 00 20 00 00 15 00 00 00 00 00 00 00 00 00 00 00 00 20 15 00 00 00 00 00 20 00 00 00 00 00 15 00 15 00 00 20 20 20 15 20 00 15 15 00 00 00 00 20 20 20 00 00 00 15 15 00 00 15 00 20 10 20 15 00 00 15 00 20 15 20 15 20 11 00 00 02 : level-6 # introduce ground 10 10 10 10 10 10 10 00 00 00 00 10 10 00 00 00 00 10 10 00 00 00 00 10 10 00 00 00 00 10 10 00 80 50 00 10 10 00 95 65 00 10 10 00 00 00 00 10 10 00 00 00 00 10 10 00 00 00 00 10 10 00 00 00 00 10 10 10 10 10 10 10 09 03 02 02 : level-7 # good clean fun with ground + mirrors 00 00 00 55 00 00 00 00 00 00 00 05 05 15 00 00 00 00 00 00 00 20 00 75 00 00 00 00 00 00 05 05 05 00 00 00 05 05 05 00 20 00 05 20 00 00 20 00 00 00 00 15 00 00 00 00 00 00 00 00 00 00 00 00 00 00 90 50 00 10 00 20 03 00 08 00 : level-8 # introduce flip-flops 05 05 05 05 05 05 05 05 05 05 05 05 05 05 05 00 05 05 05 05 05 00 05 05 05 00 00 30 00 00 05 05 00 00 05 05 05 05 00 00 05 05 00 00 25 00 00 05 05 05 00 05 05 05 05 05 00 05 05 05 05 05 05 05 05 05 05 05 05 05 05 05 09 02 02 03 : level-9 # mirror maze with a few flip-flops 15 20 00 00 20 15 00 00 15 00 35 00 20 00 00 00 00 20 00 15 00 00 00 15 00 30 00 20 00 00 45 00 00 00 00 20 00 20 15 20 00 00 00 00 00 00 25 00 20 00 20 00 00 20 00 00 00 20 35 00 00 00 00 00 15 00 20 15 00 20 00 20 02 02 10 01 : level-A # many flip-flops 00 00 10 05 05 00 00 00 00 10 05 00 05 05 00 00 00 00 50 00 25 00 00 20 70 00 30 00 00 15 70 00 30 30 00 20 70 00 30 30 00 15 70 00 25 00 00 20 70 00 30 25 00 15 65 00 30 00 00 20 00 00 05 00 00 00 00 00 05 00 00 00 00 05 11 01