5k C 语言编写的 curses 版克朗代克纸牌游戏
Klondike Solitaire game for curses in 5k of C

原始链接: https://nanochess.org/klondike_in_c.html

Oscar Toledo G. 分享了他为第 29 届国际混乱 C 代码大赛(IOCCC)开发空当接龙游戏的经历。该比赛的目标是编写出功能完备且复杂的 C 程序,同时通过严格的长度和字符限制,使其在刻意设计的干扰下难以阅读。 Toledo 选择空当接龙是出于他对该游戏的深厚感情,以及它作为 Windows 3.1 拖拽操作教学案例的历史意义。为了将逻辑压缩成紧凑且混乱的格式,他利用 `ncurses` 库进行终端交互,并结合 Unicode 符号和颜色来呈现卡牌。他将操作简化为 Tab 和空格键,并添加了发三张牌以及 Windows 风格的计分系统等功能。 该项目突显了在处理跨平台 `ncurses` 差异和 UTF-8 限制时所面临的技术挑战。尽管他的作品未获奖,但 Toledo 展示了他极具创意的混淆技术——例如交换数组索引和替换标准运算符——旨在以此向其他 C 程序员发起挑战。源码现已开放,供那些有兴趣体验这一紧凑版控制台经典游戏的用户使用。

抱歉。
相关文章

原文

by Oscar Toledo G. Jun/07/2026

Obfuscated C and Other Mysteries book by Don Libes.
I had some free time, and I noticed the 29th IOCCC started. So I decided to code an entry for the contest. If you haven't heard about the International Obfuscated C Code Contest, it is a contest running since 1984 and created by Landon Curt Noll. The objective of this contest is writing an obfuscated C program under certain size limitations specified by the rules.
You can see my previous winning entries for the IOCCC in the Contests section.

This year the maximum size is 4993 bytes (a little less than 5 kilobytes), and the number of printable characters is 2503. As there are some special rules for printable characters, there is now a tool called iocccsize for checking this.

Whenever I say obfuscated C, it is the way of writing C code in a form that does the objective but it isn't clear. For example, a simple loop counting from 1 to 10 is written like this:


#include <stdio.h>
int main(void) {
    int c;
    
    for (c = 1; c <= 10; c++)
      printf("%d\n", c);
}

But using a small fraction of the C syntax power, we can do it this way:


#include <stdio.h>
int main(void) {int c=1;while(printf("%d\n",c),11>++c);}

This gives you a small idea of what obfuscated C is about. Deciphering the best one liner of any IOCCC year is a challenge! And a way of testing your C knowledge.

The idea

After pondering several ideas, I decided for a Klondike Solitaire game in C language as I have coded several over my career. One for my own operating system about twenty years ago (I need to write more about this), and recently Klondike for Intellivision consoles.

In case you don't know, Solitaire was a game included in Windows 3.1 along Minesweeper. Both were a complete success because the gaming scene for Windows was almost null. Bored people using Windows 3.1 started playing Minesweeper and then Solitaire, and it was addictive! I didn't know until I played myself, and I understood the rules. I also discovered that there are several solitaire games, also known as patience, and this was the Klondike variation. I leaved it for good when I discovered that not all games could be won. In fact, mathematicians estimate that only 43% of the games with a single card deal can be won, and this number drops sharply to 18% if three cards are deal. Even if you can see all the cards it isn't so easy to win.

Coding it!

I started coding in Feb/06/2026, it took me three days to code the main logic in C, and I decided to use the curses library to create the cards display, use color, and added also Unicode characters to show the cards symbols. The curses library allows to control the screen for creating text interfaces without worrying about the terminal type being used.

Of course, this first version of the game was too big for the contest's limits, so I started optimizing the code. Also I had to solve the problem of the user interface for selecting and dropping the cards.

The original interface of Klondike is oriented towards moving a cursor over a card, picking it, and dropping it in another place. In fact, the legend says that Windows 3.1 included this game just to teach users how to drag&drop things with the mouse.

In order to fit in the space for the contest, my user interface has to be greatly simplified using the Tab key for selecting cards, and the Space key for dropping cards.

I don't know how to use curses

Learning curses is pretty good if you are an evil wizard... Oh sorry, I mean learning the curses library :P It felt like an eternity the learning curve for using it in the game. I discovered there are several versions of it, and I know by experience that every Linux and Mac distribution includes it in some form.

The main problem of curses is that there are way too many versions! And each operating system integrates a vaguely different version of the library. The other common problem is that the suits require UTF-8, and some curses libraries don't support it (see the Remarks). You are good with curses if you are using ASCII exclusively.

At least I found character definitions for building boxes (the cards), and Unicode provided me the symbols for the cards.

I also could use colors for red cards. Many years ago it was far more common using the ANSI/VT-52 escape sequences, but this is the type of thing that curses solves through an unified interface.

Polishing

I managed to implement a 3-card deal as an option, and also a Las Vegas scoring mode. The number of arguments when running the program selects these options (see the Remarks)

The cherry in the cake was adding a scoring system exactly like in Windows, including the bonus per time.

The source code

After several revisions and days in development, here is the source code of my Klondike Solitaire game for 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(); }

Remarks

Klondike Solitaire

Building it

gcc prog.c -o prog

Executing it


    prog
    prog a
    prog a b
    prog a b c
    

About it

Now you can play Klondike solitaire with this program.

It requires ncurses to be built, and UTF-8 support. Any recent operating system will do it (tested with Terminal and macOS 12.7.6)

In Fedora 21 (2014 is so old!) the card graphics doesn't appear, and you need to change the -lcurses linker option to -lcursesw

I tested also macOS 10.15 (Catalina), and I had Homebrew installed, so I typed brew install ncurses and it went with an automatic update to get 533 megabytes from Github. The symbols appear, but no font contains the card borders and background. I find interesting that an operating system from 2020 doesn't have the symbol support that Linux has since 2014.

I also checked with macOS 10.11 (El Capitan), but the default ncurses v5 doesn't support UTF-8. I tried installing Homebrew but it couldn't talk to Github.

You are suggested to resize the window for a 36 or 40 line terminal.

It will generate warnings on compilation to confuse you.

Playing it

You can execute it without arguments to get a Klondike solitaire game with Windoze scoring system, including bonus time scoring.

Add one argument to get a Las Vegas scoring.

Add two arguments for Windoze scoring system but dealing 3 cards.

Add three arguments for Las Vegas scoring system but dealing 3 cards.

Game keys

You can displace the cursor (an asterisk) using the Tab key, and select the card to move or drop using the Space bar.

Press Space bar in the top left to deal cards.

If you feel bored, press Enter to exit the game.

Obfuscation tricks

Everyone knows that the != operator is a surplus of the C language, use only the minus operator.

Same for the >= operator, use only >.

Save a curly brace today, use for.

Also when writing array accesses, feel the vibe, and interchange randomly the array and the index.

Use O for constants so it looks like zero.

Let's throw some trinary operators for good measure.

The best obsfucation was achieved when I added the code for the clock to give the bonus time scoring. Just like in real software.

Download

You can download my entry for the IOCCC here:

Post Mortem

Later, after the contest closed, I noticed there were some more obfuscation chances in the code, and possible optimization paths.

I enjoyed very much the live announcement of the winners, and unfortunately my entry didn't won.

I'm glad to share my entry so you can have fun playing Klondike in your text console, and of course I can claim this is the smallest solitaire game in C language ;)

Did you enjoyed this article? Share it, invite me a coffee on ko-fi, or become a monthly supporter. Your support allows me to dedicate more time to write articles like this one.

Links

Last modified: Jun/08/2026

联系我们 contact @ memedata.com