├── .github ├── FUNDING.yml └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .vscode └── launch.json ├── LICENSE ├── README.md ├── default_config.json ├── main.py └── simulator ├── errors.py └── game.py /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: montudor 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | 53 | # Translations 54 | *.mo 55 | *.pot 56 | 57 | # Django stuff: 58 | *.log 59 | local_settings.py 60 | db.sqlite3 61 | db.sqlite3-journal 62 | 63 | # Flask stuff: 64 | instance/ 65 | .webassets-cache 66 | 67 | # Scrapy stuff: 68 | .scrapy 69 | 70 | # Sphinx documentation 71 | docs/_build/ 72 | 73 | # PyBuilder 74 | target/ 75 | 76 | # Jupyter Notebook 77 | .ipynb_checkpoints 78 | 79 | # IPython 80 | profile_default/ 81 | ipython_config.py 82 | 83 | # pyenv 84 | .python-version 85 | 86 | # pipenv 87 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 88 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 89 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 90 | # install all needed dependencies. 91 | #Pipfile.lock 92 | 93 | # celery beat schedule file 94 | celerybeat-schedule 95 | 96 | # SageMath parsed files 97 | *.sage.py 98 | 99 | # Environments 100 | .env 101 | .venv 102 | env/ 103 | venv/ 104 | ENV/ 105 | env.bak/ 106 | venv.bak/ 107 | 108 | # Spyder project settings 109 | .spyderproject 110 | .spyproject 111 | 112 | # Rope project settings 113 | .ropeproject 114 | 115 | # mkdocs documentation 116 | /site 117 | 118 | # mypy 119 | .mypy_cache/ 120 | .dmypy.json 121 | dmypy.json 122 | 123 | # Pyre type checker 124 | .pyre/ 125 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Run Life Simulator", 6 | "type": "python", 7 | "request": "launch", 8 | "program": "${workspaceFolder}/main.py", 9 | "console": "integratedTerminal" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Monte Hellawell 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Life Simulator 2 | 3 | [![forthebadge](https://forthebadge.com/images/badges/designed-in-ms-paint.svg)](https://forthebadge.com) 4 | [![forthebadge](https://forthebadge.com/images/badges/made-with-python.svg)](https://forthebadge.com) 5 | [![forthebadge](https://forthebadge.com/images/badges/built-with-love.svg)](https://forthebadge.com) 6 | 7 | A simple text-based life simulator game. This is just a small hobby project, feel free to contribute by sending 8 | a pull request or create an issue if you find a problem. 9 | 10 | ## Debugging 11 | 12 | In order to debug this program, clone this repository, and open it in Visual Studio Code. From there, you can open the debug 13 | section and click the green play button. You can also launch it via a terminal by opening the repository and running: 14 | 15 | ``` 16 | $ python main.py 17 | ``` 18 | 19 | As of right now, this repository has no dependancies; this may change in the future. 20 | -------------------------------------------------------------------------------- /default_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "names": { 3 | "first_names": { 4 | "male": [ 5 | "Monte", 6 | "Jack" 7 | ], 8 | "female": [ 9 | "Evie", 10 | "Izzy", 11 | "Courtney" 12 | ], 13 | "gender_neutral": [ 14 | "Alex" 15 | ] 16 | }, 17 | "last_names": [ 18 | "Hellawell", 19 | "Dalton", 20 | "Gibbons", 21 | "Scutt", 22 | "Maltby", 23 | "Kostyszyn" 24 | ] 25 | }, 26 | "events": { 27 | "primary": { 28 | "baby": [ 29 | { 30 | "text": "{actor} ({gender}) was born happy and healthy", 31 | "type": "birth" 32 | }, 33 | { 34 | "text": "A child pops out, their name is {actor} ({gender})", 35 | "type": "birth" 36 | }, 37 | { 38 | "text": "{actor} ({gender}) was born underweight", 39 | "type": "birth" 40 | }, 41 | { 42 | "text": "{actor} ({gender}) was born obese", 43 | "type": "birth" 44 | } 45 | ], 46 | "toddler": [ 47 | { 48 | "text": "You learnt to ride a bike", 49 | "type": "hobby" 50 | }, 51 | { 52 | "text": "You kick around a football", 53 | "type": "hobby" 54 | }, 55 | { 56 | "text": "You cried because you lost your favourite toy", 57 | "type": "play" 58 | }, 59 | { 60 | "text": "You found out about farmers", 61 | "type": "learn" 62 | } 63 | ], 64 | "teen": [], 65 | "late-teens": [], 66 | "adult": [], 67 | "elderly": [] 68 | }, 69 | "meta": { 70 | "tiers": [ 71 | { 72 | "key": "baby", 73 | "ages": [0, 0], 74 | "start": true 75 | }, 76 | { 77 | "key": "toddler", 78 | "ages": [1, 12] 79 | }, 80 | { 81 | "key": "teen", 82 | "ages": [13, 15] 83 | }, 84 | { 85 | "key": "late-teens", 86 | "ages": [16, 19] 87 | }, 88 | { 89 | "key": "adult", 90 | "ages": [20, 64] 91 | }, 92 | { 93 | "key": "elderly", 94 | "ages": [65, 300] 95 | } 96 | ], 97 | "types": [ 98 | { 99 | "type": "birth", 100 | "limit": 1 101 | } 102 | ] 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from simulator.game import Game 2 | 3 | if __name__ == "__main__": 4 | game = Game() 5 | game.new() 6 | for i in range(5): 7 | print("\n"+str(game.tick())) 8 | input("Press enter to age a year") 9 | -------------------------------------------------------------------------------- /simulator/errors.py: -------------------------------------------------------------------------------- 1 | class InvalidConfig(Exception): 2 | pass 3 | -------------------------------------------------------------------------------- /simulator/game.py: -------------------------------------------------------------------------------- 1 | from . import errors 2 | import random 3 | 4 | class Person(object): 5 | GENDER_MALE = 1 6 | GENDER_FEMALE = 2 7 | GENDER_RANDOM = 3 8 | GENDER_OTHER = 4 9 | 10 | def __init__(self, *args, **kwargs): 11 | self.first_name = kwargs.get("first_name", None) 12 | self.last_name = kwargs.get("last_name", None) 13 | self.set_gender(kwargs.get("gender", self.GENDER_RANDOM)) 14 | 15 | def set_gender(self, gender): 16 | if gender in [self.GENDER_MALE, self.GENDER_FEMALE, self.GENDER_OTHER]: 17 | self.gender = gender 18 | elif gender == self.GENDER_RANDOM: 19 | self.gender = random.choice([self.GENDER_MALE, self.GENDER_FEMALE]) 20 | else: 21 | self.gender = self.GENDER_OTHER 22 | 23 | def get_gender_text(self): 24 | if self.gender == self.GENDER_MALE: 25 | return "male" 26 | elif self.gender == self.GENDER_FEMALE: 27 | return "female" 28 | else: 29 | return "other" 30 | 31 | def __str__(self): 32 | return self.name 33 | 34 | @property 35 | def name(self): 36 | return "%s %s" % (self.first_name, self.last_name) 37 | 38 | 39 | class Actor(Person): 40 | events = [] 41 | people = { 42 | "family": { 43 | "mother": None, 44 | "father": None 45 | }, 46 | "friends": { 47 | "best_friend": None 48 | }, 49 | "relationship": { 50 | "person": None, 51 | "type": None 52 | } 53 | } 54 | ticks = 0 55 | _loaded = False 56 | 57 | def __init__(self, game, *args, **kwargs): 58 | self.game = game 59 | self.config = game.get_config() 60 | self.first_name = kwargs.get("first_name", None) 61 | self.last_name = kwargs.get("last_name", None) 62 | self.gender = kwargs.get("gender", None) 63 | self.primary = kwargs.get("primary", False) 64 | self.tier = kwargs.get("tier", None) 65 | self.age = kwargs.get("age", 0) 66 | 67 | def __str__(self): 68 | return "%s %s" % (self.first_name, self.last_name) 69 | 70 | def load_events(self): 71 | if self.primary: 72 | if self.tier is None: 73 | raise errors.InvalidConfig() 74 | self.events = [] 75 | for event in self.config["events"]["primary"][self.tier]: 76 | self.events.append(Event( 77 | self.game, 78 | text=event.get("text", "Event occured"), 79 | type=event.get("type", None), 80 | )) 81 | self._loaded = True 82 | return self.events 83 | 84 | def trigger_random_event(self): 85 | if not self._loaded: 86 | self.load_events() 87 | 88 | if len(self.events) == 0: 89 | return "Nothing happened!" 90 | 91 | key = random.randint(0, len(self.events)-1) 92 | event = self.events[key] 93 | del self.events[key] 94 | return event.get_event_text() 95 | 96 | def tick(self): 97 | if self.ticks > 0: 98 | self.age += 1 99 | update_tier = None 100 | for tier in self.config["events"]["meta"]["tiers"]: 101 | if update_tier == True: 102 | self.tier = tier["key"] 103 | self.load_events() 104 | break 105 | if tier["key"] == self.tier: 106 | if tier["ages"][1] < self.age: 107 | update_tier = True 108 | continue 109 | break 110 | self.ticks += 1 111 | return self.trigger_random_event() 112 | 113 | 114 | class Event(object): 115 | def __init__(self, game, *args, **kwargs): 116 | self.game = game 117 | self.type = kwargs.get("type", None) 118 | self.text = kwargs.get("text", "Something happened") 119 | 120 | def get_event_text(self, *args, **kwargs): 121 | return self.text.format( 122 | actor=self.game.current_actor, 123 | gender=self.game.current_actor.get_gender_text().capitalize() 124 | ) 125 | 126 | 127 | class Game(object): 128 | config = None 129 | _people_pool = None 130 | active = False 131 | 132 | def __init__(self, *args, **kwargs): 133 | self.config_file = kwargs.get("config", "default_config.json") 134 | 135 | def get_config(self): 136 | if self.config is not None: 137 | return self.config 138 | with open(self.config_file, "r") as file: 139 | import json 140 | self.config = json.loads(file.read()) 141 | return self.config 142 | 143 | def get_people(self, force_gen=False): 144 | if self._people_pool is not None and force_gen is not True: 145 | return self._people_pool 146 | config = self.get_config() 147 | people = [] 148 | 149 | first_names = [] 150 | for key, items in config["names"]["first_names"].items(): 151 | for name in config["names"]["first_names"][key]: 152 | genders = [] 153 | duplicate = False 154 | if key == "male": 155 | genders = [Person.GENDER_MALE] 156 | elif key == "female": 157 | genders = [Person.GENDER_FEMALE] 158 | elif key == "gender_neutral": 159 | genders = [Person.GENDER_MALE, Person.GENDER_FEMALE, Person.GENDER_OTHER] 160 | else: 161 | gender = Person.GENDER_OTHER 162 | for gender in genders: 163 | first_names.append( 164 | { 165 | "first_name": name, 166 | "gender": gender 167 | } 168 | ) 169 | 170 | for person in first_names: 171 | for ln in config["names"]["last_names"]: 172 | new = Person( 173 | first_name=person["first_name"], 174 | last_name=ln, 175 | gender=person["gender"] 176 | ) 177 | people.append(new) 178 | self._people_pool = people 179 | return people 180 | 181 | def get_starter_tier(self): 182 | for tier in self.config.get("events", {}).get("meta", {}).get("tiers", [None]): 183 | if tier is None: 184 | raise errors.InvalidConfig() 185 | if tier.get("start", None) is True: 186 | return tier.get("key", None) 187 | else: 188 | continue 189 | else: 190 | raise errors.InvalidConfig() 191 | 192 | def generate_actor(self): 193 | pool = self.get_people() 194 | pos = random.randint(0, len(pool)-1) 195 | person = pool[pos] 196 | del pool[pos] 197 | actor = Actor( 198 | self, 199 | first_name=person.first_name, 200 | last_name=person.last_name, 201 | gender=person.gender, 202 | primary=True, 203 | tier=self.get_starter_tier() 204 | ) 205 | return actor 206 | 207 | def new(self): 208 | self.get_people(force_gen=True) 209 | self.current_actor = self.generate_actor() 210 | self.active = True 211 | 212 | def tick(self): 213 | return self.current_actor.tick() 214 | --------------------------------------------------------------------------------