IOCCC

The International Obfuscated C Code Contest

Supplemental Info

Introduction (SPOILER ALERT)

In the gameplay.markdown (gameplay.html) file I have given enough information to know how the game looks, how to move the snake, how to pause etc. In this file I am including (some) information on how the game actually works: the technical details as it were, obfuscation techniques and also some of the ways I reduced the iocccsize so I could do more with the code. Probably this file causes more spoilage than the other files though that’s not to say everything will be answered here.

If you’re still interested in this file (i.e. you’re a judge or someone who wants to know more about it) read on. Yes I write a lot. But you probably already knew that so no harm there, hey?

How does it work?

The gameplay I have already described but this explains the way things are actually done in more detail.

First I initialise curses; if this fails I print an error message (‘curses error’) and return 1. Next the screen size is determined and I subtract 1 from each the x/y. If it’s too small curses is ended and an error message (‘screen too small’) is printed - returning 1.

Next the max size of the snake is determined: if MAXSIZE is specified then it’s the value of that; otherwise it’s the default. But in either case if it’s too big based on the screen dimensions it’s capsized. The arrays for the snake are this size + 1. Other environmental variables are also processed.

Then the arrays are allocated; if either return NULL curses is ended, an error message (‘memory error’) is printed and return 1.

If no errors have occurred I set up colours, turn on bold, set up the keypad etc., place the snake, the bug, draw the walls and then it waits for the first move (or quit of course). Technically I don’t ‘wait’ for input as such: unless the wait time is negative (drawing mode) it’s just a non-blocking call up to the WAIT milliseconds but in no event will the snake move until it is told to at least once (in drawing mode it will always wait).

In drawing mode if you pause then when you resume (hit space) the snake will move the last direction it had been told to move - because the direction is set and input was detected (the same goes for another key e.g. ‘g’ - just like in normal mode). But it will then wait again for input.

The snake function both draws the snake and updates the arrays; if the new size is >= the max size the game is won (the winning size will be shown as the max).

Else the arrays are updated; when the array is being updated the body of the snake is drawn. After this the head is drawn. Depending on the length of the snake wrt the size the snake technically it might be that the number of characters the snake takes is increased rather than having moved the snake forward (i.e. the tail moved by one). This registers where the snake is and where it was but no longer is (important particularly for negative growth/shed sizes).

After that a bug is placed (but not drawn) in a pseudo-randomly selected area of the field. This function takes a parameter which if not 0 and the bug had already been placed somewhere (in other words if the bug was just eaten) call the snake function. As long as the game isn’t won at that point find a new place to place the bug.

Next the score line is updated and then whilst the game is not over (player loses/quits/wins) the movement proceeds; if the game hasn’t started yet it will jump to the top of the loop. If it’s paused the same thing until it’s resumed. The rest of the loop can be described thusly.

It checks the current max y/x, draws the walls, bug, gets any input from keyboard, and then if there is movement checks for collisions; if current max x/y are is/are smaller than original then it’s automatically a game over from running into walls regardless of that feature status - this simplifies things greatly (see below for more details).

If cannibal mode is disabled (the default) and the snake runs into itself then it’s also game over. Or did it run into the wall? If walls are passable (the default) it will come out from the other side; else it’s game over. If it comes out on the other side and the snake is there and cannibal mode is disabled then it’s also game over.

As the snake moves it will for each move grow by one up to the technical size. This might at times look a bit different from other times but it’s a visual thing based on the internal data of the game and the features enabled for example shedding and shedding size.

Finally if the snake’s new coordinates are on a bug a new bug is placed on the field, the snake grows and the score is updated. Otherwise if the bug has waited the number of moves that it waits before it finds a new place the bug is placed in another location too (and the snake does not grow). In either event the snake location is updated and redrawn.

There’s also the processing of any shedding at this point. If the snake eats a bug the snake will not shed; if it sheds the snake will not move in the same manner as if it simply moved. Growth/movement can in some ways be conflated.

How does the snake movement work?

The way the snake looks to move is that every time the snake moves the last position (the tail) is replaced with a space (' '). If the snake eats a bug then the snake will get bigger in the next N movements. Note that if the snake isn’t its full size it will only be growing and so might not appear to be moving at the tail end; I have discussed this more than enough elsewhere.

There are two arrays that store the last Y/X coordinates of the snake’s positions that are shifted forward as the snake moves about the field looking for bugs.

Drawing (manual) and computer playing (automatic) modes

How do these modes work? The timeout() function takes a signed int: if it’s < 0 it waits indefinitely; if it’s 0 it returns immediately even if there’s no input waiting (if input is there then it’s used instead); and if it’s > 0 it waits that many milliseconds. Thus a negative value allows for complete control over the snake’s movement (speed and otherwise), a value of 0 means the snake will move as long as it’s not paused etc. and otherwise it waits up to that many milliseconds (which is how the 0 value comes into play).

Collision detection

This was an interesting thing to work out. Because the snake could occupy more than one row and more than one column at a time the two arrays approach seemed good to me.

I already mentioned how the array is shifted but to be clear: the snake head is always at element size - 1 and the tail is at 0 and so it’s just a matter of verifying if the snake head is at a place that’s occupied by the snake, the wall or the bug.

The bug does its best to avoid landing where the snake is but given that snakes are very flexible and cunning who can tell what might happen? As I noted these bugs are not bugs! :)

Cannibal collision detection

How does cannibalism change collision detection? The fact that each time the snake function is called there is a space printed at the tail end was a problem to resolve: what happened is if the snake went through itself then at some point there would be a space that breaks the snake into more than one piece (where the tail was but that had been another part of the snake)!

So what I do: first the tail is cleared, then as the snake coordinates arrays is being updated I redraw the snake chars. Then at the end the head is drawn (it used to be the head was drawn first but by drawing it after you can see where the head is inside itself, creepy as it is). Thinking on it this very possibly is why (or partly) the negative shedding leaves the head somewhere in the middle of the snake (or that the head of the snake is left behind) but again I don’t think that as a problem.

There are other strange ways this looks. Not only does it look like lines intersecting at any number of places but it looks particularly strange when the snake goes the opposite direction: it has to unwind as it were, and for a moment you might not see what is truly happening. There are two ways you can look at this I think.

First is that it’s physically impossible in real life so the fact this cheat mode exists for a different kind of play means it should look unnatural: for it is unnatural!

The second is you can look at it as if two more snakes are having fun with each other. This can also appear to defy physics!

Choose your poison. Or rather your venom.

Collision detection when resizing the window of the game

What happens if the window is resized during the game? If the size is increased then the border can (and will) stay the same; but if the window is shrunk then what? It would be very difficult if not impossible to redraw the snake: where would the snake be and what about the border? The arrays would have to be adjusted too! Another issue: what if the snake is beyond the new walls?

The best thing I could think of is that if the window is shrunk then the snake is assumed to run into the wall: in other words the game is at that point over. If the window is increased in size nothing happens other than the game won’t be the full window size (or technically a few rows/columns fewer): everything will remain the same. This GREATLY simplified things.

There was another problem at some point where the snake would quickly grow if it was increased in size but I believe that’s resolved. Anyway what kind of field in real life increases in size like that so suddenly? :)

Obfuscation

Obfuscation in prog.3.c and prog.3-j.c

There are a few differences in this version that are worth noting. First of all is a define that’s in the compiler invocation Makefile is in the source file itself. Second are the following obfuscation techniques. To simplify integrating the explanations I have simply yanked and pasted them below:

There might be more things that differ but those are the significant ones.

Of course none of the above is so easy to see without a beautifier. Why did I choose the format I did? Well it came about in a sequence of possible layouts. I tried first to make it look like the game might itself but the trouble with that is it’s too random. Then I thought maybe I could spell out the words SNAKE 2020 or IOCCC 2020 or something like that. But these were proving to be a problem for me; what I was seeing on the screen just wasn’t looking right in my head.

At that point I just started playing with different patterns and I quite like what it’s turned out to be. I don’t know what I would call it but one could look at it and think of columns holding up a structure. There’s also a symmetry to it and almost certainly that’s what is most attractive about it to me for I love symmetry.

Some final notes on the layout I’d like to point out. First there are 53 lines counting blank lines. Second is that if you count the number of lines (of code) in each ‘block’ they should all be a prime (except the two blocks of 1, I guess). Third is that I tried to make sure that each column of the code (not counting the preprocessor - see below on that) start and stop at the same position (as in column in the row/column kind) on each row. Below I show the start and end column of each horizontal block. The #undef in the middle should be centred based on its length and the 120 columns for total. The preprocessor directives should also be centred in that way (that was my goal anyway).

The following were intentional:

The symmetry was even more important to my mind; observe that each vertical block (or column) starts and ends on the same column (or at least that was my goal; I think I did it but maybe I missed one here and/or there):

Furthermore based on the length of each C preprocessor directive (the #defines, the #includes, the #undef) I have attempted to centre them in the file based on the 120 columns total. I believe I have succeeded in that though again maybe I have an off by one or two here and/or there.

Most of the primes above were intentional.

Skinning the snake (i.e. decreasing the iocccsize)

This happened in a number of ways and I have already documented some in other parts. Now there are a number of reasons to do this: amongst them to make it possible to obfuscate (plus a more unique layout) but also to add more features.

Interestingly some of the obfuscation saved bytes; others of course added bytes. For example token pasting saved a few bytes; on the other hand the combining ifs to the complicated one above added 9 bytes: but another one saved a byte. I have done this more than once so it’s entirely possible there are others I am forgetting.

Obviously a more significant one is #define q mvprintw: this saved a total of 47 bytes! This was however done towards the end (or so I thought it was the end; it was anything but) when I started looking at obfuscation rather than adding features. But then because of this I looked at adding more features!

Another thing that reduced a lot of bytes - 34 to be exact - is using the mvvline() and mvhline() ncurses functions rather than use three for loops to draw the walls.

Much later on I made it so that the E() function returns 1; this was a clever way to have what I already did have - return 1 if there’s an error (i.e. memory error or curses error or screen too small) but otherwise return 0 without having to have a separate int and I could get rid of two {} pairs as well as add another use for the E() function: it calls endwin() exactly once (even though when there isn’t an error condition it can be called more than once) as well as printing the final score - or else an error. And now it also allows for printing an error and returning from main() at the same time! This also means that by returning E() I am returning 1 but if the end of main() is reached 0 is returned. In either case curses is ended.

One of these optimisations saved four bytes simply by taking advantage of the value of a loop iterator after the loop. The loop is:

    for (I = 0; A && I < A - 1; ++I)

However after the loop I had this:

    A > 1 && q(n[A - 1],e[A - 1], 'o');

When I was considering moving that line to before the loop it occurred to me that as long as A > 0 then A - 1 == I after the loop. I still had to check for A > 1 but I could refer to just I instead of A - 1!

There were quite a few other things besides these and these gains were useful to add additional features and also to add proper error reporting (when calloc() failed I called abort() and before that if initialising ncurses failed I also called abort() and did not report any errors in either event but I do now).

A few more interesting size optimisations

As I was getting to the end of obfuscation (wrt size available - and it was getting pretty obfuscated too - the majority of the techniques were already in place at this point) I was looking at the code to see if I could figure out how to save even more bytes. Here are some that I found interesting ways to go about the problem.

First, the function that turns on colours looked like:

    if (h) attron(m(PAIR(L)));
    else attroff(m(PAIR(L)));

But I thought is that else actually needed? If I were to switch the order so that the attroff() is called first then I could just do an if (h) - therefore getting rid of the else! But then I thought why not do what I’ve done elsewhere? I had:

    attroff(m(PAIR(L)));
    if (h) attron(m(PAIR(L)));

But I then saved an extra byte by making it:

    attroff(m(PAIR(L)));
    h && attron(m(PAIR(L)));

That saved two bytes.

Second let’s look at the B() function: there were two things I thought of that would save some bytes (I no longer know how many but a few at least possibly six or even more). I had the following code:

    if (V && U && o) { S(N); b(1); }

This made sure that only if the snake has caught a bug should it grow by the growth size and increase the bug count. But was checking for the V or U necessary? I gave it a look over and it occurred to me: the only time B() is called with 1 (thus o is 1 so the if is true) was if the bug actually is caught. That means I could remove the V && U && part of the if! Thus it became instead:

    if (o) { S(N); b(1); }

Then this

    !o && V && U && q U,V,' ');

…is to make sure to only clear the spot the bug is (or was) at if the bug was NOT eaten. Why not eaten? Because if it was eaten it means the snake’s head will be there!

But why did I check for the bug having coordinates? Because I thought that if the bug has coordinates obviously the function has been called before which means that we do clear (write a space) over a place the bug was actually at. But I thought could there maybe be another way of verifying if the game has started yet?

Before I ask that question I’ll point out this: is there a reason it has to be checked? Yes because right after this line new coordinates are found. But unfortunately the snake has to be placed first since the bug cannot be placed where the snake already is. So if I had to make sure the game had started (the snake has moved at least once) what could I check? In fact that means the direction is not 0! So I could just replace it with:

    !o && D && q *U,*V,' ');

That reduced the count further. But then I wondered if even that much is needed. The condition is that the bug has evaded which means the snake has moved already (so game started). What calls to this function happen before the game starts though? The original placement of the bug. Well if the above check makes sure that the bug isn’t where the snake is at it means that the snake has already been placed, right? Going further the snake always starts out at the centre (or rather the close as possible - rounding maybe an issue depending on the max y/x): in other words not at 0, 0. This means that the only thing I should have to check for is that !o: because after this call before the game even starts the score line is updated: so the empty space will be overwritten anyway. And if the score line is made an empty string it doesn’t matter because it’ll appear as all spaces anyway. Thus I could save another three bytes by changing it to:

    !o  && q *U,*V,' ');

Technically it would be a " " (see HACKING file) else for N (not referring to the variable though it might very well matter here too) movements (i.e. due to sizes) you will at times (e.g. after eating a bug) see the snake body char at 0,0. I’m not bothered about that though because the score line is meant to display something, it squeezes a few more bytes and having a space is hardly an imposition on usage (and for that matter having a ‘o’ at 0,0 isn’t either).



Creative Commons License

© Copyright 1984-2020, Leo Broukhis, Simon Cooper, Landon Curt Noll - All rights reserved
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.