various improvements - 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 cd3acc17b7b82e112bd5abbc65324283a7c5bf7f
(DIR) parent f1daa80c1d6cf199c57d820e117255f38bd3d55e
(HTM) Author: Hiltjo Posthuma <hiltjo@codemadness.org>
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(-)
---
(DIR) diff --git a/LICENSE b/LICENSE
@@ -1,6 +1,6 @@
ISC License
-Copyright (c) 2023 Hiltjo Posthuma <hiltjo@codemadness.org>
+Copyright (c) 2023-2024 Hiltjo Posthuma <hiltjo@codemadness.org>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
(DIR) 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 screenreaders/espeak).
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 square is inserted
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.
(DIR) 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 $3; }')
test "$str" != "" && fen="$str" # override
- str=$(printf '%s' "$json" | jaq '$1 == ".white.name" { print $3; }')
- test "$str" != "" && white="$str" # override
-
- str=$(printf '%s' "$json" | jaq '$1 == ".black.name" { print $3; }')
- 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
(DIR) 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.
(DIR) 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 = highlight, 2 = check or mate) */
+
+ int side_to_move; /* default: white to move: 'w' */
+ int white_can_castle[2]; /* allow king side, allow queen side? default: { 0, 0 } */
+ int black_can_castle[2]; /* allow king side, allow queen side? default: { 0, 0 } */
+
+ 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 side? */
-static int black_can_castle[2] = { 0, 0 }; /* allow king side, allow queen side? */
-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 definitions */
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 : lightsquare;
- else
- color = highlight[y][x] ? darksquarehi : darksquare;
+ x = b->flipboard ? 7 - ix : ix;
+ color = getsquarecolor(b, x, y, 0);
printf("<g><rect x=\"%d\" y=\"%d\" width=\"45\" height=\"45\" fill=\"#%02x%02x%02x\"/></g>\n",
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)\">", ix * 45, iy * 45);
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 : darksquare;
- else
- color = highlight[y][x] ? lightsquarehi : lightsquare;
+ color = getsquarecolor(b, x, y, 1);
printf("<text x=\"%d\" y=\"%d\" fill=\"#%02x%02x%02x\" text-anchor=\"end\" style=\"font-family: sans-serif; font-size: 10px\">%c</text>\n",
(ix + 1) * 45 - 2, (iy * 45) + 10, color[0], color[1], color[2], ytorank(y));
}
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 : darksquare;
- else
- color = highlight[y][x] ? lightsquarehi : lightsquare;
- } else {
- if (y % 2 == 0)
- color = highlight[y][x] ? lightsquarehi : lightsquare;
- else
- color = highlight[y][x] ? darksquarehi : darksquare;
- }
+ color = getsquarecolor(b, x, y, 1);
printf("<text x=\"%d\" y=\"%d\" fill=\"#%02x%02x%02x\" text-anchor=\"start\" style=\"font-family: sans-serif; font-size: 10px\">%c</text>\n",
(ix * 45) + 2, (iy + 1) * 45 - 3, color[0], color[1], color[2], xtofile(x));
@@ -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 : lightsquare;
- else
- color = highlight[y][x] ? darksquarehi : darksquare;
+ 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 white */
@@ -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 y2)
{
- 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 py)
+{
+ 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, y2))
+ return 1;
+
+ /* a piece is in front of it: stop this checking this direction */
+ 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, y2))
+ return 1;
+
+ /* a piece is in front of it: stop this checking this direction */
+ 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 are not in check? */
+ if (canpiecemove(b, side, x, y, x2, y2, -1, -1))
+ 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 1";
+
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, tookpiece;
+ 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 unknown */
+ (*(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(takepiece)) ||
- (side_to_move == 'b' && iswhitepiece(takepiece));
+ /* take piece (can be your own) */
+ takepiece = getpiece(b, x2, y2);
- /* if pawn move or taken a piece increase halfmove counter */
- 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); /* clear rook square */
- place('R', x2 - 1, y2); /* next to king */
- 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); /* next to king */
- place(0, i, y2); /* clear rook square */
- 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 previous square */
+ place(b, 0, i, y2); /* clear rook square */
+ place(b, 'R', x2 - 1, y2); /* next to king */
+ 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); /* clear rook square */
- place('r', x2 - 1, y2); /* next to king */
- 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); /* next to king */
- place(0, i, y2); /* clear rook square */
- 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 previous square */
+ place(b, 'R', x2 + 1, y2); /* next to king */
+ place(b, 0, i, y2); /* clear rook square */
+ 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] = 0;
- else if (i > x)
- white_can_castle[1] = 0;
+ } 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 previous square */
+ place(b, 0, i, y2); /* clear rook square */
+ place(b, 'r', x2 - 1, y2); /* next to king */
+ 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] = 0;
- else if (i < x)
- black_can_castle[0] = 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 previous square */
+ place(b, 'r', x2 + 1, y2); /* next to king */
+ place(b, 0, i, y2); /* clear rook square */
+ castled = "O-O-O";
break;
}
}
}
+ }
- /* taken en passant? */
- if (x2 == enpassantsquare[0] && y2 == enpassantsquare[1])
- 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 to it */
- 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 pieces and make the notation shorter */
- pgn("%c%c", xtofile(x), ytorank(y));
+ pgn("%c", pgnpiece(piece));
+
+ /* check ambiguity for certain pieces and make the notation shorter */
+ 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) != piece)
+ continue;
+
+ if (canpiecemove(b, b->side_to_move, i, j, x2, y2, -1, -1)) {
+ needfile = 1;
+ if (i == x)
+ needrank = 1;
+ }
+ }
+ }
+ if (needfile) {
+ pgn("%c", xtofile(x));
+ if (needrank)
+ pgn("%c", ytorank(y));
+ }
}
if (tookpiece) {
- /* pawn captures are prefixed by the file letter */
+ /* pawn captures are prefixed by the file letter (no more needed) */
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)*s);
+ 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] [moves]\n", argv0);
- fprintf(stderr, "usage: %s [-cCfF] [-o ascii|fen|svg|tty] [FEN] [moves]\n", argv0);
+ fprintf(stderr, "usage: %s [-cCfF] [-m mapping] [-o ascii|fen|pgn|svg|tty] [-t default|green|grey] [FEN] [moves]\n", argv0);
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\" (default)\n", stdout);
+
+ 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 1";
-
- 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;
}
(DIR) 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 }' "$groupsdir/matein5.csv" > "$
LC_ALL=C awk -F ',' 'int($4) >= 2700 { print $0 }' "$groupsdir/matein5.csv" > "$groupsdir/matein5_ge_2700.csv"
(
-shuffle "$groupsdir/matein1.csv" 100 | sed 10q | LC_ALL=C awk '{ print $0 ",1 point" }'
-shuffle "$groupsdir/matein2.csv" 100 | sed 10q | LC_ALL=C awk '{ print $0 ",2 points" }'
-shuffle "$groupsdir/matein3.csv" 100 | sed 10q | LC_ALL=C awk '{ print $0 ",3 points" }'
-shuffle "$groupsdir/matein4.csv" 100 | sed 10q | LC_ALL=C awk '{ print $0 ",4 points" }'
-shuffle "$groupsdir/matein5_lt_2000.csv" 100 | sed 5q | LC_ALL=C awk '{ print $0 ",5 points" }'
-shuffle "$groupsdir/matein5_ge_2000.csv" 100 | sed 3q | LC_ALL=C awk '{ print $0 ",7 points" }'
-shuffle "$groupsdir/matein5_ge_2700.csv" 100 | sed 2q | LC_ALL=C awk '{ print $0 ",10 points" }'
+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 $0 ",5" }'
+shuffle "$groupsdir/matein5_ge_2000.csv" 100 | sed 3q | LC_ALL=C awk '{ print $0 ",7" }'
+shuffle "$groupsdir/matein5_ge_2700.csv" 100 | sed 2q | LC_ALL=C awk '{ print $0 ",10" }'
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" > "$destsvg"
+ "$fenbin" -m "$pgnmapping" -t "$theme" $flip -o ascii "$fen" "$firstmove" > "$desttxt"
+ "$fenbin" -m "$pgnmapping" -t "$theme" $flip -o tty "$fen" "$firstmove" > "$destvt"
+ "$fenbin" -m "$pgnmapping" -t "$theme" $flip -o fen "$fen" "$firstmove" > "$destfen"
+ 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" >> "$index"
+ printf '<h2><a href="#puzzle-%s">%s %s</a></h2>\n' "$i" "${text_puzzle}" "$i" >> "$index"
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="360" loading="lazy" />' \
- "$img" "$i" "$title" >> "$index"
+ printf '<img src="%s" alt="%s #%s" title="%s" width="360" height="360" loading="lazy" />' \
+ "$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" >> "$solutions"
+ printf '<h2><a href="#puzzle-%s">%s %s</a></h2>\n' "$i" "${text_puzzle}" "$i" >> "$solutions"
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="%s" />\n' \
+ "${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" "$movelist" > "$destsolsvg"
+
+ # PGN of moves so far.
+ pgn="$($fenbin -m "$pgnmapping" -o pgn "$fen" "$movelist")"
- printf '<img src="%s" width="180" height="180" loading="lazy" />\n' \
- "solutions/${i}_${movecount}.svg" >> "$solutions"
+ printf '<img src="%s" width="180" height="180" loading="lazy" title="%s" />\n' \
+ "solutions/${i}_${movecount}.svg" "$pgn" >> "$solutions"
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" >> "$index"
cat "$solutions" >> "$index"
echo "</details>\n<br/><br/></footer>\n" >> "$index"
(DIR) 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[out]' \
+ -map '[out]' \
+ "$out"
+
+ rm -rf "$tmpdir"
+ rm -f "$tmppal"
+
+ n=$((n + 1))
+done
(DIR) diff --git a/tests.sh b/tests.sh
@@ -158,6 +158,12 @@ testfen 'white takes en passant: should reset halfmove counter'\
'rnbqkbnr/pppp1pp1/8/3Pp2p/8/8/PPP1PPPP/RNBQKBNR w KQkq e6 1 3'\
'd5e6'
+# check halfmove counter on check, white checks black: should increase halfmove counter
+testfen 'check halfmove counter on check, white checks black: should increase halfmove counter'\
+ '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 squares between them'\
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 it'\
+ '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 fxe6 8. b3 Nbd7 9. Nd2 O-O-O 10. c4 Be7 11. c5 Qa5 12. a3 Ne4 13. Bxe4 Bxc5 14. b4 Qb5 15. Bc2 g5 16. Ba4 Qa6 17. Bxg5 Bd6 18. Bxd8 Rxd8 19. b5 cxb5 20. Rc1+ Bc5 21. Bxb5 Qxb5 22. dxc5 Nxc5 23. Nb3 b6 24. Nxc5 Qxc5 25. Rxc5+ bxc5 26. Qg4 c4 27. Qxe6+ Kc7 28. Nf3 a5 29. Nd4 Rd7 30. Qc6+ Kd8 31. Ne6+ Ke7 32. f4 Rd6 33. Qxd6+ Kxd6 34. f5 Kc6 35. Rg1 Kd6 36. h4 h6 37. h5 Kd7 38. Rg6 Ke7 39. Rxh6 a4 40. Rg6 Kd6 41. Rg5 c3 42. h6 Ke5 43. h7 Ke4 44. h8=Q Kd3 45. Qd4+ Kc2 46. Rg2+ Kb3 47. Nc5+ Kxa3 48. Qxa4#'\
+ 'startpos'\
+ 'd2d4 d7d5 c1f4 c7c6 e2e3 c8f5 f1d3 e7e6 g2g4 g8f6 g4f5 d8b6 f5e6 f7e6 b2b3 b8d7 b1d2 e8c8 c2c4 f8e7 c4c5 b6a5 a2a3 f6e4 d3e4 e7c5 b3b4 a5b5 e4c2 g7g5 c2a4 b5a6 f4g5 c5d6 g5d8 h8d8 b4b5 c6b5 a1c1 d6c5 a4b5 a6b5 d4c5 d7c5 d2b3 b7b6 b3c5 b5c5 c1c5 b6c5 d1g4 c5c4 g4e6 c8c7 g1f3 a7a5 f3d4 d8d7 e6c6 c7d8 d4e6 d8e7 f2f4 d7d6 c6d6 e7d6 f4f5 d6c6 h1g1 c6d6 h2h4 h7h6 h4h5 d6d7 g1g6 d7e7 g6h6 a5a4 h6g6 e7d6 g6g5 c4c3 h5h6 d6e5 h6h7 e5e4 h7h8q e4d3 h8d4 d3c2 g5g2 c2b3 e6c5 b3a3 d4a4'
+
+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