fen_to_tty: add initial tty version - chess-puzzles - chess puzzle book generat… | |
git clone git://git.codemadness.org/chess-puzzles | |
Log | |
Files | |
Refs | |
README | |
LICENSE | |
--- | |
commit 26d727fe71a7e77b2eb1f733e2ad738b52089c54 | |
parent 59753af31b403f30db84f07562d2614460a2d9ab | |
Author: Hiltjo Posthuma <[email protected]> | |
Date: Mon, 18 Dec 2023 15:51:15 +0100 | |
fen_to_tty: add initial tty version | |
Diffstat: | |
M Makefile | 3 ++- | |
M README | 4 ++++ | |
A fen_to_tty.c | 329 +++++++++++++++++++++++++++++… | |
M generate.sh | 4 ++++ | |
4 files changed, 339 insertions(+), 1 deletion(-) | |
--- | |
diff --git a/Makefile b/Makefile | |
@@ -1,6 +1,7 @@ | |
build: clean | |
${CC} -o fen_to_svg fen_to_svg.c ${CFLAGS} ${LDFLAGS} | |
${CC} -o fen_to_ascii fen_to_ascii.c ${CFLAGS} ${LDFLAGS} | |
+ ${CC} -o fen_to_tty fen_to_tty.c ${CFLAGS} ${LDFLAGS} | |
db: | |
rm -f lichess_db_puzzle.csv.zst lichess_db_puzzle.csv | |
@@ -8,4 +9,4 @@ db: | |
zstd -d < lichess_db_puzzle.csv.zst > lichess_db_puzzle.csv | |
clean: | |
- rm -f fen_to_svg fen_to_ascii | |
+ rm -f fen_to_svg fen_to_ascii fen_to_tty | |
diff --git a/README b/README | |
@@ -27,6 +27,10 @@ Files | |
Read FEN and a few moves and generate an SVG image of the board. | |
* fen_to_ascii.c: | |
Read FEN and a few moves and generate a text representation of the board. | |
+* fen_to_tty.c: | |
+ Read FEN and a few moves and generate a text representation of the board | |
+ suitable for a terminal. The terminal requires UTF-8 support for chess | |
+ symbols and it uses truecolor for the board theme. | |
References | |
diff --git a/fen_to_tty.c b/fen_to_tty.c | |
@@ -0,0 +1,329 @@ | |
+/* TODO: option to flip board? */ | |
+ | |
+#include <ctype.h> | |
+#include <stdio.h> | |
+#include <string.h> | |
+ | |
+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 sid… | |
+static int black_can_castle[2] = { 0, 0 }; /* allow king side, allow queen sid… | |
+ | |
+static const int showcoords = 1; /* config: show board coordinates? */ | |
+ | |
+#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) | |
+ | |
+int | |
+isvalidsquare(int x, int y) | |
+{ | |
+ return !(x < 0 || x >= 8 || y < 0 || y >= 8); | |
+} | |
+ | |
+/* 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 | |
+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); | |
+} | |
+ | |
+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'); | |
+ } | |
+ | |
+ /* ? TODO: detect en passant, invalid castling etc? */ | |
+} | |
+ | |
+/* show board */ | |
+void | |
+showboard(void) | |
+{ | |
+ int *color; | |
+ int border[] = { 0x70, 0x49, 0x2d }; | |
+ int darksquare[] = { 0xb5, 0x88, 0x63 }; | |
+ int lightsquare[] = { 0xf0, 0xd9, 0xb5 }; | |
+ int darksquarehi[] = { 0xaa, 0xa2, 0x3a }; | |
+ int lightsquarehi[] = { 0xcd, 0xd2, 0x6a }; | |
+ int 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 (y = 0; y < 8; y++) { | |
+ color = border; | |
+ SETBGCOLOR(color[0], color[1], color[2]); | |
+ SETFGCOLOR(0xff, 0xff, 0xff); | |
+ fputs(" ", stdout); | |
+ | |
+ for (x = 0; x < 8; x++) { | |
+ if (x % 2 == 0) { | |
+ if (y % 2 == 0) | |
+ color = highlight[y][x] ? lightsquareh… | |
+ else | |
+ color = highlight[y][x] ? darksquarehi… | |
+ } else { | |
+ if (y % 2 == 0) | |
+ color = highlight[y][x] ? darksquarehi… | |
+ else | |
+ color = highlight[y][x] ? lightsquareh… | |
+ } | |
+ 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… | |
+ is filled and better visible */ | |
+ 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(' '); | |
+ } | |
+ | |
+ 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) | |
+ fputs(" a b c d e f g h ", stdout); | |
+ printf("\x1b[0m"); /* reset */ | |
+ printf("\n"); | |
+ printf("\x1b[0m"); /* reset */ | |
+ | |
+#if 0 | |
+ if (side_to_move == 'w') { | |
+ fputs("White to move\n", stdout); | |
+ } else if (side_to_move == 'b') | |
+ fputs("Black to move\n", stdout); | |
+ | |
+ if (white_can_castle[0]) | |
+ fputs("White can castle king side\n", stdout); | |
+ if (white_can_castle[1]) | |
+ fputs("White can castle queen side\n", stdout); | |
+ if (black_can_castle[0]) | |
+ fputs("Black can castle king side\n", stdout); | |
+ if (black_can_castle[1]) | |
+ fputs("Black can castle queen side\n", stdout); | |
+#endif | |
+} | |
+ | |
+int | |
+main(int argc, char *argv[]) | |
+{ | |
+ const char *fen, *moves, *s; | |
+ int x, y, x2, y2, field, piece; | |
+ char pieces[] = "PNBRQKpnbrqk", square[3]; | |
+ | |
+ if (argc != 3) { | |
+ fprintf(stderr, "usage: %s <FEN> <moves>\n", argv[0]); | |
+ return 1; | |
+ } | |
+ | |
+ fen = argv[1]; | |
+ moves = argv[2]; | |
+ | |
+ /* initial board state, FEN format */ | |
+ x = y = field = 0; | |
+ for (s = fen; *s; s++) { | |
+ /* next field, fields are: piece placement data, active color, | |
+ Castling availability, En passant target square, | |
+ Halfmove clock, Fullmove number */ | |
+ if (*s == ' ') { | |
+ field++; | |
+ continue; | |
+ } | |
+ | |
+ 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 (strchr(pieces, *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: /* TODO: en-passant square, rest of the fields */ | |
+ break; | |
+ } | |
+ /* TODO: parse which side to move, en-passant, etc */ | |
+ } | |
+ | |
+ /* 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); | |
+ place(piece, x2, y2); | |
+ s += 2; | |
+ } | |
+ } | |
+ /* highlight last move */ | |
+ highlightmove(x, y, x2, y2); | |
+ | |
+ showboard(); | |
+ | |
+ printf("\x1b[0m"); /* reset */ | |
+ | |
+ return 0; | |
+} | |
diff --git a/generate.sh b/generate.sh | |
@@ -80,11 +80,14 @@ while read -r line; do | |
img="$i.svg" | |
txt="$i.txt" | |
+ vt="$i.vt" | |
destsvg="puzzles/$img" | |
desttxt="puzzles/$txt" | |
+ destvt="puzzles/$vt" | |
./fen_to_svg "$fen" "$moves" > "$destsvg" | |
./fen_to_ascii "$fen" "$moves" > "$desttxt" | |
+ ./fen_to_tty "$fen" "$moves" > "$destvt" | |
printf '<div class="puzzle">' >> "$index" | |
printf '<h2>Puzzle %s</h2>\n' "$i" >> "$index" | |
@@ -120,6 +123,7 @@ while read -r line; do | |
printf '<p><b>%s</b>%s</p>\n' "$points" "$movetext" >> "$index" | |
printf '%s%s\n' "$points" "$movetext" >> "$desttxt" | |
+ printf '\n%s%s\n' "$points" "$movetext" >> "$destvt" | |
printf '</div>\n' >> "$index" | |