3D Tanks game. 20th IOCCC. Best non-chess game
In the eighties when I was a kid, I wrote a BASIC
program to show a 2D labyrinth and two tanks rotating along eight directions
and shooting, it was for two players. Every player pressed furiously over
keyboard trying to get first to the right position to shoot the other player.
It gave me great hours of fun playing with my friends.
By November 2011 when the
20th International Obfuscated C Code Contest
(IOCCC) started, a contest for professional C programmers, I thought this game
would be a fancy one for an entry but with a little twist: 3D graphics and dual
views, one for each player.
I finished it and sent it on time for contest and it won the
Best non-chess game category of the IOCCC, also was my fifth win since the 18th
contest.
It is a multiplayer 3D game in only 3510 bytes of C language.
Today modern games use a pretty similar core with added graphics and music,
little has changed since many years ago.
I consider it as a good ending for my winning streak and
I've decided to not participate in further IOCCC competitions.
3D Tanks running
Source code
Here is the source code written in C language:
#include <X11/Xlib.h>
#include <X11/keysym.h>
#define I XDrawLine(W,X,Y,
#define l rand()%
#define R while(
#define n *h++
#define x _ c].
#define S if(
#define _ _[
*h ;
M=512; i; N=
288; e; d; u; p; L[1
<<28]; float w=11.46; m; a ;
P; k[9304]; *j=k; c; f; q; r; t; v ;
* z; K(N,i) { t=u*+cos(N/w)-i*sin(N/w); p=i*
cos(N/w)+u*sin(N/w); u=t; } Display*W; GC Y; Pixmap
X; s(o,t,g,w,v) { h=v+k; q=2*n; R u=n, r=12+n, K(t,n), K(o,+
p+w,u+=g), d=1>p?1:p, u=u*N/d+M/2, d=N/2+r*N/d, u=u>
-M*2 ?u<M*2?u:2*M:-2*M, 1&q?i>0|p>0?I f,e,u,d):0: (i=p
,e=d,f=u ), --q); } struct{ int c,o,m,f,u,s,i ,e,r; }
_ 21 ] ; Window O; b(y) { XGCValues g ; g .
foreground=y; g. line_width = 2 ; XChangeGC(W,Y,20
,&g); } G(o) { z=x f + k; h=(x f= 381+193*c)+k; i=2*(n
=*z++); R--i&1?d=l 9-4,e =l 9 -4*(8>o):0,n=*z+++d,0<(n
=*z+++e)?h[-1]=0:0,n=*z+++d, i); } g(o) { u=0; K(x s,o<<9
); x u-=u; x r-=p; } F(m,c ) { f=5120*c; c=12; R +c--&&m
==+c |abs(x u-_ m].u)>f |x i <1|x c>2 |abs(x r-_ m].r)>f)
; return c+1; } T(o) { c=11; S!P){ a=++a%72; S!a){ P=1; R
c--){ x c=x m=0; x i=c>2?2:1 ; R x u=l 978-499<<8, x s= l
71, x r=l 979-501<<8, F(c,2) ); x f=c>2?l +2*+73:147; x o
=2>c?249<<16-c*8:64987<<8*(c >2); } } } S P){ c=11; R 4&x
i? ++x m-64&&x m-71? o=1,64> x m?(d=F(c,1))?x m=64,d<4?j[
x e]++,++_ --d].c:0:g(7):G(x m -65) :_ x i=0,x e].e-- :0,
20>++c); c=3; R c--) { S 1&x i) { o++; S x c>2) x m>15? x
i=0:G(x m++); else { S x m ) S x m-->6) g(4); S c<2) { x
s=(72+x s+L[c?B]-L[c?C])%72; S d=4*L[c?A]-L[c?D]*4) { g(d
); S F(c,2)) g(-d); } S L[c? E]&!x m&x e<3) { d=10; R _++
d].i) ; g(-4); _ d]=_ c]; _ d].e=c; x e++; x m=7; c=d; x
f = 122 ; x i=4; g(19); x o= 4092<<12; c=x e; } } } } } P
-=o<2; } v=2; R v--){ m=(108 - _ v].s)%72; b(3^_ v].c?0:
128); Q c=0,0,M,N); S!P) s(+ 36,54-a,0,-120,-+-+-+-+147,b
((1<<16)-1)); S P) { b(255); I 0,N/2,M,N/2); t=-.889*m; h
=k+340; R u=(t+=q=n)*M/32,d= n*N/48,q?I f,e,u,d):0,f=u,e=
+d,k+378>h); R x i&&c-v?s(m, x s,x u-_ v].u>>8,x r-_ v].r
>>8,x f,b(x o)):0,20>c++); c =0; R e=16+32*c,Q v^c?16:8,e
,(c^v?68:76)+c[b(x o),j] , 16), b(0,d=20*x c), Q
80-d,4+e,d,8),2>c++) ; } b(255); I M,0,M,
N); XCopyArea(W, X,O,Y,0,0,M,N,v*
M,0); } }
char*o="{\t} \f{\f}\t\t\f{\f{ }\t; \t{ }\t; \t\f{\f; \t"
"\f{\f;\t { ;\t{ }\t; \t{ }\t; \t\f{\f; \t\f{\f;\t}\t\t"
"; \t { ;\t{ }\t}\t\t; \t { ; \t}\t\t{ }\t;\t { ;\t\f{\f}\t\t; \t "
"{ ;\t} \f\f{\f}\t\t\f{\f{ }\t; \t{ }\t; \t\f{\f; \t\f{"
"\f; \t}\t\t { }\t\t{ }\t}\t\t\f{\f}\t\t { }\t\t{ }\t}\t\t{ }\t}"
"\t\t { }\t\t\f{\f}\t\t{ }\t}\t\t { }\t\t{\f\f\f\t\t }\t\t{ \f; "
" \t ; \t\f\t; \t\t ;\t }\f\f{\f;\t}\t{\t{ }\t; \t}"
"\t}\t; \t\f{\f; \t}\t{\t;\t} \t{\t{ }\t; \t{\t}\f; "
"\t\f{\f; \t\t{\t;\t}\t\t}\t{\t;\t} \t{\t{ }\t}\t\t}\t{\t;"
"\t} \t{\t; \t{\t}\f;\t}\t\t}\t}\t\f{\f} {\t}\f;\t}\t\t}\t}\t} "
"} {\t }\f\f; \t\t} ; \t} ; \t{\t }\f {\t } \f; "
" \t{\f} ; \t}\f ; \t } } {\t }\f {\t }\f\f} {\t } \f"
"{\t }\f\f} \t} } \f{\t {\f} } } \t} }\f {\t {\f} } \t\t {\f "
"; \t{\f{\t}\f\t;\t{\f ; \t{\f{\t;\t {\f ; \t{\f{\t} \t;\t{\f ; "
"\t{\f{\t;\t\t ;\t}\f\t; \t ; \t} \t; \t\t ;\t}\t\t}\f"
"\f{\f\f{\t\f\t\f} \f{\f\f{ } { \f\t\f{\f\f{\t\f{\t} }\t\f} \f \f}"
" \f}\f\f\t\f{\f\f{\f\f{\t\f{\f\f} \f{ \f}\f\f{\f\f{\t\f\t\f} \f{"
"\f\f{ } { \f\t\f{\f\f{\t\f{\t} }\t\f";
main(){
XEvent e; W=
XOpenDisplay(k);
XSelectInput(W,O=
XCreateSimpleWindow(W,DefaultRootWindow(W),64,64,M*2,N,2,P,r),3);
XMapWindow(W,O); srand(time(0)); Y=
XCreateGC(W,X=
XCreatePixmap(W,O,M,N,DefaultDepth(W,r)),P,r); R*o){ S*o^59)d=64&*o?'{'^*o++:3,
32^*o?d+=*o^9?2:1:0,*j+=P?d*9-43:d,j+=P,P=!P; else R*j=j[-3],j++,*++o^9); o++;
} R 7){ T(0);
XFlush(W); usleep(33367); R
XPending(W)) L[
XLookupKeysym(&e,
XNextEvent(W,&e)&0)]^=1==e.type/2; } }
For sake of readability on this webpage I've replaced the
control characters for tabulator and form feed with their C equivalents to make
them visible. The original file is available below to download.
Compilation
For running this program you'll need a system running some variety of *NIX and
X/Window.
Download the source code file
toledo_tanks.c
To compile you've to introduce a long command line, it's so
long that doesn't appear complete in this webpage so copy it to clipboard.
After compilation run it without arguments.
cc toledo_tanks.c -o toledo_tanks -lX11 -lm -DA=XK_Up:XK_w -DB=XK_Left:XK_a -DC=XK_Right:XK_d -DD=XK_Down:XK_s -DE=XK_BackSpace:XK_Tab "-DQ=XFillRectangle(W,X,Y,"
./toledo_tanks
Also I've developed a translation layer that allows
to run this program in Wind*ws. Get the complete pack with executable:
- toledo_tanks.zip, 13.5K, complete
program with source code and executable for Wind*ws.
The IOCCC judges also have been able to compile it over
Mac OS/X platforms, for this you must install X11 and add this to command
line for compilation:
-L /usr/X11/lib
User's manual
You will see briefly a tank spinning before each level.
Left half of the window shows the red player view and right
half shows the green player view, the background will change to
blue when the view goes inactive.
The objective is to chase and destroy the other player's tank,
there is also an aqua tank for testing your aim and getting
extra points.
- Caveat: You can shoot only 3 bullets at a time.
- Trick: You can fire faster if they explode on anything.
- Believe it or not, it has strategy!
When only one tank remains on the field, the level will advance.
Don't forget to destroy the aqua tank.
The playground is surrounded by mountains and each level is
filled with random obstacles (pyramids and cubes).
The bars show players energy and score, each bar shows energy
and indicates current player view (wider on the left), right
part grows with every point.
Keys for player 1 (lowercase):
- Tab - Fire
- w - Go ahead
- s - Go backwards
- a - Turn left
- d - Turn right
Keys for player 2:
- Backspace - Fire
- Up arrow - Go ahead
- Down arrow - Go backwards
- Left arrow - Turn left
- Right arrow - Turn right
Note: Numeric keys don't work.
If you don't like the keys assignment, you can change it on
the makefile.
Parental advisory warning
This game is highly violent, when the tank explodes you can
see the little stick man blowing in parts.
Not really, just kidding :D
Platforms tested
- openSuSE 10.2 x86-32, X11R6 1024x768 32-bits color.
- Fedora Core 8 x86-64, X11R6 1280x1024 32-bits color.
- Fedora Core 11 x86-64, X11R6 1024x768 32-bits color.
- Fedora Core 14 x86-64, X11R6 1024x768 32-bits color.
Complete feature set
- Multiplayer out of the box.
- Totally self-contained, no external files.
- Fast-furious action at 29.97 frames per second.
- Just as the movies, 16:9 aspect-ratio.
- Beautiful mountains landscape plus horizon line :)
- Tanks and bullets exploding as wires with weird physics.
- Tanks backoff when firing.
- There is an infinite number of levels.
- Always different levels.
- Animated intro.
- It has color!.
Features that programmers will find appealing
- Works both with x86-32 and x86-64 processors.
- Double buffered, portable to any X11 with 24/32-bit color.
- Memory-hungry as must be any 3-D program, uses a 256 MB array.
- Total and absolute irreverent coding style, so it generates
a lot of warnings when compiled.
- It can take several minutes to compile (known culprits:
Fedora Core 11-14 on x86-64 512 MB)
- It is written in the only true C: K&R, and no, it not means
Katherine & Rose.
- Vectorial scalable graphics, do you want a bigger
playfield? change variables M & N.
- Half of the program is formatted as 3-D cube, view with
Tabs at 8 columns and a square font.
- The other half of the program is unprintable source code,
so no typing from paper, you have to write a converter of
tabs and formfeeds to its C escaped forms.
- No text, so this program can be used and understood easily
on any country.
- This program is highly ecologic, it sleeps when it is not
doing anything.
- Virtual world with its own physics laws.
- When closing the window it will show a X11 error :P
Obfuscation tricks
- Loop code included inside loop test.
- Function arguments used as variables.
- Operators used freely.
- Precalculated values to save bytes.
- Uses the +++, +- and *+ undocumented C operators :P
History of this program
When I started working on this program, I figured that it would
be easy to fit on the IOCCC character limit, but I missed three
things, the enormous X11 names, the unportable keycodes and my
2 KB of artwork.
Very hard coding did the first thing, in fact I worked out
twenty-nine different versions, at one step I had four
parallel versions with different characteristics removed
(mountains, horizon line, score/status bars and explosions),
I've managed to fit it all together!.
The second thing was solved using the makefile, it is under
160 bytes and you get adjustable keyboard. The greatest
mistery on the earth is why X11 handles unportable keycodes to
the user.
And finally, the third "Thing" was growing and growing, and
growing, eating bytes for lunch... after endless rounds of
crunching I obtained three-hundred seventy-eight bytes of
artwork and guess what?, it doesn't fitted.
So I had to integrate a decompressor, based on the space,
tabulators and other non-counted characters.
The final program is composed of eight microfunctions, one is
the core and does most of the hard work, other functions help
doing the X11 things, explosion effects, 3-D calculations and
viewing.
- K() - 3-D transformation
- s() - Rendering
- b() - Line color selection
- G() - Explosion animation
- g() - Moves 3-D object backward and forward
- F() - Collision detection
- T() - Core (also known as main loop :D)
- main() - Initialization and uncruncher, what a common name!,
C programs should start in the cool() function :).
The source code was formatted as a cube using a specially
written program, the reality is that the program is smaller and
obfuscated of what is needed so the reformatting doesn't
exceeded the character limits. The crunched XYZ vectors were
generated by another special program.
Have fun!
Useful links:
Last modified: Feb/11/2013