Finished Version 3
This commit is contained in:
14
interface/__init__.py
Normal file
14
interface/__init__.py
Normal file
@ -0,0 +1,14 @@
|
||||
from flask import Flask
|
||||
from secret import key
|
||||
|
||||
def create_app():
|
||||
app = Flask(__name__)
|
||||
app.config['SECRET_KEY'] = key
|
||||
|
||||
from .views import views
|
||||
from .error_handlers import error_handler
|
||||
|
||||
app.register_blueprint(views, url_prefix = '/')
|
||||
app.register_blueprint(error_handler)
|
||||
|
||||
return app
|
7
interface/error_handlers.py
Normal file
7
interface/error_handlers.py
Normal file
@ -0,0 +1,7 @@
|
||||
from flask import render_template, Blueprint, request
|
||||
|
||||
error_handler = Blueprint('error_handlers', __name__)
|
||||
|
||||
@error_handler.app_errorhandler(404)
|
||||
def not_found(e):
|
||||
return render_template("404.html")
|
84
interface/static/css/style.css
Normal file
84
interface/static/css/style.css
Normal file
@ -0,0 +1,84 @@
|
||||
body {
|
||||
padding: 60px 0;
|
||||
}
|
||||
|
||||
.wrapper-answer {
|
||||
padding-left: 5px;
|
||||
height: fit-content;
|
||||
}
|
||||
|
||||
.wrapper-radio {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.wrapper-option {
|
||||
vertical-align: middle;
|
||||
border-bottom: 0.25px solid;
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
.wrapper-option.last-option {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.question {
|
||||
margin: 0 auto 24px auto;
|
||||
}
|
||||
|
||||
.sourcebook-title {
|
||||
font-style: italic;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.small-caps {
|
||||
font-variant-caps: small-caps;
|
||||
}
|
||||
|
||||
.right-padded-cell {
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.label-score {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
h1 {
|
||||
padding: 30pt 0 60pt;
|
||||
}
|
||||
|
||||
.section-head {
|
||||
margin: 40px 0px 20px 0px;
|
||||
}
|
||||
|
||||
.result, .glossary {
|
||||
margin: 30pt 0 0pt
|
||||
}
|
||||
|
||||
.graph-column {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.take-quiz {
|
||||
margin: 10px auto 10px auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.right-margin {
|
||||
margin-right: 30px;
|
||||
}
|
||||
|
||||
.left-margin {
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
.centre-buttons {
|
||||
text-align: center;
|
||||
margin: 30px 0 0 0;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 400px) {
|
||||
.graph-column {
|
||||
display: none;
|
||||
}
|
||||
}
|
BIN
interface/static/favicon.ico
Normal file
BIN
interface/static/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
BIN
interface/static/favicon.png
Normal file
BIN
interface/static/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 46 KiB |
2
interface/static/js/jquery.js
vendored
Normal file
2
interface/static/js/jquery.js
vendored
Normal file
File diff suppressed because one or more lines are too long
13
interface/static/js/script.js
Normal file
13
interface/static/js/script.js
Normal file
@ -0,0 +1,13 @@
|
||||
const menuItems = document.getElementsByClassName("nav-link");
|
||||
for(let i = 0; i< menuItems.length; i++) {
|
||||
if(menuItems[i].pathname == window.location.pathname) {
|
||||
menuItems[i].classList.add("active")
|
||||
}
|
||||
}
|
||||
const dropdownItems = document.getElementsByClassName("dropdown-item");
|
||||
for(let i = 0; i< dropdownItems.length; i++) {
|
||||
if(dropdownItems[i].pathname == window.location.pathname) {
|
||||
dropdownItems[i].classList.add("active")
|
||||
$( "#" + dropdownItems[i].id ).closest( ".dropdown" ).find(".dropdown-toggle").addClass("active")
|
||||
}
|
||||
}
|
11
interface/templates/404.html
Normal file
11
interface/templates/404.html
Normal file
@ -0,0 +1,11 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %} Page Not Found {% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<h1>Error: Page Not Found</h1>
|
||||
|
||||
<div class="container">
|
||||
<a href="/">Return to the Home Page</a>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
89
interface/templates/base.html
Normal file
89
interface/templates/base.html
Normal file
@ -0,0 +1,89 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<!-- Bootstrap .css below -->
|
||||
<link
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css"
|
||||
rel="stylesheet"
|
||||
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC"
|
||||
crossorigin="anonymous">
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.15.4/css/fontawesome.min.css"
|
||||
crossorigin="anonymous"
|
||||
/>
|
||||
<!-- Custom .css below -->
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="{{ url_for('static', filename='css/style.css') }}"
|
||||
/>
|
||||
<!-- Favicons -->
|
||||
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
||||
<link rel="apple-touch-icon" href="{{ url_for('static', filename='favicon.png') }}">
|
||||
<title>
|
||||
{% block title %}{% endblock %} | Which Masks Playbook Are You? — a Personality Quiz
|
||||
</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<!-- Create Nav Bar -->
|
||||
{% include "navbar.html" %}
|
||||
|
||||
<!-- Flash Messages -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
|
||||
<symbol id="check-circle-fill" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z"/>
|
||||
</symbol>
|
||||
<symbol id="info-fill" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path d="M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16zm.93-9.412-1 4.705c-.07.34.029.533.304.533.194 0 .487-.07.686-.246l-.088.416c-.287.346-.92.598-1.465.598-.703 0-1.002-.422-.808-1.319l.738-3.468c.064-.293.006-.399-.287-.47l-.451-.081.082-.381 2.29-.287zM8 5.5a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/>
|
||||
</symbol>
|
||||
<symbol id="exclamation-triangle-fill" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z"/>
|
||||
</symbol>
|
||||
</svg>
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
{% if category == 'error' %}
|
||||
<div class="alert alert-danger alter-dismissable fade show" role="alert">
|
||||
<div class="container">
|
||||
<svg class="bi flex-shrink-0 me-2" width="24" height="24" role="img" aria-label="Danger:"><use xlink:href="#exclamation-triangle-fill"/></svg>
|
||||
{{ message|safe }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
<!-- Create Content Block -->
|
||||
|
||||
<div class="container">
|
||||
{% block content %} {% endblock %}
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap Scripts Below -->
|
||||
<script
|
||||
src="https://code.jquery.com/jquery-3.2.1.slim.min.js"
|
||||
integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN"
|
||||
crossorigin="anonymous"
|
||||
></script>
|
||||
<script
|
||||
src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"
|
||||
integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM"
|
||||
crossorigin="anonymous"></script>
|
||||
<!-- JQuery Below -->
|
||||
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
|
||||
<script>window.jQuery || document.write('<script src="{{ url_for('static', filename='js/jquery.js') }}">\x3C/script>')</script>
|
||||
<!-- Custom .js below -->
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="{{ url_for('static', filename='js/script.js') }}"
|
||||
></script>
|
||||
</body>
|
||||
|
||||
</html>
|
28
interface/templates/compatibility.html
Normal file
28
interface/templates/compatibility.html
Normal file
@ -0,0 +1,28 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %} Compatibility and Accessibility {% endblock %}
|
||||
{% block content %}
|
||||
<h1>Compatibility and Accessibility</h1>
|
||||
|
||||
<h3 class="section-head">Browser Compatibility</h3>
|
||||
<p>
|
||||
This web app was made using the Bootstrap CSS framework. Most CSS components, browser elements, and JavaScript code should be compatible with most contemporary browsers. This version of the quiz reduces its use of JavaScript to process and evaluate results, and uses JavaScript for animations and other effects in the interface. So it should be compatible more widely.
|
||||
</p>
|
||||
<p>
|
||||
If you notice any incompatibilities or strange browser behaviour, please let me know and I'll try and see how I can fix it.
|
||||
</p>
|
||||
|
||||
<h3 class="section-head">Accessibility</h3>
|
||||
<p>
|
||||
Bootstrap as a CSS web design framework is built with a lot of accessibility features and guidance in mind, and the supporting documentation gave some guidance on what fields and metadata to populate to make page elements accessible to screen readers. I used the existing Bootstrap CSS classes and templates, and followed most existing guidance to enter metadata for html elements. This should hopefully facilitate screen readers being able to parse the page effectively. If there are any issues with this, let me know and I can make sure the page is structured in a way that is screen reader friendly.
|
||||
</p>
|
||||
<p>
|
||||
Besides that, the colour schemes and fonts have been the standard fonts to ensure that they are legible and familiar. All links are also navigable by the keyboard alone, and this should make it a lot easier to interact with the quiz (especially when having to answer several questions).
|
||||
</p>
|
||||
<p>
|
||||
Admittedly, this quiz is rather long. There are a lot of questions to answer. Although not all questions are mandatory, and a user need not fill in all answers in order to proceed.
|
||||
</p>
|
||||
<h3 class="section-head">Web Hosting</h3>
|
||||
<p>
|
||||
Because of how I re-structured this to be a web app running on the server, the way in which I host this site has changed considerably. There may be issues with how the various ports forward to each other on the server. I will monitor this. If there are any problems with accessing this, let me know and I will try to fix it.
|
||||
</p>
|
||||
{% endblock %}
|
23
interface/templates/dev.html
Normal file
23
interface/templates/dev.html
Normal file
@ -0,0 +1,23 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %} Development {% endblock %}
|
||||
{% block content %}
|
||||
<h1>Development</h1>
|
||||
<p>
|
||||
I started work on the first version of this personality quiz over a year ago, when I decided to teach myself programming. The original version ran on JavaScript and was rendered and evaluated entirely by the browser. This version runs entirely on the server, and the quiz results are evaluated by the server rather than the client.
|
||||
</p>
|
||||
<p>
|
||||
The back-end runs on Python 3.10, using the Flask framework. The web pages are templated using Jinja. The web site is rendered using html and the Bootstrap CSS framework. There have been some elements of the interface enhanced using rudimentary JavaScript, including the jQuery library. Of all of these programming languages, Python has proved to be a far more intuitive language to learn, and generally programming this has been a lot more enjoyable and a lot less frustrating than the earlier iteration that used JavaScript.
|
||||
</p>
|
||||
<p>
|
||||
The quiz runs by rendering a form with all of the questions and options on the browser. The browser then submits the responses via a POST request to the server. The server evaluates the responses and renders the results accordingly. There is no information stored on the client.
|
||||
</p>
|
||||
<p>
|
||||
All data transacted between the client and the server is stored as a Flask session for the duration that the user is on the web site. If the user closes the site, the session data is deleted and all answers submitted are lost.
|
||||
</p>
|
||||
<p>
|
||||
There are, of course, some considerable inefficiencies in the way I have set up the templating. I have written the text for all the various web pages in the templates of the respective pages. Ideally, I would have preferred having a single template for all of the web pages or views, and then have the content served on those pages render dynamically based on the URL query. But that was a level of programming that was gratuitous at this point, as really most of the pages were serving static content anyway so it did not matter just now.
|
||||
</p>
|
||||
<p>
|
||||
In addition, this is the first time I am storing the code of the quiz <a href="" title="Link to the code on my Git Repo">on my Git repo</a>, and having a much more streamlined version control process.
|
||||
</p>
|
||||
{% endblock %}
|
35
interface/templates/home.html
Normal file
35
interface/templates/home.html
Normal file
@ -0,0 +1,35 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %} Home | Which Masks Playbook Are You? — a Personality Quiz {% endblock %}
|
||||
{% block content %}
|
||||
<h1>Home page</h1>
|
||||
|
||||
<h3 class="section-head">Background</h3>
|
||||
|
||||
<p>
|
||||
This is a personality quiz to determine which playbook you are in the <em>Masks: a New Generation</em> table-top role-playing game. There are several personality quizzes out there for <em>D&D</em>, and <a href="http://www.easydamus.com/character.html">this one by Easydamus</a> in particular inspired me. I thought it would be fun to make something similar for <em>Masks</em> as it is my favourite TRPG (more on that <a href="/masks">in the appropriate section</a>).
|
||||
</p>
|
||||
<p>
|
||||
I began this project as a way of learning how to programme. I started with a farily rudimentary version of this quiz using JavaScript and html. The way that quiz worked was that it was processed on the client side, as the quiz logic was downloaded onto a browser and the scripts were run there. I had contemplated re-making the quiz using a server-side framework eventually, and in this version I taught myself how to make the quiz run on a server instead. I taught myself Python, Flask, and some rudimentary Jinja to make this.
|
||||
</p>
|
||||
<p>
|
||||
If you want to take the quiz, you can do so here:
|
||||
<a href="/quiz" class="btn btn-success take-quiz">Take the Quiz</a>
|
||||
</p>
|
||||
|
||||
<h3 class="section-head">What is a Table-Top Role-Playing Game?</h3>
|
||||
<p>
|
||||
A table-top role-playing game is a collaborative story-telling game in which a group of players work together to tell a story, with a framework of rules that govern how players share narrative control and resolve uncertain outcomes.
|
||||
</p>
|
||||
<p>
|
||||
The most famous TRPG is <em>Dungeons & Dragons</em>, currently published by Wizards of the Coast. For decades, it has defined what this hobby has been perceived as becuase it is the most widely-played TRPG in the world and, as a consequence, is also most frequently referenced in pop culture. There are many other games out there — with different rulesets, settings, artwork, feel, and narrative purpose — coverying myriad genres of storytelling like fantasy, action, adventure, horror, sci-fi, cyberpunk, et cetera. Each of these games is referred to in the community as a game system.
|
||||
</p>
|
||||
<p>
|
||||
<em>Masks</em> is another such role-playing game. Its specific hook is that it is about teenage superheroes who are juggling the emotional pressures of being a teenager with their awesome adventures of fighting villains. You can read more about <em>Masks</em> particularly in the relevant section.
|
||||
</p>
|
||||
<p>
|
||||
The way these games work is players usually play characters through which they interact with the fictional world. These characters usually fall into a number of different archetypes depending on the system. The most common archetypes, or 'character classes', are from fantasy games like <em>D&D</em>, like the Fighter, Wizard, Rogue, or Cleric. These classes in <em>D&D</em> are determined by the various roles in a fantasy adventuring party that the characters play (with some loose narrative flavour around how their class is their calling in life). These archetypes also have some flavour text around common personality traits or tropes about them.
|
||||
</p>
|
||||
<p>
|
||||
In <em>Masks</em>, the archetypes, referred to as 'playbooks', are all about being teenagers and getting up to teenage shenanigans. The archetypes are all determined by the personalities that the characters have. There are about 20 different archetypes to choose from, so naturally it can be really difficult to choose the one most suitable. So, I designed a personality quiz to help choose the best character archetype!
|
||||
</p>
|
||||
{% endblock %}
|
11
interface/templates/masks.html
Normal file
11
interface/templates/masks.html
Normal file
@ -0,0 +1,11 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %} About Masks {% endblock %}
|
||||
{% block content %}
|
||||
<h1>About <em>Masks</em></h1>
|
||||
<p><em>Masks</em> is a game by Brendan Conway using the Powered by the Apocalypse engine. For those unfamiliar with the term, Powered by the Apocalypse is a category of game systems that are based on the overall structure of the <em>Apocalypse World</em> system by Vincent and Meguey Baker. The core concept behind this system is to encourage collaborate and improvisational story-telling by streamlining the mechanics. This is in sharp contrast to traditional TRPGs that have very granular rules and hierarchical authority between a Game Master and the players.</p>
|
||||
<p>If you imagine all table-top role-playing games to exist on a spectrum between chess and improv theatre with <em>D&D</em> fifth edition right in the middle, PbtA games are much closes to the improv theatre side of the axis, whereas traditional games, while fourth edition <em>D&D</em> would be closer to chess because of its wargame heritage.</p>
|
||||
<p><em>Masks</em> is without a doubt my favourite role-playing game system, and I would strongly recommend trying it to anyone who hasn't played TRPGs before, or hasn't played Masks before. What I like most about Masks is its concept of playing teenage superheroes, and all the emotional drama and angst that that entails. For me, it has been so much fun to revisit a difficult age but from a perspective of greater maturity and in a setting where the very differences that I had been victimised for are framed not as liabilities but as superpowers: things that made characters special. This was an age when I really loved reading comic books, and that is why revisiting it with this zany aesthetic is such a delight.</p>
|
||||
<h3 class="section-head">Note on Content</h3>
|
||||
<p>The names of the playbooks, as well as their associated flavour text, are by Brendan Conway, taken from the player sheets that are part of the game documents made freely-available on-line.</p>
|
||||
<p>Special thanks to the members of the <em>Masks</em> Discord for their advice, feedback, and patience with the original quirks of the web hosting.</p>
|
||||
{% endblock %}
|
52
interface/templates/navbar.html
Normal file
52
interface/templates/navbar.html
Normal file
@ -0,0 +1,52 @@
|
||||
<nav class="navbar fixed-top navbar-expand-md navbar-dark bg-dark">
|
||||
<div class="container">
|
||||
<a href="/" class="navbar-brand mb-0 h1">Masks Personality Quiz | V.S.</a>
|
||||
<button
|
||||
class="navbar-toggler"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#navbar"
|
||||
aria-controls="navbar"
|
||||
aria-expanded="false"
|
||||
aria-label="Toggle Navigation"
|
||||
>
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbar">
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item" id="nav-home">
|
||||
<a class="nav-link" id="link-home" href="/">Home</a>
|
||||
</li>
|
||||
<li class="nav-item" id="nav-quiz">
|
||||
<a class="nav-link" id="link-quiz" href="/quiz">Take the Quiz</a>
|
||||
</li>
|
||||
<li class="nav-item dropdown" id="nav-about">
|
||||
<a
|
||||
class="nav-link dropdown-toggle"
|
||||
id="dropdown-about"
|
||||
role="button"
|
||||
href="/masks"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
>
|
||||
About
|
||||
</a>
|
||||
<ul
|
||||
class="dropdown-menu"
|
||||
aria-labelledby="dropdown-about"
|
||||
>
|
||||
<li>
|
||||
<a href="/masks" id="link-masks" class="dropdown-item">About <em>Masks</em></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://git.vsnt.uk/viveksantayana/masks-personality-quiz" id="link-dev" class="dropdown-item">View the Code</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/compatibility" id="link-compatibility" class="dropdown-item">Compatibility and Accessibility</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
82
interface/templates/quiz.html
Normal file
82
interface/templates/quiz.html
Normal file
@ -0,0 +1,82 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %} Take the Quiz {% endblock %}
|
||||
{% block content %}
|
||||
<h1>Take the Quiz</h1>
|
||||
|
||||
<p>
|
||||
Please answer the following {{ questions|length }} questions. None of the questions are mandatory, and you can skip questions you are unsure of or do not want to answer. But you cannot leave the quiz blank. The more questions you answer, the better results you will get.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
You will also be able to select which source books you would like to see results from. You can exclude source books to narrow down the range of playbooks, or add all of them to have a full range of playboosk to choose from.
|
||||
</p>
|
||||
|
||||
<form method="POST">
|
||||
<div class="form-group">
|
||||
{% for question in questions -%}
|
||||
<fieldset class="question">
|
||||
<legend>
|
||||
<strong>Question {{ loop.index }}</strong>: {{ question['question']|safe }}
|
||||
</legend>
|
||||
<table>
|
||||
<tbody>
|
||||
{% set name = 'q'~loop.index %}
|
||||
{% for answer in question['answers'] -%}
|
||||
<tr class="wrapper-option{% if loop.index == question['answers']|length %} last-option{% endif %}">
|
||||
<td class="wrapper-radio">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="radio"
|
||||
name="{{ name }}"
|
||||
id="{{ name }}.{{ loop.index }}"
|
||||
value="{{ loop.index }}"
|
||||
{% if 'submission' in session %}
|
||||
{{'checked' if session['submission'][name] == loop.index|lower else '' }}
|
||||
{% endif%}
|
||||
>
|
||||
</td>
|
||||
<td class="wrapper-answer">
|
||||
<label
|
||||
class="form-check-label"
|
||||
for="{{ name }}.{{ loop.index }}"
|
||||
>
|
||||
{{ answer['text']|safe }}
|
||||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</fieldset>
|
||||
{% endfor %}
|
||||
<fieldset class="source-filters">
|
||||
<h5>
|
||||
Please select which sourcebooks you would like to include results from.
|
||||
</h5>
|
||||
{% for source in sources -%}
|
||||
<input
|
||||
type="checkbox"
|
||||
name="{{ source }}"
|
||||
id="{{ source }}"
|
||||
{% if 'submission' in session %}
|
||||
{{'' if source not in session['submission'] else 'checked' }}
|
||||
{% else %}
|
||||
checked="true"
|
||||
{% endif %}
|
||||
class="form-check-input"
|
||||
>
|
||||
<label
|
||||
for="{{ source }}"
|
||||
class="form-check-label sourcebook-title"
|
||||
>
|
||||
{{ sources[source]['title'] }}
|
||||
</label>
|
||||
{% endfor %}
|
||||
</fieldset>
|
||||
<div class="centre-buttons">
|
||||
<button type="submit" class="btn btn-success right-margin" href="/results">Submit</button>
|
||||
<a class="btn btn-danger left-margin" href="/reset">Reset</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
153
interface/templates/results.html
Normal file
153
interface/templates/results.html
Normal file
@ -0,0 +1,153 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %} Your Results {% endblock %}
|
||||
{% block content %}
|
||||
<h1>Your Results</h1>
|
||||
<a name="top-anchor"></a>
|
||||
{% if results['selected_playbooks']|length > 1 %}
|
||||
<h3>Your Playbooks are:</h3>
|
||||
{% else%}
|
||||
<h3>Your Playbook is:</h3>
|
||||
{% endif %}
|
||||
{%for playbook in results['selected_playbooks'] -%}
|
||||
<div class="result">
|
||||
<h4>The <span class="small-caps">{{ playbook[0]|upper }}{{ playbook[1:] }}</span></h4>
|
||||
{% if playbook == 'joined' %}
|
||||
<h6>
|
||||
You have duplicated The <span class="small-caps">{{ results['joined_cloned'][0]|upper }}{{ results['joined_cloned'][1:] }}</span>
|
||||
</h6>
|
||||
{% endif %}
|
||||
<p>
|
||||
{{ playbooks[playbook]['flavour']|safe }}
|
||||
</p>
|
||||
{% set source = sources[playbooks[playbook]['source']] %}
|
||||
<p>From the <em>{{ source['title'] }}</em> {{ source['type'] }} by {{ source['author'] }}, published by {{ source['publisher'] }} in {{ source['year'] }}.</p>
|
||||
<p>
|
||||
<strong><a href="#momentoftruth" title="Click to the the glossary." class="small-caps">Moment of Truth</a></strong>: {{ playbooks[playbook]['moment']|safe }}
|
||||
</p>
|
||||
<p>
|
||||
<strong><span class="small-caps">Labels</span></strong>: <br />
|
||||
<table>
|
||||
<tbody>
|
||||
{% for label in results['selected_playbooks'][playbook] -%}
|
||||
<tr>
|
||||
<td class="right-padded-cell">
|
||||
<strong><a href="#{{ label }}" title="Click to see the glossary." class="small-caps">{{ label[0]|upper}}{{ label[1:]}}</a></strong>:
|
||||
</td>
|
||||
<td class="label-score">
|
||||
{{ results['selected_playbooks'][playbook][label] }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<h3 class="section-head">How You Fared:</h3>
|
||||
|
||||
<p>
|
||||
This chart represents how many points you got for each playbook from the source books you selected in all the questions you answered.
|
||||
</p>
|
||||
|
||||
<div class="graph">
|
||||
<table>
|
||||
<tbody>
|
||||
{% for playbook in results['playbooks'] -%}
|
||||
<tr>
|
||||
<td class="right-padded-cell">
|
||||
{{ loop.index }}.
|
||||
</td>
|
||||
<td class="small-caps right-padded-cell">
|
||||
The {{ playbook[0]|upper }}{{ playbook[1:] }}
|
||||
</td>
|
||||
<td class="right-padded-cell">
|
||||
{{ (results['playbooks'][playbook]*100/results['max_score'])|round(0) }}%
|
||||
</td>
|
||||
<td>
|
||||
<span class="graph-column">{% for i in range( (results['playbooks'][playbook]*20/results['max_score'])|round(0, 'ceil')|int) -%}X{% endfor %}</span>
|
||||
({{ results['playbooks'][playbook] }})
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="glossary">
|
||||
<table>
|
||||
<h3 class="small-caps section-head">
|
||||
Glossary
|
||||
</h3>
|
||||
<h4 class="small-caps section-head">Labels</h4>
|
||||
<p>In <em>Masks</em>, your character’s attributes are defined by their ‘labels’. This is like the character ability scores in <em>D&D</em>. Mechanically, it gives bonuses or penalties to your rolls. Narratively, it reflects your chances of success given your self image and your confidence in your abilities.</p>
|
||||
<p>Because everyone plays a teenager, your labels are all about the different dimensions of your self image, and these labels keep shifting all the time as the story progresses and events change the way you see yourself. And this happens a <em>lot</em>. Because, teenagers.</p>
|
||||
<p>Labels range on a scale of -2 to +3</p>
|
||||
<tbody>
|
||||
<tr class="wrapper-option">
|
||||
<td class="right-padded-cell small-caps">
|
||||
<a name="danger" href="#top-anchor" title="Back to the top">Danger</a>
|
||||
</td>
|
||||
<td>
|
||||
{{ labels['danger']['flavour'] }}
|
||||
{{ results['display_labels'].remove('danger') if results['display_labels'].remove('danger') is not none else '' }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="wrapper-option">
|
||||
<td class="right-padded-cell small-caps">
|
||||
<a name="freak" href="#top-anchor" title="Back to the top">Freak</a>
|
||||
</td>
|
||||
<td>
|
||||
{{ labels['freak']['flavour'] }}
|
||||
{{ results['display_labels'].remove('freak') if results['display_labels'].remove('freak') is not none else '' }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="wrapper-option">
|
||||
<td class="right-padded-cell small-caps">
|
||||
<a name="saviour" href="#top-anchor" title="Back to the top">Saviour</a>
|
||||
</td>
|
||||
<td>
|
||||
{{ labels['saviour']['flavour'] }}
|
||||
{{ results['display_labels'].remove('saviour') if results['display_labels'].remove('saviour') is not none else '' }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="wrapper-option">
|
||||
<td class="right-padded-cell small-caps">
|
||||
<a name="superior" href="#top-anchor" title="Back to the top">Superior</a>
|
||||
</td>
|
||||
<td>
|
||||
{{ labels['superior']['flavour'] }}
|
||||
{{ results['display_labels'].remove('superior') if results['display_labels'].remove('superior') is not none else '' }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="wrapper-option{% if results['display_labels']|length == 1 %} last-option{% endif %}">
|
||||
<td class="right-padded-cell small-caps">
|
||||
<a name="mundane" href="#top-anchor" title="Back to the top">Mundane</a>
|
||||
</td>
|
||||
<td>
|
||||
{{ labels['mundane']['flavour'] }}
|
||||
{{ results['display_labels'].remove('mundane') if results['display_labels'].remove('mundane') is not none else '' }}
|
||||
</td>
|
||||
</tr>
|
||||
{% for label in results['display_labels'] -%}
|
||||
<tr class="right-padded-cell wrapper-option{% if loop.index == results['display_labels']|length %} last-option{% endif %}">
|
||||
<td class="small-caps">
|
||||
<a name="{{ label }}" href="#top-anchor" title="Back to the top">{{ label[0]|upper }}{{ label[1:] }}</a>
|
||||
</td>
|
||||
<td>
|
||||
{{ labels[label]['flavour'] }}
|
||||
{{ results['display_labels'].remove(label) if results['display_labels'].remove(label) is not none else '' }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<h4 class="section-head">Your <a href="#top-anchor" class="small-caps" title="Back to the top" name="momentoftruth">Moment of Truth</a></h4>
|
||||
<p>
|
||||
In <em>Masks</em>, your <span class="small-caps">Moment of Truth</span> is a defining moment in your character’s life when you are in the spotlight. All eyes are on you, and it is when you prove who you really are. For a moment, you grow into the best version of yourself and you show to everyone who you really are.
|
||||
</p>
|
||||
<p>
|
||||
What this means mechanically is that, for one scene, you get to narrate what happens as per the script of your moment of truth. You take down a powerful threat, harness unbelievable power, or prove yourself to everyone watching. And then, it locks one of your <span class="small-caps">Labels</span>. That <span class="small-caps">Label</span> can no longer shift.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
42
interface/views.py
Normal file
42
interface/views.py
Normal file
@ -0,0 +1,42 @@
|
||||
from flask import Blueprint, render_template, request, session, redirect
|
||||
from flask.helpers import url_for
|
||||
from data.labels import labels
|
||||
from data.playbooks import playbooks
|
||||
from data.questions import questions
|
||||
from data.sources import sources
|
||||
from quiz.validators import validate_submissions
|
||||
from quiz.evaluation import evaluate_quiz
|
||||
|
||||
views = Blueprint('views', __name__)
|
||||
|
||||
@views.route('/compatibility')
|
||||
def compatibility():
|
||||
return render_template('compatibility.html')
|
||||
|
||||
@views.route('/')
|
||||
def home():
|
||||
return render_template('home.html')
|
||||
|
||||
@views.route('/masks')
|
||||
def about():
|
||||
return render_template('masks.html')
|
||||
|
||||
@views.route('/quiz', methods=['GET', 'POST'])
|
||||
def quiz():
|
||||
if request.method == 'POST':
|
||||
session['submission'] = request.form
|
||||
if validate_submissions(session['submission']):
|
||||
return redirect(url_for('views.results'))
|
||||
return render_template('quiz.html', questions=questions, sources=sources)
|
||||
|
||||
@views.route('/results')
|
||||
def results():
|
||||
if 'submission' not in session:
|
||||
return redirect(url_for('views.quiz'))
|
||||
results = evaluate_quiz(session['submission'])
|
||||
return render_template('results.html', results = results, playbooks = playbooks, labels = labels, sources = sources)
|
||||
|
||||
@views.route('/reset')
|
||||
def reset():
|
||||
session.clear()
|
||||
return redirect(url_for('views.quiz'))
|
Reference in New Issue
Block a user