-# -*- coding: utf-8 -*-\r
-"""\r
-Created on Thu Jan 6 23:35:23 2022\r
-\r
-@author: Avram Gottschlich\r
-"""\r
-"""\r
-I copied several functions from your code; \r
-if it's easier to refer to them rather than copying them that's absolutely fine\r
-\r
-This rewrites the html each time it is called\r
-If it's easy to call the puzzle/round functions each time they're updated,\r
-that would be great\r
-\r
-Requires sorttable.js, which should be included\r
-"""\r
-from turbot.channel import channel_url\r
-from boto3.dynamodb.conditions import Key\r
-\r
-website = "https://halibut.cworth.org/"\r
-#change this if we're using AWS or some other subdomain instead\r
-\r
-def find_hunt_for_hunt_id(turb, hunt_id):\r
- """Given a hunt ID find the database item for that hunt\r
-\r
- Returns None if hunt ID is not found, otherwise a\r
- dictionary with all fields from the hunt's row in the table,\r
- (channel_id, active, hunt_id, name, url, sheet_url, etc.).\r
- """\r
- response = turb.table.get_item(\r
- Key={\r
- 'hunt_id': hunt_id,\r
- 'SK': 'hunt-{}'.format(hunt_id)\r
- })\r
-\r
- if 'Item' in response:\r
- return response['Item']\r
- else:\r
- return None\r
-\r
-def hunt_puzzles_for_hunt_id(turb, hunt_id):\r
- """Return all puzzles that belong to the given hunt_id"""\r
-\r
- response = turb.table.query(\r
- KeyConditionExpression=(\r
- Key('hunt_id').eq(hunt_id) &\r
- Key('SK').begins_with('puzzle-')\r
- )\r
- )\r
-\r
- return response['Items']\r
-\r
-def elink(lin, text):\r
- #shortcutting function for creating html links\r
- #opens in a new tab for external links\r
- return '<a href="{}" target="_blank" rel="noreferrer noopener">{}</a>'.format(lin, text)\r
-\r
-def link(lin, text):\r
- #internal links, doesn't open new tab\r
- return '<a href="{}">{}</a>'.format(lin, text)\r
-\r
-def hunt_info(turb, hunt):\r
- """\r
- Retrieves list of rounds, puzzles for the given hunt \r
- """\r
- name = hunt["name"]\r
- hunt_id = hunt["hunt_id"]\r
- channel_id = hunt["channel_id"]\r
- \r
- puzzles = hunt_puzzles_for_hunt_id(turb, hunt_id)\r
- \r
- rounds = set()\r
- for puzzle in puzzles:\r
- if "rounds" not in puzzle:\r
- continue\r
- for rnd in puzzle["rounds"]:\r
- rounds.add(rnd)\r
- rounds = list(rounds)\r
- rounds.sort()\r
- \r
- return puzzles, rounds\r
-\r
-def round_stat(rnd, puzzles):\r
- #Counts puzzles, solved, list of puzzles for a given round\r
- puzzle_count = 0\r
- solved_count = 0\r
- solved_puzzles = []\r
- unsolved_puzzles = []\r
- metas = []\r
- meta_solved = 0\r
- for puzzle in puzzles:\r
- if "rounds" not in puzzle:\r
- continue \r
- if rnd in puzzle["rounds"]:\r
- if puzzle['type'] == 'meta':\r
- metas.append(puzzle)\r
- if puzzle['status'] == 'solved':\r
- meta_solved += 1\r
- else:\r
- puzzle_count += 1\r
- if puzzle['status'] == 'solved':\r
- solved_count += 1\r
- solved_puzzles.append(puzzle)\r
- else:\r
- unsolved_puzzles.append(puzzle)\r
- solved_puzzles = sorted(solved_puzzles, key = lambda i: i['name'])\r
- unsolved_puzzles = sorted(unsolved_puzzles, key = lambda i: i['name'])\r
- rnd_puzzles = metas + unsolved_puzzles + solved_puzzles\r
- return puzzle_count, solved_count, rnd_puzzles, meta_solved, len(metas)\r
- \r
- \r
- \r
-def overview(puzzles, rounds):\r
- #big board, main page. saves as index.html\r
- start = ['<!DOCTYPE html>\n',\r
- '<html>\n',\r
- '<head>\n',\r
- ' <meta charset="utf-8">\n',\r
- ' <meta name="viewport" content="width=device-width, initial-scale=1">\n',\r
- '\n',\r
- ' <link rel="stylesheet" href="overview.css">\n',\r
- ' <script type="text/javascript">\n',\r
- ' // Hide all elements with class="containerTab", except for the one that matches the clickable grid column\n',\r
- ' function openTab(tabName) {\n',\r
- ' var i, x;\n',\r
- ' x = document.getElementsByClassName("containerTab");\n',\r
- ' for (i = 0; i < x.length; i++) {\n',\r
- ' x[i].style.display = "none";\n',\r
- ' }\n',\r
- ' document.getElementById(tabName).style.display = "block";\n',\r
- ' }\n',\r
- ' </script>\n',\r
- '\n',\r
- ' <title>Hunt Overview</title>\n',\r
- ' <script src="sorttable.js"></script>\n'\r
- '</head>\n',\r
- ' <div class="sidenav">\n'\r
- ' <a href="index.html">Hunt Overview</a>'\r
- ' <a href="all.html">All Puzzles</a>\n'\r
- ' <a href="unsolved.html">Unsolved</a>\n'\r
- ' <a href="solved.html">Solved</a>\n'\r
- ' </div>\n'\r
- '<body>\n',]\r
- columns = [' <div class="row">\n']\r
- expanding = []\r
- i = 1\r
- for rnd in rounds:\r
- puzzle_count, solved_count, rnd_puzzles, meta_solved, metas = round_stat(rnd, puzzles)\r
- if metas == meta_solved and metas > 0:\r
- status = 'solved'\r
- else:\r
- status = 'unsolved'\r
- columns += [' <div class="column {}" onclick="openTab(\'b{}\');">\n'.format(status, i),\r
- ' <p>{}</p>\n'.format(rnd),\r
- ' <p>Puzzles: {}/{}</p>\n'.format(solved_count, puzzle_count),\r
- ' <p>Metas: {}/{}</p>\n'.format(meta_solved, metas),\r
- ' </div>\n']\r
- \r
- expanding += [\r
- ' <div id="b{}" class="containerTab {}" style="display:none;">\n'.format(i, status),\r
- ' <span onclick="this.parentElement.style.display=\'none\'" class="closebtn">x</span>\n',\r
- ' <h2>{}</h2>\n'.format(link(website + "_".join(rnd.split()) + "_round.html", rnd)),\r
- ' <table class="sortable">\n',\r
- ' <tr>\n',\r
- ' <th><u>Puzzle</u></th>\n',\r
- ' <th><u>Answer</u></th>\n',\r
- ' </tr>\n',]\r
-\r
- for puzzle in rnd_puzzles:\r
- if puzzle['type'] == 'meta':\r
- meta = ' [META]'\r
- else:\r
- meta = ''\r
- if puzzle['status'] == 'solved':\r
- expanding += [' <tr class=\'solved\';>\n',\r
- ' <td>{}</td>\n'.format(link(website + "_".join(puzzle['name'].split()) + ".html", puzzle['name']+meta)),\r
- ' <td>{}</td>\n'.format(puzzle['solution']),\r
- ' </tr>\n']\r
- else:\r
- expanding += [' <tr class=\'unsolved\';>\n',\r
- ' <td><b>{}</b></td>\n'.format(link(website + "_".join(puzzle['name'].split()) + ".html", puzzle['name']+meta)),\r
- ' <td></td>\n',\r
- ' </tr>\n']\r
- expanding.append(' </table>\n')\r
- expanding.append(' </div>\n')\r
- i += 1\r
- columns.append(' </div>\n')\r
- end = ['</body>\n', '</html>\n']\r
- html = start + expanding + columns + end\r
- file = "index.html"\r
- f = open(file, "w")\r
- for line in html:\r
- f.write(line)\r
- f.close()\r
- return None\r
-\r
-def round_overview(rnd, puzzles):\r
- #inputs: round name, puzzles\r
- #round overview page\r
- #saves as (round name)_round.html, in case meta/round share names.\r
- #underscores replace spaces for links.\r
- rnd_puzzles, meta_solved, metas = round_stat(rnd, puzzles)[2:]\r
- if meta_solved == metas and metas > 0:\r
- status = 'solved'\r
- else:\r
- status = 'unsolved'\r
- start = ['<html>\n',\r
- ' <head>\n',\r
- ' <link rel="stylesheet" href="individual.css">\n',\r
- ' <title>Mystery Hunt 2022</title>\n',\r
- ' <script src="sorttable.js"></script>\n',\r
- ' </head>\n',\r
- ' <body class="{}">\n'.format(status),\r
- ' <h1><b>{}</b></h1>\n'.format(rnd),\r
- ' <p>{}</p>\n'.format(link(website + "index.html", 'Hunt Overview')),\r
- ' <div>\n',\r
- ' <table class="center sortable">\n',\r
- ' <thead>\n',\r
- ' <tr>\n',\r
- ' <th>Puzzle Title/Slack</th>\n',\r
- ' <th>Puzzle Link</th>\n',\r
- ' <th>Sheet</th>\n',\r
- ' <th>Overview</th>\n',\r
- ' <th>Answer</th>\n',\r
- #' <th>Extra Links</th>\n',\r
- ' <th>Tags</th>\n',\r
- ' </tr>\n',\r
- ' </thead>\n',\r
- ' <tbody>\n']\r
- puzzle_list = []\r
- for puzzle in rnd_puzzles:\r
- if puzzle['type'] == 'meta':\r
- meta = ' [META]'\r
- else:\r
- meta = ''\r
- slack_url = channel_url(puzzle['channel_id'])\r
- \r
- if puzzle['status'] == 'solved':\r
- puzzle_list += [ ' <tr>\n',\r
- ' <td>{}</td>\n'.format(elink(slack_url, puzzle['name']+meta)),\r
- ' <td>{}</td>\n'.format(elink(puzzle['url'], 'Puzzle')),\r
- ' <td>{}</td>\n'.format(elink(puzzle['sheet_url'], 'Sheet')),\r
- ' <td>{}</td>\n'.format(link(website + "_".join(puzzle['name'].split()) + '.html', 'Overview')),\r
- ' <td>{}</td>\n'.format(puzzle['solution']),\r
- # ' <td></td>\n',\r
- ' <td>{}</td>\n'.format("".join(puzzle['tags'])),\r
- ' </tr>\n']\r
- else:\r
- puzzle_list += [ ' <tr>\n',\r
- ' <td><b>{}</b></td>\n'.format(elink(slack_url, puzzle['name']+meta)),\r
- ' <td>{}</td>\n'.format(elink(puzzle['url'], 'Puzzle')),\r
- ' <td>{}</td>\n'.format(elink(puzzle['sheet_url'], 'Sheet')),\r
- ' <td>{}</td>\n'.format(link(website + "_".join(puzzle['name'].split()) + '.html', 'Overview')),\r
- ' <td></td>\n',\r
- # ' <td></td>\n',\r
- ' <td>{}</td>\n'.format(" ".join(puzzle['tags'])),\r
- ' </tr>\n']\r
- end = [' </tbody>\n',\r
- ' </table>\n',\r
- ' </div>\n',\r
- ' </body>\n',\r
- '</html>\n']\r
- html = start + puzzle_list + end\r
- file = "{}_round.html".format('_'.join(rnd.split()))\r
- f = open(file, "w")\r
- for line in html:\r
- f.write(line)\r
- f.close()\r
- return None\r
-\r
-def puzzle_overview(puzzle):\r
- #overview page for individual puzzles. saves as (name of puzzle).html,\r
- #with underscores rather than spaces\r
- name = puzzle['name']\r
- if puzzle['type'] == 'meta':\r
- meta = ' [META]'\r
- else:\r
- meta = ''\r
- slack_url = channel_url(puzzle['channel_id'])\r
- round_url = [link(website + "_".join(rnd.split()) + "_round.html", rnd) for rnd in puzzle['rounds']]\r
- if puzzle['status'] == 'solved':\r
- solution = puzzle['solution']\r
- status = 'solved'\r
- else:\r
- solution = ""\r
- status = 'unsolved'\r
- html = ['<!DOCTYPE html>\n',\r
- '<html>\n',\r
- '<head>\n',\r
- ' <meta charset="utf-8">\n',\r
- ' <meta name="viewport" content="width=device-width, initial-scale=1">\n',\r
- ' <link rel="stylesheet" href="individual.css">\n',\r
- ' <title>{}</title>\n'.format(name+meta),\r
- ' <p>{}</p>'.format(link(website + 'index.html', 'Hunt Overview')),\r
- '\n',\r
- '</head>\n',\r
- '<body class="{}">\n'.format(status),\r
- ' <h1>{}</h1>\n'.format(name+meta),\r
- ' <div>\n',\r
- ' <table class="center">\n',\r
- ' <tr>\n',\r
- ' <td>{}</td>\n'.format(elink(slack_url, 'Channel')), #slack channel\r
- ' <td>{}</td>\n'.format(elink(puzzle['url'], 'Puzzle')),\r
- ' <td>{}</td>\n'.format(elink(puzzle['sheet_url'], 'Sheet')),\r
- ' <td>Additional Resources</td>\n',\r
- ' </tr>\n',\r
- ' </table>\n',\r
- ' <table class="center">\n',\r
- ' <tr>\n',\r
- ' <td>Round(s): {}</td>\n'.format(" ".join(round_url)), #round page on our site\r
- ' <td>Tags: {}</td>\n'.format(" ".join(puzzle['tags'])), #add tags\r
- ' </tr>\n',\r
- ' <tr>\n',\r
- ' <td>Answer: {}</td>\n'.format(solution),\r
- ' <td>State: {}</td>\n'.format(puzzle['status']),\r
- ' </tr>\n',\r
- ' </table>\n',\r
- ' </div>\n',\r
- '\n',\r
- '</body>\n',\r
- '</html>\n']\r
- underscored = "_".join(name.split())\r
- file = "{}.html".format(underscored)\r
- f = open(file, "w")\r
- for line in html:\r
- f.write(line)\r
- return None\r
-\r
-def puzzle_lists(puzzles, filt):\r
- #filt is one of "All", "Solved", "Unsolved" and spits out the appropriate list of puzzles\r
- #generates pages for all puzzles, solved puzzles, unsolved puzzles\r
- #saves as all/solved/unsolved.html, has a sidebar to link to each other and to the big board\r
- solved_puzzles = [puzzle for puzzle in puzzles if puzzle['status'] == 'solved']\r
- unsolved_puzzles = [puzzle for puzzle in puzzles if puzzle['status'] != 'solved']\r
- start = ['<html>\n',\r
- ' <head>\n',\r
- ' <link rel="stylesheet" href="overview.css">\n',\r
- ' <title>Mystery Hunt 2022</title>\n',\r
- ' <script src="sorttable.js"></script>\n',\r
- ' </head>\n',\r
- ' <div class="sidenav">\n'\r
- ' <a href="index.html">Hunt Overview</a>'\r
- ' <a href="all.html">All Puzzles</a>\n'\r
- ' <a href="unsolved.html">Unsolved</a>\n'\r
- ' <a href="solved.html">Solved</a>\n'\r
- ' </div>\n'\r
- ' <body>\n',\r
- ' <h1><b>{}</b></h1>\n'.format('{} Puzzles').format(filt),\r
- ' <p>{}</p>\n'.format(link(website + "index.html", 'Hunt Overview')),\r
- ' <div>\n',\r
- ' <table class="center sortable">\n',\r
- ' <thead>\n',\r
- ' <tr>\n',\r
- ' <th>Puzzle Title/Slack</th>\n',\r
- ' <th>Puzzle Link</th>\n',\r
- ' <th>Sheet</th>\n',\r
- ' <th>Overview</th>\n',\r
- ' <th>Answer</th>\n',\r
- ' <th>Round(s)</th>\n',\r
- ' <th>Tags</th>\n',\r
- ' </tr>\n',\r
- ' </thead>\n',\r
- ' <tbody>\n']\r
- solved_code = []\r
- unsolved_code = []\r
- for puzzle in solved_puzzles:\r
- if puzzle['type'] == 'meta':\r
- meta = ' [META]'\r
- else:\r
- meta = ''\r
- slack_url = channel_url(puzzle['channel_id'])\r
- round_url = link(website + "_".join(rnd.split()) + "_round.html", puzzle['rounds'][0])\r
- #assuming one round per puzzle for now\r
- \r
- solved_code += [' <tr>\n',\r
- ' <td>{}</td>\n'.format(elink(slack_url, puzzle['name']+meta)),\r
- ' <td>{}</td>\n'.format(elink(puzzle['url'], 'Puzzle')),\r
- ' <td>{}</td>\n'.format(elink(puzzle['sheet_url'], 'Sheet')),\r
- ' <td>{}</td>\n'.format(link(website + "_".join(puzzle['name'].split()) + '.html', 'Overview')),\r
- ' <td>{}</td>\n'.format(puzzle['solution']),\r
- ' <td>{}</td>\n'.format(round_url),\r
- ' <td>{}</td>\n'.format("".join(puzzle['tags'])),\r
- ' </tr>\n']\r
- for puzzle in unsolved_puzzles:\r
- if puzzle['type'] == 'meta':\r
- meta = ' [META]'\r
- else:\r
- meta = ''\r
- slack_url = channel_url(puzzle['channel_id'])\r
- round_url = link(website + "_".join(rnd.split()) + "_round.html", puzzle['rounds'][0])\r
- #assuming one round per puzzle for now\r
- \r
- unsolved_code += [' <tr>\n',\r
- ' <td>{}</td>\n'.format(elink(slack_url, puzzle['name']+meta)),\r
- ' <td>{}</td>\n'.format(elink(puzzle['url'], 'Puzzle')),\r
- ' <td>{}</td>\n'.format(elink(puzzle['sheet_url'], 'Sheet')),\r
- ' <td>{}</td>\n'.format(link(website + "_".join(puzzle['name'].split()) + '.html', 'Overview')),\r
- ' <td></td>\n',\r
- ' <td>{}</td>\n'.format(round_url),\r
- ' <td>{}</td>\n'.format("".join(puzzle['tags'])),\r
- ' </tr>\n']\r
- end = [' </tbody>\n',\r
- ' </table>\n',\r
- ' </div>\n',\r
- ' </body>\n',\r
- '</html>\n']\r
- if filt == "All":\r
- file1 = 'all.html'\r
- f = open(file1, "w")\r
- for line in start + unsolved_code + solved_code + end:\r
- f.write(line)\r
- f.close()\r
- elif filt == "Solved":\r
- file2 = 'solved.html'\r
- f = open(file2, 'w')\r
- for line in start + solved_code + end:\r
- f.write(line)\r
- f.close()\r
- elif filt == "Unsolved":\r
- file3 = 'unsolved.html'\r
- f = open(file3, 'w')\r
- for line in start + unsolved_code + end:\r
- f.write(line)\r
- f.close()\r
- return None\r
-\r
-\r
-\r
-puzzles, rounds = hunt_info(turb, hunt)\r
-#I am not sure where these come from\r
-overview(puzzles, rounds)\r
-for rnd in rounds:\r
- round_overview(rnd, puzzles)\r
-for puzzle in puzzles:\r
- puzzle_overview(puzzle)\r
-puzzle_lists(puzzles, "All")\r
-puzzle_lists(puzzles, "Solved")\r
-puzzle_lists(puzzles, "Unsolved")
\ No newline at end of file
+# -*- coding: utf-8 -*-
+"""
+Created on Thu Jan 6 23:35:23 2022
+
+@author: Avram Gottschlich
+"""
+"""
+I copied several functions from your code;
+if it's easier to refer to them rather than copying them that's absolutely fine
+
+This rewrites the html each time it is called
+If it's easy to call the puzzle/round functions each time they're updated,
+that would be great
+
+Requires sorttable.js, which should be included
+"""
+import boto3
+from boto3.dynamodb.conditions import Key
+import re
+
+website = "https://halibut.cworth.org/"
+#change this if we're using AWS or some other subdomain instead
+
+def channel_url(channel_id):
+ """Given a channel ID, return the URL for that channel."""
+
+ return "https://halibutthatbass.slack.com/archives/{}".format(channel_id)
+
+def find_hunt_for_hunt_id(table, hunt_id):
+ """Given a hunt ID find the database item for that hunt
+
+ Returns None if hunt ID is not found, otherwise a
+ dictionary with all fields from the hunt's row in the table,
+ (channel_id, active, hunt_id, name, url, sheet_url, etc.).
+ """
+ response = table.get_item(
+ Key={
+ 'hunt_id': hunt_id,
+ 'SK': 'hunt-{}'.format(hunt_id)
+ })
+
+ if 'Item' in response:
+ return response['Item']
+ else:
+ return None
+
+def hunt_puzzles_for_hunt_id(table, hunt_id):
+ """Return all puzzles that belong to the given hunt_id"""
+
+ response = table.query(
+ KeyConditionExpression=(
+ Key('hunt_id').eq(hunt_id) &
+ Key('SK').begins_with('puzzle-')
+ )
+ )
+
+ return response['Items']
+
+def elink(lin, text):
+ #shortcutting function for creating html links
+ #opens in a new tab for external links
+ return '<a href="{}" target="_blank" rel="noreferrer noopener">{}</a>'.format(lin, text)
+
+def link(lin, text):
+ #internal links, doesn't open new tab
+ return '<a href="{}">{}</a>'.format(lin, text)
+
+def hunt_info(table, hunt_id):
+ """
+ Retrieves list of rounds, puzzles for the given hunt
+ """
+
+ hunt = find_hunt_for_hunt_id(table, hunt_id)
+
+ name = hunt["name"]
+ channel_id = hunt["channel_id"]
+
+ puzzles = hunt_puzzles_for_hunt_id(table, hunt_id)
+
+ rounds = set()
+ for puzzle in puzzles:
+ if "rounds" not in puzzle:
+ continue
+ for rnd in puzzle["rounds"]:
+ rounds.add(rnd)
+ rounds = list(rounds)
+ rounds.sort()
+
+ return puzzles, rounds
+
+def round_stat(rnd, puzzles):
+ #Counts puzzles, solved, list of puzzles for a given round
+ puzzle_count = 0
+ solved_count = 0
+ solved_puzzles = []
+ unsolved_puzzles = []
+ metas = []
+ meta_solved = 0
+ for puzzle in puzzles:
+ if "rounds" not in puzzle:
+ continue
+ if rnd in puzzle["rounds"]:
+ if puzzle['type'] == 'meta':
+ metas.append(puzzle)
+ if puzzle['status'] == 'solved':
+ meta_solved += 1
+ else:
+ puzzle_count += 1
+ if puzzle['status'] == 'solved':
+ solved_count += 1
+ solved_puzzles.append(puzzle)
+ else:
+ unsolved_puzzles.append(puzzle)
+ solved_puzzles = sorted(solved_puzzles, key = lambda i: i['name'])
+ unsolved_puzzles = sorted(unsolved_puzzles, key = lambda i: i['name'])
+ rnd_puzzles = metas + unsolved_puzzles + solved_puzzles
+ return puzzle_count, solved_count, rnd_puzzles, meta_solved, len(metas)
+
+
+
+def overview(puzzles, rounds):
+ #big board, main page. saves as index.html
+ start = ['<!DOCTYPE html>\n',
+ '<html>\n',
+ '<head>\n',
+ ' <meta charset="utf-8">\n',
+ ' <meta name="viewport" content="width=device-width, initial-scale=1">\n',
+ '\n',
+ ' <link rel="stylesheet" href="overview.css">\n',
+ ' <script type="text/javascript">\n',
+ ' // Hide all elements with class="containerTab", except for the one that matches the clickable grid column\n',
+ ' function openTab(tabName) {\n',
+ ' var i, x;\n',
+ ' x = document.getElementsByClassName("containerTab");\n',
+ ' for (i = 0; i < x.length; i++) {\n',
+ ' x[i].style.display = "none";\n',
+ ' }\n',
+ ' document.getElementById(tabName).style.display = "block";\n',
+ ' }\n',
+ ' </script>\n',
+ '\n',
+ ' <title>Hunt Overview</title>\n',
+ ' <script src="sorttable.js"></script>\n'
+ '</head>\n',
+ ' <div class="sidenav">\n'
+ ' <a href="index.html">Hunt Overview</a>'
+ ' <a href="all.html">All Puzzles</a>\n'
+ ' <a href="unsolved.html">Unsolved</a>\n'
+ ' <a href="solved.html">Solved</a>\n'
+ ' </div>\n'
+ '<body>\n',]
+ columns = [' <div class="row">\n']
+ expanding = []
+ i = 1
+ for rnd in rounds:
+ puzzle_count, solved_count, rnd_puzzles, meta_solved, metas = round_stat(rnd, puzzles)
+ if metas == meta_solved and metas > 0:
+ status = 'solved'
+ else:
+ status = 'unsolved'
+ columns += [' <div class="column {}" onclick="openTab(\'b{}\');">\n'.format(status, i),
+ ' <p>{}</p>\n'.format(rnd),
+ ' <p>Puzzles: {}/{}</p>\n'.format(solved_count, puzzle_count),
+ ' <p>Metas: {}/{}</p>\n'.format(meta_solved, metas),
+ ' </div>\n']
+
+ expanding += [
+ ' <div id="b{}" class="containerTab {}" style="display:none;">\n'.format(i, status),
+ ' <span onclick="this.parentElement.style.display=\'none\'" class="closebtn">x</span>\n',
+ ' <h2>{}</h2>\n'.format(link(website + "_".join(rnd.split()) + "_round.html", rnd)),
+ ' <table class="sortable">\n',
+ ' <tr>\n',
+ ' <th><u>Puzzle</u></th>\n',
+ ' <th><u>Answer</u></th>\n',
+ ' </tr>\n',]
+
+ for puzzle in rnd_puzzles:
+ if puzzle['type'] == 'meta':
+ meta = ' [META]'
+ else:
+ meta = ''
+ if puzzle['status'] == 'solved':
+ expanding += [' <tr class=\'solved\';>\n',
+ ' <td>{}</td>\n'.format(link(website + "_".join(puzzle['name'].split()) + ".html", puzzle['name']+meta)),
+ ' <td>{}</td>\n'.format(puzzle['solution']),
+ ' </tr>\n']
+ else:
+ expanding += [' <tr class=\'unsolved\';>\n',
+ ' <td><b>{}</b></td>\n'.format(link(website + "_".join(puzzle['name'].split()) + ".html", puzzle['name']+meta)),
+ ' <td></td>\n',
+ ' </tr>\n']
+ expanding.append(' </table>\n')
+ expanding.append(' </div>\n')
+ i += 1
+ columns.append(' </div>\n')
+ end = ['</body>\n', '</html>\n']
+ html = start + expanding + columns + end
+ file = "index.html"
+ f = open(file, "w")
+ for line in html:
+ f.write(line)
+ f.close()
+ return None
+
+def round_overview(rnd, puzzles):
+ #inputs: round name, puzzles
+ #round overview page
+ #saves as (round name)_round.html, in case meta/round share names.
+ #underscores replace spaces for links.
+ rnd_puzzles, meta_solved, metas = round_stat(rnd, puzzles)[2:]
+ if meta_solved == metas and metas > 0:
+ status = 'solved'
+ else:
+ status = 'unsolved'
+ start = ['<html>\n',
+ ' <head>\n',
+ ' <link rel="stylesheet" href="individual.css">\n',
+ ' <title>Mystery Hunt 2022</title>\n',
+ ' <script src="sorttable.js"></script>\n',
+ ' </head>\n',
+ ' <body class="{}">\n'.format(status),
+ ' <h1><b>{}</b></h1>\n'.format(rnd),
+ ' <p>{}</p>\n'.format(link(website + "index.html", 'Hunt Overview')),
+ ' <div>\n',
+ ' <table class="center sortable">\n',
+ ' <thead>\n',
+ ' <tr>\n',
+ ' <th>Puzzle Title/Slack</th>\n',
+ ' <th>Puzzle Link</th>\n',
+ ' <th>Sheet</th>\n',
+ ' <th>Overview</th>\n',
+ ' <th>Answer</th>\n',
+ #' <th>Extra Links</th>\n',
+ ' <th>Tags</th>\n',
+ ' </tr>\n',
+ ' </thead>\n',
+ ' <tbody>\n']
+ puzzle_list = []
+ for puzzle in rnd_puzzles:
+ if puzzle['type'] == 'meta':
+ meta = ' [META]'
+ else:
+ meta = ''
+ slack_url = channel_url(puzzle['channel_id'])
+
+ if puzzle['status'] == 'solved':
+ puzzle_list += [ ' <tr>\n',
+ ' <td>{}</td>\n'.format(elink(slack_url, puzzle['name']+meta)),
+ ' <td>{}</td>\n'.format(elink(puzzle['url'], 'Puzzle')),
+ ' <td>{}</td>\n'.format(elink(puzzle['sheet_url'], 'Sheet')),
+ ' <td>{}</td>\n'.format(link(website + "_".join(puzzle['name'].split()) + '.html', 'Overview')),
+ ' <td>{}</td>\n'.format(puzzle['solution']),
+ # ' <td></td>\n',
+ ' <td>{}</td>\n'.format("".join(puzzle['tags'])),
+ ' </tr>\n']
+ else:
+ puzzle_list += [ ' <tr>\n',
+ ' <td><b>{}</b></td>\n'.format(elink(slack_url, puzzle['name']+meta)),
+ ' <td>{}</td>\n'.format(elink(puzzle['url'], 'Puzzle')),
+ ' <td>{}</td>\n'.format(elink(puzzle['sheet_url'], 'Sheet')),
+ ' <td>{}</td>\n'.format(link(website + "_".join(puzzle['name'].split()) + '.html', 'Overview')),
+ ' <td></td>\n',
+ # ' <td></td>\n',
+ ' <td>{}</td>\n'.format(" ".join(puzzle['tags'])),
+ ' </tr>\n']
+ end = [' </tbody>\n',
+ ' </table>\n',
+ ' </div>\n',
+ ' </body>\n',
+ '</html>\n']
+ html = start + puzzle_list + end
+ file = "{}_round.html".format('_'.join(rnd.split()))
+ f = open(file, "w")
+ for line in html:
+ f.write(line)
+ f.close()
+ return None
+
+def puzzle_overview(puzzle):
+ #overview page for individual puzzles. saves as (name of puzzle).html,
+ #with underscores rather than spaces
+ name = puzzle['name']
+ if puzzle['type'] == 'meta':
+ meta = ' [META]'
+ else:
+ meta = ''
+ slack_url = channel_url(puzzle['channel_id'])
+ round_url = [link(website + "_".join(rnd.split()) + "_round.html", rnd) for rnd in puzzle['rounds']]
+ if puzzle['status'] == 'solved':
+ solution = puzzle['solution']
+ status = 'solved'
+ else:
+ solution = ""
+ status = 'unsolved'
+ html = ['<!DOCTYPE html>\n',
+ '<html>\n',
+ '<head>\n',
+ ' <meta charset="utf-8">\n',
+ ' <meta name="viewport" content="width=device-width, initial-scale=1">\n',
+ ' <link rel="stylesheet" href="individual.css">\n',
+ ' <title>{}</title>\n'.format(name+meta),
+ ' <p>{}</p>'.format(link(website + 'index.html', 'Hunt Overview')),
+ '\n',
+ '</head>\n',
+ '<body class="{}">\n'.format(status),
+ ' <h1>{}</h1>\n'.format(name+meta),
+ ' <div>\n',
+ ' <table class="center">\n',
+ ' <tr>\n',
+ ' <td>{}</td>\n'.format(elink(slack_url, 'Channel')), #slack channel
+ ' <td>{}</td>\n'.format(elink(puzzle['url'], 'Puzzle')),
+ ' <td>{}</td>\n'.format(elink(puzzle['sheet_url'], 'Sheet')),
+ ' <td>Additional Resources</td>\n',
+ ' </tr>\n',
+ ' </table>\n',
+ ' <table class="center">\n',
+ ' <tr>\n',
+ ' <td>Round(s): {}</td>\n'.format(" ".join(round_url)), #round page on our site
+ ' <td>Tags: {}</td>\n'.format(" ".join(puzzle['tags'])), #add tags
+ ' </tr>\n',
+ ' <tr>\n',
+ ' <td>Answer: {}</td>\n'.format(solution),
+ ' <td>State: {}</td>\n'.format(puzzle['status']),
+ ' </tr>\n',
+ ' </table>\n',
+ ' </div>\n',
+ '\n',
+ '</body>\n',
+ '</html>\n']
+ # Replace all spaces and slashes in the name with underscores
+ underscored = re.sub(r'[ /]', '_', name)
+ file = "{}.html".format(underscored)
+ f = open(file, "w")
+ for line in html:
+ f.write(line)
+ return None
+
+def puzzle_lists(puzzles, filt):
+ #filt is one of "All", "Solved", "Unsolved" and spits out the appropriate list of puzzles
+ #generates pages for all puzzles, solved puzzles, unsolved puzzles
+ #saves as all/solved/unsolved.html, has a sidebar to link to each other and to the big board
+ solved_puzzles = [puzzle for puzzle in puzzles if puzzle['status'] == 'solved']
+ unsolved_puzzles = [puzzle for puzzle in puzzles if puzzle['status'] != 'solved']
+ start = ['<html>\n',
+ ' <head>\n',
+ ' <link rel="stylesheet" href="overview.css">\n',
+ ' <title>Mystery Hunt 2022</title>\n',
+ ' <script src="sorttable.js"></script>\n',
+ ' </head>\n',
+ ' <div class="sidenav">\n'
+ ' <a href="index.html">Hunt Overview</a>'
+ ' <a href="all.html">All Puzzles</a>\n'
+ ' <a href="unsolved.html">Unsolved</a>\n'
+ ' <a href="solved.html">Solved</a>\n'
+ ' </div>\n'
+ ' <body>\n',
+ ' <h1><b>{}</b></h1>\n'.format('{} Puzzles').format(filt),
+ ' <p>{}</p>\n'.format(link(website + "index.html", 'Hunt Overview')),
+ ' <div>\n',
+ ' <table class="center sortable">\n',
+ ' <thead>\n',
+ ' <tr>\n',
+ ' <th>Puzzle Title/Slack</th>\n',
+ ' <th>Puzzle Link</th>\n',
+ ' <th>Sheet</th>\n',
+ ' <th>Overview</th>\n',
+ ' <th>Answer</th>\n',
+ ' <th>Round(s)</th>\n',
+ ' <th>Tags</th>\n',
+ ' </tr>\n',
+ ' </thead>\n',
+ ' <tbody>\n']
+ solved_code = []
+ unsolved_code = []
+ for puzzle in solved_puzzles:
+ if puzzle['type'] == 'meta':
+ meta = ' [META]'
+ else:
+ meta = ''
+ slack_url = channel_url(puzzle['channel_id'])
+ round_url = link(website + "_".join(rnd.split()) + "_round.html", puzzle['rounds'][0])
+ #assuming one round per puzzle for now
+
+ solved_code += [' <tr>\n',
+ ' <td>{}</td>\n'.format(elink(slack_url, puzzle['name']+meta)),
+ ' <td>{}</td>\n'.format(elink(puzzle['url'], 'Puzzle')),
+ ' <td>{}</td>\n'.format(elink(puzzle['sheet_url'], 'Sheet')),
+ ' <td>{}</td>\n'.format(link(website + "_".join(puzzle['name'].split()) + '.html', 'Overview')),
+ ' <td>{}</td>\n'.format(puzzle['solution']),
+ ' <td>{}</td>\n'.format(round_url),
+ ' <td>{}</td>\n'.format("".join(puzzle['tags'])),
+ ' </tr>\n']
+ for puzzle in unsolved_puzzles:
+ if puzzle['type'] == 'meta':
+ meta = ' [META]'
+ else:
+ meta = ''
+ slack_url = channel_url(puzzle['channel_id'])
+ round_url = link(website + "_".join(rnd.split()) + "_round.html", puzzle['rounds'][0])
+ #assuming one round per puzzle for now
+
+ unsolved_code += [' <tr>\n',
+ ' <td>{}</td>\n'.format(elink(slack_url, puzzle['name']+meta)),
+ ' <td>{}</td>\n'.format(elink(puzzle['url'], 'Puzzle')),
+ ' <td>{}</td>\n'.format(elink(puzzle['sheet_url'], 'Sheet')),
+ ' <td>{}</td>\n'.format(link(website + "_".join(puzzle['name'].split()) + '.html', 'Overview')),
+ ' <td></td>\n',
+ ' <td>{}</td>\n'.format(round_url),
+ ' <td>{}</td>\n'.format("".join(puzzle['tags'])),
+ ' </tr>\n']
+ end = [' </tbody>\n',
+ ' </table>\n',
+ ' </div>\n',
+ ' </body>\n',
+ '</html>\n']
+ if filt == "All":
+ file1 = 'all.html'
+ f = open(file1, "w")
+ for line in start + unsolved_code + solved_code + end:
+ f.write(line)
+ f.close()
+ elif filt == "Solved":
+ file2 = 'solved.html'
+ f = open(file2, 'w')
+ for line in start + solved_code + end:
+ f.write(line)
+ f.close()
+ elif filt == "Unsolved":
+ file3 = 'unsolved.html'
+ f = open(file3, 'w')
+ for line in start + unsolved_code + end:
+ f.write(line)
+ f.close()
+ return None
+
+# Initialize AWS resources to talk to database
+db = boto3.resource('dynamodb')
+table = db.Table("turbot")
+puzzles, rounds = hunt_info(table, "mh2021")
+
+overview(puzzles, rounds)
+for rnd in rounds:
+ round_overview(rnd, puzzles)
+for puzzle in puzzles:
+ puzzle_overview(puzzle)
+puzzle_lists(puzzles, "All")
+puzzle_lists(puzzles, "Solved")
+puzzle_lists(puzzles, "Unsolved")