diff --git a/botany.py b/botany.py index 12dfc3a..ae7ae41 100644 --- a/botany.py +++ b/botany.py @@ -11,20 +11,17 @@ import errno import uuid from operator import itemgetter from menu_screen import * -# ideas go here -# lifecycle of a plant -# seed -> seedling -> sprout -> young plant -> mature plant -> flower -> -# pollination -> fruit -> seeds - -# neighboring plants can cross pollinate for different plants -# health based on checkups and watering # development plan + # build plant lifecycle just stepping through # - What else should it do during life? growth alone is not all that # interesting. # - how long should each stage last ? thinking realistic lmao - +# seed -> seedling -> sprout -> young plant -> mature plant -> flower -> +# pollination -> fruit -> seeds +# - TODO: pollination and end of life +# # interaction # - look at plant, how do you feel? (also gets rid of pests) # @@ -48,23 +45,6 @@ from menu_screen import * # build ascii trees -# def display_update: -# myscreen = curses.initscr() -# -# myscreen.border(0) -# myscreen.addstr(1, 2, "you've planted a seed") -# myscreen.refresh() -# -# for i in range(1,20): -# myscreen.addstr(i, 2, str(i)) -# time.sleep(1) -# myscreen.refresh() -# -# myscreen.getch() -# -# curses.endwin() -# TODO: garden file json should be prettier. - class Plant(object): # This is your plant! stage_dict = { @@ -178,7 +158,7 @@ class Plant(object): self.watered_24h = False def new_seed(self,this_filename): - os.remove(this_filename) + # Creates life after death self.__init__(this_filename) def rarity_check(self): @@ -340,11 +320,26 @@ class DataManager(object): else: return False - def enable_autosave(self,this_plant): - # creates thread to save files every minute - thread = threading.Thread(target=self.autosave, args=(this_plant,)) - thread.daemon = True - thread.start() + def start_threads(self,this_plant): + # creates threads to save files every minute + death_check_thread = threading.Thread(target=self.death_check_update, args=(this_plant,)) + death_check_thread.daemon = True + death_check_thread.start() + + autosave_thread = threading.Thread(target=self.autosave, args=(this_plant,)) + autosave_thread.daemon = True + autosave_thread.start() + + def death_check_update(self,this_plant): + # .1 second updates to minimize race condition + # TODO: improve how this is handled to eliminate race condition + while True: + is_dead = this_plant.dead_check() + if is_dead: + self.save_plant(this_plant) + self.data_write_json(this_plant) + self.garden_update(this_plant) + time.sleep(.1) def autosave(self, this_plant): # running on thread @@ -354,6 +349,7 @@ class DataManager(object): self.garden_update(this_plant) # TODO: change after debug #time.sleep(60) + # TODO: if plant dies it should force save. time.sleep(5) def load_plant(self): @@ -387,9 +383,8 @@ class DataManager(object): return age_formatted def garden_update(self, this_plant): - # garden is a list of 10 tuples sorted by plant score + # garden is a dict of dicts # garden contains one entry for each plant id - # garden should be a dict of tuples not a list age_formatted = self.plant_age_convert(this_plant) this_plant_id = this_plant.plant_id @@ -469,13 +464,9 @@ if __name__ == '__main__': my_plant = Plant(my_data.savefile_path) my_data.data_write_json(my_plant) my_plant.start_life() - my_data.enable_autosave(my_plant) - botany_menu = CursedMenu(my_plant) + my_data.start_threads(my_plant) + botany_menu = CursedMenu(my_plant,my_data.garden_file_path) botany_menu.show(["water","look","garden","instructions"], title=' botany ', subtitle='options') - # if not my_plant.dead: - # botany_menu.show(["water","look","garden","instructions"], title=' botany ', subtitle='options') - # else: - # botany_menu.show(["water","look","garden","instructions"], title=' botany ', subtitle='options') my_data.save_plant(my_plant) my_data.data_write_json(my_plant) my_data.garden_update(my_plant) diff --git a/menu_screen.py b/menu_screen.py index a7601fd..fe69058 100644 --- a/menu_screen.py +++ b/menu_screen.py @@ -1,9 +1,9 @@ -import curses, os, traceback, threading, time, datetime +import curses, os, traceback, threading, time, datetime, pickle class CursedMenu(object): #TODO: create a side panel with log of events..? '''A class which abstracts the horrors of building a curses-based menu system''' - def __init__(self, this_plant): + def __init__(self, this_plant, this_garden_file_path): '''Initialization''' self.initialized = False self.screen = curses.initscr() @@ -13,10 +13,14 @@ class CursedMenu(object): curses.curs_set(0) self.screen.keypad(1) self.plant = this_plant + self.garden_file_path = this_garden_file_path self.plant_string = self.plant.parse_plant() self.plant_ticks = str(self.plant.ticks) self.exit = False self.instructiontoggle = False + #TODO: debugging + # self.gardenmenutoggle = True + self.gardenmenutoggle = False self.maxy, self.maxx = self.screen.getmaxyx() # Highlighted and Normal line definitions curses.init_pair(1, curses.COLOR_BLACK, curses.COLOR_WHITE) @@ -28,8 +32,8 @@ class CursedMenu(object): # TODO: tweaking this to try to get rid of garble bug self.screen.clear() - def show(self, options, title="Title", subtitle="Subtitle"): - '''Draws a menu with the given parameters''' + def show(self, options, title, subtitle): + # Draws a menu with parameters self.set_options(options) self.update_options() self.title = title @@ -39,27 +43,27 @@ class CursedMenu(object): self.draw_menu() def update_options(self): + # Makes sure you can get a new plant if it dies if self.plant.dead: if "kill" in self.options: self.options.remove("kill") if "new" not in self.options: self.options.insert(-1,"new") else: + # TODO: remove after debug or bury in settings if "new" in self.options: self.options.remove("new") if "kill" not in self.options: self.options.insert(-1,"kill") - #self.draw_menu() - #self.screen.clear() def set_options(self, options): - '''Validates that the last option is "exit"''' + # Validates that the last option is "exit" if options[-1] is not 'exit': options.append('exit') self.options = options def draw_menu(self): - '''Actually draws the menu and handles branching''' + # Actually draws the menu and handles branching request = "" try: while request is not "exit": @@ -73,12 +77,9 @@ class CursedMenu(object): self.__exit__() traceback.print_exc() - def draw(self): - '''Draw the menu and lines''' - # TODO: display refresh is hacky. Could be more precise + def draw_default(self): + # Draws default menu clear_bar = " " * (int(self.maxx*2/3)) - self.screen.refresh() - self.screen.border(0) self.screen.addstr(2,2, self.title, curses.A_STANDOUT) # Title for this menu self.screen.addstr(4,2, self.subtitle, curses.A_BOLD) #Subtitle for this menu # Display all the menu items, showing the 'pos' item highlighted @@ -103,6 +104,55 @@ class CursedMenu(object): else: self.screen.addstr(5,13, clear_bar, curses.A_NORMAL) self.screen.addstr(5,13, " - you can't water a dead plant :(", curses.A_NORMAL) + + def format_garden_data(self,this_garden): + plant_table = "" + # TODO: include only live plants maybe + for plant_id in this_garden: + if this_garden[plant_id]: + if not this_garden[plant_id]["dead"]: + this_plant = this_garden[plant_id] + plant_table += this_plant["owner"] + " - " + plant_table += this_plant["age"] + " - " + plant_table += this_plant["description"] + " - " + plant_table += str(this_plant["score"]) + "\n" + return plant_table + + def draw_garden(self): + # Draws neighborhood + clear_bar = " " * (self.maxx-2) + "\n" + control_keys = [curses.KEY_UP, curses.KEY_DOWN, curses.KEY_LEFT, curses.KEY_RIGHT] + # load data + with open(self.garden_file_path, 'rb') as f: + this_garden = pickle.load(f) + # format data + if not self.gardenmenutoggle: + plant_table_formatted = self.format_garden_data(this_garden) + self.gardenmenutoggle = not self.gardenmenutoggle + else: + plant_table_formatted = "" + for line in this_garden: + plant_table_formatted += clear_bar + self.gardenmenutoggle = not self.gardenmenutoggle + + for y, line in enumerate(plant_table_formatted.splitlines(), 2): + self.screen.addstr(y+12, 2, line) + # TODO: this needs to be updated so that it only draws if the window + # is big enough.. or try catch it + self.screen.refresh() + + def draw(self): + # Draw the menu and lines + # TODO: this needs to either display the default menu screen or the + # garden/leaderboard thing based on self.gardenmenutoggle + # TODO: display refresh is hacky. Could be more precise + self.screen.refresh() + self.screen.border(0) + # if self.gardenmenutoggle: + # self.draw_garden() + # else: + # self.draw_default() + self.draw_default() try: self.screen.refresh() except Exception as exception: @@ -123,10 +173,10 @@ class CursedMenu(object): time.sleep(1) def get_user_input(self): - '''Gets the user's input and acts appropriately''' + # Gets the user's input and acts appropriately user_in = self.screen.getch() # Gets user input - '''Enter and exit Keys are special cases''' + # Enter and exit Keys are special cases if user_in == 10: return self.options[self.selected] if user_in == 27: @@ -145,49 +195,46 @@ class CursedMenu(object): self.selected = self.selected % len(self.options) return - - def handle_request(self, request): - '''This is where you do things with the request''' - if request is None: return - if request is "kill": - self.plant.kill_plant() - if request is "new": - self.plant.new_seed(self.plant.file_name) - if request == "water": - self.plant.water() - if request == "instructions": - if not self.instructiontoggle: - instructions_txt = """welcome to botany. you've been given a seed + def draw_instructions(self): + if not self.instructiontoggle: + instructions_txt = """welcome to botany. you've been given a seed that will grow into a beautiful plant. check in and water your plant every 24h to keep it growing. 5 days without water = death. your plant depends on you to live! more info is available in the readme :) - cheers, - curio""" - self.instructiontoggle = not self.instructiontoggle - else: - instructions_txt = """ + cheers, + curio""" + self.instructiontoggle = not self.instructiontoggle + else: + instructions_txt = """ - """ - self.instructiontoggle = not self.instructiontoggle - for y, line in enumerate(instructions_txt.splitlines(), 2): - self.screen.addstr(self.maxy-12+y,self.maxx-47, line) - self.screen.refresh() - + """ + self.instructiontoggle = not self.instructiontoggle + for y, line in enumerate(instructions_txt.splitlines(), 2): + self.screen.addstr(self.maxy-12+y,self.maxx-47, line) + self.screen.refresh() + def handle_request(self, request): + '''This is where you do things with the request''' + if request == None: return + if request == "kill": + self.plant.kill_plant() + if request == "new": + self.plant.new_seed(self.plant.file_name) + if request == "water": + self.plant.water() + if request == "instructions": + self.draw_instructions() + if request == "garden": + self.draw_garden() def __exit__(self): self.exit = True curses.curs_set(2) curses.endwin() os.system('clear') - -'''demo''' -# cm = CursedMenu() -# cm.show([1,"water",3], title=' botany ', subtitle='Options') -