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"