I want to understand your wizardry! Anyone willing to share?
Sure. Here's the original code for Patrick's Picochallenge:
poke(24364,3)x="웃"v="▥"h="▤"b={}for i=1,36 do b[i]=i%9<2 and""or"█"end for i in all{"⬆️","➡️",h,"⬅️","⬇️",v,x}do repeat f=1+flr(rnd(36))until b[f]!=""b[f]=i if(i==x)p=f end::_::t=btnp cls()for i=0,35 do k=b[i+1] ?k,i%9*8,6*flr(i/9)+20,k==x and 11 or 7 end b[p]=""q=p if(t(0))q-=1 if(t(1))q+=1 if(t(2))q-=9 if(t(3))q+=9 if(b[q]and#b[q]>0)p=q t=b[p] if(t=="⬆️"or t==v)b[p-10]=""b[p-9]=""b[p-8]="" if(t=="⬇️"or t==v)b[p+10]=""b[p+9]=""b[p+8]="" if(t=="⬅️"or t==h)b[p-10]=""b[p-1]=""b[p+8]="" if(t=="➡️"or t==h)b[p+10]=""b[p+1]=""b[p-8]="" b[p]=x flip()goto _
My attempt at unobfuscating and commenting it (I wrote it minified from the start):
--patrick's picochallenge --by tobiasvl --use 64x64 resolution poke(0x5f2c,3) --generate a blank board of --empty █ tiles board={} --the board is 7x4, but we --represent it as a one- --dimensional table. we also --represent it as 36 tiles, ie --a 9x4 grid, with two columns --of "" on each end, so ⬅️➡️▤▥ --tiles don't wrap around when --they destroy adjacent tiles. for i=1,36 do if i%9<2 then --first and last column board[i]="" else board[i]="█" end end --populate the board with tiles --and the player's starting tile tiles={"⬆️","➡️","▤","⬅️","⬇️","▥","웃"} for i in all(tiles) do --find a random tile which is --not in the "invisible" outer --columns repeat position=1+flr(rnd(36)) until board[position]!="" board[position]=i --remember the player if (i=="웃") player=position end --game loop ::_:: cls() --print the board --here's the only obfuscation i --left in: here i loop from --0 to 35, instead of 1 to 36, --because then i only need to --do i+1 once instead of i-1 --twice. for i=0,35 do local tile=board[i+1] --the player is green if tile=="웃" then color(11) else color(7) end --properly centering the board --takes up too many characters --so just an approximation print(tile,i%9*8,6*flr(i/9)+20) end --erase the player character --and destroy the tile board[player]="" --remember the player's position new_player=player --move the player's position if --an arrow key is pressed if (btnp(⬅️)) new_player-=1 if (btnp(➡️)) new_player+=1 if (btnp(⬆️)) new_player-=9 if (btnp(⬇️)) new_player+=9 --if we're still inside the --board proper, ie the tile isn't --nil (outside the board) or "" --(the border columns), make --that the new position. if board[new_player] and board[new_player]!="" then player=new_player tile=board[player] end --if the player lands on one of --the special tiles, destroy --adjacent tiles if tile=="⬆️" or tile=="▥" then --destroy three tiles above board[player-10]="" board[player-9]="" board[player-8]="" end if tile=="⬇️" or tile=="▥" then --destroy three tiles below board[player+10]="" board[player+9]="" board[player+8]="" end if tile=="⬅️" or tile=="▤" then --destroy three tiles left board[player-10]="" board[player-1]="" board[player+8]="" end if tile=="➡️" or tile=="▤" then --destroy three tiles right board[player+10]="" board[player+1]="" board[player-8]="" end --put the player in the new --(or old!) position board[player]="웃" --loop flip() goto _
Fun exercise. I actually found a bug while looking through it, so thanks for that!
The game itself is a demake of a game I made earlier this year, so it was really interesting to try to find smarter solutions than I did originally.
Edit: itch stripped out blank lines from the code for some reason, here's a gist.
Here's Froggy Road, de-obfuscated! I tried to make the variable names more clear and removed all the line-break-saving measures, but I didn't change any of the logic, so there are still some spots where it does a weird thing to avoid an if statement or whatever (for example, every road-lane draws a frog, but they're all hidden offscreen except for the lane which actually-currently-contains the player).
(edit - like with tobiasvl, my linebreaks get stripped here - here's a version on pastebin which keeps them intact)
frogX=0 frogY=0 died=0 camX=frogX camY=frogY ::_:: flip() cls(1) // weird arrow key input z=btnp() // add +/- 5 to x for right/left arrows frogX+=(flr(z/2)%2-z%2)*5 // add +/- 1 to y for up/down arrows frogY-=flr(z/8)%2-flr(z/4)%2 // camera eases toward frog position camX+=(frogX-camX)/3 camY+=(frogY-camY)/3 // draw road lanes for laneY=frogY+25,frogY-2,-1 do // each lane has its own randomized properties srand(laneY) // car animation properties cycleOffset=rnd() carSpeed=8+rnd(16) // perspective distortion strength // (persp=0 means "infinitely far away") persp=(laneY-camY+2.3)/12 // draw the road // (but draw it offscreen if persp<0) rectfill(-1,64+9/persp,sgn(persp)*127,127,6-laneY%2) // draw a frog in every lane... // but offset it off the screen if the // frog isn't actually in this lane print("🐱",61+(frogX-camX)/persp+(laneY-frogY)*99,62+7.5/persp,3) // each lane has a different # of cars // (early/negative lanes have no cars) for i=1,sgn(laneY-2)*rnd(8) do // a car has two halves, parallel to the lane // (near-half and far-half) for k=0,1 do // each car has five sub-circles for the body for j=-2,2 do // x-position of this sub-circle worldX=(i*carSpeed*4+j+t()*carSpeed+cycleOffset-camX)%198-99 // collision detection for the frog if laneY==frogY and abs(worldX-frogX+camX)<2 then died=1 end // far-half of car uses a different persp value persp2=persp-k/60 // get screen position of this sub-circle screenX=worldX/persp2+64 screenY=5/persp2+64 // draw this sub-circle circfill(screenX,screenY,2/persp2,laneY%5) // draw a wheel, but only if j equals +/- 2 circfill(screenX,screenY+2/persp2,(abs(j)-1)/persp2,0) end end end end // self-explanatory death check if (died>0) then goto dead end // if you're not dead, continue the game loop goto _ ::dead:: // you done goofed // random red/orange noise pset(rnd(128),rnd(128),8+rnd(2)) // death UI print("❎ reset",46,62,7) // your score is your distance, literally print("score: "..frogY,3,3) // restart command if btn(5) then run() end // haven't reset yet. resume death goto dead
And then just for the sake of easy comparison, here's the original code:
x=0y=0l=0q=x r=y f=rnd g=flr h=circfill::_::flip()cls(1)z=btnp()x+=(g(z/2)%2-z%2)*5y-=g(z/8)%2-g(z/4)%2 q+=(x-q)/3r+=(y-r)/3 for z=y+25,y-2,-1 do srand(z)o=f()m=8+f(16)p=(z-r+2.3)/12rectfill(-1,64+9/p,sgn(p)*127,127,6-z%2) ?"🐱",61+(x-q)/p+(z-y)*99,62+7.5/p,3 for i=1,sgn(z-2)*f(8)do for k=0,1 do for j=-2,2 do u=(i*m*4+j+t()*m+o-q)%198-99 if(z==y and abs(u-x+q)<2)l=1 v=p-k/60n=u/v+64w=5/v+64h(n,w,2/v,z%5)h(n,w+2/v,(abs(j)-1)/v,0)end end end end if(l>0)goto d goto _::d::pset(f(128),f(128),8+f(2)) ?"❎ reset",46,62,7 ?"score: "..y,3,3 if(btn(5))run() goto d
I just put my earlier tweetgame on Itch.io and included fully commented source code. You can see it here: https://kometbomb.itch.io/breakout-280
I de-obfuscated my code for Putt, in case anybody is interested. Planning to make this into a proper lil PicoPutt game, but gonna need to spend a LOT of time polishing it.
function _init()
spawn_level()
end
function _update()
move_player()
end
function _draw()
cls()
draw_bg()
collision()
draw_player()
end
-- levelgen
function spawn_level()
grass={}
sand={}
for i=1,5 do
j=14+i*16
k=50+rnd(35)
q=17+rnd(10)
-- make grass and sand
add(grass,{x=j,y=k,r=q})
if i>1 and
i<5 then
add(sand,{x=j+rnd(q)-q/2,y=k+rnd(q)-q/2,r=8})
end
-- make player and hole
if i==1 then
p={x=j-q/2,y=k,a=0,xd=0,yd=4,p=1,v=0}
elseif i==5 then
h={x=j+q/2,y=k}
end
end
end
-- draw functions
function draw_bg()
for v in all(grass) do
circfill(v.x,v.y,v.r,11)
end
for v in all(sand) do
circfill(v.x,v.y,v.r,4)
end
-- hole
circfill(h.x,h.y,2,0)
-- power bar
circfill(64,8,5,7)
print(p.p,63,6,0) end
-- player
function draw_player()
circfill(p.x,p.y,2,7)
circfill(p.x+p.xd,p.y+p.yd,0)
end
function move_player()
-- set angle of shot
if btnp(5) then
if p.a<6.28 then
p.a+=.1 else p.a=0
end
p.xd=sin(p.a)*4
p.yd=cos(p.a)*4
end
-- set shot power
if btnp(2) then
if p.p<5 then
p.p+=1 else
p.p=1
end
end
-- shoot
if btnp(4) then
p.v=p.p
end
-- move ball, apply deceleration
p.x+=p.v*p.xd
p.y+=p.v*p.yd
p.v*=.7
end
function collision()
-- hole/border pixel collision
if pget(p.x,p.y)==0 then
_init()
end
-- sand trap collision
if pget(p.x,p.y)==4 then
p.v/=3
end
end
And here's the obfuscated code:
::z::e={}f={}
d,xd,yd,p,s,b,t,o=0,0,4,1,0,circfill,btnp,rnd
for i=1,5 do
j,k,q=14+i*16,50+o(35),17+o(10)add(e,{x=j,y=k,r=q})
if(i>1 and i<5)add(f,{x=j+o(q)-q/2,y=k+o(q)-q/2,r=8})
if(i==1)x,y=j-q/2,k
if(i==5)w,u=j+q/2,k
end::_::cls()if t(5) then
if(d<6.28)d+=.1else d=0
xd=sin(d)*4
yd=cos(d)*4
end
if t(2) then
if(p<5)p+=1else p=1
end
if(t(4))s=p
x+=s*xd y+=s*yd s*=.7
for v in all(e) do
b(v.x,v.y,v.r,11)end
for v in all(f) do
b(v.x,v.y,v.r,4)end
b(w,u,2,0)
if(pget(x,y)==0)goto z
if(pget(x,y)==4)s/=3
b(x,y,2,7)b(64,8)b(x+xd,y+yd,0)
?p,63,6,0
flip()goto _
Here's my other game too, Lights Out. I'm more pleased with this game as it has two modes, a title screen and a win state.
Minified:
z="lights out"poke(24364,3)::x::flip()cls()k=btnp()w={[0]="","▒","█"}f=flr ?z,12,18,stat(95)%4 ?"z: classic\nx: 2000",12,32,7 if(k<9)goto x if(k>16)w[3]="█" b={}p=2m=0 for i=1,35 do b[i]=i%7<2 and 0or 2 end::_::flip()cls() ?m,30,54 for i=1,35 do j=b[i] ?w[j],i%7*8-2,6*f(i/7)+18,j*3+2 end x=p%7*8-2y=6*f(p/7)+17rect(x-1,y,x+7,y+6,9)q=0k=btnp()h={-1,1,-7,0,7}if k>9then m+=1for i in all(h)do o=b[p+i] if(o and o>0)b[p+i]=o%#w+1 end elseif k>0then q=h[f(k/2)+1]end g=b[p+q] if(g and g>0)p=p+q for i=1,35 do if(b[i]>1)goto _ end z="you win!"goto x
Unobfuscated and commented (gist, since itch strips linebreaks):
-- title screen logo title="lights out" -- 64x64 resolution poke(0x5f2c,3) -- title screen loop ::title_screen:: -- clear screen -- (we do it here because we -- jump back upon winning) flip() cls() -- read button input key=btnp() -- light values and visuals: -- 0: no light -- 1: light off -- 2: red light -- 3: green light -- (green light only in mode -- "lights out 2000") lights={[0]="","▒","█"} -- print title with color -- alternating based on time -- (stat(95) is current second) print(title,12,18,stat(95)%4) -- print menu print("z: classic\nx: 2000",12,32,7) -- if the button value is below -- 16 (all values are powers of -- two so by checking below 9 -- here we save a character), -- including 0 (no input), we -- just loop. 16 is the z key, -- so if that's the case we -- will fall through to classic -- mode. if (key<9) goto title_screen -- button value 32 is x, so in -- that case we add the green -- light value for "2000 mode". if (key>16) lights[3]="█" -- initialize the board board={} -- start in the left corner player=2 -- move counter moves=0 -- initialize the 5x5 board -- with the value 2 (red light) -- but add a column of 0 (no -- light) on either side to -- avoid wrapping when toggling for i=1,35 do -- if column is 1 or 7: if i%7<2 then board[i]=0 else board[i]=2 end end -- gameplay loop ::play:: flip() cls() -- move counter print(moves,30,54) -- print the board for i=1,35 do light=board[i] -- a trick: each light's -- color can be computed from -- its value -- none: 0*3+2 = 0 (black) -- off: 1*3+2 = 5 (dark gray) -- red: 2*3+2 = 8 -- green: 3*3+2 = 11 light_color=light*3+2 -- print lights in grid print(lights[light],i%7*8-2,6*flr(i/7)+18,light_color) end -- print the player's marker x=player%7*8-2 y=6*flr(player/7)+17 rect(x-1,y,x+7,y+6,9) -- marker movement: we find the -- new position and see if it's -- valid. if so, we move it. new_player=0 key=btnp() -- all adjacent grid indices. -- used for movement and for -- toggling lights. notice that -- 0 (ie. no movement, the -- currently marked light) is -- in position 4 in the table. -- this is a trick, used when -- mapping input keys to -- positions. directions={-1,1,-7,0,7} -- if the key is x or z (value -- is 16 or 32) we toggle: if key>9 then moves+=1 -- look at all adjacent -- lights in all directions for i in all(directions) do light=board[player+i] -- if it's inside the board if light and light>0 then -- cycle light value up -- (use #lights here so -- we cover both classic -- and 2000 mode) board[player+i]=light%#lights+1 end end -- if the key is an arrow key -- (value is 1, 2, 4 or 8): elseif key>0 then -- divide the button value by -- two and add 1 and we get -- 1, 2, 3 or 5. look that up -- in the directions table -- (recall that position 4 -- was the current light) new_player=directions[flr(key/2)+1] -- if it's inside the board light=board[player+new_player] if light and light>0 then -- move there player=player+new_player end end -- if any of the lights are -- still on, stay in the -- gameplay loop for i=1,35 do if (board[i]>1) goto play end -- otherwise, set the title to -- a congratulatory message title="you win!" -- and go back to the title -- screen goto title_screen
Sorry for the late reply, just saw this now! Here's some commented code for Ninja Punch Zone. It's been long enough that I had to go through it and figure out what every line did again, and while doing that I found a couple of things I think I can optimize further. So thanks for that! :D
EDIT: Itch's comment system mangles the formatting so I've put an easier-to-read version up here.
--INITIALIZING VARIABLES-- --the number '30' came up a lot in the math stuff bleow, --so i assigned it to a variable to save characters t=30 --the player's x position x=t --the player's FIST position, relative to the player --(it's always four pixels away from the player in the direction they're facing, --but storing the offset in a variable saves chars when drawing later on) f=4 --making aliases for some commonly used functions to save chars later on z=rectfill y=rnd a=abs --cooldown timer for the player's punch c=0 --x position of red ninjas coming in from the right/left respectively --every time we do this we randomize their position slightly, --so the ninjas always come at the player at different times r=130+y(30) l=y(30)-82 --the player's score and the high score s=0 h=0 --changing to low res mode (64x64 resolution) poke(0x5f2c,3) --the UPDATE LOOP-- function _update60() --if the player presses left or right then move them in that direction if btn(⬅️) then x=max(x-1,3) f=-4 end if btn(➡️)then x=min(x+1,62) f=4 end end --if the player presses 'O' then punch, --but only if the cooldown timer from the last punch is finished if c==0 then if(btnp(🅾️)) c=10 end --updating the cooldown timer for this frame c=max(c-1,0) --moving the two red ninjas towards the player l+=.7 r-=.7 --if the left ninja has run all the way off screen then reset them --also: some more character optimization seems possible here! --must have missed it at the time :-D if l>70 then l=y(t)-t end -- likewise, if the right ninja has ran all the way off the screen then reset them if(r<-10) r=y(t)+70 --ALSO: ^^^^ do i even need to check for the ninjas running off screen? --they should collide with the player and reset before they reach the other side. --going to look into this, I might be able to create an even more optimized version! \o/ --PUNCH CHECKING. if the punch cooldown is greater than zero, --then check if the player's arm is close enough to an enemy ninja to hit them --if it is then we add one to the score and reset the ninja's position off-screen if c>0 then if a(x-l+f)<4 then s+=1l=y(t)-t end if a(x-r+f)<4 then s+=1r=y(t)+70 end end -- if the player has beaten the high score then we update the hi-score if(s>h) h=s -- if the player has collided with either of the two ninjas -- we start a new game (reset the score and move the two ninjas to their start points) if a(x-r)<2or a(x-l)<2 then s=0 r=y(t)+130 l=y(t)-82 end -- finally, the DRAW LOOP-- -- (naughtily done in _update60() to save chars) -- clear the screen cls() --draw two rectangles for the ground and sky z(0,14,64,49,12) z(0,43,64,49,5) --draw the player ?"웃",x-4,39,0 --if the player is punching, draw a "-" for the player's extended arm if c>0 then ?"-",x+f-2,39,0 end --draw the two red ninjas ?"웃",l-3,39,8 ?"웃",r-3,39,8 --print the score and high score at the top ?s,6,16,10 ?h,54,16,10 end