chess-puzzles

chess puzzle book generator
git clone git://git.codemadness.org/chess-puzzles
Log | Files | Refs | README | LICENSE

commit b50c708ac5b44591b62aaa443022625c3fdd44cb
parent 3d921c8e0e2011ae994e508e89fc71086fffd598
Author: Hiltjo Posthuma <hiltjo@codemadness.org>
Date:   Wed, 20 Dec 2023 22:31:43 +0100

command-line parsing for formats, add OpenBSD pledge

Diffstat:
MMakefile | 7++-----
Afen.c | 730+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dfen_to_svg.c | 700-------------------------------------------------------------------------------
3 files changed, 732 insertions(+), 705 deletions(-)

diff --git a/Makefile b/Makefile @@ -1,8 +1,5 @@ build: clean - ${CC} -o fen_to_svg fen_to_svg.c ${CFLAGS} ${LDFLAGS} - ln -s fen_to_svg fen_to_ascii - ln -s fen_to_svg fen_to_tty - ln -s fen_to_svg fen_to_fen + ${CC} -o fen fen.c ${CFLAGS} ${LDFLAGS} db: rm -f lichess_db_puzzle.csv.zst lichess_db_puzzle.csv @@ -10,4 +7,4 @@ db: zstd -d < lichess_db_puzzle.csv.zst > lichess_db_puzzle.csv clean: - rm -f fen_to_ascii fen_to_fen fen_to_svg fen_to_tty + rm -f fen diff --git a/fen.c b/fen.c @@ -0,0 +1,730 @@ +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#ifdef __OpenBSD__ +#include <err.h> +#include <unistd.h> +#endif + +/* macro for truecolor RGB output to tty */ +#define SETFGCOLOR(r,g,b) printf("\x1b[38;2;%d;%d;%dm", r, g, b) +#define SETBGCOLOR(r,g,b) printf("\x1b[48;2;%d;%d;%dm", r, g, b) + +static char board[8][8]; +static char highlight[8][8]; + +static int side_to_move = 'w'; /* default: white to move */ +static int white_can_castle[2] = { 0, 0 }; /* allow king side, allow queen side? */ +static int black_can_castle[2] = { 0, 0 }; /* allow king side, allow queen side? */ +static int enpassantsquare[2] = { -1, -1 }; +static int movenumber = 1; +static int halfmove = 0; + +/* lichess default theme colors */ +static const int border[] = { 0x70, 0x49, 0x2d }; +static const int darksquare[] = { 0xb5, 0x88, 0x63 }; +static const int lightsquare[] = { 0xf0, 0xd9, 0xb5 }; +static const int darksquarehi[] = { 0xaa, 0xa2, 0x3a }; +static const int lightsquarehi[] = { 0xcd, 0xd2, 0x6a }; + +static int showcoords = 1; /* config: show board coordinates? */ +static int flipboard = 0; /* config: flip board ? */ + +int +isvalidsquare(int x, int y) +{ + return !(x < 0 || x >= 8 || y < 0 || y >= 8); +} + +int +isvalidpiece(int c) +{ + static char pieces[] = "PNBRQKpnbrqk"; + + return strchr(pieces, c) ? 1 : 0; +} + +/* place a piece, if possible */ +void +place(int piece, int x, int y) +{ + if (!isvalidsquare(x, y)) + return; + + board[y][x] = piece; +} + +/* get piece, if possible */ +int +getpiece(int x, int y) +{ + if (!isvalidsquare(x, y)) + return 0; + return board[y][x]; +} + +int +squaretoxy(const char *s, int *x, int *y) +{ + if (*s >= 'a' && *s <= 'h' && + *(s + 1) >= '1' && *(s + 1) <= '8') { + *x = *s - 'a'; + *y = '8' - *(s + 1); + return 1; + } + return 0; +} + +void +highlightmove(int x1, int y1, int x2, int y2) +{ + if (isvalidsquare(x1, y1)) + highlight[y1][x1] = 1; + + if (isvalidsquare(x2, y2)) + highlight[y2][x2] = 1; +} + +void +showboardfen(void) +{ + int x, y, piece, skip = 0; + + for (y = 0; y < 8; y++) { + if (y > 0) + putchar('/'); + skip = 0; + for (x = 0; x < 8; x++) { + piece = getpiece(x, y); + if (piece) { + if (skip) + putchar(skip + '0'); + putchar(piece); + skip = 0; + } else { + skip++; + } + } + if (skip) + putchar(skip + '0'); + } + printf(" %c ", side_to_move); + if (white_can_castle[0]) + putchar('K'); + if (white_can_castle[1]) + putchar('Q'); + if (black_can_castle[0]) + putchar('k'); + if (black_can_castle[1]) + putchar('q'); + if ((white_can_castle[0] + white_can_castle[1] + + black_can_castle[0] + black_can_castle[1]) == 0) + putchar('-'); /* no castling for either side */ + putchar(' '); + + if (enpassantsquare[0] != -1 && enpassantsquare[1] != -1) { + putchar('a' + enpassantsquare[0]); + putchar('8' - enpassantsquare[1]); + } else { + putchar('-'); + } + printf(" %d %d", halfmove, movenumber); +} + +void +showpiece_svg(int c) +{ + const char *s = ""; + + /* lichess default set, + extracted from https://github.com/lichess-org/lila/tree/master/public/piece/cburnett */ + switch (c) { + case 'K': s = "<g 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>"; break; + case 'Q': s = "<g 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>"; break; + case 'R': s = "<g 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>"; break; + case 'B': s = "<g 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>"; break; + case 'N': s = "<g 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>"; break; + case 'P': s = "<path 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\" fill=\"#fff\" stroke=\"#000\" stroke-width=\"1.5\" stroke-linecap=\"round\"/>"; break; + case 'k': s = "<g 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>"; break; + case 'q': s = "<g 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>"; break; + case 'r': s = "<g 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>"; break; + case 'b': s = "<g 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>"; break; + case 'n': s = "<g 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>"; break; + case 'p': s = "<path 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\"/>"; break; + } + + if (*s) + fputs(s, stdout); +} + +void +showboard_svg(void) +{ + const int *color; + int ix, iy, x, y, piece; + + fputs("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n" + "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n" + "<svg width=\"360\" height=\"360\" viewBox=\"0 0 360 360\" xmlns=\"http://www.w3.org/2000/svg\">\n" + "<rect fill=\"#fff\" stroke=\"#000\" x=\"0\" y=\"0\" width=\"360\" height=\"360\"/>\n", stdout); + + fputs("<!-- Board FEN: ", stdout); + showboardfen(); + fputs(" -->\n", stdout); + + for (iy = 0; iy < 8; iy++) { + y = flipboard ? 7 - iy : iy; + + for (ix = 0; ix < 8; ix++) { + x = flipboard ? 7 - ix : ix; + + if (x % 2 == 0) { + if (y % 2 == 0) + color = highlight[y][x] ? lightsquarehi : lightsquare; + else + color = highlight[y][x] ? darksquarehi : darksquare; + } else { + if (y % 2 == 0) + color = highlight[y][x] ? darksquarehi : darksquare; + else + color = highlight[y][x] ? lightsquarehi : lightsquare; + } + + printf("<g><rect x=\"%d\" y=\"%d\" width=\"45\" height=\"45\" fill=\"#%02x%02x%02x\"/></g>\n", + ix * 45, iy * 45, color[0], color[1], color[2]); + + piece = getpiece(x, y); + if (piece) { + printf("<g transform=\"translate(%d %d)\">", ix * 45, iy * 45); + showpiece_svg(piece); + fputs("</g>\n", stdout); + } + } + } + + if (showcoords) { + ix = 7; + x = flipboard ? 0 : 7; + for (iy = 0; iy < 8; iy++) { + y = flipboard ? 7 - iy : iy; + + /* inverse square color for text */ + if (x % 2 == 0) { + if (y % 2 == 0) + color = highlight[y][x] ? darksquarehi : darksquare; + else + color = highlight[y][x] ? lightsquarehi : lightsquare; + } else { + if (y % 2 == 0) + color = highlight[y][x] ? lightsquarehi : lightsquare; + else + color = highlight[y][x] ? darksquarehi : darksquare; + } + + printf("<text x=\"%d\" y=\"%d\" fill=\"#%02x%02x%02x\" text-anchor=\"end\" style=\"font-family: sans-serif; font-size: 10px\">%c</text>\n", + (ix + 1) * 45 - 2, (iy * 45) + 10, color[0], color[1], color[2], '8' - y); + } + iy = 7; + y = flipboard ? 0 : 7; + for (ix = 0; ix < 8; ix++) { + x = flipboard ? 7 - ix : ix; + + /* inverse square color for text */ + if (x % 2 == 0) { + if (y % 2 == 0) + color = highlight[y][x] ? darksquarehi : darksquare; + else + color = highlight[y][x] ? lightsquarehi : lightsquare; + } else { + if (y % 2 == 0) + color = highlight[y][x] ? lightsquarehi : lightsquare; + else + color = highlight[y][x] ? darksquarehi : darksquare; + } + + printf("<text x=\"%d\" y=\"%d\" fill=\"#%02x%02x%02x\" text-anchor=\"start\" style=\"font-family: sans-serif; font-size: 10px\">%c</text>\n", + (ix * 45) + 2, (iy + 1) * 45 - 3, color[0], color[1], color[2], x + 'a'); + } + } + + fputs("</svg>\n", stdout); +} + +void +showpiece_tty(int c) +{ + const char *s = ""; + + /* simple or use unicode character */ +#if 0 + putchar(c); + return; +#endif + + switch (c) { + case 'K': s = "♔"; break; + case 'Q': s = "♕"; break; + case 'R': s = "♖"; break; + case 'B': s = "♗"; break; + case 'N': s = "♘"; break; + case 'P': s = "♙"; break; + case 'k': s = "♚"; break; + case 'q': s = "♛"; break; + case 'r': s = "♜"; break; + case 'b': s = "♝"; break; + case 'n': s = "♞"; break; + case 'p': s = "♟"; break; + } + + if (*s) + fputs(s, stdout); +} + +/* show board */ +void +showboard_tty(void) +{ + const int *color; + int ix, iy, x, y, piece; + + printf("Board FEN:\n"); + showboardfen(); + printf("\n\n"); + + SETFGCOLOR(0x00, 0x00, 0x00); + + color = border; + SETBGCOLOR(color[0], color[1], color[2]); + SETFGCOLOR(0xff, 0xff, 0xff); + fputs(" ", stdout); + printf("\x1b[0m"); /* reset */ + SETFGCOLOR(0x00, 0x00, 0x00); + putchar('\n'); + + for (iy = 0; iy < 8; iy++) { + y = flipboard ? 7 - iy : iy; + + color = border; + SETBGCOLOR(color[0], color[1], color[2]); + SETFGCOLOR(0xff, 0xff, 0xff); + fputs(" ", stdout); + + for (ix = 0; ix < 8; ix++) { + x = flipboard ? 7 - ix : ix; + + if (x % 2 == 0) { + if (y % 2 == 0) + color = highlight[y][x] ? lightsquarehi : lightsquare; + else + color = highlight[y][x] ? darksquarehi : darksquare; + } else { + if (y % 2 == 0) + color = highlight[y][x] ? darksquarehi : darksquare; + else + color = highlight[y][x] ? lightsquarehi : lightsquare; + } + SETBGCOLOR(color[0], color[1], color[2]); + + fputs(" ", stdout); + piece = getpiece(x, y); + if (piece) { + if (piece >= 'A' && piece <= 'Z') + SETFGCOLOR(0xff, 0xff, 0xff); + else + SETFGCOLOR(0x00, 0x00, 0x00); + /* workaround: use black chess symbol, because the color + is filled and better visible */ + showpiece_tty(tolower(piece)); + } else { + fputs(" ", stdout); + } + fputs(" ", stdout); + } + printf("\x1b[0m"); /* reset */ + + color = border; + SETBGCOLOR(color[0], color[1], color[2]); + SETFGCOLOR(0xff, 0xff, 0xff); + if (showcoords) { + putchar(' '); + putchar('8' - y); + putchar(' '); + } else { + fputs(" ", stdout); + } + + printf("\x1b[0m"); /* reset */ + SETFGCOLOR(0x00, 0x00, 0x00); + putchar('\n'); + } + color = border; + SETBGCOLOR(color[0], color[1], color[2]); + SETFGCOLOR(0xff, 0xff, 0xff); + if (showcoords) { + if (flipboard) + fputs(" h g f e d c b a ", stdout); + else + fputs(" a b c d e f g h ", stdout); + } else { + fputs(" ", stdout); + } + printf("\x1b[0m"); /* reset */ + printf("\n"); +} + +void +showpiece_ascii(int c) +{ + putchar(c); +} + +/* OnlyFENs */ +void +showboard_fen(void) +{ + showboardfen(); + printf("\n"); +} + +/* show board */ +void +showboard_ascii(void) +{ + int hi[3] = { '>', ' ', '<' }; + int dark[3] = { '.', '.', '.' }; + int light[3] = { ' ', ' ', ' ' }; + int *color, ix, iy, x, y, piece; + + printf("Board FEN:\n"); + showboardfen(); + printf("\n\n"); + + for (iy = 0; iy < 8; iy++) { + y = flipboard ? 7 - iy : iy; + + fputs("+---+---+---+---+---+---+---+---+\n", stdout); + for (ix = 0; ix < 8; ix++) { + x = flipboard ? 7 - ix : ix; + + if (x % 2 == 0) { + if (y % 2 == 0) + color = highlight[y][x] ? hi : light; + else + color = highlight[y][x] ? hi : dark; + } else { + if (y % 2 == 0) + color = highlight[y][x] ? hi : dark; + else + color = highlight[y][x] ? hi : light; + } + + if (ix == 0) + putchar('|'); + putchar(color[0]); + piece = getpiece(x, y); + if (piece) + showpiece_ascii(piece); + else + putchar(color[1]); + putchar(color[2]); + putchar('|'); + } + if (showcoords) { + putchar(' '); + putchar('8' - y); + } + putchar('\n'); + } + fputs("+---+---+---+---+---+---+---+---+\n", stdout); + if (showcoords) { + if (flipboard) + printf(" h | g | f | e | d | c | b | a |\n"); + else + printf(" a | b | c | d | e | f | g | h |\n"); + } + + fputs("\n", stdout); +} + +void +usage(char *argv0) +{ + fprintf(stderr, "usage: %s [-cCfF] [-o ascii|fen|tty|svg] [FEN] [moves]\n", argv0); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + const char *fen, *moves, *s, *output = "svg"; + int x, y, x2, y2, field, piece, takepiece; + int i, j; + char square[3]; + long l; + +#ifdef __OpenBSD__ + if (pledge("stdio", NULL) = -1) + err(1, "pledge"); +#endif + + fen = "startpos"; + moves = ""; + + for (i = 1; i < argc; i++) { + if (argv[i][0] != '-') + break; + + for (j = 1; argv[i][j]; j++) { + switch (argv[i][j]) { + case 'c': showcoords = 1; break; + case 'C': showcoords = 0; break; + case 'f': flipboard = 1; break; + case 'F': flipboard = 0; break; + case 'o': + if (i + 1 >= argc) + usage(argv[0]); + output = argv[++i]; + goto next; + default: + usage(argv[0]); + break; + } + } +next: + } + if (i < argc) { + fen = argv[i]; + i++; + } + if (i < argc) { + moves = argv[i]; + i++; + } + + if (!strcmp(fen, "startpos")) + fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; + + /* initial board state, FEN format */ + x = y = field = 0; + for (s = fen; *s && field < 6; s++) { + switch (field) { + case 0: /* piece placement data */ + /* skip square */ + if (*s >= '1' && *s <= '9') { + x += (*s - '0'); + continue; + } + /* next rank */ + if (*s == '/') { + x = 0; + y++; + continue; + } + /* is piece? place it */ + if (isvalidpiece(*s)) + place(*s, x++, y); + break; + case 1: /* active color */ + if (*s == 'w' || *s == 'b') + side_to_move = *s; + break; + case 2: /* castling availability */ + if (*s == '-') { + white_can_castle[0] = 0; + white_can_castle[1] = 0; + black_can_castle[0] = 0; + black_can_castle[1] = 0; + } else if (*s == 'K') { + white_can_castle[0] = 1; + } else if (*s == 'Q') { + white_can_castle[1] = 1; + } else if (*s == 'k') { + black_can_castle[0] = 1; + } else if (*s == 'q') { + black_can_castle[1] = 1; + } + break; + case 3: /* en passant square */ + if (*s >= 'a' && *s <= 'h' && + *(s + 1) >= '1' && *(s + 1) >= '6') { + + square[0] = *s; + square[1] = *(s + 1); + square[2] = '\0'; + squaretoxy(square, &x, &y); + + enpassantsquare[0] = x; + enpassantsquare[1] = y; + } + break; + case 4: /* halfmove */ + if (!(*s >= '0' && *s <= '9')) + continue; + + l = strtol(s, NULL, 10); + if (l >= 0 && l < 32767) { + halfmove = l; + + for (; *s && isdigit((unsigned char)*s); s++) + ; + } + break; + case 5: /* move number */ + if (!(*s >= '0' && *s <= '9')) + continue; + + l = strtol(s, NULL, 10); + if (l >= 0 && l < 32767) { + movenumber = (int)l; + for (; *s && isdigit((unsigned char)*s); s++) + ; + } + break; + } + if (!*s) + break; + + /* next field, fields are: piece placement data, active color, + Castling availability, En passant target square, + Halfmove clock, Fullmove number */ + if (*s == ' ') { + field++; + continue; + } + } + + /* process moves */ + square[2] = '\0'; + x = y = x2 = y2 = -1; + for (s = moves; *s; s++) { + if (*s == ' ') + continue; + if ((*s >= 'a' && *s <= 'h') && + (*(s + 1) >= '1' && *(s + 1) <= '8') && + (*(s + 2) >= 'a' && *(s + 2) <= 'h') && + (*(s + 3) >= '1' && *(s + 3) <= '8')) { + square[0] = *s; + square[1] = *(s + 1); + + s += 2; + squaretoxy(square, &x, &y); + piece = getpiece(x, y); + + place(0, x, y); /* clear square */ + + /* place piece at new location */ + square[0] = *s; + square[1] = *(s + 1); + squaretoxy(square, &x2, &y2); + takepiece = getpiece(x2, y2); + place(piece, x2, y2); + s += 2; + + /* if pawn move or taken a piece increase halfmove counter */ + if (piece == 'p' || piece == 'P' || takepiece != 0) + halfmove = 0; + else + halfmove++; + + /* castling */ + if (piece == 'K' && y == 7 && y2 == 7 && x == 4) { + /* white: kingside castling: "e1g1" */ + if (x2 == 6) { + place('R', x2 - 1, y2); + x2 = 7; + y2 = 7; + place(0, x2, y2); /* clear rook square */ + } else if (x2 == 2) { + /* white: queenside castling: "e1c1" */ + place('R', x2 + 1, y2); + x2 = 0; + y2 = 7; + place(0, x2, y2); + } + } else if (piece == 'k' && y == 0 && y2 == 0 && x == 4) { + /* black: kingside castling: "e8g8" */ + if (x2 == 6) { + place('r', x2 - 1, y2); + x2 = 7; + y2 = 0; + place(0, x2, y2); /* clear rook square */ + } else if (x2 == 2) { + /* black: queenside castling: "e8c8" */ + place('r', x2 + 1, y2); + x2 = 0; + y2 = 0; + place(0, x2, y2); /* clear rook square */ + } + } + + /* remove the ability to castle */ + if (piece == 'K') { + white_can_castle[0] = white_can_castle[1] = 0; + } else if (piece == 'k') { + black_can_castle[0] = black_can_castle[1] = 0; + } else if (piece == 'R') { + if (x == 7 && y == 7) + white_can_castle[0] = 0; + else if (x == 0 && y == 7) + white_can_castle[1] = 0; + } else if (piece == 'r') { + if (x == 0 && y == 0) + black_can_castle[1] = 0; + else if (x == 7 && y == 0) + black_can_castle[0] = 0; + } + + /* the en passant square resets after a move */ + enpassantsquare[0] = -1; + enpassantsquare[1] = -1; + /* moved 2 squares and there is an opponent pawn next to it */ + if (piece == 'P' && y == 6 && y2 == 4) { + if (getpiece(x - 1, y2) == 'p' || + getpiece(x + 1, y2) == 'p') { + enpassantsquare[0] = x; + enpassantsquare[1] = 5; + } + } else if (piece == 'p' && y == 1 && y2 == 3) { + if (getpiece(x - 1, y2) == 'P' || + getpiece(x + 1, y2) == 'P') { + enpassantsquare[0] = x; + enpassantsquare[1] = 2; + } + } + + /* possible promotion: queen, knight, bishop */ + if (*s == 'q' || *s == 'n' || *s == 'b') { + if (side_to_move == 'w') + piece = toupper((unsigned char)*s); + else + piece = *s; + place(piece, x2, y2); + s++; + } + + /* a move by black increases the move number */ + if (side_to_move == 'b') + movenumber++; + + /* switch which side it is to move */ + side_to_move = side_to_move == 'b' ? 'w' : 'b'; + } + } + /* highlight last move */ + highlightmove(x, y, x2, y2); + + if (!strcmp(output, "ascii")) + showboard_ascii(); + else if (!strcmp(output, "fen")) + showboard_fen(); + else if (!strcmp(output, "svg")) + showboard_svg(); + else if (!strcmp(output, "tty")) + showboard_tty(); + else + usage(argv[0]); + + return 0; +} diff --git a/fen_to_svg.c b/fen_to_svg.c @@ -1,700 +0,0 @@ -/* TODO: output for PGN notation for moves */ - -#include <ctype.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -/* macro for truecolor RGB output to tty */ -#define SETFGCOLOR(r,g,b) printf("\x1b[38;2;%d;%d;%dm", r, g, b) -#define SETBGCOLOR(r,g,b) printf("\x1b[48;2;%d;%d;%dm", r, g, b) - -static char board[8][8]; -static char highlight[8][8]; - -static int side_to_move = 'w'; /* default: white to move */ -static int white_can_castle[2] = { 0, 0 }; /* allow king side, allow queen side? */ -static int black_can_castle[2] = { 0, 0 }; /* allow king side, allow queen side? */ -static int enpassantsquare[2] = { -1, -1 }; -static int movenumber = 1; -static int halfmove = 0; - -/* lichess default theme colors */ -static const int border[] = { 0x70, 0x49, 0x2d }; -static const int darksquare[] = { 0xb5, 0x88, 0x63 }; -static const int lightsquare[] = { 0xf0, 0xd9, 0xb5 }; -static const int darksquarehi[] = { 0xaa, 0xa2, 0x3a }; -static const int lightsquarehi[] = { 0xcd, 0xd2, 0x6a }; - -static int showcoords = 1; /* config: show board coordinates? */ -static int flipboard = 0; /* config: flip board ? */ - -int -isvalidsquare(int x, int y) -{ - return !(x < 0 || x >= 8 || y < 0 || y >= 8); -} - -int -isvalidpiece(int c) -{ - static char pieces[] = "PNBRQKpnbrqk"; - - return strchr(pieces, c) ? 1 : 0; -} - -/* place a piece, if possible */ -void -place(int piece, int x, int y) -{ - if (!isvalidsquare(x, y)) - return; - - board[y][x] = piece; -} - -/* get piece, if possible */ -int -getpiece(int x, int y) -{ - if (!isvalidsquare(x, y)) - return 0; - return board[y][x]; -} - -int -squaretoxy(const char *s, int *x, int *y) -{ - if (*s >= 'a' && *s <= 'h' && - *(s + 1) >= '1' && *(s + 1) <= '8') { - *x = *s - 'a'; - *y = '8' - *(s + 1); - return 1; - } - return 0; -} - -void -highlightmove(int x1, int y1, int x2, int y2) -{ - if (isvalidsquare(x1, y1)) - highlight[y1][x1] = 1; - - if (isvalidsquare(x2, y2)) - highlight[y2][x2] = 1; -} - -void -showboardfen(void) -{ - int x, y, piece, skip = 0; - - for (y = 0; y < 8; y++) { - if (y > 0) - putchar('/'); - skip = 0; - for (x = 0; x < 8; x++) { - piece = getpiece(x, y); - if (piece) { - if (skip) - putchar(skip + '0'); - putchar(piece); - skip = 0; - } else { - skip++; - } - } - if (skip) - putchar(skip + '0'); - } - printf(" %c ", side_to_move); - if (white_can_castle[0]) - putchar('K'); - if (white_can_castle[1]) - putchar('Q'); - if (black_can_castle[0]) - putchar('k'); - if (black_can_castle[1]) - putchar('q'); - if ((white_can_castle[0] + white_can_castle[1] + - black_can_castle[0] + black_can_castle[1]) == 0) - putchar('-'); /* no castling for either side */ - putchar(' '); - - if (enpassantsquare[0] != -1 && enpassantsquare[1] != -1) { - putchar('a' + enpassantsquare[0]); - putchar('8' - enpassantsquare[1]); - } else { - putchar('-'); - } - printf(" %d %d", halfmove, movenumber); -} - -void -svg_showpiece(int c) -{ - const char *s = ""; - - /* lichess default set, - extracted from https://github.com/lichess-org/lila/tree/master/public/piece/cburnett */ - switch (c) { - case 'K': s = "<g 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>"; break; - case 'Q': s = "<g 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>"; break; - case 'R': s = "<g 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>"; break; - case 'B': s = "<g 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>"; break; - case 'N': s = "<g 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>"; break; - case 'P': s = "<path 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\" fill=\"#fff\" stroke=\"#000\" stroke-width=\"1.5\" stroke-linecap=\"round\"/>"; break; - case 'k': s = "<g 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>"; break; - case 'q': s = "<g 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>"; break; - case 'r': s = "<g 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>"; break; - case 'b': s = "<g 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>"; break; - case 'n': s = "<g 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>"; break; - case 'p': s = "<path 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\"/>"; break; - } - - if (*s) - fputs(s, stdout); -} - -void -svg_showboard(void) -{ - const int *color; - int ix, iy, x, y, piece; - - fputs("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n" - "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n" - "<svg width=\"360\" height=\"360\" viewBox=\"0 0 360 360\" xmlns=\"http://www.w3.org/2000/svg\">\n" - "<rect fill=\"#fff\" stroke=\"#000\" x=\"0\" y=\"0\" width=\"360\" height=\"360\"/>\n", stdout); - - fputs("<!-- Board FEN: ", stdout); - showboardfen(); - fputs(" -->\n", stdout); - - for (iy = 0; iy < 8; iy++) { - y = flipboard ? 7 - iy : iy; - - for (ix = 0; ix < 8; ix++) { - x = flipboard ? 7 - ix : ix; - - if (x % 2 == 0) { - if (y % 2 == 0) - color = highlight[y][x] ? lightsquarehi : lightsquare; - else - color = highlight[y][x] ? darksquarehi : darksquare; - } else { - if (y % 2 == 0) - color = highlight[y][x] ? darksquarehi : darksquare; - else - color = highlight[y][x] ? lightsquarehi : lightsquare; - } - - printf("<g><rect x=\"%d\" y=\"%d\" width=\"45\" height=\"45\" fill=\"#%02x%02x%02x\"/></g>\n", - ix * 45, iy * 45, color[0], color[1], color[2]); - - piece = getpiece(x, y); - if (piece) { - printf("<g transform=\"translate(%d %d)\">", ix * 45, iy * 45); - svg_showpiece(piece); - fputs("</g>\n", stdout); - } - } - } - - if (showcoords) { - ix = 7; - x = flipboard ? 0 : 7; - for (iy = 0; iy < 8; iy++) { - y = flipboard ? 7 - iy : iy; - - /* inverse square color for text */ - if (x % 2 == 0) { - if (y % 2 == 0) - color = highlight[y][x] ? darksquarehi : darksquare; - else - color = highlight[y][x] ? lightsquarehi : lightsquare; - } else { - if (y % 2 == 0) - color = highlight[y][x] ? lightsquarehi : lightsquare; - else - color = highlight[y][x] ? darksquarehi : darksquare; - } - - printf("<text x=\"%d\" y=\"%d\" fill=\"#%02x%02x%02x\" text-anchor=\"end\" style=\"font-family: sans-serif; font-size: 10px\">%c</text>\n", - (ix + 1) * 45 - 2, (iy * 45) + 10, color[0], color[1], color[2], '8' - y); - } - iy = 7; - y = flipboard ? 0 : 7; - for (ix = 0; ix < 8; ix++) { - x = flipboard ? 7 - ix : ix; - - /* inverse square color for text */ - if (x % 2 == 0) { - if (y % 2 == 0) - color = highlight[y][x] ? darksquarehi : darksquare; - else - color = highlight[y][x] ? lightsquarehi : lightsquare; - } else { - if (y % 2 == 0) - color = highlight[y][x] ? lightsquarehi : lightsquare; - else - color = highlight[y][x] ? darksquarehi : darksquare; - } - - printf("<text x=\"%d\" y=\"%d\" fill=\"#%02x%02x%02x\" text-anchor=\"start\" style=\"font-family: sans-serif; font-size: 10px\">%c</text>\n", - (ix * 45) + 2, (iy + 1) * 45 - 3, color[0], color[1], color[2], x + 'a'); - } - } - - fputs("</svg>\n", stdout); -} - -void -tty_showpiece(int c) -{ - const char *s = ""; - - /* simple or use unicode character */ -#if 0 - putchar(c); - return; -#endif - - switch (c) { - case 'K': s = "♔"; break; - case 'Q': s = "♕"; break; - case 'R': s = "♖"; break; - case 'B': s = "♗"; break; - case 'N': s = "♘"; break; - case 'P': s = "♙"; break; - case 'k': s = "♚"; break; - case 'q': s = "♛"; break; - case 'r': s = "♜"; break; - case 'b': s = "♝"; break; - case 'n': s = "♞"; break; - case 'p': s = "♟"; break; - } - - if (*s) - fputs(s, stdout); -} - -/* show board */ -void -tty_showboard(void) -{ - const int *color; - int ix, iy, x, y, piece; - - printf("Board FEN:\n"); - showboardfen(); - printf("\n\n"); - - SETFGCOLOR(0x00, 0x00, 0x00); - - color = border; - SETBGCOLOR(color[0], color[1], color[2]); - SETFGCOLOR(0xff, 0xff, 0xff); - fputs(" ", stdout); - printf("\x1b[0m"); /* reset */ - SETFGCOLOR(0x00, 0x00, 0x00); - putchar('\n'); - - for (iy = 0; iy < 8; iy++) { - y = flipboard ? 7 - iy : iy; - - color = border; - SETBGCOLOR(color[0], color[1], color[2]); - SETFGCOLOR(0xff, 0xff, 0xff); - fputs(" ", stdout); - - for (ix = 0; ix < 8; ix++) { - x = flipboard ? 7 - ix : ix; - - if (x % 2 == 0) { - if (y % 2 == 0) - color = highlight[y][x] ? lightsquarehi : lightsquare; - else - color = highlight[y][x] ? darksquarehi : darksquare; - } else { - if (y % 2 == 0) - color = highlight[y][x] ? darksquarehi : darksquare; - else - color = highlight[y][x] ? lightsquarehi : lightsquare; - } - SETBGCOLOR(color[0], color[1], color[2]); - - fputs(" ", stdout); - piece = getpiece(x, y); - if (piece) { - if (piece >= 'A' && piece <= 'Z') - SETFGCOLOR(0xff, 0xff, 0xff); - else - SETFGCOLOR(0x00, 0x00, 0x00); - /* workaround: use black chess symbol, because the color - is filled and better visible */ - tty_showpiece(tolower(piece)); - } else { - fputs(" ", stdout); - } - fputs(" ", stdout); - } - printf("\x1b[0m"); /* reset */ - - color = border; - SETBGCOLOR(color[0], color[1], color[2]); - SETFGCOLOR(0xff, 0xff, 0xff); - if (showcoords) { - putchar(' '); - putchar('8' - y); - putchar(' '); - } else { - fputs(" ", stdout); - } - - printf("\x1b[0m"); /* reset */ - SETFGCOLOR(0x00, 0x00, 0x00); - putchar('\n'); - } - color = border; - SETBGCOLOR(color[0], color[1], color[2]); - SETFGCOLOR(0xff, 0xff, 0xff); - if (showcoords) { - if (flipboard) - fputs(" h g f e d c b a ", stdout); - else - fputs(" a b c d e f g h ", stdout); - } else { - fputs(" ", stdout); - } - printf("\x1b[0m"); /* reset */ - printf("\n"); -} - -void -ascii_showpiece(int c) -{ - putchar(c); -} - -/* OnlyFENs */ -void -fen_showboard(void) -{ - showboardfen(); - printf("\n"); -} - -/* show board */ -void -ascii_showboard(void) -{ - int hi[3] = { '>', ' ', '<' }; - int dark[3] = { '.', '.', '.' }; - int light[3] = { ' ', ' ', ' ' }; - int *color, ix, iy, x, y, piece; - - printf("Board FEN:\n"); - showboardfen(); - printf("\n\n"); - - for (iy = 0; iy < 8; iy++) { - y = flipboard ? 7 - iy : iy; - - fputs("+---+---+---+---+---+---+---+---+\n", stdout); - for (ix = 0; ix < 8; ix++) { - x = flipboard ? 7 - ix : ix; - - if (x % 2 == 0) { - if (y % 2 == 0) - color = highlight[y][x] ? hi : light; - else - color = highlight[y][x] ? hi : dark; - } else { - if (y % 2 == 0) - color = highlight[y][x] ? hi : dark; - else - color = highlight[y][x] ? hi : light; - } - - if (ix == 0) - putchar('|'); - putchar(color[0]); - piece = getpiece(x, y); - if (piece) - ascii_showpiece(piece); - else - putchar(color[1]); - putchar(color[2]); - putchar('|'); - } - if (showcoords) { - putchar(' '); - putchar('8' - y); - } - putchar('\n'); - } - fputs("+---+---+---+---+---+---+---+---+\n", stdout); - if (showcoords) { - if (flipboard) - printf(" h | g | f | e | d | c | b | a |\n"); - else - printf(" a | b | c | d | e | f | g | h |\n"); - } - - fputs("\n", stdout); -} - -int -main(int argc, char *argv[]) -{ - const char *progname, *fen, *moves, *s; - int x, y, x2, y2, field, piece, takepiece; - char square[3]; - long l; - - if (argc > 3) { - fprintf(stderr, "usage: %s <FEN> [moves] or\n", argv[0]); - fprintf(stderr, " %s <FEN> or\n", argv[0]); - fprintf(stderr, " %s\n", argv[0]); - return 1; - } - if (argc > 1) - fen = argv[1]; - else - fen = "startpos"; - - if (argc > 2) - moves = argv[2]; - else - moves = ""; - - if (!strcmp(fen, "startpos")) - fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; - - /* initial board state, FEN format */ - x = y = field = 0; - for (s = fen; *s && field < 6; s++) { - switch (field) { - case 0: /* piece placement data */ - /* skip square */ - if (*s >= '1' && *s <= '9') { - x += (*s - '0'); - continue; - } - /* next rank */ - if (*s == '/') { - x = 0; - y++; - continue; - } - /* is piece? place it */ - if (isvalidpiece(*s)) - place(*s, x++, y); - break; - case 1: /* active color */ - if (*s == 'w' || *s == 'b') - side_to_move = *s; - break; - case 2: /* castling availability */ - if (*s == '-') { - white_can_castle[0] = 0; - white_can_castle[1] = 0; - black_can_castle[0] = 0; - black_can_castle[1] = 0; - } else if (*s == 'K') { - white_can_castle[0] = 1; - } else if (*s == 'Q') { - white_can_castle[1] = 1; - } else if (*s == 'k') { - black_can_castle[0] = 1; - } else if (*s == 'q') { - black_can_castle[1] = 1; - } - break; - case 3: /* en passant square */ - if (*s >= 'a' && *s <= 'h' && - *(s + 1) >= '1' && *(s + 1) >= '6') { - - square[0] = *s; - square[1] = *(s + 1); - square[2] = '\0'; - squaretoxy(square, &x, &y); - - enpassantsquare[0] = x; - enpassantsquare[1] = y; - } - break; - case 4: /* halfmove */ - if (!(*s >= '0' && *s <= '9')) - continue; - - l = strtol(s, NULL, 10); - if (l >= 0 && l < 32767) { - halfmove = l; - - for (; *s && isdigit((unsigned char)*s); s++) - ; - } - break; - case 5: /* move number */ - if (!(*s >= '0' && *s <= '9')) - continue; - - l = strtol(s, NULL, 10); - if (l >= 0 && l < 32767) { - movenumber = (int)l; - for (; *s && isdigit((unsigned char)*s); s++) - ; - } - break; - } - if (!*s) - break; - - /* next field, fields are: piece placement data, active color, - Castling availability, En passant target square, - Halfmove clock, Fullmove number */ - if (*s == ' ') { - field++; - continue; - } - } - - /* process moves */ - square[2] = '\0'; - x = y = x2 = y2 = -1; - for (s = moves; *s; s++) { - if (*s == ' ') - continue; - if ((*s >= 'a' && *s <= 'h') && - (*(s + 1) >= '1' && *(s + 1) <= '8') && - (*(s + 2) >= 'a' && *(s + 2) <= 'h') && - (*(s + 3) >= '1' && *(s + 3) <= '8')) { - square[0] = *s; - square[1] = *(s + 1); - - s += 2; - squaretoxy(square, &x, &y); - piece = getpiece(x, y); - - place(0, x, y); /* clear square */ - - /* place piece at new location */ - square[0] = *s; - square[1] = *(s + 1); - squaretoxy(square, &x2, &y2); - takepiece = getpiece(x2, y2); - place(piece, x2, y2); - s += 2; - - /* if pawn move or taken a piece increase halfmove counter */ - /* TODO: taking enpassant should reset halfmove too */ - if (piece == 'p' || piece == 'P' || takepiece != 0) - halfmove = 0; - else - halfmove++; - - /* castling */ - if (piece == 'K' && y == 7 && y2 == 7 && x == 4) { - /* white: kingside castling: "e1g1" */ - if (x2 == 6) { - place('R', x2 - 1, y2); - x2 = 7; - y2 = 7; - place(0, x2, y2); /* clear rook square */ - } else if (x2 == 2) { - /* white: queenside castling: "e1c1" */ - place('R', x2 + 1, y2); - x2 = 0; - y2 = 7; - place(0, x2, y2); - } - } else if (piece == 'k' && y == 0 && y2 == 0 && x == 4) { - /* black: kingside castling: "e8g8" */ - if (x2 == 6) { - place('r', x2 - 1, y2); - x2 = 7; - y2 = 0; - place(0, x2, y2); /* clear rook square */ - } else if (x2 == 2) { - /* black: queenside castling: "e8c8" */ - place('r', x2 + 1, y2); - x2 = 0; - y2 = 0; - place(0, x2, y2); /* clear rook square */ - } - } - - /* remove the ability to castle */ - if (piece == 'K') { - white_can_castle[0] = white_can_castle[1] = 0; - } else if (piece == 'k') { - black_can_castle[0] = black_can_castle[1] = 0; - } else if (piece == 'R') { - if (x == 7 && y == 7) - white_can_castle[0] = 0; - else if (x == 0 && y == 7) - white_can_castle[1] = 0; - } else if (piece == 'r') { - if (x == 0 && y == 0) - black_can_castle[1] = 0; - else if (x == 7 && y == 0) - black_can_castle[0] = 0; - } - - /* the en passant square resets after a move */ - enpassantsquare[0] = -1; - enpassantsquare[1] = -1; - /* moved 2 squares and there is an opponent pawn next to it */ - if (piece == 'P' && y == 6 && y2 == 4) { - if (getpiece(x - 1, y2) == 'p' || - getpiece(x + 1, y2) == 'p') { - enpassantsquare[0] = x; - enpassantsquare[1] = 5; - } - } else if (piece == 'p' && y == 1 && y2 == 3) { - if (getpiece(x - 1, y2) == 'P' || - getpiece(x + 1, y2) == 'P') { - enpassantsquare[0] = x; - enpassantsquare[1] = 2; - } - } - - /* possible promotion: queen, knight, bishop */ - if (*s == 'q' || *s == 'n' || *s == 'b') { - if (side_to_move == 'w') - piece = toupper((unsigned char)*s); - else - piece = *s; - place(piece, x2, y2); - s++; - } - - /* a move by black increases the move number */ - if (side_to_move == 'b') - movenumber++; - - /* switch which side it is to move */ - side_to_move = side_to_move == 'b' ? 'w' : 'b'; - - /* TODO: reset enpassant square if applicable */ - } - } - /* highlight last move */ - highlightmove(x, y, x2, y2); - - progname = argv[0] ? argv[0] : "fen_to_svg"; - if ((s = strrchr(progname, '/'))) - progname = s + 1; - if (!strcmp(progname, "fen_to_ascii")) - ascii_showboard(); - else if (!strcmp(progname, "fen_to_tty")) - tty_showboard(); - else if (!strcmp(progname, "fen_to_fen")) - fen_showboard(); - else - svg_showboard(); - - return 0; -}