generate.sh - 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
       ---
       generate.sh (9356B)
       ---
            1 #!/bin/sh
            2 
            3 fenbin="./fen"
            4 db="lichess_db_puzzle.csv"
            5 # default, green, grey
            6 theme="default"
            7 lang="en" # 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="Puzzles"
           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="Puzzels"
           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=20240101 # 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 }
          109 a {
          110         color: #000;
          111 }
          112 h2 a {
          113         color: #000;
          114         text-decoration: none;
          115 }
          116 h2 a:hover:after {
          117         content: " #";
          118 }
          119 .puzzle {
          120         float: left;
          121         margin-right: 25px;
          122 }
          123 footer {
          124         clear: both;
          125 }
          126 details summary {
          127         cursor: pointer; /* show hand */
          128 }
          129 @media (prefers-color-scheme: dark) {
          130         body {
          131                 background-color: #000;
          132                 color: #bdbdbd;
          133         }
          134         h2 a, a {
          135                 color: #bdbdbd;
          136         }
          137 }
          138 </style>
          139 </head>
          140 <body>
          141 <header>
          142 <h1>${text_header}</h1>
          143 </header>
          144 <main>
          145 !
          146 
          147 # shuffle, some sort of order and point system based on rating of puzzle.
          148 count=1
          149 
          150 groupsdir="$(mktemp -d)"
          151 test "$groupsdir" = "" && exit 1
          152 
          153 grep 'mateIn1' "$db" > "$groupsdir/matein1.csv"
          154 grep 'mateIn2' "$db" > "$groupsdir/matein2.csv"
          155 grep 'mateIn3' "$db" > "$groupsdir/matein3.csv"
          156 grep 'mateIn4' "$db" > "$groupsdir/matein4.csv"
          157 grep 'mateIn5' "$db" > "$groupsdir/matein5.csv"
          158 LC_ALL=C awk -F ',' 'int($4) < 2000 { print $0 }' "$groupsdir/matein5.csv" > "$groupsdir/matein5_lt_2000.csv"
          159 LC_ALL=C awk -F ',' 'int($4) >= 2000 { print $0 }' "$groupsdir/matein5.csv" > "$groupsdir/matein5_ge_2000.csv"
          160 LC_ALL=C awk -F ',' 'int($4) >= 2700 { print $0 }' "$groupsdir/matein5.csv" > "$groupsdir/matein5_ge_2700.csv"
          161 
          162 (
          163 shuffle "$groupsdir/matein1.csv" 100 | sed 10q | LC_ALL=C awk '{ print $0 ",1" }'
          164 shuffle "$groupsdir/matein2.csv" 100 | sed 10q | LC_ALL=C awk '{ print $0 ",2" }'
          165 shuffle "$groupsdir/matein3.csv" 100 | sed 10q | LC_ALL=C awk '{ print $0 ",3" }'
          166 shuffle "$groupsdir/matein4.csv" 100 | sed 10q | LC_ALL=C awk '{ print $0 ",4" }'
          167 shuffle "$groupsdir/matein5_lt_2000.csv" 100 | sed 5q | LC_ALL=C awk '{ print $0 ",5" }'
          168 shuffle "$groupsdir/matein5_ge_2000.csv" 100 | sed 3q | LC_ALL=C awk '{ print $0 ",7" }'
          169 shuffle "$groupsdir/matein5_ge_2700.csv" 100 | sed 2q | LC_ALL=C awk '{ print $0 ",10" }'
          170 rm -rf "$groupsdir"
          171 ) | \
          172 while read -r line; do
          173         i="$count"
          174         fen=$(printf '%s' "$line" | cut -f 2 -d ',')
          175 
          176         tomove=$(printf '%s' "$line" | cut -f 2 -d ',' | cut -f 2 -d ' ')
          177         allmoves="$(printf '%s' "$line" | cut -f 3 -d ',')"
          178         firstmove=$(printf '%s' "$line" | cut -f 3 -d ',' | cut -f 1 -d ' ' ) # first move only.
          179         rating=$(printf '%s' "$line" | cut -f 4 -d ',')
          180         ratingdev=$(printf '%s' "$line" | cut -f 5 -d ',')
          181         lichess=$(printf '%s' "$line" | cut -f 9 -d ',')
          182 
          183         case "$tomove" in
          184         "w") tomove="w";;
          185         "b") tomove="b";;
          186         *) tomove="w";; # default
          187         esac
          188 
          189         # first move is played so flip when white (not black).
          190         flip=""
          191         test "$tomove" = "w" && flip="-f"
          192 
          193         # added field: points
          194         points=$(printf '%s' "$line" | cut -f "11" -d ',')
          195         if [ "$points" = "1" ]; then
          196                 points="$points ${text_point}"
          197         else
          198                 points="$points ${text_points}"
          199         fi
          200 
          201         img="$i.svg"
          202         txt="$i.txt"
          203         vt="$i.vt"
          204         destfen="puzzles/$i.fen"
          205         destsvg="puzzles/$img"
          206         desttxt="puzzles/$txt"
          207         destvt="puzzles/$vt"
          208 
          209         "$fenbin" $fenopts -m "$pgnmapping" -t "$theme" $flip -o svg "$fen" "$firstmove" > "$destsvg"
          210         "$fenbin" $fenopts -m "$pgnmapping" -t "$theme" $flip -o ascii "$fen" "$firstmove" > "$desttxt"
          211         "$fenbin" $fenopts -m "$pgnmapping" -t "$theme" $flip -o tty "$fen" "$firstmove" > "$destvt"
          212         "$fenbin" $fenopts -m "$pgnmapping" -t "$theme" $flip -o fen "$fen" "$firstmove" > "$destfen"
          213         pgn=$("$fenbin" $fenopts -l -m "$pgnmapping" -o pgn "$fen" "$firstmove")
          214 
          215         printf '<div class="puzzle" id="puzzle-%s">\n' "$i" >> "$index"
          216         printf '<h2><a href="#puzzle-%s">%s %s</a></h2>\n' "$i" "${text_puzzle}" "$i" >> "$index"
          217         test "$lichess" != "" && printf '<a href="%s">' "$lichess" >> "$index"
          218 
          219         title=""
          220         test "$rating" != "" && title="${text_puzzlerating}: $rating"
          221 
          222         printf '<img src="%s" alt="%s #%s" title="%s" width="360" height="360" loading="lazy" />' \
          223                 "$img" "${text_puzzle}" "$i" "$title" >> "$index"
          224         test "$lichess" != "" && printf '</a>' >> "$index"
          225         echo "" >> "$index"
          226 
          227         movetext=""
          228         # if there is a first move, inverse to move.
          229         if test "$firstmove" != ""; then
          230                 case "$tomove" in
          231                 "w") movetext=", ${text_blacktomove}";;
          232                 "b") movetext=", ${text_whitetomove}";;
          233                 esac
          234         else
          235                 case "$tomove" in
          236                 "w") movetext=", ${text_whitetomove}";;
          237                 "b") movetext=", ${text_blacktomove}";;
          238                 esac
          239         fi
          240 
          241         printf '<p><b>%s</b>%s</p>\n' "$points" "$movetext" >> "$index"
          242         printf '%s%s\n' "$points" "$movetext" >> "$desttxt"
          243         printf '\n%s%s\n' "$points" "$movetext" >> "$destvt"
          244 
          245         # vt
          246         printf 'Puzzle %s\n\n' "$i" >> "$indexvt"
          247         cat "$destvt" >> "$indexvt"
          248         printf '\n\n' >> "$indexvt"
          249 
          250         # solutions per puzzle.
          251         printf '<div class="puzzle-solution">\n' >> "$solutions"
          252         printf '<h2><a href="#puzzle-%s">%s %s</a></h2>\n' "$i" "${text_puzzle}" "$i" >> "$solutions"
          253 
          254         m="${allmoves}"
          255         movecount=0
          256         # create a move list, removing one move each step, for generating
          257         # the solution images.
          258 
          259         # add initial puzzle aswell for context.
          260         ptitlespeak="$("$fenbin" $fenopts -l -o speak "$fen" "$firstmove")"
          261         printf '<img src="%s" width="180" height="180" loading="lazy" alt="%s" title="%s" />\n' \
          262                 "${i}.svg" "$ptitlespeak" "$pgn, $ptitlespeak" >> "$solutions"
          263 
          264         # solution PGN
          265         pgn_solution="$("$fenbin" $fenopts -m "$pgnmapping" -o pgn "$fen" "$allmoves")"
          266 
          267         destsolpgn="puzzles/solutions/${i}.pgn"
          268         printf '%s\n' "$pgn_solution" > "$destsolpgn"
          269 
          270 #        printf 'DEBUG: #%s: "%s" "%s"\n' "$i" "$fen" "$allmoves" >&2
          271 
          272         while [ "$m" != "" ]; do
          273                 prevmoves="$m"
          274 
          275                 echo "$m"
          276 
          277                 m="${m% }"
          278                 m="${m%[^ ]*}"
          279                 m="${m% }"
          280 
          281                 test "$prevmoves" = "$m" && break # same, break also
          282         done | sort | while read -r movelist; do
          283                 # first move is already shown, skip it.
          284                 if test "$movecount" = "0"; then
          285                         movecount=$((movecount + 1))
          286                         continue
          287                 fi
          288 
          289                 # process move list in sequence.
          290                 destsolsvg="puzzles/solutions/${i}_${movecount}.svg"
          291                 "$fenbin" $fenopts -m "$pgnmapping" -t "$theme" $flip -o svg "$fen" "$movelist" > "$destsolsvg"
          292 
          293                 # PGN of moves so far.
          294                 pgn="$("$fenbin" $fenopts $fenopts -l -m "$pgnmapping" -o pgn "$fen" "$movelist")"
          295                 ptitlespeak="$("$fenbin" $fenopts -l -o speak "$fen" "$movelist")"
          296 
          297                 printf '<img src="%s" width="180" height="180" loading="lazy" alt="%s" title="%s" />\n' \
          298                         "solutions/${i}_${movecount}.svg" "$ptitlespeak" "$pgn, $ptitlespeak" >> "$solutions"
          299 
          300                 movecount=$((movecount + 1))
          301         done
          302 
          303         printf '<p><b>PGN:</b> %s</p>\n' "${pgn_solution}" >> "$solutions"
          304         printf '</div>\n' >> "$solutions"
          305 
          306         printf '</div>\n' >> "$index"
          307 
          308         # add PGN solution to solutions text file.
          309         printf '%s. %s\n' "$i" "${pgn_solution}" >> "$solutionstxt"
          310 
          311         count=$((count + 1))
          312 done
          313 
          314 # solutions / spoilers
          315 printf '<footer><br/><br/><details>\n<summary>%s</summary>\n' "$text_solutions" >> "$index"
          316 printf '<p><a href="solutions.txt">%s</a></p>\n' "${text_solutionstxtlabel}" >> "$index"
          317 
          318 # add solutions HTML to index page.
          319 cat "$solutions" >> "$index"
          320 echo "</details>\n<br/><br/></footer>\n" >> "$index"
          321 
          322 # add solutions to vt index page.
          323 printf '\n\n\n\n\n\n\n\n\n\n' >> "$indexvt"
          324 printf '\n\n\n\n\n\n\n\n\n\n' >> "$indexvt"
          325 printf '\n\n\n\n\n' >> "$indexvt"
          326 cat "$solutionstxt" >> "$indexvt"
          327 
          328 cat >> "$index" <<!
          329 </main>
          330 </body>
          331 </html>
          332 !
          333 
          334 rm -f "$solutions" "$seedfile"