fen.c - chess-puzzles - chess puzzle book generator | |
git clone git://git.codemadness.org/chess-puzzles | |
Log | |
Files | |
Refs | |
README | |
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) - … | |
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 = … | |
109 | |
110 int side_to_move; /* default: white to move: 'w' */ | |
111 int white_can_castle[2]; /* allow king side, allow queen side? d… | |
112 int black_can_castle[2]; /* allow king side, allow queen side? d… | |
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 mov… | |
120 int highlights; /* highlight moves and checks? default:… | |
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 "); brea… | |
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 "); bre… | |
284 case 'N': case 'n': speak(dutchmode ? "paard " : "knight "); bre… | |
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/maste… | |
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 use… | |
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\… | |
426 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http… | |
427 "<svg width=\"360\" height=\"360\" viewBox=\"0 0 360 360… | |
428 "<rect fill=\"#fff\" stroke=\"#000\" x=\"0\" y=\"0\" wid… | |
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=\"ev… | |
448 case 'Q': s = "<g id=\"wq\" fill=\"#fff\" fill-rule=\"ev… | |
449 case 'R': s = "<g id=\"wr\" fill=\"#fff\" fill-rule=\"ev… | |
450 case 'B': s = "<g id=\"wb\" fill=\"none\" fill-rule=\"ev… | |
451 case 'N': s = "<g id=\"wn\" fill=\"none\" fill-rule=\"ev… | |
452 case 'P': | |
453 case 'p': | |
454 s = "<path id=\"pawn\" d=\"M22.5 9c-2.21 0-4 1.7… | |
455 pieceused[0] = pieceused[1] = 0; /* unset used, … | |
456 break; | |
457 case 'k': s = "<g id=\"bk\" fill=\"none\" fill-rule=\"ev… | |
458 case 'q': s = "<g id=\"bq\" fill-rule=\"evenodd\" stroke… | |
459 case 'r': s = "<g id=\"br\" fill-rule=\"evenodd\" stroke… | |
460 case 'b': s = "<g id=\"bb\" fill=\"none\" fill-rule=\"ev… | |
461 case 'n': s = "<g id=\"bn\" fill=\"none\" fill-rule=\"ev… | |
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\" … | |
477 ix * 45, iy * 45, color[0], color[1], co… | |
478 | |
479 piece = getpiece(b, x, y); | |
480 if (piece) { | |
481 printf("<g transform=\"translate(%d %d)\… | |
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… | |
497 (ix + 1) * 45 - 2, (iy * 45) + 10, color… | |
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… | |
507 (ix * 45) + 2, (iy + 1) * 45 - 3, color[… | |
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\" fil… | |
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); /* br… | |
594 else | |
595 fputs("\x1b[30m", stdout); /* bl… | |
596 /* workaround: use black unicode chess s… | |
597 the color is filled and better visibl… | |
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,… | |
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'… | |
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,… | |
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,… | |
903 return 1; | |
904 | |
905 /* a piece is in front of it: stop this … | |
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,… | |
920 return 1; | |
921 | |
922 /* a piece is in front of it: stop this … | |
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] =… | |
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'… | |
949 return trypiecemove(b, side, piece, x1, … | |
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 … | |
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… | |
1000 if (canpiecemove(b, side, piece,… | |
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 KQk… | |
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);… | |
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);… | |
1094 ; | |
1095 } | |
1096 break; | |
1097 } | |
1098 if (!*s) | |
1099 break; | |
1100 | |
1101 /* next field, fields are: piece placement data, active … | |
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, x… | |
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, knig… | |
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 count… | |
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 == rookpi… | |
1246 castled = "O-O"; | |
1247 for (i = x; i < 8; i++) { | |
1248 if (getpiece(b, i, y2) == rookpi… | |
1249 place(b, 0, x, y); /* cl… | |
1250 place(b, 0, i, y2); /* c… | |
1251 place(b, rookpiece, 5, y… | |
1252 place(b, piece, 6, y2); … | |
1253 x2 = i; /* update square… | |
1254 break; | |
1255 } | |
1256 } | |
1257 } else if (x2 < x - 1 || (x2 < x && takepiece ==… | |
1258 /* queenside castling */ | |
1259 castled = "O-O-O"; | |
1260 for (i = x; i >= 0; i--) { | |
1261 if (getpiece(b, i, y2) == rookpi… | |
1262 place(b, 0, x, y); /* cl… | |
1263 place(b, 0, i, y2); /* c… | |
1264 place(b, rookpiece, 3, y… | |
1265 place(b, piece, 2, y2); … | |
1266 x2 = i; /* update square… | |
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] … | |
1276 } else if (piece == 'k') { | |
1277 b->black_can_castle[0] = b->black_can_castle[1] … | |
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] =… | |
1283 else if (i > x) | |
1284 b->white_can_castle[1] =… | |
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] =… | |
1293 else if (i < x) | |
1294 b->black_can_castle[0] =… | |
1295 break; | |
1296 } | |
1297 } | |
1298 } | |
1299 | |
1300 /* taken en passant? */ | |
1301 tookeps = 0; | |
1302 if (x2 == b->enpassantsquare[0] && y2 == b->enpassantsqu… | |
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… | |
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 ste… | |
1331 if (outputmode == ModePGN || outputmode == ModeSpeak) { | |
1332 if (castled) { | |
1333 pgn("%s", castled); | |
1334 | |
1335 if (side == 'w') | |
1336 speak(dutchmode ? "wit " : "whit… | |
1337 else if (side == 'b') | |
1338 speak(dutchmode ? "zwart " : "bl… | |
1339 | |
1340 if (!strcmp(castled, "O-O")) | |
1341 speak(dutchmode ? "rokeert aan k… | |
1342 else | |
1343 speak(dutchmode ? "rokeert aan d… | |
1344 } else { | |
1345 if (side == 'w') | |
1346 speak(dutchmode ? "witte " : "wh… | |
1347 else if (side == 'b') | |
1348 speak(dutchmode ? "zwarte " : "b… | |
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 p… | |
1361 countambigousmoves(b, side, piec… | |
1362 &countfile, &countrank, … | |
1363 | |
1364 if (countfile > 1 || countboard … | |
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 >… | |
1373 speak(" "); | |
1374 } | |
1375 | |
1376 if (tookpiece) { | |
1377 /* pawn captures are prefixed by… | |
1378 if (piece == 'p' || piece == 'P') | |
1379 pgn("%c", xtofile(x)); | |
1380 pgn("x"); | |
1381 speakpiece(piece); | |
1382 speak(dutchmode ? "slaat " : "ta… | |
1383 speakpiece(takepiece); | |
1384 speak(dutchmode ? "op " : "on "); | |
1385 speak("%c%c ", xtofile(x2), ytor… | |
1386 if (tookeps) | |
1387 speak("en passant "); | |
1388 } else { | |
1389 speak(dutchmode ? "naar " : "to … | |
1390 speak("%c%c ", xtofile(x2), ytor… | |
1391 } | |
1392 pgn("%c%c", xtofile(x2), ytorank(y2)); | |
1393 | |
1394 /* possible promotion: queen, rook, bish… | |
1395 if (promote) { | |
1396 speak(dutchmode ? "en promoot na… | |
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|gree… | |
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+… | |
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 du… | |
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 } |