commit 26d727fe71a7e77b2eb1f733e2ad738b52089c54
parent 59753af31b403f30db84f07562d2614460a2d9ab
Author: Hiltjo Posthuma <hiltjo@codemadness.org>
Date: Mon, 18 Dec 2023 15:51:15 +0100
fen_to_tty: add initial tty version
Diffstat:
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 side */
+static int black_can_castle[2] = { 0, 0 }; /* allow king side, allow queen side */
+
+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] ? 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(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"