valentine.sh (8866B)
1 #!/bin/sh 2 3 fenbin="./fen" 4 db="lichess_db_puzzle.csv" 5 # default, green, grey 6 theme="love" 7 lang="nl" # en, nl 8 fenopts="-d" # dutch mode (for speak output) 9 10 # texts / localization. 11 # English 12 if [ "$lang" = "en" ]; then 13 text_solutions="Solutions" 14 text_solutionstxtlabel="Text listing of solutions" 15 text_puzzles="Attraction puzzles for valentine 💕" 16 text_puzzle="Puzzle" 17 text_puzzlerating="Puzzel rating" 18 text_point="point" 19 text_points="points" 20 text_whitetomove="white to move" 21 text_blacktomove="black to move" 22 text_title="${text_puzzles}" 23 text_header="${text_puzzles}!" 24 pgnmapping="KQRBN" 25 fi 26 27 # Dutch 28 if [ "$lang" = "nl" ]; then 29 text_solutions="Oplossingen" 30 text_solutionstxtlabel="Tekstbestand, lijst met oplossingen" 31 text_puzzles="Aantrekkingspuzzles voor valentijn 💕" 32 text_puzzle="Puzzel" 33 text_puzzlerating="Puzzel moeilijkheidsgraad" 34 text_point="punt" 35 text_points="punten" 36 text_whitetomove="wit aan zet" 37 text_blacktomove="zwart aan zet" 38 text_title="${text_puzzles}" 39 text_header="${text_puzzles}!" 40 # Dutch: (K)oning, (D)ame, (T)oren, (L)oper, (P)aard. 41 pgnmapping="KDTLP" 42 fi 43 44 if ! test -f "$db"; then 45 printf 'File "%s" not found, run `make db` to update it\n' "$db" >&2 46 exit 1 47 fi 48 49 index="puzzles/index.html" 50 indexvt="puzzles/index.vt" 51 52 # clean previous files. 53 rm -rf puzzles 54 mkdir -p puzzles/solutions 55 56 solutions="$(mktemp)" 57 seedfile="$(mktemp)" 58 seed=20240201 # must be a integer value 59 # seed for random sorting, makes it deterministic for the same system 60 # seed must be sufficiently long. 61 echo "${seed}_chess_puzzles" > "$seedfile" 62 63 # shuffle(file, amount) 64 shuffle() { 65 f="$1" 66 total="$2" 67 nlines="$(wc -l < "$f")" 68 nlines="$((nlines + 0))" 69 results="$(mktemp)" 70 71 # generate list of lines to use. Not perfectly random but good enough. 72 LC_ALL=C awk -v "seed=$seed" -v "nlines=$nlines" -v "total=$total" ' 73 BEGIN { 74 srand(seed); 75 for (i = 0; i < total; i++) 76 sel[int(rand() * nlines)] = 1; 77 } 78 sel[NR] { 79 print $0; 80 }' "$f" > "$results" 81 82 # now we have less results we can use the slow sort -R. 83 sort -R --random-source "$seedfile" "$results" 84 rm -f "$results" 85 } 86 87 # solutions.txt header. 88 solutionstxt="puzzles/solutions.txt" 89 printf '%s\n\n' "${text_solutions}" >> "$solutionstxt" 90 91 cat > "$indexvt" <<! 92 ${text_header} 93 94 ! 95 96 cat > "$index" <<! 97 <!DOCTYPE html> 98 <html dir="ltr" lang="${lang}"> 99 <head> 100 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> 101 <title>${text_title}</title> 102 <style type="text/css"> 103 body { 104 font-family: sans-serif; 105 width: 775px; 106 margin: 0 auto; 107 padding: 0 10px; 108 background-color: pink; 109 color: #ff0000; 110 } 111 a { 112 color: #ff0000; 113 } 114 h2 a { 115 color: #ff0000; 116 text-decoration: none; 117 } 118 h2 a:hover:after { 119 content: " #"; 120 } 121 .puzzle { 122 float: left; 123 margin-right: 25px; 124 } 125 footer { 126 clear: both; 127 } 128 details summary { 129 cursor: pointer; /* show hand */ 130 } 131 </style> 132 </head> 133 <body> 134 <header> 135 <h1>${text_header}</h1> 136 </header> 137 <main> 138 ! 139 140 # shuffle, some sort of order and point system based on rating of puzzle. 141 count=1 142 143 groupsdir="$(mktemp -d)" 144 test "$groupsdir" = "" && exit 1 145 146 grep 'attraction' "$db" > "$groupsdir/attraction.csv" 147 LC_ALL=C awk -F ',' 'int($4) < 1500 { print $0 }' "$groupsdir/attraction.csv" > "$groupsdir/attraction_lt_1500.csv" 148 LC_ALL=C awk -F ',' 'int($4) >= 1500 && int($4) < 2000 { print $0 }' "$groupsdir/attraction.csv" > "$groupsdir/attraction_lt_2000.csv" 149 LC_ALL=C awk -F ',' 'int($4) >= 2000 { print $0 }' "$groupsdir/attraction.csv" > "$groupsdir/attraction_ge_2000.csv" 150 ( 151 shuffle "$groupsdir/attraction_lt_1500.csv" 100 | sed 20q | LC_ALL=C awk '{ print $0 ",1" }' 152 shuffle "$groupsdir/attraction_lt_2000.csv" 100 | sed 15q | LC_ALL=C awk '{ print $0 ",2" }' 153 shuffle "$groupsdir/attraction_ge_2000.csv" 100 | sed 5q | LC_ALL=C awk '{ print $0 ",3" }' 154 rm -rf "$groupsdir" 155 ) | \ 156 while read -r line; do 157 i="$count" 158 fen=$(printf '%s' "$line" | cut -f 2 -d ',') 159 160 tomove=$(printf '%s' "$line" | cut -f 2 -d ',' | cut -f 2 -d ' ') 161 allmoves="$(printf '%s' "$line" | cut -f 3 -d ',')" 162 firstmove=$(printf '%s' "$line" | cut -f 3 -d ',' | cut -f 1 -d ' ' ) # first move only. 163 rating=$(printf '%s' "$line" | cut -f 4 -d ',') 164 ratingdev=$(printf '%s' "$line" | cut -f 5 -d ',') 165 lichess=$(printf '%s' "$line" | cut -f 9 -d ',') 166 167 case "$tomove" in 168 "w") tomove="w";; 169 "b") tomove="b";; 170 *) tomove="w";; # default 171 esac 172 173 # first move is played so flip when white (not black). 174 flip="" 175 test "$tomove" = "w" && flip="-f" 176 177 # added field: points 178 points=$(printf '%s' "$line" | cut -f "11" -d ',') 179 if [ "$points" = "1" ]; then 180 points="$points ${text_point}" 181 else 182 points="$points ${text_points}" 183 fi 184 185 img="$i.svg" 186 txt="$i.txt" 187 vt="$i.vt" 188 destfen="puzzles/$i.fen" 189 destsvg="puzzles/$img" 190 desttxt="puzzles/$txt" 191 destvt="puzzles/$vt" 192 193 "$fenbin" $fenopts -m "$pgnmapping" -t "$theme" $flip -o svg "$fen" "$firstmove" > "$destsvg" 194 "$fenbin" $fenopts -m "$pgnmapping" -t "$theme" $flip -o ascii "$fen" "$firstmove" > "$desttxt" 195 "$fenbin" $fenopts -m "$pgnmapping" -t "$theme" $flip -o tty "$fen" "$firstmove" > "$destvt" 196 "$fenbin" $fenopts -m "$pgnmapping" -t "$theme" $flip -o fen "$fen" "$firstmove" > "$destfen" 197 pgn=$("$fenbin" $fenopts -l -m "$pgnmapping" -o pgn "$fen" "$firstmove") 198 199 printf '<div class="puzzle" id="puzzle-%s">\n' "$i" >> "$index" 200 printf '<h2><a href="#puzzle-%s">%s %s</a></h2>\n' "$i" "${text_puzzle}" "$i" >> "$index" 201 test "$lichess" != "" && printf '<a href="%s">' "$lichess" >> "$index" 202 203 title="" 204 test "$rating" != "" && title="${text_puzzlerating}: $rating" 205 206 printf '<img src="%s" alt="%s #%s" title="%s" width="360" height="360" loading="lazy" />' \ 207 "$img" "${text_puzzle}" "$i" "$title" >> "$index" 208 test "$lichess" != "" && printf '</a>' >> "$index" 209 echo "" >> "$index" 210 211 movetext="" 212 # if there is a first move, inverse to move. 213 if test "$firstmove" != ""; then 214 case "$tomove" in 215 "w") movetext=", ${text_blacktomove}";; 216 "b") movetext=", ${text_whitetomove}";; 217 esac 218 else 219 case "$tomove" in 220 "w") movetext=", ${text_whitetomove}";; 221 "b") movetext=", ${text_blacktomove}";; 222 esac 223 fi 224 225 printf '<p><b>%s</b>%s</p>\n' "$points" "$movetext" >> "$index" 226 printf '%s%s\n' "$points" "$movetext" >> "$desttxt" 227 printf '\n%s%s\n' "$points" "$movetext" >> "$destvt" 228 229 # vt 230 printf 'Puzzle %s\n\n' "$i" >> "$indexvt" 231 cat "$destvt" >> "$indexvt" 232 printf '\n\n' >> "$indexvt" 233 234 # solutions per puzzle. 235 printf '<div class="puzzle-solution">\n' >> "$solutions" 236 printf '<h2><a href="#puzzle-%s">%s %s</a></h2>\n' "$i" "${text_puzzle}" "$i" >> "$solutions" 237 238 m="${allmoves}" 239 movecount=0 240 # create a move list, removing one move each step, for generating 241 # the solution images. 242 243 # add initial puzzle aswell for context. 244 ptitlespeak="$("$fenbin" $fenopts -l -o speak "$fen" "$firstmove")" 245 printf '<img src="%s" width="180" height="180" loading="lazy" alt="%s" title="%s" />\n' \ 246 "${i}.svg" "$ptitlespeak" "$pgn, $ptitlespeak" >> "$solutions" 247 248 # solution PGN 249 pgn_solution="$("$fenbin" $fenopts -m "$pgnmapping" -o pgn "$fen" "$allmoves")" 250 251 destsolpgn="puzzles/solutions/${i}.pgn" 252 printf '%s\n' "$pgn_solution" > "$destsolpgn" 253 254 # printf 'DEBUG: #%s: "%s" "%s"\n' "$i" "$fen" "$allmoves" >&2 255 256 while [ "$m" != "" ]; do 257 prevmoves="$m" 258 259 echo "$m" 260 261 m="${m% }" 262 m="${m%[^ ]*}" 263 m="${m% }" 264 265 test "$prevmoves" = "$m" && break # same, break also 266 done | sort | while read -r movelist; do 267 # first move is already shown, skip it. 268 if test "$movecount" = "0"; then 269 movecount=$((movecount + 1)) 270 continue 271 fi 272 273 # process move list in sequence. 274 destsolsvg="puzzles/solutions/${i}_${movecount}.svg" 275 "$fenbin" $fenopts -m "$pgnmapping" -t "$theme" $flip -o svg "$fen" "$movelist" > "$destsolsvg" 276 277 # PGN of moves so far. 278 pgn="$("$fenbin" $fenopts $fenopts -l -m "$pgnmapping" -o pgn "$fen" "$movelist")" 279 ptitlespeak="$("$fenbin" $fenopts -l -o speak "$fen" "$movelist")" 280 281 printf '<img src="%s" width="180" height="180" loading="lazy" alt="%s" title="%s" />\n' \ 282 "solutions/${i}_${movecount}.svg" "$ptitlespeak" "$pgn, $ptitlespeak" >> "$solutions" 283 284 movecount=$((movecount + 1)) 285 done 286 287 printf '<p><b>PGN:</b> %s</p>\n' "${pgn_solution}" >> "$solutions" 288 printf '</div>\n' >> "$solutions" 289 290 printf '</div>\n' >> "$index" 291 292 # add PGN solution to solutions text file. 293 printf '%s. %s\n' "$i" "${pgn_solution}" >> "$solutionstxt" 294 295 count=$((count + 1)) 296 done 297 298 # solutions / spoilers 299 printf '<footer><br/><br/><details>\n<summary>%s</summary>\n' "$text_solutions" >> "$index" 300 printf '<p><a href="solutions.txt">%s</a></p>\n' "${text_solutionstxtlabel}" >> "$index" 301 302 # add solutions HTML to index page. 303 cat "$solutions" >> "$index" 304 echo "</details>\n<br/><br/></footer>\n" >> "$index" 305 306 # add solutions to vt index page. 307 printf '\n\n\n\n\n\n\n\n\n\n' >> "$indexvt" 308 printf '\n\n\n\n\n\n\n\n\n\n' >> "$indexvt" 309 printf '\n\n\n\n\n' >> "$indexvt" 310 cat "$solutionstxt" >> "$indexvt" 311 312 cat >> "$index" <<! 313 </main> 314 </body> 315 </html> 316 ! 317 318 rm -f "$solutions" "$seedfile"