valentine.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
---
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"