chess-puzzles

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

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:
MMakefile | 3++-
MREADME | 4++++
Afen_to_tty.c | 329+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mgenerate.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 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"