add love theme and valentine puzzles - 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 52d9636da4a82de476f21b1c61de8b7570f96c92
 (DIR) parent 208dbcea3dd150fb2eb4ef1a8469acf9df2940ab
 (HTM) Author: Hiltjo Posthuma <hiltjo@codemadness.org>
       Date:   Wed, 31 Jan 2024 21:09:22 +0100
       
       add love theme and valentine puzzles
       
       Diffstat:
         M fen.c                               |      11 +++++++++++
         A valentine.sh                        |     318 +++++++++++++++++++++++++++++++
       
       2 files changed, 329 insertions(+), 0 deletions(-)
       ---
 (DIR) 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",
 (DIR) 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"