move Plant to its own module
Without this, trying to run this against cProfiler fails because pickler can't find Plant in the "current" module. It's an annoying quirk of pickle+cProfiler. see: https://stackoverflow.com/questions/53890693/cprofile-causes-pickling-error-when-running-multiprocessing-python-code this commit also adds a bunch of notes.pull/53/head
parent
151a700774
commit
ea358f641d
372
botany.py
372
botany.py
|
@ -11,352 +11,43 @@ import errno
|
||||||
import uuid
|
import uuid
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import menu_screen as ms
|
import menu_screen as ms
|
||||||
|
from plant import Plant
|
||||||
|
|
||||||
# TODO:
|
# TODO:
|
||||||
# - Switch from personal data file to table in DB
|
# - switch from personal data file to row in DB
|
||||||
|
# - is threading necessary?
|
||||||
|
# - reduce CPU usage
|
||||||
|
|
||||||
class Plant(object):
|
# there are threads.
|
||||||
# This is your plant!
|
# - life thread. sleeps a variable amount of time based on generation bonus. increases tick count (ticks == score).
|
||||||
stage_list = [
|
# - death check; sleeps .1 per loop. checks if plant is dead and harvests it if so.
|
||||||
'seed',
|
# - autosave: saves plant file every 5 seconds. every 60 seconds, updates the garden db
|
||||||
'seedling',
|
# - screen: sleeps 1s per loop. draws interface (including plant). for seeing score/plant change without user input.
|
||||||
'young',
|
# meanwhile, the main thread handles input and redraws curses as needed.
|
||||||
'mature',
|
|
||||||
'flowering',
|
|
||||||
'seed-bearing',
|
|
||||||
]
|
|
||||||
|
|
||||||
color_list = [
|
# affordance index
|
||||||
'red',
|
# - main screen
|
||||||
'orange',
|
# navigable menu, plant, score, etc
|
||||||
'yellow',
|
# - water
|
||||||
'green',
|
# render a visualization of moistness; allow to water
|
||||||
'blue',
|
# - look
|
||||||
'indigo',
|
# print a description of plant with info below rest of UI
|
||||||
'violet',
|
# - garden
|
||||||
'white',
|
# runs a paginated view of every plant on the computer below rest of UI. to return to menu navigation must hit q.
|
||||||
'black',
|
# - visit
|
||||||
'gold',
|
# runs a prompt underneath UI where you can see who recently visited you and type in a name to visit. must submit the prompt to get back to menu navigation.
|
||||||
'rainbow',
|
# - instructions
|
||||||
]
|
# prints some explanatory text below the UI
|
||||||
|
# - exit
|
||||||
rarity_list = [
|
# quits program
|
||||||
'common',
|
|
||||||
'uncommon',
|
|
||||||
'rare',
|
|
||||||
'legendary',
|
|
||||||
'godly',
|
|
||||||
]
|
|
||||||
|
|
||||||
species_list = [
|
|
||||||
'poppy',
|
|
||||||
'cactus',
|
|
||||||
'aloe',
|
|
||||||
'venus flytrap',
|
|
||||||
'jade plant',
|
|
||||||
'fern',
|
|
||||||
'daffodil',
|
|
||||||
'sunflower',
|
|
||||||
'baobab',
|
|
||||||
'lithops',
|
|
||||||
'hemp',
|
|
||||||
'pansy',
|
|
||||||
'iris',
|
|
||||||
'agave',
|
|
||||||
'ficus',
|
|
||||||
'moss',
|
|
||||||
'sage',
|
|
||||||
'snapdragon',
|
|
||||||
'columbine',
|
|
||||||
'brugmansia',
|
|
||||||
'palm',
|
|
||||||
'pachypodium',
|
|
||||||
]
|
|
||||||
|
|
||||||
mutation_list = [
|
|
||||||
'',
|
|
||||||
'humming',
|
|
||||||
'noxious',
|
|
||||||
'vorpal',
|
|
||||||
'glowing',
|
|
||||||
'electric',
|
|
||||||
'icy',
|
|
||||||
'flaming',
|
|
||||||
'psychic',
|
|
||||||
'screaming',
|
|
||||||
'chaotic',
|
|
||||||
'hissing',
|
|
||||||
'gelatinous',
|
|
||||||
'deformed',
|
|
||||||
'shaggy',
|
|
||||||
'scaly',
|
|
||||||
'depressed',
|
|
||||||
'anxious',
|
|
||||||
'metallic',
|
|
||||||
'glossy',
|
|
||||||
'psychedelic',
|
|
||||||
'bonsai',
|
|
||||||
'foamy',
|
|
||||||
'singing',
|
|
||||||
'fractal',
|
|
||||||
'crunchy',
|
|
||||||
'goth',
|
|
||||||
'oozing',
|
|
||||||
'stinky',
|
|
||||||
'aromatic',
|
|
||||||
'juicy',
|
|
||||||
'smug',
|
|
||||||
'vibrating',
|
|
||||||
'lithe',
|
|
||||||
'chalky',
|
|
||||||
'naive',
|
|
||||||
'ersatz',
|
|
||||||
'disco',
|
|
||||||
'levitating',
|
|
||||||
'colossal',
|
|
||||||
'luminous',
|
|
||||||
'cosmic',
|
|
||||||
'ethereal',
|
|
||||||
'cursed',
|
|
||||||
'buff',
|
|
||||||
'narcotic',
|
|
||||||
'gnu/linux',
|
|
||||||
'abraxan', # rip dear friend
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, this_filename, generation=1):
|
# part of the complexity of all this is everything takes place in one curses window; thus, updates must be manually synchronized across the various logical parts of the screen.
|
||||||
# Constructor
|
# ideally, multiple windows would be used:
|
||||||
self.plant_id = str(uuid.uuid4())
|
# - the menu. it doesn't change unless the plant dies OR the plant hits stage 5, then "harvest" is dynamically added.
|
||||||
self.life_stages = (3600*24, (3600*24)*3, (3600*24)*10, (3600*24)*20, (3600*24)*30)
|
# - the plant viewer. this is updated in "real time" as the plant grows.
|
||||||
# self.life_stages = (2, 4, 6, 8, 10) # debug mode
|
# - the status display: score and plant description
|
||||||
self.stage = 0
|
# - the infow window. updated by visit/garden/instructions/look
|
||||||
self.mutation = 0
|
|
||||||
self.species = random.randint(0,len(self.species_list)-1)
|
|
||||||
self.color = random.randint(0,len(self.color_list)-1)
|
|
||||||
self.rarity = self.rarity_check()
|
|
||||||
self.ticks = 0
|
|
||||||
self.age_formatted = "0"
|
|
||||||
self.generation = generation
|
|
||||||
self.dead = False
|
|
||||||
self.write_lock = False
|
|
||||||
self.owner = getpass.getuser()
|
|
||||||
self.file_name = this_filename
|
|
||||||
self.start_time = int(time.time())
|
|
||||||
self.last_time = int(time.time())
|
|
||||||
# must water plant first day
|
|
||||||
self.watered_timestamp = int(time.time())-(24*3600)-1
|
|
||||||
self.watered_24h = False
|
|
||||||
self.visitors = []
|
|
||||||
|
|
||||||
def migrate_properties(self):
|
|
||||||
# Migrates old data files to new
|
|
||||||
if not hasattr(self, 'generation'):
|
|
||||||
self.generation = 1
|
|
||||||
if not hasattr(self, 'visitors'):
|
|
||||||
self.visitors = []
|
|
||||||
|
|
||||||
def parse_plant(self):
|
|
||||||
# Converts plant data to human-readable format
|
|
||||||
output = ""
|
|
||||||
if self.stage >= 3:
|
|
||||||
output += self.rarity_list[self.rarity] + " "
|
|
||||||
if self.mutation != 0:
|
|
||||||
output += self.mutation_list[self.mutation] + " "
|
|
||||||
if self.stage >= 4:
|
|
||||||
output += self.color_list[self.color] + " "
|
|
||||||
output += self.stage_list[self.stage] + " "
|
|
||||||
if self.stage >= 2:
|
|
||||||
output += self.species_list[self.species] + " "
|
|
||||||
return output.strip()
|
|
||||||
|
|
||||||
def rarity_check(self):
|
|
||||||
# Generate plant rarity
|
|
||||||
CONST_RARITY_MAX = 256.0
|
|
||||||
rare_seed = random.randint(1,CONST_RARITY_MAX)
|
|
||||||
common_range = round((2.0/3)*CONST_RARITY_MAX)
|
|
||||||
uncommon_range = round((2.0/3)*(CONST_RARITY_MAX-common_range))
|
|
||||||
rare_range = round((2.0/3)*(CONST_RARITY_MAX-common_range-uncommon_range))
|
|
||||||
legendary_range = round((2.0/3)*(CONST_RARITY_MAX-common_range-uncommon_range-rare_range))
|
|
||||||
|
|
||||||
common_max = common_range
|
|
||||||
uncommon_max = common_max + uncommon_range
|
|
||||||
rare_max = uncommon_max + rare_range
|
|
||||||
legendary_max = rare_max + legendary_range
|
|
||||||
godly_max = CONST_RARITY_MAX
|
|
||||||
|
|
||||||
if 0 <= rare_seed <= common_max:
|
|
||||||
rarity = 0
|
|
||||||
elif common_max < rare_seed <= uncommon_max:
|
|
||||||
rarity = 1
|
|
||||||
elif uncommon_max < rare_seed <= rare_max:
|
|
||||||
rarity = 2
|
|
||||||
elif rare_max < rare_seed <= legendary_max:
|
|
||||||
rarity = 3
|
|
||||||
elif legendary_max < rare_seed <= godly_max:
|
|
||||||
rarity = 4
|
|
||||||
return rarity
|
|
||||||
|
|
||||||
def dead_check(self):
|
|
||||||
# if it has been >5 days since watering, sorry plant is dead :(
|
|
||||||
time_delta_watered = int(time.time()) - self.watered_timestamp
|
|
||||||
if time_delta_watered > (5 * (24 * 3600)):
|
|
||||||
self.dead = True
|
|
||||||
return self.dead
|
|
||||||
|
|
||||||
def update_visitor_db(self, visitor_names):
|
|
||||||
game_dir = os.path.dirname(os.path.realpath(__file__))
|
|
||||||
garden_db_path = os.path.join(game_dir, 'sqlite/garden_db.sqlite')
|
|
||||||
conn = sqlite3.connect(garden_db_path)
|
|
||||||
for name in (visitor_names):
|
|
||||||
c = conn.cursor()
|
|
||||||
c.execute("SELECT * FROM visitors WHERE garden_name = '{}' AND visitor_name = '{}' ".format(self.owner, name))
|
|
||||||
data=c.fetchone()
|
|
||||||
if data is None:
|
|
||||||
sql = """ INSERT INTO visitors (garden_name,visitor_name,weekly_visits) VALUES('{}', '{}',1)""".format(self.owner, name)
|
|
||||||
c.execute(sql)
|
|
||||||
else:
|
|
||||||
sql = """ UPDATE visitors SET weekly_visits = weekly_visits + 1 WHERE garden_name = '{}' AND visitor_name = '{}'""".format(self.owner, name)
|
|
||||||
c.execute(sql)
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
def guest_check(self):
|
|
||||||
user_dir = os.path.expanduser("~")
|
|
||||||
botany_dir = os.path.join(user_dir,'.botany')
|
|
||||||
visitor_filepath = os.path.join(botany_dir,'visitors.json')
|
|
||||||
guest_timestamps = []
|
|
||||||
visitors_this_check = []
|
|
||||||
if os.path.isfile(visitor_filepath):
|
|
||||||
with open(visitor_filepath, 'r') as visitor_file:
|
|
||||||
data = json.load(visitor_file)
|
|
||||||
if data:
|
|
||||||
for element in data:
|
|
||||||
if element['user'] not in self.visitors:
|
|
||||||
self.visitors.append(element['user'])
|
|
||||||
if element['user'] not in visitors_this_check:
|
|
||||||
visitors_this_check.append(element['user'])
|
|
||||||
# prevent users from manually setting watered_time in the future
|
|
||||||
if element['timestamp'] <= int(time.time()) and element['timestamp'] >= self.watered_timestamp:
|
|
||||||
guest_timestamps.append(element['timestamp'])
|
|
||||||
try:
|
|
||||||
self.update_visitor_db(visitors_this_check)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
with open(visitor_filepath, 'w') as visitor_file:
|
|
||||||
visitor_file.write('[]')
|
|
||||||
else:
|
|
||||||
with open(visitor_filepath, mode='w') as f:
|
|
||||||
json.dump([], f)
|
|
||||||
os.chmod(visitor_filepath, 0o666)
|
|
||||||
if not guest_timestamps:
|
|
||||||
return self.watered_timestamp
|
|
||||||
all_timestamps = [self.watered_timestamp] + guest_timestamps
|
|
||||||
all_timestamps.sort()
|
|
||||||
# calculate # of days between each guest watering
|
|
||||||
timestamp_diffs = [(j-i)/86400.0 for i, j in zip(all_timestamps[:-1], all_timestamps[1:])]
|
|
||||||
# plant's latest timestamp should be set to last timestamp before a
|
|
||||||
# gap of 5 days
|
|
||||||
# TODO: this considers a plant watered only on day 1 and day 4 to be
|
|
||||||
# watered for all 4 days - need to figure out how to only add score
|
|
||||||
# from 24h after each watered timestamp
|
|
||||||
last_valid_element = next((x for x in timestamp_diffs if x > 5), None)
|
|
||||||
if not last_valid_element:
|
|
||||||
# all timestamps are within a 5 day range, can just use latest one
|
|
||||||
return all_timestamps[-1]
|
|
||||||
last_valid_index = timestamp_diffs.index(last_valid_element)
|
|
||||||
# slice list to only include up until a >5 day gap
|
|
||||||
valid_timestamps = all_timestamps[:last_valid_index + 1]
|
|
||||||
return valid_timestamps[-1]
|
|
||||||
|
|
||||||
def water_check(self):
|
|
||||||
self.watered_timestamp = self.guest_check()
|
|
||||||
self.time_delta_watered = int(time.time()) - self.watered_timestamp
|
|
||||||
if self.time_delta_watered <= (24 * 3600):
|
|
||||||
if not self.watered_24h:
|
|
||||||
self.watered_24h = True
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
self.watered_24h = False
|
|
||||||
return False
|
|
||||||
|
|
||||||
def mutate_check(self):
|
|
||||||
# Create plant mutation
|
|
||||||
# Increase this # to make mutation rarer (chance 1 out of x each second)
|
|
||||||
CONST_MUTATION_RARITY = 20000
|
|
||||||
mutation_seed = random.randint(1,CONST_MUTATION_RARITY)
|
|
||||||
if mutation_seed == CONST_MUTATION_RARITY:
|
|
||||||
# mutation gained!
|
|
||||||
mutation = random.randint(0,len(self.mutation_list)-1)
|
|
||||||
if self.mutation == 0:
|
|
||||||
self.mutation = mutation
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def growth(self):
|
|
||||||
# Increase plant growth stage
|
|
||||||
if self.stage < (len(self.stage_list)-1):
|
|
||||||
self.stage += 1
|
|
||||||
|
|
||||||
def water(self):
|
|
||||||
# Increase plant growth stage
|
|
||||||
if not self.dead:
|
|
||||||
self.watered_timestamp = int(time.time())
|
|
||||||
self.watered_24h = True
|
|
||||||
|
|
||||||
def start_over(self):
|
|
||||||
# After plant reaches final stage, given option to restart
|
|
||||||
# increment generation only if previous stage is final stage and plant
|
|
||||||
# is alive
|
|
||||||
if not self.dead:
|
|
||||||
next_generation = self.generation + 1
|
|
||||||
else:
|
|
||||||
# Should this reset to 1? Seems unfair.. for now generations will
|
|
||||||
# persist through death.
|
|
||||||
next_generation = self.generation
|
|
||||||
self.write_lock = True
|
|
||||||
self.kill_plant()
|
|
||||||
while self.write_lock:
|
|
||||||
# Wait for garden writer to unlock
|
|
||||||
# garden db needs to update before allowing the user to reset
|
|
||||||
pass
|
|
||||||
if not self.write_lock:
|
|
||||||
self.__init__(self.file_name, next_generation)
|
|
||||||
|
|
||||||
def kill_plant(self):
|
|
||||||
self.dead = True
|
|
||||||
|
|
||||||
def unlock_new_creation(self):
|
|
||||||
self.write_lock = False
|
|
||||||
|
|
||||||
def start_life(self):
|
|
||||||
# runs life on a thread
|
|
||||||
thread = threading.Thread(target=self.life, args=())
|
|
||||||
thread.daemon = True
|
|
||||||
thread.start()
|
|
||||||
|
|
||||||
def life(self):
|
|
||||||
# I've created life :)
|
|
||||||
while True:
|
|
||||||
if not self.dead:
|
|
||||||
if self.watered_24h:
|
|
||||||
self.ticks += 1
|
|
||||||
if self.stage < len(self.stage_list)-1:
|
|
||||||
if self.ticks >= self.life_stages[self.stage]:
|
|
||||||
self.growth()
|
|
||||||
if self.mutate_check():
|
|
||||||
pass
|
|
||||||
if self.water_check():
|
|
||||||
# Do something
|
|
||||||
pass
|
|
||||||
if self.dead_check():
|
|
||||||
# Do something else
|
|
||||||
pass
|
|
||||||
# TODO: event check
|
|
||||||
generation_bonus = round(0.2 * (self.generation - 1), 1)
|
|
||||||
adjusted_sleep_time = 1 / (1 + generation_bonus)
|
|
||||||
time.sleep(adjusted_sleep_time)
|
|
||||||
|
|
||||||
class DataManager(object):
|
class DataManager(object):
|
||||||
# handles user data, puts a .botany dir in user's home dir (OSX/Linux)
|
# handles user data, puts a .botany dir in user's home dir (OSX/Linux)
|
||||||
|
@ -645,6 +336,7 @@ if __name__ == '__main__':
|
||||||
# my_plant is either a fresh plant or an existing plant at this point
|
# my_plant is either a fresh plant or an existing plant at this point
|
||||||
my_plant.start_life()
|
my_plant.start_life()
|
||||||
my_data.start_threads(my_plant)
|
my_data.start_threads(my_plant)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
botany_menu = ms.CursedMenu(my_plant,my_data)
|
botany_menu = ms.CursedMenu(my_plant,my_data)
|
||||||
my_data.save_plant(my_plant)
|
my_data.save_plant(my_plant)
|
||||||
|
|
|
@ -0,0 +1,349 @@
|
||||||
|
import random
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
|
class Plant:
|
||||||
|
# This is your plant!
|
||||||
|
stage_list = [
|
||||||
|
'seed',
|
||||||
|
'seedling',
|
||||||
|
'young',
|
||||||
|
'mature',
|
||||||
|
'flowering',
|
||||||
|
'seed-bearing',
|
||||||
|
]
|
||||||
|
|
||||||
|
color_list = [
|
||||||
|
'red',
|
||||||
|
'orange',
|
||||||
|
'yellow',
|
||||||
|
'green',
|
||||||
|
'blue',
|
||||||
|
'indigo',
|
||||||
|
'violet',
|
||||||
|
'white',
|
||||||
|
'black',
|
||||||
|
'gold',
|
||||||
|
'rainbow',
|
||||||
|
]
|
||||||
|
|
||||||
|
rarity_list = [
|
||||||
|
'common',
|
||||||
|
'uncommon',
|
||||||
|
'rare',
|
||||||
|
'legendary',
|
||||||
|
'godly',
|
||||||
|
]
|
||||||
|
|
||||||
|
species_list = [
|
||||||
|
'poppy',
|
||||||
|
'cactus',
|
||||||
|
'aloe',
|
||||||
|
'venus flytrap',
|
||||||
|
'jade plant',
|
||||||
|
'fern',
|
||||||
|
'daffodil',
|
||||||
|
'sunflower',
|
||||||
|
'baobab',
|
||||||
|
'lithops',
|
||||||
|
'hemp',
|
||||||
|
'pansy',
|
||||||
|
'iris',
|
||||||
|
'agave',
|
||||||
|
'ficus',
|
||||||
|
'moss',
|
||||||
|
'sage',
|
||||||
|
'snapdragon',
|
||||||
|
'columbine',
|
||||||
|
'brugmansia',
|
||||||
|
'palm',
|
||||||
|
'pachypodium',
|
||||||
|
]
|
||||||
|
|
||||||
|
mutation_list = [
|
||||||
|
'',
|
||||||
|
'humming',
|
||||||
|
'noxious',
|
||||||
|
'vorpal',
|
||||||
|
'glowing',
|
||||||
|
'electric',
|
||||||
|
'icy',
|
||||||
|
'flaming',
|
||||||
|
'psychic',
|
||||||
|
'screaming',
|
||||||
|
'chaotic',
|
||||||
|
'hissing',
|
||||||
|
'gelatinous',
|
||||||
|
'deformed',
|
||||||
|
'shaggy',
|
||||||
|
'scaly',
|
||||||
|
'depressed',
|
||||||
|
'anxious',
|
||||||
|
'metallic',
|
||||||
|
'glossy',
|
||||||
|
'psychedelic',
|
||||||
|
'bonsai',
|
||||||
|
'foamy',
|
||||||
|
'singing',
|
||||||
|
'fractal',
|
||||||
|
'crunchy',
|
||||||
|
'goth',
|
||||||
|
'oozing',
|
||||||
|
'stinky',
|
||||||
|
'aromatic',
|
||||||
|
'juicy',
|
||||||
|
'smug',
|
||||||
|
'vibrating',
|
||||||
|
'lithe',
|
||||||
|
'chalky',
|
||||||
|
'naive',
|
||||||
|
'ersatz',
|
||||||
|
'disco',
|
||||||
|
'levitating',
|
||||||
|
'colossal',
|
||||||
|
'luminous',
|
||||||
|
'cosmic',
|
||||||
|
'ethereal',
|
||||||
|
'cursed',
|
||||||
|
'buff',
|
||||||
|
'narcotic',
|
||||||
|
'gnu/linux',
|
||||||
|
'abraxan', # rip dear friend
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self, this_filename, generation=1):
|
||||||
|
# Constructor
|
||||||
|
self.plant_id = str(uuid.uuid4())
|
||||||
|
self.life_stages = (3600*24, (3600*24)*3, (3600*24)*10, (3600*24)*20, (3600*24)*30)
|
||||||
|
# self.life_stages = (2, 4, 6, 8, 10) # debug mode
|
||||||
|
self.stage = 0
|
||||||
|
self.mutation = 0
|
||||||
|
self.species = random.randint(0,len(self.species_list)-1)
|
||||||
|
self.color = random.randint(0,len(self.color_list)-1)
|
||||||
|
self.rarity = self.rarity_check()
|
||||||
|
self.ticks = 0
|
||||||
|
self.age_formatted = "0"
|
||||||
|
self.generation = generation
|
||||||
|
self.dead = False
|
||||||
|
self.write_lock = False
|
||||||
|
self.owner = getpass.getuser()
|
||||||
|
self.file_name = this_filename
|
||||||
|
self.start_time = int(time.time())
|
||||||
|
self.last_time = int(time.time())
|
||||||
|
# must water plant first day
|
||||||
|
self.watered_timestamp = int(time.time())-(24*3600)-1
|
||||||
|
self.watered_24h = False
|
||||||
|
self.visitors = []
|
||||||
|
|
||||||
|
def migrate_properties(self):
|
||||||
|
# Migrates old data files to new
|
||||||
|
if not hasattr(self, 'generation'):
|
||||||
|
self.generation = 1
|
||||||
|
if not hasattr(self, 'visitors'):
|
||||||
|
self.visitors = []
|
||||||
|
|
||||||
|
def parse_plant(self):
|
||||||
|
# Converts plant data to human-readable format
|
||||||
|
output = ""
|
||||||
|
if self.stage >= 3:
|
||||||
|
output += self.rarity_list[self.rarity] + " "
|
||||||
|
if self.mutation != 0:
|
||||||
|
output += self.mutation_list[self.mutation] + " "
|
||||||
|
if self.stage >= 4:
|
||||||
|
output += self.color_list[self.color] + " "
|
||||||
|
output += self.stage_list[self.stage] + " "
|
||||||
|
if self.stage >= 2:
|
||||||
|
output += self.species_list[self.species] + " "
|
||||||
|
return output.strip()
|
||||||
|
|
||||||
|
def rarity_check(self):
|
||||||
|
# Generate plant rarity
|
||||||
|
CONST_RARITY_MAX = 256.0
|
||||||
|
rare_seed = random.randint(1,CONST_RARITY_MAX)
|
||||||
|
common_range = round((2.0/3)*CONST_RARITY_MAX)
|
||||||
|
uncommon_range = round((2.0/3)*(CONST_RARITY_MAX-common_range))
|
||||||
|
rare_range = round((2.0/3)*(CONST_RARITY_MAX-common_range-uncommon_range))
|
||||||
|
legendary_range = round((2.0/3)*(CONST_RARITY_MAX-common_range-uncommon_range-rare_range))
|
||||||
|
|
||||||
|
common_max = common_range
|
||||||
|
uncommon_max = common_max + uncommon_range
|
||||||
|
rare_max = uncommon_max + rare_range
|
||||||
|
legendary_max = rare_max + legendary_range
|
||||||
|
godly_max = CONST_RARITY_MAX
|
||||||
|
|
||||||
|
if 0 <= rare_seed <= common_max:
|
||||||
|
rarity = 0
|
||||||
|
elif common_max < rare_seed <= uncommon_max:
|
||||||
|
rarity = 1
|
||||||
|
elif uncommon_max < rare_seed <= rare_max:
|
||||||
|
rarity = 2
|
||||||
|
elif rare_max < rare_seed <= legendary_max:
|
||||||
|
rarity = 3
|
||||||
|
elif legendary_max < rare_seed <= godly_max:
|
||||||
|
rarity = 4
|
||||||
|
return rarity
|
||||||
|
|
||||||
|
def dead_check(self):
|
||||||
|
# if it has been >5 days since watering, sorry plant is dead :(
|
||||||
|
time_delta_watered = int(time.time()) - self.watered_timestamp
|
||||||
|
if time_delta_watered > (5 * (24 * 3600)):
|
||||||
|
self.dead = True
|
||||||
|
return self.dead
|
||||||
|
|
||||||
|
def update_visitor_db(self, visitor_names):
|
||||||
|
game_dir = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
garden_db_path = os.path.join(game_dir, 'sqlite/garden_db.sqlite')
|
||||||
|
conn = sqlite3.connect(garden_db_path)
|
||||||
|
for name in (visitor_names):
|
||||||
|
c = conn.cursor()
|
||||||
|
c.execute("SELECT * FROM visitors WHERE garden_name = '{}' AND visitor_name = '{}' ".format(self.owner, name))
|
||||||
|
data=c.fetchone()
|
||||||
|
if data is None:
|
||||||
|
sql = """ INSERT INTO visitors (garden_name,visitor_name,weekly_visits) VALUES('{}', '{}',1)""".format(self.owner, name)
|
||||||
|
c.execute(sql)
|
||||||
|
else:
|
||||||
|
sql = """ UPDATE visitors SET weekly_visits = weekly_visits + 1 WHERE garden_name = '{}' AND visitor_name = '{}'""".format(self.owner, name)
|
||||||
|
c.execute(sql)
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
def guest_check(self):
|
||||||
|
user_dir = os.path.expanduser("~")
|
||||||
|
botany_dir = os.path.join(user_dir,'.botany')
|
||||||
|
visitor_filepath = os.path.join(botany_dir,'visitors.json')
|
||||||
|
guest_timestamps = []
|
||||||
|
visitors_this_check = []
|
||||||
|
if os.path.isfile(visitor_filepath):
|
||||||
|
with open(visitor_filepath, 'r') as visitor_file:
|
||||||
|
data = json.load(visitor_file)
|
||||||
|
if data:
|
||||||
|
for element in data:
|
||||||
|
if element['user'] not in self.visitors:
|
||||||
|
self.visitors.append(element['user'])
|
||||||
|
if element['user'] not in visitors_this_check:
|
||||||
|
visitors_this_check.append(element['user'])
|
||||||
|
# prevent users from manually setting watered_time in the future
|
||||||
|
if element['timestamp'] <= int(time.time()) and element['timestamp'] >= self.watered_timestamp:
|
||||||
|
guest_timestamps.append(element['timestamp'])
|
||||||
|
try:
|
||||||
|
self.update_visitor_db(visitors_this_check)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
with open(visitor_filepath, 'w') as visitor_file:
|
||||||
|
visitor_file.write('[]')
|
||||||
|
else:
|
||||||
|
with open(visitor_filepath, mode='w') as f:
|
||||||
|
json.dump([], f)
|
||||||
|
os.chmod(visitor_filepath, 0o666)
|
||||||
|
if not guest_timestamps:
|
||||||
|
return self.watered_timestamp
|
||||||
|
all_timestamps = [self.watered_timestamp] + guest_timestamps
|
||||||
|
all_timestamps.sort()
|
||||||
|
# calculate # of days between each guest watering
|
||||||
|
timestamp_diffs = [(j-i)/86400.0 for i, j in zip(all_timestamps[:-1], all_timestamps[1:])]
|
||||||
|
# plant's latest timestamp should be set to last timestamp before a
|
||||||
|
# gap of 5 days
|
||||||
|
# TODO: this considers a plant watered only on day 1 and day 4 to be
|
||||||
|
# watered for all 4 days - need to figure out how to only add score
|
||||||
|
# from 24h after each watered timestamp
|
||||||
|
last_valid_element = next((x for x in timestamp_diffs if x > 5), None)
|
||||||
|
if not last_valid_element:
|
||||||
|
# all timestamps are within a 5 day range, can just use latest one
|
||||||
|
return all_timestamps[-1]
|
||||||
|
last_valid_index = timestamp_diffs.index(last_valid_element)
|
||||||
|
# slice list to only include up until a >5 day gap
|
||||||
|
valid_timestamps = all_timestamps[:last_valid_index + 1]
|
||||||
|
return valid_timestamps[-1]
|
||||||
|
|
||||||
|
def water_check(self):
|
||||||
|
self.watered_timestamp = self.guest_check()
|
||||||
|
self.time_delta_watered = int(time.time()) - self.watered_timestamp
|
||||||
|
if self.time_delta_watered <= (24 * 3600):
|
||||||
|
if not self.watered_24h:
|
||||||
|
self.watered_24h = True
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
self.watered_24h = False
|
||||||
|
return False
|
||||||
|
|
||||||
|
def mutate_check(self):
|
||||||
|
# Create plant mutation
|
||||||
|
# Increase this # to make mutation rarer (chance 1 out of x each second)
|
||||||
|
CONST_MUTATION_RARITY = 20000
|
||||||
|
mutation_seed = random.randint(1,CONST_MUTATION_RARITY)
|
||||||
|
if mutation_seed == CONST_MUTATION_RARITY:
|
||||||
|
# mutation gained!
|
||||||
|
mutation = random.randint(0,len(self.mutation_list)-1)
|
||||||
|
if self.mutation == 0:
|
||||||
|
self.mutation = mutation
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def growth(self):
|
||||||
|
# Increase plant growth stage
|
||||||
|
if self.stage < (len(self.stage_list)-1):
|
||||||
|
self.stage += 1
|
||||||
|
|
||||||
|
def water(self):
|
||||||
|
# Increase plant growth stage
|
||||||
|
if not self.dead:
|
||||||
|
self.watered_timestamp = int(time.time())
|
||||||
|
self.watered_24h = True
|
||||||
|
|
||||||
|
def start_over(self):
|
||||||
|
# After plant reaches final stage, given option to restart
|
||||||
|
# increment generation only if previous stage is final stage and plant
|
||||||
|
# is alive
|
||||||
|
if not self.dead:
|
||||||
|
next_generation = self.generation + 1
|
||||||
|
else:
|
||||||
|
# Should this reset to 1? Seems unfair.. for now generations will
|
||||||
|
# persist through death.
|
||||||
|
next_generation = self.generation
|
||||||
|
self.write_lock = True
|
||||||
|
self.kill_plant()
|
||||||
|
while self.write_lock:
|
||||||
|
# Wait for garden writer to unlock
|
||||||
|
# garden db needs to update before allowing the user to reset
|
||||||
|
pass
|
||||||
|
if not self.write_lock:
|
||||||
|
self.__init__(self.file_name, next_generation)
|
||||||
|
|
||||||
|
def kill_plant(self):
|
||||||
|
self.dead = True
|
||||||
|
|
||||||
|
def unlock_new_creation(self):
|
||||||
|
self.write_lock = False
|
||||||
|
|
||||||
|
def start_life(self):
|
||||||
|
# runs life on a thread
|
||||||
|
thread = threading.Thread(target=self.life, args=())
|
||||||
|
thread.daemon = True
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
def life(self):
|
||||||
|
# I've created life :)
|
||||||
|
while True:
|
||||||
|
if not self.dead:
|
||||||
|
if self.watered_24h:
|
||||||
|
self.ticks += 1
|
||||||
|
if self.stage < len(self.stage_list)-1:
|
||||||
|
if self.ticks >= self.life_stages[self.stage]:
|
||||||
|
self.growth()
|
||||||
|
if self.mutate_check():
|
||||||
|
pass
|
||||||
|
if self.water_check():
|
||||||
|
# Do something
|
||||||
|
pass
|
||||||
|
if self.dead_check():
|
||||||
|
# Do something else
|
||||||
|
pass
|
||||||
|
# TODO: event check
|
||||||
|
generation_bonus = round(0.2 * (self.generation - 1), 1)
|
||||||
|
adjusted_sleep_time = 1 / (1 + generation_bonus)
|
||||||
|
time.sleep(adjusted_sleep_time)
|
||||||
|
|
Loading…
Reference in New Issue