I'm working on my first PICO-8 game which is also the first time I really got into programming. Now I almost hit the token count but I don't really know how to shorten the code. I also admit I got really careless when writing the code and was mostly just happy whenever it worked. In at least three instances I'm 100% certain there needs to be a much more efficient way to code, any experienced programmer will probably cringe so bear with me.

But I'd really appreciate any help!

The first code is a function that adds text to the screen with each sentence delayed.

oprint2() is just a function I copied from Sophie Houldens talkthrough of Curse of Greed which adds an outline to the text.

Right now there are six texts but I want this function to work with an even greater number of texts and to able to make changes without so much hussle!

The text content and the if-statements to trigger them are always different.

The text style/colors are always the same. Right now I have individual timer variables are for each text which I realize is horrible. If it helps, I'd have no problem with all texts having the same speed.

function intro() camera() controls() if ending_init==0 then if help==1 then if game_start_t <= 320 then oprint2(" move\n\n ⬅️ ⬆️ ⬇️ ➡️",8,24,0,15) end if game_start_t >= 180 and game_start_t <= 460 then oprint2("\n\n search for hints\n\n ❎",8,44,0,15) end if game_start_t >= 370 and game_start_t <= 530 then oprint2("\n\nsearch through our belongings\n\n ❎",8,64,0,15) end if game_start_t >= 500 and game_start_t <= 700 then oprint2("\n\n surrender\n\n 🅾️",8,84,0,15) end if game_start_t <= 710 then game_start_t += 1 end end if p.keys >= 1 and dget(2)!=1 then if key_timer <= 240 then oprint2(" a key",8,24,0,15) end if key_timer >= 50 and key_timer <= 270 then oprint2("\n\n opens either a chest\n\n or a door",8,34,0,15) end if key_timer >= 120 and key_timer <= 300 then oprint2(" and might only fit once.",8,82,0,15) end if key_timer <= 310 then key_timer+=1 else dset(2,1) end end if steponfv >= 1 and dget(3)!=1 then if stepon_timer <= 240 then oprint2("every small decision",8,24,0,15) end if stepon_timer >= 50 and stepon_timer <= 270 then oprint2("\n\n will\n\n influence",8,34,0,15) end if stepon_timer >= 120 and stepon_timer <= 300 then oprint2(" our fate.",8,82,0,15) end if stepon_timer <= 310 then stepon_timer+=1 else dset(3,1) end end if musx == 7 and musy == 3 then if restart_timer >= 350 and restart_timer <= 600 then oprint2("the heart of the desert",8,24,0,15) end if restart_timer >= 700 and restart_timer <= 1000 then oprint2("\n\nwhere everything ends\n\n and everything begins ...",8,34,0,15) end if restart_timer >= 1100 and restart_timer <= 1400 then oprint2("we came so far this time",8,54,0,15) end if restart_timer >= 1500 and restart_timer <= 1800 then oprint2("are you ready\n\n to unlearn everything\n\n again ...?",8,74,0,15) end restart_timer+=1 if restart_timer >= 1980 then poke(rnd(23800),rnd(0x100)) end end if musx == 1 and musy == 0 and hiddendoorv == 0 and greedv == 0 and steponv == 0 and steponfv == 0 and textreadv==0 and ending_i==0 then if east_timer >= 50 and east_timer <= 250 then oprint2("this is not the empty room ...",8,24,0,15) end if east_timer >= 200 and east_timer <= 300 then oprint2("we shall head east\n\n and ascend!",8,44,0,15) end if east_timer <= 310 then east_timer+=1 end end if musx == 4 and musy == 0 and hiddendoorv != 0 and textreadv !=0 then if empty_timer >= 30 and empty_timer <= 120 then oprint2("my mind is restless",8,24,0,15) end if empty_timer >= 140 and empty_timer <= 220 then oprint2("the noise is unbearing",8,44,0,15) end if empty_timer <= 230 then empty_timer+=1 end end end end |

The second code teleports the player from certain map tiles to others when stepped on.

Sometimes it works two ways but then it changes to one tile in either x or y to prevent a loop.

My problem is again that the if-statements are always different so I don't know how to group them together and sometimes there are multiple if-statements or they require different variables than p.x and p.y.

function desert_teleport() if ending_init== 0 then if greedv >= greed_one then if p.x == 1 and p.y == 1 then p.x = 49 p.y = 1 end elseif hiddendoorv >= hiddendoor_one then if p.x == 4 and p.y == 6 then p.x = 88 p.y = 54 end elseif textreadv >= textread_one then if p.x == 79 and p.y == 60 then p.x = 121 p.y = 25 end end if (p.x >= 112 and p.x <= 127 and p.y == 47) or (p.y >= 16 and p.y <= 47 and p.x == 127) then p.x = flr(rnd(14)) + 97 p.y = flr(rnd(14)) + 34 elseif (p.y >= 16 and p.y <= 32 and p.x == 112) or (p.x >= 96 and p.x <= 111 and p.y == 32) then p.x = flr(rnd(14)) + 113 p.y = flr(rnd(14)) + 34 elseif (p.y >= 32 and p.y <= 63 and p.x == 96) or (p.x >= 96 and p.x <= 111 and p.y == 63) then p.x = flr(rnd(14)) + 113 p.y = flr(rnd(14)) + 17 elseif (p.y >= 2 and p.y <= 13 and p.x == 31) then p.x = flr(rnd(12)) + 65 p.y = flr(rnd(12)) + 1 elseif p.x == 79 and p.y == 60 then p.x = 17 p.y = 37 elseif p.x == 16 and p.y == 37 then p.x = 78 p.y = 60 elseif p.x == 35 and p.y == 15 then p.x = 89 p.y = 49 elseif p.x == 89 and p.y == 48 then p.x = 35 p.y = 14 elseif p.x == 40 and p.y == 0 then p.x = 99 p.y = 30 elseif p.x == 99 and p.y == 31 then p.x = 40 p.y = 1 elseif p.x == 6 and p.y == 63 then p.x = 107 p.y = 17 elseif p.x == 107 and p.y == 16 then p.x = 6 p.y = 62 elseif p.x == 92 and p.y == 31 then p.x = 19 p.y = 30 elseif p.x == 19 and p.y == 31 then p.x = 92 p.y = 32 elseif p.x == 0 and p.y == 10 then p.x = 110 p.y = 22 elseif p.x == 111 and p.y == 22 then p.x = 1 p.y = 10 elseif p.x == 16 and p.y == 28 then p.x = 110 p.y = 25 elseif p.x == 111 and p.y == 25 then p.x = 17 p.y = 28 elseif p.x == 56 and p.y == 0 then p.x = 40 p.y = 42 elseif p.x == 79 and p.y == 41 then p.x = 1 p.y = 38 elseif p.x == 0 and p.y == 38 then p.x = 78 p.y = 41 elseif p.x == 47 and p.y == 17 then p.x = 1 p.y = 56 elseif p.x == 0 and p.y == 56 then p.x = 46 p.y = 17 elseif p.x == 41 and p.y == 31 then p.x = 39 p.y = 49 elseif p.x == 39 and p.y == 31 then p.x = 88 p.y = 1 elseif p.x == 38 and p.y == 48 then p.x = 40 p.y = 30 elseif p.x == 40 and p.y == 48 then p.x = 69 p.y = 46 elseif p.x == 89 and p.y == 63 then p.x = 118 p.y = 57 elseif (p.x >= 16 and p.x <= 31 and p.y == 0) or (p.x >= 97 and p.x <= 110 and p.y == 0) then p.x = 88 p.y = 10 end end if ending_init==1 then if p.x == 0 or p.x == 127 or p.y == 0 or p.y >= 63 then p.x = flr(rnd(125)) + 2 p.y = flr(rnd(61)) + 2 end end end |

The last function switches out certain maptiles with others if some conditions are met. I have a lot of different tables and functions of this kind but for brevity I'll just post two.

Compared to the first two I think they are less awful but I still feel they could be optimized.

trap_table ={109,109,109,79,79,79,79,79,49,50,24,24,25,25,40,40,41,41,96,96,112,112,75,76,91,115,117,117,100,101} function place_traps() for a=0,127 do for b=0,63 do if mget(a, b) == 109 then mset(a, b, trap_table[flr(rnd(#trap_table))+1]) end end end end function place_corrupt() for a=0,127 do for b=0,63 do if mget(a,b)==79 or mget(a,b)==5 or mget(a,b)==14 or mget(a,b)==15 or mget(a,b)==46 or mget(a,b)==47 then mset(a,b,trap_table[flr(rnd(#trap_table))+1]) elseif mget(a,b)==17 or mget(a,b)==52 then mset(a,b,106) elseif mget(a,b)==18 or mget(a,b)==88 then mset(a,b,107) elseif mget(a,b)==19 then mset(a,b,122) elseif mget(a,b)==77 then mset(a,b,83) elseif mget(a,b)==78 then mset(a,b,84) elseif mget(a,b)==66 then mset(a,b,92) elseif mget(a,b)==82 then mset(a,b,108) elseif mget(a,b)==67 or mget(a,b)==68 or mget(a,b)==85 or mget(a,b)==74 or mget(a,b)==98 then mset(a,b,122) elseif mget(a,b)==56 then mset(a,b,93) elseif mget(a,b)==12 or mget(a,b)==13 or mget(a,b)==127 then mset(a,b,flr(rnd(2))+126) elseif mget(a,b)==28 or mget(a,b)==29 or mget(a,b)==8 then mset(a,b,flr(rnd(2))+7) elseif mget(a,b)==53 or mget(a,b)==54 or mget(a,b)==124 then mset(a,b,flr(rnd(2))+123) end end end end |

Sorry for so much text but I thought I'd just post them 1:1 like they are in the game.

Thanks for any help!

There are three things happening in place_corrupt. You're reading a tile value, calculating something based on that value, and writing a new value. Right now those three things are all mixed together, and so you're calling mget() and mset() lots of times with the same arguments. But you could save a lot of tokens by pulling it apart into three sections:

(I haven't tested this)

function place_corrupt() for a=0,127 do for b=0,63 do local old,new -- read old value old=mget(a,b) -- calculate new value if old==79 or old==5 or old==14 or old==15 or old==46 or old==47 then new=trap_table[flr(rnd(#trap_table))+1] elseif old==17 or old==52 then new=106 elseif ... elseif old==53 or old==54 or old==124 then new=flr(rnd(2))+123 end -- write new value mset(a,b,new) end end end |

In desert_teleport() you test p.x and p.y a lot, and you can do something similar. Assign p.x and p.y to local variables and then test the local variables, update the local variables, and assign to p.x and p.y at the end. (You save one token every time you can replace a p.x with an x)

-- read table values: local x=p.x local y=p.y -- calculate new values: if... elseif x == 38 and y == 48 then x = 40 y = 30 elseif x == 40 and y == 48 then x = 69 y = 46 elseif x == 89 and y == 63 then x = 118 y = 57 elseif ... end -- update table values: p.x=x p.y=y |

You might find the same pattern other places in your code, where you can move function calls or table reads/writes outside of if/elseif/else logic and save some tokens. In some cases it will make your code faster, too, which is a nice bonus.

There are other things you can do, but that should get you pretty far.

In addition to eliminating a bunch of property references using local variables, you can also get rid of a lot of the 'if' statements by referencing values contained in strings. The new CHR and ORD functions make this very easy and compact. Here are some bits of code that can consolidate a bunch of your specific-condition 'if' statements into a single 'for' loop and a few strings. It might be possible to do this as well with the ranged conditions, but this is a good start anyway, and it's easily expandable.

--old and new x and y values put into table form xt={79,16,35,89,40,99,6,107,92,19,0,111,16,111,56,79,0,47,0,41,39,38,40,89} yt={60,37,15,48,0,31,63,16,31,31,10,22,28,25,0,41,38,17,56,31,31,48,48,63} nxt={17,78,89,35,99,40,107,6,19,92,110,1,110,17,40,1,78,1,46,39,88,40,69,118} nyt={37,60,49,14,30,1,17,62,30,32,22,10,25,28,42,38,41,56,17,49,1,30,46,57} --empty strings to hold character-encoded values (empty spaces for separation) xs=" " ys=" " nxs=" " nys=" " --encodes table values into 4 blank-space separated symbol strings --(offset of 96 avoids all problematic characters, provides possible value range of 0-159) cls() for j=1,#xt do xs=xs..chr(xt[j]+96) ys=ys..chr(yt[j]+96) nxs=nxs..chr(nxt[j]+96) nys=nys..chr(nyt[j]+96) end --pastes all 4 strings to clipboard as one giant string (need to be separated at empty spaces) printh(xs..ys..nxs..nys,'@clip') |

And here are the results (56 tokens vs. 456 in your current code)

--posted strings after proper formatting xs="にp⬇️み☉れfょもs`エpエ▤に`◆`웃♥●☉み" ys="う✽o…`○かp○○jv|y`웃●q▤○○……か" nxs="qなみ⬇️れ☉ょfsもウaウq☉aなa🅾️♥ま☉しサ" nys="✽う➡️n~aqお~█vjy|⌂●웃▤q➡️a~🅾️▥" --replaces 24 'if' statements in game logic cls() for i=1,#xs do if x==ord(xs,i)-96 and y==ord(ys,i)-96 then x=ord(nxs,i)-96 y=ord(nys,i)-96 end end |

@gearfo

your updated place_corrupt works well in that it correctly exchanges the tiles but then weird things happen with the camera (I have 16x16 rooms and the camera just switches to the next room if you pass the border). Now the camera stops following the player and I also leave a trail behind. I can't leave the room I was currently at and most weirdly, when I walk outside of the screen, I come out of the opposide side. I think I have to look at the rest of my code but overall the suggestion worked.

The second change to p.x and p.y worked perfectly and already saved me 171 tokens!

@JadeLombax

amazing, it seemed complicated at first but i got it to work really fast! I will use this method a lot!

I know gearfo has already provided code for it - I had a look at *place_corrupt* as well because you had indicated that some unusual side effects seemed to occur; you could also try something like:

function place_corrupt() local cv,nv,kv={79,5,14,15,46,47,17,52,18,88,19,77,78,66,82,67,68,85,74,98,56,12,13,127,28,29,8,53,54,124},{-1,-1,-1,-1,-1,-1,106,106,107,107,122,83,84,92,108,122,122,122,122,122,93,-126,-126,-126,-7,-7,-7,-123,-123,-123},{} for i=1,#cv do kv[cv[i]]=nv[i] end for a=0,127 do for b=0,63 do local c=mget(a,b) if kv[c] then local d=c if kv[c]>0 then d=kv[c] elseif kv[c]==-1 then d=trap_table[flr(rnd(#trap_table))+1] else d=flr(rnd(2))-kv[c] end mset(a,b,d) end end end end |

Like gearfo's, I haven't tested this fully.

- I have tested that it runs and puts the values into kv.
- I haven't set up a map or map exploration cart to test it with.
- I haven't tested that the values in my edited version are all the same as in your original version, but I believe they are.

I hope it's obvious what I've done and why I've done it - and that even if it doesn't work as it currently is that it might generate some ideas.

Some brief explanation:

- cv current value
- nv new value
- kv key-value pairs lookup table (for speed, so as not to iterate over cv/nv in another loop inside your current two loops)
*if kv[c] then*should work the same as*if kv[c]~=nil then*- -1 is a control code to get a random element from the trap table
- the numbers added to the random numbers are now all negative numbers, so testing for >0, then ==-1 inside the loops, leaves them for the
*else*(where because they are negative they are simiply deducted instead of added)

I believe that would cut the place_corrupt function from 330 tokens to 158 tokens. I don't know how that compares to gearfo's savings.

Like gearfo's code, this separates the idea into 3 parts (getting the current/old value, finding the new value, setting the new value). Similar to JadeLombax's code this uses a lookup table (but without the compression).

Looking at gearfo's code and your original code I didn't initially see why gearfo's rewrite should cause side effects, but I suspect it's because mset(a,b,new) always occurs, even when new hasn't been set, so some values of the map are set to nil. That could easily be altered to *if new~=nil then mset(a,b,new)* then perhaps my version will prove unnecessary.

Edit: Initialised *local d=c* though this shouldn't make a difference because all paths through the inner *if* in my code set a value for d anyway. (It previously read *local d=nil* where *=nil* was redundant anyway.)

As far as I can tell this should work for intro(); I assigned values to every variable mentioned to test it, but my test was not robust (I don't know what else is being done with the timers or the variables tested in the conditions, so I was happy just to get any text to display, and leave more exacting testing to you).

As I didn't know what else might be affected by the timers, I haven't changed them, even though you indicated that how they are at the moment is not desirable. If you wanted to get it down to one timer, you would need to set up some sort of state variable to indicate which text should be accessed, with each text having a different value of text_state. Some of the code may look similar to *text_state=1* with for a condition check, *if text_state==1 and help==1* (for the first text); there would likely be complications in getting the number of timers down dependent on what else is going on with them in other parts of your code.

I haven't changed the data, although you said some parts it was okay to change.

Hopefully the changes I have made (work and) will make it easier for you to change it in other ways or add new sentences.

Written like this it will save you 50 tokens (down from 441 to about 390). There may be more savings available; for example if you continue to keep the same colors you could unpack the sentence, x, and y to variables using multiple assignment *local sentence,x,y=unpack(lookup[element][3])* then *oprint(sentence,x,y,0,15)* this would allow you to delete all the *,0,15* from the end of the sub-tables (I have presumed the numeric variables are x,y,color1,color2). The value of *x* looks like it's always the same too, at the moment, so that could be removed from the sub-tables too.

If it's needed to explain what I've done, the following link is about functions inside other functions: https://www.lua.org/pil/6.1.html

I have used a function inside a function because I didn't feel it needed to be in the global scope, and didn't know whether you might already have a function called *show* (it's a very generic name). *Show* just collects some repeated code into one place and has data passed to it for that code to use.

The only other change was to put the sentences and timer values into tables so they could be easily passed to the *show* function.

function intro() local show=function(value,lookup) for element=1,#lookup do if value>=lookup[element][1] and value<=lookup[element][2] then oprint2(unpack(lookup[element][3])) end end end camera() controls() if ending_init==0 then if help==1 then show(game_start_t,{{0,320,{" move\n\n ⬅️ ⬆️ ⬇️ ➡️",8,24,0,15}}, {180,460,{"\n\n search for hints\n\n ❎",8,44,0,15}}, {370,530,{"\n\nsearch through our belongings\n\n ❎",8,64,0,15}}, {500,700,{"\n\n surrender\n\n 🅾️",8,84,0,15}}}) game_start_t=min(game_start_t+1,710) end if p.keys >= 1 and dget(2)!=1 then show(key_timer,{{0,240,{" a key",8,24,0,15}}, {50,270,{"\n\n opens either a chest\n\n or a door",8,34,0,15}}, {120,300,{" and might only fit once.",8,82,0,15}}}) if key_timer <= 310 then key_timer+=1 else dset(2,1) end end if steponfv >= 1 and dget(3)!=1 then show(stepon_timer,{{0,240,{"every small decision",8,24,0,15}}, {50,270,{"\n\n will\n\n influence",8,34,0,15}}, {120,300,{" our fate.",8,82,0,15}}}) if stepon_timer <= 310 then stepon_timer+=1 else dset(3,1) end end if musx == 7 and musy == 3 then show(restart_timer,{{350,600,{"the heart of the desert",8,24,0,15}}, {700,1000,{"\n\nwhere everything ends\n\n and everything begins ...",8,34,0,15}}, {1100,1400,{"we came so far this time",8,54,0,15}}, {1500,1800,{"are you ready\n\n to unlearn everything\n\n again ...?",8,74,0,15}}}) restart_timer+=1 if restart_timer >= 1980 then poke(rnd(23800),rnd(0x100)) end end if musx == 1 and musy == 0 and hiddendoorv == 0 and greedv == 0 and steponv == 0 and steponfv == 0 and textreadv==0 and ending_i==0 then show(east_timer,{{50,250,{"this is not the empty room ...",8,24,0,15}}, {200,300,{"we shall head east\n\n and ascend!",8,44,0,15}}}) east_timer=min(east_timer+1,310) end if musx == 4 and musy == 0 and hiddendoorv != 0 and textreadv !=0 then show(empty_timer,{{30,120,{"my mind is restless",8,24,0,15}}, {140,220,{"the noise is unbearing",8,44,0,15}}}) empty_timer=min(empty_timer+1,230) end end end |

@remcode

I never expected so much great feedback!

Your first change for place_corrupt() worked perfectly. I copy pasted it 1:1 and it instantly worked, wow!

I tried to understand it but I don't fully get it, for example the negative values. I'll try to dissect it more thoroughly tomorrow.

I was about to apply it to my other functions but compared to place_corrupt() the others are a lot shorter because they are only looking for a handful of different sprites. I realized just have a lot of different functions of this kind.

The changes for intro() worked nicely. I tested all the texts and everything worked except the "east timer". I guess it tests by far for the most variables so there might be some complications.

The timers are not used anywhere else which is why I felt so bad about using them. I wouldn't mind it too much if all texts had the same speed. So basically the only things that change are the if-statements and the texts and I'd love it to add some more texts without losing so many tokens.

Thank you so much for your help so far and if you're interested I also added the cartridge!

What a strange - but good - experience the game is.

About the minuses; essentially I have used the minus sign for one purpose, to show that the value shouldn't be set directly.

if kv[c] then local d=c if kv[c]>0 then --positive values end up here --and will end up set directly d=kv[c] elseif kv[c]==-1 then --negative one ends up here, and is just a flag d=trap_table[flr(rnd(#trap_table))+1] else --[[ negative anything else ends up here and as when you minus a negative you get a positive the negative is effectively flipped and added on to a rnd(2) so the code here sets d (the new value for mset) so it will be the equivalent value these would have set in the original mset(a,b,flr(rnd(2))+126) mset(a,b,flr(rnd(2))+7) mset(a,b,flr(rnd(2))+123) when, respectively, kv[c] is -126,-7, or -123 because my signal to come to this else is the minus sign I needed to reinterpret the formula to account for the minus sign and the easiest way to do this was simply to deduct the value instead of adding it ]] d=flr(rnd(2))-kv[c] end |

Unless you need to separate other values that are set directly from ones that aren't set directly, using the minuses (which map values won't be set to directly) is possibly only of use here.

I have no idea why my code should not work for east_timer, but work for everything else. This seems quite odd. I can't see any typo that I introduced, nor detect any change that I feel looks significant.

There are some other savings that your code could have.

Do rewrite my intro code so that x,color1, and color2 don't need to be passed to the show function and so can be deleted from the sub-tables when show is called; this should be easy to do.

Your map tile settings in tab 1 could (probably) be coded with the excellent compression routine that JadeLombax provided.

There are, as you know, a number more long lists of if statements that could be rewritten with the techniques (or similar techniques) to those gearfo, JadeLombax, and myself have mentioned here. Particularly those that test the same variables all the time, then set the same (different/same set of) variables all the time.

In tab 8, in pal_dark, a shorter way to write a palette change is:

pal({[0]=7,12,11,13,10,14,15,4,1,10,4,8,6,3,2,5}) |

I believe the use of tables in pal commands was introduced only recently. As tables are 1 based by default, the [0] is needed to redefine color 0, otherwise the redefinition will start at color 1.

In tab 9, although it would be slightly less clear you could use pal statements such as *pal(1,129,1)* instead of *pal(1,1+128,1)*.

My presumption is the following would also save tokens.

In the draw_win_lose function in tab 5, and throughout tab 6 (as well as in the intro function) you have a number of strings all separate from one another, it's possible to encode them into a single string (or a smaller number of strings anyway; I'm unsure what the maximum string length for pico-8 is). You would need to have a separator character (or characters) and a split or separator function, an example data format (using pico-8's shift+w and shift+q for separators) might be:

"40…37…hidden inside these walls,\n no matter the cost:\n we have to find it.\n\n we read this sign\n again and again.∧39…32…\ngoing back to a room\n already visited:\n\n impossible ...!" |

You would then need to process these, (edit) the new version of pico-8 has a split function which should make this easy.

(If some of that didn't make sense, it's probably me - I'm a little tired.)

Edit: Corrected an error. If you saw it, used it, and it causes a problem, I accept responsibility. On a similar note, in *place_corrupt* where I had written the code *d=trap_table[ceil(rnd(#trap_table))]* please change it from *ceil* back to your original *flr*: *d=trap_table[flr(rnd(#trap_table))+1]*. My *ceil* was in error - for ceil(0) it will obviously fail. I am correcting this error in my post. It was a careless slip up (caused by overeagerness to save a token).

Edit: Switched to recommending the new split function near the end.

I skimmed through all of my code and used your tips and could save 500+ more tokens! And I haven't done any more extensive rewrites yet.

Which was the error in your previous text you are talking about?

Before you wrote your last comment I indeed changed all of flr()+1 in my code to ceil() but so far didn't got any error. I probably triggered place_traps and place_corrupt alone more than two dozen times. Correct me if I'm wrong but even on rnd(1) the chance to roll exactly 0 should be just 1:100000. But to be save I will change it back.

I added some new stuff so the token count is back at 7.4k. However I had more problems with the compressed size; I deleted a lot of comments and unused code but it's still at 96%.

Because I'll visit my family I'll take a short break working on the project but get back to it at the end of next week and reply with the results of more testings!

@yonn I'm glad you could save that many tokens. =)

The error was just me blindly repeating ceil having erred with it already. As you're changing it back to flr, all is good.

You are correct in thinking that the probability is remote for a 0 to occur. With pico-8's number format I might guess it's just a little more frequently than 1:100000. The problem is, with enough code with this in, eventually rnd will generate that 0; in some situations it might not be a bug fatal to the program (when something else compensates for values outside the expected) but in other situations it will in one way or another be a game-stopping bug. When it is iterated over a few hundred times the chance of it occuring can increase significantly. Best to avoid it.

If you have code with flr(rnd(*some_value*))+1 more than 4 times, it might be worth giving it its own function, something like:

function rndplus1(n) return flr(rnd(n))+1 end |

This is 11 tokens according to my pico-8, and a call to it just 3 tokens, so should save 4 tokens per call - after three or four calls to it are necessary, it comes out slightly better token-wise than having flr(rnd(*some_value*))+1 written out every time. (It appears to work when an argument isn't supplied as well, although all it returns then is 1.)

Compressed size I can't help with, other than to say I saw a comment recently that the more repetition there is in your code, the more it can be compressed - the comment was where someone had edited their program to try to save space and had caused the compressed size to increase because some repetition had been removed.

Edit:

One option for reducing the size of a cart is consider if you can use mutliple carts; this might not be suitable in all deployment situations, pico-8.txt says the following about it:

:: Exporting Multiple Cartridges Up to 16 cartridges can be bundled together by passing them to EXPORT, when generating stand-alone html or native binary players. EXPORT FOO.HTML DAT1.P8 DAT2.P8 GAME2.P8 During runtime, the extra carts can be accessed as if they were local files: RELOAD(0,0,0x2000, "DAT1.P8") -- load spritesheet from DAT1.P8 LOAD("GAME2.P8") -- load and run another cart |

I haven't done this myself, so I am unaware of the capabilites and limitations of this approach.

Edit: The new version of pico-8 includes a split function, useful for splitting a long data string (as I mentioned before) into lots of smaller strings in a table. If you update pico-8, you can use this split function and save tokens. Because of this, and because it was an inefficient draft anyway, I have removed my previous functions for processing long strings. The principle of having a long string with separator characters in it remains the same - just that split will do it for a far lower token count. Just split it into whatever data format you're going to use it in (my intermediate step was not token efficient).

On the offchance you can't update (for example, perhaps your version of pico-8 came pre-installed) I am including a split function below; I'm not going to bump the thread for this as 1) the inbuilt split function will be more efficient; 2) I presume most pico-8 users have or can get the new pico-8; 3) there are probably other better implementations already - this is just a convenience.

[Please log in to post a comment]