Last active
June 19, 2021 11:35
-
-
Save JarbasAl/fa3d7d15bc827bd2a99ca797f276a4ec to your computer and use it in GitHub Desktop.
virtual pet
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import random | |
| from time import sleep | |
| from threading import Thread | |
| from datetime import datetime | |
| class VirtualPet(Thread): | |
| def __init__(self, name="VirtualPet", speed=1.0, lifespan=1): | |
| """ | |
| :param name: creature name | |
| :param speed: | |
| :param lifespan: in hours | |
| """ | |
| super().__init__() | |
| self.name = name | |
| self.birthday = None | |
| self.is_predator = False | |
| self.species = "Virtual Pet" | |
| self.stats = {"food": 100, | |
| "water": 100, | |
| "happiness": 100, | |
| "hygiene": 100, | |
| "health": 100, | |
| "energy": 100} | |
| self.is_sleeping = False | |
| self.is_dead = False | |
| self.is_playing = False | |
| self.is_sick = False | |
| self.is_nocturnal = False | |
| self.min_temperature = -50 | |
| self.max_temperature = 100 | |
| self.cause_of_death = "Not Dead" | |
| self.lifespan = 60 * 60 * lifespan | |
| self.number_of_poo = 0 | |
| self.age = 0 | |
| self.setDaemon(True) | |
| self.callbacks = [] | |
| self.speed = speed | |
| @property | |
| def life_expectancy(self): | |
| return self.lifespan - 0.2 * self.lifespan, \ | |
| self.lifespan + 0.2 * self.lifespan | |
| @property | |
| def is_depressed(self): | |
| return self.stats["happiness"] < 30 | |
| @property | |
| def is_hungry(self): | |
| return self.stats["food"] < 40 | |
| @property | |
| def is_thirsty(self): | |
| return self.stats["water"] < 50 | |
| @property | |
| def as_json(self): | |
| data = self.stats.copy() | |
| data["species"] = self.species | |
| data["name"] = self.name | |
| data["min_temperature"] = self.min_temperature | |
| data["max_temperature"] = self.max_temperature | |
| data["is_nocturnal"] = self.is_nocturnal | |
| data["is_predator"] = self.is_predator | |
| data["is_dead"] = self.is_dead | |
| data["is_sick"] = self.is_sick | |
| data["is_playing"] = self.is_playing | |
| data["is_sleeping"] = self.is_sleeping | |
| data["number_of_poo"] = self.number_of_poo | |
| data["birthday"] = str(self.birthday) | |
| data["age"] = self.age | |
| data["lifespan"] = self.lifespan | |
| data["cause_of_death"] = self.cause_of_death | |
| return data | |
| def add_callback(self, callback): | |
| idx = len(self.callbacks) | |
| self.callbacks.append(callback) | |
| return idx | |
| def remove_callback(self, idx): | |
| self.callbacks.pop(idx) | |
| @staticmethod | |
| def validate(value): | |
| value = min(100, value) | |
| value = max(0, value) | |
| return value | |
| def validate_stats(self): | |
| for stat, value in self.stats.items(): | |
| self.stats[stat] = self.validate(value) | |
| def update(self, data): | |
| for stat in self.stats: | |
| self.stats[stat] += data[stat] | |
| self.validate_stats() | |
| def decrease_stat(self, stat, speed, steps): | |
| # speed is hours to 0 | |
| # step is seconds | |
| self.stats[stat] -= (steps * 10) / (speed * 36) | |
| def increase_stat(self, stat, speed, steps): | |
| # speed is hours to 100 | |
| # step is seconds | |
| self.stats[stat] += (steps * 10) / (speed * 36) | |
| def random_event(self): | |
| if not self.is_playing and not self.is_sleeping: | |
| random_number = random.randint(0, 600) | |
| if random_number == 0: | |
| self.number_of_poo = min(self.number_of_poo + 1, 5) | |
| if random_number == 1 and self.stats["hygiene"] > 50 and \ | |
| self.is_sick and self.stats["happiness"] > 50 and \ | |
| self.stats["health"] > 50: | |
| self.is_sick = False | |
| self.on_healed() | |
| if random_number == 2: | |
| self.stats["health"] += 0.5 | |
| if self.life_expectancy[0] < self.age < self.life_expectancy[1] \ | |
| and random_number == 3: | |
| self.kill("Old age") | |
| self.stats["health"] = 0 | |
| def step(self, steps=1.0): | |
| self.age += steps | |
| # sleep status change | |
| if datetime.now().hour <= 6 or datetime.now().hour >= 22: | |
| if self.is_nocturnal and self.is_sleeping: | |
| self.is_sleeping = False | |
| self.on_wake_up() | |
| elif not self.is_nocturnal and not self.is_sleeping: | |
| self.is_sleeping = True | |
| self.on_sleep() | |
| else: | |
| if self.is_nocturnal and not self.is_sleeping: | |
| self.is_sleeping = True | |
| self.on_sleep() | |
| elif not self.is_nocturnal and self.is_sleeping: | |
| self.is_sleeping = False | |
| self.on_wake_up() | |
| if self.is_sleeping: | |
| # recover health | |
| self.increase_stat("energy", 8, steps) | |
| self.decrease_stat("hygiene", (12 - self.number_of_poo), | |
| steps) | |
| else: | |
| # update stats | |
| if self.is_playing: | |
| self.decrease_stat("energy", 3, steps) | |
| self.decrease_stat("hygiene", 3, steps) | |
| self.decrease_stat("food", 4, steps) | |
| self.decrease_stat("water", 4, steps) | |
| self.increase_stat("happiness", 1, steps) | |
| else: | |
| self.decrease_stat("energy", 4, steps) | |
| self.decrease_stat("hygiene", | |
| 4 / (self.number_of_poo + 1), steps) | |
| self.decrease_stat("food", 4, steps) | |
| self.decrease_stat("happiness", 4, steps) | |
| self.decrease_stat("water", 6, steps) | |
| # check if too much poo | |
| if self.is_sick: | |
| self.decrease_stat("health", 24, steps) | |
| else: | |
| if self.stats["hygiene"] <= 40: | |
| self.is_sick = True | |
| self.on_sick("Lack of hygiene") | |
| elif self.stats["happiness"] <= 30 and \ | |
| self.stats["energy"] <= 50: | |
| self.is_sick = True | |
| self.on_sick("Depression") | |
| self.random_event() | |
| # enforce 0 - 100 range | |
| self.validate_stats() | |
| # check death | |
| if not self.is_dead: | |
| if self.stats["food"] == 0: | |
| self.kill("Starved") | |
| elif self.stats["health"] == 0: | |
| self.kill("Disease") | |
| elif self.stats["water"] == 0: | |
| self.kill("Died of thirst") | |
| def run(self): | |
| if self.birthday is None: | |
| self.birthday = datetime.now() | |
| while not self.is_dead: | |
| sleep(self.speed) | |
| self.step(self.speed) | |
| for cb in self.callbacks: | |
| try: | |
| cb() | |
| except Exception as e: | |
| print(e) | |
| def kill(self, cause_of_death="unknown"): | |
| self.is_dead = True | |
| self.stats["health"] = 0 | |
| self.cause_of_death = cause_of_death | |
| self.on_dead(cause_of_death) | |
| def clean_poo(self): | |
| self.number_of_poo = 0 | |
| # events | |
| def on_wake_up(self): | |
| print(self.name + " woke up") | |
| def on_sleep(self): | |
| print(self.name + " is sleeping") | |
| def on_drinking(self, water): | |
| print(self.name + " drank " + str(water) + " water") | |
| def on_eating(self, food, creature=None): | |
| print(self.name + " ate " + str(food) + " food") | |
| def on_dead(self, reason): | |
| print(self.name + " died of " + reason) | |
| def on_sick(self, reason): | |
| print(self.name + " is sick because " + reason) | |
| def on_healed(self): | |
| print(self.name + " healed") | |
| def on_playing_start(self): | |
| print(self.name + " is playing") | |
| def on_playing_stop(self): | |
| print(self.name + " stopped playing") | |
| class PrimordialSlime(VirtualPet): | |
| def __init__(self, name="Slimy", speed=0.5): | |
| super().__init__(name, speed, lifespan=9999999999) | |
| self.species = "Primordial Slime" | |
| self.is_nocturnal = True | |
| self.min_temperature = -100 | |
| self.max_temperature = 200 | |
| class GooblyGook(VirtualPet): | |
| def __init__(self, name="Goobly", speed=5.0): | |
| super().__init__(name, speed, lifespan=600) | |
| self.species = "Goobly Gook" | |
| self.min_temperature = 20 | |
| self.max_temperature = 80 | |
| class Grub(VirtualPet): | |
| def __init__(self, name="Grubus", speed=2.0): | |
| super().__init__(name, speed, lifespan=2000) | |
| self.species = "Grub" | |
| self.is_predator = True | |
| self.min_temperature = 0 | |
| self.max_temperature = 80 | |
| class VirtualHabitat(Thread): | |
| def __init__(self, name="VirtualHabitat", | |
| food=500, | |
| water=1000, | |
| temperature=30): | |
| super().__init__() | |
| self.species = {} | |
| self.name = name | |
| self.food = food | |
| self.water = water | |
| self.temperature = temperature | |
| self.callbacks = [] | |
| @property | |
| def creatures(self): | |
| creatures = [] | |
| for species in self.species: | |
| for creature in self.species[species]: | |
| creatures.append(creature) | |
| return creatures | |
| @property | |
| def number_of_poo(self): | |
| return sum([c.number_of_poo for c in self.creatures]) | |
| def add_callback(self, callback): | |
| idx = len(self.callbacks) | |
| self.callbacks.append(callback) | |
| return idx | |
| def remove_callback(self, idx): | |
| self.callbacks.pop(idx) | |
| def add_creature(self, creature): | |
| if creature.species not in self.species: | |
| self.species[creature.species] = [] | |
| self.species[creature.species].append(creature) | |
| self.species[creature.species][-1].start() | |
| def get_random_creature(self, | |
| filter_prey=False, | |
| filter_predators=False, | |
| filter_sleeping=True): | |
| alive = [c for c in self.creatures if not c.is_dead] | |
| if filter_predators: | |
| alive = [c for c in alive if not c.is_predator] | |
| if filter_prey: | |
| alive = [c for c in alive if c.is_predator] | |
| if filter_sleeping: | |
| alive = [c for c in alive if not c.is_sleeping] | |
| if not len(alive): | |
| return None | |
| return random.choice(alive) | |
| def random_event(self, creature): | |
| if not creature.is_dead: | |
| if self.temperature < creature.min_temperature: | |
| creature.kill("Ambient temperature too low") | |
| elif self.temperature > creature.max_temperature: | |
| creature.kill("Ambient temperature too high") | |
| if creature.is_sleeping or creature.is_dead: | |
| return | |
| rand = random.randint(0, 100) | |
| if creature.is_thirsty and self.water > 0: | |
| self.water -= 1 | |
| creature.stats["water"] += 1 | |
| creature.on_drinking(1) | |
| if not creature.is_predator and self.food > 0 and creature.is_hungry: | |
| self.food -= 1 | |
| creature.stats["food"] += 1 | |
| creature.on_eating(1) | |
| elif rand < 30 and creature.is_hungry and creature.is_predator: | |
| dead = self.get_random_creature(filter_predators=True) | |
| if dead is not None: | |
| food = 10 + dead.stats["food"] | |
| dead.kill("eaten by " + creature.name) | |
| creature.stats["energy"] = 30 + creature.stats["energy"] | |
| creature.stats["food"] += food | |
| creature.on_eating(food, dead) | |
| creature.stats["happiness"] += 70 | |
| elif rand == 2 and not creature.is_playing: | |
| creature.is_playing = True | |
| creature.on_playing_start() | |
| elif rand == 3 and creature.is_playing: | |
| creature.is_playing = False | |
| creature.on_playing_stop() | |
| elif rand == 4 and self.water > 5: | |
| self.water -= 5 | |
| creature.stats["water"] += 5 | |
| creature.on_drinking(5) | |
| elif random.randint(0, 600) == 0 and \ | |
| not creature.is_predator and \ | |
| self.food > 5: | |
| self.food -= 5 | |
| creature.stats["food"] += 5 | |
| creature.on_eating(5) | |
| creature.validate_stats() | |
| def step(self): | |
| for species in self.species: | |
| for creature in self.species[species]: | |
| self.random_event(creature) | |
| def run(self, speed=1): | |
| old_temp = int(self.temperature) | |
| old_water = int(self.water) | |
| old_food = int(self.food) | |
| old_poo = self.number_of_poo | |
| old_num = len(self.creatures) | |
| while len(self.creatures) > 0: | |
| sleep(speed) | |
| self.step() | |
| for cb in self.callbacks: | |
| try: | |
| cb() | |
| except Exception as e: | |
| print(e) | |
| if old_temp != int(self.temperature): | |
| self.on_temperature_change(old_temp, int(self.temperature)) | |
| old_temp = int(self.temperature) | |
| if old_water != int(self.water): | |
| self.on_water_change(old_water, int(self.water)) | |
| old_water = int(self.water) | |
| if old_food != int(self.food): | |
| self.on_food_change(old_food, int(self.food)) | |
| old_food = int(self.food) | |
| if old_poo != self.number_of_poo: | |
| self.on_poo_change(old_poo, self.number_of_poo) | |
| old_poo = self.number_of_poo | |
| if old_num != len(self.creatures): | |
| self.on_creature_number_change(old_num, len(self.creatures)) | |
| old_num = len(self.creatures) | |
| def stop(self): | |
| self.running = False | |
| for creature in self.creatures: | |
| creature.kill("Apocalypse") | |
| # actions | |
| def warm(self, value=0.5): | |
| self.temperature += value | |
| def cool(self, value=0.5): | |
| self.temperature -= value | |
| def clean(self): | |
| for c in self.creatures: | |
| c.clean_poo() | |
| def add_food(self, value=50): | |
| self.food += value | |
| def add_water(self, value=50): | |
| self.water += value | |
| # events | |
| def on_temperature_change(self, old, new): | |
| print("TEMPERATURE: " + str(new)) | |
| def on_poo_change(self, old, new): | |
| print("POO NUMBER: " + str(new)) | |
| def on_food_change(self, old, new): | |
| print("FOOD: " + str(new)) | |
| def on_water_change(self, old, new): | |
| print("WATER: " + str(new)) | |
| def on_creature_number_change(self, old, new): | |
| print("CREATURE NUMBER: " + str(new)) | |
| if __name__ == "__main__": | |
| h = VirtualHabitat(food=500) | |
| h.add_creature(PrimordialSlime()) | |
| h.add_creature(PrimordialSlime("Slimou")) | |
| h.add_creature(PrimordialSlime("Slimetic")) | |
| h.add_creature(PrimordialSlime("Slimogic")) | |
| h.add_creature(Grub("Borkus")) | |
| h.add_creature(GooblyGook("Gob")) | |
| h.add_creature(GooblyGook("Gub")) | |
| h.add_creature(GooblyGook("Gaab")) | |
| for c in h.creatures: | |
| # randomize initial stats | |
| for stat in c.stats: | |
| if stat in ["hygiene"]: | |
| continue | |
| c.stats[stat] = random.randint(40, 90) | |
| def cb(): | |
| # create food out of nowhere | |
| h.add_food(0.02) | |
| h.add_water(0.02) | |
| # temperature fluctuation | |
| if random.choice([True, False]): | |
| h.warm(0.01) | |
| else: | |
| h.cool(0.01) | |
| if h.number_of_poo > 10: | |
| print("I HATE CLEANING THE DAMN POO") | |
| h.clean() | |
| h.add_callback(cb) | |
| h.run() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment