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:
M | Makefile | | | 7 | ++----- |
A | fen.c | | | 730 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
D | fen_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;
-}