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:
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.
fillp"" (for this one, see the decimal value for your pattern in the console e.g.
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
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
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
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
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
1 token: hacking 'if' - camera/pal
if you want to call
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
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
essentially we are forcing lua to have a floating expression without an assignment.
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
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+1] (which saves a token if
x is a literal), or the expression
tbl[x] (which saves a token):
if (#tbl>0) do_x() if (#tbl>=threshold) do_y() --to: if (tbl) do_x() if (tbl[threshold]) do_y()
note: this trick won't work if the table can contain
??? 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
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
\^+, and set color with
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
\+ 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
.. 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
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.
[Please log in to post a comment]