fen.c - chess-puzzles - chess puzzle book generator
HTML git clone git://git.codemadness.org/chess-puzzles
DIR Log
DIR Files
DIR Refs
DIR README
DIR LICENSE
---
fen.c (47563B)
---
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 ((side == 'w' && piece == 'k') ||
773 (side == 'b' && piece == 'K'))
774 return 1;
775 }
776
777 /* check files and ranks (for queen and rook) */
778 for (j = 0; j < LEN(line); j += 2) {
779 for (i = 1; i < 8; i++) {
780 x = kingx + (i * line[j]);
781 y = kingy + (i * line[j + 1]);
782 if (!(piece = getpiece(b, x, y)))
783 continue;
784 /* a piece is in front of it */
785 if (piece && strchr("kKbBnNpP", piece))
786 break;
787 /* own piece blocking/defending it */
788 if ((side == 'w' && iswhitepiece(piece)) ||
789 (side == 'b' && isblackpiece(piece)))
790 break;
791 return 1;
792 }
793 }
794
795 /* check diagonals (queen and bishop) */
796 for (j = 0; j < LEN(diag); j += 2) {
797 for (i = 1; i < 8; i++) {
798 x = kingx + (i * diag[j]);
799 y = kingy + (i * diag[j + 1]);
800 if (!(piece = getpiece(b, x, y)))
801 continue;
802 /* a piece is in front of it */
803 if (piece && strchr("kKrRnNpP", piece))
804 break;
805 /* own piece blocking/defending it */
806 if ((side == 'w' && iswhitepiece(piece)) ||
807 (side == 'b' && isblackpiece(piece)))
808 break;
809 return 1;
810 }
811 }
812
813 /* check knights */
814 piece = side == 'w' ? 'n' : 'N';
815 for (j = 0; j < LEN(knight); j += 2) {
816 x = kingx + knight[j];
817 y = kingy + knight[j + 1];
818 if (getpiece(b, x, y) == piece)
819 return 1;
820 }
821
822 /* check pawns */
823 if (side == 'w') {
824 if (getpiece(b, kingx - 1, kingy - 1) == 'p' ||
825 getpiece(b, kingx + 1, kingy - 1) == 'p')
826 return 1;
827 } else if (side == 'b') {
828 if (getpiece(b, kingx - 1, kingy + 1) == 'P' ||
829 getpiece(b, kingx + 1, kingy + 1) == 'P')
830 return 1;
831 }
832
833 return 0;
834 }
835
836 /* copy the board state and try the piece move, see if the piece wouldn't put
837 ourself in check */
838 int
839 trypiecemove(struct board *b, int side, int piece,
840 int x1, int y1, int x2, int y2, int px, int py)
841 {
842 struct board tb;
843
844 board_copy(&tb, b);
845
846 /* taken en passant? remove pawn */
847 if (x2 == px && y2 == py)
848 place(&tb, 0, x2, piece == 'P' ? y2 + 1 : y2 - 1);
849
850 place(&tb, 0, x1, y1);
851 place(&tb, piece, x2, y2);
852
853 /* would put in check / still in check, not allowed */
854 return !isincheck(&tb, side);
855 }
856
857 /* can piece move from (x, y), to (x, y)?
858 en passant square if any is (px, py), otherwise (-1, -1) */
859 int
860 canpiecemove(struct board *b, int side, int piece,
861 int x1, int y1, int x2, int y2, int px, int py)
862 {
863 int king[] = { -1, -1, -1, 0, -1, 1, 0, -1, 0, 1, 1, -1, 1, 0, 1, 1 };
864 int diag[] = { -1, -1, 1, 1, -1, 1, 1, -1 };
865 int line[] = { 1, 0, 0, 1, -1, 0, 0, -1 };
866 int knight[] = { -1, -2, 1, -2, -1, 2, 1, 2,
867 -2, -1, 2, -1, -2, 1, 2, 1
868 };
869 int i, j, dir, x, y;
870 int takepiece;
871
872 if (!piece)
873 return 0; /* theres no piece so it cannot be moved */
874
875 /* can't move opponent piece */
876 if ((side == 'w' && isblackpiece(piece)) ||
877 (side == 'b' && iswhitepiece(piece)))
878 return 0;
879
880 if ((takepiece = getpiece(b, x2, y2))) {
881 /* can't take your own piece */
882 if ((side == 'w' && iswhitepiece(takepiece)) ||
883 (side == 'b' && isblackpiece(takepiece)))
884 return 0;
885 }
886
887 /* king movement */
888 if (piece == 'K' || piece == 'k') {
889 for (j = 0; j < LEN(king); j += 2) {
890 if (x1 + king[j] == x2 && y1 + king[j + 1] == y2)
891 goto trymove;
892 }
893 }
894
895 /* check files and ranks (for queen and rook) */
896 if (piece == 'Q' || piece == 'q' || piece == 'R' || piece == 'r') {
897 for (j = 0; j < LEN(line); j += 2) {
898 for (i = 1; i < 8; i++) {
899 x = x1 + (i * line[j]);
900 y = y1 + (i * line[j + 1]);
901
902 if (x == x2 && y == y2 &&
903 trypiecemove(b, side, piece, x1, y1, x2, y2, px, py))
904 return 1;
905
906 /* a piece is in front of it: stop this checking this direction */
907 if (getpiece(b, x, y))
908 break;
909 }
910 }
911 }
912
913 /* check diagonals (queen and bishop) */
914 if (piece == 'Q' || piece == 'q' || piece == 'B' || piece == 'b') {
915 for (j = 0; j < LEN(diag); j += 2) {
916 for (i = 1; i < 8; i++) {
917 x = x1 + (i * diag[j]);
918 y = y1 + (i * diag[j + 1]);
919 if (x == x2 && y == y2 &&
920 trypiecemove(b, side, piece, x1, y1, x2, y2, px, py))
921 return 1;
922
923 /* a piece is in front of it: stop this checking this direction */
924 if (getpiece(b, x, y))
925 break;
926 }
927 }
928 }
929
930 /* knight movement */
931 if (piece == 'N' || piece == 'n') {
932 for (j = 0; j < LEN(knight); j += 2) {
933 if (x1 + knight[j] == x2 && y1 + knight[j + 1] == y2)
934 goto trymove;
935 }
936 }
937
938 /* pawn move */
939 if (piece == 'P' || piece == 'p') {
940 /* direction */
941 dir = piece == 'P' ? -1 : +1;
942 j = piece == 'P' ? 6 : 1; /* start row */
943
944 /* can move to en passant square? */
945 /* en passant set? try it if possible */
946 if (px == x2 && py == y2 &&
947 (py == y1 + dir) &&
948 ((px == x1 - 1) || px == x1 + 1)) {
949 if (isenpassantplayed(b, side == 'w' ? 'b' : 'w', px, y1))
950 return trypiecemove(b, side, piece, x1, y1, x2, y2, px, py);
951 }
952
953 if (takepiece == 0) {
954 if (x1 != x2)
955 return 0; /* move on same file */
956 /* start move: can be 2 moves */
957 if (y1 == j && y2 == y1 + (2 * dir)) {
958 /* square must be empty */
959 if (getpiece(b, x1, y1 + dir))
960 return 0;
961 goto trymove;
962 }
963 /* normal: check for one move */
964 if (y2 != y1 + dir)
965 return 0;
966 goto trymove;
967 } else {
968 /* pawn takes, normal case */
969 if ((x2 == x1 - 1 || x2 == x1 + 1) &&
970 (y2 == y1 + dir))
971 goto trymove;
972 }
973 }
974 return 0;
975
976 /* previous checks for move succeeded, actually try move with the current
977 board state */
978 trymove:
979 return trypiecemove(b, side, piece, x1, y1, x2, y2, px, py);
980 }
981
982 int
983 ischeckmated(struct board *b, int side)
984 {
985 int x, y, x2, y2, px, py, piece;
986
987 px = b->enpassantsquare[0];
988 py = b->enpassantsquare[1];
989
990 /* check pieces that can block or take a piece that removes the check */
991 for (y = 0; y < 8; y++) {
992 for (x = 0; x < 8; x++) {
993 piece = getpiece(b, x, y);
994 if ((side == 'w' && !iswhitepiece(piece)) ||
995 (side == 'b' && !isblackpiece(piece)))
996 continue;
997
998 for (y2 = 0; y2 < 8; y2++) {
999 for (x2 = 0; x2 < 8; x2++) {
1000 /* can piece move and afterwards we are not in check? */
1001 if (canpiecemove(b, side, piece, x, y, x2, y2, px, py))
1002 return 0;
1003 }
1004 }
1005 }
1006 }
1007
1008 return 1;
1009 }
1010
1011 void
1012 board_setup_fen(struct board *b, const char *fen)
1013 {
1014 char square[3];
1015 const char *s;
1016 long l;
1017 int x, y, field;
1018
1019 if (!strcmp(fen, "startpos"))
1020 fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
1021
1022 square[2] = '\0';
1023
1024 /* initial board state, FEN format */
1025 x = y = field = 0;
1026 for (s = fen; *s && field < 6; s++) {
1027 switch (field) {
1028 case 0: /* piece placement data */
1029 /* skip square */
1030 if (*s >= '1' && *s <= '9') {
1031 x += (*s - '0');
1032 continue;
1033 }
1034 /* next rank */
1035 if (*s == '/') {
1036 x = 0;
1037 y++;
1038 continue;
1039 }
1040 /* is piece? place it */
1041 if (isvalidpiece(*s))
1042 place(b, *s, x++, y);
1043 break;
1044 case 1: /* active color */
1045 if (*s == 'w' || *s == 'b')
1046 b->side_to_move = *s;
1047 break;
1048 case 2: /* castling availability */
1049 if (*s == '-') {
1050 b->white_can_castle[0] = 0;
1051 b->white_can_castle[1] = 0;
1052 b->black_can_castle[0] = 0;
1053 b->black_can_castle[1] = 0;
1054 } else if (*s == 'K') {
1055 b->white_can_castle[0] = 1;
1056 } else if (*s == 'Q') {
1057 b->white_can_castle[1] = 1;
1058 } else if (*s == 'k') {
1059 b->black_can_castle[0] = 1;
1060 } else if (*s == 'q') {
1061 b->black_can_castle[1] = 1;
1062 }
1063 break;
1064 case 3: /* en passant square */
1065 if (*s >= 'a' && *s <= 'h' &&
1066 *(s + 1) >= '1' && *(s + 1) <= '6') {
1067 square[0] = *s;
1068 square[1] = *(s + 1);
1069 squaretoxy(square, &x, &y);
1070
1071 b->enpassantsquare[0] = x;
1072 b->enpassantsquare[1] = y;
1073 }
1074 break;
1075 case 4: /* halfmove */
1076 if (!(*s >= '0' && *s <= '9'))
1077 continue;
1078
1079 l = strtol(s, NULL, 10);
1080 if (l >= 0 && l < 32767) {
1081 b->halfmove = l;
1082
1083 for (; *s && ISDIGIT((unsigned char)*s); s++)
1084 ;
1085 }
1086 break;
1087 case 5: /* move number */
1088 if (!(*s >= '0' && *s <= '9'))
1089 continue;
1090
1091 l = strtol(s, NULL, 10);
1092 if (l >= 0 && l < 32767) {
1093 b->movenumber = (int)l;
1094 for (; *s && ISDIGIT((unsigned char)*s); s++)
1095 ;
1096 }
1097 break;
1098 }
1099 if (!*s)
1100 break;
1101
1102 /* next field, fields are: piece placement data, active color,
1103 Castling availability, En passant target square,
1104 Halfmove clock, Fullmove number */
1105 if (*s == ' ') {
1106 field++;
1107 continue;
1108 }
1109 }
1110 }
1111
1112 /* count ambiguity for piece moves, used to make the notation shorter */
1113 void
1114 countambigousmoves(struct board *b, int side, int piece,
1115 int x, int y, int x2, int y2, int px, int py,
1116 int *countfile, int *countrank, int *countboard)
1117 {
1118 int cf = 0, cr = 0, cb = 0, i, j;
1119
1120 /* check same file */
1121 for (i = 0; i < 8; i++) {
1122 if (getpiece(b, i, y) == piece &&
1123 canpiecemove(b, side, piece, i, y, x2, y2, px, py))
1124 cf++;
1125 }
1126
1127 /* check same rank */
1128 for (i = 0; i < 8; i++) {
1129 if (getpiece(b, x, i) == piece &&
1130 canpiecemove(b, side, piece, x, i, x2, y2, px, py))
1131 cr++;
1132 }
1133
1134 /* check whole board */
1135 if (cf <= 1 && cr <= 1) {
1136 /* check the whole board if there is any piece
1137 that can move to the same square */
1138 for (i = 0; i < 8; i++) {
1139 for (j = 0; j < 8; j++) {
1140 if (getpiece(b, i, j) == piece &&
1141 canpiecemove(b, side, piece, i, j, x2, y2, px, py))
1142 cb++;
1143 }
1144 }
1145 }
1146
1147 *countfile = cf;
1148 *countrank = cr;
1149 *countboard = cb;
1150 }
1151
1152 void
1153 board_playmoves(struct board *b, const char *moves)
1154 {
1155 char square[3];
1156 const char *castled, *s;
1157 int firstmove, i, x, y, x2, y2, side, otherside, piece;
1158 int rookpiece, takepiece, tookpiece;
1159 int countfile, countrank, countboard, px, py;
1160 int promote, tookeps;
1161
1162 /* process moves */
1163 square[2] = '\0';
1164 x = y = x2 = y2 = -1;
1165 firstmove = 1;
1166 /* clear previous highlights */
1167 memset(&(b->highlight), 0, sizeof(b->highlight));
1168
1169 for (s = moves; *s; s++) {
1170 if (*s == ' ')
1171 continue;
1172 if (!((*s >= 'a' && *s <= 'h') &&
1173 (*(s + 1) >= '1' && *(s + 1) <= '8') &&
1174 (*(s + 2) >= 'a' && *(s + 2) <= 'h') &&
1175 (*(s + 3) >= '1' && *(s + 3) <= '8')))
1176 continue;
1177
1178 /* is last move in this sequence? */
1179 if (onlylastmove && !strchr(s, ' '))
1180 silent = 0;
1181
1182 side = b->side_to_move;
1183 otherside = side == 'b' ? 'w' : 'b';
1184
1185 /* if first move and it is blacks turn, prefix
1186 with "...", because the white move was unknown */
1187 if (!onlylastmove && firstmove && side == 'b')
1188 pgn("%d. ... ", b->movenumber);
1189
1190 if (firstmove && !silent) {
1191 firstmove = 0;
1192 } else {
1193 pgn(" ");
1194 speak("\n");
1195 }
1196
1197 square[0] = *s;
1198 square[1] = *(s + 1);
1199
1200 s += 2;
1201 squaretoxy(square, &x, &y);
1202 piece = getpiece(b, x, y);
1203
1204 /* target location */
1205 square[0] = *s;
1206 square[1] = *(s + 1);
1207 squaretoxy(square, &x2, &y2);
1208
1209 /* take piece (can be your own) */
1210 takepiece = getpiece(b, x2, y2);
1211
1212 s += 2;
1213
1214 promote = 0;
1215 /* is a valid piece? should be queen, rook, bishop, knight */
1216 if (isvalidpiece(*s)) {
1217 if (side == 'w')
1218 promote = TOUPPER(*s);
1219 else
1220 promote = TOLOWER(*s);
1221 s++;
1222 }
1223
1224 /* took piece of opponent */
1225 tookpiece = (side == 'w' && isblackpiece(takepiece)) ||
1226 (side == 'b' && iswhitepiece(takepiece));
1227
1228 /* if pawn move or taken a piece increase halfmove counter */
1229 if (piece == 'p' || piece == 'P' || tookpiece)
1230 b->halfmove = 0;
1231 else
1232 b->halfmove++;
1233
1234 if (!onlylastmove && side == 'w')
1235 pgn("%d. ", b->movenumber);
1236
1237 /* castled this move? */
1238 castled = NULL;
1239
1240 /* castling */
1241 if ((piece == 'K' && y == 7 && y2 == 7) ||
1242 (piece == 'k' && y == 0 && y2 == 0)) {
1243 rookpiece = piece == 'K' ? 'R' : 'r';
1244
1245 /* kingside castling */
1246 if (x2 > x + 1 || (x2 > x && takepiece == rookpiece)) {
1247 castled = "O-O";
1248 for (i = x; i < 8; i++) {
1249 if (getpiece(b, i, y2) == rookpiece) {
1250 place(b, 0, x, y); /* clear previous square */
1251 place(b, 0, i, y2); /* clear rook square */
1252 place(b, rookpiece, 5, y2); /* rook next to king */
1253 place(b, piece, 6, y2); /* place king */
1254 x2 = i; /* update square for highlight */
1255 break;
1256 }
1257 }
1258 } else if (x2 < x - 1 || (x2 < x && takepiece == rookpiece)) {
1259 /* queenside castling */
1260 castled = "O-O-O";
1261 for (i = x; i >= 0; i--) {
1262 if (getpiece(b, i, y2) == rookpiece) {
1263 place(b, 0, x, y); /* clear previous square */
1264 place(b, 0, i, y2); /* clear rook square */
1265 place(b, rookpiece, 3, y2); /* rook next to king */
1266 place(b, piece, 2, y2); /* place king */
1267 x2 = i; /* update square for highlight */
1268 break;
1269 }
1270 }
1271 }
1272 }
1273
1274 /* remove the ability to castle */
1275 if (piece == 'K') {
1276 b->white_can_castle[0] = b->white_can_castle[1] = 0;
1277 } else if (piece == 'k') {
1278 b->black_can_castle[0] = b->black_can_castle[1] = 0;
1279 } else if (piece == 'R' && y == 7) {
1280 for (i = 0; i < 8; i++) {
1281 if (getpiece(b, i, y) == 'K') {
1282 if (i < x)
1283 b->white_can_castle[0] = 0;
1284 else if (i > x)
1285 b->white_can_castle[1] = 0;
1286 break;
1287 }
1288 }
1289 } else if (piece == 'r' && y == 0) {
1290 for (i = 0; i < 8; i++) {
1291 if (getpiece(b, i, y) == 'k') {
1292 if (i > x)
1293 b->black_can_castle[1] = 0;
1294 else if (i < x)
1295 b->black_can_castle[0] = 0;
1296 break;
1297 }
1298 }
1299 }
1300
1301 /* taken en passant? */
1302 tookeps = 0;
1303 if (x2 == b->enpassantsquare[0] && y2 == b->enpassantsquare[1] &&
1304 (piece == 'P' || piece == 'p')) {
1305 /* clear square */
1306 place(b, 0, x2, piece == 'P' ? y2 + 1 : y2 - 1);
1307 /* set a piece is taken */
1308 tookpiece = 1;
1309 takepiece = piece == 'P' ? 'p' : 'P';
1310 tookeps = 1;
1311 }
1312
1313 /* the en passant square resets after a move */
1314 px = b->enpassantsquare[0] = -1;
1315 py = b->enpassantsquare[1] = -1;
1316
1317 /* set en passant square:
1318 moved 2 squares and there is an opponent pawn next to it */
1319 if (piece == 'P' && y == 6 && y2 == 4) {
1320 if (isenpassantplayed(b, side, x, y2)) {
1321 px = b->enpassantsquare[0] = x;
1322 py = b->enpassantsquare[1] = 5;
1323 }
1324 } else if (piece == 'p' && y == 1 && y2 == 3) {
1325 if (isenpassantplayed(b, side, x, y2)) {
1326 px = b->enpassantsquare[0] = x;
1327 py = b->enpassantsquare[1] = 2;
1328 }
1329 }
1330
1331 /* PGN for move, if output is not PGN then skip this step */
1332 if (outputmode == ModePGN || outputmode == ModeSpeak) {
1333 if (castled) {
1334 pgn("%s", castled);
1335
1336 if (side == 'w')
1337 speak(dutchmode ? "wit " : "white ");
1338 else if (side == 'b')
1339 speak(dutchmode ? "zwart " : "black ");
1340
1341 if (!strcmp(castled, "O-O"))
1342 speak(dutchmode ? "rokeert aan koningszijde " : "castled kingside ");
1343 else
1344 speak(dutchmode ? "rokeert aan damezijde " : "castled queenside ");
1345 } else {
1346 if (side == 'w')
1347 speak(dutchmode ? "witte " : "white ");
1348 else if (side == 'b')
1349 speak(dutchmode ? "zwarte " : "black ");
1350
1351 if (!tookpiece) {
1352 if (!dutchmode)
1353 speak("moves ");
1354 speakpiece(piece);
1355 }
1356
1357 /* pawn move needs no notation */
1358 if (piece != 'p' && piece != 'P') {
1359 pgn("%c", pgnpiece(piece));
1360
1361 /* check ambiguity for certain pieces and make the notation shorter */
1362 countambigousmoves(b, side, piece, x, y, x2, y2, px, py,
1363 &countfile, &countrank, &countboard);
1364
1365 if (countfile > 1 || countboard > 1) {
1366 pgn("%c", xtofile(x));
1367 speak("%c", xtofile(x));
1368 }
1369 if (countrank > 1) {
1370 pgn("%c", ytorank(y));
1371 speak("%c", ytorank(y));
1372 }
1373 if (countfile > 1 || countrank > 1 || countboard > 1)
1374 speak(" ");
1375 }
1376
1377 if (tookpiece) {
1378 /* pawn captures are prefixed by the file letter (no more needed) */
1379 if (piece == 'p' || piece == 'P')
1380 pgn("%c", xtofile(x));
1381 pgn("x");
1382 speakpiece(piece);
1383 speak(dutchmode ? "slaat " : "takes ");
1384 speakpiece(takepiece);
1385 speak(dutchmode ? "op " : "on ");
1386 speak("%c%c ", xtofile(x2), ytorank(y2));
1387 if (tookeps)
1388 speak("en passant ");
1389 } else {
1390 speak(dutchmode ? "naar " : "to ");
1391 speak("%c%c ", xtofile(x2), ytorank(y2));
1392 }
1393 pgn("%c%c", xtofile(x2), ytorank(y2));
1394
1395 /* possible promotion: queen, rook, bishop, knight */
1396 if (promote) {
1397 speak(dutchmode ? "en promoot naar " : "and promotes to ");
1398 speakpiece(promote);
1399
1400 pgn("=%c", pgnpiece(promote));
1401 }
1402 }
1403 }
1404
1405 /* clear previous square (if not castled) */
1406 if (!castled) {
1407 place(b, 0, x, y);
1408 /* place piece or new promoted piece */
1409 if (promote)
1410 piece = promote;
1411 place(b, piece, x2, y2);
1412 }
1413
1414 if (ischeckmated(b, otherside)) {
1415 /* reset en passant square on checkmate */
1416 b->enpassantsquare[0] = -1;
1417 b->enpassantsquare[1] = -1;
1418
1419 pgn("#");
1420 speak(dutchmode ? "mat" : "checkmate");
1421 } else if (isincheck(b, otherside)) {
1422 pgn("+");
1423 speak(dutchmode ? "schaak" : "check");
1424 }
1425
1426 /* a move by black increases the move number */
1427 if (side == 'b')
1428 b->movenumber++;
1429
1430 /* switch which side it is to move */
1431 b->side_to_move = otherside;
1432
1433 if (!*s)
1434 break;
1435 }
1436
1437 if (!firstmove) {
1438 pgn("\n");
1439 speak("\n");
1440 }
1441
1442 /* highlight last move */
1443 if (b->highlights) {
1444 highlightmove(b, x, y);
1445 highlightmove(b, x2, y2);
1446
1447 /* highlight king in check or mate */
1448 if (isincheck(b, b->side_to_move) &&
1449 findking(b, b->side_to_move, &x, &y))
1450 highlightcheck(b, x, y);
1451 }
1452 }
1453
1454 void
1455 usage(char *argv0)
1456 {
1457 fprintf(stderr, "usage: %s [-cCfFhH] [-l] [-m mapping] "
1458 "[-o ascii|fen|pgn|speak|svg|tty] [-sS] [-t default|green|grey] "
1459 "[FEN] [moves]\n", argv0);
1460 exit(1);
1461 }
1462
1463 /* CGI: get parameter */
1464 char *
1465 getparam(const char *query, const char *s)
1466 {
1467 const char *p, *last = NULL;
1468 size_t len;
1469
1470 len = strlen(s);
1471 for (p = query; (p = strstr(p, s)); p += len) {
1472 if (p[len] == '=' && (p == query || p[-1] == '&' || p[-1] == '?'))
1473 last = p + len + 1;
1474 }
1475
1476 return (char *)last;
1477 }
1478
1479 int
1480 hexdigit(int c)
1481 {
1482 if (c >= '0' && c <= '9')
1483 return c - '0';
1484 else if (c >= 'A' && c <= 'F')
1485 return c - 'A' + 10;
1486 else if (c >= 'a' && c <= 'f')
1487 return c - 'a' + 10;
1488
1489 return 0;
1490 }
1491
1492 /* CGI: decode until NUL separator or end of "key". */
1493 int
1494 decodeparam(char *buf, size_t bufsiz, const char *s)
1495 {
1496 size_t i;
1497
1498 if (!bufsiz)
1499 return -1;
1500
1501 for (i = 0; *s && *s != '&'; s++) {
1502 switch (*s) {
1503 case '%':
1504 if (i + 3 >= bufsiz)
1505 return -1;
1506 if (!ISXDIGIT((unsigned char)*(s+1)) ||
1507 !ISXDIGIT((unsigned char)*(s+2)))
1508 return -1;
1509 buf[i++] = hexdigit(*(s+1)) * 16 + hexdigit(*(s+2));
1510 s += 2;
1511 break;
1512 case '+':
1513 if (i + 1 >= bufsiz)
1514 return -1;
1515 buf[i++] = ' ';
1516 break;
1517 default:
1518 if (i + 1 >= bufsiz)
1519 return -1;
1520 buf[i++] = *s;
1521 break;
1522 }
1523 }
1524 buf[i] = '\0';
1525
1526 return i;
1527 }
1528
1529 enum outputmode
1530 outputnametomode(const char *s)
1531 {
1532 if (!strcmp(s, "ascii"))
1533 return ModeASCII;
1534 else if (!strcmp(s, "fen"))
1535 return ModeFEN;
1536 else if (!strcmp(s, "pgn"))
1537 return ModePGN;
1538 else if (!strcmp(s, "speak"))
1539 return ModeSpeak;
1540 else if (!strcmp(s, "svg"))
1541 return ModeSVG;
1542 else if (!strcmp(s, "tty"))
1543 return ModeTTY;
1544 else
1545 return ModeInvalid;
1546 }
1547
1548 void
1549 output(struct board *b)
1550 {
1551 switch (outputmode) {
1552 case ModeASCII: output_ascii(b); break;
1553 case ModeFEN: output_fen(b); break;
1554 case ModePGN: break; /* handled in parsemoves() */
1555 case ModeSVG: output_svg(b); break;
1556 case ModeTTY: output_tty(b); break;
1557 default: break;
1558 }
1559 }
1560
1561 /* CGI mode */
1562 int
1563 cgi_mode(void)
1564 {
1565 struct board board;
1566 char *query, *p;
1567 char buf[4096];
1568
1569 board_init(&board);
1570
1571 query = getenv("QUERY_STRING");
1572 if ((p = getparam(query, "flip")) && (*p == '0' || *p == '1'))
1573 board.flipboard = *p == '1' ? 1 : 0;
1574 if ((p = getparam(query, "side")) && (*p == '0' || *p == '1'))
1575 board.showside = *p == '1' ? 1 : 0;
1576 if ((p = getparam(query, "coords")) && (*p == '0' || *p == '1'))
1577 board.showcoords = *p == '1' ? 1 : 0;
1578 if ((p = getparam(query, "dutch")) && *p == '1') {
1579 dutchmode = 1;
1580 pgn_piecemapping = "KDTLP";
1581 }
1582 if ((p = getparam(query, "output"))) {
1583 if (decodeparam(buf, sizeof(buf), p) == -1)
1584 goto badrequest;
1585 outputmode = outputnametomode(buf);
1586 if (outputmode == ModeInvalid)
1587 goto badrequest;
1588 }
1589 if ((p = getparam(query, "theme"))) {
1590 if (decodeparam(buf, sizeof(buf), p) == -1)
1591 goto badrequest;
1592 board_set_theme(&board, buf);
1593 }
1594 if ((p = getparam(query, "fen"))) {
1595 if (decodeparam(buf, sizeof(buf), p) == -1)
1596 goto badrequest;
1597 board_setup_fen(&board, buf);
1598 } else {
1599 board_setup_fen(&board, "startpos");
1600 }
1601 if ((p = getparam(query, "moves"))) {
1602 if (decodeparam(buf, sizeof(buf), p) == -1)
1603 goto badrequest;
1604 }
1605 if (!p)
1606 buf[0] = '\0';
1607
1608 fputs("Status: 200 OK\r\n", stdout);
1609 if (outputmode == ModeSVG)
1610 fputs("Content-Type: image/svg+xml\r\n\r\n", stdout);
1611 else
1612 fputs("Content-Type: text/plain\r\n\r\n", stdout);
1613
1614 board_playmoves(&board, buf);
1615
1616 output(&board);
1617
1618 return 0;
1619
1620 badrequest:
1621 fputs("Status: 400 Bad Request\r\n", stdout);
1622 fputs("Content-Type: text/plain\r\n", stdout);
1623 fputs("\r\n", stdout);
1624 fputs("Bad request: make sure to use valid parameters\n", stdout);
1625
1626 return 1;
1627 }
1628
1629 int
1630 main(int argc, char *argv[])
1631 {
1632 struct board board;
1633 const char *fen, *moves;
1634 int i, j;
1635
1636 #ifdef __OpenBSD__
1637 if (pledge("stdio", NULL) == -1)
1638 err(1, "pledge");
1639 #endif
1640
1641 if (getenv("QUERY_STRING"))
1642 return cgi_mode();
1643
1644 board_init(&board);
1645 fen = "startpos";
1646 moves = "";
1647
1648 for (i = 1; i < argc; i++) {
1649 if (argv[i][0] != '-')
1650 break;
1651
1652 for (j = 1; argv[i][j]; j++) {
1653 switch (argv[i][j]) {
1654 case 'c': board.showcoords = 1; break;
1655 case 'C': board.showcoords = 0; break;
1656 case 'd': dutchmode = 1; break; /* top secret dutch mode for "speak" */
1657 case 'f': board.flipboard = 1; break;
1658 case 'F': board.flipboard = 0; break;
1659 case 'h': board.highlights = 1; break;
1660 case 'H': board.highlights = 0; break;
1661 case 'l': onlylastmove = 1; silent = 1; break;
1662 case 'm': /* remap PGN */
1663 if (i + 1 >= argc)
1664 usage(argv[0]);
1665 i++;
1666 if (strlen(argv[i]) != 5)
1667 usage(argv[0]);
1668 pgn_piecemapping = argv[i];
1669 goto next;
1670 case 'o': /* output format */
1671 if (i + 1 >= argc)
1672 usage(argv[0]);
1673 i++;
1674
1675 outputmode = outputnametomode(argv[i]);
1676 if (outputmode == ModeInvalid)
1677 usage(argv[0]);
1678 goto next;
1679 case 's': board.showside = 1; break;
1680 case 'S': board.showside = 0; break;
1681 case 't': /* theme name */
1682 if (i + 1 >= argc)
1683 usage(argv[0]);
1684 i++;
1685 board_set_theme(&board, argv[i]);
1686 goto next;
1687 default:
1688 usage(argv[0]);
1689 break;
1690 }
1691 }
1692 next:
1693 ;
1694 }
1695 if (i < argc) {
1696 fen = argv[i];
1697 i++;
1698 }
1699 if (i < argc) {
1700 moves = argv[i];
1701 i++;
1702 }
1703
1704 board_setup_fen(&board, fen);
1705 board_playmoves(&board, moves);
1706
1707 output(&board);
1708
1709 return 0;
1710 }