0,]']±>Sï}WÿÍu§ðp8·d%qy)6o;]ÉA¦ösï÷ôôô”°ÂVl†3w »ppppwppwwwpp° °° pwwpwwww ° pwwwww °»wppwpwpppw ° » ° pp_UUUUUUUUUUUUUUUUUÿÿUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUõUUUUUUUUUUUUUUUUUUU_UUUUUUUUUUUUUUUUUUUUUUUÿÿPÿðPðÿPðÿðÿ_ðÿÿ_ðÿÿÿÿÿÿÿÿÿÿð_ÿÿÿÿð_ÿÿÿððPÿðÿðPÿÿðPðÿÿPðÿÿÿPðÿÿððPðÿÿðPÿðPððPðPPðPÿðPðPðPððPPPPÿPððPððPððPðPPPÿPððPððPððPðPÿPPðPððPÿðPððPððPÿÿPÿÿÿ°Pðÿÿ°ûPðÿÿÿ°¿»Pðÿÿð°ú ðPðÿÿð ¿ðPðÿÿð ðPÿÿð»ðPÿÿÿÿû_ÿÿÿÿÿÿÿÿÿðÿÿð_ððÿÿð_ÿÿÿðÿÿÿðÿÿÿÿðÿÿðÿðÿÿððððÿÿððððÿðððððððððððððððÿððÿððÿðððððõÿPõððPÿðððPððPÿðÿðPððPÿPðððPððPðððPðÿððPðÿððPÿðððPÿððPðÿÿðPÿPÿÿÿPÿPÿÿPððððPðððPðððPððððPððPððPðÿP »» °»ðÿP »° ÿP» û ððP» û» »° ððPð ðPððPððPPðPõðPõðPõÿÿPõUUUUUUUUUUUUUUUUõUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU`f``ffff``f`f`f```f```f`f``ff```fff`f``f`ff`f`ff`f`ff`ff```f`5J-- title: logo programmer (tic-80) -- author: aki44 -- desc: tic-80 port of your pico-8 version -- script: lua -- controls (tic-80 default gamepad mapping) -- 0 up, 1 down, 2 left, 3 right, 4 a, 5 b, 6 x, 7 y -- -- program screen -- up/down : move selection in current pane -- left : go outward (parent block, or menu if at root) -- right : go inward (menu -> current block, or enter selected child block) -- a : add command / edit selected item / confirm -- b : open command menu at current depth / cancel param -- up+b : delete selected command -- -- view screen -- a : run / pause / resume -- b : stop -- left : back to program screen -- right : reset turtle + clear -------------------------- -- constants -------------------------- BTN_UP=0 BTN_DOWN=1 BTN_LEFT=2 BTN_RIGHT=3 BTN_A=4 BTN_B=5 SCREEN_W=240 SCREEN_H=136 -------------------------- -- config -------------------------- cmd_defs={ {id="fd", label="forward", needs_param=true, dflt=20, mn=-96, mx=96, step=5, allow_zero=false}, {id="bk", label="back", needs_param=true, dflt=20, mn=-96, mx=96, step=5, allow_zero=false}, {id="rt", label="right", needs_param=true, dflt=90, mn=-360,mx=360,step=15,allow_zero=false}, {id="lt", label="left", needs_param=true, dflt=90, mn=-360,mx=360,step=15,allow_zero=false}, {id="pu", label="pen up", needs_param=false}, {id="pd", label="pen down",needs_param=false}, {id="home", label="home", needs_param=false}, {id="clear", label="clear", needs_param=false}, {id="wait", label="wait", needs_param=true, dflt=8, mn=1, mx=60, step=1, allow_zero=false}, {id="shake", label="shake", needs_param=true, dflt=2, mn=1, mx=8, step=1, allow_zero=false}, {id="wrap", label="wrap", needs_param=true, dflt=1, mn=-1, mx=1, step=1, allow_zero=false}, {id="sfx", label="sound", needs_param=true, dflt=0, mn=0, mx=63, step=1, allow_zero=true}, {id="repeat", label="repeat", needs_param=true, dflt=4, mn=1, mx=32, step=1, allow_zero=false}, {id="loop", label="loop", needs_param=false}, {id="view", label="view screen", needs_param=false, ui=true} } move_speed=2 turn_speed=10 wait_scale=4 canvas_x0=4 canvas_y0=12 canvas_x1=223 canvas_y1=123 home_x=64 home_y=68 -------------------------- -- runtime -------------------------- booted=false function TIC() if not booted then init() booted=true end update() draw() end -------------------------- -- init -------------------------- function init() init_program() init_view() screen="prog" msg="" msg_t=0 end function init_program() root=new_block("root") nav={{node=root,cursor=1,name="root"}} menu_i=1 prog_pane="menu" -- menu / block / param param_def=nil param_val=0 param_target=nil param_mode=nil -- new / edit_action / edit_repeat end function init_view() reset_turtle() lines={} run_state="idle" exec_stack={} active=nil shake_t=0 shake_s=0 camx=0 camy=0 wrap_mode=true end function reset_turtle() turtle={ x=home_x, y=home_y, a=0, pen=true } end -------------------------- -- compatibility helpers -------------------------- function add(t,v) t[#t+1]=v end function deli(t,i) table.remove(t,i) end function all(t) local i=0 return function() i=i+1 return t[i] end end function flr(x) return math.floor(x) end function rnd(n) if n==nil then return math.random() end return math.random()*n end function min(a,b) if ab then return a else return b end end function mid(a,b,c) return max(a,min(b,c)) end function abs(x) return math.abs(x) end -- pico-8 uses turns, not radians function p8cos(t) return math.cos(t*math.pi*2) end function p8sin(t) return math.sin(t*math.pi*2) end -------------------------- -- node helpers -------------------------- function new_block(kind) return {kind=kind,children={}} end function new_act(op,val) return {kind="act",op=op,val=val} end function new_repeat(n) return {kind="repeat",count=n,children={}} end function new_loop() return {kind="loop",children={}} end function current_frame() return nav[#nav] end function current_block() return current_frame().node end function current_children() return current_block().children end function current_cursor() return current_frame().cursor end function set_cursor(i) current_frame().cursor=i clamp_cursor() end function clamp_cursor() local ch=current_children() local mx=max(1,#ch) if current_frame().cursor<1 then current_frame().cursor=1 end if current_frame().cursor>mx then current_frame().cursor=mx end end function selected_node() local ch=current_children() return ch[current_cursor()] end function has_parent_block() return #nav>1 end function can_have_children(n) return n and (n.kind=="repeat" or n.kind=="loop") end function value_label(op,val) if op=="wrap" then if val>0 then return "on" else return "off" end end return ""..val end function node_label(n) if not n then return "(empty)" end if n.kind=="act" then if n.val~=nil then return op_label(n.op).." "..value_label(n.op,n.val) else return op_label(n.op) end elseif n.kind=="repeat" then return "repeat "..n.count elseif n.kind=="loop" then return "loop" end return "?" end function block_name(n) if n==root then return "root" end return node_label(n) end function op_label(id) for d in all(cmd_defs) do if d.id==id then return d.label end end return id end function find_def(id) for d in all(cmd_defs) do if d.id==id then return d end end end function insert_node(n) local ch=current_children() local idx=1 if #ch>0 then idx=min(current_cursor()+1,#ch+1) end add(ch,n) for i=#ch,idx+1,-1 do ch[i]=ch[i-1] end ch[idx]=n set_cursor(idx) end function delete_selected() local ch=current_children() if #ch<1 then return end deli(ch,current_cursor()) if #ch<1 then set_cursor(1) else set_cursor(min(current_cursor(),#ch)) end end function enter_selected_block() local n=selected_node() if can_have_children(n) then add(nav,{node=n,cursor=1,name=block_name(n)}) set_msg("entered "..block_name(n)) end end function leave_to_parent() if has_parent_block() then local old=nav[#nav].name deli(nav,#nav) set_msg("up from "..old) end end function build_node_from_def(d,v) if d.id=="repeat" then return new_repeat(v) end if d.id=="loop" then return new_loop() end return new_act(d.id,v) end -------------------------- -- param helpers -------------------------- function step_param_value(v,d) local nv=v+d if not param_def.allow_zero then if v>0 and nv==0 then nv=-param_def.step end if v<0 and nv==0 then nv= param_def.step end end nv=mid(param_def.mn,nv,param_def.mx) if (not param_def.allow_zero) and nv==0 then if d<0 then nv=max(param_def.mn,-param_def.step) elseif d>0 then nv=min(param_def.mx,param_def.step) end end return nv end -------------------------- -- update -------------------------- function update() if msg_t>0 then msg_t=msg_t-1 end if screen=="prog" then update_prog() else update_view() end update_camera() end -------------------------- -- program update -------------------------- function update_prog() if prog_pane=="param" then update_param() return end if btnp(BTN_LEFT) then prog_left() end if btnp(BTN_RIGHT) then prog_right() end if prog_pane=="menu" then if btnp(BTN_UP) then menu_i=max(1,menu_i-1) end if btnp(BTN_DOWN) then menu_i=min(#cmd_defs,menu_i+1) end if btnp(BTN_A) then menu_confirm() end if btnp(BTN_B) then prog_pane="block" end elseif prog_pane=="block" then local ch=current_children() if #ch>0 then if btnp(BTN_UP) then set_cursor(current_cursor()-1) end if btnp(BTN_DOWN) then set_cursor(current_cursor()+1) end end -- up+b deletes selected command if btn(BTN_UP) and btnp(BTN_B) then if selected_node() then delete_selected() set_msg("deleted") end return end if btnp(BTN_A) then block_confirm() end if btnp(BTN_B) then prog_pane="menu" end end end function prog_left() if prog_pane=="menu" then if has_parent_block() then leave_to_parent() end elseif prog_pane=="block" then if has_parent_block() then leave_to_parent() prog_pane="block" else prog_pane="menu" end end end function prog_right() if prog_pane=="menu" then prog_pane="block" elseif prog_pane=="block" then local n=selected_node() if can_have_children(n) then enter_selected_block() end end end function menu_confirm() local d=cmd_defs[menu_i] if d.ui and d.id=="view" then screen="view" set_msg("view screen") return end if d.needs_param then param_def=d param_val=d.dflt param_target=nil param_mode="new" prog_pane="param" else insert_node(build_node_from_def(d)) prog_pane="menu" set_msg("added "..d.label) end end function block_confirm() local n=selected_node() if not n then return end if n.kind=="repeat" then param_def=find_def("repeat") param_val=n.count param_target=n param_mode="edit_repeat" prog_pane="param" return end if n.kind=="loop" then enter_selected_block() return end if n.kind=="act" and n.val~=nil then param_def=find_def(n.op) param_val=n.val param_target=n param_mode="edit_action" prog_pane="param" end end function update_param() if btnp(BTN_UP) then param_val=step_param_value(param_val,param_def.step) end if btnp(BTN_DOWN) then param_val=step_param_value(param_val,-param_def.step) end if btnp(BTN_A) then if param_mode=="new" then insert_node(build_node_from_def(param_def,param_val)) set_msg("added "..param_def.label) prog_pane="menu" elseif param_mode=="edit_action" then param_target.val=param_val set_msg("changed") prog_pane="block" elseif param_mode=="edit_repeat" then param_target.count=param_val set_msg("changed") prog_pane="block" end param_target=nil param_mode=nil end if btnp(BTN_B) then param_target=nil param_mode=nil if prog_pane=="param" then prog_pane="block" end end end -------------------------- -- view update -------------------------- function update_view() if btnp(BTN_LEFT) then screen="prog" set_msg("program screen") return end if btnp(BTN_RIGHT) then lines={} reset_turtle() active=nil exec_stack={} run_state="idle" wrap_mode=true set_msg("reset") end if btnp(BTN_B) then stop_run() end if btnp(BTN_A) then if run_state=="idle" then start_run() elseif run_state=="run" then run_state="paused" set_msg("paused") elseif run_state=="paused" then run_state="run" set_msg("run") end end if run_state=="run" then update_runner() end end function start_run() reset_turtle() lines={} active=nil exec_stack={{kind="block",node=root,idx=1}} run_state="run" wrap_mode=true set_msg("run") end function stop_run() active=nil exec_stack={} run_state="idle" set_msg("stopped") end -------------------------- -- interpreter -------------------------- function update_runner() if active then step_active() else fetch_next() end if shake_t>0 then shake_t=shake_t-1 end end function fetch_next() while true do if #exec_stack<1 then run_state="idle" set_msg("done") return end local fr=exec_stack[#exec_stack] local kids=fr.node.children if fr.idx>#kids then if fr.kind=="repeat" then fr.left=fr.left-1 if fr.left>0 then fr.idx=1 else deli(exec_stack,#exec_stack) end elseif fr.kind=="loop" then fr.idx=1 else deli(exec_stack,#exec_stack) end else local n=kids[fr.idx] fr.idx=fr.idx+1 if n.kind=="act" then begin_action(n) if active then return end elseif n.kind=="repeat" then add(exec_stack,{kind="repeat",node=n,idx=1,left=n.count}) elseif n.kind=="loop" then add(exec_stack,{kind="loop",node=n,idx=1}) end end end end playing=false function start_sound(id) if not playing then sfx(id, "E-4", -1, 0) playing=true end end function stop_sound() if playing then sfx(-1, -1, -1, 0) playing=false end end function begin_action(n) local op=n.op if op=="fd" then if n.val>=0 then active={op="move",rem=abs(n.val),dir=1} else active={op="move",rem=abs(n.val),dir=-1} end elseif op=="bk" then if n.val>=0 then active={op="move",rem=abs(n.val),dir=-1} else active={op="move",rem=abs(n.val),dir=1} end elseif op=="rt" then if n.val>=0 then active={op="turn",rem=abs(n.val),dir=1} else active={op="turn",rem=abs(n.val),dir=-1} end elseif op=="lt" then if n.val>=0 then active={op="turn",rem=abs(n.val),dir=-1} else active={op="turn",rem=abs(n.val),dir=1} end elseif op=="wait" then active={op=op,t=n.val*wait_scale} elseif op=="shake" then active={op=op,t=10+n.val*3,s=n.val} elseif op=="sfx" then -- sfx(n.val,"E-4") start_sound(n.val) elseif op=="wrap" then wrap_mode=(n.val>0) elseif op=="pu" then turtle.pen=false elseif op=="pd" then turtle.pen=true elseif op=="home" then turtle.x=home_x turtle.y=home_y turtle.a=0 elseif op=="clear" then lines={} end end function step_active() if active.op=="move" then local d=min(move_speed,active.rem) turtle_move(d*active.dir) active.rem=active.rem-d if active.rem<=0 then active=nil end elseif active.op=="turn" then local d=min(turn_speed,active.rem) turtle.a=(turtle.a+d*active.dir)%360 active.rem=active.rem-d if active.rem<=0 then active=nil end elseif active.op=="wait" then active.t=active.t-1 if active.t<=0 then active=nil end elseif active.op=="shake" then shake_t=2 shake_s=active.s active.t=active.t-1 if active.t<=0 then active=nil shake_s=0 end end end function wrap_x(x) if xcanvas_x1 then return canvas_x0 end return x end function wrap_y(y) if ycanvas_y1 then return canvas_y0 end return y end function turtle_move(d) local steps=abs(d) local dir=(d<0) and -1 or 1 local a=turtle.a/360 for i=1,steps do local x1=turtle.x local y1=turtle.y local nx=x1+p8cos(a)*dir local ny=y1-p8sin(a)*dir local wrapped=false if wrap_mode then local wx=wrap_x(nx) local wy=wrap_y(ny) wrapped=(wx~=nx or wy~=ny) nx=wx ny=wy end if turtle.pen and not wrapped then add(lines,{x1=x1,y1=y1,x2=nx,y2=ny,c=15}) end turtle.x=nx turtle.y=ny end end -------------------------- -- camera / shake -------------------------- function update_camera() camx=0 camy=0 if screen=="view" and shake_t>0 then camx=flr(rnd(shake_s*2+1))-shake_s camy=flr(rnd(shake_s*2+1))-shake_s end end -------------------------- -- draw -------------------------- function draw() cls(1) if screen=="prog" then draw_program_screen() else draw_view_screen() end end -------------------------- -- program draw -------------------------- function draw_program_screen() rect(0,0,SCREEN_W,9,0) print("logo programmer - edit",2,2,15,false,1,true) print("depth:"..(#nav-1),180,2,12,false,1,true) rect(0,10,SCREEN_W,18,0) print("inside:",2,12,11,false,1,true) print(path_text(),44,12,15,false,1,true) rect(0,28,78,108,0) rectb(0,28,78,108,5) rect(82,28,156,108,0) rectb(82,28,156,108,5) print("commands",3,31,7,false,1,true) print("program",85,31,7,false,1,true) draw_menu_panel() draw_block_panel() if prog_pane=="param" then draw_param_box() end draw_prog_help() draw_msg() end function draw_menu_panel() local start=max(1,menu_i-6) local y=40 for i=start,min(#cmd_defs,start+10) do local c=7 if prog_pane=="menu" and i==menu_i then rect(2,y-1,45,7,6) c=0 end print(cmd_defs[i].label,4,y,c,false,1,true) y=y+8 end end function draw_block_panel() local ch=current_children() local cur=current_cursor() local start=1 if #ch>9 then start=max(1,cur-4) start=min(start,#ch-8) end local y=40 if #ch<1 then local c=(prog_pane=="block") and 10 or 6 rectb(84,41,40,9,c) print("(empty)",88,43,7,false,1,true) return end for i=start,min(#ch,start+8) do local n=ch[i] local c=7 if prog_pane=="block" and i==cur then rect(84,y-1,148,7,6) c=0 end print(node_label(n),87,y,c,false,1,true) if can_have_children(n) then print(">",228,y,c,false,1,true) end y=y+8 end end function path_text() local s="root" for i=2,#nav do s=s.." > "..nav[i].name end return s end function draw_param_box() rect(48,42,144,42,0) rectb(48,42,144,42,7) print("set "..param_def.label,56,48,11,false,1,true) print("value: "..value_label(param_def.id,param_val),56,58,7,false,1,true) print("up/down change",56,68,6,false,1,true) print("a ok b cancel",56,78,6,false,1,true) end function draw_prog_help() rect(0,126,SCREEN_W,10,0) if prog_pane=="menu" then print("U/D pick L up R prog A add B back",2,128,6,false,1,true) elseif prog_pane=="block" then if has_parent_block() then print("U/D pick L up R in A edit B cmds",2,128,6,false,1,true) else print("U/D pick L menu R in A edit B cmds",2,128,6,false,1,true) end print("U+B delete",162,128,8,false,1,true) else print("U/D val A ok B cancel",2,128,6,false,1,true) end end -------------------------- -- view draw -------------------------- function draw_view_screen() local sx=camx local sy=camy rect(canvas_x0+sx,canvas_y0+sy,canvas_x1-canvas_x0+1,canvas_y1-canvas_y0+1,0) rectb(canvas_x0+sx,canvas_y0+sy,canvas_x1-canvas_x0+1,canvas_y1-canvas_y0+1,5) for ln in all(lines) do line(ln.x1+sx,ln.y1+sy,ln.x2+sx,ln.y2+sy,ln.c) end draw_turtle(sx,sy) rect(0,0,SCREEN_W,9,0) print("logo programmer - view",2,2,7,false,1,true) print(run_state,180,2,11,false,1,true) if wrap_mode then print("wrap:on",2,110,11,false,1,true) else print("wrap:off",2,110,5,false,1,true) end rect(0,126,SCREEN_W,10,0) print("a run/pause b stop l edit r reset",2,128,6,false,1,true) if active then print("now: "..active_label(),70,110,10,false,1,true) end draw_msg() end function draw_turtle(sx,sy) local a=turtle.a/360 local x=turtle.x local y=turtle.y local x1=x+p8cos(a)*4 local y1=y-p8sin(a)*4 local x2=x+p8cos(a+0.38)*3 local y2=y-p8sin(a+0.38)*3 local x3=x+p8cos(a-0.38)*3 local y3=y-p8sin(a-0.38)*3 line(x1+sx,y1+sy,x2+sx,y2+sy,11) line(x2+sx,y2+sy,x3+sx,y3+sy,11) line(x3+sx,y3+sy,x1+sx,y1+sy,11) pix(x+sx,y+sy,10) end function active_label() if not active then return "" end if active.op=="move" then return "move" end if active.op=="turn" then return "turn" end if active.op=="wait" then return "wait" end if active.op=="shake" then return "shake" end return active.op end -------------------------- -- misc draw -------------------------- function draw_msg() if msg_t>0 then rect(0,10,SCREEN_W,8,0) print(msg,2,12,10,false,1,true) end end function set_msg(s) msg=s msg_t=45 end