1 # -*- coding: utf-8 -*-
\r
3 Created on Thu Jan 6 23:35:23 2022
\r
5 @author: Avram Gottschlich
\r
8 I copied several functions from your code;
\r
9 if it's easier to refer to them rather than copying them that's absolutely fine
\r
11 This rewrites the html each time it is called
\r
12 If it's easy to call the puzzle/round functions each time they're updated,
\r
15 Requires sorttable.js, which should be included
\r
17 from turbot.channel import channel_url
\r
18 from boto3.dynamodb.conditions import Key
\r
20 website = "https://halibut.cworth.org/"
\r
21 #change this if we're using AWS or some other subdomain instead
\r
23 def find_hunt_for_hunt_id(turb, hunt_id):
\r
24 """Given a hunt ID find the database item for that hunt
\r
26 Returns None if hunt ID is not found, otherwise a
\r
27 dictionary with all fields from the hunt's row in the table,
\r
28 (channel_id, active, hunt_id, name, url, sheet_url, etc.).
\r
30 response = turb.table.get_item(
\r
33 'SK': 'hunt-{}'.format(hunt_id)
\r
36 if 'Item' in response:
\r
37 return response['Item']
\r
41 def hunt_puzzles_for_hunt_id(turb, hunt_id):
\r
42 """Return all puzzles that belong to the given hunt_id"""
\r
44 response = turb.table.query(
\r
45 KeyConditionExpression=(
\r
46 Key('hunt_id').eq(hunt_id) &
\r
47 Key('SK').begins_with('puzzle-')
\r
51 return response['Items']
\r
53 def elink(lin, text):
\r
54 #shortcutting function for creating html links
\r
55 #opens in a new tab for external links
\r
56 return '<a href="{}" target="_blank" rel="noreferrer noopener">{}</a>'.format(lin, text)
\r
58 def link(lin, text):
\r
59 #internal links, doesn't open new tab
\r
60 return '<a href="{}">{}</a>'.format(lin, text)
\r
62 def hunt_info(turb, hunt):
\r
64 Retrieves list of rounds, puzzles for the given hunt
\r
67 hunt_id = hunt["hunt_id"]
\r
68 channel_id = hunt["channel_id"]
\r
70 puzzles = hunt_puzzles_for_hunt_id(turb, hunt_id)
\r
73 for puzzle in puzzles:
\r
74 if "rounds" not in puzzle:
\r
76 for rnd in puzzle["rounds"]:
\r
78 rounds = list(rounds)
\r
81 return puzzles, rounds
\r
83 def round_stat(rnd, puzzles):
\r
84 #Counts puzzles, solved, list of puzzles for a given round
\r
88 unsolved_puzzles = []
\r
91 for puzzle in puzzles:
\r
92 if "rounds" not in puzzle:
\r
94 if rnd in puzzle["rounds"]:
\r
95 if puzzle['type'] == 'meta':
\r
96 metas.append(puzzle)
\r
97 if puzzle['status'] == 'solved':
\r
101 if puzzle['status'] == 'solved':
\r
103 solved_puzzles.append(puzzle)
\r
105 unsolved_puzzles.append(puzzle)
\r
106 solved_puzzles = sorted(solved_puzzles, key = lambda i: i['name'])
\r
107 unsolved_puzzles = sorted(unsolved_puzzles, key = lambda i: i['name'])
\r
108 rnd_puzzles = metas + unsolved_puzzles + solved_puzzles
\r
109 return puzzle_count, solved_count, rnd_puzzles, meta_solved, len(metas)
\r
113 def overview(puzzles, rounds):
\r
114 #big board, main page. saves as index.html
\r
115 start = ['<!DOCTYPE html>\n',
\r
118 ' <meta charset="utf-8">\n',
\r
119 ' <meta name="viewport" content="width=device-width, initial-scale=1">\n',
\r
121 ' <link rel="stylesheet" href="overview.css">\n',
\r
122 ' <script type="text/javascript">\n',
\r
123 ' // Hide all elements with class="containerTab", except for the one that matches the clickable grid column\n',
\r
124 ' function openTab(tabName) {\n',
\r
126 ' x = document.getElementsByClassName("containerTab");\n',
\r
127 ' for (i = 0; i < x.length; i++) {\n',
\r
128 ' x[i].style.display = "none";\n',
\r
130 ' document.getElementById(tabName).style.display = "block";\n',
\r
134 ' <title>Hunt Overview</title>\n',
\r
135 ' <script src="sorttable.js"></script>\n'
\r
137 ' <div class="sidenav">\n'
\r
138 ' <a href="index.html">Hunt Overview</a>'
\r
139 ' <a href="all.html">All Puzzles</a>\n'
\r
140 ' <a href="unsolved.html">Unsolved</a>\n'
\r
141 ' <a href="solved.html">Solved</a>\n'
\r
144 columns = [' <div class="row">\n']
\r
148 puzzle_count, solved_count, rnd_puzzles, meta_solved, metas = round_stat(rnd, puzzles)
\r
149 if metas == meta_solved and metas > 0:
\r
152 status = 'unsolved'
\r
153 columns += [' <div class="column {}" onclick="openTab(\'b{}\');">\n'.format(status, i),
\r
154 ' <p>{}</p>\n'.format(rnd),
\r
155 ' <p>Puzzles: {}/{}</p>\n'.format(solved_count, puzzle_count),
\r
156 ' <p>Metas: {}/{}</p>\n'.format(meta_solved, metas),
\r
160 ' <div id="b{}" class="containerTab {}" style="display:none;">\n'.format(i, status),
\r
161 ' <span onclick="this.parentElement.style.display=\'none\'" class="closebtn">x</span>\n',
\r
162 ' <h2>{}</h2>\n'.format(link(website + "_".join(rnd.split()) + "_round.html", rnd)),
\r
163 ' <table class="sortable">\n',
\r
165 ' <th><u>Puzzle</u></th>\n',
\r
166 ' <th><u>Answer</u></th>\n',
\r
169 for puzzle in rnd_puzzles:
\r
170 if puzzle['type'] == 'meta':
\r
174 if puzzle['status'] == 'solved':
\r
175 expanding += [' <tr class=\'solved\';>\n',
\r
176 ' <td>{}</td>\n'.format(link(website + "_".join(puzzle['name'].split()) + ".html", puzzle['name']+meta)),
\r
177 ' <td>{}</td>\n'.format(puzzle['solution']),
\r
180 expanding += [' <tr class=\'unsolved\';>\n',
\r
181 ' <td><b>{}</b></td>\n'.format(link(website + "_".join(puzzle['name'].split()) + ".html", puzzle['name']+meta)),
\r
184 expanding.append(' </table>\n')
\r
185 expanding.append(' </div>\n')
\r
187 columns.append(' </div>\n')
\r
188 end = ['</body>\n', '</html>\n']
\r
189 html = start + expanding + columns + end
\r
190 file = "index.html"
\r
191 f = open(file, "w")
\r
197 def round_overview(rnd, puzzles):
\r
198 #inputs: round name, puzzles
\r
199 #round overview page
\r
200 #saves as (round name)_round.html, in case meta/round share names.
\r
201 #underscores replace spaces for links.
\r
202 rnd_puzzles, meta_solved, metas = round_stat(rnd, puzzles)[2:]
\r
203 if meta_solved == metas and metas > 0:
\r
206 status = 'unsolved'
\r
207 start = ['<html>\n',
\r
209 ' <link rel="stylesheet" href="individual.css">\n',
\r
210 ' <title>Mystery Hunt 2022</title>\n',
\r
211 ' <script src="sorttable.js"></script>\n',
\r
213 ' <body class="{}">\n'.format(status),
\r
214 ' <h1><b>{}</b></h1>\n'.format(rnd),
\r
215 ' <p>{}</p>\n'.format(link(website + "index.html", 'Hunt Overview')),
\r
217 ' <table class="center sortable">\n',
\r
220 ' <th>Puzzle Title/Slack</th>\n',
\r
221 ' <th>Puzzle Link</th>\n',
\r
222 ' <th>Sheet</th>\n',
\r
223 ' <th>Overview</th>\n',
\r
224 ' <th>Answer</th>\n',
\r
225 #' <th>Extra Links</th>\n',
\r
226 ' <th>Tags</th>\n',
\r
231 for puzzle in rnd_puzzles:
\r
232 if puzzle['type'] == 'meta':
\r
236 slack_url = channel_url(puzzle['channel_id'])
\r
238 if puzzle['status'] == 'solved':
\r
239 puzzle_list += [ ' <tr>\n',
\r
240 ' <td>{}</td>\n'.format(elink(slack_url, puzzle['name']+meta)),
\r
241 ' <td>{}</td>\n'.format(elink(puzzle['url'], 'Puzzle')),
\r
242 ' <td>{}</td>\n'.format(elink(puzzle['sheet_url'], 'Sheet')),
\r
243 ' <td>{}</td>\n'.format(link(website + "_".join(puzzle['name'].split()) + '.html', 'Overview')),
\r
244 ' <td>{}</td>\n'.format(puzzle['solution']),
\r
246 ' <td>{}</td>\n'.format("".join(puzzle['tags'])),
\r
249 puzzle_list += [ ' <tr>\n',
\r
250 ' <td><b>{}</b></td>\n'.format(elink(slack_url, puzzle['name']+meta)),
\r
251 ' <td>{}</td>\n'.format(elink(puzzle['url'], 'Puzzle')),
\r
252 ' <td>{}</td>\n'.format(elink(puzzle['sheet_url'], 'Sheet')),
\r
253 ' <td>{}</td>\n'.format(link(website + "_".join(puzzle['name'].split()) + '.html', 'Overview')),
\r
256 ' <td>{}</td>\n'.format(" ".join(puzzle['tags'])),
\r
258 end = [' </tbody>\n',
\r
263 html = start + puzzle_list + end
\r
264 file = "{}_round.html".format('_'.join(rnd.split()))
\r
265 f = open(file, "w")
\r
271 def puzzle_overview(puzzle):
\r
272 #overview page for individual puzzles. saves as (name of puzzle).html,
\r
273 #with underscores rather than spaces
\r
274 name = puzzle['name']
\r
275 if puzzle['type'] == 'meta':
\r
279 slack_url = channel_url(puzzle['channel_id'])
\r
280 round_url = [link(website + "_".join(rnd.split()) + "_round.html", rnd) for rnd in puzzle['rounds']]
\r
281 if puzzle['status'] == 'solved':
\r
282 solution = puzzle['solution']
\r
286 status = 'unsolved'
\r
287 html = ['<!DOCTYPE html>\n',
\r
290 ' <meta charset="utf-8">\n',
\r
291 ' <meta name="viewport" content="width=device-width, initial-scale=1">\n',
\r
292 ' <link rel="stylesheet" href="individual.css">\n',
\r
293 ' <title>{}</title>\n'.format(name+meta),
\r
294 ' <p>{}</p>'.format(link(website + 'index.html', 'Hunt Overview')),
\r
297 '<body class="{}">\n'.format(status),
\r
298 ' <h1>{}</h1>\n'.format(name+meta),
\r
300 ' <table class="center">\n',
\r
302 ' <td>{}</td>\n'.format(elink(slack_url, 'Channel')), #slack channel
\r
303 ' <td>{}</td>\n'.format(elink(puzzle['url'], 'Puzzle')),
\r
304 ' <td>{}</td>\n'.format(elink(puzzle['sheet_url'], 'Sheet')),
\r
305 ' <td>Additional Resources</td>\n',
\r
308 ' <table class="center">\n',
\r
310 ' <td>Round(s): {}</td>\n'.format(" ".join(round_url)), #round page on our site
\r
311 ' <td>Tags: {}</td>\n'.format(" ".join(puzzle['tags'])), #add tags
\r
314 ' <td>Answer: {}</td>\n'.format(solution),
\r
315 ' <td>State: {}</td>\n'.format(puzzle['status']),
\r
322 underscored = "_".join(name.split())
\r
323 file = "{}.html".format(underscored)
\r
324 f = open(file, "w")
\r
329 def puzzle_lists(puzzles, filt):
\r
330 #filt is one of "All", "Solved", "Unsolved" and spits out the appropriate list of puzzles
\r
331 #generates pages for all puzzles, solved puzzles, unsolved puzzles
\r
332 #saves as all/solved/unsolved.html, has a sidebar to link to each other and to the big board
\r
333 solved_puzzles = [puzzle for puzzle in puzzles if puzzle['status'] == 'solved']
\r
334 unsolved_puzzles = [puzzle for puzzle in puzzles if puzzle['status'] != 'solved']
\r
335 start = ['<html>\n',
\r
337 ' <link rel="stylesheet" href="overview.css">\n',
\r
338 ' <title>Mystery Hunt 2022</title>\n',
\r
339 ' <script src="sorttable.js"></script>\n',
\r
341 ' <div class="sidenav">\n'
\r
342 ' <a href="index.html">Hunt Overview</a>'
\r
343 ' <a href="all.html">All Puzzles</a>\n'
\r
344 ' <a href="unsolved.html">Unsolved</a>\n'
\r
345 ' <a href="solved.html">Solved</a>\n'
\r
348 ' <h1><b>{}</b></h1>\n'.format('{} Puzzles').format(filt),
\r
349 ' <p>{}</p>\n'.format(link(website + "index.html", 'Hunt Overview')),
\r
351 ' <table class="center sortable">\n',
\r
354 ' <th>Puzzle Title/Slack</th>\n',
\r
355 ' <th>Puzzle Link</th>\n',
\r
356 ' <th>Sheet</th>\n',
\r
357 ' <th>Overview</th>\n',
\r
358 ' <th>Answer</th>\n',
\r
359 ' <th>Round(s)</th>\n',
\r
360 ' <th>Tags</th>\n',
\r
366 for puzzle in solved_puzzles:
\r
367 if puzzle['type'] == 'meta':
\r
371 slack_url = channel_url(puzzle['channel_id'])
\r
372 round_url = link(website + "_".join(rnd.split()) + "_round.html", puzzle['rounds'][0])
\r
373 #assuming one round per puzzle for now
\r
375 solved_code += [' <tr>\n',
\r
376 ' <td>{}</td>\n'.format(elink(slack_url, puzzle['name']+meta)),
\r
377 ' <td>{}</td>\n'.format(elink(puzzle['url'], 'Puzzle')),
\r
378 ' <td>{}</td>\n'.format(elink(puzzle['sheet_url'], 'Sheet')),
\r
379 ' <td>{}</td>\n'.format(link(website + "_".join(puzzle['name'].split()) + '.html', 'Overview')),
\r
380 ' <td>{}</td>\n'.format(puzzle['solution']),
\r
381 ' <td>{}</td>\n'.format(round_url),
\r
382 ' <td>{}</td>\n'.format("".join(puzzle['tags'])),
\r
384 for puzzle in unsolved_puzzles:
\r
385 if puzzle['type'] == 'meta':
\r
389 slack_url = channel_url(puzzle['channel_id'])
\r
390 round_url = link(website + "_".join(rnd.split()) + "_round.html", puzzle['rounds'][0])
\r
391 #assuming one round per puzzle for now
\r
393 unsolved_code += [' <tr>\n',
\r
394 ' <td>{}</td>\n'.format(elink(slack_url, puzzle['name']+meta)),
\r
395 ' <td>{}</td>\n'.format(elink(puzzle['url'], 'Puzzle')),
\r
396 ' <td>{}</td>\n'.format(elink(puzzle['sheet_url'], 'Sheet')),
\r
397 ' <td>{}</td>\n'.format(link(website + "_".join(puzzle['name'].split()) + '.html', 'Overview')),
\r
399 ' <td>{}</td>\n'.format(round_url),
\r
400 ' <td>{}</td>\n'.format("".join(puzzle['tags'])),
\r
402 end = [' </tbody>\n',
\r
409 f = open(file1, "w")
\r
410 for line in start + unsolved_code + solved_code + end:
\r
413 elif filt == "Solved":
\r
414 file2 = 'solved.html'
\r
415 f = open(file2, 'w')
\r
416 for line in start + solved_code + end:
\r
419 elif filt == "Unsolved":
\r
420 file3 = 'unsolved.html'
\r
421 f = open(file3, 'w')
\r
422 for line in start + unsolved_code + end:
\r
429 puzzles, rounds = hunt_info(turb, hunt)
\r
430 #I am not sure where these come from
\r
431 overview(puzzles, rounds)
\r
433 round_overview(rnd, puzzles)
\r
434 for puzzle in puzzles:
\r
435 puzzle_overview(puzzle)
\r
436 puzzle_lists(puzzles, "All")
\r
437 puzzle_lists(puzzles, "Solved")
\r
438 puzzle_lists(puzzles, "Unsolved")