fen.c - chess-puzzles - chess puzzle book generator
(HTM) git clone git://git.codemadness.org/chess-puzzles
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) README
(DIR) LICENSE
---
fen.c (47523B)
---
1 #include <stdarg.h>
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <string.h>
5
6 #ifdef __OpenBSD__
7 #include <err.h>
8 #include <unistd.h>
9 #endif
10
11 #define LEN(s) (sizeof(s)/sizeof(*s))
12
13 /* ctype-like macros, but always compatible with ASCII / UTF-8 */
14 #define ISDIGIT(c) (((unsigned)c) - '0' < 10)
15 #define ISXDIGIT(c) ((((unsigned)c) - '0' < 10) || ((unsigned)c | 32) - 'a' < 6)
16 #define TOLOWER(c) ((((unsigned)c) - 'A' < 26) ? ((c) | 32) : (c))
17 #define TOUPPER(c) ((((unsigned)c) - 'a' < 26) ? ((c) & 0x5f) : (c))
18
19 /* macro for truecolor RGB output to tty */
20 #define SETFGCOLOR(r,g,b) printf("\x1b[38;2;%d;%d;%dm", r, g, b)
21 #define SETBGCOLOR(r,g,b) printf("\x1b[48;2;%d;%d;%dm", r, g, b)
22
23 enum outputmode { ModeInvalid = 0, ModeASCII, ModeFEN, ModePGN,
24 ModeTTY, ModeSVG, ModeSpeak };
25 enum outputmode outputmode = ModeSVG; /* default is SVG */
26
27 static int onlylastmove = 0, silent = 0, dutchmode = 0;
28
29 /* localization of letter for PGN pieces */
30 const char *pgn_piecemapping = "";
31
32 typedef unsigned char Color; /* for RGB: 0-255 */
33
34 struct theme {
35 const char *name;
36 /* RGB values */
37 Color border[3];
38 Color darksquare[3];
39 Color lightsquare[3];
40 Color darksquarehi[3];
41 Color lightsquarehi[3];
42 Color lightsquarecheck[3];
43 Color darksquarecheck[3];
44 };
45
46 struct theme themes[] = {
47 /* lichess default brown theme colors (red, green, blue) */
48 {
49 .name = "default",
50 .border = { 0x70, 0x49, 0x2d },
51 .darksquare = { 0xb5, 0x88, 0x63 },
52 .lightsquare = { 0xf0, 0xd9, 0xb5 },
53 .darksquarehi = { 0xaa, 0xa2, 0x3a },
54 .lightsquarehi = { 0xcd, 0xd2, 0x6a },
55 .lightsquarecheck = { 0xff, 0x6a, 0x6a },
56 .darksquarecheck = { 0xff, 0x3a, 0x3a }
57 },
58 /* lichess green theme */
59 {
60 .name = "green",
61 .border = { 0x33, 0x33, 0x33 },
62 .darksquare = { 0x86, 0xa6, 0x66 },
63 .lightsquare = { 0xff, 0xff, 0xdd },
64 .darksquarehi = { 0x4f, 0xa1, 0x8e },
65 .lightsquarehi = { 0x96, 0xd6, 0xd4 },
66 .lightsquarecheck = { 0xff, 0x6a, 0x6a },
67 .darksquarecheck = { 0xff, 0x3a, 0x3a }
68 },
69 /* red / love theme */
70 {
71 .name = "love",
72 .border = { 0x33, 0x33, 0x33 },
73 .darksquare = { 0xd9, 0x4c, 0x4c },
74 .lightsquare = { 0xff, 0xca, 0xca },
75 .darksquarehi = { 0xaa, 0xa2, 0x3a },
76 .lightsquarehi = { 0xcd, 0xd2, 0x6a },
77 .lightsquarecheck = { 0xff, 0x6a, 0x6a },
78 .darksquarecheck = { 0xff, 0x3a, 0x3a }
79 },
80 /* greyscale theme, highlight is still green though */
81 {
82 .name = "grey",
83 .border = { 0x00, 0x00, 0x00 },
84 .darksquare = { 0x66, 0x66, 0x66 },
85 .lightsquare = { 0xaa, 0xaa, 0xaa },
86 .darksquarehi = { 0x66, 0x61, 0x23 },
87 .lightsquarehi = { 0xa8, 0xab, 0x55 },
88 .lightsquarecheck = { 0xff, 0x6a, 0x6a },
89 .darksquarecheck = { 0xff, 0x3a, 0x3a }
90 },
91 /* print theme, highlight is still green though */
92 {
93 .name = "print",
94 .border = { 0x00, 0x00, 0x00 },
95 .darksquare = { 0xcc, 0xcc, 0xcc },
96 .lightsquare = { 0xff, 0xff, 0xff },
97 .darksquarehi = { 0xbb, 0xbb, 0xbb },
98 .lightsquarehi = { 0xdd, 0xdd, 0xdd },
99 .lightsquarecheck = { 0x77, 0x77, 0x77 },
100 .darksquarecheck = { 0x77, 0x77, 0x77 }
101 }
102 };
103
104 struct board {
105 char tiles[8][8]; /* board tiles and piece placement */
106 int enpassantsquare[2]; /* default: no: { -1, -1 } */
107
108 char highlight[8][8]; /* highlighted squares, (0 = none, 1 = highlight, 2 = check or mate) */
109
110 int side_to_move; /* default: white to move: 'w' */
111 int white_can_castle[2]; /* allow king side, allow queen side? default: { 0, 0 } */
112 int black_can_castle[2]; /* allow king side, allow queen side? default: { 0, 0 } */
113
114 int movenumber; /* default: 1 */
115 int halfmove; /* default: 0 */
116
117 int flipboard; /* flip board ? default: 0 */
118 int showcoords; /* board coordinates? default: 1 */
119 int showside; /* show indicator for which side to move: default: 1 */
120 int highlights; /* highlight moves and checks? default: 1 */
121 struct theme *theme; /* board theme */
122 };
123
124 /* set theme by name */
125 struct theme *
126 board_set_theme(struct board *b, const char *name)
127 {
128 int i;
129
130 for (i = 0; i < LEN(themes); i++) {
131 if (!strcmp(themes[i].name, name)) {
132 b->theme = &themes[i];
133 return b->theme;
134 }
135 }
136 return NULL;
137 }
138
139 /* initialize board and set sane defaults */
140 void
141 board_init(struct board *b)
142 {
143 memset(b, 0, sizeof(*b)); /* zero fields by default */
144 b->side_to_move = 'w'; /* white */
145 b->enpassantsquare[0] = -1; /* no en passant */
146 b->enpassantsquare[1] = -1;
147 b->movenumber = 1;
148 b->flipboard = 0;
149 b->showcoords = 1;
150 b->showside = 1;
151 b->highlights = 1;
152 b->theme = &themes[0]; /* use first theme as default */
153 }
154
155 /* copy entire board and its state */
156 void
157 board_copy(struct board *bd, struct board *bs)
158 {
159 memcpy(bd, bs, sizeof(*bd));
160 }
161
162 int
163 isvalidsquare(int x, int y)
164 {
165 return !(x < 0 || x >= 8 || y < 0 || y >= 8);
166 }
167
168 int
169 iswhitepiece(int piece)
170 {
171 return piece == 'K' || piece == 'Q' || piece == 'R' ||
172 piece == 'B' || piece == 'N' || piece == 'P';
173 }
174
175 int
176 isblackpiece(int piece)
177 {
178 return piece == 'k' || piece == 'q' || piece == 'r' ||
179 piece == 'b' || piece == 'n' || piece == 'p';
180 }
181
182 int
183 isvalidpiece(int c)
184 {
185 static char pieces[] = "PNBRQKpnbrqk";
186
187 return strchr(pieces, c) ? 1 : 0;
188 }
189
190 int
191 xtofile(int c)
192 {
193 return 'a' + c;
194 }
195
196 int
197 ytorank(int c)
198 {
199 return '8' - c;
200 }
201
202 int
203 filetox(int c)
204 {
205 return c - 'a';
206 }
207
208 int
209 ranktoy(int c)
210 {
211 return '8' - c;
212 }
213
214 int
215 squaretoxy(const char *s, int *x, int *y)
216 {
217 if (*s >= 'a' && *s <= 'h' &&
218 *(s + 1) >= '1' && *(s + 1) <= '8') {
219 *x = filetox(*s);
220 *y = ranktoy(*(s + 1));
221 return 1;
222 }
223 return 0;
224 }
225
226 /* write formatted string, only if output mode is ModePGN */
227 void
228 pgn(const char *fmt, ...)
229 {
230 va_list ap;
231
232 if (outputmode != ModePGN || silent)
233 return;
234
235 va_start(ap, fmt);
236 vprintf(fmt, ap);
237 va_end(ap);
238 }
239
240 /* write formatted string, only if output mode is ModeSpeak */
241 void
242 speak(const char *fmt, ...)
243 {
244 va_list ap;
245
246 if (outputmode != ModeSpeak || silent)
247 return;
248
249 va_start(ap, fmt);
250 vprintf(fmt, ap);
251 va_end(ap);
252 }
253
254 /* remap letter for PGN pieces, default: "KQRBN"
255 Dutch: (K)oning, (D)ame, (T)oren, (L)oper, (P)aard: "KDTLP" */
256 int
257 pgnpiece(int piece)
258 {
259 piece = TOUPPER(piece);
260
261 /* no mapping */
262 if (!pgn_piecemapping[0])
263 return piece;
264
265 switch (piece) {
266 case 'K': piece = pgn_piecemapping[0]; break;
267 case 'Q': piece = pgn_piecemapping[1]; break;
268 case 'R': piece = pgn_piecemapping[2]; break;
269 case 'B': piece = pgn_piecemapping[3]; break;
270 case 'N': piece = pgn_piecemapping[4]; break;
271 }
272
273 return piece;
274 }
275
276 void
277 speakpiece(int piece)
278 {
279 switch (piece) {
280 case 'K': case 'k': speak(dutchmode ? "koning " : "king "); break;
281 case 'Q': case 'q': speak(dutchmode ? "dame " : "queen "); break;
282 case 'R': case 'r': speak(dutchmode ? "toren " : "rook "); break;
283 case 'B': case 'b': speak(dutchmode ? "loper " : "bishop "); break;
284 case 'N': case 'n': speak(dutchmode ? "paard " : "knight "); break;
285 case 'P': case 'p': speak(dutchmode ? "pion " : "pawn "); break;
286 }
287 }
288
289 /* place a piece, if possible */
290 void
291 place(struct board *b, int piece, int x, int y)
292 {
293 if (!isvalidsquare(x, y))
294 return;
295
296 b->tiles[y][x] = piece;
297 }
298
299 /* get piece, if possible */
300 int
301 getpiece(struct board *b, int x, int y)
302 {
303 if (!isvalidsquare(x, y))
304 return 0;
305 return b->tiles[y][x];
306 }
307
308 void
309 highlightmove(struct board *b, int x, int y)
310 {
311 if (isvalidsquare(x, y))
312 b->highlight[y][x] = 1;
313 }
314
315 void
316 highlightcheck(struct board *b, int x, int y)
317 {
318 if (isvalidsquare(x, y))
319 b->highlight[y][x] = 2;
320 }
321
322 Color *
323 getsquarecolor(struct board *b, int x, int y, int invert)
324 {
325 struct theme *t;
326
327 t = b->theme;
328 if (((x % 2) ^ (y % 2)) == invert) {
329 switch (b->highlight[y][x]) {
330 case 1: return t->lightsquarehi;
331 case 2: return t->lightsquarecheck;
332 default: return t->lightsquare;
333 }
334 } else {
335 switch (b->highlight[y][x]) {
336 case 1: return t->darksquarehi;
337 case 2: return t->darksquarecheck;
338 default: return t->darksquare;
339 }
340 }
341 return t->lightsquare; /* never happens */
342 }
343
344 void
345 showboardfen(struct board *b)
346 {
347 int x, y, piece, skip;
348
349 for (y = 0; y < 8; y++) {
350 if (y > 0)
351 putchar('/');
352 skip = 0;
353 for (x = 0; x < 8; x++) {
354 piece = getpiece(b, x, y);
355 if (piece) {
356 if (skip)
357 putchar(skip + '0');
358 putchar(piece);
359 skip = 0;
360 } else {
361 skip++;
362 }
363 }
364 if (skip)
365 putchar(skip + '0');
366 }
367 printf(" %c ", b->side_to_move);
368 if (b->white_can_castle[0])
369 putchar('K');
370 if (b->white_can_castle[1])
371 putchar('Q');
372 if (b->black_can_castle[0])
373 putchar('k');
374 if (b->black_can_castle[1])
375 putchar('q');
376 if ((b->white_can_castle[0] + b->white_can_castle[1] +
377 b->black_can_castle[0] + b->black_can_castle[1]) == 0)
378 putchar('-'); /* no castling for either side */
379 putchar(' ');
380
381 if (b->enpassantsquare[0] != -1 && b->enpassantsquare[1] != -1) {
382 putchar(xtofile(b->enpassantsquare[0]));
383 putchar(ytorank(b->enpassantsquare[1]));
384 } else {
385 putchar('-');
386 }
387 printf(" %d %d\n", b->halfmove, b->movenumber);
388 }
389
390 void
391 showpiece_svg(int c)
392 {
393 const char *s = "";
394
395 /* lichess default set,
396 extracted from https://github.com/lichess-org/lila/tree/master/public/piece/cburnett */
397 switch (c) {
398 case 'K': s = "<use href=\"#wk\"/>"; break;
399 case 'Q': s = "<use href=\"#wq\"/>"; break;
400 case 'R': s = "<use href=\"#wr\"/>"; break;
401 case 'B': s = "<use href=\"#wb\"/>"; break;
402 case 'N': s = "<use href=\"#wn\"/>"; break;
403 case 'P': s = "<use href=\"#pawn\" fill=\"#fff\"/>"; break;
404 case 'k': s = "<use href=\"#bk\"/>"; break;
405 case 'q': s = "<use href=\"#bq\"/>"; break;
406 case 'r': s = "<use href=\"#br\"/>"; break;
407 case 'b': s = "<use href=\"#bb\"/>"; break;
408 case 'n': s = "<use href=\"#bn\"/>"; break;
409 case 'p': s = "<use href=\"#pawn\" fill=\"#000\"/>"; break;
410 }
411
412 if (*s)
413 fputs(s, stdout);
414 }
415
416 void
417 output_svg(struct board *b)
418 {
419 Color *color;
420 const char *s;
421 char pieces[] = "pPKQRBNkqrbn"; /* pieces, check if they are used for definitions */
422 unsigned char pieceused[LEN("pPKQRBNkqrbn")] = { 0 };
423 int i, ix, iy, x, y, piece;
424
425 fputs("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n"
426 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n"
427 "<svg width=\"360\" height=\"360\" viewBox=\"0 0 360 360\" xmlns=\"http://www.w3.org/2000/svg\">\n"
428 "<rect fill=\"#fff\" stroke=\"#000\" x=\"0\" y=\"0\" width=\"360\" height=\"360\"/>\n", stdout);
429
430 for (i = 0; i < LEN(pieces); i++) {
431 for (y = 0; y < 8 && !pieceused[i]; y++) {
432 for (x = 0; x < 8; x++) {
433 if (getpiece(b, x, y) == pieces[i]) {
434 pieceused[i] = 1;
435 break;
436 }
437 }
438 }
439 }
440
441 fputs("<defs>\n", stdout);
442 for (i = 0; i < LEN(pieces); i++) {
443 if (!pieceused[i])
444 continue;
445 s = NULL;
446 switch (pieces[i]) {
447 case 'K': s ="<g id=\"wk\" fill=\"none\" fill-rule=\"evenodd\" stroke=\"#000\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M22.5 11.63V6M20 8h5\" stroke-linejoin=\"miter\"/><path d=\"M22.5 25s4.5-7.5 3-10.5c0 0-1-2.5-3-2.5s-3 2.5-3 2.5c-1.5 3 3 10.5 3 10.5\" fill=\"#fff\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\"/><path d=\"M11.5 37c5.5 3.5 15.5 3.5 21 0v-7s9-4.5 6-10.5c-4-6.5-13.5-3.5-16 4V27v-3.5c-3.5-7.5-13-10.5-16-4-3 6 5 10 5 10V37z\" fill=\"#fff\"/><path d=\"M11.5 30c5.5-3 15.5-3 21 0m-21 3.5c5.5-3 15.5-3 21 0m-21 3.5c5.5-3 15.5-3 21 0\"/></g>\n"; break;
448 case 'Q': s = "<g id=\"wq\" fill=\"#fff\" fill-rule=\"evenodd\" stroke=\"#000\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M8 12a2 2 0 1 1-4 0 2 2 0 1 1 4 0zm16.5-4.5a2 2 0 1 1-4 0 2 2 0 1 1 4 0zM41 12a2 2 0 1 1-4 0 2 2 0 1 1 4 0zM16 8.5a2 2 0 1 1-4 0 2 2 0 1 1 4 0zM33 9a2 2 0 1 1-4 0 2 2 0 1 1 4 0z\"/><path d=\"M9 26c8.5-1.5 21-1.5 27 0l2-12-7 11V11l-5.5 13.5-3-15-3 15-5.5-14V25L7 14l2 12z\" stroke-linecap=\"butt\"/><path d=\"M9 26c0 2 1.5 2 2.5 4 1 1.5 1 1 .5 3.5-1.5 1-1.5 2.5-1.5 2.5-1.5 1.5.5 2.5.5 2.5 6.5 1 16.5 1 23 0 0 0 1.5-1 0-2.5 0 0 .5-1.5-1-2.5-.5-2.5-.5-2 .5-3.5 1-2 2.5-2 2.5-4-8.5-1.5-18.5-1.5-27 0z\" stroke-linecap=\"butt\"/><path d=\"M11.5 30c3.5-1 18.5-1 22 0M12 33.5c6-1 15-1 21 0\" fill=\"none\"/></g>\n"; break;
449 case 'R': s = "<g id=\"wr\" fill=\"#fff\" fill-rule=\"evenodd\" stroke=\"#000\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M9 39h27v-3H9v3zm3-3v-4h21v4H12zm-1-22V9h4v2h5V9h5v2h5V9h4v5\" stroke-linecap=\"butt\"/><path d=\"M34 14l-3 3H14l-3-3\"/><path d=\"M31 17v12.5H14V17\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\"/><path d=\"M31 29.5l1.5 2.5h-20l1.5-2.5\"/><path d=\"M11 14h23\" fill=\"none\" stroke-linejoin=\"miter\"/></g>\n"; break;
450 case 'B': s = "<g id=\"wb\" fill=\"none\" fill-rule=\"evenodd\" stroke=\"#000\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><g fill=\"#fff\" stroke-linecap=\"butt\"><path d=\"M9 36c3.39-.97 10.11.43 13.5-2 3.39 2.43 10.11 1.03 13.5 2 0 0 1.65.54 3 2-.68.97-1.65.99-3 .5-3.39-.97-10.11.46-13.5-1-3.39 1.46-10.11.03-13.5 1-1.354.49-2.323.47-3-.5 1.354-1.94 3-2 3-2z\"/><path d=\"M15 32c2.5 2.5 12.5 2.5 15 0 .5-1.5 0-2 0-2 0-2.5-2.5-4-2.5-4 5.5-1.5 6-11.5-5-15.5-11 4-10.5 14-5 15.5 0 0-2.5 1.5-2.5 4 0 0-.5.5 0 2z\"/><path d=\"M25 8a2.5 2.5 0 1 1-5 0 2.5 2.5 0 1 1 5 0z\"/></g><path d=\"M17.5 26h10M15 30h15m-7.5-14.5v5M20 18h5\" stroke-linejoin=\"miter\"/></g>\n"; break;
451 case 'N': s = "<g id=\"wn\" fill=\"none\" fill-rule=\"evenodd\" stroke=\"#000\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M22 10c10.5 1 16.5 8 16 29H15c0-9 10-6.5 8-21\" fill=\"#fff\"/><path d=\"M24 18c.38 2.91-5.55 7.37-8 9-3 2-2.82 4.34-5 4-1.042-.94 1.41-3.04 0-3-1 0 .19 1.23-1 2-1 0-4.003 1-4-4 0-2 6-12 6-12s1.89-1.9 2-3.5c-.73-.994-.5-2-.5-3 1-1 3 2.5 3 2.5h2s.78-1.992 2.5-3c1 0 1 3 1 3\" fill=\"#fff\"/><path d=\"M9.5 25.5a.5.5 0 1 1-1 0 .5.5 0 1 1 1 0zm5.433-9.75a.5 1.5 30 1 1-.866-.5.5 1.5 30 1 1 .866.5z\" fill=\"#000\"/></g>\n"; break;
452 case 'P':
453 case 'p':
454 s = "<path id=\"pawn\" d=\"M22.5 9c-2.21 0-4 1.79-4 4 0 .89.29 1.71.78 2.38C17.33 16.5 16 18.59 16 21c0 2.03.94 3.84 2.41 5.03-3 1.06-7.41 5.55-7.41 13.47h23c0-7.92-4.41-12.41-7.41-13.47 1.47-1.19 2.41-3 2.41-5.03 0-2.41-1.33-4.5-3.28-5.62.49-.67.78-1.49.78-2.38 0-2.21-1.79-4-4-4z\" stroke=\"#000\" stroke-width=\"1.5\" stroke-linecap=\"round\"/>\n";
455 pieceused[0] = pieceused[1] = 0; /* unset used, only output pawn once */
456 break;
457 case 'k': s = "<g id=\"bk\" fill=\"none\" fill-rule=\"evenodd\" stroke=\"#000\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M22.5 11.63V6\" stroke-linejoin=\"miter\"/><path d=\"M22.5 25s4.5-7.5 3-10.5c0 0-1-2.5-3-2.5s-3 2.5-3 2.5c-1.5 3 3 10.5 3 10.5\" fill=\"#000\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\"/><path d=\"M11.5 37c5.5 3.5 15.5 3.5 21 0v-7s9-4.5 6-10.5c-4-6.5-13.5-3.5-16 4V27v-3.5c-3.5-7.5-13-10.5-16-4-3 6 5 10 5 10V37z\" fill=\"#000\"/><path d=\"M20 8h5\" stroke-linejoin=\"miter\"/><path d=\"M32 29.5s8.5-4 6.03-9.65C34.15 14 25 18 22.5 24.5l.01 2.1-.01-2.1C20 18 9.906 14 6.997 19.85c-2.497 5.65 4.853 9 4.853 9\" stroke=\"#ececec\"/><path d=\"M11.5 30c5.5-3 15.5-3 21 0m-21 3.5c5.5-3 15.5-3 21 0m-21 3.5c5.5-3 15.5-3 21 0\" stroke=\"#ececec\"/></g>\n"; break;
458 case 'q': s = "<g id=\"bq\" fill-rule=\"evenodd\" stroke=\"#000\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><g stroke=\"none\"><circle cx=\"6\" cy=\"12\" r=\"2.75\"/><circle cx=\"14\" cy=\"9\" r=\"2.75\"/><circle cx=\"22.5\" cy=\"8\" r=\"2.75\"/><circle cx=\"31\" cy=\"9\" r=\"2.75\"/><circle cx=\"39\" cy=\"12\" r=\"2.75\"/></g><path d=\"M9 26c8.5-1.5 21-1.5 27 0l2.5-12.5L31 25l-.3-14.1-5.2 13.6-3-14.5-3 14.5-5.2-13.6L14 25 6.5 13.5 9 26z\" stroke-linecap=\"butt\"/><path d=\"M9 26c0 2 1.5 2 2.5 4 1 1.5 1 1 .5 3.5-1.5 1-1.5 2.5-1.5 2.5-1.5 1.5.5 2.5.5 2.5 6.5 1 16.5 1 23 0 0 0 1.5-1 0-2.5 0 0 .5-1.5-1-2.5-.5-2.5-.5-2 .5-3.5 1-2 2.5-2 2.5-4-8.5-1.5-18.5-1.5-27 0z\" stroke-linecap=\"butt\"/><path d=\"M11 38.5a35 35 1 0 0 23 0\" fill=\"none\" stroke-linecap=\"butt\"/><path d=\"M11 29a35 35 1 0 1 23 0m-21.5 2.5h20m-21 3a35 35 1 0 0 22 0m-23 3a35 35 1 0 0 24 0\" fill=\"none\" stroke=\"#ececec\"/></g>\n"; break;
459 case 'r': s = "<g id=\"br\" fill-rule=\"evenodd\" stroke=\"#000\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M9 39h27v-3H9v3zm3.5-7l1.5-2.5h17l1.5 2.5h-20zm-.5 4v-4h21v4H12z\" stroke-linecap=\"butt\"/><path d=\"M14 29.5v-13h17v13H14z\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\"/><path d=\"M14 16.5L11 14h23l-3 2.5H14zM11 14V9h4v2h5V9h5v2h5V9h4v5H11z\" stroke-linecap=\"butt\"/><path d=\"M12 35.5h21m-20-4h19m-18-2h17m-17-13h17M11 14h23\" fill=\"none\" stroke=\"#ececec\" stroke-width=\"1\" stroke-linejoin=\"miter\"/></g>\n"; break;
460 case 'b': s = "<g id=\"bb\" fill=\"none\" fill-rule=\"evenodd\" stroke=\"#000\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><g fill=\"#000\" stroke-linecap=\"butt\"><path d=\"M9 36c3.39-.97 10.11.43 13.5-2 3.39 2.43 10.11 1.03 13.5 2 0 0 1.65.54 3 2-.68.97-1.65.99-3 .5-3.39-.97-10.11.46-13.5-1-3.39 1.46-10.11.03-13.5 1-1.354.49-2.323.47-3-.5 1.354-1.94 3-2 3-2z\"/><path d=\"M15 32c2.5 2.5 12.5 2.5 15 0 .5-1.5 0-2 0-2 0-2.5-2.5-4-2.5-4 5.5-1.5 6-11.5-5-15.5-11 4-10.5 14-5 15.5 0 0-2.5 1.5-2.5 4 0 0-.5.5 0 2z\"/><path d=\"M25 8a2.5 2.5 0 1 1-5 0 2.5 2.5 0 1 1 5 0z\"/></g><path d=\"M17.5 26h10M15 30h15m-7.5-14.5v5M20 18h5\" stroke=\"#ececec\" stroke-linejoin=\"miter\"/></g>\n"; break;
461 case 'n': s = "<g id=\"bn\" fill=\"none\" fill-rule=\"evenodd\" stroke=\"#000\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M22 10c10.5 1 16.5 8 16 29H15c0-9 10-6.5 8-21\" fill=\"#000\"/><path d=\"M24 18c.38 2.91-5.55 7.37-8 9-3 2-2.82 4.34-5 4-1.042-.94 1.41-3.04 0-3-1 0 .19 1.23-1 2-1 0-4.003 1-4-4 0-2 6-12 6-12s1.89-1.9 2-3.5c-.73-.994-.5-2-.5-3 1-1 3 2.5 3 2.5h2s.78-1.992 2.5-3c1 0 1 3 1 3\" fill=\"#000\"/><path d=\"M9.5 25.5a.5.5 0 1 1-1 0 .5.5 0 1 1 1 0zm5.433-9.75a.5 1.5 30 1 1-.866-.5.5 1.5 30 1 1 .866.5z\" fill=\"#ececec\" stroke=\"#ececec\"/><path d=\"M24.55 10.4l-.45 1.45.5.15c3.15 1 5.65 2.49 7.9 6.75S35.75 29.06 35.25 39l-.05.5h2.25l.05-.5c.5-10.06-.88-16.85-3.25-21.34-2.37-4.49-5.79-6.64-9.19-7.16l-.51-.1z\" fill=\"#ececec\" stroke=\"none\"/></g>\n"; break;
462 default: break;
463 }
464 if (s)
465 fputs(s, stdout);
466 }
467 fputs("</defs>\n", stdout);
468
469 for (iy = 0; iy < 8; iy++) {
470 y = b->flipboard ? 7 - iy : iy;
471
472 for (ix = 0; ix < 8; ix++) {
473 x = b->flipboard ? 7 - ix : ix;
474 color = getsquarecolor(b, x, y, 0);
475
476 printf("<g><rect x=\"%d\" y=\"%d\" width=\"45\" height=\"45\" fill=\"#%02x%02x%02x\"/></g>\n",
477 ix * 45, iy * 45, color[0], color[1], color[2]);
478
479 piece = getpiece(b, x, y);
480 if (piece) {
481 printf("<g transform=\"translate(%d %d)\">", ix * 45, iy * 45);
482 showpiece_svg(piece);
483 fputs("</g>\n", stdout);
484 }
485 }
486 }
487
488 if (b->showcoords) {
489 ix = 7;
490 x = b->flipboard ? 0 : 7;
491 for (iy = 0; iy < 8; iy++) {
492 y = b->flipboard ? 7 - iy : iy;
493 /* inverse square color for text */
494 color = getsquarecolor(b, x, y, 1);
495
496 printf("<text x=\"%d\" y=\"%d\" fill=\"#%02x%02x%02x\" text-anchor=\"end\" style=\"font-family: sans-serif; font-size: 10px\">%c</text>\n",
497 (ix + 1) * 45 - 2, (iy * 45) + 10, color[0], color[1], color[2], ytorank(y));
498 }
499 iy = 7;
500 y = b->flipboard ? 0 : 7;
501 for (ix = 0; ix < 8; ix++) {
502 x = b->flipboard ? 7 - ix : ix;
503 /* inverse square color for text */
504 color = getsquarecolor(b, x, y, 1);
505
506 printf("<text x=\"%d\" y=\"%d\" fill=\"#%02x%02x%02x\" text-anchor=\"start\" style=\"font-family: sans-serif; font-size: 10px\">%c</text>\n",
507 (ix * 45) + 2, (iy + 1) * 45 - 3, color[0], color[1], color[2], xtofile(x));
508 }
509 }
510
511 if (b->showside) {
512 /* circle indicator for which side to move */
513 fputs("<circle cx=\"354\" stroke-width=\"1\" r=\"5\" fill=\"", stdout);
514 if (b->side_to_move == 'w') {
515 fputs("white\" stroke=\"black\" cy=\"", stdout);
516 printf("%d", b->flipboard ? 6 : 354);
517 } else {
518 fputs("black\" stroke=\"white\" cy=\"", stdout);
519 printf("%d", b->flipboard ? 354 : 6);
520 }
521 fputs("\"></circle>", stdout);
522 }
523
524 fputs("</svg>\n", stdout);
525 }
526
527 void
528 showpiece_tty(int c)
529 {
530 const char *s = "";
531
532 /* unicode characters */
533 switch (c) {
534 case 'K': s = "♔"; break;
535 case 'Q': s = "♕"; break;
536 case 'R': s = "♖"; break;
537 case 'B': s = "♗"; break;
538 case 'N': s = "♘"; break;
539 case 'P': s = "♙"; break;
540 case 'k': s = "♚"; break;
541 case 'q': s = "♛"; break;
542 case 'r': s = "♜"; break;
543 case 'b': s = "♝"; break;
544 case 'n': s = "♞"; break;
545 case 'p': s = "♟"; break;
546 }
547
548 if (*s)
549 fputs(s, stdout);
550 }
551
552 /* show board */
553 void
554 output_tty(struct board *b)
555 {
556 struct theme *t;
557 Color *color;
558 int ix, iy, x, y, piece;
559
560 t = b->theme;
561
562 SETBGCOLOR(t->border[0], t->border[1], t->border[2]);
563 fputs(" ", stdout);
564 if (b->showside) {
565 if (b->side_to_move == 'w' && b->flipboard)
566 printf("\x1b[30;47m%c", b->side_to_move);
567 else if (b->side_to_move == 'b' && !b->flipboard)
568 printf("\x1b[37;40m%c", b->side_to_move);
569 else
570 putchar(' ');
571 } else {
572 putchar(' ');
573 }
574 printf("\x1b[0m"); /* reset */
575 putchar('\n');
576
577 for (iy = 0; iy < 8; iy++) {
578 y = b->flipboard ? 7 - iy : iy;
579
580 SETBGCOLOR(t->border[0], t->border[1], t->border[2]);
581 fputs("\x1b[97m", stdout); /* bright white */
582 fputs(" ", stdout);
583
584 for (ix = 0; ix < 8; ix++) {
585 x = b->flipboard ? 7 - ix : ix;
586 color = getsquarecolor(b, x, y, 0);
587 SETBGCOLOR(color[0], color[1], color[2]);
588
589 fputs(" ", stdout);
590 piece = getpiece(b, x, y);
591 if (piece) {
592 if (piece >= 'A' && piece <= 'Z')
593 fputs("\x1b[97m", stdout); /* bright white */
594 else
595 fputs("\x1b[30m", stdout); /* black */
596 /* workaround: use black unicode chess symbol, because
597 the color is filled and better visible */
598 showpiece_tty(TOLOWER(piece));
599 } else {
600 fputs(" ", stdout);
601 }
602 fputs(" ", stdout);
603 }
604 printf("\x1b[0m"); /* reset */
605
606 color = t->border;
607 SETBGCOLOR(color[0], color[1], color[2]);
608 if (b->showcoords) {
609 fputs("\x1b[97m", stdout); /* bright white */
610 putchar(ytorank(y));
611 putchar(' ');
612 } else {
613 fputs(" ", stdout);
614 }
615
616 printf("\x1b[0m"); /* reset */
617 putchar('\n');
618 }
619 color = t->border;
620 SETBGCOLOR(color[0], color[1], color[2]);
621 fputs("\x1b[97m", stdout); /* bright white */
622 if (b->showcoords) {
623 fputs(" ", stdout);
624 for (iy = 0; iy < 8; iy++) {
625 y = b->flipboard ? 7 - iy : iy;
626 putchar(xtofile(y));
627 fputs(" ", stdout);
628 }
629 } else {
630 fputs(" ", stdout);
631 }
632
633 if (b->showside) {
634 if (b->side_to_move == 'w' && !b->flipboard)
635 printf("\x1b[30;47m%c", b->side_to_move);
636 else if (b->side_to_move == 'b' && b->flipboard)
637 printf("\x1b[37;40m%c", b->side_to_move);
638 else
639 putchar(' ');
640 } else {
641 putchar(' ');
642 }
643
644 printf("\x1b[0m"); /* reset */
645 printf("\n");
646 }
647
648 void
649 showpiece_ascii(int c)
650 {
651 putchar(c);
652 }
653
654 /* OnlyFENs */
655 void
656 output_fen(struct board *b)
657 {
658 showboardfen(b);
659 }
660
661 /* show board */
662 void
663 output_ascii(struct board *b)
664 {
665 unsigned char hi[3] = { '>', ' ', '<' };
666 unsigned char dark[3] = { '.', '.', '.' };
667 unsigned char light[3] = { ' ', ' ', ' ' };
668 unsigned char *color;
669 int ix, iy, x, y, piece;
670
671 for (iy = 0; iy < 8; iy++) {
672 y = b->flipboard ? 7 - iy : iy;
673
674 fputs("+---+---+---+---+---+---+---+---+\n", stdout);
675 for (ix = 0; ix < 8; ix++) {
676 x = b->flipboard ? 7 - ix : ix;
677
678 if (((x % 2) ^ (y % 2)) == 0)
679 color = b->highlight[y][x] ? hi : light;
680 else
681 color = b->highlight[y][x] ? hi : dark;
682
683 if (ix == 0)
684 putchar('|');
685 putchar(color[0]);
686 piece = getpiece(b, x, y);
687 if (piece)
688 showpiece_ascii(piece);
689 else
690 putchar(color[1]);
691 putchar(color[2]);
692 putchar('|');
693 }
694 if (b->showcoords) {
695 putchar(' ');
696 putchar(ytorank(y));
697 }
698 putchar('\n');
699 }
700 fputs("+---+---+---+---+---+---+---+---+\n", stdout);
701 if (b->showcoords) {
702 fputs(" ", stdout);
703 for (iy = 0; iy < 8; iy++) {
704 if (iy)
705 fputs(" | ", stdout);
706 y = b->flipboard ? 7 - iy : iy;
707 putchar(xtofile(y));
708 }
709 fputs(" |\n", stdout);
710 }
711
712 fputs("\n", stdout);
713 }
714
715 int
716 findking(struct board *b, int side, int *kingx, int *kingy)
717 {
718 int king, x, y;
719
720 king = side == 'w' ? 'K' : 'k';
721 *kingx = -1;
722 *kingy = -1;
723
724 /* find king */
725 for (y = 0; y < 8; y++) {
726 for (x = 0; x < 8; x++) {
727 if (getpiece(b, x, y) == king) {
728 *kingx = x;
729 *kingy = y;
730 return 1;
731 }
732 }
733 }
734 return 0;
735 }
736
737 int
738 isenpassantplayed(struct board *b, int side, int x, int y)
739 {
740 if (side == 'w') {
741 return (getpiece(b, x - 1, y) == 'p' ||
742 getpiece(b, x + 1, y) == 'p');
743 } else if (side == 'b') {
744 return (getpiece(b, x - 1, y) == 'P' ||
745 getpiece(b, x + 1, y) == 'P');
746 }
747 return 0;
748 }
749
750 int
751 isincheck(struct board *b, int side)
752 {
753 int king[] = { -1, -1, -1, 0, -1, 1, 0, -1, 0, 1, 1, -1, 1, 0, 1, 1 };
754 int diag[] = { -1, -1, 1, 1, -1, 1, 1, -1 };
755 int line[] = { 1, 0, 0, 1, -1, 0, 0, -1 };
756 int knight[] = { -1, -2, 1, -2, -1, 2, 1, 2,
757 -2, -1, 2, -1, -2, 1, 2, 1
758 };
759 int i, j, x, y;
760 int kingx, kingy;
761 int piece;
762
763 /* find our king */
764 if (!findking(b, side, &kingx, &kingy))
765 return 0; /* should not happen */
766
767 /* check kings (illegal) */
768 for (j = 0; j < LEN(king); j += 2) {
769 x = kingx + king[j];
770 y = kingy + king[j + 1];
771 piece = getpiece(b, x, y);
772 if (piece && strchr("kK", piece))
773 return 1;
774 }
775
776 /* check files and ranks (for queen and rook) */
777 for (j = 0; j < LEN(line); j += 2) {
778 for (i = 1; i < 8; i++) {
779 x = kingx + (i * line[j]);
780 y = kingy + (i * line[j + 1]);
781 if (!(piece = getpiece(b, x, y)))
782 continue;
783 /* a piece is in front of it */
784 if (piece && strchr("kKbBnNpP", piece))
785 break;
786 /* own piece blocking/defending it */
787 if ((side == 'w' && iswhitepiece(piece)) ||
788 (side == 'b' && isblackpiece(piece)))
789 break;
790 return 1;
791 }
792 }
793
794 /* check diagonals (queen and bishop) */
795 for (j = 0; j < LEN(diag); j += 2) {
796 for (i = 1; i < 8; i++) {
797 x = kingx + (i * diag[j]);
798 y = kingy + (i * diag[j + 1]);
799 if (!(piece = getpiece(b, x, y)))
800 continue;
801 /* a piece is in front of it */
802 if (piece && strchr("kKrRnNpP", piece))
803 break;
804 /* own piece blocking/defending it */
805 if ((side == 'w' && iswhitepiece(piece)) ||
806 (side == 'b' && isblackpiece(piece)))
807 break;
808 return 1;
809 }
810 }
811
812 /* check knights */
813 piece = side == 'w' ? 'n' : 'N';
814 for (j = 0; j < LEN(knight); j += 2) {
815 x = kingx + knight[j];
816 y = kingy + knight[j + 1];
817 if (getpiece(b, x, y) == piece)
818 return 1;
819 }
820
821 /* check pawns */
822 if (side == 'w') {
823 if (getpiece(b, kingx - 1, kingy - 1) == 'p' ||
824 getpiece(b, kingx + 1, kingy - 1) == 'p')
825 return 1;
826 } else if (side == 'b') {
827 if (getpiece(b, kingx - 1, kingy + 1) == 'P' ||
828 getpiece(b, kingx + 1, kingy + 1) == 'P')
829 return 1;
830 }
831
832 return 0;
833 }
834
835 /* copy the board state and try the piece move, see if the piece wouldn't put
836 ourself in check */
837 int
838 trypiecemove(struct board *b, int side, int piece,
839 int x1, int y1, int x2, int y2, int px, int py)
840 {
841 struct board tb;
842
843 board_copy(&tb, b);
844
845 /* taken en passant? remove pawn */
846 if (x2 == px && y2 == py)
847 place(&tb, 0, x2, piece == 'P' ? y2 + 1 : y2 - 1);
848
849 place(&tb, 0, x1, y1);
850 place(&tb, piece, x2, y2);
851
852 /* would put in check / still in check, not allowed */
853 return !isincheck(&tb, side);
854 }
855
856 /* can piece move from (x, y), to (x, y)?
857 en passant square if any is (px, py), otherwise (-1, -1) */
858 int
859 canpiecemove(struct board *b, int side, int piece,
860 int x1, int y1, int x2, int y2, int px, int py)
861 {
862 int king[] = { -1, -1, -1, 0, -1, 1, 0, -1, 0, 1, 1, -1, 1, 0, 1, 1 };
863 int diag[] = { -1, -1, 1, 1, -1, 1, 1, -1 };
864 int line[] = { 1, 0, 0, 1, -1, 0, 0, -1 };
865 int knight[] = { -1, -2, 1, -2, -1, 2, 1, 2,
866 -2, -1, 2, -1, -2, 1, 2, 1
867 };
868 int i, j, dir, x, y;
869 int takepiece;
870
871 if (!piece)
872 return 0; /* theres no piece so it cannot be moved */
873
874 /* can't move opponent piece */
875 if ((side == 'w' && isblackpiece(piece)) ||
876 (side == 'b' && iswhitepiece(piece)))
877 return 0;
878
879 if ((takepiece = getpiece(b, x2, y2))) {
880 /* can't take your own piece */
881 if ((side == 'w' && iswhitepiece(takepiece)) ||
882 (side == 'b' && isblackpiece(takepiece)))
883 return 0;
884 }
885
886 /* king movement */
887 if (piece == 'K' || piece == 'k') {
888 for (j = 0; j < LEN(king); j += 2) {
889 if (x1 + king[j] == x2 && y1 + king[j + 1] == y2)
890 goto trymove;
891 }
892 }
893
894 /* check files and ranks (for queen and rook) */
895 if (piece == 'Q' || piece == 'q' || piece == 'R' || piece == 'r') {
896 for (j = 0; j < LEN(line); j += 2) {
897 for (i = 1; i < 8; i++) {
898 x = x1 + (i * line[j]);
899 y = y1 + (i * line[j + 1]);
900
901 if (x == x2 && y == y2 &&
902 trypiecemove(b, side, piece, x1, y1, x2, y2, px, py))
903 return 1;
904
905 /* a piece is in front of it: stop this checking this direction */
906 if (getpiece(b, x, y))
907 break;
908 }
909 }
910 }
911
912 /* check diagonals (queen and bishop) */
913 if (piece == 'Q' || piece == 'q' || piece == 'B' || piece == 'b') {
914 for (j = 0; j < LEN(diag); j += 2) {
915 for (i = 1; i < 8; i++) {
916 x = x1 + (i * diag[j]);
917 y = y1 + (i * diag[j + 1]);
918 if (x == x2 && y == y2 &&
919 trypiecemove(b, side, piece, x1, y1, x2, y2, px, py))
920 return 1;
921
922 /* a piece is in front of it: stop this checking this direction */
923 if (getpiece(b, x, y))
924 break;
925 }
926 }
927 }
928
929 /* knight movement */
930 if (piece == 'N' || piece == 'n') {
931 for (j = 0; j < LEN(knight); j += 2) {
932 if (x1 + knight[j] == x2 && y1 + knight[j + 1] == y2)
933 goto trymove;
934 }
935 }
936
937 /* pawn move */
938 if (piece == 'P' || piece == 'p') {
939 /* direction */
940 dir = piece == 'P' ? -1 : +1;
941 j = piece == 'P' ? 6 : 1; /* start row */
942
943 /* can move to en passant square? */
944 /* en passant set? try it if possible */
945 if (px == x2 && py == y2 &&
946 (py == y1 + dir) &&
947 ((px == x1 - 1) || px == x1 + 1)) {
948 if (isenpassantplayed(b, side == 'w' ? 'b' : 'w', px, y1))
949 return trypiecemove(b, side, piece, x1, y1, x2, y2, px, py);
950 }
951
952 if (takepiece == 0) {
953 if (x1 != x2)
954 return 0; /* move on same file */
955 /* start move: can be 2 moves */
956 if (y1 == j && y2 == y1 + (2 * dir)) {
957 /* square must be empty */
958 if (getpiece(b, x1, y1 + dir))
959 return 0;
960 goto trymove;
961 }
962 /* normal: check for one move */
963 if (y2 != y1 + dir)
964 return 0;
965 goto trymove;
966 } else {
967 /* pawn takes, normal case */
968 if ((x2 == x1 - 1 || x2 == x1 + 1) &&
969 (y2 == y1 + dir))
970 goto trymove;
971 }
972 }
973 return 0;
974
975 /* previous checks for move succeeded, actually try move with the current
976 board state */
977 trymove:
978 return trypiecemove(b, side, piece, x1, y1, x2, y2, px, py);
979 }
980
981 int
982 ischeckmated(struct board *b, int side)
983 {
984 int x, y, x2, y2, px, py, piece;
985
986 px = b->enpassantsquare[0];
987 py = b->enpassantsquare[1];
988
989 /* check pieces that can block or take a piece that removes the check */
990 for (y = 0; y < 8; y++) {
991 for (x = 0; x < 8; x++) {
992 piece = getpiece(b, x, y);
993 if ((side == 'w' && !iswhitepiece(piece)) ||
994 (side == 'b' && !isblackpiece(piece)))
995 continue;
996
997 for (y2 = 0; y2 < 8; y2++) {
998 for (x2 = 0; x2 < 8; x2++) {
999 /* can piece move and afterwards we are not in check? */
1000 if (canpiecemove(b, side, piece, x, y, x2, y2, px, py))
1001 return 0;
1002 }
1003 }
1004 }
1005 }
1006
1007 return 1;
1008 }
1009
1010 void
1011 board_setup_fen(struct board *b, const char *fen)
1012 {
1013 char square[3];
1014 const char *s;
1015 long l;
1016 int x, y, field;
1017
1018 if (!strcmp(fen, "startpos"))
1019 fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
1020
1021 square[2] = '\0';
1022
1023 /* initial board state, FEN format */
1024 x = y = field = 0;
1025 for (s = fen; *s && field < 6; s++) {
1026 switch (field) {
1027 case 0: /* piece placement data */
1028 /* skip square */
1029 if (*s >= '1' && *s <= '9') {
1030 x += (*s - '0');
1031 continue;
1032 }
1033 /* next rank */
1034 if (*s == '/') {
1035 x = 0;
1036 y++;
1037 continue;
1038 }
1039 /* is piece? place it */
1040 if (isvalidpiece(*s))
1041 place(b, *s, x++, y);
1042 break;
1043 case 1: /* active color */
1044 if (*s == 'w' || *s == 'b')
1045 b->side_to_move = *s;
1046 break;
1047 case 2: /* castling availability */
1048 if (*s == '-') {
1049 b->white_can_castle[0] = 0;
1050 b->white_can_castle[1] = 0;
1051 b->black_can_castle[0] = 0;
1052 b->black_can_castle[1] = 0;
1053 } else if (*s == 'K') {
1054 b->white_can_castle[0] = 1;
1055 } else if (*s == 'Q') {
1056 b->white_can_castle[1] = 1;
1057 } else if (*s == 'k') {
1058 b->black_can_castle[0] = 1;
1059 } else if (*s == 'q') {
1060 b->black_can_castle[1] = 1;
1061 }
1062 break;
1063 case 3: /* en passant square */
1064 if (*s >= 'a' && *s <= 'h' &&
1065 *(s + 1) >= '1' && *(s + 1) <= '6') {
1066 square[0] = *s;
1067 square[1] = *(s + 1);
1068 squaretoxy(square, &x, &y);
1069
1070 b->enpassantsquare[0] = x;
1071 b->enpassantsquare[1] = y;
1072 }
1073 break;
1074 case 4: /* halfmove */
1075 if (!(*s >= '0' && *s <= '9'))
1076 continue;
1077
1078 l = strtol(s, NULL, 10);
1079 if (l >= 0 && l < 32767) {
1080 b->halfmove = l;
1081
1082 for (; *s && ISDIGIT((unsigned char)*s); s++)
1083 ;
1084 }
1085 break;
1086 case 5: /* move number */
1087 if (!(*s >= '0' && *s <= '9'))
1088 continue;
1089
1090 l = strtol(s, NULL, 10);
1091 if (l >= 0 && l < 32767) {
1092 b->movenumber = (int)l;
1093 for (; *s && ISDIGIT((unsigned char)*s); s++)
1094 ;
1095 }
1096 break;
1097 }
1098 if (!*s)
1099 break;
1100
1101 /* next field, fields are: piece placement data, active color,
1102 Castling availability, En passant target square,
1103 Halfmove clock, Fullmove number */
1104 if (*s == ' ') {
1105 field++;
1106 continue;
1107 }
1108 }
1109 }
1110
1111 /* count ambiguity for piece moves, used to make the notation shorter */
1112 void
1113 countambigousmoves(struct board *b, int side, int piece,
1114 int x, int y, int x2, int y2, int px, int py,
1115 int *countfile, int *countrank, int *countboard)
1116 {
1117 int cf = 0, cr = 0, cb = 0, i, j;
1118
1119 /* check same file */
1120 for (i = 0; i < 8; i++) {
1121 if (getpiece(b, i, y) == piece &&
1122 canpiecemove(b, side, piece, i, y, x2, y2, px, py))
1123 cf++;
1124 }
1125
1126 /* check same rank */
1127 for (i = 0; i < 8; i++) {
1128 if (getpiece(b, x, i) == piece &&
1129 canpiecemove(b, side, piece, x, i, x2, y2, px, py))
1130 cr++;
1131 }
1132
1133 /* check whole board */
1134 if (cf <= 1 && cr <= 1) {
1135 /* check the whole board if there is any piece
1136 that can move to the same square */
1137 for (i = 0; i < 8; i++) {
1138 for (j = 0; j < 8; j++) {
1139 if (getpiece(b, i, j) == piece &&
1140 canpiecemove(b, side, piece, i, j, x2, y2, px, py))
1141 cb++;
1142 }
1143 }
1144 }
1145
1146 *countfile = cf;
1147 *countrank = cr;
1148 *countboard = cb;
1149 }
1150
1151 void
1152 board_playmoves(struct board *b, const char *moves)
1153 {
1154 char square[3];
1155 const char *castled, *s;
1156 int firstmove, i, x, y, x2, y2, side, otherside, piece;
1157 int rookpiece, takepiece, tookpiece;
1158 int countfile, countrank, countboard, px, py;
1159 int promote, tookeps;
1160
1161 /* process moves */
1162 square[2] = '\0';
1163 x = y = x2 = y2 = -1;
1164 firstmove = 1;
1165 /* clear previous highlights */
1166 memset(&(b->highlight), 0, sizeof(b->highlight));
1167
1168 for (s = moves; *s; s++) {
1169 if (*s == ' ')
1170 continue;
1171 if (!((*s >= 'a' && *s <= 'h') &&
1172 (*(s + 1) >= '1' && *(s + 1) <= '8') &&
1173 (*(s + 2) >= 'a' && *(s + 2) <= 'h') &&
1174 (*(s + 3) >= '1' && *(s + 3) <= '8')))
1175 continue;
1176
1177 /* is last move in this sequence? */
1178 if (onlylastmove && !strchr(s, ' '))
1179 silent = 0;
1180
1181 side = b->side_to_move;
1182 otherside = side == 'b' ? 'w' : 'b';
1183
1184 /* if first move and it is blacks turn, prefix
1185 with "...", because the white move was unknown */
1186 if (!onlylastmove && firstmove && side == 'b')
1187 pgn("%d. ... ", b->movenumber);
1188
1189 if (firstmove && !silent) {
1190 firstmove = 0;
1191 } else {
1192 pgn(" ");
1193 speak("\n");
1194 }
1195
1196 square[0] = *s;
1197 square[1] = *(s + 1);
1198
1199 s += 2;
1200 squaretoxy(square, &x, &y);
1201 piece = getpiece(b, x, y);
1202
1203 /* target location */
1204 square[0] = *s;
1205 square[1] = *(s + 1);
1206 squaretoxy(square, &x2, &y2);
1207
1208 /* take piece (can be your own) */
1209 takepiece = getpiece(b, x2, y2);
1210
1211 s += 2;
1212
1213 promote = 0;
1214 /* is a valid piece? should be queen, rook, bishop, knight */
1215 if (isvalidpiece(*s)) {
1216 if (side == 'w')
1217 promote = TOUPPER(*s);
1218 else
1219 promote = TOLOWER(*s);
1220 s++;
1221 }
1222
1223 /* took piece of opponent */
1224 tookpiece = (side == 'w' && isblackpiece(takepiece)) ||
1225 (side == 'b' && iswhitepiece(takepiece));
1226
1227 /* if pawn move or taken a piece increase halfmove counter */
1228 if (piece == 'p' || piece == 'P' || tookpiece)
1229 b->halfmove = 0;
1230 else
1231 b->halfmove++;
1232
1233 if (!onlylastmove && side == 'w')
1234 pgn("%d. ", b->movenumber);
1235
1236 /* castled this move? */
1237 castled = NULL;
1238
1239 /* castling */
1240 if ((piece == 'K' && y == 7 && y2 == 7) ||
1241 (piece == 'k' && y == 0 && y2 == 0)) {
1242 rookpiece = piece == 'K' ? 'R' : 'r';
1243
1244 /* kingside castling */
1245 if (x2 > x + 1 || (x2 > x && takepiece == rookpiece)) {
1246 castled = "O-O";
1247 for (i = x; i < 8; i++) {
1248 if (getpiece(b, i, y2) == rookpiece) {
1249 place(b, 0, x, y); /* clear previous square */
1250 place(b, 0, i, y2); /* clear rook square */
1251 place(b, rookpiece, 5, y2); /* rook next to king */
1252 place(b, piece, 6, y2); /* place king */
1253 x2 = i; /* update square for highlight */
1254 break;
1255 }
1256 }
1257 } else if (x2 < x - 1 || (x2 < x && takepiece == rookpiece)) {
1258 /* queenside castling */
1259 castled = "O-O-O";
1260 for (i = x; i >= 0; i--) {
1261 if (getpiece(b, i, y2) == rookpiece) {
1262 place(b, 0, x, y); /* clear previous square */
1263 place(b, 0, i, y2); /* clear rook square */
1264 place(b, rookpiece, 3, y2); /* rook next to king */
1265 place(b, piece, 2, y2); /* place king */
1266 x2 = i; /* update square for highlight */
1267 break;
1268 }
1269 }
1270 }
1271 }
1272
1273 /* remove the ability to castle */
1274 if (piece == 'K') {
1275 b->white_can_castle[0] = b->white_can_castle[1] = 0;
1276 } else if (piece == 'k') {
1277 b->black_can_castle[0] = b->black_can_castle[1] = 0;
1278 } else if (piece == 'R' && y == 7) {
1279 for (i = 0; i < 8; i++) {
1280 if (getpiece(b, i, y) == 'K') {
1281 if (i < x)
1282 b->white_can_castle[0] = 0;
1283 else if (i > x)
1284 b->white_can_castle[1] = 0;
1285 break;
1286 }
1287 }
1288 } else if (piece == 'r' && y == 0) {
1289 for (i = 0; i < 8; i++) {
1290 if (getpiece(b, i, y) == 'k') {
1291 if (i > x)
1292 b->black_can_castle[1] = 0;
1293 else if (i < x)
1294 b->black_can_castle[0] = 0;
1295 break;
1296 }
1297 }
1298 }
1299
1300 /* taken en passant? */
1301 tookeps = 0;
1302 if (x2 == b->enpassantsquare[0] && y2 == b->enpassantsquare[1] &&
1303 (piece == 'P' || piece == 'p')) {
1304 /* clear square */
1305 place(b, 0, x2, piece == 'P' ? y2 + 1 : y2 - 1);
1306 /* set a piece is taken */
1307 tookpiece = 1;
1308 takepiece = piece == 'P' ? 'p' : 'P';
1309 tookeps = 1;
1310 }
1311
1312 /* the en passant square resets after a move */
1313 px = b->enpassantsquare[0] = -1;
1314 py = b->enpassantsquare[1] = -1;
1315
1316 /* set en passant square:
1317 moved 2 squares and there is an opponent pawn next to it */
1318 if (piece == 'P' && y == 6 && y2 == 4) {
1319 if (isenpassantplayed(b, side, x, y2)) {
1320 px = b->enpassantsquare[0] = x;
1321 py = b->enpassantsquare[1] = 5;
1322 }
1323 } else if (piece == 'p' && y == 1 && y2 == 3) {
1324 if (isenpassantplayed(b, side, x, y2)) {
1325 px = b->enpassantsquare[0] = x;
1326 py = b->enpassantsquare[1] = 2;
1327 }
1328 }
1329
1330 /* PGN for move, if output is not PGN then skip this step */
1331 if (outputmode == ModePGN || outputmode == ModeSpeak) {
1332 if (castled) {
1333 pgn("%s", castled);
1334
1335 if (side == 'w')
1336 speak(dutchmode ? "wit " : "white ");
1337 else if (side == 'b')
1338 speak(dutchmode ? "zwart " : "black ");
1339
1340 if (!strcmp(castled, "O-O"))
1341 speak(dutchmode ? "rokeert aan koningszijde " : "castled kingside ");
1342 else
1343 speak(dutchmode ? "rokeert aan damezijde " : "castled queenside ");
1344 } else {
1345 if (side == 'w')
1346 speak(dutchmode ? "witte " : "white ");
1347 else if (side == 'b')
1348 speak(dutchmode ? "zwarte " : "black ");
1349
1350 if (!tookpiece) {
1351 if (!dutchmode)
1352 speak("moves ");
1353 speakpiece(piece);
1354 }
1355
1356 /* pawn move needs no notation */
1357 if (piece != 'p' && piece != 'P') {
1358 pgn("%c", pgnpiece(piece));
1359
1360 /* check ambiguity for certain pieces and make the notation shorter */
1361 countambigousmoves(b, side, piece, x, y, x2, y2, px, py,
1362 &countfile, &countrank, &countboard);
1363
1364 if (countfile > 1 || countboard > 1) {
1365 pgn("%c", xtofile(x));
1366 speak("%c", xtofile(x));
1367 }
1368 if (countrank > 1) {
1369 pgn("%c", ytorank(y));
1370 speak("%c", ytorank(y));
1371 }
1372 if (countfile > 1 || countrank > 1 || countboard > 1)
1373 speak(" ");
1374 }
1375
1376 if (tookpiece) {
1377 /* pawn captures are prefixed by the file letter (no more needed) */
1378 if (piece == 'p' || piece == 'P')
1379 pgn("%c", xtofile(x));
1380 pgn("x");
1381 speakpiece(piece);
1382 speak(dutchmode ? "slaat " : "takes ");
1383 speakpiece(takepiece);
1384 speak(dutchmode ? "op " : "on ");
1385 speak("%c%c ", xtofile(x2), ytorank(y2));
1386 if (tookeps)
1387 speak("en passant ");
1388 } else {
1389 speak(dutchmode ? "naar " : "to ");
1390 speak("%c%c ", xtofile(x2), ytorank(y2));
1391 }
1392 pgn("%c%c", xtofile(x2), ytorank(y2));
1393
1394 /* possible promotion: queen, rook, bishop, knight */
1395 if (promote) {
1396 speak(dutchmode ? "en promoot naar " : "and promotes to ");
1397 speakpiece(promote);
1398
1399 pgn("=%c", pgnpiece(promote));
1400 }
1401 }
1402 }
1403
1404 /* clear previous square (if not castled) */
1405 if (!castled) {
1406 place(b, 0, x, y);
1407 /* place piece or new promoted piece */
1408 if (promote)
1409 piece = promote;
1410 place(b, piece, x2, y2);
1411 }
1412
1413 if (ischeckmated(b, otherside)) {
1414 /* reset en passant square on checkmate */
1415 b->enpassantsquare[0] = -1;
1416 b->enpassantsquare[1] = -1;
1417
1418 pgn("#");
1419 speak(dutchmode ? "mat" : "checkmate");
1420 } else if (isincheck(b, otherside)) {
1421 pgn("+");
1422 speak(dutchmode ? "schaak" : "check");
1423 }
1424
1425 /* a move by black increases the move number */
1426 if (side == 'b')
1427 b->movenumber++;
1428
1429 /* switch which side it is to move */
1430 b->side_to_move = otherside;
1431
1432 if (!*s)
1433 break;
1434 }
1435
1436 if (!firstmove) {
1437 pgn("\n");
1438 speak("\n");
1439 }
1440
1441 /* highlight last move */
1442 if (b->highlights) {
1443 highlightmove(b, x, y);
1444 highlightmove(b, x2, y2);
1445
1446 /* highlight king in check or mate */
1447 if (isincheck(b, b->side_to_move) &&
1448 findking(b, b->side_to_move, &x, &y))
1449 highlightcheck(b, x, y);
1450 }
1451 }
1452
1453 void
1454 usage(char *argv0)
1455 {
1456 fprintf(stderr, "usage: %s [-cCfFhH] [-l] [-m mapping] "
1457 "[-o ascii|fen|pgn|speak|svg|tty] [-sS] [-t default|green|grey] "
1458 "[FEN] [moves]\n", argv0);
1459 exit(1);
1460 }
1461
1462 /* CGI: get parameter */
1463 char *
1464 getparam(const char *query, const char *s)
1465 {
1466 const char *p, *last = NULL;
1467 size_t len;
1468
1469 len = strlen(s);
1470 for (p = query; (p = strstr(p, s)); p += len) {
1471 if (p[len] == '=' && (p == query || p[-1] == '&' || p[-1] == '?'))
1472 last = p + len + 1;
1473 }
1474
1475 return (char *)last;
1476 }
1477
1478 int
1479 hexdigit(int c)
1480 {
1481 if (c >= '0' && c <= '9')
1482 return c - '0';
1483 else if (c >= 'A' && c <= 'F')
1484 return c - 'A' + 10;
1485 else if (c >= 'a' && c <= 'f')
1486 return c - 'a' + 10;
1487
1488 return 0;
1489 }
1490
1491 /* CGI: decode until NUL separator or end of "key". */
1492 int
1493 decodeparam(char *buf, size_t bufsiz, const char *s)
1494 {
1495 size_t i;
1496
1497 if (!bufsiz)
1498 return -1;
1499
1500 for (i = 0; *s && *s != '&'; s++) {
1501 switch (*s) {
1502 case '%':
1503 if (i + 3 >= bufsiz)
1504 return -1;
1505 if (!ISXDIGIT((unsigned char)*(s+1)) ||
1506 !ISXDIGIT((unsigned char)*(s+2)))
1507 return -1;
1508 buf[i++] = hexdigit(*(s+1)) * 16 + hexdigit(*(s+2));
1509 s += 2;
1510 break;
1511 case '+':
1512 if (i + 1 >= bufsiz)
1513 return -1;
1514 buf[i++] = ' ';
1515 break;
1516 default:
1517 if (i + 1 >= bufsiz)
1518 return -1;
1519 buf[i++] = *s;
1520 break;
1521 }
1522 }
1523 buf[i] = '\0';
1524
1525 return i;
1526 }
1527
1528 enum outputmode
1529 outputnametomode(const char *s)
1530 {
1531 if (!strcmp(s, "ascii"))
1532 return ModeASCII;
1533 else if (!strcmp(s, "fen"))
1534 return ModeFEN;
1535 else if (!strcmp(s, "pgn"))
1536 return ModePGN;
1537 else if (!strcmp(s, "speak"))
1538 return ModeSpeak;
1539 else if (!strcmp(s, "svg"))
1540 return ModeSVG;
1541 else if (!strcmp(s, "tty"))
1542 return ModeTTY;
1543 else
1544 return ModeInvalid;
1545 }
1546
1547 void
1548 output(struct board *b)
1549 {
1550 switch (outputmode) {
1551 case ModeASCII: output_ascii(b); break;
1552 case ModeFEN: output_fen(b); break;
1553 case ModePGN: break; /* handled in parsemoves() */
1554 case ModeSVG: output_svg(b); break;
1555 case ModeTTY: output_tty(b); break;
1556 default: break;
1557 }
1558 }
1559
1560 /* CGI mode */
1561 int
1562 cgi_mode(void)
1563 {
1564 struct board board;
1565 char *query, *p;
1566 char buf[4096];
1567
1568 board_init(&board);
1569
1570 query = getenv("QUERY_STRING");
1571 if ((p = getparam(query, "flip")) && (*p == '0' || *p == '1'))
1572 board.flipboard = *p == '1' ? 1 : 0;
1573 if ((p = getparam(query, "side")) && (*p == '0' || *p == '1'))
1574 board.showside = *p == '1' ? 1 : 0;
1575 if ((p = getparam(query, "coords")) && (*p == '0' || *p == '1'))
1576 board.showcoords = *p == '1' ? 1 : 0;
1577 if ((p = getparam(query, "dutch")) && *p == '1') {
1578 dutchmode = 1;
1579 pgn_piecemapping = "KDTLP";
1580 }
1581 if ((p = getparam(query, "output"))) {
1582 if (decodeparam(buf, sizeof(buf), p) == -1)
1583 goto badrequest;
1584 outputmode = outputnametomode(buf);
1585 if (outputmode == ModeInvalid)
1586 goto badrequest;
1587 }
1588 if ((p = getparam(query, "theme"))) {
1589 if (decodeparam(buf, sizeof(buf), p) == -1)
1590 goto badrequest;
1591 board_set_theme(&board, buf);
1592 }
1593 if ((p = getparam(query, "fen"))) {
1594 if (decodeparam(buf, sizeof(buf), p) == -1)
1595 goto badrequest;
1596 board_setup_fen(&board, buf);
1597 } else {
1598 board_setup_fen(&board, "startpos");
1599 }
1600 if ((p = getparam(query, "moves"))) {
1601 if (decodeparam(buf, sizeof(buf), p) == -1)
1602 goto badrequest;
1603 }
1604 if (!p)
1605 buf[0] = '\0';
1606
1607 fputs("Status: 200 OK\r\n", stdout);
1608 if (outputmode == ModeSVG)
1609 fputs("Content-Type: image/svg+xml\r\n\r\n", stdout);
1610 else
1611 fputs("Content-Type: text/plain\r\n\r\n", stdout);
1612
1613 board_playmoves(&board, buf);
1614
1615 output(&board);
1616
1617 return 0;
1618
1619 badrequest:
1620 fputs("Status: 400 Bad Request\r\n", stdout);
1621 fputs("Content-Type: text/plain\r\n", stdout);
1622 fputs("\r\n", stdout);
1623 fputs("Bad request: make sure to use valid parameters\n", stdout);
1624
1625 return 1;
1626 }
1627
1628 int
1629 main(int argc, char *argv[])
1630 {
1631 struct board board;
1632 const char *fen, *moves;
1633 int i, j;
1634
1635 #ifdef __OpenBSD__
1636 if (pledge("stdio", NULL) == -1)
1637 err(1, "pledge");
1638 #endif
1639
1640 if (getenv("QUERY_STRING"))
1641 return cgi_mode();
1642
1643 board_init(&board);
1644 fen = "startpos";
1645 moves = "";
1646
1647 for (i = 1; i < argc; i++) {
1648 if (argv[i][0] != '-')
1649 break;
1650
1651 for (j = 1; argv[i][j]; j++) {
1652 switch (argv[i][j]) {
1653 case 'c': board.showcoords = 1; break;
1654 case 'C': board.showcoords = 0; break;
1655 case 'd': dutchmode = 1; break; /* top secret dutch mode for "speak" */
1656 case 'f': board.flipboard = 1; break;
1657 case 'F': board.flipboard = 0; break;
1658 case 'h': board.highlights = 1; break;
1659 case 'H': board.highlights = 0; break;
1660 case 'l': onlylastmove = 1; silent = 1; break;
1661 case 'm': /* remap PGN */
1662 if (i + 1 >= argc)
1663 usage(argv[0]);
1664 i++;
1665 if (strlen(argv[i]) != 5)
1666 usage(argv[0]);
1667 pgn_piecemapping = argv[i];
1668 goto next;
1669 case 'o': /* output format */
1670 if (i + 1 >= argc)
1671 usage(argv[0]);
1672 i++;
1673
1674 outputmode = outputnametomode(argv[i]);
1675 if (outputmode == ModeInvalid)
1676 usage(argv[0]);
1677 goto next;
1678 case 's': board.showside = 1; break;
1679 case 'S': board.showside = 0; break;
1680 case 't': /* theme name */
1681 if (i + 1 >= argc)
1682 usage(argv[0]);
1683 i++;
1684 board_set_theme(&board, argv[i]);
1685 goto next;
1686 default:
1687 usage(argv[0]);
1688 break;
1689 }
1690 }
1691 next:
1692 ;
1693 }
1694 if (i < argc) {
1695 fen = argv[i];
1696 i++;
1697 }
1698 if (i < argc) {
1699 moves = argv[i];
1700 i++;
1701 }
1702
1703 board_setup_fen(&board, fen);
1704 board_playmoves(&board, moves);
1705
1706 output(&board);
1707
1708 return 0;
1709 }