
C implementation of Tic-Tac-Toe in a single call to printf - Megabeets
https://github.com/carlini/printf-tac-toe
======
simias
Oddly enough I find that the #define macro soup detracts from the performance
here. I was rather unimpressed at first because obfuscating C code by just
#define'ing a bunch of code is a trivial and rather uninteresting way to write
unreadable code. Of course you can make any arbitrary code look like a printf
call with enough macros!

But it's actually a lot more clever than that.

I feel like this would be a lot more impressive if it was written in simple
and clear C since then you'd see that there really isn't any tic-tac-toe logic
being explicitly implemented the way you'd expect.

~~~
russfink
One time, I had to maintain C code That somebody had #define'd to look like
FORTRAN. It was impressive.

~~~
simias
I've had to deal with code like that, only poorly mimicking Lisp instead:
`#define unless(_x) if (!(_x))` etc...

One of my favourite accidental features in Rust is that macros are so annoying
and cryptic to write that people think twice before abusing them.

~~~
TeMPOraL
I understand the temptation. I keep catching myself on writing `unless( ... )`
in C++ every couple days, and I've considered adding a macro for it, but
ultimately decided against it, as the rest of the team would not understand
why I _need_ it.

------
mci
I am afraid that the author has disqualified himself by publishing the source
code before the judging of IOCCC 2020 is over. See catch 22 in the rules: "The
judges STRONGLY prefer to not know who is submitting entries to the IOCCC."[1]
Even the winners are asked not to publish the code until it is available on
the IOCCC web site.[2]

[1] [https://ioccc.org/2020/rules.txt](https://ioccc.org/2020/rules.txt) [2]
Personal communication

~~~
Luc
‘Strongly prefer‘ != disqualification

~~~
andi999
'Strongly prefer'== banned for life

------
mjburgess
Relevant talk on the motivation for printf-programming:

[https://www.usenix.org/conference/usenixsecurity15/technical...](https://www.usenix.org/conference/usenixsecurity15/technical-
sessions/presentation/carlini#:~:text=Control%2DFlow%20Bending%3A%20On%20the%20Effectiveness%20of%20Control%2DFlow%20Integrity,-Authors%3A&text=Control%2DFlow%20Integrity%20\(CFI\),is%20believed%20to%20be%20secure).

Very interesting. Roughly, that the existence of Turing-complete functions
within programs creates a fundamental vulnerability that even rigid control
over the control flow of a program cannot avoid.

------
yokohummer7
> %n takes a pointer and writes (!!) the number of bytes printed so far.

> Okay, everyone probably knows this. Let's get a bit more advanced.

Ok, but I didn't know about that. What's the use?

~~~
Someone
One use is aligning outputs:

    
    
      char *prefix = "example";
      char *line1 = "line 1";
      char *line2 = "line 2";
      printf("%s: %n%s\n", prefix, &n, line1);
      printf("%*s%s\n", n, "", line2);
    

will output

    
    
      example: line 1
               line 2
    

That’s a bit more robust than using _strlen(s)+2_ , where you have to keep
that magic constant 2 in sync with ": ". Moving ": " to a variable and using
_strlen(s)+strlen(separator)_ would fix that, though (at the price of speed,
unless you’ve a compiler that optimizes that away)

~~~
JoshTriplett
This only works if you're not dealing with Unicode, where the number of bytes,
the number of characters, and the width of those characters can all vary.

~~~
JanisL
This makes me wonder if there's some sort of Unicode equivalent for this?

~~~
7786655
wcwidth()

~~~
JanisL
Thanks, I wasn't aware of this. Here's a man page for anyone else that was
interested: [https://man7.org/linux/man-
pages/man3/wcwidth.3.html](https://man7.org/linux/man-
pages/man3/wcwidth.3.html)

------
lukaszdk
This is just crazy and I love it <3

From a description on the author's website[1] of a Doom clone written in 13k
of JavaScript.

> Until recently all content on this website was research, and while writing
> papers can be fun, sometimes you just need to blow off a little steam.

I think more companies should allow for their employees to have some plain old
fun with no strings attached on a regular basis.

[1] [https://nicholas.carlini.com/](https://nicholas.carlini.com/)

~~~
learnstats2
>I think more companies should allow for their employees to have some plain
old fun with no strings attached on a regular basis.

Maybe we could call this additional 'holiday pay' or a longer 'weekend' and
mandate it through law so that everyone benefits.

~~~
commandlinefan
Ha, if I had a longer weekend my wife would demand I use it taking her out.

~~~
learnstats2
Do you not like spending time with your wife? That seems a shame.

------
codegladiator
> in a single call to printf

> while(*d) printf(fmt, arg)

How's that a single call ?

~~~
usr1106
> How's that a single call

syntactically it is. But it's not a very clear description. So how would you
describe that single call location getting executed in a loop so everybody
understands what is really meant? A single line of code is worse.

~~~
lelf
printf once in a while

------
haberman
In case you're wondering (like me) how you'd get input from printf():

> We ab^H^Huse [the Turing-completeness of printf()] to implement a the logic
> of tic-tac-toe entirely within this one printf call (and a call to scanf()
> to read user input).

So it should be "one printf() and one scanf()."

~~~
sfoley
Should be "an infinite number of printf()s and scanf()s".

~~~
identity0
Would you say that an infinite loop has infinite lines of code?

~~~
yjftsjthsd-h
It has infinite instructions, yes. (Well, "unbounded" or "endless", if we're
being picky.)

~~~
identity0
No it doesn’t. It has finite instructions for the loop body, and then one jump
instruction for the loop.

------
blunte
Title correction: Tic Tac Toe written almost entirely in #defines.

~~~
kzrdude
And executed in the Printf virtual machine

------
eternauta3k
I was expecting the tic-tac-toe to be written in machine code in an array, and
printf would smash the stack to jump to it.

------
f2f
thanks. now i have nightmares.

------
yenwodyah
It isn't really a "single call" to printf if it's in a while loop, is it?

~~~
phire
That's what you are getting hung up over?

There is a call to scanf in the middle of the arg #define

------
rosstex
If you're reading these Nicholas, thanks for your super help in CS 161 Spring
2016!

------
peter_d_sherman
Holy Crap Batman!

Never in a million years would I have thought that printf() was Turing-
Complete -- and yet, here's the proof that it is...

And then there's this related paper:

[https://www.usenix.org/system/files/conference/usenixsecurit...](https://www.usenix.org/system/files/conference/usenixsecurity15/sec15-paper-
carlini.pdf)

Page 175: "Printf is Turing-complete"

------
phkahler
I thought the program was going to consist of _only_ the call to printf(). But
alas, that is inside a while loop.

------
staycoolboy
Excellent obfuscation. It ticks all the boxes:

\- esoteric modes of operation of a common function

\- truly novel use of macros

\- visibly beautiful

------
selimnairb
I am jealous of people who have time to do things like this.

~~~
thr0w__4w4y
I hear you on that. But for me, the jealousy dissipates quickly once I realize
that even if I had /lots/ of time, I don't think I would come up with this.

------
yitchelle
Love the attempt the blindside the reader with funky formatting showing "%N".

~~~
TapamN
That's not too unusual for IOCCC entries. Of the top of my head, there's a
flight simulator shaped like an airplane, and an addition routine shaped like
a full-adder.

A more subtle touch is that the #defines spell out NOUGHTs AnD CRoSSES.

------
techbio
I’m impressed with the aesthetics, and humor: formatted ‘fmt’, “printf
oriented programming”...

Suggests in my mind, somehow, thought I’d share: “CTRL-F oriented programming”
:)

------
anta40
ttt.c(25): fatal error C1091: compiler limit: string exceeds 65535 bytes in
length

Aaargghhh MSVC 2019 doesn't like this :(

------
hebetude
fake news, single call to while

------
tikej
Looks a bit like scientific codes from 3 decades ago :)

------
mariomatos
Some advanced clickbait we have here.

~~~
encom
Nah.

 _Dennis Ritchie HATES him. Program like a pro with this one weird trick! You
won 't believe what happens!_

That's proper bait.

~~~
qllp100
Hence advanced bait. printf() is called in a while loop, so it is hardly a
single call.

~~~
taspeotis
I don’t want to jump on the title hate bandwagon - what the author has done is
definitely creative and clever and this discussion detracts from it - but it
also uses scanf.

~~~
quietbritishjim
Where?

    
    
        int main() {
            while(*d) printf(fmt, arg);
        }

~~~
shawnz
On line 57: [https://github.com/carlini/printf-tac-
toe/blob/master/printt...](https://github.com/carlini/printf-tac-
toe/blob/master/printtt.c#L57)

~~~
quietbritishjim
Ah, well spotted, thanks. I didn't think it was fair to call the title click
bait just because it used a while loop to drive the calls to printf, but also
including a scanf call is definitely not in the same spirit.

~~~
hibbelig
Alas, it's technically difficult to get input from the user using printf.

~~~
Someone
If you’re willing to lose almost all portability, I think you could read a
value from an I/O port, pass that to _printf_ , use that as the field width
for a string to print, count the character length of the resulting string
using %n to move it into a variable, and then backspace over that to prevent
the output from dirtying your output. You could only use the results in a
subsequent call to _printf_ , though.

Of course, just hiding an assignment in a _printf_ argument would be much
easier, but wouldn’t be fun.

Of course, that would lose lots and lots of portability, and you could,
likely, only get it to work on systems that don’t do memory protection between
processes, and then, not all of them.

I guess it would not be very hard to use this trick to have printf read
joysticks on many micro’s from the 1980’s.

~~~
saagarjha
In that case it might just be easier to ROP your way to scanf.

