Introduction
Introduction Statistics Contact Development Disclaimer Help
various improvements and features - chess-puzzles - chess puzzle book generator
git clone git://git.codemadness.org/chess-puzzles
Log
Files
Refs
README
LICENSE
---
commit 1f750e11a33213399d0ae7f8817d52f35cc8b6b8
parent 0fe1f6ac19819dd3954b8520db31a66f76d7cf87
Author: Hiltjo Posthuma <[email protected]>
Date: Sat, 6 Jan 2024 15:40:35 +0100
various improvements and features
... do all the things and do them meh.
- ambigous moves improvements and fixes.
- add "speak" mode, describe a move in human-like language (dutch and english).
- add -l option to only output the PGN or description of the last move, useful
for speech engines.
- FEN output: reset en passant square on checkmate (like Lichess does).
- lichess stream script: add description of the move and use espeak to say it.
- add a page of the puzzles intended for the tty (index.vt).
- index.html improvements:
- CSS tweaks to better align 2 puzzles in the center.
- hyperlink color.
- add simple plain-text listing in solutions.txt file.
- add title and alt of the solutions moves in PGN and as a description.
- tests:
- add more of them.
- return exitcode 0 or 1 on a failure.
- prefix type of test: PGN or FEN.
Diffstat:
M TODO | 19 +++++++------------
M docs/stream_lichess.sh | 16 +++++++++++++++-
M fen.1 | 11 +++++++++--
M fen.c | 376 ++++++++++++++++++++++-------…
M generate.sh | 97 ++++++++++++++++++++++-------…
M tests.sh | 185 +++++++++++++++++++++++++++++…
6 files changed, 546 insertions(+), 158 deletions(-)
---
diff --git a/TODO b/TODO
@@ -1,12 +1,7 @@
-arbitrary test: en-passant defend against mate
-https://lichess.org/editor/rnbqkbnr/pppppppp/8/1P1PP3/2PKQ3/2PQQ3/P1PP1PPP/RNB…
-
-
-another with pawn removed: cannot defend because then we are in check
-https://lichess.org/editor/rnbqkbnr/pppppppp/8/3PP3/2PKQ3/2PQQ3/P1PP1PPP/RNBQ1…
-
-
-
+? pgnnotation make function?
+? PGN output: add game termination state?
+ - PGN output: add stalemate?
+ - PGN output: but what if resign, time-out, draw offer?
? canpiecemove(): en passant take (if not in check afterwards).
? ischeckmated(): check en passant take to defend checkmate.
@@ -23,11 +18,11 @@ after the moving piece's name (in that order of preference)…
specifies that the knight originally on the g-file moves to e2. "
? read PGN to moves?
+ - input and output piece mapping?
-x 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.
+ - test more chess960 black kingside and queenside castling
+ - test more long sequence and halfmove and movenumber counts
diff --git a/docs/stream_lichess.sh b/docs/stream_lichess.sh
@@ -28,6 +28,7 @@ curl \
-H "Authorization: Bearer $token" \
-H 'Accept: application/x-ndjson' "$url" | \
while read -r json; do
+ moveplayed="0"
if [ "$firstline" = "1" ]; then
firstline="0"
@@ -60,15 +61,28 @@ END {
fi
str=$(printf '%s' "$json" | jaq '$1 == ".moves" { print $3; }')
- test "$str" != "" && moves="$str" # override
+ if [ "$str" != "" ]; then
+ moves="$str" # override
+ moveplayed="1"
+ fi
clear
printf '%s\n\n' "$header"
./fen -o tty "$fen" "$moves"
+ if [ "$moveplayed" = "1" ]; then
+ speaktext="$(./fen -l -o speak "$fen" "$moves")"
+ fi
+ printf '\n%s\n' "$speaktext"
+
printf '\nMoves:\n'
printf '%s\n' "$moves"
printf '\nPGN:\n'
./fen -o pgn "$fen" "$moves"
+
+ # audio
+ if [ "$moveplayed" = "1" ]; then
+ (printf '%s\n' "$speaktext" | espeak) &
+ fi
done
diff --git a/fen.1 b/fen.1
@@ -1,4 +1,4 @@
-.Dd January 4, 2024
+.Dd January 5, 2024
.Dt FEN 1
.Os
.Sh NAME
@@ -7,8 +7,9 @@
.Sh SYNOPSIS
.Nm
.Op Fl cCfF
+.Op Fl l
.Op Fl m mapping
-.Op Fl o Ar ascii | fen | pgn | svg | tty
+.Op Fl o Ar ascii | fen | pgn | speak | svg | tty
.Op Fl t theme
.Op Ar FEN
.Op Ar moves
@@ -28,6 +29,9 @@ Disable board coordinates.
Flip the board, default is off.
.It Fl F
Do not flip the board.
+.It Fl l
+For PGN and speak mode only output the last move.
+For PGN this will not prefix the move number.
.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:
@@ -43,6 +47,9 @@ FEN of the board state after playing the moves.
FEN of the board state after playing the moves.
.It pgn
PGN output of the moves for the board.
+.It speak
+Write each move per line as text to stdout.
+Intended to be piped to speech applications.
.It svg
SVG image of the board.
.It tty
diff --git a/fen.c b/fen.c
@@ -15,9 +15,11 @@
#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 { ModeInvalid = 0, ModeASCII, ModeCGI, ModeFEN, ModePGN, ModeT…
+enum outputmode { ModeInvalid = 0, ModeASCII, ModeCGI, ModeFEN, ModePGN, ModeT…
enum outputmode outputmode = ModeSVG;
+static int onlylastmove = 0, silent = 0, dutchmode = 0;
+
/* localization of letter for PGN pieces */
const char *pgn_piecemapping = "";
@@ -91,12 +93,12 @@ struct board {
struct theme *theme;
};
+/* set theme by name */
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];
@@ -109,7 +111,7 @@ board_set_theme(struct board *b, const char *name)
void
board_init(struct board *b)
{
- memset(b, 0, sizeof(*b)); /* zeroed fields by default */
+ memset(b, 0, sizeof(*b)); /* zero fields by default */
b->side_to_move = 'w';
b->enpassantsquare[0] = -1; /* no en passant */
b->enpassantsquare[1] = -1;
@@ -125,41 +127,6 @@ board_copy(struct board *bd, struct board *bs)
memcpy(bd, bs, sizeof(*bd));
}
-void
-pgn(const char *fmt, ...)
-{
- va_list ap;
-
- if (outputmode != ModePGN)
- return;
-
- va_start(ap, fmt);
- vprintf(fmt, ap);
- 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)
{
@@ -224,6 +191,67 @@ squaretoxy(const char *s, int *x, int *y)
return 0;
}
+void
+pgn(const char *fmt, ...)
+{
+ va_list ap;
+
+ if (outputmode != ModePGN || silent)
+ return;
+
+ va_start(ap, fmt);
+ vprintf(fmt, ap);
+ 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;
+}
+
+void
+speak(const char *fmt, ...)
+{
+ va_list ap;
+
+ if (outputmode != ModeSpeak || silent)
+ return;
+
+ va_start(ap, fmt);
+ vprintf(fmt, ap);
+ va_end(ap);
+}
+
+void
+speakpiece(int piece)
+{
+ switch (piece) {
+ case 'K': case 'k': speak(dutchmode ? "koning " : "king "); break;
+ case 'Q': case 'q': speak(dutchmode ? "dame " : "queen "); break;
+ case 'R': case 'r': speak(dutchmode ? "toren " : "rook "); break;
+ case 'B': case 'b': speak(dutchmode ? "loper " : "bishop "); break;
+ case 'N': case 'n': speak(dutchmode ? "paard " : "knight "); break;
+ case 'P': case 'p': speak(dutchmode ? "pion " : "pawn "); break;
+ }
+}
+
/* place a piece, if possible */
void
place(struct board *b, int piece, int x, int y)
@@ -639,6 +667,19 @@ findking(struct board *b, int side, int *kingx, int *kingy)
}
int
+isenpassantplayed(struct board *b, int side, int x, int y)
+{
+ if (side == 'w') {
+ return (getpiece(b, x - 1, y) == 'p' ||
+ getpiece(b, x + 1, y) == 'p');
+ } else if (side == 'b') {
+ return (getpiece(b, x - 1, y) == 'P' ||
+ getpiece(b, x + 1, y) == 'P');
+ }
+ return 0;
+}
+
+int
isincheck(struct board *b, int side)
{
int king[] = { -1, -1, -1, 0, -1, 1, 0, -1, 0, 1, 1, -1, 1, 0, 1, 1 …
@@ -724,11 +765,17 @@ isincheck(struct board *b, int side)
}
int
-trypiecemove(struct board *b, int side, int piece, int x1, int y1, int x2, int…
+trypiecemove(struct board *b, int side, int piece,
+ int x1, int y1, int x2, int y2, int px, int py)
{
struct board tb;
board_copy(&tb, b);
+
+ /* taken en passant? remove pawn */
+ if (x2 == px && y2 == py)
+ place(&tb, 0, x2, piece == 'P' ? y2 + 1 : y2 - 1);
+
place(&tb, 0, x1, y1);
place(&tb, piece, x2, y2);
@@ -739,7 +786,8 @@ trypiecemove(struct board *b, int side, int piece, int x1, …
/* 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…
+canpiecemove(struct board *b, int side, int piece,
+ 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 };
@@ -748,9 +796,9 @@ canpiecemove(struct board *b, int side, int x1, int y1, int…
-2, -1, 2, -1, -2, 1, 2, 1
};
int i, j, dir, x, y;
- int piece, takepiece;
+ int takepiece;
- if (!(piece = getpiece(b, x1, y1)))
+ if (!piece)
return 0; /* theres no piece so it cannot be moved */
/* can't move opponent piece */
@@ -781,7 +829,7 @@ canpiecemove(struct board *b, int side, int x1, int y1, int…
y = y1 + (i * line[j + 1]);
if (x == x2 && y == y2 &&
- trypiecemove(b, side, piece, x1, y1, x2, y…
+ trypiecemove(b, side, piece, x1, y1, x2, y…
return 1;
/* a piece is in front of it: stop this checki…
@@ -798,7 +846,7 @@ canpiecemove(struct board *b, int side, int x1, int y1, int…
x = x1 + (i * diag[j]);
y = y1 + (i * diag[j + 1]);
if (x == x2 && y == y2 &&
- trypiecemove(b, side, piece, x1, y1, x2, y…
+ trypiecemove(b, side, piece, x1, y1, x2, y…
return 1;
/* a piece is in front of it: stop this checki…
@@ -822,6 +870,15 @@ canpiecemove(struct board *b, int side, int x1, int y1, in…
dir = piece == 'P' ? -1 : +1;
j = piece == 'P' ? 6 : 1; /* start row */
+ /* can move to en passant square? */
+ /* en passant set? try it if possible */
+ if (px == x2 && py == y2 &&
+ (py == y1 + dir) &&
+ ((px == x1 - 1) || px == x1 + 1)) {
+ if (isenpassantplayed(b, side == 'w' ? 'b' : 'w', px, …
+ return trypiecemove(b, side, piece, x1, y1, x2…
+ }
+
if (takepiece == 0) {
if (x1 != x2)
return 0; /* move on same file */
@@ -848,13 +905,16 @@ canpiecemove(struct board *b, int side, int x1, int y1, i…
/* previous checks for move succeeded, actually try move with the current
board state */
trymove:
- return trypiecemove(b, side, piece, x1, y1, x2, y2);
+ return trypiecemove(b, side, piece, x1, y1, x2, y2, px, py);
}
int
ischeckmated(struct board *b, int side)
{
- int x, y, x2, y2, piece;
+ int x, y, x2, y2, px, py, piece;
+
+ px = b->enpassantsquare[0];
+ py = b->enpassantsquare[0];
/* check pieces that can block or take a piece that removes the check …
for (y = 0; y < 8; y++) {
@@ -867,7 +927,7 @@ ischeckmated(struct board *b, int side)
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…
+ if (canpiecemove(b, side, piece, x, y,…
return 0;
}
}
@@ -978,13 +1038,54 @@ board_setup_fen(struct board *b, const char *fen)
}
}
+/* count ambiguity for piece moves, used to make the notation shorter */
+void
+countambigousmoves(struct board *b, int side, int piece,
+ int x, int y, int x2, int y2, int px, int py,
+ int *countfile, int *countrank, int *countboard)
+{
+ int cf = 0, cr = 0, cb = 0, i, j;
+
+ /* check same file */
+ for (i = 0; i < 8; i++) {
+ if (getpiece(b, i, y) == piece &&
+ canpiecemove(b, side, piece, i, y, x2, y2, px, py))
+ cf++;
+ }
+
+ /* check same rank */
+ for (i = 0; i < 8; i++) {
+ if (getpiece(b, x, i) == piece &&
+ canpiecemove(b, side, piece, x, i, x2, y2, px, py))
+ cr++;
+ }
+
+ /* check whole board */
+ if (cf <= 1 && cr <= 1) {
+ /* check the whole board if there is any piece
+ that can move to the same square */
+ for (i = 0; i < 8; i++) {
+ for (j = 0; j < 8; j++) {
+ if (getpiece(b, i, j) == piece &&
+ canpiecemove(b, side, piece, i, j, x2, y2,…
+ cb++;
+ }
+ }
+ }
+
+ *countfile = cf;
+ *countrank = cr;
+ *countboard = cb;
+}
+
void
board_playmoves(struct board *b, const char *moves)
{
char square[3];
const char *castled, *s;
- int firstmove, i, j, x, y, x2, y2, otherside, piece, takepiece, tookpi…
- int needfile, needrank, promote;
+ int firstmove, i, x, y, x2, y2, side, otherside, piece, takepiece, too…
+ int countfile, countrank, countboard, px, py;
+ int promote, tookeps;
/* process moves */
square[2] = '\0';
@@ -1002,17 +1103,24 @@ board_playmoves(struct board *b, const char *moves)
(*(s + 3) >= '1' && *(s + 3) <= '8')))
continue;
- otherside = b->side_to_move == 'b' ? 'w' : 'b';
+ /* is last move in this sequence? */
+ if (onlylastmove && !strchr(s, ' '))
+ silent = 0;
+
+ side = b->side_to_move;
+ otherside = side == 'b' ? 'w' : 'b';
/* if first move and it is blacks turn, prefix
with "...", because the white move was unknown */
- if (firstmove && b->side_to_move == 'b')
+ if (!onlylastmove && firstmove && side == 'b')
pgn("%d. ... ", b->movenumber);
- if (firstmove)
+ if (firstmove && !silent) {
firstmove = 0;
- else
+ } else {
pgn(" ");
+ speak("\n");
+ }
square[0] = *s;
square[1] = *(s + 1);
@@ -1038,8 +1146,8 @@ board_playmoves(struct board *b, const char *moves)
}
/* took piece of opponent */
- tookpiece = (b->side_to_move == 'w' && isblackpiece(takepiece)…
- (b->side_to_move == 'b' && iswhitepiece(takepiece)…
+ tookpiece = (side == 'w' && isblackpiece(takepiece)) ||
+ (side == 'b' && iswhitepiece(takepiece));
/* if pawn move or taken a piece increase halfmove counter */
if (piece == 'p' || piece == 'P' || tookpiece)
@@ -1047,7 +1155,7 @@ board_playmoves(struct board *b, const char *moves)
else
b->halfmove++;
- if (b->side_to_move == 'w')
+ if (!onlylastmove && side == 'w')
pgn("%d. ", b->movenumber);
/* castled this move? */
@@ -1061,7 +1169,7 @@ board_playmoves(struct board *b, const char *moves)
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); /* …
+ place(b, 'R', x2 - 1, y2); /* …
castled = "O-O";
break;
}
@@ -1071,8 +1179,8 @@ board_playmoves(struct board *b, const char *moves)
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…
+ place(b, 'R', x2 + 1, y2); /* …
castled = "O-O-O";
break;
}
@@ -1085,7 +1193,7 @@ board_playmoves(struct board *b, const char *moves)
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); /* …
+ place(b, 'r', x2 - 1, y2); /* …
castled = "O-O";
break;
}
@@ -1095,14 +1203,20 @@ board_playmoves(struct board *b, const char *moves)
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…
+ place(b, 'r', x2 + 1, y2); /* …
castled = "O-O-O";
break;
}
}
}
}
+ if (castled) {
+ /* set previous move square (for highlight) */
+ x = i;
+ y = y2;
+ place(b, piece, x2, y2); /* place king */
+ }
/* remove the ability to castle */
if (piece == 'K') {
@@ -1132,95 +1246,139 @@ board_playmoves(struct board *b, const char *moves)
}
/* taken en passant? */
- if (x2 == b->enpassantsquare[0] && y2 == b->enpassantsquare[1])
+ tookeps = 0;
+ if (x2 == b->enpassantsquare[0] && y2 == b->enpassantsquare[1]…
+ (piece == 'P' || piece == 'p')) {
+ /* clear square */
place(b, 0, x2, piece == 'P' ? y2 + 1 : y2 - 1);
+ /* set a piece is taken */
+ tookpiece = 1;
+ takepiece = piece == 'P' ? 'p' : 'P';
+ tookeps = 1;
+ }
/* the en passant square resets after a move */
- b->enpassantsquare[0] = -1;
- b->enpassantsquare[1] = -1;
+ px = b->enpassantsquare[0] = -1;
+ py = 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;
+ if (isenpassantplayed(b, side, x, y2)) {
+ px = b->enpassantsquare[0] = x;
+ py = 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 (isenpassantplayed(b, side, x, y2)) {
+ px = b->enpassantsquare[0] = x;
+ py = b->enpassantsquare[1] = 2;
}
}
- /* if output is PGN, show piece movement, else skip this step …
- if (outputmode == ModePGN) {
- /* PGN for move */
+ /* PGN for move, if output is not PGN then skip this step */
+ if (outputmode == ModePGN || outputmode == ModeSpeak) {
if (castled) {
pgn("%s", castled);
+
+ if (side == 'w')
+ speak(dutchmode ? "wit " : "white ");
+ else if (side == 'b')
+ speak(dutchmode ? "zwart " : "black ");
+
+ if (!strcmp(castled, "O-O"))
+ speak(dutchmode ? "rokeert aan konings…
+ else
+ speak(dutchmode ? "rokeert aan damezij…
} else {
+ if (side == 'w')
+ speak(dutchmode ? "witte " : "white ");
+ else if (side == 'b')
+ speak(dutchmode ? "zwarte " : "black "…
+
+ if (!tookpiece) {
+ if (!dutchmode)
+ speak("moves ");
+ speakpiece(piece);
+ }
+
/* pawn move needs no notation */
- if (piece != 'p' && piece != 'P') {
+ if (piece != 'p' && piece != 'P')
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));
- }
+ /* check ambiguity for certain pieces and make…
+ countambigousmoves(b, side, piece, x, y, x2, y…
+ &countfile, &countrank, &countboard);
+
+ if (countfile > 1) {
+ pgn("%c", xtofile(x));
+ speak("%c", xtofile(x));
}
+ if (countrank > 1) {
+ pgn("%c", ytorank(y));
+ speak("%c", ytorank(y));
+ }
+ if (countboard > 1) {
+ pgn("%c", xtofile(x));
+ speak("%c", xtofile(x));
+ }
+ if (countfile > 1 || countrank > 1 || countboa…
+ speak(" ");
+
if (tookpiece) {
/* pawn captures are prefixed by the f…
if (piece == 'p' || piece == 'P')
pgn("%c", xtofile(x));
pgn("x");
+ speakpiece(piece);
+ speak(dutchmode ? "slaat " : "takes ");
+ speakpiece(takepiece);
+ speak(dutchmode ? "op " : "on ");
+ speak("%c%c ", xtofile(x2), ytorank(y2…
+ if (tookeps)
+ speak("en passant ");
+ } else {
+ speak(dutchmode ? "naar " : "to ");
+ speak("%c%c ", xtofile(x2), ytorank(y2…
}
pgn("%c%c", xtofile(x2), ytorank(y2));
- /* possible promotion: queen, knight, bishop */
+ /* possible promotion: queen, rook, bishop, kn…
if (promote) {
- if (b->side_to_move == 'w')
+ if (side == 'w')
piece = toupper(promote);
else
piece = tolower(promote);
place(b, piece, x2, y2);
+ speak(dutchmode ? "en promoot naar " :…
+ speakpiece(promote);
+
pgn("=%c", pgnpiece(piece));
}
}
}
/* clear previous square (if not castled) */
- if (!castled)
+ if (!castled) {
place(b, 0, x, y);
- /* place piece or new promoted piece */
- place(b, piece, x2, y2);
+ /* place piece or new promoted piece */
+ place(b, piece, x2, y2);
+ }
+
+ if (ischeckmated(b, otherside)) {
+ /* reset en passant square on checkmate */
+ b->enpassantsquare[0] = -1;
+ b->enpassantsquare[1] = -1;
- if (ischeckmated(b, otherside))
pgn("#");
- else if (isincheck(b, otherside))
+ speak(dutchmode ? "mat" : "checkmate");
+ } else if (isincheck(b, otherside)) {
pgn("+");
+ speak(dutchmode ? "schaak" : "check");
+ }
/* a move by black increases the move number */
- if (b->side_to_move == 'b')
+ if (side == 'b')
b->movenumber++;
/* switch which side it is to move */
@@ -1230,8 +1388,10 @@ board_playmoves(struct board *b, const char *moves)
break;
}
- if (!firstmove)
+ if (!firstmove) {
pgn("\n");
+ speak("\n");
+ }
/* highlight last move */
highlightmove(b, x, y);
@@ -1246,7 +1406,7 @@ board_playmoves(struct board *b, const char *moves)
void
usage(char *argv0)
{
- fprintf(stderr, "usage: %s [-cCfF] [-m mapping] [-o ascii|fen|pgn|svg|…
+ fprintf(stderr, "usage: %s [-cCfF] [-l] [-m mapping] [-o ascii|fen|pgn…
exit(1);
}
@@ -1325,6 +1485,8 @@ outputnametomode(const char *s)
return ModeFEN;
else if (!strcmp(s, "pgn"))
return ModePGN;
+ else if (!strcmp(s, "speak"))
+ return ModeSpeak;
else if (!strcmp(s, "svg"))
return ModeSVG;
else if (!strcmp(s, "tty"))
@@ -1433,8 +1595,10 @@ main(int argc, char *argv[])
switch (argv[i][j]) {
case 'c': board.showcoords = 1; break;
case 'C': board.showcoords = 0; break;
+ case 'd': dutchmode = 1; break; /* top secret dutch mo…
case 'f': board.flipboard = 1; break;
case 'F': board.flipboard = 0; break;
+ case 'l': onlylastmove = 1; silent = 1; break;
case 'm': /* remap PGN */
if (i + 1 >= argc)
usage(argv[0]);
diff --git a/generate.sh b/generate.sh
@@ -4,10 +4,14 @@ fenbin="./fen"
db="lichess_db_puzzle.csv"
# default, green, grey
theme="default"
+lang="en" # en, nl
+#fenopts="-d" # dutch mode (for speak output)
# texts / localization.
# English
+if [ "$lang" = "en" ]; then
text_solutions="Solutions"
+text_solutionstxtlabel="Text listing of solutions"
text_puzzles="Puzzles"
text_puzzle="Puzzle"
text_puzzlerating="Puzzel rating"
@@ -18,20 +22,24 @@ text_blacktomove="black to move"
text_title="${text_puzzles}"
text_header="${text_puzzles}!"
pgnmapping="KQRBN"
+fi
# 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}!"
+if [ "$lang" = "nl" ]; then
+text_solutions="Oplossingen"
+text_solutionstxtlabel="Tekstbestand, lijst met 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"
+pgnmapping="KDTLP"
+fi
if ! test -f "$db"; then
printf 'File "%s" not found, run `make db` to update it\n' "$db" >&2
@@ -39,6 +47,9 @@ if ! test -f "$db"; then
fi
index="puzzles/index.html"
+indexvt="puzzles/index.vt"
+
+# clean previous files.
rm -rf puzzles
mkdir -p puzzles/solutions
@@ -73,19 +84,31 @@ sel[NR] {
rm -f "$results"
}
+# solutions.txt header.
+solutionstxt="puzzles/solutions.txt"
+printf '%s\n\n' "${text_solutions}" >> "$solutionstxt"
+
+cat > "$indexvt" <<!
+${text_header}
+
+!
+
cat > "$index" <<!
<!DOCTYPE html>
-<html>
+<html dir="ltr" lang="${lang}">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>${text_title}</title>
<style type="text/css">
body {
font-family: sans-serif;
- width: 960px;
+ width: 775px;
margin: 0 auto;
padding: 0 10px;
}
+a {
+ color: #000;
+}
h2 a {
color: #000;
text-decoration: none;
@@ -108,7 +131,7 @@ details summary {
background-color: #000;
color: #bdbdbd;
}
- h2 a {
+ h2 a, a {
color: #bdbdbd;
}
}
@@ -183,11 +206,11 @@ while read -r line; do
desttxt="puzzles/$txt"
destvt="puzzles/$vt"
- "$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")
+ "$fenbin" $fenopts -m "$pgnmapping" -t "$theme" $flip -o svg "$fen" "$…
+ "$fenbin" $fenopts -m "$pgnmapping" -t "$theme" $flip -o ascii "$fen" …
+ "$fenbin" $fenopts -m "$pgnmapping" -t "$theme" $flip -o tty "$fen" "$…
+ "$fenbin" $fenopts -m "$pgnmapping" -t "$theme" $flip -o fen "$fen" "$…
+ pgn=$("$fenbin" $fenopts -l -m "$pgnmapping" -o pgn "$fen" "$firstmove…
printf '<div class="puzzle" id="puzzle-%s">\n' "$i" >> "$index"
printf '<h2><a href="#puzzle-%s">%s %s</a></h2>\n' "$i" "${text_puzzle…
@@ -219,6 +242,11 @@ while read -r line; do
printf '%s%s\n' "$points" "$movetext" >> "$desttxt"
printf '\n%s%s\n' "$points" "$movetext" >> "$destvt"
+ # vt
+ printf 'Puzzle %s\n\n' "$i" >> "$indexvt"
+ cat "$destvt" >> "$indexvt"
+ printf '\n\n' >> "$indexvt"
+
# solutions per puzzle.
printf '<div class="puzzle-solution">\n' >> "$solutions"
printf '<h2><a href="#puzzle-%s">%s %s</a></h2>\n' "$i" "${text_puzzle…
@@ -229,11 +257,12 @@ while read -r line; do
# the solution images.
# add initial puzzle aswell for context.
- printf '<img src="%s" width="180" height="180" loading="lazy" title="%…
- "${i}.svg" "$pgn" >> "$solutions"
+ ptitlespeak="$("$fenbin" $fenopts -l -o speak "$fen" "$firstmove")"
+ printf '<img src="%s" width="180" height="180" loading="lazy" alt="%s"…
+ "${i}.svg" "$ptitlespeak" "$pgn, $ptitlespeak" >> "$solutions"
# solution PGN
- pgn_solution="$($fenbin -m "$pgnmapping" -o pgn "$fen" "$allmoves")"
+ pgn_solution="$("$fenbin" $fenopts -m "$pgnmapping" -o pgn "$fen" "$al…
destsolpgn="puzzles/solutions/${i}.pgn"
printf '%s\n' "$pgn_solution" > "$destsolpgn"
@@ -260,13 +289,14 @@ while read -r line; do
# process move list in sequence.
destsolsvg="puzzles/solutions/${i}_${movecount}.svg"
- "$fenbin" -m "$pgnmapping" -t "$theme" $flip -o svg "$fen" "$m…
+ "$fenbin" $fenopts -m "$pgnmapping" -t "$theme" $flip -o svg "…
# PGN of moves so far.
- pgn="$($fenbin -m "$pgnmapping" -o pgn "$fen" "$movelist")"
+ pgn="$("$fenbin" $fenopts $fenopts -l -m "$pgnmapping" -o pgn …
+ ptitlespeak="$("$fenbin" $fenopts -l -o speak "$fen" "$movelis…
- printf '<img src="%s" width="180" height="180" loading="lazy" …
- "solutions/${i}_${movecount}.svg" "$pgn" >> "$solution…
+ printf '<img src="%s" width="180" height="180" loading="lazy" …
+ "solutions/${i}_${movecount}.svg" "$ptitlespeak" "$pgn…
movecount=$((movecount + 1))
done
@@ -276,19 +306,30 @@ while read -r line; do
printf '</div>\n' >> "$index"
+ # add PGN solution to solutions text file.
+ printf '%s. %s\n' "$i" "${pgn_solution}" >> "$solutionstxt"
+
count=$((count + 1))
done
# solutions / spoilers
printf '<footer><br/><br/><details>\n<summary>%s</summary>\n' "$text_solutions…
+printf '<p><a href="solutions.txt">%s</a></p>\n' "${text_solutionstxtlabel}" >…
+
+# add solutions HTML to index page.
cat "$solutions" >> "$index"
echo "</details>\n<br/><br/></footer>\n" >> "$index"
+# add solutions to vt index page.
+printf '\n\n\n\n\n\n\n\n\n\n' >> "$indexvt"
+printf '\n\n\n\n\n\n\n\n\n\n' >> "$indexvt"
+printf '\n\n\n\n\n' >> "$indexvt"
+cat "$solutionstxt" >> "$indexvt"
+
cat >> "$index" <<!
</main>
</body>
</html>
!
-rm -f "$solutions"
-rm -f "$seedfile"
+rm -f "$solutions" "$seedfile"
diff --git a/tests.sh b/tests.sh
@@ -1,5 +1,8 @@
#!/bin/sh
+statuscode=0
+failed=0
+
# testfen(name, expect, fen, moves)
testfen() {
name="$1"
@@ -7,14 +10,24 @@ testfen() {
fen="$3"
moves="$4"
+ # input FEN with no moves should match output FEN (except "startpos").
+ output=$(./fen -o fen "$fen" "")
+ if test "$fen" != "startpos" && test "$fen" != "$output"; then
+ printf '[FEN] Fail: %s, input FEN does not match output FEN\n'…
+ statuscode=1
+ failed=$((failed+1))
+ fi
+
output=$(./fen -o fen "$fen" "$moves")
if test "$output" = "$expect"; then
- printf 'OK: %s\n' "$name"
+ printf '[FEN] OK: %s\n' "$name"
else
- printf 'Fail: %s\n' "$name"
+ printf '[FEN] Fail: %s\n' "$name"
printf '\texpected: %s\n' "$expect"
printf '\tgot: %s\n' "$output"
printf '\tInput FEN, moves: "%s" "%s"\n' "$fen" "$moves"
+ statuscode=1
+ failed=$((failed+1))
fi
}
@@ -27,12 +40,14 @@ testpgn() {
output=$(./fen -o pgn "$fen" "$moves")
if test "$output" = "$expect"; then
- printf 'OK: %s\n' "$name"
+ printf '[PGN] OK: %s\n' "$name"
else
- printf 'Fail: %s\n' "$name"
+ printf '[PGN] Fail: %s\n' "$name"
printf '\texpected: %s\n' "$expect"
printf '\tgot: %s\n' "$output"
printf '\tInput FEN, moves: "%s" "%s"\n' "$fen" "$moves"
+ statuscode=1
+ failed=$((failed+1))
fi
}
@@ -232,8 +247,15 @@ testfen 'black moves pawn en passant into checkmate (cant …
'rnbqkbnr/p1p1pppp/1p6/3PP3/2PKQ3/2PQQ3/P1PP1PPP/RNBQ1BNR b kq - 0 1'\
'c7c5'
-# TODO: test more chess960 black kingside and queenside castling
-# TODO: test more long sequence and halfmove and movenumber counts
+testfen 'white is checkmated (en passant square is not set), do not remove paw…
+ 'rnbqkbnr/p2ppppp/1pP5/1Pp1P3/2PKQ3/2PQQ3/P1PP1PPP/RNBQ1BNR b kq - 0 1…
+ 'rnbqkbnr/p2ppppp/1p6/1PpPP3/2PKQ3/2PQQ3/P1PP1PPP/RNBQ1BNR w kq - 0 1'\
+ 'd5c6'
+
+testfen 'white is not checkmated (en passant square is set and can be played),…
+ 'rnbqkbnr/p2ppppp/1pP5/1P2P3/2PKQ3/2PQQ3/P1PP1PPP/RNBQ1BNR b kq - 0 1'\
+ 'rnbqkbnr/p2ppppp/1p6/1PpPP3/2PKQ3/2PQQ3/P1PP1PPP/RNBQ1BNR w kq c6 0 1…
+ 'd5c6'
}
tests_pgn() {
@@ -280,8 +302,8 @@ testpgn 'black moves with knight, non-ambigous move'\
'rn2kb1r/pp4pp/1qp1p3/3p4/3P1B2/1P1BP3/P1P2P1P/RN1QK1NR b KQkq - 0 8'\
'b8d7'
-testpgn '2 queens, ambigous move, needs file and rank'\
- '8. Qh3g3'\
+testpgn '2 queens, ambigous move, needs rank'\
+ '8. Q3g3'\
'rn2kb1r/pp4pp/1qp1p3/3p4/3P1B1Q/1P1BP2Q/P1P2P1P/RN2K1NR w KQkq - 0 8'\
'h3g3'
@@ -314,11 +336,156 @@ testpgn 'black moves pawn en passant into checkmate (can…
'c7c5'
# check also if the en passant square is set (but it is not legal to play).
-testpgn 'black moves pawn en passant into checkmate (cant take en passant to d…
+testpgn 'black moves pawn en passant into checkmate (cant take en passant to d…
'1. ... c5#'\
'rnbqkbnr/p1p1pppp/1p6/3PP3/2PKQ3/2PQQ3/P1PP1PPP/RNBQ1BNR b kq c6 0 1'\
'c7c5'
+
+testpgn 'Knights on the same rank can move to the same square'\
+ '1. Nde5'\
+ 'rnbqkbnr/pppppppp/8/8/8/3N1N2/PPPPPPPP/R1BQKB1R w KQkq - 0 1'\
+ 'd3e5'
+
+testpgn 'Knights on the same rank can move to the same square, part 2'\
+ '1. Nfe5'\
+ 'rnbqkbnr/pppppppp/8/8/8/3N1N2/PPPPPPPP/R1BQKB1R w KQkq - 0 1'\
+ 'f3e5'
+
+testpgn 'Knights on the same same file can move to the same square'\
+ '1. N3c4'\
+ 'rnbqkbnr/pppppppp/8/4N3/8/4N3/PPPPPPPP/R1BQKB1R w KQkq - 0 1'\
+ 'e3c4'
+
+testpgn 'Knights on the same same file can move to the same square, part 2'\
+ '1. N5c4'\
+ 'rnbqkbnr/pppppppp/8/4N3/8/4N3/PPPPPPPP/R1BQKB1R w KQkq - 0 1'\
+ 'e5c4'
+
+testpgn 'Knights on the same same file can move to the same square, part 3'\
+ '1. N5g4'\
+ 'rnbqkbnr/pppppppp/8/4N3/8/4N3/PPPPPPPP/R1BQKB1R w KQkq - 0 1'\
+ 'e5g4'
+
+testpgn 'Rook on the same file can move to the same square'\
+ '1. R3d4'\
+ 'rnbqkbnr/pppppppp/3R4/8/8/3R4/PPPPPPPP/1NBQKBN1 w kq - 0 1'\
+ 'd3d4'
+
+testpgn 'Rook on the same file can move to the same square, part 2'\
+ '1. R6d4'\
+ 'rnbqkbnr/pppppppp/3R4/8/8/3R4/PPPPPPPP/1NBQKBN1 w kq - 0 1'\
+ 'd6d4'
+
+testpgn 'Rook on the same rank can move to the same square'\
+ '1. Rge4'\
+ 'rnbqkbnr/pppppppp/8/8/2R3R1/8/PPPPPPPP/1NBQKBN1 w kq - 0 1'\
+ 'g4e4'
+
+testpgn 'Rook on the same rank can move to the same square, part 2'\
+ '1. Rce4'\
+ 'rnbqkbnr/pppppppp/8/8/2R3R1/8/PPPPPPPP/1NBQKBN1 w kq - 0 1'\
+ 'c4e4'
+
+testpgn 'Rook on the same rank can take on the same square (with check), part …
+ '1. Rgxe4+'\
+ 'rnbqkbnr/pppp1ppp/8/8/2R1p1R1/8/PPPPPPPP/1NBQKBN1 w kq - 0 1'\
+ 'g4e4'
+
+testpgn 'Rook on the same rank can take on the same square (with check), part …
+ '1. Rcxe4+'\
+ 'rnbqkbnr/pppp1ppp/8/8/2R1p1R1/8/PPPPPPPP/1NBQKBN1 w kq - 0 1'\
+ 'c4e4'
+
+testpgn 'Knights on the same same file can take on the same square'\
+ '1. N3xc4'\
+ 'rnbqkbnr/pppppppp/8/4N3/2p5/4N3/PPPPPPPP/R1BQKB1R w KQkq - 0 1'\
+ 'e3c4'
+
+testpgn 'Knights on the same same file can take on the same square, part 2'\
+ '1. N5xc4'\
+ 'rnbqkbnr/pppppppp/8/4N3/2p5/4N3/PPPPPPPP/R1BQKB1R w KQkq - 0 1'\
+ 'e5c4'
+
+testpgn 'Knights on same files and ranks move to same square'\
+ '1. Nf3e5'\
+ 'rnbqkbnr/pppNpNpp/8/8/8/3N1N2/PPPPPPPP/R1BQKB1R w KQkq - 0 1'\
+ 'f3e5'
+
+testpgn 'Knights on same files and ranks move to same square, part 2'\
+ '1. Nd7e5'\
+ 'rnbqkbnr/pppNpNpp/8/8/8/3N1N2/PPPPPPPP/R1BQKB1R w KQkq - 0 1'\
+ 'd7e5'
+
+testpgn 'Knights on same files and ranks move to same square, part 3'\
+ '1. Nd3e5'\
+ 'rnbqkbnr/pppNpNpp/8/8/8/3N1N2/PPPPPPPP/R1BQKB1R w KQkq - 0 1'\
+ 'd3e5'
+
+testpgn '4 knights that can move to the same square, but not on the same file …
+ '1. Nfe5'\
+ 'rnbqkbnr/ppp1pNpp/2N5/8/6N1/3N4/PPPPPPPP/R1BQKB1R w KQkq - 0 1'\
+ 'f7e5'
+
+testpgn '4 knights that can move to the same square, but not on the same file …
+ '1. Nce5'\
+ 'rnbqkbnr/ppp1pNpp/2N5/8/6N1/3N4/PPPPPPPP/R1BQKB1R w KQkq - 0 1'\
+ 'c6e5'
+
+testpgn '4 knights that can move to the same square, but not on the same file …
+ '1. Nde5'\
+ 'rnbqkbnr/ppp1pNpp/2N5/8/6N1/3N4/PPPPPPPP/R1BQKB1R w KQkq - 0 1'\
+ 'd3e5'
+
+testpgn '4 knights that can move to the same square, but not on the same file …
+ '1. Nge5'\
+ 'rnbqkbnr/ppp1pNpp/2N5/8/6N1/3N4/PPPPPPPP/R1BQKB1R w KQkq - 0 1'\
+ 'g4e5'
+
+testpgn 'Move bishop, 2 bishops can move to the same square'\
+ '1. Bge4'\
+ 'rnbqkbnr/pppppppp/6B1/8/8/3B4/PPPP1PPP/RNBQK1NR w KQkq - 0 1'\
+ 'g6e4'
+
+testpgn 'Move bishop, 2 bishops can move to the same square, part 2'\
+ '1. Bde4'\
+ 'rnbqkbnr/pppppppp/6B1/8/8/3B4/PPPP1PPP/RNBQK1NR w KQkq - 0 1'\
+ 'd3e4'
+
+testpgn 'Move bishop, 2 bishops can move to the same square, part 3'\
+ '1. Bde4'\
+ 'rnbqkbnr/pppppppp/2B3B1/8/8/2BB4/PPPP1PPP/RNBQK1NR w KQkq - 0 1'\
+ 'd3e4'
+
+# white takes en passant
+testpgn 'white takes en passant'\
+ '3. dxe6'\
+ 'rnbqkbnr/pppp1pp1/8/3Pp2p/8/8/PPP1PPPP/RNBQKBNR w KQkq e6 0 3'\
+ 'd5e6'
+# black takes en passant
+testpgn 'black takes en passant'\
+ '3. ... hxg3'\
+ 'rnbqkbnr/ppppppp1/8/8/3P2Pp/4P3/PPP2P1P/RNBQKBNR b KQkq g3 0 3'\
+ 'h4g3'
+
+# NOTE: this is ambigous, but this is not dc6, because the en passant square i…
+testpgn 'white is checkmated (en passant square is not set), do not remove paw…
+ '1. c6'\
+ 'rnbqkbnr/p2ppppp/1p6/1PpPP3/2PKQ3/2PQQ3/P1PP1PPP/RNBQ1BNR w kq - 0 1'\
+ 'd5c6'
+
+testpgn 'white is not checkmated (en passant square is set and can be played),…
+ '1. dxc6'\
+ 'rnbqkbnr/p2ppppp/1p6/1PpPP3/2PKQ3/2PQQ3/P1PP1PPP/RNBQ1BNR w kq c6 0 1…
+ 'd5c6'
}
tests_fen
tests_pgn
+
+if test "$statuscode" = "1"; then
+ echo "$failed tests failed"
+else
+ echo "All tests OK"
+fi
+
+exit "$statuscode"
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.