Solitario klondike para curses en 5k

por Oscar Toledo G. 07-jun-2026
Libro Obfuscated C and Other Mysteries por Don Libes.
Tenía tiempo libre y me di cuenta que había comenzado el IOCCC 29. Así que decidí codificar una participación para el concurso. Si no has oído acerca del International Obfuscated C Code Contest, es un concurso que se realiza desde 1984 y fue creado por Landon Curt Noll. El objetivo del concurso es escribir un programa en C ofuscado bajo ciertas limitaciones de tamaño especificados por las reglas.
Puede ver mis participaciones previas del IOCCC en la sección concursos.
Este año el tamaño máximo es de 4993 bytes (un poco menos de 5 kilobytes), y el número de caracteres imprimibles es 2503. Ya que hay algunas reglas especiales para caracteres imprimibles, hay ahora una herramienta llamada iocccsize para comprobar esto.
Cuando digo C ofuscado, es la forma de escribir código C en una forma que realiza su tarea pero no es claro. Por ejemplo, un bucle simple para contar de 1 a 10 se escribe así:

#include <stdio.h>
int main(void) {
    int c;
    
    for (c = 1; c <= 10; c++)
      printf("%d\n", c);
}
Pero usando una pequeña fracción del poder de la sintaxis del C, podemos hacerlo de esta forma:

#include <stdio.h>
int main(void) {int c=1;while(printf("%d\n",c),11>++c);}
Esto da una idea de que se trata el C ofuscado. Descifrar el best one liner (o mejor código en una línea) de cualquier año del IOCCC es un desafío. Y una forma de probar tu conocimiento del C.

La idea

Después de evaluar varias ideas, decidí hacer un juego de solitario Klondike ya que he codificado varios en mi trabajo. Uno para mi propio sistema operativo hace veinte años (necesito escribir acerca de esto), y recientemente Klondike para consolas Intellivision.
Por si no sabes, Solitaire era un juego incluído en Windows 3.1 junto con Minesweeper. Ambos fueron un gran éxito debido a que la escena de juegos para Windows era casi nula. Las personas aburridas usando Windows 3.1 comenzaron a jugar Minesweeper y luego Solitaire ¡y era bastante adictivo! No fue hasta que lo jugué yo mismo y comprendí las reglas. También descubrí que hay varios juegos de solitario, y esta era la variación Klondike. Lo abandoné después de un tiempo cuando descubrí que no todos los juegos podían ser ganados. De hecho, los matemáticos estiman que solo 43% de los juegos con una carta repartida cada vez pueden ser ganados, mientras que este número baja a 18% si se reparten tres cartas cada vez. Incluso si se pueden ver todas las cartas no es fácil ganar.
Klondike Solitaire in Windows 3.1
Klondike Solitaire en Windows 3.1 (corriendo en PCjs Machines)

Codificando

Comencé a desarrollarlo el 6 de febrero de 2026 y me tomó tres días codificar la lógica principal en C, y decidí usar la librería curses para crear la pantalla de las cartas, usar el color, y agregué también caracteres Unicode para mostrar los símbolos de las cartas. La librería curses permite controlar la pantalla para crear interfaces de texto sin preocuparse sobre el tipo de terminal que se usa.
Por supuesto esta primera versión del juego era demasiado grande para los límites del concurso, así que comencé a optimizar el código. También tuve que solucionar el problema del interfaz de usuario para seleccionar y soltar las cartas.
La interfaz original de Klondike se orienta a mover un cursor sobre una carta, tomarla y soltarla en otro lugar. De hecho, la leyende dice que Windows 3.1 incluyó este juego para enseñar a los usuarios como arrastrar y soltar cosas con el ratón.
Para caber en el espacio del concurso, mi interfaz de usuario tuvo que ser simplificando usando la tecla Tab para seleccionar cartas, y la tecla de espacio para soltar las cartas.

No se usar curses

Tenía un chiste muy bueno sobre curses pero solo funciona en inglés (si quieres puedes mirarlo en el artículo en inglés). Se sintió como una eternidad la curva de aprendizaje para usarla en mi juego. Descubrí varias versiones de la librería, y sabía por experiencia que se incluye en cada distribución de Linux y Mac en alguna forma.
El principal problema de curses es que hay demasiadas versiones. Y cada sistema operativo integra una versión vagamente diferente de la librería. El otro problema común es que las figuras de las cartas usan UTF-8, y algunas librerías curses no lo soportan (vea los Comentarios). Te irá bien con curses si usas ASCII exclusivamente.
Al menos encontré las definiciones de caracteres para construir cajas (las cartas), y Unicode me proveyó los símbolos para las cartas.
También pude usar colores para las cartas rojas. Hace muchos años era más común usar secuencias de escape ANSI/VT-52, pero este el tipo de cosa que curses soluciona a través de un interface unificado.
My Klondike game for curses in action
Mi juego Klondike para curses en acción

Pulido del juego

Logré implementar la repartición de 3 cartas como una opción, y también una opción para puntuación Las Vegas. El número de argumentos cuando se ejecuta el programa selecciona estas opciones (vea los Comentarios)
La cereza del pastel fue agregar un sistema de puntuación exactamente como en Windows, incluyendo el bonus por tiempo.

El código fuente

Después de varias revisiones y días en desarrollo, aquí está el código fuente de mi juego de solitario Klondike para curses.

#include <stdlib.h>
#include <locale.h>
#include <time.h>
#include <curses.h>
#define H addch
#define L(z) for(z=0;z<52;z++)
#define _ H(ACS_CKBOARD);
#define O 255
#define I if(
#define E else
#define J H(ACS_HLINE);
int W(int *,int *);

             int                 k[O ],         x[ O ],
            b[O],             v [ O ],i,s,     m,a ,z,e,
          w,n,d,t,c,          h,g,j,f,l,y;   void  F(void)
         {d=c[k]; e=          c[x]; f=c[b]; for(g=c;g<51;g
        [k]=k[1+g],g[x]      =x[g+1],g[b]=b[g+1],g++);g[k]=
       d; g[x]=e; g[b]=f;     c=g; } void U(void) { d = O;
      L(c) I !c[x]) d = c;     c = d; I d - O) { c[x] =1;
     b[c] = 0; F(); d = c;      } } void A(void) { m = 1;
     t = O;  l =0;  d = O;       L(c) I !c[x] & b[c]) d
      = c;    I d    - O)         v[l++] = d; e = O;
      L(c)    I x    [c]             == 1 && !c[b])
         e    = c    ;I               e - O ) { I
            d == O                     ) v[l++] =
           d; v[l++                      ] = e;
                                           }

              L(c)                       I c[x]
             >  31                      && !c[b])
            v[l++ ]                    = c; I l>0)
           I v[0] ==                    O) j = 0;
          E j=x[v[0]];                   qsort(
         v,l,sizeof(m),                   &W);}
        void K (void )            { t      = c;     d =
       0; I c[x] >  31)          { L(e)     I      (e[x]
      & 7) == (c[x] & 7))       { I x[e]   > x    [c]) d =
     1; } } } void M(void)      { I c[x] ==e) return; I n &
      2) { I 1 == c[x]) {         I a > 1) a --   ; }  } I
       n & 1) { I e >  2           & e      <      7) s +=
        5; } E { I e >              2      & e       <
         7) s += 10; E                     I x
          [c] == 1) s                      +=5;
           } g = O ;                      L(f) {
             I x[f]                       == c[x]
              - 8)
               {
                   
I b[f]) { I ~n & 1) s += 5; b[f] = 0; g = f; } } } do { h = c[x] + 8;
c[x] = e; F(); e = c[x] + 8; c = O; L(f) I x[f] == h) c = f; } while
(c - O) ; t = O; } int W(int *e, int *h) { return x[*e]-x[*h]; }
int main(int r) { char *S;time_t D=time(0);setlocale(LC_CTYPE, S="");
srand(D); L(c) { for (; v[d = rand() % 52]; ) ; d[b]=d[v]=1;
d[k]=c; } c = 24; for (d = 0; d < 7; d++) for (e = 0; e < d + 1; x[c++] =
d + (e++ + 4) * 8) ; b[24] = b[26] = b[29] = b[33] = b[38] = b[44] = b[51]
= 0; initscr(); raw(); noecho(); start_color(); init_pair(1, COLOR_RED,
COLOR_WHITE); init_pair(2, COLOR_BLACK, COLOR_WHITE); A(); n = r - 1;
I n & 1) s = -52; for(;;) { for (c = 1; c < 5; c++) { for (e = 0; e < 7;
e++) { I e == 2) continue; move(c, e * 6); _ _ _ _ _
I e == 1) { H(32); H(32); H(32); H(32); } } }
L(e) { c = x[e]; g = (c & 7) * 6; I c == 1) { I a > 2 & k[e] == z)
g += 4; I a > 1 & k[e] == y) g += 2; }
h = k[e] / 13; i = k[e] % 13; for(d=0;d<4;d++){ move(c / 8 * 2 + d + 1, g);
if (!d) { H(ACS_ULCORNER); J J J H(ACS_URCORNER); } E if (d == 3) {
H(ACS_LLCORNER); J J J H(ACS_LRCORNER); } E { H(ACS_VLINE);
if (b[e]) { _ _ _ } E { I h < 2) attron(COLOR_PAIR(1));
E attron(COLOR_PAIR(2)); if (d == 2) { H(32); H(32); H(32); } E { I !i)
H(65); E I i < 9) H(49 + i); E I i == 9) { H(49); H(48); } E {
H("JQK"[i - 10]); }
addstr(&"\xe2\x99\xa5\0\xe2\x99\xa6\0\xe2\x99\xa0\0\xe2\x99\xa3"[h * 4]);
I i - 9) H(32); } I h < 2) attroff(COLOR_PAIR(1)); E attroff(COLOR_PAIR(2));
} H(ACS_VLINE); } } } attron(A_NORMAL); w = j % 8 * 6; I j == 1) I n & 2)
w += (a - 1) * 2; move(j / 8 * 2 + 1, w); H(42); I t - O) {
c = x[t]; w = c % 8 * 6; I c == 1) { I n & 2) w += (a - 1) * 2; }
move(c / 8 * 2 + 1, w); H(38); } mvprintw(0, 0, "%sScore: %d   ", S, s);
refresh(); c = getch(); I c == 10) break;
I c == 32) { I m) { I !j) { I v[0] == O) { do { d = O; L(c) I c[x] == 1)
d = c; c = d; I c - O) { c[x] = 0; b[c] = 1; F(); }
} while (c != O); } E { I n & 2) { a = 0; U(); I d - O) a++; U();
I d != O) a++, y = k[d]; U(); I d != O) a++; z = k[d]; } E U();
d = O; L(c) I !c[x]) d = c; } A(); } E { L(d) I j == x[d]) c = d;
K(); m = 0; l = 0; I !d) { for (e = 3; e < 7; e++) { g = O;
L(f) I x[f] == e) g = f; I (g == O & k[c] % 13 == 0) | (g != O &
k[c] % 13 == k[g] % 13 + 1 & k[c] / 13 == k[g] / 13)) { v[l++] = e;
} } } for (h = 0; h < 7; h++) { f = h + 32; g = O; L(e)
I (x[e] & 7) == h & x[e] > 31 & x[e] + 8 > f) { f = x[e] + 8; g = e; }
I (g == O & k[c] % 13 == 12) | (g != O & k[g] % 13 - 1 == k[c] % 13 &
((k[g] / 13) & 2) != ((k[c] / 13) & 2))) v[l++] = f; } I l) j = v[0]; } }
E { c = t; e = j; M(); d = 0; L(c) d+=c[x] < 3 | c[x] > 6; I !d)
S="Won!",s+=n&1?0:700000/difftime(time(0),D);A(); clear();
} } E I c == 9 && l) { I m) { for (c = 0; c < l; c++) I j == ((d = v[c])
- O ? x[d] : 0)) break; j = (d = v[++c % l]) - O ? x[d] : 0; } E { I l)
{ for (c = 0; c<l&j!=v[c]; c++) ; j = v[++c % l]; } } } } endwin(); }

Comentarios

Solitario Klondike

Compilación

gcc prog.c -o prog

Ejecución


    prog
    prog a
    prog a b
    prog a b c
    

Acerca del juego

Ahora puedes jugar Solitario Klondike con este programa.
Requiere ncurses para ser construido y apoyo UTF-8. Cualquier sistema operativo reciente lo hará (probado con Terminal y macOS 12.7.6)
En Fedora 21 (¡2014 es tan antiguo!) los gráficos de las cartas no aparecen, y necesitas cambiar la opción -lcurses del linker a -lcursesw.
También probé macOS 10.15 (Catalina) con Homebrew instalado, y teclee brew install ncurses y pasó por una actualización automática de 533 megabytes de Github. Los símbolos aparecen, pero ningún font contiene los bordes de las cartas y el fondo. Encuentro interesante que un sistema operativo de 2020 no tiene el soporte de símbolos que Linux tiene desde 2014.
Probé también macOS 10.11 (El Capitan), pero el ncurses v5 por defecto no admite UTF-8. Intenté instalar Homebrew pero no pudo conectarse a Github.
Se sugiere redimensionar la ventana a una terminal con 36 or 40 líneas.
Generar advertencias al compilarse para confundirte.

Jugando

Puedes ejecutarlo sin argumentos para un solitario Klondike con sistema de puntuación Windoze, incluyendo bonus de tiempo.
Agrega un argumento para obtener puntuación Las Vegas.
Agrega dos argumentos para puntuación Windoze pero repartiendo 3 cartas.
Agrega tres argumentos para puntuación Las Vegas repartiendo 3 cartas.

Teclas del juego

Puedes desplazar el cursor (un asterisco) usando la tecla Tab, y seleccionando la tarjeta para mover y soltar usando la tecla de espacio.
Presiona la barra de espacio en la esquina superior izquierda para repartir cartas.
Si te aburres oprime Enter para salir del juego.

Trucos de ofuscación

Todo el mundo sabe que el operador != es un exceso del lenguaje C. Usa solo el operador de substracción.
Lo mismo para el operador >=, usa solo >.
Ahorra llaves hoy, usa for.
Cuando escribas accesos a arreglos, siente la vibra, e intercambia aleatoriamente el arreglo y el indice.
Usa O para constantes para que luzca como cero.
Agrega algunos operadores trinarios para darle sabor al caldo.
La mejor ofuscación fue lograda cuando agregué el código para el reloj y dar la puntuación de bonus de tiempo. Justo como en el software real.
My Klondike game for curses being played
Jugando mi juego Klondike para curses.

Descarga

Puedes descargar mi participación para el IOCCC aquí:

Post Mortem

Después que el concurso cerrara, noté que había más oportunidades para ofuscar el código, y posibles caminos para optimizarlo.
Disfruté mucho el anuncio en vivo de los ganadores, pero desafortunadamente mi participación no ganó.
Comparto mi participación para que puedas divertirte jugando Klondike en tu consola de texto, y por supuesto puedo decir que este es el juego de solitario más pequeño del mundo en lenguaje C ;)
¿Te agradó este artículo? Compartelo, invitame un café en ko-fi, o puedes apoyarme mensualmente. Tu apoyo me permite dedicar más tiempo a escribir artículos como este.

Ligas

Última actualización: 08-jun-2026