fen_to_tty: add initial tty version - chess-puzzles - chess puzzle book generator
 (HTM) git clone git://git.codemadness.org/chess-puzzles
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit 26d727fe71a7e77b2eb1f733e2ad738b52089c54
 (DIR) parent 59753af31b403f30db84f07562d2614460a2d9ab
 (HTM) Author: Hiltjo Posthuma <hiltjo@codemadness.org>
       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(-)
       ---
 (DIR) 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
 (DIR) 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
 (DIR) 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;
       +}
 (DIR) 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"