Difference between revisions of "Byte Battle"
(Added an article specifically for bytebattle tricks) |
|||
Line 2: | Line 2: | ||
Bytebattles are a form of live coding, similar to Shader Showdowns, where two contestants compete in writing a visual effect in 25 minutes. The coding environment is the [[Fantasy Consoles|TIC-80 fantasy console]]. However, unlike Shader Showdowns, there is an additional limit: the final code should be less than 256 characters. This requires the contestants to use efficient code (e.g. single letter variables) and to minimize the code (e.g. remove the whitespace), all within the time limit. Unlike normal TIC-80 coding, there is no compression, so every character counts. | Bytebattles are a form of live coding, similar to Shader Showdowns, where two contestants compete in writing a visual effect in 25 minutes. The coding environment is the [[Fantasy Consoles|TIC-80 fantasy console]]. However, unlike Shader Showdowns, there is an additional limit: the final code should be less than 256 characters. This requires the contestants to use efficient code (e.g. single letter variables) and to minimize the code (e.g. remove the whitespace), all within the time limit. Unlike normal TIC-80 coding, there is no compression, so every character counts. | ||
+ | |||
== Basic optimizations == | == Basic optimizations == | ||
Line 14: | Line 15: | ||
- <code>s(w-11)</code> and <code>s(w+8)</code> both approximate <code>math.cos(w)</code>, so only <code>math.sin</code> needs to be aliased. <code>s(w-11)</code> is slightly more accurate, with the cost of one more character. | - <code>s(w-11)</code> and <code>s(w+8)</code> both approximate <code>math.cos(w)</code>, so only <code>math.sin</code> needs to be aliased. <code>s(w-11)</code> is slightly more accurate, with the cost of one more character. | ||
+ | |||
+ | |||
+ | == One-lining == | ||
+ | |||
+ | Most whitespace can be removed from LUA code. For example: <code>x=0y=0</code> is valid. All new lines can be removed or replaced with space, making the whole code a single line: | ||
+ | |||
+ | <syntaxhighlight lang="lua"> | ||
+ | function TIC()cls()for i=0,32639 do poke4(i,i)end end | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | '''Warning: Letters <code>a-f</code> and <code>A-F</code> after a number cause problems.''' <code>a=0b=0</code> is not valid code. It is advisable to only used one letter variables in the ranges <code>g-z</code> and <code>G-Z</code> from the start; this will make eventual one-lining easier. | ||
+ | |||
+ | |||
+ | == Load-function == | ||
+ | |||
+ | Function <code>load</code> takes a string of code and returns a function with no named arguments, with the code as its body. It's particularly useful for shortening the TIC function after it's onelined: | ||
+ | |||
+ | <syntaxhighlight lang="lua"> | ||
+ | TIC=load'cls()for i=0,32639 do poke4(i,i)end' | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | As a rule of thumb, one-lining and using the load trick can bring a ~ 275 character code down to 256. | ||
+ | |||
+ | <code>load</code> can be even used to minimize a function with parameters: <code>...</code> returns the parameters. For example, the following saves 3 bytes: | ||
+ | |||
+ | <syntaxhighlight lang="lua"> | ||
+ | SCN=load'r=...poke(16320,r)' | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | '''Warning: The backslash causes problems when using the load trick.''' In particular, if you have a string with escaped characters in the original code i.e. <code>print("foo\nbar")</code>, then this needs to be double-escaped: <code>load'print("foo\\nbar")'</code> | ||
+ | |||
+ | |||
+ | == Dithering == | ||
+ | |||
+ | If you have a floating point color value, TIC-80 <code>pix</code> and <code>poke4</code> functions round it (toward zero). To add dithering, add a small value, between 0 and 1, to the color. The best technique depends whether you have <code>x</code> and <code>y</code> available or only <code>i</code> and how many bytes you can spare: | ||
+ | |||
+ | {| | ||
+ | ! Expression || Length || Result || Notes | ||
+ | |- | ||
+ | | || || [[File:No dithering.png]] || No dithering | ||
+ | |- | ||
+ | | <code>s(i)*99%1</code> || 9 || [[File:Random dithering.png]] || "random" dithering, quite ugly | ||
+ | |- | ||
+ | | <code>i*481/960%1</code> || 11 || [[File:Chess dithering.png]] || chess horse | ||
+ | |- | ||
+ | | <code>(x*2-y%2)%4/4</code> || 13 || [[File:Block dithering.png]] || 2x2 block dithering | ||
+ | |- | ||
+ | | <code>(i*2-i//80%2)%4/4</code> || 17 || [[File:Block dithering from i.png]] || 2x2 block dithering (almost), from i only | ||
+ | |} | ||
+ | |||
+ | A quick example demonstrating the 2x2 block dithering: | ||
+ | |||
+ | <syntaxhighlight lang="lua"> | ||
+ | function TIC() | ||
+ | cls() | ||
+ | for i=0,2399 do | ||
+ | x=i%240 | ||
+ | y=i//240 | ||
+ | poke4(i,x/30+(x*2-y%2)%4/4) | ||
+ | end | ||
+ | end | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | |||
+ | == Palettes == | ||
+ | |||
+ | The following palettes assume that <code>j</code> goes from 0 to 47. Usually there's no need to make a new loop for this: just reuse another loop with <code>j=i%48</code>. | ||
+ | |||
+ | {| | ||
+ | ! Expression || Length || Result || Notes | ||
+ | |- | ||
+ | | <code>poke(16320+j,j*5)</code> || 17 || [[File:Gray palette.png]] || | ||
+ | |- | ||
+ | | <code>poke(16320+j,j%3*j/.4)</code> || 22 || [[File:Blue palette.png]] || Use `(j+1)%3` or `(j+2)%3` for different colors | ||
+ | |- | ||
+ | | <code>poke(16320+j,s(j)^2*255)</code> || 24 || [[File:Rainbow palette.png]] || | ||
+ | |- | ||
+ | | <code>poke(16320+j,s(j/15)*255)</code> || 25 || [[File:Blue-brown palette.png]] || `s(j/15)^2` is less bright | ||
+ | |} | ||
+ | |||
+ | Code for testing palettes: | ||
+ | |||
+ | <syntaxhighlight lang="lua"> | ||
+ | function TIC() | ||
+ | cls() | ||
+ | for j=0,47 do poke(16320+j,s(j/15)*255)end | ||
+ | for c=0,15 do rect(c*5,0,5,5,c)end | ||
+ | end | ||
+ | s=math.sin | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | |||
+ | == Motion blur == | ||
+ | |||
+ | In TIC-80 API, the <code>pix</code> and <code>poke4</code> functions round numbers towards zero. This can be abused for a motion blur: <code>poke4(i,peek4(i)-.9)</code> maps colors 1 to 15 into one lower value, but value 0 stays at it is. Like so: | ||
+ | |||
+ | <syntaxhighlight lang="lua"> | ||
+ | function TIC() | ||
+ | t=time()/9 | ||
+ | circ(t%240,t%136,9,15) | ||
+ | for i=0,32639 do poke4(i,peek4(i)-.9)end | ||
+ | end | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | |||
+ | == Basic raymarcher == | ||
+ | |||
+ | The basic structure for a raymarcher that has not been crunched to keep it readable. The map is bunch of repeated spheres here: | ||
+ | |||
+ | <syntaxhighlight lang="lua"> | ||
+ | function TIC() | ||
+ | for i=0,32639 do | ||
+ | -- ray (u,v,w), not normalized! | ||
+ | u=i%240/120-1 | ||
+ | v=i/32639-.5 | ||
+ | w=1 | ||
+ | -- camera origo (x,y,z) | ||
+ | x=3 | ||
+ | y=0 | ||
+ | z=time()/999 -- camera moves with time | ||
+ | j=0 | ||
+ | repeat | ||
+ | X=x%6-3 -- domain repetition | ||
+ | Y=y%6-3 | ||
+ | Z=z%6-3 | ||
+ | -- ray not normalized=>reduce scale | ||
+ | m=(X*X+Y*Y+Z*Z)^.5/2-1 | ||
+ | x=x+m*u | ||
+ | y=y+m*v | ||
+ | z=z+m*w | ||
+ | j=j+1 | ||
+ | until j>15 or m<.1 | ||
+ | poke4(i,j) | ||
+ | end | ||
+ | end | ||
+ | </syntaxhighlight> | ||
+ | |||
== Additional Resources == | == Additional Resources == | ||
* Code from past bytebattles https://livecode.demozoo.org/ | * Code from past bytebattles https://livecode.demozoo.org/ |
Revision as of 14:28, 17 February 2022
Contents
Bytebattles
Bytebattles are a form of live coding, similar to Shader Showdowns, where two contestants compete in writing a visual effect in 25 minutes. The coding environment is the TIC-80 fantasy console. However, unlike Shader Showdowns, there is an additional limit: the final code should be less than 256 characters. This requires the contestants to use efficient code (e.g. single letter variables) and to minimize the code (e.g. remove the whitespace), all within the time limit. Unlike normal TIC-80 coding, there is no compression, so every character counts.
Basic optimizations
- Functions, that are called 3 or more times should be aliased. For example, e=elli
with e()e()e()
is 3 bytes shorter than elli()elli()elli()
. Functions with 5 characters may already benefit from aliasing with 2 calls: r=rectb
with r()r()
is 1 character shorter than rectb()rectb()
.
- t=0
with t=t+.1
is 3 bytes shorter than t=time()/399
.
- for i=0,32639 do x=i%240y=i/240 end
is 2-3 bytes shorter than for y=0,135 do for x=0,239 do end end
.
- (x*x+y*y)^.5
is 6 bytes shorter than math.sqrt(x*x+y*y)
.
- s(w-11)
and s(w+8)
both approximate math.cos(w)
, so only math.sin
needs to be aliased. s(w-11)
is slightly more accurate, with the cost of one more character.
One-lining
Most whitespace can be removed from LUA code. For example: x=0y=0
is valid. All new lines can be removed or replaced with space, making the whole code a single line:
function TIC()cls()for i=0,32639 do poke4(i,i)end end
Warning: Letters a-f
and A-F
after a number cause problems. a=0b=0
is not valid code. It is advisable to only used one letter variables in the ranges g-z
and G-Z
from the start; this will make eventual one-lining easier.
Load-function
Function load
takes a string of code and returns a function with no named arguments, with the code as its body. It's particularly useful for shortening the TIC function after it's onelined:
TIC=load'cls()for i=0,32639 do poke4(i,i)end'
As a rule of thumb, one-lining and using the load trick can bring a ~ 275 character code down to 256.
load
can be even used to minimize a function with parameters: ...
returns the parameters. For example, the following saves 3 bytes:
SCN=load'r=...poke(16320,r)'
Warning: The backslash causes problems when using the load trick. In particular, if you have a string with escaped characters in the original code i.e. print("foo\nbar")
, then this needs to be double-escaped: load'print("foo\\nbar")'
Dithering
If you have a floating point color value, TIC-80 pix
and poke4
functions round it (toward zero). To add dithering, add a small value, between 0 and 1, to the color. The best technique depends whether you have x
and y
available or only i
and how many bytes you can spare:
A quick example demonstrating the 2x2 block dithering:
function TIC()
cls()
for i=0,2399 do
x=i%240
y=i//240
poke4(i,x/30+(x*2-y%2)%4/4)
end
end
Palettes
The following palettes assume that j
goes from 0 to 47. Usually there's no need to make a new loop for this: just reuse another loop with j=i%48
.
Code for testing palettes:
function TIC()
cls()
for j=0,47 do poke(16320+j,s(j/15)*255)end
for c=0,15 do rect(c*5,0,5,5,c)end
end
s=math.sin
Motion blur
In TIC-80 API, the pix
and poke4
functions round numbers towards zero. This can be abused for a motion blur: poke4(i,peek4(i)-.9)
maps colors 1 to 15 into one lower value, but value 0 stays at it is. Like so:
function TIC()
t=time()/9
circ(t%240,t%136,9,15)
for i=0,32639 do poke4(i,peek4(i)-.9)end
end
Basic raymarcher
The basic structure for a raymarcher that has not been crunched to keep it readable. The map is bunch of repeated spheres here:
function TIC()
for i=0,32639 do
-- ray (u,v,w), not normalized!
u=i%240/120-1
v=i/32639-.5
w=1
-- camera origo (x,y,z)
x=3
y=0
z=time()/999 -- camera moves with time
j=0
repeat
X=x%6-3 -- domain repetition
Y=y%6-3
Z=z%6-3
-- ray not normalized=>reduce scale
m=(X*X+Y*Y+Z*Z)^.5/2-1
x=x+m*u
y=y+m*v
z=z+m*w
j=j+1
until j>15 or m<.1
poke4(i,j)
end
end
Additional Resources
- Code from past bytebattles https://livecode.demozoo.org/