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