Log In  

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!

P#78580 2020-06-27 17:19 ( Edited 2020-06-27 17:21)

:: gearfo
2

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.

P#78583 2020-06-27 18:44
1

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
P#78585 2020-06-27 19:39 ( Edited 2020-06-27 20:57)
:: yonn

@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!

P#78611 2020-06-28 12:15 ( Edited 2020-06-28 13:40)

@yonn

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.)

P#78620 2020-06-28 15:52 ( Edited 2020-06-29 17:46)

@yonn

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
P#78641 2020-06-28 23:21
:: yonn
1

Cart #manutuniha-0 | 2020-06-29 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
1

@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!

P#78646 2020-06-29 01:00 ( Edited 2020-06-29 01:04)

@yonn

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.

P#78655 2020-06-29 10:28 ( Edited 2020-07-10 06:35)

Update: The new update of pico-8 includes a split function for string processing, which means this post is now redundant.

Edit: Previous post removed entirely. Please switch to using the split function; it will save tokens.

P#78696 2020-06-30 12:51 ( Edited 2020-07-10 06:38)
:: yonn

@remcode

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!

P#78788 2020-07-02 11:32

@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.

There are likely other perhaps more efficient implementations already on the BBS, but if it will save anyone looking:

--this function specific to yonn's data-format
function processdata()
 --puts data into texttable

 --replace datastring with your own data
 local datastring="1…2…textone∧3…4…texttwo"
 local temp=split(datastring,"∧")

 texttable={}
 for i=1,#temp do
  local item=add(texttable,split(temp[i],"…"))
  for j=1,2 do
   item[j]=tonum(item[j])
  end
 end
end

--this function a generic split
function split(s,ch)
 --accepts string s, character ch
 --returns a table of multiple strings split at each instance of ch

 local t={}

 local function innersplit()
  local p=1

  while p<=#s do
   if sub(s,p,p)==ch then
    add(t,sub(s,1,p-1))
    s=sub(s,p+1)
    break
   end

   p+=1

   if p>=#s then
    add(t,sub(s,1))
    return
   end
  end

  innersplit()
 end

 innersplit()

 return t
end

P#78792 2020-07-02 14:20 ( Edited 2020-07-15 10:47)

[Please log in to post a comment]

Follow Lexaloffle:        
Generated 2020-07-15 11:48 | 0.165s | 4194k | Q:56