Introduction
Introduction Statistics Contact Development Disclaimer Help
fen: various code improvements, initial code for PGN output (disabled for now) …
git clone git://git.codemadness.org/chess-puzzles
Log
Files
Refs
README
LICENSE
---
commit e536a441d52c33adea06f8ed451bd890a3ebdb9d
parent 4f6db6f748b3760f1f991e46bbb37246d201d4c8
Author: Hiltjo Posthuma <[email protected]>
Date: Sat, 23 Dec 2023 18:59:46 +0100
fen: various code improvements, initial code for PGN output (disabled for now)
- Reduce SVG output size by reusing the draw paths for pawns (saves ~4KB per
image on average). Of course this depends. On a board with no pawns it can be
~300 bytes larger.
- Remove Board FEN header for tty and ASCII outputs.
- Make the highlight function more generic (for one move).
- Improve outputs parsing, an invalid option now shows the usage().
- Rename the output functions to output_<outputname>().
- Add initial code for PGN output, needs some work, disabled for now.
... work in progress
Diffstat:
M fen.1 | 4 +++-
M fen.c | 289 +++++++++++++++++++++++++----…
M tests.sh | 53 ++++++++++++++++++++++++++++++
3 files changed, 294 insertions(+), 52 deletions(-)
---
diff --git a/fen.1 b/fen.1
@@ -7,7 +7,7 @@
.Sh SYNOPSIS
.Nm
.Op Fl cCfF
-.Op Fl o Ar ascii | fen | svg | tty
+.Op Fl o Ar ascii | fen | pgn | svg | tty
.Op Ar FEN
.Op Ar moves
.Sh DESCRIPTION
@@ -32,6 +32,8 @@ ASCII text representation of the board.
FEN of the board state after playing the moves.
.It fen
FEN of the board state after playing the moves.
+.It pgn
+PGN output of the moves for the board.
.It svg
SVG image of the board.
.It tty
diff --git a/fen.c b/fen.c
@@ -1,4 +1,5 @@
#include <ctype.h>
+#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -12,6 +13,9 @@
#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)
+enum outputmode { ModeASCII = 0, ModeFEN, ModePGN, ModeTTY, ModeSVG };
+enum outputmode outputmode = ModeSVG;
+
static char board[8][8];
static char highlight[8][8];
@@ -32,6 +36,19 @@ static const int lightsquarehi[] = { 0xcd, 0xd2, 0x6a };
static int showcoords = 1; /* config: show board coordinates? */
static int flipboard = 0; /* config: flip board ? */
+void
+pgn(const char *fmt, ...)
+{
+ va_list ap;
+
+ if (outputmode != ModePGN)
+ return;
+
+ va_start(ap, fmt);
+ vprintf(fmt, ap);
+ va_end(ap);
+}
+
int
isvalidsquare(int x, int y)
{
@@ -92,13 +109,10 @@ squaretoxy(const char *s, int *x, int *y)
}
void
-highlightmove(int x1, int y1, int x2, int y2)
+highlightmove(int x, int y)
{
- if (isvalidsquare(x1, y1))
- highlight[y1][x1] = 1;
-
- if (isvalidsquare(x2, y2))
- highlight[y2][x2] = 1;
+ if (isvalidsquare(x, y))
+ highlight[y][x] = 1;
}
void
@@ -144,7 +158,7 @@ showboardfen(void)
} else {
putchar('-');
}
- printf(" %d %d", halfmove, movenumber);
+ printf(" %d %d\n", halfmove, movenumber);
}
void
@@ -160,13 +174,13 @@ showpiece_svg(int c)
case 'R': s = "<g fill=\"#fff\" fill-rule=\"evenodd\" stroke=\"#000\" …
case 'B': s = "<g fill=\"none\" fill-rule=\"evenodd\" stroke=\"#000\" …
case 'N': s = "<g fill=\"none\" fill-rule=\"evenodd\" stroke=\"#000\" …
- case 'P': s = "<path d=\"M22.5 9c-2.21 0-4 1.79-4 4 0 .89.29 1.71.78 2…
+ case 'P': s = "<use href=\"#pawn\" fill=\"#fff\"/>"; break;
case 'k': s = "<g fill=\"none\" fill-rule=\"evenodd\" stroke=\"#000\" …
case 'q': s = "<g fill-rule=\"evenodd\" stroke=\"#000\" stroke-width=\…
case 'r': s = "<g fill-rule=\"evenodd\" stroke=\"#000\" stroke-width=\…
case 'b': s = "<g fill=\"none\" fill-rule=\"evenodd\" stroke=\"#000\" …
case 'n': s = "<g fill=\"none\" fill-rule=\"evenodd\" stroke=\"#000\" …
- case 'p': s = "<path d=\"M22.5 9c-2.21 0-4 1.79-4 4 0 .89.29 1.71.78 2…
+ case 'p': s = "<use href=\"#pawn\" fill=\"#000\"/>"; break;
}
if (*s)
@@ -174,7 +188,7 @@ showpiece_svg(int c)
}
void
-showboard_svg(void)
+output_svg(void)
{
const int *color;
int ix, iy, x, y, piece;
@@ -184,9 +198,9 @@ showboard_svg(void)
"<svg width=\"360\" height=\"360\" viewBox=\"0 0 360 360\" xml…
"<rect fill=\"#fff\" stroke=\"#000\" x=\"0\" y=\"0\" width=\"3…
- fputs("<!-- Board FEN: ", stdout);
- showboardfen();
- fputs(" -->\n", stdout);
+ fputs("<defs>\n", stdout);
+ fputs("<path id=\"pawn\" d=\"M22.5 9c-2.21 0-4 1.79-4 4 0 .89.29 1.71.…
+ fputs("</defs>\n", stdout);
for (iy = 0; iy < 8; iy++) {
y = flipboard ? 7 - iy : iy;
@@ -284,15 +298,11 @@ showpiece_tty(int c)
/* show board */
void
-showboard_tty(void)
+output_tty(void)
{
const int *color;
int ix, iy, x, y, piece;
- printf("Board FEN:\n");
- showboardfen();
- printf("\n\n");
-
SETBGCOLOR(border[0], border[1], border[2]);
fputs(" ", stdout);
printf("\x1b[0m"); /* reset */
@@ -367,25 +377,20 @@ showpiece_ascii(int c)
/* OnlyFENs */
void
-showboard_fen(void)
+output_fen(void)
{
showboardfen();
- printf("\n");
}
/* show board */
void
-showboard_ascii(void)
+output_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;
@@ -426,6 +431,124 @@ showboard_ascii(void)
fputs("\n", stdout);
}
+int
+findking(int side, int *kingx, int *kingy)
+{
+ int king, x, y;
+
+ king = side == 'w' ? 'K' : 'k';
+ *kingx = -1;
+ *kingy = -1;
+
+ /* find king */
+ for (y = 0; y < 8; y++) {
+ for (x = 0; x < 8; x++) {
+ if (getpiece(x, y) == king) {
+ *kingx = x;
+ *kingy = y;
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+
+int
+ischecked(int side)
+{
+ int diag[] = { -1, -1, 1, 1, -1, 1, 1, -1 };
+ int line[] = { 1, 0, 0, 1, -1, 0, 0, -1 };
+ int knight[] = { -1, -2, 1, -2, -1, 2, 1, 2,
+ -2, -1, 2, -1, -2, 1, 2, 1
+ };
+ int i, j, x, y;
+ int kingx, kingy;
+ int piece;
+
+ /* find our king */
+ if (!findking(side, &kingx, &kingy))
+ return 0; /* should not happen */
+
+ /* check files and ranks (for queen and rook) */
+ for (j = 0; j < 8; j += 2) {
+ for (i = 1; i < 8; i ++) {
+ x = kingx + (i * line[j]);
+ y = kingy + (i * line[j + 1]);
+ if (!(piece = getpiece(x, y)))
+ continue;
+ /* a piece is in front of it */
+ if (piece && strchr("bBnNpP", piece))
+ break;
+ /* own piece blocking/defending it */
+ if ((side == 'w' && iswhitepiece(piece)) ||
+ (side == 'b' && isblackpiece(piece)))
+ break;
+ return 1;
+ }
+ }
+
+ /* check diagonals (queen and bishop) */
+ for (j = 0; j < 8; j += 2) {
+ for (i = 1; i < 8; i ++) {
+ x = kingx + (i * diag[j]);
+ y = kingy + (i * diag[j + 1]);
+ if (!(piece = getpiece(x, y)))
+ continue;
+ /* a piece is in front of it */
+ if (piece && strchr("rRnNpP", piece))
+ break;
+ /* own piece blocking/defending it */
+ if ((side == 'w' && iswhitepiece(piece)) ||
+ (side == 'b' && isblackpiece(piece)))
+ break;
+ return 1;
+ }
+ }
+
+ /* check knights */
+ piece = side == 'w' ? 'n' : 'N';
+ for (j = 0; j < 16; j += 2) {
+ x = kingx + knight[j];
+ y = kingy + knight[j + 1];
+// highlightmove(x, y); /* DEBUG */
+ if (getpiece(x, y) == piece)
+ return 1;
+ }
+
+ /* check pawns */
+ if (side == 'w') {
+ if (getpiece(kingx - 1, kingy - 1) == 'p' ||
+ getpiece(kingx + 1, kingy - 1) == 'p')
+ return 1;
+ } else if (side == 'b') {
+ if (getpiece(kingx - 1, kingy + 1) == 'P' ||
+ getpiece(kingx + 1, kingy + 1) == 'P')
+ return 1;
+ }
+
+ return 0;
+}
+
+int
+ischeckmated(int side)
+{
+ int kingx, kingy;
+
+ // TODO: can king move out check, without being checked?
+
+ if (!ischecked(side))
+ return 0;
+
+ /* find our king */
+ if (!findking(side, &kingx, &kingy))
+ return 0; /* should not happen */
+
+ // TODO: can the king move? move it and check if it is not checked
+ // TODO: separate board state or just move and undo move?
+
+ return 0; // TODO
+}
+
void
parsefen(const char *fen)
{
@@ -528,12 +651,13 @@ void
parsemoves(const char *moves)
{
char square[3];
- const char *s;
- int i, x, y, x2, y2, piece, takepiece;
+ const char *castled, *s;
+ int firstmove, i, x, y, x2, y2, piece, takepiece, tookpiece;
/* process moves */
square[2] = '\0';
x = y = x2 = y2 = -1;
+ firstmove = 1;
for (s = moves; *s; s++) {
if (*s == ' ')
@@ -542,6 +666,14 @@ parsemoves(const char *moves)
(*(s + 1) >= '1' && *(s + 1) <= '8') &&
(*(s + 2) >= 'a' && *(s + 2) <= 'h') &&
(*(s + 3) >= '1' && *(s + 3) <= '8')) {
+ /* TODO: if first move but its blacks turn, prefix PGN
+ with "..." or something, since the white move was u…
+
+ if (firstmove)
+ firstmove = 0;
+ else
+ pgn(" ");
+
square[0] = *s;
square[1] = *(s + 1);
@@ -561,14 +693,22 @@ parsemoves(const char *moves)
s += 2;
+ /* took piece of opponent */
+ tookpiece = (side_to_move == 'w' && isblackpiece(takep…
+ (side_to_move == 'b' && iswhitepiece(takep…
+
/* if pawn move or taken a piece increase halfmove cou…
- if (piece == 'p' || piece == 'P' ||
- (side_to_move == 'w' && isblackpiece(takepiece)) ||
- (side_to_move == 'b' && iswhitepiece(takepiece)))
+ if (piece == 'p' || piece == 'P' || tookpiece)
halfmove = 0;
else
halfmove++;
+ if (side_to_move == 'w')
+ pgn("%d. ", movenumber);
+
+ /* castled this move? for PGN */
+ castled = NULL;
+
/* castling */
if (piece == 'K' && y == 7 && y2 == 7) {
/* white: kingside castling */
@@ -577,6 +717,7 @@ parsemoves(const char *moves)
if (getpiece(i, y2) == 'R') {
place(0, i, y2); /* cl…
place('R', x2 - 1, y2)…
+ castled = "O-O";
break;
}
}
@@ -586,6 +727,7 @@ parsemoves(const char *moves)
if (getpiece(i, y2) == 'R') {
place('R', x2 + 1, y2)…
place(0, i, y2); /* cl…
+ castled = "O-O-O";
break;
}
}
@@ -597,6 +739,7 @@ parsemoves(const char *moves)
if (getpiece(i, y2) == 'r') {
place(0, i, y2); /* cl…
place('r', x2 - 1, y2)…
+ castled = "O-O";
break;
}
}
@@ -606,6 +749,7 @@ parsemoves(const char *moves)
if (getpiece(i, y2) == 'r') {
place('r', x2 + 1, y2)…
place(0, i, y2); /* cl…
+ castled = "O-O-O";
break;
}
}
@@ -664,15 +808,39 @@ parsemoves(const char *moves)
/* place piece */
place(piece, x2, y2);
- /* 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++;
+ /* PGN for move */
+ if (castled) {
+ pgn("%s", castled);
+ } else {
+ /* pawn move needs no notation */
+ if (piece != 'p' && piece != 'P') {
+ pgn("%c", toupper(piece));
+ /* TODO: check ambiguity for certain p…
+ pgn("%c%c", 'a' + x, '8' - y);
+ }
+ if (tookpiece) {
+ /* pawn captures are prefixed by the f…
+ if (piece == 'p' || piece == 'P')
+ pgn("%c", 'a' + x);
+ pgn("x");
+ }
+ pgn("%c%c", 'a' + x2, '8' - y2);
+
+ /* possible promotion: queen, knight, bishop */
+ if (*s == 'q' || *s == 'n' || *s == 'b') {
+ if (side_to_move == 'w')
+ piece = toupper((unsigned char…
+ else
+ piece = *s;
+ place(piece, x2, y2);
+ s++;
+ pgn("=%c", toupper(piece));
+ }
}
+ if (ischeckmated(side_to_move == 'b' ? 'w' : 'b'))
+ pgn("#");
+ else if (ischecked(side_to_move == 'b' ? 'w' : 'b'))
+ pgn("+");
/* a move by black increases the move number */
if (side_to_move == 'b')
@@ -682,14 +850,21 @@ parsemoves(const char *moves)
side_to_move = side_to_move == 'b' ? 'w' : 'b';
}
}
+ pgn("\n");
+
+// // DEBUG
+// ischecked('w');
+// ischecked('b');
/* highlight last move */
- highlightmove(x, y, x2, y2);
+ highlightmove(x, y);
+ highlightmove(x2, y2);
}
void
usage(char *argv0)
{
+// fprintf(stderr, "usage: %s [-cCfF] [-o ascii|fen|pgn|svg|tty] [FEN] …
fprintf(stderr, "usage: %s [-cCfF] [-o ascii|fen|svg|tty] [FEN] [moves…
exit(1);
}
@@ -697,7 +872,7 @@ usage(char *argv0)
int
main(int argc, char *argv[])
{
- const char *fen, *moves, *output = "svg";
+ const char *fen, *moves;
int i, j;
#ifdef __OpenBSD__
@@ -721,7 +896,19 @@ main(int argc, char *argv[])
case 'o':
if (i + 1 >= argc)
usage(argv[0]);
- output = argv[++i];
+ i++;
+ if (!strcmp(argv[i], "ascii"))
+ outputmode = ModeASCII;
+ else if (!strcmp(argv[i], "fen"))
+ outputmode = ModeFEN;
+// else if (!strcmp(argv[i], "pgn"))
+// outputmode = ModePGN;
+ else if (!strcmp(argv[i], "svg"))
+ outputmode = ModeSVG;
+ else if (!strcmp(argv[i], "tty"))
+ outputmode = ModeTTY;
+ else
+ usage(argv[0]);
goto next;
default:
usage(argv[0]);
@@ -746,16 +933,16 @@ next:
parsefen(fen);
parsemoves(moves);
- 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]);
+// outputmode = ModeTTY; /* DEBUG */
+
+ switch (outputmode) {
+ case ModeASCII: output_ascii(); break;
+ case ModeFEN: output_fen(); break;
+// case ModePGN: break; /* handled in parsemoves() */
+ case ModeSVG: output_svg(); break;
+ case ModeTTY: output_tty(); break;
+ default: usage(argv[0]); break;
+ }
return 0;
}
diff --git a/tests.sh b/tests.sh
@@ -18,6 +18,25 @@ testfen() {
fi
}
+# testpgn(name, expect, fen, moves)
+testpgn() {
+ name="$1"
+ expect="$2"
+ fen="$3"
+ moves="$4"
+
+ output=$(./fen -o pgn "$fen" "$moves")
+ if test "$output" = "$expect"; then
+ printf 'OK: %s\n' "$name"
+ else
+ printf 'Fail: %s\n' "$name"
+ printf '\texpected: %s\n' "$expect"
+ printf '\tgot: %s\n' "$output"
+ printf '\tInput FEN, moves: "%s" "%s"\n' "$fen" "$moves"
+ fi
+}
+
+tests_fen() {
testfen 'startpos'\
'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1' \
"startpos"\
@@ -197,3 +216,37 @@ testfen '960, castle king on queenside with many empty squ…
# TODO: test more chess960 black kingside and queenside castling
# TODO: test more long sequence and halfmove and movenumber counts
+}
+
+tests_pgn() {
+testpgn 'simple pawn move'\
+ 'e5'\
+ 'rnbqkbnr/ppp1pppp/8/3p4/3PP3/8/PPP2PPP/RNBQKBNR b KQkq - 0 2'\
+ 'e7e5'
+
+testpgn 'check: check with white pawn'\
+ '4. gxf7+'\
+ 'rnbqkbnr/pppp1ppp/6P1/8/8/4p3/PPPPPP1P/RNBQKBNR w KQkq - 0 4'\
+ 'g6f7'
+testpgn 'check: check with black pawn'\
+ 'exf2+'\
+ 'rnbqkbnr/pppp1ppP/8/8/8/4p3/PPPPPP1P/RNBQKBNR b KQkq - 0 4'\
+ 'e3f2'
+
+testpgn 'check: check with bishop'\
+ '3. Bf1b5+'\
+ 'rnbqkbnr/ppp2ppp/8/3pp3/3PP3/8/PPP2PPP/RNBQKBNR w KQkq - 0 3'\
+ 'f1b5'
+testpgn 'check: check with white queen, open file'\
+ '6. Qd4e4+'\
+ 'rnbqkbnr/1ppp1pp1/p5Pp/8/3Q4/4P3/PPP1PP1P/RNB1KBNR w KQkq - 0 6'\
+ 'd4e4'
+
+testpgn 'check: check with white knight'\
+ '8. Nd5xc7+'\
+ 'rnbqkbn1/pppp1ppr/6P1/3N4/7p/4PN2/PPP1PP1P/R1BQKB1R w KQq - 2 8'\
+ 'd5c7'
+}
+
+tests_fen
+#tests_pgn
You are viewing proxied material from codemadness.org. The copyright of proxied material belongs to its original authors. Any comments or complaints in relation to proxied material should be directed to the original authors of the content concerned. Please see the disclaimer for more details.