I'm working on the classic memory matching card game, Concentration. My work-in-progress is below, but I have a beginner question regarding best practices when coding on the Pico-8.
The grid of cards is currently maintained using a 2D Lua table with each cell containing a few properties such as the picture to display and whether it's flipped or not. This is working well enough, but... I could just as easily store the card faces in the Pico-8's map data. The same goes for the collection of cards collected by each player. In fact, this game doesn't need to store any values greater than can be fit into 8 bits so I could potentially store all data this way. I'd simply be exchanging Lua table lookups with memory reads and writes.
I guess my question to more experienced coders is whether leaning on Lua data structures or Pico-8 memory structures is preferred? I don't expect that there is one simple answer to that, but are there pros and cons I might not be aware of after having read through the manual a few times?
depends on what you're doing of course.
for the cpu you're using in your card game, tables are a no-brainer I think (unless you really want to go old school).
if your game is bordering on 100% cpu you might want to use peeks and pokes in the map data. I stay away from mset/mget as they're even slower than table access if my memory serves me well.
also you have more memory available with tables, even though you'll be using more actual bytes per cell. (1Mb in lua memory vs 4k-8k in map)
it seems timings will change in the next version though...
(edit) on second thought, mset/mget make it really easier for small 2d grids and low cpu usage. 2d tables are a little painful to initialize. but still a matter of taste in the end.
You get enough RAM for Lua data structures that I don't think it's ever worth using the addressable RAM just for data structures. The only reason I can think of would be doing something tricky like exploiting optimized graphics operations to affect memory for non-graphics (or non-traditional graphics) purposes.
Some cool visual hacks manipulate addressable RAM in various ways. See Trasevol Dog's amazing work and dev write-ups (and back him on Patreon!).
My old and slow Game of Life implementation attempted to use the screen RAM itself as the source of truth for the game board. In that thread, innomin made an interesting optimization based on palette remapping.
There have been previous experiments with non-traditional uses of addressable cart ROM (the initial data in RAM) for storing structured game data or alternative media. My compression experiment stored compressed text in the graphics region to avoid storing it in the code region. (See also zep's compression routines and also axnjaxn's lzw implementation.) But even with cart ROM, I think we've seen more practical uses of compression going the other direction, e.g. storing title screens as encoded strings in the code region to preserve the graphics cart region for sprites.
There is a good reason to use map/ram: You can easily create saved games using cstore and reload. Makes sense for lengthy games only of course :)
|2d tables are a little painful to initialize|
I had this experience, too. Since then I tend to use tables like this: t[x..","..y] = something. All cells which are not initialized are just nil, which was a good fit in my case.
|You get enough RAM for Lua data structures that I don't think it's ever worth using the addressable RAM just for data structures.|
I hit the Lua memory limit fairly quickly when creating Colony on a tiny planet. All the resource connections between buildings take up a lot of space. For some data structures, I had to switch to user data memory to get a more efficent implementation.
It's certainly a work-in-progress. Those popup messages could have easily gone underneath, but I wanted to play around with palette swapping and transparencies. Having them position intelligently is on the todo list.
I'm kinda stuck deciding how best to handle animations now.
Tip: One way to store a 2D grid in a 1D table is by linearizing it, as follows:
index = width * y + x
cell = table[index]
Where 'width' is the width of your grid.
The inverse operation gives you the coordinates of a given index:
x = index % width
y = flr(index / width)
I haven't settled on whether this strategy is better than table[y][x], in terms of clarity, token count nor performance.
I wrote you on your main game page but to answer your question here, yes, you can indeed use the MAPPER as a 2D array if you decide not to create your own custom arrays and don't plan to plot anything from the MAPPER.
It can be used thus:
cls() for i=0,7 do for j=0,7 do mset(j,i,flr(rnd(256))) end end for i=0,7 do for j=0,7 do print(mget(j,i),j*16,i*6+64) end end print("",0,0)
With MSET() to set a value and MGET() to retrieve a value.
The FLR() function ensures the random number chosen is integer, which is actually not necessary as you can only store an integer from 0 to 255 per map cel.
So you could reword that line to read:
And it will still work just as well.
Mind you, you are limited to 128x32 for map storage if you plan to use all 256-of your sprite space. If not, you can go as high as 128x64 - although you are encouraged to use true Pico-8 arrays, you can use this method to store temporary small tables.
HOPE THIS HELPS !
[Please log in to post a comment]