Created
January 1, 2019 23:42
-
-
Save pnathan/309dc6a347cbb256e0ea14fea2c87415 to your computer and use it in GitHub Desktop.
World simulator, has similarties and inspirations from dwarf fortress
This file contains 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
# Licensed under AGPL3 | |
from pprint import pprint, pformat | |
from enum import Enum | |
world = [] | |
buf="" | |
def flush(): | |
global buf | |
print(buf) | |
buf="" | |
def buffer(s): | |
global buf | |
buf+=s+"\n" | |
if len(buf) > 4096: | |
flush() | |
def classify(*enums): | |
base="_".join(enums) | |
return type(base, (object,), | |
dict( | |
[(e, type(e, (object,), {"name":e})) for e in enums])) | |
state = classify("resting", | |
"moving", | |
"angry", | |
"attacking", | |
"out", | |
"horny", | |
"babymaking", | |
"dead") | |
class ch(object): | |
def __init__(self, | |
first_name, | |
last_name, | |
gender, | |
volatility, | |
relations, | |
attrs=None): | |
self.first_name=first_name | |
self.last_name=last_name | |
self.name = first_name + " " + last_name | |
self.gender=gender | |
self.health=random.choice(range(5, 15)) * 1.0 | |
self.volatility=volatility | |
self.loc=None | |
self.state = state.resting | |
self.target=None | |
self.history=[] | |
self.clock = 0 | |
self.relations=relations | |
if not attrs: | |
self.attrs=dict() | |
else: | |
self.attrs=attrs | |
def with_clock(self, age): | |
self.clock=age | |
return self | |
def with_gender(self, g): | |
self.gender=g | |
return self | |
def __str__(self): | |
if self.loc: | |
s = "<npc %s %s HP %s @ %s>" % (self.name, self.health, self.gender, self.loc.name) | |
else: | |
s = "<npc %s %s HP %s>" % (self.name, self.health, self.gender) | |
return s | |
def __repr__(self): | |
return __str__(self) | |
def longform(self): | |
r = ", ".join(map(lambda s: str(self.loc.world.relations[self][s]), | |
self.loc.world.relations[self])) | |
return " ".join([self.name, str(self.age()), str(self.health), self.gender, r]) | |
def age(self): | |
return self.clock | |
def __repr__(self): | |
return str(self) | |
def ack(self, loc): | |
self.loc = loc | |
def unack(self, loc): | |
self.loc = None | |
def istyped(self): | |
return True | |
def give_birth(self): | |
father=self.attrs['mate'] | |
g = 'm' if random.random() > 0.5 else 'f' | |
baby = ch( | |
random.choice(first_m) if g =='m' else random.choice(first_f), | |
self.last_name, | |
g, | |
self.volatility, | |
[self, father]).with_clock(0) | |
self.loc.world.place(baby, "bedroom") | |
self.loc.world.set_relations(baby, self, "%s's daughter" % self.name) | |
self.loc.world.set_relations(self, baby, "%s's mother" % baby.name) | |
self.loc.world.set_relations(baby, father, "%s's daughter" % father.name) | |
self.loc.world.set_relations(father, baby, "%s's father" % baby.name) | |
del self.attrs['pregnant'] | |
del self.attrs['mate'] | |
self.say("A baby has arrived! %s" % baby.name) | |
def state_change(self): | |
if self.state == state.dead: | |
return | |
if self.clock >= 80: | |
self.say("they were good years") | |
self.state = state.dead | |
return | |
if 'pregnant' in self.attrs: | |
if self.attrs['pregnant'] <= self.loc.world.clock - 10: | |
self.give_birth() | |
if self.health <= 0: | |
self.state = state.out | |
self.target = None | |
return | |
if self.state == state.attacking or self.state==state.babymaking or self.state == state.horny: | |
return | |
if self.state == state.out: | |
if self.health > 0: | |
self.state = state.resting | |
elif self.health < 10: | |
self.say("I have been murdered!") | |
self.state = state.dead | |
return | |
wiggle = random.random() + self.volatility | |
if wiggle <= 0.2: | |
self.state = state.resting | |
elif wiggle > 0.2 and wiggle <= 0.7: | |
self.state = state.moving | |
elif wiggle > 0.7 and wiggle <= 0.95: | |
if self.age() < 13: | |
self.state = state.moving | |
elif self.age() >= 13 and self.age() < 55: | |
self.state = state.horny | |
else: | |
self.state = state.resting | |
elif wiggle > 0.95: | |
self.state = state.angry | |
def related_to(self, other): | |
self.loc.world.is_related_to(self, other) | |
def say(self, msg): | |
print("%s: %s" % (self.name, msg)) | |
def hit_on(self, other): | |
proc = random.random() | |
if proc > 0.3: | |
self.say("I made sweet sweet love to %s" % other.name) | |
other.state = state.babymaking | |
other.target = self | |
self.target = other | |
self.state = state.babymaking | |
def attack(self, other): | |
aval = random.random() * 30 | |
proc = random.random() | |
other.state = state.attacking | |
other.target = self | |
if proc > 0.3: | |
other.health -= aval | |
self.say("Got you, %s (%s)!" % (other.target.name, other.target.health)) | |
def interact(self): | |
if self.state == state.dead: | |
return | |
self.say("%s @ %s" % (self.state.name, self.attrs)) | |
if self.target and (self.state == state.angry or self.state == state.attacking): | |
self.say("Gunning for %s" % self.target) | |
# if others beside you are here | |
others = list(set(self.loc.npcs) - set((self,))) | |
if self.state == state.angry: | |
target_choices = tuple(filter(lambda o: o.age() > 13, others)) | |
if len(target_choices) > 0 and self.age() > 13: | |
# and if they are | |
target=random.choice(target_choices) | |
if not target.state == state.out: | |
self.say("I'mma mess you up, %s" % target.name) | |
self.state = state.attacking | |
self.target=target | |
else: | |
self.say("I'm pissed") | |
elif (self.state == state.attacking and | |
self.target in self.loc.npcs): | |
self.say("You're going down, %s" % self.target.name) | |
self.attack(self.target) | |
if self.target.health <= 0: | |
self.state = state.moving | |
self.say("Feels good, man") | |
self.target.state = state.out | |
self.target.target = None | |
self.target.history.append("Attached by %s" % self) | |
self.target = None | |
elif self.state == state.horny: | |
available = list(filter(lambda o: (o.gender != self.gender and | |
not self.related_to(o)), others)) | |
if available: | |
selected=random.choice(available) | |
self.say("hey baby %s" % selected) | |
self.hit_on(selected) | |
elif self.state == state.babymaking: | |
if self.gender == 'f': | |
self.history.append("made baby with %s" % self.target) | |
self.attrs['mate'] = self.target | |
import copy | |
self.attrs['pregnant'] = copy.copy(self.loc.world.clock) | |
elif self.gender == 'm': | |
self.history.append("made baby with %s" % self.target) | |
self.state = state.resting | |
elif self.state == state.moving: | |
exits = self.loc.find_exit() | |
if exits: | |
e=random.choice(exits) | |
self.say("moving on to %s" % e.name) | |
self.loc.world.move_to(self, e) | |
else: | |
self.say("I appear to be stuck") | |
elif self.state == state.out: | |
self.say(" zzz") | |
self.health += 1 | |
else: | |
self.health = min(self.health + 1, 20) | |
first_m=["Biff", "Alex", "Anders", "Cap", "Tim", "Tank", "Steve", "Uri", "Moses", "Isaac", "Ben", "Simon", "Ruben"] | |
first_f=["Suzy", "Jean", "Billy", "Samantha", "Alane", "Audra", "Sarah", "Ella", "Artemis"] | |
last=["Smith", "Ohno", "King", "Tanner", "Bob", "Jean", "Rogers", "Stone", | |
"Goldberg", "Greenspun", "Kaplan", "Perlman", "Stein"] | |
import random | |
def random_ch(g=None): | |
if not g: | |
g = 'm' if random.random() > 0.5 else 'f' | |
if g =='m': | |
first = random.choice(first_m) | |
else: | |
first=random.choice(first_f) | |
return ch(first, random.choice(last), g, random.random() * 0.3, []) | |
class location(object): | |
def __init__(self, name, world): | |
self.name=name | |
self.npcs=[] | |
self.world = world | |
def find_exit(self): | |
return self.world.get_connected(self) | |
def add(self, npc): | |
assert npc.istyped() == True | |
self.npcs.append(npc) | |
npc.ack(self) | |
def remove(self, npc): | |
assert npc.istyped() == True | |
self.npcs.remove(npc) | |
npc.unack(self) | |
def has(self): | |
return self.npcs | |
def __repr__(self): | |
return "<loc %s %s>" % (self.name, pformat(self.npcs)) if self.npcs else "<loc %s>" % self.name | |
class AutoVivification(dict): | |
"""Implementation of perl's autovivification feature.""" | |
#https://stackoverflow.com/a/651879 | |
def __getitem__(self, item): | |
try: | |
return dict.__getitem__(self, item) | |
except KeyError: | |
value = self[item] = type(self)() | |
return value | |
def world_printer(w): | |
pprint(dict([(n.name, n.loc.name) for n in w.npcs])) | |
class World(object): | |
def __init__(self): | |
self.relations=AutoVivification() | |
self.npcs=[] | |
self.pm=AutoVivification() | |
self.locs={} | |
self.clock=0 | |
def set_relations(self, npc1, npc2, md): | |
self.relations[npc1][npc2] = md | |
import functools | |
@functools.lru_cache(maxsize=256) | |
def is_related_to(self, npc1, npc2): | |
visited=set() | |
nexts=[npc1] | |
while len(nexts)>0: | |
n = nexts.pop() | |
visited.add(n) | |
related=self.relations[n] | |
if npc2 in related: | |
return True | |
nexts.extend([a for a in related if a not in visited]) | |
return False | |
def __repr__(self): | |
return (pformat(self.pm) + "\n" + | |
pformat(list(self.locs.values()))) | |
def get_connected(self, p): | |
connected = [self.locs[l] for l in self.pm[p.name].keys()] | |
return connected | |
def connect(self, p, connected_to): | |
if not p in self.locs.keys(): | |
self.locs[p] = location(p, self) | |
for other in connected_to: | |
if not other in self.locs: | |
self.locs[other] = location(other, self) | |
self.pm[p][other]=1 | |
self.pm[other][p]=1 | |
return self | |
def move_to(self, npc, location): | |
npc.loc.remove(npc) | |
self.locs[location.name].add(npc) | |
def place(self, npc, location): | |
if npc not in self.npcs: | |
self.npcs.append(npc) | |
self.locs[location].add(npc) | |
def step(self): | |
self.clock +=1 | |
valid_npcs = [ n for n in self.npcs if n.state != state.dead] | |
for n in valid_npcs: | |
n.state_change() | |
for n in valid_npcs: | |
n.interact() | |
if n.state != state.dead: | |
n.clock += 1 | |
def dead_world(self): | |
valid_npcs = [ n for n in self.npcs if n.state != state.dead] | |
return len(valid_npcs) == 0 | |
def print_npcs(self): | |
for n in self.npcs: | |
print(n.longform()) | |
print("A total of %s people lived" % len(self.npcs)) | |
a=len(list(filter(lambda n: n.state != state.dead, self.npcs))) | |
print("And %s are still alive" % a) | |
PM=World() | |
PM.connect("street", ["neighbor", "downtown"]) | |
PM.connect("downtown", ["work", "bus depot", "pub", "club"]) | |
PM.connect("front door", ["street", "yard"]) | |
PM.connect("front door", ["living room", | |
"office", | |
"kitchen"]) | |
PM.connect("living room", ["bedroom"]) | |
#n2=random_ch().with_clock(13).with_gender('m') | |
PM.place(random_ch('f').with_clock(14), "neighbor") | |
PM.place(random_ch('f').with_clock(14), "neighbor") | |
PM.place(random_ch('f').with_clock(14), "neighbor") | |
PM.place(random_ch('m').with_clock(14), "neighbor") | |
PM.place(random_ch('f').with_clock(14), "neighbor") | |
PM.place(random_ch('f').with_clock(14), "neighbor") | |
PM.place(random_ch('f').with_clock(14), "neighbor") | |
#PM.place(n2, "neighbor") | |
#PM.place(random_ch().with_clock(13).with_gender('f'), "neighbor") | |
#PM.place(random_ch().with_clock(13).with_gender('f'), "neighbor") | |
#us1=random_ch().with_clock(13).with_gender('m') | |
PM.place(random_ch('m').with_clock(13), "bedroom") | |
PM.place(random_ch('m').with_clock(13), "pub") | |
PM.place(random_ch('m').with_clock(13), "club") | |
#PM.place(us2, "bedroom") | |
try: | |
for i in range(0, 240): | |
print("-" * 20, i, "-" * 20) | |
PM.step() | |
if PM.dead_world(): | |
break | |
except KeyboardInterrupt: | |
pass | |
flush() | |
PM.print_npcs() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment