fen_to_svg.c - chess-puzzles - chess puzzle book generator | |
git clone git://git.codemadness.org/chess-puzzles | |
Log | |
Files | |
Refs | |
README | |
LICENSE | |
--- | |
fen_to_svg.c (22185B) | |
--- | |
1 /* TODO: option to flip board? */ | |
2 | |
3 #include <ctype.h> | |
4 #include <stdio.h> | |
5 #include <stdlib.h> | |
6 #include <string.h> | |
7 | |
8 #define SETFGCOLOR(r,g,b) printf("\x1b[38;2;%d;%d;%dm", r, g, b) | |
9 #define SETBGCOLOR(r,g,b) printf("\x1b[48;2;%d;%d;%dm", r, g, b) | |
10 | |
11 static char board[8][8]; | |
12 static char highlight[8][8]; | |
13 | |
14 static int side_to_move = 'w'; /* default: white to move */ | |
15 static int white_can_castle[2] = { 0, 0 }; /* allow king side, allow que… | |
16 static int black_can_castle[2] = { 0, 0 }; /* allow king side, allow que… | |
17 static int enpassantsquare[2] = { -1, -1 }; | |
18 static int movenumber = 1; | |
19 static int halfmove = 0; | |
20 | |
21 static const int showcoords = 1; /* config: show board coordinates? */ | |
22 | |
23 int | |
24 isvalidsquare(int x, int y) | |
25 { | |
26 return !(x < 0 || x >= 8 || y < 0 || y >= 8); | |
27 } | |
28 | |
29 int | |
30 isvalidpiece(int c) | |
31 { | |
32 static char pieces[] = "PNBRQKpnbrqk"; | |
33 | |
34 return strchr(pieces, c) ? 1 : 0; | |
35 } | |
36 | |
37 /* place a piece, if possible */ | |
38 void | |
39 place(int piece, int x, int y) | |
40 { | |
41 if (!isvalidsquare(x, y)) | |
42 return; | |
43 | |
44 board[y][x] = piece; | |
45 } | |
46 | |
47 /* get piece, if possible */ | |
48 int | |
49 getpiece(int x, int y) | |
50 { | |
51 if (!isvalidsquare(x, y)) | |
52 return 0; | |
53 return board[y][x]; | |
54 } | |
55 | |
56 int | |
57 squaretoxy(const char *s, int *x, int *y) | |
58 { | |
59 if (*s >= 'a' && *s <= 'h' && | |
60 *(s + 1) >= '1' && *(s + 1) <= '8') { | |
61 *x = *s - 'a'; | |
62 *y = '8' - *(s + 1); | |
63 return 1; | |
64 } | |
65 return 0; | |
66 } | |
67 | |
68 void | |
69 highlightmove(int x1, int y1, int x2, int y2) | |
70 { | |
71 if (isvalidsquare(x1, y1)) | |
72 highlight[y1][x1] = 1; | |
73 | |
74 if (isvalidsquare(x2, y2)) | |
75 highlight[y2][x2] = 1; | |
76 } | |
77 | |
78 void | |
79 showboardfen(void) | |
80 { | |
81 int x, y, piece, skip = 0; | |
82 | |
83 for (y = 0; y < 8; y++) { | |
84 if (y > 0) | |
85 putchar('/'); | |
86 skip = 0; | |
87 for (x = 0; x < 8; x++) { | |
88 piece = getpiece(x, y); | |
89 if (piece) { | |
90 if (skip) | |
91 putchar(skip + '0'); | |
92 putchar(piece); | |
93 skip = 0; | |
94 } else { | |
95 skip++; | |
96 } | |
97 } | |
98 if (skip) | |
99 putchar(skip + '0'); | |
100 } | |
101 printf(" %c ", side_to_move); | |
102 if (white_can_castle[0]) | |
103 putchar('K'); | |
104 if (white_can_castle[1]) | |
105 putchar('Q'); | |
106 if (black_can_castle[0]) | |
107 putchar('k'); | |
108 if (black_can_castle[1]) | |
109 putchar('q'); | |
110 if ((white_can_castle[0] + white_can_castle[1] + | |
111 black_can_castle[0] + black_can_castle[1]) == 0) | |
112 putchar('-'); /* no castling for either side */ | |
113 putchar(' '); | |
114 | |
115 if (enpassantsquare[0] != -1 && enpassantsquare[1] != -1) { | |
116 putchar('a' + enpassantsquare[0]); | |
117 putchar('8' - enpassantsquare[1]); | |
118 } else { | |
119 putchar('-'); | |
120 } | |
121 printf(" %d %d", halfmove, movenumber); | |
122 } | |
123 | |
124 void | |
125 svg_showpiece(int c) | |
126 { | |
127 const char *s = ""; | |
128 | |
129 /* lichess default set, | |
130 extracted from https://github.com/lichess-org/lila/tree/maste… | |
131 switch (c) { | |
132 case 'K': s = "<g fill=\"none\" fill-rule=\"evenodd\" stroke=\"#… | |
133 case 'Q': s = "<g fill=\"#fff\" fill-rule=\"evenodd\" stroke=\"#… | |
134 case 'R': s = "<g fill=\"#fff\" fill-rule=\"evenodd\" stroke=\"#… | |
135 case 'B': s = "<g fill=\"none\" fill-rule=\"evenodd\" stroke=\"#… | |
136 case 'N': s = "<g fill=\"none\" fill-rule=\"evenodd\" stroke=\"#… | |
137 case 'P': s = "<path d=\"M22.5 9c-2.21 0-4 1.79-4 4 0 .89.29 1.7… | |
138 case 'k': s = "<g fill=\"none\" fill-rule=\"evenodd\" stroke=\"#… | |
139 case 'q': s = "<g fill-rule=\"evenodd\" stroke=\"#000\" stroke-w… | |
140 case 'r': s = "<g fill-rule=\"evenodd\" stroke=\"#000\" stroke-w… | |
141 case 'b': s = "<g fill=\"none\" fill-rule=\"evenodd\" stroke=\"#… | |
142 case 'n': s = "<g fill=\"none\" fill-rule=\"evenodd\" stroke=\"#… | |
143 case 'p': s = "<path d=\"M22.5 9c-2.21 0-4 1.79-4 4 0 .89.29 1.7… | |
144 } | |
145 | |
146 if (*s) | |
147 fputs(s, stdout); | |
148 } | |
149 | |
150 void | |
151 svg_showboard(void) | |
152 { | |
153 /* lichess default theme colors */ | |
154 const char *darksquare = "#b58863"; | |
155 const char *lightsquare = "#f0d9b5"; | |
156 const char *darksquarehi = "#aaa23a"; | |
157 const char *lightsquarehi = "#cdd26a"; | |
158 const char *color; | |
159 int x, y, piece; | |
160 | |
161 fputs("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\… | |
162 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http… | |
163 "<svg width=\"360\" height=\"360\" viewBox=\"0 0 360 360… | |
164 "<rect fill=\"#fff\" stroke=\"#000\" x=\"0\" y=\"0\" wid… | |
165 | |
166 fputs("<!-- Board FEN: ", stdout); | |
167 showboardfen(); | |
168 fputs(" -->\n", stdout); | |
169 | |
170 for (y = 0; y < 8; y++) { | |
171 for (x = 0; x < 8; x++) { | |
172 if (x % 2 == 0) { | |
173 if (y % 2 == 0) | |
174 color = highlight[y][x] ? lights… | |
175 else | |
176 color = highlight[y][x] ? darksq… | |
177 } else { | |
178 if (y % 2 == 0) | |
179 color = highlight[y][x] ? darksq… | |
180 else | |
181 color = highlight[y][x] ? lights… | |
182 } | |
183 | |
184 printf("<g><rect x=\"%d\" y=\"%d\" width=\"45\" … | |
185 x * 45, y * 45, color); | |
186 | |
187 piece = getpiece(x, y); | |
188 if (piece) { | |
189 printf("<g transform=\"translate(%d %d)\… | |
190 svg_showpiece(piece); | |
191 fputs("</g>\n", stdout); | |
192 } | |
193 } | |
194 } | |
195 | |
196 if (showcoords) { | |
197 x = 7; | |
198 for (y = 0; y < 8; y++) { | |
199 if (y % 2 == 0) | |
200 color = highlight[y][x] ? lightsquarehi … | |
201 else | |
202 color = highlight[y][x] ? darksquarehi :… | |
203 printf("<text x=\"%d\" y=\"%d\" fill=\"%s\" text… | |
204 (x + 1) * 45 - 2, (y * 45) + 10, color, … | |
205 } | |
206 y = 7; | |
207 for (x = 0; x < 8; x++) { | |
208 if (x % 2 == 0) | |
209 color = highlight[y][x] ? lightsquarehi … | |
210 else | |
211 color = highlight[y][x] ? darksquarehi :… | |
212 printf("<text x=\"%d\" y=\"%d\" fill=\"%s\" text… | |
213 (x * 45) + 2, (y + 1) * 45 - 3, color, x… | |
214 } | |
215 } | |
216 | |
217 fputs("</svg>\n", stdout); | |
218 } | |
219 | |
220 void | |
221 tty_showpiece(int c) | |
222 { | |
223 const char *s = ""; | |
224 | |
225 /* simple or use unicode character */ | |
226 #if 0 | |
227 putchar(c); | |
228 return; | |
229 #endif | |
230 | |
231 switch (c) { | |
232 case 'K': s = "♔"; break; | |
233 case 'Q': s = "♕"; break; | |
234 case 'R': s = "♖"; break; | |
235 case 'B': s = "♗"; break; | |
236 case 'N': s = "♘"; break; | |
237 case 'P': s = "♙"; break; | |
238 case 'k': s = "♚"; break; | |
239 case 'q': s = "♛"; break; | |
240 case 'r': s = "♜"; break; | |
241 case 'b': s = "♝"; break; | |
242 case 'n': s = "♞"; break; | |
243 case 'p': s = "♟"; break; | |
244 } | |
245 | |
246 if (*s) | |
247 fputs(s, stdout); | |
248 } | |
249 | |
250 /* show board */ | |
251 void | |
252 tty_showboard(void) | |
253 { | |
254 int *color; | |
255 int border[] = { 0x70, 0x49, 0x2d }; | |
256 int darksquare[] = { 0xb5, 0x88, 0x63 }; | |
257 int lightsquare[] = { 0xf0, 0xd9, 0xb5 }; | |
258 int darksquarehi[] = { 0xaa, 0xa2, 0x3a }; | |
259 int lightsquarehi[] = { 0xcd, 0xd2, 0x6a }; | |
260 int x, y, piece; | |
261 | |
262 printf("Board FEN:\n"); | |
263 showboardfen(); | |
264 printf("\n\n"); | |
265 | |
266 SETFGCOLOR(0x00, 0x00, 0x00); | |
267 | |
268 color = border; | |
269 SETBGCOLOR(color[0], color[1], color[2]); | |
270 SETFGCOLOR(0xff, 0xff, 0xff); | |
271 fputs(" ", stdout); | |
272 printf("\x1b[0m"); /* reset */ | |
273 SETFGCOLOR(0x00, 0x00, 0x00); | |
274 putchar('\n'); | |
275 | |
276 for (y = 0; y < 8; y++) { | |
277 color = border; | |
278 SETBGCOLOR(color[0], color[1], color[2]); | |
279 SETFGCOLOR(0xff, 0xff, 0xff); | |
280 fputs(" ", stdout); | |
281 | |
282 for (x = 0; x < 8; x++) { | |
283 if (x % 2 == 0) { | |
284 if (y % 2 == 0) | |
285 color = highlight[y][x] ? lights… | |
286 else | |
287 color = highlight[y][x] ? darksq… | |
288 } else { | |
289 if (y % 2 == 0) | |
290 color = highlight[y][x] ? darksq… | |
291 else | |
292 color = highlight[y][x] ? lights… | |
293 } | |
294 SETBGCOLOR(color[0], color[1], color[2]); | |
295 | |
296 fputs(" ", stdout); | |
297 piece = getpiece(x, y); | |
298 if (piece) { | |
299 if (piece >= 'A' && piece <= 'Z') | |
300 SETFGCOLOR(0xff, 0xff, 0xff); | |
301 else | |
302 SETFGCOLOR(0x00, 0x00, 0x00); | |
303 /* workaround: use black chess symbol, b… | |
304 is filled and better visible */ | |
305 tty_showpiece(tolower(piece)); | |
306 } else { | |
307 fputs(" ", stdout); | |
308 } | |
309 fputs(" ", stdout); | |
310 } | |
311 printf("\x1b[0m"); /* reset */ | |
312 | |
313 color = border; | |
314 SETBGCOLOR(color[0], color[1], color[2]); | |
315 SETFGCOLOR(0xff, 0xff, 0xff); | |
316 if (showcoords) { | |
317 putchar(' '); | |
318 putchar('8' - y); | |
319 putchar(' '); | |
320 } else { | |
321 fputs(" ", stdout); | |
322 } | |
323 | |
324 printf("\x1b[0m"); /* reset */ | |
325 SETFGCOLOR(0x00, 0x00, 0x00); | |
326 putchar('\n'); | |
327 } | |
328 color = border; | |
329 SETBGCOLOR(color[0], color[1], color[2]); | |
330 SETFGCOLOR(0xff, 0xff, 0xff); | |
331 if (showcoords) | |
332 fputs(" a b c d e f g h ", stdout); | |
333 else | |
334 fputs(" ", stdout); | |
335 printf("\x1b[0m"); /* reset */ | |
336 printf("\n"); | |
337 printf("\x1b[0m"); /* reset */ | |
338 } | |
339 | |
340 void | |
341 ascii_showpiece(int c) | |
342 { | |
343 putchar(c); | |
344 } | |
345 | |
346 /* OnlyFENs */ | |
347 void | |
348 fen_showboard(void) | |
349 { | |
350 showboardfen(); | |
351 printf("\n"); | |
352 } | |
353 | |
354 /* show board */ | |
355 /* TODO: show fancier, unicode and background square color */ | |
356 /* TODO: use the output format similar to stockfish "d" command */ | |
357 void | |
358 ascii_showboard(void) | |
359 { | |
360 int x, y, piece; | |
361 | |
362 printf("Board FEN:\n"); | |
363 showboardfen(); | |
364 printf("\n\n"); | |
365 | |
366 for (y = 0; y < 8; y++) { | |
367 fputs("+---+---+---+---+---+---+---+---+\n", stdout); | |
368 for (x = 0; x < 8; x++) { | |
369 if (x == 0) | |
370 putchar('|'); | |
371 fputs(" ", stdout); | |
372 piece = getpiece(x, y); | |
373 if (piece) | |
374 ascii_showpiece(piece); | |
375 else | |
376 fputs(" ", stdout); | |
377 fputs(" |", stdout); | |
378 } | |
379 if (showcoords) { | |
380 putchar(' '); | |
381 putchar('8' - y); | |
382 } | |
383 putchar('\n'); | |
384 } | |
385 fputs("+---+---+---+---+---+---+---+---+\n", stdout); | |
386 if (showcoords) | |
387 printf(" a | b | c | d | e | f | g | h |\n"); | |
388 | |
389 fputs("\n", stdout); | |
390 } | |
391 | |
392 int | |
393 main(int argc, char *argv[]) | |
394 { | |
395 const char *progname, *fen, *moves, *s; | |
396 int x, y, x2, y2, field, piece, takepiece; | |
397 char square[3]; | |
398 long l; | |
399 | |
400 if (argc > 3) { | |
401 fprintf(stderr, "usage: %s <FEN> [moves] or\n", argv[0]); | |
402 fprintf(stderr, " %s <FEN> or\n", argv[0]); | |
403 fprintf(stderr, " %s\n", argv[0]); | |
404 return 1; | |
405 } | |
406 if (argc > 1) | |
407 fen = argv[1]; | |
408 else | |
409 fen = "startpos"; | |
410 | |
411 if (argc > 2) | |
412 moves = argv[2]; | |
413 else | |
414 moves = ""; | |
415 | |
416 if (!strcmp(fen, "startpos")) | |
417 fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQk… | |
418 | |
419 /* initial board state, FEN format */ | |
420 x = y = field = 0; | |
421 for (s = fen; *s && field < 6; s++) { | |
422 switch (field) { | |
423 case 0: /* piece placement data */ | |
424 /* skip square */ | |
425 if (*s >= '1' && *s <= '9') { | |
426 x += (*s - '0'); | |
427 continue; | |
428 } | |
429 /* next rank */ | |
430 if (*s == '/') { | |
431 x = 0; | |
432 y++; | |
433 continue; | |
434 } | |
435 /* is piece? place it */ | |
436 if (isvalidpiece(*s)) | |
437 place(*s, x++, y); | |
438 break; | |
439 case 1: /* active color */ | |
440 if (*s == 'w' || *s == 'b') | |
441 side_to_move = *s; | |
442 break; | |
443 case 2: /* castling availability */ | |
444 if (*s == '-') { | |
445 white_can_castle[0] = 0; | |
446 white_can_castle[1] = 0; | |
447 black_can_castle[0] = 0; | |
448 black_can_castle[1] = 0; | |
449 } else if (*s == 'K') { | |
450 white_can_castle[0] = 1; | |
451 } else if (*s == 'Q') { | |
452 white_can_castle[1] = 1; | |
453 } else if (*s == 'k') { | |
454 black_can_castle[0] = 1; | |
455 } else if (*s == 'q') { | |
456 black_can_castle[1] = 1; | |
457 } | |
458 break; | |
459 case 3: /* TODO: en-passant square, rest of the fields */ | |
460 if (*s >= 'a' && *s <= 'h' && | |
461 *(s + 1) >= '1' && *(s + 1) >= '6') { | |
462 | |
463 square[0] = *s; | |
464 square[1] = *(s + 1); | |
465 square[2] = '\0'; | |
466 squaretoxy(square, &x, &y); | |
467 | |
468 enpassantsquare[0] = x; | |
469 enpassantsquare[1] = y; | |
470 } | |
471 break; | |
472 case 4: /* halfmove */ | |
473 if (!(*s >= '0' && *s <= '9')) | |
474 continue; | |
475 | |
476 l = strtol(s, NULL, 10); | |
477 if (l >= 0 && l < 32767) { | |
478 halfmove = l; | |
479 | |
480 for (; *s && isdigit((unsigned char)*s);… | |
481 ; | |
482 } | |
483 break; | |
484 case 5: /* move number */ | |
485 if (!(*s >= '0' && *s <= '9')) | |
486 continue; | |
487 | |
488 l = strtol(s, NULL, 10); | |
489 if (l >= 0 && l < 32767) { | |
490 movenumber = (int)l; | |
491 for (; *s && isdigit((unsigned char)*s);… | |
492 ; | |
493 } | |
494 break; | |
495 } | |
496 if (!*s) | |
497 break; | |
498 | |
499 /* TODO: parse which side to move, en-passant, etc */ | |
500 | |
501 /* next field, fields are: piece placement data, active … | |
502 Castling availability, En passant target square, | |
503 Halfmove clock, Fullmove number */ | |
504 if (*s == ' ') { | |
505 field++; | |
506 continue; | |
507 } | |
508 } | |
509 | |
510 /* process moves */ | |
511 square[2] = '\0'; | |
512 x = y = x2 = y2 = -1; | |
513 for (s = moves; *s; s++) { | |
514 if (*s == ' ') | |
515 continue; | |
516 if ((*s >= 'a' && *s <= 'h') && | |
517 (*(s + 1) >= '1' && *(s + 1) <= '8') && | |
518 (*(s + 2) >= 'a' && *(s + 2) <= 'h') && | |
519 (*(s + 3) >= '1' && *(s + 3) <= '8')) { | |
520 square[0] = *s; | |
521 square[1] = *(s + 1); | |
522 | |
523 s += 2; | |
524 squaretoxy(square, &x, &y); | |
525 piece = getpiece(x, y); | |
526 | |
527 place(0, x, y); /* clear square */ | |
528 | |
529 /* place piece at new location */ | |
530 square[0] = *s; | |
531 square[1] = *(s + 1); | |
532 squaretoxy(square, &x2, &y2); | |
533 takepiece = getpiece(x2, y2); | |
534 place(piece, x2, y2); | |
535 s += 2; | |
536 | |
537 /* if pawn move or taken a piece increase halfmo… | |
538 /* TODO: taking enpassant should reset halfmove … | |
539 if (piece == 'p' || piece == 'P' || takepiece !=… | |
540 halfmove = 0; | |
541 else | |
542 halfmove++; | |
543 | |
544 /* castling */ | |
545 if (piece == 'K' && y == 7 && y2 == 7 && x == 4)… | |
546 /* white: kingside castling: "e1g1" */ | |
547 if (x2 == 6) { | |
548 place('R', x2 - 1, y2); | |
549 x2 = 7; | |
550 y2 = 7; | |
551 place(0, x2, y2); /* clear rook … | |
552 } else if (x2 == 2) { | |
553 /* white: queenside castling: "e… | |
554 place('R', x2 + 1, y2); | |
555 x2 = 0; | |
556 y2 = 7; | |
557 place(0, x2, y2); | |
558 } | |
559 } else if (piece == 'k' && y == 0 && y2 == 0 && … | |
560 /* black: kingside castling: "e8g8" */ | |
561 if (x2 == 6) { | |
562 place('r', x2 - 1, y2); | |
563 x2 = 7; | |
564 y2 = 0; | |
565 place(0, x2, y2); /* clear rook … | |
566 } else if (x2 == 2) { | |
567 /* black: queenside castling: "e… | |
568 place('r', x2 + 1, y2); | |
569 x2 = 0; | |
570 y2 = 0; | |
571 place(0, x2, y2); /* clear rook … | |
572 } | |
573 } | |
574 | |
575 /* remove the ability to castle */ | |
576 if (piece == 'K') { | |
577 white_can_castle[0] = white_can_castle[1… | |
578 } else if (piece == 'k') { | |
579 black_can_castle[0] = black_can_castle[1… | |
580 } else if (piece == 'R') { | |
581 if (x == 7 && y == 7) | |
582 white_can_castle[0] = 0; | |
583 else if (x == 0 && y == 7) | |
584 white_can_castle[1] = 0; | |
585 } else if (piece == 'r') { | |
586 if (x == 0 && y == 0) | |
587 black_can_castle[1] = 0; | |
588 else if (x == 7 && y == 0) | |
589 black_can_castle[0] = 0; | |
590 } | |
591 | |
592 /* the en passant square resets after a move */ | |
593 enpassantsquare[0] = -1; | |
594 enpassantsquare[1] = -1; | |
595 /* moved 2 squares and there is an opponent pawn… | |
596 if (piece == 'P' && y == 6 && y2 == 4) { | |
597 if (getpiece(x - 1, y2) == 'p' || | |
598 getpiece(x + 1, y2) == 'p') { | |
599 enpassantsquare[0] = x; | |
600 enpassantsquare[1] = 5; | |
601 } | |
602 } else if (piece == 'p' && y == 1 && y2 == 3) { | |
603 if (getpiece(x - 1, y2) == 'P' || | |
604 getpiece(x + 1, y2) == 'P') { | |
605 enpassantsquare[0] = x; | |
606 enpassantsquare[1] = 2; | |
607 } | |
608 } | |
609 | |
610 /* possible promotion: queen, knight, bishop */ | |
611 if (*s == 'q' || *s == 'n' || *s == 'b') { | |
612 if (side_to_move == 'w') | |
613 piece = toupper((unsigned char)*… | |
614 else | |
615 piece = *s; | |
616 place(piece, x2, y2); | |
617 s++; | |
618 } | |
619 | |
620 /* a move by black increases the move number */ | |
621 if (side_to_move == 'b') | |
622 movenumber++; | |
623 | |
624 /* switch which side it is to move */ | |
625 side_to_move = side_to_move == 'b' ? 'w' : 'b'; | |
626 | |
627 /* TODO: reset enpassant square if applicable */ | |
628 } | |
629 } | |
630 /* highlight last move */ | |
631 highlightmove(x, y, x2, y2); | |
632 | |
633 progname = argv[0] ? argv[0] : "fen_to_svg"; | |
634 if ((s = strrchr(progname, '/'))) | |
635 progname = s + 1; | |
636 if (!strcmp(progname, "fen_to_ascii")) | |
637 ascii_showboard(); | |
638 else if (!strcmp(progname, "fen_to_tty")) | |
639 tty_showboard(); | |
640 else if (!strcmp(progname, "fen_to_fen")) | |
641 fen_showboard(); | |
642 else | |
643 svg_showboard(); | |
644 | |
645 return 0; | |
646 } |