Initial code commit

This commit is contained in:
Robert Miles 2020-04-11 06:25:17 -04:00
commit d8cb73fa6c
7 changed files with 404 additions and 0 deletions

113
.gitignore vendored Normal file
View File

@ -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

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2020 Robert 'khuxkm' Miles, https://khuxkm.tilde.team <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.

18
README.md Normal file
View File

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

120
main.py Normal file
View File

@ -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)

9
name.json Normal file
View File

@ -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#"]
}

122
person.py Normal file
View File

@ -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)<c: # if the list had duplicates
check = True # make sure to check again after you...
traits.append(random.choice(TRAITS)) # ...add new traits
# return object
return cls(name,age,gender,traits)

1
requirements.txt Normal file
View File

@ -0,0 +1 @@
tracery>=0.1.1