2017-03-13 05:21:17 +00:00
|
|
|
import curses, os, traceback, threading, time, datetime, pickle
|
2017-03-08 02:35:04 +00:00
|
|
|
|
|
|
|
class CursedMenu(object):
|
2017-03-08 08:18:45 +00:00
|
|
|
#TODO: create a side panel with log of events..?
|
2017-03-08 02:35:04 +00:00
|
|
|
'''A class which abstracts the horrors of building a curses-based menu system'''
|
2017-03-13 05:21:17 +00:00
|
|
|
def __init__(self, this_plant, this_garden_file_path):
|
2017-03-08 02:35:04 +00:00
|
|
|
'''Initialization'''
|
2017-03-08 21:30:28 +00:00
|
|
|
self.initialized = False
|
2017-03-08 02:35:04 +00:00
|
|
|
self.screen = curses.initscr()
|
|
|
|
curses.noecho()
|
|
|
|
curses.cbreak()
|
|
|
|
curses.start_color()
|
|
|
|
curses.curs_set(0)
|
|
|
|
self.screen.keypad(1)
|
|
|
|
self.plant = this_plant
|
2017-03-13 05:21:17 +00:00
|
|
|
self.garden_file_path = this_garden_file_path
|
2017-03-08 21:22:50 +00:00
|
|
|
self.plant_string = self.plant.parse_plant()
|
|
|
|
self.plant_ticks = str(self.plant.ticks)
|
2017-03-08 02:35:04 +00:00
|
|
|
self.exit = False
|
2017-03-08 21:22:50 +00:00
|
|
|
self.instructiontoggle = False
|
2017-03-13 05:21:17 +00:00
|
|
|
#TODO: debugging
|
|
|
|
# self.gardenmenutoggle = True
|
|
|
|
self.gardenmenutoggle = False
|
2017-03-08 21:22:50 +00:00
|
|
|
self.maxy, self.maxx = self.screen.getmaxyx()
|
2017-03-08 02:35:04 +00:00
|
|
|
# Highlighted and Normal line definitions
|
|
|
|
curses.init_pair(1, curses.COLOR_BLACK, curses.COLOR_WHITE)
|
|
|
|
self.highlighted = curses.color_pair(1)
|
|
|
|
self.normal = curses.A_NORMAL
|
2017-03-08 08:18:45 +00:00
|
|
|
screen_thread = threading.Thread(target=self.update_plant_live, args=())
|
|
|
|
screen_thread.daemon = True
|
|
|
|
screen_thread.start()
|
|
|
|
# TODO: tweaking this to try to get rid of garble bug
|
|
|
|
self.screen.clear()
|
2017-03-08 02:35:04 +00:00
|
|
|
|
2017-03-13 05:21:17 +00:00
|
|
|
def show(self, options, title, subtitle):
|
|
|
|
# Draws a menu with parameters
|
2017-03-08 02:35:04 +00:00
|
|
|
self.set_options(options)
|
2017-03-09 19:32:40 +00:00
|
|
|
self.update_options()
|
2017-03-08 02:35:04 +00:00
|
|
|
self.title = title
|
|
|
|
self.subtitle = subtitle
|
|
|
|
self.selected = 0
|
2017-03-08 21:30:28 +00:00
|
|
|
self.initialized = True
|
2017-03-08 02:35:04 +00:00
|
|
|
self.draw_menu()
|
|
|
|
|
2017-03-09 19:32:40 +00:00
|
|
|
def update_options(self):
|
2017-03-13 05:21:17 +00:00
|
|
|
# Makes sure you can get a new plant if it dies
|
2017-03-09 19:32:40 +00:00
|
|
|
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:
|
2017-03-13 05:21:17 +00:00
|
|
|
# TODO: remove after debug or bury in settings
|
2017-03-09 19:32:40 +00:00
|
|
|
if "new" in self.options:
|
|
|
|
self.options.remove("new")
|
|
|
|
if "kill" not in self.options:
|
|
|
|
self.options.insert(-1,"kill")
|
|
|
|
|
2017-03-08 02:35:04 +00:00
|
|
|
def set_options(self, options):
|
2017-03-13 05:21:17 +00:00
|
|
|
# Validates that the last option is "exit"
|
2017-03-09 19:32:40 +00:00
|
|
|
if options[-1] is not 'exit':
|
|
|
|
options.append('exit')
|
2017-03-08 02:35:04 +00:00
|
|
|
self.options = options
|
|
|
|
|
|
|
|
def draw_menu(self):
|
2017-03-13 05:21:17 +00:00
|
|
|
# Actually draws the menu and handles branching
|
2017-03-08 02:35:04 +00:00
|
|
|
request = ""
|
|
|
|
try:
|
2017-03-09 19:32:40 +00:00
|
|
|
while request is not "exit":
|
2017-03-08 02:35:04 +00:00
|
|
|
self.draw()
|
|
|
|
request = self.get_user_input()
|
|
|
|
self.handle_request(request)
|
|
|
|
self.__exit__()
|
|
|
|
|
|
|
|
# Also calls __exit__, but adds traceback after
|
|
|
|
except Exception as exception:
|
|
|
|
self.__exit__()
|
|
|
|
traceback.print_exc()
|
|
|
|
|
2017-03-13 05:21:17 +00:00
|
|
|
def draw_default(self):
|
|
|
|
# Draws default menu
|
2017-03-10 01:02:19 +00:00
|
|
|
clear_bar = " " * (int(self.maxx*2/3))
|
2017-03-08 02:35:04 +00:00
|
|
|
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
|
|
|
|
for index in range(len(self.options)):
|
|
|
|
textstyle = self.normal
|
|
|
|
if index == self.selected:
|
|
|
|
textstyle = self.highlighted
|
2017-03-09 19:32:40 +00:00
|
|
|
self.screen.addstr(5+index,4, clear_bar, curses.A_NORMAL)
|
2017-03-08 02:35:04 +00:00
|
|
|
self.screen.addstr(5+index,4, "%d - %s" % (index+1, self.options[index]), textstyle)
|
|
|
|
|
2017-03-09 19:32:40 +00:00
|
|
|
self.screen.addstr(11,2, clear_bar, curses.A_NORMAL)
|
|
|
|
self.screen.addstr(12,2, clear_bar, curses.A_NORMAL)
|
2017-03-08 23:04:09 +00:00
|
|
|
self.screen.addstr(11,2, self.plant_string, curses.A_NORMAL)
|
|
|
|
self.screen.addstr(12,2, self.plant_ticks, curses.A_NORMAL)
|
2017-03-08 21:22:50 +00:00
|
|
|
|
2017-03-08 23:04:09 +00:00
|
|
|
if not self.plant.dead:
|
|
|
|
if int(time.time()) <= self.plant.watered_timestamp + 24*3600:
|
2017-03-09 19:32:40 +00:00
|
|
|
self.screen.addstr(5,13, clear_bar, curses.A_NORMAL)
|
|
|
|
self.screen.addstr(5,13, " - plant watered today :)", curses.A_NORMAL)
|
2017-03-08 23:04:09 +00:00
|
|
|
else:
|
2017-03-09 19:32:40 +00:00
|
|
|
self.screen.addstr(5,13, clear_bar, curses.A_NORMAL)
|
2017-03-08 21:22:50 +00:00
|
|
|
else:
|
2017-03-09 19:32:40 +00:00
|
|
|
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)
|
2017-03-13 05:21:17 +00:00
|
|
|
|
|
|
|
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()
|
2017-03-08 08:18:45 +00:00
|
|
|
try:
|
|
|
|
self.screen.refresh()
|
|
|
|
except Exception as exception:
|
|
|
|
# Makes sure data is saved in event of a crash due to window resizing
|
|
|
|
self.__exit__()
|
|
|
|
traceback.print_exc()
|
2017-03-08 02:35:04 +00:00
|
|
|
|
|
|
|
def update_plant_live(self):
|
2017-03-08 08:18:45 +00:00
|
|
|
# Updates plant data on menu screen, live!
|
|
|
|
# Will eventually use this to display ascii art...
|
2017-03-09 19:32:40 +00:00
|
|
|
# self.set_options(self.options)
|
2017-03-08 23:04:09 +00:00
|
|
|
while not self.exit:
|
|
|
|
self.plant_string = self.plant.parse_plant()
|
|
|
|
self.plant_ticks = str(self.plant.ticks)
|
|
|
|
if self.initialized:
|
2017-03-09 19:32:40 +00:00
|
|
|
self.update_options()
|
2017-03-08 21:30:28 +00:00
|
|
|
self.draw()
|
2017-03-08 23:04:09 +00:00
|
|
|
time.sleep(1)
|
2017-03-08 02:35:04 +00:00
|
|
|
|
|
|
|
def get_user_input(self):
|
2017-03-13 05:21:17 +00:00
|
|
|
# Gets the user's input and acts appropriately
|
2017-03-08 02:35:04 +00:00
|
|
|
user_in = self.screen.getch() # Gets user input
|
|
|
|
|
2017-03-13 05:21:17 +00:00
|
|
|
# Enter and exit Keys are special cases
|
2017-03-08 02:35:04 +00:00
|
|
|
if user_in == 10:
|
|
|
|
return self.options[self.selected]
|
|
|
|
if user_in == 27:
|
|
|
|
return self.options[-1]
|
|
|
|
|
|
|
|
# This is a number; check to see if we can set it
|
|
|
|
if user_in >= ord('1') and user_in <= ord(str(min(9,len(self.options)+1))):
|
|
|
|
self.selected = user_in - ord('0') - 1 # convert keypress back to a number, then subtract 1 to get index
|
|
|
|
return
|
|
|
|
|
|
|
|
# Increment or Decrement
|
|
|
|
if user_in == curses.KEY_DOWN: # down arrow
|
|
|
|
self.selected += 1
|
|
|
|
if user_in == curses.KEY_UP: # up arrow
|
|
|
|
self.selected -=1
|
|
|
|
self.selected = self.selected % len(self.options)
|
|
|
|
return
|
|
|
|
|
2017-03-13 05:21:17 +00:00
|
|
|
def draw_instructions(self):
|
|
|
|
if not self.instructiontoggle:
|
|
|
|
instructions_txt = """welcome to botany. you've been given a seed
|
2017-03-08 21:22:50 +00:00
|
|
|
that will grow into a beautiful plant. check
|
2017-03-09 01:36:41 +00:00
|
|
|
in and water your plant every 24h to keep it
|
2017-03-09 02:18:01 +00:00
|
|
|
growing. 5 days without water = death. your
|
|
|
|
plant depends on you to live! more info is
|
2017-03-09 01:36:41 +00:00
|
|
|
available in the readme :)
|
2017-03-13 05:21:17 +00:00
|
|
|
cheers,
|
|
|
|
curio"""
|
|
|
|
self.instructiontoggle = not self.instructiontoggle
|
|
|
|
else:
|
|
|
|
instructions_txt = """
|
2017-03-08 21:22:50 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2017-03-09 01:36:41 +00:00
|
|
|
|
2017-03-13 05:21:17 +00:00
|
|
|
"""
|
|
|
|
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()
|
2017-03-08 02:35:04 +00:00
|
|
|
def __exit__(self):
|
|
|
|
self.exit = True
|
|
|
|
curses.curs_set(2)
|
|
|
|
curses.endwin()
|
|
|
|
os.system('clear')
|
|
|
|
|