Introduction
Introduction Statistics Contact Development Disclaimer Help
various improvements - chess-puzzles - chess puzzle book generator
git clone git://git.codemadness.org/chess-puzzles
Log
Files
Refs
README
LICENSE
---
commit cd3acc17b7b82e112bd5abbc65324283a7c5bf7f
parent f1daa80c1d6cf199c57d820e117255f38bd3d55e
Author: Hiltjo Posthuma <[email protected]>
Date: Thu, 4 Jan 2024 16:44:29 +0100
various improvements
Some are:
* Colour themes.
* Initial PGN output.
* PGN piece localization.
* generate.sh: dutch localization.
* Highlight check or checkmate in red.
* Add a struct board and localize states.
* Add a CGI mode.
* Lichess example script, show more information about the players.
* Add dark mode CSS.
* Add a script to generate animated gifs for solutions.
* Documentation improvements.
* Many bugfixes.
Diffstat:
M LICENSE | 2 +-
M TODO | 15 +++++++++------
M docs/stream_lichess.sh | 49 +++++++++++++++++++++++------…
M fen.1 | 41 +++++++++++++++++++++++++----…
M fen.c | 1120 +++++++++++++++++++++--------…
M generate.sh | 119 +++++++++++++++++++++++------…
A gifs.sh | 50 +++++++++++++++++++++++++++++…
M tests.sh | 56 +++++++++++++++++++++++++++--…
8 files changed, 1047 insertions(+), 405 deletions(-)
---
diff --git a/LICENSE b/LICENSE
@@ -1,6 +1,6 @@
ISC License
-Copyright (c) 2023 Hiltjo Posthuma <[email protected]>
+Copyright (c) 2023-2024 Hiltjo Posthuma <[email protected]>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
diff --git a/TODO b/TODO
@@ -1,3 +1,8 @@
+? canpiecemove(): en passant take (if not in check afterwards).
+? ischeckmated(): check en passant take to defend checkmate.
+
+- rename fen.c to be more unique so it can be installed in $PATH.
+
- option for output for annotating moves in a human-like way (for screenreader…
https://en.wikipedia.org/wiki/Portable_Game_Notation
PGN:
@@ -7,11 +12,9 @@ if so, the piece's file letter, numerical rank, or the exact …
after the moving piece's name (in that order of preference). Thus, Nge2
specifies that the knight originally on the g-file moves to e2. "
-- rename fen.c to be more unique so it can be installed in $PATH.
-
-? output for PGN notation for moves:
- - mainly for solutions page.
- - maybe add some option to show it in tty/ASCII mode.
-
+- add a format" parameter for the CGI mode: vt, pgn, svg, ascii.
- improve documentation.
- more tests.
+ - piece ambiguity.
+ - en passant (in check), etc.
+ - in check, checkmate.
diff --git a/docs/stream_lichess.sh b/docs/stream_lichess.sh
@@ -10,16 +10,15 @@ if [ "$1" = "" ]; then
fi
gameid="$1"
-token="" # API token here.
+token="${LICHESS_TOKEN}" # API token here.
url="https://lichess.org/api/board/game/stream/$gameid"
# start position of classical chess.
fen="rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
-white="Anonymous"
-black="Anonymous"
firstline=1
+header=""
# -N disables cURL buffering, each line is streamed as JSON.
curl \
@@ -30,22 +29,46 @@ curl \
-H 'Accept: application/x-ndjson' "$url" | \
while read -r json; do
if [ "$firstline" = "1" ]; then
+ firstline="0"
+
str=$(printf '%s' "$json" | jaq '$1 == ".initialFen" { print $…
test "$str" != "" && fen="$str" # override
- str=$(printf '%s' "$json" | jaq '$1 == ".white.name" { print $…
- test "$str" != "" && white="$str" # override
-
- str=$(printf '%s' "$json" | jaq '$1 == ".black.name" { print $…
- test "$str" != "" && black="$str" # override
-
- firstline="0"
+ header=$(printf '%s' "$json" | jaq '
+BEGIN {
+ white_name = "Anonymous";
+ black_name = "Anonymous";
+}
+$2 == "?" || $3 == "" { next; }
+$1 == ".white.name" { white_name = $3; }
+$1 == ".white.rating" { white_rating = $3; }
+$1 == ".white.provisional" && $3 == "true" { white_provisional = "?"; }
+$1 == ".black.name" { black_name = $3; }
+$1 == ".black.rating" { black_rating = $3; }
+$1 == ".black.provisional" && $3 == "true" { black_provisional = "?"; }
+$1 == ".white.aiLevel" { white_name = "AI level " $3; }
+$1 == ".black.aiLevel" { black_name = "AI level " $3; }
+END {
+ white = white_name;
+ if (white_rating != "")
+ white = white " (" white_rating white_provisional ")";
+ black = black_name;
+ if (black_rating != "")
+ black = black " (" black_rating black_provisional ")";
+ print white " vs " black;
+}')
fi
- moves=$(printf '%s' "$json" | jaq '$1 == ".moves" { print $3; }')
- test "$moves" = "" && continue
+ str=$(printf '%s' "$json" | jaq '$1 == ".moves" { print $3; }')
+ test "$str" != "" && moves="$str" # override
clear
- printf '%s vs %s\n\n' "$white" "$black"
+ printf '%s\n\n' "$header"
./fen -o tty "$fen" "$moves"
+
+ printf '\nMoves:\n'
+ printf '%s\n' "$moves"
+
+ printf '\nPGN:\n'
+ ./fen -o pgn "$fen" "$moves"
done
diff --git a/fen.1 b/fen.1
@@ -1,20 +1,23 @@
-.Dd December 21, 2023
+.Dd January 4, 2024
.Dt FEN 1
.Os
.Sh NAME
.Nm fen
-.Nd parses FEN and some moves and writes output
+.Nd parses chess FEN, plays moves and writes output
.Sh SYNOPSIS
.Nm
.Op Fl cCfF
-.\" .Op Fl o Ar ascii | fen | pgn | svg | tty
-.Op Fl o Ar ascii | fen | svg | tty
+.Op Fl m mapping
+.Op Fl o Ar ascii | fen | pgn | svg | tty
+.Op Fl t theme
.Op Ar FEN
.Op Ar moves
.Sh DESCRIPTION
.Nm
-parses the Forsyth-Edwards Notation (FEN) and some moves and write the output
-to a chosen format.
+parses the Forsyth-Edwards Notation (FEN) to setup the chess board.
+It then plays some
+.Ar moves
+and writes the output to a chosen format.
The options are as follows:
.Bl -tag -width Ds
.It Fl c
@@ -25,6 +28,11 @@ Disable board coordinates.
Flip the board, default is off.
.It Fl F
Do not flip the board.
+.It Fl m Ar mapping
+Specify a mapping to remap the piece letters to a localized PGN format.
+For example for dutch: (K)oning, (D)ame, (T)oren, (L)oper, (P)aard it could be:
+"KDTLP".
+The default is: "KQRBN".
.It Fl o Ar format
Output format to one of the following format:
.Bl -tag -width Ds
@@ -33,8 +41,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 pgn
+PGN output of the moves for the board.
.It svg
SVG image of the board.
.It tty
@@ -42,6 +50,19 @@ 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.
.El
+.It Fl t Ar theme
+Use a colour theme for certain output formats, supported are the names: brown
+(default), green, grey.
+.El
+.Sh ENVIRONMENT VARIABLES
+.Bl -tag -width Ds
+.It Ev QUERY_STRING
+If this option is set
+.Nm
+will run in "CGI" mode suitable for a web server / HTTP daemon.
+This accepts the parameters: fen, moves, flip, coords and theme, similar to the
+command-line flags.
+It will serve a SVG of the chess board and moves.
.El
.Sh EXIT STATUS
.Ex -std
@@ -57,3 +78,7 @@ fen startpos e2e4 > board.svg
.Nm
supports classical chess and chess960 only.
Input moves are not validated, they are assumed to be legal.
+.Sh BUGS
+I hope it covers the case of taking en passant during a blood moon while a leap
+second ellapses.
+If it does not, please report it.
diff --git a/fen.c b/fen.c
@@ -15,28 +15,115 @@
#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 { ModeASCII = 0, ModeCGI, ModeFEN, ModePGN, ModeTTY, ModeSVG };
enum outputmode outputmode = ModeSVG;
-static char board[8][8];
-static char highlight[8][8];
+/* localization of letter for PGN pieces */
+const char *pgn_piecemapping = "";
+
+typedef unsigned char Color;
+
+struct theme {
+ const char *name;
+ /* RGB */
+ Color border[3];
+ Color darksquare[3];
+ Color lightsquare[3];
+ Color darksquarehi[3];
+ Color lightsquarehi[3];
+ Color lightsquarecheck[3];
+ Color darksquarecheck[3];
+};
+
+struct theme themes[] = {
+/* lichess default brown theme colors (red, green, blue) */
+{
+ .name = "default",
+ .border = { 0x70, 0x49, 0x2d },
+ .darksquare = { 0xb5, 0x88, 0x63 },
+ .lightsquare = { 0xf0, 0xd9, 0xb5 },
+ .darksquarehi = { 0xaa, 0xa2, 0x3a },
+ .lightsquarehi = { 0xcd, 0xd2, 0x6a },
+ .lightsquarecheck = { 0xff, 0x6a, 0x6a },
+ .darksquarecheck = { 0xff, 0x3a, 0x3a }
+},
+/* lichess green theme */
+{
+ .name = "green",
+ .border = { 0x33, 0x33, 0x33 },
+ .darksquare = { 0x86, 0xa6, 0x66 },
+ .lightsquare = { 0xff, 0xff, 0xdd },
+ .darksquarehi = { 0x4f, 0xa1, 0x8e },
+ .lightsquarehi = { 0x96, 0xd6, 0xd4 },
+ .lightsquarecheck = { 0xff, 0x6a, 0x6a },
+ .darksquarecheck = { 0xff, 0x3a, 0x3a }
+},
+/* greyscale theme, highlight is still green */
+{
+ .name = "grey",
+ .border = { 0x00, 0x00, 0x00 },
+ .darksquare = { 0x66, 0x66, 0x66 },
+ .lightsquare = { 0xaa, 0xaa, 0xaa },
+ .darksquarehi = { 0x66, 0x61, 0x23 },
+ .lightsquarehi = { 0xa8, 0xab, 0x55 },
+ .lightsquarecheck = { 0xff, 0x6a, 0x6a },
+ .darksquarecheck = { 0xff, 0x3a, 0x3a }
+}
+};
+
+struct board {
+ char tiles[8][8]; /* board tiles and piece placement */
+ int enpassantsquare[2]; /* default: no: { -1, -1 } */
+
+ char highlight[8][8]; /* highlighted squares, (0 = none, 1 = highli…
+
+ int side_to_move; /* default: white to move: 'w' */
+ int white_can_castle[2]; /* allow king side, allow queen side? default…
+ int black_can_castle[2]; /* allow king side, allow queen side? default…
+
+ int movenumber; /* default: 1 */
+ int halfmove; /* default: 0 */
-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 int enpassantsquare[2] = { -1, -1 };
-static int movenumber = 1;
-static int halfmove = 0;
+ int flipboard; /* flip board ? default: 0 */
+ int showcoords; /* board coordinates? default: 1 */
-/* lichess default theme colors */
-static const int border[] = { 0x70, 0x49, 0x2d };
-static const int darksquare[] = { 0xb5, 0x88, 0x63 };
-static const int lightsquare[] = { 0xf0, 0xd9, 0xb5 };
-static const int darksquarehi[] = { 0xaa, 0xa2, 0x3a };
-static const int lightsquarehi[] = { 0xcd, 0xd2, 0x6a };
+ /* board theme */
+ struct theme *theme;
+};
-static int showcoords = 1; /* config: show board coordinates? */
-static int flipboard = 0; /* config: flip board ? */
+struct theme *
+board_set_theme(struct board *b, const char *name)
+{
+ int i;
+
+ /* lookup theme by name */
+ for (i = 0; i < LEN(themes); i++) {
+ if (!strcmp(themes[i].name, name)) {
+ b->theme = &themes[i];
+ return b->theme;
+ }
+ }
+ return NULL;
+}
+
+void
+board_init(struct board *b)
+{
+ memset(b, 0, sizeof(*b)); /* zeroed fields by default */
+ b->side_to_move = 'w';
+ b->enpassantsquare[0] = -1; /* no en passant */
+ b->enpassantsquare[1] = -1;
+ b->movenumber = 1;
+ b->flipboard = 0;
+ b->showcoords = 1;
+ b->theme = &themes[0]; /* use first theme as default */
+}
+
+void
+board_copy(struct board *bd, struct board *bs)
+{
+ memcpy(bd, bs, sizeof(*bd));
+}
void
pgn(const char *fmt, ...)
@@ -51,6 +138,28 @@ pgn(const char *fmt, ...)
va_end(ap);
}
+/* remap letter for PGN pieces, default: "KQRBN"
+ Dutch: (K)oning, (D)ame, (T)oren, (L)oper, (P)aard: "KDTLP" */
+int
+pgnpiece(int piece)
+{
+ piece = toupper(piece);
+
+ /* no mapping */
+ if (!pgn_piecemapping[0])
+ return piece;
+
+ switch (piece) {
+ case 'K': piece = pgn_piecemapping[0]; break;
+ case 'Q': piece = pgn_piecemapping[1]; break;
+ case 'R': piece = pgn_piecemapping[2]; break;
+ case 'B': piece = pgn_piecemapping[3]; break;
+ case 'N': piece = pgn_piecemapping[4]; break;
+ }
+
+ return piece;
+}
+
int
isvalidsquare(int x, int y)
{
@@ -79,25 +188,6 @@ isvalidpiece(int c)
return strchr(pieces, c) ? 1 : 0;
}
-/* 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
xtofile(int c)
{
@@ -134,24 +224,72 @@ squaretoxy(const char *s, int *x, int *y)
return 0;
}
+/* place a piece, if possible */
void
-highlightmove(int x, int y)
+place(struct board *b, int piece, int x, int y)
+{
+ if (!isvalidsquare(x, y))
+ return;
+
+ b->tiles[y][x] = piece;
+}
+
+/* get piece, if possible */
+int
+getpiece(struct board *b, int x, int y)
+{
+ if (!isvalidsquare(x, y))
+ return 0;
+ return b->tiles[y][x];
+}
+
+void
+highlightmove(struct board *b, int x, int y)
{
if (isvalidsquare(x, y))
- highlight[y][x] = 1;
+ b->highlight[y][x] = 1;
}
void
-showboardfen(void)
+highlightcheck(struct board *b, int x, int y)
{
- int x, y, piece, skip = 0;
+ if (isvalidsquare(x, y))
+ b->highlight[y][x] = 2;
+}
+
+Color *
+getsquarecolor(struct board *b, int x, int y, int invert)
+{
+ struct theme *t;
+
+ t = b->theme;
+ if (((x % 2) ^ (y % 2)) == invert) {
+ switch (b->highlight[y][x]) {
+ case 1: return t->lightsquarehi;
+ case 2: return t->lightsquarecheck;
+ default: return t->lightsquare;
+ }
+ } else {
+ switch (b->highlight[y][x]) {
+ case 1: return t->darksquarehi;
+ case 2: return t->darksquarecheck;
+ default: return t->darksquare;
+ }
+ }
+ return t->lightsquare; /* never happens */
+}
+
+void
+showboardfen(struct board *b)
+{
+ int x, y, piece, skip;
for (y = 0; y < 8; y++) {
if (y > 0)
putchar('/');
skip = 0;
for (x = 0; x < 8; x++) {
- piece = getpiece(x, y);
+ piece = getpiece(b, x, y);
if (piece) {
if (skip)
putchar(skip + '0');
@@ -164,27 +302,27 @@ showboardfen(void)
if (skip)
putchar(skip + '0');
}
- printf(" %c ", side_to_move);
- if (white_can_castle[0])
+ printf(" %c ", b->side_to_move);
+ if (b->white_can_castle[0])
putchar('K');
- if (white_can_castle[1])
+ if (b->white_can_castle[1])
putchar('Q');
- if (black_can_castle[0])
+ if (b->black_can_castle[0])
putchar('k');
- if (black_can_castle[1])
+ if (b->black_can_castle[1])
putchar('q');
- if ((white_can_castle[0] + white_can_castle[1] +
- black_can_castle[0] + black_can_castle[1]) == 0)
+ if ((b->white_can_castle[0] + b->white_can_castle[1] +
+ b->black_can_castle[0] + b->black_can_castle[1]) == 0)
putchar('-'); /* no castling for either side */
putchar(' ');
- if (enpassantsquare[0] != -1 && enpassantsquare[1] != -1) {
- putchar(xtofile(enpassantsquare[0]));
- putchar(ytorank(enpassantsquare[1]));
+ if (b->enpassantsquare[0] != -1 && b->enpassantsquare[1] != -1) {
+ putchar(xtofile(b->enpassantsquare[0]));
+ putchar(ytorank(b->enpassantsquare[1]));
} else {
putchar('-');
}
- printf(" %d %d\n", halfmove, movenumber);
+ printf(" %d %d\n", b->halfmove, b->movenumber);
}
void
@@ -214,12 +352,12 @@ showpiece_svg(int c)
}
void
-output_svg(void)
+output_svg(struct board *b)
{
+ Color *color;
const char *s;
char pieces[] = "pPKQRBNkqrbn"; /* pieces, check if they are used for …
unsigned char pieceused[LEN("pPKQRBNkqrbn")] = { 0 };
- const int *color;
int i, ix, iy, x, y, piece;
fputs("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n"
@@ -230,7 +368,7 @@ output_svg(void)
for (i = 0; i < LEN(pieces); i++) {
for (y = 0; y < 8 && !pieceused[i]; y++) {
for (x = 0; x < 8; x++) {
- if (getpiece(x, y) == pieces[i]) {
+ if (getpiece(b, x, y) == pieces[i]) {
pieceused[i] = 1;
break;
}
@@ -267,20 +405,16 @@ output_svg(void)
fputs("</defs>\n", stdout);
for (iy = 0; iy < 8; iy++) {
- y = flipboard ? 7 - iy : iy;
+ y = b->flipboard ? 7 - iy : iy;
for (ix = 0; ix < 8; ix++) {
- x = flipboard ? 7 - ix : ix;
-
- if (((x % 2) ^ (y % 2)) == 0)
- color = highlight[y][x] ? lightsquarehi : ligh…
- else
- color = highlight[y][x] ? darksquarehi : darks…
+ x = b->flipboard ? 7 - ix : ix;
+ color = getsquarecolor(b, x, y, 0);
printf("<g><rect x=\"%d\" y=\"%d\" width=\"45\" height…
ix * 45, iy * 45, color[0], color[1], color[2]…
- piece = getpiece(x, y);
+ piece = getpiece(b, x, y);
if (piece) {
printf("<g transform=\"translate(%d %d)\">", i…
showpiece_svg(piece);
@@ -289,38 +423,23 @@ output_svg(void)
}
}
- if (showcoords) {
+ if (b->showcoords) {
ix = 7;
- x = flipboard ? 0 : 7;
+ x = b->flipboard ? 0 : 7;
for (iy = 0; iy < 8; iy++) {
- y = flipboard ? 7 - iy : iy;
-
+ y = b->flipboard ? 7 - iy : iy;
/* inverse square color for text */
- if (((x % 2) ^ (y % 2)) == 0)
- color = highlight[y][x] ? darksquarehi : darks…
- else
- color = highlight[y][x] ? lightsquarehi : ligh…
+ color = getsquarecolor(b, x, y, 1);
printf("<text x=\"%d\" y=\"%d\" fill=\"#%02x%02x%02x\"…
(ix + 1) * 45 - 2, (iy * 45) + 10, color[0], c…
}
iy = 7;
- y = flipboard ? 0 : 7;
+ y = b->flipboard ? 0 : 7;
for (ix = 0; ix < 8; ix++) {
- x = flipboard ? 7 - ix : ix;
-
+ x = b->flipboard ? 7 - ix : ix;
/* inverse square color for text */
- if (x % 2 == 0) {
- if (y % 2 == 0)
- color = highlight[y][x] ? darksquarehi…
- else
- color = highlight[y][x] ? lightsquareh…
- } else {
- if (y % 2 == 0)
- color = highlight[y][x] ? lightsquareh…
- else
- color = highlight[y][x] ? darksquarehi…
- }
+ color = getsquarecolor(b, x, y, 1);
printf("<text x=\"%d\" y=\"%d\" fill=\"#%02x%02x%02x\"…
(ix * 45) + 2, (iy + 1) * 45 - 3, color[0], co…
@@ -335,12 +454,7 @@ showpiece_tty(int c)
{
const char *s = "";
- /* simple or use unicode character */
-#if 0
- putchar(c);
- return;
-#endif
-
+ /* unicode characters */
switch (c) {
case 'K': s = "â™”"; break;
case 'Q': s = "♕"; break;
@@ -362,34 +476,33 @@ showpiece_tty(int c)
/* show board */
void
-output_tty(void)
+output_tty(struct board *b)
{
- const int *color;
+ struct theme *t;
+ Color *color;
int ix, iy, x, y, piece;
- SETBGCOLOR(border[0], border[1], border[2]);
+ t = b->theme;
+
+ SETBGCOLOR(t->border[0], t->border[1], t->border[2]);
fputs(" ", stdout);
printf("\x1b[0m"); /* reset */
putchar('\n');
for (iy = 0; iy < 8; iy++) {
- y = flipboard ? 7 - iy : iy;
+ y = b->flipboard ? 7 - iy : iy;
- SETBGCOLOR(border[0], border[1], border[2]);
+ SETBGCOLOR(t->border[0], t->border[1], t->border[2]);
fputs("\x1b[97m", stdout); /* bright white */
fputs(" ", stdout);
for (ix = 0; ix < 8; ix++) {
- x = flipboard ? 7 - ix : ix;
-
- if (((x % 2) ^ (y % 2)) == 0)
- color = highlight[y][x] ? lightsquarehi : ligh…
- else
- color = highlight[y][x] ? darksquarehi : darks…
+ x = b->flipboard ? 7 - ix : ix;
+ color = getsquarecolor(b, x, y, 0);
SETBGCOLOR(color[0], color[1], color[2]);
fputs(" ", stdout);
- piece = getpiece(x, y);
+ piece = getpiece(b, x, y);
if (piece) {
if (piece >= 'A' && piece <= 'Z')
fputs("\x1b[97m", stdout); /* bright w…
@@ -405,9 +518,9 @@ output_tty(void)
}
printf("\x1b[0m"); /* reset */
- color = border;
+ color = t->border;
SETBGCOLOR(color[0], color[1], color[2]);
- if (showcoords) {
+ if (b->showcoords) {
fputs("\x1b[97m", stdout); /* bright white */
putchar(ytorank(y));
putchar(' ');
@@ -418,14 +531,17 @@ output_tty(void)
printf("\x1b[0m"); /* reset */
putchar('\n');
}
- color = border;
+ color = t->border;
SETBGCOLOR(color[0], color[1], color[2]);
fputs("\x1b[97m", stdout); /* bright white */
- if (showcoords) {
- if (flipboard)
- fputs(" h g f e d c b a ", stdout);
- else
- fputs(" a b c d e f g h ", stdout);
+ if (b->showcoords) {
+ fputs(" ", stdout);
+ for (iy = 0; iy < 8; iy++) {
+ y = b->flipboard ? 7 - iy : iy;
+ putchar(xtofile(y));
+ fputs(" ", stdout);
+ }
+ fputs(" ", stdout);
} else {
fputs(" ", stdout);
}
@@ -441,36 +557,37 @@ showpiece_ascii(int c)
/* OnlyFENs */
void
-output_fen(void)
+output_fen(struct board *b)
{
- showboardfen();
+ showboardfen(b);
}
/* show board */
void
-output_ascii(void)
+output_ascii(struct board *b)
{
- int hi[3] = { '>', ' ', '<' };
- int dark[3] = { '.', '.', '.' };
- int light[3] = { ' ', ' ', ' ' };
- int *color, ix, iy, x, y, piece;
+ unsigned char hi[3] = { '>', ' ', '<' };
+ unsigned char dark[3] = { '.', '.', '.' };
+ unsigned char light[3] = { ' ', ' ', ' ' };
+ unsigned char *color;
+ int ix, iy, x, y, piece;
for (iy = 0; iy < 8; iy++) {
- y = flipboard ? 7 - iy : iy;
+ y = b->flipboard ? 7 - iy : iy;
fputs("+---+---+---+---+---+---+---+---+\n", stdout);
for (ix = 0; ix < 8; ix++) {
- x = flipboard ? 7 - ix : ix;
+ x = b->flipboard ? 7 - ix : ix;
if (((x % 2) ^ (y % 2)) == 0)
- color = highlight[y][x] ? hi : light;
+ color = b->highlight[y][x] ? hi : light;
else
- color = highlight[y][x] ? hi : dark;
+ color = b->highlight[y][x] ? hi : dark;
if (ix == 0)
putchar('|');
putchar(color[0]);
- piece = getpiece(x, y);
+ piece = getpiece(b, x, y);
if (piece)
showpiece_ascii(piece);
else
@@ -478,25 +595,29 @@ output_ascii(void)
putchar(color[2]);
putchar('|');
}
- if (showcoords) {
+ if (b->showcoords) {
putchar(' ');
putchar(ytorank(y));
}
putchar('\n');
}
fputs("+---+---+---+---+---+---+---+---+\n", stdout);
- if (showcoords) {
- if (flipboard)
- printf(" h | g | f | e | d | c | b | a |\n");
- else
- printf(" a | b | c | d | e | f | g | h |\n");
+ if (b->showcoords) {
+ fputs(" ", stdout);
+ for (iy = 0; iy < 8; iy++) {
+ if (iy)
+ fputs(" | ", stdout);
+ y = b->flipboard ? 7 - iy : iy;
+ putchar(xtofile(y));
+ }
+ fputs(" |\n", stdout);
}
fputs("\n", stdout);
}
int
-findking(int side, int *kingx, int *kingy)
+findking(struct board *b, int side, int *kingx, int *kingy)
{
int king, x, y;
@@ -507,7 +628,7 @@ findking(int side, int *kingx, int *kingy)
/* find king */
for (y = 0; y < 8; y++) {
for (x = 0; x < 8; x++) {
- if (getpiece(x, y) == king) {
+ if (getpiece(b, x, y) == king) {
*kingx = x;
*kingy = y;
return 1;
@@ -518,8 +639,9 @@ findking(int side, int *kingx, int *kingy)
}
int
-ischecked(int side)
+isincheck(struct board *b, int side)
{
+ int king[] = { -1, -1, -1, 0, -1, 1, 0, -1, 0, 1, 1, -1, 1, 0, 1, 1 …
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,
@@ -530,18 +652,27 @@ ischecked(int side)
int piece;
/* find our king */
- if (!findking(side, &kingx, &kingy))
+ if (!findking(b, side, &kingx, &kingy))
return 0; /* should not happen */
+ /* check kings (illegal) */
+ for (j = 0; j < LEN(king); j += 2) {
+ x = kingx + king[j];
+ y = kingy + king[j + 1];
+ piece = getpiece(b, x, y);
+ if (piece && strchr("kK", piece))
+ return 1;
+ }
+
/* check files and ranks (for queen and rook) */
for (j = 0; j < LEN(line); j += 2) {
- for (i = 1; i < 8; i ++) {
+ for (i = 1; i < 8; i++) {
x = kingx + (i * line[j]);
y = kingy + (i * line[j + 1]);
- if (!(piece = getpiece(x, y)))
+ if (!(piece = getpiece(b, x, y)))
continue;
/* a piece is in front of it */
- if (piece && strchr("bBnNpP", piece))
+ if (piece && strchr("kKbBnNpP", piece))
break;
/* own piece blocking/defending it */
if ((side == 'w' && iswhitepiece(piece)) ||
@@ -556,10 +687,10 @@ ischecked(int side)
for (i = 1; i < 8; i++) {
x = kingx + (i * diag[j]);
y = kingy + (i * diag[j + 1]);
- if (!(piece = getpiece(x, y)))
+ if (!(piece = getpiece(b, x, y)))
continue;
/* a piece is in front of it */
- if (piece && strchr("rRnNpP", piece))
+ if (piece && strchr("kKrRnNpP", piece))
break;
/* own piece blocking/defending it */
if ((side == 'w' && iswhitepiece(piece)) ||
@@ -574,55 +705,189 @@ ischecked(int side)
for (j = 0; j < LEN(knight); j += 2) {
x = kingx + knight[j];
y = kingy + knight[j + 1];
-// highlightmove(x, y); /* DEBUG */
- if (getpiece(x, y) == piece)
+ if (getpiece(b, x, y) == piece)
return 1;
}
/* check pawns */
if (side == 'w') {
- if (getpiece(kingx - 1, kingy - 1) == 'p' ||
- getpiece(kingx + 1, kingy - 1) == 'p')
+ if (getpiece(b, kingx - 1, kingy - 1) == 'p' ||
+ getpiece(b, kingx + 1, kingy - 1) == 'p')
return 1;
} else if (side == 'b') {
- if (getpiece(kingx - 1, kingy + 1) == 'P' ||
- getpiece(kingx + 1, kingy + 1) == 'P')
+ if (getpiece(b, kingx - 1, kingy + 1) == 'P' ||
+ getpiece(b, kingx + 1, kingy + 1) == 'P')
return 1;
}
return 0;
}
-/* TODO */
int
-ischeckmated(int side)
+trypiecemove(struct board *b, int side, int piece, int x1, int y1, int x2, int…
{
- int kingx, kingy;
+ struct board tb;
+
+ board_copy(&tb, b);
+ place(&tb, 0, x1, y1);
+ place(&tb, piece, x2, y2);
+
+ /* would put in check / still in check, not allowed */
+ return !isincheck(&tb, side);
+}
+
+/* can piece move from (x, y), to (x, y)?
+ en passant square if any is (px, py), otherwise (-1, -1) */
+int
+canpiecemove(struct board *b, int side, int x1, int y1, int x2, int y2, int px…
+{
+ int king[] = { -1, -1, -1, 0, -1, 1, 0, -1, 0, 1, 1, -1, 1, 0, 1, 1 …
+ 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, dir, x, y;
+ int piece, takepiece;
- // TODO: can king move out check, without being checked?
- // TODO: can a piece defend it.
- // TODO: can the checking piece get taken without still being checked?
- // TODO: handle double-checks.
- // TODO: separate board state or just move and undo move?
+ if (!(piece = getpiece(b, x1, y1)))
+ return 0; /* theres no piece so it cannot be moved */
- if (!ischecked(side))
+ /* can't move opponent piece */
+ if ((side == 'w' && isblackpiece(piece)) ||
+ (side == 'b' && iswhitepiece(piece)))
return 0;
- /* find our king */
- if (!findking(side, &kingx, &kingy))
- return 0; /* should not happen */
+ if ((takepiece = getpiece(b, x2, y2))) {
+ /* can't take your own piece */
+ if ((side == 'w' && iswhitepiece(takepiece)) ||
+ (side == 'b' && isblackpiece(takepiece)))
+ return 0;
+ }
+
+ /* king movement */
+ if (piece == 'K' || piece == 'k') {
+ for (j = 0; j < LEN(king); j += 2) {
+ if (x1 + king[j] == x2 && y1 + king[j + 1] == y2)
+ goto trymove;
+ }
+ }
+
+ /* check files and ranks (for queen and rook) */
+ if (piece == 'Q' || piece == 'q' || piece == 'R' || piece == 'r') {
+ for (j = 0; j < LEN(line); j += 2) {
+ for (i = 1; i < 8; i++) {
+ x = x1 + (i * line[j]);
+ y = y1 + (i * line[j + 1]);
+
+ if (x == x2 && y == y2 &&
+ trypiecemove(b, side, piece, x1, y1, x2, y…
+ return 1;
+
+ /* a piece is in front of it: stop this checki…
+ if (getpiece(b, x, y))
+ break;
+ }
+ }
+ }
+
+ /* check diagonals (queen and bishop) */
+ if (piece == 'Q' || piece == 'q' || piece == 'B' || piece == 'b') {
+ for (j = 0; j < LEN(diag); j += 2) {
+ for (i = 1; i < 8; i++) {
+ x = x1 + (i * diag[j]);
+ y = y1 + (i * diag[j + 1]);
+ if (x == x2 && y == y2 &&
+ trypiecemove(b, side, piece, x1, y1, x2, y…
+ return 1;
+
+ /* a piece is in front of it: stop this checki…
+ if (getpiece(b, x, y))
+ break;
+ }
+ }
+ }
+
+ /* knight movement */
+ if (piece == 'N' || piece == 'n') {
+ for (j = 0; j < LEN(knight); j += 2) {
+ if (x1 + knight[j] == x2 && y1 + knight[j + 1] == y2)
+ goto trymove;
+ }
+ }
+ /* pawn move */
+ if (piece == 'P' || piece == 'p') {
+ /* direction */
+ dir = piece == 'P' ? -1 : +1;
+ j = piece == 'P' ? 6 : 1; /* start row */
+
+ if (takepiece == 0) {
+ if (x1 != x2)
+ return 0; /* move on same file */
+ /* start move: can be 2 moves */
+ if (y1 == j && y2 == y1 + (2 * dir)) {
+ /* square must be empty */
+ if (getpiece(b, x1, y1 + dir))
+ return 0;
+ goto trymove;
+ }
+ /* normal: check for one move */
+ if (y2 != y1 + dir)
+ return 0;
+ goto trymove;
+ } else {
+ /* pawn takes, normal case */
+ if ((x2 == x1 - 1 || x2 == x1 + 1) &&
+ (y2 == y1 + dir))
+ goto trymove;
+ }
+ }
return 0;
+
+/* previous checks for move succeeded, actually try move with the current
+ board state */
+trymove:
+ return trypiecemove(b, side, piece, x1, y1, x2, y2);
+}
+
+int
+ischeckmated(struct board *b, int side)
+{
+ int x, y, x2, y2, piece;
+
+ /* check pieces that can block or take a piece that removes the check …
+ for (y = 0; y < 8; y++) {
+ for (x = 0; x < 8; x++) {
+ piece = getpiece(b, x, y);
+ if ((side == 'w' && !iswhitepiece(piece)) ||
+ (side == 'b' && !isblackpiece(piece)))
+ continue;
+
+ for (y2 = 0; y2 < 8; y2++) {
+ for (x2 = 0; x2 < 8; x2++) {
+ /* can piece move and afterwards we ar…
+ if (canpiecemove(b, side, x, y, x2, y2…
+ return 0;
+ }
+ }
+ }
+ }
+
+ return 1;
}
void
-parsefen(const char *fen)
+board_setup_fen(struct board *b, const char *fen)
{
char square[3];
const char *s;
long l;
int x, y, field;
+ if (!strcmp(fen, "startpos"))
+ fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 …
+
square[2] = '\0';
/* initial board state, FEN format */
@@ -643,26 +908,26 @@ parsefen(const char *fen)
}
/* is piece? place it */
if (isvalidpiece(*s))
- place(*s, x++, y);
+ place(b, *s, x++, y);
break;
case 1: /* active color */
if (*s == 'w' || *s == 'b')
- side_to_move = *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;
+ b->white_can_castle[0] = 0;
+ b->white_can_castle[1] = 0;
+ b->black_can_castle[0] = 0;
+ b->black_can_castle[1] = 0;
} else if (*s == 'K') {
- white_can_castle[0] = 1;
+ b->white_can_castle[0] = 1;
} else if (*s == 'Q') {
- white_can_castle[1] = 1;
+ b->white_can_castle[1] = 1;
} else if (*s == 'k') {
- black_can_castle[0] = 1;
+ b->black_can_castle[0] = 1;
} else if (*s == 'q') {
- black_can_castle[1] = 1;
+ b->black_can_castle[1] = 1;
}
break;
case 3: /* en passant square */
@@ -672,8 +937,8 @@ parsefen(const char *fen)
square[1] = *(s + 1);
squaretoxy(square, &x, &y);
- enpassantsquare[0] = x;
- enpassantsquare[1] = y;
+ b->enpassantsquare[0] = x;
+ b->enpassantsquare[1] = y;
}
break;
case 4: /* halfmove */
@@ -682,7 +947,7 @@ parsefen(const char *fen)
l = strtol(s, NULL, 10);
if (l >= 0 && l < 32767) {
- halfmove = l;
+ b->halfmove = l;
for (; *s && isdigit((unsigned char)*s); s++)
;
@@ -694,7 +959,7 @@ parsefen(const char *fen)
l = strtol(s, NULL, 10);
if (l >= 0 && l < 32767) {
- movenumber = (int)l;
+ b->movenumber = (int)l;
for (; *s && isdigit((unsigned char)*s); s++)
;
}
@@ -714,178 +979,216 @@ parsefen(const char *fen)
}
void
-parsemoves(const char *moves)
+board_playmoves(struct board *b, const char *moves)
{
char square[3];
const char *castled, *s;
- int firstmove, i, x, y, x2, y2, piece, takepiece, tookpiece;
+ int firstmove, i, j, x, y, x2, y2, otherside, piece, takepiece, tookpi…
+ int needfile, needrank, promote;
/* process moves */
square[2] = '\0';
x = y = x2 = y2 = -1;
firstmove = 1;
+ /* clear previous highlights */
+ memset(&(b->highlight), 0, sizeof(b->highlight));
for (s = moves; *s; s++) {
if (*s == ' ')
continue;
- if ((*s >= 'a' && *s <= 'h') &&
+ if (!((*s >= 'a' && *s <= 'h') &&
(*(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…
+ (*(s + 3) >= '1' && *(s + 3) <= '8')))
+ continue;
- if (firstmove)
- firstmove = 0;
- else
- pgn(" ");
+ otherside = b->side_to_move == 'b' ? 'w' : 'b';
- square[0] = *s;
- square[1] = *(s + 1);
+ /* if first move and it is blacks turn, prefix
+ with "...", because the white move was unknown */
+ if (firstmove && b->side_to_move == 'b')
+ pgn("%d. ... ", b->movenumber);
- s += 2;
- squaretoxy(square, &x, &y);
- piece = getpiece(x, y);
+ if (firstmove)
+ firstmove = 0;
+ else
+ pgn(" ");
- /* target location */
- square[0] = *s;
- square[1] = *(s + 1);
- squaretoxy(square, &x2, &y2);
+ square[0] = *s;
+ square[1] = *(s + 1);
- /* place piece at new location */
- place(0, x, y); /* clear previous square */
- /* take piece (can be your own) */
- takepiece = getpiece(x2, y2);
+ s += 2;
+ squaretoxy(square, &x, &y);
+ piece = getpiece(b, x, y);
- s += 2;
+ /* target location */
+ square[0] = *s;
+ square[1] = *(s + 1);
+ squaretoxy(square, &x2, &y2);
- /* took piece of opponent */
- tookpiece = (side_to_move == 'w' && isblackpiece(takep…
- (side_to_move == 'b' && iswhitepiece(takep…
+ /* take piece (can be your own) */
+ takepiece = getpiece(b, x2, y2);
- /* if pawn move or taken a piece increase halfmove cou…
- 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 */
- if (x2 > x + 1 || takepiece == 'R') {
- for (i = x2; i < 8; i++) {
- if (getpiece(i, y2) == 'R') {
- place(0, i, y2); /* cl…
- place('R', x2 - 1, y2)…
- castled = "O-O";
- break;
- }
- }
- } else if (x2 < x - 1 || takepiece == 'R') {
- /* white: queenside castling */
- for (i = x2; i >= 0; i--) {
- if (getpiece(i, y2) == 'R') {
- place('R', x2 + 1, y2)…
- place(0, i, y2); /* cl…
- castled = "O-O-O";
- break;
- }
+ s += 2;
+
+ promote = 0;
+ if (*s == 'q' || *s == 'b' || *s == 'n') {
+ promote = *s;
+ s++;
+ }
+
+ /* took piece of opponent */
+ tookpiece = (b->side_to_move == 'w' && isblackpiece(takepiece)…
+ (b->side_to_move == 'b' && iswhitepiece(takepiece)…
+
+ /* if pawn move or taken a piece increase halfmove counter */
+ if (piece == 'p' || piece == 'P' || tookpiece)
+ b->halfmove = 0;
+ else
+ b->halfmove++;
+
+ if (b->side_to_move == 'w')
+ pgn("%d. ", b->movenumber);
+
+ /* castled this move? */
+ castled = NULL;
+
+ /* castling */
+ if (piece == 'K' && y == 7 && y2 == 7) {
+ /* white: kingside castling */
+ if (x2 > x + 1 || takepiece == 'R') {
+ for (i = x2; i < 8; i++) {
+ if (getpiece(b, i, y2) == 'R') {
+ place(b, 0, x, y); /* clear pr…
+ place(b, 0, i, y2); /* clear r…
+ place(b, 'R', x2 - 1, y2); /* …
+ castled = "O-O";
+ break;
}
}
- } else if (piece == 'k' && y == 0 && y2 == 0) {
- /* black: kingside castling */
- if (x2 > x + 1 || takepiece == 'r') {
- for (i = x2; i < 8; i++) {
- if (getpiece(i, y2) == 'r') {
- place(0, i, y2); /* cl…
- place('r', x2 - 1, y2)…
- castled = "O-O";
- break;
- }
- }
- } else if (x2 < x - 1 || takepiece == 'R') {
- /* black: queenside castling */
- for (i = x2; i >= 0; i--) {
- if (getpiece(i, y2) == 'r') {
- place('r', x2 + 1, y2)…
- place(0, i, y2); /* cl…
- castled = "O-O-O";
- break;
- }
+ } else if (x2 < x - 1 || takepiece == 'R') {
+ /* white: queenside castling */
+ for (i = x2; i >= 0; i--) {
+ if (getpiece(b, i, y2) == 'R') {
+ place(b, 0, x, y); /* clear pr…
+ place(b, 'R', x2 + 1, y2); /* …
+ place(b, 0, i, y2); /* clear r…
+ castled = "O-O-O";
+ break;
}
}
}
-
- /* remove the ability to castle */
- if (piece == 'K') {
- white_can_castle[0] = white_can_castle[1] = 0;
- } else if (piece == 'k') {
- black_can_castle[0] = black_can_castle[1] = 0;
- } else if (piece == 'R' && y == 7) {
- for (i = 0; i < 8; i++) {
- if (getpiece(i, y) == 'K') {
- if (i < x)
- white_can_castle[0] = …
- else if (i > x)
- white_can_castle[1] = …
+ } else if (piece == 'k' && y == 0 && y2 == 0) {
+ /* black: kingside castling */
+ if (x2 > x + 1 || takepiece == 'r') {
+ for (i = x2; i < 8; i++) {
+ if (getpiece(b, i, y2) == 'r') {
+ place(b, 0, x, y); /* clear pr…
+ place(b, 0, i, y2); /* clear r…
+ place(b, 'r', x2 - 1, y2); /* …
+ castled = "O-O";
break;
}
}
- } else if (piece == 'r' && y == 0) {
- for (i = 0; i < 8; i++) {
- if (getpiece(i, y) == 'k') {
- if (i > x)
- black_can_castle[1] = …
- else if (i < x)
- black_can_castle[0] = …
+ } else if (x2 < x - 1 || takepiece == 'R') {
+ /* black: queenside castling */
+ for (i = x2; i >= 0; i--) {
+ if (getpiece(b, i, y2) == 'r') {
+ place(b, 0, x, y); /* clear pr…
+ place(b, 'r', x2 + 1, y2); /* …
+ place(b, 0, i, y2); /* clear r…
+ castled = "O-O-O";
break;
}
}
}
+ }
- /* taken en passant? */
- if (x2 == enpassantsquare[0] && y2 == enpassantsquare[…
- place(0, x2, piece == 'P' ? y2 + 1 : y2 - 1);
-
- /* the en passant square resets after a move */
- enpassantsquare[0] = -1;
- enpassantsquare[1] = -1;
- /* moved 2 squares and there is an opponent pawn next …
- if (piece == 'P' && y == 6 && y2 == 4) {
- if (getpiece(x - 1, y2) == 'p' ||
- getpiece(x + 1, y2) == 'p') {
- enpassantsquare[0] = x;
- enpassantsquare[1] = 5;
+ /* remove the ability to castle */
+ if (piece == 'K') {
+ b->white_can_castle[0] = b->white_can_castle[1] = 0;
+ } else if (piece == 'k') {
+ b->black_can_castle[0] = b->black_can_castle[1] = 0;
+ } else if (piece == 'R' && y == 7) {
+ for (i = 0; i < 8; i++) {
+ if (getpiece(b, i, y) == 'K') {
+ if (i < x)
+ b->white_can_castle[0] = 0;
+ else if (i > x)
+ b->white_can_castle[1] = 0;
+ break;
}
- } else if (piece == 'p' && y == 1 && y2 == 3) {
- if (getpiece(x - 1, y2) == 'P' ||
- getpiece(x + 1, y2) == 'P') {
- enpassantsquare[0] = x;
- enpassantsquare[1] = 2;
+ }
+ } else if (piece == 'r' && y == 0) {
+ for (i = 0; i < 8; i++) {
+ if (getpiece(b, i, y) == 'k') {
+ if (i > x)
+ b->black_can_castle[1] = 0;
+ else if (i < x)
+ b->black_can_castle[0] = 0;
+ break;
}
}
+ }
- /* place piece */
- place(piece, x2, y2);
+ /* taken en passant? */
+ if (x2 == b->enpassantsquare[0] && y2 == b->enpassantsquare[1])
+ place(b, 0, x2, piece == 'P' ? y2 + 1 : y2 - 1);
+
+ /* the en passant square resets after a move */
+ b->enpassantsquare[0] = -1;
+ b->enpassantsquare[1] = -1;
+
+ /* set en passant square:
+ moved 2 squares and there is an opponent pawn next to it */
+ if (piece == 'P' && y == 6 && y2 == 4) {
+ if (getpiece(b, x - 1, y2) == 'p' ||
+ getpiece(b, x + 1, y2) == 'p') {
+ b->enpassantsquare[0] = x;
+ b->enpassantsquare[1] = 5;
+ }
+ } else if (piece == 'p' && y == 1 && y2 == 3) {
+ if (getpiece(b, x - 1, y2) == 'P' ||
+ getpiece(b, x + 1, y2) == 'P') {
+ b->enpassantsquare[0] = x;
+ b->enpassantsquare[1] = 2;
+ }
+ }
+ /* if output is PGN, show piece movement, else skip this step …
+ if (outputmode == ModePGN) {
/* 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", xtofile(x), ytorank(y));
+ pgn("%c", pgnpiece(piece));
+
+ /* check ambiguity for certain pieces …
+ needfile = needrank = 0;
+ for (i = 0; i < 8; i++) {
+ for (j = 0; j < 8; j++) {
+ if (x == i && j == y)
+ continue;
+ if (getpiece(b, i, j) …
+ continue;
+
+ if (canpiecemove(b, b-…
+ needfile = 1;
+ if (i == x)
+ needra…
+ }
+ }
+ }
+ if (needfile) {
+ pgn("%c", xtofile(x));
+ if (needrank)
+ pgn("%c", ytorank(y));
+ }
}
if (tookpiece) {
- /* pawn captures are prefixed by the f…
+ /* pawn captures are prefixed by the f…
if (piece == 'p' || piece == 'P')
pgn("%c", xtofile(x));
pgn("x");
@@ -893,51 +1196,177 @@ parsemoves(const char *moves)
pgn("%c%c", xtofile(x2), ytorank(y2));
/* possible promotion: queen, knight, bishop */
- if (*s == 'q' || *s == 'n' || *s == 'b') {
- if (side_to_move == 'w')
- piece = toupper((unsigned char…
+ if (promote) {
+ if (b->side_to_move == 'w')
+ piece = toupper(promote);
else
- piece = *s;
- place(piece, x2, y2);
- s++;
- pgn("=%c", toupper(piece));
+ piece = tolower(promote);
+ place(b, piece, x2, y2);
+
+ pgn("=%c", pgnpiece(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')
- movenumber++;
+ /* clear previous square (if not castled) */
+ if (!castled)
+ place(b, 0, x, y);
+ /* place piece or new promoted piece */
+ place(b, piece, x2, y2);
- /* switch which side it is to move */
- side_to_move = side_to_move == 'b' ? 'w' : 'b';
- }
+ if (ischeckmated(b, otherside))
+ pgn("#");
+ else if (isincheck(b, otherside))
+ pgn("+");
+
+ /* a move by black increases the move number */
+ if (b->side_to_move == 'b')
+ b->movenumber++;
+
+ /* switch which side it is to move */
+ b->side_to_move = otherside;
+
+ if (!*s)
+ break;
}
- pgn("\n");
-// // DEBUG
-// ischecked('w');
-// ischecked('b');
+ if (!firstmove)
+ pgn("\n");
/* highlight last move */
- highlightmove(x, y);
- highlightmove(x2, y2);
+ highlightmove(b, x, y);
+ highlightmove(b, x2, y2);
+
+ /* highlight king in check or mate */
+ if (isincheck(b, b->side_to_move) &&
+ findking(b, b->side_to_move, &x, &y))
+ highlightcheck(b, x, y);
}
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…
+ fprintf(stderr, "usage: %s [-cCfF] [-m mapping] [-o ascii|fen|pgn|svg|…
exit(1);
}
+/* CGI: get parameter */
+char *
+getparam(const char *query, const char *s)
+{
+ const char *p, *last = NULL;
+ size_t len;
+
+ len = strlen(s);
+ for (p = query; (p = strstr(p, s)); p += len) {
+ if (p[len] == '=' && (p == query || p[-1] == '&' || p[-1] == '…
+ last = p + len + 1;
+ }
+
+ return (char *)last;
+}
+
+int
+hexdigit(int c)
+{
+ if (c >= '0' && c <= '9')
+ return c - '0';
+ else if (c >= 'A' && c <= 'F')
+ return c - 'A' + 10;
+ else if (c >= 'a' && c <= 'f')
+ return c - 'a' + 10;
+
+ return 0;
+}
+
+/* CGI: decode until NUL separator or end of "key". */
+int
+decodeparam(char *buf, size_t bufsiz, const char *s)
+{
+ size_t i;
+
+ if (!bufsiz)
+ return -1;
+
+ for (i = 0; *s && *s != '&'; s++) {
+ switch (*s) {
+ case '%':
+ if (i + 3 >= bufsiz)
+ return -1;
+ if (!isxdigit((unsigned char)*(s+1)) ||
+ !isxdigit((unsigned char)*(s+2)))
+ return -1;
+ buf[i++] = hexdigit(*(s+1)) * 16 + hexdigit(*(s+2));
+ s += 2;
+ break;
+ case '+':
+ if (i + 1 >= bufsiz)
+ return -1;
+ buf[i++] = ' ';
+ break;
+ default:
+ if (i + 1 >= bufsiz)
+ return -1;
+ buf[i++] = *s;
+ break;
+ }
+ }
+ buf[i] = '\0';
+
+ return i;
+}
+
+/* CGI mode */
+int
+cgi_mode(void)
+{
+ struct board board;
+ char *query, *p;
+ char buf[4096];
+
+ board_init(&board);
+
+ query = getenv("QUERY_STRING");
+ if ((p = getparam(query, "flip")) && (*p == '0' || *p == '1'))
+ board.flipboard = *p == '1' ? 1 : 0;
+ if ((p = getparam(query, "coords")) && (*p == '0' || *p == '1'))
+ board.showcoords = *p == '1' ? 1 : 0;
+ if ((p = getparam(query, "theme"))) {
+ if (decodeparam(buf, sizeof(buf), p) == -1)
+ goto badrequest;
+ board_set_theme(&board, buf);
+ }
+ if ((p = getparam(query, "fen"))) {
+ if (decodeparam(buf, sizeof(buf), p) == -1)
+ goto badrequest;
+ board_setup_fen(&board, buf);
+ } else {
+ board_setup_fen(&board, "startpos");
+ }
+ if ((p = getparam(query, "moves"))) {
+ if (decodeparam(buf, sizeof(buf), p) == -1)
+ goto badrequest;
+ board_playmoves(&board, buf);
+ }
+
+ fputs("Status: 200 OK\r\nContent-Type: image/svg+xml\r\n\r\n", stdout);
+ output_svg(&board);
+
+ return 0;
+
+badrequest:
+ fputs("Status: 400 Bad Request\r\n", stdout);
+ fputs("Content-Type: text/plain\r\n", stdout);
+ fputs("\r\n", stdout);
+ fputs("Bad request: fen parameter use a valid format or \"startpos\" (…
+
+ return 1;
+}
+
int
main(int argc, char *argv[])
{
+ struct board board;
const char *fen, *moves;
int i, j;
@@ -946,6 +1375,10 @@ main(int argc, char *argv[])
err(1, "pledge");
#endif
+ if (getenv("QUERY_STRING"))
+ return cgi_mode();
+
+ board_init(&board);
fen = "startpos";
moves = "";
@@ -955,11 +1388,19 @@ main(int argc, char *argv[])
for (j = 1; argv[i][j]; j++) {
switch (argv[i][j]) {
- case 'c': showcoords = 1; break;
- case 'C': showcoords = 0; break;
- case 'f': flipboard = 1; break;
- case 'F': flipboard = 0; break;
- case 'o':
+ case 'c': board.showcoords = 1; break;
+ case 'C': board.showcoords = 0; break;
+ case 'f': board.flipboard = 1; break;
+ case 'F': board.flipboard = 0; break;
+ case 'm': /* remap PGN */
+ if (i + 1 >= argc)
+ usage(argv[0]);
+ i++;
+ if (strlen(argv[i]) != 5)
+ usage(argv[0]);
+ pgn_piecemapping = argv[i];
+ goto next;
+ case 'o': /* output format */
if (i + 1 >= argc)
usage(argv[0]);
i++;
@@ -967,8 +1408,8 @@ main(int argc, char *argv[])
outputmode = ModeASCII;
else if (!strcmp(argv[i], "fen"))
outputmode = ModeFEN;
-// else if (!strcmp(argv[i], "pgn"))
-// outputmode = ModePGN;
+ else if (!strcmp(argv[i], "pgn"))
+ outputmode = ModePGN;
else if (!strcmp(argv[i], "svg"))
outputmode = ModeSVG;
else if (!strcmp(argv[i], "tty"))
@@ -976,6 +1417,12 @@ main(int argc, char *argv[])
else
usage(argv[0]);
goto next;
+ case 't': /* theme name */
+ if (i + 1 >= argc)
+ usage(argv[0]);
+ i++;
+ board_set_theme(&board, argv[i]);
+ goto next;
default:
usage(argv[0]);
break;
@@ -993,20 +1440,15 @@ next:
i++;
}
- if (!strcmp(fen, "startpos"))
- fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 …
-
- parsefen(fen);
- parsemoves(moves);
-
-// outputmode = ModeTTY; /* DEBUG */
+ board_setup_fen(&board, fen);
+ board_playmoves(&board, moves);
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;
+ case ModeASCII: output_ascii(&board); break;
+ case ModeFEN: output_fen(&board); break;
+ case ModePGN: break; /* handled in parsemoves(…
+ case ModeSVG: output_svg(&board); break;
+ case ModeTTY: output_tty(&board); break;
default: usage(argv[0]); break;
}
diff --git a/generate.sh b/generate.sh
@@ -2,6 +2,36 @@
fenbin="./fen"
db="lichess_db_puzzle.csv"
+# default, green, grey
+theme="default"
+
+# texts / localization.
+# English
+text_solutions="Solutions"
+text_puzzles="Puzzles"
+text_puzzle="Puzzle"
+text_puzzlerating="Puzzel rating"
+text_point="point"
+text_points="points"
+text_whitetomove="white to move"
+text_blacktomove="black to move"
+text_title="${text_puzzles}"
+text_header="${text_puzzles}!"
+pgnmapping="KQRBN"
+
+# Dutch
+#text_solutions="Oplossingen"
+#text_puzzles="Puzzels"
+#text_puzzle="Puzzel"
+#text_puzzlerating="Puzzel moeilijkheidsgraad"
+#text_point="punt"
+#text_points="punten"
+#text_whitetomove="wit aan zet"
+#text_blacktomove="zwart aan zet"
+#text_title="${text_puzzles}"
+#text_header="${text_puzzles}!"
+# Dutch: (K)oning, (D)ame, (T)oren, (L)oper, (P)aard.
+#pgnmapping="KDTLP"
if ! test -f "$db"; then
printf 'File "%s" not found, run `make db` to update it\n' "$db" >&2
@@ -14,7 +44,7 @@ mkdir -p puzzles/solutions
solutions="$(mktemp)"
seedfile="$(mktemp)"
-seed=20231221 # must be a integer value
+seed=20240101 # must be a integer value
# seed for random sorting, makes it deterministic for the same system
# seed must be sufficiently long.
echo "${seed}_chess_puzzles" > "$seedfile"
@@ -48,7 +78,7 @@ cat > "$index" <<!
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
-<title>Puzzles</title>
+<title>${text_title}</title>
<style type="text/css">
body {
font-family: sans-serif;
@@ -73,11 +103,20 @@ footer {
details summary {
cursor: pointer; /* show hand */
}
+@media (prefers-color-scheme: dark) {
+ body {
+ background-color: #000;
+ color: #bdbdbd;
+ }
+ h2 a {
+ color: #bdbdbd;
+ }
+}
</style>
</head>
<body>
<header>
-<h1>Puzzles, happy christmas mating!</h1>
+<h1>${text_header}</h1>
</header>
<main>
!
@@ -98,13 +137,13 @@ LC_ALL=C awk -F ',' 'int($4) >= 2000 { print $0 }' "$group…
LC_ALL=C awk -F ',' 'int($4) >= 2700 { print $0 }' "$groupsdir/matein5.csv" > …
(
-shuffle "$groupsdir/matein1.csv" 100 | sed 10q | LC_ALL=C awk '{ print $0 ",1 …
-shuffle "$groupsdir/matein2.csv" 100 | sed 10q | LC_ALL=C awk '{ print $0 ",2 …
-shuffle "$groupsdir/matein3.csv" 100 | sed 10q | LC_ALL=C awk '{ print $0 ",3 …
-shuffle "$groupsdir/matein4.csv" 100 | sed 10q | LC_ALL=C awk '{ print $0 ",4 …
-shuffle "$groupsdir/matein5_lt_2000.csv" 100 | sed 5q | LC_ALL=C awk '{ print …
-shuffle "$groupsdir/matein5_ge_2000.csv" 100 | sed 3q | LC_ALL=C awk '{ print …
-shuffle "$groupsdir/matein5_ge_2700.csv" 100 | sed 2q | LC_ALL=C awk '{ print …
+shuffle "$groupsdir/matein1.csv" 100 | sed 10q | LC_ALL=C awk '{ print $0 ",1"…
+shuffle "$groupsdir/matein2.csv" 100 | sed 10q | LC_ALL=C awk '{ print $0 ",2"…
+shuffle "$groupsdir/matein3.csv" 100 | sed 10q | LC_ALL=C awk '{ print $0 ",3"…
+shuffle "$groupsdir/matein4.csv" 100 | sed 10q | LC_ALL=C awk '{ print $0 ",4"…
+shuffle "$groupsdir/matein5_lt_2000.csv" 100 | sed 5q | LC_ALL=C awk '{ print …
+shuffle "$groupsdir/matein5_ge_2000.csv" 100 | sed 3q | LC_ALL=C awk '{ print …
+shuffle "$groupsdir/matein5_ge_2700.csv" 100 | sed 2q | LC_ALL=C awk '{ print …
rm -rf "$groupsdir"
) | \
while read -r line; do
@@ -130,27 +169,35 @@ while read -r line; do
# added field: points
points=$(printf '%s' "$line" | cut -f "11" -d ',')
+ if [ "$points" = "1" ]; then
+ points="$points ${text_point}"
+ else
+ points="$points ${text_points}"
+ fi
img="$i.svg"
txt="$i.txt"
vt="$i.vt"
+ destfen="puzzles/$i.fen"
destsvg="puzzles/$img"
desttxt="puzzles/$txt"
destvt="puzzles/$vt"
- "$fenbin" $flip -o svg "$fen" "$firstmove" > "$destsvg"
- "$fenbin" $flip -o ascii "$fen" "$firstmove" > "$desttxt"
- "$fenbin" $flip -o tty "$fen" "$firstmove" > "$destvt"
+ "$fenbin" -m "$pgnmapping" -t "$theme" $flip -o svg "$fen" "$firstmove…
+ "$fenbin" -m "$pgnmapping" -t "$theme" $flip -o ascii "$fen" "$firstmo…
+ "$fenbin" -m "$pgnmapping" -t "$theme" $flip -o tty "$fen" "$firstmove…
+ "$fenbin" -m "$pgnmapping" -t "$theme" $flip -o fen "$fen" "$firstmove…
+ pgn=$("$fenbin" -m "$pgnmapping" -o pgn "$fen" "$firstmove")
printf '<div class="puzzle" id="puzzle-%s">\n' "$i" >> "$index"
- printf '<h2><a href="#puzzle-%s">Puzzle %s</a></h2>\n' "$i" "$i" >> "$…
+ printf '<h2><a href="#puzzle-%s">%s %s</a></h2>\n' "$i" "${text_puzzle…
test "$lichess" != "" && printf '<a href="%s">' "$lichess" >> "$index"
title=""
- test "$rating" != "" && title="Puzzle rating: $rating"
+ test "$rating" != "" && title="${text_puzzlerating}: $rating"
- printf '<img src="%s" alt="Puzzle #%s" title="%s" width="360" height="…
- "$img" "$i" "$title" >> "$index"
+ printf '<img src="%s" alt="%s #%s" title="%s" width="360" height="360"…
+ "$img" "${text_puzzle}" "$i" "$title" >> "$index"
test "$lichess" != "" && printf '</a>' >> "$index"
echo "" >> "$index"
@@ -158,13 +205,13 @@ while read -r line; do
# if there is a first move, inverse to move.
if test "$firstmove" != ""; then
case "$tomove" in
- "w") movetext=", black to move";;
- "b") movetext=", white to move";;
+ "w") movetext=", ${text_blacktomove}";;
+ "b") movetext=", ${text_whitetomove}";;
esac
else
case "$tomove" in
- "w") movetext=", white to move";;
- "b") movetext=", black to move";;
+ "w") movetext=", ${text_whitetomove}";;
+ "b") movetext=", ${text_blacktomove}";;
esac
fi
@@ -174,7 +221,7 @@ while read -r line; do
# solutions per puzzle.
printf '<div class="puzzle-solution">\n' >> "$solutions"
- printf '<h2><a href="#puzzle-%s">Puzzle %s</a></h2>\n' "$i" "$i" >> "$…
+ printf '<h2><a href="#puzzle-%s">%s %s</a></h2>\n' "$i" "${text_puzzle…
m="${allmoves}"
movecount=0
@@ -182,8 +229,16 @@ while read -r line; do
# the solution images.
# add initial puzzle aswell for context.
- printf '<img src="%s" width="180" height="180" loading="lazy" />\n' \
- "${i}.svg" >> "$solutions"
+ printf '<img src="%s" width="180" height="180" loading="lazy" title="%…
+ "${i}.svg" "$pgn" >> "$solutions"
+
+ # solution PGN
+ pgn_solution="$($fenbin -m "$pgnmapping" -o pgn "$fen" "$allmoves")"
+
+ destsolpgn="puzzles/solutions/${i}.pgn"
+ printf '%s\n' "$pgn_solution" > "$destsolpgn"
+
+# printf 'DEBUG: #%s: "%s" "%s"\n' "$i" "$fen" "$allmoves" >&2
while [ "$m" != "" ]; do
prevmoves="$m"
@@ -205,25 +260,27 @@ while read -r line; do
# process move list in sequence.
destsolsvg="puzzles/solutions/${i}_${movecount}.svg"
- "$fenbin" $flip -o svg "$fen" "$movelist" > "$destsolsvg"
+ "$fenbin" -m "$pgnmapping" -t "$theme" $flip -o svg "$fen" "$m…
+
+ # PGN of moves so far.
+ pgn="$($fenbin -m "$pgnmapping" -o pgn "$fen" "$movelist")"
- printf '<img src="%s" width="180" height="180" loading="lazy" …
- "solutions/${i}_${movecount}.svg" >> "$solutions"
+ printf '<img src="%s" width="180" height="180" loading="lazy" …
+ "solutions/${i}_${movecount}.svg" "$pgn" >> "$solution…
movecount=$((movecount + 1))
done
+
+ printf '<p><b>PGN:</b> %s</p>\n' "${pgn_solution}" >> "$solutions"
printf '</div>\n' >> "$solutions"
printf '</div>\n' >> "$index"
- # DEBUG
- #echo "$count: $line" >&2
-
count=$((count + 1))
done
# solutions / spoilers
-echo "<footer><br/><br/><details>\n<summary>Solutions</summary>\n" >> "$index"
+printf '<footer><br/><br/><details>\n<summary>%s</summary>\n' "$text_solutions…
cat "$solutions" >> "$index"
echo "</details>\n<br/><br/></footer>\n" >> "$index"
diff --git a/gifs.sh b/gifs.sh
@@ -0,0 +1,50 @@
+#!/bin/sh
+# create animated gifs for puzzle solutions.
+# Dependencies: ffmpeg, ImageMagick, etc.
+
+tmppal="$(mktemp '/tmp/palette_XXXXXXXX.png')"
+
+n=1
+while :; do
+ f="puzzles/${n}.svg"
+ test -f "$f" || break
+
+ tmpdir="$(mktemp -d '/tmp/puzzleanim_XXXXXXXX')"
+
+ # initial puzzle state also
+ dest="$tmpdir/0.png"
+ convert "$f" "$dest"
+
+ # solution
+ i=1
+ while :; do
+ f="puzzles/solutions/${n}_${i}.svg"
+ test -f "$f" || break
+
+ dest="$tmpdir/$i.png"
+ convert "$f" "$dest"
+ i=$((i+1))
+ done
+
+ # create video / animation.
+ out="puzzles/solutions/$n.gif"
+ rm -f "$out"
+
+ # generate palette for gif.
+ rm -f "$tmppal"
+ ffmpeg -loglevel error -stats -i "$tmpdir/%d.png"\
+ -vf palettegen "$tmppal"
+
+ # wait longer for last frame.
+ ffmpeg -loglevel error -stats -framerate 1\
+ -i "$tmpdir/%d.png" \
+ -i "$tmppal" \
+ -lavfi 'tpad=stop_mode=clone:stop_duration=4[v];[v]paletteuse[…
+ -map '[out]' \
+ "$out"
+
+ rm -rf "$tmpdir"
+ rm -f "$tmppal"
+
+ n=$((n + 1))
+done
diff --git a/tests.sh b/tests.sh
@@ -158,6 +158,12 @@ testfen 'white takes en passant: should reset halfmove cou…
'rnbqkbnr/pppp1pp1/8/3Pp2p/8/8/PPP1PPPP/RNBQKBNR w KQkq e6 1 3'\
'd5e6'
+# check halfmove counter on check, white checks black: should increase halfmov…
+testfen 'check halfmove counter on check, white checks black: should increase …
+ 'rnbqkbnr/ppp1pp1p/6p1/1B1p4/3P4/4P3/PPP2PPP/RNBQK1NR b KQkq - 21 1'\
+ 'rnbqkbnr/ppp1pp1p/6p1/3p4/3P4/4P3/PPP2PPP/RNBQKBNR w KQkq - 20 1'\
+ 'f1b5'
+
# 960 white queenside castle
testfen '960 white queenside castle'\
'qrn1bk1n/ppb1pprp/2p3p1/3p4/1B1P4/3NPB2/PPP2PPP/Q1KR2RN b q - 5 6'\
@@ -220,7 +226,7 @@ testfen '960, castle king on queenside with many empty squa…
tests_pgn() {
testpgn 'simple pawn move'\
- 'e5'\
+ '2. ... e5'\
'rnbqkbnr/ppp1pppp/8/3p4/3PP3/8/PPP2PPP/RNBQKBNR b KQkq - 0 2'\
'e7e5'
@@ -229,24 +235,60 @@ testpgn 'check: check with white pawn'\
'rnbqkbnr/pppp1ppp/6P1/8/8/4p3/PPPPPP1P/RNBQKBNR w KQkq - 0 4'\
'g6f7'
testpgn 'check: check with black pawn'\
- 'exf2+'\
+ '4. ... exf2+'\
'rnbqkbnr/pppp1ppP/8/8/8/4p3/PPPPPP1P/RNBQKBNR b KQkq - 0 4'\
'e3f2'
testpgn 'check: check with bishop'\
- '3. Bf1b5+'\
+ '3. Bb5+'\
'rnbqkbnr/ppp2ppp/8/3pp3/3PP3/8/PPP2PPP/RNBQKBNR w KQkq - 0 3'\
'f1b5'
testpgn 'check: check with white queen, open file'\
- '6. Qd4e4+'\
+ '6. Qe4+'\
'rnbqkbnr/1ppp1pp1/p5Pp/8/3Q4/4P3/PPP1PP1P/RNB1KBNR w KQkq - 0 6'\
'd4e4'
-testpgn 'check: check with white knight'\
- '8. Nd5xc7+'\
+testpgn 'check: check and takes with white knight'\
+ '8. Nxc7+'\
'rnbqkbn1/pppp1ppr/6P1/3N4/7p/4PN2/PPP1PP1P/R1BQKB1R w KQq - 2 8'\
'd5c7'
+
+testpgn 'black moves, but the move from white is unknown, use "..."'\
+ '1. ... e5'\
+ 'rnbqkbnr/pppppppp/8/8/3P4/8/PPP1PPPP/RNBQKBNR b KQkq - 0 1'\
+ 'e7e5'
+
+testpgn 'black moves with knight, ambigous move, needs file name'\
+ '8. ... Nbd7'\
+ 'rn2kb1r/pp4pp/1qp1pn2/3p4/3P1B2/1P1BP3/P1P2P1P/RN1QK1NR b KQkq - 0 8'\
+ 'b8d7'
+
+testpgn 'black moves with knight, non-ambigous move'\
+ '8. ... Nd7'\
+ 'rn2kb1r/pp4pp/1qp1p3/3p4/3P1B2/1P1BP3/P1P2P1P/RN1QK1NR b KQkq - 0 8'\
+ 'b8d7'
+
+testpgn '2 queens, ambigous move, needs file and rank'\
+ '8. Qh3g3'\
+ 'rn2kb1r/pp4pp/1qp1p3/3p4/3P1B1Q/1P1BP2Q/P1P2P1P/RN2K1NR w KQkq - 0 8'\
+ 'h3g3'
+
+testpgn 'king moves into range of king (illegal move), but played, so notate i…
+ '1. Kd5+'\
+ '8/8/3k4/8/3K2R1/8/8/8 w - - 0 1'\
+ 'd4d5'
+
+# Long list of moves, checkmate at the end.
+testpgn 'Long list of moves, compared to Lichess board analyzer'\
+ '1. d4 d5 2. Bf4 c6 3. e3 Bf5 4. Bd3 e6 5. g4 Nf6 6. gxf5 Qb6 7. fxe6 …
+ 'startpos'\
+ 'd2d4 d7d5 c1f4 c7c6 e2e3 c8f5 f1d3 e7e6 g2g4 g8f6 g4f5 d8b6 f5e6 f7e6…
+
+testpgn 'Rook on same file, only one is legal, Rd7d1+ -> Rd1+'\
+ '34. Rc6 Rd1+ 35. Rxd1 Rxd1#'\
+ '3r2k1/3r2p1/R6p/4bp2/2P1p2P/7N/2P2PP1/2R3K1 w - - 1 34'\
+ 'a6c6 d7d1 c1d1 d8d1'
}
tests_fen
-#tests_pgn
+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.