From d8cb73fa6c2d8ff0645c26446ae6f0365e065d85 Mon Sep 17 00:00:00 2001 From: khuxkm fbexl Date: Sat, 11 Apr 2020 06:25:17 -0400 Subject: [PATCH] Initial code commit --- .gitignore | 113 +++++++++++++++++++++++++++++++++++++++++++ LICENSE | 21 ++++++++ README.md | 18 +++++++ main.py | 120 ++++++++++++++++++++++++++++++++++++++++++++++ name.json | 9 ++++ person.py | 122 +++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 1 + 7 files changed, 404 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 main.py create mode 100644 name.json create mode 100644 person.py create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c32e48c --- /dev/null +++ b/.gitignore @@ -0,0 +1,113 @@ + +# Created by https://www.gitignore.io/api/python +# Edit at https://www.gitignore.io/?templates=python + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# End of https://www.gitignore.io/api/python + +# save +save.json diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7bec922 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2020 Robert 'khuxkm' Miles, https://khuxkm.tilde.team + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..0f34068 --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +# PearlyGates + +A game about heaven and hell, where you get to be the scales of morality. + +## How to play? + +Download the repo, and run `main.py` in your Python interpreter of choice. Be +sure to install the `tracery` library, which is required for the name generator. + +## How to contribute? + +For the most part, the code in `person.py` is self-explanatory. For example: + + - To add a trait, add an entry to `TRAITS`. + - To add a set of traits you cannot have at the same time (like misogynist and feminist), add an entry to `EXCLUSIONARY_TRAITS` (see comment above definition). + - To change the average life expectancy, change `AVERAGE_LIFE_EXPECTANCY`. + +The rest of the code may be poorly commented. Sorry, other coders (and future me). diff --git a/main.py b/main.py new file mode 100644 index 0000000..9f516a4 --- /dev/null +++ b/main.py @@ -0,0 +1,120 @@ +import person, json, random + +# save file defaults +SAVE = dict( + most_holy=dict(), + most_sinful=dict(), + last_20_holy_ages=[], + last_20_sinful_ages=[], + holy=0, + sinful=0, + intro=False, + day=1 +) +try: + with open("save.json") as f: + # load save file + savefile = json.load(f) + # if a key is missing from the save file it'll be set to the default automatically + SAVE.update(savefile) +except: pass + +# convenience functions +next_person = lambda: person.Person.generate() +avg = lambda l: sum(l)/len(l) + +print("Welcome to PearlyGates!") +if not SAVE["intro"]: + # display story intro fragment + print() + print("Saint Peter has retired, and has left you in charge of the gate to heaven.") + print("Rather than being held to any standard, you are given full freedom to send") + print("people to heaven or hell based on your arbitrary guidelines.") + print() + print("So, let's begin!") + # don't show it on next startup + SAVE["intro"]=True +running=True +while running: + print("It is day {!s}.".format(SAVE["day"])) + print("Would you like to:") + print("1) See the dead people?") + print("2) See stats") + print("3) Quit") + choice = None + while choice is None: + try: + choice = int(input("? ").strip()) + assert choice in (1,2,3) + except: + print("Choose 1, 2, or 3.") + choice=None + if choice==2: # stats + print("Stats:") + print("-"*80) + print("Amount of people sent to heaven all-time: {!s}".format(SAVE["holy"])) + print("Average age of holy person (over last 20): {!s}".format(avg(SAVE["last_20_holy_ages"]))) + print("Top 5 most common holy traits:") + # get all traits of people marked "holy" + holy_traits = list(SAVE["most_holy"].items()) + # sort by occurrences descending + holy_traits.sort(key=lambda x: -x[1]) + # display top 5 + for trait, count in holy_traits[:5]: + print(f" - {trait} ({count!s})") + print("-"*80) + print("Amount of people sent to hell all-time: {!s}".format(SAVE["sinful"])) + print("Average age of sinful person (over last 20): {!s}".format(avg(SAVE["last_20_sinful_ages"]))) + print("Top 5 most common sinful traits:") + # get all traits of people marked "sinful" + sinful_traits = list(SAVE["most_sinful"].items()) + # sort by occurences descending + sinful_traits.sort(key=lambda x: -x[1]) + # show top 5 + for trait, count in sinful_traits[:5]: + print(f" - {trait} ({count!s})") + print("-"*80) + elif choice==3: + print("Goodbye!") + running = False + else: + for i in range(random.randint(7,13)): + # get new person and show summary + p = next_person() + print(p.toString()) + print("Send them to:") + print("1) Heaven") + print("2) Hell") + choice = None + while choice is None: + try: + choice = int(input("? ").strip()) + assert choice in (1,2) + except: + print("Choose 1 or 2.") + choice=None + if choice==1: # heaven + print(p.pronoun,"smile"+('' if p.pronoun=="They" else "s"),"at you as",p.pronoun.lower(),"enter"+('' if p.pronoun=="They" else "s")+" the pearly gates.") + # update stats + SAVE["holy"]+=1 + SAVE["last_20_holy_ages"]=(SAVE["last_20_holy_ages"]+[p.age])[-20:] + for trait in p.traits: + if trait not in SAVE["most_holy"]: + SAVE["most_holy"][trait]=1 + else: + SAVE["most_holy"][trait]+=1 + else: + print(p.pronoun,"scream"+('' if p.pronoun=="They" else "s"),"as",p.pronoun.lower(),"fall"+('' if p.pronoun=="They" else "s")+" into the depths of hell.") + # update stats + SAVE["sinful"]+=1 + SAVE["last_20_sinful_ages"]=(SAVE["last_20_sinful_ages"]+[p.age])[-20:] + for trait in p.traits: + if trait not in SAVE["most_sinful"]: + SAVE["most_sinful"][trait]=1 + else: + SAVE["most_sinful"][trait]+=1 + # dawn of the next day + SAVE["day"]+=1 + +with open("save.json","w") as f: + json.dump(SAVE,f) diff --git a/name.json b/name.json new file mode 100644 index 0000000..60026c6 --- /dev/null +++ b/name.json @@ -0,0 +1,9 @@ +{ + "first_name_male": ["James","John","Robert","Michael","William","David","Richard","Charles","Joseph","Thomas","Christopher","Daniel","Paul","Mark","Donald","George","Kenneth","Steven","Edward","Brian","Ronald","Anthony","Kevin","Jason","Matthew","Gary","Timothy","Jose","Larry","Jeffrey","Frank","Scott","Eric","Stephen","Andrew","Raymond","Gregory","Joshua","Jerry","Dennis","Walter","Patrick","Peter","Harold","Douglas","Henry","Carl","Arthur","Ryan","Roger","Joe","Juan","Jack","Albert","Jonathan","Justin","Terry","Gerald","Keith","Samuel","Willie","Ralph","Lawrence","Nicholas","Roy","Benjamin","Bruce","Brandon","Adam","Harry","Fred","Wayne","Billy","Steve","Louis","Jeremy","Aaron","Randy","Howard","Eugene","Carlos","Russell","Bobby","Victor","Martin","Ernest","Phillip","Todd","Jesse","Craig","Alan","Shawn","Clarence","Sean","Philip","Chris","Johnny","Earl","Jimmy","Antonio"], + "first_name_female": ["Mary","Patricia","Linda","Barbara","Elizabeth","Jennifer","Maria","Susan","Margaret","Dorothy","Lisa","Nancy","Karen","Betty","Helen","Sandra","Donna","Carol","Ruth","Sharon","Michelle","Laura","Sarah","Kimberly","Deborah","Jessica","Shirley","Cynthia","Angela","Melissa","Brenda","Amy","Anna","Rebecca","Virginia","Kathleen","Pamela","Martha","Debra","Amanda","Stephanie","Carolyn","Christine","Marie","Janet","Catherine","Frances","Ann","Joyce","Diane","Alice","Julie","Heather","Teresa","Doris","Gloria","Evelyn","Jean","Cheryl","Mildred","Katherine","Joan","Ashley","Judith","Rose","Janice","Kelly","Nicole","Judy","Christina","Kathy","Theresa","Beverly","Denise","Tammy","Irene","Jane","Lori","Rachel","Marilyn","Andrea","Kathryn","Louise","Sara","Anne","Jacqueline","Wanda","Bonnie","Julia","Ruby","Lois","Tina","Phyllis","Norma","Paula","Diana","Annie","Lillian","Emily","Robin"], + "first_name": ["#first_name_male#","#first_name_female#"], + "lastname": ["Smith","Johnson","Williams","Brown","Jones","Miller","Davis","Garcia","Rodriguez","Wilson","Martinez","Anderson","Taylor","Thomas","Hernandez","Moore","Martin","Jackson","Thompson","White","Lopez","Lee","Gonzalez","Harris","Clark","Lewis","Robinson","Walker","Perez","Hall","Young","Allen","Sanchez","Wright","King","Scott","Green","Baker","Adams","Nelson","Hill","Ramirez","Campbell","Mitchell","Roberts","Carter","Phillips","Evans","Turner","Torres","Parker","Collins","Edwards","Stewart","Flores","Morris","Nguyen","Murphy","Rivera","Cook","Rogers","Morgan","Peterson","Cooper","Reed","Bailey","Bell","Gomez","Kelly","Howard","Ward","Cox","Diaz","Richardson","Wood","Watson","Brooks","Bennett","Gray","James","Reyes","Cruz","Hughes","Price","Myers","Long","Foster","Sanders","Ross","Morales","Powell","Sullivan","Russell","Ortiz","Jenkins","Gutierrez","Perry","Butler","Barnes","Fisher","Henderson","Coleman","Simmons","Patterson","Jordan","Reynolds","Hamilton","Graham","Kim","Gonzales","Alexander","Ramos","Wallace","Griffin","West","Cole","Hayes","Chavez","Gibson","Bryant","Ellis","Stevens","Murray","Ford","Marshall","Owens","Harrison","Ruiz","Kennedy","Wells","Alvarez","Woods","Mendoza","Castillo","Olson","Webb","Washington","Tucker","Freeman","Burns","Henry","Vasquez","Snyder","Simpson","Crawford","Jimenez","Porter","Mason","Shaw","Gordon","Wagner","Hunter","Romero","Hicks","Dixon","Hunt","Palmer","Robertson","Black","Holmes","Stone","Meyer","Boyd","Mills","Warren","Fox","Rose","Rice","Moreno","Schmidt","Patel","Ferguson","Nichols","Herrera","Medina","Ryan","Fernandez","Weaver","Daniels","Stephens","Gardner","Payne","Kelley","Dunn","Pierce","Arnold","Tran","Spencer","Peters","Hawkins","Grant","Hansen","Castro","Hoffman","Hart","Elliott","Cunningham","Knight","Bradley"], + "stereotypical_male_name": ["#first_name_male# #lastname#"], + "stereotypical_female_name": ["#first_name_female# #lastname#"], + "random_name": ["#first_name# #lastname#"] +} diff --git a/person.py b/person.py new file mode 100644 index 0000000..7c69d1f --- /dev/null +++ b/person.py @@ -0,0 +1,122 @@ +import tracery, json, sys, random, traceback, math + +# name generator +NAMES = None +try: + with open("name.json") as f: NAMES = tracery.Grammar(json.load(f)) +except Exception as e: + if type(e)==FileNotFoundError: + print("ERROR! name.json not found!",file=sys.stderr) + print("Download `name.json` and place it in the same folder as `person.py`.",file=sys.stderr) + traceback.print_exc() + sys.exit(-1) + +# traits list +TRAITS = """Transgender +Homosexual +Christian +Muslim +Hindu +Jewish +Plays video games +Is a jerk to service workers (cashiers, waiters, etc) +Is nice to service workers (cashiers, waiters, etc) +Kicks puppies +Might be a nazi? +Racist +Misogynist +Feminist +Bigoted +Died rich +Died poor""".splitlines() + +# list of traits you can't have together +# if a person is generated with more than one of the traits in a tuple, +# they will reroll all but one of those traits +EXCLUSIONARY_TRAITS = [ + ("Hindu","Jewish","Muslim"), + ("Is a jerk to service workers (cashiers, waiters, etc)","Is nice to service workers (cashiers, waiters, etc)"), + ("Died rich","Died poor"), + ("Misogynist","Feminist") +] + +# average life expectancy +# according to the UN, world life expectancy was ~72.6 in 2019, so we'll go with that +AVERAGE_LIFE_EXPECTANCY=72.6 + +# pronouns list +# male uses he/him, female uses she/her, and non-binary can use any pronoun in the list below +PRONOUNS = """He +She +They +Ze +Xe +Ve +Vi""".splitlines() + +class Person: + GENDER_DESCRIPTORS = ["Male","Female","Non-binary"] + def __init__(self,name,age,gender=0,traits=["Boring"]): + self.name=name + self.age=age + self.gender=gender + self.traits=traits + # force gender-specific pronouns on binary genders + if self.gender==0: + self.pronoun="He" + elif self.gender==1: + self.pronoun="She" + elif self.gender==2: + # non-binary people get to have whatever pronoun they desire from the list + self.pronoun=random.choice(PRONOUNS) + def toString(self): + # change gender number to gender descriptor + gender = self.GENDER_DESCRIPTORS[self.gender] + out="" + out+=(f"{self.name}, {self.age}\n") + out+=(f"Gender: {gender}\n") + c = len(self.traits) + out+=(f"Traits: ({c!s})\n") + for trait in self.traits: + out+=(f" - {trait}\n") + return out.strip() + def __str__(self): + return self.toString() + @classmethod + def generate(cls): + # pick a random gender + gender = random.randint(0,len(cls.GENDER_DESCRIPTORS)-1) + tag = "random_name" + # ~67% chance name is guaranteed to be stereotypical of gender if gender in binary + if gender==0: + tag = random.choice(["stereotypical_male_name","stereotypical_male_name","random_name"]) + elif gender==1: + tag = random.choice(["stereotypical_female_name","stereotypical_female_name","random_name"]) + # generate name from tracery grammar + name = NAMES.flatten("#"+tag+"#") + # pick age, skewed towards average life expectancy + age = random.choice([math.floor,math.ceil])(random.triangular(5,100,AVERAGE_LIFE_EXPECTANCY)) + # pick 3-7 random traits + traits = [random.choice(TRAITS) for i in range(random.randint(3,7))] + check = True + while check: + check = False + for trait_exc in EXCLUSIONARY_TRAITS: + # indexes of conflicting traits + ind = [i for i, c in enumerate(traits) if c in trait_exc] + # if person doesn't have anything in the list, go to next + if not ind: continue + ind.pop(0) # keep first rolled trait + if ind: # more than one? + for i in ind: + # reroll + traits[i]=random.choice(TRAITS) + check = True # check for conflicting traits again + # don't allow duplicates + c = len(traits) # length before + traits = list(set(traits)) # remove duplicates + while len(traits)=0.1.1