I enjoy the idea of lists. I really like how through code we can create pointers to pointers (or in PICO-8's case, reference to reference). I like experimenting with different simple structures in code and seeing how I can produce different kinds of little graphical effects with them.
This is similar to my Procedural Fire cart, as well as its sister cart, the Cool Sonar Effect. These two carts humor the idea of a command queue, where each entry in the queue is a function which should be called.
Anyway, this cart utilizes singly-linked lists to store string data. There are a few commands which were produced to create a tiny linked-list API. The functions are as follows:
-- link node -- d: data -- n: next node (if necessary) function link(d,n) local l={ data=d, next=n } return l end -- loop through all nodes -- in the chain and call f(d) -- on the data inside. function loop(l,f) while l!=nil do f(l) l=l.next end end -- count the size of the list -- starting with link l -- (this will hang forever -- if the list is circular) function count(l) if (l==nil) return 0 local c=0 while l do c+=1 l=l.next end return c end -- get the data for the list. -- if data is provided, also set -- the data before returning function data(l,d) if d then l.data=d end return l.data end -- get the next link for the -- list. if the next link is -- provided set the next link -- before returning function next(l,n) if n then l.next=n end return l.next end |
The example code of the cart is:
------------------------------ -- example -- clear to dark blue cls(0) -- sample text to use sss="hello world !! " -- starting node (nil) xxx=nil -- tile x and y tx,ty=0,0 -- flip flag fp=false -- create a new list with -- each node containing a -- character of the sample -- string. we do this backwards. for i=#sss,1,-1 do xxx=link(sub(sss,i,i),xxx) end loop(xxx,function(l) ?l.data,tx*4,ty*8,rnd()>0.5 and 15 or 2 if next(l)==nil then next(l,xxx) end tx+=1 if (tx>=32) tx=0 ty+=1 if (ty>=16) ty=0 if btn(❎) or btn(🅾️) then fp=not fp end if (fp==true) then flip() end end) |
Notice how in the example code, the characters in the string are added backwards. This is similar to stacking paper, or any kind of stack, really. We have access to only the top item in the stack, and if we want to read it from first to last, we have to add the items in backwards. The link
command asks for a reference to the previous link, and returns itself. When used like xxx=link(sub(sss,i,i),xxx)
we are storing the most recent link in xxx
.
Singly-linked lists (stacks) are not quite as flexible as doubly-linked lists. We cannot read backwards from a stack. Once the topmost item has been removed, we don't have any way to reference it. But, sometimes you don't need that ability. In this example, there isn't even a container object keeping track of things.
One of the more interesting commands of this set is loop()
. It asks for the first link, and also a function which will work on each link, starting with the one provided. This can allow you to iterate through each link and do whatever you'd like with its data!
for i=1,10 do ls=link(10-i,ls) end loop(ls,function(l) -- prints 9 to 0 on each line ?data(ls) end) |
You can even do things like neat mathematical models like the Fibonacci sequence
-- if we go over 23 there -- will be overflow for i=1,23 do -- fill stack with 23 0's lis=link(0,lis) end -- fill the sequence loop(lis,function(l) if data(l)==0 then data(l,1) data(next(l),1) data(next(next(l)),2) else if next(l) and next(next(l)) then data(next(next(l)), data(l)+data(next(l))) end end end) -- read the sequence loop(lis,function(l) ?data(l) end) |
O/X = select random palette
Made in like 20 minutes because I wasn't sure if Pico-8 was fast enough to handle a lot of PSETs. Turns out it's more than capable :).
-- serial ------------------------------- srl={}srl.sts={open="o",key="k", value="v"}srl.tok={open="{", close="}",ass="=",st1=",", st2=";",quote="\"",sp=" ",nl= "\n"}srl.strisnum=function(a) local b=tonum(a)local c=tostr(b) return a==c,b end;srl.strisbool= function(a)if a=="true"then return true,true elseif a== "false"then return true,false end;return false,nil end; srl.trim=function(a)local d=0; local e=0;local f;for g=1,#a do f=sub(a,g,g)if not(f==" "or f== "\n")then if d==0 then d=g;e=g else e=g end end end;return sub (a,d,e)end;srl.tostr=function(h) local a="{"local i=""for g,j in pairs(h)do a=a..tostr(g).."="i= type(j)if i=="number"then a=a.. tostr(j)elseif i=="string"then a=a..j elseif i=="boolean"then a=a..(j and"true"or"false") elseif i=="table"then a=a..srl. tostr(j)elseif i=="function"then a=a.."(f)"else a=a.."(r)"end;a=a ..";"end;a=a.."}"return a end; srl.fromstr=function(a,k)k=k or 1;local l={}local m=""local n="" local o=false;local p=""local q= ""local r=srl.sts.open;local g=1;while g<=#a do m=sub(a,g,g) if r==srl.sts.open then if m== srl.tok.open then r=srl.sts.key end elseif r==srl.sts.key then if m==srl.tok.close then return l,g+k-1 elseif m==srl.tok.ass then if#n>0 then n=srl.trim(n) p=n;n=""r=srl.sts.value end elseif m==srl.tok.st1 or m==srl. tok.st2 then if#n>0 then n=n..m end elseif m==" " or m== "\n" or m=="\t" then if#n>0 then n=n ..m end else n=n..m end elseif r==srl.sts.value then if m==srl. tok.open then if#n==0 then l[p], g=srl.fromstr(sub(a,g),g)end elseif m==srl.tok.close then n=n ..m elseif m==srl.tok.ass then n =n..m elseif m==srl.tok.st1 or m ==srl.tok.st2 then if#n>0 then q =srl.trim(n)local s,t=srl. strisnum(q)local u,v=srl. strisbool(q)if s then q=t elseif u then q=v end;n=""l[p]= q end;r=srl.sts.key elseif m== srl.tok.sp or m==srl.tok.nl then if#n>0 then n=n..m end else n=n.. m end end;g=g+1 end;return l end -------------------------------- |
Usage:
s = srl.tostr(t)
:: Converts a table to a string
t = srl.fromstr(s)
:: Converts a string back to a table.
Formatting and Differences:
- Strings aren't quoted. The parser will trim any whitespace before and after.
- Strings that look like numbers will always be turned to numbers.
- Strings that look like booleans will always be turned to booleans.
- The input string must have an opening and closing curly bracket.
- You must include a comma or semicolon after every entry, including the last one.
- The exception to the rule is the very last closing bracket.
nil
will be the string "nil" and not actual nil.- Keys are always strings.
Example:
{ a = 10; b = 20; c = { d = 12.34; e = some string; f = false; g = { h = true; }; }; } |
It's roughly 600 tokens, and could use further optimization.
But, it works! Now you can store game data in a string, which
costs only one token!
This is a little example of using layered sprites to create the illusion of liquids which adhere to each other.
If you happen to keep particles close to each other, the illusion becomes much more convincing. Below, I'm only setting the initial velocity of each new particle close together:
This is "Hellmod" which is a heavily modified version of the cartridge Four Giga Boss Fights by Guerragames. This was just something I tinkered around with in my spare time. It's not 100% polished. The original game doesn't have a license, so I don't mind taking this down if he has a problem with it.
There's some notable changes from the original:
- You can hold your angle by holding the Z button (O on consoles). Your speed is reduced significantly if your power meter is empty.
- The power meter is now hidden and is instead indicated with the color of the bullets fired from your weapon.
- You can 'overcharge' your power by not using any super bullets (holding X) in which your bullets will turn red and do additional damage for being patient.
- "Ripping the wings" off of a boss by destroying all of its bullet/laser nodes before killing it will fill your power meter to max (not overcharged). This will also cause the angel to lose its will to live, and you can purge it in a single shot afterwards. This is more difficult with later stages.
- The ships in the original game were replaced with fantasy weapons, and your hitbox has been changed to look like a little player character.
- The music was removed and replaced with an eerie drone like sound to hammer in that feeling that you're fighting solo with a giant demonic angel.
- The lives were changed to look like heart icons.
- The bosses (angels) only have one set of colors now: red and black.
- Bosses have way more health now, but now hitting their wings does a small amount of damage to them. They are stronger, but so are you.
- Death is punishing, and you will lose all of your power when you die. So, try not to.
:)
Based on Rainbow Curve by Adekto
v5:
- pressing down will only work once per item get
- added sfx for safety turning (pressing down)
v4:
- created speed option in menu (1x or 2x speed)
v3:
- added palette swapping (O or X)
- changed player sprite
v2:
- changed number font
- changed item got sprite
v1:
- up key now toggles direction
- down key cannot be repeated if held down
v0:
- initial release
edit 1: topleft cards were incorrect (changed to 8-7 pair)
music is Startropics (NES) - Town Theme
VER 0: Initial release
VER 1: Fixed shading under left M
VER 2: Date said 2020, changed to 2022
It seems that I am no longer able to release a sustained note in playback in the SFX editor using the 'A' key. A colleague on the Discord server mentioned:
Packbat:
"Checking old versions on my hard drive, it releases the sample in 0.2.0i and doesn't in 0.2.4"
The functionality stopped for me a couple versions ago, personally.
Operating System: Linux Mint 19.3
PICO-8 Version: 0.2.4b
Is this a bug, or a revoked feature?
Thanks in advance.
- Goober
Press X to add/remove the low health beeping sound which layers on top of one of the music channels. I have many memories of hearing it that way, so I added it in, haha.
Edit - This was created with respect to this post from earlier:
https://www.lexaloffle.com/bbs/?pid=98525#p
I was thinking of ways to decouple code flow from a standard procedural design and more towards what I call a chaining queue. Basically, the queue starts off with a single function. This function would then add itself with some modified parameters to the call queue via a table {func, args...}. This would closely resemble JavaScript's setTimeout() function.
This demo only uses a 1:1 call (it calls itself once, and that's it), meaning that the queue will never overfill.
I ended up making some really cool-looking color demo accidentally!
Just in time for GBJAM8.
I had to remove the breakable pots to conserve tokens.
I am literally at 8192 tokens even after making even more optimizations!
I had to add the key and door to exit the stage.
Hey again. Goober here.
This revision has me REALLY looking in every little nook and cranny I can find
to get precious tokens. I've had to resort to utilizing SECRET SPECIAL tricks
to making sure I can load as much data as possible while not using very many
tokens at all. In the beginning of my code I've created a special function:
-- handy token-saving function glb =_𝘦𝘯𝘷 function vars(...) local a={...} local b=split(a[1],",") local c=2 for i in all(b) do glb[i]=a[c] c+=1 end end |
Pair this with one of my modules - the dialog module, for example:
vars("dlg_active,dlg_id,dlg_port,dlg_snd,dlg_tpos1,dlg_tpos2,dlg_text1,dlg_text2,dlg_next,dlg_pside,dlg_cblnk,dlg_done", false,nil,0, 0,0,0, "","",0, left,0,0 ) |
This will allow me to assign multiple global variables while saving about 1/3
of the tokens. A lot of data is being converted to strings in order to save
more and more tokens. Data which doesn't need to be written to very often
may also be stored together in strings, but I haven't really gotten to that
point yet.
What will PROBABLY happen is that I'll start looking into using peek/poke
for reading and writing data. One of the problems with using poke, though, is
that each call to this is 4 tokens. [poke ( offset , value]. The ending brace
doesn't count. Peeking is about the same. Even with shorthand peeking, it takes
four tokens to assign a value.
But, there are some features added to this version!
-
When attacking and killing bats, their spheara (currency is souls) is sucked
in by the magic lantern. When the lantern takes the soul, it glows bright for
a split second. -
It's now possible to die.
While it's not possible to be hurt by the bats
right now(fixed this), you can die from spikes or falling into the water at the bottom of
the stage. This results in a neat effect! Also, when you die, a password is
displayed. While this password doesn't serve any purpose right now, I'm planning
on allowing players to input their password upon startup. - You still can't end the level yet. The key actor has not yet been implemented.
Because I'm out of tokens right now, I'll need to save some more before I can
implement this. It's in the works!
I think that's it for now. See ya!
~ Goober
View Older Posts