chess-puzzles

chess puzzle book generator
git clone git://git.codemadness.org/chess-puzzles
Log | Files | Refs | README | LICENSE

commit 52d9636da4a82de476f21b1c61de8b7570f96c92
parent 208dbcea3dd150fb2eb4ef1a8469acf9df2940ab
Author: Hiltjo Posthuma <hiltjo@codemadness.org>
Date:   Wed, 31 Jan 2024 21:09:22 +0100

add love theme and valentine puzzles

Diffstat:
Mfen.c | 11+++++++++++
Avalentine.sh | 318+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 329 insertions(+), 0 deletions(-)

diff --git a/fen.c b/fen.c @@ -61,6 +61,17 @@ struct theme themes[] = { .lightsquarecheck = { 0xff, 0x6a, 0x6a }, .darksquarecheck = { 0xff, 0x3a, 0x3a } }, +/* red / love theme */ +{ + .name = "love", + .border = { 0x33, 0x33, 0x33 }, + .darksquare = { 0xd9, 0x4c, 0x4c }, + .lightsquare = { 0xff, 0xca, 0xca }, + .darksquarehi = { 0xaa, 0xa2, 0x3a }, + .lightsquarehi = { 0xcd, 0xd2, 0x6a }, + .lightsquarecheck = { 0xff, 0x6a, 0x6a }, + .darksquarecheck = { 0xff, 0x3a, 0x3a } +}, /* greyscale theme, highlight is still green though */ { .name = "grey", diff --git a/valentine.sh b/valentine.sh @@ -0,0 +1,318 @@ +#!/bin/sh + +fenbin="./fen" +db="lichess_db_puzzle.csv" +# default, green, grey +theme="love" +lang="nl" # 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="Attraction puzzles for valentine 💕" +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" +fi + +# Dutch +if [ "$lang" = "nl" ]; then +text_solutions="Oplossingen" +text_solutionstxtlabel="Tekstbestand, lijst met oplossingen" +text_puzzles="Aantrekkingspuzzles voor valentijn 💕" +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" +fi + +if ! test -f "$db"; then + printf 'File "%s" not found, run `make db` to update it\n' "$db" >&2 + exit 1 +fi + +index="puzzles/index.html" +indexvt="puzzles/index.vt" + +# clean previous files. +rm -rf puzzles +mkdir -p puzzles/solutions + +solutions="$(mktemp)" +seedfile="$(mktemp)" +seed=20240201 # 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" + +# shuffle(file, amount) +shuffle() { + f="$1" + total="$2" + nlines="$(wc -l < "$f")" + nlines="$((nlines + 0))" + results="$(mktemp)" + +# generate list of lines to use. Not perfectly random but good enough. +LC_ALL=C awk -v "seed=$seed" -v "nlines=$nlines" -v "total=$total" ' +BEGIN { + srand(seed); + for (i = 0; i < total; i++) + sel[int(rand() * nlines)] = 1; +} +sel[NR] { + print $0; +}' "$f" > "$results" + + # now we have less results we can use the slow sort -R. + sort -R --random-source "$seedfile" "$results" + 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 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: 775px; + margin: 0 auto; + padding: 0 10px; + background-color: pink; + color: #ff0000; +} +a { + color: #ff0000; +} +h2 a { + color: #ff0000; + text-decoration: none; +} +h2 a:hover:after { + content: " #"; +} +.puzzle { + float: left; + margin-right: 25px; +} +footer { + clear: both; +} +details summary { + cursor: pointer; /* show hand */ +} +</style> +</head> +<body> +<header> +<h1>${text_header}</h1> +</header> +<main> +! + +# shuffle, some sort of order and point system based on rating of puzzle. +count=1 + +groupsdir="$(mktemp -d)" +test "$groupsdir" = "" && exit 1 + +grep 'attraction' "$db" > "$groupsdir/attraction.csv" +LC_ALL=C awk -F ',' 'int($4) < 1500 { print $0 }' "$groupsdir/attraction.csv" > "$groupsdir/attraction_lt_1500.csv" +LC_ALL=C awk -F ',' 'int($4) >= 1500 && int($4) < 2000 { print $0 }' "$groupsdir/attraction.csv" > "$groupsdir/attraction_lt_2000.csv" +LC_ALL=C awk -F ',' 'int($4) >= 2000 { print $0 }' "$groupsdir/attraction.csv" > "$groupsdir/attraction_ge_2000.csv" +( +shuffle "$groupsdir/attraction_lt_1500.csv" 100 | sed 20q | LC_ALL=C awk '{ print $0 ",1" }' +shuffle "$groupsdir/attraction_lt_2000.csv" 100 | sed 15q | LC_ALL=C awk '{ print $0 ",2" }' +shuffle "$groupsdir/attraction_ge_2000.csv" 100 | sed 5q | LC_ALL=C awk '{ print $0 ",3" }' +rm -rf "$groupsdir" +) | \ +while read -r line; do + i="$count" + fen=$(printf '%s' "$line" | cut -f 2 -d ',') + + tomove=$(printf '%s' "$line" | cut -f 2 -d ',' | cut -f 2 -d ' ') + allmoves="$(printf '%s' "$line" | cut -f 3 -d ',')" + firstmove=$(printf '%s' "$line" | cut -f 3 -d ',' | cut -f 1 -d ' ' ) # first move only. + rating=$(printf '%s' "$line" | cut -f 4 -d ',') + ratingdev=$(printf '%s' "$line" | cut -f 5 -d ',') + lichess=$(printf '%s' "$line" | cut -f 9 -d ',') + + case "$tomove" in + "w") tomove="w";; + "b") tomove="b";; + *) tomove="w";; # default + esac + + # first move is played so flip when white (not black). + flip="" + test "$tomove" = "w" && flip="-f" + + # 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" $fenopts -m "$pgnmapping" -t "$theme" $flip -o svg "$fen" "$firstmove" > "$destsvg" + "$fenbin" $fenopts -m "$pgnmapping" -t "$theme" $flip -o ascii "$fen" "$firstmove" > "$desttxt" + "$fenbin" $fenopts -m "$pgnmapping" -t "$theme" $flip -o tty "$fen" "$firstmove" > "$destvt" + "$fenbin" $fenopts -m "$pgnmapping" -t "$theme" $flip -o fen "$fen" "$firstmove" > "$destfen" + 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}" "$i" >> "$index" + test "$lichess" != "" && printf '<a href="%s">' "$lichess" >> "$index" + + title="" + test "$rating" != "" && title="${text_puzzlerating}: $rating" + + 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" + + movetext="" + # if there is a first move, inverse to move. + if test "$firstmove" != ""; then + case "$tomove" in + "w") movetext=", ${text_blacktomove}";; + "b") movetext=", ${text_whitetomove}";; + esac + else + case "$tomove" in + "w") movetext=", ${text_whitetomove}";; + "b") movetext=", ${text_blacktomove}";; + esac + fi + + printf '<p><b>%s</b>%s</p>\n' "$points" "$movetext" >> "$index" + 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}" "$i" >> "$solutions" + + m="${allmoves}" + movecount=0 + # create a move list, removing one move each step, for generating + # the solution images. + + # add initial puzzle aswell for context. + ptitlespeak="$("$fenbin" $fenopts -l -o speak "$fen" "$firstmove")" + printf '<img src="%s" width="180" height="180" loading="lazy" alt="%s" title="%s" />\n' \ + "${i}.svg" "$ptitlespeak" "$pgn, $ptitlespeak" >> "$solutions" + + # solution PGN + pgn_solution="$("$fenbin" $fenopts -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" + + echo "$m" + + m="${m% }" + m="${m%[^ ]*}" + m="${m% }" + + test "$prevmoves" = "$m" && break # same, break also + done | sort | while read -r movelist; do + # first move is already shown, skip it. + if test "$movecount" = "0"; then + movecount=$((movecount + 1)) + continue + fi + + # process move list in sequence. + destsolsvg="puzzles/solutions/${i}_${movecount}.svg" + "$fenbin" $fenopts -m "$pgnmapping" -t "$theme" $flip -o svg "$fen" "$movelist" > "$destsolsvg" + + # PGN of moves so far. + pgn="$("$fenbin" $fenopts $fenopts -l -m "$pgnmapping" -o pgn "$fen" "$movelist")" + ptitlespeak="$("$fenbin" $fenopts -l -o speak "$fen" "$movelist")" + + printf '<img src="%s" width="180" height="180" loading="lazy" alt="%s" title="%s" />\n' \ + "solutions/${i}_${movecount}.svg" "$ptitlespeak" "$pgn, $ptitlespeak" >> "$solutions" + + movecount=$((movecount + 1)) + done + + printf '<p><b>PGN:</b> %s</p>\n' "${pgn_solution}" >> "$solutions" + printf '</div>\n' >> "$solutions" + + 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" >> "$index" +printf '<p><a href="solutions.txt">%s</a></p>\n' "${text_solutionstxtlabel}" >> "$index" + +# 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" "$seedfile"