Challenge: Maximum Pixel-art, Minimum Code

I’ve been playing with the ability to directly write bytes to the screen buffer… it got me thinking… is there some really nice maths formula to draw cool screen patterns? :thinking:

Here’s the framework code :

#include <Arduboy2.h>
Arduboy2  a;
uint8_t  *b = a.sBuffer;
uint16_t &c = a.frameCount;

void setup() {
  a.begin();
}

void loop() {
  if (!a.nextFrame())
    return;

  for (int16_t i = 0; i < 1024; i++) {
    a.sBuffer[i] = /*MAGIC-->*/ i; /*<--MAGIC*/
  }
  a.display();  
}

For example:
i^(i<<1);
– draws a kind of binary tree.

The challenge is to only use a mathematical expression in place of MAGIC.
Any maths/art wizards want to conjure up something spectacular? :sparkles:

Updated 12-Jul

(Removed clear() from setup; added abbreviations for sBuffer and frameCount; simplified baseline example to i;).


Rules ~


  1. Character count for the Magic is taken between a.sBuffer[i] = to the terminating semi-colon ;. The compiled bytes are given as the increase (+) over the vanilla framework. Size isn’t everything! Aesthetic merit should be top priority. :art:
  2. Arbitrary limits: Magic should compile to < 512 bytes and have less than 100 characters. :100:
  3. The Magic must be a single statement, i.e. has just one semi-colon at the end. Use the one statement wisely!:1st_place_medal:
  4. Baffle and amaze us with your compact code. Feel free to reveal your tricks with an extended ‘readable’ version, complete with comments and line breaks… or leave us guessing! :thinking:
  5. Rules may be broken. Create something wonderful; blow our minds! :exploding_head:

Grimoire / gallery ~


@Mr.BlinkyCheckers (4)’. 7 characters → +116 bytes.

-i%2^85;
Result

Checkers-4


@acedentBinary Bonsai’. 8 characters → +6 bytes.

i^(i<<1);
Result

BinaryBonsai


@PharapCheckers (3)’. 10 characters → +14 bytes.

i&1?85:170;
Result

Checkers-3


@joyrider3774Brick Wall’. 12 characters → +110 bytes.

i%23>2?127:0;
Result

BrickWall


@acedentHoundstooth’. 16 characters → +38 bytes.

"H`\374~{\3710\30"[i%8];
Result

Houndstooth


@Mr.BlinkyCheckers (1)’. 21 characters → +36 bytes.

(i>>3&1)-1^(i>>7&1)-1;
Result

Checkers-1


@zepAncient City’. 33 characters → +28 bytes.

((((i&128)>>4)^(i&8))*31)&(i*191);
Result

AncientCity


@Mr.BlinkyRectangle’. 47 characters → +68 bytes.

(i<128)*3|(i>895)*192|(i%128<126)-1|(i%128>1)-1;
Result

Rectangle


@FMangaCheckers (2)’ (animated). 48 characters → +118 bytes.

(1<<(c/4+i&7))^(1<<((c/4-i)&7))^-((i/8^i/128)&1);
Result

Checkers-2

See original post for animated version.


@Mr.BlinkyFabric’. 49 characters → +186 bytes.

3<<((i-i/128*22)%30>>2)|192>>((i-i/128*22)%30>>2);
Result

Fabric


@brow1067Recursion’ TBC. 56 characters → +52 bytes.

i&127?b[i-1]^((b[i-1]<<1)+(i>=128?(b[i-129]>>7):0)):i==0;
Result

Recursion


@FMangaFish’ (animated). 57 characters → +66 bytes.

"\34^Zr~\x7F\x7F\x7F?=\34\14\36?w"[c/8+i*(i<<8>>15|1)&15];
Result

Fish

See original post for animated version.


@FMangaInvaders B’ TBC (animated). 68 characters → +90 bytes.

(!(c&8)^!(i&128)?i%16:-i&0xF)["\0p\30|\276\273>>>\273\276|\30\16\0"];
Result

Invaders-B

See original post for animated version.


@sparrRule-30-esque’ TBC (animated). 361 characters → +364 bytes.

(i%128<127)?b[i+1]:(c<1?(i==511)*32:((((((((b[i-1]&192)>>6)|((b[(i+127)%1024]&1)<<2))+7)%8)<4)<<7)|((((((b[i-1]&224)>>5)+7)%8)<4)<<6)|((((((b[i-1]&112)>>4)+7)%8)<4)<<5)|((((((b[i-1]&56)>>3)+7)%8)<4)<<4)|((((((b[i-1]&28)>>2)+7)%8)<4)<<3)|((((((b[i-1]&14)>>1)+7)%8)<4)<<2)|((((((b[i-1]&7))+7)%8)<4)<<1)|(((((((b[i-1]&3)<<1)|((b[(i+895)%1024]&128)>>7))+7)%8)<4))));
Result

Rule30


2 Likes

A well known pattern (might be cheating as it relies on previously computed buffer values):

i&0x7f?a.sBuffer[i-1]^((a.sBuffer[i-1]<<1)+(i>=128?(a.sBuffer[i-129]>>7):0)):i==0;
2 Likes

Wow @brow1067! I didn’t recognise the code… it was a nice surprise! :smiley: :partying_face: :triangular_ruler:
Very nicely done. Not sure if it’s ‘cheating’… but I think there’s bonus points for (1) compactness/simplicity, as well as (2) aesthetic merit. I think we should add a third criteria: (3) suitably cryptic and quirky name for each screen pattern. :wink:

I’ve had fun playing with this method. It reminds me of my first BASIC programs, that somehow generated really convincing wood-grain patterns… now lost to time…

I think @zep is really good at this on his pico8 console lots of peeks and pokes I see in his code.

1 Like

i don’t get the values they don’t seem to indicate pixels, but this is what i came up with
i%0x17>0x02?0x7F:0x00;
nothing special but looks like a brick wall

1 Like

sBuffer is the frame buffer, but it’s 1 bit per pixel (so each byte is 8 pixels) and uses an unusual column-oriented format (i.e. each byte is a column of 8 pixels) because that’s what the SSD1306 screen uses.

122aae02ad9e5ec21319b177889378bb2f73c03a

It might not be ‘cheating’, but any pixel-filling function that doesn’t rely on previously written pixel values has the advantage of being able to skip the frame buffer and be written directly to the screen.

For example, since your initial example is effectively a pure function, you could do this:

void loop()
{
	if(!arduboy.nextFrame())
		return;

	constexpr size_t size = ((Arduboy2Core::width() * Arduboy2Core::height()) / 8);

	for (size_t index = 0; index < size; ++index)
		Arduboy2Core::paint8Pixels(index ^ (index << 1));
		
	// Note the lack of the display function
}

Though it’s possible to use paint8Pixels even with non-pure (i.e. stateful) functions, as long as the state isn’t sBuffer:

// This should produce a sort of checkerboard effect
void loop()
{
	if(!arduboy.nextFrame())
		return;

	constexpr size_t size = ((Arduboy2Core::width() * Arduboy2Core::height()) / 8);

	uint8_t value = 0xAA;

	for (size_t index = 0; index < size; ++index)
	{
		Arduboy2Core::paint8Pixels(value);
		value = ~value;
	}
}

(Note: both samples are completely untested.)

1 Like

That’s a really nice looking brick wall! :brick:
Great job. Do you have a name in mind for your creation? :slight_smile:

@Pharap - Thanks. I was lazy and just waited for your handy screen buffer guide :wink:
Does Paint8Pixels add more bytes than direct sBuffer writing, after compilation?
PS- I was sure you’d have some awesome ‘magic’ to share here- was expecting Conway’s Game of Life in a single line!

The short answer: probably not because it allows you (or rather it requires you) to skip the call to display, which should theoretically reduce memory usage.

The long answer is that, as ever, it depends on what else you're doing...

If your code never uses sBuffer (directly or indirectly - i.e. you’d have to avoid using clear, display and begin) then sBuffer would be excluded from the program (because in C++ you don’t pay for what you don’t use), giving you an extra 1KB to play around with.

The thing you have to remember though is that display actually just loops through sBuffer and does the equivalent of what paint8Pixels does for each byte of sBuffer (i.e. paint8Pixels pushes an 8 pixel column to the screen over SPI).

So conceptually display is equivalent to:

for(uint8_t byte : sBuffer)
  paint8Pixels(byte);

But in reality it’s slightly more complex because:

  • A there’s a clear parameter that optionally causes the buffer to be cleared as it’s written to the screen
  • It’s actually implemented with inline assembly

Either way, the pixels have to be pushed to the display at some point. If you use sBuffer then you’re writing to sBuffer first and pushing the buffer to the screen with display, whereas using paint8Pixels circumvents the 1KB buffer and pushes straight to the screen, which naturally means you don’t have to call display, which theoretically should mean you produce less code.

It would likely be possible to do CGoL using just the screen buffer plus an extra variable or two, though that would be much more fiddly than doing it double-buffered.

Whether it’s possible to do it in a single line depends on whether you could the for loop as part of the one line. It could probably be done with a single for loop with a single nested statement.

Personally though I prefer code with nice descriptive names and decent commentry rather than trying to squash everything down as small as possible.
(People who like that sort of thing should stick to APL, where CGoL is as ‘simple’ as life ← {⊃1 ⍵ ∨.∧ 3 4 = +/ +⌿ ¯1 0 1 ∘.⊖ ¯1 0 1 ⌽¨ ⊂⍵}. :P)

In the past I’ve wasted far too much time trying to decode impenetrably terse code written with single-letter variables and no explanation of how the ‘magic’ happens.

2 Likes

Nice challenge and submissions. Made a few simple ones using ProjectAbe online:

(i < 128) * 3 | (i>895) * 192 | (i%128 < 126) - 1 | (i%128 > 1) - 1; // rectangle
(i >> 3 & 1) - 1 ^ (i >> 7 & 1) - 1; // checkers
3 << ((i-i/128*22)%30 >> 2) | 192 >> ((i-i/128*22)%30 >> 2) ;// fabric
3 Likes

Fish (updated):

"\34^Zr~\x7F\x7F\x7F?=\34\14\36?w"[c/8+i*(i<<8>>15|1)&0xF]
Spoiler

ArduboyRecording|x64


Yarduboy (updated):

pgm_read_byte(loop+131+(i>>7&1)*88+(c*(1+(i>>8))+(i-(i>>7<<7)))%88)
Spoiler

ArduboyRecording(1)

7 Likes

For anyone who cares how this works…

// Move the 7th bit into position 15 (the sign bit)
// Note that 0b1000000 is 128,
// hence this alternates every 128 columns,
// and 128 the width of the screen,
// so this alternates the sign bit between 0 and 1 for every other row of columns
auto v0 = (i << 8);

// Shift the sign bit back down to 0th place,
// thus producing either 0 or -1
// (i.e. this fills the whole word with copies of the sign bit
// because of the rules of arithmetic right shifting)
// (Note: this depends on i being a 16-bit integer,
// and would fail for more or less any other type)
auto v1 = (v0 >> 15)

// 0 → 1, -1 → -1, thus produces either 1 or -1
auto v2 = (v1 | 1);

// Either leaves the index intact or negates it,
// because x * 1 == x and x * -1 == -x
auto v3 = (i * v2);

// The whole point of the above four calculations
// is to flip the direction the image is drawn in
// every 128 columns (i.e. a full screen width)
// thus producing the alternating rows of image data

// Effectively division by 8,
// meaning the v4 cycles from 0 to 31,
// incrementing every 8 frames
auto v4 = (a.frameCount >> 3)

// Adds the possibly negated index to
// the frame value (reduced to the range 0 to 31)
auto v5 = (v4 + v3);

// Performs a modulo 16 to ensure that
// the index is within range of the image data
auto v6 = (v5 & 0xF);

// There are intentionally 16 bytes in the image,
// this is what allows the & 0xF (modulo 16) to
// constrain the index to be within image bounds
// (Note: these values are the raw ASCII values
// extracted from the string to make them more legible)
char chars[]
{
	// Block element characters
	// to illustrate how the bytes
	// correspond to the image
	0x1C, // ███░░░██
	0x5E, // █░█░░░░█
	0x5A, // █░█░░█░█
	0x72, // █░░░██░░
	0x7E, // █░░░░░░█
	0x7F, // █░░░░░░░
	0x7F, // █░░░░░░░
	0x7F, // █░░░░░░░
	0x3F, // ██░░░░░░
	0x3D, // ██░░░░█░
	0x1C, // ███░░░██
	0x0C, // ████░░██
	0x1E, // ███░░░░█
	0x3F, // ██░░░░░░
	0x77, // █░░░█░░░
	0x00, // ████████
};

// Indexes the image data
auto result = chars[v6];

Essentially it’s a very obfuscated way of doing a branchless equivalent of:

constexpr uint8_t imageSize = 16;

constexpr uint8_t image[]
{
	0x1C, // ███░░░██
	0x5E, // █░█░░░░█
	0x5A, // █░█░░█░█
	0x72, // █░░░██░░
	0x7E, // █░░░░░░█
	0x7F, // █░░░░░░░
	0x7F, // █░░░░░░░
	0x7F, // █░░░░░░░
	0x3F, // ██░░░░░░
	0x3D, // ██░░░░█░
	0x1C, // ███░░░██
	0x0C, // ████░░██
	0x1E, // ███░░░░█
	0x3F, // ██░░░░░░
	0x77, // █░░░█░░░
	0x00, // ████████
};

auto index = ((i & 0x80) != 0) ? -i : i;
auto offset = (arduboy.frameCount / 8);
auto finalIndex = ((index + offset) % imageSize);
auto result = image[finalIndex];

Or, as a one-liner:

"\x1C\x5E\x5A\x72\x7E\x7F\x7F\x7F\x3F\x3D\x1C\x0C\x1E\x3F\x77"[((((i & 0x80) != 0) ? -i : i) + (arduboy.frameCount / 8)) % 16];
3 Likes

Wow! Such great contributions. :art::microscope: :control_knobs: :alembic: :framed_picture:

I’m fascinated by the obscure (/obfuscated) style that emerges. It reminds me of those one-liner BASIC demos, printed in magazines in the 80’s-90’s to be re-typed - full of mystery and wonder! Or the more recent audio experiments of ByteBeat (which originated as a video demo too!).

I wanted to understand @joyrider3774’s ‘Brick Wall’:

i%0x17>0x02?0x7F:0x00;

Looks quite mysterious… But expanding it out a little should reveal what’s happening:

i % 23 > 2 ? 0b01111111 : 0b00000000;

I’ve been enjoying trying to create expressions with the least characters (as well as looking at compiled bytes). For values that are less than 256, they may be written more concisely in decimal form (0255), avoiding hexadecimal which adds a 2 character prefix (0x):

i%23>2?127:0;

12 characters. Nice!

Thanks @Pharap for the explanation of @FManga’s voodoo… I kept getting lost! :cold_sweat:

Woooppps… well that typo is gonna have to stay!

1 Like

I only used the hexidecimal to try and obfuscate a bit what was happening but indeed 12 chars when using normal written style is not much :slight_smile:

I still have problems trying to get the equivalent of putpixel / getpixel somehow using these oneliners not sure thats possible somehow ?

1 Like

These are insanely good!

It would be great if all entries included the spoilers showing the output!

I am not even sure how this compiles. What is the reference &loop?

Can you explain the one liner? I get there is data … and some code … within [ ] … but how does that all glue together?

2 Likes

I think you’re looking for getPixel and drawPixel.

Manipulating individual pixels will be slow though, because of the amount of behind-the-scenes bit manipulation involved.


Obfuscated is definitely the word I would use.

  • To deliberately make more confusing in order to conceal the truth.
  • To alter code while preserving its behavior but concealing its structure and intent.

Otherwise known as code golf.

I have mixed opinions about code golf…

It can be fun, it can be useful for exploring the rules of a language, and it can be interesting from an academic perspective, but sometimes just an excuse for ‘clever’ programmers to show off how ‘clever’ they are.

In general it’s not too damaging as long as people remember to keep their golfing habits away from their proper code.

If you didn’t have the 0x prefix then hexadecimal would always be either shorter or equal to the decimal representation (in terms of length).

With the prefix you have to get up to around 13 decimal digits before hexadecimal becomes (consistently) more concise.

That’s the entire point of obfuscation: to put up extra barriers that impede comprehension of the code’s behaviour.

Edit:
A good tip for understanding overly-complex expressions is to do what the compiler does (and what I did above): translate it into static single-assignment form (minus the Φ ‘phi’ instruction) by introducing one variable per sub-expression. Once you break it down into smaller steps and see which variables depend on which other variables, it often becomes a lot easier to work out what’s happening.


&loop is the address of the first byte of the loop function.

I.e. the whole snippet is basically using loop as a base address to access what I presume is the image data for the Arduboy logo.

I would also presume that the image data can move around in memory if the compiler settings change or potentially if a significant amount of other code/data is introduced, in which case the code would print a series of undecipherable pixel patterns. (The actual animation behaviour would remain the same though.)

This seems to be less about saving characters and more about intentionally obfuscating the code.

The first part is the same trick FManga used: encoding an array of bytes as a string literal rather than an array literal.* Note that there’s an implicit 0x00 on the end because of the null-terminator. (In hindsight it wouldn’t have done any harm to add an explicit \x00.)

* I would have rather used { 0x1C, ... }[index] but I suspect that’s not actually legal. (Though I haven’t checked.)

In my case I’m using hexadecimal escape codes to make the actual values of the data obvious, so readers don’t have to consult an ASCII table or read octal to make sense of it.

(Does anyone actually read/use octal these days? Unless you’re doing chmod on Linux or programming a PDP-11 it’s generally not very useful.)

The [] is just the basic indexing operator, used to index the string literal.

The code within the string literal is just the single-expression version of the code at the bottom of the code block above the one-liner.

I.e.

auto index = ((i & 0x80) != 0) ? -i : i;
auto offset = (arduboy.frameCount / 8);
auto finalIndex = ((index + offset) % imageSize);

Is reformulated as the single expression:

((((i & 0x80) != 0) ? -i : i) + (arduboy.frameCount / 8)) % 16

((i & 0x80) != 0) ? -i : i is a much simpler way of expressing ‘optionally negate i’ as opposed to all that bit shifting and multiplying malarky (i*(i<<8>>15|1)). Then the / 8 and % 16 are more obvious stand-ins for >> 3 and & 0xF. Hence it’s functionally equivalent but easier to understand.

1 Like

– Now adding to the first post :face_with_monocle:

1 Like

You probably don’t need both details and spoiler blocks.
Just details blocks should suffice.


By the way, @brow1067’s entry is actually a Sierpinsky triangle.

It probably shouldn’t compile at all, but Arduino uses -fpermissive which might as well have been called -fabomination.

More about not having a better way to access the logo on ProjectABE’s version of the library.

While it is possible to use getPixel/drawPixel as Pharap pointed out, what you really want to do is to invert your logic: instead of telling something that a pixel needs to be set, your oneliner needs to respond with whether a pixel is set or not. In other words, instead of drawPixel(5, 10) you convert i into x and y (x = i % 128 and y / 8 = (i / 128)) then use that to set the desired pixel:
(i % 128 == 5) && ((i / 128) == (10/8)) ? 1 << (10/8) : 0

The same idea can be applied to multiple points anywhere in the screen. This is probably not the best way to tackle the problem of “Maximum Pixel-art, Minimum Code”.

“Flies on a :poop:”:

(<::>(int16_t i){static int16_t t=-1,F[16][4];if(t-a.frameCount){if(t==-1){for(int j=0;j<16;++j){F[j][0]=(21+j)*383^j*479;F[j][1]=(49+j)*7^j*503;}}t=a.frameCount;for(int j=0;j<16;++j){F[j][0]+=F[j][2]-=(F[j][0]>>15|1)*3;F[j][1]+=F[j][3]-=(F[j][1]>>15|1)*3;}}int r=0;for (int j=0;j<16;++j){if((i&0x7F)==((F[j][0]>>8)+64)&&(i>>7)==((F[j][1]>>8)+32)>>3){r|=1<<(((F[j][1]>>8)+32)&7);}}return r;})(i);
Spoiler

ArduboyRecording(2)

3 Likes

Wow - another nice animation!
I feel this is moving away from the spirit of the challenge of using just “maths formula”… perhaps we should add some rules? :thinking:
e.g. Entries may not create new variables or functions …?