Tanques 3D. 20º IOCCC. Best non-chess game

En los años ochenta cuando era niño escribí un programa en lenguaje BASIC para mostrar un laberinto 2D y dos tanques rotando en ocho orientaciones y disparando, era para dos jugadores. Cada jugador oprimía furiosamente sobre el teclado tratando de llegar primero a la posición correcta para disparar al otro jugador. Me divertí muchísimo jugándolo con mis amigos.
Por noviembre 2011 cuando comenzó la vigésima edición del International Obfuscated C Code Contest (IOCCC), un concurso para profesionales del lenguaje C, me pareció que este juego sería ideal para participar pero con un pequeño cambio: gráficos 3D y vistas duales, una para cada jugador.
Lo acabé y lo envié a tiempo para participar y ganó la categoría Best non-chess game del IOCCC, también fue mi quinto premio en este concurso.
Es un juego 3D multijugador en sólo 3510 bytes de lenguaje C. Los juegos modernos utilizan un núcleo muy similar con gráficos y sonidos añadidos, poco ha cambiado desde hace muchos años.
Esta participación la considero un broche de oro para mi racha ganadora y he decidido no participar más en el IOCCC.
IOCCC 20 Best non-chess game. Jugando Tanques 3D
Jugando Tanques 3D

Código fuente

Aquí está el código fuente escrito en lenguaje C:
#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; } }
En afán de que el código sea legible en esta página, he reemplazado los caracteres de control para tabuladores y cambio de página por sus equivalentes en C para hacerlos visibles. El archivo original puede ser descargado debajo.

Compilación

Para ejecutar este programa se necesita una computadora ejecutando alguna variedad de *NIX y X/Window.
Descarge el archivo con el código fuente toledo_tanks.c
Para compilar debe introducir una larga línea de órdenes, de hecho tan larga que no se ve completa en esta página, así que copiela al portapapeles. Después de compilar debe ejecutarlo sin argumentos.
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
También desarrollé una capa de traslación que permite ejecutar este programa en Wind*ws. Descargue el paquete completo con ejecutable:
Los jueces del IOCCC han comprobado que también funciona con plataformas Mac OS/X, para esto debe instalar X11 y agregar este argumento en la línea de órdenes al compilar:
-L /usr/X11/lib

Manual del usuario

Aparecerá brevemente un tanque girando antes de cada nivel.
La mitad izquierda de la ventana muestra la vista del jugador rojo y la mitad derecha muestra la vista del jugador verde. El fondo cambiará a azul cuando la vista se vuelva inactiva.
El objetivo es perseguir y destruir el tanque contrario, también hay un tanque color azul para probar puntería y obtener puntos extras.
Cuando sólo queda un tanque en el campo entonces el nivel avanzará. Para esto no olvide destruir el tanque azul.
El área del juego está rodeada por montañas y cada nivel está ocupado por obstáculos aleatorios (pirámides y cubos).
Las barras muestran la energía y puntuación de los jugadores. Cada barra muestra la energía e indica de quién es la vista actual (sobresale a la izquierda), la parte derecha crece con cada punto.
Teclas del jugador 1 (minúsculas):
Teclas del jugador 2:
Nota: las teclas numéricas no tienen operación.
Si desea cambiar la asignación de las teclas entonces puede hacerlo al compilar (en la línea de órdenes), o en el código fuente (si usa la capa de traslación para Wind*ws).

Advertencia para los padres

Este juego es extremadamente violento, cuando el tanque explota se puede ver al pequeño hombre de palitos saltando en pedazos.
En realidad no. Sólo bromeaba :D

Plataformas probadas

Características completas

Características que les encantarán a los programadores

Trucos de confusión

La historia del programa

Cuando comencé a trabajar en este programa, me imaginé que sería fácil de compactar en el límite de caracteres del IOCCC, pero olvidé tres cosas: los enormes nombres de X11, los códigos de teclas no portables y mis 2K de dibujos.
Con mucho esfuerzo logré ajustarme a lo primero, de hecho trabajé veintinueve versiones diferentes, en cierto momento tenía cuatro versiones en paralelo con algunas características eliminadas (montañas, línea del horizonte, barras de puntuación y explosiones). ¡Me las arreglé para compactar todo!.
El segundo problema fue solucionado usando el makefile, ocupa menos de 160 bytes y se obtiene teclado ajustable. El más grande misterio sobre la Tierra es porqué X11 pasa valores sin procesar al usuario.
Y finalmente, el tercer "problema" estaba creciendo y creciendo, comiéndose los bytes ávidamente... después de noches de programar y compactar obtuve trescientos setenta y ocho bytes de dibujos y adivine... no cabían.
Así que tuve que integrar un descompactador basado en los caracteres de espacio, tabulador y otros caracteres no contados por el concurso.
El programa final se compone de ocho microfunciones, una es el núcleo y hace la mayor parte del trabajo duro, otras funciones ayudan haciendo cosas de X11, efectos de explosiones, cálculos 3-D y visualización.
El código fuente fue formateado como un cubo usando un programa especialmente escrito, la verdad es que el programa es menor y más confuso de lo necesario para que el reformateo no excediera el límite de caracteres. Los vectores XYZ compactados fueron generados por otro programa especial.
¡Diviértase!

Ligas útiles:

Última modificación: 11-feb-2013