0,]']±>Sï}WÿÍu§ðp8·d%qy)6o;]ÉA¦ösï÷ôôô”°ÂVl†315 then i=15 end --returning color r,g,b of the color if r==nil and g==nil and b==nil then return peek(0x3fc0+(i*3)),peek(0x3fc0+(i*3)+1),peek(0x3fc0+(i*3)+2) else if r==nil or r<0 then r=0 elseif r>255 then r=255 end if g==nil or g<0 then g=0 elseif g>255 then g=255 end if b==nil or b<0 then b=0 elseif b>255 then b=255 end poke(0x3fc0+(i*3)+2,b) poke(0x3fc0+(i*3)+1,g) poke(0x3fc0+(i*3),r) end end -- http://lua-users.org/wiki/CopyTable function dCopy(o) if type(o)~='table' then return o end local c={} for k,v in next,o,nil do c[dCopy(k)]=dCopy(v) end setmetatable(c,dCopy(getmetatable(o))) return c end -- Code examples and snippets · nesbox/TIC-80 Wiki · GitHub -- https://github.com/nesbox/TIC-80/wiki/code-examples-and-snippets#palette-swapping -- swap c0 and c1 colors, call pal() to reset function palswap(c0,c1) if(c0==nil and c1==nil)then for i=0,15 do poke4(0x3FF0*2+i,i)end else poke4(0x3FF0*2+c0,c1)end end -- Custom helper functions function findIn(t,f) for i,v in ipairs(t) do if f(v) then return i end end return -1 end function contains(t,val) return findIn(t,function(v) return v==val end)>-1 end function hasState(i,s) return contains(i.states,s) end function where(t,f) local r={} for _,v in ipairs(t) do if f(v) then table.insert(r,v) end end return r end function firstOrNil(t,f) for _,v in ipairs(t) do if f(v) then return v end end return nil end function addOnce(t,s) _=not contains(t,s) and table.insert(t,s) end function textSubCat(sc) return where(items,function(v) local t=types[v.type] return t.c=="text" and t.sc==sc end) end -- Must return true if the first argument should come first in the sorted array function itemSort(a,b) return types[a.type].o < types[b.type].o end function eachItem(f) for _,v in ipairs(items) do f(v) end end function eachType(t,f) for _,v in ipairs(items) do if v.type==t then f(v) end end end function eachState(s,f) for _,v in ipairs(items) do if hasState(v,s) then f(v) end end end function eachColl(p,f) for _,v in ipairs(items) do if p.x>=v.x and p.x=v.y and p.yb.r or p.yb.b) and f({x=0,y=0,type=nameToType("wall","object"),states={"stop"},done=false},p) then return end end function eachCat(c,f) for _,v in ipairs(items) do if types[v.type].c==c then f(v) end end end function nextText(i,t,d) return firstOrNil(t,function (v) return i.y+(d~=0 and 16 or 0)==v.y and i.x+(d==0 and 16 or 0)==v.x end) end function findDoRules() -- Find the rules from the level, starting with the base rules=dCopy(baseRules) activeText={} local texts=where(items,function(v) return types[v.type].c=="text" end) for _,subject in ipairs(textSubCat("noun")) do for d=0,1 do local sentence,text={subject} repeat text=nextText(sentence[#sentence],texts,d) if text then table.insert(sentence,text) end until (text==nil) if #sentence>=3 then if types[sentence[2].type].sc=="verb" and types[sentence[3].type].sc=="state" or types[sentence[3].type].sc=="noun" then addRuleByItems(sentence[1],sentence[2],sentence[3]) for i=1,3 do addActiveText(sentence[i]) end end end end end eachItem(function (v) v.states={} end) -- Clear the states for _,v in ipairs(rules) do applyState(v.noun,v.state) end end -- n (noun) and s (state) are strings function applyState(n,s) local type,action=nameToType(s,"text") -- If state is a noun if type>-1 and types[type].sc=="noun" then action=function (v) v.type=nameToType(s,"object") end sort=true -- Otherwise state is a complement else action=function (v) addOnce(v.states,s) end end -- If this is applying to objects, apply by type if n~="text" then eachType(nameToType(n,"object"),action) -- If this is applying to all text, apply by category else eachCat(n,action) end end function nameToType(n,c) return findIn(types,function(v) return v.name==n and v.c==c end) end function addActiveText(i) table.insert(activeText,{x=i.x,y=i.y}) end function addRule(n,v,s) table.insert(rules,{noun=n,verb=v,state=s,done=false}) end function addRuleByItems(n,v,s) addRule(types[n.type].name,types[v.type].name,types[s.type].name) end function addItem(x,y,t) table.insert(items,{x=x,y=y,type=t,states={},done=false}) end function sortItems() table.sort(items,itemSort) end function removeItem(i) table.remove(items,findIn(items,function(v) return v==i end)) end -- Load the level into items from the map function remap(tile,tileX,tileY) local t=findIn(types,function(v) return v.s==tile end) _=t>-1 and addItem((tileX-coords.x)*16,(tileY-coords.y)*16,t) return 0 end -- Draw an 8x16 number at the given coords function digit(n,x,y) for _,w in ipairs(digits[n+1]) do spr(106+w[1],x,y+(w[2]*8),0,1,w[3]) end end function number(n,x,y) local s=string.format("%02d",n) digit(tonumber(string.sub(s,1,1)),x,y) digit(tonumber(string.sub(s,2,2)),x+8,y) end function levelNumber(n,x,y) local b=contains(beat,n) if contains(revealed,n) then if not b then palswap(9,12) end if contains(open,n) then rect(x+1,y+1,14,14,15) number(n,x,y) else rectb(x+1,y+1,14,14,15) end if not b then palswap() end end _=n==level and highlight(x,y) end function highlight(x,y,s) local fs,s=frames[4],s or 1 local o=8*s spr(fs[frame],x,y,0,s) spr(fs[frameI],x+o,y,0,s,1,0) spr(fs[frame],x+o,y+o,0,s,0,2) spr(fs[frameI],x,y+o,0,s,0,3) end -- Prints text where x is the center of text -- https://github.com/nesbox/TIC-80/wiki/print function printc(s,x,y,c) local w=print(s,0,-50,15,false,2) print(s,x-(w/2),y,c or 15,false,2) end function anyBtn() return btn(4) or btn(5) or btn(6) or btn(7) end function getFrame(n) return frames[n][frame] end function init() local function addType(n,s,o,c,sc) table.insert(types,{name=n,s=s,o=o,c=c,sc=sc}) end local function addTextType(n,s,sc) addType(n,s,5,"text",sc) end local function addFrames(a,b,c) frames[a]={a,b,c} end local function addLevel(x,y,w,h,mt,mr,mb,ml) table.insert(levels,{x=x,y=y,w=w*16,h=h*16,mt=(mt or 0)*16,mr=(mr or 0)*16,mb=(mb or 0)*16,ml=(ml or 0)*16}) end types={} levels={} rules={} mode=0 prevMode=0 frame=1 frameI=3 frames={} wait=0 activeText={} nada=false mainSel=1 starp={{0,-14},{14,-4},{8,12},{-8,12},{-14,-4}} _=nil arrows={"/\\",108,0,"\\/",108,126,"/",0,63,"\\",0,73,"\\",230,63,"/",230,73} digits={} local d="ABCIUVW]bb11e7775d7efe13ff7f" for i=9,28,2 do local t={} for j=1,8 do if tonumber(string.sub(d,i,i+1),16)&(1<>2,(k&24)>>3}) end end table.insert(digits,t) end -- tile 1 < wall 2 < rock 3 < flag 4 < baba 5, text 5 -- ?: grass, water addType("baba",2,5,"object") addType("wall",6,2,"object") addType("rock",10,3,"object") addType("flag",8,4,"object") addType("tile",68,1,"object") addType("grass",70,1,"object") addType("water",72,4,"object") addType("skull",144,4,"object") addTextType("baba",34,"noun") addTextType("wall",38,"noun") addTextType("rock",42,"noun") addTextType("flag",40,"noun") addTextType("is",36,"verb") addTextType("stop",32,"state") addTextType("win",44,"state") addTextType("push",46,"state") addTextType("you",14,"state") addTextType("water",74,"noun") addTextType("sink",76,"state") addTextType("skull",146,"noun") addTextType("defeat",148,"state") addFrames(2,64,66) addFrames(4,5,20) addLevel(0,0,11,9,4,9,3,8) addLevel(11,0,12,14,1,8,1,8) addLevel(23,0,12,14,1,8,1,8) addLevel(35,0,22,15) addLevel(57,0,22,14,0,2) creditsBase={ {"baba is you",2}, {""}, {"original"}, {"2019",10}, {""}, {"by"}, {"arvi teikara",10}, {""}, {"tic-80 demake"}, {"2023",10}, {""}, {"by"}, {"gabby paolucci",10}, {""}, {"github.com/",5}, {"gabby-paolucci",3}, {""}, {"created on"}, {"android using"}, {"tic-80 pro",10}, {"f-droid",10}, {"material files",10}, {"termux",10}, {"neovim",10}, } credits={} activeCredits={} addRule("text","is","push") baseRules=dCopy(rules) offset={x=0,y=0} max={x=240,y=136} col=30 row=17 ticks=1 speed=.1 -- must be > 0 and <= 1 win=false open={0} beat={} revealed={0} level=0 coords={} options={cam=false,gridlines=false} onOff={[true]="on",[false]="off"} popup={ ops={"resume","return to map","restart","settings","return to menu"}, offsets={42,24,40,37.5,21}, s=1, modes={2,1,2,5,0}, opFunc={[3]=function() loadLevel() end,[4]=function() prevMode=4 end}, } function popup:a() local m=self.modes[self.s] if m~=nil then mode=m end end settings={ bases={"start in cam mode: ","gridlines: ","digits test","help","back"}, values={function() return onOff[options.cam] end,function() return onOff[options.gridlines] end}, ops={}, offsets={0,0,0,0,0}, s=1, opFunc={ [1]=function() options.cam=not options.cam end, [2]=function() options.gridlines=not options.gridlines end, [3]=function() wait=20 mode=7 end, [4]=function() wait=20 mode=8 end, [5]=function() mode=prevMode end }, } function settings:update(i) self.ops[i]=self.values[i]~=nil and self.bases[i]..self.values[i]() or self.bases[i] end function settings:updateAll() for i in ipairs(self.bases) do self:update(i) end end settings:updateAll() help={ ops={"a=wait/exit cam in place","b=cam mode toggle","x=Rewind","y=Menu","back"}, offsets={0,0,0,0,0}, s=1, opFunc={ [5]=function() mode=5 end }, } worldLevels={} worldPaths={} worldIsle={} mapSkip={} world={x=210,y=0} local function processIsleTile(tile,rx,ry) if tile>=112 and tile<=124 then table.insert(worldIsle,{t=tile-112,x=rx,y=ry}) end end local function worldRemap(tile,tilex,tiley) if tile>0 and findIn(mapSkip,function(v) return v.x==tilex and v.y==tiley end)==-1 then local rx,ry=(tilex-world.x)*8,(tiley-world.y)*8 -- Level Numbers if tile >=96 and tile<=105 then local l=tile-96 local tile2=mget(tilex+1,tiley) if tile2>=96 and tile2<=105 then l=l*10+tile2-96 table.insert(mapSkip,{x=tilex+1,y=tiley}) end table.insert(worldLevels,{l=l,x=rx,y=ry}) -- Paths elseif tile>=128 and tile<=140 then table.insert(worldPaths,{t=tile-128,x=rx,y=ry,f=0}) local dir=1 if tile==128 or tile==129 then table.insert(worldPaths,{t=tile-128,x=rx,y=ry+8,f=2}) end if tile==136 then table.insert(worldPaths,{t=0,x=rx,y=ry+8,f=2}) end if tile==137 then table.insert(worldPaths,{t=1,x=rx,y=ry+8,f=2}) end if tile==138 then table.insert(worldPaths,{t=0,x=rx,y=ry-8,f=0}) dir=-1 end if tile==139 then table.insert(worldPaths,{t=1,x=rx,y=ry-8,f=0}) dir=-1 end processIsleTile(mget(tilex,tiley+dir),rx,ry) -- Island elseif tile>=112 and tile<=124 then processIsleTile(tile,rx,ry) end end return 0 end map(world.x,world.y,30,17,0,0,-1,1,worldRemap) local findByXY=function(t,x,y) return firstOrNil(t,function(v) return v.x==x and v.y==y end) end local findAddL = function(t1,x,y,t2) local n=findByXY(t1,x,y) if n~=nil then table.insert(t2,n.l) end end local findAddP = function(t1,x,y,t2) local n=findByXY(t1,x,y) if n~=nil then table.insert(t2,{x=n.x,y=n.y}) end end connections={} for _,v in ipairs(worldLevels) do local values={levels={},paths={}} local cLevels={} findAddL(worldLevels,v.x,v.y-16,cLevels) findAddL(worldLevels,v.x,v.y+16,cLevels) findAddL(worldLevels,v.x-16,v.y,cLevels) findAddL(worldLevels,v.x+16,v.y,cLevels) values.levels=cLevels local cPaths={} findAddP(worldPaths,v.x,v.y-16,cPaths) findAddP(worldPaths,v.x,v.y+16,cPaths) findAddP(worldPaths,v.x-16,v.y,cPaths) findAddP(worldPaths,v.x+16,v.y,cPaths) values.paths=cPaths connections[v.l]=values end --trace("connections") --trace(dump(connections)) end function loadLevel() items={} history={} rules=dCopy(baseRules) coords=levels[level+1] -- Load the level into items map(coords.x,coords.y,coords.w/16,coords.h/16,0,0,-1,1,remap) sortItems() findDoRules() table.insert(history,dCopy(items)) offset={x=coords.x,y=coords.y} -- Set offsets to try to get "you" centered on the screen eachState("you",function (v) offset.x=v.x*.5 offset.y=v.y*-.5 end) wait=10 cam=options.cam start=dCopy(offset) end function boundaries(o) local o,c=o or {x=0,y=0},coords return {l=o.x-c.ml,t=o.y-c.mt,b=o.y+c.h+c.mb,r=o.x+c.w+c.mr} end function moveToLevel(n) if contains(open,n) then level=n wait=10 end end -- Modify offsets to try to keep "you" on the screen function bounds(v,o,p) if v[p]+o[p]-16<0 then o[p]=o[p]+16 elseif v[p]+o[p]+32>max[p] then o[p]=o[p]-16 end end function menu(p,m) -- Buttons for i,v in ipairs(p.ops) do rect(42,(17*(1+i))-5,152,15, i==p.s and 14 or 15) rectb(42,(17*(1+i))-5,152,15,10) _=i==p.s and spr(frames[2][frame],43,(17*(1+i))-6,0,1,0,0,2,2) print(string.upper(v),60+p.offsets[i],17*(1+i),12) end if wait>0 then wait=wait-1 -- Up elseif btn(0) then p.s=p.s-1 wait=10 if p.s<1 then p.s=1 end -- Down elseif btn(1) then p.s=p.s+1 wait=10 if p.s>5 then p.s=5 end -- A: Select elseif btn(4) then wait=20 _=p.a~=nil and p:a() local of=p.opFunc[p.s] if of~=nil then of() if p.update~=nil then p:update(p.s) end end -- B: Close menu elseif btn(5) then wait=20 mode=m end end -- All Digits Test function digitsTest(m) cls() local x,y=0,0 for i=0,9 do x=i*8 digit(i,x,y) end if wait>0 then wait=wait-1 elseif anyBtn() then wait=20 mode=m end end function gear() local x,r,a=32*3.5,13,.15 local b=r*a*2 local c=math.sin(45)*r local d=c*a local e,f,g,h=x+c-d,x-c-d,x+c+d,x-c+d tri(e,f,g,h,f,e,10) tri(g,h,f,e,h,g,10) tri(f,h,e,g,h,f,10) tri(e,g,h,f,g,e,10) rect(x-b+1,x-r-1,b*2,r*2+2,10) rect(x-r-1,x-b+1,r*2+2,b*2,10) circ(x,x,10,10) circ(x,x,2,1) end function romb(a,b,c,d,e,f,g,h) tri(a,b,c,d,e,f,5) tri(a,b,g,h,e,f,5) end function star() local x,y,p=32*5,32*3.5,starp for i=1,5 do local j,k=i<5 and i+1 or 1,i>3 and 9-i or 4-i romb(x+p[i][1]/2,y+p[i][2]/-2,x+p[k][1],y+p[k][2],x+p[j][1]/2,y+p[j][2]/-2,x,y) end end function loadCredit() local c=table.remove(credits,1) c[1]=string.upper(c[1]) -- Add y table.insert(c,2,0) -- Add centered x table.insert(c,3,(240-#c[1]*12)/2) table.insert(activeCredits,c) end init() function TIC() local diff=(math.floor(ticks*speed)%3) frame=1+diff frameI=3-diff -- Title Screen if mode==0 then if wait>0 then wait=wait-1 -- Right elseif btn(3) then mainSel=mainSel+1 wait=10 if mainSel>3 then mainSel=3 end -- Left elseif btn(2) then mainSel=mainSel-1 wait=10 if mainSel<1 then mainSel=1 end -- A: Select elseif btn(4) then wait=20 if mainSel==1 then mode=1 elseif mainSel==2 then mode=5 prevMode=0 elseif mainSel==3 then mode=6 credits=dCopy(creditsBase) activeCredits={} end -- B: Exit elseif btn(5) then exit() end cls() -- Title spr(34,32*1.5,32*1.5,0,2,0,0,2,2) spr(36,32*3,32*1.5,0,2,0,0,2,2) spr(14,32*4.5,32*1.5,0,2,0,0,2,2) -- Play button rect(32*1.5,32*3,32,32,1) tri(32*1.5+4,32*3+4,32*1.5+4,32*3+28,32*1.5+28,32*3+16,12) -- Settings button rect(32*3,32*3,32,32,1) gear() -- Credits button rect(32*4.5,32*3,32,32,1) star() -- Selection highlight & Baba highlight(32*1.5*mainSel,32*3,2.10) spr(getFrame(2),32*(1.5*mainSel-.5),32*3.25,0,1,0,0,2,2) -- Level Select elseif mode==1 then if wait>0 then wait=wait-1 -- Right elseif btn(3) then moveToLevel(level+1) -- Left elseif btn(2) then moveToLevel(level-1) -- A: Load Level elseif btn(4) then mode=2 loadLevel() -- B: Back to title screen elseif btn(5) then mode=0 wait=20 end cls() for _,v in ipairs(worldIsle) do spr(112+v.t,v.x,v.y) end for _,v in ipairs(worldLevels) do levelNumber(v.l,v.x,v.y) end for _,v in ipairs(worldPaths) do spr(128+v.t,v.x,v.y,0,1,v.f) end -- Level Mode elseif mode==2 then update=false sort=false addToHistory=false if wait>0 then wait=wait-1 else -- Directional buttons local m if btn(0) then m=function(v,u) v.y=v.y+(-16*u) end elseif btn(1) then m=function(v,u) v.y=v.y+(16*u) end elseif btn(2) then m=function(v,u) v.x=v.x+(-16*u) end elseif btn(3) then m=function(v,u) v.x=v.x+(16*u) end end if m~=nil then -- Handle movement input wait=10 if not cam then f=function(v) local undo=false if not v.done then m(v,1) v.done=true if hasState(v,"win") and hasState(v,"you") then win=true return true end eachColl(v,function(w,p) if hasState(w,"stop") then undo=true return true elseif hasState(w,"push") then if f(w) then undo=true return true end elseif hasState(p,"you") and hasState(w,"win") then win=true return true elseif hasState(w,"sink") then removeItem(w) removeItem(p) elseif hasState(p,"you") and hasState(w,"defeat") then removeItem(p) end end) _=undo and m(v,-1) end if not undo then addToHistory=true end return undo end eachState("you",f) update=true -- cam mode else m(offset,-2) end -- Y: Open popup menu elseif btn(7) then mode=4 wait=20 popup.s=1 -- X: Back a step elseif btn(6) then wait=10 if #history>0 then items=table.remove(history) update=true end -- A: In gameplay mode stand still for a turn, in cam mode go back to gameplay mode while retaining the viewport offset elseif btn(4) then if not cam then update=true else cam=false end wait=10 -- B: cam mode toggle elseif btn(5) then if cam then cam=false offset=dCopy(start) else cam=true start=dCopy(offset) end wait=10 end end cls(15) if not win then _=addToHistory and table.insert(history,dCopy(items)) if update then findDoRules() -- Modify offsets to try to keep you on the screen _=not cam and eachState("you",function (v) bounds(v,offset,"x") bounds(v,offset,"y") end) -- Set or unset nothing is you state nada=findIn(items,function(v) return hasState(v,"you") end)==-1 end -- Draw outer boundaries local b=boundaries(offset) rect(b.l,b.t,b.r-b.l,b.b-b.t,0) if options.gridlines then line(7,0,7,136,15) for i=8,240,16 do line(i,0,i,136,15) line(i+15,0,i+15,136,15) end for i=0,136,16 do line(0,i,240,i,15) line(0,i+15,240,i+15,15) end end _=sort and sortItems() -- Draw general sprites eachItem(function(v) local x,y=v.x+offset.x,v.y+offset.y -- If this is offscreen, don't draw it if x<-16 or y<-16 or x>240 or y>136 then return end local s=types[v.type].s -- change sprite per animation if applicable if frames[s] ~= nil then s=frames[s][frame] end spr(s,x,y,0,1,0,0,2,2) end) -- Draw win dust eachState("win",function(v) spr(12,v.x+offset.x,v.y+offset.y,0,1,0,0,2,2) end) -- Draw borders around active text for _,v in ipairs(activeText) do highlight(v.x+offset.x,v.y+offset.y) end -- Reset each item to not done eachItem(function(v) v.done=false end) -- If in cam mode draw arrows if cam then for i=1,18,3 do print(arrows[i],arrows[i+1],arrows[i+2],12,true,2) end end -- "Nothing is you" display _=nada and printc("Nothing is you",120,68,12) -- Won during this iteration else mode=3 win=false if level+1 < #levels then addOnce(open,level+1) addOnce(revealed,level+1) end wait=20 end -- Win state elseif mode==3 then printc("Congratulations!",120,68,12) if wait>0 then wait=wait-1 elseif anyBtn() then wait=20 mode=1 end -- In-level popup menu elseif mode==4 then menu(popup,2) -- Settings menu elseif mode==5 then menu(settings,prevMode) -- Credits elseif mode==6 then if wait>0 then wait=wait-1 elseif anyBtn() then mode=0 wait=20 end cls() _=(#activeCredits==0 and #credits>0) and loadCredit() if ticks%2==0 then local remove=false for i,v in ipairs(activeCredits) do if v[2]>152 then remove=true elseif v[2]==16 and i==#activeCredits and #credits>0 then loadCredit() end v[2]=v[2]+1 end if remove then table.remove(activeCredits,1) end if #activeCredits==0 then mode=0 wait=20 end end for _,v in ipairs(activeCredits) do if v[1]~=nil and #v[1]>0 then print(v[1],v[3],136-v[2],v[4] or 12,true,2) end end -- All digits test elseif mode==7 then digitsTest(prevMode) -- Help popup elseif mode==8 then menu(help,5) end ticks=ticks+1 end