mirror of https://github.com/tilde-team/botany
690 lines
27 KiB
Python
690 lines
27 KiB
Python
import curses
|
|
import datetime
|
|
import getpass
|
|
import json
|
|
import math
|
|
import os
|
|
import re
|
|
import string
|
|
import threading
|
|
import time
|
|
import traceback
|
|
|
|
import completer
|
|
import consts
|
|
from menu_screen import cleanup
|
|
|
|
|
|
class CursedMenu(object):
|
|
# TODO: name your plant
|
|
"""A class which abstracts the horrors of building a curses-based menu system"""
|
|
|
|
def __init__(self, this_plant, this_data):
|
|
"""Initialization"""
|
|
self.title = None
|
|
self.selected = None
|
|
self.options = None
|
|
self.selected = None
|
|
self.subtitle = None
|
|
self.initialized = False
|
|
self.screen = curses.initscr()
|
|
curses.noecho()
|
|
curses.raw()
|
|
if curses.has_colors():
|
|
curses.start_color()
|
|
try:
|
|
curses.curs_set(0)
|
|
except curses.error:
|
|
# Not all terminals support this functionality.
|
|
# When the error is ignored the screen will look a little uglier, but that's not terrible
|
|
# So in order to keep botany as accessible as possible to everyone, it should be safe to ignore the error.
|
|
pass
|
|
self.screen.keypad(True)
|
|
self.plant = this_plant
|
|
self.visited_plant = None
|
|
self.user_data = this_data
|
|
self.plant_string = self.plant.parse_plant()
|
|
self.plant_ticks = str(int(self.plant.ticks))
|
|
self.exit = False
|
|
self.infotoggle = 0
|
|
self.maxy, self.maxx = self.screen.getmaxyx()
|
|
# Highlighted and Normal line definitions
|
|
if curses.has_colors():
|
|
self.define_colors()
|
|
self.highlighted = curses.color_pair(1)
|
|
else:
|
|
self.highlighted = curses.A_REVERSE
|
|
self.normal = curses.A_NORMAL
|
|
# Threaded screen update for live changes
|
|
screen_thread = threading.Thread(target=self.update_plant_live, args=())
|
|
screen_thread.daemon = True
|
|
screen_thread.start()
|
|
# Recursive lock to prevent both threads from drawing at the same time
|
|
self.screen_lock = threading.RLock()
|
|
self.screen.clear()
|
|
self.show(["water", "look", "garden", "visit", "instructions"], title=' botany ', subtitle='options')
|
|
|
|
@staticmethod
|
|
def define_colors():
|
|
# TODO: implement colors
|
|
# set curses color pairs manually
|
|
curses.init_pair(1, curses.COLOR_BLACK, curses.COLOR_WHITE)
|
|
curses.init_pair(2, curses.COLOR_WHITE, curses.COLOR_BLACK)
|
|
curses.init_pair(3, curses.COLOR_GREEN, curses.COLOR_BLACK)
|
|
curses.init_pair(4, curses.COLOR_BLUE, curses.COLOR_BLACK)
|
|
curses.init_pair(5, curses.COLOR_MAGENTA, curses.COLOR_BLACK)
|
|
curses.init_pair(6, curses.COLOR_YELLOW, curses.COLOR_BLACK)
|
|
curses.init_pair(7, curses.COLOR_RED, curses.COLOR_BLACK)
|
|
curses.init_pair(8, curses.COLOR_CYAN, curses.COLOR_BLACK)
|
|
|
|
def show(self, options, title, subtitle):
|
|
# Draws a menu with parameters
|
|
self.set_options(options)
|
|
self.update_options()
|
|
self.title = title
|
|
self.subtitle = subtitle
|
|
self.selected = 0
|
|
self.initialized = True
|
|
self.draw_menu()
|
|
|
|
def update_options(self):
|
|
# Makes sure you can get a new plant if it dies
|
|
if self.plant.dead or self.plant.stage == 5:
|
|
if "harvest" not in self.options:
|
|
self.options.insert(-1, "harvest")
|
|
else:
|
|
if "harvest" in self.options:
|
|
self.options.remove("harvest")
|
|
|
|
def set_options(self, options):
|
|
# Validates that the last option is "exit"
|
|
if options[-1] != 'exit':
|
|
options.append('exit')
|
|
self.options = options
|
|
|
|
def draw(self):
|
|
# Draw the menu and lines
|
|
self.maxy, self.maxx = self.screen.getmaxyx()
|
|
self.screen_lock.acquire()
|
|
self.screen.refresh()
|
|
try:
|
|
self.draw_default()
|
|
self.screen.refresh()
|
|
except Exception:
|
|
# Makes sure data is saved in event of a crash due to window resizing
|
|
self.screen.clear()
|
|
self.screen.addstr(0, 0, "Enlarge terminal!", curses.A_NORMAL)
|
|
self.screen.refresh()
|
|
self.__exit__()
|
|
traceback.print_exc()
|
|
self.screen_lock.release()
|
|
|
|
def draw_menu(self):
|
|
# Actually draws the menu and handles branching
|
|
request = ""
|
|
try:
|
|
while request != "exit":
|
|
self.draw()
|
|
request = self.get_user_input()
|
|
self.handle_request(request)
|
|
self.__exit__()
|
|
|
|
# Also calls __exit__, but adds traceback after
|
|
except IOError:
|
|
self.screen.clear()
|
|
self.screen.refresh()
|
|
self.__exit__()
|
|
except Exception:
|
|
self.screen.clear()
|
|
self.screen.addstr(0, 0, "Enlarge terminal!", curses.A_NORMAL)
|
|
self.screen.refresh()
|
|
self.__exit__()
|
|
# traceback.print_exc()
|
|
|
|
def ascii_render(self, filename, ypos, xpos):
|
|
# Prints ASCII art from file at given coordinates
|
|
this_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "art")
|
|
this_filename = os.path.join(this_dir, filename)
|
|
this_file = open(this_filename, "r")
|
|
this_string = this_file.readlines()
|
|
this_file.close()
|
|
self.screen_lock.acquire()
|
|
for y, line in enumerate(this_string, 2):
|
|
self.screen.addstr(ypos + y, xpos, line, curses.A_NORMAL)
|
|
# self.screen.refresh()
|
|
self.screen_lock.release()
|
|
|
|
def draw_plant_ascii(self, this_plant):
|
|
ypos = 0
|
|
xpos = int((self.maxx - 37) / 2 + 25)
|
|
|
|
if this_plant.dead:
|
|
self.ascii_render('rip.txt', ypos, xpos)
|
|
elif datetime.date.today().month == 10 and datetime.date.today().day == 31:
|
|
self.ascii_render('jackolantern.txt', ypos, xpos)
|
|
elif this_plant.stage == 0:
|
|
self.ascii_render('seed.txt', ypos, xpos)
|
|
elif this_plant.stage == 1:
|
|
self.ascii_render('seedling.txt', ypos, xpos)
|
|
elif this_plant.stage == 2:
|
|
this_filename = consts.species[this_plant.species] + '1.txt'
|
|
self.ascii_render(this_filename, ypos, xpos)
|
|
elif this_plant.stage == 3 or this_plant.stage == 5:
|
|
this_filename = consts.species[this_plant.species] + '2.txt'
|
|
self.ascii_render(this_filename, ypos, xpos)
|
|
elif this_plant.stage == 4:
|
|
this_filename = consts.species[this_plant.species] + '3.txt'
|
|
self.ascii_render(this_filename, ypos, xpos)
|
|
|
|
def draw_default(self):
|
|
# draws default menu
|
|
clear_bar = " " * (int(self.maxx * 2 / 3))
|
|
self.screen_lock.acquire()
|
|
self.screen.addstr(1, 2, self.title, curses.A_STANDOUT) # Title for this menu
|
|
self.screen.addstr(3, 2, self.subtitle, curses.A_BOLD) # Subtitle for this menu
|
|
# clear menu on screen
|
|
for index in range(len(self.options) + 1):
|
|
self.screen.addstr(4 + index, 4, clear_bar, curses.A_NORMAL)
|
|
# display all the menu items, showing the 'pos' item highlighted
|
|
for index in range(len(self.options)):
|
|
textstyle = self.normal
|
|
if index == self.selected:
|
|
textstyle = self.highlighted
|
|
self.screen.addstr(4 + index, 4, clear_bar, curses.A_NORMAL)
|
|
self.screen.addstr(4 + index, 4, "%d - %s" % (index + 1, self.options[index]), textstyle)
|
|
|
|
self.screen.addstr(12, 2, clear_bar, curses.A_NORMAL)
|
|
self.screen.addstr(13, 2, clear_bar, curses.A_NORMAL)
|
|
self.screen.addstr(12, 2, "plant: ", curses.A_DIM)
|
|
self.screen.addstr(12, 9, self.plant_string, curses.A_NORMAL)
|
|
self.screen.addstr(13, 2, "score: ", curses.A_DIM)
|
|
self.screen.addstr(13, 9, self.plant_ticks, curses.A_NORMAL)
|
|
|
|
# display fancy water gauge
|
|
if not self.plant.dead:
|
|
water_gauge_str = self.water_gauge()
|
|
self.screen.addstr(4, 14, water_gauge_str, curses.A_NORMAL)
|
|
else:
|
|
self.screen.addstr(4, 13, clear_bar, curses.A_NORMAL)
|
|
self.screen.addstr(4, 14, "( RIP )", curses.A_NORMAL)
|
|
|
|
# draw cute ascii from files
|
|
if self.visited_plant:
|
|
# Needed to prevent drawing over a visited plant
|
|
self.draw_plant_ascii(self.visited_plant)
|
|
else:
|
|
self.draw_plant_ascii(self.plant)
|
|
self.screen_lock.release()
|
|
|
|
def water_gauge(self):
|
|
# build nice looking water gauge
|
|
water_left_pct = 1 - ((time.time() - self.plant.watered_timestamp) / 86400)
|
|
# don't allow negative value
|
|
water_left_pct = max(0, water_left_pct)
|
|
water_left = int(math.ceil(water_left_pct * 10))
|
|
water_string = "(" + (")" * water_left) + ("." * (10 - water_left)) + ") " + str(
|
|
int(water_left_pct * 100)) + "% "
|
|
return water_string
|
|
|
|
def update_plant_live(self):
|
|
# updates plant data on menu screen, live!
|
|
while not self.exit:
|
|
self.plant_string = self.plant.parse_plant()
|
|
self.plant_ticks = str(int(self.plant.ticks))
|
|
if self.initialized:
|
|
self.update_options()
|
|
self.draw()
|
|
time.sleep(1)
|
|
|
|
def get_user_input(self):
|
|
# gets the user's input
|
|
user_in = 0
|
|
try:
|
|
user_in = self.screen.getch() # Gets user input
|
|
except Exception:
|
|
self.__exit__()
|
|
if user_in == -1: # Input comes from pipe/file and is closed
|
|
raise IOError
|
|
# DEBUG KEYS - enable these lines to see curses key codes
|
|
# self.screen.addstr(2, 2, str(user_in), curses.A_NORMAL)
|
|
# self.screen.refresh()
|
|
|
|
# Resize sends curses.KEY_RESIZE, update display
|
|
if user_in == curses.KEY_RESIZE:
|
|
self.maxy, self.maxx = self.screen.getmaxyx()
|
|
self.screen.clear()
|
|
self.screen.refresh()
|
|
|
|
# enter, exit, and Q Keys are special cases
|
|
if user_in == 13:
|
|
return self.options[self.selected]
|
|
if user_in == 27:
|
|
return self.options[-1]
|
|
if user_in == 113:
|
|
self.selected = len(self.options) - 1
|
|
return
|
|
|
|
# this is a number; check to see if we can set it
|
|
if ord('1') <= user_in <= ord(str(min(7, len(self.options)))):
|
|
self.selected = user_in - ord('0') - 1 # convert keypress back to a number, then subtract 1 to get index
|
|
return
|
|
|
|
# increment or Decrement
|
|
down_keys = [curses.KEY_DOWN, 14, ord('j')]
|
|
up_keys = [curses.KEY_UP, 16, ord('k')]
|
|
|
|
if user_in in down_keys: # down arrow
|
|
self.selected += 1
|
|
if user_in in up_keys: # up arrow
|
|
self.selected -= 1
|
|
|
|
# modulo to wrap menu cursor
|
|
self.selected = self.selected % len(self.options)
|
|
return
|
|
|
|
@staticmethod
|
|
def format_garden_data(this_garden):
|
|
# Returns list of lists (pages) of garden entries
|
|
plant_table = []
|
|
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.append((this_plant["owner"],
|
|
this_plant["age"],
|
|
int(this_plant["score"]),
|
|
this_plant["description"]))
|
|
return plant_table
|
|
|
|
@staticmethod
|
|
def format_garden_entry(entry):
|
|
return "{:14.14} - {:>16} - {:>8}p - {}".format(*entry)
|
|
|
|
@staticmethod
|
|
def sort_garden_table(table, column, ascending):
|
|
""" Sort table in place by a specified column """
|
|
|
|
def key(entry):
|
|
entry = entry[column]
|
|
# In when sorting ages, convert to seconds
|
|
if column == 1:
|
|
coeffs = [24 * 60 * 60, 60 * 60, 60, 1]
|
|
nums = [int(n[:-1]) for n in entry.split(":")]
|
|
if len(nums) == len(coeffs):
|
|
entry = sum(nums[i] * coeffs[i] for i in range(len(nums)))
|
|
return entry
|
|
|
|
return table.sort(key=key, reverse=not ascending)
|
|
|
|
def filter_garden_table(self, table, pattern):
|
|
""" Filter table using a pattern, and return the new table """
|
|
|
|
def filterfunc(entry):
|
|
if len(pattern) == 0:
|
|
return True
|
|
entry_txt = self.format_garden_entry(entry)
|
|
try:
|
|
result = bool(re.search(pattern, entry_txt))
|
|
except Exception:
|
|
# In case of invalid regex, don't match anything
|
|
result = False
|
|
return result
|
|
|
|
return list(filter(filterfunc, table))
|
|
|
|
def draw_garden(self):
|
|
# draws community garden
|
|
# load data from sqlite db
|
|
this_garden = self.user_data.retrieve_garden_from_db()
|
|
# format data
|
|
self.clear_info_pane()
|
|
|
|
if self.infotoggle == 2:
|
|
# the screen IS currently showing the garden (1 page), make the
|
|
# text a bunch of blanks to clear it out
|
|
self.infotoggle = 0
|
|
return
|
|
|
|
# if infotoggle isn't 2, the screen currently displays other stuff
|
|
plant_table_orig = self.format_garden_data(this_garden)
|
|
self.infotoggle = 2
|
|
|
|
# print garden information OR clear it
|
|
index = 0
|
|
sort_column, sort_ascending = 0, True
|
|
sort_keys = ["n", "a", "s", "d"] # Name, Age, Score, Description
|
|
plant_table = plant_table_orig
|
|
self.sort_garden_table(plant_table, sort_column, sort_ascending)
|
|
while True:
|
|
entries_per_page = self.maxy - 16
|
|
index_max = min(len(plant_table), index + entries_per_page)
|
|
plants = plant_table[index:index_max]
|
|
page = [self.format_garden_entry(entry) for entry in plants]
|
|
self.screen_lock.acquire()
|
|
self.draw_info_text(page)
|
|
# Multiple pages, paginate and require keypress
|
|
page_text = "(%d-%d/%d) | sp/next | bksp/prev | s <col #>/sort | f/filter | q/quit" % (
|
|
index, index_max, len(plant_table))
|
|
self.screen.addstr(self.maxy - 2, 2, page_text)
|
|
self.screen.refresh()
|
|
self.screen_lock.release()
|
|
c = self.screen.getch()
|
|
if c == -1: # Input comes from pipe/file and is closed
|
|
raise IOError
|
|
self.infotoggle = 0
|
|
|
|
# Quit
|
|
if c == ord("q") or c == ord("x") or c == 27:
|
|
break
|
|
# Next page
|
|
elif c in [curses.KEY_ENTER, curses.KEY_NPAGE, ord(" "), ord("\n")]:
|
|
index += entries_per_page
|
|
if index >= len(plant_table):
|
|
break
|
|
# Previous page
|
|
elif c == curses.KEY_BACKSPACE or c == curses.KEY_PPAGE:
|
|
index = max(index - entries_per_page, 0)
|
|
# Next line
|
|
elif c == ord("j") or c == curses.KEY_DOWN:
|
|
index = max(min(index + 1, len(plant_table) - 1), 0)
|
|
# Previous line
|
|
elif c == ord("k") or c == curses.KEY_UP:
|
|
index = max(index - 1, 0)
|
|
# Sort entries
|
|
elif c == ord("s"):
|
|
c = self.screen.getch()
|
|
if c == -1: # Input comes from pipe/file and is closed
|
|
raise IOError
|
|
column = -1
|
|
if c < 255 and chr(c) in sort_keys:
|
|
column = sort_keys.index(chr(c))
|
|
elif ord("1") <= c <= ord("4"):
|
|
column = c - ord("1")
|
|
if column != -1:
|
|
if sort_column == column:
|
|
sort_ascending = not sort_ascending
|
|
else:
|
|
sort_column = column
|
|
sort_ascending = True
|
|
self.sort_garden_table(plant_table, sort_column, sort_ascending)
|
|
# Filter entries
|
|
elif c == ord("/") or c == ord("f"):
|
|
self.screen.addstr(self.maxy - 2, 2, "Filter: " + " " * (len(page_text) - 8))
|
|
pattern = self.get_user_string(10, self.maxy - 2, lambda x: x in string.printable)
|
|
plant_table = self.filter_garden_table(plant_table_orig, pattern)
|
|
self.sort_garden_table(plant_table, sort_column, sort_ascending)
|
|
index = 0
|
|
|
|
# Clear page before drawing next
|
|
self.clear_info_pane()
|
|
self.clear_info_pane()
|
|
|
|
def draw_plant_description(self, this_plant):
|
|
# If menu is currently showing something other than the description
|
|
self.clear_info_pane()
|
|
if self.infotoggle != 1:
|
|
# get plant description before printing
|
|
output_string = this_plant.get_plant_description()
|
|
growth_multiplier = 1 + (0.2 * (this_plant.generation - 1))
|
|
output_string += "Generation: {}\nGrowth rate: {}x".format(self.plant.generation, growth_multiplier)
|
|
self.draw_info_text(output_string)
|
|
self.infotoggle = 1
|
|
else:
|
|
# otherwise just set toggle
|
|
self.infotoggle = 0
|
|
|
|
def draw_instructions(self):
|
|
# Draw instructions on screen
|
|
self.clear_info_pane()
|
|
if self.infotoggle != 4:
|
|
instructions_txt = ("welcome to botany. you've been given a seed\n"
|
|
"that will grow into a beautiful plant. check\n"
|
|
"in and water your plant every 24h to keep it\n"
|
|
"growing. 5 days without water = death. your\n"
|
|
"plant depends on you & your friends to live!\n"
|
|
"more info is available in the readme :)\n"
|
|
"https://github.com/jifunks/botany/blob/master/README.md\n"
|
|
" cheers,\n"
|
|
" curio\n"
|
|
)
|
|
self.draw_info_text(instructions_txt)
|
|
self.infotoggle = 4
|
|
else:
|
|
self.infotoggle = 0
|
|
|
|
def clear_info_pane(self):
|
|
# Clears bottom part of screen
|
|
self.screen_lock.acquire()
|
|
clear_bar = " " * (self.maxx - 3)
|
|
this_y = 14
|
|
while this_y < self.maxy:
|
|
self.screen.addstr(this_y, 2, clear_bar, curses.A_NORMAL)
|
|
this_y += 1
|
|
self.screen.refresh()
|
|
self.screen_lock.release()
|
|
|
|
def draw_info_text(self, info_text, y_offset=0):
|
|
# print lines of text to info pane at bottom of screen
|
|
self.screen_lock.acquire()
|
|
if type(info_text) is str:
|
|
info_text = info_text.splitlines()
|
|
for y, line in enumerate(info_text, 2):
|
|
this_y = y + 12 + y_offset
|
|
if len(line) > self.maxx - 3:
|
|
line = line[:self.maxx - 3]
|
|
if this_y < self.maxy:
|
|
self.screen.addstr(this_y, 2, line, curses.A_NORMAL)
|
|
self.screen.refresh()
|
|
self.screen_lock.release()
|
|
|
|
def harvest_confirmation(self):
|
|
self.clear_info_pane()
|
|
# get plant description before printing
|
|
max_stage = len(self.plant.stage_list) - 1
|
|
harvest_text = ""
|
|
if not self.plant.dead:
|
|
if self.plant.stage == max_stage:
|
|
harvest_text += "Congratulations! You raised your plant to its final stage of growth.\n"
|
|
harvest_text += "Your next plant will grow at a speed of: {}x\n".format(
|
|
1 + (0.2 * self.plant.generation))
|
|
harvest_text += "If you harvest your plant you'll start over from a seed.\nContinue? (Y/n)"
|
|
self.draw_info_text(harvest_text)
|
|
user_in = 0
|
|
try:
|
|
user_in = self.screen.getch() # Gets user input
|
|
except Exception:
|
|
self.__exit__()
|
|
if user_in == -1: # Input comes from pipe/file and is closed
|
|
raise IOError
|
|
|
|
if user_in in [ord('Y'), ord('y')]:
|
|
self.plant.start_over()
|
|
else:
|
|
pass
|
|
self.clear_info_pane()
|
|
|
|
def build_weekly_visitor_output(self, visitors):
|
|
visitor_block = ""
|
|
visitor_line = ""
|
|
for visitor in visitors:
|
|
this_visitor_string = str(visitor) + "({}) ".format(visitors[str(visitor)])
|
|
if len(visitor_line + this_visitor_string) > self.maxx - 3:
|
|
visitor_block += '\n'
|
|
visitor_line = ""
|
|
visitor_block += this_visitor_string
|
|
visitor_line += this_visitor_string
|
|
return visitor_block
|
|
|
|
def build_latest_visitor_output(self, visitors):
|
|
visitor_line = ""
|
|
for visitor in visitors:
|
|
if len(visitor_line + visitor) > self.maxx - 10:
|
|
visitor_line += "and more"
|
|
break
|
|
visitor_line += visitor + ' '
|
|
return [visitor_line]
|
|
|
|
def get_user_string(self, xpos=3, ypos=15, filterfunc=str.isalnum, completer=None):
|
|
# filter allowed characters using filterfunc, alphanumeric by default
|
|
user_string = ""
|
|
user_input = 0
|
|
if completer:
|
|
completer = completer(self)
|
|
while user_input != 10:
|
|
user_input = self.screen.getch()
|
|
if user_input == -1: # Input comes from pipe/file and is closed
|
|
raise IOError
|
|
self.screen_lock.acquire()
|
|
# osx and unix backspace chars...
|
|
if user_input == 127 or user_input == 263:
|
|
if len(user_string) > 0:
|
|
user_string = user_string[:-1]
|
|
if completer:
|
|
completer.update_input(user_string)
|
|
self.screen.addstr(ypos, xpos, " " * (self.maxx - xpos - 1))
|
|
elif user_input in [ord('\t'), curses.KEY_BTAB] and completer:
|
|
direction = 1 if user_input == ord('\t') else -1
|
|
user_string = completer.complete(direction)
|
|
self.screen.addstr(ypos, xpos, " " * (self.maxx - xpos - 1))
|
|
elif user_input < 256 and user_input != 10:
|
|
if filterfunc(chr(user_input)) or chr(user_input) == '_':
|
|
user_string += chr(user_input)
|
|
if completer:
|
|
completer.update_input(user_string)
|
|
self.screen.addstr(ypos, xpos, str(user_string))
|
|
self.screen.refresh()
|
|
self.screen_lock.release()
|
|
return user_string
|
|
|
|
def visit_handler(self):
|
|
self.clear_info_pane()
|
|
self.draw_info_text("whose plant would you like to visit?")
|
|
self.screen.addstr(15, 2, '~')
|
|
if self.plant.visitors:
|
|
latest_visitor_string = self.build_latest_visitor_output(self.plant.visitors)
|
|
self.draw_info_text("since last time, you were visited by: ", 3)
|
|
self.draw_info_text(latest_visitor_string, 4)
|
|
self.plant.visitors = []
|
|
weekly_visitor_text = self.user_data.get_weekly_visitors(self.plant, self.maxx)
|
|
self.draw_info_text("this week you've been visited by: ", 6)
|
|
self.draw_info_text(weekly_visitor_text, 7)
|
|
guest_garden = self.get_user_string(completer=completer.LoginCompleter)
|
|
if not guest_garden:
|
|
self.clear_info_pane()
|
|
return None
|
|
if guest_garden.lower() == getpass.getuser().lower():
|
|
self.screen.addstr(16, 2, "you're already here!")
|
|
self.screen.getch()
|
|
self.clear_info_pane()
|
|
return None
|
|
home_folder = os.path.dirname(os.path.expanduser("~"))
|
|
guest_json = home_folder + f"/{guest_garden}/.botany/{guest_garden}_plant_data.json"
|
|
guest_plant_description = ""
|
|
if os.path.isfile(guest_json):
|
|
with open(guest_json) as f:
|
|
visitor_data = json.load(f)
|
|
guest_plant_description = visitor_data['description']
|
|
self.visited_plant = self.get_visited_plant(visitor_data)
|
|
guest_visitor_file = home_folder + f"/{guest_garden}/.botany/visitors.json"
|
|
if os.path.isfile(guest_visitor_file):
|
|
water_success = self.water_on_visit(guest_visitor_file)
|
|
if water_success:
|
|
self.screen.addstr(16, 2,
|
|
f"...you watered ~{str(guest_garden)}'s {guest_plant_description}...")
|
|
if self.visited_plant:
|
|
self.draw_plant_ascii(self.visited_plant)
|
|
else:
|
|
self.screen.addstr(16, 2, f"{guest_garden}'s garden is locked, but you can see in...")
|
|
else:
|
|
self.screen.addstr(16, 2, f"i can't seem to find directions to {guest_garden}...")
|
|
try:
|
|
self.screen.getch()
|
|
self.clear_info_pane()
|
|
self.draw_plant_ascii(self.plant)
|
|
finally:
|
|
self.visited_plant = None
|
|
|
|
@staticmethod
|
|
def water_on_visit(guest_visitor_file):
|
|
# using -1 here so that old running instances can be watered
|
|
guest_data = {'user': getpass.getuser(), 'timestamp': int(time.time()) - 1}
|
|
if os.path.isfile(guest_visitor_file):
|
|
if not os.access(guest_visitor_file, os.W_OK):
|
|
return False
|
|
with open(guest_visitor_file) as f:
|
|
visitor_data = json.load(f)
|
|
visitor_data.append(guest_data)
|
|
with open(guest_visitor_file, 'w') as f:
|
|
f.write(json.dumps(visitor_data, indent=2))
|
|
return True
|
|
|
|
def get_visited_plant(self, visitor_data):
|
|
""" Returns a drawable pseudo plant object from json data """
|
|
|
|
class VisitedPlant:
|
|
pass
|
|
|
|
plant = VisitedPlant()
|
|
plant.stage = 0
|
|
plant.species = 0
|
|
|
|
if "is_dead" not in visitor_data:
|
|
return None
|
|
plant.dead = visitor_data["is_dead"]
|
|
if plant.dead:
|
|
return plant
|
|
|
|
if "stage" in visitor_data:
|
|
stage = visitor_data["stage"]
|
|
if stage in self.plant.stage_list:
|
|
plant.stage = self.plant.stage_list.index(stage)
|
|
|
|
if "species" in visitor_data:
|
|
species = visitor_data["species"]
|
|
if species in self.plant.species_list:
|
|
plant.species = self.plant.species_list.index(species)
|
|
else:
|
|
return None
|
|
elif plant.stage > 1:
|
|
return None
|
|
return plant
|
|
|
|
def handle_request(self, request):
|
|
# Menu options call functions here
|
|
if request is None:
|
|
return
|
|
if request == "harvest":
|
|
self.harvest_confirmation()
|
|
if request == "water":
|
|
self.plant.water()
|
|
self.user_data.save_plant(self.plant)
|
|
self.user_data.update_garden_db(self.plant)
|
|
if request == "look":
|
|
try:
|
|
self.draw_plant_description(self.plant)
|
|
except Exception:
|
|
self.screen.refresh()
|
|
# traceback.print_exc()
|
|
if request == "instructions":
|
|
try:
|
|
self.draw_instructions()
|
|
except Exception:
|
|
self.screen.refresh()
|
|
# traceback.print_exc()
|
|
if request == "visit":
|
|
try:
|
|
self.visit_handler()
|
|
except Exception:
|
|
self.screen.refresh()
|
|
# traceback.print_exc()
|
|
if request == "garden":
|
|
try:
|
|
self.draw_garden()
|
|
except Exception:
|
|
self.screen.refresh()
|
|
# traceback.print_exc()
|
|
|
|
def __exit__(self):
|
|
self.exit = True
|
|
cleanup()
|