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.

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:
- toledo_tanks.zip, 13.5K, programa
completo con código fuente y ejecutable para Wind*ws.
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.
- Precaución: Sólo puede disparar 3 balas a la vez.
- Truco: Puede disparar más rápido cuando las balas explotan.
- Créalo o no, ¡tiene estrategia!
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):
- Tab - Disparo
- w - Ir adelante
- s - Ir atrás
- a - Girar a la izquierda
- d - Girar a la derecha
Teclas del jugador 2:
- Retroceso - Disparo
- Flecha arriba - Ir adelante
- Flecha abajo - Ir atrás
- Flecha izquierda - Girar a la izquierda
- Flecha derecha - Girar a la derecha
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
- openSuSE 10.2 x86-32, X11R6 1024x768 color 32 bits.
- Fedora Core 8 x86-64, X11R6 1280x1024 color 32 bits.
- Fedora Core 11 x86-64, X11R6 1024x768 color 32 bits.
- Fedora Core 14 x86-64, X11R6 1024x768 color 32 bits.
Características completas
- Multijugador desde el inicio
- Totalmente autocontenido, no usa archivos externos.
- Acción rápida a 29.97 cuadros por segundo.
- Justo como una película, visualización con escala 16:9
- Bonito paisaje de montañas más línea de horizonte :)
- Los tanques y balas explotan como alambres con física rara.
- Los tanques reculan al disparar.
- Hay un número infinito de niveles.
- Niveles siempre diferentes.
- Presentación animada.
- ¡Tiene color!.
Características que les encantarán a los programadores
- Trabaja con procesadores x86-32 and x86-64.
- Doble buffer, transportable a cualquier X11 con color de 24/32-bits.
- Hambriento de memoria como debe ser cualquier programa 3-D, utiliza un
arreglo de 256 megabytes
- Estilo de codificación total y absolutamente irreverente, genera un montón
de advertencias al compilarse.
- Puede tomar varios minutos para compilarse (culpables conocidos:
Fedora Core 11-14 en x86-64 512 MB)
- Está escrito en el único C verdadero: K&R, y no, no significa
Katherine & Rose.
- Gráficos vectoriales escalables, ¿quiere un área de juego mayor?,
cambie las variables M y N.
- La mitad del programa está formateado como un cubo 3-D, mirelo con
tabuladores a 8 columnas y un tipo de letra cuadrado.
- La otra mitad del progama es código fuente no imprimible, así que no
se puede teclear de papel, tiene que escribir un convertidor de tabuladores
y cambios de página a sus formas en C con escape.
- Sin texto, así que este programa puede ser utilizado y comprendido en
cualquier país.
- Este programa es altamente ecológico, ya que "duerme" mientras no hace
nada.
- Mundo virtual con sus propias leyes de la física.
- Al cerrar la ventana mostrará un error de X11 :P
Trucos de confusión
- El código del bucle se incluye dentro de la prueba del bucle.
- Argumentos de funciones usados como variables.
- Operadores usados con desparpajo.
- Valores precalculados para ahorrar bytes.
- Del lenguaje C usa los operadores no documentados +++, +- y *+ :P
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.
- K() - Transformación 3-D
- s() - Visualización
- b() - Selección de color de línea
- G() - Animación de la explosión
- g() - Mueve un objeto 3-D adelante y atrás
- F() - Detección de colisión
- T() - Núcleo (también conocido como bucle principal :D)
- main() - Inicialización y descompactador. Un nombre muy común,
los programas C deberían empezar en la función lo_mejor_aqui() :).
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