Compare commits
38 Commits
master
...
developmen
Author | SHA1 | Date | |
---|---|---|---|
9bed89b067 | |||
7e262e1bdb | |||
8187648933 | |||
d95f30a5e6 | |||
7017188708 | |||
0ff04818ae | |||
aa20416aa1 | |||
0c49d22865 | |||
b2df303632 | |||
e1bce471fe | |||
afbb41f7bd | |||
c6b656cbee | |||
4b0f63f124 | |||
da35b9c051 | |||
5c30a974a6 | |||
713c6beb48 | |||
42534e13a9 | |||
cadccf1a53 | |||
ce4694830d | |||
f7c6081ca0 | |||
a50926b192 | |||
1218dbc911 | |||
f9d76a12ea | |||
8a15cbef51 | |||
34908a2eef | |||
a16d9a4852 | |||
bda80b5a11 | |||
6762226bf2 | |||
a064bd6b9f | |||
49b986834f | |||
fbd17907bd | |||
f6f25d73a4 | |||
68533992b2 | |||
c9708e761e | |||
2d04ae5bd8 | |||
c1962714d1 | |||
e198663adb | |||
c62b8196dd |
17
.env.example
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
SERVER_NAME= # URL where this will be hosted.
|
||||||
|
FLASK_DEBUG=False
|
||||||
|
|
||||||
|
TZ=Europe/London # Time Zone
|
||||||
|
|
||||||
|
## App Configuration
|
||||||
|
SECRET_KEY= # Long, secure, secret string.
|
||||||
|
DATABASE_TYPE=SQLite # SQLite or MySQL, defaults to SQLite
|
||||||
|
DATABASE_HOST= # Required if MySQL. Must match name of Docker service, or provide host if database is on an external server. Defaults to localhost.
|
||||||
|
DATABASE_PORT= # Required if MySQL. Defaults to 3306
|
||||||
|
|
||||||
|
## MySQL Database Configuration (Required if configured to MySQL Database.)
|
||||||
|
# Note that if using the Docker service, these configuration values will also be used when creating the database in the mysql container.
|
||||||
|
MYSQL_RANDOM_ROOT_PASSWORD=True
|
||||||
|
MYSQL_DATABASE= # Required if MySQL.
|
||||||
|
MYSQL_USER= # Required if MySQL
|
||||||
|
MYSQL_PASSWORD= # Required if MySQL. Create secure password string. Note '@' character cannot be used.
|
2
.gitignore
vendored
@ -280,3 +280,5 @@ dist
|
|||||||
.yarn/install-state.gz
|
.yarn/install-state.gz
|
||||||
.pnp.*
|
.pnp.*
|
||||||
|
|
||||||
|
# Ignore Database File
|
||||||
|
**/data/database.db
|
30
docker-compose.yml
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
version: '3.9'
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
app:
|
||||||
|
|
||||||
|
services:
|
||||||
|
nginx:
|
||||||
|
container_name: wanderhome_nginx
|
||||||
|
image: nginx:alpine
|
||||||
|
volumes:
|
||||||
|
- ./src:/usr/share/nginx/html:ro
|
||||||
|
- ./nginx:/etc/nginx:ro
|
||||||
|
ports:
|
||||||
|
- 80:80
|
||||||
|
- 443:443
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
- wanderhome
|
||||||
|
|
||||||
|
wanderhome:
|
||||||
|
container_name: wanderhome_server
|
||||||
|
image: wanderhome
|
||||||
|
build: ./server
|
||||||
|
env_file:
|
||||||
|
- ./.env
|
||||||
|
ports:
|
||||||
|
- 5000:5000
|
||||||
|
volumes:
|
||||||
|
- app:/app/data
|
||||||
|
restart: unless-stopped
|
6
nginx/conf.d/proxy_headers.conf
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Forwarded-Host $host;
|
||||||
|
proxy_set_header X-Forwarded-Port $server_port;
|
17
nginx/conf.d/wanderhome.conf
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
server {
|
||||||
|
server_name _;
|
||||||
|
listen 80 default_server;
|
||||||
|
listen [::]:80 default_server;
|
||||||
|
|
||||||
|
# Proxy API requests to the server
|
||||||
|
location ^~ /api/ {
|
||||||
|
include /etc/nginx/conf.d/proxy_headers.conf;
|
||||||
|
proxy_pass http://wanderhome:5000;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Proxy to the main app for all other requests
|
||||||
|
location / {
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
alias /usr/share/nginx/html/;
|
||||||
|
}
|
||||||
|
}
|
25
nginx/fastcgi.conf
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||||
|
fastcgi_param QUERY_STRING $query_string;
|
||||||
|
fastcgi_param REQUEST_METHOD $request_method;
|
||||||
|
fastcgi_param CONTENT_TYPE $content_type;
|
||||||
|
fastcgi_param CONTENT_LENGTH $content_length;
|
||||||
|
|
||||||
|
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
|
||||||
|
fastcgi_param REQUEST_URI $request_uri;
|
||||||
|
fastcgi_param DOCUMENT_URI $document_uri;
|
||||||
|
fastcgi_param DOCUMENT_ROOT $document_root;
|
||||||
|
fastcgi_param SERVER_PROTOCOL $server_protocol;
|
||||||
|
fastcgi_param REQUEST_SCHEME $scheme;
|
||||||
|
fastcgi_param HTTPS $https if_not_empty;
|
||||||
|
|
||||||
|
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
|
||||||
|
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
|
||||||
|
|
||||||
|
fastcgi_param REMOTE_ADDR $remote_addr;
|
||||||
|
fastcgi_param REMOTE_PORT $remote_port;
|
||||||
|
fastcgi_param SERVER_ADDR $server_addr;
|
||||||
|
fastcgi_param SERVER_PORT $server_port;
|
||||||
|
fastcgi_param SERVER_NAME $server_name;
|
||||||
|
|
||||||
|
# PHP only, required if PHP was built with --enable-force-cgi-redirect
|
||||||
|
fastcgi_param REDIRECT_STATUS 200;
|
24
nginx/fastcgi_params
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
fastcgi_param QUERY_STRING $query_string;
|
||||||
|
fastcgi_param REQUEST_METHOD $request_method;
|
||||||
|
fastcgi_param CONTENT_TYPE $content_type;
|
||||||
|
fastcgi_param CONTENT_LENGTH $content_length;
|
||||||
|
|
||||||
|
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
|
||||||
|
fastcgi_param REQUEST_URI $request_uri;
|
||||||
|
fastcgi_param DOCUMENT_URI $document_uri;
|
||||||
|
fastcgi_param DOCUMENT_ROOT $document_root;
|
||||||
|
fastcgi_param SERVER_PROTOCOL $server_protocol;
|
||||||
|
fastcgi_param REQUEST_SCHEME $scheme;
|
||||||
|
fastcgi_param HTTPS $https if_not_empty;
|
||||||
|
|
||||||
|
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
|
||||||
|
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
|
||||||
|
|
||||||
|
fastcgi_param REMOTE_ADDR $remote_addr;
|
||||||
|
fastcgi_param REMOTE_PORT $remote_port;
|
||||||
|
fastcgi_param SERVER_ADDR $server_addr;
|
||||||
|
fastcgi_param SERVER_PORT $server_port;
|
||||||
|
fastcgi_param SERVER_NAME $server_name;
|
||||||
|
|
||||||
|
# PHP only, required if PHP was built with --enable-force-cgi-redirect
|
||||||
|
fastcgi_param REDIRECT_STATUS 200;
|
98
nginx/mime.types
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
|
||||||
|
types {
|
||||||
|
text/html html htm shtml;
|
||||||
|
text/css css;
|
||||||
|
text/xml xml;
|
||||||
|
image/gif gif;
|
||||||
|
image/jpeg jpeg jpg;
|
||||||
|
application/javascript js;
|
||||||
|
application/atom+xml atom;
|
||||||
|
application/rss+xml rss;
|
||||||
|
|
||||||
|
text/mathml mml;
|
||||||
|
text/plain txt;
|
||||||
|
text/vnd.sun.j2me.app-descriptor jad;
|
||||||
|
text/vnd.wap.wml wml;
|
||||||
|
text/x-component htc;
|
||||||
|
|
||||||
|
image/png png;
|
||||||
|
image/svg+xml svg svgz;
|
||||||
|
image/tiff tif tiff;
|
||||||
|
image/vnd.wap.wbmp wbmp;
|
||||||
|
image/webp webp;
|
||||||
|
image/x-icon ico;
|
||||||
|
image/x-jng jng;
|
||||||
|
image/x-ms-bmp bmp;
|
||||||
|
|
||||||
|
font/woff woff;
|
||||||
|
font/woff2 woff2;
|
||||||
|
|
||||||
|
application/java-archive jar war ear;
|
||||||
|
application/json json;
|
||||||
|
application/mac-binhex40 hqx;
|
||||||
|
application/msword doc;
|
||||||
|
application/pdf pdf;
|
||||||
|
application/postscript ps eps ai;
|
||||||
|
application/rtf rtf;
|
||||||
|
application/vnd.apple.mpegurl m3u8;
|
||||||
|
application/vnd.google-earth.kml+xml kml;
|
||||||
|
application/vnd.google-earth.kmz kmz;
|
||||||
|
application/vnd.ms-excel xls;
|
||||||
|
application/vnd.ms-fontobject eot;
|
||||||
|
application/vnd.ms-powerpoint ppt;
|
||||||
|
application/vnd.oasis.opendocument.graphics odg;
|
||||||
|
application/vnd.oasis.opendocument.presentation odp;
|
||||||
|
application/vnd.oasis.opendocument.spreadsheet ods;
|
||||||
|
application/vnd.oasis.opendocument.text odt;
|
||||||
|
application/vnd.openxmlformats-officedocument.presentationml.presentation
|
||||||
|
pptx;
|
||||||
|
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
|
||||||
|
xlsx;
|
||||||
|
application/vnd.openxmlformats-officedocument.wordprocessingml.document
|
||||||
|
docx;
|
||||||
|
application/vnd.wap.wmlc wmlc;
|
||||||
|
application/wasm wasm;
|
||||||
|
application/x-7z-compressed 7z;
|
||||||
|
application/x-cocoa cco;
|
||||||
|
application/x-java-archive-diff jardiff;
|
||||||
|
application/x-java-jnlp-file jnlp;
|
||||||
|
application/x-makeself run;
|
||||||
|
application/x-perl pl pm;
|
||||||
|
application/x-pilot prc pdb;
|
||||||
|
application/x-rar-compressed rar;
|
||||||
|
application/x-redhat-package-manager rpm;
|
||||||
|
application/x-sea sea;
|
||||||
|
application/x-shockwave-flash swf;
|
||||||
|
application/x-stuffit sit;
|
||||||
|
application/x-tcl tcl tk;
|
||||||
|
application/x-x509-ca-cert der pem crt;
|
||||||
|
application/x-xpinstall xpi;
|
||||||
|
application/xhtml+xml xhtml;
|
||||||
|
application/xspf+xml xspf;
|
||||||
|
application/zip zip;
|
||||||
|
|
||||||
|
application/octet-stream bin exe dll;
|
||||||
|
application/octet-stream deb;
|
||||||
|
application/octet-stream dmg;
|
||||||
|
application/octet-stream iso img;
|
||||||
|
application/octet-stream msi msp msm;
|
||||||
|
|
||||||
|
audio/midi mid midi kar;
|
||||||
|
audio/mpeg mp3;
|
||||||
|
audio/ogg ogg;
|
||||||
|
audio/x-m4a m4a;
|
||||||
|
audio/x-realaudio ra;
|
||||||
|
|
||||||
|
video/3gpp 3gpp 3gp;
|
||||||
|
video/mp2t ts;
|
||||||
|
video/mp4 mp4;
|
||||||
|
video/mpeg mpeg mpg;
|
||||||
|
video/quicktime mov;
|
||||||
|
video/webm webm;
|
||||||
|
video/x-flv flv;
|
||||||
|
video/x-m4v m4v;
|
||||||
|
video/x-mng mng;
|
||||||
|
video/x-ms-asf asx asf;
|
||||||
|
video/x-ms-wmv wmv;
|
||||||
|
video/x-msvideo avi;
|
||||||
|
}
|
30
nginx/nginx.conf
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
user nginx;
|
||||||
|
worker_processes auto;
|
||||||
|
|
||||||
|
error_log /var/log/nginx/error.log notice;
|
||||||
|
pid /var/run/nginx.pid;
|
||||||
|
|
||||||
|
|
||||||
|
events {
|
||||||
|
worker_connections 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
http {
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
|
||||||
|
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||||
|
'$status $body_bytes_sent "$http_referer" '
|
||||||
|
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||||
|
|
||||||
|
access_log /var/log/nginx/access.log main;
|
||||||
|
|
||||||
|
sendfile on;
|
||||||
|
#tcp_nopush on;
|
||||||
|
keepalive_timeout 65;
|
||||||
|
server_tokens off;
|
||||||
|
#gzip on;
|
||||||
|
include /etc/nginx/conf.d/*.conf;
|
||||||
|
include /etc/nginx/conf.d/sites-enabled/*.conf;
|
||||||
|
}
|
17
nginx/scgi_params
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
|
||||||
|
scgi_param REQUEST_METHOD $request_method;
|
||||||
|
scgi_param REQUEST_URI $request_uri;
|
||||||
|
scgi_param QUERY_STRING $query_string;
|
||||||
|
scgi_param CONTENT_TYPE $content_type;
|
||||||
|
|
||||||
|
scgi_param DOCUMENT_URI $document_uri;
|
||||||
|
scgi_param DOCUMENT_ROOT $document_root;
|
||||||
|
scgi_param SCGI 1;
|
||||||
|
scgi_param SERVER_PROTOCOL $server_protocol;
|
||||||
|
scgi_param REQUEST_SCHEME $scheme;
|
||||||
|
scgi_param HTTPS $https if_not_empty;
|
||||||
|
|
||||||
|
scgi_param REMOTE_ADDR $remote_addr;
|
||||||
|
scgi_param REMOTE_PORT $remote_port;
|
||||||
|
scgi_param SERVER_PORT $server_port;
|
||||||
|
scgi_param SERVER_NAME $server_name;
|
16
nginx/uwsgi_params
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
uwsgi_param QUERY_STRING $query_string;
|
||||||
|
uwsgi_param REQUEST_METHOD $request_method;
|
||||||
|
uwsgi_param CONTENT_TYPE $content_type;
|
||||||
|
uwsgi_param CONTENT_LENGTH $content_length;
|
||||||
|
|
||||||
|
uwsgi_param REQUEST_URI $request_uri;
|
||||||
|
uwsgi_param PATH_INFO $document_uri;
|
||||||
|
uwsgi_param DOCUMENT_ROOT $document_root;
|
||||||
|
uwsgi_param SERVER_PROTOCOL $server_protocol;
|
||||||
|
uwsgi_param REQUEST_SCHEME $scheme;
|
||||||
|
uwsgi_param HTTPS $https if_not_empty;
|
||||||
|
|
||||||
|
uwsgi_param REMOTE_ADDR $remote_addr;
|
||||||
|
uwsgi_param REMOTE_PORT $remote_port;
|
||||||
|
uwsgi_param SERVER_PORT $server_port;
|
||||||
|
uwsgi_param SERVER_NAME $server_name;
|
4
server/.dockerignore
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
env/
|
||||||
|
venv/
|
||||||
|
__pycache__/
|
||||||
|
data/database.db
|
143
server/CLIent.py
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
import requests
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
SERVER = 'http://127.0.0.1:5000/'
|
||||||
|
|
||||||
|
INSTRUCTIONS = [
|
||||||
|
'---- Instructions ----',
|
||||||
|
'This quiz will present a series of multiple choice questions.',
|
||||||
|
'You can select your response by entering the index number corresponding with your chosen option(s).',
|
||||||
|
'For most questions, you may only select one option.',
|
||||||
|
'Some questions allow multiple options to be selected, and will advice you accordingly.',
|
||||||
|
'To select multiple options, enter their index number(s) separated by commas.'
|
||||||
|
]
|
||||||
|
|
||||||
|
WELCOME = [
|
||||||
|
'---- What *Wanderhome* Playbook Are You? ----',
|
||||||
|
'',
|
||||||
|
'*Wanderhome* is about going on a journey, and reflecting on how the journey changes us.',
|
||||||
|
'The road is full of myriad travellers, each with their rich lives and stories.',
|
||||||
|
'This quiz will help you figure out what kinds of stories you will bring to the table.',
|
||||||
|
'Will you join me?',
|
||||||
|
''
|
||||||
|
]
|
||||||
|
|
||||||
|
GOODBYE = [
|
||||||
|
'---- Thank you for taking the What Wanderhome Playbook Are You? Quiz ----',
|
||||||
|
'Programmed and Written by Vivek Santayana',
|
||||||
|
'Based on the table-top role-playing game *Wanderhome* by Jay Dragon, published by Possum Creek Games.',
|
||||||
|
'Check out Wanderhome at https://possumcreekgames.com/en-gb/pages/wanderhome',
|
||||||
|
'I strongly recommend this game: it is amazing!',
|
||||||
|
'',
|
||||||
|
'Good bye, and may your journeys surprise you!',
|
||||||
|
''
|
||||||
|
]
|
||||||
|
|
||||||
|
def sanitise_text(raw_text:str) -> str:
|
||||||
|
format_replacements = {
|
||||||
|
'<p>': '',
|
||||||
|
'</p>': '\n',
|
||||||
|
'<em>': '*',
|
||||||
|
'</em>': '*',
|
||||||
|
'<strong>': '**',
|
||||||
|
'</strong>': '**',
|
||||||
|
'æ': 'æ',
|
||||||
|
'‘': '‘',
|
||||||
|
'’': '’',
|
||||||
|
'ï': 'ï'
|
||||||
|
}
|
||||||
|
for key, value in format_replacements.items():
|
||||||
|
raw_text = raw_text.replace(key, value)
|
||||||
|
|
||||||
|
return raw_text
|
||||||
|
|
||||||
|
def render(output:list):
|
||||||
|
print('\n'.join(['', *output, '']))
|
||||||
|
x = input('Press Enter to proceed.')
|
||||||
|
|
||||||
|
def render_question(question) -> str:
|
||||||
|
print(sanitise_text(raw_text=question['question']))
|
||||||
|
print('-- Options --')
|
||||||
|
for index, answer in enumerate(question['answers']):
|
||||||
|
print(index, sanitise_text(raw_text=answer))
|
||||||
|
print('')
|
||||||
|
prompt = f'Please select up to {question["select"]} options, separated by commas: ' if question['select'] > 1 else 'Please select an answer: '
|
||||||
|
return input(prompt)
|
||||||
|
|
||||||
|
def parse_input(raw_answer) -> list:
|
||||||
|
try: return list(set(int(answer) for answer in raw_answer.split(',')))
|
||||||
|
except: return [ ]
|
||||||
|
|
||||||
|
def process_input(answer_input:list, question:dict) -> Union[list, int]: return answer_input[0] if question['select'] == 1 else answer_input
|
||||||
|
|
||||||
|
def show_input_error(): input('\n *** Your response was invalid. Please try again *** \n\nPress enter to retry.')
|
||||||
|
|
||||||
|
def validate_answer(answers:list, question:dict) -> bool:
|
||||||
|
def check_range(answer:int) -> bool: return answer >= 0 and answer < len(question['answers'])
|
||||||
|
def check_input_number(answers:list) -> bool: return len(answers) > 0 and len(answers) <= question['select']
|
||||||
|
if not check_input_number(answers=answers): return False
|
||||||
|
try: return all([check_range(answer=int(answer)) for answer in answers])
|
||||||
|
except ValueError: return False
|
||||||
|
|
||||||
|
def render_questions() -> dict:
|
||||||
|
answers = [ ]
|
||||||
|
try:
|
||||||
|
questions = requests.get(url=f'{SERVER}/api/questions/').json()
|
||||||
|
except Exception as exception:
|
||||||
|
print(exception)
|
||||||
|
quit()
|
||||||
|
for index, question in enumerate(questions):
|
||||||
|
answer_valid = False
|
||||||
|
while not answer_valid:
|
||||||
|
print('')
|
||||||
|
print(f'---- Question {index + 1} ----')
|
||||||
|
answer = render_question(question)
|
||||||
|
answer = parse_input(answer)
|
||||||
|
answer_valid = validate_answer(answer, question)
|
||||||
|
if not answer_valid: show_input_error()
|
||||||
|
answers.append(process_input(answer, question))
|
||||||
|
try: return requests.post(url=f'{SERVER}api/submit/',json=answers).json()
|
||||||
|
except Exception as exception:
|
||||||
|
print(exception)
|
||||||
|
quit()
|
||||||
|
|
||||||
|
def render_results(results:dict):
|
||||||
|
print('\n---- Results ----\n')
|
||||||
|
plural = len(results['playbooks']) > 1
|
||||||
|
print(f'Your { "results are" if plural else "result is"}:')
|
||||||
|
for playbook in results['playbooks']:
|
||||||
|
name = list(playbook.keys())[0]
|
||||||
|
data = list(playbook.values())[0]
|
||||||
|
animals = ''
|
||||||
|
for index, animal in enumerate(data['animals']):
|
||||||
|
article = 'an' if animal[0] in ['a','e','i', 'o', 'u'] else 'a'
|
||||||
|
conjunction = ', or ' if index == len(data['animals']) - 2 else '.' if index == len(data["animals"]) - 1 else ', '
|
||||||
|
animals += f'{article} {animal}{conjunction}'
|
||||||
|
print(f'\n**The {name[0].upper()}{name[1:]}**\n(pp.{data["pages"]})\n')
|
||||||
|
print(sanitise_text(data['flavour']),'\n')
|
||||||
|
print(sanitise_text(data['blurb']),'\n')
|
||||||
|
print('You are alive. Your care is ',sanitise_text(data['care']),'.\n')
|
||||||
|
print('Your animal form is ', animals, '\n')
|
||||||
|
print('You can always:')
|
||||||
|
for action in data['actions']:
|
||||||
|
print('-- ', sanitise_text(action))
|
||||||
|
input('\nPress enter to continue.')
|
||||||
|
print('\n-- Your score for each playbook: --')
|
||||||
|
for playbook, score in results['all_playbooks'].items(): print(f'The {playbook[0].upper()}{playbook[1:]}: {score*"x"} ({round(100*score/results["max_score"])}%)')
|
||||||
|
|
||||||
|
def run_quiz():
|
||||||
|
render(output=WELCOME)
|
||||||
|
render(output=INSTRUCTIONS)
|
||||||
|
run = True
|
||||||
|
while run:
|
||||||
|
render_results(render_questions())
|
||||||
|
repeat_prompt_valid = False
|
||||||
|
while not repeat_prompt_valid:
|
||||||
|
repeat_prompt = input('\nDo you want to do the quiz again? (y) Yes / (n) No: ')
|
||||||
|
if repeat_prompt.lower() in ['y', 'n', 'yes', 'no']: repeat_prompt_valid = True
|
||||||
|
if not repeat_prompt_valid: show_input_error()
|
||||||
|
run = True if repeat_prompt.lower() in ['y', 'yes'] else False
|
||||||
|
render(GOODBYE)
|
||||||
|
|
||||||
|
"""Running the Quiz"""
|
||||||
|
if __name__ == '__main__': run_quiz()
|
8
server/Dockerfile
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
FROM python:3.10-slim
|
||||||
|
ARG DATA=./data/
|
||||||
|
ENV DATA=$DATA
|
||||||
|
WORKDIR /app
|
||||||
|
COPY . .
|
||||||
|
RUN pip install --upgrade pip && pip install -r requirements.txt
|
||||||
|
RUN chmod +x install.py && ./install.py
|
||||||
|
CMD [ "gunicorn", "-b", "0.0.0.0:5000", "-w", "5", "wsgi:app" ]
|
28
server/app/__init__.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
from .config import Development as Config
|
||||||
|
from .models import *
|
||||||
|
from .extensions import cors, db
|
||||||
|
|
||||||
|
from flask import Flask
|
||||||
|
from werkzeug.middleware.proxy_fix import ProxyFix
|
||||||
|
|
||||||
|
def create_app():
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.config.from_object(Config())
|
||||||
|
app.wsgi_app = ProxyFix(app.wsgi_app, x_proto = 1, x_host = 1)
|
||||||
|
|
||||||
|
cors.init_app(app=app)
|
||||||
|
db.init_app(app=app)
|
||||||
|
|
||||||
|
from .views import views
|
||||||
|
|
||||||
|
app.register_blueprint(
|
||||||
|
blueprint = views,
|
||||||
|
url_prefix = '/api'
|
||||||
|
)
|
||||||
|
|
||||||
|
"""Create database before first request"""
|
||||||
|
@app.before_first_request
|
||||||
|
def _create_database_tables():
|
||||||
|
with app.app_context(): db.create_all()
|
||||||
|
|
||||||
|
return app
|
38
server/app/config.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
load_dotenv('../.env')
|
||||||
|
|
||||||
|
class Config(object):
|
||||||
|
"""Basic App Configuration"""
|
||||||
|
APP_HOST = '0.0.0.0'
|
||||||
|
DATA = './data/'
|
||||||
|
DEBUG = False
|
||||||
|
TESTING = False
|
||||||
|
SECRET_KEY = os.getenv('SECRET_KEY')
|
||||||
|
SERVER_NAME = os.getenv('SERVER_NAME')
|
||||||
|
SESSION_COOKIE_SECURE = True
|
||||||
|
|
||||||
|
"""Database Driver Configuration"""
|
||||||
|
DATABASE_TYPE = os.getenv('DATABASE_TYPE') or 'SQLite'
|
||||||
|
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||||
|
|
||||||
|
if DATABASE_TYPE.lower() == 'mysql' and os.getenv('MYSQL_DATABASE') and os.getenv('MYSQL_USER') and os.getenv('MYSQL_PASSWORD'):
|
||||||
|
DATABASE_HOST = os.getenv('DATABASE_HOST') or 'localhost'
|
||||||
|
DATABASE_PORT = int(os.getenv('DATABASE_PORT') or 3306)
|
||||||
|
MYSQL_DATABASE = os.getenv('MYSQL_DATABASE')
|
||||||
|
MYSQL_USER = os.getenv('MYSQL_USER')
|
||||||
|
MYSQL_PASSWORD = os.getenv('MYSQL_PASSWORD')
|
||||||
|
SQLALCHEMY_DATABASE_URI = f'mysql+pymysql://{MYSQL_USER}:{MYSQL_PASSWORD}@{DATABASE_HOST}:{DATABASE_PORT}/{MYSQL_DATABASE}'
|
||||||
|
else: SQLALCHEMY_DATABASE_URI = f'sqlite:///{Path(os.path.abspath(f"{DATA}/database.db"))}'
|
||||||
|
|
||||||
|
class Production(Config): pass
|
||||||
|
|
||||||
|
class Development(Config):
|
||||||
|
APP_HOST = '127.0.0.1'
|
||||||
|
DEBUG = True
|
||||||
|
SERVER_NAME = '127.0.0.1:5000'
|
||||||
|
SESSION_COOKIE_SECURE = False
|
||||||
|
|
||||||
|
class Testing(Development):
|
||||||
|
TESTING = True
|
4
server/app/extensions.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
from flask_cors import CORS
|
||||||
|
cors = CORS()
|
||||||
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
db = SQLAlchemy()
|
1
server/app/models/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from .entry import Entry
|
27
server/app/models/entry.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
from ..extensions import db
|
||||||
|
from ..tools.data import declutter_results
|
||||||
|
from ..tools.models import JsonString
|
||||||
|
|
||||||
|
from sqlalchemy_json import MutableJson
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
class Entry(db.Model):
|
||||||
|
|
||||||
|
id = db.Column(db.String(36), primary_key=True)
|
||||||
|
timestamp = db.Column(db.DateTime, nullable=False)
|
||||||
|
answers = db.Column(JsonString, nullable=False)
|
||||||
|
results = db.Column(MutableJson, nullable=False)
|
||||||
|
|
||||||
|
def __repr__(self) -> str: return f'Entry with <id {self.id}>.'
|
||||||
|
|
||||||
|
def __init__(self, answers:list, results:dict):
|
||||||
|
self.id = uuid4().hex
|
||||||
|
self.timestamp = datetime.utcnow()
|
||||||
|
self.answers = answers.copy()
|
||||||
|
self.results = declutter_results(results)
|
||||||
|
|
||||||
|
def add(self):
|
||||||
|
db.session.add(self)
|
||||||
|
db.session.commit()
|
0
server/app/tools/__init__.py
Normal file
24
server/app/tools/data.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
from flask import current_app as app
|
||||||
|
|
||||||
|
import json
|
||||||
|
from os.path import isfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from copy import deepcopy
|
||||||
|
|
||||||
|
def check_file(filename:str) -> bool:
|
||||||
|
data_dir = Path(app.config.get('DATA'))
|
||||||
|
if isfile(f'./{data_dir}/{filename}'): return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def load(filename:str) -> dict:
|
||||||
|
data_dir = Path(app.config.get('DATA'))
|
||||||
|
with open(f'./{data_dir}/{filename}') as file: return json.load(file)
|
||||||
|
|
||||||
|
def declutter_results(raw_results:dict) -> dict:
|
||||||
|
data = deepcopy(raw_results)
|
||||||
|
playbook_lists = [ ]
|
||||||
|
for item in data['playbooks']:
|
||||||
|
playbook_lists.append(list(item.keys())[0])
|
||||||
|
data['playbooks'] = playbook_lists
|
||||||
|
return data
|
16
server/app/tools/models.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
from sqlalchemy import JSON, TypeDecorator
|
||||||
|
from sqlalchemy.dialects.sqlite import JSON
|
||||||
|
|
||||||
|
from json import dumps, loads
|
||||||
|
|
||||||
|
|
||||||
|
class JsonString(TypeDecorator):
|
||||||
|
"""Enables JSON storage by encoding and decoding on the fly."""
|
||||||
|
|
||||||
|
impl = JSON
|
||||||
|
|
||||||
|
def process_bind_param(self, value, dialect):
|
||||||
|
return dumps(value)
|
||||||
|
|
||||||
|
def process_result_value(self, value, dialect):
|
||||||
|
return loads(value)
|
37
server/app/tools/quiz.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
from .data import load
|
||||||
|
|
||||||
|
def render_questions() -> list:
|
||||||
|
data = load('questions.json')
|
||||||
|
for question in data:
|
||||||
|
_answers = [ answer['text'] for answer in question['answers'] ]
|
||||||
|
question['answers'] = _answers
|
||||||
|
question.pop('max', None)
|
||||||
|
return data
|
||||||
|
|
||||||
|
def evaluate_answers(answers:list) -> dict:
|
||||||
|
playbooks = load('playbooks.json')
|
||||||
|
questions = load('questions.json')
|
||||||
|
scores = dict.fromkeys(playbooks,0)
|
||||||
|
for index, answer in enumerate(answers):
|
||||||
|
question = questions[index]
|
||||||
|
if type(answer) is list:
|
||||||
|
answer = answer[0:question['select']]
|
||||||
|
for _answer in answer:
|
||||||
|
for match in question['answers'][int(_answer)]['matches']: scores[match] += 1
|
||||||
|
else:
|
||||||
|
for match in question['answers'][int(answer)]['matches']: scores[match] += 1
|
||||||
|
return scores
|
||||||
|
|
||||||
|
def compile_results(results:dict) -> dict:
|
||||||
|
output = {
|
||||||
|
'all_playbooks': results.copy(),
|
||||||
|
'playbooks': [ ],
|
||||||
|
'score': max(results.values()),
|
||||||
|
'max_score': 0
|
||||||
|
}
|
||||||
|
for question in load('questions.json'):
|
||||||
|
output['max_score'] += question.pop('max', 1)
|
||||||
|
playbooks = load('playbooks.json')
|
||||||
|
for playbook, score in results.items():
|
||||||
|
if score == output['score']: output['playbooks'].append({playbook: playbooks[playbook].copy()})
|
||||||
|
return output
|
65
server/app/views.py
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
from .models import Entry
|
||||||
|
from .tools.data import load
|
||||||
|
from .tools.quiz import compile_results, evaluate_answers, render_questions
|
||||||
|
|
||||||
|
from flask import Blueprint, jsonify, request
|
||||||
|
|
||||||
|
import numpy
|
||||||
|
|
||||||
|
views = Blueprint(
|
||||||
|
name='views',
|
||||||
|
import_name=__name__
|
||||||
|
)
|
||||||
|
|
||||||
|
@views.route('/questions/')
|
||||||
|
def _questions():
|
||||||
|
return render_questions()
|
||||||
|
|
||||||
|
@views.route('/submit/', methods=['POST'])
|
||||||
|
def _submit():
|
||||||
|
answers = request.json
|
||||||
|
scores = evaluate_answers(answers)
|
||||||
|
results = compile_results(results=scores)
|
||||||
|
new_entry = Entry(answers=answers, results=results)
|
||||||
|
new_entry.add()
|
||||||
|
return jsonify(results)
|
||||||
|
|
||||||
|
@views.route('/count/')
|
||||||
|
def _count():
|
||||||
|
return jsonify(len(Entry.query.all()))
|
||||||
|
|
||||||
|
@views.route('/playbooks/')
|
||||||
|
def _playbooks():
|
||||||
|
playbooks = dict.fromkeys(load('playbooks.json'), 0)
|
||||||
|
for entry in Entry.query.all():
|
||||||
|
for _playbook in entry.results['playbooks']: playbooks[_playbook] += 1
|
||||||
|
return playbooks
|
||||||
|
|
||||||
|
@views.route('/answers/')
|
||||||
|
def _answers():
|
||||||
|
answers = { }
|
||||||
|
for index, question in enumerate(render_questions()):
|
||||||
|
answers[index] = { }
|
||||||
|
for _index, answer in enumerate(question['answers']): answers[index][_index] = 0
|
||||||
|
for entry in Entry.query.all():
|
||||||
|
for index, answer in enumerate(entry.answers):
|
||||||
|
if type(answer) is list:
|
||||||
|
for option in answer: answers[index][option] += 1
|
||||||
|
else:
|
||||||
|
answers[index][answer] += 1
|
||||||
|
return list(answers.values())
|
||||||
|
|
||||||
|
@views.route('/scores/')
|
||||||
|
def _scores():
|
||||||
|
playbooks = { playbook: list() for playbook in load('playbooks.json') }
|
||||||
|
for playbook, scores in playbooks.items():
|
||||||
|
for entry in Entry.query.all():
|
||||||
|
score = entry.results['all_playbooks'][playbook]
|
||||||
|
percentage_score = 100 * score/entry.results['max_score']
|
||||||
|
scores.append(percentage_score)
|
||||||
|
output = { playbook: dict() for playbook in playbooks }
|
||||||
|
for playbook, scores in playbooks.items():
|
||||||
|
output[playbook]['mean'] = numpy.mean(scores)
|
||||||
|
output[playbook]['median'] = numpy.median(scores)
|
||||||
|
output[playbook]['standard_deviation'] = numpy.std(scores)
|
||||||
|
return output
|
333
server/data/playbooks.json
Normal file
@ -0,0 +1,333 @@
|
|||||||
|
{
|
||||||
|
"caretaker": {
|
||||||
|
"blurb": "The Caretaker pays attention and tends to the small and forgotten gods, helping each one find a home.",
|
||||||
|
"pages": "48-51",
|
||||||
|
"flavour": "Someone must pay attention to all the small and forgotten things in the world. Someone must listen to the voiceless.",
|
||||||
|
"care": "tender, supportive, and silent",
|
||||||
|
"animals": [
|
||||||
|
"stoat",
|
||||||
|
"salamander",
|
||||||
|
"lemur",
|
||||||
|
"crow",
|
||||||
|
"reflective animal",
|
||||||
|
"quiet animal"
|
||||||
|
],
|
||||||
|
"actions": [
|
||||||
|
"Pause, tilt your head to the side, and keep going.",
|
||||||
|
"Play with one of your gods.",
|
||||||
|
"Say something in silence better than words can.",
|
||||||
|
"Notice a little friend everyone else overlooked.",
|
||||||
|
"Say: ‘<em>Hold this.</em>’",
|
||||||
|
"Ask: ‘<em>Hush, can you hear that?</em>’"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"dancer": {
|
||||||
|
"blurb": "The Dancer opens up to all the magical and strange forces in the world, inviting them to dance and exist in the moment.",
|
||||||
|
"pages": "52-55",
|
||||||
|
"flavour": "Your inner fire is a song in your heart, not a voice in your head. The world will not quiet you.",
|
||||||
|
"care": "intense, vocal, and full of light",
|
||||||
|
"animals": [
|
||||||
|
"fox",
|
||||||
|
"heron",
|
||||||
|
"tamarin",
|
||||||
|
"skink",
|
||||||
|
"lively animal",
|
||||||
|
"hopeful animal"
|
||||||
|
],
|
||||||
|
"actions": [
|
||||||
|
"Dance idly.",
|
||||||
|
"Laugh and smile.",
|
||||||
|
"Leap up on top of something.",
|
||||||
|
"Give a song to those around you.",
|
||||||
|
"Ask: ‘Will you dance a dance with me?’",
|
||||||
|
"Ask: ‘Do you want to be my friend?’"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"exile": {
|
||||||
|
"blurb": "Banished from their homeland, the Exile travels through the Hæth looking for somewhere that can take them in and help them heal.",
|
||||||
|
"pages": "56-59",
|
||||||
|
"flavour": "Your past clings to your shoulders like an old woolen cloak. Someday, perhaps, you can return to your home.",
|
||||||
|
"care": "fragile, skittish, and terrified of being broken again",
|
||||||
|
"animals": [
|
||||||
|
"deer",
|
||||||
|
"tiger",
|
||||||
|
"eagle",
|
||||||
|
"skunk",
|
||||||
|
"rare animal",
|
||||||
|
"nomadic animal"
|
||||||
|
],
|
||||||
|
"actions": [
|
||||||
|
"Say an expression in your traditional language.",
|
||||||
|
"Keep an eye on the exits.",
|
||||||
|
"Push something out of sight or out of mind.",
|
||||||
|
"Play a tune that reminds you of home.",
|
||||||
|
"Say: ‘You look familiar.’",
|
||||||
|
"Ask: ‘Can I tell you a story about my home?’"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"firelight": {
|
||||||
|
"blurb": "The Firelight is accompanied by a firefly that lights their path and helps guide people through the world.",
|
||||||
|
"pages": "60-63",
|
||||||
|
"flavour": "It is easy to get lost in the darkness and the deep. The firefly at your side will always guide the way.",
|
||||||
|
"care": "forward-thinking, mutual, and shining bright",
|
||||||
|
"animals": [
|
||||||
|
"racoon",
|
||||||
|
"olm",
|
||||||
|
"albatross",
|
||||||
|
"sphynx cat",
|
||||||
|
"nocturnal animal",
|
||||||
|
"steady animal"
|
||||||
|
],
|
||||||
|
"actions": [
|
||||||
|
"Shrug.",
|
||||||
|
"Pet your firefly.",
|
||||||
|
"Illuminate all that is hard to see.",
|
||||||
|
"Keep walking.",
|
||||||
|
"Say: ‘Watch your step.’",
|
||||||
|
"Ask: ‘Do you need a hand?’"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"fool": {
|
||||||
|
"blurb": "The Fool is unused to and unfamiliar with the complicated and tangled world outside, and approaches everything with the same naїve optimism.",
|
||||||
|
"pages": "64-67",
|
||||||
|
"flavour": "Life is an adventure, and there’s never shame in learning new things.",
|
||||||
|
"care": "silly, naïve, and more profound than most expect",
|
||||||
|
"animals": [
|
||||||
|
"parrot",
|
||||||
|
"star-nosed mole",
|
||||||
|
"samoyed",
|
||||||
|
"anole",
|
||||||
|
"goofy animal",
|
||||||
|
"oblivious animal"
|
||||||
|
],
|
||||||
|
"actions": [
|
||||||
|
"Meander around.",
|
||||||
|
"Give great advice.",
|
||||||
|
"Give terrible advice.",
|
||||||
|
"Do something that’s actually pretty funny.",
|
||||||
|
"Ask: ‘But why?’",
|
||||||
|
"Ask: ‘What’s going on?’"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"guardian": {
|
||||||
|
"blurb": "The Guardian takes care of a ward, a young child with a difficult past and in desperate need of care.",
|
||||||
|
"pages": "68-71",
|
||||||
|
"flavour": "Hold your ward close to your heart. Someday the world will hurt them, but this will not be that day.",
|
||||||
|
"care": "paternal, protective, and unconditional",
|
||||||
|
"animals": [
|
||||||
|
"bear",
|
||||||
|
"hen",
|
||||||
|
"wolf",
|
||||||
|
"rabbit",
|
||||||
|
"wary animal",
|
||||||
|
"strong animal"
|
||||||
|
],
|
||||||
|
"actions": [
|
||||||
|
"Sigh and shake your head.",
|
||||||
|
"Keep both eyes on someone.",
|
||||||
|
"Derive a practical lesson from a situation.",
|
||||||
|
"Tell a story that makes someone embarrassed.",
|
||||||
|
"Ask: ‘Where do you think you’re going?’",
|
||||||
|
"Ask: ‘Where is my ward?’"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"moth-tender": {
|
||||||
|
"blurb": "The Moth-Tender assists the carrier moths that fly across the Hæth, delivering letters and parcels to everyone who needs them.",
|
||||||
|
"pages": "72-75",
|
||||||
|
"flavour": "Carrier moths travel across the Hæth, bringing news, letters, and tiny boxes. You wander the land, keeping an eye on these moths and their towers.",
|
||||||
|
"care": "consistent and prompt, and it arrives in small packages",
|
||||||
|
"animals": [
|
||||||
|
"bat",
|
||||||
|
"horse",
|
||||||
|
"pigeon",
|
||||||
|
"rabbit",
|
||||||
|
"persistent animal",
|
||||||
|
"dogged animal"
|
||||||
|
],
|
||||||
|
"actions": [
|
||||||
|
"Follow the moths.",
|
||||||
|
"Fidget.",
|
||||||
|
"Write something down on a piece of paper.",
|
||||||
|
"Tell everyone what phase the moon is in right now.",
|
||||||
|
"Ask: ‘Have you heard the news?’",
|
||||||
|
"Say: ‘I have a letter for you!’"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"peddler": {
|
||||||
|
"blurb": "The Peddler transports supplies and wares from place to place, ensuring that each community has access to everything they need to survive.",
|
||||||
|
"pages": "76-79",
|
||||||
|
"flavour": "The road is long and filled with merchants, traders, and everyone else doing their part to make sure everywhere in the Hæth is provided for. No one’s home can stand alone.",
|
||||||
|
"care": "material, solid, and dependable",
|
||||||
|
"animals": [
|
||||||
|
"donkey",
|
||||||
|
"condor",
|
||||||
|
"llama",
|
||||||
|
"crocodile",
|
||||||
|
"rugged animal",
|
||||||
|
"tireless animal"
|
||||||
|
],
|
||||||
|
"actions": [
|
||||||
|
"Know someone who can help.",
|
||||||
|
"Intensely examine something.",
|
||||||
|
"Sit down and do the math.",
|
||||||
|
"Trade for or trade away one of your many wares.",
|
||||||
|
"Ask: ‘What can I do for you?’",
|
||||||
|
"Say: ‘I have a deal for you.’"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"pilgrim": {
|
||||||
|
"blurb": "In search of a faraway place, the Pilgrim is carried by their faith and desire to reach a home that might not even exist.",
|
||||||
|
"pages": "80-83",
|
||||||
|
"flavour": "The gods have given you a path forward, a place you hope can finally give you what you seek. Some days you worry you’ll never make it there at all.",
|
||||||
|
"care": "enduring, faithful, and expressed one step at a time",
|
||||||
|
"animals": [
|
||||||
|
"ibis",
|
||||||
|
"bison",
|
||||||
|
"ferret",
|
||||||
|
"newt",
|
||||||
|
"devoted animal",
|
||||||
|
"ceaseless animal"
|
||||||
|
],
|
||||||
|
"actions": [
|
||||||
|
"Recite a small prayer.",
|
||||||
|
"Chatter away.",
|
||||||
|
"Shield yourself from harsh conditions.",
|
||||||
|
"Place your fate in improbable coincidence, and have it work out.",
|
||||||
|
"Ask: ‘Do you think we’ll make it?’",
|
||||||
|
"Say: ‘Lead the way.’"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"poet": {
|
||||||
|
"blurb": "The Poet is a writer using their journeys as inspiration for their project, trying to tie together the threads that intertwine the history of this land with their own heart.",
|
||||||
|
"pages": "84-87",
|
||||||
|
"flavour": "The song of the world is a poem that can be captured by ink and paper, if only you could find the right words.",
|
||||||
|
"care": "eloquent, observant, and occasionally overwrought",
|
||||||
|
"animals": [
|
||||||
|
"porcupine",
|
||||||
|
"rook",
|
||||||
|
"terrier",
|
||||||
|
"toad",
|
||||||
|
"lyrical animal",
|
||||||
|
"pensive animal"
|
||||||
|
],
|
||||||
|
"actions": [
|
||||||
|
"Self-deprecate.",
|
||||||
|
"Cite your sources, in hopes that they can help.",
|
||||||
|
"Write down a moment that feels relevant to your project.",
|
||||||
|
"Provide a new perspective others might not have.",
|
||||||
|
"Ask: ‘What used to be here?’",
|
||||||
|
"Ask: ‘Can you explain?’"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ragamuffin": {
|
||||||
|
"blurb": "The Ragamuffin is a little rascal of a kid who just wants to cause problems and have fun.",
|
||||||
|
"pages": "88-91",
|
||||||
|
"flavour": "Run! Scream! Play! Steal! And above all, live!",
|
||||||
|
"care": "young, exuberant, and naïve",
|
||||||
|
"animals": [
|
||||||
|
"otter",
|
||||||
|
"gecko",
|
||||||
|
"capuchin",
|
||||||
|
"kitten",
|
||||||
|
"cute animal",
|
||||||
|
"young animal"
|
||||||
|
],
|
||||||
|
"actions": [
|
||||||
|
"Get distracted.",
|
||||||
|
"Get really invested in a new interest.",
|
||||||
|
"Blurt out a secret.",
|
||||||
|
"Somehow manage to squeeze yourself out of trouble.",
|
||||||
|
"Ask: ‘Do you wanna hang out with me?’.",
|
||||||
|
"Ask: ‘Do you wanna see something really cool?’"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"shepherd": {
|
||||||
|
"blurb": "The Shepherd tends to a herd of bumblebees, keeping an eye out with them as they travel to different pastures.",
|
||||||
|
"pages": "92-95",
|
||||||
|
"flavour": "Herds of chubby bumblebees can be found across the Hæth, and tending to those flocks is simple, honest work.",
|
||||||
|
"care": "measureless, watchful, and gentle",
|
||||||
|
"animals": [
|
||||||
|
"ram",
|
||||||
|
"turtle",
|
||||||
|
"sheepdog",
|
||||||
|
"hawk",
|
||||||
|
"guiding animal",
|
||||||
|
"peaceful animal"
|
||||||
|
],
|
||||||
|
"actions": [
|
||||||
|
"Pat a bumble on its head.",
|
||||||
|
"Stare off into the distance.",
|
||||||
|
"Make an offhand observation that turns out to be correct.",
|
||||||
|
"Rest your back against something and take a moment to breathe.",
|
||||||
|
"Say: ‘They’re friendly, don’t fret.’",
|
||||||
|
"Ask: ‘Can I teach you something someone once taught me?’"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"teacher": {
|
||||||
|
"blurb": "The Teacher is a traveling professor, who visits kids throughout the Hæth to instruct them on specialized knowledge and hidden secrets.",
|
||||||
|
"pages": "96-99",
|
||||||
|
"flavour": "Across the Hæth, there are students who need teaching. You travel from town to town and help them learn the little pieces they might not yet know.",
|
||||||
|
"care": "wise, distant, and gentle",
|
||||||
|
"animals": [
|
||||||
|
"owl",
|
||||||
|
"hare",
|
||||||
|
"chameleon",
|
||||||
|
"orangutan",
|
||||||
|
"wise animal",
|
||||||
|
"attentive animal"
|
||||||
|
],
|
||||||
|
"actions": [
|
||||||
|
"Sit down, surrounded by others.",
|
||||||
|
"Fumble for your supplies.",
|
||||||
|
"Clear your throat and get everyone’s attention.",
|
||||||
|
"Tell the table about something related to a subject you teach.",
|
||||||
|
"Ask: ‘Can you show me?’",
|
||||||
|
"Ask: ‘What can we learn from this?’"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"vagabond": {
|
||||||
|
"blurb": "The Vagabond was once convicted by a faraway and cruel authority, and has been forced to reinvent themself on the road.",
|
||||||
|
"pages": "100-103",
|
||||||
|
"flavour": "The world’s taken everything from you, beat down on your shoulders, and given you an aching heart. Some people think you’re a criminal, or a monster. You know what you are.",
|
||||||
|
"care": "invisible, cautious, and unimaginably deep",
|
||||||
|
"animals": [
|
||||||
|
"possum",
|
||||||
|
"rat",
|
||||||
|
"rattlesnake",
|
||||||
|
"raven",
|
||||||
|
"misunderstood animal",
|
||||||
|
"sneaky animal"
|
||||||
|
],
|
||||||
|
"actions": [
|
||||||
|
"Be somewhere you’re not supposed to be.",
|
||||||
|
"Have something you’re not supposed to have.",
|
||||||
|
"Mutter something you’re not supposed to say.",
|
||||||
|
"Lie.",
|
||||||
|
"Say: ‘I have a bad feeling about this.’",
|
||||||
|
"Ask: ‘Do you trust me?’"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"veteran": {
|
||||||
|
"blurb": "The Veteran was once a great hero, who held the entire world on the tip of their blade. No longer.",
|
||||||
|
"pages": "104-107",
|
||||||
|
"flavour": "You wield the blade that must never be drawn again.",
|
||||||
|
"care": "intense, sober, and mindful",
|
||||||
|
"animals": [
|
||||||
|
"badger",
|
||||||
|
"hellbender",
|
||||||
|
"raven",
|
||||||
|
"mouse",
|
||||||
|
"dangerous animal",
|
||||||
|
"headstrong animal"
|
||||||
|
],
|
||||||
|
"actions": [
|
||||||
|
"Repeat a calming phrase.",
|
||||||
|
"Spend time practicing a craft you’re not very good at.",
|
||||||
|
"Drum against the pommel of your blade.",
|
||||||
|
"Leap to your feet.",
|
||||||
|
"Say: ‘I don’t do that anymore.’",
|
||||||
|
"Ask: ‘What are you hiding?’",
|
||||||
|
"Unsheathe your blade and immediately kill the person in front of you. Then, remove your character from the game. You cannot play them any longer."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
764
server/data/questions.json
Normal file
@ -0,0 +1,764 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"question": "<p><em>Wanderhome</em> is about a group of animals travelling through a vast, pastoral landscape.</p><p>Choose <strong>up to three</strong> personality traits to describe what kind of animal you are.</p>",
|
||||||
|
"select": 3,
|
||||||
|
"max": 2,
|
||||||
|
"answers": [
|
||||||
|
{
|
||||||
|
"text": "Dangerous",
|
||||||
|
"matches": [
|
||||||
|
"guardian",
|
||||||
|
"vagabond",
|
||||||
|
"veteran"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Headstrong",
|
||||||
|
"matches": [
|
||||||
|
"moth-tender",
|
||||||
|
"pilgrim",
|
||||||
|
"veteran"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Hopeful",
|
||||||
|
"matches": [
|
||||||
|
"dancer",
|
||||||
|
"fool",
|
||||||
|
"pilgrim"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Lively",
|
||||||
|
"matches": [
|
||||||
|
"dancer",
|
||||||
|
"poet",
|
||||||
|
"ragamuffin"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Misunderstood",
|
||||||
|
"matches": [
|
||||||
|
"exile",
|
||||||
|
"fool",
|
||||||
|
"vagabond"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Pensive",
|
||||||
|
"matches": [
|
||||||
|
"caretaker",
|
||||||
|
"poet",
|
||||||
|
"teacher"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Persistent",
|
||||||
|
"matches": [
|
||||||
|
"exile",
|
||||||
|
"moth-tender",
|
||||||
|
"peddler"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Quiet",
|
||||||
|
"matches": [
|
||||||
|
"caretaker",
|
||||||
|
"firelight",
|
||||||
|
"shepherd"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Sagacious",
|
||||||
|
"matches": [
|
||||||
|
"guardian",
|
||||||
|
"shepherd",
|
||||||
|
"teacher"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Tireless",
|
||||||
|
"matches": [
|
||||||
|
"firelight",
|
||||||
|
"peddler",
|
||||||
|
"ragamuffin"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"question": "<p>Journeying through the <em>Hæth</em> requires all travellers to care about the people and places around them.</p><p>Choose <strong>up to three</strong> characteristics that describe what your care is like.</p>",
|
||||||
|
"select": 3,
|
||||||
|
"max": 3,
|
||||||
|
"answers": [
|
||||||
|
{
|
||||||
|
"text": "Cautious",
|
||||||
|
"matches": [
|
||||||
|
"exile",
|
||||||
|
"guardian",
|
||||||
|
"shepherd",
|
||||||
|
"vagabond",
|
||||||
|
"veteran"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Dependable",
|
||||||
|
"matches": [
|
||||||
|
"caretaker",
|
||||||
|
"guardian",
|
||||||
|
"moth-tender",
|
||||||
|
"peddler",
|
||||||
|
"pilgrim"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Enduring",
|
||||||
|
"matches": [
|
||||||
|
"dancer",
|
||||||
|
"firelight",
|
||||||
|
"moth-tender",
|
||||||
|
"peddler",
|
||||||
|
"pilgrim"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Exuberant",
|
||||||
|
"matches": [
|
||||||
|
"dancer",
|
||||||
|
"firelight",
|
||||||
|
"fool",
|
||||||
|
"poet",
|
||||||
|
"ragamuffin"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Gentle",
|
||||||
|
"matches": [
|
||||||
|
"caretaker",
|
||||||
|
"exile",
|
||||||
|
"shepherd",
|
||||||
|
"teacher",
|
||||||
|
"veteran"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Intense",
|
||||||
|
"matches": [
|
||||||
|
"dancer",
|
||||||
|
"firelight",
|
||||||
|
"poet",
|
||||||
|
"shepherd",
|
||||||
|
"veteran"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Naïve",
|
||||||
|
"matches": [
|
||||||
|
"fool",
|
||||||
|
"guardian",
|
||||||
|
"pilgrim",
|
||||||
|
"ragamuffin",
|
||||||
|
"vagabond"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Subtle",
|
||||||
|
"matches": [
|
||||||
|
"caretaker",
|
||||||
|
"exile",
|
||||||
|
"moth-tender",
|
||||||
|
"teacher",
|
||||||
|
"vagabond"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Wise",
|
||||||
|
"matches": [
|
||||||
|
"firelight",
|
||||||
|
"fool",
|
||||||
|
"peddler",
|
||||||
|
"poet",
|
||||||
|
"teacher"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"question": "<p>The landscape across the Hæth has many vibrant places.</p><p>What kind of place do you come from?</p>",
|
||||||
|
"select": 1,
|
||||||
|
"answers": [
|
||||||
|
{
|
||||||
|
"text": "Comfortable places that provide shelter and food, like farms, gardens, or markets",
|
||||||
|
"matches": [
|
||||||
|
"caretaker",
|
||||||
|
"guardian",
|
||||||
|
"teacher"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Verdant places that are lush and inviting, like fields, glens, or lagoons",
|
||||||
|
"matches": [
|
||||||
|
"exile",
|
||||||
|
"ragamuffin",
|
||||||
|
"shepherd"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Liminal places that are the way between other places, like bridges, ports, or taverns",
|
||||||
|
"matches": [
|
||||||
|
"firelight",
|
||||||
|
"moth-tender",
|
||||||
|
"vagabond"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Sprawling places that inspire wonder, like carnivals, castles, or metropolises",
|
||||||
|
"matches": [
|
||||||
|
"dancer",
|
||||||
|
"fool",
|
||||||
|
"peddler"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Lonely places where the rest of the world does not intrude, like caves, graveyards, or moors",
|
||||||
|
"matches": [
|
||||||
|
"pilgrim",
|
||||||
|
"poet",
|
||||||
|
"veteran"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"question": "<p>Travellers in the Hæth find themselves journeying together even if their reasons for travelling are vastly different.</p><p>What was the reason that made you embark on your journey?</p>",
|
||||||
|
"select": 1,
|
||||||
|
"answers": [
|
||||||
|
{
|
||||||
|
"text": "A past that I want to leave behind",
|
||||||
|
"matches": [
|
||||||
|
"exile",
|
||||||
|
"fool",
|
||||||
|
"veteran"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "A future I want to go towards",
|
||||||
|
"matches": [
|
||||||
|
"dancer",
|
||||||
|
"guardian",
|
||||||
|
"pilgrim"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "A desire to learn more and see the world",
|
||||||
|
"matches": [
|
||||||
|
"firelight",
|
||||||
|
"poet",
|
||||||
|
"teacher"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "A want for the riches and bounties of the Hæth",
|
||||||
|
"matches": [
|
||||||
|
"peddler",
|
||||||
|
"moth-tender",
|
||||||
|
"vagabond"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "A need to be with the people on the trail",
|
||||||
|
"matches": [
|
||||||
|
"caretaker",
|
||||||
|
"ragamuffin",
|
||||||
|
"shepherd"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"question": "<p>Along our travels, we must be prepared for whatever the road has in store for us.</p><p>What thing do you carry spares of should your companions not have it already?</p>",
|
||||||
|
"select": 1,
|
||||||
|
"answers": [
|
||||||
|
{
|
||||||
|
"text": "Blankets for when it is chilly",
|
||||||
|
"matches": [
|
||||||
|
"exile",
|
||||||
|
"moth-tender",
|
||||||
|
"shepherd"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Torches to light dark tunnels",
|
||||||
|
"matches": [
|
||||||
|
"firelight",
|
||||||
|
"peddler",
|
||||||
|
"vagabond"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Snacks for when we are peckish",
|
||||||
|
"matches": [
|
||||||
|
"guardian",
|
||||||
|
"teacher",
|
||||||
|
"ragamuffin"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "A song or story for when it is too quiet",
|
||||||
|
"matches": [
|
||||||
|
"dancer",
|
||||||
|
"fool",
|
||||||
|
"poet"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Incense for when we need to show respect",
|
||||||
|
"matches": [
|
||||||
|
"caretaker",
|
||||||
|
"pilgrim",
|
||||||
|
"veteran"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"question": "<p>Our journey will take us through the sweeping arc of the seasons.</p><p>Which season do you look forward to the most?</p>",
|
||||||
|
"select": 1,
|
||||||
|
"answers": [
|
||||||
|
{
|
||||||
|
"text": "Leap: a time of melting frost and great rain",
|
||||||
|
"matches": [
|
||||||
|
"caretaker",
|
||||||
|
"exile",
|
||||||
|
"vagabond"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Bright: when flowers bloom and the sun blazes",
|
||||||
|
"matches": [
|
||||||
|
"dancer",
|
||||||
|
"pilgrim",
|
||||||
|
"ragamuffin"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Breathe: a season of swarming bugs and fleeting days",
|
||||||
|
"matches": [
|
||||||
|
"moth-tender",
|
||||||
|
"peddler",
|
||||||
|
"shepherd"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Silt: when the leaves turn red and fall as the days get colder",
|
||||||
|
"matches": [
|
||||||
|
"fool",
|
||||||
|
"poet",
|
||||||
|
"teacher"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Chill: a season hushed in deep snow and thick frost",
|
||||||
|
"matches": [
|
||||||
|
"firelight",
|
||||||
|
"guardian",
|
||||||
|
"veteran"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"question": "<p>When travelling with companions, it may be difficult to find time alone.</p><p>What do you do to get those precious moments of solitude along the way?</p>",
|
||||||
|
"select": 1,
|
||||||
|
"answers": [
|
||||||
|
{
|
||||||
|
"text": "Wake up early before everyone else",
|
||||||
|
"matches": [
|
||||||
|
"caretaker",
|
||||||
|
"guardian",
|
||||||
|
"shepherd"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Stay up late after everyone else has gone to bed",
|
||||||
|
"matches": [
|
||||||
|
"firelight",
|
||||||
|
"moth-tender",
|
||||||
|
"teacher"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Excuse myself and retire to some place quiet",
|
||||||
|
"matches": [
|
||||||
|
"exile",
|
||||||
|
"fool",
|
||||||
|
"peddler"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Lose myself for a moment in vigorous activity",
|
||||||
|
"matches": [
|
||||||
|
"dancer",
|
||||||
|
"ragamuffin",
|
||||||
|
"veteran"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Withdraw silently into my own mind, even around other people",
|
||||||
|
"matches": [
|
||||||
|
"pilgrim",
|
||||||
|
"poet",
|
||||||
|
"vagabond"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"question": "<p>The Hæth is full of small gods, many forgotten, scattered amongst its many settlements and sprawling wilderness.</p><p>Which of these gods are you least likely to forget?</p>",
|
||||||
|
"select": 1,
|
||||||
|
"answers": [
|
||||||
|
{
|
||||||
|
"text": "The god of a warm, comforting bath who smells like citrus",
|
||||||
|
"matches": [
|
||||||
|
"poet",
|
||||||
|
"shepherd",
|
||||||
|
"teacher"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "The god of overflowing tankards who will obligingly buy you a round if you ask nicely",
|
||||||
|
"matches": [
|
||||||
|
"fool",
|
||||||
|
"peddler",
|
||||||
|
"vagabond"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "The god of dense forest canopies who will shelter you from inclement weather",
|
||||||
|
"matches": [
|
||||||
|
"exile",
|
||||||
|
"caretaker",
|
||||||
|
"veteran"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "The god of bioluminescent mushrooms who will light your way in the dark",
|
||||||
|
"matches": [
|
||||||
|
"dancer",
|
||||||
|
"firelight",
|
||||||
|
"moth-tender"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "The god of childish wonder who changes form for each new phenomenon you encounter",
|
||||||
|
"matches": [
|
||||||
|
"pilgrim",
|
||||||
|
"guardian",
|
||||||
|
"ragamuffin"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"question": "<p>Everyone in the Hæth is be fundamentally good, except the mighty whose souls may be weighed down by power or poisoned by the struggle.</p><p>Of all these people whose goodness may be in conflict, for whom do you have the greatest understanding?</p>",
|
||||||
|
"select": 1,
|
||||||
|
"answers": [
|
||||||
|
{
|
||||||
|
"text": "The monarch who ushered in great change",
|
||||||
|
"matches": [
|
||||||
|
"dancer",
|
||||||
|
"moth-tender",
|
||||||
|
"ragamuffin"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "The lord who tried to make the land prosper",
|
||||||
|
"matches": [
|
||||||
|
"caretaker",
|
||||||
|
"peddler",
|
||||||
|
"shepherd"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "The general who protected his people",
|
||||||
|
"matches": [
|
||||||
|
"exile",
|
||||||
|
"guardian",
|
||||||
|
"teacher"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "The hero who sought power to defend his kin",
|
||||||
|
"matches": [
|
||||||
|
"fool",
|
||||||
|
"poet",
|
||||||
|
"vagabond"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "The soldier who did his duty",
|
||||||
|
"matches": [
|
||||||
|
"firelight",
|
||||||
|
"pilgrim",
|
||||||
|
"veteran"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"question": "<p>There was once a great war that tore the Hæth asunder.</p><p>But where were you during it?</p>",
|
||||||
|
"select": 1,
|
||||||
|
"answers": [
|
||||||
|
{
|
||||||
|
"text": "In the midst of it, taking a stand for what I believe in",
|
||||||
|
"matches": [
|
||||||
|
"exile",
|
||||||
|
"moth-tender",
|
||||||
|
"peddler",
|
||||||
|
"vagabond",
|
||||||
|
"veteran"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "On the sidelines, looking after the lost and wounded",
|
||||||
|
"matches": [
|
||||||
|
"caretaker",
|
||||||
|
"firelight",
|
||||||
|
"guardian",
|
||||||
|
"poet",
|
||||||
|
"teacher"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Far away, holding on to the peace",
|
||||||
|
"matches": [
|
||||||
|
"dancer",
|
||||||
|
"fool",
|
||||||
|
"pilgrim",
|
||||||
|
"ragamuffin",
|
||||||
|
"shepherd"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"question": "<p>The war has left its many scars along the Hæth and its people.</p><p>What shadow of the war are you trying to escape?</p>",
|
||||||
|
"select": 1,
|
||||||
|
"answers": [
|
||||||
|
{
|
||||||
|
"text": "Guilt for my part in it",
|
||||||
|
"matches": [
|
||||||
|
"exile",
|
||||||
|
"guardian",
|
||||||
|
"moth-tender",
|
||||||
|
"peddler",
|
||||||
|
"veteran"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Shame for not taking a stand",
|
||||||
|
"matches": [
|
||||||
|
"dancer",
|
||||||
|
"firelight",
|
||||||
|
"fool",
|
||||||
|
"pilgrim",
|
||||||
|
"vagabond"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Pain from its many losses",
|
||||||
|
"matches": [
|
||||||
|
"caretaker",
|
||||||
|
"poet",
|
||||||
|
"ragamuffin",
|
||||||
|
"shepherd",
|
||||||
|
"teacher"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"question": "<p>The time for violence has long ended, and the Hæth is now a place of peace.</p><p>Why do you believe violence is wrong?</p>",
|
||||||
|
"select": 1,
|
||||||
|
"answers": [
|
||||||
|
{
|
||||||
|
"text": "Because of who we become when we resort to violence",
|
||||||
|
"matches": [
|
||||||
|
"guardian",
|
||||||
|
"firelight",
|
||||||
|
"veteran"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Because of the pain it causes to those around us",
|
||||||
|
"matches": [
|
||||||
|
"exile",
|
||||||
|
"moth-tender",
|
||||||
|
"ragamuffin"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Because all gods, big and small, have warned us against it",
|
||||||
|
"matches": [
|
||||||
|
"caretaker",
|
||||||
|
"dancer",
|
||||||
|
"pilgrim"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Because peaceful solutions to conflict are just more effective",
|
||||||
|
"matches": [
|
||||||
|
"peddler",
|
||||||
|
"poet",
|
||||||
|
"shepherd"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Because the ends do not justify the means",
|
||||||
|
"matches": [
|
||||||
|
"fool",
|
||||||
|
"teacher",
|
||||||
|
"vagabond"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"question": "<p>Journeys through the Heath involve incidental companions: people who are coincidentally going the same way for now, who may part ways or end their travels should their paths diverge from their companions’.</p><p>What would mark the end of your journey?</p>",
|
||||||
|
"select": 1,
|
||||||
|
"answers": [
|
||||||
|
{
|
||||||
|
"text": "Arriving at the place I am seeking",
|
||||||
|
"matches": [
|
||||||
|
"firelight",
|
||||||
|
"pilgrim",
|
||||||
|
"vagabond"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Finding the person I am looking for",
|
||||||
|
"matches": [
|
||||||
|
"guardian",
|
||||||
|
"ragamuffin",
|
||||||
|
"shepherd"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Completing a great task I set myself",
|
||||||
|
"matches": [
|
||||||
|
"fool",
|
||||||
|
"poet",
|
||||||
|
"teacher"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Becoming the person I want to be",
|
||||||
|
"matches": [
|
||||||
|
"dancer",
|
||||||
|
"exile",
|
||||||
|
"veteran"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Acquiring what I need",
|
||||||
|
"matches": [
|
||||||
|
"caretaker",
|
||||||
|
"moth-tender",
|
||||||
|
"peddler"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"question": "<p>When we first set out on our journey, we did so in search of some place to call home.</p><p>What would it take for you to call a place your home?</p>",
|
||||||
|
"select": 1,
|
||||||
|
"answers": [
|
||||||
|
{
|
||||||
|
"text": "Familiar comforts of the place I come from",
|
||||||
|
"matches": [
|
||||||
|
"teacher",
|
||||||
|
"vagabond",
|
||||||
|
"veteran"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Shrines and knicknacks reminding me of my time on the road",
|
||||||
|
"matches": [
|
||||||
|
"caretaker",
|
||||||
|
"firelight",
|
||||||
|
"pilgrim"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "A picturesque landscape with majestic sunsets",
|
||||||
|
"matches": [
|
||||||
|
"fool",
|
||||||
|
"poet",
|
||||||
|
"shepherd"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "A charming workshop where I can keep myself busy",
|
||||||
|
"matches": [
|
||||||
|
"dancer",
|
||||||
|
"moth-tender",
|
||||||
|
"peddler"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "People with whom I belong",
|
||||||
|
"matches": [
|
||||||
|
"exile",
|
||||||
|
"guardian",
|
||||||
|
"ragamuffin"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"question": "<p>Journeys through the Hæth have a way of continuing even after they end.</p><p>After your travels have ended, how will you keep the journey alive?</p>",
|
||||||
|
"select": 1,
|
||||||
|
"answers": [
|
||||||
|
{
|
||||||
|
"text": "In the stories I tell or the songs I sing",
|
||||||
|
"matches": [
|
||||||
|
"dancer",
|
||||||
|
"poet",
|
||||||
|
"teacher"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Through the trinkets with which I will furnish my home",
|
||||||
|
"matches": [
|
||||||
|
"caretaker",
|
||||||
|
"moth-tender",
|
||||||
|
"peddler"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "By always leaving my door open for my companions",
|
||||||
|
"matches": [
|
||||||
|
"exile",
|
||||||
|
"fool",
|
||||||
|
"shepherd"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "By visiting old friends regularly",
|
||||||
|
"matches": [
|
||||||
|
"guardian",
|
||||||
|
"ragamuffin",
|
||||||
|
"veteran"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "By embarking on another journey once again",
|
||||||
|
"matches": [
|
||||||
|
"firelight",
|
||||||
|
"pilgrim",
|
||||||
|
"vagabond"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
0
server/install.py
Normal file
3
server/main.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from app import create_app
|
||||||
|
app = create_app()
|
||||||
|
if __name__ == '__main__': app.run()
|
18
server/requirements.txt
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
click==8.1.3
|
||||||
|
Flask==2.2.2
|
||||||
|
Flask-Cors==3.0.10
|
||||||
|
Flask-SQLAlchemy==2.5.1
|
||||||
|
greenlet==1.1.3
|
||||||
|
gunicorn==20.1.0
|
||||||
|
itsdangerous==2.1.2
|
||||||
|
Jinja2==3.1.2
|
||||||
|
MarkupSafe==2.1.1
|
||||||
|
numpy==1.23.2
|
||||||
|
pip==22.2.2
|
||||||
|
python-dotenv==0.20.0
|
||||||
|
setuptools==65.3.0
|
||||||
|
six==1.16.0
|
||||||
|
SQLAlchemy==1.4.40
|
||||||
|
sqlalchemy-json==0.5.0
|
||||||
|
Werkzeug==2.2.2
|
||||||
|
wheel==0.37.1
|
2
server/wsgi.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
from main import app
|
||||||
|
if __name__ == '__main__': app.run()
|
BIN
src/assets/UncialAntiqua-Regular.0cc686dc.ttf
Normal file
BIN
src/assets/cover.dafe41be.png
Normal file
After Width: | Height: | Size: 510 KiB |
13
src/assets/index.0b8786b7.js
Normal file
1
src/assets/index.9745abf5.css
Normal file
BIN
src/assets/wh_ic_banner.55188797.png
Normal file
After Width: | Height: | Size: 866 KiB |
12
src/browserconfig.xml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<browserconfig>
|
||||||
|
<msapplication>
|
||||||
|
<tile>
|
||||||
|
<square70x70logo src="img/icons/mstile-70x70.png"/>
|
||||||
|
<square150x150logo src="img/icons/mstile-150x150.png"/>
|
||||||
|
<square310x310logo src="img/icons/mstile-310x310.png"/>
|
||||||
|
<wide310x150logo src="img/icons/mstile-310x150.png"/>
|
||||||
|
<TileColor>#1eff1e</TileColor>
|
||||||
|
</tile>
|
||||||
|
</msapplication>
|
||||||
|
</browserconfig>
|
BIN
src/img/icons/android-chrome-192x192.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
src/img/icons/android-chrome-512x512.png
Normal file
After Width: | Height: | Size: 96 KiB |
BIN
src/img/icons/apple-touch-icon-114x114-precomposed.png
Normal file
After Width: | Height: | Size: 8.4 KiB |
BIN
src/img/icons/apple-touch-icon-114x114.png
Normal file
After Width: | Height: | Size: 8.4 KiB |
BIN
src/img/icons/apple-touch-icon-120x120-precomposed.png
Normal file
After Width: | Height: | Size: 8.9 KiB |
BIN
src/img/icons/apple-touch-icon-120x120.png
Normal file
After Width: | Height: | Size: 9.1 KiB |
BIN
src/img/icons/apple-touch-icon-144x144-precomposed.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
src/img/icons/apple-touch-icon-144x144.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
src/img/icons/apple-touch-icon-152x152-precomposed.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
src/img/icons/apple-touch-icon-152x152.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
src/img/icons/apple-touch-icon-180x180-precomposed.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
src/img/icons/apple-touch-icon-180x180.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
src/img/icons/apple-touch-icon-57x57-precomposed.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
src/img/icons/apple-touch-icon-57x57.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
src/img/icons/apple-touch-icon-60x60-precomposed.png
Normal file
After Width: | Height: | Size: 3.7 KiB |
BIN
src/img/icons/apple-touch-icon-60x60.png
Normal file
After Width: | Height: | Size: 3.7 KiB |
BIN
src/img/icons/apple-touch-icon-72x72-precomposed.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
src/img/icons/apple-touch-icon-72x72.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
BIN
src/img/icons/apple-touch-icon-76x76-precomposed.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
src/img/icons/apple-touch-icon-76x76.png
Normal file
After Width: | Height: | Size: 5.0 KiB |
BIN
src/img/icons/apple-touch-icon-precomposed.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
src/img/icons/apple-touch-icon.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
src/img/icons/favicon-16x16.png
Normal file
After Width: | Height: | Size: 856 B |
BIN
src/img/icons/favicon-32x32.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
src/img/icons/favicon.ico
Normal file
After Width: | Height: | Size: 7.2 KiB |
BIN
src/img/icons/mstile-144x144.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
src/img/icons/mstile-150x150.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
src/img/icons/mstile-310x150.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
src/img/icons/mstile-310x310.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
src/img/icons/mstile-70x70.png
Normal file
After Width: | Height: | Size: 6.8 KiB |
1156
src/img/icons/safari-pinned-tab.svg
Normal file
After Width: | Height: | Size: 78 KiB |
22
src/index.html
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="img/icons/apple-touch-icon.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="img/icons/favicon-32x32.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="img/icons/favicon-16x16.png">
|
||||||
|
<link rel="manifest" href="/site.webmanifest">
|
||||||
|
<link rel="mask-icon" href="img/icons/safari-pinned-tab.svg" color="#5bbad5">
|
||||||
|
<meta name="msapplication-TileColor" content="#1e441e">
|
||||||
|
<meta name="msapplication-TileImage" content="img/icons/mstile-144x144.png">
|
||||||
|
<meta name="theme-color" content="#1e441e">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Wanderhome Quiz | V.S.</title>
|
||||||
|
<script type="module" crossorigin src="/assets/index.0b8786b7.js"></script>
|
||||||
|
<link rel="stylesheet" href="/assets/index.9745abf5.css">
|
||||||
|
</head>
|
||||||
|
<body class="bg-lime-100 h-screen">
|
||||||
|
<div class="bg-lime-100" id="app"></div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
19
src/site.webmanifest
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "Wanderhome Quiz",
|
||||||
|
"short_name": "Wanderhome Quiz",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "img/icons/android-chrome-192x192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "img/icons/android-chrome-512x512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"theme_color": "#1e441e",
|
||||||
|
"background_color": "#3a5a40",
|
||||||
|
"display": "standalone"
|
||||||
|
}
|