Log In  


hello!

sorry if this is "yet another" boring token-saving post (haven't been around long enough to know how common they are), but here's a list of (fairly) generalizable token saving tricks i've compiled while working on my upcoming game, in case it might be helpful to anyone. i think there are some on here that i haven't seen elsewhere on bbs, but also some of these may be pretty basic :)

this list does not include unpack(split()) tricks, or _ENV tricks, which can both have enormous token windfalls.

also, by far the best way to save tokens is not using these tricks, but to stare at your code hard... really hard... until something clicks and you realize you can convey 3 variables' worth of information with one, that you can reuse functions, that you're doing the same computation throughout your code many times and can use a variable, etc. or you know, to delete stuff.

these are kind of last-resort, please god just 3 more tokens sorts of things (although they do add up). hope they are of some use. with that aside, let's begin!

2 tokens: flr to \

this one is basic but it took me a while to learn about \. flr(a/b) can be written as a\b, saving 2 tokens. if flooring the result of a multiplication, assuming that one side is a constant, you can just invert: flr(a*2.5) to a\0.4

2 tokens: ceil to \ !?

this one is situational, but interestingly floor "decreases" a number even if it's negative, making it kind of function a bit like ceil in the opposite direction. the token save here only works if you can already negate the expression for free:

a+=ceil(p*2)

--to: (note += changes to -=)

a-=p\-.5

of course you can use order of ops to fix any precedence issues this might cause (this time moving the negation to a constant in case there's no +=/-=):

a=2*ceil(p*2)

--to:

a=p\-.5*-2

1 token: calling apis with string params

since lua lets you call functions without parens if the only argument is a string/table literal, you can save a token on many standard api calls, e.g. sfx(5) to sfx"5", stat"", rnd"", btnp"", btn"", dget"", fillp"" (for this one, see the decimal value for your pattern in the console e.g. fillp"23130.5" for fillp(▒)), or even poke"" if you want to set that value to 0.

this will also work on your own functions too! if you call functions with a single truthy value, you can do fn"1" instead of fn(true), you can even do this if you call a function with a single numeric argument, but keep in mind this won't work if you do any comparisons with that argument. "3"+3 is allowed in lua, but not "3">3!

1 token: foreach

foreach(a, function(x) end) saves a token over for x in all(a) do. note that this means you can't break or return early, and it does come at the cost of performance (since you are introducing the overhead of function calls). it's not worth the token for nested loops / huge arrays!

1 token: next, inext

easy one:

for k,v in pairs(tbl) do
end

--to:

--unordered
for k,v in next,tbl do
end

--ordered but only monotonically increasing past 1 are guaranteed:
for k,v in inext,tbl do
end

1 token: use return value of add/del/deli

if you can guarantee that the item being added/removed is present, you can replace a reference with the function:

add(a,b)
b.x=y

--to:

add(a,b).x=y

1 token: hacking 'if' - add/del/deli

if you call add, del, or deli, the call will only "go through" if the first argument (the list) is a list. if it's falsey, it's a no-op. so you can save a token with:

if (x) add(arr, i)
--to:
add(x and arr,i)

note also that these functions are nil-forgiving for the second argument, too - add(arr,nil) does nothing, so you don't need if(x) add(arr,x)

1 token: hacking 'if' - camera/pal

if you want to call camera with x under condition c, save a token with:

if (c) camera(x)

--to:

camera(c and x)

note that this resets the camera if not c (which is actually sometimes what you want!)

the same applies to pal:

pal(c and unspl"5,6,7")

1 token: hacking 'if' - function call next to assignment

if you want to call any function under a condition, if there is assignment (or a function call that can take an extra argument) nearby, you can remove the if and save a token:

if (x) f()
k=v

--to:

k=v,x and f()

note that in this case the order matters! if it was the other way around:

k=v
if (x) f()

you can't make the same conversion if f() relies on the new value of k.

function call next to fully-loaded function call

note that this same trick works if you are calling another function nearby with all of its parameters passed:

if (x) f()
pset(50,50,9)

--to:

--pset doesn't take a 4th argument, but who cares!
pset(50,50,9,x and f())

same caveat applies here with ordering - f() will be called before pset()

essentially we are forcing lua to have a floating expression without an assignment.

not as or

in all of the 'hacking if' cases above, if your condition x is of the form not y, you can usually do or y instead of and not y!

1-3 tokens: hacking 'if' - function calls in ternary

finally, this is kind of specific, but let's say you have an expression that should go one way under one condition, and another way otherwise. but let's also say that in one of those cases, you also need to make a function call.

if the call returns truthy, it can be prefixed with and to the value associated with the condition under which you want it to run:

---- pset returns truthy, so prefix with `and`
----  -3 tokens (-2 if on "truthy" side of ternary)

a=c and x or y
if(not c) pset(3,3,5)

--to:

a=c and x or pset(3,3,5) and y

if the call returns falsey, it can be prefixed with or, but you'll have to add parens:

---- sfx returns falsey, so prefix with `or` and add parens
----  -1 token (-2 if on "falsey" side of ternary)

func(c and x or y)
if(c) sfx"3"

--to:

func(c and (sfx"3" or x) or y)

1 token: max to coerce to num

you can use max() to coerce a potentially non-number value into 0: max(nil)=>0; max(false)=>0; max(5)=>5. this saves a token over x or 0 because if you are doing math you will pay an extra token for the parens:

5+(x or 0)
--to
5+max(x)

it does have limits: be aware that max(true)=>0, and max("5")=>5

2 tokens per argument: local functions

if a function is only used by one other function, you can define it inside the other (locally) and remove any arguments already in scope:

function g(a,x)
 ...
end
function f()
 local a,b,c=...
 g(a,b)
 g(a,c)
end

--to:

function f()
 local a,b,c=...
 local function g(x)
  ...
 end
 g(b)
 g(c)
end

1 token: table length checks

you can replace the expression #tbl>x with tbl[x+1] (which saves a token if x is a literal), or the expression #tbl>=x with tbl[x] (which saves a token):

if (#tbl>0) do_x()
if (#tbl>=threshold) do_y()

--to:

if (tbl[1]) do_x()
if (tbl[threshold]) do_y()

note: this trick won't work if the table can contain false!

??? tokens: practical bit ops

sometimes bit ops can be useful to save a bunch of tokens. pardon the somewhat specific examples, hopefully these get you thinking:

x!=0 and y!=0 to x&y!=0 (-2 tokens)
x==0 and y==0 to x|y==0 (-2 tokens)

(these also have the advantage of not using a boolean operator, so if you invert one of these (e.g. x|y!=0 instead of x!=0 or y!=0), you won't need parens if you and it with something)

x^^=0xf0: if x starts at 0, this toggles between 0 and a high number. if passed into e.g. camera(x), can be used to toggle showing/hiding something

0!=~0, so if you want to toggle something, say a controls option that you want to store in cartdata, you can do it pretty tersely with the binary not operator ~:

menuitem(1,"toggle foo",function() dset(0,~dget"0") end)
...
if dget"1"==0 then
 --default, since cartdata defaults to 0
else
 --toggled
end

1 token: pal color 1

another highly specific one, but if you're using pal on color 1, save a token with pal(1,x) to pal{x}

1 token: mid for range-checking

mid() can sometimes save a token when doing a range check of the form x >= 0 and x < y (or negated):

x>=0 and x<8192
--to:
mid(x,8191)==x

one extra edge case with this not involving 0 that i thought was cool:

dt<.1 or dt>.25
--to:
mid(dt,.1,.25)!=dt

--wait it's the same number of tokens...
--but the first one has an `or`, so if it needs
--to be combined with `and`, it'll need parens!

(context here is flashing a selection once, dt is the time since the selection)

2 tokens: do you really need cls()?

it's common to add cls() when starting a new project, but if you fill the whole screen each frame with e.g. map() you might not need it!

??? tokens: control codes

a LOT can be done with control codes, this is a large topic. but in general, i've found that if you are printing to the screen you generally don't ever need to pass (constant) args to print (that is, ?) since you can move the cursor with \^j and \^+, and set color with \f.

moreover, if you are printing a bunch of hard-coded text, e.g. a title screen, you generally only need a single call to print (?) since you can jump around on the screen so easily. need a shadow? first print your text in a dark color, then use \^j or \+ to jump backwards and slightly up and re-print your text in the foreground color.

i've spent so much time tweaking horrible controlcode-ridden strings, i kind of want to write a utility cart that is a kind of WYSIWYG editor for text that will spit out a single horrible string to print. EDIT: i've done this!

1 token: return value of ?

this one is a little hacky, but since ? is an alias for print that can be called without parens, you can still capture its return value if you need to get the max x-drawn. just make sure you add a newline after the last argument to ?:

-- line x1 will be max_x-3, the -3 has to be on the next line
line(
 ?text_of_unknown_length
 -3,80,8,80,9)

x+=?text_of_unknown_length

1 token: .. operator with numbers

the .. string concat operator can be weird with numbers. if it's placed directly after a number, the parser will complain, expecting a fractional component following the first .. but if you add a space...:

?"minutes: "..t\60.." seconds: "..t%60
--malformed number near '60..'!

--i thought this meant that you needed parens:
?"minutes: "..(t\60).." seconds: "..t%60

--but you can save that token if you just add a space!
?"minutes: "..t\60 .." seconds: "..t%60
13


Great job!


OMG I didn't know any of this! Thank you!


great stuff! and presented in nice concise way, too

> also, by far the best way to save tokens is not using these tricks, but to stare at your code hard... really hard... until something clicks and you realize you can convey 3 variables' worth of information with one, that you can reuse functions, that you're doing the same computation throughout your code many times and can use a variable, etc. or you know, to delete stuff.

👏


thanks for the feedback, glad this is helpful!

i just added another (situational) one, replacing ceil with \


1

I was just re-reading this (it's still excellent!) and I noticed an error:
> x!=0 and y!=0 to x&y!=0

this isn't equivalent! e.g. when x=1 y=2.

personally, I get a lot of use out of this equivalence: x<0 or y<0 to x|y<0 (mostly to save characters, but it saves tokens too in this case).



[Please log in to post a comment]