Basic bingo site
Getty Ritter
7 years ago
1 | #!/usr/bin/env python3 | |
2 | ||
3 | import base64 | |
4 | import flask | |
5 | import random | |
6 | import openpyxl | |
7 | import svgwrite | |
8 | ||
9 | app = flask.Flask(__name__) | |
10 | ||
11 | TEXT_STYLE = 'font-family: "Arial", "Helvetica", sans-serif;' | |
12 | ||
13 | @app.route('/') | |
14 | def index(): | |
15 | return flask.send_from_directory('.', 'page.html') | |
16 | ||
17 | ||
18 | @app.route('/process', methods=['POST']) | |
19 | def process(): | |
20 | if 'file' not in flask.request.files: | |
21 | raise Exception('No file uploaded!') | |
22 | f = flask.request.files['file'] | |
23 | if f.filename == '': | |
24 | raise Exception('No file uploaded!') | |
25 | choices = choices_from_excel(openpyxl.load_workbook(f.stream)) | |
26 | choice_str = base64.urlsafe_b64encode('\x1c'.join(choices).encode('utf-8')) | |
27 | return flask.redirect('/bingo.svg?choices={0}'.format(choice_str.decode('utf-8'))) | |
28 | ||
29 | ||
30 | @app.route('/bingo.svg') | |
31 | def generate(): | |
32 | choice_str = base64.urlsafe_b64decode(flask.request.args.get('choices')) | |
33 | choices = choice_str.decode('utf-8').split('\x1c') | |
34 | print(choices) | |
35 | return mk_svg(choices) | |
36 | ||
37 | ||
38 | def choices_from_excel(wb): | |
39 | sheet = wb.get_sheet_by_name(wb.get_sheet_names()[0]) | |
40 | choices = [] | |
41 | for row in range(1, sheet.max_row + 1): | |
42 | choices.append(sheet.cell(row=row, column=1).value) | |
43 | return choices | |
44 | ||
45 | ||
46 | def mk_svg(choices): | |
47 | choices = list(choices) | |
48 | if len(choices) < 24: | |
49 | raise Exception('Must have at least 24 possible choices!') | |
50 | random.shuffle(choices) | |
51 | d = svgwrite.Drawing(size=('8.5in', '11in')) | |
52 | # t = svgwrite.mixins.Transform.translate('0.25in', '0.25in') | |
53 | for x in range(6): | |
54 | d.add(d.line(('{0}in'.format(x*1.6), '0in'), | |
55 | ('{0}in'.format(x*1.6), '8in'), | |
56 | stroke=svgwrite.rgb(0, 0, 0))).translate(45, 45) | |
57 | for y in range(6): | |
58 | d.add(d.line(('0in', '{0}in'.format(y*1.6)), | |
59 | ('8in', '{0}in'.format(y*1.6)), | |
60 | stroke=svgwrite.rgb(0, 0, 0))).translate(45, 45) | |
61 | for x in range(5): | |
62 | for y in range(5): | |
63 | if x == 2 and y == 2: | |
64 | text = 'FREE\nSQUARE' | |
65 | color = '#b00' | |
66 | else: | |
67 | text = choices.pop() | |
68 | color = '#000' | |
69 | d.add(d.text(text, | |
70 | insert= | |
71 | ('{0}in'.format(x*1.6 + 0.8), | |
72 | '{0}in'.format(y*1.6 + 0.8)), | |
73 | text_anchor='middle', | |
74 | alignment_baseline='central', | |
75 | style=TEXT_STYLE, | |
76 | fill=color)).translate(45, 45) | |
77 | return d.tostring() | |
78 | ||
79 | ||
80 | if __name__ == '__main__': | |
81 | app.run() |
1 | <!DOCTYPE html> | |
2 | <html> | |
3 | <head> | |
4 | <title>Bingo Card Generator</title> | |
5 | ||
6 | <meta charset="utf-8"> | |
7 | <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> | |
8 | <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous"> | |
9 | <style type="text/css" > | |
10 | body { background-color: #eee; } | |
11 | .main { | |
12 | background-color: #fff; | |
13 | width: 800px; | |
14 | margin-left: auto; | |
15 | margin-right: auto; | |
16 | margin-top: 20px; | |
17 | padding: 40px; | |
18 | } | |
19 | .help { padding: 10px; } | |
20 | </style> | |
21 | </head> | |
22 | <body> | |
23 | <div class="main"> | |
24 | <h1>Bingo Card Creator</h1> | |
25 | <div class="help"> | |
26 | To use this, create an excel spreadsheet with all the possible | |
27 | cells you want along the left-hand side, with each cell being | |
28 | a different row but all in the first column, and then upload | |
29 | it here. | |
30 | </div> | |
31 | <div class="help"> | |
32 | Once you get to the bingo card, you can refresh it to create a | |
33 | new bingo card, or bookmark it to create more bingo cards | |
34 | later without having to re-upload the spreadsheet. | |
35 | </div> | |
36 | <div> | |
37 | <form id="mainform" enctype="multipart/form-data" action="/process" method="post"> | |
38 | <div class="form-group" id="bingo-fields"> | |
39 | <input name="file" type="file"> | |
40 | </div> | |
41 | <button type="submit" class="btn btn-primary">Create Bingo Card</button> | |
42 | </form> | |
43 | </div> | |
44 | </div> | |
45 | <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script> | |
46 | <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.11.0/umd/popper.min.js" integrity="sha384-b/U6ypiBEHpOf/4+1nzFpr53nxSS+GLCkfwBdFNTxtclqqenISfwAzpKaMNFNmj4" crossorigin="anonymous"></script> | |
47 | <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/js/bootstrap.min.js" integrity="sha384-h0AbiXch4ZDo7tp9hKZ4TsHbi047NrKGLO3SEJAg45jXxnGIfYzk4Si90RDIqNm1" crossorigin="anonymous"></script> | |
48 | <script> | |
49 | </script> | |
50 | </body> | |
51 | </html> |