diff --git a/.gitignore b/.gitignore index f8b73e7..029599b 100644 --- a/.gitignore +++ b/.gitignore @@ -138,3 +138,5 @@ dmypy.json # Cython debug symbols cython_debug/ +# Secret File for Keys +secret.py \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ae7c666 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,5 @@ +FROM python:3.10-alpine +WORKDIR /app +COPY . . +RUN pip install -r requirements.txt +CMD ["python","app.py"] \ No newline at end of file diff --git a/README.md b/README.md index 46dd7d5..37fc44d 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,81 @@ -# masks-personality-quiz +# Which *Masks* Playbook Are You? — a Personality Quiz -A personality quiz to determine which Masks playbook you should play. \ No newline at end of file +## About the Project + +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. + +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. + +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. + +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. + +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. + +Moreover, the algorithm with which the quiz calculates Labels is highly inaccurate. +It is difficult to find an algorithm that simulates Label shifts like during play because of how many variables and conditions there are to what constitutes a valid Label shift, and the fact that which Labels are being shifted might be complicated by the existence of custom Labels like ‘Soldier’. +So the Labels that the quiz returns are highly inaccurate, often not adding up to the right numbers. + +In addition, this is the first time I am storing the code of the quiz on my git repo, and having a much more streamlined version control process. +I have also Dockerised the app so it can be deployed seamlessly. + +## Set Up + +To run an instance of this app, you will need Docker and Docker Compose installed. + +To set up an instance, you will need to clone the repository, + +You will also need to create a module called `secret.py` and enter a secret key for Flask to use to encrypt session data. +The easiest way to do this is to make a copy of `secret.py.example`, remove `.example` from the filename, and then add a random string as a value for the variable defined therein. + +After you have created `secret.py`, run `sudo docker-compose up -d` from the root folder. +The current set-up in the `docker-compose.yml` does not expose the container to the internet, but exposes it on an internal Docker network. +This can be changed by amending the `docker-compose.yml` entry on `line 8` to: + +```yml + - 5000:5000 +``` + +## Version History + +### Current Version: 3.0.0 (1 November 2021) + +### Changelog + +- Re-built the quiz to run on the server side rather than on the client. +- Built using Python, Flask, Jinja, and html primarily. +- Changed the layout to a cleaner, more accessible and modern style using the Bootstrap CSS framework, +- Expanded to 30 questions. +- Refined the algorithm by which the Joined duplicates another playbook based on the second-highest scoring match rather than selecting a playbook entirely at random. +- Dockerised the quiz and hosting it on my web server, serving it via the front-end reverse proxy. + +### Past Versions + +Because I only started using Git relatively recently, I have not uploaded the code from the older versions onto the repo. +The repo starts with version 3. + +#### Version 2 (2 May 2020) + +- First added the functionality to determine character Labels. +- Re-wrote many of the questions, expanded the quiz to 25 questions. +- Added extensible databases to allow for more questions to be added dynamically. +- Added the functionality to filter Playbooks based on source books. +- Added a glossary to explain game terms. + +#### Version 1 (20 April 2020) + +- First made the quiz to run entirely in the browser using JavaScript. +- Rudimentary quiz that returned the playbook most suited. diff --git a/app.py b/app.py new file mode 100644 index 0000000..3001df6 --- /dev/null +++ b/app.py @@ -0,0 +1,5 @@ +from interface import create_app + +app = create_app() + +if __name__ == '__main__': app.run(host='0.0.0.0') \ No newline at end of file diff --git a/data/__init__.py b/data/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/data/labels.py b/data/labels.py new file mode 100644 index 0000000..fecb66f --- /dev/null +++ b/data/labels.py @@ -0,0 +1,23 @@ +labels = { + 'danger': { + 'flavour': 'seeing yourself as threatening, strong, bloody-knuckled, and risky. Other people see you as a danger when they think they should steer clear of you because you might bring them harm. You see yourself as a danger when you believe you can take down other dangerous threats, and when you think you yourself are a threat to other people.' + }, + 'freak': { + 'flavour': 'seeing yourself as strange, unusual, unique, and powerful. Other people see you as a freak when they think you’re odd, unlike them, something unnatural or outside of their understanding. You see yourself as a freak when you accept and own the things you can do that no one else can, and when you think you don’t belong with the people and the world around you.' + }, + 'saviour': { + 'flavour': 'seeing yourself as defending, guarding, protecting, and stalwart. Other people see you as a savior when they think of you as noble or selfsacrificing, or a bit overbearing and moralizing. You see yourself as a savior when you think of yourself as a martyr, someone who gladly sacrifices to protect and defend others.' + }, + 'superior': { + 'flavour': 'seeing yourself as smart, capable, crafty, and quick. Other people see you as superior when they think you’re the smartest person in the room, an arrogant and egotistical jerk. You see yourself as superior when you think you’re cleverer than everyone else, and when you know exactly what to say to make the people around you do what you want.' + }, + 'mundane': { + 'flavour': 'seeing yourself as normal, human, empathetic, and understanding. Other people see you as mundane when they think of you as all too normal and uninteresting, but also comprehending and sympathetic. You see yourself as mundane when you think you’re regular, just a person, not special, and focused on normal human things like feelings and emotions.' + }, + 'soldier': { + 'flavour': 'You work for a metahuman law enforcement agency (A.E.G.I.S.) that keeps the world safe from all manner of superhuman, supernatural, and extraterrestrial threats. You volunteered to work with a team of young superheroes as part of a new A.E.G.I.S. program designed to keep Halcyon City safe.', + 'custom': True, + 'default_value': 2, + 'playbooks': ['soldier'] + } +} \ No newline at end of file diff --git a/data/playbooks.py b/data/playbooks.py new file mode 100644 index 0000000..02fe34a --- /dev/null +++ b/data/playbooks.py @@ -0,0 +1,248 @@ +playbooks = { + # Core Playbooks + 'beacon': { + 'source': 'core', + 'flavour': 'You don’t have to do this. You could probably have a safe, decent, simple life. It’d be nice, but... come on. Superpowers! Aliens! Wizards! Time travel! You’re out of your depth but who cares? This is awesome. Everybody should try it.', + 'moment': 'This is the moment when you show them exactly why you belong here. You do any one thing, take out any one enemy, no matter how insane, no matter how ridiculous, because that’s you. Their jaws are gonna drop when you’re done. Of course, pulling off a stunt like this tends to bring unwanted attention and a dangerous reputation…', + 'labels': { + 'danger': -1, + 'freak': -1, + 'saviour': 2, + 'superior': 0, + 'mundane': 2 + } + }, + 'bull': { + 'source': 'core', + 'flavour': 'You’re big, strong, and tough. You know what fighting really is, and you’re good at it. Sure, you’ve got a soft side, too. But you only show that to the people you care about the most. Everybody else? They can eat your fist.', + 'moment': 'This is what you do best. You let loose, all the pent up strength and rage and glee, and you break whatever stands in your way. You are a walking demolition crew. What can stand up to you? Nothing. Not buildings. Not structures. Not enemies. Nothing. Of course, now the people who changed you know exactly where to find you…', + 'labels': { + 'danger': 2, + 'freak': 1, + 'saviour': -1, + 'superior': 1, + 'mundane': -1 + } + }, + 'delinquent': { + 'source': 'core', + 'flavour': 'You’ve got these cool powers. But everyone keeps telling you how to use ’em. You know what they need? Someone to give them trouble, to make sure they don’t always get their way. And hey! You’re the perfect hero to do it.', + 'moment': 'moment of truthThis is when you show them what you really are. Whether you’re the hero underneath the rebel facade… or the one who can make the hard choices heroes can’t make. You do whatever it takes to show that truth, whether it’s saving the day from a terrible villain or stopping a bad guy once and for all. Of course, once you’ve shown what you really are, there’s no going back to playing the clown…', + 'labels': { + 'danger': 0, + 'freak': 0, + 'saviour': -1, + 'superior': 2, + 'mundane': 1 + } + }, + 'doomed': { + 'source': 'core', + 'flavour': 'Something about your powers dooms you. It’s just a matter of time before your doom comes for you. Until then, though... you’ve got a nemesis who needs fighting and a world that needs saving. After all, it’s better to burn out than fade away.', + 'moment': 'The prickly tingling fear of your doom, always in your head—it holds you back most of the time. But right here, right now? It gives you the confidence to do anything. After all, what’s the worst that could happen? Is it worse than your doom? Do impossible things. Do anything. But mark a doomsign after you’re finished.', + 'labels': { + 'danger': 1, + 'freak': 1, + 'saviour': 1, + 'superior': -1, + 'mundane': 0 + } + }, + 'janus': { + 'source': 'core', + 'flavour': 'Wake up. Breakfast. School. Work. Homework. Sleep. Repeat. It burns you up, being stuck in this life, unable to make a real difference. That is... until you put on the mask. And then, you can be someone else: a hero.', + 'moment': 'The mask is a lie, and some piece of you has always known that. Doesn’t matter if others can see it. You’re the one that can do the impossible. Mask off. Costume on. And you’re going to save the damn day. Of course, you better hope nobody nasty is watching…', + 'labels': { + 'danger': 0, + 'freak': -1, + 'saviour': 0, + 'superior': 0, + 'mundane': 3 + } + }, + 'legacy': { + 'source': 'core', + 'flavour': 'You’re the latest in a storied heroic lineage, a family that shares a name and a cause. Now, everybody is watching and waiting to see if you’ve got what it takes to uphold that tradition. No pressure, right?', + 'moment': 'This is the moment when you prove how much the mantle belongs to you. You seize control of all your powers, and you defeat even impossible odds to prove you are worthy of the name you carry. You accomplish feats even your predecessors couldn’t do. Of course, after you prove something like that, you can expect still more responsibilities to be placed on your shoulders…', + 'labels': { + 'danger': -1, + 'freak': 0, + 'saviour': 2, + 'superior': 0, + 'mundane': 1 + } + }, + 'nova': { + 'source': 'core', + 'flavour': 'You’re a font of power. Channel it, and you can remake the world into exactly what you want. Unleash it, and you can do miracles. It’s wonderful... and terrifying. Lose control for even a second, and other people get hurt.', + 'moment': 'Your mind’s eye opens, and you can see the world around you like never before. You can control it, at will, with ease. Of course, warping reality tends to have ramifications down the line, but in your moment of godhood… how could you possibly be worried?', + 'labels': { + 'danger': 1, + 'freak': 2, + 'saviour': 0, + 'superior': 0, + 'mundane': -1 + } + }, + 'outsider': { + 'source': 'core', + 'flavour': 'You’re not from here. Your home is an amazing place, full of beauty and wonder. But there’s something to this place, something special that you’re missing back home. Something... human. So yeah, you’ll be hanging around. At least for now.', + 'moment': 'You embrace your home and call them for aid. They will answer your call—in force!—arriving exactly when you need them to turn the tide. They fight and serve you for the rest of the battle. Of course, when all is said and done… they’d probably like to take you home with them. You did, after all, just prove yourself worthy.', + 'labels': { + 'danger': -1, + 'freak': 1, + 'saviour': 0, + 'superior': 2, + 'mundane': 0 + } + }, + 'protege': { + 'source': 'core', + 'flavour': 'You proved yourself to an experienced hero. They think you’ve got what it takes. They’ve been training you for a while, and now you have to decide... do you want to be them? Or will you find your own path?', + 'moment': 'The moment that you show who you really are: your mentor, or something different. You can do whatever your mentor could do and more. You can do the incredible, even the things they always failed to accomplish. Of course, they’re not going to see you the same way, no matter which path you choose…', + 'labels': { + 'danger': -1, + 'freak': 0, + 'saviour': 1, + 'superior': 2, + 'mundane': 0 + } + }, + 'transformed': { + 'source': 'core', + 'flavour': 'You can recall a time not too long ago when you looked... normal. When you didn’t feel their stares. When you didn’t hear their gasps. When no one thought of you as a monster. Those were the days, huh.', + 'moment': 'It’s so easy to forget that you’re not your body, and you’re not the voice in your head—you’re both. Be the monster, and save them anyway. Smash down walls, and speak softly. Because when you embrace it, you can do anything. Of course, putting on a display like this is sure to rile up those who see only the monster when they look at you…', + 'labels': { + 'danger': 1, + 'freak': 3, + 'saviour': 0, + 'superior': -1, + 'mundane': -1 + } + }, + # Limited Edition Playbooks + # Halcyon City Herald Collection Supplement + 'innocent': { + 'source': 'halcyon', + 'flavour': 'Time travel is great! Or so you thought, until you landed in a strange new world with a dark, broken, damaged, dangerous, adult version of yourself. Not what you had wanted to become. Question is, what are you going to do about it?', + 'moment': 'You’ve fought, struggled, and worked so hard to figure out who you are, whether you’re just the same as your future self or whether you’re different… but right now, that’s all out the window. The distinction between your future self and your present self vanishes in the face of the trial before you, and you become exactly the powerful, adamant figure that everyone fears or hopes you will one day become. You can do exactly what your future self could do, and everyone around you sees them in you more clearly than ever. Of course, after this it’s going to be hard to treat you as two different people…', + 'labels': { + 'danger': 0, + 'freak': 0, + 'saviour': 1, + 'superior': -1, + 'mundane': 2 + } + }, + 'joined': { + 'source': 'halcyon', + 'flavour': 'You’d be nothing without them—your partner, your sibling, your friend, your rival, your other half. You’re tied to their powers and to them, through and through. The rest of the world only ever sees you two as halves of a whole—not as two separate people. And the two of you aren’t sure if they’re right.', + 'moment': 'You’re on your own. It’s like missing an arm. Like fighting naked. Like holding your breath. You’re missing something vital… but you’re moving faster than ever, thinking faster than ever, doing things you couldn’t even do while relying on both of your strength combined. And it’s hitting you, hard—you can do this. Without them. And you can win. It’s going to be hard to come down off this high and rejoin with them afterwards, isn’t it?', + 'labels': { + 'danger': 0, + 'freak': 0, + 'saviour': 0, + 'superior': 0, + 'mundane': 0 + } + }, + 'newborn': { + 'source': 'halcyon', + 'flavour': 'You’re a brand new being, created through scientific inquiry, feat of engineering, or random chance. This world is all new to you, full of wonder and adventure. It’s not easy, though—everyone has an opinion about who you are and what you should do. It’s time to find out for yourself who you really are.', + 'moment': 'Something snaps into focus, and suddenly you’re a full thing, true and complete. You’d never have known how fragmented you were before, if not for here, this moment. You’re not a series of individual lessons. You’re not a series of subroutines and programs. You’re… a person. This must be what it’s like to be… human. And this fullness? It gives you a control over yourself, a unity of purpose you’ve never experienced before. Of course, now that you’re showing off all your potential, it’s only a matter of time before someone comes forward to reduce you to a machine again…', + 'labels': { + 'danger': 1, + 'freak': 2, + 'saviour': 0, + 'superior': 1, + 'mundane': -2 + } + }, + 'reformed': { + 'source': 'halcyon', + 'flavour': 'Villainy used to be a way of life for you. Then you saw just what your selfishness and hate created. The supervillain life is hard to quit. But you know this best: sometimes the villain needs saving too.', + 'moment': 'You’ve seen your greatest mistakes, and the rest of the world has, too. They’re all watching you now, judging every move you make. When everything is on the line and your back is against the wall, though, you’ll show them what you’re made of— that being a hero is a choice. An act of will. And you’ve got what it takes to save the day. Of course, afterward, you can expect both sides, hero and villain, to deeply question where your loyalties truly lie…', + 'labels': { + 'danger': 2, + 'freak': 1, + 'saviour': -1, + 'superior': 0, + 'mundane': 0 + } + }, + 'star': { + 'source': 'halcyon', + 'flavour': 'Being a hero isn’t just about doing right. It’s about being seen doing right. Let them think you’re shallow for loving the spotlight and the cameras, for making speeches, for smiling so much. You’ll be a hero in all the ways that matter.', + 'moment': 'Sometimes it can be hard to tell where the show stops and where you begin—but not today. Not now. Because right now, there is no show. Right now, you are the thing you pretend to be—bold and bright and beautiful and amazing and powerful and confident. Right now, you draw strength from your audience, comfort from their belief in you, and you can do anything they think you can. Of course, after such an impassioned performance, your audience will just have even more demands…', + 'labels': { + 'danger': -1, + 'freak': 1, + 'saviour': 1, + 'superior': 2, + 'mundane': -1 + } + }, + # Secrets of A.E.G.I.S. Supplement + 'brain': { + 'source': 'aegis', + 'flavour': 'You’ve always been the smartest kid in the room. Your inventions are world-class, your tactical plans are flawless, and your mind is a steel-trap memory palace of extraordinary ideas. If only the others knew how sometimes, none of that seems to matter. None of that keeps the shadows at bay. None of that can make up for what you did... or might do.', + 'moment': 'Sooner or later, all the super powers, elite training, and experience are helpless in the face of evil or disaster. That’s when somebody like you, gifted as you are with a peerless intellect, can rise to the occasion. Your plan, your invention, or your lightning-fast thought processes save the day, in a way no one else could have foreseen. Of course, after you’ve shown how different you are from them, that distance between you and the others is now that much greater. And the world is only going to pull you farther apart…', + 'labels': { + 'danger': 0, + 'freak': 0, + 'saviour': 1, + 'superior': 2, + 'mundane': -1 + } + }, + 'soldier': { + 'source': 'aegis', + 'flavour': 'You’re an agent of something greater than you—a real force fighting to make the world a better place. Through them, you stand for something important. You just hope that, when push comes to shove, you stand for the right thing.', + 'moment': 'Freedom isn’t free. But not every mission ends in tragedy. When things look bleakest, when your back is against the wall, when it seems like the dawn will never come… you find a way forward without violence. Your enemies lay down their arms and surrender; your allies step back from the brink of chaos. Of course, the people you’ve saved aren’t going to forget what you’ve done here today; they may even come to see you as a symbol of the higher cause you claim to serve…', + 'labels': { + 'danger': -1, + 'freak': 0, + 'saviour': 2, + 'superior': 1, + 'mundane': 0, + 'soldier': 2 + } + }, + # Unbound Supplement + 'harbinger': { + 'source': 'unbound', + 'flavour': 'You’re from the future, and you know how things turn out. You came back with a mission — to make sure history changes for the better.
But things are scrambled. Your memories, not quite right. You’re not sure how this present becomes your future. So until you can figure it out, you might as well do what you can, where you can, all the while trying to connect the dots between your world and this one.', + 'moment': 'Everything you do could affect the future. For all you know, saving that one guy means that now the future is full of pterodactyls. The ripples are always so hard to track, and you’re not sure if you’ve helped or hurt— not really. Until now. In this moment, it’s all clear. You can see the course of events laid out before you like a river, and you know exactly what you have to do to ensure the future outcome you want. Of course, after this, you’ve changed enough of the timeline to invalidate your prior research— reset all the names in your “Connecting the dots” section.', + 'labels': { + 'danger': 0, + 'freak': 0, + 'saviour': 2, + 'superior': 1, + 'mundane': -1 + } + }, + 'nomad': { + 'source': 'unbound', + 'flavour': 'Maybe one time you had a home. A life with a schedule. But if you did, that was ages ago. You’ve been on your own, bouncing around space, time and everything in between, for years.
Except now, you’ve left those farscapes and come back to Earth. And letting other people into your life is way harder than travelling to other dimensions ever was.', + 'moment': 'You basically exist with one foot out the door, ready to leave this place, to go back out into the wide expanse of the universe. You’ve never fully committed. That is, until today. Until right now. Now, you pour everything you have and everything you are into this moment. You pull off tricks no one from this planet has ever seen before. You use your tools in ways no one here could have ever imagined. You devote yourself, here and now, to a cause, and you achieve your goal in ways that you never could’ve if you’d only stayed home. Of course, now you’ve proved to everyone that you really don’t belong here, and the very skills that let you succeed are the ones you earned from out there…', + 'labels': { + 'danger': 0, + 'freak': 2, + 'saviour': -1, + 'superior': 2, + 'mundane': -1 + } + }, + 'scion': { + 'source': 'unbound', + 'flavour': 'You’re a child—not an acolyte, not a creation, just the friggin’ kid—of a true villain. And when anyone who knows looks at you, all they can see is your parent. Like you don’t even matter. Well, forget that. You’re out to prove yourself as someone different from them, and how better to do that than to be a superhero?', + 'moment': 'People have always tried to define you by your lineage. As if from the moment you were born, you were meant to be some villain to be defeated. But…they’re right, aren’t they? That darkness is in you. So right here, right now, you’re not fighting it—you’re embracing it. Both hero and villain, and greater besides. You’re overcoming impossible odds in ways no hero would approve of, and no villain could comprehend. Of course, after seeing what you can really do when you embrace the whole of yourself, the rest of the world isn’t going to forget who you really are…', + 'labels': { + 'danger': 1, + 'freak': 0, + 'saviour': 1, + 'superior': 0, + 'mundane': 0 + } + } +} \ No newline at end of file diff --git a/data/questions.py b/data/questions.py new file mode 100644 index 0000000..76d1639 --- /dev/null +++ b/data/questions.py @@ -0,0 +1,2763 @@ +questions = [ + { + 'question': 'How do you get to school?', + 'answers': [ + { + 'text': 'I drive to school myself and make a flashy entrance.', + 'matches': { + 'nomad': 2, + 'outsider': 2, + 'soldier': 2, + 'star': 3, + }, + 'increase': [ + 'saviour', + 'freak' + ], + 'decrease': [ + 'danger', + 'superior' + ] + }, + { + 'text': 'I take public transport to save the planet.', + 'matches': { + 'beacon': 2, + 'brain': 2, + 'harbinger': 1, + 'janus': 3, + 'joined': 1, + }, + 'increase': [ + 'superior', + 'mundane' + ], + 'decrease': [ + 'freak', + 'saviour' + ] + }, + { + 'text': 'I get a lift from my parents/guardians/caregivers.', + 'matches': { + 'legacy': 2, + 'newborn': 1, + 'scion': 3, + 'protege': 1 + }, + 'increase': [ + 'mundane', + 'superior' + ], + 'decrease': [ + 'danger', + 'freak' + ] + }, + { + 'text': 'I walk. I know the streets very well.', + 'matches': { + 'bull': 2, + 'delinquent': 3, + 'doomed': 2, + 'innocent': 2, + 'nomad': 2, + 'reformed': 3 + }, + 'increase': { + 'mundane', + 'danger' + }, + 'decrease': { + 'superior', + 'freak' + } + }, + { + 'text': 'I just use my powers. Why bother with the bus if you can fly?', + 'matches': { + 'bull': 1, + 'nova': 3, + 'transformed': 2 + }, + 'increase': [ + 'danger', + 'freak' + ], + 'decrease': [ + 'superior', + 'mundane' + ] + } + ] + }, + { + 'question': 'How do you wear your school uniform?', + 'answers': [ + { + 'text': 'Making my own unique style statement.', + 'matches': { + 'legacy': 2, + 'reformed': 2, + 'star': 3, + 'delinquent': 2, + }, + 'increase': [ + 'freak', + 'superior', + ], + 'decrease': [ + 'mundane', + 'soldier', + ], + }, + { + 'text': 'Neat and tidy, but in whatever style the rest of the gang follows.', + 'matches': { + 'janus': 2, + 'joined': 3, + 'scion': 2, + 'soldier': 3, + 'protege': 2, + 'brain': 1, + }, + 'increase': [ + 'freak', + 'mundane', + ], + 'decrease': [ + 'danger', + 'superior', + ], + }, + { + 'text': 'Really shabby and untidy. They didn’t have many options in the Lost & Found.', + 'matches': { + 'delinquent': 2, + 'newborn': 3, + 'bull': 2, + 'doomed': 2, + }, + 'increase': [ + 'mundane', + 'freak', + ], + 'decrease': [ + 'superior', + 'danger', + ], + }, + { + 'text': 'In absolute tatters. Probably nicked from a skip. Or off a corpse. Sure smells like it!', + 'matches': { + 'harbinger': 2, + 'innocent': 2, + 'nova': 2, + 'doomed': 2, + 'transformed': 2, + }, + 'increase': [ + 'mundane', + 'danger', + ], + 'decrease': [ + 'saviour', + 'freak', + ], + }, + { + 'text': 'I refuse to wear school uniform in protest of the School’s sexist dress code!', + 'matches': { + 'outsider': 3, + 'transformed': 3, + 'nomad': 2, + }, + 'increase': [ + 'freak', + 'saviour', + ], + 'decrease': [ + 'mundane', + 'superior', + ], + }, + ] + }, + { + 'question': 'Which table do you eat lunch at?', + 'answers': [ + { + 'text': 'With all the nerds.', + 'matches': { + 'brain': 2, + 'newborn': 2, + 'nomad': 2, + 'star': 2, + }, + 'increase': [ + 'saviour', + 'freak', + ], + 'decrease': [ + ], + }, + { + 'text': 'With all the jocks.', + 'matches': { + 'bull': 2, + 'beacon': 2, + 'legacy': 2, + 'harbinger': 2, + 'soldier': 2, + }, + 'increase': [ + 'superior', + 'danger', + ], + 'decrease': [ + ], + }, + { + 'text': 'With all the burnouts.', + 'matches': { + 'doomed': 2, + 'delinquent': 2, + 'nova': 2, + 'innocent': 2, + 'reformed': 2, + }, + 'increase': [ + 'superior', + 'freak', + ], + 'decrease': [ + 'soldier', + 'mundane', + ], + }, + { + 'text': 'With my superhero fam.', + 'matches': { + 'protege': 2, + 'outsider': 2, + 'janus': 2, + 'joined': 2, + 'scion': 2, + 'soldier': 2, + 'star': 2, + }, + 'increase': [ + 'superior', + 'saviour', + ], + 'decrease': [ + ], + }, + { + 'text': 'In a toilet cubicle.', + 'matches': { + 'newborn': 2, + 'transformed': 2, + 'nomad': 2, + }, + 'increase': [ + 'mundane', + 'freak', + ], + 'decrease': [ + ], + } + ] + }, + { + 'question': 'What is your act for the school talent show going to be?', + 'answers': [ + { + 'text': 'Faithful adaptation of monologues from Aeschelus’ tragedies with period-appropriate costumes and set design.', + 'matches': { + 'brain': 3, + 'legacy': 1, + 'scion': 1, + 'reformed': 1, + 'star': 3, + }, + 'increase': [ + 'freak', + 'superior', + ], + 'decrease': [ + 'danger', + 'mundane', + ], + }, + { + 'text': 'This is a fantastic opportunity for me to teach my fellow students about fascinating things about different cultures!', + 'matches': { + 'brain': 2, + 'outsider': 2, + 'nomad': 3, + 'star': 2, + }, + 'increase': [ + 'mundane', + 'freak', + ], + 'decrease': [ + 'danger', + 'superior', + ], + }, + { + 'text': 'Get my band together and rock out with the new set we’ve been working on!', + 'matches': { + 'star': 3, + 'joined': 2, + }, + 'increase': [ + 'mundane', + 'superior', + ], + 'decrease': [ + 'danger', + 'freak', + ], + }, + { + 'text': 'Um… there was this film I saw with my parents once where this woman did self-defence classes as part of a beauty pageant?', + 'matches': { + 'protege': 3, + 'soldier': 2, + 'beacon': 3, + 'bull': 2, + 'legacy': 2, + }, + 'increase': [ + 'danger', + 'saviour', + ], + 'decrease': [ + 'mundane', + 'superior', + ], + }, + { + 'text': 'Urgh… I’ll take part begrudgingly if I have to. Maybe just be a back-up singer or an extra in someone else’s act.', + 'matches': { + 'delinquent': 1, + 'reformed': 1, + 'scion': 2, + 'janus': 2, + }, + 'increase': [ + 'superior', + 'mundane', + ], + 'decrease': [ + 'danger', + 'mundane', + ], + }, + { + 'text': 'I’m staying well clear of the talent show. I don’t have the time for this kind of stuff.', + 'matches': { + 'doomed': 2, + 'harbinger': 3, + 'transformed': 2, + 'innocent': 2, + 'protege': 3, + 'newborn': 2, + 'nova': 2, + 'delinquent': 3, + }, + 'increase': [ + 'freak', + 'superior', + ], + 'decrease': [ + 'mundane', + 'danger', + ], + } + ] + }, + { + 'question': 'What are you most concerned about?', + 'answers': [ + { + 'text': 'The past.', + 'matches': { + 'scion': 1, + 'reformed': 3, + 'bull': 2, + 'legacy': 2, + }, + 'increase': [ + 'mundane', + 'danger', + ], + 'decrease': [ + 'saviour', + 'freak', + ], + }, + { + 'text': 'The present.', + 'matches': { + 'beacon': 3, + 'delinquent': 1, + 'janus': 2, + 'transformed': 2, + 'joined': 2, + 'star': 2, + 'soldier': 2, + 'nova': 2, + }, + 'increase': [ + 'saviour', + 'danger', + ], + 'decrease': [ + 'superior', + 'mundane', + ], + }, + { + 'text': 'The future.', + 'matches': { + 'newborn': 2, + 'janus': 1, + 'harbinger': 3, + 'legacy': 2, + 'innocent': 2, + 'doomed': 3, + 'brain': 2, + 'protege': 3, + 'soldier': 2, + }, + 'increase': [ + 'mundane', + 'superior', + ], + 'decrease': [ + 'danger', + 'soldier', + ], + }, + { + 'text': 'Distant worlds.', + 'matches': { + 'brain': 2, + 'outsider': 3, + 'nomad': 3, + 'soldier': 1, + }, + 'increase': [ + 'mundane', + 'freak', + ], + 'decrease': [ + 'saviour', + 'superior', + ], + }, + { + 'text': 'Alternate realities.', + 'matches': { + 'innocent': 3, + 'harbinger': 2, + 'brain': 2, + }, + 'increase': [ + 'saviour', + 'freak', + ], + 'decrease': [ + 'mundane', + 'danger', + ], + } + ] + }, + { + 'question': 'What are you most afraid of?', + 'answers': [ + { + 'text': 'Being unpopular.', + 'matches': { + 'janus': 2, + 'nomad': 2, + 'outsider': 2, + 'newborn': 3, + 'nova': 2, + 'soldier': 2, + 'star': 3, + 'brain': 2, + }, + 'increase': [ + 'superior', + 'mundane', + ], + 'decrease': [ + 'danger', + 'saviour', + ], + }, + { + 'text': 'Being rejected.', + 'matches': { + 'joined': 3, + 'transformed': 2, + 'outsider': 2, + 'bull': 2, + 'janus': 2, + 'nomad': 2, + }, + 'increase': [ + 'superior', + 'freak', + ], + 'decrease': [ + 'mundane', + 'danger', + ], + }, + { + 'text': 'Being weak.', + 'matches': { + 'bull': 3, + 'soldier': 3, + 'innocent': 2, + 'beacon': 3, + 'harbinger': 2, + 'delinquent': 2, + }, + 'increase': [ + 'superior', + 'mundane', + ], + 'decrease': [ + 'soldier', + 'mundane', + ], + }, + { + 'text': 'Being a failure.', + 'matches': { + 'beacon': 2, + 'legacy': 3, + 'protege': 3, + 'innocent': 3, + 'harbinger': 3, + 'reformed': 2, + 'scion': 3, + 'brain': 3, + }, + 'increase': [ + 'superior', + 'mundane', + ], + 'decrease': [ + 'saviour', + 'freak', + ], + }, + { + 'text': 'Being me.', + 'matches': { + 'nova': 3, + 'newborn': 3, + 'doomed': 3, + 'transformed': 3, + 'joined': 2, + 'reformed': 3, + 'janus': 2, + }, + 'increase': [ + 'saviour', + 'danger', + ], + 'decrease': [ + 'superior', + 'freak', + ], + } + ] + }, + { + 'question': 'What do you doodle in the margins of your exercise books?', + 'answers': [ + { + 'text': 'Sketches of my superhero costume and gadget schematics.', + 'matches': { + 'beacon': 2, + 'brain': 2, + 'harbinger': 3, + }, + 'increase': [ + 'mundane', + 'saviour', + ], + 'decrease': [ + ], + }, + { + 'text': 'Cartoons of my friends and I fighting crime.', + 'matches': { + 'janus': 2, + 'innocent': 2, + 'star': 2, + }, + 'increase': [ + 'superior', + 'mundane', + ], + 'decrease': [ + ], + }, + { + 'text': 'Lines of code or structures of molecules.', + 'matches': { + 'brain': 3, + 'newborn': 3, + }, + 'increase': [ + 'freak', + 'superior', + ], + 'decrease': [ + ], + }, + { + 'text': 'The name of my crush. Over and over again.', + 'matches': { + 'doomed': 2, + 'bull': 3, + 'outsider': 2, + 'joined': 3, + }, + 'increase': [ + 'saviour', + 'freak', + ], + 'decrease': [ + 'superior', + 'mundane', + ], + }, + { + 'text': 'It’s all written in a cypher that only I can read!', + 'matches': { + 'brain': 2, + 'nomad': 3, + 'scion': 3, + 'delinquent': 2, + }, + 'increase': [ + 'saviour', + 'freak', + ], + 'decrease': [ + 'mundane', + 'superior', + ], + } + ] + }, + { + 'question': 'Do you know that weird kid in our class… you know, the really strange one?…', + 'answers': [ + { + 'text': 'Ew! I’d never hang out with such a loser!', + 'matches': { + 'legacy': 2, + 'scion': 2, + 'star': 3, + 'delinquent': 3, + }, + 'increase': [ + 'mundane', + 'superior', + ], + 'decrease': [ + 'saviour', + 'superior', + ], + }, + { + 'text': 'Yeah, I’ve seen them around a couple of times, but I don’t really interact with them.', + 'matches': { + 'janus': 3, + 'brain': 2, + 'reformed': 2, + 'nomad': 1, + 'harbinger': 1, + 'innocent': 1, + 'nova': 2, + }, + 'increase': [ + 'saviour', + 'mundane', + ], + 'decrease': [ + ], + }, + { + 'text': 'We hang out from time to time. They’re not that bad once you get to know them!', + 'matches': { + 'reformed': 3, + 'soldier': 2, + 'janus': 3, + 'brain': 2, + 'nomad': 2, + 'beacon': 1, + 'nova': 2, + }, + 'increase': [ + 'superior', + 'mundane', + ], + 'decrease': [ + ], + }, + { + 'text': 'What did you say about my best friend!? And who are you calling weird, anyway?', + 'matches': { + 'beacon': 2, + 'joined': 3, + 'bull': 2, + 'nova': 2, + 'nomad': 2, + 'newborn': 1, + }, + 'increase': [ + 'superior', + 'saviour', + ], + 'decrease': [ + 'mundane', + 'danger', + ], + }, + { + 'text': 'Wait, are you talking about me?', + 'matches': { + 'transformed': 3, + 'outsider': 3, + 'newborn': 3, + }, + 'increase': [ + 'mundane', + 'freak', + ], + 'decrease': [ + 'superior', + 'freak', + ], + } + ] + }, + { + 'question': 'What are you up to this weekend?', + 'answers': [ + { + 'text': 'I’m studying. I have this huge assignment due soon and I need to get it done before I can rest easy.', + 'matches': { + 'brain': 3, + 'newborn': 2, + 'janus': 2, + 'nomad': 1, + }, + 'increase': [ + 'mundane', + 'superior', + ], + 'decrease': [ + 'danger', + 'freak', + ], + }, + { + 'text': 'I was going to go to the climate strike on Friday.', + 'matches': { + 'beacon': 1, + 'harbinger': 3, + 'nomad': 1, + }, + 'increase': [ + 'mundane', + 'saviour', + ], + 'decrease': [ + 'superior', + 'freak', + ], + }, + { + 'text': 'I’m on patrol. Evil never sleeps, so neither should I!', + 'matches': { + 'protege': 3, + 'beacon': 2, + 'legacy': 2, + 'soldier': 2, + }, + 'increase': [ + 'mundane', + 'saviour', + ], + 'decrease': [ + 'superior', + 'danger', + ], + }, + { + 'text': 'I’m going to be out and about with the gang. Nothing special. Just painting the town red!', + 'matches': { + 'delinquent': 2, + 'joined': 2, + 'beacon': 1, + 'scion': 1, + 'innocent': 2, + 'nova': 2, + 'bull': 2, + }, + 'increase': [ + 'saviour', + 'danger', + ], + 'decrease': [ + ], + }, + { + 'text': 'I’ll be at a party. There’s going to be uni students there as my friend’s brother has come to visit!', + 'matches': { + 'innocent': 3, + 'scion': 2, + 'delinquent': 2, + 'doomed': 2, + 'nova': 2, + 'outsider': 2, + }, + 'increase': [ + 'freak', + 'superior', + ], + 'decrease': [ + 'mundane', + 'danger', + ], + } + ] + }, + { + 'question': 'Are you going to prom with someone? Because I have this cute friend whom you’d really get on with…', + 'answers': [ + { + 'text': 'I’m not going to Prom, I’m afraid: I’ve got this thing I need to do and we’re going to be out of town.', + 'matches': { + 'scion': 3, + 'reformed': 2, + 'protege': 3, + 'legacy': 2, + 'doomed': 2, + 'nomad': 2, + }, + 'increase': [ + 'mundane', + 'freak', + ], + 'decrease': [ + 'superior', + 'danger', + ], + }, + { + 'text': 'Oh. They sound really nice. What class are they in? What music are they into?', + 'matches': { + 'bull': 2, + 'janus': 2, + 'innocent': 2, + 'outsider': 2, + 'brain': 1, + 'nomad': 1, + }, + 'increase': [ + 'superior', + 'mundane', + ], + 'decrease': [ + 'superior', + 'freak', + ], + }, + { + 'text': 'Nah, I don’t care. Prom is just a frivolous popularity contest for a bunch of narcissists anyway!', + 'matches': { + 'transformed': 3, + 'outsider': 2, + 'delinquent': 3, + 'innocent': 2, + 'harbinger': 2, + 'soldier': 2, + }, + 'increase': [ + 'mundane', + 'superior', + ], + 'decrease': [ + ], + }, + { + 'text': 'Wait, of course I’ve got someone to go with! What, do you think I’m some sort of loser?', + 'matches': { + 'beacon': 2, + 'legacy': 2, + 'star': 3, + 'innocent': 2, + 'joined': 2, + }, + 'increase': [ + 'freak', + 'superior', + ], + 'decrease': [ + ], + }, + { + 'text': 'I do have a prom date! It’s nobody you know. They’re in a different year. Actually they don’t even go here, they’re at a different school! No, really!', + 'matches': { + 'joined': 3, + 'newborn': 2, + }, + 'increase': [ + 'superior', + 'freak', + ], + 'decrease': [ + ], + } + ] + }, + { + 'question': 'What are you most looking forward to about Prom?', + 'answers': [ + { + 'text': 'Dressing up nice and fancy and then owning the dance floor. Becoming the Prom Monarch.', + 'matches': { + 'protege': 2, + 'star': 3, + 'scion': 1, + 'legacy': 1, + 'brain': 2 + }, + 'increase': [ + 'superior' + ], + 'decrease': [ + 'freak' + ], + }, + { + 'text': 'The buffet. Especially I can sneak booze into the punch.', + 'matches': { + 'delinquent': 3, + 'reformed': 3, + 'scion': 1, + 'bull': 1, + 'beacon': 2 + }, + 'increase': [ + 'danger' + ], + 'decrease': [ + 'saviour' + ], + }, + { + 'text': 'The photo booth. I am here to make memories with my friends. That is, before we all leave school and go our separate ways.', + 'matches': { + 'janus': 2, + 'nomad': 3, + 'doomed': 2, + 'outsider': 2, + 'beacon': 2, + 'brain': 1, + 'harbinger': 2 + }, + 'increase': [ + 'mundane' + ], + 'decrease': [ + 'freak' + ], + }, + { + 'text': 'Trouble. I may not cause it, but it will certainly find me.', + 'matches': { + 'soldier': 3, + 'legacy': 2, + 'protege': 1, + 'bull': 2, + 'nova': 2, + 'transformed': 2, + 'scion': 2, + 'innocent': 2 + }, + 'increase': [ + 'saviour', + 'danger' + ], + 'decrease': [ + 'superior', + 'mundane' + ], + }, + { + 'text': 'My date.', + 'matches': { + 'joined': 3, + 'janus': 2, + 'star': 2, + 'newborn': 1 + }, + 'increase': [ + 'mundane' + ], + 'decrease': [ + 'superior' + ], + } + ] + }, + { + 'question': 'The relationship you’re in isn’t really going anywhere. How do you decide to end it?', + 'answers': [ + { + 'text': 'Just be direct and honest. It’s the kindest thing to do in the long run.', + 'matches': { + 'janus': 3, + 'nomad': 2, + 'outsider': 2, + 'newborn': 3, + 'soldier': 2, + }, + 'increase': [ + 'superior', + 'mundane', + ], + 'decrease': [ + 'danger', + 'saviour', + ], + }, + { + 'text': 'Hook up with their best friend. They’ll probably get the message.', + 'matches': { + 'legacy': 3, + 'doomed': 2, + 'reformed': 2, + 'scion': 3, + 'nova': 2, + }, + 'increase': [ + 'mundane', + 'danger', + ], + 'decrease': [ + ], + }, + { + 'text': 'New phone, who dis?', + 'matches': { + 'bull': 3, + 'innocent': 2, + 'brain': 3, + 'harbinger': 2, + }, + 'increase': [ + 'mundane', + 'superior', + ], + 'decrease': [ + ], + }, + { + 'text': 'Keep being difficult until they break up with me first, so that I can look like the one who didn’t give up.', + 'matches': { + 'bull': 1, + 'beacon': 2, + 'delinquent': 2, + 'protege': 2, + 'innocent': 2, + }, + 'increase': [ + 'saviour', + 'freak', + ], + 'decrease': [ + 'mundane', + 'superior', + ], + }, + { + 'text': 'I can’t handle confrontation! One of us will probably move away or die by the time I really have to tell them anyway.', + 'matches': { + 'joined': 3, + 'bull': 3, + 'doomed': 2, + 'transformed': 2, + }, + 'increase': [ + 'freak', + 'mundane', + ], + 'decrease': [ + 'superior', + 'mundane', + ], + } + ] + }, + { + 'question': 'You’re representing your school in a big national tournament, and you’re away on a big school trip staying in a Travelodge together. It’s the night before the main event. What do you do?', + 'answers': [ + { + 'text': 'Our teacher is asleep! Time to partaay!!', + 'matches': { + 'doomed': 3, + 'reformed': 2, + 'nova': 2, + 'legacy': 1, + 'star': 3, + }, + 'increase': [ + 'mundane', + 'danger', + ], + 'decrease': [ + 'soldier', + 'danger', + ], + }, + { + 'text': 'Tomorrow is the most important day of my life, and I need my rest so we can win!', + 'matches': { + 'legacy': 2, + 'protege': 3, + 'brain': 2, + 'soldier': 2, + 'janus': 2, + }, + 'increase': [ + 'freak', + 'superior', + ], + 'decrease': [ + 'mundane', + 'soldier', + ], + }, + { + 'text': 'I only joined this team to get close to my crush anyway, and now is the time to make my move!', + 'matches': { + 'transformed': 2, + 'outsider': 3, + 'bull': 3, + 'doomed': 2, + 'joined': 2, + }, + 'increase': [ + 'danger', + 'mundane', + ], + 'decrease': [ + ], + }, + { + 'text': 'Let’s all sneak into the pool after-hours and go swimming! It’d be great for team bonding.', + 'matches': { + 'beacon': 2, + 'protege': 2, + 'star': 2, + 'nova': 3, + 'joined': 2, + 'delinquent': 2, + }, + 'increase': [ + 'freak', + 'superior', + ], + 'decrease': [ + ], + }, + { + 'text': 'I’m probably the un-cool killjoy who has been kept out of the loop.', + 'matches': { + 'transformed': 2, + 'newborn': 2, + 'nomad': 3, + 'harbinger': 2, + 'innocent': 2, + }, + 'increase': [ + 'mundane', + 'freak', + ], + 'decrease': [ + ], + } + ] + }, + { + 'question': 'What is your aesthetic in your day-to-day life?', + 'answers': [ + { + 'text': 'Black. Like the colour of my soul.', + 'matches': { + 'doomed': 3, + 'reformed': 3, + 'nova': 2, + 'scion': 2, + 'delinquent': 2, + }, + 'increase': [ + 'superior', + 'freak', + ], + 'decrease': [ + 'danger', + 'mundane', + ], + }, + { + 'text': 'Trendy. Whatever’s colourful and on the cover of NME.', + 'matches': { + 'star': 3, + 'legacy': 2, + 'janus': 2, + 'joined': 2, + }, + 'increase': [ + 'freak', + 'superior', + ], + 'decrease': [ + 'mundane', + 'soldier', + ], + }, + { + 'text': 'Geek chic. I’m all for comics and pop culture.', + 'matches': { + 'janus': 2, + 'outsider': 1, + 'nomad': 2, + 'brain': 3, + }, + 'increase': [ + 'danger', + 'mundane', + ], + 'decrease': [ + ], + }, + { + 'text': 'I’m wearing this ‘ironically’, just so you know!', + 'matches': { + 'beacon': 2, + 'harbinger': 2, + 'nomad': 3, + 'delinquent': 2, + 'star': 2, + }, + 'increase': [ + 'freak', + 'superior', + ], + 'decrease': [ + ], + }, + { + 'text': 'Does it have pockets?', + 'matches': { + 'transformed': 2, + 'newborn': 2, + 'harbinger': 2, + 'innocent': 2, + 'beacon': 2, + 'protege': 2, + 'bull': 1, + 'soldier': 2, + }, + 'increase': [ + 'mundane', + 'freak', + ], + 'decrease': [ + ], + } + ] + }, + { + 'question': 'What does your super suit look like?', + 'answers': [ + { + 'text': 'I don’t do the whole superhero malarkey. I fight with my real identity because I’ve got nothing to be scared of.', + 'matches': { + 'innocent': 3, + 'doomed': 2, + 'reformed': 2, + 'transformed': 2, + }, + 'increase': [ + 'mundane', + 'danger', + ], + 'decrease': [ + 'saviour', + 'danger', + ], + }, + { + 'text': 'Something which conveys a very unique sense of my personality: a striking design, a consistent meaningful motif, and a reflection of my beliefs.', + 'matches': { + 'protege': 3, + 'outsider': 2, + 'janus': 2, + 'scion': 2, + 'soldier': 2, + }, + 'increase': [ + 'freak', + 'saviour', + ], + 'decrease': [ + 'mundane', + 'soldier', + ], + }, + { + 'text': 'The most important thing is a flashy logo. Everything else is just added extras.', + 'matches': { + 'legacy': 2, + 'star': 3, + 'protege': 2, + }, + 'increase': [ + 'mundane', + 'danger', + ], + 'decrease': [ + ], + }, + { + 'text': 'Something functional and practical. I’m talking pockets, utility belts, gadget pouchers!', + 'matches': { + 'beacon': 3, + 'harbinger': 3, + 'brain': 2, + 'delinquent': 1, + 'protege': 2, + 'nomad': 2, + }, + 'increase': [ + 'freak', + 'superior', + ], + 'decrease': [ + 'danger', + 'mundane', + ], + }, + { + 'text': 'Yeah, about that… I kind of keep tearing through every super suit I wear.', + 'matches': { + 'transformed': 3, + 'nova': 3, + 'bull': 2, + 'newborn': 2, + }, + 'increase': [ + 'saviour', + 'freak', + ], + 'decrease': [ + ], + } + ] + }, + { + 'question': 'A giant robot is smashing up the school campus. What do you do?', + 'answers': [ + { + 'text': 'Protect the science lab! My science project is in there!', + 'matches': { + 'brain': 3, + 'newborn': 3, + 'outsider': 2, + 'nomad': 2, + 'janus': 2, + 'transformed': 3, + }, + 'increase': [ + 'saviour', + 'superior', + ], + 'decrease': [ + 'freak', + 'danger', + ], + }, + { + 'text': 'Charge in like a hero to prove myself and impress my crush!', + 'matches': { + 'bull': 3, + 'joined': 2, + 'outsider': 2, + 'doomed': 3, + 'protege': 3, + 'star': 2, + }, + 'increase': [ + 'mundane', + 'danger', + ], + 'decrease': [ + 'saviour', + 'superior', + ], + }, + { + 'text': 'Evacuate the civillians. We have to get people safe!', + 'matches': { + 'beacon': 2, + 'legacy': 3, + 'soldier': 3, + 'janus': 2, + }, + 'increase': [ + 'danger', + 'saviour', + ], + 'decrease': [ + 'freak', + 'soldier', + ], + }, + { + 'text': 'Trick the school bully to get in harm’s way.', + 'matches': { + 'delinquent': 2, + 'reformed': 2, + 'scion': 3, + }, + 'increase': [ + 'mundane', + 'danger', + ], + 'decrease': [ + 'saviour', + 'danger', + ], + }, + { + 'text': 'Attack decisively with ferocious abandon. Nobody smashes up the school but me!', + 'matches': { + 'nova': 3, + 'delinquent': 3, + 'bull': 2, + 'soldier': 2, + 'innocent': 2, + 'harbinger': 2, + }, + 'increase': [ + 'mundane', + 'freak', + ], + 'decrease': [ + 'saviour', + 'danger', + ], + } + ] + }, + { + 'question': 'What kind of superhero catchphrase do you have?', + 'answers': [ + { + 'text': 'Something pithy and cool that makes me look like a badass.', + 'matches': { + 'star': 3, + 'nomad': 3, + }, + 'increase': [ + 'mundane', + 'danger', + ], + 'decrease': [ + 'soldier', + 'danger', + ], + }, + { + 'text': 'Something inspiring about the valuess I stand for that rallies people to my cause.', + 'matches': { + 'janus': 2, + 'legacy': 2, + 'soldier': 2, + }, + 'increase': [ + 'freak', + 'saviour', + ], + 'decrease': [ + 'superior', + 'soldier', + ], + }, + { + 'text': 'Something onomatopoeic and punchy for when the villains are, well, getting punched!', + 'matches': { + 'beacon': 2, + 'bull': 2, + 'nova': 3, + 'transformed': 2, + }, + 'increase': [ + 'superior', + 'danger', + ], + 'decrease': [ + 'soldier', + 'danger', + ], + }, + { + 'text': 'Something so arcane and complicated that my enemies’ befuddlement buys me time to make my move.', + 'matches': { + 'brain': 3, + 'scion': 2, + 'reformed': 2, + 'outsider': 2, + 'doomed': 2, + }, + 'increase': [ + 'danger', + 'superior', + ], + 'decrease': [ + 'danger', + 'freak', + ], + }, + { + 'text': 'A terrible pun because my real superpower is the power of cringe.', + 'matches': { + 'innocent': 2, + 'joined': 2, + 'beacon': 2, + }, + 'increase': [ + 'superior', + 'freak', + ], + 'decrease': [ + 'saviour', + 'mundane', + ], + } + ] + }, + { + 'question': 'How do you feel about having superpowers?', + 'answers': [ + { + 'text': 'It is a great responsibility and calling for a higher purpose.', + 'matches': { + 'soldier': 3, + 'legacy': 3, + 'harbinger': 2, + }, + 'increase': [ + 'mundane', + 'saviour', + ], + 'decrease': [ + ], + }, + { + 'text': 'I am still honing my skills to be the best that I can be.', + 'matches': { + 'protege': 2, + 'beacon': 2, + }, + 'increase': [ + 'freak', + 'superior', + ], + 'decrease': [ + ], + }, + { + 'text': 'It makes me better than those around me. That’s just a fact!', + 'matches': { + 'brain': 1, + 'scion': 2, + 'reformed': 3, + 'delinquent': 2, + }, + 'increase': [ + 'freak', + 'superior', + ], + 'decrease': [ + 'mundane', + 'superior', + ], + }, + { + 'text': 'It’s a huge liability and is going to get me and those around me hurt.', + 'matches': { + 'doomed': 2, + 'nova': 3, + 'bull': 1, + 'innocent': 2, + }, + 'increase': [ + 'saviour', + 'danger', + ], + 'decrease': [ + 'saviour', + 'freak', + ], + }, + { + 'text': 'Powers don’t set us apart from others; our actions do.', + 'matches': { + 'janus': 3, + 'transformed': 3, + 'outsider': 2, + 'joined': 2, + }, + 'increase': [ + 'freak', + 'mundane', + ], + 'decrease': [ + 'superior', + 'mundane', + ], + } + ] + }, + { + 'question': 'Another student just lied to a teacher to cover for you and your antics. They now look to you. What do you do?', + 'answers': [ + { + 'text': 'I appreciate their help. And by ‘appreciate’ I mean I’ll take advantage of it at every given opportunity because this kid’s a sucker.', + 'matches': { + 'reformed': 3, + 'delinquent': 1, + 'star': 2, + }, + 'increase': [ + 'mundane', + 'superior', + ], + 'decrease': [ + ], + }, + { + 'text': 'I appreciate their help. And by ‘appreciate’ I mean I’ll thank them politely and brush them off before they ask any further questions.', + 'matches': { + 'nomad': 3, + 'delinquent': 3, + 'scion': 2, + 'innocent': 2, + 'brain': 2, + }, + 'increase': [ + 'superior', + 'freak', + ], + 'decrease': [ + ], + }, + { + 'text': 'I appreciate their help. And by ‘appreciate’ I mean I’ll tell them the bare minimum of what they need to know, and nothing else for their own safety!', + 'matches': { + 'legacy': 2, + 'scion': 2, + 'delinquent': 2, + 'harbinger': 1, + 'innocent': 2, + 'soldier': 1, + }, + 'increase': [ + 'freak', + 'saviour', + ], + 'decrease': [ + 'soldier', + 'saviour', + ], + }, + { + 'text': 'I appreciate their help. And by ‘appreciate’ I mean I’ll bring them in the loop about what’s happening, as the more informed they are the better they can protect themselves.', + 'matches': { + 'doomed': 2, + 'nova': 2, + 'bull': 1, + 'harbinger': 2, + 'protege': 3, + 'soldier': 2, + 'transformed': 1, + }, + 'increase': [ + 'superior', + 'saviour', + ], + 'decrease': [ + 'freak', + 'mundane', + ], + }, + { + 'text': 'I appreciate their help. And by ‘appreciate’ I mean I’m going to make them my new best friend and I’m going to tell them everything including my secret identity.', + 'matches': { + 'janus': 2, + 'newborn': 3, + 'beacon': 2, + 'joined': 3, + 'outsider': 1, + }, + 'increase': [ + 'freak', + 'mundane', + ], + 'decrease': [ + 'saviour', + 'mundane', + ], + } + ] + }, + { + 'question': 'You’ve overheard some people scheming to utterly humiliate someone you despise at their big birthday party. What do you do?', + 'answers': [ + { + 'text': 'Bury the hatchet and go tell them about the plan. I may not like them, but even they don’t deserve this!', + 'matches': { + 'outsider': 3, + 'janus': 2, + 'bull': 2, + 'protege': 1, + 'harbinger': 2, + 'joined': 3, + }, + 'increase': [ + 'danger', + 'mundane', + ], + 'decrease': [ + ], + }, + { + 'text': 'Inveigle myself an invite so I can be there to see it happen (but act surprised afterwards, of course)!', + 'matches': { + 'star': 2, + 'reformed': 2, + 'scion': 2, + 'nova': 2, + 'beacon': 3, + 'harbinger': 2, + 'innocent': 2, + }, + 'increase': [ + 'mundane', + 'superior', + ], + 'decrease': [ + ], + }, + { + 'text': 'Join in the conspiracy. Hey, the enemy of my enemy is my friend, right?', + 'matches': { + 'delinquent': 3, + 'bull': 2, + }, + 'increase': [ + 'saviour', + 'danger', + ], + 'decrease': [ + ], + }, + { + 'text': 'Help my rival in the aftermath so that they notice how much of the bigger person I’m being.', + 'matches': { + 'scion': 3, + 'reformed': 2, + 'star': 2, + 'legacy': 3, + 'newborn': 2, + 'soldier': 1, + }, + 'increase': [ + 'danger', + 'saviour', + ], + 'decrease': [ + ], + }, + { + 'text': 'Get on the good side of these people so they don’t turn their vengeance on me.', + 'matches': { + 'brain': 2, + 'janus': 2, + 'doomed': 2, + 'outsider': 2, + 'transformed': 3, + 'newborn': 2, + 'nomad': 1, + }, + 'increase': [ + 'freak', + 'mundane', + ], + 'decrease': [ + 'saviour', + 'mundane', + ], + } + ] + }, + { + 'question': 'One of your rivals has been edging up to a confrontation with you. How do you put them in their place?', + 'answers': [ + { + 'text': 'Get better grades than them and do better at school. Can’t argue with that.', + 'matches': { + 'brain': 3, + 'protege': 3, + 'outsider': 1, + 'newborn': 3, + 'transformed': 2, + 'delinquent': 1, + 'janus': 2, + }, + 'increase': [ + 'freak', + 'superior', + ], + 'decrease': [ + 'danger', + 'mundane', + ], + }, + { + 'text': 'Beating them in a one-on-one showdown on the korfball court, absolutely humiliating them in front of the whole school, and in front of their fellow jocks.', + 'matches': { + 'bull': 2, + 'protege': 3, + 'star': 1, + 'soldier': 2, + 'beacon': 1, + 'innocent': 2, + 'newborn': 2, + 'transformed': 3, + 'delinquent': 3, + }, + 'increase': [ + 'soldier', + 'danger', + ], + 'decrease': [ + 'freak', + 'danger', + ], + }, + { + 'text': 'Fight crime and stop evil. Win the adoration and respect of the city. That’ll show them.', + 'matches': { + 'legacy': 2, + 'harbinger': 2, + 'soldier': 3, + 'beacon': 2, + 'delinquent': 2, + 'doomed': 3, + }, + 'increase': [ + 'freak', + 'saviour', + ], + 'decrease': [ + 'mundane', + 'saviour', + ], + }, + { + 'text': 'Use my powers to intimidate them, and give them a sense of what they’re dealing with.', + 'matches': { + 'nova': 3, + 'bull': 2, + 'nomad': 2, + 'newborn': 2, + 'transformed': 2, + 'delinquent': 2, + }, + 'increase': [ + 'superior', + 'freak', + ], + 'decrease': [ + 'danger', + 'freak', + ], + }, + { + 'text': 'Play the long game: take something they love (or someone they love, if that’s more effective).', + 'matches': { + 'scion': 1, + 'reformed': 2, + 'delinquent': 1, + }, + 'increase': [ + 'freak', + 'danger', + ], + 'decrease': [ + ], + } + ] + }, + { + 'question': 'You’ve just heard that a friend of yours fancies the same person you’ve had your eyes on. Do you…?', + 'answers': [ + { + 'text': 'Take this opportunity to tell my crush how I feel. Carpe diem, right?', + 'matches': { + 'bull': 3, + 'outsider': 3, + 'doomed': 2, + 'nova': 1, + 'joined': 2, + 'harbinger': 3, + }, + 'increase': [ + 'freak', + 'danger', + ], + 'decrease': [ + ], + }, + { + 'text': 'Scare away the competition with my superpowers. They’ve crossed the wrong person!', + 'matches': { + 'bull': 1, + 'nova': 3, + 'star': 2, + 'legacy': 2, + 'reformed': 3, + }, + 'increase': [ + 'saviour', + 'freak', + ], + 'decrease': [ + 'soldier', + 'danger', + ], + }, + { + 'text': 'Impress my crush with my superhuman abilities. Surely they’ll fall for that?', + 'matches': { + 'protege': 3, + 'beacon': 2, + 'soldier': 3, + 'newborn': 2, + }, + 'increase': [ + 'superior', + 'freak', + ], + 'decrease': [ + 'mundane', + 'soldier', + ], + }, + { + 'text': 'Try talking it through with my friend so we don’t let it come between us.', + 'matches': { + 'janus': 2, + 'joined': 2, + 'transformed': 1, + 'innocent': 2, + }, + 'increase': [ + 'danger', + 'mundane', + ], + 'decrease': [ + 'superior', + 'mundane', + ], + }, + { + 'text': 'Dissuade them by starting a rumour. Or two. Or several. Oh dear… I’m in too deep. Help!', + 'matches': { + 'delinquent': 2, + 'scion': 2, + 'joined': 2, + 'nomad': 2, + }, + 'increase': [ + 'mundane', + 'freak', + ], + 'decrease': [ + 'mundane', + 'superior', + ], + } + ] + }, + { + 'question': 'You just found some really upsetting graffiti calling you a freak on the bathroom mirror. What do you do?', + 'answers': [ + { + 'text': 'Level the building. That should get rid of the graffiti.', + 'matches': { + 'doomed': 2, + 'nova': 3, + 'bull': 2, + 'legacy': 2, + }, + 'increase': [ + 'saviour', + 'freak', + ], + 'decrease': [ + ], + }, + { + 'text': 'Find the person in school who hates me the most and wring an apology out of them.', + 'matches': { + 'delinquent': 1, + 'bull': 3, + 'nova': 2, + 'harbinger': 2, + 'innocent': 2, + 'soldier': 3, + }, + 'increase': [ + 'superior', + 'danger', + ], + 'decrease': [ + ], + }, + { + 'text': 'Erase it as quickly as I can and go cry in a bathroom stall.', + 'matches': { + 'janus': 2, + 'outsider': 3, + 'nomad': 3, + 'brain': 2, + 'reformed': 3, + 'protege': 2, + }, + 'increase': [ + 'danger', + 'freak', + ], + 'decrease': [ + ], + }, + { + 'text': 'Work on my image and try and fit in more.', + 'matches': { + 'beacon': 3, + 'star': 3, + 'outsider': 3, + 'transformed': 2, + 'innocent': 3, + 'scion': 3, + 'reformed': 2, + 'joined': 3, + }, + 'increase': [ + 'freak', + 'mundane', + ], + 'decrease': [ + ], + }, + { + 'text': 'Give the person whoever wrote it something really freaky to worry about.', + 'matches': { + 'delinquent': 2, + 'transformed': 3, + 'outsider': 2, + 'harbinger': 3, + 'reformed': 3, + 'scion': 2, + 'legacy': 3, + 'newborn': 3, + 'protege': 3, + }, + 'increase': [ + 'mundane', + 'freak', + ], + 'decrease': [ + ], + } + ] + }, + { + 'question': 'Why didn’t you finish your homework?', + 'answers': [ + { + 'text': 'Had some family drama to deal with.', + 'matches': { + 'legacy': 1, + 'scion': 2, + 'janus': 1, + 'outsider': 1, + 'newborn': 2, + }, + 'increase': [ + 'danger', + 'mundane', + ], + 'decrease': [ + 'soldier', + 'mundane', + ], + }, + { + 'text': 'Something came up with my friends and me.', + 'matches': { + 'janus': 2, + 'outsider': 2, + 'bull': 2, + 'joined': 2, + }, + 'increase': [ + 'mundane', + 'freak', + ], + 'decrease': [ + 'soldier', + 'mundane', + ], + }, + { + 'text': 'I was busy training and lost track of time.', + 'matches': { + 'protege': 2, + 'beacon': 2, + 'harbinger': 2, + 'reformed': 2, + 'brain': 1, + 'soldier': 1, + }, + 'increase': [ + 'freak', + 'danger', + ], + 'decrease': [ + 'mundane', + 'soldier', + ], + }, + { + 'text': 'Saving the world is more important than homework!', + 'matches': { + 'legacy': 2, + 'innocent': 2, + 'beacon': 1, + 'star': 2, + 'brain': 2, + 'soldier': 2, + }, + 'increase': [ + 'danger', + 'saviour', + ], + 'decrease': [ + 'mundane', + 'soldier', + ], + }, + { + 'text': 'I just could not be bothered!', + 'matches': { + 'delinquent': 2, + 'nomad': 2, + 'transformed': 2, + 'reformed': 2, + 'scion': 1, + 'doomed': 2, + 'brain': 1, + }, + 'increase': [ + 'danger', + 'superior', + ], + 'decrease': [ + ], + } + ] + }, + { + 'question': 'What was the last thing you were sent to the Principal’s office for?', + 'answers': [ + { + 'text': 'Flying off the handle at a teacher.', + 'matches': { + 'nova': 2, + 'bull': 1, + 'protege': 1, + 'doomed': 2, + 'transformed': 2, + }, + 'increase': [ + 'superior', + 'freak', + ], + 'decrease': [ + 'soldier', + 'freak', + ], + }, + { + 'text': 'Picking a fight with another student.', + 'matches': { + 'bull': 2, + 'nova': 1, + 'beacon': 2, + 'soldier': 2, + 'protege': 2, + 'harbinger': 1, + 'reformed': 1, + }, + 'increase': [ + 'freak', + 'danger', + ], + 'decrease': [ + 'mundane', + 'soldier', + ], + }, + { + 'text': 'Getting caught with something illegal.', + 'matches': { + 'beacon': 1, + 'scion': 2, + 'delinquent': 2, + 'newborn': 2, + 'soldier': 2, + 'outsider': 1, + 'doomed': 1, + }, + 'increase': [ + 'superior', + 'danger', + ], + 'decrease': [ + 'mundane', + 'freak', + ], + }, + { + 'text': 'Skiving.', + 'matches': { + 'legacy': 2, + 'harbinger': 2, + 'beacon': 1, + 'star': 2, + 'brain': 2, + 'soldier': 2, + 'nomad': 2, + }, + 'increase': [ + 'superior', + 'mundane', + ], + 'decrease': [ + 'freak', + 'saviour', + ], + }, + { + 'text': 'Being a patsy for someone else’s misdemeanours.', + 'matches': { + 'brain': 2, + 'janus': 2, + 'outsider': 2, + 'reformed': 2, + 'scion': 1, + 'doomed': 2, + 'soldier': 2, + 'innocent': 2, + 'joined': 2, + }, + 'increase': [ + 'freak', + 'mundane', + ], + 'decrease': [ + ], + } + ] + }, + { + 'question': 'Why did the School authorities give you another chance?', + 'answers': [ + { + 'text': 'Because my record was spotless until that one incident.', + 'matches': { + 'brain': 2, + 'newborn': 2, + 'soldier': 2, + }, + 'increase': [ + 'freak', + 'mundane', + ], + 'decrease': [ + ], + }, + { + 'text': 'Because they wouldn’t dare exclude me, as my family is well connected.', + 'matches': { + 'legacy': 2, + 'scion': 2, + 'protege': 1, + 'newborn': 2, + 'star': 2, + 'joined': 2, + }, + 'increase': [ + 'mundane', + 'superior', + ], + 'decrease': [ + ], + }, + { + 'text': 'Because they saw my potential and wanted me to succeed.', + 'matches': { + 'beacon': 2, + 'protege': 2, + 'janus': 2, + 'harbinger': 2, + 'reformed': 2, + }, + 'increase': [ + 'danger', + 'saviour', + ], + 'decrease': [ + ], + }, + { + 'text': 'Because I was the aggrieved party, and excluding me would be discriminatory.', + 'matches': { + 'transformed': 2, + 'newborn': 1, + 'bull': 1, + 'outsider': 2, + 'doomed': 2, + 'innocent': 2, + }, + 'increase': [ + 'superior', + 'freak', + ], + 'decrease': [ + ], + }, + { + 'text': 'Because I’d pose a greater danger out of school than I would be in school.', + 'matches': { + 'nomad': 2, + 'nova': 2, + 'bull': 2, + 'delinquent': 2, + }, + 'increase': [ + 'mundane', + 'danger', + ], + 'decrease': [ + ], + } + ] + }, + { + 'question': 'You recently discovered that your love interest, best friend, or any other teen who is really close to you has an internship at the massive evil corporation run by one of the most formidable supervillains in the city. What do you do?', + 'answers': [ + { + 'text': 'Blow the place up. Can’t have an internship if the entire complex has been reduced to rubble.', + 'matches': { + 'bull': 3, + 'nova': 2, + 'soldier': 2, + 'doomed': 2, + 'legacy': 3, + }, + 'increase': [ + 'freak', + 'danger', + 'soldier' + ], + 'decrease': [ + 'mundane', + 'superior' + ], + }, + { + 'text': 'Also start interning there and infiltrate the place using subterfute and keep an eye on the situation.', + 'matches': { + 'janus': 2, + 'beacon': 3, + 'soldier': 1, + 'scion': 2, + 'reformed': 1, + 'brain': 3, + 'delinquent': 2, + 'protege': 2, + }, + 'increase': [ + 'mundane', + 'superior', + ], + 'decrease': [ + 'danger', + 'freak', + 'soldier' + ], + }, + { + 'text': 'Use whatever influence, connections, and clout I have to stop the internship from continuing.', + 'matches': { + 'legacy': 2, + 'scion': 3, + 'star': 2, + 'harbinger': 2, + 'reformed': 2, + 'protege': 1 + }, + 'increase': [ + 'superior', + 'saviour', + ], + 'decrease': [ + 'mundane', + 'freak', + 'soldier' + ], + }, + { + 'text': 'To be honest, they are more likely to be studying me as part of the internship.', + 'matches': { + 'newborn': 3, + 'outsider': 3, + 'nomad': 2, + 'transformed': 3, + + }, + 'increase': [ + 'freak', + 'superior', + ], + 'decrease': [ + 'saviour', + 'mundane' + ], + }, + { + 'text': 'Tell them the truth.', + 'matches': { + 'janus': 3, + 'joined': 3, + 'innocet': 2, + 'beacon': 2, + }, + 'increase': [ + 'mundane', + 'saviour', + ], + 'decrease': [ + 'saviour', + 'danger' + ], + } + ] + }, + { + 'question': 'What are you most scared about for when you leave school?', + 'answers': [ + { + 'text': 'Having all my glory days behind me.', + 'matches': { + 'legacy': 2, + 'delinquent': 3, + 'star': 2, + + }, + 'increase': [ + 'superior', + 'mundane' + ], + 'decrease': [ + 'saviour', + 'soldier' + ], + }, + { + 'text': 'A big, bad world that I do not understand.', + 'matches': { + 'soldier': 2, + 'bull': 2, + 'newborn': 3, + 'beacon': 2, + 'outsider': 2, + 'protege': 2 + }, + 'increase': [ + + ], + 'decrease': [ + + ], + }, + { + 'text': 'All the people I am close to drifting away.', + 'matches': { + 'nomad': 2, + 'outsider': 1, + 'janus': 2, + 'joined': 1 + }, + 'increase': [ + + ], + 'decrease': [ + + ], + }, + { + 'text': 'Being looked down upon by others for who I am or what I have done.', + 'matches': { + 'reformed': 3, + 'scion': 2, + 'transformed': 2, + 'innocent': 2, + 'delinquent': 2, + }, + 'increase': [ + + ], + 'decrease': [ + + ], + }, + { + 'text': 'The world literally coming to an end.', + 'matches': { + 'brain': 2, + 'harbinger': 3, + 'nova': 3, + 'innocent': 3, + 'doomed': 3 + }, + 'increase': [ + + ], + 'decrease': [ + + ], + } + ] + }, + { + 'question': 'An adult superhero/villain yells, ‘That’s it, you’re gounded! Go to your room and think about what you’ve done!’ What do you do?', + 'answers': [ + { + 'text': 'Go back to my room begrudgingly and ruminate.', + 'matches': { + 'janus': 2, + 'beacon': 2, + 'newborn': 2, + 'protege': 2, + 'brain': 1, + 'soldier': 2, + + }, + 'increase': [ + 'mundane', + 'soldier' + ], + 'decrease': [ + 'superior' + ], + }, + { + 'text': 'Go back to my room, slam the door loudly, and then sneak out the window.', + 'matches': { + 'delinquent': 2, + 'scion': 2, + 'star': 2, + 'nomad': 2, + 'beacon': 1, + 'protege': 1, + }, + 'increase': [ + 'superior', + ], + 'decrease': [ + 'soldier' + 'mundane' + ], + }, + { + 'text': 'Plead, ‘You have to let me go to the Model UN competition/foil the archvillain’s evil plot that my friends and I have been working on for so long!’ I promise this will not happen again.', + 'matches': { + 'brain': 2, + 'legacy': 2, + 'innocent': 2, + 'harbinger': 2, + 'protege': 1, + 'reformed': 1 + }, + 'increase': [ + 'superior' + ], + 'decrease': [ + 'danger' + ], + }, + { + 'text': 'Protest, ‘That’s not fair! It wasn’t even my fault!’', + 'matches': { + 'nova': 2, + 'transformed': 2, + 'doomed': 2, + 'reformed': 2, + 'delinquent': 1, + 'legacy': 1, + + }, + 'increase': [ + 'superior' + ], + 'decrease': [ + 'danger' + ], + }, + { + 'text': 'Yell, ‘You’re not even my real parents!’ and flip the table.', + 'matches': { + 'bull': 2, + 'reformed': 2, + 'joined': 2, + 'outsider': 2, + 'scion': 1, + 'newborn': 1, + 'nomad': 1, + }, + 'increase': [ + 'danger' + ], + 'decrease': [ + 'mundane' + ], + } + ] + }, + { + 'question': 'You’ve just had the most awful day at school, and an even worse day fighting crime. How do you deal with it?', + 'answers': [ + { + 'text': 'Break things. Punch people.', + 'matches': { + 'bull': 2, + 'delinquent': 2, + 'transformed': 2, + 'harbinger': 2 + }, + 'increase': [ + 'danger' + ], + 'decrease': [ + 'mundane' + ], + }, + { + 'text': 'Read. Listen to music. Watch a film. Do something creative.', + 'matches': { + 'brain': 2, + 'star': 2, + 'innocent': 2, + 'nova': 2 + }, + 'increase': [ + 'superior' + ], + 'decrease': [ + 'danger' + ], + }, + { + 'text': 'Seek guidance from adults.', + 'matches': { + 'legacy': 2, + 'protege': 2, + 'soldier': 2, + }, + 'increase': [ + 'saviour' + ], + 'decrease': [ + 'danger' + ], + }, + { + 'text': 'Seek comfort from my friends.', + 'matches': { + 'janus': 2, + 'beacon': 2, + 'nomad': 2, + 'joined': 2, + 'reformed': 2 + }, + 'increase': [ + 'mundane' + ], + 'decrease': [ + 'superior' + ], + }, + { + 'text': 'Sad montage of me all alone, crying into a tub of ice-cream in front of the telly alongside my pet.', + 'matches': { + 'outsider': 2, + 'doomed': 2, + 'newborn': 2, + 'scion': 2 + }, + 'increase': [ + 'mundane' + ], + 'decrease': [ + 'danger' + ], + } + ] + } +] \ No newline at end of file diff --git a/data/sources.py b/data/sources.py new file mode 100644 index 0000000..afa66e8 --- /dev/null +++ b/data/sources.py @@ -0,0 +1,30 @@ +sources = { + 'core': { + 'title': 'Masks: a New Generation', + 'type':'core book', + 'author':'Brenadan Conway', + 'publisher':'Magpie Games', + 'year':'2016' + }, + 'halcyon': { + 'title':'Halcyon City Herald Collection', + 'type':'setting supplement', + 'author':'Elizabeth Chaipraditkul, et al.', + 'publisher':'Magpie Games', + 'year':'2017' + }, + 'aegis': { + 'title':'Secrets of A.E.G.I.S.', + 'type':'setting supplement', + 'author':'Cam Banks, et al.', + 'publisher':'Magpie Games', + 'year':'2018' + }, + 'unbound': { + 'title':'Unbound', + 'type':'setting supplement', + 'author':'Misha Bushyager, et al.', + 'publisher':'Magpie Games', + 'year':'2018' + } +} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..fd3642b --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,9 @@ +version: '3.9' + +services: + quiz: + build: . + container_name: masks_quiz + ports: + - 5000 + restart: unless-stopped \ No newline at end of file diff --git a/interface/__init__.py b/interface/__init__.py new file mode 100644 index 0000000..117a23b --- /dev/null +++ b/interface/__init__.py @@ -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 \ No newline at end of file diff --git a/interface/error_handlers.py b/interface/error_handlers.py new file mode 100644 index 0000000..4454e4c --- /dev/null +++ b/interface/error_handlers.py @@ -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") \ No newline at end of file diff --git a/interface/static/css/style.css b/interface/static/css/style.css new file mode 100644 index 0000000..f962c03 --- /dev/null +++ b/interface/static/css/style.css @@ -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; + } +} \ No newline at end of file diff --git a/interface/static/favicon.ico b/interface/static/favicon.ico new file mode 100644 index 0000000..0cb2ad5 Binary files /dev/null and b/interface/static/favicon.ico differ diff --git a/interface/static/favicon.png b/interface/static/favicon.png new file mode 100644 index 0000000..bfd3856 Binary files /dev/null and b/interface/static/favicon.png differ diff --git a/interface/static/js/jquery.js b/interface/static/js/jquery.js new file mode 100644 index 0000000..c4c6022 --- /dev/null +++ b/interface/static/js/jquery.js @@ -0,0 +1,2 @@ +/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="
",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0Error: Page Not Found + + + +{% endblock %} \ No newline at end of file diff --git a/interface/templates/base.html b/interface/templates/base.html new file mode 100644 index 0000000..eb901cd --- /dev/null +++ b/interface/templates/base.html @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + {% block title %}{% endblock %} | Which Masks Playbook Are You? — a Personality Quiz + + + + + + + {% include "navbar.html" %} + + + + + + + + + + + + + + {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + {% for category, message in messages %} + {% if category == 'error' %} + + {% endif %} + {% endfor %} + {% endif %} + {% endwith %} + + + +
+ {% block content %} {% endblock %} +
+ + + + + + + + + + + + \ No newline at end of file diff --git a/interface/templates/compatibility.html b/interface/templates/compatibility.html new file mode 100644 index 0000000..1a7f9d6 --- /dev/null +++ b/interface/templates/compatibility.html @@ -0,0 +1,28 @@ +{% extends "base.html" %} +{% block title %} Compatibility and Accessibility {% endblock %} +{% block content %} +

Compatibility and Accessibility

+ +

Browser Compatibility

+

+ 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. +

+

+ If you notice any incompatibilities or strange browser behaviour, please let me know and I'll try and see how I can fix it. +

+ +

Accessibility

+

+ 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. +

+

+ 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). +

+

+ 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. +

+

Web Hosting

+

+ 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. +

+{% endblock %} \ No newline at end of file diff --git a/interface/templates/dev.html b/interface/templates/dev.html new file mode 100644 index 0000000..7d94f8c --- /dev/null +++ b/interface/templates/dev.html @@ -0,0 +1,23 @@ +{% extends "base.html" %} +{% block title %} Development {% endblock %} +{% block content %} +

Development

+

+ 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. +

+

+ 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. +

+

+ 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. +

+

+ 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. +

+

+ 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. +

+

+ In addition, this is the first time I am storing the code of the quiz on my Git repo, and having a much more streamlined version control process. +

+{% endblock %} \ No newline at end of file diff --git a/interface/templates/home.html b/interface/templates/home.html new file mode 100644 index 0000000..018a681 --- /dev/null +++ b/interface/templates/home.html @@ -0,0 +1,35 @@ +{% extends "base.html" %} +{% block title %} Home | Which Masks Playbook Are You? — a Personality Quiz {% endblock %} +{% block content %} +

Home page

+ +

Background

+ +

+ This is a personality quiz to determine which playbook you are in the Masks: a New Generation table-top role-playing game. There are several personality quizzes out there for D&D, and this one by Easydamus in particular inspired me. I thought it would be fun to make something similar for Masks as it is my favourite TRPG (more on that in the appropriate section). +

+

+ 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. +

+

+ If you want to take the quiz, you can do so here: + Take the Quiz +

+ +

What is a Table-Top Role-Playing Game?

+

+ 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. +

+

+ The most famous TRPG is Dungeons & Dragons, 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. +

+

+ Masks 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 Masks particularly in the relevant section. +

+

+ 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 D&D, like the Fighter, Wizard, Rogue, or Cleric. These classes in D&D 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. +

+

+ In Masks, 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! +

+{% endblock %} \ No newline at end of file diff --git a/interface/templates/masks.html b/interface/templates/masks.html new file mode 100644 index 0000000..1d2aca4 --- /dev/null +++ b/interface/templates/masks.html @@ -0,0 +1,11 @@ +{% extends "base.html" %} +{% block title %} About Masks {% endblock %} +{% block content %} +

About Masks

+

Masks 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 Apocalypse World 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.

+

If you imagine all table-top role-playing games to exist on a spectrum between chess and improv theatre with D&D 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 D&D would be closer to chess because of its wargame heritage.

+

Masks 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.

+

Note on Content

+

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.

+

Special thanks to the members of the Masks Discord for their advice, feedback, and patience with the original quirks of the web hosting.

+{% endblock %} \ No newline at end of file diff --git a/interface/templates/navbar.html b/interface/templates/navbar.html new file mode 100644 index 0000000..935d629 --- /dev/null +++ b/interface/templates/navbar.html @@ -0,0 +1,52 @@ + \ No newline at end of file diff --git a/interface/templates/quiz.html b/interface/templates/quiz.html new file mode 100644 index 0000000..f1901cb --- /dev/null +++ b/interface/templates/quiz.html @@ -0,0 +1,82 @@ +{% extends "base.html" %} +{% block title %} Take the Quiz {% endblock %} +{% block content %} +

Take the Quiz

+ +

+ 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. +

+ +

+ 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. +

+ +
+
+ {% for question in questions -%} +
+ + Question {{ loop.index }}: {{ question['question']|safe }} + + + + {% set name = 'q'~loop.index %} + {% for answer in question['answers'] -%} + + + + + {% endfor %} + +
+ + + +
+
+ {% endfor %} +
+
+ Please select which sourcebooks you would like to include results from. +
+ {% for source in sources -%} + + + {% endfor %} +
+
+ + Reset +
+
+
+{% endblock %} \ No newline at end of file diff --git a/interface/templates/results.html b/interface/templates/results.html new file mode 100644 index 0000000..8d56d48 --- /dev/null +++ b/interface/templates/results.html @@ -0,0 +1,153 @@ +{% extends "base.html" %} +{% block title %} Your Results {% endblock %} +{% block content %} +

Your Results

+ + {% if results['selected_playbooks']|length > 1 %} +

Your Playbooks are:

+ {% else%} +

Your Playbook is:

+ {% endif %} + {%for playbook in results['selected_playbooks'] -%} +
+

The {{ playbook[0]|upper }}{{ playbook[1:] }}

+ {% if playbook == 'joined' %} +
+ You have duplicated The {{ results['joined_cloned'][0]|upper }}{{ results['joined_cloned'][1:] }} +
+ {% endif %} +

+ {{ playbooks[playbook]['flavour']|safe }} +

+ {% set source = sources[playbooks[playbook]['source']] %} +

From the {{ source['title'] }} {{ source['type'] }} by {{ source['author'] }}, published by {{ source['publisher'] }} in {{ source['year'] }}.

+

+ Moment of Truth: {{ playbooks[playbook]['moment']|safe }} +

+

+ Labels:
+ + + {% for label in results['selected_playbooks'][playbook] -%} + + + + + {% endfor %} + +
+ {{ label[0]|upper}}{{ label[1:]}}: + + {{ results['selected_playbooks'][playbook][label] }} +
+

+
+ {% endfor %} + +

How You Fared:

+ +

+ This chart represents how many points you got for each playbook from the source books you selected in all the questions you answered. +

+ +
+ + + {% for playbook in results['playbooks'] -%} + + + + + + + {% endfor %} + +
+ {{ loop.index }}. + + The {{ playbook[0]|upper }}{{ playbook[1:] }} + + {{ (results['playbooks'][playbook]*100/results['max_score'])|round(0) }}% + + {% for i in range( (results['playbooks'][playbook]*20/results['max_score'])|round(0, 'ceil')|int) -%}X{% endfor %} + ({{ results['playbooks'][playbook] }}) +
+
+
+ +

+ Glossary +

+

Labels

+

In Masks, your character’s attributes are defined by their ‘labels’. This is like the character ability scores in D&D. 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.

+

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 lot. Because, teenagers.

+

Labels range on a scale of -2 to +3

+ + + + + + + + + + + + + + + + + + + + + + {% for label in results['display_labels'] -%} + + + + + {% endfor %} + +
+ Danger + + {{ labels['danger']['flavour'] }} + {{ results['display_labels'].remove('danger') if results['display_labels'].remove('danger') is not none else '' }} +
+ Freak + + {{ labels['freak']['flavour'] }} + {{ results['display_labels'].remove('freak') if results['display_labels'].remove('freak') is not none else '' }} +
+ Saviour + + {{ labels['saviour']['flavour'] }} + {{ results['display_labels'].remove('saviour') if results['display_labels'].remove('saviour') is not none else '' }} +
+ Superior + + {{ labels['superior']['flavour'] }} + {{ results['display_labels'].remove('superior') if results['display_labels'].remove('superior') is not none else '' }} +
+ Mundane + + {{ labels['mundane']['flavour'] }} + {{ results['display_labels'].remove('mundane') if results['display_labels'].remove('mundane') is not none else '' }} +
+ {{ label[0]|upper }}{{ label[1:] }} + + {{ labels[label]['flavour'] }} + {{ results['display_labels'].remove(label) if results['display_labels'].remove(label) is not none else '' }} +
+

Your Moment of Truth

+

+ In Masks, your Moment of Truth 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. +

+

+ 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 Labels. That Label can no longer shift. +

+ +
+{% endblock %} \ No newline at end of file diff --git a/interface/views.py b/interface/views.py new file mode 100644 index 0000000..9962182 --- /dev/null +++ b/interface/views.py @@ -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')) diff --git a/quiz/__init__.py b/quiz/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/quiz/evaluation.py b/quiz/evaluation.py new file mode 100644 index 0000000..144e3bc --- /dev/null +++ b/quiz/evaluation.py @@ -0,0 +1,86 @@ +from data.sources import sources +from data.playbooks import playbooks +from data.labels import labels +from data.questions import questions +from random import randrange + +def evaluate_quiz(submission): + # Set up dictionaries to count scores + results = { + 'playbooks' : {}, + 'selected_playbooks': {}, + 'display_labels': [], + 'max_score': 0 + } + for playbook in playbooks: + if playbooks[playbook]['source'] in set.intersection(set(sources), set(submission)): + results['playbooks'][playbook] = 0 + + for answer in submission: + if answer.startswith('q'): + qno = int(answer[1:]) - 1 + ano = int(submission[answer]) - 1 + match_list = questions[qno]['answers'][ano]['matches'] + for match in match_list: + if match in results['playbooks']: results['playbooks'][match] += match_list[match] + results['max_score'] += max(match_list.values()) + high_score = max(results['playbooks'].values()) + for playbook, score in results['playbooks'].items(): + if score == high_score: + results['selected_playbooks'][playbook] = {} + for label in labels: + if 'custom' not in labels[label] or not labels[label]['custom']: + results['selected_playbooks'][playbook][label] = playbooks[playbook]['labels'][label] + if label not in results['display_labels']: results['display_labels'].append(label) + else: + if playbook in labels[label]['playbooks']: + if label in playbooks[playbook]['labels']: + results['selected_playbooks'][playbook][label] = playbooks[playbook]['labels'][label] + else: + results['selected_playbooks'][playbook][label] = labels[label]['default_value'] + if label not in results['display_labels']: results['display_labels'].append(label) + + if 'joined' in results['selected_playbooks']: + if len(results['selected_playbooks']) > 1: + l = list(results['selected_playbooks']) + l.remove('joined') + i = randrange(len(l)-1) + results['joined_cloned'] = l[i] + results['selected_playbooks']['joined'] = results['selected_playbooks'][l[i]].copy() + else: + d = results['playbooks'].copy() + del d['joined'] + high_score = max(d.values()) + p = [] + for playbook, score in d.items(): + if score == high_score: + p.append(playbook) + p_sel = p[randrange(len(p)-1)] if len(p) > 1 else p[0] + results['selected_playbooks'][p_sel] = {} + results['joined_cloned'] = p_sel + for label in labels: + if 'custom' not in labels[label] or not labels[label]['custom']: + results['selected_playbooks'][p_sel][label] = playbooks[playbook]['labels'][label] + if label not in results['display_labels']: results['display_labels'].append(label) + else: + if playbook in labels[label]['playbooks']: + if label in playbooks[playbook]['labels']: + results['selected_playbooks'][p_sel][label] = playbooks[playbook]['labels'][label] + else: + results['selected_playbooks'][p_sel][label] = labels[label]['default_value'] + if label not in results['display_labels']: results['display_labels'].append(label) + results['selected_playbooks']['joined'] = results['selected_playbooks'][p_sel].copy() + + for answer in submission: + if answer.startswith('q'): + qno = int(answer[1:]) - 1 + ano = int(submission[answer]) - 1 + for increase in questions[qno]['answers'][ano]['increase']: + for playbook in results['selected_playbooks']: + if increase in results['selected_playbooks'][playbook]: + if results['selected_playbooks'][playbook][increase] < 3: results['selected_playbooks'][playbook][increase] += 1 + for decrease in questions[qno]['answers'][ano]['decrease']: + for playbook in results['selected_playbooks']: + if decrease in results['selected_playbooks'][playbook]: + if results['selected_playbooks'][playbook][decrease] > -2: results['selected_playbooks'][playbook][decrease] -= 1 + return results diff --git a/quiz/validators.py b/quiz/validators.py new file mode 100644 index 0000000..0914063 --- /dev/null +++ b/quiz/validators.py @@ -0,0 +1,23 @@ +from flask.helpers import flash +from data.sources import sources +from flask import flash + +def validate_questions(submissions): + for key in submissions: + if 'q' in key and int(key[1:]): + return True + flash('Error: You cannot leave the quiz blank.', category='error') + return False + +def validate_filters(submissions): + if not set.intersection(set(submissions.keys()), set(sources.keys())): + flash('Error: You must select at least one source book to show results from.', category='error') + return False + else: + return True + +def validate_submissions(submissions): + if validate_questions(submissions) and validate_filters(submissions): + return True + else: + return False \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..1086595 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,7 @@ +click==8.0.3 +colorama==0.4.4 +Flask==2.0.2 +itsdangerous==2.0.1 +Jinja2==3.0.2 +MarkupSafe==2.0.1 +Werkzeug==2.0.2 diff --git a/secret.py.example b/secret.py.example new file mode 100644 index 0000000..ee9b6d6 --- /dev/null +++ b/secret.py.example @@ -0,0 +1 @@ +key = '' \ No newline at end of file