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:
M | fen.c | | | 11 | +++++++++++ |
A | valentine.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"