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" |