generate.sh - chess-puzzles - chess puzzle book generator | |
git clone git://git.codemadness.org/chess-puzzles | |
Log | |
Files | |
Refs | |
README | |
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"… | |
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.cs… | |
159 LC_ALL=C awk -F ',' 'int($4) >= 2000 { print $0 }' "$groupsdir/matein5.c… | |
160 LC_ALL=C awk -F ',' 'int($4) >= 2700 { print $0 }' "$groupsdir/matein5.c… | |
161 | |
162 ( | |
163 shuffle "$groupsdir/matein1.csv" 100 | sed 10q | LC_ALL=C awk '{ print $… | |
164 shuffle "$groupsdir/matein2.csv" 100 | sed 10q | LC_ALL=C awk '{ print $… | |
165 shuffle "$groupsdir/matein3.csv" 100 | sed 10q | LC_ALL=C awk '{ print $… | |
166 shuffle "$groupsdir/matein4.csv" 100 | sed 10q | LC_ALL=C awk '{ print $… | |
167 shuffle "$groupsdir/matein5_lt_2000.csv" 100 | sed 5q | LC_ALL=C awk '{ … | |
168 shuffle "$groupsdir/matein5_ge_2000.csv" 100 | sed 3q | LC_ALL=C awk '{ … | |
169 shuffle "$groupsdir/matein5_ge_2700.csv" 100 | sed 2q | LC_ALL=C awk '{ … | |
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 … | |
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 "$f… | |
210 "$fenbin" $fenopts -m "$pgnmapping" -t "$theme" $flip -o ascii "… | |
211 "$fenbin" $fenopts -m "$pgnmapping" -t "$theme" $flip -o tty "$f… | |
212 "$fenbin" $fenopts -m "$pgnmapping" -t "$theme" $flip -o fen "$f… | |
213 pgn=$("$fenbin" $fenopts -l -m "$pgnmapping" -o pgn "$fen" "$fir… | |
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_… | |
217 test "$lichess" != "" && printf '<a href="%s">' "$lichess" >> "$… | |
218 | |
219 title="" | |
220 test "$rating" != "" && title="${text_puzzlerating}: $rating" | |
221 | |
222 printf '<img src="%s" alt="%s #%s" title="%s" width="360" height… | |
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_… | |
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" al… | |
262 "${i}.svg" "$ptitlespeak" "$pgn, $ptitlespeak" >> "$solu… | |
263 | |
264 # solution PGN | |
265 pgn_solution="$("$fenbin" $fenopts -m "$pgnmapping" -o pgn "$fen… | |
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… | |
292 | |
293 # PGN of moves so far. | |
294 pgn="$("$fenbin" $fenopts $fenopts -l -m "$pgnmapping" -… | |
295 ptitlespeak="$("$fenbin" $fenopts -l -o speak "$fen" "$m… | |
296 | |
297 printf '<img src="%s" width="180" height="180" loading="… | |
298 "solutions/${i}_${movecount}.svg" "$ptitlespeak"… | |
299 | |
300 movecount=$((movecount + 1)) | |
301 done | |
302 | |
303 printf '<p><b>PGN:</b> %s</p>\n' "${pgn_solution}" >> "$solution… | |
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_sol… | |
316 printf '<p><a href="solutions.txt">%s</a></p>\n' "${text_solutionstxtlab… | |
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" |