Got collision with ball working. HUD text for PAWSing and WAITing to prompt user as needed.
3 files changed, 54 insertions(+), 17 deletions(-)

M build/index.html
M qc.rkt
M spec.org
M build/index.html +7 -6
@@ 27,19 27,20 @@ var Sounds={ponk:(make_sound("a/Interfac
 function play(what){return (((s) => {((s)["currentTime"]=0);return ((s.play)());})(Sounds[what]));};
 function all_stop(){return ((() => {var a=((Object.values)(Sounds)),n=a.length,i=0;while((true&&true&&(i<n))){(((s) => {((s.pause)());((s)["currentTime"]=0);return (i+=1);})(a[i]));};undefined;return undefined;})());};
 function arrange(rack,run,origin){"( rack run origin -- (p0 p1 p2 ...)) Compute tile coordinates for rack locations.";return (((x) => {return (((y) => {return ((() => {return (((a) => {{var i=0,t=rack.length;while((true&&true&&(i<t))){(((i_1) => {(((zero_p((i_1%run)))===false)?(x=(x+32)):((x=(first(origin))),(y=(y+48))));((a.push)([x,y]));return (i+=1);})(i));};undefined;}return a;})([]));})());})((second(origin))));})((first(origin))));};
-function make_level(name,layout,run,base,turned,start,method){"( name layout run  base turned  start /method/ -- level) Create level.";return (((offset) => {return (((tiles) => {return ((() => {return {name:name,layout:layout,tiles:tiles,tmobs:(adjust(tiles,[-32,-96])),run:run,base:base,turn:turned,start:start,tops:(((a) => {{var a_2=layout,n=a_2.length,i=0;while((true&&true&&(i<n))){(((x) => {((a.push)((x-1)));return (i+=1);})(a_2[i]));};undefined;}return a;})([])),done:(turned.length-1),method:(method||"advance"),state:"play",tick:0};})());})((arrange(layout,run,offset))));})([((APP.screen.width-(32*run))/2),(APP.screen.height-16-(48*(layout.length/run)))]));};
+function make_level(name,layout,run,base,turned,start,method){"( name layout run  base turned  start /method/ -- level) Create level.";return (((offset) => {return (((tiles) => {return ((() => {return {name:name,layout:layout,tiles:tiles,tmobs:(adjust(tiles,[-32,-96])),run:run,base:base,turn:turned,start:start,tops:(((a) => {{var a_2=layout,n=a_2.length,i=0;while((true&&true&&(i<n))){(((x) => {((a.push)((x-1)));return (i+=1);})(a_2[i]));};undefined;}return a;})([])),done:(turned.length-1),method:(method||"advance"),state:"PLAY",tick:0};})());})((arrange(layout,run,offset))));})([((APP.screen.width-(32*run))/2),(APP.screen.height-16-(48*(layout.length/run)))]));};
 var Layouts={full:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,1,0,1,0,1,0,0,0,0,0,0,0,1,0,1,0,1,0,1,0,1,0,0,0,0,0,1,0,1,0,1,0,1,0,1,0,1,0,0,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],demo:[0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,1,0,0,0,1,0,1,0,1,0,0,0,0,0,0,0,0]};
 var Levels=[(make_level("Gentle Demo",Layouts.demo,7,["#ffc2c7","#b6e5d8"],["#8fdde7","#ffff00"],10)),(make_level("Sweet Breeze",Layouts.full,15,["#ffc2c7","#b6e5d8"],["#8fdde7","#ffff00"],22)),(make_level("Light Rain",Layouts.full,15,["#eeb5eb","#c26dbc"],["#3cacae","#c8f4f9"],22)),(make_level("Thunder!",Layouts.full,15,["#000c66","#050a30"],["#0000ff","#ff3333","#7ec8e3"],22))];
 var Game={cx:false,timestamp_old:false,nlevel:0,level:false,clock:0,log:{fps:0}};
-function level_set(n,state){"( n /state/ --) Set (or reset) level.";(all_stop());((yarnball)["state"]="HIDE");((Game)["nlevel"]=n);((Game)["level"]=Levels[n]);((Game)["level"]["state"]=(state||Game.level.state));((Game)["clock"]=0);return (mob_dropping(player,Game.level.start));};
+function level_set(n,state){"( n /state/ --) Set (or reset) level.";(all_stop());((yarnball)["state"]="HIDE");((Game)["nlevel"]=n);((Game)["level"]=Levels[n]);((Game)["level"]["state"]=(state||Game.level.state));((Game)["clock"]=0);((Sounds)["king"]["loop"]=true);(play("king"));return (mob_dropping(player,Game.level.start));};
 var player={i:22,t:-1,x:0,face:0,frame:0,tick:0,state:"IDLE",score:0,falls:0,anim:{IDLE:[[0,7],[1,8],0],IDL2:[[0,15],[0,15],0],SLEP:[[9,15],[10,15],0],JUMP:[[2,3],[3,3],[4,99]],PLAY:[],PTLI:[],PTLO:[],FALL:[[5,15],[6,15],[5,5],[6,5],2],DROP:[[6,5],[7,5],[8,99]]}};
 var yarnball={i:22,target:-1,x:0,frame:0,tick:0,state:"HIDE",anim:{IDLE:[[0,99]],JUMP:[[2,10],[1,10],[2,99]],DROP:[[0,10],[2,2],[0,99]]}};
 function draw_tile(cx,offset,top,left,right){"( cx  offset  top left right --) Draw block to jump on.";(cx["fillStyle"]=left);(draw_path(cx,(adjust([[0,16],[32,32],[32,64],[0,64]],offset))));((cx.fill)());(cx["fillStyle"]=right);(draw_path(cx,(adjust([[32,64],[32,32],[64,16],[64,64]],offset))));((cx.fill)());(cx["fillStyle"]=top);(draw_path(cx,(adjust([[0,16],[32,0],[64,16],[32,32]],offset))));return ((cx.fill)());};
 function draw_tile_mini(cx,offset,top,left,right){"( cx  offset  top left right --) Draw mini-block for HUD.";{var a=[[left,[[0,4],[8,8],[8,16],[0,12]]],[right,[[8,16],[8,8],[16,4],[16,12]]],[top,[[0,4],[8,0],[16,4],[8,8]]]],n=a.length,i=0;while((true&&true&&(i<n))){(((x) => {(cx["fillStyle"]=(first(x)));(draw_path(cx,(adjust((second(x)),offset))));((cx.fill)());return (i+=1);})(a[i]));};undefined;}(cx["strokeStyle"]="black");(draw_path(cx,(adjust([[0,4],[8,0],[16,4],[16,12],[8,16],[0,12]],offset))));return ((cx.stroke)());};
-function draw_hud(cx,game,player_3,colour){"( cx game player /colour/ --) Draw information overlay.";(colour=(colour||"black"));(cx["font"]="20px HUD");(cx["fillStyle"]=colour);((cx["fillText"])(("Score: "+player_3.score),32,40));((cx["fillText"])(("Falls: "+player_3.falls),32,60));((cx["fillText"])(game.level.name,32,128));{var i=0,t=game.level.turn.length;while((true&&true&&(i<t))){(((i_4) => {(draw_tile_mini(cx,[(32+(32*i_4)),(128+20)],game.level.turn[i_4],(first(game.level.base)),(second(game.level.base))));return (i+=1);})(i));};undefined;}return (((x,y) => {(cx["font"]="10px HUD");(cx["fillStyle"]=colour);((cx["fillText"])(("fps: "+game.log.fps),x,y));return ((cx["fillText"])(("v: "+VERSION),x,(y+10)));})((APP.screen.width-64),(APP.screen.height-26)));};
+function draw_hud(cx,game,player_3,colour){"( cx game player /colour/ --) Draw information overlay.";(colour=(colour||"black"));(cx["font"]="20px HUD");(cx["fillStyle"]=colour);((cx["fillText"])(("Score: "+player_3.score),32,40));((cx["fillText"])(("Falls: "+player_3.falls),32,60));((cx["fillText"])(game.level.name,32,128));{var i=0,t=game.level.turn.length;while((true&&true&&(i<t))){(((i_4) => {(draw_tile_mini(cx,[(32+(32*i_4)),(128+20)],game.level.turn[i_4],(first(game.level.base)),(second(game.level.base))));return (i+=1);})(i));};undefined;}((("PAWS"===game.level.state)===false)?undefined:((() => {return (((p) => {(cx["font"]="64px HUD");(cx["fillStyle"]="red");((cx["fillText"])("!PAWSED!",((first(p))-64),((second(p))+64)));(cx["font"]="20px HUD");return ((cx["fillText"])("Cat is taking a little break...",((first(p))-64),((second(p))+84)));})((whereis(game.level.tmobs,player_3))));})()));(((("WINS"===game.level.state)||("WAIT"===game.level.state))===false)?undefined:((() => {(cx["font"]="24px HUD");(cx["fillStyle"]=colour);return ((cx["fillText"])("Press [space] or [enter] to continue...",64,(APP.screen.height-100)));})()));return (((x,y) => {(cx["font"]="10px HUD");(cx["fillStyle"]=colour);((cx["fillText"])(("fps: "+game.log.fps),x,y));return ((cx["fillText"])(("v: "+VERSION),x,(y+10)));})((APP.screen.width-64),(APP.screen.height-26)));};
 function whereis(rack,mob){"( rack mob -- (x y)) Convert mob location on rack to screen coords.";return ((((undefined===mob.target)||(-1===mob.target))===false)?(((p) => {return (((t) => {return (((dx) => {return (((dy) => {return ((() => {return [((first(p))+(mob.x*dx)),((second(p))+(mob.x*dy))];})());})(((second(t))-(second(p)))));})(((first(t))-(first(p)))));})(rack[mob.target]));})(rack[mob.i])):rack[mob.i]);};
 function draw(screen,cx){"( screen cx --) Draw current scene.";((cx["clearRect"])(0,0,screen.width,screen.height));(cx["strokeStyle"]="red");((cx["strokeRect"])(0,0,screen.width,screen.height));(((base,turn) => {return ((() => {var i=0,t=Game.level.layout.length;while((true&&true&&(i<t))){(((i_5) => {(((1===Game.level.layout[i_5])===false)?undefined:((() => {return (draw_tile(cx,Game.level.tiles[i_5],turn[Game.level.tops[i_5]],(first(base)),(second(base))));})()));return (i+=1);})(i));};undefined;return undefined;})());})(Game.level.base,Game.level.turn));(((p) => {return (((f) => {return ((() => {((("FALL"===player.state)===false)?undefined:((() => {return (p=[(first(p)),((second(p))+(first(player.x)))]);})()));((("DROP"===player.state)===false)?undefined:((() => {return (p=[(first(p)),((second(p))-(first(player.x)))]);})()));return ((cx["drawImage"])(Sprites.cat,(128*(first(f))),(128*player.face),128,128,(first(p)),(second(p)),128,128));})());})((frame_get(player))));})((whereis(Game.level.tmobs,player))));(((!("HIDE"===yarnball.state))===false)?undefined:((() => {return (((p) => {return ((() => {((("DROP"===yarnball.state)===false)?((("JUMP"===yarnball.state)===false)?undefined:((() => {return (p=[(first(p)),((second(p))-(64*((Math.sin)((yarnball.x*3.14159)))))]);})())):((() => {return (p=[(first(p)),((second(p))-(first(yarnball.x)))]);})()));return ((cx["drawImage"])(Sprites.mob,0,0,128,128,(first(p)),(second(p)),128,128));})());})((whereis(Game.level.tmobs,yarnball))));})()));return (draw_hud(cx,Game,player));};
-function level_winning(level){"( level --) Level has been completed.";((level)["state"]="win");(all_stop());return (play("win"));};
+function level_pawsing(level){"( level --) Pause gameplay.";((level)["state"]="PAWS");return ((level)["tick"]=0);};
+function level_winning(level){"( level --) Level has been completed.";((level)["state"]="WINS");((yarnball)["state"]="HIDE");(all_stop());return (play("win"));};
 function level_done_p(level){"( level -- f) Is level completed (all cubes turned correct colour)?";return (0===((level.tops.filter)(((x) => {return (!((x<0)||(level.done===x)));})))["length"]);};
 function mob_dropping(m,target){"( m target -- m) Set up to drop from height.";((m)["i"]=target);((m)["target"]=-1);((m)["x"]=[150,10]);return (state_set(m,"DROP"));};
 function player_landing(p,target){"( p target --) Land player p on target.";((p)["i"]=target);((p)["target"]=-1);(state_set(p,"IDLE"));(((tops) => {return (tops[p.i]=1);})(Game.level.tops));(play("ponk"));return (((level_done_p(Game.level))===false)?undefined:((() => {return (level_winning(Game.level));})()));};

          
@@ 48,12 49,12 @@ function yarnball_landing(m,target){((m)
 function frame_get(m,n){"( m /n/ -- (s d)|f|state) Get next frame from current animation.";return m.anim[m.state][(n||m.frame)];};
 function frame_set(m,f){"( m f -- m) Set m.frame to f. Reset ticks.";((m)["frame"]=f);((m)["tick"]=0);return m;};
 function state_set(m,s){"( m s -- m) Set new state for mob.";((m)["state"]=s);return (frame_set(m,0));};
-function animate(){"( --) Handle animation loops.";((player)["tick"]=(player.tick+1));(((frame) => {return ((() => {return ((((second(frame))<player.tick)===false)?undefined:((() => {return (((fn) => {return (((next) => {return ((() => {return (((array_p(next))===false)?(((string_p(next))===false)?((() => {return (frame_set(player,next));})()):((() => {((player)["state"]=frame);return (frame_set(player,0));})())):((() => {return (frame_set(player,fn));})()));})());})((frame_get(player,fn))));})((player.frame+1)));})()));})());})((frame_get(player))));((("JUMP"===player.state)===false)?((("FALL"===player.state)===false)?((("DROP"===player.state)===false)?undefined:((() => {((player)["x"]=[((first(player.x))-(second(player.x))),(second(player.x))]);return (((zero_p((first(player.x))))===false)?undefined:((() => {return (player_landing(player,Game.level.start));})()));})())):((() => {((player)["x"]=[((first(player.x))+(second(player.x))),(2+(second(player.x)))]);return (((320<(first(player.x)))===false)?undefined:((() => {return (mob_dropping(player,Game.level.start));})()));})())):((() => {((player)["x"]=(player.x+0.1));return (((1.0<=player.x)===false)?undefined:((() => {return (((zero_p(Game.level.layout[player.target]))===false)?(player_landing(player,player.target)):(player_falling(player,player.target)));})()));})()));((yarnball)["tick"]=(yarnball.tick+1));return (((("HIDE"===yarnball.state)&&(!(player.i===Game.level.start))&&(0.97<((Math.random)())))===false)?((("DROP"===yarnball.state)===false)?((("JUMP"===yarnball.state)===false)?undefined:((() => {((yarnball)["x"]=(yarnball.x+0.05));return (((1.0<=yarnball.x)===false)?undefined:((() => {return (((zero_p(Game.level.layout[yarnball.target]))===false)?(yarnball_landing(yarnball,yarnball.target)):(state_set(yarnball,"HIDE")));})()));})())):((() => {((yarnball)["x"]=[((first(yarnball.x))-(second(yarnball.x))),(second(yarnball.x))]);return (((zero_p((first(yarnball.x))))===false)?undefined:((() => {return (yarnball_landing(yarnball,Game.level.start));})()));})())):((() => {return (mob_dropping(yarnball,Game.level.start));})()));};
+function animate(){"( --) Handle animation loops.";((player)["tick"]=(player.tick+1));(((frame) => {return ((() => {return ((((second(frame))<player.tick)===false)?undefined:((() => {return (((fn) => {return (((next) => {return ((() => {return (((array_p(next))===false)?(((string_p(next))===false)?((() => {return (frame_set(player,next));})()):((() => {((player)["state"]=frame);return (frame_set(player,0));})())):((() => {return (frame_set(player,fn));})()));})());})((frame_get(player,fn))));})((player.frame+1)));})()));})());})((frame_get(player))));((("JUMP"===player.state)===false)?((("FALL"===player.state)===false)?((("DROP"===player.state)===false)?undefined:((() => {((player)["x"]=[((first(player.x))-(second(player.x))),(second(player.x))]);return (((zero_p((first(player.x))))===false)?undefined:((() => {return (player_landing(player,Game.level.start));})()));})())):((() => {((player)["x"]=[((first(player.x))+(second(player.x))),(2+(second(player.x)))]);return (((320<(first(player.x)))===false)?undefined:((() => {return (mob_dropping(player,Game.level.start));})()));})())):((() => {((player)["x"]=(player.x+0.1));return (((1.0<=player.x)===false)?undefined:((() => {return (((zero_p(Game.level.layout[player.target]))===false)?(player_landing(player,player.target)):(player_falling(player,player.target)));})()));})()));((yarnball)["tick"]=(yarnball.tick+1));(((("HIDE"===yarnball.state)&&(!("PAWS"===Game.level.state))&&(99<Game.level.layout.length)&&(!(player.i===Game.level.start))&&(0.97<((Math.random)())))===false)?((("DROP"===yarnball.state)===false)?((("JUMP"===yarnball.state)===false)?undefined:((() => {((yarnball)["x"]=(yarnball.x+0.05));return (((1.0<=yarnball.x)===false)?undefined:((() => {return (((zero_p(Game.level.layout[yarnball.target]))===false)?(yarnball_landing(yarnball,yarnball.target)):(state_set(yarnball,"HIDE")));})()));})())):((() => {((yarnball)["x"]=[((first(yarnball.x))-(second(yarnball.x))),(second(yarnball.x))]);return (((zero_p((first(yarnball.x))))===false)?undefined:((() => {return (yarnball_landing(yarnball,Game.level.start));})()));})())):((() => {return (mob_dropping(yarnball,Game.level.start));})()));(((((-1===player.target)&&(yarnball.target===player.i))||((-1<player.target)&&(yarnball.target===player.target)))===false)?undefined:((() => {return (level_pawsing(Game.level));})()));return (((("PAWS"===Game.level.state)&&(150<Game.level.tick))===false)?undefined:((() => {return ((Game)["level"]["state"]="PLAY");})()));};
 var d2face={sw:0,se:1,ne:3,nw:2};
 function dir2loc(start,d){"( start d -> location) Figure out new location from start and direction.";return (((run) => {return ((("sw"===d)===false)?((("se"===d)===false)?((("ne"===d)===false)?((("nw"===d)===false)?undefined:((() => {return (start-run-(-1));})())):((() => {return (start-run-1);})())):((() => {return (start+run+1);})())):((() => {return (start+run+-1);})()));})(Game.level.run));};
 function jumping(mob,d){"( mob d --) Set mob up for jump.";((mob)["state"]="JUMP");((mob)["x"]=0.0);((mob)["target"]=(dir2loc(mob.i,d)));((mob)["face"]=d2face[d]);return (frame_set(mob,0));};
 function game_loop(timestamp){(((cx) => {return (((fps_limit) => {return (((delta) => {return (((seconds_passed) => {return (((fps) => {return ((() => {return (((delta>=fps_limit)===false)?undefined:((() => {((Game)["log"]["fps"]=fps);((Game)["timestamp_old"]=timestamp);((Game)["level"]["tick"]=(Game.level.tick+1));(draw(APP.screen,cx));return (animate());})()));})());})(((Math.round)((1.0/seconds_passed)))));})((delta/1000)));})((timestamp-Game["timestamp_old"])));})((1000/APP.fps)));})(Game.cx));return ((window["requestAnimationFrame"])(game_loop));};
-((window["addEventListener"])("keyup",((e) => {return (((idle) => {return ((("play"===Game.level.state)===false)?(((("win"===Game.level.state)||("wait"===Game.level.state))===false)?((("lose"===Game.level.state)===false)?undefined:((() => {return (((("Enter"===e.key)||(" "===e.key))===false)?undefined:((() => {return (level_set(Game.nlevel));})()));})())):((() => {return (((("Enter"===e.key)||(" "===e.key))===false)?undefined:((() => {return (level_set((Game.nlevel+1)));})()));})())):((() => {return (((idle&&("z"===e.key))===false)?(((idle&&("x"===e.key))===false)?(((idle&&("a"===e.key))===false)?(((idle&&("s"===e.key))===false)?undefined:((() => {return (jumping(player,"nw"));})())):((() => {return (jumping(player,"ne"));})())):((() => {return (jumping(player,"se"));})())):((() => {return (jumping(player,"sw"));})()));})()));})((("IDLE"===player.state)||("IDL2"===player.state)||("SLEP"===player.state))));})));
+((window["addEventListener"])("keyup",((e) => {return (((idle) => {return ((("PLAY"===Game.level.state)===false)?(((("WINS"===Game.level.state)||("WAIT"===Game.level.state))===false)?((("LOSE"===Game.level.state)===false)?undefined:((() => {return (((("Enter"===e.key)||(" "===e.key))===false)?undefined:((() => {return (level_set(Game.nlevel));})()));})())):((() => {return (((("Enter"===e.key)||(" "===e.key))===false)?undefined:((() => {return (level_set((Game.nlevel+1)));})()));})())):((() => {return (((idle&&("z"===e.key))===false)?(((idle&&("x"===e.key))===false)?(((idle&&("a"===e.key))===false)?(((idle&&("s"===e.key))===false)?undefined:((() => {return (jumping(player,"nw"));})())):((() => {return (jumping(player,"ne"));})())):((() => {return (jumping(player,"se"));})())):((() => {return (jumping(player,"sw"));})()));})()));})((("IDLE"===player.state)||("IDL2"===player.state)||("SLEP"===player.state))));})));
 function init(){(level_set(0));(((canvas) => {return ((() => {return ((Game)["cx"]=((canvas["getContext"])("2d")));})());})(((document["getElementById"])("the-canvas"))));return ((window["requestAnimationFrame"])(game_loop));};
 (init());
 </script>

          
M qc.rkt +43 -7
@@ 146,7 146,7 @@ 
                [done  (- turned.length 1)] ; All tops must be this value to clear.
                [method (or method 'advance)] ; How to handle landing on qube: advance or toggle
 
-               [state 'play] ; play, win, lose, wait?
+               [state 'PLAY] ; play, win, lose, paws, wait?
                [tick 0]
                )))
 

          
@@ 190,6 190,7 @@ 
                        [fps 0])]
                  ))
 
+
    (define (level-set n state)
      "( n /state/ --) Set (or reset) level."
 

          
@@ 199,6 200,9 @@ 
      (:= Game.level (ref Levels n))
      (:= Game.level.state (or state Game.level.state))
      (:= Game.clock 0)
+
+     (:= Sounds.king.loop #t) (play 'king)
+
      (mob-dropping player Game.level.start))
      
 

          
@@ 287,6 291,21 @@ 
                        (ref game.level.turn i)
                        (first game.level.base) (second game.level.base)))
 
+     (when (= 'PAWS game.level.state)
+       (let ([p (whereis game.level.tmobs player)])
+         (:= cx 'font "64px HUD") (:= cx 'fillStyle "red")
+         (cx.fillText "!PAWSED!" (- (first p) 64) (+ (second p) 64))
+         (:= cx 'font "20px HUD")
+         (cx.fillText "Cat is taking a little break..."
+                      (- (first p) 64) (+ (second p) 84))
+         ))
+
+     (when (or (= 'WINS game.level.state)
+               (= 'WAIT game.level.state))
+       (:= cx 'font "24px HUD") (:= cx 'fillStyle colour)
+       (cx.fillText "Press [space] or [enter] to continue..."
+                    64 (- APP.screen.height 100)))
+
      (let ([x (- APP.screen.width 64)] [y (- APP.screen.height 26)])
        (:= cx 'font "10px HUD") (:= cx 'fillStyle colour)
        (cx.fillText (+ "fps: " game.log.fps) x y)

          
@@ 365,13 384,19 @@ 
 
    ; Animation
 
+   (define (level-pawsing level)
+     "( level --) Pause gameplay."
+
+     (:= level.state 'PAWS)
+     (:= level.tick 0))
+   
    (define (level-winning level)
      "( level --) Level has been completed."
 
-     (:= level.state 'win)
+     (:= level.state 'WINS)
+     (:= yarnball.state 'HIDE)
      (all-stop) ; Kill all audio.
-     (play 'win)
-     )
+     (play 'win))
 
    (define (level-done? level)
      "( level -- f) Is level completed (all cubes turned correct colour)?"

          
@@ 477,6 502,8 @@ 
      (:= yarnball.tick (+ yarnball.tick 1))
 
      (cond [(and (= 'HIDE yarnball.state)
+                 (not (= 'PAWS Game.level.state))
+                 (< 99 Game.level.layout.length)
                  (not (= player.i Game.level.start))
                  (< 0.97 (Math.random)))
             (mob-dropping yarnball Game.level.start)]

          
@@ 495,6 522,15 @@ 
            )
 
      ; ...more animations to follow...
+
+     ; Collisions?
+     (when (or (and (= -1 player.target) (= yarnball.target player.i))
+               (and (< -1 player.target) (= yarnball.target player.target)))
+       (level-pawsing Game.level))
+
+     ; Level Housekeeping
+     (when (and (= 'PAWS Game.level.state) (< 150 Game.level.tick))
+       (:= Game.level.state 'PLAY))
      )
 
    (define d2face (object (sw 0) (se 1) (ne 3) (nw 2)))

          
@@ 551,16 587,16 @@ 
                               (= 'SLEP player.state))]
                     )
 
-                (cond [(= 'play Game.level.state)
+                (cond [(= 'PLAY Game.level.state)
                        (cond [(and idle (= "z" e.key)) (jumping player 'sw)]
                              [(and idle (= "x" e.key)) (jumping player 'se)]
                              [(and idle (= "a" e.key)) (jumping player 'ne)]
                              [(and idle (= "s" e.key)) (jumping player 'nw)]
                              )]
-                      [(or (= 'win Game.level.state) (= 'wait Game.level.state))
+                      [(or (= 'WINS Game.level.state) (= 'WAIT Game.level.state))
                        (when (or (= "Enter" e.key) (= " " e.key))
                          (level-set (+ Game.nlevel 1)))]
-                      [(= 'lose Game.level.state)
+                      [(= 'LOSE Game.level.state)
                        (when (or (= "Enter" e.key) (= " " e.key))
                          (level-set Game.nlevel))]
                       )

          
M spec.org +4 -4
@@ 113,7 113,7 @@ 
 
   itch.io full-screen play not supported at this time.
 
-* Tasks [57%]
+* Tasks [60%]
   - [X] Get animation working.
   - [X] Get transfer between blocks working.
   - [X] Add guard to layout.

          
@@ 139,11 139,11 @@ 
   - [X] Figure out when done.
   - [X] Level colour schemes.
   - [X] Levels
-  - [ ] Cat chases yarnball off pyramid on collision.
   - [X] Get yarnball drop working.
+  - [X] Make sure music restarts on level change.
+  - [ ] Countdown clock.
   - [ ] Randomly generated levels.
-  - [ ] Make sure music restarts on level change.
-  - [ ] Countdown clock.
+  - [ ] Cat chases yarnball off pyramid on collision.
   - [ ] When cat returns from fall, choose sw or se orientation.
   - [ ] Level methods
   - [ ] Reward on end of level?